Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\migrate;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Utility\NestedArray;
|
Chris@0
|
6 use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
Chris@0
|
7
|
Chris@0
|
8 /**
|
Chris@0
|
9 * Stores a row.
|
Chris@0
|
10 */
|
Chris@0
|
11 class Row {
|
Chris@0
|
12
|
Chris@0
|
13 /**
|
Chris@0
|
14 * The actual values of the source row.
|
Chris@0
|
15 *
|
Chris@0
|
16 * @var array
|
Chris@0
|
17 */
|
Chris@0
|
18 protected $source = [];
|
Chris@0
|
19
|
Chris@0
|
20 /**
|
Chris@0
|
21 * The source identifiers.
|
Chris@0
|
22 *
|
Chris@0
|
23 * @var array
|
Chris@0
|
24 */
|
Chris@0
|
25 protected $sourceIds = [];
|
Chris@0
|
26
|
Chris@0
|
27 /**
|
Chris@0
|
28 * The destination values.
|
Chris@0
|
29 *
|
Chris@0
|
30 * @var array
|
Chris@0
|
31 */
|
Chris@0
|
32 protected $destination = [];
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@0
|
35 * Level separator of destination and source properties.
|
Chris@0
|
36 */
|
Chris@0
|
37 const PROPERTY_SEPARATOR = '/';
|
Chris@0
|
38
|
Chris@0
|
39 /**
|
Chris@0
|
40 * The mapping between source and destination identifiers.
|
Chris@0
|
41 *
|
Chris@0
|
42 * @var array
|
Chris@0
|
43 */
|
Chris@0
|
44 protected $idMap = [
|
Chris@0
|
45 'original_hash' => '',
|
Chris@0
|
46 'hash' => '',
|
Chris@0
|
47 'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
Chris@0
|
48 ];
|
Chris@0
|
49
|
Chris@0
|
50 /**
|
Chris@0
|
51 * Whether the source has been frozen already.
|
Chris@0
|
52 *
|
Chris@0
|
53 * Once frozen the source can not be changed any more.
|
Chris@0
|
54 *
|
Chris@0
|
55 * @var bool
|
Chris@0
|
56 */
|
Chris@0
|
57 protected $frozen = FALSE;
|
Chris@0
|
58
|
Chris@0
|
59 /**
|
Chris@0
|
60 * The raw destination properties.
|
Chris@0
|
61 *
|
Chris@0
|
62 * Unlike $destination which is set by using
|
Chris@0
|
63 * \Drupal\Component\Utility\NestedArray::setValue() this array contains
|
Chris@0
|
64 * the destination as setDestinationProperty was called.
|
Chris@0
|
65 *
|
Chris@0
|
66 * @var array
|
Chris@0
|
67 * The raw destination.
|
Chris@0
|
68 *
|
Chris@0
|
69 * @see getRawDestination()
|
Chris@0
|
70 */
|
Chris@0
|
71 protected $rawDestination = [];
|
Chris@0
|
72
|
Chris@0
|
73 /**
|
Chris@0
|
74 * TRUE when this row is a stub.
|
Chris@0
|
75 *
|
Chris@0
|
76 * @var bool
|
Chris@0
|
77 */
|
Chris@0
|
78 protected $isStub = FALSE;
|
Chris@0
|
79
|
Chris@0
|
80 /**
|
Chris@0
|
81 * The empty destination properties.
|
Chris@0
|
82 *
|
Chris@0
|
83 * @var array
|
Chris@0
|
84 */
|
Chris@0
|
85 protected $emptyDestinationProperties = [];
|
Chris@0
|
86
|
Chris@0
|
87 /**
|
Chris@18
|
88 * Constructs a \Drupal\migrate\Row object.
|
Chris@0
|
89 *
|
Chris@0
|
90 * @param array $values
|
Chris@0
|
91 * An array of values to add as properties on the object.
|
Chris@0
|
92 * @param array $source_ids
|
Chris@0
|
93 * An array containing the IDs of the source using the keys as the field
|
Chris@0
|
94 * names.
|
Chris@0
|
95 * @param bool $is_stub
|
Chris@0
|
96 * TRUE if the row being created is a stub.
|
Chris@0
|
97 *
|
Chris@0
|
98 * @throws \InvalidArgumentException
|
Chris@0
|
99 * Thrown when a source ID property does not exist.
|
Chris@0
|
100 */
|
Chris@0
|
101 public function __construct(array $values = [], array $source_ids = [], $is_stub = FALSE) {
|
Chris@0
|
102 $this->source = $values;
|
Chris@0
|
103 $this->sourceIds = $source_ids;
|
Chris@0
|
104 $this->isStub = $is_stub;
|
Chris@0
|
105 foreach (array_keys($source_ids) as $id) {
|
Chris@0
|
106 if (!$this->hasSourceProperty($id)) {
|
Chris@14
|
107 throw new \InvalidArgumentException("$id is defined as a source ID but has no value.");
|
Chris@0
|
108 }
|
Chris@0
|
109 }
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 /**
|
Chris@0
|
113 * Retrieves the values of the source identifiers.
|
Chris@0
|
114 *
|
Chris@0
|
115 * @return array
|
Chris@0
|
116 * An array containing the values of the source identifiers. Returns values
|
Chris@0
|
117 * in the same order as defined in $this->sourceIds.
|
Chris@0
|
118 */
|
Chris@0
|
119 public function getSourceIdValues() {
|
Chris@0
|
120 return array_merge($this->sourceIds, array_intersect_key($this->source, $this->sourceIds));
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 /**
|
Chris@0
|
124 * Determines whether a source has a property.
|
Chris@0
|
125 *
|
Chris@0
|
126 * @param string $property
|
Chris@0
|
127 * A property on the source.
|
Chris@0
|
128 *
|
Chris@0
|
129 * @return bool
|
Chris@0
|
130 * TRUE if the source has property; FALSE otherwise.
|
Chris@0
|
131 */
|
Chris@0
|
132 public function hasSourceProperty($property) {
|
Chris@0
|
133 return NestedArray::keyExists($this->source, explode(static::PROPERTY_SEPARATOR, $property));
|
Chris@0
|
134 }
|
Chris@0
|
135
|
Chris@0
|
136 /**
|
Chris@0
|
137 * Retrieves a source property.
|
Chris@0
|
138 *
|
Chris@18
|
139 * This function directly retrieves a source property. It does not unescape
|
Chris@18
|
140 * '@' symbols. This is most useful in source plugins when you don't want to
|
Chris@18
|
141 * worry about escaping '@' symbols. If using this in a process plugin to
|
Chris@18
|
142 * retrieve a source property based on a configuration value, consider if the
|
Chris@18
|
143 * ::get() function might be more appropriate, to allow the migration to
|
Chris@18
|
144 * potentially specify a destination key as well.
|
Chris@18
|
145 *
|
Chris@0
|
146 * @param string $property
|
Chris@0
|
147 * A property on the source.
|
Chris@0
|
148 *
|
Chris@0
|
149 * @return mixed|null
|
Chris@0
|
150 * The found returned property or NULL if not found.
|
Chris@0
|
151 */
|
Chris@0
|
152 public function getSourceProperty($property) {
|
Chris@0
|
153 $return = NestedArray::getValue($this->source, explode(static::PROPERTY_SEPARATOR, $property), $key_exists);
|
Chris@0
|
154 if ($key_exists) {
|
Chris@0
|
155 return $return;
|
Chris@0
|
156 }
|
Chris@0
|
157 }
|
Chris@0
|
158
|
Chris@0
|
159 /**
|
Chris@0
|
160 * Returns the whole source array.
|
Chris@0
|
161 *
|
Chris@0
|
162 * @return array
|
Chris@0
|
163 * An array of source plugins.
|
Chris@0
|
164 */
|
Chris@0
|
165 public function getSource() {
|
Chris@0
|
166 return $this->source;
|
Chris@0
|
167 }
|
Chris@0
|
168
|
Chris@0
|
169 /**
|
Chris@0
|
170 * Sets a source property.
|
Chris@0
|
171 *
|
Chris@0
|
172 * This can only be called from the source plugin.
|
Chris@0
|
173 *
|
Chris@0
|
174 * @param string $property
|
Chris@0
|
175 * A property on the source.
|
Chris@0
|
176 * @param mixed $data
|
Chris@0
|
177 * The property value to set on the source.
|
Chris@0
|
178 *
|
Chris@0
|
179 * @throws \Exception
|
Chris@0
|
180 */
|
Chris@0
|
181 public function setSourceProperty($property, $data) {
|
Chris@0
|
182 if ($this->frozen) {
|
Chris@0
|
183 throw new \Exception("The source is frozen and can't be changed any more");
|
Chris@0
|
184 }
|
Chris@0
|
185 else {
|
Chris@0
|
186 NestedArray::setValue($this->source, explode(static::PROPERTY_SEPARATOR, $property), $data, TRUE);
|
Chris@0
|
187 }
|
Chris@0
|
188 }
|
Chris@0
|
189
|
Chris@0
|
190 /**
|
Chris@0
|
191 * Freezes the source.
|
Chris@0
|
192 *
|
Chris@0
|
193 * @return $this
|
Chris@0
|
194 */
|
Chris@0
|
195 public function freezeSource() {
|
Chris@0
|
196 $this->frozen = TRUE;
|
Chris@0
|
197 return $this;
|
Chris@0
|
198 }
|
Chris@0
|
199
|
Chris@0
|
200 /**
|
Chris@0
|
201 * Clones the row with an empty set of destination values.
|
Chris@0
|
202 *
|
Chris@0
|
203 * @return static
|
Chris@0
|
204 */
|
Chris@0
|
205 public function cloneWithoutDestination() {
|
Chris@0
|
206 return (new static($this->getSource(), $this->sourceIds, $this->isStub()))->freezeSource();
|
Chris@0
|
207 }
|
Chris@0
|
208
|
Chris@0
|
209 /**
|
Chris@0
|
210 * Tests if destination property exists.
|
Chris@0
|
211 *
|
Chris@0
|
212 * @param array|string $property
|
Chris@0
|
213 * An array of properties on the destination.
|
Chris@0
|
214 *
|
Chris@0
|
215 * @return bool
|
Chris@0
|
216 * TRUE if the destination property exists.
|
Chris@0
|
217 */
|
Chris@0
|
218 public function hasDestinationProperty($property) {
|
Chris@0
|
219 return NestedArray::keyExists($this->destination, explode(static::PROPERTY_SEPARATOR, $property));
|
Chris@0
|
220 }
|
Chris@0
|
221
|
Chris@0
|
222 /**
|
Chris@0
|
223 * Sets destination properties.
|
Chris@0
|
224 *
|
Chris@0
|
225 * @param string $property
|
Chris@0
|
226 * The name of the destination property.
|
Chris@0
|
227 * @param mixed $value
|
Chris@0
|
228 * The property value to set on the destination.
|
Chris@0
|
229 */
|
Chris@0
|
230 public function setDestinationProperty($property, $value) {
|
Chris@0
|
231 $this->rawDestination[$property] = $value;
|
Chris@0
|
232 NestedArray::setValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property), $value, TRUE);
|
Chris@0
|
233 }
|
Chris@0
|
234
|
Chris@0
|
235 /**
|
Chris@0
|
236 * Removes destination property.
|
Chris@0
|
237 *
|
Chris@0
|
238 * @param string $property
|
Chris@0
|
239 * The name of the destination property.
|
Chris@0
|
240 */
|
Chris@0
|
241 public function removeDestinationProperty($property) {
|
Chris@0
|
242 unset($this->rawDestination[$property]);
|
Chris@0
|
243 NestedArray::unsetValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property));
|
Chris@0
|
244 }
|
Chris@0
|
245
|
Chris@0
|
246 /**
|
Chris@0
|
247 * Sets a destination to be empty.
|
Chris@0
|
248 *
|
Chris@0
|
249 * @param string $property
|
Chris@0
|
250 * The destination property.
|
Chris@0
|
251 */
|
Chris@0
|
252 public function setEmptyDestinationProperty($property) {
|
Chris@0
|
253 $this->emptyDestinationProperties[] = $property;
|
Chris@0
|
254 }
|
Chris@0
|
255
|
Chris@0
|
256 /**
|
Chris@0
|
257 * Gets the empty destination properties.
|
Chris@0
|
258 *
|
Chris@0
|
259 * @return array
|
Chris@0
|
260 * An array of destination properties.
|
Chris@0
|
261 */
|
Chris@0
|
262 public function getEmptyDestinationProperties() {
|
Chris@0
|
263 return $this->emptyDestinationProperties;
|
Chris@0
|
264 }
|
Chris@0
|
265
|
Chris@0
|
266 /**
|
Chris@0
|
267 * Returns the whole destination array.
|
Chris@0
|
268 *
|
Chris@0
|
269 * @return array
|
Chris@0
|
270 * An array of destination values.
|
Chris@0
|
271 */
|
Chris@0
|
272 public function getDestination() {
|
Chris@0
|
273 return $this->destination;
|
Chris@0
|
274 }
|
Chris@0
|
275
|
Chris@0
|
276 /**
|
Chris@0
|
277 * Returns the raw destination. Rarely necessary.
|
Chris@0
|
278 *
|
Chris@0
|
279 * For example calling setDestination('foo/bar', 'baz') results in
|
Chris@0
|
280 * @code
|
Chris@0
|
281 * $this->destination['foo']['bar'] = 'baz';
|
Chris@0
|
282 * $this->rawDestination['foo/bar'] = 'baz';
|
Chris@0
|
283 * @endcode
|
Chris@0
|
284 *
|
Chris@0
|
285 * @return array
|
Chris@0
|
286 * The raw destination values.
|
Chris@0
|
287 */
|
Chris@0
|
288 public function getRawDestination() {
|
Chris@0
|
289 return $this->rawDestination;
|
Chris@0
|
290 }
|
Chris@0
|
291
|
Chris@0
|
292 /**
|
Chris@0
|
293 * Returns the value of a destination property.
|
Chris@0
|
294 *
|
Chris@18
|
295 * This function directly returns a destination property. The property name
|
Chris@18
|
296 * should not begin with an @ symbol. This is most useful in a destination
|
Chris@18
|
297 * plugin.
|
Chris@18
|
298 *
|
Chris@0
|
299 * @param string $property
|
Chris@0
|
300 * The name of a property on the destination.
|
Chris@0
|
301 *
|
Chris@0
|
302 * @return mixed
|
Chris@0
|
303 * The destination value.
|
Chris@0
|
304 */
|
Chris@0
|
305 public function getDestinationProperty($property) {
|
Chris@0
|
306 return NestedArray::getValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property));
|
Chris@0
|
307 }
|
Chris@0
|
308
|
Chris@0
|
309 /**
|
Chris@18
|
310 * Retrieve a source or destination property.
|
Chris@18
|
311 *
|
Chris@18
|
312 * If the property key begins with '@' return a destination property,
|
Chris@18
|
313 * otherwise return a source property. the '@' symbol itself can be escaped
|
Chris@18
|
314 * as '@@'. Returns NULL if property is not found. Useful in process plugins
|
Chris@18
|
315 * to retrieve a row property specified in a configuration key which may be
|
Chris@18
|
316 * either a source or destination property prefixed with an '@'.
|
Chris@18
|
317 *
|
Chris@18
|
318 * @param string $property
|
Chris@18
|
319 * The property to get.
|
Chris@18
|
320 *
|
Chris@18
|
321 * @return mixed|null
|
Chris@18
|
322 * The requested property.
|
Chris@18
|
323 */
|
Chris@18
|
324 public function get($property) {
|
Chris@18
|
325 $values = $this->getMultiple([$property]);
|
Chris@18
|
326 return reset($values);
|
Chris@18
|
327 }
|
Chris@18
|
328
|
Chris@18
|
329 /**
|
Chris@18
|
330 * Retrieve multiple source and destination properties at once.
|
Chris@18
|
331 *
|
Chris@18
|
332 * @param string[] $properties
|
Chris@18
|
333 * An array of values to retrieve, with destination values prefixed with @.
|
Chris@18
|
334 *
|
Chris@18
|
335 * @return array
|
Chris@18
|
336 * An array of property values, keyed by property name.
|
Chris@18
|
337 */
|
Chris@18
|
338 public function getMultiple(array $properties) {
|
Chris@18
|
339 $return = [];
|
Chris@18
|
340 foreach ($properties as $orig_property) {
|
Chris@18
|
341 $property = $orig_property;
|
Chris@18
|
342 $is_source = TRUE;
|
Chris@18
|
343 if ($property[0] == '@') {
|
Chris@18
|
344 $property = preg_replace_callback('/^(@?)((?:@@)*)([^@]|$)/', function ($matches) use (&$is_source) {
|
Chris@18
|
345 // If there are an odd number of @ in the beginning, it's a
|
Chris@18
|
346 // destination.
|
Chris@18
|
347 $is_source = empty($matches[1]);
|
Chris@18
|
348 // Remove the possible escaping and do not lose the terminating
|
Chris@18
|
349 // non-@ either.
|
Chris@18
|
350 return str_replace('@@', '@', $matches[2]) . $matches[3];
|
Chris@18
|
351 }, $property);
|
Chris@18
|
352 }
|
Chris@18
|
353 if ($is_source) {
|
Chris@18
|
354 $return[$orig_property] = $this->getSourceProperty($property);
|
Chris@18
|
355 }
|
Chris@18
|
356 else {
|
Chris@18
|
357 $return[$orig_property] = $this->getDestinationProperty($property);
|
Chris@18
|
358 }
|
Chris@18
|
359 }
|
Chris@18
|
360 return $return;
|
Chris@18
|
361 }
|
Chris@18
|
362
|
Chris@18
|
363 /**
|
Chris@0
|
364 * Sets the Migrate ID mappings.
|
Chris@0
|
365 *
|
Chris@0
|
366 * @param array $id_map
|
Chris@0
|
367 * An array of mappings between source ID and destination ID.
|
Chris@0
|
368 */
|
Chris@0
|
369 public function setIdMap(array $id_map) {
|
Chris@0
|
370 $this->idMap = $id_map;
|
Chris@0
|
371 }
|
Chris@0
|
372
|
Chris@0
|
373 /**
|
Chris@0
|
374 * Retrieves the Migrate ID mappings.
|
Chris@0
|
375 *
|
Chris@0
|
376 * @return array
|
Chris@0
|
377 * An array of mapping between source and destination identifiers.
|
Chris@0
|
378 */
|
Chris@0
|
379 public function getIdMap() {
|
Chris@0
|
380 return $this->idMap;
|
Chris@0
|
381 }
|
Chris@0
|
382
|
Chris@0
|
383 /**
|
Chris@0
|
384 * Recalculates the hash for the row.
|
Chris@0
|
385 */
|
Chris@0
|
386 public function rehash() {
|
Chris@0
|
387 $this->idMap['original_hash'] = $this->idMap['hash'];
|
Chris@0
|
388 $this->idMap['hash'] = hash('sha256', serialize($this->source));
|
Chris@0
|
389 }
|
Chris@0
|
390
|
Chris@0
|
391 /**
|
Chris@0
|
392 * Checks whether the row has changed compared to the original ID map.
|
Chris@0
|
393 *
|
Chris@0
|
394 * @return bool
|
Chris@0
|
395 * TRUE if the row has changed, FALSE otherwise. If setIdMap() was not
|
Chris@0
|
396 * called, this always returns FALSE.
|
Chris@0
|
397 */
|
Chris@0
|
398 public function changed() {
|
Chris@0
|
399 return $this->idMap['original_hash'] != $this->idMap['hash'];
|
Chris@0
|
400 }
|
Chris@0
|
401
|
Chris@0
|
402 /**
|
Chris@0
|
403 * Returns if this row needs an update.
|
Chris@0
|
404 *
|
Chris@0
|
405 * @return bool
|
Chris@0
|
406 * TRUE if the row needs updating, FALSE otherwise.
|
Chris@0
|
407 */
|
Chris@0
|
408 public function needsUpdate() {
|
Chris@0
|
409 return $this->idMap['source_row_status'] == MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
|
Chris@0
|
410 }
|
Chris@0
|
411
|
Chris@0
|
412 /**
|
Chris@0
|
413 * Returns the hash for the source values..
|
Chris@0
|
414 *
|
Chris@0
|
415 * @return mixed
|
Chris@0
|
416 * The hash of the source values.
|
Chris@0
|
417 */
|
Chris@0
|
418 public function getHash() {
|
Chris@0
|
419 return $this->idMap['hash'];
|
Chris@0
|
420 }
|
Chris@0
|
421
|
Chris@0
|
422 /**
|
Chris@0
|
423 * Reports whether this row is a stub.
|
Chris@0
|
424 *
|
Chris@0
|
425 * @return bool
|
Chris@0
|
426 * The current stub value.
|
Chris@0
|
427 */
|
Chris@0
|
428 public function isStub() {
|
Chris@0
|
429 return $this->isStub;
|
Chris@0
|
430 }
|
Chris@0
|
431
|
Chris@0
|
432 }
|