mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
aeccb8b8e0
API: Deprecate SS_Datetime. The DBField subclasses are have all been renamed to start with “DB” and be in the SilverStripe\Model\FieldType namespace. To keep DataObject definitions concise, the original short variations of their names are preserved as service definitions. Most of the field generation code doesn’t need to change, but where field classes are referenced directly, changes will be needed. SS_Datetime, which is commonly referenced outside the model system itself, has been preserved as a subclass of DBDatetime. This has been marked as deprecated and can be removed in SilverStripe 5. A few places that referred to $db and $casting values weren’t using the Injector to instantiate the relevant classes. This meant that the remapping we have created as part of moving classes into a namespace didn’t work.
248 lines
7.4 KiB
PHP
248 lines
7.4 KiB
PHP
<?php
|
|
|
|
use SilverStripe\Model\FieldType\DBField;
|
|
|
|
/**
|
|
* Utility class to render views of the differences between two data objects (or two versions of the
|
|
* same data object).
|
|
*
|
|
* Construcing a diff object is done as follows:
|
|
* <code>
|
|
* $fromRecord = Versioned::get_version('SiteTree', $pageID, $fromVersion);
|
|
* $toRecord = Versioned::get_version('SiteTree, $pageID, $toVersion);
|
|
* $diff = new DataDifferencer($fromRecord, $toRecord);
|
|
* </code>
|
|
*
|
|
* And then it can be used in a number of ways. You can use the ChangedFields() method in a template:
|
|
* <pre>
|
|
* <dl class="diff">
|
|
* <% with Diff %>
|
|
* <% loop ChangedFields %>
|
|
* <dt>$Title</dt>
|
|
* <dd>$Diff</dd>
|
|
* <% end_loop %>
|
|
* <% end_with %>
|
|
* </dl>
|
|
* </pre>
|
|
*
|
|
* Or you can get the diff'ed content as another DataObject, that you can insert into a form.
|
|
* <code>
|
|
* $form->loadDataFrom($diff->diffedData());
|
|
* </code>
|
|
*
|
|
* If there are fields whose changes you aren't interested in, you can ignore them like so:
|
|
* <code>
|
|
* $diff->ignoreFields('AuthorID', 'Status');
|
|
* </code>
|
|
*
|
|
* @package framework
|
|
* @subpackage misc
|
|
*/
|
|
class DataDifferencer extends ViewableData {
|
|
protected $fromRecord;
|
|
protected $toRecord;
|
|
|
|
protected $ignoredFields = array("ID","Version","RecordID");
|
|
|
|
/**
|
|
* Construct a DataDifferencer to show the changes between $fromRecord and $toRecord.
|
|
* If $fromRecord is null, this will represent a "creation".
|
|
*
|
|
* @param DataObject (Optional)
|
|
* @param DataObject
|
|
*/
|
|
public function __construct($fromRecord, DataObject $toRecord) {
|
|
if(!$toRecord) user_error("DataDifferencer constructed without a toRecord", E_USER_WARNING);
|
|
$this->fromRecord = $fromRecord;
|
|
$this->toRecord = $toRecord;
|
|
parent::__construct();
|
|
}
|
|
|
|
/**
|
|
* Specify some fields to ignore changes from. Repeated calls are cumulative.
|
|
* @param $ignoredFields An array of field names to ignore. Alternatively, pass the field names as
|
|
* separate args.
|
|
*/
|
|
public function ignoreFields($ignoredFields) {
|
|
if(!is_array($ignoredFields)) $ignoredFields = func_get_args();
|
|
$this->ignoredFields = array_merge($this->ignoredFields, $ignoredFields);
|
|
}
|
|
|
|
/**
|
|
* Get a DataObject with altered values replaced with HTML diff strings, incorporating
|
|
* <ins> and <del> tags.
|
|
*/
|
|
public function diffedData() {
|
|
if($this->fromRecord) {
|
|
$diffed = clone $this->fromRecord;
|
|
$fields = array_keys($diffed->toMap() + $this->toRecord->toMap());
|
|
} else {
|
|
$diffed = clone $this->toRecord;
|
|
$fields = array_keys($this->toRecord->toMap());
|
|
}
|
|
|
|
$hasOnes = array_merge($this->fromRecord->hasOne(), $this->toRecord->hasOne());
|
|
|
|
// Loop through properties
|
|
foreach($fields as $field) {
|
|
if(in_array($field, $this->ignoredFields)) continue;
|
|
if(in_array($field, array_keys($hasOnes))) continue;
|
|
|
|
// Check if a field from-value is comparable
|
|
$toField = $this->toRecord->obj($field);
|
|
if(!($toField instanceof DBField)) continue;
|
|
$toValue = $toField->forTemplate();
|
|
|
|
// Show only to value
|
|
if(!$this->fromRecord) {
|
|
$diffed->setField($field, "<ins>{$toValue}</ins>");
|
|
continue;
|
|
}
|
|
|
|
// Check if a field to-value is comparable
|
|
$fromField = $this->fromRecord->obj($field);
|
|
if(!($fromField instanceof DBField)) continue;
|
|
$fromValue = $fromField->forTemplate();
|
|
|
|
// Show changes between the two, if any exist
|
|
if($fromValue != $toValue) {
|
|
$diffed->setField($field, Diff::compareHTML($fromValue, $toValue));
|
|
}
|
|
}
|
|
|
|
// Loop through has_one
|
|
foreach($hasOnes as $relName => $relSpec) {
|
|
if(in_array($relName, $this->ignoredFields)) continue;
|
|
|
|
// Create the actual column name
|
|
$relField = "{$relName}ID";
|
|
$toTitle = '';
|
|
if($this->toRecord->hasMethod($relName)) {
|
|
$relObjTo = $this->toRecord->$relName();
|
|
if($relObjTo) {
|
|
$toTitle = ($relObjTo->hasMethod('Title') || $relObjTo->hasField('Title')) ? $relObjTo->Title : '';
|
|
} else {
|
|
$toTitle = '';
|
|
}
|
|
}
|
|
|
|
if(!$this->fromRecord) {
|
|
if($relObjTo) {
|
|
if($relObjTo instanceof Image) {
|
|
// Using relation name instead of database column name, because of FileField etc.
|
|
// TODO Use CMSThumbnail instead to limit max size, blocked by DataDifferencerTest and GC
|
|
// not playing nice with mocked images
|
|
$diffed->setField($relName, "<ins>" . $relObjTo->getTag() . "</ins>");
|
|
} else {
|
|
$diffed->setField($relField, "<ins>" . $toTitle . "</ins>");
|
|
}
|
|
}
|
|
} else if($this->fromRecord->$relField != $this->toRecord->$relField) {
|
|
$fromTitle = '';
|
|
if($this->fromRecord->hasMethod($relName)) {
|
|
$relObjFrom = $this->fromRecord->$relName();
|
|
if($relObjFrom) {
|
|
$fromTitle = ($relObjFrom->hasMethod('Title') || $relObjFrom->hasField('Title'))
|
|
? $relObjFrom->Title
|
|
: '';
|
|
} else {
|
|
$fromTitle = '';
|
|
}
|
|
|
|
}
|
|
if(isset($relObjFrom) && $relObjFrom instanceof Image) {
|
|
// TODO Use CMSThumbnail (see above)
|
|
$diffed->setField(
|
|
// Using relation name instead of database column name, because of FileField etc.
|
|
$relName,
|
|
Diff::compareHTML($relObjFrom->getTag(), $relObjTo->getTag())
|
|
);
|
|
} else {
|
|
// Set the field.
|
|
$diffed->setField(
|
|
$relField,
|
|
Diff::compareHTML($fromTitle, $toTitle)
|
|
);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return $diffed;
|
|
}
|
|
|
|
/**
|
|
* Get a SS_List of the changed fields.
|
|
* Each element is an array data containing
|
|
* - Name: The field name
|
|
* - Title: The human-readable field title
|
|
* - Diff: An HTML diff showing the changes
|
|
* - From: The older version of the field
|
|
* - To: The newer version of the field
|
|
*/
|
|
public function ChangedFields() {
|
|
$changedFields = new ArrayList();
|
|
|
|
if($this->fromRecord) {
|
|
$base = $this->fromRecord;
|
|
$fields = array_keys($this->fromRecord->toMap());
|
|
} else {
|
|
$base = $this->toRecord;
|
|
$fields = array_keys($this->toRecord->toMap());
|
|
}
|
|
|
|
foreach($fields as $field) {
|
|
if(in_array($field, $this->ignoredFields)) continue;
|
|
|
|
if(!$this->fromRecord || $this->fromRecord->$field != $this->toRecord->$field) {
|
|
// Only show HTML diffs for fields which allow HTML values in the first place
|
|
$fieldObj = $this->toRecord->dbObject($field);
|
|
if($this->fromRecord) {
|
|
$fieldDiff = Diff::compareHTML(
|
|
$this->fromRecord->$field,
|
|
$this->toRecord->$field,
|
|
(!$fieldObj || $fieldObj->stat('escape_type') != 'xml')
|
|
);
|
|
} else {
|
|
if($fieldObj && $fieldObj->stat('escape_type') == 'xml') {
|
|
$fieldDiff = "<ins>" . $this->toRecord->$field . "</ins>";
|
|
} else {
|
|
$fieldDiff = "<ins>" . Convert::raw2xml($this->toRecord->$field) . "</ins>";
|
|
}
|
|
}
|
|
$changedFields->push(new ArrayData(array(
|
|
'Name' => $field,
|
|
'Title' => $base->fieldLabel($field),
|
|
'Diff' => $this->fromRecord
|
|
? Diff::compareHTML($this->fromRecord->$field, $this->toRecord->$field)
|
|
: "<ins>" . $this->toRecord->$field . "</ins>",
|
|
'From' => $this->fromRecord ? $this->fromRecord->$field : null,
|
|
'To' => $this->toRecord ? $this->toRecord->$field : null,
|
|
)));
|
|
}
|
|
}
|
|
|
|
return $changedFields;
|
|
}
|
|
|
|
/**
|
|
* Get an array of the names of every fields that has changed.
|
|
* This is simpler than {@link ChangedFields()}
|
|
*/
|
|
public function changedFieldNames() {
|
|
$diffed = clone $this->fromRecord;
|
|
$fields = array_keys($diffed->toMap());
|
|
|
|
$changedFields = array();
|
|
|
|
foreach($fields as $field) {
|
|
if(in_array($field, $this->ignoredFields)) continue;
|
|
if($this->fromRecord->$field != $this->toRecord->$field) {
|
|
$changedFields[] = $field;
|
|
}
|
|
}
|
|
|
|
return $changedFields;
|
|
}
|
|
}
|