fields.inc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  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. public function fields($entity_type, $bundle, $migration = NULL) {
  14. $fields = array();
  15. $field_instance_info = field_info_instances($entity_type, $bundle);
  16. foreach ($field_instance_info as $machine_name => $instance) {
  17. $field_info = field_info_field($machine_name);
  18. $type = $field_info['type'];
  19. if (user_access(MIGRATE_ACCESS_ADVANCED)) {
  20. $fields[$machine_name] = $instance['label'] . ' (' . $field_info['type'] . ')';
  21. }
  22. else {
  23. $fields[$machine_name] = $instance['label'];
  24. }
  25. // Look for subfields
  26. $class_list = _migrate_class_list('MigrateFieldHandler');
  27. $disabled = unserialize(variable_get('migrate_disabled_handlers', serialize(array())));
  28. $fields_found = FALSE;
  29. foreach ($class_list as $class_name => $handler) {
  30. if (!in_array($class_name, $disabled) && $handler->handlesType($type)
  31. && method_exists($handler, 'fields')) {
  32. migrate_instrument_start($class_name . '->fields');
  33. $subfields = call_user_func(array($handler, 'fields'), $type,
  34. $instance, $migration);
  35. migrate_instrument_stop($class_name . '->fields');
  36. foreach ($subfields as $subfield_name => $subfield_label) {
  37. $fields[$machine_name . ':' . $subfield_name] = $subfield_label;
  38. }
  39. $fields_found = TRUE;
  40. }
  41. }
  42. if (!$fields_found) {
  43. // Check the default field handler last.
  44. migrate_instrument_start('MigrateDefaultFieldHandler->fields');
  45. $subfields = call_user_func(
  46. array(new MigrateDefaultFieldHandler, 'fields'), $type, $instance,
  47. $migration);
  48. migrate_instrument_stop('MigrateDefaultFieldHandler->fields');
  49. foreach ($subfields as $subfield_name => $subfield_label) {
  50. $fields[$machine_name . ':' . $subfield_name] = $subfield_label;
  51. }
  52. }
  53. }
  54. return $fields;
  55. }
  56. public function prepare($entity, stdClass $row) {
  57. migrate_instrument_start('MigrateDestinationEntity->prepareFields');
  58. // Look for Field API fields attached to this destination and handle appropriately
  59. $migration = Migration::currentMigration();
  60. $destination = $migration->getDestination();
  61. $entity_type = $destination->getEntityType();
  62. $bundle = $destination->getBundle();
  63. $instances = field_info_instances($entity_type, $bundle);
  64. foreach ($instances as $machine_name => $instance) {
  65. if (property_exists($entity, $machine_name)) {
  66. // Normalize to an array
  67. if (!is_array($entity->$machine_name)) {
  68. $entity->$machine_name = array($entity->$machine_name);
  69. }
  70. $field_info = field_info_field($machine_name);
  71. $entity->$machine_name = migrate_field_handler_invoke_all($entity, $field_info,
  72. $instance, $entity->$machine_name);
  73. }
  74. }
  75. migrate_instrument_stop('MigrateDestinationEntity->prepareFields');
  76. }
  77. public function complete($entity, stdClass $row) {
  78. migrate_instrument_start('MigrateDestinationEntity->completeFields');
  79. // Look for Field API fields attached to this destination and handle appropriately
  80. $migration = Migration::currentMigration();
  81. $destination = $migration->getDestination();
  82. $entity_type = $destination->getEntityType();
  83. $bundle = $destination->getBundle();
  84. $instances = field_info_instances($entity_type, $bundle);
  85. foreach ($instances as $machine_name => $instance) {
  86. if (property_exists($entity, $machine_name)) {
  87. // Normalize to an array
  88. if (!is_array($entity->$machine_name)) {
  89. $entity->$machine_name = array($entity->$machine_name);
  90. }
  91. $field_info = field_info_field($machine_name);
  92. migrate_field_handler_invoke_all($entity, $field_info,
  93. $instance, $entity->$machine_name, 'complete');
  94. }
  95. }
  96. migrate_instrument_stop('MigrateDestinationEntity->completeFields');
  97. }
  98. }
  99. abstract class MigrateFieldHandler extends MigrateHandler {
  100. // Derived classes are expected to implement one or both of the prepare/complete
  101. // handlers.
  102. // abstract public function prepare($entity, array $field_info, array $instance, array $values);
  103. // abstract public function complete($entity, array $field_info, array $instance, array $values);
  104. /**
  105. * Determine the language of the field
  106. *
  107. * @param $entity
  108. * @param $field_info
  109. * @param $arguments
  110. * @return string language code
  111. */
  112. function getFieldLanguage($entity, $field_info, array $arguments) {
  113. $migration = Migration::currentMigration();
  114. switch (TRUE) {
  115. case !field_is_translatable($migration->getDestination()->getEntityType(), $field_info):
  116. return LANGUAGE_NONE;
  117. case isset($arguments['language']):
  118. return $arguments['language'];
  119. case !empty($entity->language) && $entity->language != LANGUAGE_NONE:
  120. return $entity->language;
  121. break;
  122. default:
  123. return $migration->getDestination()->getLanguage();
  124. }
  125. }
  126. }
  127. /**
  128. * A fallback field handler to do basic copying of field data.
  129. */
  130. class MigrateDefaultFieldHandler extends MigrateFieldHandler {
  131. public function __construct() {}
  132. /**
  133. * Implements MigrateFieldHandler::fields().
  134. *
  135. * @param $field_type
  136. * @param $field_instance
  137. *
  138. * @return array
  139. */
  140. public function fields($field_type, $field_instance) {
  141. $field_info = field_info_field($field_instance['field_name']);
  142. $fields = array();
  143. $first = TRUE;
  144. foreach ($field_info['columns'] as $column_name => $column_info) {
  145. // The first column is the primary value, which is mapped directly to
  146. // the field name - so, don't include it here among the subfields.
  147. if ($first) {
  148. $first = FALSE;
  149. }
  150. else {
  151. $fields[$column_name] = empty($column_info['description']) ?
  152. $column_name : $column_info['description'];
  153. }
  154. }
  155. return $fields;
  156. }
  157. /**
  158. * Implements MigrateFieldHandler::prepare().
  159. *
  160. * @param $entity
  161. * @param array $field_info
  162. * @param array $instance
  163. * @param array $values
  164. *
  165. * @return null
  166. */
  167. public function prepare($entity, array $field_info, array $instance,
  168. array $values) {
  169. $arguments = array();
  170. if (isset($values['arguments'])) {
  171. $arguments = array_filter($values['arguments']);
  172. unset($values['arguments']);
  173. }
  174. $language = $this->getFieldLanguage($entity, $field_info, $arguments);
  175. // Get the name of the primary (first) column, which is mapped separately.
  176. reset($field_info['columns']);
  177. $primary_column = key($field_info['columns']);
  178. // Setup the standard Field API array for saving.
  179. $delta = 0;
  180. foreach ($values as $value) {
  181. // Handle multivalue arguments (especially for subfields).
  182. $delta_arguments = array();
  183. foreach ($arguments as $name => $argument) {
  184. if (is_array($argument) && array_key_exists($delta, $argument)) {
  185. $delta_arguments[$name] = $argument[$delta];
  186. }
  187. else {
  188. $delta_arguments[$name] = $argument;
  189. }
  190. }
  191. $return[$language][$delta] = array($primary_column => $value) +
  192. array_intersect_key($delta_arguments, $field_info['columns']);
  193. $delta++;
  194. }
  195. return isset($return) ? $return : NULL;
  196. }
  197. /**
  198. * Overrides MigrateHandler::handlesType().
  199. *
  200. * @param string $type
  201. *
  202. * @return bool
  203. */
  204. public function handlesType($type) {
  205. // We claim to handle any type.
  206. return TRUE;
  207. }
  208. }
  209. /**
  210. * Base class for creating field handlers for fields with a single value.
  211. *
  212. * To use this class just extend it and pass key where the field's value should
  213. * be stored to the constructor, then register the type(s):
  214. * @code
  215. * class MigrateLinkFieldHandler extends MigrateSimpleFieldHandler {
  216. * public function __construct() {
  217. * parent::__construct('url');
  218. * $this->registerTypes(array('link'));
  219. * }
  220. * }
  221. * @endcode
  222. */
  223. abstract class MigrateSimpleFieldHandler extends MigrateFieldHandler {
  224. protected $fieldValueKey = 'value';
  225. protected $skipEmpty = FALSE;
  226. /**
  227. * Construct a simple field handler.
  228. *
  229. * @param $options
  230. * Array of options (rather than unamed parameters so you don't have to
  231. * what TRUE or FALSE means). The following keys are used:
  232. * - 'value_key' string with the name of the key in the fields value array.
  233. * - 'skip_empty' Boolean indicating that empty values should not be saved.
  234. */
  235. public function __construct($options = array()) {
  236. if (isset($options['value_key'])) {
  237. $this->fieldValueKey = $options['value_key'];
  238. }
  239. if (isset($options['skip_empty'])) {
  240. $this->skipEmpty = $options['skip_empty'];
  241. }
  242. }
  243. public function prepare($entity, array $field_info, array $instance, array $values) {
  244. $arguments = array();
  245. if (isset($values['arguments'])) {
  246. $arguments = $values['arguments'];
  247. unset($values['arguments']);
  248. }
  249. $language = $this->getFieldLanguage($entity, $field_info, $arguments);
  250. // Let the derived class skip empty values.
  251. if ($this->skipEmpty) {
  252. $values = array_filter($values, array($this, 'notNull'));
  253. }
  254. // Setup the Field API array for saving.
  255. $delta = 0;
  256. foreach ($values as $value) {
  257. if (is_array($language)) {
  258. $current_language = $language[$delta];
  259. }
  260. else {
  261. $current_language = $language;
  262. }
  263. $return[$current_language][] = array($this->fieldValueKey => $value);
  264. $delta++;
  265. }
  266. return isset($return) ? $return : NULL;
  267. }
  268. /**
  269. * Returns TRUE only for values which are not NULL.
  270. *
  271. * @param $value
  272. * @return bool
  273. */
  274. protected function notNull($value) {
  275. return !is_null($value);
  276. }
  277. }
  278. class MigrateTextFieldHandler extends MigrateFieldHandler {
  279. public function __construct() {
  280. $this->registerTypes(array('text', 'text_long', 'text_with_summary'));
  281. }
  282. static function arguments($summary = NULL, $format = NULL, $language = NULL) {
  283. $arguments = array();
  284. if (!is_null($summary)) {
  285. $arguments['summary'] = $summary;
  286. }
  287. if (!is_null($format)) {
  288. $arguments['format'] = $format;
  289. }
  290. if (!is_null($language)) {
  291. $arguments['language'] = $language;
  292. }
  293. return $arguments;
  294. }
  295. /**
  296. * Implementation of MigrateFieldHandler::fields().
  297. *
  298. * @param $type
  299. * The field type.
  300. * @param $instance
  301. * Instance info for the field.
  302. * @param Migration $migration
  303. * The migration context for the parent field. We can look at the mappings
  304. * and determine which subfields are relevant.
  305. * @return array
  306. */
  307. public function fields($type, $instance, $migration = NULL) {
  308. $fields = array();
  309. if ($type == 'text_with_summary') {
  310. $fields['summary'] = t('Subfield: <a href="@doc">Summary of field contents</a>',
  311. array('@doc' => 'http://drupal.org/node/1224042#summary'));
  312. }
  313. if ($instance['settings']['text_processing']) {
  314. $fields['format'] = t('Subfield: <a href="@doc">Text format for the field</a>',
  315. array('@doc' => 'http://drupal.org/node/1224042#format'));
  316. }
  317. $field = field_info_field($instance['field_name']);
  318. if (field_is_translatable($instance['entity_type'], $field)) {
  319. $fields['language'] = t('Subfield: <a href="@doc">Language for the field</a>',
  320. array('@doc' => 'http://drupal.org/node/1224042#language'));
  321. }
  322. return $fields;
  323. }
  324. public function prepare($entity, array $field_info, array $instance, array $values) {
  325. if (isset($values['arguments'])) {
  326. $arguments = $values['arguments'];
  327. unset($values['arguments']);
  328. }
  329. else {
  330. $arguments = array();
  331. }
  332. $migration = Migration::currentMigration();
  333. $destination = $migration->getDestination();
  334. $language = $this->getFieldLanguage($entity, $field_info, $arguments);
  335. $max_length = isset($field_info['settings']['max_length']) ?
  336. $field_info['settings']['max_length'] : 0;
  337. // Setup the standard Field API array for saving.
  338. $delta = 0;
  339. foreach ($values as $value) {
  340. $item = array();
  341. if (isset($arguments['summary'])) {
  342. if (is_array($arguments['summary'])) {
  343. $item['summary'] = $arguments['summary'][$delta];
  344. }
  345. else {
  346. $item['summary'] = $arguments['summary'];
  347. }
  348. }
  349. if (isset($arguments['format'])) {
  350. if (is_array($arguments['format'])) {
  351. $format = $arguments['format'][$delta];
  352. }
  353. else{
  354. $format = $arguments['format'];
  355. }
  356. }
  357. elseif (!empty($instance['settings']['text_processing'])) {
  358. $format = $destination->getTextFormat();
  359. }
  360. else {
  361. $format = NULL;
  362. }
  363. $item['format'] = $item['value_format'] = $format;
  364. // Make sure the value will fit
  365. if ($max_length) {
  366. $item['value'] = drupal_substr($value, 0, $max_length);
  367. if (!empty($arguments['track_overflow'])) {
  368. $value_length = drupal_strlen($value);
  369. if ($value_length > $max_length) {
  370. $migration->saveMessage(
  371. t('Value for field !field exceeds max length of !max_length, actual length is !length',
  372. array('!field' => $instance['field_name'], '!max_length' => $max_length,
  373. '!length' => $value_length)),
  374. Migration::MESSAGE_INFORMATIONAL);
  375. }
  376. }
  377. }
  378. else {
  379. $item['value'] = $value;
  380. }
  381. if (is_array($language)) {
  382. $current_language = $language[$delta];
  383. }
  384. else {
  385. $current_language = $language;
  386. }
  387. $return[$current_language][] = $item;
  388. $delta++;
  389. }
  390. return isset($return) ? $return : NULL;
  391. }
  392. }
  393. class MigrateValueFieldHandler extends MigrateSimpleFieldHandler {
  394. public function __construct() {
  395. parent::__construct(array(
  396. 'value_key' => 'value',
  397. 'skip_empty' => FALSE,
  398. ));
  399. $this->registerTypes(array('value', 'list', 'list_boolean', 'list_integer',
  400. 'list_float', 'list_text', 'number_integer', 'number_decimal', 'number_float'));
  401. }
  402. }
  403. class MigrateTaxonomyTermReferenceFieldHandler extends MigrateFieldHandler {
  404. public function __construct() {
  405. $this->registerTypes(array('taxonomy_term_reference'));
  406. }
  407. /**
  408. * Implementation of MigrateFieldHandler::fields().
  409. *
  410. * @param $type
  411. * The field type.
  412. * @param $instance
  413. * Instance info for the field.
  414. * @param Migration $migration
  415. * The migration context for the parent field. We can look at the mappings
  416. * and determine which subfields are relevant.
  417. * @return array
  418. */
  419. public function fields($type, $instance, $migration = NULL) {
  420. return array(
  421. 'source_type' => t('Option: <a href="@doc">Set to \'tid\' when the value is a source ID</a>',
  422. array('@doc' => 'http://drupal.org/node/1224042#source_type')),
  423. 'create_term' => t('Option: <a href="@doc">Set to TRUE to create referenced terms when necessary</a>',
  424. array('@doc' => 'http://drupal.org/node/1224042#create_term')),
  425. 'ignore_case' => t('Option: <a href="@doc">Set to TRUE to ignore case differences between source data and existing term names</a>',
  426. array('@doc' => 'http://drupal.org/node/1224042#ignore_case')),
  427. );
  428. }
  429. public function prepare($entity, array $field_info, array $instance, array $values) {
  430. if (isset($values['arguments'])) {
  431. $arguments = $values['arguments'];
  432. unset($values['arguments']);
  433. }
  434. else {
  435. $arguments = array();
  436. }
  437. if (count($values) == 1 && empty($values[0])) {
  438. $values = array();
  439. }
  440. $tids = array();
  441. if (isset($arguments['source_type']) && $arguments['source_type'] == 'tid') {
  442. // Nothing to do. We have tids already.
  443. $tids = $values;
  444. }
  445. elseif ($values) {
  446. $vocab_name = $field_info['settings']['allowed_values'][0]['vocabulary'];
  447. $names = taxonomy_vocabulary_get_names();
  448. // Get the vocabulary for this term
  449. if (isset($field_info['settings']['allowed_values'][0]['vid'])) {
  450. $vid = $field_info['settings']['allowed_values'][0]['vid'];
  451. }
  452. else {
  453. $vid = $names[$vocab_name]->vid;
  454. }
  455. // Remove leading and trailing spaces in term names
  456. $values = array_map('trim', $values);
  457. // Cannot use taxonomy_term_load_multiple() since we have an array of names.
  458. // It wants a singular value. This query may return case-insensitive
  459. // matches.
  460. $existing_terms = db_select('taxonomy_term_data', 'td')
  461. ->fields('td', array('tid', 'name'))
  462. ->condition('td.name', $values, 'IN')
  463. ->condition('td.vid', $vid)
  464. ->execute()
  465. ->fetchAllKeyed(1, 0);
  466. // If we're ignoring case, change both the matched term name keys and the
  467. // source values to lowercase.
  468. if (isset($arguments['ignore_case']) && $arguments['ignore_case']) {
  469. $ignore_case = TRUE;
  470. $existing_terms = array_change_key_case($existing_terms);
  471. foreach ($values as $value) {
  472. $lower_values[$value] = strtolower($value);
  473. }
  474. }
  475. else {
  476. $ignore_case = FALSE;
  477. }
  478. foreach ($values as $value) {
  479. if (isset($existing_terms[$value])) {
  480. $tids[] = $existing_terms[$value];
  481. }
  482. elseif ($ignore_case && isset($existing_terms[$lower_values[$value]])) {
  483. $tids[] = $existing_terms[$lower_values[$value]];
  484. }
  485. elseif (!empty($arguments['create_term'])) {
  486. $new_term = new stdClass();
  487. $new_term->vid = $vid;
  488. $new_term->name = $value;
  489. $new_term->vocabulary_machine_name = $vocab_name;
  490. // This term is being created with no fields, but we should still call
  491. // field_attach_validate() before saving, as that invokes
  492. // hook_field_attach_validate().
  493. MigrateDestinationEntity::fieldAttachValidate('taxonomy_term', $new_term);
  494. taxonomy_term_save($new_term);
  495. $tids[] = $new_term->tid;
  496. // Add newly created term to existing array.
  497. $existing_terms[$value] = $new_term->tid;
  498. }
  499. else {
  500. // No term is found for the source value and none is set to be
  501. // created: warn that data has not been imported.
  502. $migration = Migration::currentMigration();
  503. $migration->saveMessage(t("No matching taxonomy term found for source value '@value' in vocabulary %vocab.", array(
  504. '@value' => $value,
  505. '%vocab' => $names[$vocab_name]->name,
  506. )), MigrationBase::MESSAGE_INFORMATIONAL);
  507. }
  508. }
  509. }
  510. $language = $this->getFieldLanguage($entity, $field_info, $arguments);
  511. $result = array();
  512. $delta = 0;
  513. foreach ($tids as $tid) {
  514. if (is_array($language)) {
  515. $current_language = $language[$delta];
  516. }
  517. else {
  518. $current_language = $language;
  519. }
  520. $result[$current_language][] = array('tid' => $tid);
  521. $delta++;
  522. }
  523. return $result;
  524. }
  525. }
  526. /**
  527. * The next generation of file field handler. This class focuses on the file
  528. * field itself, and offloads understanding of obtaining the actual file and
  529. * dealing with the file entity to an embedded MigrateFileInterface instance.
  530. */
  531. abstract class MigrateFileFieldBaseHandler extends MigrateFieldHandler {
  532. /**
  533. * Implementation of MigrateFieldHandler::fields().
  534. *
  535. * @param $type
  536. * The file field type - 'file', 'image', etc.
  537. * @param $instance
  538. * Instance info for the field.
  539. * @param Migration $migration
  540. * The migration context for the parent field. We can look at the mappings
  541. * and determine which subfields are relevant.
  542. * @return array
  543. */
  544. public function fields($type, $instance, $migration = NULL) {
  545. $fields = array(
  546. 'file_class' => t('Option: <a href="@doc">Implementation of MigrateFile to use</a>',
  547. array('@doc' => 'http://drupal.org/node/1540106#file_class')),
  548. );
  549. $field = field_info_field($instance['field_name']);
  550. if (field_is_translatable($instance['entity_type'], $field)) {
  551. $fields['language'] = t('Subfield: Language for the field');
  552. }
  553. // If we can identify the file class mapped to this field, pick up the
  554. // subfields specific to that class.
  555. if ($migration) {
  556. $field_mappings = $migration->getFieldMappings();
  557. $class_mapping = $instance['field_name'] . ':file_class';
  558. if (isset($field_mappings[$class_mapping])) {
  559. $mapping = $field_mappings[$class_mapping];
  560. $file_class = $mapping->getDefaultValue();
  561. }
  562. }
  563. if (empty($file_class)) {
  564. $file_class = 'MigrateFileUri';
  565. }
  566. $fields += call_user_func(array($file_class, 'fields'));
  567. return $fields;
  568. }
  569. /**
  570. * Implementation of MigrateFieldHandler::prepare().
  571. *
  572. * Prepare file data for saving as a Field API file field.
  573. *
  574. * @return array
  575. * Field API array suitable for inserting in the destination object.
  576. */
  577. public function prepare($entity, array $field_info, array $instance, array $values) {
  578. if (isset($values['arguments'])) {
  579. $arguments = $values['arguments'];
  580. unset($values['arguments']);
  581. }
  582. else {
  583. $arguments = array();
  584. }
  585. $default_language = $this->getFieldLanguage($entity, $field_info, $arguments);
  586. $migration = Migration::currentMigration();
  587. // One can override the source class via CLI or drushrc.php (the
  588. // option is named file_function for historical reasons)
  589. if ($migration->getOption('file_function')) {
  590. $file_class = $migration->getOption('file_function');
  591. }
  592. elseif (!empty($arguments['file_class'])) {
  593. $file_class = $arguments['file_class'];
  594. }
  595. else {
  596. $file_class = 'MigrateFileUri';
  597. }
  598. // If a destination directory (relative to the Drupal public files directory)
  599. // is not explicitly provided, use the default for the field.
  600. if (empty($arguments['destination_dir'])) {
  601. $arguments['destination_dir'] = $this->destinationDir($field_info, $instance);
  602. }
  603. $return = array();
  604. // Note that what $value represents depends on the file class -
  605. // MigrateFileUri expects a filespec/URI, MigrateFileFid expects a file ID,
  606. // etc.
  607. foreach ($values as $delta => $value) {
  608. if ($value) {
  609. // Handle potentially multiple arguments
  610. $instance_arguments = array();
  611. foreach ($arguments as $key => $argument) {
  612. // For a scalar argument, pass it directly
  613. if (!is_array($argument)) {
  614. $instance_arguments[$key] = $argument;
  615. }
  616. else {
  617. if (isset($argument[$delta])) {
  618. $instance_arguments[$key] = $argument[$delta];
  619. }
  620. else {
  621. $migration->saveMessage(
  622. t('No data for subfield %key at row %delta for field %field',
  623. array('%key' => $key, '%delta' => $delta, '%field' => $field_info['field_name'])),
  624. Migration::MESSAGE_WARNING);
  625. }
  626. }
  627. }
  628. // If the parent entity doesn't have an explicit uid, give ownership
  629. // to the anonymous account
  630. $owner = isset($entity->uid) ? $entity->uid : 0;
  631. // Call the MigrateFileInterface implementation to do the real work
  632. $source = new $file_class($instance_arguments);
  633. $file = $source->processFile($value, $owner);
  634. // Assuming we got back a valid file ID, build the proper field
  635. // array out of it. We assume that if we did not get back a fid, the
  636. // MigrateFile class has saved a message indicating why.
  637. if ($file) {
  638. $field_array = array('fid' => $file->fid);
  639. $language = isset($instance_arguments['language']) ? $instance_arguments['language'] : $default_language;
  640. if (is_array($language)) {
  641. $language = $language[$delta];
  642. }
  643. $return[$language][] = $this->buildFieldArray($field_array, $instance_arguments, $delta);
  644. }
  645. }
  646. }
  647. return $return;
  648. }
  649. /**
  650. * Determine where the migrated file should go.
  651. *
  652. * @param $field_info
  653. * Field API info on the general field.
  654. * @param $instance
  655. * Field API info on the field instance for this entity type.
  656. * @return string
  657. * Directory relative to the Drupal public files directory.
  658. */
  659. protected function destinationDir($field_info, $instance) {
  660. // Only apply for file/image types
  661. if (isset($instance['settings']['file_directory'])) {
  662. $destination_dir = file_field_widget_uri($field_info, $instance);
  663. }
  664. else {
  665. $destination_dir = 'public://';
  666. }
  667. return $destination_dir;
  668. }
  669. /**
  670. * Add any type-specific subfields to a file field array.
  671. *
  672. * @param $field_array
  673. * The field array so far (generally will just contain a fid).
  674. * @param $arguments
  675. * Array of arguments passed to the field handler, from which we'll extract
  676. * our own subfields.
  677. * @param $delta
  678. * Index of field values being worked on, for pulling the corresponding
  679. * subfield values if we have an array of them.
  680. */
  681. abstract protected function buildFieldArray($field_array, $arguments, $delta);
  682. }
  683. /**
  684. * Handle for file fields.
  685. */
  686. class MigrateFileFieldHandler extends MigrateFileFieldBaseHandler {
  687. public function __construct() {
  688. $this->registerTypes(array('file'));
  689. }
  690. /**
  691. * Implementation of MigrateFieldHandler::fields().
  692. * Note that file and image fields support slightly different field lists.
  693. *
  694. * @param $type
  695. * The file field type - 'file' or 'image'
  696. * @param $instance
  697. * Instance info for the field.
  698. * @param Migration $migration
  699. * The migration context for the parent field. We can look at the mappings
  700. * and determine which subfields are relevant.
  701. * @return array
  702. */
  703. public function fields($type, $instance, $migration = NULL) {
  704. $fields = parent::fields($type, $instance, $migration);
  705. $fields += array(
  706. 'description' => t('Subfield: <a href="@doc">String to be used as the description value</a>',
  707. array('@doc' => 'http://drupal.org/node/1224042#description')),
  708. 'display' => t('Subfield: <a href="@doc">String to be used as the display value</a>',
  709. array('@doc' => 'http://drupal.org/node/1224042#display')),
  710. );
  711. return $fields;
  712. }
  713. /**
  714. * Implementation of MigrateFileFieldBaseHandler::buildFieldArray().
  715. */
  716. protected function buildFieldArray($field_array, $arguments, $delta) {
  717. if (isset($arguments['description'])) {
  718. if (is_array($arguments['description'])) {
  719. $field_array['description'] = $arguments['description'][$delta];
  720. }
  721. else {
  722. $field_array['description'] = $arguments['description'];
  723. }
  724. }
  725. else {
  726. $field_array['description'] = '';
  727. }
  728. if (isset($arguments['display'])) {
  729. if (is_array($arguments['display'])) {
  730. $field_array['display'] = $arguments['display'][$delta];
  731. }
  732. else {
  733. $field_array['display'] = $arguments['display'];
  734. }
  735. }
  736. else {
  737. $field_array['display'] = 1;
  738. }
  739. return $field_array;
  740. }
  741. }
  742. /**
  743. * Handle for image fields;
  744. */
  745. class MigrateImageFieldHandler extends MigrateFileFieldBaseHandler {
  746. public function __construct() {
  747. $this->registerTypes(array('image'));
  748. }
  749. /**
  750. * Implementation of MigrateFieldHandler::fields().
  751. * Note that file and image fields support slightly different field lists.
  752. *
  753. * @param $type
  754. * The file field type - 'file' or 'image'
  755. * @param $instance
  756. * Instance info for the field.
  757. * @param Migration $migration
  758. * The migration context for the parent field. We can look at the mappings
  759. * and determine which subfields are relevant.
  760. * @return array
  761. */
  762. public function fields($type, $instance, $migration = NULL) {
  763. $fields = parent::fields($type, $instance, $migration);
  764. $fields += array(
  765. 'alt' => t('Subfield: <a href="@doc">String to be used as the alt value</a>',
  766. array('@doc' => 'http://drupal.org/node/1224042#alt')),
  767. 'title' => t('Subfield: <a href="@doc">String to be used as the title value</a>',
  768. array('@doc' => 'http://drupal.org/node/1224042#title')),
  769. );
  770. return $fields;
  771. }
  772. /**
  773. * Implementation of MigrateFileFieldBaseHandler::buildFieldArray().
  774. */
  775. protected function buildFieldArray($field_array, $arguments, $delta) {
  776. if (isset($arguments['alt'])) {
  777. if (is_array($arguments['alt'])) {
  778. $field_array['alt'] = $arguments['alt'][$delta];
  779. }
  780. else {
  781. $field_array['alt'] = $arguments['alt'];
  782. }
  783. }
  784. if (isset($arguments['title'])) {
  785. if (is_array($arguments['title'])) {
  786. $field_array['title'] = $arguments['title'][$delta];
  787. }
  788. else {
  789. $field_array['title'] = $arguments['title'];
  790. }
  791. }
  792. return $field_array;
  793. }
  794. }
  795. class MigrateNodeReferenceFieldHandler extends MigrateSimpleFieldHandler {
  796. public function __construct() {
  797. parent::__construct(array(
  798. 'value_key' => 'nid',
  799. 'skip_empty' => TRUE,
  800. ));
  801. $this->registerTypes(array('node_reference'));
  802. }
  803. protected function notNull($value) {
  804. return !is_null($value) && $value !== FALSE;
  805. }
  806. }
  807. class MigrateUserReferenceFieldHandler extends MigrateSimpleFieldHandler {
  808. public function __construct() {
  809. parent::__construct(array(
  810. 'value_key' => 'uid',
  811. 'skip_empty' => TRUE,
  812. ));
  813. $this->registerTypes(array('user_reference'));
  814. }
  815. protected function notNull($value) {
  816. return !is_null($value) && $value !== FALSE;
  817. }
  818. }