Chris@0: '', Chris@0: 'hash' => '', Chris@0: 'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE, Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * Whether the source has been frozen already. Chris@0: * Chris@0: * Once frozen the source can not be changed any more. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: protected $frozen = FALSE; Chris@0: Chris@0: /** Chris@0: * The raw destination properties. Chris@0: * Chris@0: * Unlike $destination which is set by using Chris@0: * \Drupal\Component\Utility\NestedArray::setValue() this array contains Chris@0: * the destination as setDestinationProperty was called. Chris@0: * Chris@0: * @var array Chris@0: * The raw destination. Chris@0: * Chris@0: * @see getRawDestination() Chris@0: */ Chris@0: protected $rawDestination = []; Chris@0: Chris@0: /** Chris@0: * TRUE when this row is a stub. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: protected $isStub = FALSE; Chris@0: Chris@0: /** Chris@0: * The empty destination properties. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $emptyDestinationProperties = []; Chris@0: Chris@0: /** Chris@18: * Constructs a \Drupal\migrate\Row object. Chris@0: * Chris@0: * @param array $values Chris@0: * An array of values to add as properties on the object. Chris@0: * @param array $source_ids Chris@0: * An array containing the IDs of the source using the keys as the field Chris@0: * names. Chris@0: * @param bool $is_stub Chris@0: * TRUE if the row being created is a stub. Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: * Thrown when a source ID property does not exist. Chris@0: */ Chris@0: public function __construct(array $values = [], array $source_ids = [], $is_stub = FALSE) { Chris@0: $this->source = $values; Chris@0: $this->sourceIds = $source_ids; Chris@0: $this->isStub = $is_stub; Chris@0: foreach (array_keys($source_ids) as $id) { Chris@0: if (!$this->hasSourceProperty($id)) { Chris@14: throw new \InvalidArgumentException("$id is defined as a source ID but has no value."); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves the values of the source identifiers. Chris@0: * Chris@0: * @return array Chris@0: * An array containing the values of the source identifiers. Returns values Chris@0: * in the same order as defined in $this->sourceIds. Chris@0: */ Chris@0: public function getSourceIdValues() { Chris@0: return array_merge($this->sourceIds, array_intersect_key($this->source, $this->sourceIds)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determines whether a source has a property. Chris@0: * Chris@0: * @param string $property Chris@0: * A property on the source. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the source has property; FALSE otherwise. Chris@0: */ Chris@0: public function hasSourceProperty($property) { Chris@0: return NestedArray::keyExists($this->source, explode(static::PROPERTY_SEPARATOR, $property)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves a source property. Chris@0: * Chris@18: * This function directly retrieves a source property. It does not unescape Chris@18: * '@' symbols. This is most useful in source plugins when you don't want to Chris@18: * worry about escaping '@' symbols. If using this in a process plugin to Chris@18: * retrieve a source property based on a configuration value, consider if the Chris@18: * ::get() function might be more appropriate, to allow the migration to Chris@18: * potentially specify a destination key as well. Chris@18: * Chris@0: * @param string $property Chris@0: * A property on the source. Chris@0: * Chris@0: * @return mixed|null Chris@0: * The found returned property or NULL if not found. Chris@0: */ Chris@0: public function getSourceProperty($property) { Chris@0: $return = NestedArray::getValue($this->source, explode(static::PROPERTY_SEPARATOR, $property), $key_exists); Chris@0: if ($key_exists) { Chris@0: return $return; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the whole source array. Chris@0: * Chris@0: * @return array Chris@0: * An array of source plugins. Chris@0: */ Chris@0: public function getSource() { Chris@0: return $this->source; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a source property. Chris@0: * Chris@0: * This can only be called from the source plugin. Chris@0: * Chris@0: * @param string $property Chris@0: * A property on the source. Chris@0: * @param mixed $data Chris@0: * The property value to set on the source. Chris@0: * Chris@0: * @throws \Exception Chris@0: */ Chris@0: public function setSourceProperty($property, $data) { Chris@0: if ($this->frozen) { Chris@0: throw new \Exception("The source is frozen and can't be changed any more"); Chris@0: } Chris@0: else { Chris@0: NestedArray::setValue($this->source, explode(static::PROPERTY_SEPARATOR, $property), $data, TRUE); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Freezes the source. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function freezeSource() { Chris@0: $this->frozen = TRUE; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Clones the row with an empty set of destination values. Chris@0: * Chris@0: * @return static Chris@0: */ Chris@0: public function cloneWithoutDestination() { Chris@0: return (new static($this->getSource(), $this->sourceIds, $this->isStub()))->freezeSource(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests if destination property exists. Chris@0: * Chris@0: * @param array|string $property Chris@0: * An array of properties on the destination. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the destination property exists. Chris@0: */ Chris@0: public function hasDestinationProperty($property) { Chris@0: return NestedArray::keyExists($this->destination, explode(static::PROPERTY_SEPARATOR, $property)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets destination properties. Chris@0: * Chris@0: * @param string $property Chris@0: * The name of the destination property. Chris@0: * @param mixed $value Chris@0: * The property value to set on the destination. Chris@0: */ Chris@0: public function setDestinationProperty($property, $value) { Chris@0: $this->rawDestination[$property] = $value; Chris@0: NestedArray::setValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property), $value, TRUE); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Removes destination property. Chris@0: * Chris@0: * @param string $property Chris@0: * The name of the destination property. Chris@0: */ Chris@0: public function removeDestinationProperty($property) { Chris@0: unset($this->rawDestination[$property]); Chris@0: NestedArray::unsetValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a destination to be empty. Chris@0: * Chris@0: * @param string $property Chris@0: * The destination property. Chris@0: */ Chris@0: public function setEmptyDestinationProperty($property) { Chris@0: $this->emptyDestinationProperties[] = $property; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the empty destination properties. Chris@0: * Chris@0: * @return array Chris@0: * An array of destination properties. Chris@0: */ Chris@0: public function getEmptyDestinationProperties() { Chris@0: return $this->emptyDestinationProperties; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the whole destination array. Chris@0: * Chris@0: * @return array Chris@0: * An array of destination values. Chris@0: */ Chris@0: public function getDestination() { Chris@0: return $this->destination; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the raw destination. Rarely necessary. Chris@0: * Chris@0: * For example calling setDestination('foo/bar', 'baz') results in Chris@0: * @code Chris@0: * $this->destination['foo']['bar'] = 'baz'; Chris@0: * $this->rawDestination['foo/bar'] = 'baz'; Chris@0: * @endcode Chris@0: * Chris@0: * @return array Chris@0: * The raw destination values. Chris@0: */ Chris@0: public function getRawDestination() { Chris@0: return $this->rawDestination; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the value of a destination property. Chris@0: * Chris@18: * This function directly returns a destination property. The property name Chris@18: * should not begin with an @ symbol. This is most useful in a destination Chris@18: * plugin. Chris@18: * Chris@0: * @param string $property Chris@0: * The name of a property on the destination. Chris@0: * Chris@0: * @return mixed Chris@0: * The destination value. Chris@0: */ Chris@0: public function getDestinationProperty($property) { Chris@0: return NestedArray::getValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property)); Chris@0: } Chris@0: Chris@0: /** Chris@18: * Retrieve a source or destination property. Chris@18: * Chris@18: * If the property key begins with '@' return a destination property, Chris@18: * otherwise return a source property. the '@' symbol itself can be escaped Chris@18: * as '@@'. Returns NULL if property is not found. Useful in process plugins Chris@18: * to retrieve a row property specified in a configuration key which may be Chris@18: * either a source or destination property prefixed with an '@'. Chris@18: * Chris@18: * @param string $property Chris@18: * The property to get. Chris@18: * Chris@18: * @return mixed|null Chris@18: * The requested property. Chris@18: */ Chris@18: public function get($property) { Chris@18: $values = $this->getMultiple([$property]); Chris@18: return reset($values); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Retrieve multiple source and destination properties at once. Chris@18: * Chris@18: * @param string[] $properties Chris@18: * An array of values to retrieve, with destination values prefixed with @. Chris@18: * Chris@18: * @return array Chris@18: * An array of property values, keyed by property name. Chris@18: */ Chris@18: public function getMultiple(array $properties) { Chris@18: $return = []; Chris@18: foreach ($properties as $orig_property) { Chris@18: $property = $orig_property; Chris@18: $is_source = TRUE; Chris@18: if ($property[0] == '@') { Chris@18: $property = preg_replace_callback('/^(@?)((?:@@)*)([^@]|$)/', function ($matches) use (&$is_source) { Chris@18: // If there are an odd number of @ in the beginning, it's a Chris@18: // destination. Chris@18: $is_source = empty($matches[1]); Chris@18: // Remove the possible escaping and do not lose the terminating Chris@18: // non-@ either. Chris@18: return str_replace('@@', '@', $matches[2]) . $matches[3]; Chris@18: }, $property); Chris@18: } Chris@18: if ($is_source) { Chris@18: $return[$orig_property] = $this->getSourceProperty($property); Chris@18: } Chris@18: else { Chris@18: $return[$orig_property] = $this->getDestinationProperty($property); Chris@18: } Chris@18: } Chris@18: return $return; Chris@18: } Chris@18: Chris@18: /** Chris@0: * Sets the Migrate ID mappings. Chris@0: * Chris@0: * @param array $id_map Chris@0: * An array of mappings between source ID and destination ID. Chris@0: */ Chris@0: public function setIdMap(array $id_map) { Chris@0: $this->idMap = $id_map; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves the Migrate ID mappings. Chris@0: * Chris@0: * @return array Chris@0: * An array of mapping between source and destination identifiers. Chris@0: */ Chris@0: public function getIdMap() { Chris@0: return $this->idMap; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Recalculates the hash for the row. Chris@0: */ Chris@0: public function rehash() { Chris@0: $this->idMap['original_hash'] = $this->idMap['hash']; Chris@0: $this->idMap['hash'] = hash('sha256', serialize($this->source)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks whether the row has changed compared to the original ID map. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the row has changed, FALSE otherwise. If setIdMap() was not Chris@0: * called, this always returns FALSE. Chris@0: */ Chris@0: public function changed() { Chris@0: return $this->idMap['original_hash'] != $this->idMap['hash']; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns if this row needs an update. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the row needs updating, FALSE otherwise. Chris@0: */ Chris@0: public function needsUpdate() { Chris@0: return $this->idMap['source_row_status'] == MigrateIdMapInterface::STATUS_NEEDS_UPDATE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the hash for the source values.. Chris@0: * Chris@0: * @return mixed Chris@0: * The hash of the source values. Chris@0: */ Chris@0: public function getHash() { Chris@0: return $this->idMap['hash']; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Reports whether this row is a stub. Chris@0: * Chris@0: * @return bool Chris@0: * The current stub value. Chris@0: */ Chris@0: public function isStub() { Chris@0: return $this->isStub; Chris@0: } Chris@0: Chris@0: }