diff --git a/.gitattributes b/.gitattributes index ee1b8406aff..71afe90e30b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,5 @@ /tests/behat/features export-ignore /tests/php export-ignore -/docs export-ignore /.tx export-ignore /.gitattributes export-ignore /.gitignore export-ignore diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 11a8c1ead41..00000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Build Docs -on: - push: - branches: - - '3' - - '4' - paths: - - 'docs/**' -jobs: - build: - name: build-docs - runs-on: ubuntu-latest - steps: - - name: Run build hook - run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_BUILD_HOOK }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40304bcc2c5..27a957c848e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,4 +12,4 @@ We ask for this so that the ownership in the license is clear and unambiguous, a ## Contributing code -See [contributing code](docs/en/05_Contributing/01_Code.md) +See [contributing code](https://docs.silverstripe.org/en/4/contributing/code/) diff --git a/SUPPORT.md b/SUPPORT.md index d68b5647189..afd42e89c15 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -6,7 +6,7 @@ If you require support there are two types of support available: community suppo ## Community support -Community support is provided by members of our open source community and is provided out of their good will. Please be kind, polite and corteous to those that try to help you; you may wish to refer to our [code of conduct](docs/en/05_Contributing/10_Code_of_conduct.md). +Community support is provided by members of our open source community and is provided out of their good will. Please be kind, polite and corteous to those that try to help you; you may wish to refer to our [code of conduct](https://docs.silverstripe.org/en/4/contributing/code_of_conduct/). You can find support via: diff --git a/docs/LICENSE b/docs/LICENSE deleted file mode 100644 index 8c3ecb0fd44..00000000000 --- a/docs/LICENSE +++ /dev/null @@ -1,2 +0,0 @@ -Licensed under Creative Commons Attribution 3.0 New Zealand -http://creativecommons.org/licenses/by/3.0/nz/ \ No newline at end of file diff --git a/docs/_manifest_exclude b/docs/_manifest_exclude deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/docs/en/00_Getting_Started/00_Server_Requirements.md b/docs/en/00_Getting_Started/00_Server_Requirements.md deleted file mode 100644 index a65e0d512f7..00000000000 --- a/docs/en/00_Getting_Started/00_Server_Requirements.md +++ /dev/null @@ -1,327 +0,0 @@ ---- -title: Server Requirements -icon: server -summary: What you will need to run Silverstripe CMS on a web server ---- - -# Requirements - -Silverstripe CMS needs to be installed on a web server. Content authors and website administrators use their web browser -to access a web-based GUI to do their day-to-day work. Website designers and developers require access to the files on -the server to update templates, website logic, and perform upgrades or maintenance. - -## PHP - -* PHP >=7.4 -* PHP extensions: `ctype`, `dom`, `fileinfo`, `hash`, `intl`, `mbstring`, `session`, `simplexml`, `tokenizer`, `xml` -* PHP configuration: `memory_limit` with at least `48M` -* PHP extension for image manipulation: Either `gd` or `imagick` -* PHP extension for a database connector (e.g. `pdo` or `mysqli`) - -Use [phpinfo()](http://php.net/manual/en/function.phpinfo.php) to inspect your configuration. - -Silverstripe CMS tracks the official [PHP release support timeline](https://www.php.net/supported-versions.php). When a PHP version reaches end-of-life, Silverstripe CMS drops support for it in the next minor release. - -## Database - -* MySQL >=5.6 ( - built-in, [commercially supported](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)) -* PostgreSQL ([third party module](https://addons.silverstripe.org/add-ons/silverstripe/postgresql), community - supported) -* SQL Server ([third party module](https://addons.silverstripe.org/add-ons/silverstripe/mssql), community supported) -* SQLite ([third party module](https://addons.silverstripe.org/add-ons/silverstripe/sqlite3), community supported) - -### Default MySQL Collation - -In Silverstripe CMS Recipe 4.7 and later, new projects default to the `utf8mb4_unicode_ci` collation when running -against MySQL, which offers better support for multi-byte characters such as emoji. However, this may cause issues -related to Varchar fields exceeding the maximum indexable size: - -- MySQL 5.5 and lower cannot support indexes larger than 768 bytes (192 characters) -- MySQL 5.6 supports larger indexes (3072 bytes) if the `innodb_large_prefix` setting is enabled (but not by default) -- MySQL 5.7 and newer have `innodb_large_prefix` enabled by default -- MariaDB ~10.1 matches MySQL 5.6's behaviour, >10.2 matches 5.7's. - -You can rectify this issue by upgrading MySQL, enabling the `innodb_large_prefix` setting if available, or reducing the -size of affected fields. If none of these solutions are currently suitable, you can remove the new collation -configuration from `app/_config/mysite.yml` to default back to the previous default collation. - -Existing projects that upgrade to Recipe 4.7.0 will unintentionally adopt this configuration change. Recipe 4.7.1 and -later are unaffected. See [the release notes](/changelogs/4.7.0/#default-mysql-collation-updated) for more information. - -### Connection mode (sql_mode) when using MySQL server >=5.7.5 - -In MySQL versions >=5.7.5, the `ANSI` sql_mode setting behaves differently and includes the `ONLY_FULL_GROUP_BY` -setting. It is generally recommended to leave this setting as-is because it results in deterministic SQL. However, for -some advanced cases, the sql_mode can be configured on the database connection via the configuration API ( -see `MySQLDatabase::$sql_mode` for more details.) This setting is only available in Silverstripe CMS 4.7 and later. - - ### MySQL/MariaDB Int width in schema - -MySQL 8.0.17 stopped reporting the width attribute for integers while MariaDB did not change its behaviour. -This results in constant rebuilding of the schema when MySQLSchemaManager expects a field to look like e.g. -`INT(8)` and MySQL server reports it simply as `INT`. MySQLSchemaManager has been updated to detect the MySQL -server implementation and act accordingly. In cases when auto-detection fails, you can force the desired behaviour like this: -```yml -SilverStripe\ORM\Connect\MySQLSchemaManager: - schema_use_int_width: true # or false when INT widths should be ignored -``` - -## Webserver Configuration - -### Overview - -Silverstripe CMS needs to handle a variety of HTTP requests, and relies on the hosting environment to be configured securely -to enforce restrictions. There are secure defaults in place for Apache, but you should be aware of the configuration -regardless of your webserver setup. - -### Public webroot - -The webroot of your webserver should be configured to the `public/` subfolder. Projects created prior to Silverstripe CMS -4.1 might be using the main project folder as the webroot. In this case, you are responsible for ensuring access to -system files such as configuration in `*.yml` is protected from public access. We strongly recommend switching to more -secure hosting via the `public/`. See [4.1.0 upgrading guide](/changelogs/4.1.0). - -### Filesystem permissions - -During runtime, Silverstripe CMS needs read access for the webserver user to your base path (including your webroot). It -also needs write access for the webserver user to the following locations: - -* `public/assets/`: Used by the CMS and other logic to [store uploads](/developer_guides/files/file_storage) -* `TEMP_PATH`: Temporary file storage used for the default filesystem-based cache adapters in - [Manifests](/developer_guides/execution_pipeline/manifests), [Object Caching](/developer_guides/performance/caching) - and [Partial Template Caching](/developer_guides/templates/partial_template_caching). - See [Environment Management](/getting_started/environment_management). -* `.graphql-generated`: silverstripe/graphql version 4 introduces this directory. This is where your schema is - stored once it [has been built](/developer_guides/graphql/getting_started/building_the_schema). Best practice - is to create it ahead of time, but if the directory doesn't exist and your project root is writable, the graphql - module will create it for you. -* `public/_graphql`: silverstripe/graphql version 4 introduces this directory. It's used for - [schema introspection](/developer_guides/graphql/tips_and_tricks#schema-introspection). You should treat this folder - the same way you treat the `.graphql-generated` folder. - -[info] -If you are still using silverstripe/graphql 3.x, you do not need the `.graphql-generated` or `public/_graphql` directories. -[/info] - -If you aren't explicitly [packaging](#building-packaging-deployment) -your Silverstripe CMS project during your deployment process, additional write access may be required to generate supporting -files on the fly. This is not recommended, because it can lead to extended execution times as well as cause -inconsistencies between multiple server environments when manifest and cache storage isn't shared between servers. - -Note that permissions may be required for other directories for specific functionality - for example if you use the -[i18nTextCollector](api:SilverStripe\i18n\TextCollection\i18nTextCollector) you will need to provide write access to the -relevant i18n `lang` directories. - -### Assets - -Silverstripe CMS allows CMS authors to upload files into the `public/assets/` folder, which should be served by your -webserver. **No PHP execution should be allowed in this folder**. This is configured for Apache by default -via `public/assets/.htaccess`. The file is generated dynamically during the `dev/build` stage. - -Additionally, access is whitelisted by file extension through a dynamically generated whitelist based on -the `File.allowed_extensions` setting -(see [File Security](/developer_guides/files/file_security#file-types)). This whitelist uses the same defaults -configured through file upload through Silverstripe CMS, so is considered a second line of defence. - -### Secure Assets {#secure-assets} - -Files can be kept in draft stage, and access restricted to certain user groups. These files are stored in a -special `.protected/` folder (defaulting to `public/assets/.protected/`). -**Requests to files in this folder should be denied by your webserver**. - -Requests to files in the `.protected/` folder are routed to PHP by default when using Apache, -through `public/assets/.htaccess`. If you are using another webserver, please follow our guides to ensure a secure -setup. See [Developer Guides: File Security](/developer_guides/files/file_security) for details. - -For additional security, we recommend moving the `.protected/` folder out of `public/assets/`. This removes the -possibility of a misconfigured webserver accidentally exposing these files under URL paths, and forces read access via -PHP. - -This can be configured via [.env](/getting_started/environment_management) variable, relative to the `index.php` -location. - -``` -SS_PROTECTED_ASSETS_PATH="../.protected/" -``` - -The resulting folder structure will look as follows: - -``` -.protected/ - /my-protected-file.txt -public/ - index.php - assets/ - my-public-file.txt -vendor/ -app/ -``` - -Don't forget to include this additional folder in any syncing and backup processes! - -### Building, Packaging and Deployment {#building-packaging-deployment} - -It is common to build a Silverstripe CMS application into a package on one environment (e.g. a CI server), and then deploy -the package to a (separate) webserver environment(s). This approach relies on all auto-generated files required by -Silverstripe CMS to be included in the package, or generated on the fly on each webserver environment. - -The easiest way to ensure this is to commit auto generated files to source control. If those changes are considered too -noisy, here's some pointers for auto-generated files to trigger and include in a deployment package: - -* `public/_resources/`: Frontend assets copied from the (inaccessible) `vendor/` folder - via [silverstripe/vendor-plugin](https://github.com/silverstripe/vendor-plugin). - See [Templates: Requirements](/developer_guides/templates/requirements#exposing-assets-webroot). -* `.graphql-generated/` and `public/_graphql/`: Schema and type definitions required by CMS and any GraphQL API endpoint. - Generated by - [silverstripe/graphql v4](https://github.com/silverstripe/silverstripe-graphql). See - [building the schema](/developer_guides/graphql/getting_started/building_the_schema) and - [deploying the schema](/developer_guides/graphql/getting_started/deploying_the_schema). -* Various recipes create default files in `app/` and `public/` on `composer install` - and `composer update` via - [silverstripe/recipe-plugin](https://github.com/silverstripe/recipe-plugin). - -### Web Worker Concurrency - -It's generally a good idea to run multiple workers to serve multiple HTTP requests to Silverstripe CMS concurrently. The -exact number depends on your website needs. The CMS attempts to request multiple views concurrently. It also -routes [protected and draft files](/developer_guides/files/file_security) -through Silverstripe CMS. This can increase your concurrency requirements, e.g. when authors batch upload and view dozens of -draft files in the CMS. - -When allowing upload of large files through the CMS (through PHP settings), these files might be used -as [protected and draft files](/developer_guides/files/file_security). Files in this state get served by Silverstripe CMS -rather than your webserver. Since the framework uses [PHP streams](https://www.php.net/manual/en/ref.stream.php), this -allows serving of files larger than your PHP memory limit. Please be aware that streaming operations don't count towards -PHP's [max_execution_time](https://www.php.net/manual/en/function.set-time-limit.php), which can risk exhaustion of web -worker pools for long-running downloads. - -### URL Rewriting - -Silverstripe CMS expects URL paths to be rewritten to `public/index.php`. For Apache, this is preconfigured -through `.htaccess` files, and expects using the `mod_rewrite` module. By default, these files are located -in `public/.htaccess` and `public/assets/.htaccess`. - -### HTTP Headers - -Silverstripe CMS can add HTTP headers to responses it handles directly. These headers are often sensitive, for example -preventing HTTP caching for responses displaying data based on user sessions, or when serving protected assets. You need -to ensure those headers are kept in place in your webserver. For example, Apache allows this -through `Header setifempty` (see [docs](https://httpd.apache.org/docs/current/mod/mod_headers.html#header)). -See [Developer Guide: Performance](/developer_guides/performance/) -and [Developer Guides: File Security](/developer_guides/files/file_security) for more details. - -Silverstripe CMS relies on the `Host` header to construct URLs such as "reset password" links, so you'll need to ensure that -the systems hosting it only allow valid values for this header. -See [Developer Guide: Security - Request hostname forgery](/developer_guides/security/secure_coding#request-hostname-forgery) -. - -### CDNs and other Reverse Proxies - -If your Silverstripe CMS site is hosted behind multiple HTTP layers, you're in charge of controlling which forwarded headers -are considered valid, and which IPs can set them. -See [Developer Guide: Security - Request hostname forgery](/developer_guides/security/secure_coding#request-hostname-forgery) -. - -### Symlinks - -Silverstripe CMS is a modular system, with modules installed and updated via the `composer` PHP dependency manager. These -are usually stored in `vendor/`, outside of the `public/` webroot. Since many modules rely on serving frontend assets -such as CSS files or images, these are mapped over to the `public/_resources/` folder automatically. If the filesystem -supports it, this is achieved through symlinks. Depending on your hosting and deployment mechanisms, you may need to -configure the plugin to copy files instead. -See [silverstripe/vendor-plugin](https://github.com/silverstripe/vendor-plugin) for details. - -### Caches - -Silverstripe CMS relies on various [caches](/developer_guides/performance/caching/) -to achieve performant responses. By default, those caches are stored in a temporary filesystem folder, and are not -shared between multiple server instances. Alternative cache backends such as Redis can be -[configured](/developer_guides/performance/caching/). - -While cache objects can expire, when using filesystem caching the files are not actively pruned. For long-lived server -instances, this can become a capacity issue over time - see -[workaround](https://github.com/silverstripe/silverstripe-framework/issues/6678). - -### Error pages - -The default installation -includes [silverstripe/errorpage](https://addons.silverstripe.org/add-ons/silverstripe/errorpage), which generates -static error pages that bypass PHP execution when those pages are published in the CMS. Once published, the static files -are located in `public/assets/error-404.html` and `public/assets/error-500.html`. The default `public/.htaccess` file is -configured to have Apache serve those pages based on their HTTP status code. - -### Other webservers (Nginx, IIS, Lighttpd) - -Serving through webservers other than Apache requires more manual configuration, since the defaults configured -through `.htaccess` don't apply. Please apply the considerations above to your webserver to ensure a secure hosting -environment. In particular, configure protected assets correctly to avoid exposing draft or protected files uploaded -through the CMS. - -There are various community supported installation instructions for different environments. Nginx is a popular choice, -see [Nginx webserver configuration](https://forum.silverstripe.org/t/nginx-webserver-configuration/2246). - -Silverstripe CMS is known to work with Microsoft IIS, and generates `web.config` files by default -( -see [Microsoft IIS and SQL Server configuration](https://forum.silverstripe.org/t/microsoft-iis-webserver-and-sql-server-support/2245)) -. - -Additionally, there are community supported guides for installing Silverstripe CMS on various environments: - -* [Hosting via Bitnami](https://bitnami.com/stack/silverstripe/virtual-machine): In the cloud or as a locally hosted - virtual machine -* [Vagrant/Virtualbox with CentOS](https://forum.silverstripe.org/t/installing-via-vagrant-virtualbox-with-centos/2248) -* [macOS with Homebrew](https://forum.silverstripe.org/t/installing-on-osx-with-homebrew/2247) -* [macOS with MAMP](https://forum.silverstripe.org/t/installing-on-osx-with-mamp/2249) -* [Windows with WAMP](https://forum.silverstripe.org/t/installing-on-windows-via-wamp/2250) -* [Vagrant with silverstripe-australia/vagrant-environment](https://github.com/silverstripe-australia/vagrant-environment) -* [Vagrant with BetterBrief/vagrant-skeleton](https://github.com/BetterBrief/vagrant-skeleton) - -### Email - -Silverstripe CMS uses SwiftMailer to send email messages. New installations setup with silverstripe/installer are configured to use a `sendmail` found in `/usr/sbin/sendmail` or another location specified via configuration. Alternatively email can be configured to use SMTP or other mail transports instead of sendmail. - -You _must_ ensure emails are being sent from your _production_ environment. You can do this by testing that the ***Lost password*** form available at `/Security/lostpassword` sends an email to your inbox, or with the following code snippet that can be run via a `SilverStripe\Dev\BuildTask`: - -```php -$email = new SilverStripe\Control\Email\Email('no-reply@mydomain.com', 'myuser@gmail.com', 'My test subject', 'My email body text'); -$email->send(); -``` - -Using the code snippet above also tests that the ability to set the "from" address is working correctly. - -See the [email section](/developer_guides/email) for further details, including how to set the administrator "from" email address, change the `sendmail` binary location and how to use SMTP or other mail transports instead of sendmail. - - -## PHP Requirements for older Silverstripe CMS releases {#php-support} - -Silverstripe CMS's PHP support has changed over time and if you are looking to upgrade PHP on your Silverstripe CMS site, this -table may be of use: - -| Silverstripe CMS Version | PHP Version | More information | -| -------------------- | ----------- | ---------------- | -| 3.0 - 3.5 | 5.3 - 5.6 | | -| 3.6 | 5.3 - 7.1 | | -| 3.7 | 5.3 - 7.4 | [changelog](https://docs.silverstripe.org/en/3/changelogs/3.7.4/) | -| 4.0 - 4.4 | 5.6+ | | -| 4.5 - 4.9 | 7.1+ | [blog post](https://www.silverstripe.org/blog/our-plan-for-ending-php-5-6-support-in-silverstripe-4/) | -| 4.10 | 7.3+ | [changelog](/Changelogs/4.10.0#phpeol/) | -| 4.11 + | 7.4+ | [changelog](/Changelogs/4.11.0#phpeol) | - -## CMS browser requirements - -Silverstripe CMS supports the following web browsers: - -* Google Chrome -* Microsoft Edge -* Mozilla Firefox - -We aim to provide satisfactory experiences in Apple Safari. Silverstripe CMS works well across Windows, Linux, and Mac -operating systems. - -## End user requirements - -Silverstripe CMS is designed to make excellent, standards-compliant websites that are compatible with a wide range of -industry standard browsers and operating systems. A competent developer is able to produce websites that meet W3C -guidelines for HTML, CSS, JavaScript, and accessibility, in addition to meeting specific guide lines, such as -e-government requirements. diff --git a/docs/en/00_Getting_Started/02_Composer.md b/docs/en/00_Getting_Started/02_Composer.md deleted file mode 100644 index a1d0c9c10b8..00000000000 --- a/docs/en/00_Getting_Started/02_Composer.md +++ /dev/null @@ -1,362 +0,0 @@ ---- -title: Composer -summary: What is composer and how to use it with Silverstripe CMS ---- - -# Using Silverstripe CMS with Composer - -## Requirements - -[Composer](http://getcomposer.org/) is a package management tool for PHP that lets you install and upgrade Silverstripe CMS -and its modules. We also have separate instructions -for [installing modules with Composer](/developer_guides/extending/modules). - -Before installing Composer you should ensure your system has the version control -system, [Git installed](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git). Composer uses Git to check out -the code dependencies you need to run your Silverstripe CMS website from the code repositories maintained on GitHub. - -Next, [install composer](https://getcomposer.org/download/). For our documentation we assume the `composer` command is -installed globally. You should now be able to run the command: - -``` -composer help -``` - -## Create a new site - -Composer can create a new site for you, using the installer as a template. By default it will download the latest stable -version: - -``` -composer create-project silverstripe/installer my-project -``` - -If you want to get all additional fixtures for testing, such as behat and phpunit configuration, an -example `.env.example` file, and all documentation, then it's recommended to use `--prefer-source` -to include these files. - -If you want a minimal installation with the bare essentials to get working without any additional overhead, and don't -plan on contributing back changes to framework, use `--prefer-dist` (default) for a more lightweight install. - -`./my-project` should be the root directory where your site will live. For example, on OS X, you might use a -subdirectory of `~/Sites`. As long as your web server is up and running, this will get all the code that you need. Now -visit the site in your web browser, and the installation process will be completed. - -You can also specify a version to download that version explicitly, i.e. this will download the older `4.3.3` release: - -``` -composer create-project silverstripe/installer ./my-project 4.3.3 -``` - -When `create-project` is used with a release version like above, it will try to get the code from archives instead of -creating git repositories. If you're planning to contribute to Silverstripe CMS, -see [Using development versions](#using-development-versions). - -## Adding modules to your project - -Composer isn't only used to download Silverstripe CMS, it is also used to manage all Silverstripe CMS modules. -You can find thousands of modules on [https://addons.silverstripe.org](https://addons.silverstripe.org). -Installing a module can be done with the following command: - -``` -composer require silverstripe/blog -``` - -This will install the `silverstripe/blog` module in the latest compatible version. If you know the specific version you -want to install already (such as `^2`), you can add it after the package name as -a [version constraint](http://getcomposer.org/doc/01-basic-usage.md#the-require-key): - -``` -composer require silverstripe/blog ^2 -``` - -[warning] -**Version constraints:** `master` is not a legal version string - it's a branch name. These are different things. The -version string that would get you the branch is `dev-master`. The version string that would get you a numeric branch is -a little different. The version string for the `4` branch is `4.x-dev`. -[/warning] - -## Updating dependencies - -Except for the control code of the Voyager space probe, every piece of code in the universe gets updated from time to -time. Silverstripe CMS modules are no exception. - -To get the latest updates of the modules in your project, run this command: - -``` -composer update -``` - -Updates to the required modules will be installed, and the `composer.lock` file will get updated with the specific -commits and version constraints for each of them. - -## Deploying projects with Composer - -When deploying projects with composer, you could just push the code and run `composer update`. This, however, is risky. -In particular, if you were referencing development dependencies and a change was made between your testing and your -deployment to production, you would end up deploying untested code. Not cool! - -The `composer.lock` file helps with this. It references the specific commits that have been checked out, rather than the -version string. You can run `composer install` to install dependencies from this rather than `composer.json`. - -So your deployment process, as it relates to Composer, should be as follows: - -* Run `composer update` on your development version before you start whatever testing you have planned. Perform all the - necessary testing. -* Check `composer.lock` into your repository. -* Deploy your project code base, using the deployment tool of your choice. -* Run `composer install --no-dev -o` on your production version. In this command, the `--no-dev` command tells Composer - not to install your development-only dependencies, and `-o` is an alias for `--optimise-autoloader`, which will - convert your PSR-0 and PSR-4 autoloader definitions into a classmap to improve the speed of the autoloader. - -## Composer managed modules, Git and .gitignore - -Modules and themes managed by Composer should not be committed with your projects source code. Silverstripe CMS ships with -a [.gitignore](http://git-scm.com/docs/gitignore) file by default which prevents this. For more details -read [Should I commit the dependencies in my vendor directory?](https://getcomposer.org/doc/faqs/should-i-commit-the-dependencies-in-my-vendor-directory.md) -. - -## Dev Environments for Contributing Code {#contributing} - -So you want to contribute to Silverstripe CMS? Fantastic! You can do this with composer too. You have to tell composer three -things in order to be able to do this: - -- Keep the full git repository information -- Include dependencies marked as "developer" requirements -- Use the development version, not the latest stable version - -The first two steps are done as part of the initial create project using additional arguments. - -``` -composer create-project --keep-vcs --dev silverstripe/installer ./my-project 4.x-dev --prefer-source -``` - -The process will take a bit longer, since all modules are checked out as full git repositories which you can work on. -The command checks out from the 4.x release line. To check out from master instead, replace `4.x-dev` -with `dev-master` (more info -on [composer version naming](http://getcomposer.org/doc/02-libraries.md#specifying-the-version)). - -The `--keep-vcs` flag will make sure you have access to the git history of the installer and the requirements - -The `--dev` flag is optional, and can be used to add a couple modules which are useful for Silverstripe CMS development: - -* The `behat-extension` module allows running [Behat](http://behat.org) integration tests -* The `docsviewer` module will let you preview changes to the project documentation -* The `buildtools` module which adds [phing](http://phing.info) tasks for creating Silverstripe CMS releases - -Once the `create-project` command completes, you need to edit the `composer.json` in the project root and remove -the `@stable` markers from the `silverstripe/cms` and `silverstripe/framework` version entries. -Another `composer update --dev` call will now fetch from the development branch instead. Note that you can also convert -an existing composer project with these steps. - -Please read the ["Contributing Code"](/contributing/code) documentation to find out how to create forks and send pull -requests. - -# Advanced usage - -## Manually editing composer.json - -To remove dependencies, or if you prefer seeing all your dependencies in a text file, you can edit the `composer.json` -file. It will appear in your project root, and by default, it will look something like this: - -```json -{ - "name": "silverstripe/installer", - "description": "The Silverstripe Framework Installer", - "require": { - "php": ">=7.3", - "silverstripe/cms": "^4", - "silverstripe/framework": "^4", - "silverstripe-themes/simple": "^3" - }, - "require-dev": { - "silverstripe/docsviewer": "^3" - }, - "minimum-stability": "dev", - "prefer-stable": true -} -``` - -To add modules, you should add more entries into the `"require"` section. For example, we might add the blog and forum -modules. Be careful with the commas at the end of the lines! - -Save your file, and then run the following command to refresh the installed packages: - -``` -composer update -``` - -## Using development versions - -Composer will by default download the latest stable version of silverstripe/installer. The `composer.json` file that -comes with silverstripe/installer may also explicitly state it requires the stable version of cms and framework - this -is to ensure that when developers are getting started, running `composer update` won't upgrade their project to an -unstable version - -However it is relatively easy to tell composer to use development versions. Not only is this required if you want to -contribute back to the Silverstripe CMS project, it also allows you to get fixes and API changes early. - -This is a two step process. First you get composer to start a project based on the latest unstable -silverstripe/installer - -``` -composer create-project silverstripe/installer ./my-project dev-master -``` - -Or for the latest development version in the 4.0.x series - -``` -composer create-project silverstripe/installer ./my-project 4.0.x-dev -``` - -## Working with project forks and unreleased modules - -By default, Composer will install modules listed on the Packagist site. There are a few reasons that you might not want -to do this. For example: - -* You may have your own fork of a module, either specific to a project, or because you are working on a pull request -* You may have a module that hasn't been released to the public. - -There are many ways that you can address this, but this is one that we recommend, because it minimises the changes you -would need to make to switch to an official version in the future. - -This is how you do it: - -* **Ensure that all of your fork repositories have correct composer.json files.** Set up the project forks as you would - a distributed package. If you have cloned a repository that already has a composer.json file, then there's nothing you - need to do, but if not, you will need to create one yourself. - -* **List all your fork repositories in your project's composer.json files.** You do this in a `repositories` section. - Set the `type` to `vcs`, and `url` to the URL of the repository. The result will look something like this: - -```json -{ - "name": "silverstripe/installer", - "description": "The Silverstripe Framework Installer", - "repositories": [ - { - "type": "vcs", - "url": "git@github.com:sminnee/silverstripe-cms.git" - } - ] -} -``` - -* **Install the module as you would normally.** Use the regular composer function - there are no special flags to use a - fork. Your fork will be used in place of the package version. - -``` -composer require silverstripe/cms -``` - -Composer will scan all of the repositories you list, collect meta-data about the packages within them, and use them in -favour of the packages listed on packagist. To switch back to using the mainline version of the package, just remove -the `repositories` section from `composer.json` and run `composer update`. - -Now add an "upstream" remote to the original repository location so you can rebase or merge your fork as required. - -``` -cd cms -git remote add -f upstream git://github.com/silverstripe/silverstripe-cms.git -``` - -For more information, read -the ["Repositories" chapter of the Composer documentation](http://getcomposer.org/doc/05-repositories.md). - -### Forks and branch names - -Generally, you should keep using the same pattern of branch names as the main repositories does. If your version is a -fork of 4.0, then call the branch `4.0`, not `4.0-myproj` or `myproj`. Otherwise, the dependency resolution gets -confused. - -Sometimes, however, this isn't feasible. For example, you might have a number of project forks stored in a single -repository, such as your personal GitHub fork of a project. Or you might be testing/developing a feature branch. Or it -might just be confusing to other team members to call the branch of your modified version `4.0`. - -In this case, you need to use Composer's aliasing feature to specify how you want the project branch to be treated, when -it comes to dependency resolution. - -Open `composer.json`, and find the module's `require`. Then put `as (core version name)` on the end. - -```json -{ - "require": { - "php": ">=5.5.0", - "silverstripe/cms": "3.5.1.2", - "silverstripe/framework": "dev-myproj as 4.0.x-dev", - "silverstripe-themes/simple": "~3.2.0" - } -} -``` - -What this means is that when the `myproj` branch is checked out into a project, this will satisfy any dependencies -that `4.0.x-dev` would meet. So, if another module has `"silverstripe/framework": "^4.0.0"` in its dependency list, it -won't get a conflict. - -Both the version and the alias are specified as Composer versions, not branch names. For the relationship between -branch/tag names and Composer versions, -read [the relevant Composer documentation](http://getcomposer.org/doc/02-libraries.md#specifying-the-version). - -This is not the only way to set things up in Composer. For more information on this topic, read -the ["Aliases" chapter of the Composer documentation](http://getcomposer.org/doc/articles/aliases.md). - -## FAQ - -### Error "The requested package silverstripe/framework 1.0.0 could not be found" - -Composer needs hints about the base package version, either by using `composer create-project` -as described above, or by checking out the `silverstripe-installer` project directly from version control. In order to -use Composer on archive downloads from silverstripe.org, or other unversioned sources, an advanced workaround is to set -the `COMPOSER_ROOT_VERSION` before every command -([details](http://getcomposer.org/doc/03-cli.md#composer-root-version)) - -### How do I convert an existing module to using Composer? - -Simply decide on a [unique name and vendor prefix](https://packagist.org/about), create a `composer.json`, and either -commit it or send a pull request to the module author. Look at existing modules like -the ["blog" module](https://github.com/silverstripe/silverstripe-blog/blob/master/composer.json) for good examples on -what this file should contain. It's important that the file contains a custom "type" to declare it as a -`silverstripe-module` or `silverstripe-theme` ( -see [custom installers](http://getcomposer.org/doc/articles/custom-installers.md)). Then register the module -on [packagist.org](http://packagist.org). - -### How should I name my module? - -Follow the packagist.org advice on choosing a [unique name and vendor prefix](https://packagist.org/about). Please don't -use the `silverstripe/` vendor prefix, since that's reserved for modules produced by Silverstripe Ltd. In -order to declare that your module is in fact a Silverstripe CMS module, use the "silverstripe" tag in the composer.json -file, and set the "type" to "silverstripe-module". - -### What about themes? - -Themes are technically just "modules" which are placed in the `themes/` subdirectory. We denote a special type for them -in the `composer.json` (`"type": "silverstripe-theme"`), which triggers their installation into the correct path. - -### How do I convert an existing project to Composer? - -Copy the `composer.json` file from a newer release, and adjust the version settings in the "require" section to your -needs. Then refer to the [upgrading documentation](/upgrading). You'll also need to update your webserver configuration -from there (`.htaccess` or `web.config` files), in order to prevent web access to the composer-generated files. - -### Do I need composer on my live server? - -It depends on your deployment process. If you copy or rsync files to your live server, the process stays the same. If -the live server hosts a git repository checkout, which is updated to push a newer version, you'll also need to -run `composer install` afterwards. We recommend looking -into [Composer "lock" files](http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file) for this purpose. - -### Can I keep using Downloads, Subversion Externals or Git Submodules? - -Composer is more than just a file downloader. It comes with additional features such as -[autoloading](http://getcomposer.org/doc/01-basic-usage.md#autoloading) -or [scripts](http://getcomposer.org/doc/articles/scripts.md) -which some modules will start relying on. Please check the module README for specific installation instructions. - -### I don't want to get development versions of everything! - -You don't have to, Composer is designed to work on the constraints you set. You can declare -the ["minimum-stability"](http://getcomposer.org/doc/04-schema.md#minimum-stability) -on your project as suitable, or even whitelist specific modules as tracking a development branch while keeping others to -their stable release. Read up -on [Composer "lock" files](http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file) on how this all -fits together. diff --git a/docs/en/00_Getting_Started/03_Environment_Management.md b/docs/en/00_Getting_Started/03_Environment_Management.md deleted file mode 100644 index aa87a727dc5..00000000000 --- a/docs/en/00_Getting_Started/03_Environment_Management.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: Environment Management -summary: How to configure your server environment for Silverstripe CMS ---- - -# Environment management - -As part of website development and hosting it is natural for our sites to be hosted on several different environments. -These can be our laptops for local development, a testing server for customers to test changes on, or a production -server. - -For each of these environments we may require slightly different configurations for our servers. This could be our debug -level, caching backends, or - of course - sensitive information such as database credentials. - -To manage environment variables, as well as other server globals, the [api:SilverStripe\Core\Environment] class provides -a set of APIs and helpers. - -## Security considerations - -Sensitive credentials should not be stored in a VCS or project code and should only be stored on the environment in -question. When using live environments the use of `.env` files is discouraged and instead one should use "first class" -environment variables. - -If you do use a `.env` file on your servers, you must ensure that external access to `.env` files is blocked by the -webserver. - -## Managing environment variables with .env files - -By default a file named `.env` must be placed in your project root (ie: the same folder as your `composer.json`) or the -parent directory. If this file exists, it will be automatically loaded by the framework and the environment variables -will be set. An example `.env` file is included in the default installer named `.env.example`. - -**Note:** The file must be named exactly `.env` and not any variation (such as `mysite.env` or `.env.mysite`) or it will -not be detected automatically. If you wish to load environment variables from a file with a different name, you will -need to do so manually. See the [Including an extra `.env` file](#including-an-extra-env-file) section below for more -information. - -## Managing environment variables with Apache - -You can set "real" environment variables using Apache. Please -[see the Apache docs for more information](https://httpd.apache.org/docs/current/env.html). - -## How to access the environment variables - -Accessing the environment variables should be done via the `Environment::getEnv()` method. - -```php -use SilverStripe\Core\Environment; -Environment::getEnv('SS_DATABASE_CLASS'); -``` - -Individual settings can be assigned via `Environment::setEnv()` or `Environment::putEnv()` methods. - -```php -use SilverStripe\Core\Environment; -Environment::setEnv('API_KEY', 'AABBCCDDEEFF012345'); -``` - -### Using environment variables in config - -To use environment variables in `.yaml` configs you can reference them using backticks. You can have multiple -environment variables within a single value, though the overall value must start and end with backticks. - -```yaml -SilverStripe\Core\Injector\Injector: - MyServiceClass: - properties: - MyProperty: '`ENV_VAR_ONE`' - MultiValueProperty: '`ENV_VAR_ONE`:`ENV_VAR_TWO`' - ThisWillNotSubstitute: 'lorem `REGULAR_TEXT` ipsum' -``` - -[info] -Environment variables cannot be used outside of Injector config as of version 4.2. -[/info] - -## Including an extra .env file - -Sometimes it may be useful to include an extra `.env` file - on a shared local development environment where all -database credentials could be the same. To do this, you can add this snippet to your `app/_config.php` file: - -Note that by default variables cannot be overloaded from this file; Existing values will be preferred over values in -this file. - -```php -use SilverStripe\Core\EnvironmentLoader; -$env = BASE_PATH . '/app/.env'; -$loader = new EnvironmentLoader(); -$loader->loadFile($env); -``` - -## Core environment variables - -Silverstripe core environment variables are listed here, though you're free to define any you need for your application. - -| Name | Description | -| ---- | ----------- | -| `SS_DATABASE_CLASS` | The database class to use, MySQLPDODatabase, MySQLDatabase, MSSQLDatabase, etc. defaults to MySQLDatabase.| -| `SS_DATABASE_SERVER`| The database server to use, defaulting to localhost.| -| `SS_DATABASE_USERNAME`| The database username (mandatory).| -| `SS_DATABASE_PASSWORD`| The database password (mandatory).| -| `SS_DATABASE_PORT`| The database port.| -| `SS_DATABASE_SUFFIX`| A suffix to add to the database name.| -| `SS_DATABASE_PREFIX`| A prefix to add to the database name.| -| `SS_DATABASE_TIMEZONE`| Set the database timezone to something other than the system timezone. -| `SS_DATABASE_NAME` | Set the database name. Assumes the `$database` global variable in your config is missing or empty. | -| `SS_DATABASE_CHOOSE_NAME`| Boolean/Int. If defined, then the system will choose a default database name for you if one isn't give in the $database variable. The database name will be "SS_" followed by the name of the folder into which you have installed Silverstripe. If this is enabled, it means that the phpinstaller will work out of the box without the installer needing to alter any files. This helps prevent accidental changes to the environment. If `SS_DATABASE_CHOOSE_NAME` is an integer greater than one, then an ancestor folder will be used for the database name. This is handy for a site that's hosted from /sites/examplesite/www or /buildbot/allmodules-2.3/build. If it's 2, the parent folder will be chosen; if it's 3 the grandparent, and so on.| -| `SS_DEPRECATION_ENABLED` | Enable deprecation notices for this environment.| -| `SS_ENVIRONMENT_TYPE`| The environment type: dev, test or live.| -| `SS_DEFAULT_ADMIN_USERNAME`| The username of the default admin. This is a user with administrative privileges.| -| `SS_DEFAULT_ADMIN_PASSWORD`| The password of the default admin. This will not be stored in the database.| -| `SS_USE_BASIC_AUTH`| Baseline protection for requests handled by Silverstripe. Usually requires additional security measures for comprehensive protection. See [Environment Types](/developer_guides/debugging/environment_types) for caveats.| -| `SS_SEND_ALL_EMAILS_TO`| If you define this constant, all emails will be redirected to this address.| -| `SS_SEND_ALL_EMAILS_FROM`| If you define this constant, all emails will be sent from this address.| -| `SS_ERROR_LOG` | Relative path to the log file. | -| `SS_PROTECTED_ASSETS_PATH` | Path to secured assets - defaults to ASSETS_PATH/.protected | -| `SS_DATABASE_MEMORY` | Used for SQLite3 DBs | -| `SS_TRUSTED_PROXY_IPS` | IP address or CIDR range to trust proxy headers from. If left blank no proxy headers are trusted. Can be set to 'none' (trust none) or '*' (trust all) | -| `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to | -| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a CacheInterface or CacheFactory class name | -| `SS_IGNORE_DOT_ENV` | If set the .env file will be ignored. This is good for live to mitigate any performance implications of loading the .env file | -| `SS_BASE_URL` | The url to use when it isn't determinable by other means (eg: for CLI commands) | -| `SS_DATABASE_SSL_KEY` | Absolute path to SSL key file | -| `SS_DATABASE_SSL_CERT` | Absolute path to SSL certificate file | -| `SS_DATABASE_SSL_CA` | Absolute path to SSL Certificate Authority bundle file | -| `SS_DATABASE_SSL_CIPHER` | Optional setting for custom SSL cipher | -| `SS_FLUSH_ON_DEPLOY` | Try to detect deployments through file system modifications and flush on the first request after every deploy. Does not run "dev/build", but only "flush". Possible values are `true` (check for a framework PHP file modification time), `false` (no checks, skip deploy detection) or a path to a specific file or folder to be checked. See [DeployFlushDiscoverer](api:SilverStripe\Core\Startup\DeployFlushDiscoverer) for more details.

False by default. | -| `SS_TEMP_PATH` | File storage used for the default cache adapters in [Manifests](/developer_guides/execution_pipeline/manifests), [Object Caching](/developer_guides/performance/caching) and [Partial Template Caching](/developer_guides/templates/partial_template_caching). It defaults to creating a sub-directory of PHP's built-in `sys_get_temp_dir()`. Can be an absolute path (with a leading `/`), or a path relative to the project root. | diff --git a/docs/en/00_Getting_Started/04_Directory_Structure.md b/docs/en/00_Getting_Started/04_Directory_Structure.md deleted file mode 100644 index 748a56bf41e..00000000000 --- a/docs/en/00_Getting_Started/04_Directory_Structure.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: Directory Structure -summary: An overview of what each directory contains in a Silverstripe CMS installation -icon: sitemap ---- - -# Directory Structure - -## Introduction - -The directory-structure in Silverstripe is built on "convention over configuration", so the placement of some files and -directories is meaningful to its logic. - -## Core Structure - -Directory | Description ---------- | ----------- -`public/` | Webserver public webroot -`public/assets/` | Images and other files uploaded via the Silverstripe CMS. You can also place your own content inside it, and link to it from within the content area of the CMS. -`public/assets/.protected/` | Default location for [protected assets](/developer_guides/files/file_security) -`public/_resources/` | Exposed public files added from modules. Folders within this parent will match that of the source root location (this can be altered by configuration). -`vendor/` | Silverstripe modules and other supporting libraries (the framework is in `vendor/silverstripe/framework`) -`themes/` | Standard theme installation location - -## Custom Code Structure - -We're using `app/` as the default folder. Note that until Silverstripe 4.2, this directory was named `mysite/`, and PHP -code was stored in a `code/` rather than `src/` folder. - -| Directory | Description | - | --------- | ----------- | -| `app/` | This directory contains all of your code that defines your website. | -| `app/_config` | YAML configuration specific to your application | -| `app/src` | PHP code for model and controller (subdirectories are optional) | -| `app/tests` | PHP Unit tests | -| `app/templates` | HTML [templates](/developer_guides/templates) with *.ss-extension for the `$default` theme | -| `app/css ` | CSS files | -| `app/images ` | Images used in the HTML templates | -| `app/javascript` | Javascript and other script files | -| `app/client` | More complex projects can alternatively contain frontend assets in a common `client` folder | -| `app/themes/` | Custom nested themes (note: theme structure is described below) | - -Arbitrary directory-names are allowed, as long as they don't collide with existing modules or the directories lists in -"Core Structure". Here's how you would reconfigure your default folder to `myspecialapp`. - -*myspecialapp/_config/config.yml* - -```yml ---- -Name: myspecialapp ---- -SilverStripe\Core\Manifest\ModuleManifest: - project: 'myspecialapp' -``` - -Check our [JavaScript Coding Conventions](javascript_coding_conventions) for more details on folder and file naming in -Silverstripe core modules. - -## Themes Structure - -| Directory | Description | - | ------------------ | --------------------------- | -| `themes/simple/` | Standard "simple" theme | -| `themes//` | Custom theme base directory | -| `themes//templates` | Theme templates | -| `themes//css` | Theme CSS files | - -See [themes](/developer_guides/templates/themes). - -## Module Structure {#module_structure} - -Modules are commonly stored as composer packages in the `vendor/` folder. They need to have a `_config.php` file or -a `_config/` directory present, and should follow the same conventions as posed in "Custom Site Structure". - -Example Forum: - -| Directory | Description | - | --------- | ----------- | -| `vendor/silverstripe/blog/`| This directory contains all of your code that defines your website. | -| `vendor/silverstripe/blog/code` | PHP code for model and controller (subdirectories are optional) | -| ... | ... | - -Note: Before Silverstripe 4.x, modules were living as top-level folders in the webroot itself. Some modules might not -have been upgraded to support placement in `vendor/`. - -### Module documentation - -Module developers can bundle developer documentation with their code by producing plain text files inside a 'docs' -folder located in the module folder. These files can be written with the Markdown syntax -(see [Contributing Documentation](/contributing/documentation)) -and include media such as images or videos. - -Inside the `docs/` folder, developers should organise the markdown files into each separate language they wish to write -documentation for (usually just `en`). Inside each languages' subfolder, developers then have freedom to create whatever -structure they wish for organising the documentation they wish. - -Example Blog Documentation: - -| Directory | Description | - | --------- | ----------- | -| `vendor/silverstripe/blog/docs` | | -| `vendor/silverstripe/blog/docs/_manifest_exclude` | Empty file to signify that Silverstripe does not need to load classes from this folder | -| `vendor/silverstripe/blog/docs/en/` | English documentation | -| `vendor/silverstripe/blog/docs/en/index.md` | Documentation homepage. Should provide an introduction and links to remaining docs | -| `vendor/silverstripe/blog/docs/en/Getting_Started.md` | Documentation page. Naming convention is Uppercase and underscores. | -| `vendor/silverstripe/blog/docs/en/_images/` | Folder to store any images or media | -| `vendor/silverstripe/blog/docs/en/Some_Topic/` | You can organise documentation into nested folders. Naming convention is Uppercase and underscores. | -| `vendor/silverstripe/blog/docs/en/04_Some_Topic/00_Getting_Started.md`|Structure is created by use of numbered prefixes. This applies to nested folders and documentations pages, index.md should not have a prefix.| - -## Autoloading - -Silverstripe recursively detects classes in PHP files by building up a manifest used for autoloading, as well as -respecting Composer's built-in autoloading for libraries. This means in most cases, you don't need to worry about -include paths or `require()` calls in your own code - after adding a new class, simply regenerate the manifest by using -a `flush=1` query parameter. See the ["Manifests" documentation](/developer_guides/execution_pipeline/manifests) for -details. - -## Best Practices - -### Making /assets readonly - -See [Secure coding](/developer_guides/security/secure_coding#filesystem) diff --git a/docs/en/00_Getting_Started/05_Recipes.md b/docs/en/00_Getting_Started/05_Recipes.md deleted file mode 100644 index 8538a1a7b79..00000000000 --- a/docs/en/00_Getting_Started/05_Recipes.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Recipes -summary: What Recipes are, and how they are used in Silverstripe CMS -icon: clipboard ---- - -# Adding features to your project with Recipes - -To achieve more complex use cases in Silverstripe CMS, you may need to combine many modules and add extra configuration to integrate these together. Silverstripe CMS Recipes streamline this process for common use cases. - -## What are Silverstripe CMS Recipes? - -Recipes are used to implement common broad feature sets by shipping a collection of modules along with the relevant integration logic. They allow developers to quickly get started while retaining the ability to customise their integration to their specific needs. - -Before each version of a supported CMS recipe is released, it is comprehensively regression tested and passed to a third party for a security-focused audit, making sure that projects have a secure starting point or a safe and secure upgrade with each recipe release. - -## What's the difference between a recipe and a module? - -Silverstripe CMS is powered by a system of components in the form of Composer packages. It consists of two types of package: - -- **Modules**, which provide pieces of functionality (such as `silverstripe/cms` and `silverstripe/framework`) -- **Recipes**, which group related Modules together to make them easier to install and release. - -By design, modules tend to be small and serve a specific function. You may need to combine many modules to achieve a wider goal. - -For example, the `silverstripe/blog` module by itself simply allows you to create blog posts. It does not include all the features you could want in a blog, like a comment system or widgets to display related content. - -The `silverstripe/recipe-blog` recipe installs `silverstripe/blog` module, but also: -- `silverstripe/widgets` and `silverstripe/content-widget` to display widgets -- `silverstripe/comments` and `silverstripe/comment-notifications` to allow the management of comments on blog post -- `silverstripe/spamprotection` and `silverstripe/akismet` to provide basic SPAM protection on comments. - -## Finding recipes for Silverstripe CMS - -The Silverstripe CMS project maintains a number of recipes. Some third parties also maintain recipes. - -[Search Packagist for all packages with the `silverstripe-recipe`](https://packagist.org/?query=silverstripe&type=silverstripe-recipe) type to find recipes you can install on your Silverstripe CMS project. - -## Releasing supported recipes - -When we announce a new release of Silverstripe CMS and publish a changelog for it, we refer to a new set of _Recipe_ versions, which include new versions of some or all of their associated Modules. The easiest way to keep up to date with new Silverstripe CMS releases is to depend on one of the core Recipes: - -- [`silverstripe/recipe-core`](https://packagist.org/packages/silverstripe/recipe-core): Contains only the base - framework, without the admin UI or CMS features. -- [`silverstripe/recipe-cms`](https://packagist.org/packages/silverstripe/recipe-cms): Includes `recipe-core`, and adds - the admin UI and CMS features. We recommend specifying this recipe in your dependencies. -- [`silverstripe/installer`](https://packagist.org/packages/silverstripe/installer): Includes `recipe-cms`, and adds a - default theme for the front-end of your site. We recommend creating new projects based on this recipe ( - via `composer create-project silverstripe/installer myproject ^4`). - -When determining whether you are running the latest version of Silverstripe CMS, it is easier to refer to the Recipe -version than the individual Module versions, which may not align with Recipe versions. You can use Packagist to find -detailed information on what versions of individual modules are contained in each Recipe release. diff --git a/docs/en/00_Getting_Started/index.md b/docs/en/00_Getting_Started/index.md deleted file mode 100644 index 794927bca8c..00000000000 --- a/docs/en/00_Getting_Started/index.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Getting Started -introduction: Silverstripe is a web application. This means that you will need to have a webserver and database. We will take you through the setup of the server environment as well the application itself. -icon: rocket ---- - -## Server Requirements - -Silverstripe requires PHP 7.1 or newer. It runs on many webservers and databases, but is most commonly served using -Apache and MySQL/MariaDB. - -If you are setting up your own environment, you'll need to consider a few configuration settings such as URL rewriting -and protecting access to certain files. Refer to our [server requirements](server_requirements) for details. - -## Quickstart Installation - -If you're running Apache with MySQL/MariaDB already, and know your way around webservers, follow these steps to get -started. Silverstripe is installed via [Composer](https://getcomposer.org), a package management tool for PHP that lets -you install and upgrade the framework and other modules. Assuming you've got this tool, run the following command to -install Silverstripe: - -``` -composer create-project silverstripe/installer my-project -``` - -Within the newly created `my-project` folder, point your webserver at the `public/` folder. - -Now create a `.env` file your project root (not the `public/` folder). It sets up the minimum -required [environment variables](environment_management). Replace the placeholders as required: - -``` -SS_DATABASE_CLASS="MySQLDatabase" -SS_DATABASE_NAME="" -SS_DATABASE_SERVER="localhost" -SS_DATABASE_USERNAME="" -SS_DATABASE_PASSWORD="" -SS_DEFAULT_ADMIN_USERNAME="admin" -SS_DEFAULT_ADMIN_PASSWORD="password" -SS_ENVIRONMENT_TYPE="" -``` - -Now you should be able to build your database by running this command: - -``` -vendor/bin/sake dev/build -``` - -Your website should be available on your domain now (e.g. `http://localhost`). The CMS login can be accessed at `/admin`. - -For more information on how to maintain your installation or install projects, check -out [Using Silverstripe with Composer](composer). - -## Guided Installation - -If you are unsure on how this all works, please jump on our [lessons](https://www.silverstripe.org/learn/lessons/v4/). -Webserver setup is covered in -[Lesson 4: Setting up a local dev environment](https://www.silverstripe.org/learn/lessons/v4/up-and-running-setting-up-a-local-silverstripe-dev-environment-1). - -## Keep learning - -[CHILDREN] - -## Troubleshooting - -If you run into trouble, see [the Tips & Tricks forum](https://forum.silverstripe.org/c/tips) or get help on -our [Slack channel](https://www.silverstripe.org/community/slack-signup/). - diff --git a/docs/en/01_Lessons/index.md b/docs/en/01_Lessons/index.md deleted file mode 100644 index 8d9a36811a2..00000000000 --- a/docs/en/01_Lessons/index.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Lessons -introduction: The lessons take a step by step look at how to build a Silverstripe CMS application. -icon: graduation-cap ---- - -* [How to set up a local development environment in Silverstripe CMS](https://www.silverstripe.org/learn/lessons/v4/up-and-running-setting-up-a-local-silverstripe-dev-environment-1) -* [Lesson 1: Creating your first project](https://www.silverstripe.org/learn/lessons/v4/creating-your-first-project) -* [Lesson 2: Migrating static templates into your theme](https://www.silverstripe.org/learn/lessons/v4/migrating-static-templates-into-your-theme-1) -* [Lesson 3: Adding dynamic content](https://www.silverstripe.org/learn/lessons/v4/adding-dynamic-content-1) -* [Lesson 4: Working with multiple templates](https://www.silverstripe.org/learn/lessons/v4/working-with-multiple-templates-1) -* [Lesson 5: The holder/page pattern](https://www.silverstripe.org/learn/lessons/v4/the-holderpage-pattern-1) -* [Lesson 6: Adding Custom Fields to a Page](https://www.silverstripe.org/learn/lessons/v4/adding-custom-fields-to-a-page-1) -* [Lesson 7: Working with Files and Images](https://www.silverstripe.org/learn/lessons/v4/working-with-files-and-images-1) -* [Lesson 8: Introduction to the ORM](https://www.silverstripe.org/learn/lessons/v4/introduction-to-the-orm-1) -* [Lesson 9: Data Relationships - $has_many](https://www.silverstripe.org/learn/lessons/v4/working-with-data-relationships-has-many-1) -* [Lesson 10: Data Relationships - $many_many](https://www.silverstripe.org/learn/lessons/v4/working-with-data-relationships-many-many-1) -* [Lesson 11: Introduction to frontend forms](https://www.silverstripe.org/learn/lessons/v4/introduction-to-frontend-forms-1) -* [Lesson 12: Data Extensions and SiteConfig](https://www.silverstripe.org/learn/lessons/v4/data-extensions-and-siteconfig-1) -* [Lesson 13: Introduction to ModelAdmin](https://www.silverstripe.org/learn/lessons/v4/introduction-to-modeladmin-1) -* [Lesson 14: Controller Actions/DataObjects as Pages](https://www.silverstripe.org/learn/lessons/v4/controller-actions-dataobjects-as-pages-1) -* [Lesson 15: Building a Search Form](https://www.silverstripe.org/learn/lessons/v4/building-a-search-form-1) -* [Lesson 16: Lists and Pagination](https://www.silverstripe.org/learn/lessons/v4/lists-and-pagination-1) -* [Lesson 17: Ajax Behaviour and Viewable Data](https://www.silverstripe.org/learn/lessons/v4/ajax-behaviour-and-viewabledata-1) -* [Lesson 18: Dealing with Arbitrary Template Data](https://www.silverstripe.org/learn/lessons/v4/dealing-with-arbitrary-template-data-1) -* [Lesson 19: Creating Filtered Views](https://www.silverstripe.org/learn/lessons/v4/creating-filtered-views-1) -* [Lesson 20: Beyond the ORM: Building Custom SQL](https://www.silverstripe.org/learn/lessons/v4/beyond-the-orm-building-custom-sql-1) -* [Lesson 21: Advanced Environment Configuration](https://www.silverstripe.org/learn/lessons/v4/advanced-environment-configuration-1) - -## Help: If you get stuck - -* [The Tips & Tricks forum](https://forum.silverstripe.org/c/tips): Review some existing solutions to common problems. -* [Silverstripe CMS Community](http://www.silverstripe.org/community/): Join our community chat via Slack, or ask a question - on Stack Overflow. diff --git a/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md b/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md deleted file mode 100644 index 7e1aa27ed6f..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md +++ /dev/null @@ -1,770 +0,0 @@ ---- -title: Introduction to the Data Model and ORM -summary: Introduction to creating and querying a database records through the ORM (object-relational mapping) -icon: database ---- - -# Introduction to the Data Model and ORM - -Silverstripe uses an [object-relational mapping](http://en.wikipedia.org/wiki/Object-relational_mapping) to represent its -information. - -* Each database table maps to a PHP class. -* Each database row maps to a PHP object. -* Each database column maps to a property on a PHP object. - -All data tables in Silverstripe CMS are defined as subclasses of [DataObject](api:SilverStripe\ORM\DataObject). The [DataObject](api:SilverStripe\ORM\DataObject) class represents a -single row in a database table, following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern) -design pattern. Database Columns are defined as [Data Types](/developer_guides/model/data_types_and_casting) in the static `$db` variable -along with any [relationships](relations) defined as `$has_one`, `$has_many`, `$many_many` properties on the class. - -Let's look at a simple example: - -**app/src/Player.php** - -```php -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - private static $db = [ - 'PlayerNumber' => 'Int', - 'FirstName' => 'Varchar(255)', - 'LastName' => 'Text', - 'Birthday' => 'Date' - ]; -} -``` - -This `Player` class definition will create a database table `Player` with columns for `PlayerNumber`, `FirstName` and -so on. After writing this class, we need to regenerate the database schema. - -## Generating the Database Schema - -After adding, modifying or removing `DataObject` subclasses, make sure to rebuild your Silverstripe CMS database. The -database schema is generated automatically by visiting the URL http://www.yoursite.com/dev/build while authenticated as an administrator. - -This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema -as required. - -It will perform the following changes: - -* Create any missing tables -* Create any missing fields -* Create any missing indexes -* Alter the field type of any existing fields -* Rename any obsolete tables that it previously created to _obsolete_(tablename) - -It **won't** do any of the following - -* Delete tables -* Delete fields -* Rename any tables that it doesn't recognize. This allows other applications to coexist in the same database, as long as - their table names don't match a Silverstripe CMS data class. - - -[notice] -You need to be logged in as an administrator to perform this command, unless your site is in [dev mode](../debugging), -or the command is run through [CLI](../cli). -[/notice] - -When rebuilding the database schema through the [ClassLoader](api:SilverStripe\Core\Manifest\ClassLoader) the following additional properties are -automatically set on the `DataObject`. - -* ID: Primary Key. This will use the database's built-in auto-numbering system on the base table, and apply the same ID to all subclass tables. -* ClassName: An enumeration listing this data-class and all of its subclasses. -* Created: A date/time field set to the creation date of this record -* LastEdited: A date/time field set to the date this record was last edited through `write()` - -**app/src/Player.php** - -```php -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - private static $db = [ - 'PlayerNumber' => 'Int', - 'FirstName' => 'Varchar(255)', - 'LastName' => 'Text', - 'Birthday' => 'Date' - ]; -} -``` - -Generates the following `SQL`. - -```sql -CREATE TABLE `Player` ( - `ID` int(11) NOT NULL AUTO_INCREMENT, - `ClassName` enum('Player') DEFAULT 'Player', - `LastEdited` datetime DEFAULT NULL, - `Created` datetime DEFAULT NULL, - `PlayerNumber` int(11) NOT NULL DEFAULT '0', - `FirstName` varchar(255) DEFAULT NULL, - `LastName` mediumtext, - `Birthday` datetime DEFAULT NULL, - - PRIMARY KEY (`ID`), - KEY `ClassName` (`ClassName`) -); -``` - -## Creating Data Records - -A new instance of a [DataObject](api:SilverStripe\ORM\DataObject) can be created using the `new` syntax. - -```php -$player = new Player(); -``` - -Or, a better way is to use the `create` method. - -```php -$player = Player::create(); -``` - -[notice] -Using the `create()` method provides chainability, which can add elegance and brevity to your code, e.g. `Player::create()->write()`. More importantly, however, it will look up the class in the [Injector](../extending/injector) so that the class can be overridden by [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). -[/notice] - - -Database columns and properties can be set as class properties on the object. The Silverstripe CMS ORM handles the saving -of the values through a custom `__set()` method. - -```php -$player->FirstName = "Sam"; -$player->PlayerNumber = 07; -``` - -To save the `DataObject` to the database, use the `write()` method. The first time `write()` is called, an `ID` will be -set. - -```php -$player->write(); -``` - -For convenience, the `write()` method returns the record's ID. This is particularly useful when creating new records. - -```php -$player = Player::create(); -$id = $player->write(); -``` - -## Querying Data - -With the `Player` class defined we can query our data using the `ORM` or Object-Relational Model. The `ORM` provides -shortcuts and methods for fetching, sorting and filtering data from our database. - -```php -$players = Player::get(); -// returns a `DataList` containing all the `Player` objects. - -$player = Player::get()->byID(2); -// returns a single `Player` object instance that has the ID of 2. - -echo $player->ID; -// returns the players 'ID' column value - -echo $player->dbObject('LastEdited')->Ago(); -// calls the `Ago` method on the `LastEdited` property. -``` - -The `ORM` uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods -are `filter()` and `sort()`: - -```php -$members = Player::get()->filter([ - 'FirstName' => 'Sam' -])->sort('Surname'); - -// returns a `DataList` containing all the `Player` records that have the `FirstName` of 'Sam' - -``` - -[info] -Provided `filter` values are automatically escaped and do not require any escaping. -[/info] - -[info] -`DataObject::get()->byID()` and `DataObject::get_by_id()` achieve similar results, but the object returned by `DataObject::get_by_id()` is cached against a `static` property within `DataObject`. - -`DataObject::get_by_id()` is a legacy ORM method, and it is recommended that you use `DataObject::get()->byID()` wherever possible -[/info] - -## Lazy Loading - -The `ORM` doesn't actually execute the [SQLSelect](api:SilverStripe\ORM\Queries\SQLSelect) until you iterate on the result with a `foreach()` or `<% loop %>`. - -It's smart enough to generate a single efficient query at the last moment in time without needing to post-process the -result set in PHP. In `MySQL` the query generated by the ORM may look something like this - -```php -$players = Player::get()->filter([ - 'FirstName' => 'Sam' -]); - -$players = $players->sort('Surname'); - -// executes the following single query -// SELECT * FROM Player WHERE FirstName = 'Sam' ORDER BY Surname -``` - -This also means that getting the count of a list of objects will be done with a single, efficient query. - -```php -$players = Player::get()->filter([ - 'FirstName' => 'Sam' -])->sort('Surname'); - -// This will create an single SELECT COUNT query -// SELECT COUNT(*) FROM Player WHERE FirstName = 'Sam' -echo $players->Count(); -``` - -## Looping over a list of objects - -`get()` returns a `DataList` instance. You can loop over `DataList` instances in both PHP and templates. - -```php -$players = Player::get(); - -foreach($players as $player) { - echo $player->FirstName; -} -``` - -Notice that we can step into the loop safely without having to check if `$players` exists. The `get()` call is robust, and will at worst return an empty `DataList` object. If you do want to check if the query returned any records, you can use the `exists()` method, e.g. - -```php -$players = Player::get(); - -if($players->exists()) { - // do something here -} -``` - -See the [Lists](lists) documentation for more information on dealing with [SS_List](api:SilverStripe\ORM\SS_List) instances. - -## Returning a single DataObject - -There are a couple of ways of getting a single DataObject from the ORM. If you know the ID number of the object, you -can use `byID($id)`: - -```php -$player = Player::get()->byID(5); -``` - -`get()` returns a [DataList](api:SilverStripe\ORM\DataList) instance. You can use operations on that to get back a single record. - -```php -$players = Player::get(); - -$first = $players->first(); -$last = $players->last(); -``` - -## Sorting - -If you would like to sort the list by `FirstName` in an ascending way (from A to Z). - -```php - // Sort can either be Ascending (ASC) or Descending (DESC) -$players = Player::get()->sort('FirstName', 'ASC'); - - // Ascending is implied -$players = Player::get()->sort('FirstName'); -``` - -To reverse the sort - -```php -$players = Player::get()->sort('FirstName', 'DESC'); - -// or.. -$players = Player::get()->sort('FirstName', 'ASC')->reverse(); -``` - -However you might have several entries with the same `FirstName` and would like to sort them by `FirstName` and -`LastName` - -```php -$players = Players::get()->sort([ - 'FirstName' => 'ASC', - 'LastName'=>'ASC' -]); -``` - -You can also sort randomly. Using the `DB` class, you can get the random sort method per database type. - -```php -$random = DB::get_conn()->random(); -$players = Player::get()->sort($random); -``` - -## Filtering Results - -The `filter()` method filters the list of objects that gets returned. - -```php -$players = Player::get()->filter([ - 'FirstName' => 'Sam' -]); -``` - -Each element of the array specifies a filter. You can specify as many filters as you like, and they **all** must be -true for the record to be included in the result. - -The key in the filter corresponds to the field that you want to filter and the value in the filter corresponds to the -value that you want to filter to. - -So, this would return only those players called "Sam Minnée". - -```php -$players = Player::get()->filter([ - 'FirstName' => 'Sam', - 'LastName' => 'Minnée', -]); - -// SELECT * FROM Player WHERE FirstName = 'Sam' AND LastName = 'Minnée' -``` - -There is also a shorthand way of getting Players with the FirstName of Sam. - -```php -$players = Player::get()->filter('FirstName', 'Sam'); -``` - -Or if you want to find both Sam and Sig. - -```php -$players = Player::get()->filter( - 'FirstName', ['Sam', 'Sig'] -); - -// SELECT * FROM Player WHERE FirstName IN ('Sam', 'Sig') -``` - -You can use [SearchFilters](searchfilters) to add additional behavior to your `filter` command rather than an -exact match. - -```php -$players = Player::get()->filter([ - 'FirstName:StartsWith' => 'S', - 'PlayerNumber:GreaterThan' => '10', -]); -``` - -### filterAny - -Use the `filterAny()` method to match multiple criteria non-exclusively (with an "OR" disjunctive), - -```php -$players = Player::get()->filterAny([ - 'FirstName' => 'Sam', - 'Age' => 17, -]); - -// SELECT * FROM Player WHERE ("FirstName" = 'Sam' OR "Age" = '17') -``` - -You can combine both conjunctive ("AND") and disjunctive ("OR") statements. - -```php -$players = Player::get() - ->filter([ - 'LastName' => 'Minnée', - ]) - ->filterAny([ - 'FirstName' => 'Sam', - 'Age' => 17, - ]); -// SELECT * FROM Player WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17')) -``` - -You can use [SearchFilters](searchfilters) to add additional behavior to your `filterAny` command. - -```php -$players = Player::get()->filterAny([ - 'FirstName:StartsWith' => 'S', - 'PlayerNumber:GreaterThan' => '10', -]); -``` - -### Filtering by null values - -Since null values in SQL are special, they are non-comparable with other values, certain filters will add -`IS NULL` or `IS NOT NULL` predicates automatically to your query. As per ANSI SQL-92, any comparison -condition against a field will filter out nulls by default. Therefore, it's necessary to include certain null -checks to ensure that exclusion filters behave predictably. - -For instance, the below code will select only values that do not match the given value, including nulls. - - -```php -$players = Player::get()->filter('FirstName:not', 'Sam'); -// ... WHERE "FirstName" != 'Sam' OR "FirstName" IS NULL -// Returns rows with any value (even null) other than Sam -``` - -If null values should be excluded, include the null in your check. - - -```php -$players = Player::get()->filter('FirstName:not', ['Sam', null]); -// ... WHERE "FirstName" != 'Sam' AND "FirstName" IS NOT NULL -// Only returns non-null values for "FirstName" that aren't Sam. -// Strictly the IS NOT NULL isn't necessary, but is included for explicitness -``` - -It is also often useful to filter by all rows with either empty or null for a given field. - - -```php -$players = Player::get()->filter('FirstName', [null, '']); -// ... WHERE "FirstName" == '' OR "FirstName" IS NULL -// Returns rows with FirstName which is either empty or null -``` - -### Filtering by aggregates - -You can use aggregate expressions in your filters, as well. - -```php -// get the teams that have more than 10 players -$teams = Team::get()->filter('Players.Count():GreaterThan', 10); - -// get the teams with at least one player who has scored 5 or more points -$teams = Team::get()->filter('Players.Min(PointsScored):GreaterThanOrEqual', 5); - -// get the teams with players who are averaging more than 15 points -$teams = Team::get()->filter('Players.Avg(PointsScored):GreaterThan', 15); - -// get the teams whose players have scored less than 300 points combined -$teams = Team::get()->filter('Players.Sum(PointsScored):LessThan', 300); -``` - -### filterByCallback - -It is also possible to filter by a PHP callback, this will force the data model to fetch all records and loop them in -PHP, thus `filter()` or `filterAny()` are to be preferred over `filterByCallback()`. - -[notice] -Because `filterByCallback()` has to run in PHP, it has a significant performance tradeoff, and should not be used on large recordsets. - -`filterByCallback()` will always return an `ArrayList`. -[/notice] - -The first parameter to the callback is the item, the second parameter is the list itself. The callback will run once -for each record, if the callback returns true, this record will be added to the list of returned items. - -The below example will get all `Players` aged over 10. - -```php -$players = Player::get()->filterByCallback(function($item, $list) { - return ($item->Age() > 10); -}); -``` - -### Exclude - -The `exclude()` method is the opposite to the filter in that it removes entries from a list. - -```php -$players = Player::get()->exclude('FirstName', 'Sam'); - -// SELECT * FROM Player WHERE FirstName != 'Sam' -``` - -Remove both Sam and Sig.. - -```php -$players = Player::get()->exclude([ - 'FirstName' => ['Sam','Sig'] -]); -``` - -`Exclude` follows the same pattern as filter, so for removing only Sam Minnée from the list: - -```php -$players = Player::get()->exclude([ - 'FirstName' => 'Sam', - 'Surname' => 'Minnée', -]); - -// SELECT * FROM Player WHERE (FirstName != 'Sam' OR LastName != 'Minnée') -``` - -Removing players with *either* the first name of Sam or the last name of Minnée requires multiple `->exclude` calls: - -```php -$players = Player::get()->exclude('FirstName', 'Sam')->exclude('Surname', 'Minnée'); - -// SELECT * FROM Player WHERE FirstName != 'Sam' AND LastName != 'Minnée' -``` - -And removing Sig and Sam with that are either age 17 or 43. - -```php -$players = Player::get()->exclude([ - 'FirstName' => ['Sam', 'Sig'], - 'Age' => [17, 43] -]); - -// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '43')); -``` - -You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command. - -```php -$players = Player::get()->exclude([ - 'FirstName:EndsWith' => 'S', - 'PlayerNumber:LessThanOrEqual' => '10' -]); -``` - -### Subtract - -You can subtract entries from a [DataList](api:SilverStripe\ORM\DataList) by passing in another DataList to `subtract()` - -```php -$sam = Player::get()->filter('FirstName', 'Sam'); -$players = Player::get(); - -$noSams = $players->subtract($sam); -``` - -Though for the above example it would probably be easier to use `filter()` and `exclude()`. A better use case could be -when you want to find all the members that does not exist in a Group. - -```php -// ... Finding all members that does not belong to $group. -use SilverStripe\Security\Member; -$otherMembers = Member::get()->subtract($group->Members()); -``` - -### Limit - -You can limit the amount of records returned in a DataList by using the `limit()` method. - -```php -use SilverStripe\Security\Member; -$members = Member::get()->limit(5); -``` - -`limit()` accepts two arguments, the first being the amount of results you want returned, with an optional second -parameter to specify the offset, which allows you to tell the system where to start getting the results from. The -offset, if not provided as an argument, will default to 0. - -```php -// Return 10 members with an offset of 4 (starting from the 5th result). -$members = Member::get()->sort('Surname')->limit(10, 4); -``` - -[alert] -Note that the `limit` argument order is different from a MySQL LIMIT clause. -[/alert] - -### Mapping classes to tables with DataObjectSchema - -Note that in most cases, the underlying database table for any DataObject instance will be the same as the class name. -However in cases where dealing with namespaced classes, especially when using DB schema which don't support -slashes in table names, it is necessary to provide an alternate mapping. - -For instance, the below model will be stored in the table name `BannerImage` - - -```php -namespace SilverStripe\BannerManager; -use SilverStripe\ORM\DataObject; - -class BannerImage extends DataObject -{ - private static $table_name = 'BannerImage'; -} -``` - -Note that any model class which does not explicitly declare a `table_name` config option will have a name -automatically generated for them. In the above case, the table name would have been -`SilverStripe\BannerManager\BannerImage` - -When creating raw SQL queries that contain table names, it is necessary to ensure your queries have the correct -table. This functionality can be provided by the [DataObjectSchema](api:SilverStripe\ORM\DataObjectSchema) service, which can be accessed via -`DataObject::getSchema()`. This service provides the following methods, most of which have a table and class -equivalent version. - -Methods which return class names: - -* `tableClass($table)` Finds the class name for a given table. This also handles suffixed tables such as `Table_Live`. -* `baseDataClass($class)` Returns the base data class for the given class. -* `classForField($class, $field)` Finds the specific class that directly holds the given field - -Methods which return table names: - -* `tableName($class)` Returns the table name for a given class or object. -* `baseDataTable($class)` Returns the base data class for the given class. -* `tableForField($class, $field)` Finds the specific class that directly holds the given field and returns the table. - -Note that in cases where the class name is required, an instance of the object may be substituted. - -For example, if running a query against a particular model, you will need to ensure you use the correct -table and column. - - -```php -use SilverStripe\ORM\Queries\SQLSelect; -use SilverStripe\ORM\DataObject; - -public function countDuplicates($model, $fieldToCheck) -{ - $table = DataObject::getSchema()->tableForField($model, $field); - $query = new SQLSelect(); - $query->setFrom("\"{$table}\""); - $query->setWhere(["\"{$table}\".\"{$field}\"" => $model->$fieldToCheck]); - return $query->count(); -} -``` - -### Raw SQL - -Occasionally, the system described above won't let you do exactly what you need to do. In these situations, we have -methods that manipulate the SQL query at a lower level. When using these, please ensure that all table and field names -are escaped with double quotes, otherwise some DB backends (e.g. PostgreSQL) won't work. - -Under the hood, query generation is handled by the [DataQuery](api:SilverStripe\ORM\DataQuery) class. This class does provide more direct access -to certain SQL features that `DataList` abstracts away from you. - -In general, we advise against using these methods unless it's absolutely necessary. If the ORM doesn't do quite what -you need it to, you may also consider extending the ORM with new data types or filter modifiers - -#### Where clauses - -You can specify a WHERE clause fragment (that will be combined with other filters using AND) with the `where()` method: - -```php -$members = Member::get()->where("\"FirstName\" = 'Sam'"); -``` - -#### Joining Tables - -You can specify a join with the `innerJoin` and `leftJoin` methods. Both of these methods have the same arguments: - -* The name of the table to join to. -* The filter clause for the join. -* An optional alias. - -```php -// Without an alias -$members = Member::get() - ->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\""); - -$members = Member::get() - ->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "Rel"); -``` - -[alert] -Passing a *$join* statement will filter results further by the JOINs performed against the foreign table. It will -**not** return the additionally joined data. -[/alert] - -### Default Values - -Define the default values for all the `$db` fields. This example sets the "Status"-column on Player to "Active" -whenever a new object is created. - -```php -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - - private static $defaults = [ - "Status" => 'Active', - ]; -} -``` - -[notice] -Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See -[Data Types and Casting](/developer_guides/model/data_types_and_casting) for details. -[/notice] - -## Subclasses - -Inheritance is supported in the data model: separate tables will be linked together, the data spread across these -tables. The mapping and saving logic is handled by Silverstripe CMS , you don't need to worry about writing SQL most of the -time. - -For example, suppose we have the following set of classes: - -```php -use SilverStripe\CMS\Model\SiteTree; - -class Page extends SiteTree -{ - -} -class NewsPage extends Page -{ - private static $db = [ - 'Summary' => 'Text' - ]; -} -``` - -The data for the following classes would be stored across the following tables: - -```yml -SilverStripe\CMS\Model\SiteTree: - ID: Int - ClassName: Enum('SiteTree', 'Page', 'NewsPage') - Created: Datetime - LastEdited: Datetime - Title: Varchar - Content: Text -NewsPage: - ID: Int - Summary: Text -``` - -Accessing the data is transparent to the developer. - -```php -$news = NewsPage::get(); - -foreach($news as $article) { - echo $article->Title; -} -``` - -The way the ORM stores the data is this: - -* "Base classes" are direct sub-classes of [DataObject](api:SilverStripe\ORM\DataObject). They are always given a table, whether or not they have - special fields. This is called the "base table". In our case, `SiteTree` is the base table. - -* The base table's ClassName field is set to class of the given record. It's an enumeration of all possible - sub-classes of the base class (including the base class itself). - -* Each sub-class of the base object will also be given its own table, *as long as it has custom fields*. In the - example above, NewsSection didn't have its own data, so an extra table would be redundant. - -* In all the tables, ID is the primary key. A matching ID number is used for all parts of a particular record: - record #2 in Page refers to the same object as record #2 in [SiteTree](api:SilverStripe\CMS\Model\SiteTree). - -To retrieve a news article, Silverstripe CMS joins the [SiteTree](api:SilverStripe\CMS\Model\SiteTree), [Page](api:SilverStripe\CMS\Model\SiteTree\Page) and NewsPage tables by their ID fields. - -## Related Lessons -* [Introduction to the ORM](https://www.silverstripe.org/learn/lessons/v4/introduction-to-the-orm-1) -* [Adding custom fields to a page](https://www.silverstripe.org/learn/lessons/v4/adding-custom-fields-to-a-page-1) - - -## Related Documentation - -* [Data Types and Casting](/developer_guides/model/data_types_and_casting) - -## API Documentation - -* [DataObject](api:SilverStripe\ORM\DataObject) -* [DataList](api:SilverStripe\ORM\DataList) -* [DataQuery](api:SilverStripe\ORM\DataQuery) -* [DataObjectSchema](api:SilverStripe\ORM\DataObjectSchema) diff --git a/docs/en/02_Developer_Guides/00_Model/02_Relations.md b/docs/en/02_Developer_Guides/00_Model/02_Relations.md deleted file mode 100644 index 056fa66b8a5..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/02_Relations.md +++ /dev/null @@ -1,653 +0,0 @@ ---- -title: Relations between Records -summary: Relate models together using the ORM using has_one, has_many, and many_many. -icon: link ---- - -# Relations between Records - -In most situations you will likely see more than one [DataObject](api:SilverStripe\ORM\DataObject) and several classes in your data model may relate -to one another. An example of this is a `Player` object may have a relationship to one or more `Team` or `Coach` classes -and could take part in many `Games`. Relations are a key part of designing and building a good data model. - -Relations are built through static array definitions on a class, in the format ` => `. -Silverstripe CMS supports a number of relationship types and each relationship type can have any number of relations. - -## has_one - -Many-to-1 and 1-to-1 relationships create a database-column called "``ID", in the example below this would be "TeamID" on the "Player"-table. - -```php -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - private static $has_one = [ - "Team" => Team::class, - ]; -} - -class Team extends DataObject -{ - private static $db = [ - 'Title' => 'Varchar' - ]; - - private static $has_many = [ - 'Players' => Player::class, - ]; -} -``` - -This defines a relationship called `Team` which links to a `Team` class. The `ORM` handles navigating the relationship -and provides a short syntax for accessing the related object. - -To create a has_one/has_many relationship to core classes (File, Image, etc), reference the Classname::class, like below. - -```php -use SilverStripe\ORM\DataObject; -use SilverStripe\Assets\Image; -use SilverStripe\Assets\File; - -class Team extends DataObject -{ - private static $has_many = [ - 'Teamphoto' => Image::class, - 'Lineup' => File::class - ]; -} -``` - -At the database level, the `has_one` creates a `TeamID` field on `Player`. A `has_many` field does not impose any database changes. It merely injects a new method into the class to access the related records (in this case, `Players()`) - -```php -$player = Player::get()->byId(1); - -$team = $player->Team(); -// returns a 'Team' instance. - -echo $player->Team()->Title; -// returns the 'Title' column on the 'Team' or `getTitle` if it exists. -``` - -The relationship can also be navigated in [templates](../templates). - -```ss -<% with $Player %> - <% if $Team %> - Plays for $Team.Title - <% end_if %> -<% end_with %> -``` - -## Polymorphic has_one - -A has_one can also be polymorphic, which allows any type of object to be associated. -This is useful where there could be many use cases for a particular data structure. - -An additional column is created called "``Class", which along -with the ID column identifies the object. - -To specify that a has_one relation is polymorphic set the type to [api:SilverStripe\ORM\DataObject] -Ideally, the associated has_many (or belongs_to) should be specified with dot notation. - -```php -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - private static $has_many = [ - "Fans" => Fan::class.".FanOf", - ]; -} -class Team extends DataObject -{ - private static $has_many = [ - "Fans" => Fan::class.".FanOf", - ]; -} - -// Type of object returned by $fan->FanOf() will vary -class Fan extends DataObject -{ - - // Generates columns FanOfID and FanOfClass - private static $has_one = [ - "FanOf" => DataObject::class, - ]; -} -``` - -[warning] -Note: The use of polymorphic relationships can affect query performance, especially -on joins, and also increases the complexity of the database and necessary user code. -They should be used sparingly, and only where additional complexity would otherwise -be necessary. E.g. Additional parent classes for each respective relationship, or -duplication of code. -[/warning] - -## has_many - -Defines 1-to-many joins. As you can see from the previous example, `$has_many` goes hand in hand with `$has_one`. - -[alert] -Please specify a $has_one-relationship on the related child-class as well, in order to have the necessary accessors -available on both ends. To add a $has_one-relationship on core classes, yml config settings can be used: -```yml -SilverStripe\Assets\Image: - has_one: - MyDataObject: MyDataObject -``` -[/alert] - -```php -use SilverStripe\ORM\DataObject; - -class Team extends DataObject -{ - private static $db = [ - 'Title' => 'Varchar', - ]; - - private static $has_many = [ - 'Players' => Player::class, - ]; -} -class Player extends DataObject -{ - - private static $has_one = [ - "Team" => Team::class, - ]; -} -``` - -Much like the `has_one` relationship, `has_many` can be navigated through the `ORM` as well. The only difference being -you will get an instance of [HasManyList](api:SilverStripe\ORM\HasManyList) rather than the object. - -```php -$team = Team::get()->first(); - -echo $team->Players(); -// [HasManyList] - -echo $team->Players()->Count(); -// returns '14'; - -foreach($team->Players() as $player) { - echo $player->FirstName; -} -``` - -To specify multiple `$has_many` to the same object you can use dot notation to distinguish them like below: - -```php -use SilverStripe\ORM\DataObject; - -class Person extends DataObject -{ - private static $has_many = [ - "Managing" => Company::class.".Manager", - "Cleaning" => Company::class.".Cleaner", - ]; -} -class Company extends DataObject -{ - private static $has_one = [ - "Manager" => Person::class, - "Cleaner" => Person::class, - ]; -} -``` - -Multiple `$has_one` relationships are okay if they aren't linking to the same object type. Otherwise, they have to be -named. With that said, naming is recommended in all cases as it makes your code more resilient to change. Adding new relationships is easier when you don't need to review and update existing ones. - -You can use `RelationValidationService` for validation of relationships. This tool will point out the relationships which may need a review. - -If you're using the default scaffolded form fields with multiple `has_one` relationships, you will end up with a CMS field for each relation. If you don't want these you can remove them by their IDs: - -```php -public function getCMSFields() -{ - $fields = parent::getCMSFields(); - $fields->removeByName(['ManagerID', 'CleanerID']); - return $fields; -} -``` - -## belongs_to - -Defines a 1-to-1 relationship with another object, which declares the other end of the relationship with a -corresponding `$has_one`. A single database column named `ID` will be created in the object with the -`$has_one`, but the $belongs_to by itself will not create a database field. This field will hold the ID of the object -declaring the `$belongs_to`. - -Similarly with `$has_many`, dot notation can be used to explicitly specify the `$has_one` which refers to this relation. -This is not mandatory unless the relationship would be otherwise ambiguous. - -You can use `RelationValidationService` for validation of relationships. This tool will point out the relationships which may need a review. - -```php -use SilverStripe\ORM\DataObject; - -class Team extends DataObject -{ - - private static $has_one = [ - 'Coach' => Coach::class - ]; -} -class Coach extends DataObject -{ - - private static $belongs_to = [ - 'Team' => Team::class.'.Coach' - ]; -} -``` - -## many_many - -Defines many-to-many joins, which uses a third table created between the two to join pairs. -There are two ways in which this can be declared, which are described below, depending on -how the developer wishes to manage this join table. - -[warning] -Please specify a $belongs_many_many-relationship on the related class as well, in order -to have the necessary accessors available on both ends. You can use `RelationValidationService` for validation of relationships. This tool will point out the relationships which may need a review. - -Example configuration: - -```yaml -SilverStripe\Dev\Validation\RelationValidationService: - output_enabled: true -``` - -[/warning] - -Much like the `has_one` relationship, `many_many` can be navigated through the `ORM` as well. -The only difference being you will get an instance of [ManyManyList](api:SilverStripe\ORM\ManyManyList) or -[ManyManyThroughList](api:SilverStripe\ORM\ManyManyThroughList) rather than the object. - -```php -$team = Team::get()->byId(1); - -$supporters = $team->Supporters(); -// returns a 'ManyManyList' instance. -``` - -### Automatic many_many table - -If you specify only a single class as the other side of the many-many relationship, then a -table will be automatically created between the two (this-class)_(relationship-name), will -be created with a pair of ID fields. - -Extra fields on the mapping table can be created by declaring a `many_many_extraFields` -config to add extra columns. - -```php -use SilverStripe\ORM\DataObject; - -class Team extends DataObject -{ - private static $many_many = [ - "Supporters" => Supporter::class, - ]; - - private static $many_many_extraFields = [ - 'Supporters' => [ - 'Ranking' => 'Int' - ] - ]; -} - -class Supporter extends DataObject -{ - private static $belongs_many_many = [ - "Supports" => Team::class, - ]; -} -``` - -To ensure this `many_many` is sorted by "Ranking" by default you can add this to your config: - -```yaml -Team_Supporters: - default_sort: '"Team_Supporter"."Ranking" ASC' -``` - -`Team_Supporters` is the table name automatically generated for the many_many relation in this case. - -### many_many through relationship joined on a separate DataObject - -If necessary, a third DataObject class can instead be specified as the joining table, -rather than having the ORM generate an automatically scaffolded table. This has the following -advantages: - - - Allows versioning of the mapping table, including support for the - [ownership api](/developer_guides/model/versioning). - - Allows support of other extensions on the mapping table (e.g. subsites). - - Extra fields can be managed separately to the joined dataobject, even via a separate - GridField or form. - -This is declared via array syntax, with the following keys on the many_many: - - `through` Class name of the mapping table - - `from` Name of the has_one relationship pointing back at the object declaring many_many - - `to` Name of the has_one relationship pointing to the object declaring belongs_many_many. - -Just like any normal DataObject, you can apply a default sort which will be applied when -accessing many many through relations. - -Note: The `through` class must not also be the name of any field or relation on the parent -or child record. - -The syntax for `belongs_many_many` is unchanged. - -```php -use SilverStripe\ORM\DataObject; - -class Team extends DataObject -{ - private static $many_many = [ - "Supporters" => [ - 'through' => TeamSupporter::class, - 'from' => 'Team', - 'to' => 'Supporter', - ] - ]; -} -class Supporter extends DataObject -{ - // Prior to 4.2.0, this also needs to include the reverse relation name via dot-notation - // i.e. 'Supports' => Team::class . '.Supporters' - private static $belongs_many_many = [ - 'Supports' => Team::class, - ]; -} -class TeamSupporter extends DataObject -{ - private static $db = [ - 'Ranking' => 'Int', - ]; - - private static $has_one = [ - 'Team' => Team::class, - 'Supporter' => Supporter::class, - ]; - - private static $default_sort = '"TeamSupporter"."Ranking" ASC'; -} -``` - -In order to filter on the join table during queries, you can use the class name of the joining table -for any sql conditions. - -```php -$team = Team::get()->byId(1); -$supporters = $team->Supporters()->where(['"TeamSupporter"."Ranking"' => 1]); -``` - -Note: ->filter() currently does not support joined fields natively due to the fact that the -query for the join table is isolated from the outer query controlled by DataList. - -### Polymorphic many_many (Experimental) - -Using many_many through, it is possible to support polymorphic relations on the mapping table. -Note, that this feature is currently experimental, and has certain limitations: - - This feature only works with many_many through - - This feature will only allow polymorphic many_many, but not belongs_many_many. However, - you can have a has_many relation to the mapping table on this side, and iterate through this - to collate parent records. - -For instance, this is how you would link an arbitrary object to many_many tags. - -```php -use SilverStripe\ORM\DataObject; - -class SomeObject extends DataObject -{ - // This same many_many may also exist on other classes - private static $many_many = [ - "Tags" => [ - 'through' => TagMapping::class, - 'from' => 'Parent', - 'to' => 'Tag', - ] - ]; -} -class Tag extends DataObject -{ - // has_many works, but belongs_many_many will not - private static $has_many = [ - 'TagMappings' => TagMapping::class, - ]; - - /** - * Example iterator placeholder for belongs_many_many. - * This is a list of arbitrary types of objects - * @return Generator|DataObject[] - */ - public function TaggedObjects() - { - foreach ($this->TagMappings() as $mapping) { - yield $mapping->Parent(); - } - } - -} -class TagMapping extends DataObject -{ - private static $has_one = [ - 'Parent' => DataObject::class, // Polymorphic has_one - 'Tag' => Tag::class, - ]; -} -``` - -### Using many_many in templates - -The relationship can also be navigated in [templates](../templates). -The joined record can be accessed via `Join` or `TeamSupporter` property (many_many through only) - -```ss -<% with $Supporter %> - <% loop $Supports %> - Supports $Title <% if $TeamSupporter %>(rank $TeamSupporter.Ranking)<% end_if %> - <% end_loop %> -<% end_with %> -``` - -You can also use `$Join` in place of the join class alias (`$TeamSupporter`), if your template -is class-agnostic and doesn't know the type of the join table. - -## belongs_many_many - -The belongs_many_many represents the other side of the relationship on the target data class. -When using either a basic many_many or a many_many through, the syntax for belongs_many_many is the same. - -To specify multiple $many_manys between the same classes, specify use the dot notation to -distinguish them like below: - - -```php -use SilverStripe\ORM\DataObject; - -class Category extends DataObject -{ - - private static $many_many = [ - 'Products' => Product::class, - 'FeaturedProducts' => Product::class, - ]; -} - -class Product extends DataObject -{ - private static $belongs_many_many = [ - 'Categories' => Category::class.'.Products', - 'FeaturedInCategories' => Category::class.'.FeaturedProducts', - ]; -} -``` - -If you're unsure about whether an object should take on `many_many` or `belongs_many_many`, -the best way to think about it is that the object where the relationship will be edited -(i.e. via checkboxes) should contain the `many_many`. For instance, in a `many_many` of -Product => Categories, the `Product` should contain the `many_many`, because it is much -more likely that the user will select Categories for a Product than vice-versa. - - -## Cascading deletions - -Relationships between objects can cause cascading deletions, if necessary, through configuration of the -`cascade_deletes` config on the parent class. - -```php -use SilverStripe\ORM\DataObject; - -class ParentObject extends DataObject { - private static $has_one = [ - 'Child' => ChildObject::class, - ]; - private static $cascade_deletes = [ - 'Child', - ]; -} -class ChildObject extends DataObject { -} -``` - -In this example, when the Parent object is deleted, the Child specified by the has_one relation will also -be deleted. Note that all relation types (has_many, many_many, belongs_many_many, belongs_to, and has_one) -are supported, as are methods that return lists of objects but do not correspond to a physical database relation. - -If your object is versioned, cascade_deletes will also act as "cascade unpublish", such that any unpublish -on a parent object will trigger unpublish on the child, similarly to how `owns` causes triggered publishing. -See the [versioning docs](/developer_guides/model/versioning) for more information on ownership. - -[alert] -Declaring cascade_deletes implies delete permissions on the listed objects. -Built-in controllers using delete operations check canDelete() on the owner, but not on the owned object. -[/alert] - -## Cascading duplications - -Similar to `cascade_deletes` there is also a `cascade_duplicates` config which works in much the same way. -When you invoke `$dataObject->duplicate()`, relation names specified by this config will be duplicated -and saved against the new clone object. - -Note that duplications will act differently depending on the kind of relation: - - Exclusive relationships (e.g. has_many, belongs_to) will be explicitly duplicated. - - Non-exclusive many_many will not be duplicated, but the mapping table values will instead - be copied for this record. - - Non-exclusive has_one relationships are not normally necessary to duplicate, since both parent and clone - can normally share the same relation ID. However, if this is declared in `cascade_duplicates` any - has one will be similarly duplicated as though it were an exclusive relationship. - -For example: - -```php -use SilverStripe\ORM\DataObject; - -class ParentObject extends DataObject { - private static $many_many = [ - 'RelatedChildren' => ChildObject::class, - ]; - private static $cascade_duplicates = [ 'RelatedChildren' ]; -} -class ChildObject extends DataObject { -} -``` - -When duplicating objects you can disable recursive duplication by passing in `false` to the second -argument of duplicate(). - -E.g. - -```php -$parent = ParentObject::get()->first(); -$dupe = $parent->duplicate(true, false); -``` - -## Adding relations - -Adding new items to a relations works the same, regardless if you're editing a **has_many** or a **many_many**. They are -encapsulated by [HasManyList](api:SilverStripe\ORM\HasManyList) and [ManyManyList](api:SilverStripe\ORM\ManyManyList), both of which provide very similar APIs, e.g. an `add()` -and `remove()` method. - -```php -$team = Team::get()->byId(1); - -// create a new supporter -$supporter = new Supporter(); -$supporter->Name = "Foo"; -$supporter->write(); - -// add the supporter. -$team->Supporters()->add($supporter); -``` - -## Custom Relations - -You can use the ORM to get a filtered result list without writing any SQL. For example, this snippet gets you the -"Players"-relation on a team, but only containing active players. - -See [DataObject::$has_many](api:SilverStripe\ORM\DataObject::$has_many) for more info on the described relations. - -```php -use SilverStripe\ORM\DataObject; - -class Team extends DataObject -{ - private static $has_many = [ - "Players" => Player::class - ]; - - public function ActivePlayers() - { - return $this->Players()->filter('Status', 'Active'); - } -} - -``` - -[notice] -Adding new records to a filtered `RelationList` like in the example above doesn't automatically set the filtered -criteria on the added record. -[/notice] - -## Relations on Unsaved Objects - -You can also set *has_many* and *many_many* relations before the `DataObject` is saved. This behavior uses the -[UnsavedRelationList](api:SilverStripe\ORM\UnsavedRelationList) and converts it into the correct `RelationList` when saving the `DataObject` for the first -time. - -This unsaved lists will also recursively save any unsaved objects that they contain. - -As these lists are not backed by the database, most of the filtering methods on `DataList` cannot be used on a list of -this type. As such, an `UnsavedRelationList` should only be used for setting a relation before saving an object, not -for displaying the objects contained in the relation. - -## Link Tracking - -You can control the visibility of the `Link Tracking` tab by setting the `show_sitetree_link_tracking` config. -This defaults to `false` for most `DataObject`'s. - -It is also possible to control the visibility of the `File Tracking` tab by setting the `show_file_link_tracking` config. - -## Related Lessons -* [Working with data relationships -- has_many](https://www.silverstripe.org/learn/lessons/v4/working-with-data-relationships-has-many-1) -* [Working with data relationships -- many_many](https://www.silverstripe.org/learn/lessons/v4/working-with-data-relationships-many-many-1) - -## Related Documentation - -* [Introduction to the Data Model and ORM](data_model_and_orm) -* [Lists](lists) - -## API Documentation - -* [HasManyList](api:SilverStripe\ORM\HasManyList) -* [ManyManyList](api:SilverStripe\ORM\ManyManyList) -* [DataObject](api:SilverStripe\ORM\DataObject) -* [LinkTracking](api:SilverStripe\CMS\Model\SiteTreeLinkTracking) diff --git a/docs/en/02_Developer_Guides/00_Model/03_Lists.md b/docs/en/02_Developer_Guides/00_Model/03_Lists.md deleted file mode 100644 index 43ab6e0d732..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/03_Lists.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -title: Managing Lists -summary: The SS_List interface allows you to iterate through and manipulate a list of objects. -icon: list ---- - -# Managing Lists - -Whenever using the ORM to fetch records or navigate relationships you will receive an [SS_List](api:SilverStripe\ORM\SS_List) instance commonly as -either [DataList](api:SilverStripe\ORM\DataList) or [RelationList](api:SilverStripe\ORM\RelationList). This object gives you the ability to iterate over each of the results or -modify. - -## Iterating over the list - -[SS_List](api:SilverStripe\ORM\SS_List) implements `IteratorAggregate`, allowing you to loop over the instance. - -```php -use SilverStripe\Security\Member; - -$members = Member::get(); - -foreach($members as $member) { - echo $member->Name; -} -``` - -Or in the template engine: - -```ss -<% loop $Members %> - -<% end_loop %> -``` - -## Finding an item by value - -```php -// $list->find($key, $value); - -// -$members = Member::get(); - -echo $members->find('ID', 4)->FirstName; -// returns 'Sam' -``` - -## Maps - -A map is an array where the array indexes contain data as well as the values. You can build a map from any list - -```php -$members = Member::get()->map('ID', 'FirstName'); - -// $members = [ -// 1 => 'Sam' -// 2 => 'Sig' -// 3 => 'Will' -// ]; -``` - -This functionality is provided by the [Map](api:SilverStripe\ORM\Map) class, which can be used to build a map around any `SS_List`. - -```php -$members = Member::get(); -$map = new Map($members, 'ID', 'FirstName'); -``` - -## Column - -```php -$members = Member::get(); - -echo $members->column('Email'); - -// returns [ -// 'sam@silverstripe.com', -// 'sig@silverstripe.com', -// 'will@silverstripe.com' -// ]; -``` - -## Iterating over a large list {#chunkedFetch} - -When iterating over a DataList, all DataObjects in the list will be loaded in memory. This can consume a lot of memory when working with a large data set. - -To limit the number of DataObjects loaded in memory, you can use the `chunkedFetch()` method on your DataList. In most cases, you can iterate over the results of `chunkedFetch()` the same way you would iterate over your DataList. Internally, `chunkedFetch()` will split your DataList query into smaller queries and keep running through them until it runs out of results. - -```php -$members = Member::get(); -foreach ($members as $member) { - echo $member->Email; -} - -// This call will produce the same output, but it will use less memory and run more queries against the database -$members = Member::get()->chunkedFetch(); -foreach ($members as $member) { - echo $member->Email; -} -``` - -`chunkedFetch()` will respect any filter or sort condition applied to the DataList. By default, chunk will limit each query to 1000 results. You can explicitly set this limit by passing an integer to `chunkedFetch()`. - -```php -$members = Member::get() - ->filter('Email:PartialMatch', 'silverstripe.com') - ->sort('Email') - ->chunkedFetch(10); -foreach ($members as $member) { - echo $member->Email; -} -``` - -There are some limitations: -* `chunkedFetch()` will ignore any limit or offset you have applied to your DataList -* you cannot "count" a chunked list or do any other call against it aside from iterating it -* while iterating over a chunked list, you cannot perform any operation that would alter the order of the items. - -## ArrayList - -[ArrayList](api:SilverStripe\ORM\ArrayList) exists to wrap a standard PHP array in the same API as a database backed list. - -```php -$sam = Member::get()->byId(5); -$sig = Member::get()->byId(6); - -$list = new ArrayList(); -$list->push($sam); -$list->push($sig); - -echo $list->Count(); -// returns '2' -``` - -## Related Lessons -* [Lists and pagination](https://www.silverstripe.org/learn/lessons/v4/lists-and-pagination-1) - -## API Documentation - -* [SS_List](api:SilverStripe\ORM\SS_List) -* [RelationList](api:SilverStripe\ORM\RelationList) -* [DataList](api:SilverStripe\ORM\DataList) -* [ArrayList](api:SilverStripe\ORM\ArrayList) -* [Map](api:SilverStripe\ORM\Map) diff --git a/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md b/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md deleted file mode 100644 index 5448cae3d7e..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md +++ /dev/null @@ -1,256 +0,0 @@ ---- -title: Data Types, Overloading and Casting -summary: Learn how how data is stored going in and coming out of the ORM and how to modify it. -icon: code ---- - -# Data Types and Casting - -Each model in a Silverstripe CMS [DataObject](api:SilverStripe\ORM\DataObject) will handle data at some point. This includes database columns such as -the ones defined in a `$db` array or simply a method that returns data for the template. - -A Data Type is represented in Silverstripe CMS by a [DBField](api:SilverStripe\ORM\FieldType\DBField) subclass. The class is responsible for telling the ORM -about how to store its data in the database and how to format the information coming out of the database, i.e. on a template. - -In the `Player` example, we have four database columns each with a different data type (Int, Varchar). - -**app/src/Player.php** - -```php -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - private static $db = [ - 'PlayerNumber' => 'Int', - 'FirstName' => 'Varchar(255)', - 'LastName' => 'Text', - 'Birthday' => 'Date' - ]; -} -``` - -## Available Types - -* `'Boolean'`: A boolean field (see: [DBBoolean](api:SilverStripe\ORM\FieldType\DBBoolean)). -* `'Currency'`: A number with 2 decimal points of precision, designed to store currency values (see: [DBCurrency](api:SilverStripe\ORM\FieldType\DBCurrency)). -* `'Date'`: A date field (see: [DBDate](api:SilverStripe\ORM\FieldType\DBDate)). -* `'Decimal'`: A decimal number (see: [DBDecimal](api:SilverStripe\ORM\FieldType\DBDecimal)). -* `'Enum'`: An enumeration of a set of strings (see: [DBEnum](api:SilverStripe\ORM\FieldType\DBEnum)). -* `'HTMLText'`: A variable-length string of up to 2MB, designed to store HTML (see: [DBHTMLText](api:SilverStripe\ORM\FieldType\DBHTMLText)). -* `'HTMLVarchar'`: A variable-length string of up to 255 characters, designed to store HTML (see: [DBHTMLVarchar](api:SilverStripe\ORM\FieldType\DBHTMLVarchar)). -* `'Int'`: An integer field (see: [DBInt](api:SilverStripe\ORM\FieldType\DBInt)). -* `'Percentage'`: A decimal number between 0 and 1 that represents a percentage (see: [DBPercentage](api:SilverStripe\ORM\FieldType\DBPercentage)). -* `'Datetime'`: A date / time field (see: [DBDatetime](api:SilverStripe\ORM\FieldType\DBDatetime)). -* `'Text'`: A variable-length string of up to 2MB, designed to store raw text (see: [DBText](api:SilverStripe\ORM\FieldType\DBText)). -* `'Time'`: A time field (see: [DBTime](api:SilverStripe\ORM\FieldType\DBTime)). -* `'Varchar'`: A variable-length string of up to 255 characters, designed to store raw text (see: [DBVarchar](api:SilverStripe\ORM\FieldType\DBVarchar)). - -See the [API documentation](api:SilverStripe\ORM\FieldType\DBField) for a full list of available Data Types. You can define your own [DBField](api:SilverStripe\ORM\FieldType\DBField) instances if required as well. - -## Default Values - -### Default values for new objects - -For complex default values for newly instantiated objects see [Dynamic Default Values](how_tos/dynamic_default_fields). -For simple values you can make use of the `$defaults` array. For example: - -```php -use SilverStripe\ORM\DataObject; - -class Car extends DataObject -{ - private static $db = [ - 'Wheels' => 'Int', - 'Condition' => 'Enum(["New","Fair","Junk"])' - ]; - - private static $defaults = [ - 'Wheels' => 4, - 'Condition' => 'New' - ]; -} -``` - -### Default values for new database columns - -When adding a new `$db` field to a DataObject you can specify a default value -to be applied to all existing records when the column is added in the database -for the first time. This will also be applied to any newly created objects -going forward. You do this by passing an argument for the default value in your -`$db` items. - -For integer values, the default is the first parameter in the field specification. -For string values, you will need to declare this default using the options array. -For enum values, it's the second parameter. - -For example: - -```php -use SilverStripe\ORM\DataObject; - -class Car extends DataObject -{ - private static $db = [ - 'Wheels' => 'Int(4)', - 'Condition' => 'Enum(["New","Fair","Junk"], "New")', - 'Make' => 'Varchar(["default" => "Honda"])', - ); -} -``` - -## Formatting Output - -The Data Type does more than setup the correct database schema. They can also define methods and formatting helpers for -output. You can manually create instances of a Data Type and pass it through to the template. - -In this case, we'll create a new method for our `Player` that returns the full name. By wrapping this in a [DBVarchar](api:SilverStripe\ORM\FieldType\DBVarchar) -object we can control the formatting and it allows us to call methods defined from `Varchar` as `LimitCharacters`. - -**app/src/Player.php** - -```php -use SilverStripe\ORM\FieldType\DBField; -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - public function getName() - { - return DBField::create_field('Varchar', $this->FirstName . ' '. $this->LastName); - } -} -``` - -Then we can refer to a new `Name` column on our `Player` instances. In templates we don't need to use the `get` prefix. - -```php -$player = Player::get()->byId(1); - -echo $player->Name; -// returns "Sam Minnée" - -echo $player->getName(); -// returns "Sam Minnée"; - -echo $player->getName()->LimitCharacters(2); -// returns "Sa…" -``` - -## Casting - -Rather than manually returning objects from your custom functions. You can use the `$casting` property. - -```php -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - private static $casting = [ - "Name" => 'Varchar', - ]; - - public function getName() - { - return $this->FirstName . ' '. $this->LastName; - } -} -``` - -The properties on any Silverstripe CMS object can be type casted automatically, by transforming its scalar value into an -instance of the [DBField](api:SilverStripe\ORM\FieldType\DBField) class, providing additional helpers. For example, a string can be cast as a [DBText](api:SilverStripe\ORM\FieldType\DBText) -type, which has a `FirstSentence()` method to retrieve the first sentence in a longer piece of text. - -On the most basic level, the class can be used as simple conversion class from one value to another, e.g. to round a -number. - -```php -use SilverStripe\ORM\FieldType\DBField; -DBField::create_field('Double', 1.23456)->Round(2); // results in 1.23 -``` - -Of course that's much more verbose than the equivalent PHP call. The power of [DBField](api:SilverStripe\ORM\FieldType\DBField) comes with its more -sophisticated helpers, like showing the time difference to the current date: - -```php -use SilverStripe\ORM\FieldType\DBField; -DBField::create_field('Date', '1982-01-01')->TimeDiff(); // shows "30 years ago" -``` - -## Casting ViewableData - -Most objects in Silverstripe CMS extend from [ViewableData](api:SilverStripe\View\ViewableData), which means they know how to present themselves in a view -context. Through a `$casting` array, arbitrary properties and getters can be casted: - -```php -use SilverStripe\View\ViewableData; - -class MyObject extends ViewableData -{ - - private static $casting = [ - 'MyDate' => 'Date' - ]; - - public function getMyDate() - { - return '1982-01-01'; - } -} - -$obj = new MyObject; -$obj->getMyDate(); // returns string -$obj->MyDate; // returns string -$obj->obj('MyDate'); // returns object -$obj->obj('MyDate')->InPast(); // returns boolean -``` - -## Casting HTML Text - -The database field types [DBHTMLVarchar](api:SilverStripe\ORM\FieldType\DBHTMLVarchar)/[DBHTMLText](api:SilverStripe\ORM\FieldType\DBHTMLText) and [DBVarchar](api:SilverStripe\ORM\FieldType\DBVarchar)/[DBText](api:SilverStripe\ORM\FieldType\DBText) are exactly the same in -the database. However, the template engine knows to escape fields without the `HTML` prefix automatically in templates, -to prevent them from rendering HTML interpreted by browsers. This escaping prevents attacks like CSRF or XSS (see -"[security](../security)"), which is important if these fields store user-provided data. - -See the [Template casting](/developer_guides/templates/casting) section for controlling casting in your templates. - -## Overloading - -"Getters" and "Setters" are functions that help save fields to our [DataObject](api:SilverStripe\ORM\DataObject) instances. By default, the -methods `getField()` and `setField()` are used to set column data. They save to the protected array, `$obj->record`. -We can overload the default behavior by making a function called "get``" or "set``". - -The following example will use the result of `getStatus` instead of the 'Status' database column. We can refer to the -database column using `getField()`. - -```php -use SilverStripe\ORM\DataObject; - -/** - * @property Float $Cost - */ -class Product extends DataObject -{ - - private static $db = [ - 'Title' => 'Varchar(255)', - 'Cost' => 'Int', //cost in pennies/cents - ]; - - public function getCost() - { - return $this->getField('Cost') / 100; - } - - public function setCost($value) - { - return $this->setField('Cost', $value * 100); - } - -} -``` - -## API Documentation - -* [DataObject](api:SilverStripe\ORM\DataObject) -* [DBField](api:SilverStripe\ORM\FieldType\DBField) diff --git a/docs/en/02_Developer_Guides/00_Model/05_Extending_DataObjects.md b/docs/en/02_Developer_Guides/00_Model/05_Extending_DataObjects.md deleted file mode 100644 index 6d60febeb4d..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/05_Extending_DataObjects.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Extending DataObjects -summary: Modify the data model without using subclasses. ---- - -# Extending DataObjects - -You can add properties and methods to existing [DataObject](api:SilverStripe\ORM\DataObject)s like [Member](api:SilverStripe\Security\Member) without hacking core code or sub -classing by using [DataExtension](api:SilverStripe\ORM\DataExtension). See the [Extending SilverStripe](../extending) guide for more information on -[DataExtension](api:SilverStripe\ORM\DataExtension). - -The following documentation outlines some common hooks that the [Extension](api:SilverStripe\Core\Extension) API provides specifically for managing -data records. - -## onBeforeWrite - -You can customise saving-behavior for each DataObject, e.g. for adding workflow or data customization. The function is -triggered when calling *write()* to save the object to the database. This includes saving a page in the CMS or altering -a `ModelAdmin` record. - -Example: Disallow creation of new players if the currently logged-in player is not a team-manager. - -```php -use SilverStripe\Security\Security; -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - private static $has_many = [ - 'Teams' => 'Team', - ]; - - public function onBeforeWrite() - { - // check on first write action, aka "database row creation" (ID-property is not set) - if (!$this->isInDb()) { - $currentPlayer = Security::getCurrentUser(); - - if (!$currentPlayer->IsTeamManager()) { - throw new \Exception('Player-creation not allowed'); - } - } - - // check on every write action - if (!$this->record['TeamID']) { - throw new \Exception('Cannot save player without a valid team'); - } - - // CAUTION: You are required to call the parent-function, otherwise - // SilverStripe will not execute the request. - parent::onBeforeWrite(); - } -} - -``` - -## onBeforeDelete - -Triggered before executing *delete()* on an existing object. - -Example: Checking for a specific [permission](permissions) to delete this type of object. It checks if a -member is logged in who belongs to a group containing the permission "PLAYER_DELETE". - -```php -use SilverStripe\Security\Permission; -use SilverStripe\Security\Security; -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - - private static $has_many = [ - "Teams" => "Team" - ]; - - public function onBeforeDelete() - { - if(!Permission::check('PLAYER_DELETE')) { - Security::permissionFailure($this); - exit(); - } - - parent::onBeforeDelete(); - } -} -``` - -[notice] -Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate*. Please check `$this->isInDb()` to toggle -these two modes, as shown in the example above. -[/notice] - -## Related Lessons -* [Working with data relationships - $has_many](https://www.silverstripe.org/learn/lessons/v4/working-with-data-relationships-has-many-1) diff --git a/docs/en/02_Developer_Guides/00_Model/06_SearchFilters.md b/docs/en/02_Developer_Guides/00_Model/06_SearchFilters.md deleted file mode 100644 index fc70be15f88..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/06_SearchFilters.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: SearchFilter Modifiers -summary: Use suffixes on your ORM queries. -icon: search ---- - -# SearchFilter Modifiers - -The `filter` and `exclude` operations specify exact matches by default. However, when filtering `DataList`s, there are a number of suffixes that -you can put on field names to change this behavior. These are represented as `SearchFilter` subclasses and include. - - * [ExactMatchFilter](api:SilverStripe\ORM\Filters\ExactMatchFilter) - * [StartsWithFilter](api:SilverStripe\ORM\Filters\StartsWithFilter) - * [EndsWithFilter](api:SilverStripe\ORM\Filters\EndsWithFilter) - * [PartialMatchFilter](api:SilverStripe\ORM\Filters\PartialMatchFilter) - * [GreaterThanFilter](api:SilverStripe\ORM\Filters\GreaterThanFilter) - * [GreaterThanOrEqualFilter](api:SilverStripe\ORM\Filters\GreaterThanOrEqualFilter) - * [LessThanFilter](api:SilverStripe\ORM\Filters\LessThanFilter) - * [LessThanOrEqualFilter](api:SilverStripe\ORM\Filters\LessThanOrEqualFilter) - -An example of a `SearchFilter` in use: - -```php -// fetch any player that starts with a S -$players = Player::get()->filter([ - 'FirstName:StartsWith' => 'S', - 'PlayerNumber:GreaterThan' => '10' -]); - -// to fetch any player that's name contains the letter 'z' -$players = Player::get()->filterAny([ - 'FirstName:PartialMatch' => 'z', - 'LastName:PartialMatch' => 'z' -]); -``` - -Developers can define their own [SearchFilter](api:SilverStripe\ORM\Filters\SearchFilter) if needing to extend the ORM filter and exclude behaviors. - -These suffixes can also take modifiers themselves. The modifiers currently supported are `":not"`, `":nocase"` and -`":case"`. These negate the filter, make it case-insensitive and make it case-sensitive, respectively. The default -comparison uses the database's default. For MySQL and MSSQL, this is case-insensitive. For PostgreSQL, this is -case-sensitive. - -```php -// Fetch players that their FirstName is 'Sam' -// Caution: This might be case in-sensitive if MySQL or MSSQL is used -$players = Player::get()->filter([ - 'FirstName:ExactMatch' => 'Sam' -]); - -// Fetch players that their FirstName is 'Sam' (force case-sensitive) -$players = Player::get()->filter([ - 'FirstName:ExactMatch:case' => 'Sam' -]); - -// Fetch players that their FirstName is 'Sam' (force NOT case-sensitive) -$players = Player::get()->filter([ - 'FirstName:ExactMatch:nocase' => 'Sam' -]); -``` - -By default the `:ExactMatch` filter is applied, hence why we can shorthand the above to: -```php -$players = Player::get()->filter('FirstName', 'Sam'); // Default DB engine behaviour -$players = Player::get()->filter('FirstName:case', 'Sam'); // case-sensitive -$players = Player::get()->filter('FirstName:nocase', 'Sam'); // NOT case-sensitive -``` - -Note that all search filters (e.g. `:PartialMatch`) refer to services registered with [Injector](api:SilverStripe\Core\Injector\Injector) -within the `DataListFilter.` prefixed namespace. New filters can be registered using the below yml -config: - - -```yaml -SilverStripe\Core\Injector\Injector: - DataListFilter.CustomMatch: - class: MyVendor\Search\CustomMatchFilter -``` - -The following is a query which will return everyone whose first name starts with "S", either lowercase or uppercase: - -```php -$players = Player::get()->filter([ - 'FirstName:StartsWith:nocase' => 'S' -]); - -// use :not to perform a converse operation to filter anything but a 'W' -$players = Player::get()->filter([ - 'FirstName:StartsWith:not' => 'W' -]); -``` - -## Related Lessons -* [Introduction to ModelAdmin](https://www.silverstripe.org/learn/lessons/v4/introduction-to-modeladmin-1) -* [Building a search form](https://www.silverstripe.org/learn/lessons/v4/building-a-search-form-1) - -## API Documentation - -* [SearchFilter](api:SilverStripe\ORM\Filters\SearchFilter) diff --git a/docs/en/02_Developer_Guides/00_Model/07_Permissions.md b/docs/en/02_Developer_Guides/00_Model/07_Permissions.md deleted file mode 100644 index 9f07c715979..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/07_Permissions.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Model-Level Permissions -summary: Reduce risk by securing models. -icon: lock ---- - -# Model-Level Permissions - -Models can be modified in a variety of controllers and user interfaces, all of which can implement their own security -checks. Often it makes sense to centralize those checks on the model, regardless of the used controller. - -The API provides four methods for this purpose: `canEdit()`, `canCreate()`, `canView()` and `canDelete()`. - -Since they're PHP methods, they can contain arbitrary logic matching your own requirements. They can optionally receive -a `$member` argument, and default to the currently logged in member (through `Security::getCurrentUser()`). - -[notice] -By default, all `DataObject` subclasses can only be edited, created and viewed by users with the 'ADMIN' permission -code. -[/notice] - -```php -use SilverStripe\Security\Permission; -use SilverStripe\ORM\DataObject; - -class MyDataObject extends DataObject -{ - public function canView($member = null) - { - return Permission::check('CMS_ACCESS_CMSMain', 'any', $member); - } - - public function canEdit($member = null) - { - return Permission::check('CMS_ACCESS_CMSMain', 'any', $member); - } - - public function canDelete($member = null) - { - return Permission::check('CMS_ACCESS_CMSMain', 'any', $member); - } - - public function canCreate($member = null, $context = []) - { - return Permission::check('CMS_ACCESS_CMSMain', 'any', $member); - } -} -``` - -[alert] -These checks are not enforced on low-level ORM operations such as `write()` or `delete()`, but rather rely on being -checked in the invoking code. The CMS default sections as well as custom interfaces like [ModelAdmin](api:SilverStripe\Admin\ModelAdmin) or -[GridField](api:SilverStripe\Forms\GridField\GridField) already enforce these permissions. -[/alert] - -## API Documentation - -* [DataObject](api:SilverStripe\ORM\DataObject) -* [Permission](api:SilverStripe\Security\Permission) diff --git a/docs/en/02_Developer_Guides/00_Model/08_SQL_Select.md b/docs/en/02_Developer_Guides/00_Model/08_SQL_Select.md deleted file mode 100644 index bc0a53e6230..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/08_SQL_Select.md +++ /dev/null @@ -1,323 +0,0 @@ ---- -title: SQL Queries -summary: Write and modify direct database queries through SQLExpression subclasses. -iconBrand: searchengin ---- - -# SQLSelect - -## Introduction - -An object representing a SQL select query, which can be serialized into a SQL statement. -It is easier to deal with object-wrappers than string-parsing a raw SQL-query. -This object is used by the Silverstripe CMS ORM internally. - -Dealing with low-level SQL is not encouraged, since the ORM provides -powerful abstraction APIs (see [datamodel](/developer_guides/model/data_model_and_orm)). -Starting with Silverstripe CMS 3, records in collections are lazy loaded, -and these collections have the ability to run efficient SQL -such as counts or returning a single column. - -For example, if you want to run a simple `COUNT` SQL statement, -the following three statements are functionally equivalent: - -```php -use SilverStripe\ORM\DB; -use SilverStripe\ORM\Queries\SQLSelect; -use SilverStripe\Security\Member; - -// Through raw SQL. -$count = DB::query('SELECT COUNT(*) FROM "Member"')->value(); - -// Through SQLSelect abstraction layer. -$query = new SQLSelect(); -$count = $query->setFrom('Member')->setSelect('COUNT(*)')->value(); - -// Through the ORM. -$count = Member::get()->count(); -``` - -If you do use raw SQL, you'll run the risk of breaking -various assumptions the ORM and code based on it have: - -* Custom getters/setters (object property can differ from database column) -* DataObject hooks like `onBeforeWrite()` and `onBeforeDelete()` -* Automatic casting -* Default values set through objects -* Database abstraction - -We'll explain some ways to use *SELECT* with the full power of SQL, -but still maintain a connection to the ORM where possible. - -[warning] -Please read our [security topic](/developer_guides/security) to find out -how to properly prepare user input and variables for use in queries -[/warning] - -## Usage - -### SELECT - -Selection can be done by creating an instance of `SQLSelect`, which allows -management of all elements of a SQL SELECT query, including columns, joined tables, -conditional filters, grouping, limiting, and sorting. - -E.g.: - -```php -$sqlQuery = new SQLSelect(); -$sqlQuery->setFrom('Player'); -$sqlQuery->selectField('FieldName', 'Name'); -$sqlQuery->selectField('YEAR("Birthday")', 'Birthyear'); -$sqlQuery->addLeftJoin('Team','"Player"."TeamID" = "Team"."ID"'); -$sqlQuery->addWhere(['YEAR("Birthday") = ?' => 1982]); -// $sqlQuery->setOrderBy(...); -// $sqlQuery->setGroupBy(...); -// $sqlQuery->setHaving(...); -// $sqlQuery->setLimit(...); -// $sqlQuery->setDistinct(true); - -// Get the raw SQL (optional) and parameters -$rawSQL = $sqlQuery->sql($parameters); - -// Execute and return a Query object -$result = $sqlQuery->execute(); - -// Iterate over results -foreach($result as $row) { - echo $row['BirthYear']; -} -``` - -The result of `SQLSelect::execute()` is an array lightly wrapped in a database-specific subclass of [Query](api:SilverStripe\ORM\Connect\Query). -This class implements the *Iterator*-interface, and provides convenience-methods for accessing the data. - -### DELETE - -Deletion can be done either by calling `DB::query`/`DB::prepared_query` directly, -by creating a `SQLDelete` object, or by transforming a `SQLSelect` into a `SQLDelete` -object instead. - -For example, creating a `SQLDelete` object: - -```php -use SilverStripe\ORM\Queries\SQLDelete; - -$query = SQLDelete::create() - ->setFrom('"SiteTree"') - ->setWhere(['"SiteTree"."ShowInMenus"' => 0]); -$query->execute(); -``` - -Alternatively, turning an existing `SQLSelect` into a delete: - -```php -use SilverStripe\ORM\Queries\SQLSelect; - -$query = SQLSelect::create() - ->setFrom('"SiteTree"') - ->setWhere(['"SiteTree"."ShowInMenus"' => 0]) - ->toDelete(); -$query->execute(); -``` - -Directly querying the database: - -```php -use SilverStripe\ORM\DB; -DB::prepared_query('DELETE FROM "SiteTree" WHERE "SiteTree"."ShowInMenus" = ?', [0]); -``` - -### INSERT/UPDATE - -INSERT and UPDATE can be performed using the `SQLInsert` and `SQLUpdate` classes. -These both have similar aspects in that they can modify content in -the database, but each are different in the way in which they behave. - -Previously, similar operations could be performed by using the `DB::manipulate` -function which would build the INSERT and UPDATE queries on the fly. This method -still exists, but internally uses `SQLUpdate` / `SQLInsert`, although the actual -query construction is now done by the `DBQueryBuilder` object. - -Each of these classes implement the interface `SQLWriteExpression`, noting that each -accepts write key/value pairs in a number of similar ways. These include the following -API methods: - - * `addAssignments` - Takes a list of assignments as an associative array of key -> value pairs, - but also supports SQL expressions as values if necessary - * `setAssignments` - Replaces all existing assignments with the specified list - * `getAssignments` - Returns all currently given assignments, as an associative array - in the format `['Column' => ['SQL' => ['parameters]]]` - * `assign` - Singular form of addAssignments, but only assigns a single column value - * `assignSQL` - Assigns a column the value of a specified SQL expression without parameters - `assignSQL('Column', 'SQL')` is shorthand for `assign('Column', ['SQL' => []])` - -SQLUpdate also includes the following API methods: - - * `clear` - Clears all assignments - * `getTable` - Gets the table to update - * `setTable` - Sets the table to update (this should be ANSI-quoted) - e.g. `$query->setTable('"SiteTree"');` - -SQLInsert also includes the following API methods: - * `clear` - Clears all rows - * `clearRow` - Clears all assignments on the current row - * `addRow` - Adds another row of assignments, and sets the current row to the new row - * `addRows` - Adds a number of arrays, each representing a list of assignment rows, - and sets the current row to the last one - * `getColumns` - Gets the names of all distinct columns assigned - * `getInto` - Gets the table to insert into - * `setInto` - Sets the table to insert into (this should be ANSI-quoted), - e.g. `$query->setInto('"SiteTree"');` - -E.g.: - -```php -use SilverStripe\ORM\Queries\SQLUpdate; - -$update = SQLUpdate::create('"SiteTree"')->addWhere(['ID' => 3]); - -// assigning a list of items -$update->addAssignments([ - '"Title"' => 'Our Products', - '"MenuTitle"' => 'Products' -]); - -// Assigning a single value -$update->assign('"MenuTitle"', 'Products'); - -// Assigning a value using parameterised expression -$title = 'Products'; -$update->assign('"MenuTitle"', [ - 'CASE WHEN LENGTH("MenuTitle") > LENGTH(?) THEN "MenuTitle" ELSE ? END' => - [$title, $title] -]); - -// Assigning a value using a pure SQL expression -$update->assignSQL('"Date"', 'NOW()'); - -// Perform the update -$update->execute(); -``` - -In addition to assigning values, the SQLInsert object also supports multi-row -inserts. For database connectors and API that don't have multi-row insert support -these are translated internally as multiple single row inserts. - -For example: - -```php -use SilverStripe\ORM\Queries\SQLInsert; - -$insert = SQLInsert::create('"SiteTree"'); - -// Add multiple rows in a single call. Note that column names do not need -// to be symmetric -$insert->addRows([ - ['"Title"' => 'Home', '"Content"' => '

This is our home page

'], - ['"Title"' => 'About Us', '"ClassName"' => 'AboutPage'] -]); - -// Adjust an assignment on the last row -$insert->assign('"Content"', '

This is about us

'); - -// Add another row -$insert->addRow(['"Title"' => 'Contact Us']); - -$columns = $insert->getColumns(); -// $columns will be ['"Title"', '"Content"', '"ClassName"']; - -$insert->execute(); -``` - -### Value Checks - -Raw SQL is handy for performance-optimized calls, -e.g. when you want a single column rather than a full-blown object representation. - -Example: Get the count from a relationship. - -```php -use SilverStripe\ORM\Queries\SQLSelect; - -$sqlQuery = new SQLSelect(); -$sqlQuery->setFrom('Player'); -$sqlQuery->addSelect('COUNT("Player"."ID")'); -$sqlQuery->addWhere(['"Team"."ID"' => 99]); -$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"'); -$count = $sqlQuery->execute()->value(); -``` - -Note that in the ORM, this call would be executed in an efficient manner as well: - -```php -$count = $myTeam->Players()->count(); -``` - -### Mapping - -Creates a map based on the first two columns of the query result. -This can be useful for creating dropdowns. - -Example: Show player names with their birth year, but set their birth dates as values. - -```php -use SilverStripe\ORM\Queries\SQLSelect; -use SilverStripe\Forms\DropdownField; - -$sqlQuery = new SQLSelect(); -$sqlQuery->setFrom('Player'); -$sqlQuery->setSelect('Birthdate'); -$sqlQuery->selectField('CONCAT("Name", ' - ', YEAR("Birthdate")', 'NameWithBirthyear'); -$map = $sqlQuery->execute()->map(); -$field = new DropdownField('Birthdates', 'Birthdates', $map); -``` - -Note that going through SQLSelect is just necessary here -because of the custom SQL value transformation (`YEAR()`). -An alternative approach would be a custom getter in the object definition: - -```php -use SilverStripe\ORM\DataObject; - -class Player extends DataObject -{ - private static $db = [ - 'Name' => 'Varchar', - 'Birthdate' => 'Date' - ]; - function getNameWithBirthyear() { - return date('y', $this->Birthdate); - } -} -$players = Player::get(); -$map = $players->map('Name', 'NameWithBirthyear'); -``` - -### Data types - -As of Silverstripe CMS 4.4, the following PHP types will be used to return database content: - - * booleans will be an integer 1 or 0, to ensure consistency with MySQL that doesn't have native booleans - * integer types returned as integers - * floating point / decimal types returned as floats - * strings returned as strings - * dates / datetimes returned as strings - -Up until Silverstripe CMS 4.3, bugs meant that strings were used for every column type. - -## Related Lessons -* [Building custom SQL](https://www.silverstripe.org/learn/lessons/v4/beyond-the-orm-building-custom-sql-1) - - -## Related Documentation - -* [Introduction to the Data Model and ORM](data_model_and_orm) - -## API Documentation - -* [DataObject](api:SilverStripe\ORM\DataObject) -* [SQLSelect](api:SilverStripe\ORM\Queries\SQLSelect) -* [DB](api:SilverStripe\ORM\DB) -* [Query](api:SilverStripe\ORM\Connect\Query) -* [Database](api:SilverStripe\ORM\Connect\Database) diff --git a/docs/en/02_Developer_Guides/00_Model/09_Validation.md b/docs/en/02_Developer_Guides/00_Model/09_Validation.md deleted file mode 100644 index 6ac23e9bdbe..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/09_Validation.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: Model Validation and Constraints -summary: Validate your data at the model level -icon: check-square ---- - -# Validation and Constraints - -Traditionally, validation in Silverstripe CMS has been mostly handled on the controller through [form validation](../forms). - -While this is a useful approach, it can lead to data inconsistencies if the record is modified outside of the -controller and form context. - -Most validation constraints are actually data constraints which belong on the model. Silverstripe CMS provides the -[DataObject::validate()](api:SilverStripe\ORM\DataObject::validate()) method for this purpose. - -By default, there is no validation - objects are always valid! However, you can overload this method in your DataObject -sub-classes to specify custom validation, or use the `validate` hook through a [DataExtension](api:SilverStripe\ORM\DataExtension). - -Invalid objects won't be able to be written - a [ValidationException](api:SilverStripe\ORM\ValidationException) will be thrown and no write will occur. - -It is expected that you call `validate()` in your own application to test that an object is valid before attempting a -write, and respond appropriately if it isn't. - -The return value of `validate()` is a [ValidationResult](api:SilverStripe\ORM\ValidationResult) object. - -```php -use SilverStripe\ORM\DataObject; - -class MyObject extends DataObject -{ - - private static $db = [ - 'Country' => 'Varchar', - 'Postcode' => 'Varchar' - ]; - - public function validate() - { - $result = parent::validate(); - - if($this->Country == 'DE' && $this->Postcode && strlen($this->Postcode) != 5) { - $result->addError('Need five digits for German postcodes'); - } - - return $result; - } -} -``` - -## API Documentation - -* [DataObject](api:SilverStripe\ORM\DataObject) -* [ValidationResult](api:SilverStripe\ORM\ValidationResult); diff --git a/docs/en/02_Developer_Guides/00_Model/10_Versioning.md b/docs/en/02_Developer_Guides/00_Model/10_Versioning.md deleted file mode 100644 index bcb49ac5fc1..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/10_Versioning.md +++ /dev/null @@ -1,1310 +0,0 @@ ---- -title: Versioning -summary: Add versioning to your database content through the Versioned extension. ---- - -# Versioning - -Database content in Silverstripe CMS can be "staged" before its publication, as well as track all changes through the -lifetime of a database record. - -It is most commonly applied to pages in the CMS (the `SiteTree` class). Draft content edited in the CMS can be different -from published content shown to your website visitors. - -Versioning in Silverstripe CMS is handled through the [Versioned](api:SilverStripe\Versioned\Versioned) class. As a [DataExtension](api:SilverStripe\ORM\DataExtension) it is possible to be applied to any [DataObject](api:SilverStripe\ORM\DataObject) subclass. The extension class will automatically update read and write operations done via the ORM via the `augmentSQL` database hook. - -[notice] -There are two complementary modules that improve content editor experience around "owned" nested objects (e.g. elemental blocks). -Those are in experimental status right now, but we would appreciate any feedback and contributions. - -You can check them out on github: - - https://github.com/silverstripe/silverstripe-versioned-snapshots - - https://github.com/silverstripe/silverstripe-versioned-snapshot-admin - -The first one adds extra metadata to versions about object parents at the moment of version creation. -The second module extends CMS History UI adding control over nested objects. - -![](../../_images/snapshot-admin.png) - -*Example screenshot from versioned-snapshot-admin* -[/notice] - -## Understanding versioning concepts - -This section discusses how Silverstripe CMS implements versioning and related high level concepts without digging into technical details. - -### Stages - -In most cases, you'll want to have one polished version of a `Page` visible to the general public while your editors might be working off a draft version. Silverstripe CMS handles this through the concept of _stage_. - -By default, adding the `Versioned` extension to a DataObject will create 2 stages: -* "Stage" for tracking draft content -* "Live" for tracking content publicly visible. - -Publishing a versioned `DataObject` is equivalent to copying the version from the "Stage" stage to the "Live" stage. - -You can disable stages if your DataObject doesn't require a published version. This will allow you to keep track of all changes that have been applied to a DataObject and who made them. - -### Ownership and relations between DataObjects {#ownership} - -Typically when publishing versioned DataObjects, it is necessary to ensure that some linked components -are published along with it. Unless this is done, site content can appear incorrectly published. - -For instance, a page which has a list of rotating banners will require them to be published -whenever that page is. - -This is solved through the Ownership API, which declares a two-way relationship between -objects along database relations. This relationship is similar to many_many/belongs_many_many -and has_one/has_many, however it relies on a pre-existing relationship to function. - -#### Cascade publishing - -If an object "owns" other objects, you'll usually want to publish the child objects when the parent object gets published. If those child objects themselves own other objects, you'll want the grand-children to be published along with the parent. - -Silverstripe CMS makes this possible by using the concept of _cascade publishing_. You can choose to recursively publish an object. When an object is recursively published – either through a user action or through code – all other records it owns that implement the Versioned extension will automatically be published. Publication will also cascade to children of children and so on. - -A non-recursive publish operation is also available if you want to publish a new version of a object without cascade publishing all its children. - -[alert] -Declaring ownership implies publish permissions on owned objects. -Built-in controllers using cascading publish operations check canPublish() -on the owner, but not on the owned object. -[/alert] - -#### Ownership of unversioned object - -An unversioned object can own other versioned object. An unversioned object can be configured to automatically publish children versioned objects on save. - -An unversioned object can also be owned by a versioned object. This can be used to recursively publish _children-of-children_ object without requiring the intermediate relationship to go through a versioned object. This behavior can be helpful if you wish to group multiple versioned objects together. - -#### Ownership through media insertion in content - -Images and other files are tracked as versioned objects. If a file is referenced through an HTML text field, it needs to be published for it to be accessible to the public. Silverstripe CMS will automatically pick up when an object references files through an HTML text field and recursively publish those files. - -This behavior works both for versioned and unversioned objects. - -### Grouping versioned DataObjects into a ChangeSet (aka Campaigns) - -Sometimes, multiple pages or records may be related in organic ways that cannot be properly expressed through an ownership relation. There's still value in being able to publish those as a block. - -For example, your editors may be about to launch a new contest through their website. They've drafted a page to promote the contest, another page with the rules and conditions, a registration page for users to sign up, some promotional images, new sponsor records, etc. All this content needs to become visible simultaneously. - -Changes to many objects can be grouped together using the [`ChangeSet`](api:SilverStripe\Versioning\ChangeSet) object. In the CMS, editors can manage `ChangeSet`s through the "Campaign" section, if the `silverstripe/campaign-admin` module is installed). By grouping a series of content changes together as cohesive unit, content editors can bulk publish an entire body of content all at once, which affords them much more power and control over interdependent content types. - -Records can be added to a changeset in the CMS by using the "Add to campaign" button -that is available on the edit forms of all pages and files. Programmatically, this is done by creating a `SilverStripe\Versioned\ChangeSet` object and invoking its `addObject(DataObject $record)` method. - -[info] -DataObjects can be added to more than one ChangeSet. -Most of the time, these objects contain changes. -A ChangeSet can contain unchanged objects as well. -[/info] - -#### Implicit vs. Explicit inclusions - -Items can be added to a changeset in two ways -- *implicitly* and *explicitly*. - -An *implicit* inclusion occurs when a record is added to a changeset by virtue of another object declaring ownership of it via the `$owns` setting. Implicit inclusion of owned objects ensures that when a changeset is published, the action cascades through not only all of the items explicitly added to the changeset, but also all of the records that each of those items owns. - -An *explicit* inclusion is much more direct, occurring only when a user has opted to include a record in a changeset either through the UI or programmatically. - -It is possible for an item to be included both implicitly and explicitly in a changeset. For instance, if a page owns a file, and the page gets added to a changeset, the file is implicitly added. That same file, however, can still be added to the changeset explicitly through the file editor. In this case, the file is considered to be *explicitly* added. If the file is later removed from the changeset, it is then considered *implicitly* added, due to its owner page still being in the changeset. - -## Implementing a versioned DataObject - -This section explains how to take a regular DataObject and add versioning to it. - -### Applying the Versioned extension to your DataObject - -```php -extending and the Configuration documentation. -[/notice] - -[warning] -Versioning only works if you are adding the extension to the base class. That is, the first subclass -of `DataObject`. Adding this extension to children of the base class will have unpredictable behaviour. -[/warning] - - -### Defining ownership between related versioned DataObjects - -You can use the `owns` static private property on a DataObject to specify which relationships are ownership relationships. The `owns` property should be defined on the _owner_ DataObject. - -For example, let's say you have a `MyPage` page type that displays banners containing an image. Each `MyPage` owns many `Banners`, which in turn owns an `Image`. - - -```php - Banner::class - ]; - private static $owns = [ - 'Banners' - ]; -} -class Banner extends DataObject -{ - private static $extensions = [ - Versioned::class - ]; - private static $has_one = [ - 'Parent' => MyPage::class, - 'Image' => Image::class, - ]; - private static $owns = [ - 'Image' - ]; -} -``` - -If a `MyPage` gets published, all its related `Banners` will also be published, which will cause all `Image` DataObjects to be published. - -Note that ownership cannot be used with polymorphic relations. E.g. has_one to non-type specific `DataObject`. - -#### Unversioned DataObject ownership - -*Requires Silverstripe CMS 4.1 or newer* - -Ownership can be used with non-versioned DataObjects, as the necessary functionality is included by default -by the versioned object through the [`RecursivePublishable`](api:SilverStripe\Versioned\RecursivePublishable) extension which is -applied to all objects. - -However, it is important to note that even when saving un-versioned objects, it is necessary to use -`->publishRecursive()` to trigger a recursive publish. - -The `owns` feature works the same regardless of whether these objects are versioned, so you can use any combination of -versioned or unversioned dataobjects. You only need to call `->publishRecursive()` on the top most -object in the tree. - -#### DataObject ownership with custom relations - -In some cases you might need to apply ownership where there is no underlying database relation, such as -those calculated at runtime based on business logic. In cases where you are not backing ownership -with standard relations (`has_one`, `has_many`, etc) it is necessary to declare ownership on both -sides of the relation. - -This can be done by creating methods on both sides of your relation (e.g. parent and child class) -that can be used to traverse between each, and then by ensuring you configure both -`owns` config (on the parent) and `owned_by` (on the child). - -E.g. - -```php -first(); - } -} -``` - -#### DataObject ownership in HTML content - -If you are using [`DBHTMLText`](api:SilverStripe\ORM\FieldType\DBHTMLText) or [`DBHTMLVarchar`](api:SilverStripe\ORM\FieldType\DBHTMLVarchar) fields in your `DataObject::$db` definitions, -it's likely that your authors can insert images into those fields via the CMS interface. -These images are usually considered to be owned by the `DataObject`, and should be published alongside it. -The ownership relationship is tracked through an `[image]` [shortcode](/developer-guides/extending/shortcodes), -which is automatically transformed into an `` tag at render time. In addition to storing the image path, -the shortcode references the database identifier of the `Image` object. - -### Controlling how CMS users interact with versioned DataObjects - -By default the versioned module includes a `VersionedGridfieldDetailForm` that extends gridfield with versioning support for DataObjects. - -You can disable this on a per-model basis using the following code: - -```php -getComponentByType(GridFieldDetailForm::class) - ->setItemRequestClass(VersionedGridFieldItemRequest::class); - $gridField = GridField::create('Items', 'Items', $this->Items(), $config); - $fields->addFieldToTab('Root.Items', $gridField); - return $fields; - } -} -``` - -## Interacting with versioned DataObjects - -This section deals with specialised operations that can be performed on versioned DataObjects. - -### Reading latest versions by stage - -By default, all records are retrieved from the "Draft" stage (so the `MyRecord` table in our example). You can -explicitly request a certain stage through various getters on the `Versioned` class. - -```php -byID(99); -$liveRecord = Versioned::get_by_stage('MyRecord', Versioned::LIVE)->byID(99); -``` - -### Reading historical versions - -The above commands will just retrieve the latest version of its respective stage for you, but not older versions stored -in the `_versions` tables. - -```php -, ); -``` - -[alert] -The record is retrieved as a `DataObject`, but saving back modifications via `write()` will create a new version, -rather than modifying the existing one. -[/alert] - -In order to get a list of all versions for a specific record, we need to generate specialized [Versioned_Version](api:SilverStripe\Versioned\Versioned_Version) -objects, which expose the same database information as a `DataObject`, but also include information about when and how -a record was published. - -```php -byID(99); // stage doesn't matter here -$versions = $record->allVersions(); -echo $versions->First()->Version; // instance of Versioned_Version -``` - -### Writing changes to a versioned DataObject - -When you call the `write()` method on a versioned DataObject, this will transparently create a new version of this DataObject in the _Stage_ stage. - -To write your changes without creating new version, call [writeWithoutVersion()](api:SilverStripe\Versioned\Versioned::writeWithoutVersion()) instead. -```php -byID(99); // This will retrieve the latest draft version of record ID 99. -echo $record->Version; // This will output the version ID. Let's assume it's 13. - - -$record->Title = "Foo Bar"; -$record->write(); // This will create a new version of record ID 99. -echo $record->Version; // Will output 14. - -$record->Title = "FOO BAR"; -$record->writeWithoutVersion(); -echo $record->Version; // Will still output 14. -``` - -Similarly, an "unpublish" operation does the reverse, and removes a record from a specific stage. - -### Publishing a versioned DataObject - -There's two main methods used to publish a versioned DataObject: -* `publishSingle()` publishes this record to live from the draft -* `publishRecursive()` publishes this record, and any dependent objects this record may refer to. - -In most regular cases, you'll want to use `publishRecursive`. - -`publishRecursive` can be called on unversioned DataObject as well if they implement the `RecursivePublishable` extension. - -```php -byID(99); -$record->MyField = 'changed'; - -// Will create a new revision in Stage. Editors will be able to see this revision, but not visitors to the website. -$record->write(); - -// This will publish the changes so they are visible publicly. -$record->publishRecursive(); -``` - -### Unpublishing and archiving a versioned DataObject - -Archiving and unpublishing are similar operations, both will prevent a versioned DataObject from being publicly accessible. Archiving will also remove the record from the _Stage_ stage; other ORMs may refer to this concept as _soft-deletion_. - -Use `doUnpublish()` to unpublish an item. Simply call `delete()` to archive an item. The SilverStripe ORM doesn't allow you to _hard-delete_ versioned DataObjects. - -```php -byID(99); - -// Visitors to the site won't be able to see this record anymore, but editors can still edit it and re-publish it. -$record->doUnpublish(); - - -// Editors won't be able to see this record anymore, but it will still be in the database and may be restore. -$record->delete(); -``` - -Note that `doUnpublish()` and `doArchive()` do not work recursively. If you wish to unpublish or archive dependants records, you have to do it manually. - -### Rolling back to an older version -Rolling back allows you to return a DataObject to a previous state. You can rollback a single DataObject using the `rollbackSingle()` method. You can also rollback all dependent records using the `rollbackRecursive()` method. - -Both `rollbackSingle()` and `rollbackRecursive()` expect a single argument, which may be a specific version ID or a stage name. - -```php -byID(99); - -// This will take the current live version of a record - and all it's associated DataObjects - and copy it to the -// "Stage" stage. This is equivalent to dismissing any draft work and reverting to what was last published. -$record->rollbackRecursive(Versioned::LIVE); - -// This will restore a specific version of the record to "Stage" without affecting any owned DataObjects. -$versionToRestore = 10; -$record->rollbackSingle($versionToRestore); - -// The live version of the record won't be affected unless you publish you're rolled back record. -$record->publishRecursive(); -``` - -Note that internally, rolling back a DataObject creates a new version identical of the restored version ID. For example, -if the live version of `$record` is 10 and the staged version is 13, rolling back to live will create a version 14 in _Stage_ that is identical to version 10. - -### Restoring an archived version - -Archived records can still be retrieved using `get_including_deleted()`. This will include archived as well as current records. You can use the `isArchived()` method to determine if a record is archived or not. Calling the `write()` method on an archived record will restore it to the _Stage_ stage. - -```php -isArchived()) { - $myRecord->write(); - } -} -``` - -## Interacting with ChangeSet - -This section explains how you can interact with ChangeSets. - -### Adding and removing DataObjects to a change set - -* `$myChangeSet->addObject(DataObject $record)`: Add a record and all of its owned records to the changeset (`canEdit()` dependent). -* `$myChangeSet->removeObject(DataObject $record)`: Removes a record and all of its owned records from the changeset (`canEdit()` dependent). - -### Performing actions on the ChangeSet object - -* `$myChangeSet->publish()`: Publishes all items in the changeset that have modifications, along with all their owned records (`canPublish()` dependent). Closes the changeset on completion. -* `$myChangeSet->sync()`: Find all owned records with modifications for each item in the changeset, and include them implicitly. -* `$myChangeSet->validate()`: Ensure all owned records with modifications for each item in the changeset are included. This method should not need to be invoked if `sync()` is being used on each mutation to the changeset. - -### Getting information about the state of the ChangeSet - -ChangeSets can exists in three different states: - -* `open` No action has been taken on the ChangeSet. Resolves to `publishing` or `reverting`. -* `published`: The ChangeSet has published changes to all of its items and its now closed. -* `reverted`: The ChangeSet has reverted changes to all of its items and its now closed. (Future API, not supported yet) - -### Getting information about items in a ChangeSet - -Each item in the ChangeSet stores `VersionBefore` and `VersionAfter` fields. As such, they can compute the type of change they are adding to their parent ChangeSet. Change types include: - -* `created`: This ChangeSet item is for a record that does not yet exist -* `modified`: This ChangeSet item is for a record that differs from what is on the live stage -* `deleted`: This ChangeSet item will no longer exist when the ChangeSet is published -* `none`: This ChangeSet item is exactly as it is on the live stage - -## Advanced versioning topics - -These topics are targeted towards more advanced use cases that might require developers to extend the behavior of versioning. - -### How versioned DataObjects are tracked in the database - -Depending on whether staging is enabled, one or more new tables will be created for your records. `_versions` -is always created to track historic versions for your model. If staging is enabled this will also create a new -`_Live` table once you've rebuilt the database. - -[notice] -Note that the "Stage" naming has a special meaning here, it will leave the original table name unchanged, rather than -adding a suffix. -[/notice] - - * `MyRecord` table: Contains staged data - * `MyRecord_Live` table: Contains live data - * `MyRecord_Versions` table: Contains a version history (new record created on each save) - -Similarly, any subclass you create on top of a versioned base will trigger the creation of additional tables, which are -automatically joined as required: - - * `MyRecordSubclass` table: Contains only staged data for subclass columns - * `MyRecordSubclass_Live` table: Contains only live data for subclass columns - * `MyRecordSubclass_Versions` table: Contains only version history for subclass columns - -Because `many_many` relationships create their own sets of records on their own tables, representing content changes to a DataObject, they can therefore be versioned. This is done using the ["through" setting](https://docs.silverstripe.org/en/4/developer_guides/model/relations/#many-many-through-relationship-joined-on-a-separate-dataobject) on a `many_many` definition. This setting allows you to specify a custom DataObject through which to map the `many_many` relation. As such, it is possible to version your `many_many` data by versioning a "through" dataobject. For example: - -```php - 'Varchar(100)', - 'Price' => 'Currency', - ]; - - private static $many_many = [ - 'Categories' => [ - 'through' => 'ProductCategory', - 'from' => 'Product', - 'to' => 'Category', - ], - ]; -} -``` - -```php - 'Int', - ]; - - private static $has_one = [ - 'Product' => Product::class, - 'Category'=> Category::class, - ]; - - private static $extensions = [ - Versioned::class, - ]; -} -``` - -### Writing custom queries to retrieve versioned DataObject - -We generally discourage writing `Versioned` queries from scratch, due to the complexities involved through joining -multiple tables across an inherited table scheme (see [Versioned::augmentSQL()](api:SilverStripe\Versioned\Versioned::augmentSQL())). If possible, try to stick to smaller modifications of the generated `DataList` objects. - -Example: Get the first 10 live records, filtered by creation date: - -```php -limit(10)->sort('Created', 'ASC'); -``` - -### Controlling what stage is displayed in the front end - -The current stage for each request is determined by `VersionedHTTPMiddleware` before any controllers initialize, through -`Versioned::choose_site_stage()`. It checks for a `stage` GET parameter, so you can force a draft stage by appending -`?stage=Stage` to your request. - -Since Silverstripe CMS 4.2, the current stage setting is no longer "sticky" in the session. -Any links presented on the view produced with `?stage=Stage` need to have the same GET parameters in order -to retain the stage. If you are using the `SiteTree->Link()` and `Controller->Link()` methods, -this is automatically the case for `DataObject` links, controller links and form actions. -Note that this behaviour applies for unversioned objects as well, since the views -these are presented in might still contain dependent objects that are versioned. - -You can opt for a session base stage setting through the `Versioned.use_session` setting. -Warning: This can lead to leaking of unpublished information, if a live URL is viewed in draft mode, -and the result is cached due to aggressive cache settings (not varying on cookie values). - -*app/src/MyObject.php* - -```php -get(MyObjectController::class)->Link($this->ID); - } - - public function CustomLink() - { - $link = Controller::join_links('custom-route', $this->ID, '?rand=' . rand()); - $this->extend('updateLink', $link); // updates $link by reference - return $link; - } - - public function LiveLink() - { - // Force live link even when current view is in draft mode - return Controller::join_links(Injector::inst()->get(MyObjectController::class)->Link($this->ID), '?stage=Live'); - } -} -``` - -*app/src/MyObjectController.php* - -```php -byID($request->param('ID')); - if (!$obj) { - return $this->httpError(404); - } - - // Construct view - $html = sprintf('%s', $obj->Link(), $obj->ID); - - return $html; - } - - public function Link($action = null) - { - // Construct link with graceful handling of GET parameters - $link = Controller::join_links('my-objects', $action); - - // Allow Versioned and other extension to update $link by reference. - // Calls VersionedStateExtension->updateLink(). - $this->extend('updateLink', $link, $action); - - return $link; - } -} -``` - -*app/_config/routes.yml* - -```yaml -SilverStripe\Control\Director: - rules: - 'my-objects/$ID': 'MyObjectController' -``` - -[alert] -The `choose_site_stage()` call only deals with setting the default stage, and doesn't check if the user is -authenticated to view it. As with any other controller logic, please use `DataObject->canView()` to determine -permissions, and avoid exposing unpublished content to your users. -[/alert] - -### Controlling permissions to versioned DataObjects - -By default, `Versioned` will come out of the box with security extensions which restrict the visibility of objects in Draft (stage) or Archive viewing mode. - -[alert] -As is standard practice, user code should always invoke `canView()` on any object before -rendering it. DataLists do not filter on `canView()` automatically, so this must be -done via user code. This can be achieved either by wrapping `<% if $canView %>;` in -your template, or by implementing your visibility check in PHP. -[/alert] - -#### Version specific _can_ methods - -Versioned DataObjects get additional permission check methods to verify what operation a Member is allowed to perform: -* `canPublish()` -* `canUnpublish()` -* `canArchive()` -* `canViewVersioned()`. - -These methods accept an optional Member argument. If not provided, they will assume you want to check the permission against the current Member. When performing a version operation on behalf of a Member, you'll probably want to use these methods to confirm they are authorised, - -```php -byID(99); -$member = Security::getCurrentUser(); -if ($record->canPublish($member)) { - $record->publishRecursive(); -} - -``` - -There's also a `canViewStage()` method which can be used to check if a Member can access a specific stage. - -```php -canViewStage(Versioned::LIVE, $member); - -// Check if `$member` can view the Stage version of $record. -$record->canViewStage(Versioned::DRAFT, $member); - -// Both parameters are optional. This is equivalent to calling the method with Versioned::LIVE and -// Security::getCurrentUser(); -$record->canViewStage(); -``` - -#### Customising permissions for a versioned DataObject - -`Versioned` object visibility can be customised in one of the following ways by editing your user code: - - * Override the `canViewVersioned` method in your code. Make sure that this returns true or - false if the user is not allowed to view this object in the current viewing mode. - * Override the `canView` method to override the method visibility completely. - -E.g. - -```php -getSourceQueryParam("Versioned.mode"); - $stage = $this->getSourceQueryParam("Versioned.stage"); - if ($mode === 'Stage' && $stage === 'Live') { - return true; - } - - // Only admins can view non-live objects - return Permission::checkMember($member, 'ADMIN'); - } -} -``` - -If you want to control permissions of an object in an extension, you can also use -one of the below extension points in your `DataExtension` subclass: - - * `canView` to update the visibility of the object's `canView` - * `canViewNonLive` to update the visibility of this object only in non-live mode. - -Note that unlike canViewVersioned, the canViewNonLive method will -only be invoked if the object is in a non-published state. - -E.g. - -```php -init()` method for this purpose, for example: - -**app/src/MyController.php** -```php -public function init() -{ - parent::init(); - Versioned::set_stage(Versioned::DRAFT); -} -``` - -### Low level write and publication methods - -Silverstripe CMS will usually call these low level methods for you. However if you have specialised needs, you may call them directly. - -To move a saved version from one stage to another, call [writeToStage(stage)](api:SilverStripe\Versioned\Versioned::writeToStage()) on the object. This is used internally to publish DataObjects. - -`copyVersionToStage($versionID, $stage)` allows you to restore a previous version to a specific stage. This is used internally when performing a rollback. - -The current stage is stored as global state on the `Versioned` object. It is usually modified by controllers, e.g. when a preview is initialized. But it can also be set and reset temporarily to force a specific operation to run on a certain stage. - -```php - 'Varchar', - ]; - - private static $extensions = [ - Versioned::class, - ]; -} -``` - -### Configure frontend asset building - -If you haven't already configured frontend asset building for your project, you will need to configure some basic -packages to be built via webpack in order to enable history viewer functionality. If you have this configured for -your project already, ensure you have the `react-apollo` and `graphql-tag` libraries in your `package.json` -requirements, and skip this section. - -You can configure your directory structure like so: - -**package.json** - -```json -{ - "name": "my-project", - "scripts": { - "build": "yarn && NODE_ENV=production webpack -p --bail --progress", - "watch": "yarn && NODE_ENV=development webpack --watch --progress" - }, - "dependencies": { - "react-apollo": "^0.7.1", - "graphql-tag": "^0.1.17" - }, - "devDependencies": { - "@silverstripe/webpack-config": "^0.4.1", - "webpack": "^2.6.1" - }, - "jest": { - "roots": [ - "client/src" - ], - "moduleDirectories": [ - "app/client/src", - "node_modules", - "node_modules/@silverstripe/webpack-config/node_modules", - "vendor/silverstripe/admin/client/src", - "vendor/silverstripe/admin/node_modules" - ] - }, - "babel": { - "presets": [ - "env", - "react" - ], - "plugins": [ - "transform-object-rest-spread" - ] - }, - "engines": { - "node": "^6.x" - } -} -``` - -**webpack.config.js** - -```json -const Path = require('path'); -// Import the core config -const webpackConfig = require('@silverstripe/webpack-config'); -const { - resolveJS, - externalJS, - moduleJS, - pluginJS, -} = webpackConfig; - -const ENV = process.env.NODE_ENV; -const PATHS = { - MODULES: 'node_modules', - FILES_PATH: '../', - ROOT: Path.resolve(), - SRC: Path.resolve('app/client/src'), - DIST: Path.resolve('app/client/dist'), -}; - -const config = [ - { - name: 'js', - entry: { - bundle: `${PATHS.SRC}/boot/index.js`, - }, - output: { - path: PATHS.DIST, - filename: 'js/[name].js', - }, - devtool: (ENV !== 'production') ? 'source-map' : '', - resolve: resolveJS(ENV, PATHS), - externals: externalJS(ENV, PATHS), - module: moduleJS(ENV, PATHS), - plugins: pluginJS(ENV, PATHS), - } -]; - -// Use WEBPACK_CHILD=js or WEBPACK_CHILD=css env var to run a single config -module.exports = (process.env.WEBPACK_CHILD) - ? config.find((entry) => entry.name === process.env.WEBPACK_CHILD) - : module.exports = config; -``` - -**composer.json** - -```json -"extra": { - "expose": [ - "app/client/dist" - ] -} -``` - -**app/client/src/boot/index.js** - -```js -console.log('Hello world'); -``` - -**.eslintrc.js** - -```js -module.exports = require('@silverstripe/webpack-config/.eslintrc'); -``` - -At this stage, running `yarn build` should show you a linting warning for the console statement, and correctly build -`app/client/dist/js/bundle.js`. - -### Expose GraphQL scaffolding - -Only a minimal amount of data is required to be exposed via GraphQL scaffolding, and only to the "admin" GraphQL -schema. For more information, see [ReactJS, Redux and GraphQL](../../customising_the_admin_interface/react_redux_and_graphql). - -**app/_config/graphql.yml** - -```yaml -SilverStripe\GraphQL\Manager: - schemas: - admin: - scaffolding: - types: - MyVersionedObject: - fields: [ID, LastEdited] - operations: - readOne: true - rollback: true - SilverStripe\Security\Member: - fields: [ID, FirstName, Surname] - operations: - readOne: true -``` - -Once configured, flush your cache and explore the new GraphQL schema to ensure it loads correctly. You can use a GraphQL -application such as GraphiQL, or [silverstripe-graphql-devtools](https://github.com/silverstripe/silverstripe-graphql-devtools) -for a browser solution: - -``` -composer require --dev silverstripe/graphql-devtools dev-master -``` - -### Configure the necessary GraphQL queries and mutations - -The history viewer interface uses two main operations: - -* Read a list of versions for a DataObject -* Revert to an older version of a DataObject - -For this we need one query and one mutation: - -**app/client/src/state/readOneMyVersionedObjectQuery.js** - -```js -import { graphql } from 'react-apollo'; -import gql from 'graphql-tag'; - -// GraphQL query for retrieving the version history of a specific object. The results of -// the query must be set to the "versions" prop on the component that this HOC is -// applied to for binding implementation. -const query = gql` -query ReadHistoryViewerMyVersionedObject ($id: ID!, $limit: Int!, $offset: Int!) { - readOneMyVersionedObject( - Versioning: { - Mode: LATEST - }, - ID: $id - ) { - ID - Versions (limit: $limit, offset: $offset) { - pageInfo { - totalCount - } - edges { - node { - Version - Author { - FirstName - Surname - } - Publisher { - FirstName - Surname - } - Published - LiveVersion - LatestDraftVersion - LastEdited - } - } - } - } -} -`; - -const config = { - options({recordId, limit, page}) { - return { - variables: { - limit, - offset: ((page || 1) - 1) * limit, - id: recordId, - } - }; - }, - props( - { - data: { - error, - refetch, - readOneMyVersionedObject, - loading: networkLoading, - }, - ownProps: { - actions = { - versions: {} - }, - limit, - recordId, - }, - } - ) { - const versions = readOneMyVersionedObject || null; - - const errors = error && error.graphQLErrors && - error.graphQLErrors.map((graphQLError) => graphQLError.message); - - return { - loading: networkLoading || !versions, - versions, - graphQLErrors: errors, - actions: { - ...actions, - versions: { - ...versions, - goToPage(page) { - refetch({ - offset: ((page || 1) - 1) * limit, - limit, - id: recordId, - }); - } - }, - }, - }; - }, -}; - -export { query, config }; - -export default graphql(query, config); -``` - -**app/client/src/state/revertToMyVersionedObjectVersionMutation.js** - -```js -import { graphql } from 'react-apollo'; -import gql from 'graphql-tag'; - -const mutation = gql` -mutation revertMyVersionedObjectToVersion($id:ID!, $toVersion:Int!) { - rollbackMyVersionedObject( - ID: $id - ToVersion: $toVersion - ) { - ID - } -} -`; - -const config = { - props: ({ mutate, ownProps: { actions } }) => { - const revertToVersion = (id, toVersion) => mutate({ - variables: { - id, - toVersion, - }, - }); - - return { - actions: { - ...actions, - revertToVersion, - }, - }; - }, - options: { - // Refetch versions after mutation is completed - refetchQueries: ['ReadHistoryViewerMyVersionedObject'] - } -}; - -export { mutation, config }; - -export default graphql(mutation, config); -```` - -### Register your GraphQL query and mutation with Injector - -Once your GraphQL query and mutation are created, you will need to tell the JavaScript Injector about them. -This does two things: - -* Allow them to be loaded by core components. -* Allow Injector to provide them in certain contexts. They should be available for `MyVersionedObject` history viewer - instances, but not for CMS pages for example. - -**app/client/src/boot/index.js** - -```js -/* global window */ -import Injector from 'lib/Injector'; -import readOneMyVersionedObjectQuery from 'state/readOneMyVersionedObjectQuery'; -import revertToMyVersionedObjectVersionMutation from 'state/revertToMyVersionedObjectVersionMutation'; - -window.document.addEventListener('DOMContentLoaded', () => { - // Register GraphQL operations with Injector as transformations - Injector.transform( - 'myversionedobject-history', (updater) => { - updater.component( - 'HistoryViewer.Form_ItemEditForm', - readOneMyVersionedObjectQuery, 'ElementHistoryViewer'); - } - ); - - Injector.transform( - 'myversionedobject-history-revert', (updater) => { - updater.component( - 'HistoryViewerToolbar.VersionedAdmin.HistoryViewer.MyVersionedObject.HistoryViewerVersionDetail', - revertToMyVersionedObjectVersionMutation, - 'MyVersionedObjectRevertMutation' - ); - } - ); -}); -``` -[hint] -GraphQL scaffolding derives the names for the schema from the first part of the namespace and the classname, while in -the Injector the database table name is used. So in the case of -```php -namespace Foo\Bar; -// … -class MyVersionedObject extends DataObject -{ - private static $table_name = 'MyTableName'; - // … -} -``` -you would have to use `readOneFooMyVersionedObject`, `rollbackFooMyVersionedObject` and -`HistoryViewerToolbar.VersionedAdmin.HistoryViewer.MyTableName.HistoryViewerVersionDetail` respectively -[/hint] - - -For more information, see [ReactJS, Redux and GraphQL](../../customising_the_admin_interface/react_redux_and_graphql). - -### Adding the HistoryViewerField - -Firstly, ensure your JavaScript bundle is included throughout the CMS: - -```yml ---- -Name: CustomAdmin -After: - - 'versionedadmincmsconfig' - - 'versionededitform' - - 'cmsscripts' - - 'elemental' # Only needed if silverstripe-elemental is installed ---- -SilverStripe\Admin\LeftAndMain: - extra_requirements_javascript: - - app/client/dist/js/bundle.js - -``` - -Then you can add the [HistoryViewerField](api:SilverStripe\VersionedAdmin\Forms\HistoryViewerField) to your object's CMS -fields in the same way as any other form field: - -```php -use SilverStripe\VersionedAdmin\Forms\HistoryViewerField; - -public function getCMSFields() -{ - $fields = parent::getCMSFields(); - - $fields->addFieldToTab('Root.History', HistoryViewerField::create('MyObjectHistory')); - - return $fields; -} -``` - -### Previewable DataObjects - -The history viewer will automatically detect and render a side-by-side preview panel for DataObjects that implement -[CMSPreviewable](api:SilverStripe\ORM\CMSPreviewable). Please note that if you are adding this functionality, you -will also need to expose the `AbsoluteLink` field in your GraphQL read scaffolding, and add it to the fields in -`readOneMyVersionedObjectQuery`. - -## API Documentation - -* [Versioned](api:SilverStripe\Versioned\Versioned) -* [HistoryViewerField](api:SilverStripe\VersionedAdmin\Forms\HistoryViewerField) diff --git a/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md b/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md deleted file mode 100644 index f4f768b2051..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md +++ /dev/null @@ -1,323 +0,0 @@ ---- -title: Building Model and Search Interfaces around Scaffolding -summary: A Model-driven approach to defining your application UI. -icon: hammer ---- - -# Scaffolding - -The ORM already has a lot of information about the data represented by a `DataObject` through its `$db` property, so -Silverstripe CMS will use that information to scaffold some interfaces. This is done though [FormScaffolder](api:SilverStripe\Forms\FormScaffolder) -to provide reasonable defaults based on the property type (e.g. a checkbox field for booleans). You can then further -customise those fields as required. - -## Form Fields - -An example is `DataObject`, Silverstripe CMS will automatically create your CMS interface so you can modify what you need. - -```php -use SilverStripe\ORM\DataObject; - -class MyDataObject extends DataObject -{ - - private static $db = [ - 'IsActive' => 'Boolean', - 'Title' => 'Varchar', - 'Content' => 'Text' - ]; - - public function getCMSFields() - { - // parent::getCMSFields() does all the hard work and creates the fields for Title, IsActive and Content. - $fields = parent::getCMSFields(); - $fields->dataFieldByName('IsActive')->setTitle('Is active?'); - - return $fields; - } -} -``` - -To fully customise your form fields, start with an empty FieldList. - -```php -public function getCMSFields() -{ - $fields = FieldList::create( - TabSet::create("Root", - Tab::create("Main", - CheckboxSetField::create('IsActive','Is active?'), - TextField::create('Title'), - TextareaField::create('Content') - ->setRows(5) - ) - ) - ); - - return $fields; -} -``` - -You can also alter the fields of built-in and module `DataObject` classes through your own -[DataExtension](/developer_guides/extending/extensions), and a call to `DataExtension->updateCMSFields`. - -[info] -`FormField` scaffolding takes [`$field_labels` config](#field-labels) into account as well. -[/info] - -## Searchable Fields - -The `$searchable_fields` property uses a mixed array format that can be used to further customise your generated admin -system. The default is a set of array values listing the fields. - -[info] -`$searchable_fields` will default to use the [`$summary_fields` config](#summary-fields) if not defined. This works fine unless -your `$summary_fields` config specifies fields that are not stored in the database. -[/info] - -```php -use SilverStripe\ORM\DataObject; - -class MyDataObject extends DataObject -{ - - private static $searchable_fields = [ - 'Name', - 'ProductCode' - ]; -} -``` - -### Specify a form field or search filter - -Searchable fields will appear in the search interface with a default form field (usually a [TextField](api:SilverStripe\Forms\TextField)) and a -default search filter assigned (usually an [ExactMatchFilter](api:SilverStripe\ORM\Filters\ExactMatchFilter)). To override these defaults, you can specify -additional information on `$searchable_fields`: - -```php -use SilverStripe\ORM\DataObject; - -class MyDataObject extends DataObject -{ - - private static $searchable_fields = [ - 'Name' => 'PartialMatchFilter', - 'ProductCode' => NumericField::class - ]; -} -``` - -If you assign a single string value, you can set it to be either a [FormField](api:SilverStripe\Forms\FormField) or [SearchFilter](api:SilverStripe\ORM\Filters\SearchFilter). To specify -both, you can assign an array: - -```php -use SilverStripe\ORM\DataObject; - -class MyDataObject extends DataObject -{ - - private static $searchable_fields = [ - 'Name' => [ - 'field' => TextField::class, - 'filter' => 'PartialMatchFilter', - ], - 'ProductCode' => [ - 'title' => 'Product code #', - 'field' => NumericField::class, - 'filter' => 'PartialMatchFilter', - ], - ]; -} -``` - -### Searching on relations - -To include relations (`$has_one`, `$has_many` and `$many_many`) in your search, you can use a dot-notation. - -```php -use SilverStripe\ORM\DataObject; - -class Team extends DataObject -{ - private static $db = [ - 'Title' => 'Varchar' - ]; - - private static $many_many = [ - 'Players' => 'Player' - ]; - - private static $searchable_fields = [ - 'Title', - 'Players.Name', - ]; -} - -class Player extends DataObject -{ - private static $db = [ - 'Name' => 'Varchar', - 'Birthday' => 'Date', - ]; - - private static $belongs_many_many = [ - 'Teams' => 'Team' - ]; -} - -``` - -### Searching many db fields on a single search field - -Use a single search field that matches on multiple database fields with `'match_any'`. This also supports specifying a field and a filter, though it is not necessary to do so. - -```php -class Order extends DataObject -{ - private static $db = [ - 'Name' => 'Varchar', - ]; - - private static $has_one = [ - 'Customer' => Customer::class, - 'ShippingAddress' => Address::class, - ]; - - private static $searchable_fields = [ - 'CustomName' => [ - 'title' => 'First Name', - 'field' => TextField::class, - 'match_any' => [ - // Searching with the "First Name" field will show Orders matching either Name, Customer.FirstName, or ShippingAddress.FirstName - 'Name', - 'Customer.FirstName', - 'ShippingAddress.FirstName', - ] - ] - ]; -} -``` - -[alert] -If you don't specify a field, you must use the name of a real database field instead of a custom name so that a default field can be determined. -[/alert] - -## Summary Fields - -Summary fields can be used to show a quick overview of the data for a specific [DataObject](api:SilverStripe\ORM\DataObject) record. The most common use -is their display as table columns, e.g. in the search results of a [ModelAdmin](api:SilverStripe\Admin\ModelAdmin) CMS interface. - -```php -use SilverStripe\ORM\DataObject; - -class MyDataObject extends DataObject -{ - private static $db = [ - 'Name' => 'Text', - 'OtherProperty' => 'Text', - 'ProductCode' => 'Int', - ]; - - private static $summary_fields = [ - 'Name', - 'ProductCode', - ]; -} -``` - -### Relations in summary fields - -To include relations or field manipulations in your summaries, you can use a dot-notation. - -```php -use SilverStripe\ORM\DataObject; - -class OtherObject extends DataObject -{ - private static $db = [ - 'Title' => 'Varchar', - ]; -} - -class MyDataObject extends DataObject -{ - private static $db = [ - 'Name' => 'Text', - 'Description' => 'HTMLText', - ]; - - private static $has_one = [ - 'OtherObject' => 'OtherObject', - ]; - - private static $summary_fields = [ - 'Name' => 'Name', - 'Description.Summary' => 'Description (summary)', - 'OtherObject.Title' => 'Other Object Title', - ]; -} - -``` - -### Images in summary fields - -Non-textual elements (such as images and their manipulations) can also be used in summaries. - -```php -use SilverStripe\ORM\DataObject; - -class MyDataObject extends DataObject -{ - private static $db = [ - 'Name' => 'Text', - ]; - - private static $has_one = [ - 'HeroImage' => 'Image', - ]; - - private static $summary_fields = [ - 'Name' => 'Name', - 'HeroImage.CMSThumbnail' => 'Hero Image', - ]; -} - -``` - -## Field labels - -In order to re-label any summary fields, you can use the `$field_labels` static. This will also affect the output of `$object->fieldLabels()` and `$object->fieldLabel()`. - -```php -use SilverStripe\ORM\DataObject; - -class MyDataObject extends DataObject -{ - private static $db = [ - 'Name' => 'Text', - ]; - - private static $has_one = [ - 'HeroImage' => 'Image', - ]; - - private static $summary_fields = [ - 'Name', - 'HeroImage.CMSThumbnail', - ]; - - private static $field_labels = [ - 'Name' => 'Name', - 'HeroImage.CMSThumbnail' => 'Hero', - ]; -} -``` - -## Related Documentation - -* [SearchFilters](searchfilters) - -## API Documentation - -* [FormScaffolder](api:SilverStripe\Forms\FormScaffolder) -* [DataObject](api:SilverStripe\ORM\DataObject) diff --git a/docs/en/02_Developer_Guides/00_Model/12_Indexes.md b/docs/en/02_Developer_Guides/00_Model/12_Indexes.md deleted file mode 100644 index 5a79c971254..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/12_Indexes.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -title: Indexes -summary: Add Indexes to your Data Model to optimize database queries. -icon: database ---- - -# Indexes -Indexes are a great way to improve performance in your application, especially as it grows. By adding indexes to your -data model you can reduce the time taken for the framework to find and filter data objects. - -The addition of an indexes should be carefully evaluated as they can also increase the cost of other operations such as -`UPDATE`/`INSERT` and `DELETE`. An index on a column whose data is non unique will actually cost you performance. -E.g. In most cases an index on `boolean` status flag, or `ENUM` state will not increase query performance. - -It's important to find the right balance to achieve fast queries using the optimal set of indexes; For Silverstripe CMS -applications it's a good practice to: -- add indexes on columns which are frequently used in `filter`, `where` or `orderBy` statements -- for these, only include indexes for columns which are the most restrictive (return the least number of rows) - -The Silverstripe CMS framework already places certain indexes for you by default: -- The primary key for each model has a `PRIMARY KEY` unique index -- The `ClassName` column if your model inherits from `DataObject` -- All relationships defined in the model have indexes for their `has_one` entity (for `many_many` relationships -this index is present on the associative entity). - -## Defining an index -Indexes are represented on a `DataObject` through the `DataObject::$indexes` array which maps index names to a -descriptor. There are several supported notations: - -```php -use SilverStripe\ORM\DataObject; - -class MyObject extends DataObject -{ - private static $indexes = [ - '' => true, - '' => [ - 'type' => '', - 'columns' => ['', ''], - ], - '' => ['', ''], - ]; -} -``` - -The `` is used to put a standard non-unique index on the column specified. For complex or large tables -we recommend building the index to suite the requirements of your data. - -The `` can be an arbitrary identifier in order to allow for more than one index on a specific database -column. The "advanced" notation supports more `` notations. These vary between database drivers, but all of them -support the following: - - * `index`: Standard non unique index. - * `unique`: Index plus uniqueness constraint on the value - * `fulltext`: Fulltext content index - -**app/src/MyTestObject.php** - -```php -use SilverStripe\ORM\DataObject; - -class MyTestObject extends DataObject -{ - private static $db = [ - 'MyField' => 'Varchar', - 'MyOtherField' => 'Varchar', - ]; - - private static $indexes = [ - 'MyIndexName' => ['MyField', 'MyOtherField'], - ]; -} -``` - -[alert] -Please note that if you have previously used the removed `value` key to define an index's contents, Silverstripe CMS will -now throw an error. Use `columns` instead. -[/alert] - -## Complex/Composite Indexes -For complex queries it may be necessary to define a complex or composite index on the supporting object. To create a -composite index, define the fields in the index order as a comma separated list. - -*Note* Most databases only use the leftmost prefix to optimise the query, try to ensure the order of the index and your -query parameters are the same. e.g. -- index (col1) - `WHERE col1 = ?` -- index (col1, col2) = `WHERE (col1 = ? AND col2 = ?)` -- index (col1, col2, col3) = `WHERE (col1 = ? AND col2 = ? AND col3 = ?)` - -The index would not be used for a query `WHERE col2 = ?` or for `WHERE col1 = ? OR col2 = ?` - -As an alternative to a composite index, you can also create a hashed column which is a combination of information from -other columns. If this is indexed, smaller and reasonably unique it might be faster that an index on the whole column. - -## Index Creation/Destruction -Indexes are generated and removed automatically during a `dev/build`. Caution if you're working with large tables and -modify an index as the next `dev/build` will `DROP` the index, and then `ADD` it. - -As of 3.7.0 `default_sort` fields will automatically become database indexes as this provides significant performance -benefits. - -## API Documentation - -* [DataObject](api:SilverStripe\ORM\DataObject) diff --git a/docs/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md b/docs/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md deleted file mode 100644 index e0b8b572ef9..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: Dynamic Default Fields -summary: Learn how to add default values to your models ---- - -# Default Values and Records - -## Static Default Values -The [DataObject::$defaults](api:SilverStripe\ORM\DataObject::$defaults) array allows you to specify simple static values to be the default values when a record is created. - -A simple example is if you have a dog and by default its bark is "Woof": -```php -use SilverStripe\ORM\DataObject; - -class Dog extends DataObject -{ - private static $db = [ - 'Bark' => 'Varchar(10)', - ]; - - private static $defaults = [ - 'Bark' => 'Woof', - ]; -} -``` - -## Dynamic Default Values - -In many situations default values need to be dynamically calculated. In order to do this, the -[DataObject::populateDefaults()](api:SilverStripe\ORM\DataObject::populateDefaults()) method will need to be overloaded. - -This method is called whenever a new record is instantiated, and you must be sure to call the method on the parent -object! - -A simple example is to set a field to the current date and time: - -```php -/** - * Sets the Date field to the current date. - */ -public function populateDefaults() -{ - $this->Date = date('Y-m-d'); - parent::populateDefaults(); -} -``` - -It's also possible to get the data from any other source, or another object, just by using the usual data retrieval -methods. For example: - -```php -/** - * This method combines the Title of the parent object with the Title of this - * object in the FullTitle field. - */ -public function populateDefaults() -{ - if($parent = $this->Parent()) { - $this->FullTitle = $parent->Title . ': ' . $this->Title; - } else { - $this->FullTitle = $this->Title; - } - parent::populateDefaults(); -} -``` - -## Static Default Records -The [DataObject::$default_records](api:SilverStripe\ORM\DataObject::$default_records) array allows you to specify default records created on dev/build. - -A simple example of this is having a region model and wanting a list of regions created when the site is built: -```php -use SilverStripe\ORM\DataObject; - -class Region extends DataObject -{ - private static $db = [ - 'Title' => 'Varchar(45)', - ]; - - private static $default_records = [ - ['Title' => 'Auckland'], - ['Title' => 'Coromandel'], - ['Title' => 'Waikato'], - ]; -} -``` diff --git a/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md b/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md deleted file mode 100644 index b47dbb0ad3b..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md +++ /dev/null @@ -1,163 +0,0 @@ ---- -title: Grouping DataObject sets -summary: Learn how to split the results of a query into subgroups ---- - -# Grouping lists of records - -The [SS_List](api:SilverStripe\ORM\SS_List) class is designed to return a flat list of records. -These lists can get quite long, and hard to present on a single list. -[Pagination](/developer_guides/templates/how_tos/pagination) is one way to solve this problem, -by splitting up the list into multiple pages. - -In this howto, we present an alternative to pagination: -Grouping a list by various criteria, through the [GroupedList](api:SilverStripe\ORM\GroupedList) class. -This class is a [ListDecorator](api:SilverStripe\ORM\ListDecorator), which means it wraps around a list, -adding new functionality. - -It provides a `groupBy()` method, which takes a field name, and breaks up the managed list -into a number of arrays, where each array contains only objects with the same value of that field. -Similarly, the `GroupedBy()` method builds on this and returns the same data in a template-friendly format. - -## Grouping Sets By First Letter - -This example deals with breaking up a [SS_List](api:SilverStripe\ORM\SS_List) into sub-headings by the first letter. - -Let's say you have a set of Module objects, each representing a Silverstripe CMS module, and you want to output a list of -these in alphabetical order, with each letter as a heading; something like the following list: - - * B - * Blog - * C - * CMS Workflow - * Custom Translations - * D - * Database Plumber - * ... - -The first step is to set up the basic data model, -along with a method that returns the first letter of the title. This -will be used both for grouping and for the title in the template. - -```php -use SilverStripe\ORM\DataObject; - -class Module extends DataObject -{ - private static $db = [ - 'Title' => 'Text' - ]; - - /** - * Returns the first letter of the module title, used for grouping. - * @return string - */ - public function getTitleFirstLetter() - { - return $this->Title[0]; - } -} -``` - -The next step is to create a method or variable that will contain/return all the objects, -sorted by title. For this example this will be a method on the `Page` class. - -```php -use SilverStripe\CMS\Model\SiteTree; -use SilverStripe\ORM\GroupedList; - -class Page extends SiteTree -{ - /** - * Returns all modules, sorted by their title. - * @return GroupedList - */ - public function getGroupedModules() - { - return GroupedList::create(Module::get()->sort('Title')); - } -} -``` - -The final step is to render this into a template. The `GroupedBy()` method breaks up the set into -a number of sets, grouped by the field that is passed as the parameter. -In this case, the `getTitleFirstLetter()` method defined earlier is used to break them up. - -```ss -<%-- Modules list grouped by TitleFirstLetter --%> -

Modules

-<% loop $GroupedModules.GroupedBy(TitleFirstLetter) %> -

$TitleFirstLetter

-
    - <% loop $Children %> -
  • $Title
  • - <% end_loop %> -
-<% end_loop %> -``` - -## Grouping Sets By Month - -Grouping a set by month is a very similar process. -The only difference would be to sort the records by month name, and -then create a method on the DataObject that returns the month name, -and pass that to the [GroupedList::GroupedBy()](api:SilverStripe\ORM\GroupedList::GroupedBy()) call. - -We're reusing our example `Module` object, -but grouping by its built-in `Created` property instead, -which is automatically set when the record is first written to the database. -This will have a method which returns the month it was posted in: - -```php -use SilverStripe\ORM\DataObject; - -class Module extends DataObject -{ - /** - * Returns the month name this news item was posted in. - * @return string - */ - public function getMonthCreated() - { - return date('F', strtotime($this->Created)); - } -} -``` - -The next step is to create a method that will return all records that exist, -sorted by month name from January to December. This can be accomplshed by sorting by the `Created` field: - -```php -use SilverStripe\CMS\Model\SiteTree; -use SilverStripe\ORM\GroupedList; - -class Page extends SiteTree -{ - /** - * Returns all news items, sorted by the month they were posted - * @return GroupedList - */ - public function getGroupedModulesByDate() - { - return GroupedList::create(Module::get()->sort('Created')); - } -} -``` -The final step is to render this into the template using the [GroupedList::GroupedBy()](api:SilverStripe\ORM\GroupedList::GroupedBy()) method. - -```ss -// Modules list grouped by the Month Posted -

Modules

-<% loop $GroupedModulesByDate.GroupedBy(MonthCreated) %> -

$MonthCreated

-
    - <% loop $Children %> -
  • $Title ($Created.Nice)
  • - <% end_loop %> -
-<% end_loop %> -``` - -## Related - - * [Howto: "Pagination"](/developer_guides/templates/how_tos/pagination) diff --git a/docs/en/02_Developer_Guides/00_Model/How_Tos/index.md b/docs/en/02_Developer_Guides/00_Model/How_Tos/index.md deleted file mode 100644 index 29eb663e527..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/How_Tos/index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: How To's ---- -# How To's: Model and Databases - -[CHILDREN] \ No newline at end of file diff --git a/docs/en/02_Developer_Guides/00_Model/index.md b/docs/en/02_Developer_Guides/00_Model/index.md deleted file mode 100644 index e828140f819..00000000000 --- a/docs/en/02_Developer_Guides/00_Model/index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Model and Databases -summary: Learn how Silverstripe CMS manages database tables, ways to query your database and how to publish data. -introduction: This guide will cover how to create and manipulate data within Silverstripe CMS and how to use the ORM (Object Relational Model) to query data. -icon: database ---- - -In Silverstripe CMS, application data will be represented by a [DataObject](api:SilverStripe\ORM\DataObject) class. A `DataObject` subclass defines the -data columns, relationships and properties of a particular data record. For example, [Member](api:SilverStripe\Security\Member) is a `DataObject` -which stores information about a person, CMS user or mail subscriber. - -[CHILDREN Exclude="How_tos"] - -## How to's - -[CHILDREN Folder="How_Tos"] diff --git a/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md b/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md deleted file mode 100644 index a811df6e971..00000000000 --- a/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md +++ /dev/null @@ -1,590 +0,0 @@ ---- -title: Template Syntax -summary: A look at the operations, variables and language controls you can use within templates. -icon: code ---- - -# Template Syntax - -A template can contain any markup language (e.g HTML, CSV, JSON..) and before being rendered to the user, they're -processed through [SSViewer](api:SilverStripe\View\SSViewer). This process replaces placeholders such as `$Var` with real content from your -[model](../model) and allows you to define logic controls like `<% if $Var %>`. - -An example of a Silverstripe CMS template is below: - -**app/templates/Page.ss** - -```ss - - - <% base_tag %> - $Title - <% require themedCSS("screen") %> - - -
-

Bob's Chicken Shack

-
- - <% with $CurrentMember %> -

Welcome $FirstName $Surname.

- <% end_with %> - - <% if $Dishes %> -
    - <% loop $Dishes %> -
  • $Title ($Price.Nice)
  • - <% end_loop %> -
- <% end_if %> - - <% include Footer %> - - -``` - -[note] -Templates can be used for more than HTML output. You can use them to output your data as JSON, XML, CSV or any other -text-based format. -[/note] - -## Template file location - -Silverstripe CMS templates are plain text files that have an `.ss` extension and are located within the `templates` directory of -a module, theme, or your `app/` folder. - -By default, templates will have the same name as the class they are used to render. So, your `Page` class will -be rendered with the `templates/Page.ss` template. - -When the class has a namespace, the namespace will be interpreted as a subfolder within the `templates` path. -For example, the class `SilverStripe\Control\Controller` will be rendered with the -`templates/SilverStripe/Control/Controller.ss` template. - -If you are using template "types" like `Layout` or `Includes`, these are just folders which you need -to append to your template file location. One exception is the `<% include %>` template tag, -where you need to leave out the `Includes/` folder. - -## Variables - -Variables are placeholders that will be replaced with data from the [DataModel](../model/) or the current -[Controller](../controllers). Variables are prefixed with a `$` character. Variable names must start with an -alphabetic character or underscore, with subsequent characters being alphanumeric or underscore: - -```ss -$Title -``` - -This inserts the value of the Title database field of the page being displayed in place of `$Title`. - -Variables can be chained together, and include arguments. - -```ss -$Foo -$Foo(param) -$Foo.Bar -``` - -These variables will call a method / field on the object and insert the returned value as a string into the template. - -* `$Foo` will call `$obj->Foo()` (or the field `$obj->Foo`) -* `$Foo(param)` will call `$obj->Foo("param")` -* `$Foo.Bar` will call `$obj->Foo()->Bar()` - -If a variable returns a string, that string will be inserted into the template. If the variable returns an object, then -the system will attempt to render the object through its `forTemplate()` method. If the `forTemplate()` method has not -been defined, the system will return an error. - -[notice] -If you wish to pass parameters to getter functions, you must use the full method name, e.g. $getThing('param'). Also, parameters must be literals, and cannot be other template variables (`$getThing($variable)` will not work) -[/notice] - -[note] -For more detail around how variables are inserted and formatted into a template see -[Formatting, Modifying and Casting Variables](casting) -[/note] - -Variables can come from your database fields, or custom methods you define on your objects. - -**app/src/Page.php** - -```php -public function UsersIpAddress() -{ - return $this->getRequest()->getIP(); -} -``` - -**app/src/Page.ss** - -```html -

You are coming from $UsersIpAddress.

-``` - -[note] - Method names that begin with `get` will automatically be resolved when their prefix is excluded. For example, the above method call `$UsersIpAddress` would also invoke a method named `getUsersIpAddress()`. -[/note] - -The variables that can be used in a template vary based on the object currently in [scope](#scope). Scope defines what -object the methods get called on. For the standard `Page.ss` template the scope is the current [PageController](api:SilverStripe\CMS\Controllers\ContentController\PageController) -class. This object gives you access to all the database fields on [PageController](api:SilverStripe\CMS\Model\SiteTree\PageController), its corresponding [Page](api:SilverStripe\CMS\Model\SiteTree\Page) -record and any subclasses of those two. - -**app/src/Layout/Page.ss** - -```ss -$Title -// returns the page `Title` property - -$Content -// returns the page `Content` property -``` - -## Conditional Logic - -The simplest conditional block is to check for the presence of a value (does not equal 0, null, false). - -```ss -<% if $CurrentMember %> -

You are logged in as $CurrentMember.FirstName $CurrentMember.Surname.

-<% end_if %> -``` - -A conditional can also check for a value other than falsy. - -```ss -<% if $MyDinner == "kipper" %> - Yummy, kipper for tea. -<% end_if %> -``` - -[notice] -When inside template tags variables should have a '$' prefix, and literals should have quotes. -[/notice] - -Conditionals can also provide the `else` case. - -```ss -<% if $MyDinner == "kipper" %> - Yummy, kipper for tea -<% else %> - I wish I could have kipper :-( -<% end_if %> -``` - -`else_if` commands can be used to handle multiple `if` statements. - -```ss -<% if $MyDinner == "quiche" %> - I don't like quiche -<% else_if $MyDinner == $YourDinner %> - We both have good taste -<% else %> - Can I have some of your chips? -<% end_if %> -``` - -### Negation - -You can check if a variable is false with `<% if not %>`. - -```ss -<% if not $DinnerInOven %> - I'm going out for dinner tonight. -<% end_if %> -``` - -Note that you cannot combine this with other operators such as `==`. - - -For more nuanced check you can use the `!` operator. - -```ss -<% if $MyDinner != "quiche" %> - Lets go out -<% end_if %> -``` - -### Boolean Logic - -Multiple checks can be done using `||`, `or`, `&&` or `and`. - -If *either* of the conditions is true. - -```ss -<% if $MyDinner == "kipper" || $MyDinner == "salmon" %> - yummy, fish for tea -<% end_if %> -``` - -If *both* of the conditions are true. - -```ss -<% if $MyDinner == "quiche" && $YourDinner == "kipper" %> - Lets swap dinners -<% end_if %> -``` - -### Inequalities - -You can use inequalities like `<`, `<=`, `>`, `>=` to compare numbers. - -```ss -<% if $Number >= "5" && $Number <= "10" %> - Number between 5 and 10 -<% end_if %> -``` - -## Includes - -Within Silverstripe CMS templates we have the ability to include other templates using the `<% include %>` tag. The includes -will be searched for using the same filename look-up rules as a regular template. However in the case of the include tag -an additional `Includes` directory will be inserted into the resolved path just prior to the filename. - -```ss -<% include SideBar %> -<% include MyNamespace/SideBar %> -``` - -When using subfolders in your template structure -(e.g. to fit with namespaces in your PHP class structure), the `Includes/` folder needs to be innermost. - -```ss -<% include MyNamespace/SideBar %> -``` - -The `include` tag can be particularly helpful for nested functionality and breaking large templates up. In this example, -the include only happens if the user is logged in. - -```ss -<% if $CurrentMember %> - <% include MembersOnlyInclude %> -<% end_if %> -``` - -Includes can't directly access the parent scope when the include is included. However you can pass arguments to the -include. - -```ss -<% with $CurrentMember %> - <% include MemberDetails Top=$Top, Name=$Name %> -<% end_with %> -``` - -## Looping Over Lists - -The `<% loop %>` tag is used to iterate or loop over a collection of items such as [DataList](api:SilverStripe\ORM\DataList) or an [ArrayList](api:SilverStripe\ORM\ArrayList) -collection. - -```ss -

Children of $Title

-
    - <% loop $Children %> -
  • $Title
  • - <% end_loop %> -
-``` - -This snippet loops over the children of a page, and generates an unordered list showing the `Title` property from each -page. - -[notice] -The `$Title` inside the loop refers to the Title property on each object that is looped over, not the current page like -the reference of `$Title` outside the loop. - -This demonstrates the concept of [Scope](#scope). When inside a <% loop %> the scope of the template has changed to the -object that is being looped over. -[/notice] - -### Altering the list - -`<% loop %>` statements iterate over a [DataList](api:SilverStripe\ORM\DataList) instance. As the template has access to the list object, -templates can call [DataList](api:SilverStripe\ORM\DataList) methods. - -Sorting the list by a given field. - -```ss -
    - <% loop $Children.Sort(Title, ASC) %> -
  • $Title
  • - <% end_loop %> -
-``` - -Limiting the number of items displayed. - -```ss -
    - <% loop $Children.Limit(10) %> -
  • $Title
  • - <% end_loop %> -
-``` - -Reversing the loop. - -```ss -
    - <% loop $Children.Reverse %> -
  • $Title
  • - <% end_loop %> -
-``` - -Filtering the loop. - -```ss -
    - <% loop $Children.Filter('School', 'College') %> -
  • $Title
  • - <% end_loop %> -
-``` - -Methods can also be chained. - -```ss -
    - <% loop $Children.Filter('School', 'College').Sort(Score, DESC) %> -
  • $Title
  • - <% end_loop %> -
-``` - -### Position Indicators - -Inside the loop scope, there are many variables at your disposal to determine the current position in the list and -iteration. - - * `$Even`, `$Odd`: Returns boolean, handy for zebra striping. - * `$EvenOdd`: Returns a string, either 'even' or 'odd'. Useful for CSS classes. - * `$First`, `$Last`, `$Middle`: Booleans about the position in the list. - * Note: as of CMS 4.7.0 `$IsFirst` and `$IsLast` will be preferred. The original - syntax will continue to work, but will be deprecated in a future release. - * `$FirstLast`: Returns a string, "first", "last", "first last" (if both), or "". Useful for CSS classes. - * `$Pos`: The current position in the list (integer). - Will start at 1, but can take a starting index as a parameter. - * `$FromEnd`: The position of the item from the end (integer). - Last item defaults to 1, but can be passed as a parameter. - * `$TotalItems`: Number of items in the list (integer). - -```ss -
    - <% loop $Children.Reverse %> - <% if First %> -
  • My Favourite
  • - <% end_if %> - -
  • Child $Pos of $TotalItems - $Title
  • - <% end_loop %> -
-``` - -[info] -A common task is to paginate your lists. See the [Pagination](how_tos/pagination) how to for a tutorial on adding -pagination. -[/info] - -### Modulus and MultipleOf - -`$Modulus` and `$MultipleOf` can help to build column and grid layouts. - -```ss -// returns an int -$Modulus(value, offset) - -// returns a boolean. -$MultipleOf(factor, offset) - -<% loop $Children %> -
- ... -
-<% end_loop %> - -// returns
,
, -``` - -[hint] -`$Modulus` is useful for floated grid CSS layouts. If you want 3 rows across, put $Modulus(3) as a class and add a -`clear: both` to `.column-1`. -[/hint] - -`$MultipleOf(value, offset)` can also be utilized to build column and grid layouts. In this case we want to add a `
` -after every 3rd item. - -```ss -<% loop $Children %> - <% if $MultipleOf(3) %> -
- <% end_if %> -<% end_loop %> -``` - -### Escaping - -Sometimes you will have template tags which need to roll into one another. Use `{}` to contain variables. - -```ss -$Foopx // will returns "" (as it looks for a `Foopx` value) -{$Foo}px // returns "3px" (CORRECT) -``` - -Or when having a `$` sign in front of the variable such as displaying money. - -```ss -$$Foo // returns "" -${$Foo} // returns "$3" -``` - -You can also use a backslash to escape the name of the variable, such as: - -```ss -$Foo // returns "3" -\$Foo // returns "$Foo" -``` - -[hint] -For more information on formatting and casting variables see [Formatting, Modifying and Casting Variables](casting) -[/hint] - -## Scope - -In the `<% loop %>` section, we saw an example of two **scopes**. Outside the `<% loop %>...<% end_loop %>`, we were in -the scope of the top level `Page`. But inside the loop, we were in the scope of an item in the list (i.e the `Child`). - -The scope determines where the value comes from when you refer to a variable. Typically the outer scope of a `Page.ss` -layout template is the [PageController](api:SilverStripe\CMS\Controllers\ContentController\PageController) that is currently being rendered. - -When the scope is a `PageController` it will automatically also look up any methods in the corresponding `Page` data -record. In the case of `$Title` the flow looks like - - $Title --> [Looks up: Current PageController and parent classes] --> [Looks up: Current Page and parent classes] - -The list of variables you could use in your template is the total of all the methods in the current scope object, parent -classes of the current scope object, and any [Extension](api:SilverStripe\Core\Extension) instances you have. - -### Navigating Scope - -#### Up - -When in a particular scope, `$Up` takes the scope back to the previous level. - -```ss -

Children of '$Title'

- -<% loop $Children %> -

Page '$Title' is a child of '$Up.Title'

- - <% loop $Children %> -

Page '$Title' is a grandchild of '$Up.Up.Title'

- <% end_loop %> -<% end_loop %> -``` - -Given the following structure, it will output the text. -``` - My Page - | - +-+ Child 1 - | | - | +- Grandchild 1 - | - +-+ Child 2 - - Children of 'My Page' - - Page 'Child 1' is a child of 'My Page' - Page 'Grandchild 1' is a grandchild of 'My Page' - Page 'Child 2' is a child of 'MyPage' -``` -[notice] -Additional selectors implicitly change the scope so you need to put additional `$Up` to get what you expect. -[/notice] - -```ss -

Children of '$Title'

-<% loop $Children.Sort('Title').First %> - <%-- We have two additional selectors in the loop expression so... --%> -

Page '$Title' is a child of '$Up.Up.Up.Title'

-<% end_loop %> -``` - -#### Top - -While `$Up` provides us a way to go up one level of scope, `$Top` is a shortcut to jump to the top most scope of the -page. The previous example could be rewritten to use the following syntax. - -```ss -

Children of '$Title'

- -<% loop $Children %> -

Page '$Title' is a child of '$Top.Title'

- - <% loop $Children %> -

Page '$Title' is a grandchild of '$Top.Title'

- <% end_loop %> -<% end_loop %> -``` - -### With - -The `<% with %>` tag lets you change into a new scope. Consider the following example: - -```ss -<% with $CurrentMember %> - Hello, $FirstName, welcome back. Your current balance is $Balance. -<% end_with %> -``` - -This is functionalty the same as the following: - -```ss -Hello, $CurrentMember.FirstName, welcome back. Your current balance is $CurrentMember.Balance -``` - -Notice that the first example is much tidier, as it removes the repeated use of the `$CurrentMember` accessor. - -Outside the `<% with %>.`, we are in the page scope. Inside it, we are in the scope of `$CurrentMember` object. We can -refer directly to properties and methods of the [Member](api:SilverStripe\Security\Member) object. `$FirstName` inside the scope is equivalent to -`$CurrentMember.FirstName`. - -### Me - -`$Me` outputs the current object in scope. This will call the `forTemplate` of the object. - -```ss -$Me -``` - -## Comments - -Using standard HTML comments is supported. These comments will be included in the published site. - -```ss -$EditForm -``` - -However you can also use special Silverstripe CMS comments which will be stripped out of the published site. This is useful -for adding notes for other developers but for things you don't want published in the public html. - -```ss -$EditForm <%-- Some hidden comment about the form --%> -``` - -## Related Lessons -* [Creating your first theme](https://www.silverstripe.org/learn/lessons/v4/creating-your-first-theme-1) - -## Related Documentation - -[CHILDREN Exclude="How_Tos"] - -## How to's - -[CHILDREN Folder="How_Tos"] - -## API Documentation - -* [SSViewer](api:SilverStripe\View\SSViewer) -* [ThemeManifest](api:SilverStripe\View\ThemeManifest) - - diff --git a/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md b/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md deleted file mode 100644 index d2747f91fa2..00000000000 --- a/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md +++ /dev/null @@ -1,422 +0,0 @@ ---- -title: Common Variables -summary: Some of the common variables and methods your templates can use, including Menu, SiteConfig, and more. ---- - -# Common Variables - -The page below describes a few of common variables and methods you'll see in a Silverstripe CMS template. This is not an -exhaustive list. From your template you can call any method, database field, or relation on the object which is -currently in scope as well as its subclasses or extensions. - -Knowing what methods you can call can be tricky, but the first step is to understand the scope you're in. Scope is -explained in more detail on the [syntax](syntax#scope) page. Many of the methods listed below can be called from any -scope, and you can specify additional static methods to be available globally in templates by implementing the -[TemplateGlobalProvider](api:SilverStripe\View\TemplateGlobalProvider) interface. - -[notice] -Want a quick way of knowing what scope you're in? Try putting `$ClassName` in your template. You should see a string -such as `Page` of the object that's in scope. The methods you can call on that object then are any functions, database -properties or relations on the `Page` class, `PageController` class as well as anything from their subclasses **or** -extensions. -[/notice] - -Outputting these variables is only the start, if you want to format or manipulate them before adding them to the template -have a read of the [Formatting, Modifying and Casting Variables](casting) documentation. - -[alert] -Some of the following only apply when you have the `CMS` module installed. If you're using the `Framework` alone, this -functionality may not be included. -[/alert] - - -## Base Tag - -```ss - - <% base_tag %> - - .. - -``` - -The `<% base_tag %>` placeholder is replaced with the HTML base element. Relative links within a document (such as ``) will become relative to the URI specified in the base tag. This ensures the browser knows where -to locate your site’s images and css files. - -It renders in the template as `` - -[alert] -A `<% base_tag %>;` is nearly always required or assumed by Silverstripe CMS to exist. -[/alert] - -## CurrentMember - -Returns the currently logged in [Member](api:SilverStripe\Security\Member) instance, if there is one logged in. - -```ss -<% if $CurrentMember %> - Welcome back, $CurrentMember.FirstName -<% end_if %> -``` - -## Title and Menu Title - -```ss -$Title -$MenuTitle -``` - -Most objects within Silverstripe CMS will respond to `$Title` (i.e they should have a `Title` database field or at least a -`getTitle()` method). - -The CMS module in particular provides two fields to label a page: `Title` and `MenuTitle`. `Title` is the title -displayed on the web page, while `MenuTitle` can be a shorter version suitable for size-constrained menus. - -[notice] -If `MenuTitle` is left blank by the CMS author, it'll just default to the value in `Title`. -[/notice] - -## Page Content - -```ss -$Content -``` - -It returns the database content of the `Content` property. With the CMS Module, this is the value of the WYSIWYG editor -but it is also the standard for any object that has a body of content to output. - -[info] -Please note that this database content can be "versioned", meaning that draft content edited in the CMS can be different -from published content shown to your website visitors. In templates, you don't need to worry about this distinction. - -The `$Content` variable contains the published content by default,and only preview draft content if explicitly -requested (e.g. by the "preview" feature in the CMS) (see the [versioning documentation](/../model/versioning) for -more details). -[/info] - -### SiteConfig: Global settings - -[notice] -`SiteConfig` is a module that is bundled with the CMS. If you wish to include `SiteConfig` in your framework only -web pages, you'll need to install it via composer. -[/notice] - -```ss -$SiteConfig.Title -``` - -The [SiteConfig](../configuration/siteconfig) object allows content authors to modify global data in the CMS, rather -than PHP code. By default, this includes a Website title and a Tagline. - -`SiteConfig` can be extended to hold other data, for example a logo image which can be uploaded through the CMS or -global content such as your footer content. - - -## Meta Tags - -The `$MetaTags` placeholder in a template returns a segment of HTML appropriate for putting into the `` tag. It -will set up title, keywords and description meta-tags, based on the CMS content and is editable in the 'Meta-data' tab -on a per-page basis. - -[notice] -If you don’t want to include the title tag use `$MetaTags(false)`. -[/notice] - -By default `$MetaTags` renders (assuming 4.11.0 is the current version of silverstripe/framework): - -```ss -Title of the Page - - -``` - -`$MetaTags(false)` will render - -```ss - - -``` - -If using `$MetaTags(false)` we can provide a more custom `title`. - -```ss -$MetaTags(false) -$Title - Bob's Fantasy Football -``` - -### Disabling the meta generator tag - -The meta generator tag includes the current version of `silverstripe/framework`. This version number -provides aggregate installation numbers to the product team who maintain Silverstripe CMS which is -used to make informed product decisions. - -If you dislike this behaviour, the entire meta generator tag can be disabled via: - -```yml -SilverStripe\CMS\Model\SiteTree: - meta_generator: '' -``` - -The version portion of the metagenerator tag can be disabled via: - -```yml -SilverStripe\CMS\Model\SiteTree: - show_meta_generator_version: false -``` - -### Modifying Meta Tags - -You can override the `MetaComponents()` method on your `SiteTree` sub-classes or make use of the `MetaComponents` extension point to manipulate the underlying data that is rendered by `$MetaTags`. Example (for `Page` class): - -```php -public function MetaComponents() -{ - $tags = parent::MetaComponents(); - // Override the content of the Title tag (needs to be html) - if ($this->MetaTitle) { - $tags['title']['content'] = $this->obj('MetaTitle')->forTemplate(); - } - // Provide a default Meta Description - if (!$tags['description']['attributes']['content']) { - // provide raw text as attributes will be escaped later - $tags['description']['attributes']['content'] = $this->dbObject('Content')->LimitCharactersToClosestWord(300); - } - return $tags; -} -``` - -## Links - -```ss -.. -``` - -All objects that could be accessible in Silverstripe CMS should define a `Link` method and an `AbsoluteLink` method. Link -returns the relative URL for the object and `AbsoluteLink` outputs your full website address along with the relative -link. - -```ss -$Link - - -$AbsoluteLink - -``` - -### Linking Modes - -```ss -$isSection -$isCurrent -``` - -When looping over a list of `SiteTree` instances through a `<% loop $Menu %>` or `<% loop $Children %>`, `$isSection` and `$isCurrent` -will return true or false based on page being looped over relative to the currently viewed page. - -For instance, to only show the menu item linked if it's the current one: - -```ss -<% if $isCurrent %> - $Title -<% else %> - $Title -<% end_if %> -``` - -An example for checking for `current` or `section` is as follows: - -```ss -$MenuTitle -``` - -**Additional Utility Method** - - * `$InSection(page-url)`: This if block will pass if we're currently on the page-url page or one of its children. - -```ss -<% if $InSection(about-us) %> -

You are viewing the about us section

-<% end_if %> -``` - -### URLSegment - -This returns the part of the URL of the page you're currently on. For example on the `/about-us/offices/` web page the -`URLSegment` will be `offices`. `URLSegment` cannot be used to generate a link since it does not output the full path. -It can be used within templates to generate anchors or other CSS classes. - -```ss -
- -
- - -``` - -## ClassName - -Returns the class of the current object in [scope](syntax#scope) such as `Page` or `HomePage`. The `$ClassName` can be -handy for a number of uses. A common use case is to add to your `` tag to influence CSS styles and JavaScript -behavior based on the page type used: - -```ss - - - -``` - -## Children Loops - -```ss -<% loop $Children %> - -<% end_loop %> -``` - -Will loop over all Children records of the current object context. Children are pages that sit under the current page in -the `CMS` or a custom list of data. This originates in the `Versioned` extension's `getChildren` method. - -[alert] -For doing your website navigation most likely you'll want to use `$Menu` since its independent of the page -context. -[/alert] - -### ChildrenOf - -```ss -<% loop $ChildrenOf() %> - -<% end_loop %> -``` - -Will create a list of the children of the given page, as identified by its `URLSegment` value. This can come in handy -because it's not dependent on the context of the current page. For example, it would allow you to list all staff member -pages underneath a "staff" holder on any page, regardless if its on the top level or elsewhere. - - -### AllChildren - -Content authors have the ability to hide pages from menus by un-selecting the `ShowInMenus` checkbox within the CMS. -This option will be honored by `<% loop $Children %>` and `<% loop $Menu %>` however if you want to ignore the user -preference, `AllChildren` does not filter by `ShowInMenus`. - -```ss -<% loop $AllChildren %> - ... -<% end_loop %> -``` - -### Menu Loops - -```ss -<% loop $Menu(1) %> - ... -<% end_loop %> -``` - -`$Menu(1)` returns the top-level menu of the website. You can also create a sub-menu using `$Menu(2)`, and so forth. - -[notice] -Pages with the `ShowInMenus` property set to `false` will be filtered out. -[/notice] - -## Access to a specific Page - -```ss -<% with $Page(my-page) %> - $Title -<% end_with %> -``` - -Page will return a single page from site, looking it up by URL. - -## Access to Parent and Level Pages - -### Level - -```ss -<% with $Level(1) %> - $Title -<% end_with %> -``` - -Will return a page in the current path, at the level specified by the numbers. It is based on the current page context, -looking back through its parent pages. `Level(1)` being the top most level. - -For example, imagine you're on the "bob marley" page, which is three levels in: "about us > staff > bob marley". - -* `$Level(1).Title` would return "about us" -* `$Level(2).Title` would return "staff" -* `$Level(3).Title` would return "bob marley" - -### Parent - -```ss - - -$Parent.Title - - -$Parent.Parent.Title - -``` - -## Navigating Scope - -See [scope](syntax#scope). - -## Breadcrumbs - -Breadcrumbs are the path of pages which need to be taken to reach the current page, and can be a great navigation aid -for website users. - -While you can achieve breadcrumbs through the `$Level()` control manually, there's a nicer shortcut: The -`$Breadcrumbs` variable. - -```ss -$Breadcrumbs -``` - -By default, it uses the template defined in `templates/BreadcrumbsTemplate.ss` -of the `silverstripe/cms` module. - -```ss -<% if $Pages %> - <% loop $Pages %> - <% if $Last %>$Title.XML<% else %>$MenuTitle.XML »<% end_if %> - <% end_loop %> -<% end_if %> -``` - -[info] -To customise the markup that `$Breadcrumbs` generates, copy `templates/BreadcrumbsTemplate.ss` - from the `silverstripe/cms` module to your theme (e.g.: `themes/you-theme/templates/BreadcrumbsTemplate.ss`). - Modify the newly copied template and flush your Silverstripe CMS cache. -[/info] - -## Forms - -```ss -$Form -``` - -A page will normally contain some content and potentially a form of some kind. For example, the log-in page has a -Silverstripe CMS log-in form. If you are on such a page, the `$Form` variable will contain the HTML content of the form. -Placing it just below `$Content` is a good default. - - -## Related Lessons -* [Adding dynamic content](https://www.silverstripe.org/learn/lessons/v4/adding-dynamic-content-1) - -## Related Documentation - - * [Casting and Formatting Variables](casting) - * [Template Inheritance](template_inheritance) - -## API Documentation - - * [ContentController](api:SilverStripe\CMS\Controllers\ContentController): The main controller responsible for handling pages. - * [Controller](api:SilverStripe\Control\Controller): Generic controller (not specific to pages.) - * [DataObject](api:SilverStripe\ORM\DataObject): Underlying model class for page objects. - * [ViewableData](api:SilverStripe\View\ViewableData): Underlying object class for pretty much anything displayable. diff --git a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md deleted file mode 100644 index c21b2a3b09b..00000000000 --- a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md +++ /dev/null @@ -1,469 +0,0 @@ ---- -title: Requirements -summary: How to include and require other assets in your templates such as javascript and CSS files. -iconBrand: js ---- - -# Requirements - -The requirements class takes care of including CSS and JavaScript into your applications. This is preferred to hard -coding any references in the `` tag of your template, as it enables a more flexible handling through the -[Requirements](api:SilverStripe\View\Requirements) class. - -The examples below are using certain folder naming conventions (CSS files in `css/`, JavaScript files in `javascript/`). -Silverstripe CMS core modules like `cms` use a different naming convention (CSS and JavaScript files in `client/src/`). -The `Requirements` class can work with arbitrary file paths. - -## Exposing static assets - -Before requiring static asset files in PHP code or in a template, those assets need to be "exposed". This process allows Silverstripe CMS projects and Silverstripe CMS modules to make static asset files available via the web server from locations that would otherwise be blocked from web server access, such as the `vendor` folder. - -### Configuring your project "exposed" folders - -Exposed assets are made available in your web root in a dedicated "resources" directory. Prior to Silverstripe CMS 4.4, the name of this directory was hardcoded to `resources`. In Silverstripe CMS 4.4 and above, the name of the resources directory can be configured by defining the `extra.resources-dir` key in your `composer.json`. Silverstripe CMS projects created from `silverstripe/installer` 4.4 and above will automatically be configured to use `_resources` as their resource directory. - -Each folder that needs to be exposed must be entered under the `extra.expose` key in your `composer.json` file. Module developers should use a path relative to the root of their module (don't include the "vendor/package-developer/package-name" path). - -This is a sample Silverstripe CMS project `composer.json` file configured to expose some assets. - -```json -{ - "name": "app/myproject", - "type": "silverstripe-project", - "require": { - "silverstripe/recipe-cms": "4.4.x-dev" - }, - "extra": { - "resources-dir": "_resources", - "expose": [ - "app/client/dist", - "app/images" - ] - } -} -``` - -Files contained inside the `app/client/dist` and `app/images` will be made publicly available under the `_resources` directory. - -Silverstripe CMS projects should not track the "resources" directory in their source control system. - -### Exposing assets in the web root {#exposing-assets-webroot} - -Silverstripe CMS projects ship with [silverstripe/vendor-plugin](https://github.com/silverstripe/vendor-plugin). -This Composer plugin automatically tries to expose assets from your project and installed modules after installation, or after an update. - -Developers can explicitly expose static assets by calling `composer vendor-expose`. This is necessary after updating your `resources-dir` or `expose` configuration in your `composer.json` file. - -`composer vendor-expose` accepts an optional `method` argument (e.g.: `composer vendor-expose auto`). This controls how the files are exposed in the "resources" directory: -* `none` disables all symlink / copy -* `copy` copies the exposed files -* `symlink` create symbolic links to the exposed folder -* `junction` uses a junction (Windows only) -* `auto` creates symbolic links (or junctions on Windows), but fails over to copy. - -### Referencing exposed assets - -When referencing exposed static assets, use either the project file path (relative to the project root folder) or a module name and relative file path to that module's root folder. E.g.: - -```php -// When referencing project files, use the same path defined in your `composer.json` file. -Requirements::javascript('app/client/dist/bundle.js'); - -// When referencing theme files, use a path relative to the root of your project -Requirements::javascript('themes/simple/javascript/script.js'); - -// When referencing files from a module, you need to prefix the path with the module name. -Requirements::javascript('silverstripe/admin:client/dist/js/bundle.js'); -``` - -When rendered in HTML code, these URLs will be rewritten to their matching path inside the "resources" directory. - -## Template Requirements API - -**/templates/SomeTemplate.ss** - -``` -<% require css("/css/some_file.css") %> -<% require themedCSS("some_themed_file") %> -<% require javascript("/javascript/some_file.js") %> -``` - -[alert] -Requiring assets from the template is restricted compared to the PHP API. -[/alert] - -## PHP Requirements API - -It is common practice to include most Requirements either in the *init()*-method of your [controller](../controllers/), or -as close to rendering as possible (e.g. in [FormField](api:SilverStripe\Forms\FormField)). - -```php -use SilverStripe\Control\Director; -use SilverStripe\View\Requirements; - -class MyCustomController extends Controller -{ - protected function init() - { - parent::init(); - - Requirements::javascript("/javascript/some_file.js"); - Requirements::css("/css/some_file.css"); - } -} -``` - -### CSS Files - -```php -use SilverStripe\View\Requirements; - -Requirements::css($path, $media); -``` - -If you're using the CSS method a second argument can be used. This argument defines the 'media' attribute of the -`` element, so you can define 'screen' or 'print' for example. - -```php -Requirements::css("/css/some_file.css", "screen,projection"); -``` - -### Javascript Files - -```php -Requirements::javascript($path, $options); -``` - -A variant on the inclusion of custom javascript is the inclusion of *templated* javascript. Here, you keep your -JavaScript in a separate file and instead load, via search and replace, several PHP-generated variables into that code. - -```php -$vars = [ - "MemberID" => Security::getCurrentUser()->ID, -]; - -Requirements::javascriptTemplate("/javascript/some_file.js", $vars); -``` - -In this example, `some_file.js` is expected to contain a replaceable variable expressed as `MemberID`. - -If you are using front-end script combination mechanisms, you can optionally declare -that your included files provide these scripts. This will ensure that subsequent -Requirement calls that rely on those included scripts will not double include those -files. - -```php -Requirements::javascript('/javascript/dist/bundle.js', ['provides' => [ - '/javascript/jquery.js' - '/javascript/src/main.js', - '/javascript/src/functions.js' -]]); -Requirements::javascript('/javascript/jquery.js'); // Will skip this file -``` - -You can also use the second argument to add the 'async' and/or 'defer attributes to the script tag generated: - -```php -Requirements::javascript( - "/javascript/some_file.js", - [ - "async" => true, - "defer" => true, - ] -); -``` - -### Custom Inline CSS or Javascript - -You can also quote custom scripts directly. This may seem a bit ugly, but is useful when you need to transfer some kind -of 'configuration' from the database in a raw format. You'll need to use the `heredoc` syntax to quote JS and CSS, -this is generally speaking the best way to do these things - it clearly marks the copy as belonging to a different -language. - -```php -Requirements::customScript(<</javascript/foo.js', - '/javascript/bar.js', - ] -); -``` - -[alert] -To make debugging easier in your local environment, combined files is disabled when running your application in `dev` -mode. You can re-enable dev combination by setting `Requirements_Backend.combine_in_dev` to true. -[/alert] - -### Configuring combined file storage - -In some situations or server configurations, it may be necessary to customise the behaviour of generated javascript -files in order to ensure that current files are served in requests. - -By default, files will be generated on demand in the format `assets/_combinedfiles/name-.js`, -where `` represents the hash of the source files used to generate that content. The default flysystem backend, -as used by the `[AssetStore](api:SilverStripe\Assets\Storage\AssetStore)` backend, is used for this storage, but it can be substituted for any -other backend. - -You can also use any of the below options in order to tweak this behaviour: - - * `Requirements.disable_flush_combined` - By default all combined files are deleted on flush. - If combined files are stored in source control, and thus updated manually, you might want to - turn this on to disable this behaviour. - * `Requirements_Backend.combine_hash_querystring` - By default the `` of the source files is appended to - the end of the combined file (prior to the file extension). If combined files are versioned in source control, - or running in a distributed environment (such as one where the newest version of a file may not always be - immediately available) then it may sometimes be necessary to disable this. When this is set to true, the hash - will instead be appended via a querystring parameter to enable cache busting, but not in the - filename itself. I.e. `assets/_combinedfiles/name.js?m=` - * `Requirements_Backend.default_combined_files_folder` - This defaults to `_combinedfiles`, and is the folder - within the configured asset backend that combined files will be stored in. If using a backend shared with - other systems, it is usually necessary to distinguish combined files from other assets. - * `Requirements_Backend.combine_in_dev` - By default combined files will not be combined except in test - or live environments. Turning this on will allow for pre-combining of files in development mode. - -In some cases it may be necessary to create a new storage backend for combined files, if the default location -is not appropriate. Normally a single backend is used for all site assets, so a number of objects must be -replaced. For instance, the below will set a new set of dependencies to write to `app/javascript/combined` - - -```yml ---- -Name: myrequirements ---- -SilverStripe\View\Requirements: - disable_flush_combined: true -SilverStripe\View\Requirements_Backend: - combine_in_dev: true - combine_hash_querystring: true - default_combined_files_folder: 'combined' -SilverStripe\Core\Injector\Injector: - # Create adapter that points to the custom directory root - SilverStripe\Assets\Flysystem\PublicAdapter.custom-adapter: - class: SilverStripe\Assets\Flysystem\PublicAssetAdapter - constructor: - Root: ./app/javascript - # Set flysystem filesystem that uses this adapter - League\Flysystem\Filesystem.custom-filesystem: - class: 'League\Flysystem\Filesystem' - constructor: - Adapter: '%$SilverStripe\Assets\Flysystem\PublicAdapter.custom-adapter' - # Create handler to generate assets using this filesystem - SilverStripe\Assets\Storage\GeneratedAssetHandler.custom-generated-assets: - class: SilverStripe\Assets\Flysystem\GeneratedAssets - properties: - Filesystem: %$League\Flysystem\Filesystem.custom-filesystem - # Assign this generator to the requirements builder - SilverStripe\View\Requirements_Backend: - properties: - AssetHandler: '%$SilverStripe\Assets\Storage\GeneratedAssetHandler.custom-generated-assets' -``` - -In the above configuration, automatic expiry of generated files has been disabled, and it is necessary for -the developer to maintain these files manually. This may be useful in environments where assets must -be pre-cached, where scripts must be served alongside static files, or where no framework php request is -guaranteed. Alternatively, files may be served from instances other than the one which generated the -page response, and file synchronisation might not occur fast enough to propagate combined files to -mirrored filesystems. - -In any case, care should be taken to determine the mechanism appropriate for your development -and production environments. - -### Combined CSS Files - -You can also combine CSS files into a media-specific stylesheets as you would with the `Requirements::css` call - use -the third parameter of the `combine_files` function: - -```php -$loader = SilverStripe\View\ThemeResourceLoader::inst(); -$themes = SilverStripe\View\SSViewer::get_themes(); - -$printStylesheets = [ - $loader->findThemedCSS('print_HomePage.css', $themes), - $loader->findThemedCSS('print_Page.css', $themes) -]; - -SilverStripe\View\Requirements::combine_files('print.css', $printStylesheets, 'print'); -``` - -By default, all requirements files are flushed (deleted) when ?flush querystring parameter is set. -This can be disabled by setting the `Requirements.disable_flush_combined` config to `true`. - -[alert] -When combining CSS files, take care of relative urls, as these will not be re-written to match -the destination location of the resulting combined CSS. -[/alert] - -### Combined JS Files - -You can also add the 'async' and/or 'defer' attributes to combined Javascript files as you would with the -`Requirements::javascript` call - use the third parameter of the `combine_files` function: - -```php -$loader = SilverStripe\View\ThemeResourceLoader::inst(); -$themes = SilverStripe\View\SSViewer::get_themes(); - -$scripts = [ - $loader->findThemedJavascript('some_script.js', $themes), - $loader->findThemedJavascript('some_other_script.js', $themes) -]; - -SilverStripe\View\Requirements::combine_files('scripts.js', $scripts, ['async' => true, 'defer' => true]); -``` - -### Minification of CSS and JS files - -You can minify combined Javascript and CSS files at runtime using an implementation of the -`SilverStripe\View\Requirements_Minifier` interface. - -```php -namespace MyProject; - -use SilverStripe\View\Requirements_Minifier; - -class MyMinifier implements Requirements_Minifier -{ - /** - * Minify the given content - * - * @param string $content - * @param string $type Either js or css - * @param string $filename Name of file to display in case of error - * @return string minified content - */ - public function minify ($content, $type, $fileName) - { - // Minify $content; - - return $minifiedContent; - } -} -``` - -Then, inject this service in `Requirements_Backend`. - -```yaml -SilverStripe\Core\Injector\Injector: - SilverStripe\View\Requirements_Backend: - properties: - MinifyCombinedFiles: true - Minifier: %$MyProject\MyMinifier -``` - -[alert] -While the framework does afford you the option of minification at runtime, we recommend using one of many frontend build -tools to do this for you, e.g. [Webpack](https://webpack.github.io/), [Gulp](http://gulpjs.com/), or [Grunt](https://gruntjs.com/). -[/alert] - - -## Clearing assets - -```php -Requirements::clear(); -``` - -Clears all defined requirements. You can also clear specific requirements. - -```php -Requirements::clear('modulename/javascript/some-lib.js'); -``` - -[alert] -Depending on where you call this command, a Requirement might be *re-included* afterwards. -[/alert] - -## Blocking - -Requirements can also be explicitly blocked from inclusion, which is useful to avoid conflicting JavaScript logic or -CSS rules. These blocking rules are independent of where the `block()` call is made. It applies both for already -included requirements, and ones included after the `block()` call. - -One common example is to block the core `jquery.js` added by various form fields and core controllers, and use a newer -version in a custom location. This assumes you have tested your application with the newer version. - -```php -Requirements::block('silverstripe/admin:thirdparty/jquery/jquery.js'); -``` - -[alert] -The CMS also uses the `Requirements` system, and its operation can be affected by `block()` calls. Avoid this by -limiting the scope of your blocking operations, e.g. in `init()` of your controller. -[/alert] - -## Inclusion Order - -Requirements acts like a stack, where everything is rendered sequentially in the order it was included. There is no way -to change inclusion-order, other than using *Requirements::clear* and rebuilding the whole set of requirements. - -[alert] -Inclusion order is both relevant for CSS and Javascript files in terms of dependencies, inheritance and overlays - be -careful when messing with the order of requirements. -[/alert] - -## Javascript placement - -By default, Silverstripe CMS includes all Javascript files at the bottom of the page body, unless there's another script -already loaded, then, it's inserted before the first `