Versioning in SilverStripe 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.
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 handles this through the concept of _stage_.
By default, adding the `Versioned` extension to a DataObject will create a 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 DataObject
Typically when publishing versioned DataObjects, it is necessary to ensure that some linked components
If an object "owns" other objects, you'll usually want to publish the children object when the parent object gets published. If those children objects themselves own other objects, you'll want the grand-children to be published along with the parent.
SilverStripe makes this possible by using the concept of _cascade publishing_. You can choose to recursively publish a object. When a 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.
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 object together.
Images and other files are tracked as versioned object. If a file is referenced through an HTML text field, it needs to be published for it to be accessible to the public. SilverStripe will automatically pick up when a object references a files through an HTML text field and recursively publish those files.
Sometimes, multiple pages or records may be related in organic ways that can not 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 sponsors 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` 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.
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 programatically.
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.
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`.
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 `<img>` 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
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
When you call the `write()` method on a versioned DataObject, this will transparently create a new version of this DataObject in the the _Stage_ stage.
To write your changes without creating new version, call `writeWithoutVersion()` instead.(api:SilverStripe\Versioned\Versioned::writeWithoutVersion()) instead.
### 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.
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 dependant records using the `rollbackRecursive()` method.
Both `rollbackSingle()` and `rollbackRecursive()` expect a single argument, which may be a specific version ID or a stage name.
// 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.
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
<?php
use MyRecord;
use SilverStripe\Versioned\Versioned;
// This script will restore all archived entries for MyRecord.
*`$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
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:
### 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. `<class>_versions`
is always created to track historic versions for your model. If staging is enabled this will also create a new
`<class>_Live` table once you've rebuilt the database.
<divclass="notice"markdown="1">
Note that the "Stage" naming has a special meaning here, it will leave the original table name unchanged, rather than
adding a suffix.
</div>
*`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:
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.
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.
Versioned DataObjects get additional permission check methods to verify what oepration 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 version operation on behalf of a Member, you'll probably want to use these method to confirm they are authorised,
```php
<?php
use SilverStripe\Security\Security;
$record = MyRecord::get()->byID(99);
$member = Security::getCurrentUser();
if ($record->canPublish($member)) {
$record->publishRecursive();
}
```
There's also a `canViewStage()` method which can be use to check if a Member can access a specific stage.
```php
<?php
use SilverStripe\Versioned\Versioned;
// Check if `$member` can view the Live version of $record.
$record->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
In templates, you don't need to worry about this distinction. The `$Content` variable contain the published content by
default, and only preview draft content if explicitly requested (e.g. by the "preview" feature in the CMS, or by adding ?stage=Stage to the URL). If you want
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)` allow 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.