diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 2cfc2f6..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: CI - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -jobs: - linting: - runs-on: ubuntu-latest - steps: - - uses: shivammathur/setup-php@15c43e89cdef867065b0213be354c2841860869e - with: - php-version: "8.3" - - uses: actions/checkout@v3 - - name: Install Dependencies - run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - - name: Execute PHP Linting via Pint - run: ./vendor/bin/pint --bail - tests: - runs-on: ubuntu-latest - steps: - - uses: shivammathur/setup-php@15c43e89cdef867065b0213be354c2841860869e - with: - php-version: "8.3" - - uses: actions/checkout@v3 - - name: Install Dependencies - run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - - name: Execute tests (Unit and Feature tests) via Pest - run: ./vendor/bin/pest diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b2944ab --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,71 @@ +name: Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + linting: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.3" + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: none + + - name: Install dependencies + run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + + - name: Execute PHP Linting via Pint + run: ./vendor/bin/pint --bail + + test: + needs: [ linting ] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: [ 8.2, 8.3, 8.4 ] + laravel: [ 11.*, 12.* ] + statamic: [ 5.* ] + stability: [ prefer-stable ] + exclude: + - php: 8.2 + laravel: 11.* + - php: 8.2 + laravel: 12.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - S${{ matrix.statamic }} - ${{ matrix.stability }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: pcov + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "statamic/cms:${{ matrix.statamic }}" "orchestra/testbench:${{ matrix.laravel == '11.*' && '^9.0' || '^10.0' }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction -W + + - name: Execute tests + run: vendor/bin/pest --coverage-clover coverage.xml + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false diff --git a/.junie/guidelines.md b/.junie/guidelines.md new file mode 100644 index 0000000..c07efb9 --- /dev/null +++ b/.junie/guidelines.md @@ -0,0 +1,109 @@ +### Project Guidelines + +#### Purpose +The Statamic Builder speeds up building Statamic sites by providing a fluent, PHP-based API to define sites, blueprints, fieldsets, collections, navigations, and taxonomies. This approach enhances code readability and maintainability by replacing traditional YAML configuration with PHP classes. + +#### Build and Configuration + +This project is a Statamic addon that integrates deeply with Statamic's core systems and provides a fluent API for +building blueprints and fieldsets. + +- **Requirements**: PHP 8.2+, Statamic 5.4+, Laravel 10/11/12. +- **Installation**: + ```bash + composer install + ``` +- **Service Provider**: `Tdwesten\StatamicBuilder\ServiceProvider` is automatically registered via Laravel's package + discovery. It handles component discovery, repository binding, and command registration. +- **Configuration**: + Publish the configuration file to customize discovery paths and register components manually: + ```bash + php artisan vendor:publish --tag=statamic + ``` +- **Exporting to YAML**: + If you need to generate standard Statamic YAML files from your PHP definitions: + ```bash + php artisan statamic-builder:export + ``` + +#### Testing +The project uses [Pest](https://pestphp.com/) for testing, along with `orchestra/testbench` for Laravel/Statamic integration. + +- **Configuring Tests**: + - Tests extend `Tests\TestCase`, which boots the Statamic environment. + - No additional database configuration is typically required as it uses in-memory storage for testing. +- **Running Tests**: + Execute the following command to run the full test suite: + ```bash + ./vendor/bin/pest + ``` + To run tests and static analysis (Rector): + ```bash + composer test + ``` +- **Adding Tests**: + - Place new tests in `tests/Unit` or `tests/Feature`. + - For new field types, use the generator to create a starting test: `composer generate-field MyField`. + +**Verified Example Test Case:** +```php +displayName('Title'), + ]), + ]), + ]; + } + }; + + $array = $blueprint->toArray(); + + expect($blueprint->getHandle())->toBe('test_handle'); + expect($array['tabs']['main']['sections'][0]['display'])->toBe('General'); +}); +``` + +#### Development Information + +- **Mandatory Tasks**: Always perform the following tasks when completing a task or before submitting a pull request: + - **Update README**: Ensure `README.md` reflects any new features or changes. + - **Format Code**: Run `vendor/bin/pint` to maintain consistent code style. + - **Run Tests**: Ensure all tests pass by running `./vendor/bin/pest`. +- **Code Style**: Follow PSR-12 and Laravel coding standards. `laravel/pint` is included for formatting. +- **Static Analysis/Refactoring**: Rector is used for automated refactoring and code quality. Run it via: + ```bash + ./vendor/bin/rector + ``` +- **Fluent API Design**: Always use the `make()` static method for instantiating fields and chainable setters (e.g., + `->displayName()`, `->instructions()`, `->required()`) for configuration. +- **Custom Field Types**: New field types should extend `Tdwesten\StatamicBuilder\FieldTypes\Field`. +- **Field Generator**: + ```bash + composer generate-field MyFieldName + ``` + This command populates `src/FieldTypes/` and `tests/Unit/` using templates in the `field-generator/` directory. + +#### Auto Registration & Discovery + +The addon supports auto-discovery to avoid manual registration in `config/statamic/builder.php`. + +- **Enable**: Set `'auto_registration' => true` in the config. +- **Requirements**: + - **Blueprints**: Must implement `public static function handle(): string` and + `public static function blueprintNamespace(): string`. + - **Collections, Taxonomies, Globals, Navigations**: Must implement `public static function handle(): string`. + - **Sites**: Must implement `public function handle(): string`. +- **Default Paths**: Components are discovered in `app/Blueprints`, `app/Collections`, etc. These can be customized in + the `auto_discovery` configuration. diff --git a/.output.txt b/.output.txt new file mode 100644 index 0000000..ab16e47 --- /dev/null +++ b/.output.txt @@ -0,0 +1,443 @@ + + PASS Tests\Feature\AssetContainerRegistrationTest + ✓ registered asset containers are found by the repository 0.24s + ✓ all includes registered asset containers 0.01s + PASS Tests\Feature\AutoDiscoveryTest + ✓ it can auto discover asset containers 0.03s + PASS Tests\Feature\CollectionRegistrationTest + ✓ registered collections can be found by handle 0.02s + ✓ register method properly configures collection with all settings 0.02s + PASS Tests\Feature\GlobalSaveTest + ✓ it can save global variables 0.03s + ✓ it can save global variables for blueprint-based globals 0.02s + PASS Tests\Feature\Http\Controllers\AssetContainerControllerTest + ✓ it shows not editable view for builder defined asset container 0.33s + ✓ it shows not editable view for builder defined asset container blue… 0.24s + PASS Tests\Feature\Http\Controllers\NavigationControllerTest + ✓ it shows the not editable view when editing a builder defined navig… 0.24s + ✓ it shows the not editable view when editing a builder defined navig… 0.53s + ✓ it shows the not editable view when editing a builder defined navig… 0.26s + PASS Tests\Unit\AssetContainerRepositoryTest + ✓ ::all includes builder-registered asset containers 0.02s + ✓ can find by handle 0.01s + PASS Tests\Unit\AssetContainerTest + ✓ Has a title 0.01s + ✓ Has a handle 0.01s + ✓ Has a disk 0.01s + ✓ Can be registered 0.01s + PASS Tests\Unit\BlueprintTest + ✓ Has a title 0.01s + ✓ it can be set to hidden 0.01s + ✓ Tabs are renderd 0.01s + ✓ it throws an exception when adding a field to a tab 0.01s + ✓ you can set a title 0.01s + ✓ you can get the handle 0.01s + ✓ base blueprint static handle returns empty string by default 0.01s + ✓ static blueprintNamespace returns empty string by default 0.01s + ✓ blueprint with empty tabs returns empty array 0.01s + ✓ blueprint throws exception when non-tab field is in registerTabs 0.01s + PASS Tests\Unit\CollectionRepositoryTest + ✓ ::find does not throw when no result is found 0.02s + ✓ ::find returns null for nonexistent handles 0.01s + ✓ ::findByHandle finds builder-registered collection 0.01s + ✓ ::all includes builder-registered collections 0.01s + ✓ getCollectionByHandle returns null for non-existent collection 0.01s + PASS Tests\Unit\CollectionTest + ✓ Has a title 0.01s + ✓ Has a handle 0.01s + ✓ Has a route 0.01s + ✓ Has multiple route for multisites 0.01s + ✓ Can have multiple sites 0.01s + ✓ Has slugs 0.01s + ✓ Has default values for optional methods 0.01s + PASS Tests\Unit\FieldParserTest + ✓ it preserves field order when flattening mixed fields 0.02s + PASS Tests\Unit\FieldTest + ✓ it can render to a array 0.01s + ✓ Can set a handle prefix 0.01s + ✓ Can set a display name 0.01s + ✓ Can set instructions 0.01s + ✓ Can set visibility 0.01s + ✓ Can set required 0.01s + ✓ Can set instructions position 0.01s + ✓ Can set listable 0.01s + ✓ Can set replicator preview 0.01s + ✓ Can set width 0.01s + ✓ Can set antlers 0.01s + ✓ Can set duplicate 0.01s + ✓ Can set hide display 0.01s + ✓ Can set localizable 0.01s + ✓ Can set validation to sometimes 0.02s + ✓ can set multiple validation rules 0.01s + ✓ Can set a custom icon 0.01s + ✓ Can create a thirth-party field 0.01s + PASS Tests\Unit\Fields\ArrTest + ✓ it can render to a array 0.02s + ✓ it can render to a array with mode 0.01s + ✓ it can render to a array with keys 0.01s + PASS Tests\Unit\Fields\AssetsTest + ✓ it can render to a array 0.02s + ✓ it can set max files 0.01s + ✓ it can set min files 0.01s + ✓ it can set mode 0.01s + ✓ it can set container 0.01s + ✓ it can set folder 0.01s + ✓ it can restrict 0.01s + ✓ it can allow uploads 0.01s + ✓ it can show filename 0.01s + ✓ it can show set alt 0.01s + ✓ it can set query_scopes 0.01s + ✓ it can set dynamic folder 0.02s + PASS Tests\Unit\Fields\BardTest + ✓ it can render to a array 0.02s + ✓ you can add multiple buttons 0.01s + ✓ you can add custom buttons as a string 0.01s + ✓ you can add a single button 0.01s + ✓ you can set the inline option 0.01s + ✓ you can set inline to false 0.01s + ✓ you can set inline to break (accordion) 0.01s + PASS Tests\Unit\Fields\ButtonGroupTest + ✓ it can render to a array 0.02s + ✓ it can render to a array with options 0.01s + ✓ it can render to a array with default value 0.01s + ✓ it can set default value using defaultValue method 0.01s + PASS Tests\Unit\Fields\CheckboxesTest + ✓ it can render to a array 0.02s + ✓ it can render to a array with options 0.01s + ✓ it can render to a array with default value 0.01s + ✓ it can render to a array with inline 0.01s + PASS Tests\Unit\Fields\CodeTest + ✓ it can render to a array 0.02s + ✓ you can set the mode 0.01s + ✓ you can set the mode selectable 0.01s + ✓ you can set the indent type 0.01s + ✓ you can set the indent size 0.01s + ✓ you can set the key map 0.01s + ✓ you can set the line numbers 0.01s + ✓ you can set the line wrapping 0.01s + ✓ you can set the rulers 0.01s + PASS Tests\Unit\Fields\CollectionsTest + ✓ it can render to a array 0.02s + ✓ it can have max items 0.01s + ✓ it can have a mode 0.01s + PASS Tests\Unit\Fields\ColorTest + ✓ it can render to a array 0.02s + ✓ you can set the allow any 0.01s + ✓ you can set the swatches 0.01s + ✓ you can set the default value 0.01s + PASS Tests\Unit\Fields\ConditionalLogicTest + ✓ Can set conditional logic if 0.01s + ✓ Can set conditional logic if with multiple conditions 0.01s + ✓ Can set conditional logic if any 0.01s + ✓ Can set conditional logic with custom if 0.01s + ✓ Can set conditional logic with custom if_any 0.01s + ✓ Can set conditional logic unless 0.01s + ✓ Can set conditional logic unless with multiple conditions 0.01s + ✓ Can set conditional logic with custom unless 0.01s + PASS Tests\Unit\Fields\DateTest + ✓ it can render to a array 0.02s + ✓ it can render to a array with mode 0.01s + ✓ it can render to a array with inline 0.01s + ✓ it can render to a array with full width 0.01s + ✓ it can render to a array with columns 0.01s + ✓ it can render to a array with rows 0.01s + ✓ it can render to a array with time enabled 0.01s + ✓ it can render to a array with time seconds enabled 0.01s + ✓ it can render to a array with earliest date 0.01s + ✓ it can render to a array with latest date 0.01s + ✓ it can render to a array with format 0.01s + PASS Tests\Unit\Fields\DictionaryTest + ✓ it can render to a array 0.02s + ✓ it type can be set 0.01s + ✓ it can set additional dictionary options 0.01s + ✓ it can set placeholder 0.01s + ✓ it can set max items 0.01s + PASS Tests\Unit\Fields\EntriesTest + ✓ it can render to a array 0.02s + ✓ it can render to a array with max items 0.01s + ✓ it can render to a array with mode 0.01s + ✓ it can render to a array with collections 0.01s + ✓ it can render to a array with search index 0.01s + ✓ it can render the queryScopes to the array 0.01s + PASS Tests\Unit\Fields\FieldsetTest + ✓ it can be instantiated 0.02s + ✓ it can be registered 0.01s + ✓ getPrefix returns null when no prefix is set 0.01s + ✓ getPrefix returns prefix when set in constructor 0.01s + ✓ prefix method sets prefix and returns instance for chaining 0.01s + ✓ getFields returns collection of fields with prefix applied 0.01s + ✓ it can be converted to an array 0.01s + ✓ A fieldset can be used in a blueprint 0.01s + ✓ A fieldset can be used in group 0.01s + ✓ A fieldset can be used in a fieldset 0.01s + PASS Tests\Unit\Fields\FloatValTest + ✓ it can render to a array 0.02s + PASS Tests\Unit\Fields\ForeignFieldTest + ✓ Foreign field can be rendered 0.01s + ✓ Foreign field can be rendered with config 0.01s + PASS Tests\Unit\Fields\ForeignFieldsetTest + ✓ Foreign fieldset can be rendered 0.02s + ✓ Foreign fieldset can be rendered with prefix 0.01s + PASS Tests\Unit\Fields\FormTest + ✓ it can render to a array 0.01s + PASS Tests\Unit\Fields\GridTest + ✓ it can render to a array 0.01s + ✓ it can render to a array with reorderable 0.01s + ✓ it can render to a array with add row 0.01s + ✓ it can render to a array with max rows 0.01s + ✓ it can render to a array with min rows 0.01s + ✓ it can render to a array with mode 0.01s + ✓ it can render to a array with fields 0.01s + PASS Tests\Unit\Fields\GroupTest + ✓ Renders a group to array 0.01s + ✓ A group can have a group 0.01s + ✓ Can set to fullscreen 0.01s + PASS Tests\Unit\Fields\HiddenTest + ✓ it can render to a array 0.01s + PASS Tests\Unit\Fields\HtmlTest + ✓ it can render to a array 0.01s + PASS Tests\Unit\Fields\IconTest + ✓ it can render to a array 0.02s + ✓ it can render to a array with directory 0.01s + ✓ it can render to a array with folder 0.01s + ✓ it can render to a array with default 0.01s + PASS Tests\Unit\Fields\IntegerTest + ✓ it can render to a array 0.01s + ✓ it can have a prepend 0.01s + ✓ it can have a append 0.01s + PASS Tests\Unit\Fields\LinkTest + ✓ it can render to a array 0.01s + ✓ it can render to a array with collections 0.01s + ✓ it can render to a array with container 0.01s + PASS Tests\Unit\Fields\ListsTest + ✓ it can render to a array 0.01s + ✓ it can render to a array with options 0.01s + PASS Tests\Unit\Fields\MarkdownTest + ✓ it can render to a array 0.02s + ✓ it can render to a array with buttons 0.01s + ✓ it can render a array with buttons as string 0.01s + ✓ Can set a asset container 0.01s + ✓ Can set a asset folder 0.01s + ✓ Can resetrict to a asset folder 0.01s + ✓ can set automatic line breaks 0.01s + ✓ can set automatic links 0.01s + ✓ can set escape markup 0.01s + ✓ can set heading anchors 0.01s + ✓ Can set smartypants 0.01s + ✓ Can set default value 0.01s + ✓ Can enable table of contents 0.01s + ✓ Can define parser 0.01s + PASS Tests\Unit\Fields\MoneyTest + ✓ it can render to a array 0.02s + PASS Tests\Unit\Fields\NavsTest + ✓ it can render to a array 0.01s + ✓ Can set max items 0.01s + ✓ Can set mode 0.01s + PASS Tests\Unit\Fields\NumberTest + ✓ it can render to a array 0.01s + PASS Tests\Unit\Fields\PasswordTest + ✓ it can render to a array 0.02s + PASS Tests\Unit\Fields\RadioTest + ✓ it can render to a array 0.01s + ✓ it can have options 0.01s + ✓ it can be inline 0.01s + ✓ it can cast booleans 0.01s + PASS Tests\Unit\Fields\RangeTest + ✓ it can render to a array 0.01s + ✓ can have a min 0.01s + ✓ can have a max 0.02s + ✓ can have a step 0.02s + ✓ can have a prepend 0.02s + ✓ can have a append 0.02s + PASS Tests\Unit\Fields\RatingTest + ✓ it can render to a array 0.02s + PASS Tests\Unit\Fields\RelationshipTest + ✓ it can render to a array 0.01s + PASS Tests\Unit\Fields\ReplicatorTest + ✓ it can render to a array 0.01s + ✓ it can have sets 0.01s + ✓ it can render the same output 0.01s + ✓ can set collapse to false 0.01s + ✓ can set collapse to true 0.01s + ✓ can set collapse to accordion 0.01s + PASS Tests\Unit\Fields\RevealerTest + ✓ it can render to a array 0.02s + ✓ it can have a ui mode 0.01s + ✓ it can have a input_label 0.01s + PASS Tests\Unit\Fields\SectionFieldTest + ✓ it can render to a array 0.01s + PASS Tests\Unit\Fields\SectionTest + ✓ Section can be rendered 0.02s + PASS Tests\Unit\Fields\SelectTest + ✓ it can render to a array 0.01s + ✓ it can set taggable 0.01s + ✓ it can set push tags 0.01s + ✓ it can set placeholder 0.01s + ✓ it can set multiple 0.01s + ✓ it can set max items 0.01s + ✓ it can set clearable 0.01s + ✓ it can set searchable 0.01s + ✓ it can set cast booleans 0.01s + PASS Tests\Unit\Fields\SetGroupTest + ✓ it can render to a array 0.02s + PASS Tests\Unit\Fields\SetTest + ✓ it can render to a array 0.02s + PASS Tests\Unit\Fields\SitesTest + ✓ it can render to a array 0.02s + ✓ Can set max items 0.01s + ✓ Can set mode 0.02s + PASS Tests\Unit\Fields\SlugTest + ✓ it can render to a array 0.02s + ✓ it can set from 0.01s + ✓ it can set generate 0.01s + ✓ it can show regenerate 0.02s + PASS Tests\Unit\Fields\SpacerTest + ✓ it can render to a array 0.02s + PASS Tests\Unit\Fields\StructuresTest + ✓ it can render to a array 0.02s + ✓ it can set max_items 0.01s + ✓ it can set mode 0.01s + PASS Tests\Unit\Fields\TabTest + ✓ Tab can be rendered 0.02s + PASS Tests\Unit\Fields\TableTest + ✓ it can render to a array 0.02s + PASS Tests\Unit\Fields\TaggableTest + ✓ it can render to a array 0.01s + PASS Tests\Unit\Fields\TaxonomiesTest + ✓ it can render to a array 0.02s + ✓ Can set max items 0.02s + ✓ Can set mode 0.02s + PASS Tests\Unit\Fields\TemplateTest + ✓ it can render to a array 0.02s + ✓ it can hide partials 0.02s + ✓ it can set blueprint 0.01s + ✓ it can set folder 0.02s + PASS Tests\Unit\Fields\TermsTest + ✓ Terms field renders 0.02s + ✓ You can set max items 0.01s + ✓ Terms field renders with multiple taxonomies 0.01s + ✓ Terms field renders with multiple taxonomies and mode 0.01s + ✓ Terms field renders with create option 0.01s + ✓ can have query scopes 0.01s + PASS Tests\Unit\Fields\TextTest + ✓ it can render to a array 0.02s + ✓ Renders expected array data 0.01s + ✓ Can set input type to email 0.01s + ✓ Can set input type to number 0.01s + ✓ Can add a placeholder 0.01s + ✓ Can add a default value 0.01s + ✓ Can add a character limit 0.01s + ✓ Can add autocomplete options 0.01s + ✓ Can add prepend text 0.01s + ✓ Can add append text 0.01s + PASS Tests\Unit\Fields\TextareaTest + ✓ it can render to a array 0.02s + ✓ it can have a character limit 0.01s + ✓ it can have a placeholder 0.01s + ✓ it can have a default value 0.01s + PASS Tests\Unit\Fields\TimeTest + ✓ it can render to a array 0.02s + ✓ it can have seconds enabled 0.01s + PASS Tests\Unit\Fields\ToggleTest + ✓ it can render to a array 0.01s + ✓ it can have inline label 0.01s + ✓ it can have inline label when true 0.01s + PASS Tests\Unit\Fields\UserGroupsTest + ✓ it can render to a array 0.01s + ✓ Can set max items 0.01s + ✓ Can set mode 0.01s + PASS Tests\Unit\Fields\UserRolesTest + ✓ it can render to a array 0.02s + ✓ it can have max items 0.01s + ✓ it can have a mode 0.01s + PASS Tests\Unit\Fields\UsersTest + ✓ it can render to a array 0.01s + ✓ can have max items 0.01s + ✓ can have query scopes 0.01s + ✓ can have mode 0.01s + PASS Tests\Unit\Fields\VideoTest + ✓ it can render to a array 0.02s + ✓ it can have a default 0.01s + ✓ it can have a placeholder 0.01s + PASS Tests\Unit\Fields\WidthTest + ✓ it can render to a array 0.01s + ✓ it can have options 0.01s + ✓ it can have a default 0.01s + PASS Tests\Unit\Fields\YamlTest + ✓ it can render to a array 0.01s + ✓ it can have a default 0.01s + PASS Tests\Unit\FieldsetRepositoryTest + ✓ ::all includes builder-registered fieldsets 0.02s + ✓ ::find finds builder-registered fieldset 0.01s + ✓ ::save does not save builder-registered fieldset 0.01s + PASS Tests\Unit\GlobalRepositoryTest + ✓ ::find does not throw when no result is found 0.01s + ✓ ::find returns null for nonexistent handles 0.01s + ✓ ::findByHandle finds builder-registered global 0.01s + ✓ ::all includes builder-registered globals 0.01s + ✓ ::all includes globals from blueprints 0.01s + PASS Tests\Unit\GlobalSetTest + ✓ Has a handle 0.02s + ✓ Has a title 0.01s + ✓ Has sites 0.01s + ✓ Register method creates global set with proper configuration 0.01s + PASS Tests\Unit\MakeBlueprintCommandTest + ✓ it can create a blueprint 0.06s + ✓ it shows registration reminder when auto_registration is false 0.02s + PASS Tests\Unit\MakeCollectionCommandTest + ✓ it can create a collection 0.02s + ✓ it shows registration reminder when auto_registration is false 0.02s + PASS Tests\Unit\MakeFieldsetCommandTest + ✓ it can create a fieldset 0.02s + ✓ it shows registration reminder when auto_registration is false 0.01s + PASS Tests\Unit\MakeGlobalSetCommandTest + ✓ it can generate a global set class 0.02s + ✓ it shows registration reminder when auto_registration is false 0.02s + PASS Tests\Unit\MakeNavigationCommandTest + ✓ it can generate a navigation class 0.02s + ✓ it shows registration reminder when auto_registration is false 0.02s + PASS Tests\Unit\MakeSiteCommandTest + ✓ it can create a site 0.02s + ✓ it shows registration reminder when auto_registration is false 0.02s + PASS Tests\Unit\MakeTaxonomyCommandTest + ✓ it can create a taxonomy 0.02s + ✓ it shows registration reminder when auto_registration is false 0.02s + PASS Tests\Unit\NavigationRepositoryTest + ✓ ::find does not throw when no result is found 0.01s + ✓ ::find returns null for nonexistent handles 0.01s + ✓ ::findByHandle finds builder-registered navigation 0.01s + ✓ ::all includes builder-registered navigations 0.01s + PASS Tests\Unit\NavigationTest + ✓ Has a handle 0.01s + ✓ Has a title 0.01s + ✓ Has default collections 0.01s + ✓ Has default sites 0.01s + ✓ Has default expectsRoot 0.01s + ✓ Has default maxDepth 0.01s + PASS Tests\Unit\ServiceProviderTest + ✓ it binds the navigation repository contract 0.01s + ✓ it binds the global repository contract 0.01s + ✓ it binds the asset container repository contract 0.01s + ✓ it binds the eloquent navigation repository when driver is eloquent 0.01s + ✓ it binds the eloquent global repository when driver is eloquent 0.02s + PASS Tests\Unit\SiteTest + ✓ Has a name 0.02s + ✓ Has a handle 0.01s + ✓ Has a url 0.01s + ✓ Has a locale 0.01s + ✓ Has extra attributes 0.01s + ✓ Can convert to array 0.01s + PASS Tests\Unit\SitesTest + ✓ getSavedSites returns builder configured sites 0.02s + ✓ getSavedSites returns parent result when config is null 0.01s + ✓ getSavedSites caches result 0.01s + ✓ getSavedSites maps site classes to array with handle as key 0.01s + PASS Tests\Unit\TaxonomyBlueprintTest + ✓ taxonomy blueprints can be edited 0.02s + PASS Tests\Unit\TaxonomyRepositoryTest + ✓ ::all includes builder-registered taxonomies 0.01s + ✓ ::findByHandle finds builder-registered taxonomy 0.01s + ✓ getTaxonomyByHandle returns null for non-existent taxonomy 0.01s + ✓ getTaxonomyByHandle returns taxonomy instance for valid handle 0.01s + Tests: 349 passed (798 assertions) + Duration: 6.95s diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8889ebd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.2.0] - 2026-01-04 + +### Added + +- Added `make:site` command to generate Site classes. +- Added `make:asset-container` command to generate Asset Container classes. +- Added support for Asset Containers via `BaseAssetContainer`. +- Enhanced support for Global Sets, including repository integration and saving. +- Enhanced support for Navigations, including repository integration. +- Added support for Multi-site through PHP classes. +- Added new field types: `Color`, `Hidden`, `Money`, `Number`, `Password`, `Rating`, `Time`, `Video`. +- Added auto-discovery for all component types. +- Integrated Statamic Eloquent Driver support for Global Sets and Navigations. + +### Changed + +- Refactored all generator commands to use a common `GeneratorCommand` base class for consistency. +- Improved Blueprint and Fieldset repository integration to better handle PHP-defined components. +- Standardized class naming in generator commands to use `StudlyCase`. + +### Fixed + +- Improved code style and consistency across the project. +- Fixed Various minor issues with component registration. diff --git a/COMPATIBILITY_FIX.md b/COMPATIBILITY_FIX.md deleted file mode 100644 index 0f872af..0000000 --- a/COMPATIBILITY_FIX.md +++ /dev/null @@ -1,55 +0,0 @@ -# Statamic v5.67.0 Compatibility Fix - -## Summary - -This fix ensures compatibility with Statamic v5.67.0 where the `BlueprintRepository::$directory` property was changed to `BlueprintRepository::$directories`. The implementation maintains full backward compatibility with earlier versions. - -## Changes Made - -### 1. Updated `src/Repositories/BlueprintRepository.php` - -Added a new protected method `getDirectory()` that: -- Checks for the new `directory()` method introduced in v5.67.0 (first priority) -- Checks for the new `$directories` property (associative array with 'default' key) -- Falls back to the old `$directory` property for pre-v5.67.0 compatibility -- Provides a sensible default if none of the above are available - -Updated `makeBlueprintFromFile()` to use `getDirectory()` instead of directly accessing `$this->directory`. - -## Compatibility Matrix - -| Statamic Version | Property/Method | Status | -|-----------------|-----------------|--------| -| v5.0 - v5.66.x | `$directory` (string) | ✅ Supported | -| v5.67.0+ | `$directories` (array) + `directory()` method | ✅ Supported | - -## Testing - -All compatibility scenarios have been tested: - -1. ✅ v5.67.0+ with `directory()` method -2. ✅ v5.67.0+ accessing `$directories['default']` directly -3. ✅ Pre-v5.67.0 with `$directory` property -4. ✅ Simple array format (first element) -5. ✅ Fallback to default when no properties set -6. ✅ Empty directories array falls back to directory -7. ✅ Multiple directories in associative array -8. ✅ No PHP syntax errors - -Run the test suite: -```bash -php test-compatibility.php -``` - -## Security - -CodeQL analysis performed - no security vulnerabilities detected. - -## Backward Compatibility - -This fix maintains full backward compatibility. No changes are required to existing code that uses this package. - -## References - -- Statamic CMS Issue: https://github.com/statamic/cms/blob/d4f75c67534712cef8fd7e185488a27dba3170da/src/Fields/BlueprintRepository.php#L21 -- Original Issue: https://github.com/tdwesten/statamic-builder/blob/e39234b5eaec049e29085aae4187b54660477aea/src/Repositories/BlueprintRepository.php#L92 diff --git a/README.md b/README.md index 83adc44..3a82843 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,92 @@ [![Latest Version on Packagist](https://img.shields.io/packagist/v/tdwesten/statamic-builder.svg?style=flat-square)](https://packagist.org/packages/tdwesten/statamic-builder) -[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/tdwesten/statamic-builder/ci.yml?branch=main&label=tests&style=flat-square)](https://github.com/tdwesten/statamic-builder/actions/workflows/ci.yml) +[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/tdwesten/statamic-builder/tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/tdwesten/statamic-builder/actions/workflows/tests.yml) [![Total Downloads](https://img.shields.io/packagist/dt/tdwesten/statamic-builder.svg?style=flat-square)](https://packagist.org/packages/tdwesten/statamic-builder) ![Github](https://github.com/tdwesten/statamic-builder/assets/224501/fb3da5da-6035-4ca4-9d70-3509c8b626c6) # Statamic Builder -The Statamic Builder speeds up building Statamic sites. It offers a clear method to define sites, blueprints, fieldsets, collections, naviations and taxonomies using PHP classes. This approach enhances code readability and maintainability compared to writing YAML files. +The Statamic Builder speeds up building Statamic sites. It offers a clear method to define sites, blueprints, fieldsets, +collections, navigations and taxonomies using PHP classes. This approach enhances code readability and maintainability +compared to writing YAML files. -For example, you can define a collection blueprint as follows: +## Features + +- **Fluent API**: Define Statamic components using a clean, chainable PHP API. +- **Auto-discovery**: Automatically discover and register your components from the filesystem. +- **Navigation Support**: Easily define and manage Statamic Navigations in PHP. +- **Global Sets & Taxonomies**: Full support for Global Sets and Taxonomies. +- **Multi-site Support**: Define and manage multiple sites through PHP classes. +- **Artisan Commands**: Generate blueprints, fieldsets, collections, and more with dedicated commands. +- **YAML Export**: Export your PHP-defined components to standard Statamic YAML files. +- **Eloquent Support**: Full support for Statamic Eloquent Driver for Global Sets and Navigations. + +## Installation + +You can install this addon with composer: + +```bash +composer require tdwesten/statamic-builder +``` + +## Requirements + +- PHP 8.2+ +- Statamic 5.4+ +- Laravel 11 or 12 + +## Configuration + +You can publish the configuration file using: + +```bash +php artisan vendor:publish --tag=statamic +``` + +The configuration file allows you to manually register components or enable auto-discovery. + +### Options + +| Option | Description | +|---------------------|----------------------------------------------------------------| +| `blueprints` | Manual registration of blueprints, grouped by namespace. | +| `fieldsets` | Manual registration of fieldsets. | +| `collections` | Manual registration of collections. | +| `taxonomies` | Manual registration of taxonomies. | +| `globals` | Manual registration of global sets. | +| `sites` | Manual registration of sites. | +| `navigations` | Manual registration of navigations. | +| `asset_containers` | Manual registration of asset containers. | +| `auto_registration` | Enable or disable auto-discovery of components. | +| `auto_discovery` | Define custom paths for auto-discovery of each component type. | + +## Auto Registration & Discovery + +Enable `auto_registration` in `config/statamic/builder.php` to automatically find components in your `app/` directory. ```php - true, +``` + +### Discovery Requirements + +For components to be auto-discovered, they must implement certain static methods: + +- **Blueprints**: Must implement `static function handle()` and `static function blueprintNamespace()`. +- **Collections, Taxonomies, Globals, Navigations, Asset Containers**: Must implement `static function handle()`. +- **Sites**: Must implement `function handle()`. + +## Blueprints and Fieldsets + +### Creating a Blueprint + +1. Generate a blueprint: + ```bash + php artisan make:blueprint PageBlueprint + ``` +2. Define your fields in the `registerTabs` method: +```php namespace App\Blueprints; use Tdwesten\StatamicBuilder\Blueprint; @@ -22,13 +96,17 @@ use Tdwesten\StatamicBuilder\FieldTypes\Tab; class PageBlueprint extends Blueprint { - public $title = 'Page'; - - public $handle = 'page'; + public static function handle(): string + { + return 'page'; + } - public $hidden = false; + public static function blueprintNamespace(): string + { + return 'collections.pages'; + } - public function registerTabs(): Array + public function registerTabs(): array { return [ Tab::make('General', [ @@ -45,451 +123,349 @@ class PageBlueprint extends Blueprint } ``` -## Installation +### Creating a Fieldset -You can install this addon with composer. Run the following command in your terminal to install the addon. +1. Generate a fieldset: + ```bash + php artisan make:fieldset HeroFieldset + ``` +2. Define fields: -```bash -composer require tdwesten/statamic-builder -``` +```php +namespace App\Fieldsets; -## Blueprints and Fieldsets +use Tdwesten\StatamicBuilder\Fieldset; +use Tdwesten\StatamicBuilder\FieldTypes\Assets; +use Tdwesten\StatamicBuilder\FieldTypes\Text; -This addon allows you to create blueprints and fieldsets in a fluent way. This makes it easier to define and maintain your blueprints and fieldsets. +class HeroFieldset extends Fieldset +{ + public function registerFields(): array + { + return [ + Text::make('title')->displayName('Title')->required(), + Assets::make('image')->displayName('Image')->maxItems(1), + ]; + } +} +``` -### How to create a blueprint +## Collections, Taxonomies, and Globals -1. Create a new blueprint by running the following command for a page blueprint for example: +### Collections - ```bash - php artisan make:blueprint PageBlueprint - ``` +Generate a collection: -2. Define your blueprint in the generated file. For example: - - ```php - displayName('Title') - ->instructions('The title of the page') - ->required(), - Assets::make('image') - ->displayName('Image') - ->maxItems(1) - ->instructions('The image of the page') - ->required(), - ]), - ]), - ]; - } - } - ``` +```bash +php artisan make:collection Articles +``` -3. Register the blueprint in your `config/statamic/builder.php` file: - - ```php - [ - 'collections.pages' => [ - 'page' => \App\Blueprints\PageBlueprint::class, - ], - ], - ]; - ``` +```php +namespace App\Collections; -4. That's it! You can now use your blueprint in your Statamic application. +use Tdwesten\StatamicBuilder\BaseCollection; -### How to create a fieldset +class Articles extends BaseCollection +{ + public static function handle(): string + { + return 'articles'; + } -1. Create a new fieldset by running the following command for a hero fieldset for example: + public function title(): string + { + return 'Articles'; + } +} +``` - ```bash - php artisan make:fieldset HeroFieldset - ``` +Most methods in `BaseCollection` now have default implementations, so you only need to override what you want to +change (e.g., `route()`, `sites()`, `template()`). -2. Define your fieldset in the generated file. For example add a title and image field to the hero fieldset: - - ```php - displayName('Title') - ->instructions('The title of the hero') - ->required(), - Assets::make('image') - ->displayName('Image') - ->maxItems(1) - ->instructions('The image of the hero') - ->required(), - ]; - } - } - ``` +### Taxonomies -3. Register the fieldset in your `config/statamic/builder.php` file: - - ```php - [ - 'collections.pages' => [ - 'page' => \App\Blueprints\PageBlueprint::class, - ], - ], - 'fieldsets' => [ - \App\Fieldsets\HeroFieldset::class, - ], - ]; - ``` +Generate a taxonomy: -4. Now you can use your fieldset in your blueprints. For example: +```bash +php artisan make:taxonomy Categories +``` - ```php - prefix('myseo_') - ForeignField::make('mytext','foreign_fields.bard') - ->config([ - 'width'=>'25', - 'display' => "My bard Field" - 'validate' => 'required|string|max:3', - ]) - ]), - ]), - ]; - } - } + public function title(): string + { + return 'Site Settings'; + } +} ``` -### Supported Fieldtypes +By default, the global set will use the default site. You can override the `sites()` method to support multiple sites. + +#### Blueprint for Global Sets -All default Statamic field types are supported. You can create custom field types by utilizing the `Field` class. For example to create a custom field type you can use the following code: +To define fields for your global set, create a blueprint with the same handle in the `globals` namespace: ```php -Field::make('custom_field') - ->withAttributes([ - 'type' => 'custom_type', - 'display' => 'Custom Field', - 'instructions' => 'This is a custom field', - 'required' => true, - 'options' => [ - 'option1' => 'Option 1', - 'option2' => 'Option 2', - ], - // Add more attributes here... - ]); -``` +namespace App\Blueprints\Globals; -## How to register Collections and Taxonomies +use Tdwesten\StatamicBuilder\Blueprint; +use Tdwesten\StatamicBuilder\FieldTypes\Section; +use Tdwesten\StatamicBuilder\FieldTypes\Text; +use Tdwesten\StatamicBuilder\FieldTypes\Tab; -This addon enables you to define collections and taxonomies in PHP classes, simplifying the process of defining and managing them. +class SiteSettingsBlueprint extends Blueprint +{ + public static function handle(): string + { + return 'site_settings'; + } -### How to register a collection + public static function blueprintNamespace(): string + { + return 'globals'; + } -1. Generate a new collection blueprint, for example for an articles collection blueprint run the following command: + public function registerTabs(): array + { + return [ + Tab::make('General', [ + Section::make('General', [ + Text::make('site_name') + ->displayName('Site Name') + ->required() + ]), + ]), + ]; + } +} +``` - ```bash - php artisan make:collection Articles - ``` +## Asset Containers -2. Define your Articles collection blueprint in the generated file. For example, the file has all options available to define a collection blueprint. For example: - - ```php - [ - \App\Collections\Articles::class, - ], - ]; - ``` +```php +namespace App\AssetContainers; -### How to register a taxonomy +use Tdwesten\StatamicBuilder\BaseAssetContainer; -1. Generate a new taxonomy blueprint, for example for a categories taxonomy blueprint run the following command: +class Main extends BaseAssetContainer +{ + public static function handle(): string + { + return 'main'; + } - ```bash - php artisan make:taxonomy Categories - ``` + public function title(): string + { + return 'Main Assets'; + } -2. Define your taxonomy in the generated file. For example, the file has all options available to define a taxonomy. For example: + public function disk(): string + { + return 'public'; + } +} +``` - ```php - [ - \App\Taxonomies\Categories::class, - ], - ]; - ``` +## Multi-site Support +Generate a site: - public function title(): string - { - return 'Site Settings'; - } +```bash +php artisan make:site Blog +``` - // Add more options here... - } - ``` +### Working with Foreign Fieldsets -3. Add the global set to the `config/statamic/builder.php` file: +When working with a mixed codebase or utilizing other Statamic addons, you can import their fieldsets using +`ForeignFieldset` and `ForeignField`. - ```php - [ - \App\Globals\SiteSettings::class, - ], - ]; - ``` +```php +use Tdwesten\StatamicBuilder\FieldTypes\ForeignField; +use Tdwesten\StatamicBuilder\FieldTypes\ForeignFieldset; + +// In your registerTabs() method: +Section::make('External', [ + ForeignFieldset::make('statamic-peak-seo::seo_basic') + ->prefix('myseo_'), + + ForeignField::make('mytext', 'foreign_fields.bard') + ->config([ + 'width' => '25', + 'display' => "My Bard Field", + 'validate' => 'required', + ]) +]), +``` -### How to create a Site +## Supported Field Types + +Statamic Builder supports all core Statamic field types. Use the `make($handle)` method to instantiate them. + +| Field Type | Class | +| --- | --- | +| Array | `Arr` | +| Assets | `Assets` | +| Bard | `Bard` | +| Button Group | `ButtonGroup` | +| Checkboxes | `Checkboxes` | +| Code | `Code` | +| Collections | `Collections` | +| Color | `Color` | +| Date | `Date` | +| Dictionary | `Dictionary` | +| Entries | `Entries` | +| Float | `FloatVal` | +| Form | `Form` | +| Grid | `Grid` | +| Group | `Group` | +| Hidden | `Hidden` | +| HTML | `Html` | +| Icon | `Icon` | +| Integer | `Integer` | +| Link | `Link` | +| Lists | `Lists` | +| Markdown | `Markdown` | +| Money | `Money` | +| Navs | `Navs` | +| Number | `Number` | +| Password | `Password` | +| Radio | `Radio` | +| Range | `Range` | +| Rating | `Rating` | +| Replicator | `Replicator` | +| Reveal | `Revealer` | +| Section | `Section` | +| Select | `Select` | +| Sites | `Sites` | +| Slug | `Slug` | +| Spacer | `Spacer` | +| Structures | `Structures` | +| Table | `Table` | +| Taggable | `Taggable` | +| Taxonomies | `Taxonomies` | +| Template | `Template` | +| Terms | `Terms` | +| Text | `Text` | +| Textarea | `Textarea` | +| Time | `Time` | +| Toggle | `Toggle` | +| User Groups | `UserGroups` | +| User Roles | `UserRoles` | +| Users | `Users` | +| Video | `Video` | +| Width | `Width` | +| YAML | `Yaml` | +| ... and many more. | | + +### Custom Fields + +You can create a custom field by extending the `Field` class or using the generator: -> [!WARNING] -The sites are cached forever. When adding a site, you need to clear the cache. +```bash +composer generate-field MyField +``` -1. Create a new site by running the following command: +## Artisan Commands - ```bash - php artisan make:site Blog - ``` +| Command | Description | +|---------------------------|-------------------------------------| +| `make:blueprint` | Create a new Blueprint class. | +| `make:fieldset` | Create a new Fieldset class. | +| `make:collection` | Create a new Collection class. | +| `make:taxonomy` | Create a new Taxonomy class. | +| `make:global-set` | Create a new Global Set class. | +| `make:navigation` | Create a new Navigation class. | +| `make:asset-container` | Create a new Asset Container class. | +| `make:site` | Create a new Site class. | +| `statamic-builder:export` | Export definitions to YAML. | -2. Define your site in the generated file. For example: +## Exporting to YAML - ```php - 'bar']; - */ - public function attributes(): array - { - return []; - } - } - ``` +### Version 1.1.0 (Refactoring & Auto-discovery) -3. Register the Site in your `config/statamic/builder.php` file: +- **Base Classes**: `BaseCollection`, `BaseGlobalSet`, and `BaseNavigation` now provide default implementations for + several methods that were previously abstract. While this simplifies new components, ensure your existing components + still behave as expected if they were relying on the previous abstract structure. +- **Search Index**: `BaseCollection::searchIndex()` return type is now nullable (`?string`). +- **Blueprints**: Blueprints now prefer static `handle()` and `blueprintNamespace()` methods for better auto-discovery + support. - ```php - [ - \App\Sites\Blog::class - ], - ]; - ``` +### Version 1.2.0 (Enhanced Components & Commands) -4. Clear the cache: -```bash - php artisan cache:clear -``` +- **Global Sets & Navigations**: Fully integrated with Statamic repositories, allowing for saving and better multi-site + support. +- **New Commands**: Added `make:site` and `make:asset-container` for faster development. +- **Generator Refactoring**: Unified logic for all generator commands. +- **Field Types**: Added several new field types including `Color`, `Hidden`, `Money`, `Number`, `Password`, `Rating`, + `Time`, and `Video`. diff --git a/composer.json b/composer.json index 607abea..39e9f7d 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "tdwesten/statamic-builder", "description": "A fluent blueprint and fieldset builder for Statamic.", - "version": "v1.1.0", + "version": "v2.0.0", "license": "MIT", "autoload": { "psr-4": { @@ -34,8 +34,8 @@ }, "require": { "php": "^8.2", - "illuminate/support": "^10.0|^11.0|^12.0", - "laravel/framework": "^10.0|^11.0|^12.0", + "illuminate/support": "^11.0|^12.0", + "laravel/framework": "^11.0|^12.0", "statamic/cms": "^5.4" }, "require-dev": { diff --git a/composer.lock b/composer.lock index ff60064..fed3c5f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9155821ee70eb5f6962740a9b0768a0a", + "content-hash": "b44c17995860fb638dafe3fbf2ca8d34", "packages": [ { "name": "ajthinking/archetype", @@ -71,16 +71,16 @@ }, { "name": "brick/math", - "version": "0.14.0", + "version": "0.14.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", "shasum": "" }, "require": { @@ -119,7 +119,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.0" + "source": "https://github.com/brick/math/tree/0.14.1" }, "funding": [ { @@ -127,7 +127,7 @@ "type": "github" } ], - "time": "2025-08-29T12:40:03+00:00" + "time": "2025-11-24T14:40:29+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -650,31 +650,31 @@ }, { "name": "fruitcake/php-cors", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/fruitcake/php-cors.git", - "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", - "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", "shasum": "" }, "require": { - "php": "^7.4|^8.0", - "symfony/http-foundation": "^4.4|^5.4|^6|^7" + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" }, "require-dev": { - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^2", "phpunit/phpunit": "^9", - "squizlabs/php_codesniffer": "^3.5" + "squizlabs/php_codesniffer": "^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -705,7 +705,7 @@ ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" }, "funding": [ { @@ -717,28 +717,28 @@ "type": "github" } ], - "time": "2023-10-12T05:21:21+00:00" + "time": "2025-12-03T09:33:47+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.1.3", + "version": "v1.1.4", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3" + "phpoption/phpoption": "^1.9.5" }, "require-dev": { - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, "type": "library", "autoload": { @@ -767,7 +767,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" }, "funding": [ { @@ -779,7 +779,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:45:45+00:00" + "time": "2025-12-27T19:43:20+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1403,16 +1403,16 @@ }, { "name": "laravel/framework", - "version": "v11.46.1", + "version": "v11.47.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "5fd457f807570a962a53b403b1346efe4cc80bb8" + "reference": "86693ffa1ba32f56f8c44e31416c6665095a62c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/5fd457f807570a962a53b403b1346efe4cc80bb8", - "reference": "5fd457f807570a962a53b403b1346efe4cc80bb8", + "url": "https://api.github.com/repos/laravel/framework/zipball/86693ffa1ba32f56f8c44e31416c6665095a62c5", + "reference": "86693ffa1ba32f56f8c44e31416c6665095a62c5", "shasum": "" }, "require": { @@ -1614,20 +1614,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-09-30T14:51:32+00:00" + "time": "2025-11-28T18:20:11+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.7", + "version": "v0.3.8", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc" + "reference": "096748cdfb81988f60090bbb839ce3205ace0d35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/a1891d362714bc40c8d23b0b1d7090f022ea27cc", - "reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc", + "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35", + "reference": "096748cdfb81988f60090bbb839ce3205ace0d35", "shasum": "" }, "require": { @@ -1643,7 +1643,7 @@ "require-dev": { "illuminate/collections": "^10.0|^11.0|^12.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3|^3.4", + "pestphp/pest": "^2.3|^3.4|^4.0", "phpstan/phpstan": "^1.12.28", "phpstan/phpstan-mockery": "^1.1.3" }, @@ -1671,22 +1671,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.7" + "source": "https://github.com/laravel/prompts/tree/v0.3.8" }, - "time": "2025-09-19T13:47:56+00:00" + "time": "2025-11-21T20:52:52+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.6", + "version": "v2.0.7", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "038ce42edee619599a1debb7e81d7b3759492819" + "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819", - "reference": "038ce42edee619599a1debb7e81d7b3759492819", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd", "shasum": "" }, "require": { @@ -1695,7 +1695,7 @@ "require-dev": { "illuminate/support": "^10.0|^11.0|^12.0", "nesbot/carbon": "^2.67|^3.0", - "pestphp/pest": "^2.36|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", "phpstan/phpstan": "^2.0", "symfony/var-dumper": "^6.2.0|^7.0.0" }, @@ -1734,20 +1734,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-10-09T13:42:30+00:00" + "time": "2025-11-21T20:52:36+00:00" }, { "name": "league/commonmark", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", - "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", "shasum": "" }, "require": { @@ -1784,7 +1784,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.8-dev" + "dev-main": "2.9-dev" } }, "autoload": { @@ -1841,7 +1841,7 @@ "type": "tidelift" } ], - "time": "2025-07-20T12:47:49+00:00" + "time": "2025-11-26T21:48:24+00:00" }, { "name": "league/config", @@ -1927,16 +1927,16 @@ }, { "name": "league/csv", - "version": "9.27.1", + "version": "9.28.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797" + "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797", - "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/6582ace29ae09ba5b07049d40ea13eb19c8b5073", + "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073", "shasum": "" }, "require": { @@ -1946,14 +1946,14 @@ "require-dev": { "ext-dom": "*", "ext-xdebug": "*", - "friendsofphp/php-cs-fixer": "^3.75.0", - "phpbench/phpbench": "^1.4.1", - "phpstan/phpstan": "^1.12.27", + "friendsofphp/php-cs-fixer": "^3.92.3", + "phpbench/phpbench": "^1.4.3", + "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.2", "phpstan/phpstan-strict-rules": "^1.6.2", - "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.3.6", - "symfony/var-dumper": "^6.4.8 || ^7.3.0" + "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.5.4", + "symfony/var-dumper": "^6.4.8 || ^7.4.0 || ^8.0" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", @@ -2014,7 +2014,7 @@ "type": "github" } ], - "time": "2025-10-25T08:35:20+00:00" + "time": "2025-12-27T15:18:42+00:00" }, { "name": "league/flysystem", @@ -2271,33 +2271,38 @@ }, { "name": "league/uri", - "version": "7.5.1", + "version": "7.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "81fb5145d2644324614cc532b28efd0215bda430" + "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", - "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.5", - "php": "^8.1" + "league/uri-interfaces": "^7.7", + "php": "^8.1", + "psr/http-factory": "^1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -2325,6 +2330,7 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ + "URN", "data-uri", "file-uri", "ftp", @@ -2337,9 +2343,11 @@ "psr-7", "query-string", "querystring", + "rfc2141", "rfc3986", "rfc3987", "rfc6570", + "rfc8141", "uri", "uri-template", "url", @@ -2349,7 +2357,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.5.1" + "source": "https://github.com/thephpleague/uri/tree/7.7.0" }, "funding": [ { @@ -2357,26 +2365,25 @@ "type": "github" } ], - "time": "2024-12-08T08:40:02+00:00" + "time": "2025-12-07T16:02:06+00:00" }, { "name": "league/uri-interfaces", - "version": "7.5.0", + "version": "7.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", - "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -2384,6 +2391,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -2408,7 +2416,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interfaces and classes for URI representation and interaction", + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -2433,7 +2441,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0" }, "funding": [ { @@ -2441,20 +2449,20 @@ "type": "github" } ], - "time": "2024-12-08T08:18:47+00:00" + "time": "2025-12-07T16:03:21+00:00" }, { "name": "maennchen/zipstream-php", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", - "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", "shasum": "" }, "require": { @@ -2465,7 +2473,7 @@ "require-dev": { "brianium/paratest": "^7.7", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.16", + "friendsofphp/php-cs-fixer": "^3.86", "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.5", @@ -2511,7 +2519,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1" }, "funding": [ { @@ -2519,7 +2527,7 @@ "type": "github" } ], - "time": "2025-07-17T11:15:13+00:00" + "time": "2025-12-10T09:58:31+00:00" }, { "name": "michelf/php-smartypants", @@ -2577,16 +2585,16 @@ }, { "name": "monolog/monolog", - "version": "3.9.0", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", "shasum": "" }, "require": { @@ -2604,7 +2612,7 @@ "graylog2/gelf-php": "^1.4.2 || ^2.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", + "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.8", "phpstan/phpstan": "^2", @@ -2664,7 +2672,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" }, "funding": [ { @@ -2676,20 +2684,20 @@ "type": "tidelift" } ], - "time": "2025-03-24T10:02:05+00:00" + "time": "2026-01-02T08:56:05+00:00" }, { "name": "nesbot/carbon", - "version": "3.10.3", + "version": "3.11.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f" + "reference": "bdb375400dcd162624531666db4799b36b64e4a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", - "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", + "reference": "bdb375400dcd162624531666db4799b36b64e4a1", "shasum": "" }, "require": { @@ -2697,9 +2705,9 @@ "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", - "symfony/clock": "^6.3.12 || ^7.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0" + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -2781,7 +2789,7 @@ "type": "tidelift" } ], - "time": "2025-09-06T13:39:36+00:00" + "time": "2025-12-02T21:04:28+00:00" }, { "name": "nette/schema", @@ -2850,20 +2858,20 @@ }, { "name": "nette/utils", - "version": "v4.0.8", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", - "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72", + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72", "shasum": "" }, "require": { - "php": "8.0 - 8.5" + "php": "8.2 - 8.5" }, "conflict": { "nette/finder": "<3", @@ -2886,7 +2894,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -2933,22 +2941,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.8" + "source": "https://github.com/nette/utils/tree/v4.1.1" }, - "time": "2025-08-06T21:43:34+00:00" + "time": "2025-12-22T12:14:32+00:00" }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -2991,37 +2999,37 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.3.2", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "eb61920a53057a7debd718a5b89c2178032b52c0" + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/eb61920a53057a7debd718a5b89c2178032b52c0", - "reference": "eb61920a53057a7debd718a5b89c2178032b52c0", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.3.4" + "symfony/console": "^7.3.6" }, "require-dev": { "illuminate/console": "^11.46.1", "laravel/pint": "^1.25.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.4", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.3.4", + "symfony/var-dumper": "^7.3.5", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -3064,7 +3072,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.2" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" }, "funding": [ { @@ -3080,20 +3088,20 @@ "type": "github" } ], - "time": "2025-10-18T11:10:27+00:00" + "time": "2025-11-20T02:34:59+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.4", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", - "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", "shasum": "" }, "require": { @@ -3143,7 +3151,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" }, "funding": [ { @@ -3155,7 +3163,7 @@ "type": "tidelift" } ], - "time": "2025-08-21T11:53:16+00:00" + "time": "2025-12-27T19:41:33+00:00" }, { "name": "pixelfear/composer-dist-plugin", @@ -3739,20 +3747,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -3811,22 +3819,22 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "time": "2025-09-04T20:59:21+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "rebing/graphql-laravel", - "version": "9.12.0", + "version": "9.14.0", "source": { "type": "git", "url": "https://github.com/rebing/graphql-laravel.git", - "reference": "be1e87d20b7ad55009d17ac10926ead447d5328c" + "reference": "918a57797b38dcd86dcd1a14138bcf8613fe8eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rebing/graphql-laravel/zipball/be1e87d20b7ad55009d17ac10926ead447d5328c", - "reference": "be1e87d20b7ad55009d17ac10926ead447d5328c", + "url": "https://api.github.com/repos/rebing/graphql-laravel/zipball/918a57797b38dcd86dcd1a14138bcf8613fe8eef", + "reference": "918a57797b38dcd86dcd1a14138bcf8613fe8eef", "shasum": "" }, "require": { @@ -3915,7 +3923,7 @@ ], "support": { "issues": "https://github.com/rebing/graphql-laravel/issues", - "source": "https://github.com/rebing/graphql-laravel/tree/9.12.0" + "source": "https://github.com/rebing/graphql-laravel/tree/9.14.0" }, "funding": [ { @@ -3923,20 +3931,20 @@ "type": "github" } ], - "time": "2025-11-05T18:57:59+00:00" + "time": "2025-12-08T09:53:36+00:00" }, { "name": "rhukster/dom-sanitizer", - "version": "1.0.7", + "version": "1.0.8", "source": { "type": "git", "url": "https://github.com/rhukster/dom-sanitizer.git", - "reference": "c2a98f27ad742668b254282ccc5581871d0fb601" + "reference": "757e4d6ac03afe9afa4f97cbef453fc5c25f0729" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/c2a98f27ad742668b254282ccc5581871d0fb601", - "reference": "c2a98f27ad742668b254282ccc5581871d0fb601", + "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/757e4d6ac03afe9afa4f97cbef453fc5c25f0729", + "reference": "757e4d6ac03afe9afa4f97cbef453fc5c25f0729", "shasum": "" }, "require": { @@ -3966,9 +3974,9 @@ "description": "A simple but effective DOM/SVG/MathML Sanitizer for PHP 7.4+", "support": { "issues": "https://github.com/rhukster/dom-sanitizer/issues", - "source": "https://github.com/rhukster/dom-sanitizer/tree/1.0.7" + "source": "https://github.com/rhukster/dom-sanitizer/tree/1.0.8" }, - "time": "2023-11-06T16:46:48+00:00" + "time": "2024-04-15T08:48:55+00:00" }, { "name": "scrivo/highlight.php", @@ -4112,20 +4120,20 @@ }, { "name": "spatie/error-solutions", - "version": "2.0.2", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "d077da6c230767b23c4ef2dc43f98c0d1a46b6fd" + "reference": "e85d2c280015da5a1b2c474e64f2a80e9920a631" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d077da6c230767b23c4ef2dc43f98c0d1a46b6fd", - "reference": "d077da6c230767b23c4ef2dc43f98c0d1a46b6fd", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e85d2c280015da5a1b2c474e64f2a80e9920a631", + "reference": "e85d2c280015da5a1b2c474e64f2a80e9920a631", "shasum": "" }, "require": { - "php": "^8.0" + "php": "^8.2" }, "require-dev": { "illuminate/broadcasting": "^10.0|^11.0|^12.0", @@ -4139,8 +4147,8 @@ "psr/simple-cache": "^3.0", "psr/simple-cache-implementation": "^3.0", "spatie/ray": "^1.28", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", + "symfony/cache": "^5.4|^6.0|^7.0|^8.0", + "symfony/process": "^5.4|^6.0|^7.0|^8.0", "vlucas/phpdotenv": "^5.5" }, "suggest": { @@ -4172,7 +4180,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/2.0.2" + "source": "https://github.com/spatie/error-solutions/tree/2.0.4" }, "funding": [ { @@ -4180,7 +4188,7 @@ "type": "github" } ], - "time": "2025-11-06T08:39:38+00:00" + "time": "2025-11-28T09:04:19+00:00" }, { "name": "spatie/shiki-php", @@ -4249,16 +4257,16 @@ }, { "name": "statamic/cms", - "version": "v5.69.0", + "version": "v5.70.0", "source": { "type": "git", "url": "https://github.com/statamic/cms.git", - "reference": "e8dde7db27f775ed5b256fcf57232ef0674bf576" + "reference": "4cc7da4dad596e4270d7c1c4f20909906cf9273c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/statamic/cms/zipball/e8dde7db27f775ed5b256fcf57232ef0674bf576", - "reference": "e8dde7db27f775ed5b256fcf57232ef0674bf576", + "url": "https://api.github.com/repos/statamic/cms/zipball/4cc7da4dad596e4270d7c1c4f20909906cf9273c", + "reference": "4cc7da4dad596e4270d7c1c4f20909906cf9273c", "shasum": "" }, "require": { @@ -4267,7 +4275,7 @@ "ext-json": "*", "guzzlehttp/guzzle": "^6.3 || ^7.0", "james-heinrich/getid3": "^1.9.21", - "laravel/framework": "^10.40 || ^11.34 || ^12.0", + "laravel/framework": "^10.48.29 || ^11.44.1 || ^12.1.1", "laravel/prompts": "^0.1.16 || ^0.2.0 || ^0.3.0", "league/commonmark": "^2.2", "league/csv": "^9.0", @@ -4277,12 +4285,14 @@ "nesbot/carbon": "^2.62.1 || ^3.0", "pixelfear/composer-dist-plugin": "^0.1.4", "rebing/graphql-laravel": "^9.8", - "rhukster/dom-sanitizer": "^1.0.6", + "rhukster/dom-sanitizer": "^1.0.7", "spatie/blink": "^1.3", "spatie/error-solutions": "^1.0 || ^2.0", "statamic/stringy": "^3.1.2", "stillat/blade-parser": "^1.10.1 || ^2.0", + "symfony/http-foundation": "^6.4.29 || ^7.3.7", "symfony/lock": "^6.4", + "symfony/process": "^6.4.14 || ^7.1.7", "symfony/var-exporter": "^6.0", "symfony/yaml": "^6.0 || ^7.0", "ueberdosis/tiptap-php": "^1.4", @@ -4350,7 +4360,7 @@ ], "support": { "issues": "https://github.com/statamic/cms/issues", - "source": "https://github.com/statamic/cms/tree/v5.69.0" + "source": "https://github.com/statamic/cms/tree/v5.70.0" }, "funding": [ { @@ -4358,7 +4368,7 @@ "type": "github" } ], - "time": "2025-11-06T21:22:49+00:00" + "time": "2025-12-03T18:32:28+00:00" }, { "name": "statamic/stringy", @@ -4485,16 +4495,16 @@ }, { "name": "symfony/clock", - "version": "v7.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", - "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", "shasum": "" }, "require": { @@ -4539,7 +4549,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.3.0" + "source": "https://github.com/symfony/clock/tree/v7.4.0" }, "funding": [ { @@ -4551,24 +4561,28 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { "name": "symfony/console", - "version": "v7.3.6", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" + "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", - "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6", + "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6", "shasum": "" }, "require": { @@ -4576,7 +4590,7 @@ "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2" + "symfony/string": "^7.2|^8.0" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -4590,16 +4604,16 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4633,7 +4647,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.6" + "source": "https://github.com/symfony/console/tree/v7.4.3" }, "funding": [ { @@ -4653,20 +4667,20 @@ "type": "tidelift" } ], - "time": "2025-11-04T01:21:42+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/css-selector", - "version": "v7.3.6", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "84321188c4754e64273b46b406081ad9b18e8614" + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/84321188c4754e64273b46b406081ad9b18e8614", - "reference": "84321188c4754e64273b46b406081ad9b18e8614", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", "shasum": "" }, "require": { @@ -4702,7 +4716,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.3.6" + "source": "https://github.com/symfony/css-selector/tree/v7.4.0" }, "funding": [ { @@ -4722,7 +4736,7 @@ "type": "tidelift" } ], - "time": "2025-10-29T17:24:25+00:00" + "time": "2025-10-30T13:39:42+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4793,32 +4807,33 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.6", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "bbe40bfab84323d99dab491b716ff142410a92a8" + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/bbe40bfab84323d99dab491b716ff142410a92a8", - "reference": "bbe40bfab84323d99dab491b716ff142410a92a8", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "conflict": { "symfony/deprecation-contracts": "<2.5", "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ @@ -4850,7 +4865,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.6" + "source": "https://github.com/symfony/error-handler/tree/v7.4.0" }, "funding": [ { @@ -4870,20 +4885,20 @@ "type": "tidelift" } ], - "time": "2025-10-31T19:12:50+00:00" + "time": "2025-11-05T14:29:59+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.3.3", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", - "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", "shasum": "" }, "require": { @@ -4900,13 +4915,14 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4934,7 +4950,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" }, "funding": [ { @@ -4954,7 +4970,7 @@ "type": "tidelift" } ], - "time": "2025-08-13T11:49:31+00:00" + "time": "2025-10-28T09:38:46+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5034,23 +5050,23 @@ }, { "name": "symfony/finder", - "version": "v7.3.5", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f" + "reference": "fffe05569336549b20a1be64250b40516d6e8d06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f", + "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", + "reference": "fffe05569336549b20a1be64250b40516d6e8d06", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -5078,7 +5094,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.5" + "source": "https://github.com/symfony/finder/tree/v7.4.3" }, "funding": [ { @@ -5098,27 +5114,26 @@ "type": "tidelift" } ], - "time": "2025-10-15T18:45:57+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.7", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4" + "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/db488a62f98f7a81d5746f05eea63a74e55bb7c4", - "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a70c745d4cea48dbd609f4075e5f5cbce453bd52", + "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php83": "^1.27" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" }, "conflict": { "doctrine/dbal": "<3.6", @@ -5127,13 +5142,13 @@ "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4.12|^7.1.5", - "symfony/clock": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0" + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -5161,7 +5176,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.7" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.3" }, "funding": [ { @@ -5181,29 +5196,29 @@ "type": "tidelift" } ], - "time": "2025-11-08T16:41:12+00:00" + "time": "2025-12-23T14:23:49+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.7", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "10b8e9b748ea95fa4539c208e2487c435d3c87ce" + "reference": "885211d4bed3f857b8c964011923528a55702aa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/10b8e9b748ea95fa4539c208e2487c435d3c87ce", - "reference": "10b8e9b748ea95fa4539c208e2487c435d3c87ce", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/885211d4bed3f857b8c964011923528a55702aa5", + "reference": "885211d4bed3f857b8c964011923528a55702aa5", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^7.3", - "symfony/http-foundation": "^7.3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -5213,6 +5228,7 @@ "symfony/console": "<6.4", "symfony/dependency-injection": "<6.4", "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", "symfony/form": "<6.4", "symfony/http-client": "<6.4", "symfony/http-client-contracts": "<2.5", @@ -5230,27 +5246,27 @@ }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^7.1", - "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^7.1", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0", - "symfony/var-exporter": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", "twig/twig": "^3.12" }, "type": "library", @@ -5279,7 +5295,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.7" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.3" }, "funding": [ { @@ -5299,7 +5315,7 @@ "type": "tidelift" } ], - "time": "2025-11-12T11:38:40+00:00" + "time": "2025-12-31T08:43:57+00:00" }, { "name": "symfony/lock", @@ -5386,16 +5402,16 @@ }, { "name": "symfony/mailer", - "version": "v7.3.5", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba" + "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba", - "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba", + "url": "https://api.github.com/repos/symfony/mailer/zipball/e472d35e230108231ccb7f51eb6b2100cac02ee4", + "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4", "shasum": "" }, "require": { @@ -5403,8 +5419,8 @@ "php": ">=8.2", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/mime": "^7.2", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -5415,10 +5431,10 @@ "symfony/twig-bridge": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -5446,7 +5462,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.5" + "source": "https://github.com/symfony/mailer/tree/v7.4.3" }, "funding": [ { @@ -5466,24 +5482,25 @@ "type": "tidelift" } ], - "time": "2025-10-24T14:27:20+00:00" + "time": "2025-12-16T08:02:06+00:00" }, { "name": "symfony/mime", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35" + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35", - "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35", + "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -5498,11 +5515,11 @@ "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3" + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" }, "type": "library", "autoload": { @@ -5534,7 +5551,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.4" + "source": "https://github.com/symfony/mime/tree/v7.4.0" }, "funding": [ { @@ -5554,7 +5571,7 @@ "type": "tidelift" } ], - "time": "2025-09-16T08:38:17+00:00" + "time": "2025-11-16T10:14:42+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6143,6 +6160,86 @@ "time": "2025-07-08T02:45:35+00:00" }, { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, + { "name": "symfony/polyfill-uuid", "version": "v1.33.0", "source": { @@ -6227,16 +6324,16 @@ }, { "name": "symfony/process", - "version": "v7.3.4", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" + "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", + "url": "https://api.github.com/repos/symfony/process/zipball/2f8e1a6cdf590ca63715da4d3a7a3327404a523f", + "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f", "shasum": "" }, "require": { @@ -6268,7 +6365,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.4" + "source": "https://github.com/symfony/process/tree/v7.4.3" }, "funding": [ { @@ -6288,20 +6385,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-12-19T10:00:43+00:00" }, { "name": "symfony/routing", - "version": "v7.3.6", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091" + "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/c97abe725f2a1a858deca629a6488c8fc20c3091", - "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091", + "url": "https://api.github.com/repos/symfony/routing/zipball/5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090", + "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090", "shasum": "" }, "require": { @@ -6315,11 +6412,11 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6353,7 +6450,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.6" + "source": "https://github.com/symfony/routing/tree/v7.4.3" }, "funding": [ { @@ -6373,7 +6470,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T07:57:47+00:00" + "time": "2025-12-19T10:00:43+00:00" }, { "name": "symfony/service-contracts", @@ -6464,22 +6561,23 @@ }, { "name": "symfony/string", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f96476035142921000338bad71e5247fbc138872" + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", - "reference": "f96476035142921000338bad71e5247fbc138872", + "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -6487,11 +6585,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6530,7 +6628,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.4" + "source": "https://github.com/symfony/string/tree/v7.4.0" }, "funding": [ { @@ -6550,27 +6648,27 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:36:48+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/translation", - "version": "v7.3.4", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "ec25870502d0c7072d086e8ffba1420c85965174" + "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174", - "reference": "ec25870502d0c7072d086e8ffba1420c85965174", + "url": "https://api.github.com/repos/symfony/translation/zipball/7ef27c65d78886f7599fdd5c93d12c9243ecf44d", + "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5|^3.0" + "symfony/translation-contracts": "^2.5.3|^3.3" }, "conflict": { "nikic/php-parser": "<5.0", @@ -6589,17 +6687,17 @@ "require-dev": { "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0" + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6630,7 +6728,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.4" + "source": "https://github.com/symfony/translation/tree/v7.4.3" }, "funding": [ { @@ -6650,7 +6748,7 @@ "type": "tidelift" } ], - "time": "2025-09-07T11:39:36+00:00" + "time": "2025-12-29T09:31:36+00:00" }, { "name": "symfony/translation-contracts", @@ -6736,16 +6834,16 @@ }, { "name": "symfony/uid", - "version": "v7.3.1", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb" + "reference": "2498e9f81b7baa206f44de583f2f48350b90142c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb", - "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb", + "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c", + "reference": "2498e9f81b7baa206f44de583f2f48350b90142c", "shasum": "" }, "require": { @@ -6753,7 +6851,7 @@ "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "symfony/console": "^6.4|^7.0" + "symfony/console": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6790,7 +6888,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.3.1" + "source": "https://github.com/symfony/uid/tree/v7.4.0" }, "funding": [ { @@ -6802,24 +6900,28 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-25T11:02:55+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.5", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" + "reference": "7e99bebcb3f90d8721890f2963463280848cba92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", - "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7e99bebcb3f90d8721890f2963463280848cba92", + "reference": "7e99bebcb3f90d8721890f2963463280848cba92", "shasum": "" }, "require": { @@ -6831,10 +6933,10 @@ "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", "twig/twig": "^3.12" }, "bin": [ @@ -6873,7 +6975,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.3" }, "funding": [ { @@ -6893,7 +6995,7 @@ "type": "tidelift" } ], - "time": "2025-09-27T09:00:46+00:00" + "time": "2025-12-18T07:04:31+00:00" }, { "name": "symfony/var-exporter", @@ -6978,28 +7080,28 @@ }, { "name": "symfony/yaml", - "version": "v7.3.5", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc" + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc", - "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0" + "symfony/console": "^6.4|^7.0|^8.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -7030,7 +7132,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.5" + "source": "https://github.com/symfony/yaml/tree/v7.4.1" }, "funding": [ { @@ -7050,7 +7152,7 @@ "type": "tidelift" } ], - "time": "2025-09-27T09:00:46+00:00" + "time": "2025-12-04T18:11:45+00:00" }, { "name": "thecodingmachine/safe", @@ -7193,23 +7295,23 @@ }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "0d72ac1c00084279c1816675284073c5a337c20d" + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d", - "reference": "0d72ac1c00084279c1816675284073c5a337c20d", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "php": "^7.4 || ^8.0", - "symfony/css-selector": "^5.4 || ^6.0 || ^7.0" + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^2.0", @@ -7242,9 +7344,9 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" }, - "time": "2024-12-21T16:25:41+00:00" + "time": "2025-12-02T11:56:42+00:00" }, { "name": "ueberdosis/tiptap-php", @@ -7317,26 +7419,26 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.2", + "version": "v5.6.3", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + "reference": "955e7815d677a3eaa7075231212f2110983adecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.3", + "graham-campbell/result-type": "^1.1.4", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3", - "symfony/polyfill-ctype": "^1.24", - "symfony/polyfill-mbstring": "^1.24", - "symfony/polyfill-php80": "^1.24" + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -7385,7 +7487,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" }, "funding": [ { @@ -7397,7 +7499,7 @@ "type": "tidelift" } ], - "time": "2025-04-30T23:37:27+00:00" + "time": "2025-12-27T19:49:13+00:00" }, { "name": "voku/portable-ascii", @@ -7475,16 +7577,16 @@ }, { "name": "webonyx/graphql-php", - "version": "v15.25.2", + "version": "v15.29.3", "source": { "type": "git", "url": "https://github.com/webonyx/graphql-php.git", - "reference": "da3891cb35fa694ad5a796a6c4acfa6bf987740a" + "reference": "abe7cf760fd7d322e3e685b0b2fe14cedb73fafe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/da3891cb35fa694ad5a796a6c4acfa6bf987740a", - "reference": "da3891cb35fa694ad5a796a6c4acfa6bf987740a", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/abe7cf760fd7d322e3e685b0b2fe14cedb73fafe", + "reference": "abe7cf760fd7d322e3e685b0b2fe14cedb73fafe", "shasum": "" }, "require": { @@ -7497,13 +7599,13 @@ "amphp/http-server": "^2.1", "dms/phpunit-arraysubset-asserts": "dev-master", "ergebnis/composer-normalize": "^2.28", - "friendsofphp/php-cs-fixer": "3.89.1", + "friendsofphp/php-cs-fixer": "3.92.3", "mll-lab/php-cs-fixer-config": "5.11.0", "nyholm/psr7": "^1.5", "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "2.1.31", - "phpstan/phpstan-phpunit": "2.0.7", + "phpstan/phpstan": "2.1.33", + "phpstan/phpstan-phpunit": "2.0.11", "phpstan/phpstan-strict-rules": "2.0.7", "phpunit/phpunit": "^9.5 || ^10.5.21 || ^11", "psr/http-message": "^1 || ^2", @@ -7511,9 +7613,9 @@ "react/promise": "^2.0 || ^3.0", "rector/rector": "^2.0", "symfony/polyfill-php81": "^1.23", - "symfony/var-exporter": "^5 || ^6 || ^7", + "symfony/var-exporter": "^5 || ^6 || ^7 || ^8", "thecodingmachine/safe": "^1.3 || ^2 || ^3", - "ticketswap/phpstan-error-formatter": "1.2.3" + "ticketswap/phpstan-error-formatter": "1.2.4" }, "suggest": { "amphp/http-server": "To leverage async resolving with webserver on AMPHP platform", @@ -7538,7 +7640,7 @@ ], "support": { "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v15.25.2" + "source": "https://github.com/webonyx/graphql-php/tree/v15.29.3" }, "funding": [ { @@ -7546,7 +7648,7 @@ "type": "open_collective" } ], - "time": "2025-10-25T09:33:47+00:00" + "time": "2025-12-29T13:48:44+00:00" }, { "name": "wilderborn/partyline", @@ -8047,16 +8149,16 @@ }, { "name": "laravel/pail", - "version": "v1.2.3", + "version": "v1.2.4", "source": { "type": "git", "url": "https://github.com/laravel/pail.git", - "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a" + "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pail/zipball/8cc3d575c1f0e57eeb923f366a37528c50d2385a", - "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a", + "url": "https://api.github.com/repos/laravel/pail/zipball/49f92285ff5d6fc09816e976a004f8dec6a0ea30", + "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30", "shasum": "" }, "require": { @@ -8073,9 +8175,9 @@ "require-dev": { "laravel/framework": "^10.24|^11.0|^12.0", "laravel/pint": "^1.13", - "orchestra/testbench-core": "^8.13|^9.0|^10.0", - "pestphp/pest": "^2.20|^3.0", - "pestphp/pest-plugin-type-coverage": "^2.3|^3.0", + "orchestra/testbench-core": "^8.13|^9.17|^10.8", + "pestphp/pest": "^2.20|^3.0|^4.0", + "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0", "phpstan/phpstan": "^1.12.27", "symfony/var-dumper": "^6.3|^7.0" }, @@ -8122,20 +8224,20 @@ "issues": "https://github.com/laravel/pail/issues", "source": "https://github.com/laravel/pail" }, - "time": "2025-06-05T13:55:57+00:00" + "time": "2025-11-20T16:29:35+00:00" }, { "name": "laravel/pint", - "version": "v1.25.1", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9" + "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9", - "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/69dcca060ecb15e4b564af63d1f642c81a241d6f", + "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f", "shasum": "" }, "require": { @@ -8146,13 +8248,13 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.87.2", - "illuminate/view": "^11.46.0", - "larastan/larastan": "^3.7.1", - "laravel-zero/framework": "^11.45.0", + "friendsofphp/php-cs-fixer": "^3.90.0", + "illuminate/view": "^12.40.1", + "larastan/larastan": "^3.8.0", + "laravel-zero/framework": "^12.0.4", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3.1", - "pestphp/pest": "^2.36.0" + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest": "^3.8.4" }, "bin": [ "builds/pint" @@ -8178,6 +8280,7 @@ "description": "An opinionated code formatter for PHP.", "homepage": "https://laravel.com", "keywords": [ + "dev", "format", "formatter", "lint", @@ -8188,20 +8291,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-09-19T02:57:12+00:00" + "time": "2025-11-25T21:15:52+00:00" }, { "name": "laravel/tinker", - "version": "v2.10.1", + "version": "v2.10.2", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3" + "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/22177cc71807d38f2810c6204d8f7183d88a57d3", - "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3", + "url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c", + "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c", "shasum": "" }, "require": { @@ -8252,9 +8355,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.10.1" + "source": "https://github.com/laravel/tinker/tree/v2.10.2" }, - "time": "2025-01-27T14:24:01+00:00" + "time": "2025-11-20T16:29:12+00:00" }, { "name": "mockery/mockery", @@ -8401,16 +8504,16 @@ }, { "name": "nunomaduro/collision", - "version": "v8.8.2", + "version": "v8.8.3", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb" + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", - "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4", "shasum": "" }, "require": { @@ -8432,7 +8535,7 @@ "laravel/sanctum": "^4.1.1", "laravel/tinker": "^2.10.1", "orchestra/testbench-core": "^9.12.0 || ^10.4", - "pestphp/pest": "^3.8.2", + "pestphp/pest": "^3.8.2 || ^4.0.0", "sebastian/environment": "^7.2.1 || ^8.0" }, "type": "library", @@ -8496,7 +8599,7 @@ "type": "patreon" } ], - "time": "2025-06-25T02:12:12+00:00" + "time": "2025-11-20T02:55:25+00:00" }, { "name": "orchestra/canvas", @@ -8639,20 +8742,21 @@ }, { "name": "orchestra/sidekick", - "version": "v1.2.17", + "version": "v1.2.19", "source": { "type": "git", "url": "https://github.com/orchestral/sidekick.git", - "reference": "371ce2882ee3f5bf826b36e75d461e51c9cd76c2" + "reference": "79e87f3f56df1fb78f62397bc405a6af8d784263" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/sidekick/zipball/371ce2882ee3f5bf826b36e75d461e51c9cd76c2", - "reference": "371ce2882ee3f5bf826b36e75d461e51c9cd76c2", + "url": "https://api.github.com/repos/orchestral/sidekick/zipball/79e87f3f56df1fb78f62397bc405a6af8d784263", + "reference": "79e87f3f56df1fb78f62397bc405a6af8d784263", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", + "composer/semver": "^3.0", "php": "^8.1", "symfony/polyfill-php83": "^1.32" }, @@ -8670,6 +8774,7 @@ "autoload": { "files": [ "src/Eloquent/functions.php", + "src/Filesystem/functions.php", "src/Http/functions.php", "src/functions.php" ], @@ -8690,9 +8795,9 @@ "description": "Packages Toolkit Utilities and Helpers for Laravel", "support": { "issues": "https://github.com/orchestral/sidekick/issues", - "source": "https://github.com/orchestral/sidekick/tree/v1.2.17" + "source": "https://github.com/orchestral/sidekick/tree/v1.2.19" }, - "time": "2025-10-02T11:02:26+00:00" + "time": "2025-12-30T04:11:10+00:00" }, { "name": "orchestra/testbench", @@ -9320,16 +9425,16 @@ }, { "name": "pestphp/pest-plugin-profanity", - "version": "v4.2.0", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/pestphp/pest-plugin-profanity.git", - "reference": "c37e5e2c7136ee4eae12082e7952332bc1c6600a" + "reference": "343cfa6f3564b7e35df0ebb77b7fa97039f72b27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-profanity/zipball/c37e5e2c7136ee4eae12082e7952332bc1c6600a", - "reference": "c37e5e2c7136ee4eae12082e7952332bc1c6600a", + "url": "https://api.github.com/repos/pestphp/pest-plugin-profanity/zipball/343cfa6f3564b7e35df0ebb77b7fa97039f72b27", + "reference": "343cfa6f3564b7e35df0ebb77b7fa97039f72b27", "shasum": "" }, "require": { @@ -9370,9 +9475,9 @@ "unit" ], "support": { - "source": "https://github.com/pestphp/pest-plugin-profanity/tree/v4.2.0" + "source": "https://github.com/pestphp/pest-plugin-profanity/tree/v4.2.1" }, - "time": "2025-10-28T23:14:11+00:00" + "time": "2025-12-08T00:13:17+00:00" }, { "name": "phar-io/manifest", @@ -9547,16 +9652,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.3", + "version": "5.6.6", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", "shasum": "" }, "require": { @@ -9566,7 +9671,7 @@ "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", "phpstan/phpdoc-parser": "^1.7|^2.0", - "webmozart/assert": "^1.9.1" + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { "mockery/mockery": "~1.3.5 || ~1.6.0", @@ -9605,22 +9710,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" }, - "time": "2025-08-01T19:43:32+00:00" + "time": "2025-12-22T21:13:58+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.10.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { @@ -9663,9 +9768,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2024-11-09T15:12:26+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -9716,11 +9821,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.32", + "version": "2.1.33", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", - "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", "shasum": "" }, "require": { @@ -9765,27 +9870,27 @@ "type": "github" } ], - "time": "2025-11-11T15:18:17+00:00" + "time": "2025-12-05T10:24:31+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "12.4.0", + "version": "12.5.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c" + "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c", - "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4a9739b51cbcb355f6e95659612f92e282a7077b", + "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.6.1", + "nikic/php-parser": "^5.7.0", "php": ">=8.3", "phpunit/php-file-iterator": "^6.0", "phpunit/php-text-template": "^5.0", @@ -9793,10 +9898,10 @@ "sebastian/environment": "^8.0.3", "sebastian/lines-of-code": "^4.0", "sebastian/version": "^6.0", - "theseer/tokenizer": "^1.2.3" + "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^12.3.7" + "phpunit/phpunit": "^12.5.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -9805,7 +9910,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "12.4.x-dev" + "dev-main": "12.5.x-dev" } }, "autoload": { @@ -9834,7 +9939,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.4.0" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.2" }, "funding": [ { @@ -9854,7 +9959,7 @@ "type": "tidelift" } ], - "time": "2025-09-24T13:44:41+00:00" + "time": "2025-12-24T07:03:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -10208,16 +10313,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.14", + "version": "v0.12.18", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "95c29b3756a23855a30566b745d218bee690bef2" + "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/95c29b3756a23855a30566b745d218bee690bef2", - "reference": "95c29b3756a23855a30566b745d218bee690bef2", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ddff0ac01beddc251786fe70367cd8bbdb258196", + "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196", "shasum": "" }, "require": { @@ -10225,8 +10330,8 @@ "ext-tokenizer": "*", "nikic/php-parser": "^5.0 || ^4.0", "php": "^8.0 || ^7.4", - "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" }, "conflict": { "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" @@ -10281,27 +10386,27 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.14" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.18" }, - "time": "2025-10-27T17:15:31+00:00" + "time": "2025-12-17T14:35:46+00:00" }, { "name": "rector/rector", - "version": "2.2.8", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "303aa811649ccd1d32e51e62d5c85949d01b5f1b" + "reference": "f7166355dcf47482f27be59169b0825995f51c7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/303aa811649ccd1d32e51e62d5c85949d01b5f1b", - "reference": "303aa811649ccd1d32e51e62d5c85949d01b5f1b", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/f7166355dcf47482f27be59169b0825995f51c7d", + "reference": "f7166355dcf47482f27be59169b0825995f51c7d", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.32" + "phpstan/phpstan": "^2.1.33" }, "conflict": { "rector/rector-doctrine": "*", @@ -10335,7 +10440,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.2.8" + "source": "https://github.com/rectorphp/rector/tree/2.3.0" }, "funding": [ { @@ -10343,7 +10448,7 @@ "type": "github" } ], - "time": "2025-11-12T18:38:00+00:00" + "time": "2025-12-25T22:00:18+00:00" }, { "name": "sebastian/cli-parser", @@ -11296,16 +11401,16 @@ }, { "name": "statamic/eloquent-driver", - "version": "v4.35.0", + "version": "v4.35.2", "source": { "type": "git", "url": "https://github.com/statamic/eloquent-driver.git", - "reference": "3b1ee251da3b68195eb0559b559f1bb0ab7e48bf" + "reference": "e5fdedbff1bb2802678e047c5020ee5db4611182" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/statamic/eloquent-driver/zipball/3b1ee251da3b68195eb0559b559f1bb0ab7e48bf", - "reference": "3b1ee251da3b68195eb0559b559f1bb0ab7e48bf", + "url": "https://api.github.com/repos/statamic/eloquent-driver/zipball/e5fdedbff1bb2802678e047c5020ee5db4611182", + "reference": "e5fdedbff1bb2802678e047c5020ee5db4611182", "shasum": "" }, "require": { @@ -11343,9 +11448,9 @@ "description": "Allows you to store Statamic data in a database.", "support": { "issues": "https://github.com/statamic/eloquent-driver/issues", - "source": "https://github.com/statamic/eloquent-driver/tree/v4.35.0" + "source": "https://github.com/statamic/eloquent-driver/tree/v4.35.2" }, - "time": "2025-10-30T18:00:41+00:00" + "time": "2025-12-12T09:38:43+00:00" }, { "name": "symfony/polyfill-php84", @@ -11488,23 +11593,23 @@ }, { "name": "theseer/tokenizer", - "version": "1.3.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb" + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/d74205c497bfbca49f34d4bc4c19c17e22db4ebb", - "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.1" }, "type": "library", "autoload": { @@ -11526,7 +11631,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.0" + "source": "https://github.com/theseer/tokenizer/tree/2.0.1" }, "funding": [ { @@ -11534,27 +11639,27 @@ "type": "github" } ], - "time": "2025-11-13T13:44:09+00:00" + "time": "2025-12-08T11:19:18+00:00" }, { "name": "webmozart/assert", - "version": "1.12.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + "reference": "1b34b004e35a164bc5bb6ebd33c844b2d8069a54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/1b34b004e35a164bc5bb6ebd33c844b2d8069a54", + "reference": "1b34b004e35a164bc5bb6ebd33c844b2d8069a54", "shasum": "" }, "require": { "ext-ctype": "*", "ext-date": "*", "ext-filter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.2" }, "suggest": { "ext-intl": "", @@ -11564,7 +11669,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-feature/2-0": "2.0-dev" } }, "autoload": { @@ -11580,6 +11685,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], "description": "Assertions to validate method input/output with nice error messages.", @@ -11590,9 +11699,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.1" + "source": "https://github.com/webmozarts/assert/tree/2.0.0" }, - "time": "2025-10-29T15:56:20+00:00" + "time": "2025-12-16T21:36:00+00:00" } ], "aliases": [], diff --git a/config/builder.php b/config/builder.php index 75f883f..9aa72e4 100644 --- a/config/builder.php +++ b/config/builder.php @@ -102,4 +102,62 @@ 'sites' => [ // App\Sites\Blog::class, ], + + /* + |-------------------------------------------------------------------------- + | Register Asset Containers + |-------------------------------------------------------------------------- + | + | Here you can register the asset containers that you want to use in your + | Statamic site. + | + */ + 'asset_containers' => [ + // App\AssetContainers\Main::class, + ], + + /* + |-------------------------------------------------------------------------- + | Register Navigations + |-------------------------------------------------------------------------- + | + | Here you can register the navigations that you want to use in your + | Statamic site. + | + */ + 'navigations' => [ + // App\Navigations\Main::class, + ], + + /* + |-------------------------------------------------------------------------- + | Auto Registration + |-------------------------------------------------------------------------- + | + | Here you can enable auto registration of your blueprints, fieldsets, + | collections, taxonomies, globals, sites, and navigations. + | + */ + 'auto_registration' => false, + + /* + |-------------------------------------------------------------------------- + | Auto Discovery Paths + |-------------------------------------------------------------------------- + | + | Here you can define the paths that will be used to auto discover your + | blueprints, fieldsets, collections, taxonomies, globals, sites, and + | navigations. + | + */ + 'auto_discovery' => [ + 'blueprints' => app_path('Blueprints'), + 'fieldsets' => app_path('Fieldsets'), + 'collections' => app_path('Collections'), + 'taxonomies' => app_path('Taxonomies'), + 'globals' => app_path('Globals'), + 'navigations' => app_path('Navigations'), + 'asset_containers' => app_path('AssetContainers'), + 'sites' => app_path('Sites'), + ], ]; diff --git a/coverage.txt b/coverage.txt new file mode 100644 index 0000000..c3f4e76 --- /dev/null +++ b/coverage.txt @@ -0,0 +1,603 @@ + + PASS Tests\Unit\AssetContainerRepositoryTest + ✓ ::all includes builder-registered asset containers 0.18s + + PASS Tests\Unit\BlueprintTest + ✓ Has a title 0.01s + ✓ it can be set to hidden + ✓ Tabs are renderd + ✓ it throws an exception when adding a field to a tab + ✓ you can set a title + ✓ you can get the handle + + PASS Tests\Unit\CollectionRepositoryTest + ✓ ::find does not throw when no result is found 0.03s + ✓ ::find returns null for nonexistent handles 0.02s + ✓ ::findByHandle finds builder-registered collection 0.03s + ✓ ::all includes builder-registered collections 0.03s + + PASS Tests\Unit\CollectionTest + ✓ Has a title + ✓ Has a handle + ✓ Has a route + ✓ Has multiple route for multisites + ✓ Can have multiple sites + ✓ Has slugs + ✓ Has default values for optional methods + + PASS Tests\Unit\FieldParserTest + ✓ it preserves field order when flattening mixed fields 0.01s + + PASS Tests\Unit\FieldTest + ✓ it can render to a array + ✓ Can set a handle prefix + ✓ Can set a display name + ✓ Can set instructions + ✓ Can set visibility + ✓ Can set required + ✓ Can set instructions position + ✓ Can set listable + ✓ Can set replicator preview + ✓ Can set width + ✓ Can set antlers + ✓ Can set duplicate + ✓ Can set hide display + ✓ Can set localizable + ✓ Can set validation to sometimes + ✓ can set multiple validation rules + ✓ Can set a custom icon + ✓ Can create a thirth-party field + + PASS Tests\Unit\Fields\ArrTest + ✓ it can render to a array 0.01s + ✓ it can render to a array with mode + ✓ it can render to a array with keys + + PASS Tests\Unit\Fields\AssetsTest + ✓ it can render to a array + ✓ it can set max files + ✓ it can set min files + ✓ it can set mode + ✓ it can set container + ✓ it can set folder + ✓ it can restrict + ✓ it can allow uploads + ✓ it can show filename + ✓ it can show set alt + ✓ it can set query_scopes + ✓ it can set dynamic folder + + PASS Tests\Unit\Fields\BardTest + ✓ it can render to a array 0.01s + ✓ you can add multiple buttons + ✓ you can add custom buttons as a string + ✓ you can add a single button + ✓ you can set the inline option + ✓ you can set inline to false + ✓ you can set inline to break (accordion) + + PASS Tests\Unit\Fields\ButtonGroupTest + ✓ it can render to a array 0.01s + ✓ it can render to a array with options + ✓ it can render to a array with default value + ✓ it can set default value using defaultValue method + + PASS Tests\Unit\Fields\CheckboxesTest + ✓ it can render to a array + ✓ it can render to a array with options + ✓ it can render to a array with default value + ✓ it can render to a array with inline + + PASS Tests\Unit\Fields\CodeTest + ✓ it can render to a array + ✓ you can set the mode + ✓ you can set the mode selectable + ✓ you can set the indent type + ✓ you can set the indent size + ✓ you can set the key map + ✓ you can set the line numbers + ✓ you can set the line wrapping + ✓ you can set the rulers + + PASS Tests\Unit\Fields\CollectionsTest + ✓ it can render to a array 0.01s + ✓ it can have max items + ✓ it can have a mode + + PASS Tests\Unit\Fields\CollorTest + ✓ it can render to a array + ✓ you can set the allow any + ✓ you can set the swatches + ✓ you can set the default value + + PASS Tests\Unit\Fields\ConditionalLogicTest + ✓ Can set conditional logic if + ✓ Can set conditional logic if with multiple conditions + ✓ Can set conditional logic if any + ✓ Can set conditional logic with custom if + ✓ Can set conditional logic with custom if_any + ✓ Can set conditional logic unless + ✓ Can set conditional logic unless with multiple conditions + ✓ Can set conditional logic with custom unless + + PASS Tests\Unit\Fields\DateTest + ✓ it can render to a array 0.01s + ✓ it can render to a array with mode + ✓ it can render to a array with inline + ✓ it can render to a array with full width + ✓ it can render to a array with columns + ✓ it can render to a array with rows + ✓ it can render to a array with time enabled + ✓ it can render to a array with time seconds enabled + ✓ it can render to a array with earliest date + ✓ it can render to a array with latest date + ✓ it can render to a array with format + + PASS Tests\Unit\Fields\DictionaryTest + ✓ it can render to a array 0.01s + ✓ it type can be set + ✓ it can set additional dictionary options + ✓ it can set placeholder + ✓ it can set max items + + PASS Tests\Unit\Fields\EntriesTest + ✓ it can render to a array + ✓ it can render to a array with max items + ✓ it can render to a array with mode + ✓ it can render to a array with collections + ✓ it can render to a array with search index + ✓ it can render the queryScopes to the array + + PASS Tests\Unit\Fields\FieldsetTest + ✓ it can be instantiated + ✓ it can be registered + ✓ it can be converted to an array + ✓ A fieldset can be used in a blueprint + ✓ A fieldset can be used in group + ✓ A fieldset can be used in a fieldset + + PASS Tests\Unit\Fields\FloatValTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\ForeignFieldTest + ✓ Foreign field can be rendered + ✓ Foreign field can be rendered with config + + PASS Tests\Unit\Fields\ForeignFieldsetTest + ✓ Foreign fieldset can be rendered + ✓ Foreign fieldset can be rendered with prefix + + PASS Tests\Unit\Fields\FormTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\GridTest + ✓ it can render to a array + ✓ it can render to a array with reorderable + ✓ it can render to a array with add row + ✓ it can render to a array with max rows + ✓ it can render to a array with min rows + ✓ it can render to a array with mode + ✓ it can render to a array with fields + + PASS Tests\Unit\Fields\GroupTest + ✓ Renders a group to array + ✓ A group can have a group + ✓ Can set to fullscreen + + PASS Tests\Unit\Fields\HtmlTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\IconTest + ✓ it can render to a array + ✓ it can render to a array with directory + ✓ it can render to a array with folder + ✓ it can render to a array with default + + PASS Tests\Unit\Fields\IntegerTest + ✓ it can render to a array + ✓ it can have a prepend + ✓ it can have a append + + PASS Tests\Unit\Fields\LinkTest + ✓ it can render to a array + ✓ it can render to a array with collections + ✓ it can render to a array with container + + PASS Tests\Unit\Fields\ListsTest + ✓ it can render to a array + ✓ it can render to a array with options + + PASS Tests\Unit\Fields\MarkdownTest + ✓ it can render to a array + ✓ it can render to a array with buttons + ✓ it can render a array with buttons as string + ✓ Can set a asset container + ✓ Can set a asset folder + ✓ Can resetrict to a asset folder + ✓ can set automatic line breaks + ✓ can set automatic links + ✓ can set escape markup + ✓ can set heading anchors + ✓ Can set smartypants + ✓ Can set default value + ✓ Can enable table of contents + ✓ Can define parser + + PASS Tests\Unit\Fields\NavsTest + ✓ it can render to a array 0.01s + ✓ Can set max items + ✓ Can set mode + + PASS Tests\Unit\Fields\RadioTest + ✓ it can render to a array + ✓ it can have options + ✓ it can be inline + ✓ it can cast booleans + + PASS Tests\Unit\Fields\RangeTest + ✓ it can render to a array + ✓ can have a min + ✓ can have a max + ✓ can have a step + ✓ can have a prepend + ✓ can have a append + + PASS Tests\Unit\Fields\RelationshipTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\ReplicatorTest + ✓ it can render to a array + ✓ it can have sets + ✓ it can render the same output + ✓ can set collapse to false + ✓ can set collapse to true + ✓ can set collapse to accordion + + PASS Tests\Unit\Fields\RevealerTest + ✓ it can render to a array + ✓ it can have a ui mode + ✓ it can have a input_label + + PASS Tests\Unit\Fields\SectionFieldTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\SectionTest + ✓ Section can be rendered + + PASS Tests\Unit\Fields\SelectTest + ✓ it can render to a array + ✓ it can set taggable + ✓ it can set push tags + ✓ it can set placeholder + ✓ it can set multiple + ✓ it can set max items + ✓ it can set clearable + ✓ it can set searchable + ✓ it can set cast booleans + + PASS Tests\Unit\Fields\SetGroupTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\SetTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\SitesTest + ✓ it can render to a array + ✓ Can set max items + ✓ Can set mode + + PASS Tests\Unit\Fields\SlugTest + ✓ it can render to a array + ✓ it can set from + ✓ it can set generate + ✓ it can show regenerate + + PASS Tests\Unit\Fields\SpacerTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\StructuresTest + ✓ it can render to a array + ✓ it can set max_items + ✓ it can set mode + + PASS Tests\Unit\Fields\TabTest + ✓ Tab can be rendered + + PASS Tests\Unit\Fields\TableTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\TaggableTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\TaggebleTest + ✓ it can render to a array + + PASS Tests\Unit\Fields\TaxonomiesTest + ✓ it can render to a array + ✓ Can set max items + ✓ Can set mode + + PASS Tests\Unit\Fields\TemplateTest + ✓ it can render to a array + ✓ it can hide partials + ✓ it can set blueprint + ✓ it can set folder + + PASS Tests\Unit\Fields\TermsTest + ✓ Terms field renders + ✓ You can set max items + ✓ Terms field renders with multiple taxonomies + ✓ Terms field renders with multiple taxonomies and mode + ✓ Terms field renders with create option + ✓ can have query scopes + + PASS Tests\Unit\Fields\TextTest + ✓ it can render to a array + ✓ Renders expected array data + ✓ Can set input type to email + ✓ Can set input type to number + ✓ Can add a placeholder + ✓ Can add a default value + ✓ Can add a character limit + ✓ Can add autocomplete options + ✓ Can add prepend text + ✓ Can add append text + + PASS Tests\Unit\Fields\TextareaTest + ✓ it can render to a array + ✓ it can have a character limit + ✓ it can have a placeholder + ✓ it can have a default value + + PASS Tests\Unit\Fields\TimeTest + ✓ it can render to a array + ✓ it can have seconds enabled + + PASS Tests\Unit\Fields\ToggleTest + ✓ it can render to a array + ✓ it can have inline label + ✓ it can have inline label when true + + PASS Tests\Unit\Fields\UserGroupsTest + ✓ it can render to a array + ✓ Can set max items + ✓ Can set mode + + PASS Tests\Unit\Fields\UserRolesTest + ✓ it can render to a array + ✓ it can have max items + ✓ it can have a mode + + PASS Tests\Unit\Fields\UsersTest + ✓ it can render to a array + ✓ can have max items + ✓ can have query scopes + ✓ can have mode + + PASS Tests\Unit\Fields\VideoTest + ✓ it can render to a array + ✓ it can have a default + ✓ it can have a placeholder + + PASS Tests\Unit\Fields\WidthTest + ✓ it can render to a array + ✓ it can have options + ✓ it can have a default + + PASS Tests\Unit\Fields\YamlTest + ✓ it can render to a array + ✓ it can have a default + + PASS Tests\Unit\FieldsetRepositoryTest + ✓ ::all includes builder-registered fieldsets 0.03s + ✓ ::find finds builder-registered fieldset 0.02s + ✓ ::save does not save builder-registered fieldset 0.02s + + PASS Tests\Unit\GlobalRepositoryTest + ✓ ::find does not throw when no result is found 0.03s + ✓ ::find returns null for nonexistent handles 0.03s + ✓ ::findByHandle finds builder-registered global 0.03s + ✓ ::all includes builder-registered globals 0.02s + ✓ ::all includes globals from blueprints 0.03s + + PASS Tests\Unit\GlobalSetTest + ✓ Has a handle + ✓ Has a title + ✓ Has sites + + PASS Tests\Unit\MakeBlueprintCommandTest + ✓ it can create a blueprint 0.06s + + PASS Tests\Unit\MakeCollectionCommandTest + ✓ it can create a collection 0.03s + + PASS Tests\Unit\MakeFieldsetCommandTest + ✓ it can create a fieldset 0.03s + + PASS Tests\Unit\MakeGlobalSetCommandTest + ✓ it can generate a global set class 0.03s + + PASS Tests\Unit\MakeNavigationCommandTest + ✓ it can generate a navigation class 0.03s + + PASS Tests\Unit\MakeSiteCommandTest + ✓ it can create a site 0.03s + + PASS Tests\Unit\MakeTaxonomyCommandTest + ✓ it can create a taxonomy 0.03s + + PASS Tests\Unit\NavigationRepositoryTest + ✓ ::find does not throw when no result is found 0.03s + ✓ ::find returns null for nonexistent handles 0.02s + ✓ ::findByHandle finds builder-registered navigation 0.03s + ✓ ::all includes builder-registered navigations 0.03s + + PASS Tests\Unit\NavigationTest + ✓ Has a handle + ✓ Has a title + ✓ Has default collections + ✓ Has default sites + ✓ Has default expectsRoot + ✓ Has default maxDepth + + PASS Tests\Unit\ServiceProviderTest + ✓ it binds the navigation repository contract 0.03s + ✓ it binds the global repository contract 0.02s + ✓ it binds the asset container repository contract 0.02s + ✓ it binds the eloquent navigation repository when driver is eloquent 0.02s + ✓ it binds the eloquent global repository when driver is eloquent 0.03s + + PASS Tests\Unit\SiteTest + ✓ Has a name + ✓ Has a handle + ✓ Has a url + ✓ Has a locale + ✓ Has extra attributes + ✓ Can convert to array + + PASS Tests\Unit\TaxonomyBlueprintTest + ✓ taxonomy blueprints can be edited 0.05s + + PASS Tests\Unit\TaxonomyRepositoryTest + ✓ ::all includes builder-registered taxonomies 0.03s + ✓ ::findByHandle finds builder-registered taxonomy 0.02s + + Tests: 305 passed (697 assertions) + Duration: 1.82s + + BaseCollection ......................................................................................................................................................................... 14..124 / 53.1% + BaseGlobalSet ........................................................................................................................................................................... 14..19 / 75.0% + BaseNavigation .............................................................................................................................................................................. 14 / 91.7% + BaseSite ........................................................................................................................................................................................ 100.0% + BaseTaxonomy .................................................................................................................................................................................... 100.0% + Blueprint ............................................................................................................................................................................... 45, 53 / 92.3% + Console/Export .................................................................................................................................................................................... 0.0% + Console/ImportBlueprints ........................................................................................................................................................................ 100.0% + Console/MakeBlueprintCommand .................................................................................................................................................................... 100.0% + Console/MakeCollectionCommand ................................................................................................................................................................... 100.0% + Console/MakeFieldsetCommand ..................................................................................................................................................................... 100.0% + Console/MakeGlobalSetCommand .................................................................................................................................................................... 100.0% + Console/MakeNavigationCommand ................................................................................................................................................................... 100.0% + Console/MakeSiteCommand ......................................................................................................................................................................... 100.0% + Console/MakeTaxonomyCommand ..................................................................................................................................................................... 100.0% + Contracts/Append ................................................................................................................................................................................ 100.0% + Contracts/Blueprint ............................................................................................................................................................................. 100.0% + Contracts/ConditionalLogic ...................................................................................................................................................................... 100.0% + Contracts/DefaultValue .......................................................................................................................................................................... 100.0% + Contracts/Fullscreen ............................................................................................................................................................................ 100.0% + Contracts/Makeble ............................................................................................................................................................................... 100.0% + Contracts/MaxItems .............................................................................................................................................................................. 100.0% + Contracts/Prepend ............................................................................................................................................................................... 100.0% + Contracts/QueryScopes ........................................................................................................................................................................... 100.0% + Contracts/Renderable ............................................................................................................................................................................ 100.0% + Contracts/UISelectMode .......................................................................................................................................................................... 100.0% + Enums/ArrayModeOption ........................................................................................................................................................................... 100.0% + Enums/AssetsUIModeOption ........................................................................................................................................................................ 100.0% + Enums/AutocompleteOption ........................................................................................................................................................................ 100.0% + Enums/BardButton ................................................................................................................................................................................ 100.0% + Enums/BardInlineOption .......................................................................................................................................................................... 100.0% + Enums/BardToolbarMode ........................................................................................................................................................................... 100.0% + Enums/CodeIndentTypeOption ...................................................................................................................................................................... 100.0% + Enums/CodeKeyMapOption .......................................................................................................................................................................... 100.0% + Enums/CodeModeOption ............................................................................................................................................................................ 100.0% + Enums/CollapseOption ............................................................................................................................................................................ 100.0% + Enums/DateModeOption ............................................................................................................................................................................ 100.0% + Enums/DynamicFolderOption ....................................................................................................................................................................... 100.0% + Enums/GridModeOption ............................................................................................................................................................................ 100.0% + Enums/Icon ...................................................................................................................................................................................... 100.0% + Enums/InputTypeOption ........................................................................................................................................................................... 100.0% + Enums/ListableOption ............................................................................................................................................................................ 100.0% + Enums/MarkdownButtonOption ...................................................................................................................................................................... 100.0% + Enums/OperatorOption ............................................................................................................................................................................ 100.0% + Enums/RevealerModeUI ............................................................................................................................................................................ 100.0% + Enums/UIModeOption .............................................................................................................................................................................. 100.0% + Enums/VisibilityOption .......................................................................................................................................................................... 100.0% + Exceptions/BlueprintRenderException ............................................................................................................................................................. 100.0% + FieldTypes/Arr .................................................................................................................................................................................... 0.0% + FieldTypes/Assets ................................................................................................................................................................................. 0.0% + FieldTypes/Bard ................................................................................................................................................................................... 0.0% + FieldTypes/ButtonGroup ............................................................................................................................................................................ 0.0% + FieldTypes/Checkboxes ............................................................................................................................................................................. 0.0% + FieldTypes/Code ................................................................................................................................................................................... 0.0% + FieldTypes/Collections .......................................................................................................................................................................... 100.0% + FieldTypes/Collor ................................................................................................................................................................................. 0.0% + FieldTypes/Date ................................................................................................................................................................................... 0.0% + FieldTypes/Dictionary ............................................................................................................................................................................. 0.0% + FieldTypes/Entries ................................................................................................................................................................................ 0.0% + FieldTypes/Field ....................................................................................................................................................... 129, 199, 218..227, 312 / 92.7% + FieldTypes/FloatVal ............................................................................................................................................................................... 0.0% + FieldTypes/ForeignField ........................................................................................................................................................................... 0.0% + FieldTypes/ForeignFieldset ........................................................................................................................................................................ 0.0% + FieldTypes/Form ................................................................................................................................................................................... 0.0% + FieldTypes/Grid ................................................................................................................................................................................... 0.0% + FieldTypes/Group .................................................................................................................................................................................. 0.0% + FieldTypes/Html ................................................................................................................................................................................... 0.0% + FieldTypes/Icon ................................................................................................................................................................................... 0.0% + FieldTypes/Integer ................................................................................................................................................................................ 0.0% + FieldTypes/Link ................................................................................................................................................................................... 0.0% + FieldTypes/Lists .................................................................................................................................................................................. 0.0% + FieldTypes/Markdown ............................................................................................................................................................................... 0.0% + FieldTypes/Navs ................................................................................................................................................................................. 100.0% + FieldTypes/Radio .................................................................................................................................................................................. 0.0% + FieldTypes/Range .................................................................................................................................................................................. 0.0% + FieldTypes/Relationship ......................................................................................................................................................................... 100.0% + FieldTypes/Replicator ............................................................................................................................................................................. 0.0% + FieldTypes/Revealer ............................................................................................................................................................................... 0.0% + FieldTypes/Section ................................................................................................................................................................................ 0.0% + FieldTypes/SectionField ......................................................................................................................................................................... 100.0% + FieldTypes/Select ................................................................................................................................................................................. 0.0% + FieldTypes/Set .................................................................................................................................................................................... 0.0% + FieldTypes/SetGroup ............................................................................................................................................................................... 0.0% + FieldTypes/Sites ................................................................................................................................................................................ 100.0% + FieldTypes/Slug ................................................................................................................................................................................... 0.0% + FieldTypes/Spacer ................................................................................................................................................................................. 0.0% + FieldTypes/Structures ........................................................................................................................................................................... 100.0% + FieldTypes/Tab .................................................................................................................................................................................... 0.0% + FieldTypes/Table ................................................................................................................................................................................ 100.0% + FieldTypes/Taggable ............................................................................................................................................................................... 0.0% + FieldTypes/Taggeble ............................................................................................................................................................................... 0.0% + FieldTypes/Taxonomies ........................................................................................................................................................................... 100.0% + FieldTypes/Template ............................................................................................................................................................................... 0.0% + FieldTypes/Terms .................................................................................................................................................................................. 0.0% + FieldTypes/Text ................................................................................................................................................................................... 0.0% + FieldTypes/Textarea ............................................................................................................................................................................... 0.0% + FieldTypes/Time ................................................................................................................................................................................... 0.0% + FieldTypes/Toggle ................................................................................................................................................................................. 0.0% + FieldTypes/UserGroups ........................................................................................................................................................................... 100.0% + FieldTypes/UserRoles ............................................................................................................................................................................ 100.0% + FieldTypes/Users .................................................................................................................................................................................. 0.0% + FieldTypes/Video .................................................................................................................................................................................. 0.0% + FieldTypes/Width .................................................................................................................................................................................. 0.0% + FieldTypes/Yaml ................................................................................................................................................................................... 0.0% + Fieldset ................................................................................................................................................................................ 78..92 / 80.6% + Helpers/FieldParser ............................................................................................................................................................................. 100.0% + Http/Controllers/AssetContainerBlueprintController ................................................................................................................................................ 0.0% + Http/Controllers/CollectionBlueprintsController ................................................................................................................................................... 0.0% + Http/Controllers/CollectionsController ............................................................................................................................................................ 0.0% + Http/Controllers/FieldsetController ............................................................................................................................................................... 0.0% + Http/Controllers/GlobalsBlueprintsController ...................................................................................................................................................... 0.0% + Http/Controllers/GlobalsController ................................................................................................................................................................ 0.0% + Http/Controllers/NavigationBlueprintController .................................................................................................................................................... 0.0% + Http/Controllers/NavigationController ............................................................................................................................................................. 0.0% + Http/Controllers/TaxonomiesController ............................................................................................................................................................. 0.0% + Http/Controllers/TaxonomyBlueprintsController ....................................................................................................................................... 17..22, 23 / 40.0% + Http/Controllers/UserBlueprintController .......................................................................................................................................................... 0.0% + Importer ........................................................................................................................................................................................ 100.0% + Repositories/AssetContainerRepository ........................................................................................................................................................... 100.0% + Repositories/BlueprintRepository .................................................................................................. 25..53, 63..69, 82, 86..87, 96..105, 130..153, 164..174, 134 / 29.9% + Repositories/CollectionRepository ........................................................................................................................................................... 46 / 94.7% + Repositories/EloquentGlobalRepository .................................................................................................................................. 30..65, 75..76, 83..100 / 16.2% + Repositories/EloquentNavigationRepository ....................................................................................................................................... 31..32, 40..76 / 20.7% + Repositories/FieldsetRepository ............................................................................................................................................. 17, 33, 37, 45, 74 / 85.3% + Repositories/GlobalRepository .................................................................................................................................................. 54, 63, 90..101 / 81.1% + Repositories/NavigationRepository ........................................................................................................................................... 43, 53..64, 81..87 / 62.5% + Repositories/TaxonomyRepository ......................................................................................................................................................... 40..46 / 78.9% + ServiceProvider ............................................................................................... 31, 35, 39, 43, 47, 51..52, 57, 61..63, 68..70, 75..77, 82..84, 116, 129, 53..85 / 76.1% + Sites/Sites ............................................................................................................................................................................. 18..27 / 27.3% + Stache/Stores/CollectionsStore .............................................................................................................................................................. 22 / 92.9% + Stache/Stores/GlobalsStore .................................................................................................................................................................. 22 / 92.9% + Stache/Stores/NavigationStore ............................................................................................................................................................... 22 / 92.9% + Stache/Stores/TaxonomiesStore ............................................................................................................................................................... 22 / 92.9% + ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Total: 36.1 % + diff --git a/resources/views/not-editable.blade.php b/resources/views/not-editable.blade.php index 28cde39..09d67cb 100644 --- a/resources/views/not-editable.blade.php +++ b/resources/views/not-editable.blade.php @@ -2,18 +2,28 @@ @section('title', __('Configure Collection')) @section('content') -
-
-

{{ $type }} {{ 'not editable' }}

-

{{ __("Because this $type is registered with the Statamic Builder it's only editable in PHP.") }}

+
+
+

{{ $type }} {{ 'not editable' }}

+

{{ __("Because this $type is registered with the Statamic Builder it's only editable in PHP.") }}

@if ($filePath) +
+
+ + + + {{ $filePath }} +
+
+ @if ($isLocal)
-
+ class="w-full lg:w-1/2 p-4 flex items-start hover:bg-gray-200 dark:hover:bg-dark-600 rounded-md group"> +
@@ -75,13 +85,13 @@ class="w-full lg:w-1/2 p-4 flex items-start hover:bg-gray-200 rounded-md group">
-

{{ __('Edit in Visual Studio Code') }}

-

{{ __('Open this Blueprint in Visual Studio Code to edit it.') }}

+

{{ __('Edit in Visual Studio Code') }}

+

{{ __('Open this Blueprint in Visual Studio Code to edit it.') }}

- diff --git a/src/BaseAssetContainer.php b/src/BaseAssetContainer.php new file mode 100644 index 0000000..eccfd2c --- /dev/null +++ b/src/BaseAssetContainer.php @@ -0,0 +1,59 @@ +title()->replace('_', ' '); + } + + public function disk(): string + { + return config('filesystems.default'); + } + + public function allowUploads(): bool + { + return true; + } + + public function allowMoving(): bool + { + return true; + } + + public function allowRenaming(): bool + { + return true; + } + + public function createFolders(): bool + { + return true; + } + + public function searchIndex(): ?string + { + return null; + } + + public function register() + { + $container = StatamicAssetContainer::make($this->handle()) + ->title($this->title()) + ->disk($this->disk()) + ->allowUploads($this->allowUploads()) + ->allowMoving($this->allowMoving()) + ->allowRenaming($this->allowRenaming()) + ->createFolders($this->createFolders()) + ->searchIndex($this->searchIndex()); + + return $container; + } +} diff --git a/src/BaseCollection.php b/src/BaseCollection.php index 1b841b4..e5a0ca7 100644 --- a/src/BaseCollection.php +++ b/src/BaseCollection.php @@ -3,56 +3,126 @@ namespace Tdwesten\StatamicBuilder; use Statamic\Facades\Collection as StatamicCollection; +use Statamic\Facades\Site; abstract class BaseCollection { - abstract public function title(): string; - abstract public static function handle(): string; - abstract public function route(): null|string|array; + public function title(): string + { + return (string) \Statamic\Support\Str::of(static::handle())->title()->replace('_', ' '); + } - abstract public function slugs(): bool; + public function route(): null|string|array + { + return null; + } - abstract public function titleFormat(): null|string|array; + public function slugs(): bool + { + return true; + } - abstract public function mount(): ?string; + public function titleFormat(): null|string|array + { + return null; + } - abstract public function date(): bool; + public function mount(): ?string + { + return null; + } - abstract public function sites(): array; + public function date(): bool + { + return false; + } + + public function sites(): array + { + return [Site::default()->handle()]; + } - abstract public function template(): ?string; + public function template(): ?string + { + return null; + } - abstract public function layout(): ?string; + public function layout(): ?string + { + return null; + } - abstract public function inject(): array; + public function inject(): array + { + return []; + } - abstract public function searchIndex(): string; + public function searchIndex(): ?string + { + return null; + } - abstract public function revisionsEnabled(): bool; + public function revisionsEnabled(): bool + { + return false; + } - abstract public function defaultPublishState(): bool; + public function defaultPublishState(): bool + { + return true; + } - abstract public function originBehavior(): string; + public function originBehavior(): string + { + return 'select'; + } - abstract public function structure(): ?array; + public function structure(): ?array + { + return null; + } - abstract public function sortBy(): ?string; + public function sortBy(): ?string + { + return null; + } - abstract public function sortDir(): ?string; + public function sortDir(): ?string + { + return null; + } - abstract public function taxonomies(): array; + public function taxonomies(): array + { + return []; + } - abstract public function propagate(): ?bool; + public function propagate(): ?bool + { + return null; + } - abstract public function previewTargets(): array; + public function previewTargets(): array + { + return []; + } - abstract public function autosave(): bool|int|null; + public function autosave(): bool|int|null + { + return null; + } - abstract public function futureDateBehavior(): ?string; + public function futureDateBehavior(): ?string + { + return null; + } - abstract public function pastDateBehavior(): ?string; + public function pastDateBehavior(): ?string + { + return null; + } public function visible(): bool { @@ -61,6 +131,7 @@ public function visible(): bool public function register() { + /** @var \Statamic\Entries\Collection */ $collection = StatamicCollection::make($this->handle()) ->title($this->title()) ->routes($this->route()) diff --git a/src/BaseGlobalSet.php b/src/BaseGlobalSet.php index c92aece..342b696 100644 --- a/src/BaseGlobalSet.php +++ b/src/BaseGlobalSet.php @@ -9,9 +9,15 @@ abstract class BaseGlobalSet { abstract public static function handle(): string; - abstract public function title(): string; + public function title(): string + { + return (string) \Statamic\Support\Str::of(static::handle())->title()->replace('_', ' '); + } - abstract public function sites(): array; + public function sites(): array + { + return [Site::default()->handle()]; + } public function register() { @@ -19,10 +25,12 @@ public function register() $global = StatamicGlobalSet::make($this->handle()) ->title($this->title()); - $sites = $this->sites() ?? [Site::default()->handle()]; + $sites = $this->sites() ?? [\Statamic\Facades\Site::default()->handle()]; foreach ($sites as $site) { - $global->addLocalization($global->makeLocalization($site)); + if (! $global->in($site)) { + $global->addLocalization($global->makeLocalization($site)); + } } return $global; diff --git a/src/BaseNavigation.php b/src/BaseNavigation.php new file mode 100644 index 0000000..281939f --- /dev/null +++ b/src/BaseNavigation.php @@ -0,0 +1,50 @@ +title()->replace('_', ' '); + } + + public function collections(): array + { + return []; + } + + public function sites(): array + { + return [Site::default()->handle()]; + } + + public function expectsRoot(): bool + { + return false; + } + + public function maxDepth(): ?int + { + return null; + } + + public function register() + { + /** @var \Statamic\Structures\Nav */ + $nav = StatamicNav::make($this->handle()) + ->title($this->title()) + ->collections($this->collections()) + ->maxDepth($this->maxDepth()) + ->expectsRoot($this->expectsRoot()); + + $nav->sites($this->sites()); + + return $nav; + } +} diff --git a/src/Blueprint.php b/src/Blueprint.php index 9e1171f..aa6d928 100644 --- a/src/Blueprint.php +++ b/src/Blueprint.php @@ -16,16 +16,25 @@ abstract class Blueprint implements BlueprintInterface protected $hidden = false; - public function __construct(string $handle) + public function __construct(?string $handle = null) { - $this->handle = $handle; + $this->handle = $handle ?? static::handle(); $this->tabs = collect($this->registerTabs()); } - public static function make(string $handle) + public static function handle(): string { - return new static($handle); + return ''; + } + public static function blueprintNamespace(): string + { + return ''; + } + + public static function make(?string $handle = null) + { + return new static($handle); } public function toArray() diff --git a/src/Console/GeneratorCommand.php b/src/Console/GeneratorCommand.php new file mode 100644 index 0000000..48b4729 --- /dev/null +++ b/src/Console/GeneratorCommand.php @@ -0,0 +1,55 @@ +info("Remember to register your new {$this->getShortType()} in config/statamic/builder.php"); + } + } + + /** + * Get the short type name for the registration message. + * + * @return string + */ + protected function getShortType() + { + return str_replace('Statamic Builder ', '', $this->type); + } + + /** + * {@inheritDoc} + */ + protected function getNameInput() + { + $input = trim($this->argument('name')); + + return Str::studly($input); + } + + /** + * {@inheritDoc} + */ + protected function buildClass($name) + { + $stub = parent::buildClass($name); + + $handle = Str::of($name)->afterLast('\\')->snake(); + + return str_replace(['{{ handle }}', '{{handle}}'], $handle, $stub); + } +} diff --git a/src/Console/MakeAssetContainerCommand.php b/src/Console/MakeAssetContainerCommand.php new file mode 100644 index 0000000..560e607 --- /dev/null +++ b/src/Console/MakeAssetContainerCommand.php @@ -0,0 +1,37 @@ +discoverCollections(); + $this->discoverBlueprints(); + $this->discoverFieldsets(); + $this->discoverTaxonomies(); + $this->discoverGlobals(); + $this->discoverNavigations(); + $this->discoverAssetContainers(); + $this->discoverSites(); + } + + protected function discoverCollections() + { + $path = config('statamic.builder.auto_discovery.collections'); + if (! $path || ! File::isDirectory($path)) { + return; + } + + $classes = $this->discoverClasses($path, BaseCollection::class); + + $collections = config('statamic.builder.collections', []); + foreach ($classes as $class) { + if (! in_array($class, $collections)) { + $collections[] = $class; + } + } + config(['statamic.builder.collections' => $collections]); + } + + protected function discoverBlueprints() + { + $path = config('statamic.builder.auto_discovery.blueprints'); + if (! $path || ! File::isDirectory($path)) { + return; + } + + $classes = $this->discoverClasses($path, Blueprint::class); + + $blueprints = config('statamic.builder.blueprints', []); + foreach ($classes as $class) { + $handle = $class::handle(); + $namespace = $class::blueprintNamespace(); + + if ($handle && $namespace) { + $blueprints[$namespace][$handle] = $class; + } + } + config(['statamic.builder.blueprints' => $blueprints]); + } + + protected function discoverFieldsets() + { + $path = config('statamic.builder.auto_discovery.fieldsets'); + if (! $path || ! File::isDirectory($path)) { + return; + } + + $classes = $this->discoverClasses($path, Fieldset::class); + + $fieldsets = config('statamic.builder.fieldsets', []); + foreach ($classes as $class) { + if (! in_array($class, $fieldsets)) { + $fieldsets[] = $class; + } + } + config(['statamic.builder.fieldsets' => $fieldsets]); + } + + protected function discoverTaxonomies() + { + $path = config('statamic.builder.auto_discovery.taxonomies'); + if (! $path || ! File::isDirectory($path)) { + return; + } + + $classes = $this->discoverClasses($path, BaseTaxonomy::class); + + $taxonomies = config('statamic.builder.taxonomies', []); + foreach ($classes as $class) { + if (! in_array($class, $taxonomies)) { + $taxonomies[] = $class; + } + } + config(['statamic.builder.taxonomies' => $taxonomies]); + } + + protected function discoverGlobals() + { + $path = config('statamic.builder.auto_discovery.globals'); + if (! $path || ! File::isDirectory($path)) { + return; + } + + $classes = $this->discoverClasses($path, BaseGlobalSet::class); + + $globals = config('statamic.builder.globals', []); + foreach ($classes as $class) { + if (! in_array($class, $globals)) { + $globals[] = $class; + } + } + config(['statamic.builder.globals' => $globals]); + } + + protected function discoverNavigations() + { + $path = config('statamic.builder.auto_discovery.navigations'); + if (! $path || ! File::isDirectory($path)) { + return; + } + + $classes = $this->discoverClasses($path, BaseNavigation::class); + + $navigations = config('statamic.builder.navigations', []); + foreach ($classes as $class) { + if (! in_array($class, $navigations)) { + $navigations[] = $class; + } + } + config(['statamic.builder.navigations' => $navigations]); + } + + protected function discoverAssetContainers() + { + $path = config('statamic.builder.auto_discovery.asset_containers'); + if (! $path || ! File::isDirectory($path)) { + return; + } + + $classes = $this->discoverClasses($path, BaseAssetContainer::class); + + $assetContainers = config('statamic.builder.asset_containers', []); + foreach ($classes as $class) { + if (! in_array($class, $assetContainers)) { + $assetContainers[] = $class; + } + } + config(['statamic.builder.asset_containers' => $assetContainers]); + } + + protected function discoverSites() + { + $path = config('statamic.builder.auto_discovery.sites'); + if (! $path || ! File::isDirectory($path)) { + return; + } + + $classes = $this->discoverClasses($path, BaseSite::class); + + $sites = config('statamic.builder.sites', []); + foreach ($classes as $class) { + if (! in_array($class, $sites)) { + $sites[] = $class; + } + } + config(['statamic.builder.sites' => $sites]); + } + + protected function discoverClasses($path, $baseClass) + { + $classes = []; + $finder = new Finder; + $finder->files()->in($path)->name('*.php'); + + foreach ($finder as $file) { + $class = $this->getClassFromFile($file->getRealPath()); + if ($class && class_exists($class)) { + $reflection = new ReflectionClass($class); + if ($reflection->isSubclassOf($baseClass) && ! $reflection->isAbstract()) { + $classes[] = $class; + } + } + } + + return $classes; + } + + protected function getClassFromFile($path) + { + $contents = file_get_contents($path); + $namespace = ''; + if (preg_match('/namespace\s+(.+?);/', $contents, $matches)) { + $namespace = $matches[1]; + } + + $class = ''; + if (preg_match('/class\s+(\w+)/', $contents, $matches)) { + $class = $matches[1]; + } + + return $namespace ? $namespace.'\\'.$class : $class; + } +} diff --git a/src/FieldTypes/Collor.php b/src/FieldTypes/Color.php similarity index 97% rename from src/FieldTypes/Collor.php rename to src/FieldTypes/Color.php index b08bd3d..3a99218 100644 --- a/src/FieldTypes/Collor.php +++ b/src/FieldTypes/Color.php @@ -6,7 +6,7 @@ use Tdwesten\StatamicBuilder\Contracts\DefaultValue; use Tdwesten\StatamicBuilder\Contracts\Makeble; -class Collor extends Field +class Color extends Field { use DefaultValue; use Makeble; diff --git a/src/FieldTypes/Taggeble.php b/src/FieldTypes/Hidden.php similarity index 60% rename from src/FieldTypes/Taggeble.php rename to src/FieldTypes/Hidden.php index 72bab48..2d2efbf 100644 --- a/src/FieldTypes/Taggeble.php +++ b/src/FieldTypes/Hidden.php @@ -3,13 +3,15 @@ namespace Tdwesten\StatamicBuilder\FieldTypes; use Illuminate\Support\Collection; +use Tdwesten\StatamicBuilder\Contracts\DefaultValue; use Tdwesten\StatamicBuilder\Contracts\Makeble; -class Taggeble extends Field +class Hidden extends Field { + use DefaultValue; use Makeble; - protected $type = '__type__'; + protected $type = 'hidden'; public function __construct(string $handle) { @@ -18,6 +20,8 @@ public function __construct(string $handle) public function fieldToArray(): Collection { - return collect([]); + return collect([ + 'default' => $this->default, + ]); } } diff --git a/src/FieldTypes/Money.php b/src/FieldTypes/Money.php new file mode 100644 index 0000000..f9ffbc2 --- /dev/null +++ b/src/FieldTypes/Money.php @@ -0,0 +1,37 @@ + $this->default, + 'currency' => $this->currency, + ]); + } + + public function currency(string $currency) + { + $this->currency = $currency; + + return $this; + } +} diff --git a/src/FieldTypes/Number.php b/src/FieldTypes/Number.php new file mode 100644 index 0000000..acda49b --- /dev/null +++ b/src/FieldTypes/Number.php @@ -0,0 +1,73 @@ + $this->default, + 'prepend' => $this->prepend, + 'append' => $this->append, + 'min' => $this->min, + 'max' => $this->max, + 'step' => $this->step, + 'placeholder' => $this->placeholder, + ]); + } + + public function min(int|float $min) + { + $this->min = $min; + + return $this; + } + + public function max(int|float $max) + { + $this->max = $max; + + return $this; + } + + public function step(int|float $step) + { + $this->step = $step; + + return $this; + } + + public function placeholder(string $placeholder) + { + $this->placeholder = $placeholder; + + return $this; + } +} diff --git a/src/FieldTypes/Password.php b/src/FieldTypes/Password.php new file mode 100644 index 0000000..c1cc5a5 --- /dev/null +++ b/src/FieldTypes/Password.php @@ -0,0 +1,34 @@ + $this->placeholder, + ]); + } + + public function placeholder(string $placeholder) + { + $this->placeholder = $placeholder; + + return $this; + } +} diff --git a/src/FieldTypes/Rating.php b/src/FieldTypes/Rating.php new file mode 100644 index 0000000..637e875 --- /dev/null +++ b/src/FieldTypes/Rating.php @@ -0,0 +1,77 @@ + $this->default, + 'min' => $this->min, + 'max' => $this->max, + 'step' => $this->step, + 'clearable' => $this->clearable, + 'color' => $this->color, + ]); + } + + public function min(int $min) + { + $this->min = $min; + + return $this; + } + + public function max(int $max) + { + $this->max = $max; + + return $this; + } + + public function step(int|float $step) + { + $this->step = $step; + + return $this; + } + + public function clearable(bool $clearable = true) + { + $this->clearable = $clearable; + + return $this; + } + + public function color(string $color) + { + $this->color = $color; + + return $this; + } +} diff --git a/src/FieldTypes/Time.php b/src/FieldTypes/Time.php index 7330c63..5bd0559 100644 --- a/src/FieldTypes/Time.php +++ b/src/FieldTypes/Time.php @@ -3,10 +3,12 @@ namespace Tdwesten\StatamicBuilder\FieldTypes; use Illuminate\Support\Collection; +use Tdwesten\StatamicBuilder\Contracts\DefaultValue; use Tdwesten\StatamicBuilder\Contracts\Makeble; class Time extends Field { + use DefaultValue; use Makeble; protected $type = 'time'; @@ -22,6 +24,7 @@ public function fieldToArray(): Collection { return collect([ 'seconds_enabled' => $this->secondsEnabled, + 'default' => $this->default, ]); } diff --git a/src/FieldTypes/Video.php b/src/FieldTypes/Video.php index f9aa7ed..7479028 100644 --- a/src/FieldTypes/Video.php +++ b/src/FieldTypes/Video.php @@ -3,10 +3,12 @@ namespace Tdwesten\StatamicBuilder\FieldTypes; use Illuminate\Support\Collection; +use Tdwesten\StatamicBuilder\Contracts\DefaultValue; use Tdwesten\StatamicBuilder\Contracts\Makeble; class Video extends Field { + use DefaultValue; use Makeble; protected $type = 'video'; @@ -22,6 +24,7 @@ public function fieldToArray(): Collection { return collect([ 'placeholder' => $this->placeholder, + 'default' => $this->default, ]); } diff --git a/src/Fieldset.php b/src/Fieldset.php index 2808a5f..5a9bc66 100644 --- a/src/Fieldset.php +++ b/src/Fieldset.php @@ -87,7 +87,7 @@ public function prefix($prefix) return $this; } - public function getPrefix(): string + public function getPrefix(): ?string { return $this->prefix; } diff --git a/src/Http/Controllers/AssetContainersController.php b/src/Http/Controllers/AssetContainersController.php new file mode 100644 index 0000000..1701528 --- /dev/null +++ b/src/Http/Controllers/AssetContainersController.php @@ -0,0 +1,30 @@ +getAssetContainerByHandle($container->handle()); + + if ($builderContainer) { + $reflectionClass = new \ReflectionClass($builderContainer); + $filePath = $reflectionClass->getFileName(); + + return view('statamic-builder::not-editable', [ + 'type' => 'Asset Container', + 'isLocal' => boolval(config('app.env') === 'local'), + 'filePath' => $filePath, + ]); + } + + return parent::edit($container); + } +} diff --git a/src/Http/Controllers/NavigationBlueprintController.php b/src/Http/Controllers/NavigationBlueprintController.php index 2ab7466..ebb009a 100644 --- a/src/Http/Controllers/NavigationBlueprintController.php +++ b/src/Http/Controllers/NavigationBlueprintController.php @@ -9,10 +9,10 @@ class NavigationBlueprintController extends StatamicNavigationBlueprintControlle { public function edit($navHandle) { - $navBlueprint = BlueprintRepository::findBlueprint('navigations', $navHandle); + $navBlueprint = BlueprintRepository::findBlueprint('navigation', $navHandle); if ($navBlueprint) { - $blueprintPath = BlueprintRepository::findBlueprintPath('navigations', $navHandle); + $blueprintPath = BlueprintRepository::findBlueprintPath('navigation', $navHandle); return view('statamic-builder::not-editable', [ 'type' => 'Blueprint', diff --git a/src/Http/Controllers/NavigationController.php b/src/Http/Controllers/NavigationController.php index a864c00..0590a1c 100644 --- a/src/Http/Controllers/NavigationController.php +++ b/src/Http/Controllers/NavigationController.php @@ -4,14 +4,31 @@ use Statamic\Http\Controllers\CP\Navigation\NavigationController as StatamicNavigationController; use Tdwesten\StatamicBuilder\Repositories\BlueprintRepository; +use Tdwesten\StatamicBuilder\Repositories\NavigationRepository; class NavigationController extends StatamicNavigationController { public function edit($navHandle) { - $navBlueprint = BlueprintRepository::findBlueprintInNamespace('navigation', $navHandle); + /** @var NavigationRepository */ + $navigationRepository = app(\Statamic\Contracts\Structures\NavigationRepository::class); - if ($navBlueprint->isNotEmpty()) { + $navigation = $navigationRepository->getNavigationByHandle($navHandle); + + if ($navigation) { + $reflectionClass = new \ReflectionClass($navigation); + $filePath = $reflectionClass->getFileName(); + + return view('statamic-builder::not-editable', [ + 'type' => 'Navigation', + 'isLocal' => boolval(config('app.env') === 'local'), + 'filePath' => $filePath, + ]); + } + + $navBlueprint = BlueprintRepository::findBlueprint('navigation', $navHandle); + + if ($navBlueprint) { $blueprintPath = BlueprintRepository::findBlueprintPath('navigation', $navHandle); return view('statamic-builder::not-editable', [ diff --git a/src/Repositories/AssetContainerRepository.php b/src/Repositories/AssetContainerRepository.php index e316559..c11a2a2 100644 --- a/src/Repositories/AssetContainerRepository.php +++ b/src/Repositories/AssetContainerRepository.php @@ -3,18 +3,65 @@ namespace Tdwesten\StatamicBuilder\Repositories; use Illuminate\Support\Collection; +use Statamic\Assets\AssetContainer as StatamicAssetContainer; use Statamic\Stache\Repositories\AssetContainerRepository as StatamicAssetContainerRepository; +use Statamic\Stache\Stache; class AssetContainerRepository extends StatamicAssetContainerRepository { - public function all(): Collection + /** + * @var Collection + */ + private $assetContainers; + + public function __construct(Stache $stache) + { + parent::__construct($stache); + + $this->initializeAssetContainers(); + } + + private function initializeAssetContainers() { - $builderKeys = BlueprintRepository::findBlueprintInNamespace('assets')->map(function ($blueprint) { - return $blueprint->getHandle(); + $containers = collect(config('statamic.builder.asset_containers', [])); + + $this->assetContainers = collect(); + + $containers->each(function (string $container): void { + if (class_exists($container, true)) { + $this->assetContainers->put($container::handle(), $container); + } }); + } + + public function findByHandle($handle): ?StatamicAssetContainer + { + $container = $this->assetContainers->get($handle); + + if ($container) { + return (new $container)->register(); + } + + return parent::findByHandle($handle); + } + + public function all(): Collection + { + $keys = $this->store->paths()->keys()->merge($this->assetContainers->keys())->unique(); + + return $keys->map(function ($key) { + return $this->findByHandle($key); + })->filter(); + } + + public function getAssetContainerByHandle($handle): ?\Tdwesten\StatamicBuilder\BaseAssetContainer + { + $container = $this->assetContainers->get($handle, null); - $keys = $this->store->paths()->keys()->merge($builderKeys)->unique(); + if ($container) { + return new $container; + } - return $this->store->getItems($keys); + return null; } } diff --git a/src/Repositories/EloquentGlobalRepository.php b/src/Repositories/EloquentGlobalRepository.php new file mode 100644 index 0000000..f9593e4 --- /dev/null +++ b/src/Repositories/EloquentGlobalRepository.php @@ -0,0 +1,136 @@ +initializeGlobals(); + } + + public function all(): GlobalCollection + { + // Get database globals + $databaseGlobals = parent::all(); + + // Get builder-registered global instances from classes, keyed by handle + $builderGlobals = $this->globals->map(function ($globalClass, $handle) { + return (new $globalClass)->register(); + }); + + $blueprints = BlueprintRepository::findBlueprintInNamespace('globals'); + + $builderKeys = $blueprints + ->reject(function ($value, $handle) use ($databaseGlobals, $builderGlobals) { + return $databaseGlobals->has($handle) || $builderGlobals->has($handle); + }) + ->map(function ($value) { + $contents = $value->toArray(); + $global = StatamicGlobalSet::make($value->getHandle()) + ->title($contents['title'] ?? null); + + foreach (\Statamic\Facades\Site::all() as $site) { + if (! $global->in($site->handle())) { + $global->addLocalization($global->makeLocalization($site->handle())); + } + } + + return $global; + }); + + // Combine both collections - builder globals take precedence + return $databaseGlobals->merge($builderKeys)->merge($builderGlobals); + } + + public function find($id): ?GlobalSet + { + if ($global = $this->globals->get($id)) { + return (new $global)->register(); + } + + $global = parent::find($id); + + if ($global) { + return $global; + } + + return $this->findInBlueprints($id); + } + + private function findInBlueprints($handle): ?GlobalSet + { + $blueprint = BlueprintRepository::findBlueprintInNamespace('globals')->get($handle); + + if (! $blueprint) { + return null; + } + + $global = \Statamic\Facades\GlobalSet::make($handle) + ->title($blueprint->toArray()['title'] ?? null); + + foreach (\Statamic\Facades\Site::all() as $site) { + if (! $global->in($site->handle())) { + $global->addLocalization($global->makeLocalization($site->handle())); + } + } + + return $global; + } + + private function initializeGlobals() + { + $globals = collect(config('statamic.builder.globals', [])); + + $this->globals = collect(); + + $globals->each(function (string $set): void { + if (class_exists($set, true)) { + $this->globals->put($set::handle(), $set); + } + }); + } + + public function findByHandle($handle): ?GlobalSet + { + $set = $this->globals->get($handle); + + if ($set) { + return (new $set)->register(); + } + + $global = parent::findByHandle($handle); + + if ($global) { + return $global; + } + + return $this->findInBlueprints($handle); + } + + public function getGlobalByHandle($handle): ?BaseGlobalSet + { + $global = $this->globals->get($handle, null); + + if ($global) { + return new $global; + } + + return null; + } +} diff --git a/src/Repositories/EloquentNavigationRepository.php b/src/Repositories/EloquentNavigationRepository.php index bad9c13..b9097ff 100644 --- a/src/Repositories/EloquentNavigationRepository.php +++ b/src/Repositories/EloquentNavigationRepository.php @@ -5,19 +5,63 @@ use Illuminate\Support\Collection; use Statamic\Contracts\Structures\Nav as NavContract; use Statamic\Eloquent\Structures\NavigationRepository as StatamicNavigationRepository; -use Statamic\Eloquent\Structures\NavModel; +use Statamic\Stache\Stache; class EloquentNavigationRepository extends StatamicNavigationRepository { + /** + * @var Collection + */ + private $navigations; + + public function __construct(Stache $stache) + { + parent::__construct($stache); + + $this->initializeNavigations(); + } + + private function initializeNavigations() + { + $navigations = collect(config('statamic.builder.navigations', [])); + + $this->navigations = collect(); + + $navigations->each(function (string $navigation): void { + if (class_exists($navigation, true)) { + $this->navigations->put($navigation::handle(), $navigation); + } + }); + } + public function all(): Collection { - $builderKeys = BlueprintRepository::findBlueprintInNamespace('navigation')->transform(function ($value, $key) { - $model = new NavModel($value->toArray()); + // Get database navigations + $databaseNavigations = parent::all(); - return app(NavContract::class)->fromModel($model); + // Get builder-registered navigation instances from classes, keyed by handle + $customNavigations = $this->navigations->map(function ($navigation) { + return (new $navigation)->register(); }); - return $builderKeys; + $blueprints = BlueprintRepository::findBlueprintInNamespace('navigation'); + + $builderKeys = $blueprints + ->reject(function ($value, $handle) use ($databaseNavigations, $customNavigations) { + return $databaseNavigations->has($handle) || $customNavigations->has($handle); + }) + ->map(function ($value) { + $contents = $value->toArray(); + $nav = \Statamic\Facades\Nav::make($value->getHandle()) + ->title($contents['title'] ?? null); + + $nav->sites(\Statamic\Facades\Site::all()->map->handle()->all()); + + return $nav; + }); + + // Combine all - builder classes take highest precedence + return $databaseNavigations->merge($builderKeys)->merge($customNavigations); } public function find($id): ?NavContract @@ -31,4 +75,15 @@ public function findByHandle($handle): ?NavContract { return $this->find($handle); } + + public function getNavigationByHandle($handle): ?\Tdwesten\StatamicBuilder\BaseNavigation + { + $navigation = $this->navigations->get($handle, null); + + if ($navigation) { + return new $navigation; + } + + return null; + } } diff --git a/src/Repositories/GlobalRepository.php b/src/Repositories/GlobalRepository.php index 46facad..5d0574c 100644 --- a/src/Repositories/GlobalRepository.php +++ b/src/Repositories/GlobalRepository.php @@ -25,12 +25,38 @@ public function __construct(Stache $stache) public function all(): GlobalCollection { - $keys = $this->store->paths()->keys(); + $blueprints = BlueprintRepository::findBlueprintInNamespace('globals'); - // add custom globals - $keys = $this->globals->keys()->merge($keys); + $builderKeys = $blueprints->map(function ($blueprint) { + return $blueprint->getHandle(); + }); + + $keys = $this->store->paths()->keys()->merge($builderKeys)->merge($this->globals->keys())->unique(); + + $items = $this->store->getItems($keys, $this->globals)->map(function ($item, $key) use ($blueprints) { + if ($item) { + return $item; + } + + $blueprint = $blueprints->get($key); + + if ($blueprint) { + $global = \Statamic\Facades\GlobalSet::make($key) + ->title($blueprint->toArray()['title'] ?? null); + + foreach (\Statamic\Facades\Site::all() as $site) { + if (! $global->in($site->handle())) { + $global->addLocalization($global->makeLocalization($site->handle())); + } + } - return GlobalCollection::make($this->store->getItems($keys, $this->globals)); + return $global; + } + + return null; + })->filter(); + + return GlobalCollection::make($items); } public function find($id): ?GlobalSet @@ -39,7 +65,33 @@ public function find($id): ?GlobalSet return (new $global)->register(); } - return parent::find($id); + $global = parent::find($id); + + if ($global) { + return $global; + } + + return $this->findInBlueprints($id); + } + + private function findInBlueprints($handle): ?GlobalSet + { + $blueprint = BlueprintRepository::findBlueprintInNamespace('globals')->get($handle); + + if (! $blueprint) { + return null; + } + + $global = \Statamic\Facades\GlobalSet::make($handle) + ->title($blueprint->toArray()['title'] ?? null); + + foreach (\Statamic\Facades\Site::all() as $site) { + if (! $global->in($site->handle())) { + $global->addLocalization($global->makeLocalization($site->handle())); + } + } + + return $global; } private function initializeGlobals() @@ -63,7 +115,13 @@ public function findByHandle($handle): ?GlobalSet return (new $set)->register(); } - return parent::findByHandle($handle); + $global = parent::findByHandle($handle); + + if ($global) { + return $global; + } + + return $this->findInBlueprints($handle); } public function getGlobalByHandle($handle): ?BaseGlobalSet diff --git a/src/Repositories/NavigationRepository.php b/src/Repositories/NavigationRepository.php index 46b6d35..a1085ed 100644 --- a/src/Repositories/NavigationRepository.php +++ b/src/Repositories/NavigationRepository.php @@ -3,18 +3,126 @@ namespace Tdwesten\StatamicBuilder\Repositories; use Illuminate\Support\Collection; +use Statamic\Contracts\Structures\Nav; use Statamic\Stache\Repositories\NavigationRepository as StatamicNavigationRepository; +use Statamic\Stache\Stache; +use Tdwesten\StatamicBuilder\BaseNavigation; class NavigationRepository extends StatamicNavigationRepository { + /** + * @var Collection + */ + private $navigations; + + public function __construct(Stache $stache) + { + parent::__construct($stache); + + $this->initializeNavigations(); + } + + private function initializeNavigations() + { + $navigations = collect(config('statamic.builder.navigations', [])); + + $this->navigations = collect(); + + $navigations->each(function (string $navigation): void { + if (class_exists($navigation, true)) { + $this->navigations->put($navigation::handle(), $navigation); + } + }); + } + public function all(): Collection { - $builderKeys = BlueprintRepository::findBlueprintInNamespace('navigation')->map(function ($blueprint) { + $blueprints = BlueprintRepository::findBlueprintInNamespace('navigation'); + + $builderKeys = $blueprints->map(function ($blueprint) { return $blueprint->getHandle(); }); - $keys = $this->store->paths()->keys()->merge($builderKeys)->unique(); + $keys = $this->store->paths()->keys()->merge($builderKeys)->merge($this->navigations->keys())->unique(); + + return $this->store->getItems($keys, $this->navigations)->map(function ($item, $key) use ($blueprints) { + if ($item) { + return $item; + } + + $blueprint = $blueprints->get($key); + + if ($blueprint) { + $nav = \Statamic\Facades\Nav::make($key) + ->title($blueprint->toArray()['title'] ?? null); + + $nav->sites(\Statamic\Facades\Site::all()->map->handle()->all()); + + return $nav; + } + + return null; + })->filter(); + } + + public function find($id): ?Nav + { + $navigation = $this->navigations->get($id); + + if ($navigation) { + return (new $navigation)->register(); + } + + $nav = parent::find($id); + + if ($nav) { + return $nav; + } + + return $this->findInBlueprints($id); + } + + public function findByHandle($handle): ?Nav + { + $navigation = $this->navigations->get($handle); + + if ($navigation) { + return (new $navigation)->register(); + } + + $nav = parent::findByHandle($handle); + + if ($nav) { + return $nav; + } + + return $this->findInBlueprints($handle); + } + + private function findInBlueprints($handle): ?Nav + { + $blueprint = BlueprintRepository::findBlueprintInNamespace('navigation')->get($handle); + + if (! $blueprint) { + return null; + } + + $nav = \Statamic\Facades\Nav::make($handle) + ->title($blueprint->toArray()['title'] ?? null); + + $nav->sites(\Statamic\Facades\Site::all()->map->handle()->all()); + + return $nav; + } + + public function getNavigationByHandle($handle): ?BaseNavigation + { + $navigation = $this->navigations->get($handle, null); + + if ($navigation) { + return new $navigation; + } - return $this->store->getItems($keys); + return null; } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 561eced..30ccfa1 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -9,7 +9,9 @@ class ServiceProvider extends AddonServiceProvider { public function register() { - $this->mergeConfigFrom(__DIR__.'/../config/builder.php', 'builder'); + $this->mergeConfigFrom(__DIR__.'/../config/builder.php', 'statamic.builder'); + + (new Discovery)->discover(); $this->bindRepositories(); @@ -53,6 +55,12 @@ protected function registerControllers() ); }); + $this->app->bind(\Statamic\Http\Controllers\CP\Assets\AssetContainersController::class, function () { + return new \Tdwesten\StatamicBuilder\Http\Controllers\AssetContainersController( + app('request') + ); + }); + $this->app->bind(\Statamic\Http\Controllers\CP\Users\UserBlueprintController::class, function () { return new \Tdwesten\StatamicBuilder\Http\Controllers\UserBlueprintController; }); @@ -99,15 +107,23 @@ protected function bindStores() $this->app->singleton(\Statamic\Stache\Stores\GlobalsStore::class, function () { return new \Tdwesten\StatamicBuilder\Stache\Stores\GlobalsStore(app('stache')); }); + + $this->app->singleton(\Statamic\Stache\Stores\NavigationStore::class, function () { + return new \Tdwesten\StatamicBuilder\Stache\Stores\NavigationStore(app('stache')); + }); } protected function bindRepositories() { - $this->app->singleton(\Statamic\Stache\Repositories\AssetContainerRepository::class, function () { + $this->app->singleton(\Statamic\Contracts\Assets\AssetContainerRepository::class, function () { return new \Tdwesten\StatamicBuilder\Repositories\AssetContainerRepository(app('stache')); }); - $this->app->singleton(\Statamic\Stache\Repositories\GlobalRepository::class, function () { + $this->app->singleton(\Statamic\Contracts\Globals\GlobalRepository::class, function () { + if (config('statamic.eloquent-driver.globals.driver') === 'eloquent') { + return new \Tdwesten\StatamicBuilder\Repositories\EloquentGlobalRepository(app('stache')); + } + return new \Tdwesten\StatamicBuilder\Repositories\GlobalRepository(app('stache')); }); @@ -116,7 +132,7 @@ protected function bindRepositories() ->setDirectory(resource_path('fieldsets')); }); - $this->app->singleton(\Statamic\Stache\Repositories\NavigationRepository::class, function () { + $this->app->singleton(\Statamic\Contracts\Structures\NavigationRepository::class, function () { if (config('statamic.eloquent-driver.navigations.driver') === 'eloquent') { return new \Tdwesten\StatamicBuilder\Repositories\EloquentNavigationRepository(app('stache')); } @@ -161,6 +177,7 @@ public function boot() Console\Export::class, Console\MakeNavigationCommand::class, Console\MakeSiteCommand::class, + Console\MakeAssetContainerCommand::class, ]); } diff --git a/src/Stache/Stores/CollectionsStore.php b/src/Stache/Stores/CollectionsStore.php index d1f7902..67e50dc 100644 --- a/src/Stache/Stores/CollectionsStore.php +++ b/src/Stache/Stores/CollectionsStore.php @@ -36,8 +36,8 @@ public function getItems($keys, $collections = null) { $this->handleFileChanges(); - return collect($keys)->map(function ($key) use ($collections) { - return $this->getItem($key, $collections); + return collect($keys)->mapWithKeys(function ($key) use ($collections) { + return [$key => $this->getItem($key, $collections)]; }); } } diff --git a/src/Stache/Stores/GlobalsStore.php b/src/Stache/Stores/GlobalsStore.php index 74b66ea..4f960e7 100644 --- a/src/Stache/Stores/GlobalsStore.php +++ b/src/Stache/Stores/GlobalsStore.php @@ -5,7 +5,6 @@ use Illuminate\Support\Collection; use Statamic\Stache\Stores\GlobalsStore as StatamicGlobalsStore; use Tdwesten\StatamicBuilder\BaseGlobalSet; -use Tdwesten\StatamicBuilder\BaseTaxonomy; class GlobalsStore extends StatamicGlobalsStore { @@ -15,12 +14,12 @@ public function getItem($key, $globals = null) $globals = $globals ?? collect([]); if ($globals->has($key)) { - /** @var BaseTaxonomy */ + /** @var BaseGlobalSet */ $globalSet = $globals->get($key); - $globalSet = new $globalSet($key); + $globalSet = new $globalSet; if (! $globalSet instanceof BaseGlobalSet) { - throw new \Exception("Collection [{$key}] must extend [Tdwesten\StatamicBuilder\BaseGlobalSet]"); + throw new \Exception("Global Set [{$key}] must extend [Tdwesten\StatamicBuilder\BaseGlobalSet]"); } $item = $globalSet->register(); @@ -37,8 +36,8 @@ public function getItems($keys, $globals = null) { $this->handleFileChanges(); - return collect($keys)->map(function ($key) use ($globals) { - return $this->getItem($key, $globals); + return collect($keys)->mapWithKeys(function ($key) use ($globals) { + return [$key => $this->getItem($key, $globals)]; }); } } diff --git a/src/Stache/Stores/NavigationStore.php b/src/Stache/Stores/NavigationStore.php new file mode 100644 index 0000000..5cec96e --- /dev/null +++ b/src/Stache/Stores/NavigationStore.php @@ -0,0 +1,43 @@ +has($key)) { + /** @var BaseNavigation */ + $navigation = $navigations->get($key); + $navigation = new $navigation; + + if (! $navigation instanceof BaseNavigation) { + throw new \Exception("Navigation [{$key}] must extend [Tdwesten\StatamicBuilder\BaseNavigation]"); + } + + $item = $navigation->register(); + + $this->cacheItem($item); + + return $item; + } + + return parent::getItem($key); + } + + public function getItems($keys, $navigations = null) + { + $this->handleFileChanges(); + + return collect($keys)->mapWithKeys(function ($key) use ($navigations) { + return [$key => $this->getItem($key, $navigations)]; + }); + } +} diff --git a/src/Stache/Stores/TaxonomiesStore.php b/src/Stache/Stores/TaxonomiesStore.php index 5c58aad..fe7d45d 100644 --- a/src/Stache/Stores/TaxonomiesStore.php +++ b/src/Stache/Stores/TaxonomiesStore.php @@ -36,8 +36,8 @@ public function getItems($keys, $taxonomies = null) { $this->handleFileChanges(); - return collect($keys)->map(function ($key) use ($taxonomies) { - return $this->getItem($key, $taxonomies); + return collect($keys)->mapWithKeys(function ($key) use ($taxonomies) { + return [$key => $this->getItem($key, $taxonomies)]; }); } } diff --git a/stubs/AssetContainer.stub b/stubs/AssetContainer.stub new file mode 100644 index 0000000..9306fa4 --- /dev/null +++ b/stubs/AssetContainer.stub @@ -0,0 +1,72 @@ + '/blog/{slug}', 'nl' => '/blog/{slug}']; - * - * Docs: https://statamic.dev/collections#routing - */ public function route(): null|string|array { - return null; - } - - /** - * Generate a slug for the collection? - * - * Docs: https://statamic.dev/collections#slugs - */ - public function slugs(): bool - { - return true; - } - - /** - * Return the title format for the collection - * - * Docs: https://statamic.dev/collections#titles - */ - public function titleFormat(): null|string|array - { - return null; - } - - /** - * Return the mount for the collection - * - * Docs: https://statamic.dev/collections#mounting - */ - public function mount(): ?string - { - return null; + return '/{{ handle }}/{slug}'; } /** * Use dates in the collection? - * - * Docs: ?? */ public function date(): bool { @@ -99,87 +30,13 @@ class {{ class }} extends BaseCollection } /** - * Return the sites for the collection - * - * Example: return [Site::default()->handle(), 'nl']; - * - * Docs: https://statamic.dev/collections#sites - */ - public function sites(): array - { - return [Site::default()->handle()]; - } - - /** - * Return the default publish state for the collection - * - * Example: return 'blog/show'; - */ - public function template(): ?string - { - return null; - } - - /** - * Return the default layout for the collection - * - * Example: return 'blog/layout'; - */ - public function layout(): ?string - { - return null; - } - - /** - * Return the default inject for the collection - * - * @docs https://statamic.dev/collections#inject - */ - public function inject(): array - { - return []; - } - - /** - * Return the default search index for the collection - * - * @docs https://statamic.dev/search - */ - public function searchIndex(): string - { - return 'default'; - } - - /** - * Enable revisions for the collection? - * - * @docs https://statamic.dev/revisions - */ - public function revisionsEnabled(): bool - { - return false; - } - - /** - * Return the default publish state for the collection - * - * While creating new entries in this collection the published toggle will default to true instead of false (draft). - * - * Example: return true; + * Generate a slug for the collection? */ - public function defaultPublishState(): bool + public function slugs(): bool { return true; } - /** - * Origin behavior for the collection - */ - public function originBehavior(): string - { - return 'select'; - } - /** * Return the structure for the collection * @@ -188,112 +45,9 @@ class {{ class }} extends BaseCollection * 'slugs' => true, * 'max_depth' => 3, * ]; - * - * @docs https://statamic.dev/structures */ public function structure(): ?array { return null; } - - /** - * Return the sort by for the collection - * - * @example return 'title'; - * - * @docs https://statamic.dev/collections#default-sort-order-in-listings - */ - public function sortBy(): ?string - { - return null; - } - - /** - * Return the sort direction for the collection - * - * @example return 'asc' / 'desc'; - * - * @docs https://statamic.dev/collections#default-sort-order-in-listings - */ - public function sortDir(): ?string - { - return null; - } - - /** - * Return the taxonomies for the collection - * - * @example return ['tags', 'categories']; - * - * @docs https://statamic.dev/collections#taxonomies - */ - public function taxonomies(): array - { - return []; - } - - /** - * Propagate the collection? - * - * @docs https://statamic.dev/tips/localizing-entries#propagation - */ - public function propagate(): ?bool - { - return null; - } - - /** - * Return the preview targets for the collection - * - * @example return [ - * [ - * 'label' => 'Entry', - * 'format' => '/blog/{slug}', - * 'refresh' => true, - * ], - * ]; - * - * @docs https://statamic.dev/live-preview#preview-targets - */ - public function previewTargets(): array - { - return []; - } - - /** - * Return the autosave interval for the collection - * - * @example return true; - * @example return 5000; // 5 seconds - */ - public function autosave(): bool|int|null - { - return null; - } - - /** - * Return the future date behavior for the collection - * - * Possible values are 'public', 'unlisted', 'private' - * - * @example return 'public'; - * - * @docs https://statamic.dev/collections#dates - */ - public function futureDateBehavior(): ?string - { - return null; - } - - /** - * Return the past date behavior for the collection - * - * Possible values are 'public', 'unlisted', 'private' - * - * @example return 'public'; - */ - public function pastDateBehavior(): ?string - { - return null; - } } diff --git a/stubs/Global-Set.stub b/stubs/Global-Set.stub index cb0cae7..97d7738 100644 --- a/stubs/Global-Set.stub +++ b/stubs/Global-Set.stub @@ -2,43 +2,22 @@ namespace {{ namespace }}; -use Statamic\Facades\Site; use Tdwesten\StatamicBuilder\BaseGlobalSet; class {{ class }} extends BaseGlobalSet { - /** - * The handle for this global set - * - * Example: return 'footer'; - */ public static function handle(): string { - // TODO: Change to the correct global set handle - - return 'footer'; + return '{{ handle }}'; } - /** - * The title for this global set - * - * Example: return 'Footer'; - */ public function title(): string { - // TODO: Change to the correct title - - return 'Footer'; + return '{{ class }}'; } - /** - * The sites for this global set - * - * Example: return ['default'] or ['default', 'en', 'de']; - */ public function sites(): array { return ['default']; } - } diff --git a/stubs/blueprint.stub b/stubs/blueprint.stub index 39d8633..239de31 100644 --- a/stubs/blueprint.stub +++ b/stubs/blueprint.stub @@ -30,6 +30,16 @@ class {{ class }} extends Blueprint */ public $hidden = false; + public static function handle(): string + { + return '{{ handle }}'; + } + + public static function blueprintNamespace(): string + { + return ''; + } + public function registerTabs(): Array { return [ diff --git a/stubs/navigation.stub b/stubs/navigation.stub index 09c54d6..03ce9ba 100644 --- a/stubs/navigation.stub +++ b/stubs/navigation.stub @@ -2,56 +2,32 @@ namespace {{ namespace }}; -use Tdwesten\StatamicBuilder\Blueprint; -use Tdwesten\StatamicBuilder\FieldTypes\Section; -use Tdwesten\StatamicBuilder\FieldTypes\Text; -use Tdwesten\StatamicBuilder\FieldTypes\Tab; +use Tdwesten\StatamicBuilder\BaseNavigation; -class {{ class }} extends Blueprint +class {{ class }} extends BaseNavigation { - /** - * The blueprint title. - * - * @var string - */ - public $title = ''; - - /** - * The blueprint handle. - * - * @var string - */ - public $handle = ''; + public static function handle(): string + { + return '{{ handle }}'; + } - /** - * Hide the blueprint from the blueprint dropdown. - * - * @var bool - */ - public $hidden = false; + public function title(): string + { + return '{{ class }}'; + } - public function registerTabs(): Array + public function collections(): array { - return [ - Tab::make('General', [ - Section::make('General', [ - Text::make('title') - ->displayName('Title') - ->required() - ]), - ]), - ]; + return []; } - public function toArray() + public function expectsRoot(): bool { - $blueprint = [ - 'title' => $this->title, - 'hide' => $this->hidden, - 'tabs' => $this->tabsToArray(), - 'handle' => $this->handle - ]; + return false; + } - return $blueprint; + public function maxDepth(): ?int + { + return null; } } diff --git a/test-compatibility.php b/test-compatibility.php deleted file mode 100755 index f728af8..0000000 --- a/test-compatibility.php +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env php -getMessage()}\n"; - $testsFailed++; - } catch (Exception $e) { - echo "✗ ERROR: {$description}\n"; - echo " Error: {$e->getMessage()}\n"; - $testsFailed++; - } -} - -// Test 1: v5.67.0 style with directory() method -test('v5.67.0 with directory() method', function () { - $repo = new class extends \Tdwesten\StatamicBuilder\Repositories\BlueprintRepository - { - protected $directories = ['default' => 'resources/blueprints']; - - public function directory() - { - return $this->directories['default']; - } - - public function testGetDirectory() - { - return $this->getDirectory(); - } - }; - - assert($repo->testGetDirectory() === 'resources/blueprints', 'Should use directory() method'); -}); - -// Test 2: v5.67.0 style without directory() method -test('v5.67.0 accessing directories[default] directly', function () { - $repo = new class extends \Tdwesten\StatamicBuilder\Repositories\BlueprintRepository - { - protected $directories = ['default' => 'custom/blueprints']; - - public function testGetDirectory() - { - return $this->getDirectory(); - } - }; - - assert($repo->testGetDirectory() === 'custom/blueprints', 'Should access directories[default]'); -}); - -// Test 3: Old style (pre v5.67.0) -test('Pre v5.67.0 with directory property', function () { - $repo = new class extends \Tdwesten\StatamicBuilder\Repositories\BlueprintRepository - { - protected $directory = 'old/blueprints'; - - public function testGetDirectory() - { - return $this->getDirectory(); - } - }; - - assert($repo->testGetDirectory() === 'old/blueprints', 'Should use old directory property'); -}); - -// Test 4: Simple array style (alternative format) -test('Simple array format (first element)', function () { - $repo = new class extends \Tdwesten\StatamicBuilder\Repositories\BlueprintRepository - { - protected $directories = ['first/path', 'second/path']; - - public function testGetDirectory() - { - return $this->getDirectory(); - } - }; - - assert($repo->testGetDirectory() === 'first/path', 'Should use first element'); -}); - -// Test 5: Fallback when nothing is set -test('Fallback to default when no properties set', function () { - $repo = new class extends \Tdwesten\StatamicBuilder\Repositories\BlueprintRepository - { - public function testGetDirectory() - { - return $this->getDirectory(); - } - }; - - assert($repo->testGetDirectory() === 'resources/blueprints', 'Should use default path'); -}); - -// Test 6: Empty directories array falls back to directory -test('Empty directories array falls back to directory', function () { - $repo = new class extends \Tdwesten\StatamicBuilder\Repositories\BlueprintRepository - { - protected $directory = 'fallback/path'; - - protected $directories = []; - - public function testGetDirectory() - { - return $this->getDirectory(); - } - }; - - assert($repo->testGetDirectory() === 'fallback/path', 'Should fallback to directory when directories is empty'); -}); - -// Test 7: Multiple directories in associative array -test('Multiple directories in associative array', function () { - $repo = new class extends \Tdwesten\StatamicBuilder\Repositories\BlueprintRepository - { - protected $directories = [ - 'default' => 'resources/blueprints', - 'vendor' => 'vendor/blueprints', - 'custom' => 'custom/blueprints', - ]; - - public function testGetDirectory() - { - return $this->getDirectory(); - } - }; - - assert($repo->testGetDirectory() === 'resources/blueprints', 'Should use default key from associative array'); -}); - -// Test 8: Verify no syntax errors in actual file -test('No PHP syntax errors in BlueprintRepository', function () { - $output = []; - $returnCode = 0; - exec('php -l '.__DIR__.'/src/Repositories/BlueprintRepository.php', $output, $returnCode); - - assert($returnCode === 0, 'BlueprintRepository should have no syntax errors'); -}); - -echo "\n"; -echo "=================================================\n"; -echo "Test Results:\n"; -echo " Passed: {$testsPassed}\n"; -echo " Failed: {$testsFailed}\n"; -echo "=================================================\n"; - -if ($testsFailed > 0) { - exit(1); -} - -echo "\n✅ All tests passed! The fix is compatible with both Statamic v5.56.0 and v5.67.0+\n\n"; -exit(0); diff --git a/tests/Feature/AssetContainerRegistrationTest.php b/tests/Feature/AssetContainerRegistrationTest.php new file mode 100644 index 0000000..687157f --- /dev/null +++ b/tests/Feature/AssetContainerRegistrationTest.php @@ -0,0 +1,33 @@ + [ + IntegrationTestAssetContainer::class, + ]]); + + $container = AssetContainer::find('integration_assets'); + + expect($container)->not->toBeNull(); + expect($container->handle())->toBe('integration_assets'); +}); + +test('all includes registered asset containers', function (): void { + config(['statamic.builder.asset_containers' => [ + IntegrationTestAssetContainer::class, + ]]); + + $containers = AssetContainer::all(); + + expect($containers->map->handle()->toArray())->toContain('integration_assets'); +}); diff --git a/tests/Feature/AutoDiscoveryTest.php b/tests/Feature/AutoDiscoveryTest.php new file mode 100644 index 0000000..5817a49 --- /dev/null +++ b/tests/Feature/AutoDiscoveryTest.php @@ -0,0 +1,58 @@ +discover(); + + // 4. Verify it's in the config + $registeredInStatamic = config('statamic.builder.asset_containers', []); + + expect($registeredInStatamic)->toContain('App\AssetContainers\DiscoverableAssetContainer'); + + // 5. Verify Statamic can find it + $container = AssetContainer::find('discovered_assets'); + expect($container)->not->toBeNull(); + expect($container->handle())->toBe('discovered_assets'); +}); diff --git a/tests/Feature/CollectionRegistrationTest.php b/tests/Feature/CollectionRegistrationTest.php new file mode 100644 index 0000000..63a79ba --- /dev/null +++ b/tests/Feature/CollectionRegistrationTest.php @@ -0,0 +1,29 @@ + [ + TestCollection::class, + ]]); + + $collection = Collection::find('shows'); + + expect($collection)->not->toBeNull(); + expect($collection->handle())->toBe('shows'); +}); + +test('register method properly configures collection with all settings', function (): void { + $collectionClass = new TestCollection; + $collection = $collectionClass->register(); + + expect($collection)->not->toBeNull() + ->and($collection->handle())->toBe('shows') + ->and($collection->title())->toBe('Shows') + ->and($collection->routes()->toArray())->toHaveKey('default') + ->and($collection->requiresSlugs())->toBeTrue() + ->and($collection->dated())->toBeFalse() + ->and($collection->revisionsEnabled())->toBeFalse() + ->and($collection->defaultPublishState())->toBeTrue(); +}); diff --git a/tests/Feature/GlobalSaveTest.php b/tests/Feature/GlobalSaveTest.php new file mode 100644 index 0000000..540cfb4 --- /dev/null +++ b/tests/Feature/GlobalSaveTest.php @@ -0,0 +1,65 @@ + [TestGlobalSet::class]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Globals\GlobalRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\GlobalRepository(app('stache')); + }); +}); + +test('it can save global variables', function (): void { + $globalSet = GlobalSet::findByHandle('test_global'); + expect($globalSet)->not()->toBeNull(); + + $localization = $globalSet->in('default'); + expect($localization)->not()->toBeNull(); + + $localization->set('test_field', 'test_value'); + $localization->save(); + + $globalSet = GlobalSet::findByHandle('test_global'); + $localization = $globalSet->in('default'); + + expect($localization->get('test_field'))->toBe('test_value'); +}); + +test('it can save global variables for blueprint-based globals', function (): void { + config(['statamic.builder.blueprints.globals' => [ + 'blueprint_global' => \Tests\Helpers\TestBlueprint::class, + ]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Globals\GlobalRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\GlobalRepository(app('stache')); + }); + + $globalSet = GlobalSet::findByHandle('blueprint_global'); + // Note: findByHandle in GlobalRepository only looks in $this->globals (classes) + // or parent::findByHandle (stache/file). + // If it's ONLY a blueprint, it might not be found by findByHandle unless it's already saved to disk. + // BUT GlobalSet::all() should include it. + + $all = GlobalSet::all(); + expect($all->has('blueprint_global'))->toBeTrue(); + + $globalSet = $all->get('blueprint_global'); + expect($globalSet)->not()->toBeNull(); + + $localization = $globalSet->in('default'); + expect($localization)->not()->toBeNull(); + + $localization->set('test_field', 'blueprint_value'); + $localization->save(); + + // Now it should also be findable by handle because it's on disk (parent::findByHandle) + $globalSet = GlobalSet::findByHandle('blueprint_global'); + expect($globalSet)->not()->toBeNull(); + + $localization = $globalSet->in('default'); + expect($localization->get('test_field'))->toBe('blueprint_value'); +}); diff --git a/tests/Feature/Http/Controllers/AssetContainerControllerTest.php b/tests/Feature/Http/Controllers/AssetContainerControllerTest.php new file mode 100644 index 0000000..e946fda --- /dev/null +++ b/tests/Feature/Http/Controllers/AssetContainerControllerTest.php @@ -0,0 +1,48 @@ + 'base64:mwP+tC6f059kYshh+2x46KOf4R66I65f2Dq9Xm5C7M8=']); + config(['statamic.builder.asset_containers' => [ + TestAssetContainer::class, + ]]); + + $container = AssetContainer::find('test_assets'); + + $this->actingAs(User::make()->makeSuper()->save()) + ->get(cp_route('asset-containers.edit', $container)) + ->assertStatus(200) + ->assertViewIs('statamic-builder::not-editable') + ->assertViewHas('type', 'Asset Container'); +}); + +test('it shows not editable view for builder defined asset container blueprint', function () { + config(['app.key' => 'base64:mwP+tC6f059kYshh+2x46KOf4R66I65f2Dq9Xm5C7M8=']); + config(['statamic.builder.blueprints' => [ + 'assets' => [ + 'test_assets' => TestAssetContainerBlueprint::class, + ], + ]]); + + $container = AssetContainer::make('test_assets')->save(); + + $this->actingAs(User::make()->makeSuper()->save()) + ->get(cp_route('asset-containers.blueprint.edit', $container)) + ->assertStatus(200) + ->assertViewIs('statamic-builder::not-editable') + ->assertViewHas('type', 'Blueprint'); +}); + +class TestAssetContainer extends BaseAssetContainer +{ + public static function handle(): string + { + return 'test_assets'; + } +} diff --git a/tests/Feature/Http/Controllers/NavigationControllerTest.php b/tests/Feature/Http/Controllers/NavigationControllerTest.php new file mode 100644 index 0000000..0480273 --- /dev/null +++ b/tests/Feature/Http/Controllers/NavigationControllerTest.php @@ -0,0 +1,62 @@ + 'base64:m97Vl6m1xj5qVyXWjXWjXWjXWjXWjXWjXWjXWjXWjXU=']); + config(['statamic.builder.navigations' => [TestNavigation::class]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Structures\NavigationRepository::class, function () { + return new NavigationRepository(app('stache')); + }); + + $user = User::make()->makeSuper()->save(); + + $this->actingAs($user) + ->get(cp_route('navigation.edit', 'test_navigation')) + ->assertStatus(200) + ->assertViewIs('statamic-builder::not-editable') + ->assertViewHas('type', 'Navigation'); +}); + +test('it shows the not editable view when editing a builder defined navigation blueprint', function () { + config(['app.key' => 'base64:m97Vl6m1xj5qVyXWjXWjXWjXWjXWjXWjXWjXWjXWjXU=']); + config(['statamic.builder.blueprints' => [ + 'navigation' => [ + 'test_navigation' => TestNavigationBlueprint::class, + ], + ]]); + + $user = User::make()->makeSuper()->save(); + + $this->actingAs($user) + ->get(cp_route('navigation.blueprint.edit', 'test_navigation')) + ->assertStatus(200) + ->assertViewIs('statamic-builder::not-editable') + ->assertViewHas('type', 'Blueprint'); +}); + +test('it shows the not editable view when editing a builder defined navigation with eloquent repository', function () { + config(['app.key' => 'base64:m97Vl6m1xj5qVyXWjXWjXWjXWjXWjXWjXWjXWjXWjXU=']); + config(['statamic.builder.navigations' => [TestNavigation::class]]); + config(['statamic.eloquent-driver.navigations.driver' => 'eloquent']); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Structures\NavigationRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\EloquentNavigationRepository(app('stache')); + }); + + $user = User::make()->makeSuper()->save(); + + $this->actingAs($user) + ->get(cp_route('navigation.edit', 'test_navigation')) + ->assertStatus(200) + ->assertViewIs('statamic-builder::not-editable') + ->assertViewHas('type', 'Navigation'); +}); diff --git a/tests/Helpers/TestAssetContainer.php b/tests/Helpers/TestAssetContainer.php new file mode 100644 index 0000000..ed7c0bc --- /dev/null +++ b/tests/Helpers/TestAssetContainer.php @@ -0,0 +1,23 @@ +in('Feature'); +uses(Tests\TestCase::class)->in(__DIR__); /* |-------------------------------------------------------------------------- diff --git a/tests/Unit/AssetContainerRepositoryTest.php b/tests/Unit/AssetContainerRepositoryTest.php new file mode 100644 index 0000000..d7025ca --- /dev/null +++ b/tests/Unit/AssetContainerRepositoryTest.php @@ -0,0 +1,37 @@ + [ + TestAssetContainer::class, + ]]); + + $store = Mockery::mock(\Statamic\Stache\Stores\AssetContainerStore::class); + $store->shouldReceive('paths')->andReturn(collect()); + + $stache = Mockery::mock(\Statamic\Stache\Stache::class); + $stache->shouldReceive('store')->with('asset-containers')->andReturn($store); + + $repository = new AssetContainerRepository($stache); + $containers = $repository->all(); + + expect($containers->map->handle()->toArray())->toContain('main_assets'); +}); + +test('can find by handle', function (): void { + config(['statamic.builder.asset_containers' => [ + TestAssetContainer::class, + ]]); + + $stache = Mockery::mock(\Statamic\Stache\Stache::class); + $stache->shouldReceive('store')->with('asset-containers')->andReturn(Mockery::mock(\Statamic\Stache\Stores\AssetContainerStore::class)); + + $repository = new AssetContainerRepository($stache); + $container = $repository->findByHandle('main_assets'); + + expect($container)->toBeInstanceOf(StatamicAssetContainer::class); + expect($container->handle())->toBe('main_assets'); +}); diff --git a/tests/Unit/AssetContainerTest.php b/tests/Unit/AssetContainerTest.php new file mode 100644 index 0000000..f9690df --- /dev/null +++ b/tests/Unit/AssetContainerTest.php @@ -0,0 +1,39 @@ +title())->toBe( + 'Main Assets' + ); +}); + +test('Has a handle', function (): void { + $container = new TestAssetContainer; + + expect($container->handle())->toBe( + 'main_assets' + ); +}); + +test('Has a disk', function (): void { + $container = new TestAssetContainer; + + expect($container->disk())->toBe( + 'assets' + ); +}); + +test('Can be registered', function (): void { + $container = new TestAssetContainer; + $registered = $container->register(); + + expect($registered)->toBeInstanceOf(\Statamic\Assets\AssetContainer::class); + expect($registered->handle())->toBe('main_assets'); + expect($registered->title())->toBe('Main Assets'); + expect($registered->diskHandle())->toBe('assets'); +}); diff --git a/tests/Unit/BlueprintTest.php b/tests/Unit/BlueprintTest.php index b911fa1..eb1d4d3 100644 --- a/tests/Unit/BlueprintTest.php +++ b/tests/Unit/BlueprintTest.php @@ -82,3 +82,53 @@ expect($blueprint->getHandle())->toBe('test_blueprint'); }); + +test('base blueprint static handle returns empty string by default', function (): void { + $blueprint = new class extends \Tdwesten\StatamicBuilder\Blueprint + { + public function registerTabs(): array + { + return []; + } + }; + + expect($blueprint::handle())->toBe(''); +}); + +test('static blueprintNamespace returns empty string by default', function (): void { + $blueprint = new class extends \Tdwesten\StatamicBuilder\Blueprint + { + public function registerTabs(): array + { + return []; + } + }; + + expect($blueprint::blueprintNamespace())->toBe(''); +}); + +test('blueprint with empty tabs returns empty array', function (): void { + $blueprint = new class extends \Tdwesten\StatamicBuilder\Blueprint + { + public function registerTabs(): array + { + return []; + } + }; + + expect($blueprint->toArray()['tabs'])->toBe([]); +}); + +test('blueprint throws exception when non-tab field is in registerTabs', function (): void { + $blueprint = new class extends \Tdwesten\StatamicBuilder\Blueprint + { + public function registerTabs(): array + { + return [ + \Tdwesten\StatamicBuilder\FieldTypes\Text::make('title'), + ]; + } + }; + + $blueprint->toArray(); +})->throws(\Tdwesten\StatamicBuilder\Exceptions\BlueprintRenderException::class, 'Only tabs are allowed in the register function of a blueprint'); diff --git a/tests/Unit/CollectionRepositoryTest.php b/tests/Unit/CollectionRepositoryTest.php new file mode 100644 index 0000000..b1a0ddc --- /dev/null +++ b/tests/Unit/CollectionRepositoryTest.php @@ -0,0 +1,55 @@ + []]); +}); + +test('::find does not throw when no result is found', function (): void { + Collection::find('id-that-does-not-exist'); +})->throwsNoExceptions(); + +test('::find returns null for nonexistent handles', function (): void { + $nullset = Collection::find('id-that-does-not-exist'); + + expect($nullset)->toBeNull(); +}); + +test('::findByHandle finds builder-registered collection', function (): void { + config(['statamic.builder.collections' => [TestCollection::class]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Stache\Repositories\CollectionRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\CollectionRepository(app('stache')); + }); + + $collection = Collection::findByHandle('shows'); + + expect($collection)->not()->toBeNull(); + expect($collection->handle())->toBe('shows'); + expect($collection->title())->toBe('Shows'); +}); + +test('::all includes builder-registered collections', function (): void { + config(['statamic.builder.collections' => [TestCollection::class]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Stache\Repositories\CollectionRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\CollectionRepository(app('stache')); + }); + + $collections = Collection::all(); + + expect($collections->has('shows'))->toBeTrue(); +}); + +test('getCollectionByHandle returns null for non-existent collection', function (): void { + config(['statamic.builder.collections' => []]); + + $repository = new \Tdwesten\StatamicBuilder\Repositories\CollectionRepository(app('stache')); + $result = $repository->getCollectionByHandle('non-existent'); + + expect($result)->toBeNull(); +}); diff --git a/tests/Unit/CollectionTest.php b/tests/Unit/CollectionTest.php index f7afa21..900c31c 100644 --- a/tests/Unit/CollectionTest.php +++ b/tests/Unit/CollectionTest.php @@ -51,3 +51,32 @@ expect($collection->slugs())->toBeTrue(); }); + +test('Has default values for optional methods', function (): void { + $collection = new TestCollection; + + expect($collection->titleFormat())->toBeNull() + ->and($collection->mount())->toBeNull() + ->and($collection->date())->toBeFalse() + ->and($collection->template())->toBeNull() + ->and($collection->layout())->toBeNull() + ->and($collection->inject())->toBe([]) + ->and($collection->searchIndex())->toBe('search_index') // TestCollection overrides this + ->and($collection->revisionsEnabled())->toBeFalse() + ->and($collection->defaultPublishState())->toBeTrue() + ->and($collection->originBehavior())->toBe('select') + ->and($collection->structure())->toBe([ // TestCollection overrides this + 'root' => false, + 'slugs' => false, + 'max_depth' => null, + ]) + ->and($collection->sortBy())->toBeNull() + ->and($collection->sortDir())->toBeNull() + ->and($collection->taxonomies())->toBe([]) + ->and($collection->propagate())->toBeNull() + ->and($collection->previewTargets())->toBe([]) + ->and($collection->autosave())->toBeNull() + ->and($collection->futureDateBehavior())->toBeNull() + ->and($collection->pastDateBehavior())->toBeNull() + ->and($collection->visible())->toBeTrue(); +}); diff --git a/tests/Unit/Fields/BardTest.php b/tests/Unit/Fields/BardTest.php index dc758fa..8a424ef 100644 --- a/tests/Unit/Fields/BardTest.php +++ b/tests/Unit/Fields/BardTest.php @@ -59,3 +59,17 @@ expect($field->toArray()['field']['inline'])->toBe(true); }); + +test('you can set inline to false', function (): void { + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Bard('title'); + $field->inline(BardInlineOption::False); + + expect($field->toArray()['field']['inline'])->toBe(false); +}); + +test('you can set inline to break (accordion)', function (): void { + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Bard('title'); + $field->inline(BardInlineOption::Break); + + expect($field->toArray()['field']['inline'])->toBe('accordion'); +}); diff --git a/tests/Unit/Fields/ButtonGroupTest.php b/tests/Unit/Fields/ButtonGroupTest.php index 0201037..3c07d50 100644 --- a/tests/Unit/Fields/ButtonGroupTest.php +++ b/tests/Unit/Fields/ButtonGroupTest.php @@ -49,3 +49,10 @@ expect($field->toArray()['field']['default'])->toBe('option1'); }); + +it('can set default value using defaultValue method', function (): void { + $field = new \Tdwesten\StatamicBuilder\FieldTypes\ButtonGroup('title'); + $field->defaultValue('option2'); + + expect($field->toArray()['field']['default'])->toBe('option2'); +}); diff --git a/tests/Unit/Fields/CollorTest.php b/tests/Unit/Fields/ColorTest.php similarity index 83% rename from tests/Unit/Fields/CollorTest.php rename to tests/Unit/Fields/ColorTest.php index bfa30cb..0d4e02d 100644 --- a/tests/Unit/Fields/CollorTest.php +++ b/tests/Unit/Fields/ColorTest.php @@ -3,7 +3,7 @@ use Tdwesten\StatamicBuilder\Enums\VisibilityOption; it('can render to a array', function (): void { - $field = new \Tdwesten\StatamicBuilder\FieldTypes\Collor('title'); + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Color('title'); $field->displayName('Display Name') ->instructions('Enter the title') ->visibility(VisibilityOption::Hidden) @@ -31,21 +31,21 @@ }); test('you can set the allow any', function (): void { - $field = new \Tdwesten\StatamicBuilder\FieldTypes\Collor('title'); + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Color('title'); $field->allowAny(false); expect($field->toArray()['field']['allow_any'])->toBe(false); }); test('you can set the swatches', function (): void { - $field = new \Tdwesten\StatamicBuilder\FieldTypes\Collor('title'); + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Color('title'); $field->swatches(['#000000', '#ffffff']); expect($field->toArray()['field']['swatches'])->toBe(['#000000', '#ffffff']); }); test('you can set the default value', function (): void { - $field = new \Tdwesten\StatamicBuilder\FieldTypes\Collor('title'); + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Color('title'); $field->default('#000000'); expect($field->toArray()['field']['default'])->toBe('#000000'); diff --git a/tests/Unit/Fields/ConditionalLogicTest.php b/tests/Unit/Fields/ConditionalLogicTest.php index a0f2d26..be0aba9 100644 --- a/tests/Unit/Fields/ConditionalLogicTest.php +++ b/tests/Unit/Fields/ConditionalLogicTest.php @@ -36,3 +36,60 @@ 'title' => 'equals red', ]); }); + +test('Can set conditional logic with custom if', function (): void { + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Field('title'); + $field + ->type('text') + ->ifCustom('show_title === true && color === "red"'); + + expect($field->toArray()['field']['if'])->toBe([ + 'custom' => 'show_title === true && color === "red"', + ]); +}); + +test('Can set conditional logic with custom if_any', function (): void { + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Field('title'); + $field + ->type('text') + ->ifAnyCustom('show_title === true || color === "red"'); + + expect($field->toArray()['field']['if_any'])->toBe([ + 'custom' => 'show_title === true || color === "red"', + ]); +}); + +test('Can set conditional logic unless', function (): void { + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Field('title'); + $field + ->type('text') + ->unless('hide_title', OperatorOption::Equals, 'true'); + + expect($field->toArray()['field']['unless'])->toBe([ + 'hide_title' => 'equals true', + ]); +}); + +test('Can set conditional logic unless with multiple conditions', function (): void { + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Field('title'); + $field + ->type('text') + ->unless('hide_title', OperatorOption::Equals, 'true') + ->unless('show_content', OperatorOption::Not, 'false'); + + expect($field->toArray()['field']['unless'])->toBe([ + 'hide_title' => 'equals true', + 'show_content' => 'not false', + ]); +}); + +test('Can set conditional logic with custom unless', function (): void { + $field = new \Tdwesten\StatamicBuilder\FieldTypes\Field('title'); + $field + ->type('text') + ->unlessCustom('hide_title === true'); + + expect($field->toArray()['field']['unless'])->toBe([ + 'custom' => 'hide_title === true', + ]); +}); diff --git a/tests/Unit/Fields/FieldsetTest.php b/tests/Unit/Fields/FieldsetTest.php index 4644ec8..46c9f59 100644 --- a/tests/Unit/Fields/FieldsetTest.php +++ b/tests/Unit/Fields/FieldsetTest.php @@ -19,6 +19,34 @@ expect($fields)->toBeArray(); }); +test('getPrefix returns null when no prefix is set', function (): void { + $fieldset = TestFieldset::make(); + + expect($fieldset->getPrefix())->toBeNull(); +}); + +test('getPrefix returns prefix when set in constructor', function (): void { + $fieldset = TestFieldset::make('my_prefix'); + + expect($fieldset->getPrefix())->toBe('my_prefix'); +}); + +test('prefix method sets prefix and returns instance for chaining', function (): void { + $fieldset = TestFieldset::make(); + $result = $fieldset->prefix('new_prefix'); + + expect($result)->toBe($fieldset) + ->and($fieldset->getPrefix())->toBe('new_prefix'); +}); + +test('getFields returns collection of fields with prefix applied', function (): void { + $fieldset = TestFieldset::make('test_prefix'); + $fields = $fieldset->getFields(); + + expect($fields)->toBeInstanceOf(\Illuminate\Support\Collection::class) + ->and($fields->count())->toBeGreaterThan(0); +}); + it('can be converted to an array', function (): void { $fieldset = TestFieldset::make('test'); $fields = $fieldset->toArray(); diff --git a/tests/Unit/Fields/ForeignFieldTest.php b/tests/Unit/Fields/ForeignFieldTest.php new file mode 100644 index 0000000..b2ecd3d --- /dev/null +++ b/tests/Unit/Fields/ForeignFieldTest.php @@ -0,0 +1,25 @@ + 'handle', + 'field' => 'field_name', + 'config' => [], + ]; + + expect($field->toArray())->toBe($expected); +}); + +test('Foreign field can be rendered with config', function (): void { + $field = ForeignField::make('handle', 'field_name')->config(['display' => 'Custom Display']); + $expected = [ + 'handle' => 'handle', + 'field' => 'field_name', + 'config' => ['display' => 'Custom Display'], + ]; + + expect($field->toArray())->toBe($expected); +}); diff --git a/tests/Unit/Fields/ForeignFieldsetTest.php b/tests/Unit/Fields/ForeignFieldsetTest.php new file mode 100644 index 0000000..fd2d875 --- /dev/null +++ b/tests/Unit/Fields/ForeignFieldsetTest.php @@ -0,0 +1,23 @@ + 'handle', + 'prefix' => null, + ]; + + expect($fieldset->toArray())->toBe($expected); +}); + +test('Foreign fieldset can be rendered with prefix', function (): void { + $fieldset = ForeignFieldset::make('handle')->prefix('prefix_'); + $expected = [ + 'import' => 'handle', + 'prefix' => 'prefix_', + ]; + + expect($fieldset->toArray())->toBe($expected); +}); diff --git a/tests/Unit/Fields/HiddenTest.php b/tests/Unit/Fields/HiddenTest.php new file mode 100644 index 0000000..b5074fe --- /dev/null +++ b/tests/Unit/Fields/HiddenTest.php @@ -0,0 +1,10 @@ +default('secret'); + + expect($field->toArray()['field']['type'])->toBe('hidden'); + expect($field->toArray()['field']['default'])->toBe('secret'); +}); diff --git a/tests/Unit/Fields/MoneyTest.php b/tests/Unit/Fields/MoneyTest.php new file mode 100644 index 0000000..b63a5f0 --- /dev/null +++ b/tests/Unit/Fields/MoneyTest.php @@ -0,0 +1,11 @@ +currency('USD')->default(100); + + expect($field->toArray()['field']['type'])->toBe('money'); + expect($field->toArray()['field']['currency'])->toBe('USD'); + expect($field->toArray()['field']['default'])->toBe(100); +}); diff --git a/tests/Unit/Fields/NumberTest.php b/tests/Unit/Fields/NumberTest.php new file mode 100644 index 0000000..b01da21 --- /dev/null +++ b/tests/Unit/Fields/NumberTest.php @@ -0,0 +1,23 @@ +default(5) + ->min(0) + ->max(10) + ->step(1) + ->placeholder('Enter count') + ->prepend('Qty:') + ->append('pcs'); + + expect($field->toArray()['field']['type'])->toBe('number'); + expect($field->toArray()['field']['default'])->toBe(5); + expect($field->toArray()['field']['min'])->toBe(0); + expect($field->toArray()['field']['max'])->toBe(10); + expect($field->toArray()['field']['step'])->toBe(1); + expect($field->toArray()['field']['placeholder'])->toBe('Enter count'); + expect($field->toArray()['field']['prepend'])->toBe('Qty:'); + expect($field->toArray()['field']['append'])->toBe('pcs'); +}); diff --git a/tests/Unit/Fields/PasswordTest.php b/tests/Unit/Fields/PasswordTest.php new file mode 100644 index 0000000..0bae8a5 --- /dev/null +++ b/tests/Unit/Fields/PasswordTest.php @@ -0,0 +1,10 @@ +placeholder('Secret'); + + expect($field->toArray()['field']['type'])->toBe('password'); + expect($field->toArray()['field']['placeholder'])->toBe('Secret'); +}); diff --git a/tests/Unit/Fields/RatingTest.php b/tests/Unit/Fields/RatingTest.php new file mode 100644 index 0000000..dbd368d --- /dev/null +++ b/tests/Unit/Fields/RatingTest.php @@ -0,0 +1,21 @@ +default(3) + ->min(1) + ->max(5) + ->step(0.5) + ->clearable(true) + ->color('yellow'); + + expect($field->toArray()['field']['type'])->toBe('rating'); + expect($field->toArray()['field']['default'])->toBe(3); + expect($field->toArray()['field']['min'])->toBe(1); + expect($field->toArray()['field']['max'])->toBe(5); + expect($field->toArray()['field']['step'])->toBe(0.5); + expect($field->toArray()['field']['clearable'])->toBe(true); + expect($field->toArray()['field']['color'])->toBe('yellow'); +}); diff --git a/tests/Unit/Fields/ReplicatorTest.php b/tests/Unit/Fields/ReplicatorTest.php index 4f45097..74eceb9 100644 --- a/tests/Unit/Fields/ReplicatorTest.php +++ b/tests/Unit/Fields/ReplicatorTest.php @@ -115,3 +115,24 @@ expect($field->toArray())->toBe($output); }); + +test('can set collapse to false', function (): void { + $field = Replicator::make('replicator', []) + ->collapse(\Tdwesten\StatamicBuilder\Enums\CollapseOption::False); + + expect($field->toArray()['field']['collapse'])->toBe(false); +}); + +test('can set collapse to true', function (): void { + $field = Replicator::make('replicator', []) + ->collapse(\Tdwesten\StatamicBuilder\Enums\CollapseOption::True); + + expect($field->toArray()['field']['collapse'])->toBe(true); +}); + +test('can set collapse to accordion', function (): void { + $field = Replicator::make('replicator', []) + ->collapse(\Tdwesten\StatamicBuilder\Enums\CollapseOption::Accordion); + + expect($field->toArray()['field']['collapse'])->toBe('accordion'); +}); diff --git a/tests/Unit/Fields/SectionTest.php b/tests/Unit/Fields/SectionTest.php new file mode 100644 index 0000000..701dbb6 --- /dev/null +++ b/tests/Unit/Fields/SectionTest.php @@ -0,0 +1,15 @@ +displayName('Title'), + ]); + + $array = $section->toArray(); + + expect($array['display'])->toBe('General'); + expect($array['fields'][0]['handle'])->toBe('title'); +}); diff --git a/tests/Unit/Fields/TabTest.php b/tests/Unit/Fields/TabTest.php new file mode 100644 index 0000000..f0c7b9d --- /dev/null +++ b/tests/Unit/Fields/TabTest.php @@ -0,0 +1,18 @@ +displayName('Title'), + ]), + ])->displayName('Main'); + + $array = $tab->toArray(); + + expect($array['display'])->toBe('Main'); + expect($array['sections'][0]['display'])->toBe('General'); +}); diff --git a/tests/Unit/Fields/TaggebleTest.php b/tests/Unit/Fields/TaggebleTest.php deleted file mode 100644 index 9b3e659..0000000 --- a/tests/Unit/Fields/TaggebleTest.php +++ /dev/null @@ -1,31 +0,0 @@ -displayName('Display Name') - ->instructions('Enter the title') - ->visibility(VisibilityOption::Hidden) - ->required() - ->instructionsPosition('below') - ->listable() - ->replicatorPreview(true) - ->width(50); - - expect($field->toArray()['field']['display'])->toBe('Display Name'); - - expect($field->toArray()['field']['instructions'])->toBe('Enter the title'); - - expect($field->toArray()['field']['visibility'])->toBe('hidden'); - - expect($field->toArray()['field']['validate'])->toBe(['required']); - - expect($field->toArray()['field']['instructions_position'])->toBe('below'); - - expect($field->toArray()['field']['listable'])->toBe(true); - - expect($field->toArray()['field']['replicator_preview'])->toBe(true); - - expect($field->toArray()['field']['width'])->toBe(50); -}); diff --git a/tests/Unit/FieldsetRepositoryTest.php b/tests/Unit/FieldsetRepositoryTest.php new file mode 100644 index 0000000..7884c43 --- /dev/null +++ b/tests/Unit/FieldsetRepositoryTest.php @@ -0,0 +1,46 @@ + [ + \Tests\Helpers\TestFieldset::class, + ]]); + + $repository = Mockery::mock(FieldsetRepository::class)->makePartial()->shouldAllowMockingProtectedMethods(); + $repository->shouldAllowMockingProtectedMethods(); + $repository->shouldReceive('getStandardFieldsets')->andReturn(collect()); + $repository->shouldReceive('getNamespacedFieldsets')->andReturn(collect()); + + $fieldsets = $repository->all(); + + expect($fieldsets->contains(fn ($fieldset) => $fieldset->handle() === 'test_fieldset'))->toBeTrue(); +}); + +test('::find finds builder-registered fieldset', function (): void { + config(['statamic.builder.fieldsets' => [ + \Tests\Helpers\TestFieldset::class, + ]]); + + $repository = new FieldsetRepository; + $fieldset = $repository->find('test_fieldset'); + + expect($fieldset)->toBeInstanceOf(StatamicFieldset::class); + expect($fieldset->handle())->toBe('test_fieldset'); +}); + +test('::save does not save builder-registered fieldset', function (): void { + config(['statamic.builder.fieldsets' => [ + \Tests\Helpers\TestFieldset::class, + ]]); + + $repository = Mockery::mock(FieldsetRepository::class)->makePartial()->shouldAllowMockingProtectedMethods(); + $repository->shouldNotReceive('parent::save'); + + $fieldset = StatamicFieldset::make('test_fieldset'); + $repository->save($fieldset); + + // If it didn't throw and Mockery didn't complain, it's good. + expect(true)->toBeTrue(); +}); diff --git a/tests/Unit/GlobalRepositoryTest.php b/tests/Unit/GlobalRepositoryTest.php index 8c83aef..1649f02 100644 --- a/tests/Unit/GlobalRepositoryTest.php +++ b/tests/Unit/GlobalRepositoryTest.php @@ -1,9 +1,12 @@ []]); +}); -pest()->extend(TestCase::class); test('::find does not throw when no result is found', function (): void { GlobalSet::find('id-that-does-not-exist'); })->throwsNoExceptions(); @@ -13,3 +16,46 @@ expect($nullset)->toBeNull(); }); + +test('::findByHandle finds builder-registered global', function (): void { + config(['statamic.builder.globals' => [TestGlobalSet::class]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Globals\GlobalRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\GlobalRepository(app('stache')); + }); + + $globalSet = GlobalSet::findByHandle('test_global'); + + expect($globalSet)->not()->toBeNull(); + expect($globalSet->handle())->toBe('test_global'); + expect($globalSet->title())->toBe('Test Global Set'); +}); + +test('::all includes builder-registered globals', function (): void { + config(['statamic.builder.globals' => [TestGlobalSet::class]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Globals\GlobalRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\GlobalRepository(app('stache')); + }); + + $globals = GlobalSet::all(); + + expect($globals->has('test_global'))->toBeTrue(); +}); + +test('::all includes globals from blueprints', function (): void { + config(['statamic.builder.blueprints.globals' => [ + 'blueprint_global' => \Tests\Helpers\TestBlueprint::class, + ]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Globals\GlobalRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\GlobalRepository(app('stache')); + }); + + $globals = GlobalSet::all(); + + expect($globals->has('blueprint_global'))->toBeTrue(); +}); diff --git a/tests/Unit/GlobalSetTest.php b/tests/Unit/GlobalSetTest.php new file mode 100644 index 0000000..5867a28 --- /dev/null +++ b/tests/Unit/GlobalSetTest.php @@ -0,0 +1,31 @@ +handle())->toBe('test_global'); +}); + +test('Has a title', function (): void { + $global = new TestGlobalSet; + + expect($global->title())->toBe('Test Global Set'); +}); + +test('Has sites', function (): void { + $global = new TestGlobalSet; + + expect($global->sites())->toBe(['default']); +}); + +test('Register method creates global set with proper configuration', function (): void { + $globalClass = new TestGlobalSet; + $global = $globalClass->register(); + + expect($global)->not->toBeNull() + ->and($global->handle())->toBe('test_global') + ->and($global->title())->toBe('Test Global Set') + ->and($global->existsIn('default'))->toBeTrue(); +}); diff --git a/tests/Unit/MakeBlueprintCommandTest.php b/tests/Unit/MakeBlueprintCommandTest.php new file mode 100644 index 0000000..25fa220 --- /dev/null +++ b/tests/Unit/MakeBlueprintCommandTest.php @@ -0,0 +1,27 @@ +artisan('make:blueprint', ['name' => 'TestBlueprint']) + ->assertExitCode(0); + + $path = app_path('Blueprints/TestBlueprint.php'); + + expect(File::exists($path))->toBeTrue(); + + File::delete($path); +}); + +test('it shows registration reminder when auto_registration is false', function (): void { + config(['statamic.builder.auto_registration' => false]); + + $this->artisan('make:blueprint', ['name' => 'AnotherBlueprint']) + ->expectsOutput('Remember to register your new Blueprint in config/statamic/builder.php') + ->assertExitCode(0); + + $path = app_path('Blueprints/AnotherBlueprint.php'); + File::delete($path); +}); diff --git a/tests/Unit/MakeCollectionCommandTest.php b/tests/Unit/MakeCollectionCommandTest.php new file mode 100644 index 0000000..4b1121f --- /dev/null +++ b/tests/Unit/MakeCollectionCommandTest.php @@ -0,0 +1,27 @@ +artisan('make:collection', ['name' => 'BlogCollection']) + ->assertExitCode(0); + + $path = app_path('Collections/BlogCollection.php'); + + expect(File::exists($path))->toBeTrue(); + + File::delete($path); +}); + +test('it shows registration reminder when auto_registration is false', function (): void { + config(['statamic.builder.auto_registration' => false]); + + $this->artisan('make:collection', ['name' => 'AnotherCollection']) + ->expectsOutput('Remember to register your new Collection in config/statamic/builder.php') + ->assertExitCode(0); + + $path = app_path('Collections/AnotherCollection.php'); + File::delete($path); +}); diff --git a/tests/Unit/MakeFieldsetCommandTest.php b/tests/Unit/MakeFieldsetCommandTest.php new file mode 100644 index 0000000..a817fef --- /dev/null +++ b/tests/Unit/MakeFieldsetCommandTest.php @@ -0,0 +1,27 @@ +artisan('make:fieldset', ['name' => 'CommonFieldset']) + ->assertExitCode(0); + + $path = app_path('Fieldsets/CommonFieldset.php'); + + expect(File::exists($path))->toBeTrue(); + + File::delete($path); +}); + +test('it shows registration reminder when auto_registration is false', function (): void { + config(['statamic.builder.auto_registration' => false]); + + $this->artisan('make:fieldset', ['name' => 'AnotherFieldset']) + ->expectsOutput('Remember to register your new Fieldset in config/statamic/builder.php') + ->assertExitCode(0); + + $path = app_path('Fieldsets/AnotherFieldset.php'); + File::delete($path); +}); diff --git a/tests/Unit/MakeGlobalSetCommandTest.php b/tests/Unit/MakeGlobalSetCommandTest.php new file mode 100644 index 0000000..ae3c9e9 --- /dev/null +++ b/tests/Unit/MakeGlobalSetCommandTest.php @@ -0,0 +1,34 @@ +artisan('make:global-set', ['name' => $name]); + + $path = app_path('Globals/FooterGlobal.php'); + + expect(File::exists($path))->toBeTrue(); + + $content = File::get($path); + + expect($content)->toContain('class FooterGlobal extends BaseGlobalSet'); + expect($content)->toContain("return 'footer_global';"); + expect($content)->toContain('public function title(): string'); + expect($content)->toContain("return 'FooterGlobal';"); + expect($content)->toContain('public function sites(): array'); + + File::deleteDirectory(app_path('Globals')); +}); + +test('it shows registration reminder when auto_registration is false', function (): void { + config(['statamic.builder.auto_registration' => false]); + + $this->artisan('make:global-set', ['name' => 'HeaderGlobal']) + ->expectsOutput('Remember to register your new Global Set in config/statamic/builder.php') + ->assertExitCode(0); + + File::deleteDirectory(app_path('Globals')); +}); diff --git a/tests/Unit/MakeNavigationCommandTest.php b/tests/Unit/MakeNavigationCommandTest.php new file mode 100644 index 0000000..4156377 --- /dev/null +++ b/tests/Unit/MakeNavigationCommandTest.php @@ -0,0 +1,31 @@ +artisan('make:navigation', ['name' => $name]); + + $path = app_path('Navigations/MainNavigation.php'); + + expect(File::exists($path))->toBeTrue(); + + $content = File::get($path); + + expect($content)->toContain('class MainNavigation extends BaseNavigation'); + expect($content)->toContain("return 'main_navigation';"); + + File::deleteDirectory(app_path('Navigations')); +}); + +test('it shows registration reminder when auto_registration is false', function (): void { + config(['statamic.builder.auto_registration' => false]); + + $this->artisan('make:navigation', ['name' => 'FooterNavigation']) + ->expectsOutput('Remember to register your new Navigation in config/statamic/builder.php') + ->assertExitCode(0); + + File::deleteDirectory(app_path('Navigations')); +}); diff --git a/tests/Unit/MakeSiteCommandTest.php b/tests/Unit/MakeSiteCommandTest.php new file mode 100644 index 0000000..263f005 --- /dev/null +++ b/tests/Unit/MakeSiteCommandTest.php @@ -0,0 +1,27 @@ +artisan('make:site', ['name' => 'TestSiteClass']) + ->assertExitCode(0); + + $path = app_path('Sites/TestSiteClass.php'); + + expect(File::exists($path))->toBeTrue(); + + File::delete($path); +}); + +test('it shows registration reminder when auto_registration is false', function (): void { + config(['statamic.builder.auto_registration' => false]); + + $this->artisan('make:site', ['name' => 'AnotherSiteClass']) + ->expectsOutput('Remember to register your new Site in config/statamic/builder.php') + ->assertExitCode(0); + + $path = app_path('Sites/AnotherSiteClass.php'); + File::delete($path); +}); diff --git a/tests/Unit/MakeTaxonomyCommandTest.php b/tests/Unit/MakeTaxonomyCommandTest.php new file mode 100644 index 0000000..5d55eb6 --- /dev/null +++ b/tests/Unit/MakeTaxonomyCommandTest.php @@ -0,0 +1,27 @@ +artisan('make:taxonomy', ['name' => 'TagsTaxonomy']) + ->assertExitCode(0); + + $path = app_path('Taxonomies/TagsTaxonomy.php'); + + expect(File::exists($path))->toBeTrue(); + + File::delete($path); +}); + +test('it shows registration reminder when auto_registration is false', function (): void { + config(['statamic.builder.auto_registration' => false]); + + $this->artisan('make:taxonomy', ['name' => 'CategoriesTaxonomy']) + ->expectsOutput('Remember to register your new Taxonomy in config/statamic/builder.php') + ->assertExitCode(0); + + $path = app_path('Taxonomies/CategoriesTaxonomy.php'); + File::delete($path); +}); diff --git a/tests/Unit/NavigationRepositoryTest.php b/tests/Unit/NavigationRepositoryTest.php new file mode 100644 index 0000000..0a096e7 --- /dev/null +++ b/tests/Unit/NavigationRepositoryTest.php @@ -0,0 +1,46 @@ + []]); +}); + +test('::find does not throw when no result is found', function (): void { + Nav::find('id-that-does-not-exist'); +})->throwsNoExceptions(); + +test('::find returns null for nonexistent handles', function (): void { + $nullnav = Nav::find('id-that-does-not-exist'); + + expect($nullnav)->toBeNull(); +}); + +test('::findByHandle finds builder-registered navigation', function (): void { + config(['statamic.builder.navigations' => [TestNavigation::class]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Structures\NavigationRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\NavigationRepository(app('stache')); + }); + + $navigation = Nav::findByHandle('test_navigation'); + + expect($navigation)->not()->toBeNull(); + expect($navigation->handle())->toBe('test_navigation'); + expect($navigation->title())->toBe('Test Navigation'); +}); + +test('::all includes builder-registered navigations', function (): void { + config(['statamic.builder.navigations' => [TestNavigation::class]]); + + // Re-initialize the repository with the new config + app()->singleton(\Statamic\Contracts\Structures\NavigationRepository::class, function () { + return new \Tdwesten\StatamicBuilder\Repositories\NavigationRepository(app('stache')); + }); + + $navigations = Nav::all(); + + expect($navigations->has('test_navigation'))->toBeTrue(); +}); diff --git a/tests/Unit/NavigationTest.php b/tests/Unit/NavigationTest.php new file mode 100644 index 0000000..ebae443 --- /dev/null +++ b/tests/Unit/NavigationTest.php @@ -0,0 +1,39 @@ +handle())->toBe('test_navigation'); +}); + +test('Has a title', function (): void { + $nav = new TestNavigation; + + expect($nav->title())->toBe('Test Navigation'); +}); + +test('Has default collections', function (): void { + $nav = new TestNavigation; + + expect($nav->collections())->toBe([]); +}); + +test('Has default sites', function (): void { + $nav = new TestNavigation; + + expect($nav->sites())->toBeArray(); +}); + +test('Has default expectsRoot', function (): void { + $nav = new TestNavigation; + + expect($nav->expectsRoot())->toBeFalse(); +}); + +test('Has default maxDepth', function (): void { + $nav = new TestNavigation; + + expect($nav->maxDepth())->toBeNull(); +}); diff --git a/tests/Unit/ServiceProviderTest.php b/tests/Unit/ServiceProviderTest.php new file mode 100644 index 0000000..e2f7a37 --- /dev/null +++ b/tests/Unit/ServiceProviderTest.php @@ -0,0 +1,60 @@ +toBeInstanceOf(BuilderNavigationRepository::class); +}); + +test('it binds the global repository contract', function (): void { + $repository = app(GlobalRepository::class); + + expect($repository)->toBeInstanceOf(BuilderGlobalRepository::class); +}); + +test('it binds the asset container repository contract', function (): void { + $repository = app(AssetContainerRepository::class); + + expect($repository)->toBeInstanceOf(BuilderAssetContainerRepository::class); +}); + +test('it binds the eloquent navigation repository when driver is eloquent', function (): void { + config(['statamic.eloquent-driver.navigations.driver' => 'eloquent']); + + // Re-register the service provider or just the binding + app()->singleton(NavigationRepository::class, function () { + if (config('statamic.eloquent-driver.navigations.driver') === 'eloquent') { + return new \Tdwesten\StatamicBuilder\Repositories\EloquentNavigationRepository(app('stache')); + } + + return new \Tdwesten\StatamicBuilder\Repositories\NavigationRepository(app('stache')); + }); + + $repository = app(NavigationRepository::class); + + expect($repository)->toBeInstanceOf(\Tdwesten\StatamicBuilder\Repositories\EloquentNavigationRepository::class); +}); + +test('it binds the eloquent global repository when driver is eloquent', function (): void { + config(['statamic.eloquent-driver.globals.driver' => 'eloquent']); + + // Re-register the service provider or just the binding + app()->singleton(GlobalRepository::class, function () { + if (config('statamic.eloquent-driver.globals.driver') === 'eloquent') { + return new \Tdwesten\StatamicBuilder\Repositories\EloquentGlobalRepository(app('stache')); + } + + return new \Tdwesten\StatamicBuilder\Repositories\GlobalRepository(app('stache')); + }); + + $repository = app(GlobalRepository::class); + + expect($repository)->toBeInstanceOf(\Tdwesten\StatamicBuilder\Repositories\EloquentGlobalRepository::class); +}); diff --git a/tests/Unit/SiteTest.php b/tests/Unit/SiteTest.php index 3298240..f0659a9 100644 --- a/tests/Unit/SiteTest.php +++ b/tests/Unit/SiteTest.php @@ -41,3 +41,17 @@ 'extra' => 'attributes', ]); }); + +test('Can convert to array', function (): void { + $site = new TestSite; + + $array = $site->toArray(); + + expect($array)->toBeArray() + ->and($array['name'])->toBe('Blog') + ->and($array['url'])->toBe('http://blog.test') + ->and($array['locale'])->toBe('en_US') + ->and($array['attributes'])->toBe([ + 'extra' => 'attributes', + ]); +}); diff --git a/tests/Unit/SitesTest.php b/tests/Unit/SitesTest.php new file mode 100644 index 0000000..eb38e0b --- /dev/null +++ b/tests/Unit/SitesTest.php @@ -0,0 +1,58 @@ + [ + TestSite::class, + ]]); + + $sites = new Sites(config('statamic.sites')); + $result = $sites->all(); + + expect($result)->toHaveCount(1) + ->and($result->first()->handle())->toBe('blog'); +}); + +test('getSavedSites returns parent result when config is null', function (): void { + config(['statamic.builder.sites' => null]); + + $sites = new Sites(config('statamic.sites')); + $result = $sites->all(); + + expect($result)->not->toBeNull(); +}); + +test('getSavedSites caches result', function (): void { + config(['statamic.builder.sites' => [ + TestSite::class, + ]]); + + Cache::shouldReceive('rememberForever') + ->once() + ->with('statamic.builder.sites', \Mockery::type('Closure')) + ->andReturnUsing(function ($key, $callback) { + return $callback(); + }); + + $sites = new Sites(config('statamic.sites')); + $sites->all(); +}); + +test('getSavedSites maps site classes to array with handle as key', function (): void { + config(['statamic.builder.sites' => [ + TestSite::class, + ]]); + + $sites = new Sites(config('statamic.sites')); + $result = $sites->all(); + + expect($result->first())->not->toBeNull() + ->and($result->has('blog'))->toBeTrue(); +}); diff --git a/tests/Unit/TaxonomyBlueprintTest.php b/tests/Unit/TaxonomyBlueprintTest.php index cabed5e..2f07f1b 100644 --- a/tests/Unit/TaxonomyBlueprintTest.php +++ b/tests/Unit/TaxonomyBlueprintTest.php @@ -2,9 +2,6 @@ use Statamic\Facades\Taxonomy; use Tdwesten\StatamicBuilder\Http\Controllers\TaxonomyBlueprintsController; -use Tests\TestCase; - -pest()->extend(TestCase::class); const TEST_FILES_DIRECTORY = __DIR__.'/../__fixtures__'; const TAX_DIRECTORY = TEST_FILES_DIRECTORY.'/content/taxonomies'; diff --git a/tests/Unit/TaxonomyRepositoryTest.php b/tests/Unit/TaxonomyRepositoryTest.php new file mode 100644 index 0000000..290f4b0 --- /dev/null +++ b/tests/Unit/TaxonomyRepositoryTest.php @@ -0,0 +1,47 @@ + [ + \Tests\Helpers\TestTaxonomy::class, + ]]); + + $repository = new TaxonomyRepository(app('stache')); + $taxonomies = $repository->all(); + + expect($taxonomies->has('test_taxonomy'))->toBeTrue(); +}); + +test('::findByHandle finds builder-registered taxonomy', function (): void { + config(['statamic.builder.taxonomies' => [ + \Tests\Helpers\TestTaxonomy::class, + ]]); + + $repository = new TaxonomyRepository(app('stache')); + $taxonomy = $repository->findByHandle('test_taxonomy'); + + expect($taxonomy)->not()->toBeNull(); + expect($taxonomy->handle())->toBe('test_taxonomy'); +}); + +test('getTaxonomyByHandle returns null for non-existent taxonomy', function (): void { + config(['statamic.builder.taxonomies' => []]); + + $repository = new TaxonomyRepository(app('stache')); + $result = $repository->getTaxonomyByHandle('non-existent'); + + expect($result)->toBeNull(); +}); + +test('getTaxonomyByHandle returns taxonomy instance for valid handle', function (): void { + config(['statamic.builder.taxonomies' => [ + \Tests\Helpers\TestTaxonomy::class, + ]]); + + $repository = new TaxonomyRepository(app('stache')); + $result = $repository->getTaxonomyByHandle('test_taxonomy'); + + expect($result)->not()->toBeNull() + ->and($result)->toBeInstanceOf(\Tests\Helpers\TestTaxonomy::class); +});