You are here

gathercontent.module in GatherContent 8.3

Main module file for GatherContent module.

File

gathercontent.module
View source
<?php

/**
 * @file
 * Main module file for GatherContent module.
 */
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\gathercontent\DAO\Content;
use Drupal\gathercontent\DAO\Project;
use Drupal\gathercontent\DAO\Template;
use Drupal\gathercontent\Entity\Mapping;
use Drupal\gathercontent\Entity\OperationItem;
use Drupal\gathercontent\Event\GatherContentEvents;
use Drupal\gathercontent\Event\PostImportEvent;
use Drupal\gathercontent\Event\PostNodeSaveEvent;
use Drupal\gathercontent\Event\PostNodeUploadEvent;
use Drupal\gathercontent\Event\PostUploadEvent;
use Drupal\gathercontent\Event\PreNodeSaveEvent;
use Drupal\gathercontent\Event\PreNodeUploadEvent;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\taxonomy\Entity\Term;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Implements hook_entity_base_field_info().
 *
 * Add a 'GC mapping ID' and 'GC ID fields' base field to all node types.
 *
 * {@inheritdoc}
 */
function gathercontent_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];
  if ($entity_type
    ->id() === 'node') {
    $fields['gc_mapping_id'] = $storage_definition = BaseFieldDefinition::create('integer')
      ->setLabel(t('GC mapping ID'))
      ->setDescription(t('The ID of GatherContent mapping.'))
      ->setReadOnly(TRUE);
    $fields['gc_id'] = $storage_definition = BaseFieldDefinition::create('integer')
      ->setLabel(t('GC ID'))
      ->setDescription(t('The ID of GatherContent content.'))
      ->setReadOnly(TRUE);
  }
  if ($entity_type
    ->id() === 'file') {
    $fields['gc_id'] = $storage_definition = BaseFieldDefinition::create('integer')
      ->setLabel(t('GC ID'))
      ->setDescription(t('The ID of GatherContent content.'))
      ->setReadOnly(TRUE);
  }
  return $fields;
}

/**
 * Function for fetching, creating and updating content from GatherContent.
 *
 * @param int $gc_id
 *   ID of GatherContent piece of content.
 * @param string $uuid
 *   UUID of \Drupal\gathercontent\Entity\Operation.
 * @param bool $drupal_status
 *   Drupal status - published/unpublished.
 * @param string $node_update_method
 *   Name of the node update method.
 * @param int|null $status
 *   ID of status from GatherContent.
 * @param string|null $parent_menu_item
 *   Parent menu item ID if we want to create menu item.
 *
 * @return bool
 *   Return nid if operation was successful.
 */
function _gc_fetcher($gc_id, $uuid, $drupal_status, $node_update_method, $status = NULL, $parent_menu_item = NULL) {
  $user = \Drupal::currentUser();
  $tsid = NULL;
  $content_obj = new Content();
  $content = $content_obj
    ->getContent($gc_id);
  if (!empty($status)) {
    $status_obj = new Project();
    $status = $status_obj
      ->getStatus($content->project_id, $status);
  }
  $temp_obj = new Template();
  $template = $temp_obj
    ->getTemplate($content->template_id);
  $operation_item = \Drupal::entityTypeManager()
    ->getStorage('gathercontent_operation_item')
    ->create([
    'operation_uuid' => $uuid,
    'item_status' => !empty($status) ? $status->name : $content->status->data->name,
    'item_status_color' => !empty($status) ? $status->color : $content->status->data->color,
    'template_name' => $template->name,
    'item_name' => $content->name,
    'gc_id' => $gc_id,
  ]);
  $mapping_id = Drupal::entityQuery('gathercontent_mapping')
    ->condition('gathercontent_project_id', $content->project_id)
    ->condition('gathercontent_template_id', $content->template_id)
    ->execute();
  if (!empty($mapping_id)) {
    $mapping = Mapping::load(reset($mapping_id));

    // If mapping exists, start mapping remote fields to local ones.
    $mapping_data = unserialize($mapping
      ->getData());
    if (empty($mapping_data)) {
      return FALSE;
    }
    $mapping_data_copy = $mapping_data;
    $first = array_shift($mapping_data_copy);
    $content_type = $mapping
      ->getContentType();
    $langcode = isset($first['language']) ? $first['language'] : Language::LANGCODE_NOT_SPECIFIED;
    $entity = gc_get_destination_node($gc_id, $node_update_method, $content_type, $langcode);
    $entity
      ->set('gc_id', $gc_id);
    $entity
      ->set('gc_mapping_id', $mapping
      ->id());
    $entity
      ->setOwnerId($user
      ->id());
    if ($entity
      ->isNew()) {
      $entity
        ->setPublished($drupal_status);
    }
    if ($entity !== FALSE) {

      /** @var \Drupal\node\NodeInterface $entity */
      try {
        $content_obj = new Content();
        $files = $content_obj
          ->getFiles($gc_id);
        $is_translatable = \Drupal::moduleHandler()
          ->moduleExists('content_translation') && \Drupal::service('content_translation.manager')
          ->isEnabled('node', $mapping
          ->getContentType());
        foreach ($content->config as $pane) {
          $is_translatable &= isset($mapping_data[$pane->name]['language']) && $mapping_data[$pane->name]['language'] != Language::LANGCODE_NOT_SPECIFIED;
          if ($is_translatable) {
            $language = $mapping_data[$pane->name]['language'];
            if (!$entity
              ->hasTranslation($language)) {
              $entity
                ->addTranslation($language);
              if ($entity
                ->isNew()) {
                $entity
                  ->getTranslation($language)
                  ->setPublished($drupal_status);
              }
            }
          }
          else {
            $language = Language::LANGCODE_NOT_SPECIFIED;
          }
          foreach ($pane->elements as $field) {
            if (isset($mapping_data[$pane->name]['elements'][$field->name]) && !empty($mapping_data[$pane->name]['elements'][$field->name])) {
              $local_field_name = $mapping_data[$pane->name]['elements'][$field->name];
              if (isset($mapping_data[$pane->name]['type']) && $mapping_data[$pane->name]['type'] === 'content' || !isset($mapping_data[$pane->name]['type'])) {
                gc_gc_process_content_pane($entity, $local_field_name, $field, $is_translatable, $language, $files);
              }
              elseif (isset($mapping_data[$pane->name]['type']) && $mapping_data[$pane->name]['type'] === 'metatag') {
                gc_gc_process_metatag_pane($entity, $local_field_name, $field, $mapping
                  ->getContentType(), $is_translatable, $language);
              }
            }
          }
        }
        if (!$is_translatable && empty($entity
          ->getTitle())) {
          $entity
            ->setTitle($content->name);
        }
        \Drupal::service('event_dispatcher')
          ->dispatch(GatherContentEvents::PRE_NODE_SAVE, new PreNodeSaveEvent($entity, $content, $files));
        $entity
          ->save();

        // Create menu link items.
        $menu_link_defaults = menu_ui_get_menu_link_defaults($entity);
        if (!(bool) $menu_link_defaults['id']) {
          if ($is_translatable) {
            $languages = $entity
              ->getTranslationLanguages();
            $original_link_id = NULL;
            foreach ($languages as $langcode => $language) {
              $localized_entity = $entity
                ->hasTranslation($langcode) ? $entity
                ->getTranslation($langcode) : NULL;
              if (!is_null($localized_entity)) {
                gc_create_menu_link($entity
                  ->id(), $localized_entity
                  ->getTitle(), $parent_menu_item, $langcode, $original_link_id);
              }
            }
          }
          else {
            gc_create_menu_link($entity
              ->id(), $entity
              ->getTitle(), $parent_menu_item);
          }
        }
        \Drupal::service('event_dispatcher')
          ->dispatch(GatherContentEvents::POST_NODE_SAVE, new PostNodeSaveEvent($entity, $content, $files));
        $operation_item->status = "Success";
        $operation_item->nid = $entity
          ->id();
        $operation_item
          ->save();
        return $entity
          ->id();
      } catch (Exception $e) {
        \Drupal::logger('gc_import')
          ->error(print_r($e, TRUE), []);
        $operation_item->status = "Operation failed:" . $e
          ->getMessage();
        $operation_item
          ->save();
        return FALSE;
      }
    }
    else {
      $operation_item->status = "System error, please contact you administrator.";
      $operation_item
        ->save();
      return FALSE;
    }
  }
  else {
    $operation_item->status = "Operation failed: Template not mapped.";
    $operation_item
      ->save();
    return FALSE;
  }
}

/**
 * Get Node object based on type of update.
 *
 * @param int $gc_id
 *   ID of item in GatherContent.
 * @param string $node_update_method
 *   Name of the node update method.
 * @param int $node_type_id
 *   ID of the node type.
 * @param string $langcode
 *   Language of translation if applicable.
 *
 * @return \Drupal\node\NodeInterface
 *   Return loaded node.
 */
function gc_get_destination_node($gc_id, $node_update_method, $node_type_id, $langcode) {
  switch ($node_update_method) {
    case 'update_if_not_changed':
      $result = \Drupal::entityQuery('node')
        ->condition('gc_id', $gc_id)
        ->sort('created', 'DESC')
        ->range(0, 1)
        ->execute();
      if ($result) {
        $node = Node::load(reset($result));
        $query_result = \Drupal::entityQuery('gathercontent_operation_item')
          ->condition('gc_id', $gc_id)
          ->sort('changed', 'DESC')
          ->range(0, 1)
          ->execute();
        $operation = OperationItem::load(reset($query_result));
        if ($node
          ->getChangedTime() === $operation
          ->getChangedTime()) {
          return $node;
        }
      }
      break;
    case 'always_update':
      $result = \Drupal::entityQuery('node')
        ->condition('gc_id', $gc_id)
        ->sort('created', 'DESC')
        ->range(0, 1)
        ->execute();
      if ($result) {
        return Node::load(reset($result));
      }
      break;
  }
  return Node::create([
    'type' => $node_type_id,
    'langcode' => $langcode,
  ]);
}

/**
 * Processing function for metatag panes.
 *
 * @param \Drupal\node\NodeInterface $entity
 *   Object of node.
 * @param string $local_field_name
 *   Name of local Drupal field.
 * @param object $field
 *   Object of GatherContent field.
 * @param string $content_type
 *   Name of Content type, we are mapping to.
 * @param bool $is_translatable
 *   Indicator if node is translatable.
 * @param string $language
 *   Language of translation if applicable.
 *
 * @throws \Exception
 *   If content save fails, exceptions is thrown.
 */
function gc_gc_process_metatag_pane(NodeInterface &$entity, $local_field_name, $field, $content_type, $is_translatable, $language) {
  if (\Drupal::moduleHandler()
    ->moduleExists('metatag') && check_metatag($content_type)) {
    $metatag_fields = get_metatag_fields($content_type);
    foreach ($metatag_fields as $metatag_field) {
      if ($is_translatable) {
        $current_value = unserialize($entity
          ->getTranslation($language)->{$metatag_field}->value);
        $current_value[$local_field_name] = $field->value;
        $entity
          ->getTranslation($language)->{$metatag_field}->value = serialize($current_value);
      }
      else {
        $current_value = unserialize($entity->{$metatag_field}->value);
        $current_value[$local_field_name] = $field->value;
        $entity->{$metatag_field}->value = serialize($current_value);
      }
    }
  }
  else {
    throw new Exception("Metatag module not enabled or entity doesn't support\n    metatags while trying to map values with metatag content.");
  }
}

/**
 * Processing function for content panes.
 *
 * @param \Drupal\node\NodeInterface $entity
 *   Object of node.
 * @param string $local_field_name
 *   Name of local Drupal field.
 * @param object $field
 *   Object of GatherContent field.
 * @param bool $is_translatable
 *   Indicator if node is translatable.
 * @param string $language
 *   Language of translation if applicable.
 * @param array $files
 *   Array of files fetched from GatherContent.
 */
function gc_gc_process_content_pane(NodeInterface &$entity, $local_field_name, $field, $is_translatable, $language, array $files) {
  $field_info = FieldConfig::loadByName('node', $entity
    ->bundle(), $local_field_name);
  if (!is_null($field_info)) {
    $is_translatable = $is_translatable && $field_info
      ->isTranslatable();
  }
  switch ($field->type) {
    case 'files':
      gc_gc_process_files_field($entity, $local_field_name, $field->name, $is_translatable, $language, $files);
      break;
    case 'choice_radio':
      gc_gc_process_choice_radio_field($entity, $local_field_name, $is_translatable, $language, $field->options);
      break;
    case 'choice_checkbox':
      gc_gc_process_choice_checkbox_field($entity, $local_field_name, $is_translatable, $language, $field->options);
      break;
    case 'section':
      gc_gc_process_section_field($entity, $local_field_name, $is_translatable, $language, $field);
      break;
    default:
      gc_gc_process_default_field($entity, $local_field_name, $is_translatable, $language, $field);
      break;
  }
}

/**
 * Create menu link if requested.
 *
 * @param int $nid
 *   ID of \Drupal\node\NodeInterface object.
 * @param string $title
 *   Title for menu link.
 * @param string $plid
 *   Parent menu link ID, null if we don't want to create menu link.
 * @param null|string $lang
 *   Langcode for menu link.
 * @param null|int $original_link_id
 *   ID of menu link item in default language.
 */
function gc_create_menu_link($nid, $title, $plid, $lang = NULL, &$original_link_id = NULL) {
  $weight = 1;
  if (!empty($plid)) {
    if (is_null($lang)) {

      // Single language node.
      list($menu_name, $mlid) = explode(':', $plid);

      // Get parent menu link ID.
      if ($menu_name === 'node') {
        _gc_get_menu_by_gc_id($mlid, $menu_name);
      }
      $link = [
        'link' => [
          'uri' => 'entity:node/' . $nid,
        ],
        'title' => $title,
        'menu_name' => $menu_name,
        'parent' => $mlid,
      ];
      MenuLinkContent::create($link)
        ->set('weight', $weight)
        ->save();
    }
    elseif (\Drupal::moduleHandler()
      ->moduleExists('content_translation') && \Drupal::service('content_translation.manager')
      ->isEnabled('menu_link_content')) {
      if (!is_null($lang) && is_null($original_link_id)) {

        // Multi language node - first language.
        list($menu_name, $mlid) = explode(':', $plid);

        // Get parent menu link ID.
        if ($menu_name === 'node') {
          _gc_get_menu_by_gc_id($mlid, $menu_name, $lang);
        }
        $link = [
          'link' => [
            'uri' => 'entity:node/' . $nid,
          ],
          'title' => $title,
          'menu_name' => $menu_name,
          'parent' => $mlid,
          'langcode' => $lang,
        ];
        $menu_link = MenuLinkContent::create($link);
        $menu_link
          ->set('weight', $weight);
        $menu_link
          ->save();
        $original_link_id = $menu_link
          ->id();
      }
      elseif (!is_null($lang) && !is_null($original_link_id)) {

        // Multi language node - other language.
        list($menu_name, $mlid) = explode(':', $plid);
        if ($menu_name === 'node') {
          _gc_get_menu_by_gc_id($mlid, $menu_name, $lang);
        }
        $link = [
          'link' => [
            'uri' => 'entity:node/' . $nid,
          ],
          'title' => $title,
          'menu_name' => $menu_name,
          'parent' => $mlid,
          'langcode' => $lang,
        ];

        // Load parent item.
        $original_item = MenuLinkContent::load($original_link_id);
        $original_item
          ->addTranslation($lang, $link);
        $original_item
          ->save();
      }
    }
  }
}

/**
 * Load menu name and menu link id for other languages by node ID.
 *
 * @param int $mlid
 *   Menu link ID.
 * @param string $menu_name
 *   Name of the menu.
 * @param string|null $language
 *   Langcode if menu link item will be translatable.
 */
function _gc_get_menu_by_gc_id(&$mlid, &$menu_name, $language = NULL) {

  // Load node by gc_id.
  $node_ids = \Drupal::entityQuery('node')
    ->condition('gc_id', $mlid)
    ->execute();
  if (!empty($node_ids)) {

    // Load menu_link by node_id.
    $node = reset($node_ids);
    $ml_result = \Drupal::entityQuery('menu_link_content')
      ->condition('link.uri', 'entity:node/' . $node);
    if (!is_null($language)) {
      $ml_result
        ->condition('langcode', $language);
    }
    $mls = $ml_result
      ->execute();
    if (!empty($mls)) {
      $ml = reset($mls);
      $ml_object = MenuLinkContent::load($ml);
      $menu_name = $ml_object
        ->getMenuName();
      $mlid = 'menu_link_content:' . $ml_object
        ->uuid();
    }
  }
}

/**
 * Default processing function, when no other matches found, usually for text.
 *
 * @param \Drupal\node\NodeInterface $entity
 *   Object of node.
 * @param string $local_field_name
 *   Local field name.
 * @param bool $is_translatable
 *   Indicator if node is translatable.
 * @param string $language
 *   Language of translation if applicable.
 * @param object $field
 *   Object with field attributes.
 */
function gc_gc_process_default_field(NodeInterface &$entity, $local_field_name, $is_translatable, $language, $field) {
  $value = $field->value;
  $target =& $entity;
  if ($is_translatable) {
    $target = $entity
      ->getTranslation($language);
  }

  // Title is not a field, breaks everything. Short-circuit here.
  if ($local_field_name === 'title') {
    $target
      ->setTitle($value);
    return;
  }

  // For all non-title fields, decide what to do based on Drupal field type.
  $field_info = FieldConfig::loadByName('node', $entity
    ->bundle(), $local_field_name);
  switch ($field_info
    ->getType()) {
    case 'datetime':
      $value = strtotime($value);
      if ($value === FALSE) {

        // If we failed to convert to a timestamp, abort.
        return;
      }
      $target->{$local_field_name} = [
        'value' => gmdate(DATETIME_DATETIME_STORAGE_FORMAT, $value),
      ];
      break;
    case 'date':
      $value = strtotime($value);
      if ($value === FALSE) {
        return;
      }
      $target->{$local_field_name} = [
        'value' => gmdate(DATETIME_DATE_STORAGE_FORMAT, $value),
      ];
      break;
    default:

      // Probably some kind of text field.
      $target->{$local_field_name} = [
        'value' => $value,
        'format' => $field->plain_text ? 'plain_text' : 'basic_html',
      ];
      break;
  }
}

/**
 * Processing function for section type of field.
 *
 * @param \Drupal\node\NodeInterface $node
 *   Object of node.
 * @param string $local_field_name
 *   Local field name.
 * @param bool $is_translatable
 *   Indicator if node is translatable.
 * @param string $language
 *   Language of translation if applicable.
 * @param object $field
 *   Object with field attributes.
 */
function gc_gc_process_section_field(NodeInterface &$node, $local_field_name, $is_translatable, $language, $field) {
  if ($is_translatable) {
    $node
      ->getTranslation($language)->{$local_field_name} = [
      'value' => '<h3>' . $field->title . '</h3>' . $field->subtitle,
      'format' => 'basic_html',
    ];
  }
  else {
    $node->{$local_field_name} = [
      'value' => '<h3>' . $field->title . '</h3>' . $field->subtitle,
      'format' => 'basic_html',
    ];
  }
}

/**
 * Processing function for checkbox type of field.
 *
 * @param \Drupal\node\NodeInterface $node
 *   Object of node.
 * @param string $local_field_name
 *   Local field name.
 * @param bool $is_translatable
 *   Indicator if node is translatable.
 * @param string $language
 *   Language of translation if applicable.
 * @param array $options
 *   Array of options.
 */
function gc_gc_process_choice_checkbox_field(NodeInterface &$node, $local_field_name, $is_translatable, $language, array $options) {
  $field = FieldConfig::loadByName('node', $node
    ->bundle(), $local_field_name);
  $node->{$local_field_name} = [
    NULL,
  ];
  $selected_options = [];
  foreach ($options as $option) {
    if ($option->selected) {
      if ($field
        ->getType() === 'entity_reference') {

        /** @var \Drupal\taxonomy\Entity\Term $term */
        $term = array_shift(\Drupal::entityTypeManager()
          ->getStorage('taxonomy_term')
          ->loadByProperties([
          'gathercontent_option_ids' => $option->name,
        ]));
        $selected_options[] = $term
          ->id();
      }
      else {
        $selected_options[] = $option->name;
      }
    }
    if ($is_translatable) {
      $node
        ->getTranslation($language)->{$local_field_name} = $selected_options;
    }
    else {
      $node->{$local_field_name} = $selected_options;
    }
  }
}

/**
 * Processing function for radio type of field.
 *
 * @param \Drupal\node\NodeInterface $node
 *   Object of node.
 * @param string $local_field_name
 *   Local field name.
 * @param bool $is_translatable
 *   Indicator if node is translatable.
 * @param string $language
 *   Language of translation if applicable.
 * @param array $options
 *   Array of options.
 */
function gc_gc_process_choice_radio_field(NodeInterface &$node, $local_field_name, $is_translatable, $language, array $options) {
  $field = FieldConfig::loadByName('node', $node
    ->bundle(), $local_field_name);
  foreach ($options as $option) {
    if (!$option->selected) {
      continue;
    }
    if (isset($option->value)) {
      if (empty($option->value)) {
        continue;
      }

      // Dealing with "Other" option.
      if ($field
        ->getType() === 'entity_reference') {

        // Load vocabulary id.
        if (!empty($field
          ->getSetting('handler_settings')['auto_create_bundle'])) {
          $vid = $field
            ->getSetting('handler_settings')['auto_create_bundle'];
        }
        else {
          $handler_settings = $field
            ->getSetting('handler_settings');
          $handler_settings = reset($handler_settings);
          $vid = array_shift($handler_settings);
        }

        // Prepare confitions.
        $condition_array = [
          'name' => $option->value,
          'vid' => $vid,
        ];
        if ($is_translatable && $language !== LanguageInterface::LANGCODE_NOT_SPECIFIED) {
          $condition_array['langcode'] = $language;
        }
        $terms = \Drupal::entityTypeManager()
          ->getStorage('taxonomy_term')
          ->loadByProperties($condition_array);

        /** @var \Drupal\taxonomy\Entity\Term $term */
        $term = array_shift($terms);
        if (empty($term)) {
          $term = Term::create([
            'vid' => $vid,
            'name' => $option->value,
            'langcode' => $language,
          ]);
          $term
            ->save();
        }
        if ($is_translatable && $node
          ->hasTranslation($language)) {
          $node
            ->getTranslation($language)
            ->set($local_field_name, $term
            ->id());
        }
        else {
          $node
            ->set($local_field_name, $term
            ->id());
        }
      }
      else {
        if ($is_translatable) {
          $node
            ->getTranslation($language)->{$local_field_name}->value = $option->value;
        }
        else {
          $node->{$local_field_name}->value = $option->value;
        }
      }
    }
    else {

      // Dealing with predefined options.
      if ($field
        ->getType() === 'entity_reference') {
        $terms = \Drupal::entityTypeManager()
          ->getStorage('taxonomy_term')
          ->loadByProperties([
          'gathercontent_option_ids' => $option->name,
        ]);

        /** @var \Drupal\taxonomy\Entity\Term $term */
        $term = array_shift($terms);
        if (!empty($term)) {
          if ($is_translatable) {
            $node
              ->getTranslation($language)
              ->set($local_field_name, $term
              ->id());
          }
          else {
            $node
              ->set($local_field_name, $term
              ->id());
          }
        }
      }
      else {
        if ($is_translatable) {
          $node
            ->getTranslation($language)->{$local_field_name}->value = $option->name;
        }
        else {
          $node->{$local_field_name}->value = $option->name;
        }
      }
    }
  }
}

/**
 * Processing function for file type of field.
 *
 * @param \Drupal\node\NodeInterface $node
 *   Object of node.
 * @param string $local_field_name
 *   Local field name.
 * @param string $gc_field_name
 *   Name of field in GatherContent.
 * @param bool $is_translatable
 *   Indicator if node is translatable.
 * @param string $language
 *   Language of translation if applicable.
 * @param array $files
 *   Array of remote files.
 */
function gc_gc_process_files_field(NodeInterface &$node, $local_field_name, $gc_field_name, $is_translatable, $language, array $files) {
  $found_files = [];

  /** @var \Drupal\field\Entity\FieldConfig $translatable_file_config */
  $translatable_file_config = $node
    ->getFieldDefinition($local_field_name);
  $translatable_file = $translatable_file_config
    ->get('third_party_settings')['content_translation']['translation_sync']['file'];
  foreach ($files as $file) {
    if ($file->field === $gc_field_name) {
      $drupal_files = \Drupal::entityQuery('file')
        ->condition('gc_id', $file->id)
        ->condition('filename', $file->filename)
        ->execute();
      if (empty($drupal_files)) {
        if (!($node
          ->language()
          ->getId() !== $language && $translatable_file === '0')) {
          $file_dir = $node
            ->getFieldDefinition($local_field_name)
            ->getSetting('file_directory');
          $file_dir = PlainTextOutput::renderFromHtml(\Drupal::token()
            ->replace($file_dir, []));
          $uri_scheme = $node
            ->getFieldDefinition($local_field_name)
            ->getFieldStorageDefinition()
            ->getSetting('uri_scheme') . '://';
          $create_dir = \Drupal::service('file_system')
            ->realpath($uri_scheme) . '/' . $file_dir;
          file_prepare_directory($create_dir, FILE_CREATE_DIRECTORY);
          $local_file = file_save_data(file_get_contents($file->url), $uri_scheme . $file_dir . '/' . $file->filename);
          $local_file
            ->set('gc_id', $file->id)
            ->set('langcode', $language)
            ->set('filesize', $file->size);
          $found_files[] = [
            'target_id' => $local_file
              ->id(),
          ];
        }
      }
      else {
        $drupal_file = reset($drupal_files);
        $found_files[] = [
          'target_id' => $drupal_file,
        ];
      }
    }
  }
  if ($is_translatable) {
    $node
      ->getTranslation($language)
      ->set($local_field_name, $found_files);
  }
  else {
    $node
      ->set($local_field_name, $found_files);
  }
}

/**
 * Batch operation callback.
 *
 * We are doing real import thing here.
 *
 * @param int $gc_id
 *   ID of content we want to import.
 * @param int $status_id
 *   ID of status, if 0 then we don't want to change status.
 * @param string $operation_uuid
 *   UUID of operation.
 * @param bool $drupal_status
 *   Status on node in Drupal - published/unpublished.
 * @param string $node_update_method
 *   Name of the node update method.
 * @param int|null $parent_menu_item
 *   ID of mlid.
 * @param array $context
 *   Context of operation.
 */
function gathercontent_import_process($gc_id, $status_id, $operation_uuid, $drupal_status, $node_update_method, $parent_menu_item = NULL, array &$context = []) {
  if (($nid = _gc_fetcher($gc_id, $operation_uuid, $drupal_status, $node_update_method, $status_id, $parent_menu_item)) !== FALSE) {
    if ($status_id != 0) {

      // Change status.
      $content_obj = new Content();
      $content_obj
        ->updateStatus($gc_id, $status_id);
    }
  }
  $context['results']['uuid'] = $operation_uuid;
}

/**
 * Finished callback.
 *
 * {@inheritdoc}
 */
function gathercontent_import_finished($success, $results, $operations) {
  if ($success) {

    // Select all items with uuid.
    $result = \Drupal::entityQuery('gathercontent_operation_item')
      ->condition('operation_uuid', $results['uuid'])
      ->execute();
    if (!empty($result)) {
      $operation_items = OperationItem::loadMultiple($result);
      $success_counter = 0;
      $nids = [
        'success' => [],
        'failed' => [],
      ];
      foreach ($operation_items as $operation_item) {

        /** @var \Drupal\gathercontent\Entity\OperationItem $operation_item */
        if ($operation_item
          ->getStatus() === 'Success') {
          $success_counter++;
          $nids['success'][] = [
            'nid' => $operation_item
              ->get('nid')->value,
            'gc_id' => $operation_item
              ->get('gc_id')->value,
          ];
        }
        else {
          $nids['failed'][] = [
            'nid' => $operation_item
              ->get('nid')->value,
            'gc_id' => $operation_item
              ->get('gc_id')->value,
          ];
        }
      }
      $unsuccessful = count($result) - $success_counter;
      drupal_set_message(\Drupal::translation()
        ->formatPlural($success_counter, '1 item was imported successfully.', '@count items were imported successfully.'));
      if ($unsuccessful > 0) {
        drupal_set_message(\Drupal::translation()
          ->formatPlural($unsuccessful, '1 item was not imported. Check errors below.', '@count items were not imported. Check errors below.'), 'error');
      }
      \Drupal::service('event_dispatcher')
        ->dispatch(GatherContentEvents::POST_IMPORT, new PostImportEvent($nids['success'], $nids['failed'], $results['uuid']));
    }
    return new RedirectResponse('admin/config/gathercontent/import/result/' . $results['uuid']);
  }
  else {
    $error_operation = reset($operations);
    drupal_set_message(t('An error occurred while processing @operation with arguments : @args', [
      '@operation' => $error_operation[0],
      '@args' => print_r($error_operation[0], TRUE),
    ]), 'error');
  }
  return TRUE;
}

/**
 * Batch operation callback.
 *
 * We are doing real update thing here.
 *
 * @param int $gc_id
 *   ID of content we want to import.
 * @param string $operation_uuid
 *   UUID of operation.
 * @param string $node_update_method
 *   Name of the node update method.
 * @param array $context
 *   Context of operation.
 */
function gathercontent_update_process($gc_id, $operation_uuid, $node_update_method, array &$context) {
  _gc_fetcher($gc_id, $operation_uuid, 0, $node_update_method);
  $context['results']['uuid'] = $operation_uuid;
}

/**
 * Finished callback.
 *
 * @inheritdoc
 */
function gathercontent_update_finished($success, $results, $operations) {
  if ($success) {

    // Select all items with uuid.
    $result = \Drupal::entityQuery('gathercontent_operation_item')
      ->condition('operation_uuid', $results['uuid'])
      ->execute();
    if (!empty($result)) {
      $operation_items = OperationItem::loadMultiple($result);
      $success_counter = 0;
      $nids = [
        'success' => [],
        'failed' => [],
      ];
      foreach ($operation_items as $operation_item) {

        /** @var \Drupal\gathercontent\Entity\OperationItem $operation_item */
        if ($operation_item
          ->getStatus() === 'Success') {
          $success_counter++;
          $nids['success'][] = [
            'nid' => $operation_item
              ->get('nid')->value,
            'gc_id' => $operation_item
              ->get('gc_id')->value,
          ];
        }
        else {
          $nids['failed'][] = [
            'nid' => $operation_item
              ->get('nid')->value,
            'gc_id' => $operation_item
              ->get('gc_id')->value,
          ];
        }
      }
      $unsuccessful = count($result) - $success_counter;
      drupal_set_message(\Drupal::translation()
        ->formatPlural($success_counter, '1 item was imported successfully.', '@count items were imported successfully.'));
      if ($unsuccessful > 0) {
        drupal_set_message(\Drupal::translation()
          ->formatPlural($unsuccessful, '1 item was not imported. Check errors below.', '@count items were not imported. Check errors below.'), 'error');
      }
      \Drupal::service('event_dispatcher')
        ->dispatch(GatherContentEvents::POST_IMPORT, new PostImportEvent($nids['success'], $nids['failed'], $results['uuid']));
    }
    return new RedirectResponse('admin/config/gathercontent/update/result/' . $results['uuid']);
  }
  else {
    $error_operation = reset($operations);
    drupal_set_message(t('An error occurred while processing @operation with arguments : @args', [
      '@operation' => $error_operation[0],
      '@args' => print_r($error_operation[0], TRUE),
    ]), 'error');
  }
  return TRUE;
}

/**
 * Upload batch operation callback.
 *
 * @param \Drupal\node\NodeInterface $entity
 *   Object of entity we want to upload.
 * @param string $uuid
 *   UUID of \Drupal\gathercontent\Entity\Operation entity.
 * @param array $context
 *   Context of operation.
 */
function gathercontent_upload_process(NodeInterface $entity, $uuid, array &$context) {

  // 1. Load template from remote
  // 2. Compare local and remote template
  // 3. If templates are same, load node from remote.
  // 4. Set values based on mapping.
  $mapping = Mapping::load($entity
    ->get('gc_mapping_id')
    ->getValue());
  $tmp_obj = new Template();
  $remote_template = $tmp_obj
    ->getTemplate($mapping
    ->getGathercontentTemplateId());
  $cont_obj = new Content();
  $remote_node = $cont_obj
    ->getContent($entity
    ->get('gc_id')
    ->getValue());
  $config = $remote_node->config;
  $mapping_data = unserialize($mapping
    ->getData());
  $operation_item = \Drupal::entityTypeManager()
    ->getStorage('gathercontent_operation_item')
    ->create([
    'operation_uuid' => $uuid,
    'item_status' => $remote_node->status->data->name,
    'item_status_color' => $remote_node->status->data->color,
    'template_name' => $remote_template->name,
    'item_name' => $remote_node->name,
    'gc_id' => $entity
      ->get('gc_id'),
    'nid' => $entity
      ->id(),
  ]);
  if ($remote_template->config == unserialize($mapping
    ->getTemplate())->config) {
    try {
      foreach ($config as &$pane) {
        $is_translatable = \Drupal::moduleHandler()
          ->moduleExists('content_translation') && \Drupal::service('content_translation.manager')
          ->isEnabled('node', $mapping
          ->getContentType()) && isset($mapping_data[$pane->name]['language']) && $mapping_data[$pane->name]['language'] != Language::LANGCODE_NOT_SPECIFIED;
        if ($is_translatable) {
          $language = $mapping_data[$pane->name]['language'];
        }
        else {
          $language = Language::LANGCODE_NOT_SPECIFIED;
        }
        foreach ($pane->elements as &$field) {
          if (isset($mapping_data[$pane->name]['elements'][$field->name]) && !empty($mapping_data[$pane->name]['elements'][$field->name])) {
            $local_field_name = $mapping_data[$pane->name]['elements'][$field->name];
            if (isset($mapping_data[$pane->name]['type']) && $mapping_data[$pane->name]['type'] === 'content' || !isset($mapping_data[$pane->name]['type'])) {
              $field_info = FieldConfig::loadByName('node', $entity
                ->bundle(), $local_field_name);
              if (!is_null($field_info)) {
                $is_translatable = $is_translatable && $field_info
                  ->isTranslatable();
              }
              switch ($field->type) {
                case 'files':

                  // There is currently no API for manipulating with files.
                  break;
                case 'choice_radio':
                  $option_names = [];
                  foreach ($field->options as &$option) {

                    // Set selected to false for each option.
                    $option->selected = FALSE;
                    $option_names[] = $option->name;
                  }

                  // Fetch local selected option.
                  if ($is_translatable) {
                    $selected = $entity
                      ->getTranslation($language)->{$local_field_name}->value;
                  }
                  else {
                    $selected = $entity->{$local_field_name}->value;
                  }
                  if (!in_array($selected, $option_names)) {

                    // If it's other, then find that option in remote.
                    foreach ($field->options as &$option) {
                      if (isset($option->value)) {
                        $option->selected = TRUE;
                        $option->value = $selected;
                      }
                    }
                  }
                  else {

                    // If it's checkbox, find it by remote option name,
                    // which should be same.
                    foreach ($field->options as &$option) {
                      if ($option->name == $selected) {
                        $option->selected = TRUE;
                      }
                    }
                  }
                  break;
                case 'choice_checkbox':
                  foreach ($field->options as &$option) {

                    // Set selected to false for each option.
                    $option->selected = FALSE;
                  }

                  // Fetch local selected option.
                  if ($is_translatable) {
                    $selected = $entity
                      ->getTranslation($language)->{$local_field_name}->value;
                  }
                  else {
                    $selected = $entity->{$local_field_name}->value;
                  }

                  // If it's checkbox, find it by remote option name,
                  // which should be same.
                  foreach ($field->options as &$option) {
                    if (isset($selected[$option->name])) {
                      $option->selected = TRUE;
                    }
                  }
                  break;
                case 'section':

                  // We don't upload this because this field shouldn't be
                  // edited.
                  break;
                default:
                  if ($local_field_name === 'title') {
                    if ($is_translatable) {
                      $field->value = $entity
                        ->getTranslation($language)
                        ->getTitle();
                    }
                    else {
                      $field->value = $entity
                        ->getTitle();
                    }
                  }
                  else {
                    if ($is_translatable) {
                      $field->value = $entity
                        ->getTranslation($language)->{$local_field_name}->value;
                    }
                    else {
                      $field->value = $entity->{$local_field_name}->value;
                    }
                  }
                  break;
              }
            }
            elseif ($mapping_data[$pane->name]['type'] === 'metatag') {
              if (\Drupal::moduleHandler()
                ->moduleExists('metatag') && check_metatag($entity
                ->getType())) {
                $metatag_fields = get_metatag_fields($entity
                  ->getType());
                foreach ($metatag_fields as $metatag_field) {
                  if ($is_translatable) {
                    $field->value = $entity
                      ->getTranslation($language)->{$metatag_field}
                      ->value();
                  }
                  else {
                    $field->value = $entity->{$metatag_field}
                      ->value();
                  }
                }
              }
            }
          }
          else {
            $operation_item->status = "System error, please contact you administrator.";
            $operation_item
              ->save();
          }
        }
      }
      $event = \Drupal::service('event_dispatcher')
        ->dispatch(GatherContentEvents::PRE_NODE_UPLOAD, new PreNodeUploadEvent($entity, $config));

      /** @var \Drupal\gathercontent\Event\PreNodeUploadEvent $event */
      $config = $event
        ->getGathercontentValues();
      if ($cont_obj
        ->postContent($entity
        ->get('gc_id')
        ->getValue(), $config)) {
        $operation_item->status = "Success";
        $operation_item
          ->save();
        \Drupal::service('event_dispatcher')
          ->dispatch(GatherContentEvents::POST_NODE_UPLOAD, new PostNodeUploadEvent($entity, $config));
      }
      else {
        $operation_item->status = 'Mapping doesn\'t match';
        $operation_item
          ->save();
      }
    } catch (\Exception $e) {
      \Drupal::logger('gc_upload')
        ->error(print_r($e, TRUE), []);
      $operation_item->status = 'Mapping doesn\'t match';
      $operation_item
        ->save();
    }
  }
  else {
    $operation_item->status = 'Mapping doesn\'t match';
    $operation_item
      ->save();
  }
  $context['results']['uuid'] = $uuid;
}

/**
 * Finished callback.
 *
 * @inheritdoc
 */
function gathercontent_upload_finished($success, $results, $operations) {
  if ($success) {

    // Select all items with uuid.
    $result = \Drupal::entityQuery('gathercontent_operation_item')
      ->condition('operation_uuid', $results['uuid'])
      ->execute();
    if (!empty($result)) {
      $operation_items = OperationItem::loadMultiple($result);
      $success_counter = 0;
      $nids = [
        'success' => [],
        'failed' => [],
      ];
      foreach ($operation_items as $operation_item) {

        /** @var \Drupal\gathercontent\Entity\OperationItem $operation_item */
        if ($operation_item
          ->getStatus() === 'Success') {
          $success_counter++;
          $nids['success'][] = [
            'nid' => $operation_item
              ->get('nid')->value,
            'gc_id' => $operation_item
              ->get('gc_id')->value,
          ];
        }
        else {
          $nids['failed'][] = [
            'nid' => $operation_item
              ->get('nid')->value,
            'gc_id' => $operation_item
              ->get('gc_id')->value,
          ];
        }
      }
      $unsuccessful = count($result) - $success_counter;
      drupal_set_message(\Drupal::translation()
        ->formatPlural($success_counter, '1 item was uploaded successfully.', '@count items were uploaded successfully.'));
      if ($unsuccessful > 0) {
        drupal_set_message(\Drupal::translation()
          ->formatPlural($unsuccessful, '1 item was not uploaded. Check errors below.', '@count items were not uploaded. Check errors below.'), 'error');
      }
      \Drupal::service('event_dispatcher')
        ->dispatch(GatherContentEvents::POST_UPLOAD, new PostUploadEvent($nids['success'], $nids['failed'], $results['uuid']));
    }
    return new RedirectResponse('admin/config/gathercontent/upload/result/' . $results['uuid']);
  }
  else {
    $error_operation = reset($operations);
    drupal_set_message(t('An error occurred while processing @operation with arguments : @args', [
      '@operation' => $error_operation[0],
      '@args' => print_r($error_operation[0], TRUE),
    ]), 'error');
  }
  return TRUE;
}

/**
 * Check if content type has any metatag fields.
 *
 * @param string $content_type
 *   Machine name of content type.
 *
 * @return bool
 *   TRUE if metatag field exists.
 */
function check_metatag($content_type) {
  $instances = \Drupal::service('entity_field.manager')
    ->getFieldDefinitions('node', $content_type);
  foreach ($instances as $name => $instance) {

    /** @var \Drupal\Core\Field\FieldDefinitionInterface $instance */
    if ($instance
      ->getType() === 'metatag') {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Get list of metatag fields.
 *
 * @param string $content_type
 *   Machine name of content type.
 *
 * @return array
 *   Array of metatag fields.
 */
function get_metatag_fields($content_type) {
  $instances = \Drupal::service('entity_field.manager')
    ->getFieldDefinitions('node', $content_type);
  $fields = [];
  foreach ($instances as $name => $instance) {

    /** @var \Drupal\Core\Field\FieldDefinitionInterface $instance */
    if ($instance
      ->getType() === 'metatag') {
      $fields[] = $instance
        ->getName();
    }
  }
  return $fields;
}

Functions

Namesort descending Description
check_metatag Check if content type has any metatag fields.
gathercontent_entity_base_field_info Implements hook_entity_base_field_info().
gathercontent_import_finished Finished callback.
gathercontent_import_process Batch operation callback.
gathercontent_update_finished Finished callback.
gathercontent_update_process Batch operation callback.
gathercontent_upload_finished Finished callback.
gathercontent_upload_process Upload batch operation callback.
gc_create_menu_link Create menu link if requested.
gc_gc_process_choice_checkbox_field Processing function for checkbox type of field.
gc_gc_process_choice_radio_field Processing function for radio type of field.
gc_gc_process_content_pane Processing function for content panes.
gc_gc_process_default_field Default processing function, when no other matches found, usually for text.
gc_gc_process_files_field Processing function for file type of field.
gc_gc_process_metatag_pane Processing function for metatag panes.
gc_gc_process_section_field Processing function for section type of field.
gc_get_destination_node Get Node object based on type of update.
get_metatag_fields Get list of metatag fields.
_gc_fetcher Function for fetching, creating and updating content from GatherContent.
_gc_get_menu_by_gc_id Load menu name and menu link id for other languages by node ID.