diff --git a/en/02_Developer_Guides/00_Model/02_Relations.md b/en/02_Developer_Guides/00_Model/02_Relations.md index b515d90d..0d72b411 100644 --- a/en/02_Developer_Guides/00_Model/02_Relations.md +++ b/en/02_Developer_Guides/00_Model/02_Relations.md @@ -167,13 +167,12 @@ in situations like adding multiple lists of links to a [`SiteConfig`](api:Silver An additional column is created called `Relation`, along with the `Class` and `ID` columns of a normal polymorphic `has_one` relation. -[warning] -If you save records into the `has_one` relation programatically, you must set the relation in the -`Relation` field, or it won't be included when you fetch the `has_many` relation list. - -Generally it is better to instead add the record with the `has_one` relation into its corresponding `has_many` relation -directly - see [adding relations](#adding-relations). -[/warning] +> [!WARNING] +> If you save records into the `has_one` relation programatically, you must set the relation in the +> `Relation` field, or it won't be included when you fetch the `has_many` relation list. +> +> Generally it is better to instead add the record with the `has_one` relation into its corresponding `has_many` relation +> directly - see [adding relations](#adding-relations). To specify that a `has_one` relation is multi-relational define the relation like so: @@ -198,9 +197,8 @@ class Fan extends DataObject } ``` -[hint] -Multi-relational `has_one` relations *must* be [polymorphic](#polymorphic-has-one). -[/hint] +> [!TIP] +> Multi-relational `has_one` relations *must* be [polymorphic](#polymorphic-has-one). It is best practice for your `has_many` relations to indicate which relation they're pointing at using dot notation. For example: @@ -265,19 +263,18 @@ class Coach extends DataObject Defines one-to-many joins. As you can see from the previous example, `$has_many` goes hand in hand with `$has_one`. -[alert] -When defining a `has_many` relation, you *must* specify a `has_one` relationship on the related class as well. To add a `has_one` relation on core classes, yml config settings can be used: - -```yml -SilverStripe\Assets\Image: - has_one: - Team: App\Model\Team -``` - -You can point multiple `has_many` relations at a single `has_one` relation if you use a [multi-relational `has_one`](#multi-relational-has-one). - -Note that in some cases you may be better off using a `many_many` relation instead. Carefully consider whether you are defining a "one-to-many" or a "many-to-many" relationship. -[/alert] +> [!CAUTION] +> When defining a `has_many` relation, you *must* specify a `has_one` relationship on the related class as well. To add a `has_one` relation on core classes, yml config settings can be used: +> +> ```yml +> SilverStripe\Assets\Image: +> has_one: +> Team: App\Model\Team +> ``` +> +> You can point multiple `has_many` relations at a single `has_one` relation if you use a [multi-relational `has_one`](#multi-relational-has-one). +> +> Note that in some cases you may be better off using a `many_many` relation instead. Carefully consider whether you are defining a "one-to-many" or a "many-to-many" relationship. ```php namespace App\Model; diff --git a/en/02_Developer_Guides/02_Controllers/07_CMS_JSON_APIs.md b/en/02_Developer_Guides/02_Controllers/07_CMS_JSON_APIs.md index f5ebeec8..db91cbff 100644 --- a/en/02_Developer_Guides/02_Controllers/07_CMS_JSON_APIs.md +++ b/en/02_Developer_Guides/02_Controllers/07_CMS_JSON_APIs.md @@ -23,9 +23,8 @@ Because of this you should generally avoid updating large parts of a DataObject Create a subclass of [`LeftAndMain`](api:SilverStripe\Admin\LeftAndMain). This ensures that users must be logged in to the admin interface to access the endpoint. Additionally, it provides access to the methods [`LeftAndMain::jsonSuccess()`](api:SilverStripe\Admin\LeftAndMain::jsonSuccess()) and [`LeftAndMain::jsonError()`](api:SilverStripe\Admin\LeftAndMain::jsonError()). -[warning] -To enhance security, do not create a direct subclass of [`Controller`](api:SilverStripe\Control\Controller) routed using YAML on the `/admin` route. This practice is strongly discouraged as it circumvents the requirement to log in to the CMS to access the endpoints. At best you'd be re-implementing logic that already exists. -[/warning] +> [!WARNING] +> To enhance security, do not create a direct subclass of [`Controller`](api:SilverStripe\Control\Controller) routed using YAML on the `/admin` route. This practice is strongly discouraged as it circumvents the requirement to log in to the CMS to access the endpoints. At best you'd be re-implementing logic that already exists. When naming this class, it's best practice to add a "Controller" suffix to this class, for instance name it `MySomethingController`. @@ -114,9 +113,8 @@ Generally you should not include a message outlining the nature of the error whe If you do include a message, remember that error messages are only intended for developers so do not use the `_t()` function to make them translatable. Do not use any returned messages on the frontend for things like toast notifications, instead those messages should be added directly in JavaScript. -[info] -Despite the slightly convoluted JSON format returned by `jsonError()` with multiple nodes, its usage remains consistent with `FormSchema`. It's better to use this method for uniformity rather than introducing separate methods for `FormSchema` and non-FormSchema failures. -[/info] +> [!NOTE] +> Despite the slightly convoluted JSON format returned by `jsonError()` with multiple nodes, its usage remains consistent with `FormSchema`. It's better to use this method for uniformity rather than introducing separate methods for `FormSchema` and non-FormSchema failures. ## CSRF token @@ -130,9 +128,8 @@ import Config from 'lib/Config'; const securityID = Config.get('SecurityID'); ``` -[warning] -The `lib/Config` import is provided by the `silverstripe/admin` module via [`@silverstripe/webpack-config`](https://www.npmjs.com/package/@silverstripe/webpack-config). -[/warning] +> [!WARNING] +> The `lib/Config` import is provided by the `silverstripe/admin` module via [`@silverstripe/webpack-config`](https://www.npmjs.com/package/@silverstripe/webpack-config). Ensure the security of your endpoints by validating the security token on relevant endpoints. @@ -144,9 +141,8 @@ if (!SecurityToken::inst()->checkRequest($this->getRequest())) { } ``` -[info] -The `400` HTTP status code used here is consistent with the code used when the CSRF check fails when submitting data using `FormSchema`. -[/info] +> [!NOTE] +> The `400` HTTP status code used here is consistent with the code used when the CSRF check fails when submitting data using `FormSchema`. ## Passing values from PHP to global JavaScript @@ -211,9 +207,8 @@ backend.post(endpoint, data, headers) }); ``` -[warning] -The `lib/Config` and `lib/Backend` imports are provided by the `silverstripe/admin` module via [`@silverstripe/webpack-config`](https://www.npmjs.com/package/@silverstripe/webpack-config). -[/warning] +> [!WARNING] +> The `lib/Config` and `lib/Backend` imports are provided by the `silverstripe/admin` module via [`@silverstripe/webpack-config`](https://www.npmjs.com/package/@silverstripe/webpack-config). On the controller's endpoint method, retrieve the POST data using `$json = json_decode($this->getRequest()->getBody());`. diff --git a/en/02_Developer_Guides/03_Forms/07_Using_GridField_With_Arbitrary_Data.md b/en/02_Developer_Guides/03_Forms/07_Using_GridField_With_Arbitrary_Data.md index 44bae256..2279167e 100644 --- a/en/02_Developer_Guides/03_Forms/07_Using_GridField_With_Arbitrary_Data.md +++ b/en/02_Developer_Guides/03_Forms/07_Using_GridField_With_Arbitrary_Data.md @@ -8,9 +8,8 @@ icon: table [`GridField`](api:SilverStripe\Forms\GridField\GridField) is often used for displaying and editing `DataObject` records - but it can be used with other data as well. You might have data that is pulled from an API for example, which you want to display in the admin area of your Silverstripe CMS project. -[info] -This document assumes you're familiar with `GridField` - see the [`GridField` documentation](/developer_guides/forms/field_types/gridfield/) for information about using `GridField`. -[/info] +> [!NOTE] +> This document assumes you're familiar with `GridField` - see the [`GridField` documentation](/developer_guides/forms/field_types/gridfield/) for information about using `GridField`. Data which isn't represented by `DataObject` records can come in two forms: @@ -25,11 +24,10 @@ Some grid field components may require specific information, such as which colum Regardless of how you get your data, whether it's from a web API or some other source, you'll need to store it in an `ArrayList`. For best results, each record should also be explicitly instantiated as an `ArrayData` object in the list. -[hint] -The `ID` field shown here isn't necessary if you only want to view the records as rows in the `GridField`, but if you want to be able to view *each* record in a read-only form view, the `ID` field is mandatory. - -See [viewing data in a form](#arraydata-view) for more information. -[/hint] +> [!TIP] +> The `ID` field shown here isn't necessary if you only want to view the records as rows in the `GridField`, but if you want to be able to view *each* record in a read-only form view, the `ID` field is mandatory. +> +> See [viewing data in a form](#arraydata-view) for more information. ```php use SilverStripe\ORM\ArrayList; @@ -116,9 +114,8 @@ For data to be viewed in a read-only form, each record in the list must have an You'll need to add a `GridFieldDetailForm` component to the `GridField` and tell it how to represent your data by passing a [`FieldList`](api:SilverStripe\Forms\FieldList) into [`GridFieldDetailForm::setFields()`](api:SilverStripe\Forms\GridField\GridFieldDetailForm::setFields()). -[hint] -Because `ArrayData` doesn't implement a `canEdit()` method, the form will be implicitly turned into a read-only form for you. You don't need to worry about passing in read-only form fields. -[/hint] +> [!TIP] +> Because `ArrayData` doesn't implement a `canEdit()` method, the form will be implicitly turned into a read-only form for you. You don't need to worry about passing in read-only form fields. ```php use SilverStripe\Forms\FieldList; @@ -151,11 +148,10 @@ However, if you omit these method implementations, you must instead pass the req To represent your data as rows in a `GridField`, you can rely on the default `GridFieldConfig` object that the field will build for itself. If you implement a `summaryFields()` method in your data class, the `GridField` will call that method to find out what fields it should display. -[hint] -The `ID` field shown here isn't necessary if you only want to view/edit the records as rows in the `GridField`, but if you want to be able to view *each* record in a read-only form view, the `ID` field is mandatory. - -See [viewing data in a form](#custom-view) for more information. -[/hint] +> [!TIP] +> The `ID` field shown here isn't necessary if you only want to view/edit the records as rows in the `GridField`, but if you want to be able to view *each* record in a read-only form view, the `ID` field is mandatory. +> +> See [viewing data in a form](#custom-view) for more information. ```php namespace App\Data; @@ -221,9 +217,8 @@ If you want to be able to filter your `GridField`, you will need to tell the `Gr What's more, we don't have to pass the search fields into the `BasicSearchContext` instance either if we implement a `scaffoldSearchFields()` method. -[hint] -You can optionally implement the `i18n_singular_name()` method to return a localised string to represent the plural name of this model. This is used in the filter header as the placeholder text for the general search field. -[/hint] +> [!TIP] +> You can optionally implement the `i18n_singular_name()` method to return a localised string to represent the plural name of this model. This is used in the filter header as the placeholder text for the general search field. ```php namespace App\Data; @@ -257,9 +252,8 @@ class DataRepresentation extends ViewableData No changes are required to the `GridField` components, assuming you didn't remove the `GridFieldFilterHeader` component. -[hint] -The `BasicSearchContext` respects some (*but not all*) [`$searchable_fields` configuration options](/developer_guides/model/scaffolding/#searchable-fields), so you can implement a `searchableFields()` method in your class to further customise the `GridField` filtering experience. -[/hint] +> [!TIP] +> The `BasicSearchContext` respects some (*but not all*) [`$searchable_fields` configuration options](/developer_guides/model/scaffolding/#searchable-fields), so you can implement a `searchableFields()` method in your class to further customise the `GridField` filtering experience. ### Exporting data {#custom-export} @@ -282,9 +276,8 @@ If the class representing your data has a `getCMSFields()` method, the return va If your class doesn't implement a `canEdit()` method, or it does and the method returns `false`, the form will be read-only. -[hint] -You can optionally implement the `i18n_plural_name()` method to return a localised string to represent the singular name of this model. This is used in the add button, breadcrumbs, and toasts. -[/hint] +> [!TIP] +> You can optionally implement the `i18n_plural_name()` method to return a localised string to represent the singular name of this model. This is used in the add button, breadcrumbs, and toasts. ```php namespace App\Data; @@ -332,9 +325,8 @@ For new records, the `write()` method *must* set the `ID` field on the record, s Records with no `ID` field or which have a non-numeric value for their `ID` field are considered new (unsaved) records. -[hint] -If you have specific validation rules you want to apply, you can also implement a `getCMSCompositeValidator()` method as described in [validation in the CMS](/developer_guides/forms/validation/#validation-in-the-cms). -[/hint] +> [!TIP] +> If you have specific validation rules you want to apply, you can also implement a `getCMSCompositeValidator()` method as described in [validation in the CMS](/developer_guides/forms/validation/#validation-in-the-cms). ```php namespace App\Data; @@ -421,13 +413,11 @@ $gridField->getConfig()->addComponent(GridFieldEditButton::create()); You can also enable creating new records and deleting records by adding the [`GridFieldAddNewButton`](api:SilverStripe\Forms\GridField\GridFieldAddNewButton) and [`GridFieldDeleteAction`](api:SilverStripe\Forms\GridField\GridFieldDeleteAction) components to your `GridField` config. -[hint] -At this point your `GridField` config is essentially the same as a [`GridFieldConfig_RecordEditor`](api:SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor) - so you could set up your `GridField` like so: - -```php -use SilverStripe\Forms\GridField\GridField; -use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; -$gridField = GridField::create('MyData', 'My data', $list, GridFieldConfig_RecordEditor::create()); -``` - -[/hint] +> [!TIP] +> At this point your `GridField` config is essentially the same as a [`GridFieldConfig_RecordEditor`](api:SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor) - so you could set up your `GridField` like so: +> +> ```php +> use SilverStripe\Forms\GridField\GridField; +> use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; +> $gridField = GridField::create('MyData', 'My data', $list, GridFieldConfig_RecordEditor::create()); +> ``` diff --git a/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md b/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md index c902463f..1e79b45c 100644 --- a/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md +++ b/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md @@ -13,10 +13,9 @@ Usually `GridField` is used with `DataObject` records - but it can be used with See [using `GridField` with arbitrary data](/developer_guides/forms/using_gridfield_with_arbitrary_data/) for more information. -[info] -`GridField` powers the automated data UI of [ModelAdmin](api:SilverStripe\Admin\ModelAdmin). For more information about `ModelAdmin` see the -[Customizing the CMS](/developer_guides/customising_the_admin_interface) guide. -[/info] +> [!NOTE] +> `GridField` powers the automated data UI of [ModelAdmin](api:SilverStripe\Admin\ModelAdmin). For more information about `ModelAdmin` see the +> [Customizing the CMS](/developer_guides/customising_the_admin_interface) guide. ```php use SilverStripe\Forms\GridField\GridField; @@ -221,12 +220,11 @@ $gridField->setConfig($config); Similar to `GridFieldConfig_Base` with the addition support of the ability to view a [`GridFieldDetailForm`](api:SilverStripe\Forms\GridField\GridFieldDetailForm) containing a read-only view of the data record. -[info] -Each record in the list must have an `ID` field, and the value of that field must be a positive integer. - -If the class representing your data has a `getCMSFields()` method, the return value of that method will be used for the fields displayed in the read-only view. -Otherwise, you'll need to pass in a [`FieldList`](api:SilverStripe\Forms\FieldList) to [`GridFieldDetailForm::setFields()`](api:SilverStripe\Forms\GridField\GridFieldDetailForm::setFields()). -[/info] +> [!NOTE] +> Each record in the list must have an `ID` field, and the value of that field must be a positive integer. +> +> If the class representing your data has a `getCMSFields()` method, the return value of that method will be used for the fields displayed in the read-only view. +> Otherwise, you'll need to pass in a [`FieldList`](api:SilverStripe\Forms\FieldList) to [`GridFieldDetailForm::setFields()`](api:SilverStripe\Forms\GridField\GridFieldDetailForm::setFields()). ```php use SilverStripe\Forms\GridField\GridFieldConfig_RecordViewer; @@ -246,18 +244,15 @@ $gridField->setConfig($config); Similar to `GridFieldConfig_RecordViewer` with the addition support to edit or delete each of the records. -[info] Each record in the list must have an `ID` field, and the value of that field must be a positive integer. If the class representing your data has a `getCMSFields()` method, the return value of that method will be used for the fields displayed in the read-only view. Otherwise, you'll need to pass in a [`FieldList`](api:SilverStripe\Forms\FieldList) to [`GridFieldDetailForm::setFields()`](api:SilverStripe\Forms\GridField\GridFieldDetailForm::setFields()). -[/info] -[warning] -The class representing your data *must* implement [`DataObjectInterface`](api:SilverStripe\ORM\DataObjectInterface) so that your records can be edited. - -See [using `GridField` with arbitrary data](/developer_guides/forms/using_gridfield_with_arbitrary_data/) for more information. -[/warning] +> [!WARNING] +> The class representing your data *must* implement [`DataObjectInterface`](api:SilverStripe\ORM\DataObjectInterface) so that your records can be edited. +> +> See [using `GridField` with arbitrary data](/developer_guides/forms/using_gridfield_with_arbitrary_data/) for more information. ```php use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; diff --git a/en/02_Developer_Guides/11_Integration/00_CSV_Import.md b/en/02_Developer_Guides/11_Integration/00_CSV_Import.md index 45b0d5ca..7a91280c 100644 --- a/en/02_Developer_Guides/11_Integration/00_CSV_Import.md +++ b/en/02_Developer_Guides/11_Integration/00_CSV_Import.md @@ -91,9 +91,8 @@ Let's create a simple upload form (which is used for `MyDataObject` instances). You'll need to add a route to your controller to make it accessible via URL (see [Routing](../../controllers/routing/)). -[warning] -Don't forget to perform [permission checks](#permission-checks) if the data is provided by users. -[/warning] +> [!WARNING] +> Don't forget to perform [permission checks](#permission-checks) if the data is provided by users. ```php namespace App\Control; diff --git a/en/02_Developer_Guides/11_Integration/How_Tos/Import_CSV_through_a_Controller.md b/en/02_Developer_Guides/11_Integration/How_Tos/Import_CSV_through_a_Controller.md index 64e6a740..0a3c1632 100644 --- a/en/02_Developer_Guides/11_Integration/How_Tos/Import_CSV_through_a_Controller.md +++ b/en/02_Developer_Guides/11_Integration/How_Tos/Import_CSV_through_a_Controller.md @@ -81,6 +81,5 @@ class MyController extends Controller } ``` -[alert] -If the file you're loading data from is uploaded by a user, you should pass `true` to the [`CsvBulkLoader::setCheckPermissions()`](api:SilverStripe\Dev\CsvBulkLoader::setCheckPermissions()) method. Otherwise, permissions will not be respected and the user may alter data in ways they would otherwise not be permitted to. -[/alert] +> [!CAUTION] +> If the file you're loading data from is uploaded by a user, you should pass `true` to the [`CsvBulkLoader::setCheckPermissions()`](api:SilverStripe\Dev\CsvBulkLoader::setCheckPermissions()) method. Otherwise, permissions will not be respected and the user may alter data in ways they would otherwise not be permitted to. diff --git a/en/04_Changelogs/5.2.0.md b/en/04_Changelogs/5.2.0.md index 46ba83e4..84734adf 100644 --- a/en/04_Changelogs/5.2.0.md +++ b/en/04_Changelogs/5.2.0.md @@ -45,9 +45,8 @@ Traditionally, if you wanted to have multiple `has_many` relations for the same This release includes a new `has_one` syntax to declare that your `has_one` should be allowed to handle multiple reciprocal `has_many` relations. The syntax for that is as follows: -[hint] -Multi-relational `has_one` relations *must* be polymorphic. -[/hint] +> [!TIP] +> Multi-relational `has_one` relations *must* be polymorphic. ```php namespace App\Model; @@ -72,11 +71,10 @@ class MyExample extends DataObject Multiple `has_many` relations on a single class can point to the above `has_one` relation, and they will be correctly saved and resolved when you get the relation list. -[warning] -This new feature means sometimes the value in the associative `has_one` configuration array will be an array, rather than a class name. -If you are relying on fetching this configuration to find the class names of `has_one` relations, consider using -[`DataObject::hasOne()`](api:SilverStripe\ORM\DataObject::hasOne()) or [`DataObjectSchema::hasOneComponent()`](api:SilverStripe\ORM\DataObjectSchema::hasOneComponent()) instead. -[/warning] +> [!WARNING] +> This new feature means sometimes the value in the associative `has_one` configuration array will be an array, rather than a class name. +> If you are relying on fetching this configuration to find the class names of `has_one` relations, consider using +> [`DataObject::hasOne()`](api:SilverStripe\ORM\DataObject::hasOne()) or [`DataObjectSchema::hasOneComponent()`](api:SilverStripe\ORM\DataObjectSchema::hasOneComponent()) instead. See [multi-relational `has_one` in the relations docs](/developer_guides/model/relations/#multi-relational-has-one) for more details about this relation type. @@ -212,9 +210,8 @@ You can also now optionally implement a `canView()` method on your `BuildTask` i Typehints using PHPStan-style generic types have been added to PHPDocs in many areas of the codebase of supported modules. The primary goal of this is to improve the developer experience by correctly reporting to your IDE what types it should expect, for example when looping through a `DataList`. In many cases your IDE will now know what types to expect without needing you to prompt it with [`@var` annotation comments](https://docs.phpdoc.org/3.0/guide/references/phpdoc/tags/var.html). -[info] -There are some cases where this goal conflicts with having types that are correctly identified by PHPStan itself (or other static analysis tools). For example conditional return types aren't supported as widely in IDEs as generics themselves are, so we opted to not use conditional return types even when those would result in a more accurate type for static analysis tools. -[/info] +> [!NOTE] +> There are some cases where this goal conflicts with having types that are correctly identified by PHPStan itself (or other static analysis tools). For example conditional return types aren't supported as widely in IDEs as generics themselves are, so we opted to not use conditional return types even when those would result in a more accurate type for static analysis tools. While you should see some improvements immediately after updating, there are some changes you can make to your own codebase to best use the new generic type hints. @@ -243,9 +240,8 @@ The generic typing on the `Extension` class can be used to tell your IDE what ty For this to be useful, you need to tell your IDE that your subclass `@extends` the `Extension` class, and tell it what type the owner should be. -[warning] -Don't forget to include a `use` statement, even if you're not explicitly referencing the type anywhere in your actual code. Your IDE needs the `use` statement to resolve the FQCN for the class you're referencing in the typehint. -[/warning] +> [!WARNING] +> Don't forget to include a `use` statement, even if you're not explicitly referencing the type anywhere in your actual code. Your IDE needs the `use` statement to resolve the FQCN for the class you're referencing in the typehint. ```php namespace App\Extension; @@ -306,11 +302,10 @@ class HomepageController extends PageController A [`LogicException`](https://www.php.net/manual/en/class.logicexception.php) is now thrown by [`FormSchema::getSchema()`](api:SilverStripe\Forms\Schema::getSchema()) if a react component was not found for a field type. -[warning] -If your project or a module you're using is currently trying to include a field which doesn't have a react component into a react-rendered form, it will have been silently failing. The form will have been rendering everything except for the field(s) which have no react component. - -This will now fail by throwing an exception, which means your form won't render at all until you remove or replace the field(s). -[/warning] +> [!WARNING] +> If your project or a module you're using is currently trying to include a field which doesn't have a react component into a react-rendered form, it will have been silently failing. The form will have been rendering everything except for the field(s) which have no react component. +> +> This will now fail by throwing an exception, which means your form won't render at all until you remove or replace the field(s). ### Other new features