<?php
/**
 * Import Post Controller
 *
 * This file enables advanced post imports inside the GREYD.SUITE.
 * Posts of all supported post types can be exported via the WordPress
 * backend (edit.php) and later be imported to any GREYD.SUITE site.
 *
 * The export contains a JSON file that consists of one or multiple
 * arrays of all exported posts. Each of the array contains the following:
 *  - post_elements     array of WP_Post properties
 *  - meta              all post_meta information
 *  - taxonomies        all taxonomy terms
 *  - media             all nested media elements (thumbnail & other)
 *
 * @since 0.8.4
 */
namespace Greyd;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

new Post_Import();

class Post_Import {

	/**
	 * Holds all WP_Post objects for either ex- or import.
	 *
	 * @var array
	 */
	public static $posts = array();

	/**
	 * Holds all WP_Media objects for either ex- or import.
	 *
	 * @var array
	 */
	public static $media = array();

	/**
	 * Constructor
	 */
	public function __construct() {

		add_action( 'greyd_ajax_mode_check_post_import', array( $this, 'check_import' ) );
		add_action( 'greyd_ajax_mode_post_import', array( $this, 'handle_import' ) );
		add_action( 'greyd_ajax_mode_posttype_import', array( $this, 'handle_posttype_import' ) );
		add_filter( 'greyd_import_post_meta-dynamic_meta', array( $this, 'set_dynamic_meta' ), 10, 2 );

		add_filter( 'greyd_filter_post_content_before_post_import', array( $this, 'filter_block_content_on_import' ), 10, 3 );
	}

	/**
	 * Check the uploaded file
	 *
	 * @action 'greyd_ajax_mode_check_post_import'
	 *
	 * @param array $data   holds the $_FILES['data']
	 */
	public function check_import( $data ) {

		Post_Export_Helper::enable_logs();

		do_action( 'greyd_post_export_log', "\r\n\r\n" . 'CHECK IMPORT POST DATA' . "\r\n\r\n", $data );

		set_time_limit( 5000 );

		// check for errors
		if ( isset( $data['error'] ) && $data['error'] > 0 ) {
			$file_errors = array(
				1 => sprintf(
					__( "The uploaded file exceeds the server's maximum file limit (max %s MB). The limit is defined in the <u>php.ini</u> file.", 'greyd_hub' ),
					intval( ini_get( 'upload_max_filesize' ) )
				),
				2 => __( "The uploaded file exceeds the allowed file size of the html form.", 'greyd_hub' ),
				3 => __( "The uploaded file was only partially uploaded.", 'greyd_hub' ),
				4 => __( "No file was uploaded.", 'greyd_hub' ),
				6 => __( "Missing a temporary folder.", 'greyd_hub' ),
				7 => __( "Failed to save the file.", 'greyd_hub' ),
				8 => __( "The file was stopped while uploading.", 'greyd_hub' ),
			);
			Post_Export_Helper::error( $file_errors[ $data['error'] ] );
		}

		// file info
		$filename = $data['name'];
		$filepath = $data['tmp_name'];
		$filetype = $data['type'];

		// check filetype
		if ( $filetype != 'application/zip' && $filetype != 'application/x-zip-compressed' ) {
			Post_Export_Helper::error( __( "Please select a valid ZIP archive.", 'greyd_hub' ) );
		}
		do_action( 'greyd_post_export_log', '  - file is valid zip.' );

		// create tmp zip
		$new_file = Post_Export_Helper::get_file_path( 'tmp' ) . $filename;
		$result   = move_uploaded_file( $filepath, $new_file );
		do_action( 'greyd_post_export_log', sprintf( '  - temporary file „%s“ created.', $new_file ) );

		// get post data
		$post_data = self::get_zip_posts_file_contents( $new_file );

		if ( ! is_array( $post_data ) ) {
			Post_Export_Helper::error( $post_data );
		}

		// get conflicting posts
		$conflicts = self::import_get_conflict_posts_for_backend_form( $post_data );

		if ( $conflicts ) {
			$return = $conflicts;
		}
		// return file name when no conflicts found
		else {
			$return = $filename;
		}

		Post_Export_Helper::success( $return );
	}

	/**
	 * Handle the ajax import action.
	 *
	 * @action 'greyd_ajax_mode_post_import'
	 *
	 * @param array $data   holds the $_POST['data']
	 */
	public function handle_import( $data ) {

		Post_Export_Helper::enable_logs();

		do_action( 'greyd_post_export_log', "\r\n\r\n" . 'HANDLE IMPORT' . "\r\n", $data );

		set_time_limit( 5000 );

		$filename = isset( $data['filename'] ) ? $data['filename'] : '';

		if ( empty( $filename ) ) {
			Post_Export_Helper::error( __( "The file name is empty.", 'greyd_hub' ) );
		}

		// get post data
		$zip_file  = Post_Export_Helper::get_file_path( 'tmp' ) . $filename;
		$post_data = self::get_zip_posts_file_contents( $zip_file );

		// error
		if ( ! is_array( $post_data ) ) {
			Post_Export_Helper::error( $post_data );
		}

		// get conflicts with current posts
		$conflicts        = isset( $data['conflicts'] ) ? (array) $data['conflicts'] : array();
		$conflict_actions = self::import_get_conflict_actions_from_backend_form( $conflicts );

		$result = self::import_posts( $post_data, $conflict_actions, $zip_file );

		if ( is_wp_error( $result ) ) {
			Post_Export_Helper::error( $result->get_error_message() );
		}

		// delete tmp file
		self::delete_tmp_files();

		Post_Export_Helper::success( sprintf( __( "Post file '%s' has been imported successfully.", 'greyd_hub' ), $filename ) );
	}

	/**
	 * Handle the ajax import action of a posttype.
	 *
	 * @since 1.2.7
	 *
	 * This differs from handle_import() as it's only importing the
	 * first post (which is the posttype) and returns back to the
	 * javascript handler, which then calls handle_import() with a
	 * second AJAX call.
	 * This ensures all the taxonomies etc. are setup before importing
	 * the actual posts - otherwise they could not be set.
	 *
	 * @action 'greyd_ajax_mode_posttype_import'
	 *
	 * @param array $data   holds the $_POST['data']
	 */
	public function handle_posttype_import( $data ) {

		Post_Export_Helper::enable_logs();

		do_action( 'greyd_post_export_log', "\r\n\r\n" . 'HANDLE IMPORT POSTTYPE' . "\r\n", $data );

		set_time_limit( 5000 );

		$filename = isset( $data['filename'] ) ? $data['filename'] : '';

		if ( empty( $filename ) ) {
			Post_Export_Helper::error( __( "The file name is empty.", 'greyd_hub' ) );
		}

		// get post data
		$zip_file  = Post_Export_Helper::get_file_path( 'tmp' ) . $filename;
		$post_data = self::get_zip_posts_file_contents( $zip_file );

		// error
		if ( ! is_array( $post_data ) ) {
			Post_Export_Helper::error( $post_data );
		}

		// get conflicts with current posts
		$conflicts        = isset( $data['conflicts'] ) ? (array) $data['conflicts'] : array();
		$conflict_actions = self::import_get_conflict_actions_from_backend_form( $conflicts );

		// only insert the first post, as it is the posttype itself
		$first_key     = array_key_first( $post_data );
		$new_post_data = array( $first_key => $post_data[ $first_key ] );

		// import the post
		$result = self::import_posts( $new_post_data, $conflict_actions, $zip_file );

		if ( is_wp_error( $result ) ) {
			Post_Export_Helper::error( $result->get_error_message() );
		}

		// add the newly created post to the conflict actions
		$new_post_id   = self::$posts[ $first_key ];
		$new_conflicts = array_merge(
			$conflicts,
			array(
				$first_key . '-' . $new_post_id => 'skip',
			)
		);

		// return the new conflicts array to the Javascript handler
		Post_Export_Helper::success( json_encode( $new_conflicts ) );
	}

	/**
	 * Import posts
	 *
	 * @param WP_Post[] $posts          Preparred post objects.
	 * @param array     $conflict_actions   Array of posts that already exist on the current blog.
	 *                                      Keyed by the same ID as in the @param $posts.
	 *                                  @property post_id: ID of the current post.
	 *                                  @property action: Action to be done (skip|replace|keep)
	 * @param string    $zip_file          Path to imported ZIP archive. (optional)
	 *
	 * @return mixed                    True on success. WP_Error on fatal conflict.
	 */
	public static function import_posts( $posts, $conflict_actions = array(), $zip_file = '' ) {

		// reset the class vars
		self::$posts = array();
		self::$media = array();

		$first_post = null;

		/**
		 * Filter the current posts on import
		 *
		 * @filter 'greyd_import_conflict_actions'
		 *
		 * @param array $conflict_actions   All existing posts with a conflict.
		 * @param array $posts              All posts to be imported.
		 */
		$conflict_actions = (array) apply_filters( 'greyd_import_conflict_actions', $conflict_actions, $posts );

		/**
		 * Loop through posts and insert them
		 */
		foreach ( $posts as $post_id => $post ) {

			do_action( 'greyd_post_export_log', "\r\n----------\r\n" );

			// typecasting $post arrays (eg. via remote requests)
			$post = is_array( $post ) ? (object) $post : $post;
			if ( ! is_object( $post ) ) {
				do_action( 'greyd_post_export_log', sprintf( "  - WP_Post object for post of ID '%s' not set correctly:", $post_id ), $post );
				continue;
			}

			$is_first_post = false;
			if ( ! $first_post ) {
				$first_post    = $post;
				$is_first_post = true;
			}

			do_action( 'greyd_post_export_log', "\r\n" . sprintf( "Insert post '%s'.", $post->post_name ) );

			// handle the post language
			if (
				isset( $post->language )
				&& (
					( is_array($post->language) && isset( $post->language['code'] ) && ! empty( $post->language['code'] ) )
					|| ( is_object($post->language) && isset( $post->language->code ) && ! empty( $post->language->code ) )
				)
			) {

				$post_language_args       = isset( $post->language ) ? (array) $post->language : array();
				$post_language_code       = isset( $post_language_args['code'] ) ? $post_language_args['code'] : '';
				$translation_post_ids     = isset( $post_language_args['post_ids'] ) ? (array) $post_language_args['post_ids'] : array();
				$languages_on_this_site   = Post_Export_Helper::get_language_codes();
				$languages_of_this_post   = ! empty( $translation_post_ids ) ? array_keys( $translation_post_ids ) : array();
				$supported_translations   = array_intersect( $languages_on_this_site, $languages_of_this_post );
				$unsupported_translations = array_diff( $languages_of_this_post, $languages_on_this_site );
				$skip_this_translation    = false;

				// switch to the post's language
				$language_switched = Post_Export_Helper::switch_to_post_lang( $post );

				if ( $language_switched ) {
					do_action( 'greyd_post_export_log', '  - language supported & switched!' );
				} elseif ( count( $supported_translations ) > 0 ) {

					// skip this post if there is a supported translated version
					do_action( 'greyd_post_export_log', '  - There is at least 1 supported language for this post: ' . implode( ', ', $supported_translations ) );
					$skip_this_translation = true;
				} else {
					do_action( 'greyd_post_export_log', '  - There is no supported language for this post.' );

					// check if another translation of this post has already been imported
					if ( ! empty( $translation_post_ids ) ) {
						foreach ( (array) $translation_post_ids as $lang => $translated_post_id ) {
							if ( isset( self::$posts[ $translated_post_id ] ) ) {
								do_action( 'greyd_post_export_log', '  - Another translation of this post has already been imported - we skip this one.' );

								self::$posts[ $post_id ] = $current_post_id;
								$skip_this_translation   = true;
								break;
							}
						}
						do_action( 'greyd_post_export_log', '  - No other translation of this post has been imported - we import this one.' );
					}
				}

				// skip this translation
				if ( $skip_this_translation ) {
					// delete all copies of this post (which is not supported, and a worse translation...)
					if ( isset( $conflict_actions[ $post_id ] ) && isset( $conflict_actions[ $post_id ]['post_id'] ) ) {
						$translated_id = $conflict_actions[ $post_id ]['post_id'];
						$deleted       = wp_trash_post( $translated_id );
						if ( $deleted ) {
							do_action( 'greyd_post_export_log', "  - This version (id: $translated_id ) was trashed, we import a better suited translation." );
						}
					}
					// remove it from the import array to not change it at all
					unset( $posts[ $post_id ] );
					continue;
				}
			}

			/**
			 * Filter the post array before it is imported.
			 *
			 * @filter 'greyd_import_postarr'
			 *
			 * @param array $postarr        Array of post params used for the import.
			 * @param WP_Post $post         Preparred WP_Post object (including meta, taxonomy terms etc.).
			 * @param bool $is_first_post   Whether this is the first post of the import.
			 */
			$postarr = apply_filters(
				'greyd_import_postarr',
				array(
					'post_title'   => $post->post_title,
					'post_name'    => $post->post_name,
					'post_content' => $post->post_content,
					'post_excerpt' => $post->post_excerpt,
					'post_type'    => $post->post_type,
					'post_author'  => isset( $post->post_author ) ? $post->post_author : get_current_user_id(),
					'post_date'    => $post->post_date,
					'post_status'  => $post->post_status,
				),
				$post,
				$is_first_post
			);

			// handle conflict actions
			if ( isset( $conflict_actions[ $post_id ] ) ) {
				$current_post    = (array) $conflict_actions[ $post_id ];
				$current_post_id = $current_post['post_id'];
				$conflict_action = $current_post['action'];

				if ( $conflict_action === 'replace' ) {
					do_action( 'greyd_post_export_log', sprintf( '  - replace existing post with ID: %s.', $current_post_id ) );
					// add @property ID to the array to replace the existing post.
					$postarr['ID'] = $current_post_id;
				} elseif ( $conflict_action === 'skip' ) {
					do_action( 'greyd_post_export_log', sprintf( '  - skip this post and use the existing post: %s.', $current_post_id ) );
					// add the post to the class var for later replacement
					self::$posts[ $post_id ] = $current_post_id;
					// remove it from the import array to not change it at all
					unset( $posts[ $post_id ] );
					continue;
				} elseif ( $conflict_action === 'update' ) {
					do_action( 'greyd_post_export_log', sprintf( '  - update the existing post: %s.', $current_post_id ) );
					// add the post to the class var for later replacement
					self::$posts[ $post_id ] = $current_post_id;
					continue;
				} elseif ( $conflict_action === 'keep' ) {
					do_action( 'greyd_post_export_log', '  - insert post with new ID' );
				}
			}

			// now we insert the post
			do_action( 'greyd_post_export_log', '  - try to insert post with the following data:', array_map(
				function( $value ) {
					return is_string( $value ) ? esc_attr( $value ) : $value;
				},
				$postarr
			) );
			$result = self::create_post( $postarr, $post, $zip_file );

			if ( is_wp_error( $result ) ) {
				return $result;
			} elseif ( $result ) {
				self::$posts[ $post_id ] = $result;

				/**
				 * Set the new post id for all unsupported translations of this post as well
				 *
				 * @since 1.0.9
				 */
				if ( isset( $unsupported_translations ) && ! empty( $unsupported_translations ) ) {
					foreach ( $unsupported_translations as $lang_code ) {
						if ( $lang_code != $post_language_code ) {
							$old_post_id                 = $translation_post_ids[ $lang_code ];
							self::$posts[ $old_post_id ] = $post_id;
							do_action( 'greyd_post_export_log', "  - unsupported translation of this post has been linked with this post (old_post_id: $old_post_id, new_id: $post_id)" );
						}
					}
				}
			}
		}

		/**
		 * Check if first post is a custom posttype.
		 * If it is, we propably need to do some additional action, such as
		 * rewrite the permalinks.
		 */
		if (
			class_exists( '\Greyd\Posttypes\Dynamic_Posttypes' )
			&& $first_post->post_type === 'tp_posttypes'
		) {
			do_action( 'greyd_post_export_log', '  - first post is a posttype: ' . $first_post->post_name );

			// register all dynamic post types & taxonomies
			\Greyd\Posttypes\Dynamic_Posttypes::add_dynamic_posttypes();

			// save transient for rewriting permalink rules
			set_transient( 'flush_rewrite', true );
		}

		do_action( 'greyd_post_export_log', "\r\n----------\r\n\r\n" . 'All posts imported. Now we loop through them.' );

		/**
		 * After we inserted all the posts, we can now do additional actions
		 */
		foreach ( $posts as $old_post_id => $post ) {

			$post = is_array( $post ) ? (object) $post : $post;

			if ( ! isset( self::$posts[ $old_post_id ] ) ) {
				continue;
			}

			$new_post_id = self::$posts[ $old_post_id ];

			do_action( 'greyd_post_export_log', sprintf( "\r\n" . "Check new post '%s' (old id: %s)", $new_post_id, $old_post_id ) );

			// switch to the post's language
			Post_Export_Helper::switch_to_post_lang( $post );

			// update the post content
			if ( ! empty( $post->post_content ) ) {

				$content = $post->post_content;

				// replace nested posts in post content
				$content = self::replace_nested_posts( $content, $post );

				// switch to the post's language again as we could have been
				// switched during the replacement of nested posts.
				Post_Export_Helper::switch_to_post_lang( $post );

				// replace nested terms in post content
				$content = self::replace_nested_terms( $content, $post );

				// replace strings in post content
				$content = self::replace_strings( $content, $new_post_id );

				/**
				 * Filter post content before import.
				 *
				 * @filter 'greyd_filter_post_content_before_post_import'
				 *
				 * @param string    $content    The post content.
				 * @param int       $post_id    The post ID.
				 * @param object    $post       The post object.
				 */
				$content = apply_filters( 'greyd_filter_post_content_before_post_import', $content, $new_post_id, $post );

				// update the post content
				$result = wp_update_post(
					array(
						'ID'           => $new_post_id,
						'post_content' => $content,
					),
					true,
					false
				);

				if ( is_wp_error( $result ) ) {
					do_action( 'greyd_post_export_log', '  - post-content could not be updated.' );
				} else {
					do_action( 'greyd_post_export_log', '  - post-content successfully updated.' );
				}
			}

			/**
			 * ------   I M P O R T A N T   ------
			 *
			 * All additonal actions to the post, like adding post-meta options or
			 * setting taxonomy terms need to be done AFTER we called 'wp_update_post'
			 * to update the post-content.
			 * Otherwise those changes are overwritten!
			 */

			// set meta options
			if ( ! empty( $post->meta ) ) {
				self::set_meta( $new_post_id, $post->meta, $post );
			}

			// set terms
			if ( ! empty( $post->terms ) ) {
				self::set_taxonomy_terms( $new_post_id, $post->terms, $post );
			}

			// set translations
			self::set_translations( $new_post_id, $post );

			// replace thumbnail ID
			if ( $thumbnail_id  = get_post_thumbnail_id( $new_post_id ) ) {
				do_action( 'greyd_post_export_log', "\r\n" . sprintf( "Replace thumbnail for post '%s'.", $post->post_name ) );
				$result = false;
				if ( isset( self::$posts[ $thumbnail_id ] ) ) {
					$result = set_post_thumbnail( $new_post_id, self::$posts[ $thumbnail_id ] );
				}
				if ( $result ) {
					do_action( 'greyd_post_export_log', sprintf( "  - thumbnail ID changed from '%s' to '%s'", $thumbnail_id, self::$posts[ $thumbnail_id ] ) );
				} else {
					do_action( 'greyd_post_export_log', sprintf( "  - thumbnail ID '%s' could not be changed.", $thumbnail_id ) );
				}
			}

			/**
			 * Add action to handle additional actions after a post was imported.
			 *
			 * @action 'greyd_after_import_post'
			 */
			do_action( 'greyd_after_import_post', $new_post_id, $post );
		}

		return true;
	}

	/**
	 * Insert a single post
	 *
	 * if $postarr['ID'] is set, the post gets updated. otherwise a new post is created.
	 *
	 * @param array   $postarr  All arguments to be set via wp_insert_post().
	 * @param WP_Post $post     Post object with additional properties.
	 *                          See export_post() for details.
	 * @param string  $zip_file (optional) Full path to the imported ZIP archive.
	 *
	 * @return mixed  Post-ID on success. WP_Error on failure.
	 */
	public static function create_post( $postarr, $post, $zip_file = '' ) {

		// do_action( "greyd_post_export_log", "  - create post with the following attributes: ", $postarr );

		// normal post
		if ( $postarr['post_type'] !== 'attachment' ) {

			// insert post
			$new_post_id = wp_insert_post( $postarr, true, false );

			// error
			if ( is_wp_error( $new_post_id ) ) {
				do_action( 'greyd_post_export_log', "\r\nPost could not be inserted: " . $new_post_id->get_error_message() );
			}
			// success
			else {
				do_action( 'greyd_post_export_log', sprintf( "\r\nPost inserted with the ID '%s'", strval( $new_post_id ) ) );
			}
		}
		// attachment
		else {

			$file  = false;
			$media = isset( $post->media ) ? (array) $post->media : null;

			if ( $media && isset( $media['name'] ) ) {

				$filename    = $media['name'];
				$time_folder = date( 'Y\/m', strtotime( $postarr['post_date'] ) );
				$upload_dir  = wp_upload_dir( $time_folder, true, true );

				if ( ! empty( $zip_file ) ) {
					$file_data = self::get_zip_media_file( $zip_file, $filename );
				} else {
					$file_data = Helper::get_file_contents( $media['url'] );
				}

				if ( wp_mkdir_p( $upload_dir['path'] ) ) {
					$file = $upload_dir['path'] . '/' . $filename;
				} else {
					$file = $upload_dir['basedir'] . '/' . $filename;
				}

				// delete old files if attachment is being replaced
				if ( isset( $postarr['ID'] ) ) {
					do_action( 'greyd_post_export_log', '  - delete old attachment files.' );

					$result = self::delete_current_attachment_files( $postarr['ID'], $file );

					if ( ! $result ) {
						do_action( 'greyd_post_export_log', '  - old attachment files could not be deleted.' );
					} else {
						do_action( 'greyd_post_export_log', '  - old attachment files deleted.' );
					}
				}

				$bytes = file_put_contents( $file, $file_data );
				if ( $bytes === false ) {
					do_action( 'greyd_post_export_log', "  - attachment file '$file' could not be written." );
				} else {
					do_action( 'greyd_post_export_log', "  - attachment file '$file' written (size: {$bytes}b)." );
				}

				// add mime type
				$postarr['post_mime_type'] = wp_check_filetype( $filename, null )['type'];
			}

			// insert post
			$new_post_id = wp_insert_attachment( $postarr, $file );

			// error
			if ( is_wp_error( $new_post_id ) ) {
				do_action( 'greyd_post_export_log', "\r\nMedia file could not be inserted: " . $new_post_id->get_error_message() );
			}
			// success
			else {
				// regenerate attachment metadata
				if ( $file ) {
					do_action( 'greyd_post_export_log', '  - regenerate attachment metadata.' );

					require_once ABSPATH . 'wp-admin/includes/image.php';
					$attach_data = wp_generate_attachment_metadata( $new_post_id, $file );
					$result      = wp_update_attachment_metadata( $new_post_id, $attach_data );
					if ( $result === false ) {
						do_action( 'greyd_post_export_log', '  - attachment metadata could not be updated: ', $attach_data );
					} else {
						do_action( 'greyd_post_export_log', '  - attachment metadata updated: ', $attach_data );
					}
				}

				do_action( 'greyd_post_export_log', sprintf( "\r\nMedia file inserted with the ID '%s'", $new_post_id ) );
			}
		}

		return $new_post_id;
	}

	/**
	 * Delete all current attachment files.
	 *
	 * @see EnableMediaReplace\Replacer->removeCurrent()
	 * @link https://github.com/short-pixel-optimizer/enable-media-replace/blob/master/classes/replacer.php
	 *
	 * @param int    $post_id           The attachment WP_Post ID.
	 * @param string $new_file_path     URL to the new file (does not exist yet).
	 *
	 * @return bool
	 */
	public static function delete_current_attachment_files( $post_id, $new_file_path ) {

		$old_file_path = get_attached_file( $post_id );
		$meta          = wp_get_attachment_metadata( $post_id );
		$backup_sizes  = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
		$result        = wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $old_file_path );

		// @todo replace occurences of the new file path on the entire website
		if ( $new_file_path !== $old_file_path ) {

		}

		return $result;
	}

	/**
	 * Replace all nested posts inside a subject, often the post content.
	 *
	 * @param  string $subject  The subject were the strings need to be replaced.
	 * @param  object $post     Preparred post object with the @property 'nested'.
	 *
	 * @return string $content  Content with all nested elements replaced.
	 */
	public static function replace_nested_posts( $subject, $post ) {

		// get $post->nested
		$nested = isset( $post->nested ) ? ( is_array( $post->nested ) || is_object( $post->nested ) ? (array) $post->nested : null ) : null;

		if (
			empty( $nested ) ||
			empty( $subject )
		) {
			do_action( 'greyd_post_export_log', "\r\n" . sprintf( "No nested elements found for post '%s'.", $post->post_name ) );
			return $subject;
		}

		do_action( 'greyd_post_export_log', "\r\n" . sprintf( "Replace nested elements for post '%s'.", $post->post_name ) );

		foreach ( $nested as $nested_id => $nested_postarr ) {

			if ( ! is_array( $nested_postarr ) ) {
				$nested_postarr = (array) $nested_postarr;
			}

			$replace_string = self::get_nested_post_replacement( $nested_id, $nested_postarr );

			// replace the string
			$subject = str_replace( '{{' . $nested_id . '}}', $replace_string, $subject );

			// replace the front url if post ID was found
			if ( is_numeric( $replace_string ) ) {

				$replace_front_url = $nested_postarr['post_type'] === 'attachment' ? wp_get_attachment_url( $replace_string ) : get_permalink( $replace_string );

				// replace '{{' . $nested_id . '-front-url}}'
				$subject = str_replace( '{{' . $nested_id . '-front-url}}', $replace_front_url, $subject );
			}

			do_action( 'greyd_post_export_log', sprintf( "  - replace '%s' with '%s'.", '{{' . $nested_id . '}}', $replace_string ) );
		}

		do_action( 'greyd_post_export_log', '=> nested elements were replaced' );
		return $subject;
	}

	/**
	 * Get the replacement for the original ID.
	 *
	 * This function looks for an imported or existing post by ID,
	 * name and posttype. If nothing is found, either the original
	 * ID, post_name or frontend url (for attachments) are returned.
	 */
	public static function get_nested_post_replacement( $nested_id, $nested_postarr ) {

		// post imported: use the imported post-ID
		if ( isset( self::$posts[ $nested_id ] ) ) {
			$replace_string = self::$posts[ $nested_id ];
		}
		// no postarr set: use the initial post-ID
		elseif ( ! $nested_postarr || ! is_array( $nested_postarr ) ) {
			$replace_string = $nested_id;
		}
		// post exists: use existing post-ID
		elseif ( $existing_post = Post_Export_Helper::get_post_by_name_and_type( (object) $nested_postarr ) ) {
			$replace_string = $existing_post->ID;
		}
		// attachments: use the frontend url
		elseif ( $nested_postarr['post_type'] === 'attachment' ) {
			$replace_string = $nested_postarr['front_url'];
		}
		// fallback: use the name
		else {
			$replace_string = $nested_postarr['post_name'];
		}
		return $replace_string;
	}

	/**
	 * Replace all nested terms inside a subject, often the post content.
	 *
	 * @param  string $subject  The subject were the strings need to be replaced.
	 * @param  object $post     Preparred post object with the @property 'nested'.
	 *
	 * @return string $content  Content with all nested elements replaced.
	 */
	public static function replace_nested_terms( $subject, $post ) {

		// get $post->nested_terms
		$nested = isset( $post->nested_terms ) ? ( is_array( $post->nested_terms ) || is_object( $post->nested_terms ) ? (array) $post->nested_terms : null ) : null;

		if (
			empty( $nested ) ||
			empty( $subject )
		) {
			do_action( 'greyd_post_export_log', "\r\n" . sprintf( "No nested elements found for post '%s'.", $post->post_name ) );
			return $subject;
		}

		do_action( 'greyd_post_export_log', "\r\n" . sprintf( "Replace nested terms for post '%s'.", $post->post_name ) );

		foreach ( $nested as $nested_id => $nested_term_object ) {

			if ( ! is_object( $nested_term_object ) ) {
				$nested_term_object = (object) $nested_term_object;
			}

			$term_object = get_term_by( 'slug', $nested_term_object->slug, $nested_term_object->taxonomy );

			if ( $term_object ) {
				$replace_string = $term_object->term_id;
				do_action( 'greyd_post_export_log', "  - term of taxonomy '{$nested_term_object->taxonomy}' with slug '{$nested_term_object->slug}' found.", $term_object );
			} else {
				$replace_string = $nested_id;
				do_action( 'greyd_post_export_log', "  - term of taxonomy '{$nested_term_object->taxonomy}' with slug '{$nested_term_object->slug}' could not be found." );
			}

			// replace the string
			$subject = str_replace( '{{' . $nested_id . '}}', $replace_string, $subject );

			do_action( 'greyd_post_export_log', sprintf( "  - replace '%s' with '%s'.", '{{' . $nested_id . '}}', $replace_string ) );
		}

		do_action( 'greyd_post_export_log', '=> nested elements were replaced' );
		return $subject;
	}
	/**
	 * Replace strings in subject
	 *
	 * @param string $subject
	 * @param int    $post_id
	 *
	 * @return string $subject
	 */
	public static function replace_strings( $subject, $post_id, $log = true ) {

		if ( empty( $subject ) ) {
			return $subject;
		}

		if ( $log ) {
			do_action( 'greyd_post_export_log', "\r\n" . 'Replace strings.' );
		}

		// get patterns
		$replace_strings = (array) get_nested_string_patterns( $subject, $post_id );
		foreach ( $replace_strings as $name => $string ) {
			$subject = str_replace( '{{' . $name . '}}', $string, $subject );
			if ( $log ) {
				do_action( 'greyd_post_export_log', sprintf( "  - '%s' was replaced with '%s'.", $name, $string ) );
			}
		}
		if ( $log ) {
			do_action( 'greyd_post_export_log', '=> strings were replaced' );
		}
		return $subject;
	}

	/**
	 * Given an array of meta, set meta to another post.
	 *
	 * @param int     $post_id  Post ID.
	 * @param array   $meta     Array of meta as key => value.
	 * @param WP_Post $post     The preparred post object (optional).
	 */
	public static function set_meta( $post_id, $meta, $post = null ) {
		do_action( 'greyd_post_export_log', "\r\n" . 'Set post meta.' );

		$existing_meta = (array) get_post_meta( $post_id );

		foreach ( (array) $meta as $meta_key => $meta_values ) {

			// don't import blacklisted meta
			if ( in_array( $meta_key, Post_Export_Helper::blacklisted_meta(), true ) ) {
				continue;
			}
			// skip certain mety keys
			elseif ( Post_Export_Helper::maybe_skip_meta_option( $meta_key, $meta_values ) ) {
				continue;
			}

			foreach ( (array) $meta_values as $meta_placement => $meta_value ) {
				$has_prev_value = (
						isset( $existing_meta[ $meta_key ] )
						&& is_array( $existing_meta[ $meta_key ] )
						&& array_key_exists( $meta_placement, $existing_meta[ $meta_key ] )
					) ? true : false;
				if ( $has_prev_value ) {
					$prev_value = maybe_unserialize( $existing_meta[ $meta_key ][ $meta_placement ] );
				}

				if ( ! is_array( $meta_value ) ) {
					$meta_value = maybe_unserialize( $meta_value );
				}

				/**
				 * Add filter to modify the post meta value before import
				 *
				 * @filter 'greyd_import_post_meta-{{meta_key}}'
				 */
				$meta_value = apply_filters( 'greyd_import_post_meta-' . $meta_key, $meta_value, $post_id, $post );

				if ( $has_prev_value ) {
					update_post_meta( $post_id, $meta_key, $meta_value, $prev_value );
				} else {
					add_post_meta( $post_id, $meta_key, $meta_value );
				}
			}
		}
		do_action( 'greyd_post_export_log', '=> post meta set' );
	}

	/**
	 * Modify dynamic meta value after import
	 *
	 * @filter 'greyd_import_post_meta-dynamic_meta'
	 *
	 * @param mixed $meta_value
	 * @param int   $post_id
	 *
	 * @return mixed $meta_value
	 */
	public function set_dynamic_meta( $meta_value, $post_id ) {

		$meta_value = (array) $meta_value;

		foreach ( $meta_value as $key => $value ) {
			if ( preg_match( '/\{\{(.+?)\}\}/', $value, $matches ) ) {
				$inner = isset( $matches[1] ) ? $matches[1] : '';

				// ID found
				if ( is_numeric( $inner ) ) {
					$replace_string     = isset( self::$posts[ $inner ] ) ? self::$posts[ $inner ] : $inner;
					$meta_value[ $key ] = preg_replace( '/\{\{(.+?)\}\}/', $replace_string, $value );
					do_action( 'greyd_post_export_log', sprintf( "  - ID '%s' in the field '%s' was replaced with '%s'", $inner, $key, $replace_string ) );
				} else {
					$meta_value[ $key ] = self::replace_strings( $value, $post_id, false );
					do_action( 'greyd_post_export_log', sprintf( "  - All strings in the field '%s' were replaced", $key ) );
				}
			}
		}

		return $meta_value;
	}

	/**
	 * Given an array of terms by taxonomy, set those terms to another post. This function will cleverly merge
	 * terms into the post and create terms that don't exist.
	 *
	 * @param int   $post_id        Post ID.
	 * @param array $taxonomy_terms Array with taxonomy as key and array of terms as values.
	 */
	public static function set_taxonomy_terms( $post_id, $taxonomy_terms, $post ) {
		do_action( 'greyd_post_export_log', "\r\n" . 'Set taxonomy terms.' );

		foreach ( (array) $taxonomy_terms as $taxonomy => $terms ) {

			if ( ! taxonomy_exists( $taxonomy ) ) {
				do_action( 'greyd_post_export_log', "  - taxonomy '{$taxonomy}' doesn't exist." );

				// we temporary register the taxonomy if it is dynamic
				$taxonomies = Post_Export_Helper::get_dynamic_taxonomies( $post->post_type );

				// register taxonomy if it is dynamic
				if ( in_array( $taxonomy, $taxonomies ) && ! taxonomy_exists( $taxonomy ) ) {
					$result = register_taxonomy( $taxonomy, $post->post_type );
					if ( is_wp_error( $result ) ) {
						do_action( 'greyd_post_export_log', "    - taxonomy '{$taxonomy}' could not be registered: " . $result->get_error_message() );
					} else {
						do_action( 'greyd_post_export_log', "    - taxonomy '{$taxonomy}' registered." );
					}
				}

				// continue if taxonomy still doesn't exist
				if ( ! in_array( $taxonomy, $taxonomies ) && ! taxonomy_exists( $taxonomy ) ) {
					continue;
				}
			}

			$term_ids        = array();
			$term_id_mapping = array();

			foreach ( (array) $terms as $term_array ) {
				if ( ! is_array( $term_array ) ) {
					$term_array = (array) $term_array;
				}

				if ( ! isset( $term_array['slug'] ) ) {
					continue;
				}

				$term = get_term_by( 'slug', $term_array['slug'], $taxonomy );

				if ( empty( $term ) ) {

					$term = wp_insert_term(
						$term_array['name'],
						$taxonomy,
						array(
							'slug'        => $term_array['slug'],
							'description' => isset( $term_array['description'] ) ? $term_array['description'] : '',
						)
					);

					if ( is_wp_error( $term ) ) {
						do_action( 'greyd_post_export_log', "    - term '{$term_array['name']}' of taxonomy '$taxonomy' could not be inserted: " . $term->get_error_message() );
					} else {
						$term_id_mapping[ $term_array['term_id'] ] = $term['term_id'];
						$term_ids[]                                = $term['term_id'];
						do_action( 'greyd_post_export_log', "    - term '{$term_array['name']}' of taxonomy '$taxonomy' inserted with id '{$term['term_id']}'." );
					}
				} else {
					$term_id_mapping[ $term_array['term_id'] ] = $term->term_id;
					$term_ids[]                                = $term->term_id;
					do_action( 'greyd_post_export_log', "    - term '{$term_array['name']}' of taxonomy '$taxonomy' found with id {$term->term_id}." );
				}
			}

			foreach ( (array) $terms as $term_array ) {
				if ( ! is_array( $term_array ) ) {
					$term_array = (array) $term_array;
				}

				if ( empty( $term_array['parent'] ) ) {
					$term = wp_update_term(
						$term_id_mapping[ $term_array['term_id'] ],
						$taxonomy,
						array(
							'parent' => '',
						)
					);
				} elseif ( isset( $term_id_mapping[ $term_array['parent'] ] ) ) {
					$term = wp_update_term(
						$term_id_mapping[ $term_array['term_id'] ],
						$taxonomy,
						array(
							'parent' => $term_id_mapping[ $term_array['parent'] ],
						)
					);
				}
			}

			$new_term_ids = wp_set_object_terms( $post_id, $term_ids, $taxonomy );

			if ( is_wp_error( $new_term_ids ) ) {
				do_action( 'greyd_post_export_log', "  - term ids of taxonomy '$taxonomy' could not be set to post: " . $new_term_ids->get_error_message() );
			} else {
				do_action( 'greyd_post_export_log', "  - term ids '" . implode( ', ', $new_term_ids ) . "' of taxonomy '$taxonomy' set to post." );
			}
		}
		do_action( 'greyd_post_export_log', isset( $new_term_ids ) ? '=> all taxonomy terms set' : '=> no taxonomy terms' );
	}

	/**
	 * Check if other post translations are better suited to be imported on this site.
	 * If yes, we should skip this post and not import it.
	 *
	 * @param WP_Post $post
	 *
	 * @return bool|int     Whether there is a better version. Returns the post id if it was already inserted.
	 */
	public static function better_post_translation_exists( $post ) {

		$language               = isset( $post->language ) ? (array) $post->language : array();
		$translation_post_ids   = isset( $language['post_ids'] ) ? (array) $language['post_ids'] : array();
		$languages_on_this_site = Post_Export_Helper::get_language_codes();
		$languages_of_this_post = ! empty( $translation_post_ids ) ? array_keys( $translation_post_ids ) : array();
		$supported_translations = array_intersect( $languages_on_this_site, $languages_of_this_post );

		// check if there is at least 1 equal language
		if ( count( $supported_translations ) > 0 ) {
			do_action( 'greyd_post_export_log', '  - There is at least 1 supported language for this post: ' . implode( ', ', $supported_translations ) );
			return true;
		} elseif ( ! empty( $translation_post_ids ) ) {

			do_action( 'greyd_post_export_log', '  - There is no supported language for this post.' );

			// we check if another translation of this post has already been imported
			foreach ( (array) $translation_post_ids as $lang => $translated_post_id ) {
				if ( isset( self::$posts[ $translated_post_id ] ) ) {
					do_action( 'greyd_post_export_log', '  - Another translation of this post has already been imported - we skip this one.' );
					return true;
				}
			}
			do_action( 'greyd_post_export_log', '  - No other translation of this post has been imported - we import this one.' );
		}
		return false;
	}

	/**
	 * Set the language of a post and link it to it's source post if possible.
	 *
	 * @param int     $post_id      Post ID on this stage.
	 * @param WP_Post $post     Old WP_Post object (Post ID might differ).
	 *
	 * @return bool
	 */
	public static function set_translations( $post_id, $post ) {

		$language = isset( $post->language ) && ! empty( $post->language ) ? (array) $post->language : null;

		if (
			! $language ||
			! isset( $language['args'] ) ||
			! isset( $language['code'] ) ||
			! isset( $language['tool'] )
		) {
			return false;
		}

		do_action( 'greyd_post_export_log', "\r\n" . 'Set translations for the post:', $language );

		$args     = (array) $language['args'];
		$code     = strval( $language['code'] );
		$tool     = strval( $language['tool'] );
		$post_ids = isset( $language['post_ids'] ) ? (array) $language['post_ids'] : null;

		if ( $tool === 'wpml' ) {

			$wpml_element_type = apply_filters( 'wpml_element_type', $post->post_type );
			$wpml_element_trid = apply_filters( 'wpml_element_trid', null, $post_id, $wpml_element_type );

			// get source language
			$source_language_code = isset( $args['source_language_code'] ) ? $args['source_language_code'] : null;
			if ( ! empty( $source_language_code ) ) {

				// get the (original) post-id of the source post...
				$original_source_language_post_id = isset( $post_ids[ $source_language_code ] ) ? $post_ids[ $source_language_code ] : null;
				if ( ! empty( $original_source_language_post_id ) ) {

					// ...get it's post-id on this stage
					$current_source_language_post_id = isset( self::$posts[ $original_source_language_post_id ] ) ? self::$posts[ $original_source_language_post_id ] : null;
					if ( ! empty( $current_source_language_post_id ) ) {

						// get the unique translation-id (trid)
						$wpml_element_trid = apply_filters( 'wpml_element_trid', $wpml_element_trid, $current_source_language_post_id, $wpml_element_type );
					}
				}
			}

			if ( empty( $wpml_element_trid ) ) {
				return false;
			}

			// set the translation
			do_action(
				'wpml_set_element_language_details',
				array(
					'element_id'           => $post_id,
					'element_type'         => $wpml_element_type,
					'trid'                 => $wpml_element_trid,
					'language_code'        => $code,
					'source_language_code' => $source_language_code,
					'check_duplicates'     => false,
				)
			);

			return true;
		}

		return false;
	}

	/**
	 * Filter the post content before import.
	 */
	public function filter_block_content_on_import( $content, $new_post_id, $post ) {
		$content = str_replace( '\\u002d\\u002d', '--', $content );
		$content = str_replace( '\u002d\u002d', '--', $content );
		$content = str_replace( 'u002du002d', '--', $content );

		return $content;
	}


	/**
	 * =================================================================
	 *                          Helper functions
	 * =================================================================
	 */

	/**
	 * Get all posts from class var
	 */
	public static function get_all_posts() {
		return self::$posts;
	}

	/**
	 * Get all posts from class var
	 */
	public static function get_all_media() {
		return self::$media;
	}

	/**
	 * Get the „posts.json“ file contents inside an imported zip archive
	 *
	 * @param string $filepath  Relative path to the zip including filename.
	 *
	 * @return mixed            String with error message on failure.
	 *                          Array of contents on success.
	 */
	public static function get_zip_posts_file_contents( $filepath ) {

		if ( ! file_exists( $filepath ) ) {
			__( "The ZIP archive could not be found. It may have been moved or deleted.", 'greyd_hub' );
		} else {
			do_action( 'greyd_post_export_log', sprintf( '  - zip archive „%s“ found.', $filepath ) );
		}

		// open 'posts.json' file inside zip archive
		$zip       = 'zip://' . $filepath . '#posts.json';
		$json_file = Helper::get_file_contents( $zip );

		if ( ! $json_file ) {
			return __( "The ZIP archive does not contain a valid \"posts.json\" file.", 'greyd_hub' );
		} else {
			do_action( 'greyd_post_export_log', sprintf( '  - file „%s“ found.', 'posts.json' ) );
		}

		// decode json data
		$contents = json_decode( $json_file, true );
		if ( $contents === null && json_last_error() !== JSON_ERROR_NONE ) {
			return __( "The post.json file could not be read.", 'greyd_hub' );
		} else {
			do_action( 'greyd_post_export_log', '  - decoded json.' );
		}

		if ( ! is_array( $contents ) ) {
			return __( "The posts.json file does not contain any data.", 'greyd_hub' );
		} else {
			do_action( 'greyd_post_export_log', '  - json contains object.' );
		}

		// convert posts back to objects
		foreach ( $contents as $post_id => $post ) {
			$contents[ $post_id ] = (object) $post;
		}

		return $contents;
	}

	/**
	 * Get the file contents of media file inside imported zip archive
	 *
	 * @param string $filepath  Relative path to the zip including filename.
	 * @param string $medianame Name of the media file.
	 *
	 * @return mixed            String with error message on failure.
	 *                          Array of contents on success.
	 */
	public static function get_zip_media_file( $filepath, $medianame ) {

		if ( ! file_exists( $filepath ) ) {
			__( "The ZIP archive could not be found. It may have been moved or deleted.", 'greyd_hub' );
		} else {
			do_action( 'greyd_post_export_log', sprintf( '  - zip archive „%s“ found.', $filepath ) );
		}

		// open 'posts.json' file inside zip archive
		$zip        = 'zip://' . $filepath . '#media/' . $medianame;
		$media_file = Helper::get_file_contents( $zip );

		if ( ! $media_file ) {
			return sprintf( __( "The file '%s' could not be found in the ZIP archive.", 'greyd_hub' ), 'media/' . $medianame );
		} else {
			do_action( 'greyd_post_export_log', sprintf( '  - file „%s“ found.', 'posts.json' ) );
		}

		return $media_file;
	}

	/**
	 * Delete all temporary files for import
	 *
	 * usually called after a successfull import
	 */
	public static function delete_tmp_files() {
		$path = Post_Export_Helper::get_file_path( 'tmp' );
		$dir  = substr( $path, -1 ) === '/' ? substr( $path, 0, -1 ) : $path;

		foreach ( scandir( $dir ) as $item ) {
			if ( $item == '.' || $item == '..' ) {
				continue;
			}
			$file = $dir . DIRECTORY_SEPARATOR . $item;
			if ( ! is_dir( $file ) ) {
				unlink( $file );
			}
		}
		do_action( 'greyd_post_export_log', "\r\n" . sprintf( "All files inside folder '%s' deleted.", $dir ) );
	}

	/**
	 * Check if there are existing posts in conflict with posts to be imported.
	 *
	 * @param WP_Post[] $posts  WP_Posts keyed by ID
	 *
	 * @return WP_Post[]        Returns WP_Posts keyed by the original IDs. Contains
	 *                          post object and full html link object.
	 */
	public static function get_conflicting_posts( $posts ) {

		$conflicts = array();
		foreach ( $posts as $post_id => $post ) {
			if ( $existing_post = Post_Export_Helper::get_post_by_name_and_type( $post ) ) {
				$conflicts[ $post_id ] = $existing_post;
			}
		}

		/**
		 * @filter 'greyd_import_conflicts'
		 */
		return apply_filters( 'greyd_import_post_conflicts', $conflicts, $posts );
	}

	/**
	 * Get link to post as html object.
	 * Example: <a>Beispiel Seite (Seite)</a>
	 */
	public static function get_post_link_html( $post ) {

		if ( ! is_object( $post ) ) {
			return '';
		}

		if ( $post->post_type === 'attachment' ) {
			$post_title = basename( get_attached_file( $post->ID ) );
			$post_type  = __( "Image/File", 'greyd_hub' );
			$post_url   = wp_get_attachment_url( $post->ID );
		} else {
			$post_title = $post->post_title;
			$post_type  = get_post_type_object( $post->post_type )->labels->singular_name;
			$post_url   = get_edit_post_link( $post );
		}
		$post_title = empty( $post_title ) ? '<i>' . __( "unknown post", 'greyd_hub' ) . '</i>' : $post_title;
		$post_type  = empty( $post_type ) ? '<i>' . __( "unknown post type", 'greyd_hub' ) . '</i>' : $post_type;

		return "<a href='$post_url' target='_blank' title='" . __( "open in new tab", 'greyd_hub' ) . "'>$post_title ($post_type)</a>";
	}

	/**
	 * Get conflicting posts as decoded array to be read and displayed
	 * in the backend 'check-import' overlay-form via backend.js.
	 *
	 * @param WP_Post[] $posts  WP_Posts keyed by ID
	 *
	 * @return string|bool  Decoded string when conflicts found, false otherwise.
	 */
	public static function import_get_conflict_posts_for_backend_form( $posts ) {

		// get conflicting posts
		$conflicts = self::get_conflicting_posts( $posts );

		if ( count( $conflicts ) > 0 ) {
			foreach ( $conflicts as $post_id => $post ) {

				// get the post link to display in the backend
				$conflicts[ $post_id ]->post_link = self::get_post_link_html( $post );
				/**
				 * We add the original ID of the import to the existing post ID.
				 *
				 * In the backend the dropdowns to decide what to do with existing
				 * posts get named by the ID. So their name will be something
				 * like '12-54'
				 *
				 * On the import we have form-data like array('12-54' => 'replace').
				 * We later convert this data via the function
				 * self::import_get_conflict_actions_from_backend_form()
				 */
				$conflicts[ $post_id ]->ID = $post_id . '-' . $conflicts[ $post_id ]->ID;
			}
			if ( count( $conflicts ) > 4 ) {
				array_unshift(
					$conflicts,
					(object) array(
						'ID'        => 'multioption',
						'post_link' => __( 'Multiselect', 'greyd_hub' ),
						'post_type' => '',
					)
				);
			}
			// we don't set keys to keep the order when decoding the array to JS
			return json_encode( array_values( $conflicts ) );
		}
		return false;
	}

	/**
	 * Get all conflicting post actions from the backend 'check-import' overlay-form.
	 *
	 * We have form data from the backend like array( '43-708' => 'skip' )
	 * and we convert this to a proper array that can be used for the function
	 * Post_Import::import_posts() (See function doc for details)
	 *
	 * @return array
	 */
	public static function import_get_conflict_actions_from_backend_form( $conflicts ) {
		$conflict_actions = array();
		foreach ( (array) $conflicts as $ids => $action ) {
			if ( strpos( $ids, '-' ) !== false ) {
				// original format: '43-708' => 'skip'
				$ids                          = explode( '-', $ids );
				$post_id                      = $ids[0];
				$conflict_actions[ $post_id ] = array(
					'post_id' => $ids[1],
					'action'  => $action,
				);
				// new format: 43 => array( 'post_id' => '708', 'action' => 'skip' )
			}
		}
		return $conflict_actions;
	}
}
