fields.inc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. <?php
  2. /**
  3. * @file
  4. * Support for processing entity fields
  5. */
  6. class MigrateFieldsEntityHandler extends MigrateDestinationHandler {
  7. public function __construct() {
  8. $this->registerTypes(array('entity'));
  9. }
  10. /**
  11. * Implementation of MigrateDestinationHandler::fields().
  12. *
  13. * @param $entity_type
  14. * The entity type (node, user, etc.) for which to list fields.
  15. * @param $bundle
  16. * The bundle (article, blog, etc.), if any, for which to list fields.
  17. * @param Migration $migration
  18. * Optionally, the migration providing the context.
  19. * @return array
  20. * An array keyed by field name, with field descriptions as values.
  21. */
  22. public function fields($entity_type, $bundle, $migration = NULL) {
  23. $fields = array();
  24. $field_instance_info = field_info_instances($entity_type, $bundle);
  25. foreach ($field_instance_info as $machine_name => $instance) {
  26. $field_info = field_info_field($machine_name);
  27. $type = $field_info['type'];
  28. $fields[$machine_name] = t('Field:') . ' ' . $instance['label'] .
  29. ' (' . $field_info['type'] . ')';
  30. // Look for subfields
  31. $class_list = _migrate_class_list('MigrateFieldHandler');
  32. $disabled = unserialize(variable_get('migrate_disabled_handlers', serialize(array())));
  33. foreach ($class_list as $class_name => $handler) {
  34. if (!in_array($class_name, $disabled) && $handler->handlesType($type)
  35. && method_exists($handler, 'fields')) {
  36. migrate_instrument_start($class_name . '->fields');
  37. $subfields = call_user_func(array($handler, 'fields'), $type,
  38. $machine_name, $migration);
  39. migrate_instrument_stop($class_name . '->fields');
  40. foreach ($subfields as $subfield_name => $subfield_label) {
  41. $fields[$machine_name . ':' . $subfield_name] = $subfield_label;
  42. }
  43. }
  44. }
  45. }
  46. return $fields;
  47. }
  48. public function prepare($entity, stdClass $row) {
  49. migrate_instrument_start('MigrateDestinationEntity->prepareFields');
  50. // Look for Field API fields attached to this destination and handle appropriately
  51. $migration = Migration::currentMigration();
  52. $destination = $migration->getDestination();
  53. $entity_type = $destination->getEntityType();
  54. $bundle = $destination->getBundle();
  55. $instances = field_info_instances($entity_type, $bundle);
  56. foreach ($instances as $machine_name => $instance) {
  57. if (property_exists($entity, $machine_name)) {
  58. // Normalize to an array
  59. if (!is_array($entity->$machine_name)) {
  60. $entity->$machine_name = array($entity->$machine_name);
  61. }
  62. $field_info = field_info_field($machine_name);
  63. $entity->$machine_name = migrate_field_handler_invoke_all($entity, $field_info,
  64. $instance, $entity->$machine_name);
  65. }
  66. }
  67. migrate_instrument_stop('MigrateDestinationEntity->prepareFields');
  68. }
  69. public function complete($entity, stdClass $row) {
  70. migrate_instrument_start('MigrateDestinationEntity->completeFields');
  71. // Look for Field API fields attached to this destination and handle appropriately
  72. $migration = Migration::currentMigration();
  73. $destination = $migration->getDestination();
  74. $entity_type = $destination->getEntityType();
  75. $bundle = $destination->getBundle();
  76. $instances = field_info_instances($entity_type, $bundle);
  77. foreach ($instances as $machine_name => $instance) {
  78. if (property_exists($entity, $machine_name)) {
  79. // Normalize to an array
  80. if (!is_array($entity->$machine_name)) {
  81. $entity->$machine_name = array($entity->$machine_name);
  82. }
  83. $field_info = field_info_field($machine_name);
  84. migrate_field_handler_invoke_all($entity, $field_info,
  85. $instance, $entity->$machine_name, 'complete');
  86. }
  87. }
  88. migrate_instrument_stop('MigrateDestinationEntity->completeFields');
  89. }
  90. }
  91. abstract class MigrateFieldHandler extends MigrateHandler {
  92. // Derived classes are expected to implement one or both of the prepare/complete
  93. // handlers.
  94. // abstract public function prepare($entity, array $field_info, array $instance, array $values);
  95. // abstract public function complete($entity, array $field_info, array $instance, array $values);
  96. /**
  97. * Determine the language of the field
  98. *
  99. * @param $entity
  100. * @param $field_info
  101. * @param $arguments
  102. * @return string language code
  103. */
  104. function getFieldLanguage($entity, $field_info, array $arguments) {
  105. $migration = Migration::currentMigration();
  106. switch (TRUE) {
  107. case !field_is_translatable($migration->getDestination()->getEntityType(), $field_info):
  108. return LANGUAGE_NONE;
  109. case isset($arguments['language']):
  110. return $arguments['language'];
  111. case !empty($entity->language) && $entity->language != LANGUAGE_NONE:
  112. return $entity->language;
  113. break;
  114. default:
  115. return $migration->getDestination()->getLanguage();
  116. }
  117. }
  118. }
  119. /**
  120. * Base class for creating field handlers for fields with a single value.
  121. *
  122. * To use this class just extend it and pass key where the field's value should
  123. * be stored to the constructor, then register the type(s):
  124. * @code
  125. * class MigrateLinkFieldHandler extends MigrateSimpleFieldHandler {
  126. * public function __construct() {
  127. * parent::__construct('url');
  128. * $this->registerTypes(array('link'));
  129. * }
  130. * }
  131. * @endcode
  132. */
  133. abstract class MigrateSimpleFieldHandler extends MigrateFieldHandler {
  134. protected $fieldValueKey = 'value';
  135. protected $skipEmpty = FALSE;
  136. /**
  137. * Construct a simple field handler.
  138. *
  139. * @param $options
  140. * Array of options (rather than unamed parameters so you don't have to
  141. * what TRUE or FALSE means). The following keys are used:
  142. * - 'value_key' string with the name of the key in the fields value array.
  143. * - 'skip_empty' Boolean indicating that empty values should not be saved.
  144. */
  145. public function __construct($options = array()) {
  146. if (isset($options['value_key'])) {
  147. $this->fieldValueKey = $options['value_key'];
  148. }
  149. if (isset($options['skip_empty'])) {
  150. $this->skipEmpty = $options['skip_empty'];
  151. }
  152. }
  153. public function prepare($entity, array $field_info, array $instance, array $values) {
  154. $arguments = array();
  155. if (isset($values['arguments'])) {
  156. $arguments = $values['arguments'];
  157. unset($values['arguments']);
  158. }
  159. $language = $this->getFieldLanguage($entity, $field_info, $arguments);
  160. // Let the derived class skip empty values.
  161. if ($this->skipEmpty) {
  162. $values = array_filter($values, array($this, 'notNull'));
  163. }
  164. // Setup the Field API array for saving.
  165. $delta = 0;
  166. foreach ($values as $value) {
  167. if (is_array($language)) {
  168. $current_language = $language[$delta];
  169. }
  170. else {
  171. $current_language = $language;
  172. }
  173. $return[$current_language][] = array($this->fieldValueKey => $value);
  174. $delta++;
  175. }
  176. return isset($return) ? $return : NULL;
  177. }
  178. /**
  179. * Returns TRUE only for values which are not NULL.
  180. *
  181. * @param $value
  182. * @return bool
  183. */
  184. protected function notNull($value) {
  185. return !is_null($value);
  186. }
  187. }
  188. class MigrateTextFieldHandler extends MigrateFieldHandler {
  189. public function __construct() {
  190. $this->registerTypes(array('text', 'text_long', 'text_with_summary'));
  191. }
  192. static function arguments($summary = NULL, $format = NULL, $language = NULL) {
  193. $arguments = array();
  194. if (!is_null($summary)) {
  195. $arguments['summary'] = $summary;
  196. }
  197. if (!is_null($format)) {
  198. $arguments['format'] = $format;
  199. }
  200. if (!is_null($language)) {
  201. $arguments['language'] = $language;
  202. }
  203. return $arguments;
  204. }
  205. public function fields($type) {
  206. $fields = array();
  207. if ($type == 'text_with_summary') {
  208. $fields['summary'] = t('Subfield: Summary of field contents');
  209. }
  210. $fields += array(
  211. 'format' => t('Subfield: Text format for the field'),
  212. 'language' => t('Subfield: Language for the field'),
  213. );
  214. return $fields;
  215. }
  216. public function prepare($entity, array $field_info, array $instance, array $values) {
  217. if (isset($values['arguments'])) {
  218. $arguments = $values['arguments'];
  219. unset($values['arguments']);
  220. }
  221. else {
  222. $arguments = array();
  223. }
  224. $migration = Migration::currentMigration();
  225. $destination = $migration->getDestination();
  226. $language = $this->getFieldLanguage($entity, $field_info, $arguments);
  227. $max_length = isset($field_info['settings']['max_length']) ?
  228. $field_info['settings']['max_length'] : 0;
  229. // Setup the standard Field API array for saving.
  230. $delta = 0;
  231. foreach ($values as $value) {
  232. $item = array();
  233. if (isset($arguments['summary'])) {
  234. if (is_array($arguments['summary'])) {
  235. $item['summary'] = $arguments['summary'][$delta];
  236. }
  237. else {
  238. $item['summary'] = $arguments['summary'];
  239. }
  240. }
  241. if (isset($arguments['format'])) {
  242. if (is_array($arguments['format'])) {
  243. $format = $arguments['format'][$delta];
  244. }
  245. else {
  246. $format = $arguments['format'];
  247. }
  248. }
  249. else {
  250. $format = $destination->getTextFormat();
  251. }
  252. $item['format'] = $item['value_format'] = $format;
  253. // Make sure the value will fit
  254. if ($max_length) {
  255. $item['value'] = drupal_substr($value, 0, $max_length);
  256. if (!empty($arguments['track_overflow'])) {
  257. $value_length = drupal_strlen($value);
  258. if ($value_length > $max_length) {
  259. $migration->saveMessage(
  260. t('Value for field !field exceeds max length of !max_length, actual length is !length',
  261. array('!field' => $instance['field_name'], '!max_length' => $max_length,
  262. '!length' => $value_length)),
  263. Migration::MESSAGE_INFORMATIONAL);
  264. }
  265. }
  266. }
  267. else {
  268. $item['value'] = $value;
  269. }
  270. if (is_array($language)) {
  271. $current_language = $language[$delta];
  272. }
  273. else {
  274. $current_language = $language;
  275. }
  276. $return[$current_language][] = $item;
  277. $delta++;
  278. }
  279. return isset($return) ? $return : NULL;
  280. }
  281. }
  282. class MigrateValueFieldHandler extends MigrateSimpleFieldHandler {
  283. public function __construct() {
  284. parent::__construct(array(
  285. 'value_key' => 'value',
  286. 'skip_empty' => FALSE,
  287. ));
  288. $this->registerTypes(array('value', 'list', 'list_boolean', 'list_integer',
  289. 'list_float', 'list_text', 'number_integer', 'number_decimal', 'number_float'));
  290. }
  291. }
  292. class MigrateTaxonomyTermReferenceFieldHandler extends MigrateFieldHandler {
  293. public function __construct() {
  294. $this->registerTypes(array('taxonomy_term_reference'));
  295. }
  296. public function fields($type) {
  297. return array(
  298. 'source_type' => t('Option: Set to \'tid\' when the value is a source ID'),
  299. 'create_term' => t('Option: Set to TRUE to create referenced terms when necessary')
  300. );
  301. }
  302. public function prepare($entity, array $field_info, array $instance, array $values) {
  303. if (isset($values['arguments'])) {
  304. $arguments = $values['arguments'];
  305. unset($values['arguments']);
  306. }
  307. else {
  308. $arguments = array();
  309. }
  310. if (empty($values[0])) {
  311. $values = array();
  312. }
  313. $tids = array();
  314. if (isset($arguments['source_type']) && $arguments['source_type'] == 'tid') {
  315. // Nothing to do. We have tids already.
  316. $tids = $values;
  317. }
  318. elseif ($values) {
  319. // Get the vocabulary for this term
  320. if (isset($field_info['settings']['allowed_values'][0]['vid'])) {
  321. $vid = $field_info['settings']['allowed_values'][0]['vid'];
  322. }
  323. else {
  324. $vocab_name = $field_info['settings']['allowed_values'][0]['vocabulary'];
  325. $names = taxonomy_vocabulary_get_names();
  326. $vid = $names[$vocab_name]->vid;
  327. }
  328. // Cannot use taxonomy_term_load_multiple() since we have an array of names.
  329. // It wants a singular value. This query may return case-insensitive
  330. // matches.
  331. $existing_terms = db_select('taxonomy_term_data', 'td')
  332. ->fields('td', array('tid', 'name'))
  333. ->condition('td.name', $values, 'IN')
  334. ->condition('td.vid', $vid)
  335. ->execute()
  336. ->fetchAllKeyed(1, 0);
  337. foreach ($values as $value) {
  338. if (isset($existing_terms[$value])) {
  339. $tids[] = $existing_terms[$value];
  340. }
  341. elseif (!empty($arguments['create_term'])) {
  342. $new_term = new stdClass();
  343. $new_term->vid = $vid;
  344. $new_term->name = $value;
  345. taxonomy_term_save($new_term);
  346. $tids[] = $new_term->tid;
  347. }
  348. }
  349. }
  350. $language = $this->getFieldLanguage($entity, $field_info, $arguments);
  351. $result = array();
  352. $delta = 0;
  353. foreach ($tids as $tid) {
  354. if (is_array($language)) {
  355. $current_language = $language[$delta];
  356. }
  357. else {
  358. $current_language = $language;
  359. }
  360. $result[$current_language][] = array('tid' => $tid);
  361. $delta++;
  362. }
  363. return $result;
  364. }
  365. }
  366. /**
  367. * The next generation of file field handler. This class focuses on the file
  368. * field itself, and offloads understanding of obtaining the actual file and
  369. * dealing with the file entity to an embedded MigrateFileInterface instance.
  370. */
  371. abstract class MigrateFileFieldBaseHandler extends MigrateFieldHandler {
  372. /**
  373. * Implementation of MigrateFieldHandler::fields().
  374. *
  375. * @param $type
  376. * The file field type - 'file', 'image', etc.
  377. * @param $parent_field
  378. * Name of the parent field.
  379. * @param Migration $migration
  380. * The migration context for the parent field. We can look at the mappings
  381. * and determine which subfields are relevant.
  382. * @return array
  383. */
  384. public function fields($type, $parent_field, $migration = NULL) {
  385. $fields = array(
  386. 'file_class' => t('Option: <a href="@doc">Implementation of MigrateFile to use</a>',
  387. array('@doc' => 'http://drupal.org/node/1540106#file_class')),
  388. 'language' => t('Subfield: Language for the field'),
  389. );
  390. // If we can identify the file class mapped to this field, pick up the
  391. // subfields specific to that class.
  392. if ($migration) {
  393. $field_mappings = $migration->getFieldMappings();
  394. $class_mapping = $parent_field . ':file_class';
  395. if (isset($field_mappings[$class_mapping])) {
  396. $mapping = $field_mappings[$class_mapping];
  397. $file_class = $mapping->getDefaultValue();
  398. }
  399. }
  400. if (!isset($file_class)) {
  401. $file_class = 'MigrateFileUri';
  402. }
  403. $fields += call_user_func(array($file_class, 'fields'));
  404. return $fields;
  405. }
  406. /**
  407. * Implementation of MigrateFieldHandler::prepare().
  408. *
  409. * Prepare file data for saving as a Field API file field.
  410. *
  411. * @return array
  412. * Field API array suitable for inserting in the destination object.
  413. */
  414. public function prepare($entity, array $field_info, array $instance, array $values) {
  415. if (isset($values['arguments'])) {
  416. $arguments = $values['arguments'];
  417. unset($values['arguments']);
  418. }
  419. else {
  420. $arguments = array();
  421. }
  422. $language = $this->getFieldLanguage($entity, $field_info, $arguments);
  423. $migration = Migration::currentMigration();
  424. // One can override the source class via CLI or drushrc.php (the
  425. // option is named file_function for historical reasons)
  426. if ($migration->getOption('file_function')) {
  427. $file_class = $migration->getOption('file_function');
  428. }
  429. elseif (!empty($arguments['file_class'])) {
  430. $file_class = $arguments['file_class'];
  431. }
  432. else {
  433. $file_class = 'MigrateFileUri';
  434. }
  435. // If a destination directory (relative to the Drupal public files directory)
  436. // is not explicitly provided, use the default for the field.
  437. if (empty($arguments['destination_dir'])) {
  438. $arguments['destination_dir'] = $this->destinationDir($field_info, $instance);
  439. }
  440. $return = array();
  441. $delta = 0;
  442. // Note that what $value represents depends on the file class -
  443. // MigrateFileUri expects a filespec/URI, MigrateFileFid expects a file ID,
  444. // etc.
  445. foreach ($values as $value) {
  446. if ($value) {
  447. // If the parent entity doesn't have an explicit uid, give ownership
  448. // to the anonymous account
  449. $owner = isset($entity->uid) ? $entity->uid : 0;
  450. // Call the MigrateFileInterface implementation to do the real work
  451. $source = new $file_class($arguments);
  452. $file = $source->processFile($value, $owner);
  453. // Assuming we got back a valid file ID, build the proper field
  454. // array out of it. We assume that if we did not get back a fid, the
  455. // MigrateFile class has saved a message indicating why.
  456. if ($file) {
  457. $field_array = array('fid' => $file->fid);
  458. $return[$language][] = $this->buildFieldArray($field_array, $arguments, $delta);
  459. }
  460. }
  461. $delta++;
  462. }
  463. return $return;
  464. }
  465. /**
  466. * Determine where the migrated file should go.
  467. *
  468. * @param $field_info
  469. * Field API info on the general field.
  470. * @param $instance
  471. * Field API info on the field instance for this entity type.
  472. * @return string
  473. * Directory relative to the Drupal public files directory.
  474. */
  475. protected function destinationDir($field_info, $instance) {
  476. $destination_dir = file_field_widget_uri($field_info, $instance);
  477. return $destination_dir;
  478. }
  479. /**
  480. * Add any type-specific subfields to a file field array.
  481. *
  482. * @param $field_array
  483. * The field array so far (generally will just contain a fid).
  484. * @param $arguments
  485. * Array of arguments passed to the field handler, from which we'll extract
  486. * our own subfields.
  487. * @param $delta
  488. * Index of field values being worked on, for pulling the corresponding
  489. * subfield values if we have an array of them.
  490. */
  491. abstract protected function buildFieldArray($field_array, $arguments, $delta);
  492. }
  493. /**
  494. * Handle for file fields.
  495. */
  496. class MigrateFileFieldHandler extends MigrateFileFieldBaseHandler {
  497. public function __construct() {
  498. $this->registerTypes(array('file'));
  499. }
  500. /**
  501. * Implementation of MigrateFieldHandler::fields().
  502. * Note that file and image fields support slightly different field lists.
  503. *
  504. * @param $type
  505. * The file field type - 'file' or 'image'
  506. * @param $parent_field
  507. * Name of the parent field.
  508. * @param Migration $migration
  509. * The migration context for the parent field. We can look at the mappings
  510. * and determine which subfields are relevant.
  511. * @return array
  512. */
  513. public function fields($type, $parent_field, $migration = NULL) {
  514. $fields = parent::fields($type, $parent_field, $migration);
  515. $fields += array(
  516. 'description' => t('Subfield: String to be used as the description value'),
  517. 'display' => t('Subfield: String to be used as the display value'),
  518. );
  519. return $fields;
  520. }
  521. /**
  522. * Implementation of MigrateFileFieldBaseHandler::buildFieldArray().
  523. */
  524. protected function buildFieldArray($field_array, $arguments, $delta) {
  525. if (isset($arguments['description'])) {
  526. if (is_array($arguments['description'])) {
  527. $field_array['description'] = $arguments['description'][$delta];
  528. }
  529. else {
  530. $field_array['description'] = $arguments['description'];
  531. }
  532. }
  533. else {
  534. $field_array['description'] = '';
  535. }
  536. if (isset($arguments['display'])) {
  537. if (is_array($arguments['display'])) {
  538. $field_array['display'] = $arguments['display'][$delta];
  539. }
  540. else {
  541. $field_array['display'] = $arguments['display'];
  542. }
  543. }
  544. else {
  545. $field_array['display'] = 1;
  546. }
  547. return $field_array;
  548. }
  549. }
  550. /**
  551. * Handle for image fields;
  552. */
  553. class MigrateImageFieldHandler extends MigrateFileFieldBaseHandler {
  554. public function __construct() {
  555. $this->registerTypes(array('image'));
  556. }
  557. /**
  558. * Implementation of MigrateFieldHandler::fields().
  559. * Note that file and image fields support slightly different field lists.
  560. *
  561. * @param $type
  562. * The file field type - 'file' or 'image'
  563. * @param $parent_field
  564. * Name of the parent field.
  565. * @param Migration $migration
  566. * The migration context for the parent field. We can look at the mappings
  567. * and determine which subfields are relevant.
  568. * @return array
  569. */
  570. public function fields($type, $parent_field, $migration = NULL) {
  571. $fields = parent::fields($type, $parent_field, $migration);
  572. $fields += array(
  573. 'alt' => t('Subfield: String to be used as the alt value'),
  574. 'title' => t('Subfield: String to be used as the title value'),
  575. );
  576. return $fields;
  577. }
  578. /**
  579. * Implementation of MigrateFileFieldBaseHandler::buildFieldArray().
  580. */
  581. protected function buildFieldArray($field_array, $arguments, $delta) {
  582. if (isset($arguments['alt'])) {
  583. if (is_array($arguments['alt'])) {
  584. $field_array['alt'] = $arguments['alt'][$delta];
  585. }
  586. else {
  587. $field_array['alt'] = $arguments['alt'];
  588. }
  589. }
  590. if (isset($arguments['title'])) {
  591. if (is_array($arguments['title'])) {
  592. $field_array['title'] = $arguments['title'][$delta];
  593. }
  594. else {
  595. $field_array['title'] = $arguments['title'];
  596. }
  597. }
  598. return $field_array;
  599. }
  600. }
  601. class MigrateNodeReferenceFieldHandler extends MigrateSimpleFieldHandler {
  602. public function __construct() {
  603. parent::__construct(array(
  604. 'value_key' => 'nid',
  605. 'skip_empty' => TRUE,
  606. ));
  607. $this->registerTypes(array('node_reference'));
  608. }
  609. }
  610. class MigrateUserReferenceFieldHandler extends MigrateSimpleFieldHandler {
  611. public function __construct() {
  612. parent::__construct(array(
  613. 'value_key' => 'uid',
  614. 'skip_empty' => TRUE,
  615. ));
  616. $this->registerTypes(array('user_reference'));
  617. }
  618. }