<?php
/**
 * @package   Quick_Featured_Images_Tools
 * @author    Kybernetik Services <wordpress@kybernetik.com.de>
 * @license   GPL-2.0+
 * @link      https://www.kybernetik-services.com
 * @copyright 2014 Kybernetik Services
 */
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;

class Quick_Featured_Images_Tools { // only for debugging: extends Quick_Featured_Images_Base {

	/**
	 * Instance of this class.
	 *
	 * @since    1.0.0
	 *
	 * @var      object
	 */
	protected static $instance = null;

	/**
	 * Required user capability to use this plugin
	 *
	 * @since   1.0.0
	 *
	 * @var     string
	 */
	protected $required_user_cap = null;

	/**
	 * Slug of the plugin screen.
	 *
	 * @since    1.0.0
	 *
	 * @var      string
	 */
	protected $plugin_screen_hook_suffix = null;

	/**
	 * Name of this plugin.
	 *
	 * @since    7.0
	 *
	 * @var      string
	 */
	protected $plugin_name = null;

	/**
	 * Unique identifier for this plugin.
	 *
	 * It is the same as in class Quick_Featured_Images_Admin
	 * Has to be set here to be used in non-object context, e.g. callback functions
	 *
	 * @since    7.0
	 *
	 * @var      string
	 */
	protected $plugin_slug = null;

	/**
	 * Unique identifier for the admin page of this class.
	 *
	 * @since    7.0
	 *
	 * @var      string
	 */
	protected $page_slug = null;

	/**
	 * Unique identifier for the admin parent page of this class.
	 *
	 * @since    7.0
	 *
	 * @var      string
	 */
	protected $parent_page_slug = null;

	/**
	 * Unique identifier in the WP options table
	 *
	 * @since    12.0
	 *
	 * @var      string
	 */
	private $settings_db_slug = null;

	/**
	 * Valid progress steps
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $valid_steps = null;

	/**
	 * User selected progress step
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $selected_step = null;

	/**
	 * Whether an image id is required or not (depends on the selected action)
	 *
	 * @since    2.0
	 *
	 * @var      bool
	 */
	protected $is_image_required = null;

	/**
	 * User selected ID of the new featured image
	 *
	 * @since    1.0.0
	 *
	 * @var      integer
	 */
	protected $selected_image_id = null;

	/**
	 * User selected IDs of the new featured images
	 *
	 * @since    6.0
	 *
	 * @var      array
	 */
	protected $selected_multiple_image_ids = null;

	/**
	 * User selected ID of the featured image to replace
	 *
	 * @since    1.0.0
	 *
	 * @var      integer
	 */
	protected $selected_old_image_ids = null;

	/**
	 * Whether the id of a to be replaced image is set or not
	 *
	 * @since    2.0
	 *
	 * @var      bool
	 */
	protected $is_error_no_old_image = null;

	/**
	 * Whether the user jumps from 'select' directly to 'confirm' omitting 'refine'
	 *
	 * @since    5.1
	 *
	 * @var      bool
	 */
	protected $is_skip_refine = null;

	/**
	 * Width of thumbnail images in the current WordPress settings
	 *
	 * @since    2.0
	 *
	 * @var      integer
	 */
	protected $used_thumbnail_width = null;
	
	/**
	 * Height of thumbnail images in the current WordPress settings
	 *
	 * @since    2.0
	 *
	 * @var      integer
	 */
	protected $used_thumbnail_height = null;
	
	/**
	 * Minimum length of image dimensions to search for
	 *
	 * @since    2.0
	 *
	 * @var      integer
	 */
	protected $min_image_length = null;
	
	/**
	 * Maximum length of image dimensions to search for
	 *
	 * @since    2.0
	 *
	 * @var      integer
	 */
	protected $max_image_length = null;
	
	/**
	 * User selected action the plugin should perform
	 *
	 * @since    1.0.0
	 *
	 * @var      string
	 */
	protected $selected_action = null;

	/**
	 * Valid names and descriptions of the actions
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $valid_actions = null;

	/**
	 * Valid names and descriptions of the actions without a user selected image
	 *
	 * @since    5.0
	 *
	 * @var      array
	 */
	protected $valid_actions_without_image = null;

	/**
	 * Valid names and descriptions of the actions with multiple user selected images
	 *
	 * @since    6.0
	 *
	 * @var      array
	 */
	protected $valid_actions_multiple_images = null;

	/**
	 * User selected filters the plugin should perform
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $selected_filters = array();

	/**
	 * Valid names and descriptions of the filters
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $valid_filters = null;

	/**
	 * User selected options the plugin should consider
	 *
	 * @since    5.1
	 *
	 * @var      array
	 */
	protected $selected_options = array();

	/**
	 * Valid names and descriptions of the options
	 *
	 * @since    5.1
	 *
	 * @var      array
	 */
	protected $valid_options = null;

	/**
	 * Valid names and descriptions of the post statuses
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $valid_statuses = null;
	
	/**
	 * User selected search term
	 *
	 * @since    1.0.0
	 *
	 * @var      string
	 */
	protected $selected_search_term = null;
	
	/**
	 * User selected category
	 *
	 * @since    1.0.0
	 *
	 * @var      integer
	 */
	protected $selected_category_id = null;

	/**
	 * User selected tag
	 *
	 * @since    1.0.0
	 *
	 * @var      integer
	 */
	protected $selected_tag_id = null;
	
	/**
	 * User selected names and descriptions of post types supporting featured images by default
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $selected_post_types = null;
	
	/**
	 * Valid names and descriptions of the post types supporting featured images by default
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $valid_post_types = null;
	
	/**
	 * User selected post ids
	 *
	 * @since    1.0.0
	 *
	 * @var      array
	 */
	protected $selected_post_ids = null;
	
	/**
	 * Valid names and descriptions of image sizes
	 *
	 * @since    2.0
	 *
	 * @var      array
	 */
	protected $valid_image_dimensions = null;

	/**
	 * User given image sizes
	 *
	 * @since    2.0
	 *
	 * @var      array
	 */
	protected $selected_image_dimensions = null;
	
	/**
	 * Transient reference for temporary storaging of data
	 *
	 * @since     11.0
	 *
	 * @var      string
	 */
	protected $transient_name = null;
		
	 /**
	 * Initialize the plugin by loading admin scripts & styles and adding a
	 * settings page and menu.
	 *
	 * @access   private
	 * @since     1.0.0
	 */
	private function __construct() {

		// Call some values from public plugin class.
		$plugin = Quick_Featured_Images_Admin::get_instance();
		$this->plugin_name = $plugin->get_plugin_name();
		$this->plugin_slug = $plugin->get_plugin_slug();
		$this->page_slug = $this->plugin_slug . '-tools';
		$this->parent_page_slug = $plugin->get_page_slug();
		$this->plugin_version = $plugin->get_plugin_version();
		$this->settings_db_slug = $plugin->get_settings_db_slug();

		// get settings
		$settings = get_option( $this->settings_db_slug, array() );
		if ( isset( $settings[ 'minimum_role_all_pages' ] ) ) {
			switch ( $settings[ 'minimum_role_all_pages' ] ) {
				case 'administrator':
					$this->required_user_cap = 'manage_options';
					break;
				default:
					$this->required_user_cap = 'edit_others_posts';
			}
		} else {
			$this->required_user_cap = 'edit_others_posts';
		}
		
		// Load admin style sheet and JavaScript.
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );

		// Add the admin page and menu item.
		add_action( 'admin_menu', array( $this, 'add_plugin_admin_menu' ) );
		
		// Add 'Bulk set' link in rows of media library list
		add_filter( 'media_row_actions', array( &$this, 'add_media_row_action' ), 10, 2 );

	}
	
	/**
	 * Do the admin main function 
	 *
	 * @since     1.0.0
	 *
	 */
	public function main() {
		// set variables
		$this->set_server_config();
		$this->set_default_values();
		// get current step
		$this->selected_step = $this->get_sanitized_step();
		//$this->dambedei( $_REQUEST );
		//$this->dambedei( $_SERVER );
		/*
		 * print content
		 */
		// no action and image required, just the start page
		if ( 'start' == $this->selected_step ) {
			// print header
			$this->display_header();
			include_once( 'views/form_start.php' );
		} else {
			// get user selected action
			$this->selected_action = $this->get_sanitized_action();
			// check if action is defined, else print error page
			if ( ! $this->selected_action ) {
				$this->display_error( 'wrong_action', false );
			} else {
				if ( 'remove_orphaned' == $this->selected_action ) {
					// if 'perform' step
					if ( 'perform' == $this->selected_step ) {
						check_admin_referer( 'quickfi_confirm', $this->plugin_slug . '_nonce' );
						// filter posts and apply action to found posts
						$results = $this->delete_orphaned();
					} else {
						check_admin_referer( 'quickfi_start', $this->plugin_slug . '_nonce' );
						// find "file-less" thumbnail entries
						$results = $this->find_orphaned();
						if ( $results ) {
							// jump to confirmation step
							$this->selected_step = 'confirm';
						} else {
							// jump to result step directly
							$this->selected_step = 'perform';
						}
					} // if( perform )
					// print header
					$this->display_header();
					if ( 'confirm' == $this->selected_step ) {
						// print refine form again if there are no results
						include_once( 'views/form_confirm.php' );	
					} else {
						// print results
						include_once( 'views/section_results.php' );	
					} // if( confirm )
				} else {
					// check whether thumb id is not required due to selected action
					if ( in_array( $this->selected_action, array_merge( array_keys( $this->valid_actions_without_image ), array_keys( $this->valid_actions_multiple_images ) ) ) ) {
						$this->is_image_required = false;
					}
					// get selected image id, else 0
					$this->selected_image_id = $this->get_sanitized_image_id();
					// get selected image ids, else empty array
					$this->selected_multiple_image_ids = $this->get_sanitized_multiple_image_ids();
					// check whether an image id is available if required
					if ( $this->is_image_required && ! $this->selected_image_id ) {
						$this->display_error( 'no_image', false );
					// check whether selected attachment is an image if required
					} elseif ( $this->is_image_required && ! wp_attachment_is_image( $this->selected_image_id ) ) {
						$this->display_error( 'no_result', sprintf( __( 'Wrong image ID %d', 'quick-featured-images' ), $this->selected_image_id ) );
					// check whether there are selected images if required
					} elseif ( 'assign_randomly' == $this->selected_action && ! $this->selected_multiple_image_ids ) {
						$this->display_error( 'no_image', false );
					} else {
						// get user selected filters
						$this->selected_filters = $this->get_sanitized_filter_names();
						// get user selected options
						$this->selected_options = $this->get_sanitized_option_names();
						// after the old image selection page (filter_replace.php) and if no old image was selected
						if ( 'replace' == $this->selected_action && 'confirm' == $this->selected_step && ! isset( $_POST[ 'replacement_image_ids' ] ) ) {
							// stay on the selection page with a warning
							$this->selected_step = 'select';
							$this->is_error_no_old_image = true;
						// check if user comes from direct link in media page
						} elseif ( 'assign' == $this->selected_action && 'select' == $this->selected_step && $this->selected_image_id && isset( $_REQUEST[ '_wpnonce' ] ) ) {
							// go to the filter selection page directly
							$this->is_direct_access = true;
						// check if user comes from the selection page and has not select any filter
						} elseif ( 'refine' == $this->selected_step	&& empty( $this->selected_filters ) ) {
							// skip refine page, go to the confirm page directly
							$this->selected_step = 'confirm';
							$this->is_skip_refine = true;
						}

						// print header
						$this->display_header();
						// print content based of process
						switch ( $this->selected_step ) {
							case 'select':
								if ( $this->is_error_no_old_image ) {
									check_admin_referer( 'quickfi_refine', $this->plugin_slug . '_nonce' );
								} elseif ( $this->is_direct_access ) {
									// no referer check
									check_admin_referer( 'bulk-assign' );
								} else {
									check_admin_referer( 'quickfi_start', $this->plugin_slug . '_nonce' );
								}
								// print selected thumbnail if required
								include_once( 'views/section_image.php' );	
								// print form to select the posts to apply the action to
								include_once( 'views/form_select.php' );	
								break;
							case 'refine':
								check_admin_referer( 'quickfi_select', $this->plugin_slug . '_nonce' );
								// print selected thumbnail if required
								include_once( 'views/section_image.php' );	
								// print form to refine choice
								include_once( 'views/form_refine.php' );	
								// print form for going back to the filter selection without loosing input data
								include_once( 'views/form_back_to_selection.php' );	
								break;
							case 'confirm':
								if ( $this->is_skip_refine ) {
									check_admin_referer( 'quickfi_select', $this->plugin_slug . '_nonce' );
								} else {
									check_admin_referer( 'quickfi_refine', $this->plugin_slug . '_nonce' );
								}
								// filter posts
								$results = $this->find_posts();
								// print selected thumbnail if required
								include_once( 'views/section_image.php' );	
								// print refine form again if there are no results
								include_once( 'views/form_confirm.php' );	
								// print form to refine choice if filters were selected
								if ( $this->selected_filters ) {
									include_once( 'views/form_refine.php' );	
								}
								// print form for going back to the filter selection without loosing input data
								include_once( 'views/form_back_to_selection.php' );	
								break;
							case 'perform':
								check_admin_referer( 'quickfi_confirm', $this->plugin_slug . '_nonce' );
								// filter posts and apply action to found posts
								$results = $this->perform_action();
								// print results
								include_once( 'views/section_results.php' );	
								// print form for going back to the filter selection without loosing input data
								include_once( 'views/form_back_to_selection.php' );	
								break;
						} // switch( selected step )
					} // if( image available )
				} // if ( remove_orphaned )
			} // if( action available )
		} // if( is start )
		// print footer
		$this->display_footer();
	}
	
	/**
	 * Set variables
	 *
	 * @access   private
	 * @since     1.0.0
	 */
	private function set_default_values() {
		/*
		 * Note: The order of the entries affects the order in the frontend page
		 *
		 */
		// process steps
		$this->valid_steps = array(
			'start'		=> __( 'Select', 'quick-featured-images' ),
			'select'	=> __( 'Add filter', 'quick-featured-images' ),
			'refine' 	=> __( 'Refine', 'quick-featured-images' ),
			'confirm'	=> __( 'Confirm', 'quick-featured-images' ),
			'perform'	=> __( 'Perform', 'quick-featured-images' ),
		);
		// actions
		$this->valid_actions = array(
			'assign'			=> __( 'Set the selected image as new featured image', 'quick-featured-images' ),
			'replace'			=> __( 'Replace featured images by the selected image', 'quick-featured-images' ),
			'remove'			=> __( 'Remove the selected image as featured image', 'quick-featured-images' ),
		);
		$this->valid_actions_without_image = array(
			'remove_any_img'	=> __( 'Remove any image as featured image', 'quick-featured-images' ),
			'remove_orphaned'	=> __( 'Remove all featured images without existing image files', 'quick-featured-images' ),
		);
		$this->valid_actions_multiple_images = array(
			'assign_randomly'	=> __( 'Set multiple images randomly as featured images', 'quick-featured-images' ),
		);
		// process options
		$this->valid_options = array(
			'overwrite'         => __( 'Overwrite featured images', 'quick-featured-images' ),
			'orphans_only'      => __( 'Consider only posts without any featured image', 'quick-featured-images' ),
		);
		// filters
		$this->valid_filters = array(
			'filter_post_types' 		=> __( 'Post Type Filter', 'quick-featured-images' ),
			'filter_category' 			=> __( 'Category Filter', 'quick-featured-images' ),
			'filter_tag' 				=> __( 'Tag Filter', 'quick-featured-images' ),
		);
		// post types (generic)
		$label_posts = 'Posts';
		$label_pages = 'Pages';
		$this->valid_post_types = array(
			'post' => _x( $label_posts, 'post type general name' ),
			'page' => _x( $label_pages, 'post type general name' ),
		);
		// statuses
		$text             = 'Private';
		$label_private    = _x( $text, 'post status' );
		$text             = 'Published';
		$label_publish    = _x( $text, 'post status' );
		$text             = 'Scheduled';
		$label_future     = _x( $text, 'post status' );
		$text             = 'Pending';
		$label_pending    = _x( $text, 'post status' );
		$text             = 'Draft';
		$label_draft      = _x( $text, 'post status' );
		$this->valid_statuses = array(
			'publish' => $label_publish,
			'pending' => $label_pending,
			'draft'   => $label_draft,
			'future'  => $label_future,
			'private' => $label_private
		);
		// image dimensions
		$this->valid_image_dimensions = array(
			'max_width' 	=> __( 'Image width in pixels lower than', 'quick-featured-images' ),
			'max_height' 	=> __( 'Image height in pixels lower than', 'quick-featured-images' ),
		);
		// default: user selected image is required
		$this->is_image_required = true;
		// default: start form
		$this->selected_step = 'start';
		// default: no images
		$this->selected_old_image_ids = array();
		$this->selected_image_id = 0;
		$this->selected_multiple_image_ids = array();
		$this->is_error_no_old_image = false;
		$this->is_direct_access = false;
		$this->is_skip_refine = false;
		// default: no category
		$this->selected_category_id = 0;
		// default: no tag
		$this->selected_tag_id = 0;
		// default: all post types
		$this->selected_post_types = array_keys( $this->valid_post_types ); // default: posts and pages
		// default: no selected posts
		$this->selected_post_ids = array();
		// get user defined dimensions for thumbnails, else take 150 px and set maximum value if necessary
		$max_dimension = 160; // width of thumbnail column in px at 1024 px window width
		$this->used_thumbnail_width  = get_option( 'thumbnail_size_w', $max_dimension );
		$this->used_thumbnail_height = get_option( 'thumbnail_size_h', $max_dimension );
		$this->used_thumbnail_width = $this->used_thumbnail_width > $max_dimension ? $max_dimension : $this->used_thumbnail_width;
		$this->used_thumbnail_height = $this->used_thumbnail_height > $max_dimension ? $max_dimension : $this->used_thumbnail_height;
		 // default:  stored sizes for thumbnails
		$this->selected_image_dimensions = array(
			'max_width' 	=> $this->used_thumbnail_width,
			'max_height' 	=> $this->used_thumbnail_height,
		);
		// default: min 1 x 1 px, max 9999 x 9999 px images
		$this->min_image_length = 1;
		$this->max_image_length = 9999;
		// slug for cached results
		$this->transient_name = 'quick_featured_images_results';
	}
	
	/**
	 * Set server timeout for PHP scripts in seconds
	 *
	 * @access   private
	 * @since    12.2
	 */
	private function set_server_config() {
		// to prevent blank pages for this script:
		// set server timeout to 3000 seconds if lower
		$value = (int) ini_get( 'max_execution_time' );
		if ( 2999 > $value ) {
			ini_set( 'max_execution_time', '3000' );
		}
		// and set allowed memory space to 512 MB if lower
		preg_match( '/(\d+)(\w+)/', ini_get( 'memory_limit' ), $matches );
		if ( $matches ) {
			$value = (int) $matches[ 1 ];
			switch ( strtolower( $matches[ 2 ] ) ) {
				case 'g':
				case 'gb':
					$value *= 1024;
				case 'm':
				case 'mb':
					$value *= 1024;
				case 'k':
				case 'kb':
					$value *= 1024;
			}
			
			if ( 500000000 > $value ) {
				ini_set( 'memory_limit', '512M' );
			}
		}
	}
	
	/**
	 *
	 * Render the header of the admin page
	 *
	 * @access   private
	 * @since    1.0.0
	 */
	private function display_header() {
		include_once( 'views/section_header_progress.php' );
	}
	
	/**
	 *
	 * Render the footer of the admin page
	 *
	 * @access   private
	 * @since    1.0.0
	 */
	private function display_footer() {
		include_once( 'views/section_footer.php' );
	}
	
	/**
	 *
	 * Render the error page
	 *
	 * @access   private
	 * @since    1.0.0
	 */
	private function display_error( $reason, $value_name ) {	
		// print header
		$this->display_header();
		// print error message
		switch ( $reason ) {
			case 'missing_input_value':
				$msg = sprintf( __( 'The input field %s is empty.', 'quick-featured-images' ), $value_name );
				$solution = __( 'Type in a value into the input field.', 'quick-featured-images' );
				break;
			case 'missing_variable':
				$msg = sprintf( __( '%s is not defined.', 'quick-featured-images' ), $value_name );
				$solution = __ ('Check how to define the value.', 'quick-featured-images' );
				break;
			case 'no_image':
				$msg = __( 'There is no selected image.', 'quick-featured-images' );
				$solution = __( 'Select an image from the media library.', 'quick-featured-images' );
				break;
			case 'wrong_action':
				$msg = __( 'You have not selected an action.', 'quick-featured-images' );
				$solution = __( 'Start again and select which action you want to apply.', 'quick-featured-images' );
				break;
			case 'wrong_value':
				$msg = sprintf( __( 'The input field %s has an invalid value.', 'quick-featured-images' ), $value_name );
				$solution = __( 'Type in valid values in the input field.', 'quick-featured-images' );
				break;
			case 'no_result':
				$msg = $value_name;
				$solution = __( 'Type in values stored by WordPress.', 'quick-featured-images' );
				break;
			default:
				$text = 'An error occurred. Please try again.';
				$msg = __( $text );
				$text = 'Unknown';
				$solution = ( $value_name ) ? $value_name : _x( $text, 'name' );
		} // switch ( $reason )
		include_once( 'views/section_errormsg.php' );
		//die();
	} // display_error()

	/**
	 * Call the WP Query to find thumbnail entries without existing image files
	 * 
	 * @access   private
	 * @since     13.0
	 *
	 */
	private function find_orphaned() {
		$orphaned_ids = array();
		global $wpdb;
		// get IDs of images flagged as featured images
		$thumb_ids = $wpdb->get_col( "SELECT DISTINCT meta_value FROM $wpdb->postmeta WHERE meta_key = '_thumbnail_id';" );
		// if there are featured images
		if ( $thumb_ids ) {
			// check if the corresponding image files exist
			foreach( $thumb_ids as $thumbnail_id ) {
				$filepath = get_attached_file( $thumbnail_id );
				if ( ! file_exists( $filepath ) ) {
					// collect ID in array
					$orphaned_ids[] = $thumbnail_id;
				}
			}
		}
		// return IDs of "file-less" featured images or empty array
		return $orphaned_ids;
	}

	/**
	 * Call the WP Query to delete all thumbnail entries without existing image files
	 * 
	 * @access   private
	 * @since     13.0
	 *
	 */
	private function delete_orphaned() {
		global $wpdb;
		// look for "file-less" featured images
		$orphaned_ids = $this->find_orphaned();
		// if there are none, return false
		if ( empty( $orphaned_ids ) ) {
			return false;
		} else {
			// delete orphaned thumbnail entries in database, return number of deleted entries or false
			return $wpdb->query( sprintf( "DELETE FROM $wpdb->postmeta WHERE meta_key = '_thumbnail_id' AND meta_value IN ( %s );", implode( ',', $orphaned_ids ) ) );
		}
	}

	/**
	 * Call the WP Query and apply the selected action to found posts
	 * 
	 * Is an alias to 'find_posts( true )' for more readability
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 */
	private function perform_action() {
		return $this->find_posts( true );
	}

	/**
	 * Do the loop to find posts, change the thumbnail if param is true
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    affected posts
	 */
	private function find_posts( $perform = false ) {
		// initialise result array 
		$results = array();
		// define thumbnail properties
		$size = array( 
			absint( $this->used_thumbnail_width / 2 ), 
			absint( $this->used_thumbnail_height / 2 ) 
		);
		$attr = array(
			'class' => 'attachment-thumbnail',
			'style' => vsprintf( 'width: %dpx; height: %dpx;', $size ), /* for SVGs */
		);
		// define caching arrays for better performance while calculating attachment images
		$false_id = 'false_id'; // something to use as an array key
		$current_featured_images = array();
		$future_featured_images = array();
		$current_featured_images[ $false_id ] = false;
		$future_featured_images[ $false_id ] = false;
		// get selected options once
		$is_option = array();
		foreach ( array_keys( $this->valid_options ) as $key ) {
			$is_option[ $key ] = in_array( $key, $this->selected_options );
		}

		/* three types of tasks:
			if perform:
				if no transient:
					1: set thumbs via query
				else:
					2: set thumbs via transient
			else:
				3: get preview via query
		*/
		if ( $perform ) { // really make changes
			// check for cached data; use them for fast processing, else use query
			// if removal was selected use query, too
			if ( false === ( $query_results = get_transient( $this->transient_name ) ) ) {
				// they weren't there, so use the query
				$the_query = new WP_Query( $this->get_query_args() );
				//printf( '<p>%s</p>', $the_query->request ); // just for debugging
				// The Loop
				if ( $the_query->have_posts() ) {
					// do task dependent on selected action
					switch ( $this->selected_action ) {
						case 'assign':
							while ( $the_query->have_posts() ) {
								$the_query->the_post();
								// get the post id once
								$post_id = get_the_ID();
								// check if there is an existing featured image
								$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
								// if post with featured images should be ignored, jump to next loop
								if ( $thumb_id and $is_option[ 'orphans_only' ] ) {
									continue;
								}
								$success = false;
								// if no existing featured image or if permission to overwrite it
								if ( ! $thumb_id or $is_option[ 'overwrite' ] ) {
									// set featured image id
									$thumb_id = $this->selected_image_id;
									// do the task
									$success = set_post_thumbnail( $post_id, $thumb_id );
								}
								// get html for featured image for check
								$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
								// if existing featured image
								if ( $thumb_id ) {
									// get thumbnail html if not yet got
									if ( ! isset( $current_featured_images[ $thumb_id ] ) ) {
										$current_featured_images[ $thumb_id ] = wp_get_attachment_image( $thumb_id, $size, false, $attr );
									}
								} else {
									// nothing changed
									$thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
								}
								// store edit link, post title, image html, success of action (true or false)
								$results[] = array( 
									get_edit_post_link(), 
									get_the_title(),
									$current_featured_images[ $thumb_id ],
									$success
								);
							} // while(have_posts)
							break;
						case 'assign_randomly':
							$last_index = count( $this->selected_multiple_image_ids ) - 1;
							while ( $the_query->have_posts() ) {
								$the_query->the_post();
								// get the post id once
								$post_id = get_the_ID();
								// check if there is an existing featured image
								$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
								// if post with featured images should be ignored, jump to next loop
								if ( $thumb_id and $is_option[ 'orphans_only' ] ) {
									continue;
								}
								$success = false;
								// if no existing featured image or if permission to overwrite it
								if ( ! $thumb_id or $is_option[ 'overwrite' ] ) {
									// set featured image id
									$thumb_id = $this->selected_multiple_image_ids[ rand( 0, $last_index ) ];
									// do the task
									$success = set_post_thumbnail( $post_id, $thumb_id );
								}
								// get html for featured image for check
								$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
								// if existing featured image
								if ( $thumb_id ) {
									// get thumbnail html if not yet got
									if ( ! isset( $current_featured_images[ $thumb_id ] ) ) {
										$current_featured_images[ $thumb_id ] = wp_get_attachment_image( $thumb_id, $size, false, $attr );
									}
								} else {
									// nothing changed
									$thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
								}
								// store edit link, post title, image html, success of action (true or false)
								$results[] = array( 
									get_edit_post_link(), 
									get_the_title(),
									$current_featured_images[ $thumb_id ],
									$success
								);
							} // while(have_posts)
							break;
						case 'replace':
							while ( $the_query->have_posts() ) {
								$the_query->the_post();
								// get the post id once
								$post_id = get_the_ID();
								// do the task
								$success = set_post_thumbnail( $post_id, $this->selected_image_id );
								// get html for featured image for check
								$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
								if ( $thumb_id ) {
									// get thumbnail html if not yet got
									if ( ! isset( $current_featured_images[ $thumb_id ] ) ) {
										$current_featured_images[ $thumb_id ] = wp_get_attachment_image( $thumb_id, $size, false, $attr );
									}
								} else {
									// nothing changed
									$thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
								}
								// store edit link, post title, image html, success of action (true or false)
								$results[] = array( 
									get_edit_post_link(), 
									get_the_title(),
									$current_featured_images[ $thumb_id ],
									$success
								);
							} // while(have_posts)
							break;
						case 'remove':
						case 'remove_any_img':
							while ( $the_query->have_posts() ) {
								$the_query->the_post();
								// get the post id once
								$post_id = get_the_ID();
								// do the task
								$success = delete_post_thumbnail( $post_id );
								// get html for featured image for check
								$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
								// if existing featured image
								if ( $thumb_id ) {
									// get thumbnail html if not yet got
									if ( ! isset( $current_featured_images[ $thumb_id ] ) ) {
										$current_featured_images[ $thumb_id ] = wp_get_attachment_image( $thumb_id, $size, false, $attr );
									}
								} else {
									// nothing changed
									$thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
								}
								// store edit link, post title, image html, success of action (true or false)
								$results[] = array( 
									get_edit_post_link(), 
									get_the_title(),
									$current_featured_images[ $thumb_id ],
									$success
								);
							} // while(have_posts)
							break;
					} // switch(selected_action)
				} // if( have_posts )
				// Restore original post data after the query
				wp_reset_postdata();
			} else {
				// else if there are cached results
				// do task dependent on selected action
				switch ( $this->selected_action ) {
					case 'assign':
						foreach ( $query_results as $post_id => $post_data ) {
							$thumb_id = $post_data[ 0 ];
							// cast "false" value to boolean false
							if ( $thumb_id == $false_id ) {
								$thumb_id = false;
							}
							// check if there is an existing featured image
							$current_thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							$success = false;
							// if no existing featured image or if permission to overwrite it
							if ( ! $current_thumb_id or $is_option[ 'overwrite' ] ) {
								// do the task
								$success = set_post_thumbnail( $post_id, $thumb_id );
							}
							// get html for featured image for check
							$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							// if existing featured image
							if ( $thumb_id ) {
								// get thumbnail html if not yet got
								if ( ! isset( $current_featured_images[ $thumb_id ] ) ) {
									$current_featured_images[ $thumb_id ] = wp_get_attachment_image( $thumb_id, $size, false, $attr );
								}
							} else {
								// nothing changed
								$thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
							}
							// store edit link, post title, image html, success of action (true or false)
							$results[] = array( 
								$post_data[ 1 ], // get_edit_post_link()
								$post_data[ 2 ], // get_the_title()
								$current_featured_images[ $thumb_id ],
								$success
							);
						} // foreach()
						break;
					case 'assign_randomly':
						foreach ( $query_results as $post_id => $post_data ) {
							$thumb_id = $post_data[ 0 ];
							// cast "false" value to boolean false
							if ( $thumb_id == $false_id ) {
								$thumb_id = false;
							}
							$success = false;
							// check if there is an existing featured image
							$current_thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							// if existing featured image
							if ( $current_thumb_id ) {
								// if new image
								if ( $thumb_id ) {
									// if permission to overwrite existing image
									if ( $is_option[ 'overwrite' ] ) {
										// do the task
										$success = set_post_thumbnail( $post_id, $thumb_id );
									} else {
										// do nothing : keep existing image
									} // if ( overwrite )
								} // if ( new image )
							// if no existing featured image
							} else {
								// if new image
								if ( $thumb_id ) {
									// do the task
									$success = set_post_thumbnail( $post_id, $thumb_id );
								} else {
									// do nothing : no image
								} // if ( new image )
							} // if ( existing image )
							// get html for featured image for check
							$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							// if existing featured image
							if ( $thumb_id ) {
								// get thumbnail html if not yet got
								if ( ! isset( $current_featured_images[ $thumb_id ] ) ) {
									$current_featured_images[ $thumb_id ] = wp_get_attachment_image( $thumb_id, $size, false, $attr );
								}
							} else {
								// nothing changed
								$thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
							}
							// store edit link, post title, image html, success of action (true or false)
							$results[] = array( 
								$post_data[ 1 ], // get_edit_post_link()
								$post_data[ 2 ], // get_the_title()
								$current_featured_images[ $thumb_id ],
								$success
							);
						} // foreach()
						break;
					case 'replace':
						foreach ( $query_results as $post_id => $post_data ) {
							$thumb_id = $post_data[ 0 ];
							// do the task
							$success = set_post_thumbnail( $post_id, $thumb_id );
							// get html for featured image for check
							$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							if ( $thumb_id ) {
								// get thumbnail html if not yet got
								if ( ! isset( $current_featured_images[ $thumb_id ] ) ) {
									$current_featured_images[ $thumb_id ] = wp_get_attachment_image( $thumb_id, $size, false, $attr );
								}
							} else {
								// nothing changed
								$thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
							}
							// store edit link, post title, image html, success of action (true or false)
							$results[] = array( 
								$post_data[ 1 ], // get_edit_post_link()
								$post_data[ 2 ], // get_the_title()
								$current_featured_images[ $thumb_id ],
								$success
							);
						} // foreach()
						break;
					case 'remove':
					case 'remove_any_img':
						foreach ( $query_results as $post_id => $post_data ) {
							$thumb_id = $post_data[ 0 ];
							// do the task
							$success = delete_post_thumbnail( $post_id );
							// get html for featured image for check
							$thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							if ( $thumb_id ) {
								// get thumbnail html if not yet got
								if ( ! isset( $current_featured_images[ $thumb_id ] ) ) {
									$current_featured_images[ $thumb_id ] = wp_get_attachment_image( $thumb_id, $size, false, $attr );
								}
							} else {
								// nothing changed
								$thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
							}
							// store edit link, post title, image html, success of action (true or false)
							$results[] = array( 
								$post_data[ 1 ], // get_edit_post_link()
								$post_data[ 2 ], // get_the_title()
								$current_featured_images[ $thumb_id ],
								$success
							);
						} // foreach()
						break;
				} // switch(selected_action)
				// delete cached results manually
				delete_transient( $this->transient_name );
			} // if transient
		} else {
			$query_results = array();
			$the_query = new WP_Query( $this->get_query_args() );
			//printf( '<p>%s</p>', esc_html( $the_query->request ) ); // just for debugging
			// The Loop
			if ( $the_query->have_posts() ) {
				// do task dependent on selected action
				switch ( $this->selected_action ) {
					case 'assign':
						while ( $the_query->have_posts() ) {
							$the_query->the_post();
							// get the post id once
							$post_id = get_the_ID();
							// check if there is an existing featured image
							$current_thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							// if post with featured images should be ignored, jump to next loop
							if ( $current_thumb_id and $is_option[ 'orphans_only' ] ) {
								continue;
							}
							if ( $current_thumb_id ) {
								// get thumbnail html if not yet got
								if ( ! isset( $current_featured_images[ $current_thumb_id ] ) ) {
									$current_featured_images[ $current_thumb_id ] = wp_get_attachment_image( $current_thumb_id, $size, false, $attr );
								}
								// get html of future thumbnail
								if ( $is_option[ 'overwrite' ] ) {
									// preview old thumb + new thumb
									$future_thumb_id = $this->selected_image_id;
									// get thumbnail html if not yet got
									if ( ! isset( $future_featured_images[ $future_thumb_id ] ) ) {
										$future_featured_images[ $future_thumb_id ] = wp_get_attachment_image( $future_thumb_id, $size, false, $attr );
									}
								} else {
									// preview old thumb + old thumb
									$future_thumb_id = $current_thumb_id;
									$future_featured_images[ $future_thumb_id ] = $current_featured_images[ $current_thumb_id ];
								}
							} else {
								// preview no old thumb + new thumb
								$current_thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
								// get html of future thumbnail
								$future_thumb_id = $this->selected_image_id;
								// get thumbnail html if not yet got
								if ( ! isset( $future_featured_images[ $future_thumb_id ] ) ) {
									$future_featured_images[ $future_thumb_id ] = wp_get_attachment_image( $future_thumb_id, $size, false, $attr );
								}
							}
							// store edit link, post title, post date, post author, current image html, future image html
							$post_link = get_edit_post_link();
							$post_title = get_the_title();
							$results[] = array( 
								$post_link, 
								$post_title,
								get_the_date(),
								get_the_author(),
								$current_featured_images[ $current_thumb_id ],
								$future_featured_images[ $future_thumb_id ],
								get_post_status(),
								get_post_type(),
							);
							// notice result for cache
							$query_results[ $post_id ] = array( $future_thumb_id, $post_link, $post_title );
						} // while(have_posts)
						break;
					case 'assign_randomly':
						$last_index = count( $this->selected_multiple_image_ids ) - 1;
						/*
						 * 1. use selected images multiple times randomly and
						 * 2. overwrite existing featured images
						 */
						if ( $is_option[ 'overwrite' ] ) {
							while ( $the_query->have_posts() ) {
								$the_query->the_post();
								// get the post id once
								$post_id = get_the_ID();
								// check if there is an existing featured image
								$current_thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
								// if post with featured images should be ignored, jump to next loop
								if ( $current_thumb_id and $is_option[ 'orphans_only' ] ) {
									continue;
								}
								// if existing featured image
								if ( $current_thumb_id ) {
									// get thumbnail html if not yet got
									if ( ! isset( $current_featured_images[ $current_thumb_id ] ) ) {
										$current_featured_images[ $current_thumb_id ] = wp_get_attachment_image( $current_thumb_id, $size, false, $attr );
									}
								} else {
									$current_thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
								}
								// set image randomly : future image = new image
								$future_thumb_id = $this->selected_multiple_image_ids[ rand( 0, $last_index ) ]; // get thumb id randomly
								// get thumbnail html if not yet got
								if ( ! isset( $future_featured_images[ $future_thumb_id ] ) ) {
									$future_featured_images[ $future_thumb_id ] = wp_get_attachment_image( $future_thumb_id, $size, false, $attr );
								}
								// store edit link, post title, post date, post author, current image html, future image html
								$post_link = get_edit_post_link();
								$post_title = get_the_title();
								$results[] = array( 
									$post_link, 
									$post_title,
									get_the_date(),
									get_the_author(),
									$current_featured_images[ $current_thumb_id ],
									$future_featured_images[ $future_thumb_id ],
									get_post_status(),
									get_post_type(),
								);
								// notice result for cache
								$query_results[ $post_id ] = array( $future_thumb_id, $post_link, $post_title );
							} // while(have_posts)
						/* else 
						 * 1. use selected images multiple times randomly and
						 * 2. do not overwrite existing featured images
						 */
						} else {
							while ( $the_query->have_posts() ) {
								$the_query->the_post();
								// get the post id once
								$post_id = get_the_ID();
								// check if there is an existing featured image
								$current_thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
								// if post with featured images should be ignored, jump to next loop
								if ( $current_thumb_id and $is_option[ 'orphans_only' ] ) {
									continue;
								}
								// if existing featured image
								if ( $current_thumb_id ) {
									// get thumbnail html if not yet got
									if ( ! isset( $current_featured_images[ $current_thumb_id ] ) ) {
										$current_featured_images[ $current_thumb_id ] = wp_get_attachment_image( $current_thumb_id, $size, false, $attr );
									}
									// do nothing : future image = current image
									$future_thumb_id = $current_thumb_id;
									$future_featured_images[ $future_thumb_id ] = $current_featured_images[ $current_thumb_id ];
								} else {
									$current_thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
									// set image randomly : future image = new image
									$future_thumb_id = $this->selected_multiple_image_ids[ rand( 0, $last_index ) ]; // get thumb id randomly
									// get thumbnail html if not yet got
									if ( ! isset( $future_featured_images[ $future_thumb_id ] ) ) {
										$future_featured_images[ $future_thumb_id ] = wp_get_attachment_image( $future_thumb_id, $size, false, $attr );
									}
								}
								// store edit link, post title, post date, post author, current image html, future image html
								$post_link = get_edit_post_link();
								$post_title = get_the_title();
								$results[] = array( 
									$post_link, 
									$post_title,
									get_the_date(),
									get_the_author(),
									$current_featured_images[ $current_thumb_id ],
									$future_featured_images[ $future_thumb_id ],
									get_post_status(),
									get_post_type(),
								);
								// notice result for cache
								$query_results[ $post_id ] = array( $future_thumb_id, $post_link, $post_title );
							} // while(have_posts)
						} // if ( overwrite )
						break;
					case 'replace':
						while ( $the_query->have_posts() ) {
							$the_query->the_post();
							// get the post id once
							$post_id = get_the_ID();
							// check if there is an existing featured image
							$current_thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							if ( $current_thumb_id ) {
								// get thumbnail html if not yet got
								if ( ! isset( $current_featured_images[ $current_thumb_id ] ) ) {
									$current_featured_images[ $current_thumb_id ] = wp_get_attachment_image( $current_thumb_id, $size, false, $attr );
								}
							} else {
								$current_thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
							}
							// get html of future thumbnail
							$future_thumb_id = $this->selected_image_id;
							// get thumbnail html if not yet got
							if ( ! isset( $future_featured_images[ $future_thumb_id ] ) ) {
								$future_featured_images[ $future_thumb_id ] = wp_get_attachment_image( $future_thumb_id, $size, false, $attr );
							}
							// store edit link, post title, post date, post author, current image html, future image html
							$post_link = get_edit_post_link();
							$post_title = get_the_title();
							$results[] = array( 
								$post_link, 
								$post_title,
								get_the_date(),
								get_the_author(),
								$current_featured_images[ $current_thumb_id ],
								$future_featured_images[ $future_thumb_id ],
								get_post_status(),
								get_post_type(),
							);
							// notice result for cache
							$query_results[ $post_id ] = array( $future_thumb_id, $post_link, $post_title );
						} // while(have_posts)
						break;
					case 'remove':
					case 'remove_any_img':
						$future_thumb_id = false;
						while ( $the_query->have_posts() ) {
							$the_query->the_post();
							// get the post id once
							$post_id = get_the_ID();
							// get html for featured image
							$current_thumb_id = $this->get_sanitized_post_thumbnail_id( $post_id );
							if ( $current_thumb_id ) {
								// get thumbnail html if not yet got
								if ( ! isset( $current_featured_images[ $current_thumb_id ] ) ) {
									$current_featured_images[ $current_thumb_id ] = wp_get_attachment_image( $current_thumb_id, $size, false, $attr );
								}
							} else {
								$current_thumb_id = $false_id; // cast from '' or 'false' to a value to use as an array key
							}
							// store edit link, post title, post date, post author, current image html, future image html
							$post_link = get_edit_post_link();
							$post_title = get_the_title();
							$results[] = array( 
								$post_link, 
								$post_title,
								get_the_date(),
								get_the_author(),
								$current_featured_images[ $current_thumb_id ],
								$future_thumb_id,
								get_post_status(),
								get_post_type(),
							);
							// notice result for cache
							$query_results[ $post_id ] = array( $future_thumb_id, $post_link, $post_title );
						} // while(have_posts)
						break;
				} // switch(selected_action)
			} // if( have_posts )
			// Restore original post data after the query
			wp_reset_postdata();
			// store results as transient for 1 day at the longest
			set_transient( $this->transient_name, $query_results, DAY_IN_SECONDS );
		} // if perform

		// return results
		return $results;
	}
	
	/**
	 * Check the arguments for WP_Query depended on users selection
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    the args
	 */
	private function get_query_args() {
		// define default params
		$args[ 'posts_per_page' ] =  -1; // do not use pagination, return whole result at once
		$args[ 'no_found_rows' ] = true; // since no pagination: tell WordPress not to run SQL_CALC_FOUND_ROWS on the SQL query; drastically speeding up the query
		$args[ 'orderby' ] = 'title';
		$args[ 'order' ] = 'ASC';
		$args[ 'ignore_sticky_posts' ] = true;
		$args[ 'post_type' ] = $this->selected_post_types;
		switch ( $this->selected_action ) {
			case 'replace':
				$this->selected_post_ids = $this->get_post_ids_of_old_thumbnails();
				$args[ 'post__in' ] = $this->get_id_array_for_query( $this->selected_post_ids );
				break;
			case 'remove':
				$this->selected_post_ids = $this->get_post_ids_of_thumbnail();
				$args[ 'post__in' ] = $this->get_id_array_for_query( $this->selected_post_ids );
				break;
		} // switch(selected_action)

		if ( $this->selected_filters ) {
			foreach ( $this->selected_filters as $filter ) {
				switch ( $filter ) {
					case 'filter_post_types':
						$this->selected_post_types = $this->get_sanitized_post_types();
						if ( $this->selected_post_types ) {
							$args[ 'post_type' ] = $this->selected_post_types;
						} else {
							// add a fictitious post type to get no result (and not to get a list of all posts)
							$args[ 'post_type' ] = 'abcdefghi'; // assume there is not and will be never a post type with this name
						}
						break;
					case 'filter_category':
						$this->selected_category_id = $this->get_sanitized_category_id();
						// if there is a selected category assign it to the query
						if ( 0 < $this->selected_category_id ) {
							$args[ 'cat' ] = $this->selected_category_id; // todo: user selects more than 1 category, 'category__in'
						}
						break;
					case 'filter_tag':
						$this->selected_tag_id = $this->get_sanitized_tag_id();
						// if there is a selected tag assign it to the query
						if ( 0 < $this->selected_tag_id ) {
							$args[ 'tag_id' ] = $this->selected_tag_id; // todo: user selects more than 1 tag, 'tag__in'
						}
						break;
				} // switch(filter)
			} // foreach(selected_filters)
		} // if(selected_filters)
		#$this->dambedei($args);
		return $args;
	}

	/**
	 *
	 * Render options of HTML selection lists with strings as values
	 *
	 * @access   private
	 * @since     1.0.0
	 */
	private function get_html_options_strings( $arr, $key, $options, $first_empty = true ) {
		$output = $first_empty ? $this->get_html_empty_option() : '';
		$is_key = isset( $arr[ $key ] );
		if ( $is_key ) { 
			foreach ( $options as $key => $label ) {
				$output .= sprintf( '<option value="%s" %s>%s</option>', $key, selected( $is_key , true, false ), esc_html( $label ) );
			}
		} else {
			foreach ( $options as $key => $label ) {
				$output .= sprintf( '<option value="%s">%s</option>', $key, esc_html( $label ) );
			}
		}
		return $output;
	}
	
	/**
	 *
	 * Return empty option for selection field
	 *
	 * @access   private
	 * @since    3.0
	 */
	private function get_html_empty_option() {
		$text = '&mdash; Select &mdash;';
		return sprintf( '<option value="">%s</option>', esc_html__( $text ) );
	}

	/**
	 * Returns the post ids which are assigned with the featured images which should be replaced
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    the post ids assigned with the thumbnails
	 */
	private function get_post_ids_of_old_thumbnails() {
		$key = 'replacement_image_ids';
		if ( isset( $_POST[ $key ] ) ) {
			if ( is_array( $_POST[ $key ] ) ) {
				$this->selected_old_image_ids = $this->get_sanitized_array( $key, $this->get_featured_image_ids() );
			} else {
				$this->selected_old_image_ids = explode( ',', $_POST[ $key ] );
			}
			return $this->get_post_ids_of_featured_image_ids( $this->selected_old_image_ids );
		} else {
			return array();
		}
	}

	/**
	 * Returns the post ids which are assigned with the featured image which should be removed
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    the post ids assigned with the thumbnail
	 */
	private function get_post_ids_of_thumbnail() {
		$post_ids = array();
		global $wpdb;
		// get a normal array all names of meta keys except the WP builtins meta keys beginning with an underscore '_'
		$results = $wpdb->get_results( $wpdb->prepare( "SELECT `post_id` FROM $wpdb->postmeta WHERE `meta_key` = '_thumbnail_id' AND `meta_value` = %d", $this->selected_image_id ), ARRAY_N );
		// flatten and sanitize results
		if ( $results ) {
			foreach ( $results as $r ) {
				$post_ids[] = absint( $r[ 0 ] );
			}
		}
		if ( empty( $post_ids ) ) {
			$post_ids[] = 0; // enter at least one element with no sense to yield 0 results with WP_QUERY()
		}
		return $post_ids;
	}
	
	/**
	 * Returns the posts ids which are assigned to given featured image ids
	 *
	 * @access   private
	 * @since     2.0
	 *
	 * @return    array    the post ids assigned to given featured images
	 */
	private function get_post_ids_of_featured_image_ids( $image_ids = array() ) {
		$post_ids = array();
		global $wpdb;
		// get a normal array with all IDs of posts assigned with the image ids
		foreach ( $image_ids as $id ) {
			$results = $wpdb->get_results( $wpdb->prepare( "SELECT `post_id` FROM $wpdb->postmeta WHERE `meta_key` = '_thumbnail_id' AND `meta_value` = %d", $id ), ARRAY_N );
			// flatten and sanitize results
			if ( $results ) {
				foreach ( $results as $r ) {
					$post_ids[] = absint( $r[ 0 ] );
				}
			}
		} // foreach()
		return $post_ids;
	}

	/**
	 * Returns the thumbnails ids which are assigned with a post
	 *
	 * @access   private
	 * @since     1.0.0
 	 *
	 * @return    array    the image ids assigned to posts as featured images
	 */
	private function get_featured_image_ids() {
		$image_ids = array();
		global $wpdb;
		// get a normal array all names of meta keys except the WP builtins meta keys beginning with an underscore '_'
		$results = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT `meta_value` FROM $wpdb->postmeta WHERE `meta_key` = '_thumbnail_id' AND `meta_value` != %d", $this->selected_image_id ), ARRAY_N );
		// flatten and sanitize results
		if ( $results ) {
			foreach ( $results as $r ) {
				$image_ids[] = absint( $r[ 0 ] );
			}
		}
		return $image_ids;
	}

	/**
	 * Check the step parameter and return safe values
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    string    the name of the step the plugin should take
	 */
	private function get_sanitized_step() {
		return $this->get_sanitized_value(
			'step',
			array_keys( $this->valid_steps ),
			'start'
		);
	}

	/**
	 * Check the action parameter and return safe values 
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    string    the name of the action the plugin should perform, else empty string
	 */
	private function get_sanitized_action() {
		return $this->get_sanitized_value(
			'action',
			array_keys( array_merge( $this->valid_actions, $this->valid_actions_without_image, $this->valid_actions_multiple_images ) )
		);
	}

	/**
	 * Check the requested filters and return safe values 
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    the names of the filters
	 */
	private function get_sanitized_filter_names() {
		return $this->get_sanitized_array(
			'filters',
			array_keys( $this->valid_filters )
		);
	}

	/**
	 * Check the requested options and return safe values 
	 *
	 * @access   private
	 * @since     5.1
	 *
	 * @return    array    the names of the options
	 */
	private function get_sanitized_option_names() {
		return $this->get_sanitized_array(
			'options',
			array_keys( $this->valid_options )
		);
	}

	/**
	 * Check the requested post types and return safe values 
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    the names of the selected post types
	 */
	private function get_sanitized_post_types() {
		return $this->get_sanitized_array(
			'post_types',
			array_keys( $this->valid_post_types )
		);
	}

	/**
	 * Check the parameter defined by key and return safe value
	 * Written to return a single value, e.g. for radio buttons
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    mixed    the user selected valid value or the default value
	 */
	private function get_sanitized_value( $key, $valid_values, $default_value = null ) {
		$value = isset( $_REQUEST[ $key ] ) ? $_REQUEST[ $key ] : $default_value;
		if ( in_array( $value, $valid_values ) ) {
			return $value;            
		} else {                       
			return $default_value;          
		}                             
	}

	/**
	 * Check the parameter and return safe values 
	 * Written to return multiple values, e.g. for checkboxes
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    the user selected valid values or the default values
	 */
	private function get_sanitized_array( $key, $valid_array, $default_array = array() ) {
		if ( isset( $_POST[ $key ] ) and is_array( $_POST[ $key ] ) ) {
			return $this->get_array_intersect( $_POST[ $key ], $valid_array );
		} else {
			return $default_array;
		}
	}

	/**
	 * Check the parameters and return safe values 
	 * Written to return multiple values associated with key names, e.g. for WP Query
	 * The function filters out empty strings
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    the user selected valid values or the default values
	 */
	private function get_sanitized_associated_array( $key, $valid_array, $default_array = array() ) {
		$queries = array();
		$arr = isset( $_POST[ $key ] ) ? $_POST[ $key ] : $default_array;
		if ( ! empty( $arr ) && is_array( $arr ) ) {
			foreach ( array_keys( $valid_array ) as $key ) {
				if ( array_key_exists( $key, $arr ) and isset( $arr[ $key ] ) ) {
					$queries[ $key ] = $arr[ $key ];
				}
			}
		}
		return $queries;
	}

	/**
	 * Return the intersection of two given arrays
	 * Runs 5 times faster than PHP's array_intersect()
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    array    the intersection of two arrays
	 */
	private function get_array_intersect( $a, $b ) { 
		$m = array(); 
		$intersection = array(); 
		// copy first array to array
		$len = sizeof( $a );
		for( $i = 0; $i < $len; $i++ ) { 
			$m[] = $a[ $i ]; 
		} 
		// append second array to array
		$len = sizeof( $b );
		for( $i = 0; $i < $len; $i++ ) { 
			$m[] = $b[ $i ]; 
		} 
		// make values sorted
		sort( $m ); 
		// compare value with the next one and append to intersection array if equal
		$len = sizeof( $m ) - 1;
		for( $i = 0; $i < $len; $i++ ) { 
			if ( $m[ $i ] == $m[ $i + 1 ] ) $intersection[] = $m[ $i ]; 
		} 
		// return intersection
		return $intersection; 
	}
	
	/**
	 * Check the integer value of a user selected value else default value
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    integer    the id or 0
	 */
	private function get_sanitized_id( $key, $default = 0 ) {
		$given_id = absint( sanitize_text_field( $_REQUEST[ $key ]  ) );
		if ( ( ! isset( $_REQUEST[ $key ] ) ) or empty( $_REQUEST[ $key ] ) or 0 > $given_id ) {
			return $default;
		} else {
			return $given_id;
		}
	}
	
	/**
	 * Check the id of selected featured image and return safe value
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    integer    the id or 0
	 */
	private function get_sanitized_image_id() {
		return $this->get_sanitized_id( 'image_id' );
	}
	
	/**
	 * Check the ids of selected featured images and return safe value
	 *
	 * @access   private
	 * @since     6.0
	 *
	 * @return    array    the ids or empty
	 */
	private function get_sanitized_multiple_image_ids() {
		if ( ! isset( $_POST[ 'multiple_image_ids' ] ) or empty( $_POST[ 'multiple_image_ids' ] ) ) {
			return array();
		} else {
			// read: sanatize string, make array out of string, convert each array value to integer, return result array
			return array_map( 'absint', explode( ',', sanitize_text_field( $_POST[ 'multiple_image_ids' ] ) ) );
		}
	}
	
	/**
	 * Check the id of selected tag and return safe value
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    integer    the id or 0
	 */
	private function get_sanitized_tag_id() {
		return $this->get_sanitized_id( 'tag_id' );
	}
	
	/**
	 * Check the id of selected category and return safe value
	 *
	 * @access   private
	 * @since     1.0.0
	 *
	 * @return    integer    the id or 0
	 */
	private function get_sanitized_category_id() {
		return $this->get_sanitized_id( 'category_id' );
	}

	/**
	 * Get the ID of a post's featured image, else 0
	 *
	 * @access   private
	 * @since     11.2
	 *
	 * @return    integer    the id or 0
	 */
	private function get_sanitized_post_thumbnail_id( $post_id ) {
		// check if an image with the given ID exists in the media library, else set id to 0
		$current_thumb_id = (int) get_post_thumbnail_id( $post_id );
		if ( $current_thumb_id and wp_attachment_is_image( $current_thumb_id ) ) {
			return $current_thumb_id;
		} else {
			return 0;
		}
	}

	/**
	 * If results in array, return them, else say query something like "no results in array"
	 *
	 * @access   private
	 * @since     2.0
	 *
	 * @return    array    Array with content or 0
	 */
	private function get_id_array_for_query( $arr ) {
		if ( empty( $arr ) ) {
			return array( 0 );
		} else {
			return $arr;
		}
	}
	
	/**
	 * Returns the url of the plugin's admin part
	 *
	 * @since    1.0.0
	 */
	public function get_plugin_admin_url() {
		return plugin_dir_url( __FILE__ );
	}

	/**
	 * Returns the url of the plugin's images folder without an trailing slash	
	 *
	 * @since    1.0.0
	 */
	public function get_admin_images_url() {
		return sprintf( '%s/assets/images', $this->get_plugin_admin_url() );
	}
	
	/**
	 * Return an instance of this class.
	 *
	 * @since     1.0.0
	 *
	 * @return    object    A single instance of this class.
	 */
	public static function get_instance() {

		/*
		 * @TODO :
		 *
		 * - Uncomment following lines if the admin class should only be available for super admins
		 */
		/* if( ! is_super_admin() ) {
			return;
		} */

		// If the single instance hasn't been set, set it now.
		if ( null == self::$instance ) {
			self::$instance = new self;
		}

		return self::$instance;
	}

	/**
	 * Return the page headline.
	 *
	 * @since    7.0
	 *
	 *@return    page headline variable.
	 */
	public function get_page_headline() {
		//return __( 'Set, replace, remove', 'quick-featured-images' );
		$text = 'Bulk Edit';
		return __( $text );
		// just for the translation editor to catch this string
		$text = __( 'Set, replace, remove', 'quick-featured-images' );
	}

	/**
	 * Return the page description.
	 *
	 * @since    8.0
	 *
	 *@return    page description variable.
	 */
	public function get_page_description() {
		return __( 'Bulk set, replace and remove featured images for existing posts', 'quick-featured-images' );
	}

	/**
	 * Return the page slug.
	 *
	 * @since    7.0
	 *
	 *@return    page slug variable.
	 */
	public function get_page_slug() {
		return $this->page_slug;
	}

	/**
	 * Return the required user capability.
	 *
	 * @since    7.0
	 *
	 *@return    required user capability variable.
	 */
	public function get_required_user_cap() {
		return $this->required_user_cap;
	}

	/**
	 * Register and enqueue admin-specific style sheet.
	 *
	 * @since     1.0.0
	 *
	 * @return    null    Return early if no settings page is registered.
	 */
	public function enqueue_admin_styles() {

		if ( ! isset( $this->plugin_screen_hook_suffix ) ) {
			return;
		}

		// request css only if this plugin was called
		$screen = get_current_screen();
		if ( $this->plugin_screen_hook_suffix == $screen->id ) {
			wp_enqueue_style( $this->plugin_slug .'-admin-styles', plugins_url( 'assets/css/admin.min.css', __FILE__ ), array(), $this->plugin_version );
		}

	}

	/**
	 * Register and enqueue admin-specific JavaScript.
	 *
	 * @since     1.0.0
	 *
	 * @return    null    Return early if no settings page is registered.
	 */
	public function enqueue_admin_scripts() {

		if ( ! isset( $this->plugin_screen_hook_suffix ) ) {
			return;
		}

		$screen = get_current_screen();
		if ( $this->plugin_screen_hook_suffix == $screen->id ) {
			// load script
			wp_enqueue_script( $this->plugin_slug . '-admin-script', plugins_url( 'assets/js/admin.js', __FILE__ ), array( 'jquery' ), $this->plugin_version );
			// Enqueue all stuff to use media API, requires at least WP 3.5
			wp_enqueue_media();
		}

	}

	/**
	 * Register the administration menu for this plugin into the WordPress Dashboard menu.
	 *
	 * @since     1.0.0
	 */
	public function add_plugin_admin_menu() {

		// get translated string of the menu label and page headline
		$label = $this->get_page_headline();
		
		/*
		 * Add the top level menu page of this plugin
		 *
		 */
		//$this->plugin_screen_hook_suffix = add_object_page(...);
		$this->plugin_screen_hook_suffix = add_submenu_page( 
			$this->parent_page_slug, // parent_slug
			sprintf( '%s: %s', $this->plugin_name, $label ), // page_title
			$label, // menu_title
			$this->required_user_cap, // capability to use the following function
			$this->page_slug, // menu_slug
			array( $this, 'main' ) // function to execute when loading this page
		);		
	}
	
	/**
	 * Add a "Bulk set" link to the media row actions
	 *
	 * @since    4.1
	 */
	function add_media_row_action( $actions, $post ) {

		// if current media is not an image or user has not the right or thumbnails are not supported return without change
		if ( 'image/' != substr( $post->post_mime_type, 0, 6 ) || ! current_user_can( $this->required_user_cap ) || ! current_theme_supports( 'post-thumbnails' ) )
			return $actions;
		
		// else build the link with nonce
		$url = wp_nonce_url( admin_url( sprintf( 'admin.php?page=%s&step=select&action=assign&image_id=%d', $this->page_slug, $post->ID ) ), 'bulk-assign' );
		
		// add it
		$actions['quick-featured-images'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), esc_html__( 'Bulk set as featured image', 'quick-featured-images' ) );
		
		// return extended action links list
		return $actions;
	}

}

