|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Charcoal\Object; |
|
4
|
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
|
6
|
|
|
|
|
7
|
|
|
// From 'charcoal-core' |
|
8
|
|
|
use Charcoal\Loader\CollectionLoader; |
|
9
|
|
|
|
|
10
|
|
|
// From 'charcoal-object' |
|
11
|
|
|
use Charcoal\Object\ObjectRevision; |
|
12
|
|
|
use Charcoal\Object\ObjectRevisionInterface; |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* |
|
16
|
|
|
*/ |
|
17
|
|
|
trait RevisionableTrait |
|
18
|
|
|
{ |
|
19
|
|
|
/** |
|
20
|
|
|
* @var boolean $revisionEnabled |
|
21
|
|
|
*/ |
|
22
|
|
|
protected $revisionEnabled = true; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* The class name of the object revision model. |
|
26
|
|
|
* |
|
27
|
|
|
* Must be a fully-qualified PHP namespace and an implementation of |
|
28
|
|
|
* {@see \Charcoal\Object\ObjectRevisionInterface}. Used by the model factory. |
|
29
|
|
|
* |
|
30
|
|
|
* @var string |
|
31
|
|
|
*/ |
|
32
|
|
|
private $objectRevisionClass = ObjectRevision::class; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* @param boolean $enabled The (revision) enabled flag. |
|
36
|
|
|
* @return RevisionableInterface Chainable |
|
37
|
|
|
*/ |
|
38
|
|
|
public function setRevisionEnabled($enabled) |
|
39
|
|
|
{ |
|
40
|
|
|
$this->revisionEnabled = !!$enabled; |
|
41
|
|
|
return $this; |
|
42
|
|
|
} |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* @return boolean |
|
46
|
|
|
*/ |
|
47
|
|
|
public function getRevisionEnabled() |
|
48
|
|
|
{ |
|
49
|
|
|
return $this->revisionEnabled; |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* Create a revision collection loader. |
|
54
|
|
|
* |
|
55
|
|
|
* @return CollectionLoader |
|
56
|
|
|
*/ |
|
57
|
|
View Code Duplication |
public function createRevisionObjectCollectionLoader() |
|
|
|
|
|
|
58
|
|
|
{ |
|
59
|
|
|
$loader = new CollectionLoader([ |
|
60
|
|
|
'logger' => $this->logger, |
|
|
|
|
|
|
61
|
|
|
'factory' => $this->modelFactory(), |
|
62
|
|
|
'model' => $this->getRevisionObjectPrototype(), |
|
63
|
|
|
]); |
|
64
|
|
|
|
|
65
|
|
|
return $loader; |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
/** |
|
69
|
|
|
* Create a revision object. |
|
70
|
|
|
* |
|
71
|
|
|
* @return ObjectRevisionInterface |
|
72
|
|
|
*/ |
|
73
|
|
|
public function createRevisionObject() |
|
74
|
|
|
{ |
|
75
|
|
|
$rev = $this->modelFactory()->create($this->getObjectRevisionClass()); |
|
76
|
|
|
|
|
77
|
|
|
return $rev; |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* Retrieve the revision object prototype. |
|
82
|
|
|
* |
|
83
|
|
|
* @return ObjectRevisionInterface |
|
84
|
|
|
*/ |
|
85
|
|
|
public function getRevisionObjectPrototype() |
|
86
|
|
|
{ |
|
87
|
|
|
$proto = $this->modelFactory()->get($this->getObjectRevisionClass()); |
|
88
|
|
|
|
|
89
|
|
|
return $proto; |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
|
|
/** |
|
93
|
|
|
* Set the class name of the object revision model. |
|
94
|
|
|
* |
|
95
|
|
|
* @param string $className The class name of the object revision model. |
|
96
|
|
|
* @throws InvalidArgumentException If the class name is not a string. |
|
97
|
|
|
* @return AbstractPropertyDisplay Chainable |
|
98
|
|
|
*/ |
|
99
|
|
|
protected function setObjectRevisionClass($className) |
|
100
|
|
|
{ |
|
101
|
|
|
if (!is_string($className)) { |
|
102
|
|
|
throw new InvalidArgumentException( |
|
103
|
|
|
'Route class name must be a string.' |
|
104
|
|
|
); |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
$this->objectRevisionClass = $className; |
|
108
|
|
|
return $this; |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* Retrieve the class name of the object revision model. |
|
113
|
|
|
* |
|
114
|
|
|
* @return string |
|
115
|
|
|
*/ |
|
116
|
|
|
public function getObjectRevisionClass() |
|
117
|
|
|
{ |
|
118
|
|
|
return $this->objectRevisionClass; |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
/** |
|
122
|
|
|
* Alias of {@see self::getObjectRevisionClass()}. |
|
123
|
|
|
* |
|
124
|
|
|
* @return string |
|
125
|
|
|
*/ |
|
126
|
|
|
public function objectRevisionClass() |
|
127
|
|
|
{ |
|
128
|
|
|
return $this->getObjectRevisionClass(); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* @see \Charcoal\Object\ObjectRevision::create_fromObject() |
|
133
|
|
|
* @return ObjectRevision |
|
134
|
|
|
*/ |
|
135
|
|
|
public function generateRevision() |
|
136
|
|
|
{ |
|
137
|
|
|
$rev = $this->createRevisionObject(); |
|
138
|
|
|
|
|
139
|
|
|
$rev->createFromObject($this); |
|
140
|
|
|
if (!empty($rev->getDataDiff())) { |
|
141
|
|
|
$rev->save(); |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
return $rev; |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* @see \Charcoal\Object\ObejctRevision::lastObjectRevision |
|
149
|
|
|
* @return ObjectRevision |
|
150
|
|
|
*/ |
|
151
|
|
|
public function latestRevision() |
|
152
|
|
|
{ |
|
153
|
|
|
$rev = $this->createRevisionObject(); |
|
154
|
|
|
$rev = $rev->lastObjectRevision($this); |
|
155
|
|
|
|
|
156
|
|
|
return $rev; |
|
157
|
|
|
} |
|
158
|
|
|
|
|
159
|
|
|
/** |
|
160
|
|
|
* @see \Charcoal\Object\ObejctRevision::objectRevisionNum() |
|
161
|
|
|
* |
|
162
|
|
|
* @todo Should return NULL if source does not exist. |
|
163
|
|
|
* |
|
164
|
|
|
* @param integer $revNum The revision number. |
|
165
|
|
|
* @return ObjectRevision |
|
166
|
|
|
*/ |
|
167
|
|
|
public function revisionNum($revNum) |
|
168
|
|
|
{ |
|
169
|
|
|
$rev = $this->createRevisionObject(); |
|
170
|
|
|
$rev = $rev->objectRevisionNum($this, intval($revNum)); |
|
171
|
|
|
|
|
172
|
|
|
return $rev; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* Retrieves all revisions for the current objet |
|
177
|
|
|
* |
|
178
|
|
|
* @param callable $callback Optional object callback. |
|
179
|
|
|
* @return array |
|
180
|
|
|
*/ |
|
181
|
|
|
public function allRevisions(callable $callback = null) |
|
182
|
|
|
{ |
|
183
|
|
|
$loader = $this->createRevisionObjectCollectionLoader(); |
|
184
|
|
|
$loader |
|
185
|
|
|
->addOrder('revTs', 'desc') |
|
186
|
|
|
->addFilters([ |
|
187
|
|
|
[ |
|
188
|
|
|
'property' => 'targetType', |
|
189
|
|
|
'value' => $this->objType(), |
|
|
|
|
|
|
190
|
|
|
], |
|
191
|
|
|
[ |
|
192
|
|
|
'property' => 'targetId', |
|
193
|
|
|
'value' => $this->id(), |
|
|
|
|
|
|
194
|
|
|
], |
|
195
|
|
|
]); |
|
196
|
|
|
|
|
197
|
|
|
if ($callback !== null) { |
|
198
|
|
|
$loader->setCallback($callback); |
|
199
|
|
|
} |
|
200
|
|
|
|
|
201
|
|
|
$revisions = $loader->load(); |
|
202
|
|
|
return $revisions->objects(); |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* @param integer $revNum The revision number to revert to. |
|
207
|
|
|
* @throws InvalidArgumentException If revision number is invalid. |
|
208
|
|
|
* @return boolean Success / Failure. |
|
209
|
|
|
*/ |
|
210
|
|
|
public function revertToRevision($revNum) |
|
211
|
|
|
{ |
|
212
|
|
|
if (!$revNum) { |
|
213
|
|
|
throw new InvalidArgumentException( |
|
214
|
|
|
'Invalid revision number' |
|
215
|
|
|
); |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
$rev = $this->revisionNum(intval($revNum)); |
|
219
|
|
|
|
|
220
|
|
|
if (!$rev->id()) { |
|
221
|
|
|
return false; |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
if (isset($obj['lastModifiedBy'])) { |
|
|
|
|
|
|
225
|
|
|
$obj['lastModifiedBy'] = $rev->getRevUser(); |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
$this->setData($rev->getDataObj()); |
|
|
|
|
|
|
229
|
|
|
$this->update(); |
|
|
|
|
|
|
230
|
|
|
|
|
231
|
|
|
return true; |
|
232
|
|
|
} |
|
233
|
|
|
|
|
234
|
|
|
/** |
|
235
|
|
|
* Retrieve the object model factory. |
|
236
|
|
|
* |
|
237
|
|
|
* @return \Charcoal\Factory\FactoryInterface |
|
238
|
|
|
*/ |
|
239
|
|
|
abstract public function modelFactory(); |
|
240
|
|
|
|
|
241
|
|
|
/** |
|
242
|
|
|
* @return \Charcoal\Model\MetadataInterface |
|
243
|
|
|
*/ |
|
244
|
|
|
abstract public function metadata(); |
|
245
|
|
|
} |
|
246
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.