web-dev-qa-db-ja.com

Drupal 7のMigrate 2.2での複数の値の単一ファイルフィールドへの移行

イメージノードタイプに対して複数のダウンロード可能なファイルを含めることができるファイルフィールドがあります。これらの画像には、ダウンロード可能な複数のバージョン(psd、jpg、eps)があります。このフィールドに複数の値を追加できるようにしたいと思います。それらが複数のソース列(つまり、eps_filename、jpg_filename、psd_filename)から来ている場合、どうすればよいですか?

私はここに関連するドキュメントを見つけました: http://drupal.org/node/101281 ただし、そのドキュメントは、値の詳細が単に区切り文字付きの文字列である可能性がある分類用語に関するものです。このためにMigrateFileFieldHandler :: arguments()を使用しているため、セパレーターを指定することはできません。

さらに、私がやりたいのは、後で戻って、移行したノードにさらにファイルを追加できるようにすることです。これは、ファイルが大きいほどサーバーに到達するまでに時間がかかるため、対応するファイル(jpg、pngなど)を先に読み込んでから、後でepsファイルを追加するためです。これで、systemOfRecordを使用して以前に移行したノードを更新する方法をすでに理解しました。しかし、私は複数値フィールドに追加する方法がわかりません。

Drupal 7とMigrateモジュールバージョン2.2を使用しています。データを最初にファイルフィールドに移行するために使用しているサンプルコードを以下に示します。

$jpg_arguments = MigrateFileFieldHandler::arguments(NULL,
  'file_link', FILE_EXISTS_RENAME, 'en', array('source_field' => 'jpg_name'),
  array('source_field' => 'jpg_filename'), array('source_field' => 'jpg_filename'));

$this->addFieldMapping('field_file', 'jpg_uri')
     ->arguments($jpg_arguments);

トリックはprepareRow()関数を使用していると思います。しかし、今のところ、それを実行することはまだできていません。

何か案は?私は本当にどんな助けにも感謝します。

ありがとう!

4
Patrick

実際、私は先月これを理解しましたが、ここでコメントに戻りました。ソリューションは、誰もが提案したものよりもはるかに複雑でした。実際には、MigrateDestinationNodeクラスを拡張し、そのクラスで使用されているものの修正バージョンである独自のインポート関数を作成する必要がありました。以下のソースコードを貼り付けます。

基本的に私がしなければならなかったのは、システムのレコードが宛先に設定されているときに既存のノードで更新を行おうとするインポートを変更して、ファイルを保存するフィールドタイプをチェックし、ファイルのフィールドデータをマージすることでした。追加するファイルがあるノードに既にあるファイル。

また、この新しい拡張クラスを宛先として使用し、System of RecordをMigration :: DESTINATIONに設定する必要もあります。

これがコードです:

<?php
class ImageAssetNodeAddEPSMigration extends BasicClientMigration {
  public function __construct() {
    parent::__construct();

    //This allows us to update the existing record.
    $this->systemOfRecord = Migration::DESTINATION;

    $this->description = t('Image Asset Files - Adding the EPS files');
    $this->dependencies = array('ImageAssetNode');

    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'destid1' => array(
          'type' => 'int',
          'not null' => TRUE,
          'description' => 'Node ID.',
          'alias' => 'an',
        )
      ),
      MigrateDestinationNode::getKeySchema()
    );

    // We get the data from the database for this.
    $query = db_select('migrate_map_imageassetnode', 'an')
        ->fields('an', array('destid1'));
    $query->innerJoin('migrate_client_image_asset_node', 'ta', 'an.sourceid1 = ta.fcid');
    $query->fields('ta', array('eps_psd_filename', 'eps_psd_name', 'eps_psd_uri', 'fcid') );

    // Passing the cache_counts option means the source count (shown in
    // drush migrate-status) will be cached - this can be very handy when
    // dealing with a slow source database.
    $this->source = new MigrateSourceSQL($query, array(), NULL,
      array('cache_counts' => TRUE));

    // Set up our destination - nodes of type migrate_example_beer
    $this->destination = new MigrateDestinationNodeExtended('media_images');

    /*
      Assign mappings TO destination fields FROM source fields. To discover
      the names used in these calls, use the drush commands
      drush migrate-fields-destination ImageAssetNode
      drush migrate-fields-source ImageAssetNode
    */

    // Mapped fields
    $this->addFieldMapping('nid', 'destid1')
         ->description(t('The original node id.'));

    // Copy an image file, write DB record to files table, and save in Field storage.
    // Note we specify the source query fields that will map to the file alt/title/description
    // values.

    $eps_arguments = MigrateFileFieldHandler::arguments(NULL,
      'file_link', FILE_EXISTS_RENAME, 'en', array('source_field' => 'eps_psd_name'),
      array('source_field' => 'eps_psd_filename'), array('source_field' => 'eps_psd_filename'), NULL, TRUE);

    $this->addFieldMapping('field_file', 'eps_psd_uri')
         ->arguments($eps_arguments);

    // Add fields that are mapped via argument
    #$this->addFieldMapping(NULL, 'eps_psd_uri');
    $this->addFieldMapping(NULL, 'eps_psd_filename');
    $this->addFieldMapping(NULL, 'eps_psd_name');

    // No unmapped source fields
    $this->addUnmigratedSources( array('fcid', 'jpg_uri', 'jpg_filename', 'jpg_name', 'category_tid') );

    // Unmapped destination fields
    $this->addUnmigratedDestinations( array('is_new', 'name', 'created', 'changed',
      'status', 'promote', 'revision', 'language', 'sticky', 'uid', 'revision_uid',
      'title', 'body', 'field_image', 'field_keywords', 'field_download_count',
      'field_images_category', 'field_media_status', 'field_media_audience',
      'field_roles_download', 'field_roles_share', 'field_roles_view', 'path', 'comment',
      'bookmarks', 'cart', 'download', 'pathauto') );
  }
  /*
  public function prepare(&$node, &$row) {

  }*/

  public function prepareRow($current_row) {
    //If the file does not exist then skip it.
    if (!file_exists(drupal_realpath($current_row->eps_psd_uri))) {
      return FALSE;
    }

    $result = db_query("SELECT ff.* FROM field_data_field_file ff INNER JOIN migrate_map_imageassetnode ian ON ff.entity_id = ian.destid1 WHERE sourceid1 = :sid", array(':sid' => $current_row->fcid));

    //If there is more then one file field entry attached to the node then skip.
    if ($result->rowCount() > 1) {
      return FALSE;
    }
  }
}

class MigrateDestinationNodeExtended extends MigrateDestinationNode {
  /**
   * Basic initialization
   *
   * @param string $bundle
   *  A.k.a. the content type (page, article, etc.) of the node.
   * @param array $options
   *  Options applied to nodes.
   */
  public function __construct($bundle, array $options = array()) {
    parent::__construct($bundle, $options);
  }

  /**
   * Import a single node.
   *
   * @param $node
   *  Node object to build. Prefilled with any fields mapped in the Migration.
   * @param $row
   *  Raw source data object - passed through to prepare/complete handlers.
   * @return array
   *  Array of key fields (nid only in this case) of the node that was saved if
   *  successful. FALSE on failure.
   */
  public function import(stdClass $node, stdClass $row) {
    // Updating previously-migrated content?
    $migration = Migration::currentMigration();

    if (isset($row->migrate_map_destid1)) {
      // Make sure is_new is off
      $node->is_new = FALSE;

      if (isset($node->nid)) {
        if ($node->nid != $row->migrate_map_destid1) {
          throw new MigrateException(t("Incoming nid !nid and map destination nid !destid1 don't match",
            array('!nid' => $node->nid, '!destid1' => $row->migrate_map_destid1)));
        }
      }
      else {
        $node->nid = $row->migrate_map_destid1;
      }

      // Get the existing vid, tnid so updates don't generate notices
      $values = db_select('node', 'n')
                   ->fields('n', array('vid', 'tnid'))
                   ->condition('nid', $node->nid)
                   ->execute()
                   ->fetchAssoc();

      $node->vid = $values['vid'];
      $node->tnid = $values['tnid'];
    }

    if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
      if (!isset($node->nid)) {
        throw new MigrateException(t('System-of-record is DESTINATION, but no destination nid provided'));
      }

      $old_node = node_load($node->nid);

      if (!isset($node->created) && isset($old_node->created)) {
        $node->created = $old_node->created;
      }

      if (!isset($node->vid) && isset($old_node->vid)) {
        $node->vid = $old_node->vid;
      }

      if (!isset($node->status) && isset($old_node->status)) {
        $node->status = $old_node->status;
      }

      if (!isset($node->uid) && isset($old_node->uid)) {
        $node->uid = $old_node->uid;
      }
    }

    // Set some required properties.
    // Set type before invoking prepare handlers - they may take type-dependent actions.
    $node->type = $this->bundle;

    if ($migration->getSystemOfRecord() == Migration::SOURCE) {
      if (!isset($node->language)) {
        $node->language = $this->language;
      }

      // Apply defaults, allow standard node prepare hooks to fire.
      // node_object_prepare() will blow these away, so save them here and
      // stuff them in later if need be.
      if (isset($node->created)) {
        $created = MigrationBase::timestamp($node->created);
      }
      else {
        // To keep node_object_prepare() from choking
        $node->created = REQUEST_TIME;
      }

      if (isset($node->changed)) {
        $changed = MigrationBase::timestamp($node->changed);
      }

      if (isset($node->uid)) {
        $uid = $node->uid;
      }

      node_object_prepare($node);

      if (isset($created)) {
        $node->created = $created;
      }

      // No point to resetting $node->changed here, node_save() will overwrite it
      if (isset($uid)) {
        $node->uid = $uid;
      }
    }

    // Invoke migration prepare handlers
    $this->prepare($node, $row);

    if (!isset($node->revision)) {
      $node->revision = 0; // Saves disk space and writes. Can be overridden.
    }

    // Trying to update an existing node
    if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
      // Incoming data overrides existing data, so only copy non-existent fields
      if ( !isset($old_node) || ( isset($old_node) && (empty($old_node) || !is_array($old_node)) )) {
        $return = FALSE;
      }
      else {
        foreach ($old_node as $field => $value) {
          // An explicit NULL in the source data means to wipe to old     value (i.e.,
          // don't copy it over from $old_node)
          if (property_exists($node, $field) && $node->$field === NULL) {
            // Ignore this field
          }
          elseif ($field == 'field_file') {
            $file_field = $node->$field;
            $lang_files = array();

            // We can import this, got to do it on a per language basis
            foreach ($value AS $lang => $files) {
              if (isset($file_field[$lang])) {
                $new_node_files = $file_field[$lang];
              }
              else {
                $new_node_files = array();
              }

              $lang_files[$lang] = array_merge($new_node_files, $files);
            }

            $node->$field = $lang_files;
          }
          elseif (!isset($node->$field)) {
            $node->$field = $old_node->$field;
          }
        }
      }

    }

    if (isset($node->nid) && !(isset($node->is_new) && $node->is_new)) {
      $updating = TRUE;
    }
    else {
      $updating = FALSE;
    }

    migrate_instrument_start('node_save');
    node_save($node);
    migrate_instrument_stop('node_save');

    if (isset($node->nid)) {
      if ($updating) {
        $this->numUpdated++;
      }
      else {
        $this->numCreated++;
      }

      // Unfortunately, http://drupal.org/node/722688 was not accepted, so fix
      // the changed timestamp
      if (isset($changed)) {
        db_update('node')
          ->fields(array('changed' => $changed))
          ->condition('nid', $node->nid)
          ->execute();

        $node->changed = $changed;
      }

      // Potentially fix uid and timestamp in node_revisions.
      $query = db_update('node_revision')
                 ->condition('vid', $node->vid);

      if (isset($changed)) {
        $fields['timestamp'] = $changed;
      }

      $revision_uid = isset($node->revision_uid) ? $node->revision_uid : $node->uid;

      if ($revision_uid != $GLOBALS['user']->uid) {
        $fields['uid'] = $revision_uid;
      }

      if (!empty($fields)) {
        // We actually have something to update.
        $query->fields($fields);
        $query->execute();

        if (isset($changed)) {
          $node->timestamp = $changed;
        }
      }

      $return = array($node->nid);
    }
    else {
      $return = FALSE;
    }

    $this->complete($node, $row);
    return $return;
  }
}
?>

Pastebinリンクも: http://Pastebin.com/ykzgVvCF

1
Patrick

うーん。 1パスでこれを行わない方が簡単かもしれませんが、2ステップの移行です。 1つの移行タスクで新しい親レコードを作成し、2番目の移行タスクとして、N個のfile_fieldsに新しいノードまたはエンティティIDを埋め戻します。

さらに、System of Recordを調べましたが、この機能についての私の理解は、古いDBと新しいDBスキーマの間で1:1のDBスキーマに対してのみ機能することです。たとえば、古いDBの行のNフィールドであり、たとえば新しいDBの親行IDに相互参照されるN行である可能性があるため、複数値フィールドエントリは検査できません。

私は「Query in Prepare」メソッドを使用しましたが、group_concatバージョンも試してみました。それで問題は2セントです。

1
tenken

必要なのはprepareRowだと思います。これは、複数のフィールドが単一の配列フィールドにマージされる野球の移行の例のコードです。

 function prepareRow($row) {
    // Collect all the batters into one multi-value field.
    for ($i=1; $i <= 9; $i++ ) {
      $key = "visiting_batter_$i";
      $visiting_batters[] = $row->$key;
      $key = "home_batter_$i";
      $home_batters[] = $row->$key;
    }
    $row->visiting_batters = implode(',', $visiting_batters);
    $row->home_batters = implode(',', $home_batters);
    $row->title = "$row->home_team vs. $row->visiting_team. " . gmdate('M d, Y', strtotime($row->start_date));
  }

(ファイルmigrate_example_baseball.migrate.incから)

しかし、私は自分のコードでprepareRowと結果を混合しているので、これは簡単に見えるが、それが機能することを保証することはできません。

1
Tony