Source of file DataDifferencer.php
Size: 9,264 Bytes - Last Modified: 2021-12-23T10:35:47+00:00
/var/www/docs.ssmods.com/process/src/src/DataDifferencer.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 | <?php namespace SilverStripe\Versioned; use SilverStripe\Assets\Image; use SilverStripe\Core\Convert; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\FieldType\DBField; use SilverStripe\View\ArrayData; use SilverStripe\View\Parsers\Diff; use SilverStripe\View\ViewableData; /** * 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> */ class DataDifferencer extends ViewableData { protected $fromRecord; protected $toRecord; protected $ignoredFields = ["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 $fromRecord * @param DataObject $toRecord */ public function __construct(DataObject $fromRecord = null, DataObject $toRecord = null) { $this->fromRecord = $fromRecord; $this->toRecord = $toRecord; parent::__construct(); } /** * Specify some fields to ignore changes from. Repeated calls are cumulative. * @param array $ignoredFields An array of field names to ignore. Alternatively, pass the field names as * separate args. * @return $this */ public function ignoreFields($ignoredFields) { if (!is_array($ignoredFields)) { $ignoredFields = func_get_args(); } $this->ignoredFields = array_merge($this->ignoredFields, $ignoredFields); return $this; } /** * 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, DBField::create_field('HTMLFragment', "<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, DBField::create_field('HTMLFragment', 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"; // Using relation name instead of database column name, because of FileField etc. $setField = is_a($relSpec, Image::class, true) ? $relName : $relField; $toTitle = ''; /** @var DataObject $relObjTo */ $relObjTo = null; if ($this->toRecord->hasMethod($relName)) { $relObjTo = $this->toRecord->$relName(); $toTitle = $this->getObjectDisplay($relObjTo); } if (!$this->fromRecord) { $diffed->setField( $setField, DBField::create_field('HTMLFragment', "<ins>{$toTitle}</ins>") ); } elseif ($this->fromRecord->$relField != $this->toRecord->$relField) { $fromTitle = ''; $relObjFrom = null; if ($this->fromRecord->hasMethod($relName)) { $relObjFrom = $this->fromRecord->$relName(); $fromTitle = $this->getObjectDisplay($relObjFrom); } // Set the field. $diffed->setField( $setField, DBField::create_field('HTMLFragment', Diff::compareHTML($fromTitle, $toTitle)) ); } } return $diffed; } /** * Get HTML to display for a dataobject * * @param DataObject $object * @return string HTML output */ protected function getObjectDisplay($object = null) { if (!$object || !$object->isInDB()) { return ''; } // Use image tag // TODO Use CMSThumbnail instead to limit max size, blocked by DataDifferencerTest and GC // not playing nice with mocked images if ($object instanceof Image) { return $object->getTag(); } // Format title return $object->obj('Title')->forTemplate(); } /** * 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->config()->get('escape_type') != 'xml') ); } else { if ($fieldObj && $fieldObj->config()->get('escape_type') == 'xml') { $fieldDiff = "<ins>" . $this->toRecord->$field . "</ins>"; } else { $fieldDiff = "<ins>" . Convert::raw2xml($this->toRecord->$field) . "</ins>"; } } $changedFields->push(new ArrayData([ 'Name' => $field, 'Title' => $base->fieldLabel($field), 'Diff' => $fieldDiff, '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 = []; foreach ($fields as $field) { if (in_array($field, $this->ignoredFields)) { continue; } if ($this->fromRecord->$field != $this->toRecord->$field) { $changedFields[] = $field; } } return $changedFields; } } |