diff --git a/content/collections/docs/4-to-5.md b/content/collections/docs/4-to-5.md new file mode 100644 index 000000000..0501b3829 --- /dev/null +++ b/content/collections/docs/4-to-5.md @@ -0,0 +1,416 @@ +--- +id: 91e8f239-2f99-47bc-b4dd-3518cd3e36ae +blueprint: page +title: 'Upgrade from 4 to 5' +intro: 'A guide for upgrading from 4 to 5. For most sites (those running Laravel > 9), the process will take less than 5 minutes.' +template: page +--- +## Overview + +First read through this guide to see if there's anything that you might need to adjust. While there are many items on this page, a majority of them only apply to addons or custom code. We've noted who each item would apply to so you can more easily scan through the changes. + +### Upgrade using Composer + +In your `composer.json`, change the `statamic/cms` requirement: + +```json +"statamic/cms": "^4.0" // [tl!--] +"statamic/cms": "^5.0" // [tl!++] +``` + +Then run: + +``` shell +composer update statamic/cms --with-dependencies +``` + +## High impact changes + +### PHP and Laravel support +**Affects apps using PHP < 8.1 or Laravel < 10.** + +- The minimum version of PHP is now 8.1. +- The minimum version of Laravel is now 10. + +We highly recommend upgrading all the way to Laravel 11 and PHP 8.3. + +:::tip +If you want to (semi-)automate the Laravel upgrade process, we recommend using [Laravel Shift](https://laravelshift.com/discounts/statamic-1983) (use that link for a special 19.83% discount 🤘). +::: + +### Site configuration changes +**Affects everyone.** + +_Note that Statamic will attempt to migrate this for you automatically during the upgrade._ + +The site config has been moved from `config/statamic/sites.php` into `resources/sites.yaml`. This allows you to manage your sites from the Control Panel. + +```php +// config/statamic/sites.php +return [ // [tl! --:start] + 'sites' => [ + 'default' => [ + 'name' => 'First Site', + 'url' => config('app.url'), + 'locale' => 'en_US', + ], + 'two' => [ + 'name' => 'Second Site', + 'url' => config('app.url').'/fr/', + 'locale' => 'fr_FR', + ] + ] +]; // [tl! --:end] +``` + +```yaml +# resources/sites.yaml +default: # [tl! ++:start] + name: First Site + url: '{{ config:app:url }}' + locale: en_US +two: + name: Second Site + url: '{{ config:app:url }}/fr/' + locale: fr_FR # [tl! ++:end] +``` + +_**Note:** Text direction is now also automatic, [based on each site's language](/multi-site#text-direction). You do not need to migrate `direction` values to your `resources/sites.yaml`._ + +There is now also a new `multisite` boolean in `config/statamic/system.php`. Previously, the multi-site feature would be considered "enabled" as soon as you configured a second site. Now there is an explicit option to enable it. + +This should be set to `true` if you previously had 2 or more sites configured. + +```php +// config/statamic/system.php +'multisite' => true, +``` + +## Medium impact changes + +### Blueprint default value usage +**Affects apps that have `default` defined in their blueprints or fieldsets.** + +In v4, setting a `default` value for a field would only make it show up in the respective publish forms. In v5, these values will actually be used where appropriate, such as within front-end templates. + +```yaml +handle: myfield +field: + type: text + default: my default value +``` +```yaml +title: My Entry +# the "myfield" is missing +``` +```antlers +{{ if myfield }}yes{{ else }}no{{ /if }}: {{ myfield }} + +v4 outputs: "no: " {{# [tl! --] #}} +v5 outputs: "yes: my default value" {{# [tl! ++] #}} +``` + +In most cases, this is what you would have expected to happen anyway. + +### Site methods +**Affects apps or addons using the `Site::hasMultiple()` method.** + +Continuing from the multi-site configuration changes above, there are now two separate methods for determining multi-site state. + +- The new `Site::multiEnabled()` method will return true if the `multisite` boolean in `system.php` is enabled. +- The existing `Site::hasMultiple()` method will return `true` if at least two sites are configured. + +You should decide whether each existing usage of `Site::hasMultiple()` should imply that the feature is enabled entirely, or if you need to actually count the number of sites. + +### Laravel Helpers package has been removed +**Affects apps or addons relying on methods from that package in custom code.** + +The `laravel/helpers` package provided support for the older global-style string/array/misc functions like `array_get`, `str_contains`, `snake_case`, `ends_with`, etc. You will now need to either require this package yourself, or update to use the underlying methods. + +For example: + +```php +namespace App\Example; + +use Statamic\Support\Arr; // [tl! ++,**] +use Statamic\Support\Str; // [tl! ++,**] + +class Example +{ + function example() + { + array_get($arr, $key); // [tl! --,**] + Arr::get($arr, $key); // [tl! ++,**] + + str_start($str, $prefix); // [tl! --,**] + Str::start($str, $prefix); // [tl! ++,**] + + ends_with($str, $needle); // [tl! --,**] + Str::endsWith($str, $needle); // [tl! ++,**] + } +} +``` + +We recommend making the code changes. However, if you just want to require the package: + +```sh +composer require laravel/helpers +``` + +### Statamic will now use your app's default pagination view +**Affects apps using the `auto_links` pagination variable in templates.** + +Previously, Statamic used the `pagination::default` view to rendering pagination links. In Statamic 5, it will use your app's default pagination view, typically `pagination::tailwind`. + +To avoid making code changes, you may wish to change the default view back to `pagination::default`: + +```php +// app/Providers/AppServiceProvider.php + +use Illuminate\Pagination\Paginator; // [tl! ++] + +public function boot(): void +{ + Paginator::defaultView('pagination::default'); // [tl! ++] +} +``` + +### Updated `please` file for Laravel 11 +**Affects apps upgrading to Laravel 11 and the new application skeleton (including all upgrades via Laravel Shift).** + +Previously, our `please` command line utility assumed an `app/Console/Kernel.php` file existed in your application. However, with the introduction of Laravel 11, this file is no longer included with the new application structure. + +When upgrading apps to Laravel 11 with the new application skeleton, you'll need to update the `please` file in your app's root directory: + +```php +#!/usr/bin/env php +handleCommand(new ArgvInput); + +exit($status); +``` + +## Low impact changes + +### Regex Antlers Parser has been removed +**Affects apps that are still using the `regex` parser.** + +The new Antlers parser was introduced in 3.3 and was made the default in 3.4. + +You would have been using the old Parser if either of these apply: +- In `config/statamic/antlers.php`, the `version` was set to `regex`. +- The `antlers.php` file doesn't exist at all. + +The newer parser should be backwards compatible but if you encounter any errors, you may need to check the [docs](/antlers). + +### Validation rule changes +**Affects apps using `unique_entry_value`, `unique_term_value`, or `unique_user_value` rules.** + +_Note that assuming you haven't done anything too unusual, the following changes may have been performed automatically by Statamic during the update process._ + +We have updated our custom validation rules to use the more modern Laravel syntax. This means dropping the string based aliases in favor of classes. + +```yaml +validate: + - 'unique_entry_value:{collection},{id},{site}' #[tl!--] + - 'new \Statamic\Rules\UniqueEntryValue({collection},{id},{site})' #[tl!++] + + - 'unique_term_value:{taxonomy},{id},{site}' #[tl!--] + - 'new \Statamic\Rules\UniqueTermValue({taxonomy},{id},{site})' #[tl!++] + + - 'unique_user_value:{id}' #[tl!--] + - 'new \Statamic\Rules\UniqueUserValue({id})' #[tl!++] +``` + +### Antlers sanitization +**Affects apps or addons using the `sanitize` modifier or the `Html::sanitize` method.** + +The `sanitize` modifier (and `Html::sanitize()` method) method has changed under the hood from using `htmlentities` to `htmlspecialchars`. + +This change allows for things like accent characters (ü, í, etc) to remain unescaped. This is likely what you want to happen anyway, but if you have a reason for them to be converted, you should use `entities` modifier or `Html::entities()` method respectively. + +### Seed removed from `shuffle` modifier +**Affects apps using the shuffle modifier with an argument.** + +In Laravel 11, the underlying randomization technique was made more secure and no longer supports seeds. If you need to support seeds, you will need to use a custom modifier. + +``` +{{ my_array | shuffle:123 }} {{# [tl! --] #}} +{{ my_array | custom_shuffle_with_seed:123 }} {{# [tl! ++] #}} +``` + +The shuffle modifier without an argument will continue to work without any modification needed. + +### The `modify_date` modifier is now immutable +**Affects apps using the modify_date modifier.** + +In Statamic 4, the `modify_date` modifier would modify date variable which would then be reflected elsewhere in your template. + +In Statamic 5, this is fixed, but if you were relying on this incorrect behavior you will need to handle it. + +```antlers +{{ date }} // 1st of may +{{ date | modify_date('+1 day') }} // 2nd of may +{{ date }} // 2nd of may {{# [tl! --] #}} +{{ date }} // 1st of may {{# [tl! ++] #}} +``` + +### The `svg` tag sanitizes by default +**Affects apps that use the `svg` tag.** + +The `{{ svg }}` will now sanitize the output by default. This meant things like JavaScript or other valid but potentially insecure elements will be filtered out. + +For most people this won't be a problem but if you rely on this advanced SVG features, you may want to disable it. + +```antlers +{{ svg + src="foo.svg" + sanitize="false" {{# [tl! ++] #}} +}} +``` + +Alternatively, you can opt out of it completely in a service provider: + +```php +public function boot() +{ + \Statamic\Tags\Svg::disableSanitization(); // [tl! ++] +} +``` + +### Bard JS value is now an object +**Affects apps or addons that are manually targeting Bard's value in JS** + +Previously, to prevent issues with how Laravel would trim whitespace on submitted strings, Bard's value would be a JSON stringified version of an object. In Statamic 5, it will just be the object. + +```js +let bardValue = JSON.parse(getBardValue()); // [tl! --] +let bardValue = getBardValue(); // [tl! ++] +``` + +### User roles inherit from groups +**Affects apps or addons using the `roles`/`hasRole` methods on the `User` class, or the `user:is`/`is` tags.** + +In v4, the `User` class's `roles` method would only return roles defined explicitly on the user. It would not return roles inherited through any assigned groups. + +This affected calling the `roles` method directly, the `hasRole` method, or the `user:is` and `is` tags. + +This was a common point of confusion. So in v5, including the inherited roles is the more "default" behavior. + +```php +// To get explicit roles... +$user->roles(); // [tl! --] +$user->explicitRoles(); // [tl! ++] + +// To check if a user has a role explicitly defined... +$user->hasRole($role); // [tl! --] +$user->explicitRoles()->has($role->handle()); // [tl! ++] + +// To set explicit roles... +$user->roles($roles); // [tl! --] +$user->explicitRoles($roles); // [tl! ++] + +// To get all roles, including ones through user groups... +getAllRoles($user); // (It was complicated) [tl! --] +$user->roles(); // [tl! ++] + +// To check if a user has a role, including ones through user groups... +getAllRoles($user)->has($role->handle()); // (It was complicated) [tl! --] +$user->hasRole($role); // [tl! ++] +``` + + +### Misc class method changes +The following methods have been removed: +- `Statamic\Entries\Collection::revisions()` removed. Use `revisionsEnabled()`. + +The following interfaces have added `findOrFail()` methods: +- `Statamic\Contracts\Assets\AssetContainerRepository` +- `Statamic\Contracts\Assets\AssetRepository` +- `Statamic\Contracts\Auth\UserRepository` +- `Statamic\Contracts\Entries\CollectionRepository` +- `Statamic\Contracts\Entries\EntryRepository` +- `Statamic\Contracts\Globals\GlobalVariablesRepository` +- `Statamic\Contracts\Structures\NavigationRepository` +- `Statamic\Contracts\Taxonomies\TermRepository` + +The following methods have changed: +- `Statamic\StaticCaching\Cacher::getCachedPage()` now returns a `Statamic\StaticCaching\Page`. + +## Zero impact changes + +These are items that you can completely ignore. They are suggestions on how to improve your code using newly added features. + +### JS Slug generation + +The `$slugify` JS API has been deprecated. This could be a good time to use the new slug helpers. + +If you are generating simple slug/handles, you can use the global helper: + +```js +let slug = this.$slugify('Foo Bar'); // foo-bar [tl! --] +let slug = str_slug('Foo Bar'); // foo-bar [tl! ++] + +let handle = this.$slugify('Foo Bar', '_'); // foo_bar [tl! --] +let handle = snake_case('Foo Bar'); // foo_bar [tl! ++] +``` + +If your slugs need to factor in language logic (like an entry's slug would), then you can use the new server side feature: + +```js +let slug = getSlug('Foo Bar'); // [tl! --:start] + +function getSlug(value) { + return this.$slugify(value); +} // [tl! --:end] + +let slug = await getSlug('Foo Bar'); // [tl! ++:start] + +async function getSlug(value) { + return this.$slug.async().create('Foo Bar'); +} // [tl! ++:end] +``` + + +### Addon test case + +If you have an addon, there's a good chance your `TestCase` is a bit complicated. + +You should be able to extend the new `AddonTestCase` and specify your service provider in favor of manually wiring up all the Testbench bits. + +```php +use Orchestra\Testbench\TestCase as OrchestraTestCase; // [tl! --] +use Statamic\Testing\AddonTestCase; // [tl! ++] + +abstract class TestCase extends OrchestraTestCase // [tl! --] +abstract class TestCase extends AddonTestCase // [tl! ++] +{ + protected string $addonServiceProvider = YourServiceProvider::class; // [tl! ++] + + protected function getPackageProviders($app) // [tl! --:start] + { + return [ + GraphQLServiceProvider::class, + StatamicServiceProvider::class, + YourServiceProvider::class, + ]; + } + + // etc... [tl! --:end] +} +``` diff --git a/content/collections/docs/antlers-legacy.md b/content/collections/docs/antlers-legacy.md deleted file mode 100644 index f5b3982d5..000000000 --- a/content/collections/docs/antlers-legacy.md +++ /dev/null @@ -1,426 +0,0 @@ ---- -id: dcf80ee6-209e-45aa-af42-46bbe01996e2 -blueprint: page -title: 'Antlers Templates (Legacy Regex)' -intro: |- - Antlers is a simple and powerful templating engine provided with Statamic. It can fetch and filter content, display and modify data, tap into core features like user authentication and search, and handle complex logic. - :::warning This parser has been retired - We have a [brand new, completely rewritten Antlers Parser](/antlers) jam packed with new features, performance improvements, and bug fixes. Make sure to use it if you're on Statamic 3.3 or higher. - ::: -template: page -nav_title: 'Antlers Templates (Legacy)' ---- -## Overview - -Antlers view files are often called templates. Any files in your `resources/views` directory using an `.antlers.html` file extension will be parsed with the Antlers engine. - -:::tip -The `.antlers.html` extension is important. Without it, your template will be rendered as **unparsed, static HTML**. -::: - -## Antlers Syntax - -Antlers adds capabilities on top of HTML through the use of curly brace expressions. Those curly braces – often called double mustaches or squiggly gigglies – look a whole lot like _antlers_ to us, hence the name. - -```antlers -{{ hello_world }} -``` - -Before getting into listing all the things that happen _inside_ an Antlers expression, lets take a moment to establish the rules for properly formatting one. - -### Formatting Rules - -1. Each set of curly braces **must** stay together always, like Kenan & Kel or Wayne & Garth. -2. Expressions are **case sensitive**. -3. Hyphens and underscores are **not** interchangeable. -4. Whitespace between the curly braces and inner text is **recommended**, but optional. -5. You **can** break up an expression onto multiple lines. - -Consistency is important. We recommend using single spaces between braces, lowercase variable names, and underscores as word separators. Picking your style and stick to it. Future you will thank you, but don't expect a postcard. - -``` antlers -This is great! -{{ perfectenschlag }} - -This is allowed. -{{squished}} - -This can make sense when you have lots of parameters. -{{ - testimonials - limit="5" - order="username" -}} - -This is terrible in every possible way. -{{play-sad_Trombone }} -``` - -## Displaying Data - -You can display data passed into your Antlers views by wrapping the variable in double curly braces. For example, given the following data: - -``` yaml ---- -title: DJ Jazzy Jeff & The Fresh Prince -songs: - - Boom! Shake the Room - - Summertime - - Just Cruisin' ---- -``` - -You can display the contents of the `title` variable like this: - -``` antlers -

{{ title }}

-``` - -``` html -

DJ Jazzy Jeff & The Fresh Prince

-``` - -### Arrays -Arrays are a collection of elements (values or variables). You can loop through the elements of the array using the `{{ value }}` variable, or reach in and pluck out specific elements by their index. - -#### Looping - -``` antlers - -``` - -``` html - -``` - -#### Plucking - -``` antlers -

Time to {{ songs:0 }} cuz we're {{ songs:2 }}.

-``` - -``` html -

Time to Boom! Shake the Room cuz we're Just Crusin'.

-``` - -### Dictionaries -Dictionaries are represented in YAML by nested key:value pairs, _inside_ another variable name. These are sometimes called element maps, or associative arrays. - -``` yaml -mailing_address: - address: 123 Foo Ave - city: Barville - province: West Exampleton - country: Docsylvania -``` - -#### Accessing Data -You can access the keys inside the dictionary by "gluing" the parent/child keys together you want to traverse through, much like breadcrumbs. - -``` antlers -I live in {{ mailing_address:city }}. -``` - -### Multi-Dimensional Arrays -More complex data is stored in objects or arrays inside arrays. This is usually called a multi-dimensional array. - -``` yaml -skaters: - - - name: Tony Hawk - style: Vert - - - name: Rodney Mullen - style: Street -``` - -If you know the names of the variables inside the array, you can loop through the items and access their variables. - -``` antlers -{{ skaters }} -
-

{{ name }}

-

{{ style }}

-
-{{ /skaters }} -``` - -``` html -
-

Tony Hawk

-

Vert

-
-
-

Rodney Mullen

-

Street

-
-``` - -### Dynamic Access -If you don't know the names of the keys inside the array – which can happen when working with dynamic or user submitted data – you can access the elements dynamically using variables for the key names. - -Using the mailing list example, we could use a `field` variable to pluck out specific keys. - -``` md ---- -field: country -mailing_address: - address: 123 Scary Mansion Lane - country: Docsylvania - city: Arteefem - postal_code: RU 7337 ---- -{{ mailing_address[field] }} - -// Output -Docsylvania -``` - -You can use this same syntax with literal key names as well. - -``` -// These are equivalent -{{ mailing_address:city }} -{{ mailing_address['city'] }} -``` - -You can combine literal and dynamic keys and get real fancy if you need to. - -``` -{{ complex_data:3[field]['title'] }} -``` - -## Modifying Data - -The way data is stored is not always the way you want it presented. The simplest way of modifying data is through the use of variable modifiers. - -### Variable Modifiers - -Each variable modifier is a function that accepts the value of a variable, manipulates it in some way, and returns it. Modifiers can be chained and are executed in sequence, from left to right inside the Antlers statement. - -Let's look at an example. - -``` ---- -title: Nickelodeon Studios ---- - -// NICKELODEON STUDIOS rocks! -

{{ title | upper | ensure_right:rocks! }}

- -// NICKELODEON STUDIOS ROCKS! (order matters) -

{{ title | ensure_right:rocks! | upper }}

-``` - -There are over 130 built in [modifiers][modifiers] that do everything from find and replace to automatically write HTML for you. - -Modifiers can be written in two styles in order to support different use cases and improve readability. - -### String/Shorthand Style - -Modifiers are separated by `|` pipe delimiters. Parameters are delimited by `:` colons. This is usually the recommended style while working with string variables, conditions, and when you don't need to pass multi-word arguments in a parameter. - -``` -{{ string_var | modifier_1 | modifier_2:param1:param2 }} -``` - -If you use this string/shorthand style on arrays, you need to make sure the closing tag matches the opening one **exactly**. You may notice this looks terrible and is quite annoying. That's why we also have the... - -### Array/Tag Parameter Style - -Modifiers on array variables are formatted like Tag parameters. Parameters are separated with `|` pipes. You can’t use modifiers in conditions when you format them this way. - -``` -{{ array_var modifier="param1|param2" }} - // Magic goes here -{{ /array_var }} -``` - -:::warning -You **cannot** mix and match modifier styles. -ie. This totally won't work: `{{ var | foo | bar="baz" }}` -::: - -### Escaping Data - -By default, Antlers `{{ }}` statements are _not_ automatically escaped. Because content is often stored along with HTML markup, this is the most logical default behavior. **But remember: never render user-submitted data without escaping it first!** - -The simplest way to escape data is by using the [sanitize](/modifiers/sanitize) modifier. This will run the data through PHP's `htmlspecialchars()` function and prevent XSS attacks and other potential nastiness. - -``` -{{ user_submitted_content | sanitize }} -``` - -## Logic & Conditions {#conditions} - -Antlers can handle logic and conditional statements, just like native PHP. You can use logic to check settings, variables, or even user data and alter the output of your page. - -You may construct conditional statements using the `if`, `else`, `elseif`, `unless` keywords, and use any of PHP's [comparison](https://www.php.net/manual/en/language.operators.comparison.php) and [logical](https://www.php.net/manual/en/language.operators.logical.php) operators. - -``` -{{ if songs === 1 }} -

There is a song!

-{{ elseif songs > 100 }} -

There are lots of songs!

-{{ elseif songs }} -

There are songs. -{{ else }} -

There are no songs.

-{{ /if }} -``` - - -
Antlers variables are null by default. Keep your logic statements simple and skip checking for existence altogether.
- -### Shorthand Conditions (Ternary) {#ternary} - -Basic ternary operators will let you write a simple if/else statement all in one line. - -``` -// Basic: verbose -This item is {{ if is_sold }}sold{{ else }}available{{ /if }}. - -// Ternary: nice and short -This item is {{ is_sold ? "sold" : "available" }}. -``` - -Learn more about [ternary operators][ternary] in PHP. - -### Modifiers Inside Conditions - -If you want to manipulate a variable with [modifiers](/modifiers) before evaluating a condition, wrap the expression in (parenthesis). - -``` -{{ if (number_of_bedrooms | count) > 10 }} -

Who are you, Dwayne Johnson?

-{{ /if }} -``` - -### Variable Fallbacks (Null Coalescence) {#null-coalescence} - -When all you need to do is display a variable and set a fallback when it’s null, use the null coalescence operator (`??`). - -``` -{{ meta_title ?? title ?? "No Title Set" }} -``` - -### Conditional Variable Fallbacks - -What if you want to combine an `is set` check with a ternary operator? No problem. - -``` -// Short and sweet -{{ show_title ?= title }} - -// Longer and bitterer -{{ if show_title }}{{ title }}{{ /if }} -``` - -### Using Tags in Conditions - -Yes, you can even use tags in conditions. When working with [tags][tags] instead of variables, you **must** wrap the tag in a pair of additional single braces to tell the parser to run that logic first. - -``` -{{ if {session:some_var} == "Statamic is rad!" }} - ... -{{ /if }} -``` - -## Code Comments {#comments} - -Antlers also allows you to define comments in your views. However, unlike HTML comments, Antlers are not included in the rendered HTML. You can use these comments to "turn off" chunks of code, document your work, or leave notes for yourself and other developers. - -``` -{{# Remember to replace the lorem ipsum this time. #}} -``` - -## Tags - -[Tags][tags] are the primary method for implementing most of Statamic's dynamic features, like search, forms, nav building, pagination, collection listing, filtering, image resizing, and so on. - -Tags often look quite similar to variables, so it pays to learn the list of available [Tags][tags] so you don't mix them up or create conflicts. - -### Variables Inside Tag Parameters - -There are two ways to use variables _inside_ a Tag's parameters. - -You can use **dynamic binding** to pass the value of a variable via its name. - -``` -{{ nav :from="segment_1" }} -``` - -Alternatively, you can use **string interpolation** and reference any variables with _single braces_. This method lets you concatenate a string giving you the ability assemble arguments out of various parts. Like Frankenstein's monster. - -``` -{{ nav from="{segment_1}/{segment_2}" }} -``` - -### Modifiers Inside Parameters - -If using a variable inside of a Tag is nice, using a variable with a modifier inside of a Tag is better. Or more complicated. Either way, it works exactly as you’d expect with one small caveat: When using a modifier inside of a Tag, no whitespace is allowed between variables, pipes, and modifiers. Collapse that stuff. - -``` -// Totally fine. -{{ partially:broken src="{featured_image|url}" }} - -// Totally not. -{{ partially:broken src="{ featured_image | url }" }} -``` - -## Prevent Parsing - -You may find you need to prevent Antlers statements from being parsed. This is common if you're using a JavaScript library like [Vue.js][vue], or perhaps you want to display code examples (like we do in these docs). In either case, you have a few options. - -First, you may use an `@` symbol to tell Antlers to leave it alone like a jellyfish on the beach. The `@` symbol will be stripped out automatically leaving nothing but your expression behind. - -``` -Hey, look at that @{{ noun }}! -``` - -### The `noparse` Tag - -Use this method if you need to prevent entire code blocks from being parsed. - -``` -{{ noparse }} - Welcome to {{ fast_food_chain }}, - home of the {{ fast_food_chain_specialty_item }}, - can I take your order? -{{ /noparse }} -``` - -## Using Antlers in Content - -By default, Antlers expressions and tags are **not** parsed inside your content. This is for performance and security reasons. - -For example, a guest author with limited access to the control panel could conceivably write some template code to fetch and display published/private content from a collection they don't have access to. - -If this isn't a concern of yours, you can enable Antlers parsing on a per-field basis by setting `antlers: true` in your blueprint. - -## Using PHP in Antlers - -PHP is disabled by default, but if you change your view's file extension from `.antlers.html` to `.antlers.php`, you can write all the PHP you want in that template. - -## IDEs & Syntax Highlighters - -Syntax highlighting packages are available for most of the popular IDEs. Make life sweeter, like they do with tea in the south. - -- [Antlers Toolbox for VS Code](https://antlers.dev/) -- [Antlers Language Support for PHP Storm](https://plugins.jetbrains.com/plugin/19203-antlers-language-support) -- [Antlers for Atom](https://github.com/addisonhall/language-antlers) -- [Antlers for Sublime](https://github.com/addisonhall/antlers-statamic-sublime-syntax) - - -[ternary]: https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.ternary -[vue]: https://vuejs.org -[modifiers]: /modifiers -[tags]: /tags \ No newline at end of file diff --git a/content/collections/docs/antlers.md b/content/collections/docs/antlers.md index 1329bb34b..069f9b93d 100644 --- a/content/collections/docs/antlers.md +++ b/content/collections/docs/antlers.md @@ -4,9 +4,6 @@ blueprint: page title: 'Antlers Templates' intro: |- Antlers is a simple and powerful templating engine provided with Statamic. It can fetch and filter content, display, modify, and set variables, tap into core features like user authentication and search, and handle complex logic. Coming from Laravel and want to stick to Blade? [We got you covered](/blade). - :::tip Hot Tip - For sites running Statamic 3.2 and older you'll need to use the [legacy Antlers parser](/antlers-legacy). For all other projects, keep reading. You're in the right place. - ::: template: page --- ## Overview @@ -32,13 +29,22 @@ This is a very simple Antlers tag: ### Configuring -You can configure advanced settings (or switch to the [legacy Antlers parser](/antlers-legacy)) in `config/statamic/antlers.php`. The `runtime` version is the fresh new default parser as of Statamic 3.4, as documented on this very page. +You can configure advanced settings, like guarded variables, tags & modifiers in `config/statamic/antlers.php`. ```php // config/statamic/antlers.php return [ - 'version' => 'runtime', - // ... + 'guardedVariables' => [ + 'config.app.key', + ], + + 'guardedTags' => [ + // + ], + + 'guardedModifiers' => [ + // + ], ]; ``` @@ -375,10 +381,6 @@ There are more than 150 built-in [modifiers](/reference/modifiers) that can do a You can even create [Macros](/modifiers/macro) to combine sets of often used modifiers into one, new reusable one. -#### Legacy Syntax - -The New Antlers Parser still supports what we're now calling the "[Legacy Syntax](/antlers-legacy#stringshorthand-style)" styles, and will continue to do so until Statamic 4.0. - ### Creating Variables You can now set variables by using the assignment operator, `=`. @@ -1283,6 +1285,14 @@ The `@` can also be used to escape individual braces within tag parameters or st // "string {foo} bar" ``` +### Tag parameters + +You may ignore the contents of tag parameters by prefixing the parameter with a backslash. This could be useful allow you to avoid having to escape each curly brace like the example above if you are providing some JS/JSON in a parameter: + +``` +{{ form:create \x-data="{ submittable: false }" }} +``` + ### The `noparse` Tag Use this method if you need to prevent entire code blocks from being parsed. diff --git a/content/collections/docs/debugging.md b/content/collections/docs/debugging.md index 9c5abe8bc..a94d00b79 100644 --- a/content/collections/docs/debugging.md +++ b/content/collections/docs/debugging.md @@ -20,6 +20,18 @@ Statamic will try to detect why you're receiving a specific exception and provid + +## Fake SQL Queries + +By default, Statamic doesn't use a database, so our query builders don't actually execute SQL queries. However, we "fake" the queries so that they appear in your preferred debugging tools like [Ray](https://myray.app) or Debugbar (more on that below). + +They are enabled when you're in debug mode, but if you'd like to disable them you can do so in `config/statamic/system.php`: + +```php +'fake_sql_queries' => false, +``` + + ## Debug Bar The debug bar is a convenient way to explore many of the things happening in any given page request. You can see data Statamic is fetching, which views are being rendered, information on the current route, available variables, user's session, request data, and more. diff --git a/content/collections/docs/forms.md b/content/collections/docs/forms.md index 348050770..4ecae2460 100644 --- a/content/collections/docs/forms.md +++ b/content/collections/docs/forms.md @@ -365,6 +365,8 @@ email: # other settings here ``` +If you don't want the attachments to be kept around on your server, you should pick the `files` fieldtype option explained in the [File Uploads](#file-uploads) section. + ### Using Markdown Mailable Templates Laravel allows you to create email templates [using Markdown](https://laravel.com/docs/mail#markdown-mailables). It's pretty simple to wire these up with your form emails: @@ -413,15 +415,15 @@ You can customize the components further by reviewing the [Laravel documentation Sometimes your fans want to show you things they've created, like scissor-cut love letters and innocent selfies with cats. No problem! File input types to the rescue. Inform Statamic you intend to collect files, specify where you'd like the uploads to go, and whether you'd like them to simply be placed in a directory somewhere, or become reusable Assets. -First up, add `files="true"` to your form tag. (This will add `enctype="multipart/form-data"` to the generated `
` tag. That's always so difficult to remember.) +First, add a file upload field to your blueprint: +- Add an `assets` field if you want the uploaded files to be stored in one of your asset containers. +- Add a `files` field if you're only wanting to attach the uploads to the email. Anything uploaded using this fieldtype will be attached and then deleted after the emails are sent. -``` -{{ form:create formset="contact" files="true" }} -... -{{ /form:create }} -``` +Then decide if you need single or multiple files to be uploaded. + +### Single files -Then add an `assets` field to your blueprint, with a `max_files` setting of `1`: +On your field, add a `max_files` setting of `1`: ``` @@ -443,7 +445,7 @@ You have two methods available to you: First, You can create separate fields for each upload. This is useful if each has a separate purpose, like Resume, Cover Letter, and Headshot. You'll need to explicitly create each and every one in your formset. -Or, you can enable multiple files on one field by dropping the `max_files` setting on your assets field, and using array syntax on your input by adding a set of square brackets to the `name` attribute: +Or, you can enable multiple files on one field by dropping the `max_files` setting on your field, and using array syntax on your input by adding a set of square brackets to the `name` attribute: ``` diff --git a/content/collections/docs/laravel.md b/content/collections/docs/laravel.md index d281e0554..43169138e 100644 --- a/content/collections/docs/laravel.md +++ b/content/collections/docs/laravel.md @@ -24,7 +24,7 @@ You'll get a bunch of things automatically set up for you, like a pages collecti ## Supported Versions of Laravel -**Laravel 9 and Laravel 10 are supported with Statamic 4+.** If you need Laravel 8 support, you can still use Statamic 3.x. +**Laravel 10 & Laravel 11 are supported with Statamic 5.** If you need Laravel 9 support, you can still use Statamic 4.x. ## Install Statamic diff --git a/content/collections/docs/licensing.md b/content/collections/docs/licensing.md index 9f5f16b36..5ce4371ff 100644 --- a/content/collections/docs/licensing.md +++ b/content/collections/docs/licensing.md @@ -62,6 +62,8 @@ Statamic pings The Outpost (our validation web service) on a regular basis. The This happens once per hour, and only when logged into the control panel. Changing your license key setting will trigger an immediate ping to the The Outpost. Tampering with outgoing API call will cause Statamic to consider your license invalid. If that happens, you'll need to open a [support request][support] to reinstate your license. +If you need to run Statamic in an environment without an internet connection, please [contact support](https://statamic.com/support). + ## One License Per Site Each license entitles you to run one production installation. You will need to specify the domains you plan to use from the "Sites" area of your Statamic Account. Domain are treated as wildcards so you can use subdomains for locales, testing, and other purposes. diff --git a/content/collections/docs/multi-site.md b/content/collections/docs/multi-site.md index 1f7a37c05..64aa4df6b 100644 --- a/content/collections/docs/multi-site.md +++ b/content/collections/docs/multi-site.md @@ -17,79 +17,111 @@ Each site can have different base URLs: - subdomains: `example.com` and `fr.example.com` - subdirectories: `example.com` and `example.com/fr/` -[More details on how to convert to a multi-site setup](/tips/converting-from-single-to-multi-site) +::: tip +Every Statamic install needs at least one site. Building zero sites is a bad way to build a website and clients will probably challenge any invoices. +::: + +### Converting Existing Content to Multi-Site + +The simplest way to convert existing content to multi-site friendly structure is to run the automated command: + +``` shell +php please multisite +``` + +Read more on [converting to a multi-site setup](/tips/converting-from-single-to-multi-site). ## Configuration -Let's look at a full site configuration and then we'll explore all of its options. +### Enabling Multi-Site + +First, enable `multisite` in your `config/statamic/system.php`: ``` php -# config/statamic/sites.php - -return [ - 'sites' => [ - 'default' => [ - 'name' => config('app.name'), - 'locale' => 'en_US', - 'url' => '/', - 'direction' => 'ltr', - 'lang' => 'en', - ] - ] -]; +'multisite' => true, ``` -### Sites -Every Statamic install needs at least one site. Building zero sites is a bad way to build a website and clients will probably challenge any invoices. +### Adding New Sites -### Locale -Each site has a `locale` used to format region-specific data (like date strings, number formats, etc). This should correspond to the server's locale. By default Statamic will use English – United States (`en_US`). +Next, you can add new sites through the control panel: -:::tip -To see the list of installed locales on your system or server, run the command `locale -a`. -::: +
+ Configure sites page in control panel +
-### Language -Statamic's control panel has been translated into more than a dozen languages. The language translations files live in `resources/lang`. +Or directly in your `resources/sites.yaml` file: -You may specify which language translation to be used for each site with the `lang` setting. If you leave it off, it'll use the short version of the `locale`. e.g. If the locale is `en_US`, the lang will be `en`. +``` yaml +default: + name: First Site + url: / + locale: en_US +second: + name: Second Site + url: /second/ + locale: en_US +``` + +## Available Options -```php -'sites' => [ - 'de' => [ - 'name' => 'Deutsche', - 'locale' => 'de_DE', - // 'lang' => 'de', // Not needed as 'de' is implied. - ], - 'de_ch' => [ - 'name' => 'Deutsche (Switzerland)', - 'locale' => 'de_CH', - 'lang' => 'de_CH', // We want the de_CH language, not de. - ] -] +Let's look at a full site configuration and then we'll explore all of its options. + +``` yaml +# resources/sites.yaml + +en: + name: English + url: / + locale: en_US + lang: en + attributes: + theme: standard ``` -Note that both Statamic and Laravel don't ship with frontend language translations out of the box. You have to provide your own string files for this. There is a great package called [Laravel Lang](https://github.com/Laravel-Lang/lang) containing over 75 languages that can help you out with this. +### Handle + +Each site is keyed by its `handle`, which is important for directory structure, as well as referencing sites in collection configs, etc. throughout your site. Changing this is non-trivial, and you should be careful if you already have established content in this site. Read more about [renaming sites](#renaming-sites). + +``` yaml +en: # <- This is your site handle + name: English + +``` +### Name + +Each site has a `name`, which is a display-friendly representation of your site's name mostly seen within control panel UI. Changing this does not affect content relations. + +``` yaml +en: + name: English +``` + +::: tip +You'll notice the default site dynamically references a [config variable](/variables/config), but feel free to change this! + +``` yaml +default: + name: '{{ config:app:name }}' +``` +::: ### URL -URL is required to define the root domain Statamic will serve and generate all URLs relative to. The default `url` is `/`, which is portable and works fine in most typical sites. Statamic uses a little magic to work out what a full URL is based on the domain the site is running on. + +Each site requires a URL to define the root domain Statamic will serve and generate all URLs relative to. The default `url` is `/`, which is portable and works fine in most typical sites. Statamic uses a little magic to work out what a full URL is based on the domain the site is running on. :::best-practice -It can be a good idea to change this to a **fully qualified, absolute URL**. This ensures that server/environment configurations or external quirks don't interfere with that "magic". Using an environment variable is an ideal solution here. - -```php -'sites' => [ - 'en' => [ - // ... - 'url' => env('APP_URL') - ], - 'fr' => [ - // ... - 'url' => env('APP_URL').'fr/' - ] -] +It can be a good idea to change this to a **fully qualified, absolute URL**. This ensures that server/environment configurations or external quirks don't interfere with that "magic". + +```yaml +en: + # ... + url: '{{ config:app:url }}' +fr: + # ... + url: '{{ config:app:url }}/fr/' ``` +By default, this is linked to your `APP_URL` environment variable, which allows you to control the exact URL by environment: ```env # production APP_URL=https://mysite.com/ @@ -97,46 +129,44 @@ APP_URL=https://mysite.com/ # development APP_URL=http://mysite.test/ ``` +::: + +### Locale + +Each site has a `locale` used to format region-specific data (like date strings, number formats, etc). This should correspond to the server's locale. By default Statamic will use English – United States (`en_US`). +:::tip +To see the list of installed locales on your system or server, run the command `locale -a`. ::: -### Text Direction +### Language -All sites are Left-To-right (`ltr`) by default, and you may omit the setting entirely. But if any of your sites is in a `rtl` text direction (like Arabic or Hebrew), you may define the direction in the config and use it on your front-end wherever necessary. +Statamic's control panel has been translated into more than a dozen languages. The language translations files live in `resources/lang`. -```php -'sites' => [ - 'en' => [ - 'name' => 'English', - ], - 'he' => [ - 'name' => 'Hebrew', - 'direction' => 'rtl', - ] -] -``` +You may specify which language translation to be used for each site with the `lang` setting. If you leave it off, it'll use the short version of the `locale`. e.g. If the locale is `en_US`, the lang will be `en`. -``` - +``` yaml +de: + name: Deutsche + locale: de_DE + # Lang not needed, as `de` is implied +de_CH: + name: 'Deutsche (Switzerland)' + locale: de_CH + lang: de_CH # We want the `de_CH` language, not `de` ``` -:::tip -Statamic's `direction` is `ltr` by default. You only need to set `direction` when your site is `rtl`. -::: +Note that both Statamic and Laravel don't ship with frontend language translations out of the box. You have to provide your own string files for this. There is a great package called [Laravel Lang](https://github.com/Laravel-Lang/lang) containing over 75 languages that can help you out with this. ### Additional Attributes -You may also add an array of arbitrary attributes to your site's config, which can later be accessed with the [site variable](/variables/site) . - -```php -'sites' => [ - 'en' => [ - 'name' => 'English', - 'attributes' => [ - 'theme' => 'standard', - ] - ], -] +You may also include additional arbitrary `attributes` in your site's config, which can later be accessed with the [site variable](/variables/site). + +``` yaml +en: + # ... + attributes: + theme: standard ``` ``` @@ -147,27 +177,21 @@ You may also add an array of arbitrary attributes to your site's config, which c Nothing fancy happens here, the values are passed along "as is" to your templates. If you need them to be editable, or store more complex data, you could use [Globals](/globals). ::: +## Text Direction + +Text direction is automatically inferred by Statamic, based on the [language](#language) of your configured site. -## Adding a Site +For example, most sites will be `ltr`, but Statamic will automatically use `rtl` for languages like Arabic or Hebrew. -To add another site to an existing multi-site installation, add another array to the `$sites` configuration array along with the desired settings. +If you need to reference text direction in your front end, you make use the [site variable](/variables/site): -```php -'sites' => [ - 'en' => [ - 'name' => 'English', - // - ], - 'de' => [ - 'name' => 'German', - // - ], -]; +``` + ``` -## Renaming a Site +## Renaming Sites -If you rename a site handle, you'll need to update a few folders and config settings along with it. Replace `{old_handle}` with the new handle in these locations: +If you rename a site's [handle](#handle), you'll need to update a few folders and config settings along with it. Replace `{old_handle}` with the new handle in these locations: **Content Folders** @@ -206,15 +230,15 @@ permissions: [Views](/views) can be organized into site directories. -If a requested view exists in a subdirectory with the same name as your site, it will load it instead. This allows you have site-specific views without any extra configuration. +If a requested view exists in a subdirectory with the same name as your site [handle](#handle), it will load it instead. This allows you have site-specific views without any extra configuration. -``` php -# config/statamic/sites.php +``` yaml +# resources/sites.yaml -'sites' => [ - 'site_one' => [ /* ... */ ], - 'site_two' => [ /* ... */ ], -] +site_one: + # ... +site_two: + # ... ``` ``` files theme:serendipity-light diff --git a/content/collections/docs/updating.md b/content/collections/docs/updating.md index bb0f86768..493ed3eb8 100644 --- a/content/collections/docs/updating.md +++ b/content/collections/docs/updating.md @@ -53,11 +53,4 @@ To go back to a more traditional version range constraint, you may want to repla ## Major Upgrades -Upgrading between major Statamic versions sometimes involves extra manual steps. Check out these guides for further details. - -- [3.4 to 4.0](/upgrade-guide/3-4-to-4-0) -- [3.3 to 3.4](/upgrade-guide/3-3-to-3-4) -- [3.2 to 3.3](/upgrade-guide/3-2-to-3-3) -- [3.1 to 3.2](/upgrade-guide/3-1-to-3-2) -- [3.0 to 3.1](/upgrade-guide/3-0-to-3-1) -- [2.x to 3.x](/upgrade-guide/v2-to-v3) +Upgrading between major Statamic versions sometimes involves extra manual steps. Check out [these guides](/upgrade-guide) for further details. diff --git a/content/collections/docs/upgrade-guide.md b/content/collections/docs/upgrade-guide.md index dd6d7eee7..9759889a5 100644 --- a/content/collections/docs/upgrade-guide.md +++ b/content/collections/docs/upgrade-guide.md @@ -5,6 +5,7 @@ template: page id: f12f8ba3-19ff-48cb-a07b-653b05082d7e blueprint: page --- +- [4.0 to 5.0](/upgrade-guide/4-to-5) - [3.4 to 4.0](/upgrade-guide/3-4-to-4-0) - [3.3 to 3.4](/upgrade-guide/3-3-to-3-4) - [3.2 to 3.3](/upgrade-guide/3-2-to-3-3) diff --git a/content/collections/docs/validation.md b/content/collections/docs/validation.md index 63648f777..a1f1924ad 100644 --- a/content/collections/docs/validation.md +++ b/content/collections/docs/validation.md @@ -98,7 +98,7 @@ You may use any validation rule provided by Laravel. You can view the complete l handle: highlander field: type: text - validate: 'unique_entry_value:{collection},{id},{site}' + validate: 'new \Statamic\Rules\UniqueEntryValue({collection}, {id}, {site})' ``` This works like Laravel's `unique` validation rule, but for Statamic entries. The rule should be used verbatim as shown above. Statamic will replace the `collection`, `id`, and `site` behind the scenes. @@ -120,10 +120,10 @@ You can then customize the error message right in your `resources/lang/{lang}/va handle: foo field: type: text - validate: 'unique_term_value:{taxonomy},{id},{site}' + validate: 'new \Statamic\Rules\UniqueTermValue({taxonomy}, {id}, {site})' ``` -This works like the `unique_entry_value` rule, but for taxonomy terms. +This works like the `UniqueEntryValue` rule, but for taxonomy terms. ### Unique User Value @@ -132,13 +132,13 @@ This works like the `unique_entry_value` rule, but for taxonomy terms. handle: login field: type: text - validate: 'unique_user_value:{id}' + validate: 'new \Statamic\Rules\UniqueUserValue({id})' ``` -This works like the `unique_entry_value` rule, but for users. +This works like the `UniqueEntryValue` rule, but for users. :::tip -If you want to override the field that is being validated (e.g. in Livewire Form Objects), you can do so by passing a second parameter to the validation rule, such as `unique_user_value:{id},username`. +If you want to override the field that is being validated (e.g. in Livewire Form Objects), you can do so by passing a second parameter to the validation rule, such as `new \Statamic\Rules\UniqueUserValue({id}, "username")`. ::: ## Custom Rules diff --git a/content/collections/extending-docs/addons.md b/content/collections/extending-docs/addons.md index c32af1e5e..e8b0d21c1 100644 --- a/content/collections/extending-docs/addons.md +++ b/content/collections/extending-docs/addons.md @@ -1,13 +1,13 @@ --- +id: 5bd75435-806e-458b-872e-7528f24df7e6 +blueprint: page title: Addons template: page updated_by: 42bb2659-2277-44da-a5ea-2f1eed146402 updated_at: 1569264134 -intro: An addon is a composer package you intend to reuse, distribute, or sell. For simple or private packages, consider implementing directly into your Laravel application. +intro: 'An addon is a composer package you intend to reuse, distribute, or sell. For simple or private packages, consider implementing directly into your Laravel application.' stage: 1 -id: 5bd75435-806e-458b-872e-7528f24df7e6 --- - ## Creating an Addon You can generate an addon with a console command: @@ -542,6 +542,11 @@ The `update()` method is where your custom data migration logic happens. Feel fr That's it! Statamic should now automatically run your update script as your users update their addons. +## Testing + +Statamic automatically scaffolds a PHPUnit test suite when you generate an addon with `php please make:addon`. + +To learn more about writing addon tests, please review our [Testing in Addons](/extending/testing-in-addons) guide. ## Publishing to the Marketplace @@ -584,4 +589,4 @@ An example use case is a custom fieldtype maintained by a third party vendor. Ev :::tip An example use case is a frontend theme with sample content. This is the kind of thing you would install into your app once and modify to fit your own style. You would essentially own and maintain the installed files yourself. -::: +::: \ No newline at end of file diff --git a/content/collections/extending-docs/slugify.md b/content/collections/extending-docs/slugify.md deleted file mode 100644 index a735af0f0..000000000 --- a/content/collections/extending-docs/slugify.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Slugify -stage: 1 -id: d84613d5-2b2b-465d-8f02-c71b6d2aadf1 ---- -You can use the `` component to generate a slug based off another property: - -``` html - - - -``` - -When the value of the `from` prop changes (ie. the `title` property), it will update the `slug` property. - -If you update the slug manually (ie. by typing in the field), the component will realize, and prevent any further automatic slug generation. - -If you want underscores instead of dashes, you can pass in `separator="_"`. diff --git a/content/collections/extending-docs/slugs.md b/content/collections/extending-docs/slugs.md new file mode 100644 index 000000000..75033c44d --- /dev/null +++ b/content/collections/extending-docs/slugs.md @@ -0,0 +1,85 @@ +--- +title: Slugs +stage: 1 +id: d84613d5-2b2b-465d-8f02-c71b6d2aadf1 +--- +## Slugify Vue Component + +You can use the `` component to generate a slug based off another property: + +``` html + + + +``` + +When the value of the `from` prop changes (ie. the `title` property), it will update the `slug` property. + +If you update the slug manually (ie. by typing in the field), the component will realize, and prevent any further automatic slug generation. + +If you want underscores instead of dashes, you can pass in `separator="_"`. + + +## JavaScript API + +### Basic Slugs +You may also create slugs programmatically. + +```js +Statamic.$slug.create('Hello World'); // hello-world +``` + +You may also define the separating character: + +```js +Statamic.$slug.separatedBy('_').create('Hello World'); // hello_world +``` + +You may use the `str_slug` and `snake_case` global methods respectively as aliases for both of these: + +```js +str_slug('Hello World'); // hello-world +snake_case('Hello World'); // hello_world +``` + +:::tip +When you're within a Vue component, you may use `this.$slug` instead of `Statamic.$slug`. +::: + +### More Oomph + +When you need more accurate slugs, you can leverage PHP's more powerful slug logic. By calling `async`, the `create` method will become Promise-based as it requests slugs from the server: + +```js +Statamic.$slug.async().create('Hello World').then(slug => { + console.log(slug); // 'hello-world' +}) +``` + +This is particularly useful when you need to provide the language: + +```js +Statamic.$slug.in('zh').async().separatedBy('_') + .create('你好世界') + .then(slug => console.log(slug)); // ni_hao_shi_jie +``` + +:::tip +If you don't provide a language, the language of the selected site in the control panel will be used. +::: + +### Debouncing + +If you will be calling this repeatedly, such as via user's keystrokes, debouncing is useful to prevent excess calls to the server. + +Debouncing will be automatically handled as long as you call `create` on the same instance: + +```js +const slugger = Statamic.$slug.async().separatedBy('_'); + +slugger.create('one').then(slug => console.log(slug)); +slugger.create('two').then(slug => console.log(slug)); +slugger.create('three').then(slug => console.log(slug)); + +// console: 'three' +``` diff --git a/content/collections/extending-docs/testing-in-addons.md b/content/collections/extending-docs/testing-in-addons.md new file mode 100644 index 000000000..eb1caf49a --- /dev/null +++ b/content/collections/extending-docs/testing-in-addons.md @@ -0,0 +1,153 @@ +--- +id: 15db07e8-6e83-4b6e-89bb-d050b5d2c823 +blueprint: page +title: 'Testing in Addons' +template: page +nav_title: Testing +intro: "There's only one thing better than manual testing... automated testing. Addons are scaffolded with PHPUnit test suites out-of-the-box. Learn how to write & run tests." +--- +When you create an addon with the `php please make:addon` command, Statamic will automatically scaffold the necessary files for a PHPUnit test suite: + +``` files theme:serendipity-light +tests/ + ExampleTest.php + TestCase.php +phpunit.xml +``` + +## The `TestCase` + +The `TestCase` class extends Statamic's built-in `AddonTestCase` which is responsible for booting your addon's service provider, amongst other things. + +Under the hood, Statamic's `AddonTestCase` extends [Orchestra Testbench](https://github.com/orchestral/testbench)'s `TestCase` class. Testbench allows you to test against a *real* Laravel application. + +If you need to change any config settings for your test suite, like enabling Statamic Pro or configuring the REST API, add a `resolveApplicationConfiguration` method to your `TestCase`: + +```php +protected function resolveApplicationConfiguration($app) +{ + parent::resolveApplicationConfiguration($app); + + $app['config']->set('statamic.editions.pro', true); + + $app['config']->set('statamic.api.resources', [ + 'collections' => true, + 'navs' => true, + 'taxonomies' => true, + 'assets' => true, + 'globals' => true, + 'forms' => true, + 'users' => true, + ]); +} +``` + + +## Writing Tests + +All of your tests should extend your addon's `TestCase` class, like so: + +```php +assertTrue(true); + } +} +``` + +For more information on writing tests, please review the [Laravel Testing Documentation](https://laravel.com/docs/10.x/testing). + +### The Stache + +During tests, any Stache items (like entries, terms, global sets) will be saved inside your `tests/__fixtures__` directory. + +However, most of the time, you'll probably want to blow away old content between test runs. To do this, you may add the `PreventsSavingStacheItemsToDisk` trait to your tests: + +```php +use Statamic\Testing\Concerns\PreventsSavingStacheItemsToDisk; // [tl! focus] + +class ExampleTest extends TestCase +{ + use PreventsSavingStacheItemsToDisk; // [tl! focus] + + // ... +} +``` + +## Running Tests + +Once you've written some tests, you can run them using `phpunit`: + +```bash +./vendor/bin/phpunit +``` + +You may run a specific test by passing the `--filter` argument: + +```bash +# Runs all tests in the CheckoutTest +./vendor/bin/phpunit --filter=CheckoutTest + +# Runs the specific user_cant_checkout_without_payment test +./vendor/bin/phpunit --filter=user_cant_checkout_without_payment + +# Runs all tests with checkout in their name. +./vendor/bin/phpunit --filter=checkout +``` + +### GitHub Actions + +When you're using GitHub to store your addon's source code, you can take advantage of GitHub Actions so your addon's tests are run whenever you push to your repository or whenever a pull request is submitted. + +Running tests on GitHub Actions (or any CI platform for that matter) saves you running the tests locally after every change and also means you can run your addon's tests against multiple PHP & Laravel versions. + +For ease of use, here's an example GitHub Actions workflow: + +```yaml +name: Test Suite + +on: + push: + pull_request: + +jobs: + php_tests: + strategy: + matrix: + php: [8.2, 8.3] + laravel: [10.*, 11.*] + os: [ubuntu-latest] + + name: ${{ matrix.php }} - ${{ matrix.laravel }} + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v1 + + - 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 + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update + composer install --no-interaction + + - name: Run PHPUnit + run: vendor/bin/phpunit +``` \ No newline at end of file diff --git a/content/collections/modifiers/modify_date.md b/content/collections/modifiers/modify_date.md index 0d2711458..c94ee1899 100644 --- a/content/collections/modifiers/modify_date.md +++ b/content/collections/modifiers/modify_date.md @@ -25,5 +25,5 @@ April 1, 2000 ``` :::tip -This modifier **modifies the variable directly** which will be passed onto any additional modifiers. +As of Statamic 5, this modifier will return a copy of the Date. Earlier versions would **modify the variable directly** which will be passed onto any additional modifiers. ::: diff --git a/content/collections/repositories/asset-container-repository.md b/content/collections/repositories/asset-container-repository.md index d79fac51a..3595bb9a3 100644 --- a/content/collections/repositories/asset-container-repository.md +++ b/content/collections/repositories/asset-container-repository.md @@ -26,6 +26,7 @@ use Statamic\Facades\AssetContainer; | `all()` | Get all AssetContainers | | `find($id)` | Get AssetContainer by `id` | | `findByHandle($handle)` | Get AssetContainer by `handle` | +| `findOrFail($id)` | Get AssetContainer by `id`. Throws an `AssetContainerNotFoundException` when the asset container cannot be found. | | `queryAssets()` | Query Builder for the AssetContainer's [Assets](#assets) | | `make()` | Makes a new AssetContainer instance | @@ -45,6 +46,12 @@ $videos->queryAssets() ->get(); ``` +When an asset container can't be found, the `AssetContainer::find()` method will return `null`. If you'd prefer an exception be thrown, you may use the `findOrFail` method: + +```php +AssetContainer::findOrFail('videos'); +``` + ## Creating Start by making an instance of an asset container with the `make` method. You can pass the handle into it. @@ -71,4 +78,3 @@ Finally, save it. ```php $container->save(); ``` - diff --git a/content/collections/repositories/asset-repository.md b/content/collections/repositories/asset-repository.md index d532dfda5..d2b9c307c 100644 --- a/content/collections/repositories/asset-repository.md +++ b/content/collections/repositories/asset-repository.md @@ -20,12 +20,13 @@ use Statamic\Facades\Asset; | Methods | Description | | ------- | ----------- | | `all()` | Get all Assets | -| `find()` | Get Asset by `filename` | -| `findByPath()` | Get Asset by `path` | -| `findByUrl()` | Get Asset by `url` | +| `find($filename)` | Get Asset by `filename` | +| `findByPath($path)` | Get Asset by `path` | +| `findByUrl($url)` | Get Asset by `url` | +| `findOrFail($filename)` | Get Asset by `filename`. Throws an `AssetNotFoundException` when the asset cannot be found. | | `query()` | Query Builder | -| `whereContainer()` | Find Assets by [AssetContainer](#asset-container) | -| `whereFolder()` | Find Assets in a filesystem folder | +| `whereContainer($container)` | Find Assets by [AssetContainer](#asset-container) | +| `whereFolder($folder)` | Find Assets in a filesystem folder | | `make()` | Makes a new `Asset` instance | :::tip diff --git a/content/collections/repositories/collection-repository.md b/content/collections/repositories/collection-repository.md index 1e8048694..6987539a6 100644 --- a/content/collections/repositories/collection-repository.md +++ b/content/collections/repositories/collection-repository.md @@ -23,7 +23,8 @@ use Statamic\Facades\Collection; | `all()` | Get all Collections | | `find($id)` | Get Collection by `id` | | `findByHandle($handle)` | Get Collection by `handle` | -| `findByMount($mount)` | Get Asset by mounted entry `id` | +| `findByMount($mount)` | Get Collection by mounted entry `id` | +| `findOrFail($id)` | Get Collection by `id`. Throws a `CollectionNotFoundException` when the collection cannot be found. | | `handleExists($handle)` | Check to see if `Collection` exists | | `handles()` | Get all `Collection` handles | | `queryEntries()` | Query Builder for [Entries](/repositories/entry-repository) | @@ -44,6 +45,12 @@ $blog = Collection::find('blog'); $blog->queryEntries()->get(); ``` +When a collection can't be found, the `Collection::find()` method will return `null`. If you'd prefer an exception be thrown, you may use the `findOrFail` method: + +```php +Collection::findOrFail('blog'); +``` + ## Creating Start by making an instance of a collection with the `make` method. You can pass the handle into it. diff --git a/content/collections/repositories/entry-repository.md b/content/collections/repositories/entry-repository.md index 4b551c33b..ea2e6df10 100644 --- a/content/collections/repositories/entry-repository.md +++ b/content/collections/repositories/entry-repository.md @@ -22,7 +22,7 @@ use Statamic\Facades\Entry; | `all()` | Get all Entries | | `find($id)` | Get Entry by `id` | | `findByUri($uri, $site)` | Get Entry by `uri`, optionally in a site | -| `findOrFail($id)` | Get Entry by `id`. Throws an `EntryNotFoundException` when entry can not be found. | +| `findOrFail($id)` | Get Entry by `id`. Throws an `EntryNotFoundException` when the entry cannot be found. | | `query()` | Query Builder | | `whereCollection($handle)` | Get all Entries in a `Collection` | | `whereInCollection([$handles])` | Get all Entries in an array of `Collections` | @@ -41,7 +41,7 @@ Entry::query()->where('id', 123)->first(); Entry::find(123); ``` -When an entry can't be found, the `Entry::find()` method will return `null`. If you'd prefer an exception be thrown, you can use the `findOrFail` method: +When an entry can't be found, the `Entry::find()` method will return `null`. If you'd prefer an exception be thrown, you may use the `findOrFail` method: ```php Entry::findOrFail(123); diff --git a/content/collections/repositories/form-repository.md b/content/collections/repositories/form-repository.md index b863e7fc5..3e9922d38 100644 --- a/content/collections/repositories/form-repository.md +++ b/content/collections/repositories/form-repository.md @@ -20,6 +20,7 @@ use Statamic\Facades\Form; | ------- | ----------- | | `all()` | Get all Forms | | `find($handle)` | Get Form by `handle` | +| `findOrFail($handle)` | Get Form by `handle`. Throws a `FormNotFoundException` when the form cannot be found. | | `make()` | Makes a new `Form` instance | :::tip @@ -36,6 +37,12 @@ The `handle` is the name of the form's YAML file. Form::find('postbox'); ``` +When a form can't be found, the `Form::find()` method will return `null`. If you'd prefer an exception be thrown, you may use the `findOrFail` method: + +```php +Form::findOrFail('postbox'); +``` + #### Get all forms from your site ```php diff --git a/content/collections/repositories/global-repository.md b/content/collections/repositories/global-repository.md index 63498c31c..1d2f0eeaa 100644 --- a/content/collections/repositories/global-repository.md +++ b/content/collections/repositories/global-repository.md @@ -20,6 +20,7 @@ use Statamic\Facades\GlobalSet; | `all()` | Get all GlobalSets | | `find($id)` | Get GlobalSets by `id` | | `findByHandle($handle)` | Get GlobalSets by `handle` | +| `findOrFail($id)` | Get GlobalSets by `id`. Throws a `GlobalSetNotFoundException` when the global set cannot be found. | | `make()` | Makes a new `GlobalSet` instance | ## Querying diff --git a/content/collections/repositories/taxonomy-repository.md b/content/collections/repositories/taxonomy-repository.md index 4d4021356..c9d39ebc6 100644 --- a/content/collections/repositories/taxonomy-repository.md +++ b/content/collections/repositories/taxonomy-repository.md @@ -24,6 +24,7 @@ use Statamic\Facades\Taxonomy; | `find($id)` | Get Taxonomy by `id` | | `findByHandle($handle)` | Get Taxonomy by `handle` | | `findByUri($mount)` | Get Taxonomy by `uri` | +| `findOrFail($id)` | Get Taxonomy by `id`. Throws a `TaxonomyNotFoundException` when the taxonomy cannot be found. | | `handleExists($handle)` | Check to see if `Taxonomy` exists | | `handles()` | Get all `Taxonomy` handles | | `queryTerms()` | Query Builder for [Terms](/content-queries/term-repository) | @@ -43,6 +44,12 @@ $tags = Taxonomy::find('tags'); $tags->queryTerms()->get(); ``` +When a taxonomy can't be found, the `Taxonomy::find()` method will return `null`. If you'd prefer an exception be thrown, you may use the `findOrFail` method: + +```php +Taxonomy::findOrFail('tags'); +``` + ## Creating Start by making an instance of a taxonomy with the `make` method. You can pass the handle into it. diff --git a/content/collections/repositories/term-repository.md b/content/collections/repositories/term-repository.md index 11dfdb579..1a58b9df3 100644 --- a/content/collections/repositories/term-repository.md +++ b/content/collections/repositories/term-repository.md @@ -23,6 +23,7 @@ use Statamic\Facades\Term; | `all()` | Get all Terms | | `find($id)` | Get Term by `id` | | `findByUri($uri)` | Get Term by `uri` | +| `findOrFail($id)` | Get Term by `id`. Throws a `TermNotFoundException` when the term cannot be found. | | `query()` | Query Builder | | `make()` | Makes a new `Term` instance | @@ -41,6 +42,12 @@ Term::query()->where('id', 'tags::123')->first(); Term::find('tags::123'); ``` +When a term can't be found, the `Term::find()` method will return `null`. If you'd prefer an exception be thrown, you may use the `findOrFail` method: + +```php +Term::findOrFail('tags::123'); +``` + #### Get all tags ```php diff --git a/content/collections/repositories/user-repository.md b/content/collections/repositories/user-repository.md index 9bd9fa187..414faad7f 100644 --- a/content/collections/repositories/user-repository.md +++ b/content/collections/repositories/user-repository.md @@ -24,6 +24,7 @@ use Statamic\Facades\User; | `find($id)` | Get User by `id` | | `findByEmail($email)` | Get User by `email` | | `findByOAuthID($provider, $id)` | Get User by an ID from an OAuth provider | +| `findOrFail($id)` | Get User by `id`. Throws a `UserNotFoundException` when the user cannot be found. | | `query()` | Query Builder | | `make()` | Makes a new `User` instance | @@ -40,6 +41,12 @@ User::query() User::find('abc123'); ``` +When a user can't be found, the `User::find()` method will return `null`. If you'd prefer an exception be thrown, you may use the `findOrFail` method: + +```php +User::findOrFail('abc123'); +``` + #### Get a user by email ```php diff --git a/content/collections/tags/svg.md b/content/collections/tags/svg.md index 4eb9cf223..a6bf3ee25 100644 --- a/content/collections/tags/svg.md +++ b/content/collections/tags/svg.md @@ -17,7 +17,7 @@ parameters: - name: sanitize type: boolean - description: Determines whether the SVG should be sanitized before being output. Defaults to `false`. + description: Determines whether the SVG should be sanitized before being output. Defaults to `true`. - name: '*' type: string diff --git a/content/collections/tips/converting-from-single-to-multi-site.md b/content/collections/tips/converting-from-single-to-multi-site.md index ca98489be..e4bc20043 100644 --- a/content/collections/tips/converting-from-single-to-multi-site.md +++ b/content/collections/tips/converting-from-single-to-multi-site.md @@ -1,6 +1,6 @@ --- id: e1da92af-a0d8-40bb-9417-52675fad5e1f -title: 'Converting from Single to Multi-site' +title: 'Converting from Single to Multi-Site' template: page categories: - development @@ -9,55 +9,65 @@ categories: updated_by: 3a60f79d-8381-4def-a970-5df62f0f5d56 updated_at: 1622821029 --- -## Move files +## Automated Conversion -:::tip -This can be automated using the following command: +We recommend using the following command to convert from a single to [multi-site](/multi-site) installation: -``` +``` shell php please multisite ``` -It's important to run the command **before** making changes to the config file. -::: +## Manual Conversion + +If you wish to better understand how to manually convert from a single to multi-site installation, the steps are as follows. + +### Enable Multisite Config + +First, enable `multisite` in your `config/statamic/system.php`: + +``` php +'multisite' => true, +``` + +### Adding New Sites -2. For each collection: - - Move all entries into a directory named after the first site's handle. (eg. `content/collections/blog/*.md` into `content/collections/blog/default/*.md`) +Next, you can add new sites through the control panel at `/cp/sites`, or directly in your `resources/sites.yaml` file: + +``` yaml +default: + name: First Site + url: / + locale: en_US +second: + name: Second Site + url: /second/ + locale: en_US +``` + +By default, the first site handle is named `default`, but feel free to rename it. This handle will be used below. + +### Update Default Site Content + +Now you'll need to update your default site content file & folder structure, so that Statamic knows where to find it, now that you've enabled multi-site. + +1. For each collection: + - Move all entries into a directory named after your default site's handle. (eg. `content/collections/blog/*.md` into `content/collections/blog/default/*.md`) - Add a `sites` array to the collection's yaml file with each site you want the entries to be available in. -3. For each structure: - - Take the `root` and `tree` variables, and move them in a file in a subdirectory named after the first site's handle. (eg. `content/structures/pages.yaml` to `content/structures/default/pages.yaml`) +2. For each tree structure: + - Take the `root` and `tree` variables, and move them in a file in a subdirectory named after your default site's handle. (eg. `content/trees/navigation/pages.yaml` to `content/trees/navigation/default/pages.yaml`) - Add a `sites` array to the root structure's yaml file with each site you want the structure to be available in. -4. For each global set: - - Take the values inside the `data` array, and move them to the top level in a file in a subdirectory named after the first site's handle. (eg. `content/globals/pages.yaml` to `content/globals/default/pages.yaml`) +3. For each global set: + - Take the values inside the `data` array, and move them to the top level in a file in a subdirectory named after the default site's handle. (eg. `content/globals/pages.yaml` to `content/globals/default/pages.yaml`) - Add a `sites` array to the root global's yaml file with each site you want the global to be available in. -5. Clear the cache: - ``` shell - php artisan cache:clear - ``` At this point, your content will be available in the default site. You will need to localize each piece of content by following the steps in its respective documentation. -**If you don't add the `sites` to the respective container files, the site selector will not be visible in the control panel.** +_**Note:** If you don't add the `sites` to the respective container files, the site selector will not be visible in the control panel._ -## Update config +### Clear The Cache -Add the new site to your `config/statamic/sites.php` config. +Finally, clear your cache! -``` php -return [ - 'sites' => [ - 'default' => [ - 'name' => 'First site', - 'locale' => 'en_US', - 'url' => '/', - ], - 'second' => [ - 'name' => 'Second site', - 'locale' => 'en_US', - 'url' => '/second/', - ], - ] -]; +``` shell +php artisan cache:clear ``` - -By default, the first site is named `default`, but feel free to rename it. diff --git a/content/trees/collections/docs.yaml b/content/trees/collections/docs.yaml index cac4fca20..3797e610f 100644 --- a/content/trees/collections/docs.yaml +++ b/content/trees/collections/docs.yaml @@ -16,6 +16,8 @@ tree: entry: a1d1a50e-fb42-4a9e-b03f-59dc47f21dc2 - entry: e077f513-45c1-4eff-ba87-210340dd6f54 + - + entry: 91e8f239-2f99-47bc-b4dd-3518cd3e36ae - entry: ec130472-4f44-4e7e-8dce-71d0c93e8fef - diff --git a/content/trees/navigation/extending_docs.yaml b/content/trees/navigation/extending_docs.yaml index 2ab9a43b8..fa518d8c8 100644 --- a/content/trees/navigation/extending_docs.yaml +++ b/content/trees/navigation/extending_docs.yaml @@ -39,7 +39,7 @@ tree: entry: 785ffa10-8b63-44b1-9da3-3837250cacbe - id: 0cdd7170-92ad-11ed-b05e-0800200c9a66 - url: /preferences#adding-fields + url: '/preferences#adding-fields' title: Preferences - id: 9e649d17-d0b2-48a9-abf4-534c38c0c7ce @@ -98,7 +98,7 @@ tree: entry: e2577828-504b-490b-a8b6-10991ae8a0b6 - id: 37a82f80-92ad-11ed-b05e-0800200c9a66 - url: /preferences#using-javascript + url: '/preferences#using-javascript' title: Preferences - id: a6b12519-6352-452b-9daf-b7dd76880fb2 @@ -146,3 +146,6 @@ tree: id: a7f60530-f033-11ed-afc5-0800200c9a66 entry: 5f26a634-19ae-4413-8b9e-1ed9c2c76bb0 title: Vite + - + id: c08f0634-c324-4320-a698-e5098eaaf6a1 + entry: 15db07e8-6e83-4b6e-89bb-d050b5d2c823 diff --git a/public/img/configure-sites.png b/public/img/configure-sites.png new file mode 100644 index 000000000..14fbd000b Binary files /dev/null and b/public/img/configure-sites.png differ