entity_api.inc 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. /**
  3. * @file
  4. * Support for entity types implementing the Entity API.
  5. */
  6. /**
  7. * Destination class implementing migration into entity types.
  8. *
  9. * To make entity properties that correspond to columns in the entity's base
  10. * table available as FieldMapping destinations, they must be present in Entity
  11. * API's entity property info and have setter callbacks defined. Because the
  12. * EntityDefaultMetadataController doesn't add setter callbacks to the default
  13. * entity property info it produces, the custom entity needs to provide this
  14. * either in an implementation of hook_entity_property_info(), or via EntityAPI
  15. * in a custom metadata controller class.
  16. */
  17. class MigrateDestinationEntityAPI extends MigrateDestinationEntity {
  18. /**
  19. * Info about the current entity type.
  20. *
  21. * @var array
  22. */
  23. protected $info;
  24. /**
  25. * Name of the entity id key (for example, nid for nodes).
  26. *
  27. * @var string
  28. */
  29. protected $id;
  30. /**
  31. * Name of the entity revision key (for example, vid for nodes).
  32. *
  33. * @var string
  34. */
  35. protected $revision;
  36. /**
  37. * Gets the schema for the base key(s) of an entity type.
  38. *
  39. * @param string $entity_type
  40. * A Drupal entity type.
  41. */
  42. static public function getKeySchema($entity_type = NULL) {
  43. // Migrate UI invokes $destination->getKeySchema() without any parameters.
  44. if (!$entity_type) {
  45. if (isset($this)) {
  46. if ($this instanceof MigrateDestination) {
  47. $entity_type = $this->entityType;
  48. }
  49. elseif ($this instanceof Migration) {
  50. $entity_type = $this->destination->entityType;
  51. }
  52. }
  53. else {
  54. return array();
  55. }
  56. }
  57. $info = entity_get_info($entity_type);
  58. $schema = drupal_get_schema($info['base table']);
  59. $key = isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'];
  60. $key_schema = $schema['fields'][$key];
  61. $revision_key = isset($info['entity keys']['revision']) ? $info['entity keys']['revision'] : NULL;
  62. $revision_schema = empty($revision_key) ? NULL : $schema['fields'][$revision_key];
  63. // We can't have any form of serial fields here, since the mapping table
  64. // already has it's own.
  65. $key_schema['auto_increment'] = FALSE;
  66. if ($key_schema['type'] == 'serial') {
  67. $key_schema['type'] = 'int';
  68. }
  69. $return = array($key => $key_schema);
  70. if (!empty($revision_key)) {
  71. $return[$revision_key] = $revision_schema;
  72. }
  73. return $return;
  74. }
  75. /**
  76. * Return an options array (language, text_format), used for creating fields.
  77. *
  78. * @param string $language
  79. * @param string $text_format
  80. */
  81. static public function options($language, $text_format) {
  82. return compact('language', 'text_format');
  83. }
  84. /**
  85. * Basic initialization
  86. *
  87. * @param string $entity_type
  88. * @param string $bundle
  89. * @param array $options
  90. * Options (language, text_format) used for creating fields.
  91. */
  92. public function __construct($entity_type, $bundle, array $options = array()) {
  93. parent::__construct($entity_type, $bundle, $options);
  94. $this->info = entity_get_info($entity_type);
  95. $this->id = isset($this->info['entity keys']['name']) ? $this->info['entity keys']['name'] : $this->info['entity keys']['id'];
  96. $this->revision = isset($this->info['entity keys']['revision']) ? $this->info['entity keys']['revision'] : NULL;
  97. }
  98. /**
  99. * Returns a list of fields available to be mapped for entities attached to
  100. * a particular bundle.
  101. *
  102. * @param Migration $migration
  103. * Optionally, the migration containing this destination.
  104. * @return array
  105. * Keys: machine names of the fields (to be passed to addFieldMapping)
  106. * Values: Human-friendly descriptions of the fields.
  107. */
  108. public function fields($migration = NULL) {
  109. $properties = entity_get_property_info($this->entityType);
  110. $fields = array();
  111. foreach ($properties['properties'] as $name => $property_info) {
  112. if (isset($property_info['setter callback'])) {
  113. $fields[$name] = $property_info['description'];
  114. }
  115. }
  116. // Then add in anything provided by handlers
  117. $fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle);
  118. return $fields;
  119. }
  120. /**
  121. * Deletes multiple entities.
  122. *
  123. * @param array $ids
  124. * An array of entity ids of the entities to delete.
  125. */
  126. public function bulkRollback(array $ids) {
  127. migrate_instrument_start('entity_delete_multiple');
  128. $this->prepareRollback($ids);
  129. $result = entity_delete_multiple($this->entityType, $ids);
  130. $this->completeRollback($ids);
  131. migrate_instrument_stop('entity_delete_multiple');
  132. return $result;
  133. }
  134. /**
  135. * Imports a single entity.
  136. *
  137. * @param stdClass $entity
  138. * Generic entity object, refilled with any fields mapped in the Migration.
  139. * @param stdClass $row
  140. * Raw source data object - passed through to prepare/complete handlers.
  141. *
  142. * @return array
  143. * An array of key fields (entity id, and revision id if applicable) of the
  144. * entity that was saved if successful. FALSE on failure.
  145. */
  146. public function import(stdClass $entity, stdClass $row) {
  147. $migration = Migration::currentMigration();
  148. // Updating previously-migrated content?
  149. if (isset($row->migrate_map_destid1)) {
  150. if (isset($entity->{$this->id})) {
  151. if ($entity->{$this->id} != $row->migrate_map_destid1) {
  152. throw new MigrateException(t("Incoming id !id and map destination id !destid1 don't match",
  153. array('!id' => $entity->{$this->id}, '!destid1' => $row->migrate_map_destid1)));
  154. }
  155. }
  156. else {
  157. $entity->{$this->id} = $row->migrate_map_destid1;
  158. }
  159. }
  160. elseif ($migration->getSystemOfRecord() == Migration::SOURCE) {
  161. unset($entity->{$this->id});
  162. }
  163. if (isset($row->migrate_map_destid2)) {
  164. if (isset($entity->{$this->revision})) {
  165. if ($entity->{$this->revision} != $row->migrate_map_destid2) {
  166. throw new MigrateException(t("Incoming revision !id and map destination revision !destid2 don't match",
  167. array('!id' => $entity->{$this->revision}, '!destid2' => $row->migrate_map_destid2)));
  168. }
  169. }
  170. else {
  171. $entity->{$this->revision} = $row->migrate_map_destid2;
  172. }
  173. }
  174. if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
  175. if (!isset($entity->{$this->id})) {
  176. throw new MigrateException(t('System-of-record is DESTINATION, but no destination id provided'));
  177. }
  178. // Load the entity that's being updated, update its values, then
  179. // substitute the (fake) passed in entity with that one.
  180. $old_entity = entity_load_single($this->entityType, $entity->{$this->id});
  181. if (empty($old_entity)) {
  182. throw new MigrateException(t("Failed to load entity of type %type and id %id", array('%type' => $this->entityType, '%id' => $entity->{$this->id})));
  183. }
  184. // Prepare the entity to get the right array structure.
  185. $this->prepare($entity, $row);
  186. foreach ($entity as $field => $value) {
  187. $old_entity->$field = $entity->$field;
  188. }
  189. $entity = $old_entity;
  190. }
  191. else {
  192. // Create a real entity object, update its values with the ones we have
  193. // and pass it along.
  194. $new_entity = array();
  195. if (!empty($this->bundle) && !empty($this->info['entity keys']['bundle'])) {
  196. $new_entity[$this->info['entity keys']['bundle']] = $this->bundle;
  197. }
  198. $new_entity = entity_create($this->entityType, $new_entity);
  199. foreach ($entity as $field => $value) {
  200. $new_entity->$field = $entity->$field;
  201. }
  202. // If a destination id exists, the entity is obviously not new.
  203. if (!empty($new_entity->{$this->id}) && isset($new_entity->is_new)) {
  204. unset($new_entity->is_new);
  205. }
  206. $entity = $new_entity;
  207. $this->prepare($entity, $row);
  208. }
  209. $updating = (!empty($entity->{$this->id}) && empty($entity->is_new));
  210. migrate_instrument_start('entity_save');
  211. entity_save($this->entityType, $entity);
  212. // It's probably not worth keeping the static cache around.
  213. entity_get_controller($this->entityType)->resetCache();
  214. migrate_instrument_stop('entity_save');
  215. $this->complete($entity, $row);
  216. if (isset($entity->{$this->id}) && $entity->{$this->id} > 0) {
  217. if ($updating) {
  218. $this->numUpdated++;
  219. }
  220. else {
  221. $this->numCreated++;
  222. }
  223. $return = array($entity->{$this->id});
  224. if (isset($entity->{$this->revision}) && $entity->{$this->revision} > 0) {
  225. $return[] = array($entity->{$this->revision});
  226. }
  227. return $return;
  228. }
  229. return FALSE;
  230. }
  231. /**
  232. * Clear the field cache after an import or rollback.
  233. */
  234. public function postImport() {
  235. field_cache_clear();
  236. }
  237. public function postRollback() {
  238. field_cache_clear();
  239. }
  240. }