<?php
/**
 * Media Library Assistant Shortcode handler(s)
 *
 * @package Media Library Assistant
 * @since 2.20
 */

// The MLA database access functions aren't available to "front end" posts/pages
if ( !class_exists( 'MLAQuery' ) ) {
	require_once( MLA_PLUGIN_PATH . 'includes/class-mla-data-query.php' );
	MLAQuery::initialize();
}

if ( !class_exists( 'MLAData' ) ) {
	require_once( MLA_PLUGIN_PATH . 'includes/class-mla-data.php' );
	MLAData::initialize();
}

if ( !class_exists( 'MLATemplate_Support' ) ) {
	require_once( MLA_PLUGIN_PATH . 'includes/class-mla-template-support.php' );
}
//error_log( __LINE__ . ' DEBUG: MLAShortcode_Support $_REQUEST = ' . var_export( $_REQUEST, true ), 0 );

/**
 * Class MLA (Media Library Assistant) Shortcode Support provides the functions that
 * implement the [mla_gallery] and [mla_tag_cloud] shortcodes. It also implements the
 * mla_get_shortcode_attachments() and mla_get_terms() database access functions.
 *
 * @package Media Library Assistant
 * @since 2.20
 */
class MLAShortcode_Support {
	/**
	 * Verify the presence of Ghostscript for mla_viewer
	 *
	 * @since 2.20
	 *
	 * @param	string	Non-standard location to override default search, e.g.,
	 *					'C:\Program Files (x86)\gs\gs9.15\bin\gswin32c.exe'
	 * @param	boolean	Force ghostscript-only tests, used by 
	 *                  MLASettings_Shortcodes::mla_compose_shortcodes_tab()
	 *
	 * @return	boolean	true if Ghostscript available else false
	 */
	public static function mla_ghostscript_present( $explicit_path = '', $ghostscript_only = false ) {
		static $ghostscript_present = NULL;

		// If $ghostscript_only = false, let the mla_debug parameter control logging
		if ( $ghostscript_only ) {
			$mla_debug_category = MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL;
		} else {
			$mla_debug_category = NULL;
		}

		MLACore::mla_debug_add( __LINE__ . " MLAShortcode_Support::mla_ghostscript_present( {$ghostscript_only} ) explicit_path = " . var_export( $explicit_path, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
		MLACore::mla_debug_add( __LINE__ . " MLAShortcode_Support::mla_ghostscript_present( {$ghostscript_only} ) ghostscript_present = " . var_export( $ghostscript_present, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );

		if ( ! $ghostscript_only ) {
			if ( isset( $ghostscript_present ) ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, ghostscript_present = ' . var_export( $ghostscript_present, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
				return $ghostscript_present;
			}

			if ( 'checked' != MLACore::mla_get_option( 'enable_ghostscript_check' ) ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, disabled', $mla_debug_category );
				return $ghostscript_present = true;
			}

			// Imagick must be installed as well
			if ( ! class_exists( 'Imagick' ) ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, Imagick missing', $mla_debug_category );
				return $ghostscript_present = false;
			}
		} // not ghostscript_only

		// Look for exec() - from http://stackoverflow.com/a/12980534/866618
		$blacklist = preg_split( '/,\s*/', ini_get('disable_functions') . ',' . ini_get('suhosin.executor.func.blacklist') );
		if ( in_array('exec', $blacklist) ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, exec in blacklist', $mla_debug_category );
			return $ghostscript_present = false;
		}

		if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3) ) ) {
			if ( ! empty( $explicit_path ) ) {
				$return = exec( 'dir /o:n/s/b "' . $explicit_path . '"' );
				MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, WIN explicit path = ' . var_export( $return, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
				if ( ! empty( $return ) ) {
					return $ghostscript_present = true;
				} else {
					return $ghostscript_present = false;
				}
			}

			$return = getenv('GSC');
			MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, getenv(GSC) = ' . var_export( $return, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
			if ( ! empty( $return ) ) {
				return $ghostscript_present = true;
			}

			$return = exec('where gswin*c.exe');
			MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>,  exec(where gswin*c.exe) = ' . var_export( $return, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
			if ( ! empty( $return ) ) {
				return $ghostscript_present = true;
			}

			$return = exec('dir /o:n/s/b "C:\Program Files\gs\*gswin*c.exe"');
			MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>,  exec(dir /o:n/s/b "C:\Program Files\gs\*gswin*c.exe") = ' . var_export( $return, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
			if ( ! empty( $return ) ) {
				return $ghostscript_present = true;
			}

			$return = exec('dir /o:n/s/b "C:\Program Files (x86)\gs\*gswin32c.exe"');
			MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>,  exec(dir /o:n/s/b "C:\Program Files (x86)\gs\*gswin32c.exe") = ' . var_export( $return, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
			if ( ! empty( $return ) ) {
				return $ghostscript_present = true;
			}

			MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, WIN detection failed', $mla_debug_category );
			return $ghostscript_present = false;
		} // Windows platform

		if ( ! empty( $explicit_path ) ) {
			exec( 'test -e ' . $explicit_path, $dummy, $ghostscript_path );
			MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, explicit path = ' . var_export( $explicit_path, true ) . ', ghostscript_path = ' . var_export( $ghostscript_path, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
			return ( $explicit_path === $ghostscript_path );
		}

		$return = exec('which gs');
		MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, exec(which gs) = ' . var_export( $return, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
		if ( ! empty( $return ) ) {
			return $ghostscript_present = true;
		}

		$test_path = '/usr/bin/gs';
		$output = array();
		$return_arg = -1;
		$return = exec( 'test -e ' . $test_path, $output, $return_arg );
		MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, test_path = ' . var_export( $test_path, true ) . ', return_arg = ' . var_export( $return_arg, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
		MLACore::mla_debug_add( __LINE__ . ' <strong>MLAShortcode_Support::mla_ghostscript_present</strong>, return = ' . var_export( $return, true ) . ', output = ' . var_export( $output, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL );
		return $ghostscript_present = ( $test_path === $return_arg );
	}

	/**
	 * Removes MIME type restriction from Photonic Gallery query
	 *
	 * @since 2.76
	 *
	 * @param	object	$wp_query Current WP_Query object
	 */
	public static function _photonic_pre_get_posts( $wp_query ) {
		// This has already been applied in MLA's database query
		$wp_query->set( 'post_mime_type', '' );
	}

	/**
	 * Informs _get_attachment_image_src() of the 'size=icon_feature' setting
	 *
	 * @since 2.90
	 *
	 * @var	string shortcode 'size' parameter value
	 */
	private static $size_parameter = '';

	/**
	 * Filters the image src result, returning the "Featured Image" or an icon to represent a non-image attachment.
	 *
	 * @since 2.76
	 *
	 * @param array|false  $image         Either array with src, width & height, icon src, or false.
	 * @param int          $attachment_id Image attachment ID.
	 * @param string|array $size          Size of image. Image size or array of width and height values
	 *                                    (in that order). Default 'thumbnail'.
	 * @param bool         $icon          Whether the image should be treated as an icon. Default false.
  	 */
	public static function _get_attachment_image_src( $image, $attachment_id, $size, $icon ) {
		static $nested_call = false;

		if ( $nested_call ) {
			return $image;
		}

		if ( 'none' === self::$size_parameter ) {
			return false;
		} elseif ( ( 'icon_only' === self::$size_parameter ) ||  ( 'icon_feature' === self::$size_parameter ) ) {
			// No native images allowed
			$image = false;
		}

		// If a native image exists, we're done
		if ( false !== $image ) {
			return $image;
		}

		// Look for the "Featured Image" as an alternate thumbnail for PDFs, etc.
		if ( ( 'icon_only' !== self::$size_parameter ) && ( 'checked' == MLACore::mla_get_option( 'enable_featured_image' ) ) ) {
			$nested_call = true;
			$feature = get_the_post_thumbnail( $attachment_id, $size, array( 'class' => 'attachment-thumbnail' ) );
			$nested_call = false;

			if ( ! empty( $feature ) ) {
				$match_count = preg_match_all( '# width=\"([^\"]+)\" height=\"([^\"]+)\" src=\"([^\"]+)\" #', $feature, $matches, PREG_OFFSET_CAPTURE );
				if ( ! ( ( $match_count == false ) || ( $match_count == 0 ) ) ) {
					$image = array( $matches[3][0][0], $matches[1][0][0], $matches[2][0][0] );
					return $image;
				}
			}
		} // enable_featured_image

		// For any of the three "icon" variations, try to substitute an icon image
		if ( 0 === strpos( self::$size_parameter, 'icon' )  ) {
			if ( $src = wp_mime_type_icon( $attachment_id ) ) {
				/** This filter is documented in wp-includes/post.php */
				$icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
	
				$src_file = $icon_dir . '/' . wp_basename( $src );
				@list( $width, $height ) = getimagesize( $src_file );
			}
	
			if ( $src && $width && $height ) {
				$image = array( $src, $width, $height );
			}
		}

		return $image;
	}

	/**
	 * Errors found in function mla_validate_attributes()
	 *
	 * @since 2.80
	 *
	 * @var	array
	 */
	private static $attributes_errors = array();

	/**
	 * Make sure $attr is an array, repair line-break damage, merge with $content
	 *
	 * @since 2.20
	 *
	 * @param	mixed	$attr Array or string containing shortcode attributes
	 * @param	string	$content Optional content for enclosing shortcodes
	 *
	 * @return	array	clean attributes array
	 */
	public static function mla_validate_attributes( $attr, $content = NULL ) {
//error_log( __LINE__ . " mla_validate_attributes() attr = " . var_export( $attr, true ), 0 );
//error_log( __LINE__ . " mla_validate_attributes() content = " . var_export( $content, true ), 0 );
/*		if ( empty( $attr ) ) {
			$attr = array();
		} elseif ( is_string( $attr ) ) {
			$attr = shortcode_parse_atts( $attr );
		} // */
		if ( is_string( $attr ) ) {
			$attr = shortcode_parse_atts( $attr );
		}
		
		if ( empty( $attr ) ) {
			$attr = array();
		}
//error_log( __LINE__ . " mla_validate_attributes() attr = " . var_export( $attr, true ), 0 );

		// Numeric keys indicate parse errors
		$not_valid = false;
		foreach ( $attr as $key => $value ) {
			if ( is_numeric( $key ) ) {
				$not_valid = true;
				break;
			}
		}

		if ( $not_valid ) {
			/*
			 * Found an error, e.g., line break(s) among the atttributes.
			 * Try to reconstruct the input string without them.
			 */
			$new_attr = '';
			foreach ( $attr as $key => $value ) {
				$value = str_replace( array( '&#038;', '&#8216;', '&#8217;', '&#8220;', '&#8221;', '&#8242;', '&#8243;', '&amp;', '<br />', '<br>', '<p>', '</p>', "\r", "\n", "\t" ),
		 	                            array( '&',      '\'',      '\'',      '"',       '"',       '\'',      '"',       '&',     ' ',      ' ',    ' ',   ' ',    ' ',  ' ',  ' ' ), $value );
//error_log( __LINE__ . " mla_validate_attributes() value = " . var_export( $value, true ), 0 );
				$break_tag = strpos( $value, '<br' );
				if ( ( false !== $break_tag ) && ( ($break_tag + 3) == strlen( $value ) ) ) {
					$value = substr( $value, 0, ( strlen( $value ) - 3) );
				}

				if ( is_numeric( $key ) ) {
					if ( '/>' !== $value ) {
						$new_attr .= $value . ' ';
					}
				} else {
					$delimiter = ( false === strpos( $value, '"' ) ) ? '"' : "'";
					$new_attr .= $key . '=' . $delimiter . $value . $delimiter . ' ';
				}
			}
//error_log( __LINE__ . " mla_validate_attributes() new_attr = " . var_export( $new_attr, true ), 0 );

			$attr = shortcode_parse_atts( $new_attr );
//error_log( __LINE__ . " mla_validate_attributes() attr = " . var_export( $attr, true ), 0 );

			// Remove empty values and still-invalid parameters
			$new_attr = array();
			foreach ( $attr as $key => $value ) {
				if ( is_numeric( $key ) || empty( $value ) ) {
					self::$attributes_errors['raw'][] = '[' . $key . '] => ' . $value;
					self::$attributes_errors['escaped'][] = '[' . $key . '] => ' . esc_html( $value );
					continue;
				}

				$new_attr[ $key ] = $value;
			}

			$attr = $new_attr;
		} // not_valid
//error_log( __LINE__ . " mla_validate_attributes() attr = " . var_export( $attr, true ), 0 );

		// Look for parameters in an enclosing shortcode
		if ( ! ( empty( $content ) || isset( $attr['mla_alt_shortcode'] ) ) ) {
			$content = str_replace( array( '&#038;', '&#8216;', '&#8217;', '&#8220;', '&#8221;', '&#8242;', '&#8243;', '&amp;', '<br />', '<br>', '<p>', '</p>', "\r", "\n", "\t" ),
			                          array( '&',      '\'',      '\'',      '"',       '"',       '\'',      '"',       '&',     ' ',      ' ',    ' ',   ' ',    ' ',  ' ',  ' ' ), $content );
			$content_attr = shortcode_parse_atts( $content );
			if ( is_array( $content_attr ) ) {
				// Remove empty values and still-invalid parameters
				$new_attr = array();
				foreach ( $content_attr as $key => $value ) {
					if ( is_numeric( $key ) || empty( $value ) ) {
						self::$attributes_errors['raw'][] = 'content [' . $key . '] => ' . $value;
						self::$attributes_errors['escaped'][] = 'content [' . $key . '] => ' . esc_html( $value );
						continue;
					}

					$new_attr[ $key ] = $value;
				}

				$attr = array_merge( $attr, $new_attr );
			}
		}

		return $attr;
	}

	/**
	 * Turn debug collection and display on or off
	 *
	 * @since 2.20
	 *
	 * @var	boolean
	 */
	private static $mla_debug = false;

	/**
	 * Default values when global $post is not set
	 *
	 * @since 2.40
	 *
	 * @var	array
	 */
	private static $empty_post = array( 
				'ID' => 0,
				'post_author' => 0,
				'post_date' => '0000-00-00 00:00:00',
				'post_date_gmt' => '0000-00-00 00:00:00',
				'post_content' => '',
				'post_title' => '',
				'post_excerpt' => '',
				'post_status' => 'publish',
				'comment_status' => 'open',
				'ping_status' => 'open',
				'post_name' => '',
				'to_ping' => 'None',
				'pinged' => 'None',
				'post_modified' => '0000-00-00 00:00:00',
				'post_modified_gmt' => '0000-00-00 00:00:00',
				'post_content_filtered' => 'None',
				'post_parent' => 0,
				'guid' => '',
				'menu_order' => 0,
				'post_type' => 'post',
				'post_mime_type' => '',
				'comment_count' => 0,
			);

	/**
	 * The MLA Gallery shortcode.
	 *
	 * This is a superset of the WordPress Gallery shortcode for displaying images on a post,
	 * page or custom post type. It is adapted from /wp-includes/media.php gallery_shortcode.
	 * Enhancements include many additional selection parameters and full taxonomy support.
	 *
	 * @since 2.20
	 *
	 * @param array $attr Attributes of the shortcode
	 * @param string $content Optional content for enclosing shortcodes
	 *
	 * @return string HTML content to display gallery.
	 */
	public static function mla_gallery_shortcode( $attr, $content = NULL ) {
//error_log( __LINE__ . " mla_gallery_shortcode() _REQUEST = " . var_export( $_REQUEST, true ), 0 );
//error_log( __LINE__ . " mla_gallery_shortcode() attr = " . var_export( $attr, true ), 0 );
//error_log( __LINE__ . " mla_gallery_shortcode() content = " . var_export( $content, true ), 0 );
		global $post;

		// Some do_shortcode callers may not have a specific post in mind
		if ( ! is_object( $post ) ) {
			$post = (object) self::$empty_post;
		}

		// $instance supports multiple galleries in one page/post	
		static $instance = 0;
		$instance++;

		// Some values are already known, and can be used in data selection parameters
		$upload_dir = wp_upload_dir();
		$page_values = array(
			'instance' => $instance,
			'selector' => "mla_gallery-{$instance}",
			'site_url' => site_url(),
			'base_url' => $upload_dir['baseurl'],
			'base_dir' => $upload_dir['basedir'],
			'id' => $post->ID,
			'page_ID' => $post->ID,
			'page_author' => $post->post_author,
			'page_date' => $post->post_date,
			'page_content' => $post->post_content,
			'page_title' => $post->post_title,
			'page_excerpt' => $post->post_excerpt,
			'page_status' => $post->post_status,
			'page_name' => $post->post_name,
			'page_modified' => $post->post_modified,
			'page_parent' => $post->post_parent,
			'page_guid' => $post->guid,
			'page_type' => $post->post_type,
			'page_mime_type' => $post->post_mime_type,
			'page_url' => get_page_link(),
		);

		/*
		 * Make sure $attr is an array, even if it's empty,
		 * and repair damage caused by link-breaks in the source text
		 */
		$attr = self::mla_validate_attributes( $attr, $content );

		// Filter the attributes before $mla_page_parameter and "request:" prefix processing.
		$attr = apply_filters( 'mla_gallery_raw_attributes', $attr );

		/*
		 * The mla_paginate_current parameter can be changed to support
		 * multiple galleries per page.
		 */
		if ( ! isset( $attr['mla_page_parameter'] ) ) {
			$attr['mla_page_parameter'] = self::$mla_get_shortcode_attachments_parameters['mla_page_parameter'];
		}

		// The mla_page_parameter can contain page_level parameters like {+page_ID+}
		$attr_value = str_replace( '{+', '[+', str_replace( '+}', '+]', $attr['mla_page_parameter'] ) );
		$mla_page_parameter = MLAData::mla_parse_template( $attr_value, $page_values );

		/*
		 * Special handling of the mla_paginate_current parameter to make
		 * "MLA pagination" easier. Look for this parameter in $_REQUEST
		 * if it's not present in the shortcode itself.
		 */
		if ( ! isset( $attr[ $mla_page_parameter ] ) ) {
			if ( isset( $_REQUEST[ $mla_page_parameter ] ) ) {
				$attr[ $mla_page_parameter ] = sanitize_text_field( wp_unslash( $_REQUEST[ $mla_page_parameter ] ) );
			}
		}

		// These are the parameters for gallery display
		$mla_item_specific_arguments = array(
			'mla_link_attributes' => '',
			'mla_link_class' => '',
			'mla_link_href' => '',
			'mla_link_text' => '',
			'mla_nolink_text' => '',
			'mla_rollover_text' => '',
			'mla_image_class' => '',
			'mla_image_alt' => '',
			'mla_image_attributes' => '',
			'mla_caption' => '',
			'mla_alt_ids_value' => NULL,
		);

		// These arguments must not be passed on to alternate gallery shortcodes
		$mla_arguments = array_merge( array(
			'mla_output' => 'gallery',
			'mla_style' => MLACore::mla_get_option('default_style'),
			'mla_markup' => MLACore::mla_get_option('default_markup'),
			'mla_float' => 'none', // before v2.90: is_rtl() ? 'right' : 'left',
			'mla_itemwidth' => MLACore::mla_get_option('mla_gallery_itemwidth'),
			'mla_margin' => MLACore::mla_get_option('mla_gallery_margin'),
			'mla_target' => '',
			'mla_debug' => false,
			'mla_allow_rml' => false,
			'mla_rml_folder' => NULL,
			'mla_rml_include_children' => false,

			'mla_named_transfer' => false,
			'mla_viewer' => false,
			'mla_single_thread' => false,
			'mla_viewer_extensions' => 'ai,eps,pdf,ps',
			'mla_viewer_limit' => '0',
			'mla_viewer_width' => '0',
			'mla_viewer_height' => '0',
			'mla_viewer_best_fit' => NULL,
			'mla_viewer_page' => '1',
			'mla_viewer_resolution' => '0',
			'mla_viewer_quality' => '0',
			'mla_viewer_type' => '',

			'mla_alt_shortcode' => NULL,
			'mla_alt_ids_name' => 'ids',
			'mla_alt_ids_template' => NULL,

			// paginatation arguments defined in $mla_get_shortcode_attachments_parameters
			// 'mla_page_parameter' => 'mla_paginate_current', handled in code with $mla_page_parameter
			// 'mla_paginate_current' => NULL,
			// 'mla_paginate_total' => NULL,
			// 'id' => NULL,

			'mla_end_size'=> 1,
			'mla_mid_size' => 2,
			'mla_prev_text' => '&laquo; ' . __( 'Previous', 'media-library-assistant' ),
			'mla_next_text' => __( 'Next', 'media-library-assistant' ) . ' &raquo;',
			'mla_paginate_type' => 'plain',
			'mla_paginate_rows' => NULL ),
			$mla_item_specific_arguments
		);

		$html5 = current_theme_supports( 'html5', 'gallery' );
		$default_arguments = array_merge( array(
			'size' => 'thumbnail', // or 'medium', 'large', 'full' or registered size
			'itemtag' => $html5 ? 'figure' : 'dl',
			'icontag' => $html5 ? 'div' : 'dt',
			'captiontag' => $html5 ? 'figcaption' : 'dd',
			'columns' => MLACore::mla_get_option('mla_gallery_columns'),
			'link' => 'permalink', // or 'post', 'file', a registered size, etc.
			// Photonic-specific
			'id' => NULL,
			'style' => NULL,
			'type' => 'default', // also used by WordPress.com Jetpack!
			'thumb_width' => 75,
			'thumb_height' => 75,
			'thumbnail_size' => 'thumbnail',
			'slide_size' => 'large',
			'slideshow_height' => 500,
			'fx' => 'fade',
			'timeout' => 4000,
			'speed' => 1000,
			'pause' => NULL),
			$mla_arguments
		);

		// Convert to boolean
		$arguments['mla_named_transfer'] = 'true' === ( ( ! empty( $arguments['mla_named_transfer'] ) ) ? trim( strtolower( $arguments['mla_named_transfer'] ) ) : 'false' );

		// Apply default arguments set in the markup template
		$template = $mla_arguments['mla_markup'];
		if ( isset( $attr['mla_markup'] ) && MLATemplate_Support::mla_fetch_custom_template( $attr['mla_markup'], 'gallery', 'markup', '[exists]' ) ) {
			$template = $attr['mla_markup'];
		}

		$arguments = MLATemplate_Support::mla_fetch_custom_template( $template, 'gallery', 'markup', 'arguments' );
		if ( ! empty( $arguments ) ) {
			$attr = wp_parse_args( $attr, self::mla_validate_attributes( array(), $arguments ) );
		}

		/*
		 * Look for page-level, 'request:' and 'query:' substitution parameters,
		 * which can be added to any input parameter
		 */
		foreach ( $attr as $attr_key => $attr_value ) {
			/*
			 * attachment-specific Gallery Display Content parameters must be evaluated
			 * later, when all of the information is available.
			 */
			if ( array_key_exists( $attr_key, $mla_item_specific_arguments ) ) {
				continue;
			}

			$attr_value = str_replace( '{+', '[+', str_replace( '+}', '+]', $attr_value ) );
			$replacement_values = MLAData::mla_expand_field_level_parameters( $attr_value, $attr, $page_values );
			$attr[ $attr_key ] = MLAData::mla_parse_template( $attr_value, $replacement_values );
		}

		// Merge gallery arguments with defaults, pass the query arguments on to mla_get_shortcode_attachments.
		$attr = apply_filters( 'mla_gallery_attributes', $attr );
		$content = apply_filters( 'mla_gallery_initial_content', $content, $attr );
		$arguments = shortcode_atts( $default_arguments, $attr );
		$arguments = apply_filters( 'mla_gallery_arguments', $arguments );

		// Decide which templates to use
		if ( ( 'none' !== $arguments['mla_style'] ) && ( 'theme' !== $arguments['mla_style'] ) ) {
			if ( !MLATemplate_Support::mla_fetch_custom_template( $arguments['mla_style'], 'gallery', 'style', '[exists]' ) ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>mla_gallery mla_style</strong> "' . $arguments['mla_style'] . '" ' . __( 'not found', 'media-library-assistant' ), MLACore::MLA_DEBUG_CATEGORY_ANY );
				$arguments['mla_style'] = $default_arguments['mla_style'];
			}
		}

		if ( !MLATemplate_Support::mla_fetch_custom_template( $arguments['mla_markup'], 'gallery', 'markup', '[exists]' ) ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>mla_gallery mla_markup</strong> "' . $arguments['mla_markup'] . '" ' . __( 'not found', 'media-library-assistant' ), MLACore::MLA_DEBUG_CATEGORY_ANY );
			$arguments['mla_markup'] = $default_arguments['mla_markup'];
		}

		// Look for "no effect" alternate gallery shortcode to support plugins such as Justified Image Grid
		if ( is_string( $arguments['mla_alt_shortcode'] ) && ( in_array( $arguments['mla_alt_shortcode'], array( 'mla_gallery', 'no' ) ) ) ) {
			$arguments['mla_alt_shortcode'] = NULL;
			$arguments['mla_alt_ids_name'] = 'ids';
			$arguments['mla_alt_ids_value'] = NULL;
			$arguments['mla_alt_ids_template'] = NULL;
		}

		self::$mla_debug = ( ! empty( $arguments['mla_debug'] ) ) ? trim( strtolower( $arguments['mla_debug'] ) ) : false;
		if ( self::$mla_debug ) {
			if ( 'true' == self::$mla_debug ) {
				MLACore::mla_debug_mode( 'buffer' );
			} elseif ( 'log' == self::$mla_debug ) {
				MLACore::mla_debug_mode( 'log' );
			} else {
				self::$mla_debug = false;
			}
		}

		if ( self::$mla_debug ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug REQUEST', 'media-library-assistant' ) . '</strong> = ' . var_export( $_REQUEST, true ) );

			if ( ! empty( self::$attributes_errors ) ) {
				if ( 'log' == self::$mla_debug ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug attributes_errors', 'media-library-assistant' ) . '</strong> = ' . var_export( self::$attributes_errors['raw'], true ) );
				} else {
				MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug attributes_errors', 'media-library-assistant' ) . '</strong> = ' . var_export( self::$attributes_errors['escaped'], true ) );
				}

				self::$attributes_errors = array();
			}

			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug attributes', 'media-library-assistant' ) . '</strong> = ' . var_export( $attr, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug arguments', 'media-library-assistant' ) . '</strong> = ' . var_export( $arguments, true ) );
		}

		// Determine output type
		$output_parameters = array_map( 'strtolower', array_map( 'trim', explode( ',', $arguments['mla_output'] ) ) );
		if ( ! in_array( $output_parameters[0], array( 'gallery', 'next_link', 'current_link', 'previous_link', 'next_page', 'previous_page', 'paginate_links' ) ) ) {
			$output_parameters[0] = 'gallery';
		}

		$is_gallery = 'gallery' == $output_parameters[0];
		$is_pagination = in_array( $output_parameters[0], array( 'previous_page', 'next_page', 'paginate_links' ) ); 

		if ( $is_pagination && ( NULL !== $arguments['mla_paginate_rows'] ) ) {
			$attachments['found_rows'] = absint( $arguments['mla_paginate_rows'] );
		} else {
			// Look for negative phrases in keyword search
			if ( ! empty( $attr['s'] ) ) {
				$term_delimiter = /* ! empty( $attr['mla_term_delimiter'] ) ? $attr['mla_term_delimiter'] : */ ',';
				$negative_delimiter = ! empty( $attr['mla_negative_delimiter'] ) ? $attr['mla_negative_delimiter'] : '/';
				$search_phrases = MLAQuery::mla_divide_search_string( $attr['s'], $term_delimiter, $negative_delimiter );

				if ( ! empty( $search_phrases['negative'] ) ) {
					$negative_arguments = $attr;
					unset( $negative_arguments[ $mla_page_parameter ] );
					unset( $negative_arguments['nopaging'] );
					unset( $negative_arguments['offset'] );
					unset( $negative_arguments['paged'] );
					$negative_arguments['orderby'] = 'none';
					$save_excludes = explode( ',', ! empty( $negative_arguments['exclude'] ) ? $negative_arguments['exclude'] : '' );
					$negative_arguments['exclude'] = '';

					$negative_arguments['s'] = $search_phrases['negative'];
					$negative_arguments['fields'] = 'ids';
					$excluded_items = self::mla_get_shortcode_attachments( $post->ID, $negative_arguments, false );

					$attr['s'] = $search_phrases['positive'];
					$attr['exclude'] = implode( ',', array_merge( $save_excludes, $excluded_items ) );
				}
			}

			// Look for negative phrases in taxonomy term keyword search
			if ( ! empty( $attr['mla_terms_phrases'] ) ) {
				$term_delimiter = ! empty( $attr['mla_term_delimiter'] ) ? $attr['mla_term_delimiter'] : ',';
				$negative_delimiter = ! empty( $attr['mla_negative_delimiter'] ) ? $attr['mla_negative_delimiter'] : '/';
				$search_phrases = MLAQuery::mla_divide_search_string( $attr['mla_terms_phrases'], $term_delimiter, $negative_delimiter );

				if ( ! empty( $search_phrases['negative'] ) ) {
					$negative_arguments = $attr;
					unset( $negative_arguments[ $mla_page_parameter ] );
					unset( $negative_arguments['nopaging'] );
					unset( $negative_arguments['offset'] );
					unset( $negative_arguments['paged'] );
					$negative_arguments['orderby'] = 'none';
					$save_excludes = explode( ',', ! empty( $negative_arguments['exclude'] ) ? $negative_arguments['exclude'] : '' );
					$negative_arguments['exclude'] = '';

					$negative_arguments['mla_terms_phrases'] = $search_phrases['negative'];
					$negative_arguments['fields'] = 'ids';
					$excluded_items = self::mla_get_shortcode_attachments( $post->ID, $negative_arguments, false );

					$attr['mla_terms_phrases'] = $search_phrases['positive'];
					$attr['exclude'] = implode( ',', array_merge( $save_excludes, $excluded_items ) );
				}
			}

			$attachments = self::mla_get_shortcode_attachments( $post->ID, $attr, true );
		}

		if ( is_string( $attachments ) ) {
			return $attachments;
		}

		$current_rows = count( $attachments );

		if ( isset( $attachments['max_num_pages'] ) ) {
			$max_num_pages = $attachments['max_num_pages'];
			unset( $attachments['max_num_pages'] );
			$current_rows--;
		} else {
			$max_num_pages = 1;
		}

		if ( isset( $attachments['found_rows'] ) ) {
			$found_rows = $attachments['found_rows'];
			unset( $attachments['found_rows'] );
			$current_rows--;
		} else {
			$found_rows = $current_rows;
		}

		if ( ( $is_gallery && empty($attachments) ) || ( $is_pagination && empty( $found_rows ) ) ) {
			if ( self::$mla_debug ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug empty gallery', 'media-library-assistant' ) . '</strong>, query = ' . var_export( $attr, true ) );
				$output = MLACore::mla_debug_flush();
			} else {
				$output =  '';
			}

			if ( ! empty( $arguments['mla_nolink_text'] ) ) {
				$attr_value = str_replace( '{+', '[+', str_replace( '+}', '+]', $arguments['mla_nolink_text'] ) );
				$replacement_values = MLAData::mla_expand_field_level_parameters( $attr_value, $attr, $page_values );
				$output .= MLAData::mla_parse_template( $attr_value, $replacement_values );
			}

			return $output;
		} // empty $attachments

		// Pass size argument to _get_attachment_image_src() and replace special values
		$size = strtolower( $arguments['size'] );
		self::$size_parameter = $size;

		if ( ( 'icon_only' === $size ) || ( 'icon_feature' === $size ) ) {
			$size = 'icon';
		}

		$size_class = $size;

		// Look for Photonic-enhanced gallery; use the [gallery] shortcode if found
		global $photonic;

		$is_photonic = false;
		if ( is_object( $photonic ) && ! empty( $arguments['style'] ) && empty( $arguments['mla_alt_shortcode'] ) ) {
			if ( 'default' != strtolower( $arguments['type'] ) )  {
				return '<p>' . __( '<strong>Photonic-enhanced [mla_gallery]</strong> type must be <strong>default</strong>, query = ', 'media-library-assistant' ) . var_export( $attr, true ) . '</p>';
			}

			if ( isset( $arguments['pause'] ) && ( 'false' == $arguments['pause'] ) ) {
				$arguments['pause'] = NULL;
			}

			$arguments['mla_alt_shortcode'] = 'gallery';
			$is_photonic = true;
		}

		// Look for user-specified alternate gallery shortcode
		$processing_alt_ids_value = false;
		if ( is_string( $arguments['mla_alt_shortcode'] ) ) {
			// Replace data-selection parameters with the "ids" list
			$blacklist = array_merge( self::$mla_get_shortcode_attachments_parameters, self::$mla_get_shortcode_dynamic_attachments_parameters );

			// Other MLA shortcodes use some of the same parameters, e.g., mla_link_href, so let them thru
			if ( !in_array( $arguments['mla_alt_shortcode'], array( 'mla_tag_cloud', 'mla_term_list' ) ) ) {
				$blacklist = array_merge( $mla_arguments, $blacklist );
			}

			$blacklist = apply_filters( 'mla_gallery_alt_shortcode_blacklist', $blacklist );
			$alt_attr = apply_filters( 'mla_gallery_alt_shortcode_attributes', $attr );

			$mla_alt_shortcode_args = '';
			foreach ( $alt_attr as $key => $value ) {
				if ( array_key_exists( $key, $blacklist ) ) {
					continue;
				}

				$slashed = addcslashes( $value, chr(0).chr(7).chr(8)."\f\n\r\t\v\"\\\$" );
				if ( ( false !== strpos( $value, ' ' ) ) || ( false !== strpos( $value, '\'' ) ) || ( $slashed != $value ) ) {
					$value = '"' . $slashed . '"';
				}

				$mla_alt_shortcode_args .= empty( $mla_alt_shortcode_args ) ? $key . '=' . $value : ' ' . $key . '=' . $value;
			} // foreach $attr

			// Restore original delimiters
			$mla_alt_shortcode_args = str_replace( '[+', '{+', str_replace( '+]', '+}', $mla_alt_shortcode_args ) );

			/*
			 * If an alternate value has been specified we must delay alt shortcode execution
			 * and accumulate $mla_alt_shortcode_ids in the template Item section.
			 */
			$mla_alt_ids_value = is_null( $arguments['mla_alt_ids_value'] ) ? NULL : str_replace( '{+', '[+', str_replace( '+}', '+]', $arguments['mla_alt_ids_value'] ) );
			$mla_alt_shortcode_ids = array();
			$mla_alt_ids_template = is_null( $arguments['mla_alt_ids_template'] ) ? NULL : str_replace( '{+', '[+', str_replace( '+}', '+]', $arguments['mla_alt_ids_template'] ) );

			if ( is_null( $mla_alt_ids_value ) ) {
				$mla_alt_shortcode_ids = apply_filters_ref_array( 'mla_gallery_alt_shortcode_ids', array( $mla_alt_shortcode_ids, $arguments['mla_alt_ids_name'], &$attachments ) );
				if ( is_array( $mla_alt_shortcode_ids ) ) {
					if ( 0 == count( $mla_alt_shortcode_ids ) ) {
						foreach ( $attachments as $value ) {
							$mla_alt_shortcode_ids[] = $value->ID;
						} // foreach $attachments
					}

					// Apply the template when mla_alt_ids_template present
					if ( is_string( $mla_alt_ids_template ) ) {
						$page_values['alt_ids'] = implode( ',', $mla_alt_shortcode_ids );
						$replacement_values = MLAData::mla_expand_field_level_parameters( $mla_alt_ids_template, $attr, $page_values );
						$mla_alt_shortcode_ids = $arguments['mla_alt_ids_name'] . '="' . MLAData::mla_parse_template( $mla_alt_ids_template, $replacement_values ) . '"';
						unset( $page_values['alt_ids'] );
					} else {
						$mla_alt_shortcode_ids = $arguments['mla_alt_ids_name'] . '="' . implode( ',', $mla_alt_shortcode_ids ) . '"';
					}
				}

				if ( self::$mla_debug ) {
					$output = MLACore::mla_debug_flush();
				} else {
					$output = '';
				}

				// Execute the alternate gallery shortcode with the new parameters
				$content = apply_filters( 'mla_gallery_final_content', $content );

				if ( $is_photonic  ) {
					add_filter( 'pre_get_posts', 'MLAShortcode_Support::_photonic_pre_get_posts', 10, 4 );
					add_filter( 'wp_get_attachment_image_src', 'MLAShortcode_Support::_get_attachment_image_src', 10, 4 );
				}

				if ( ! empty( $content ) ) {
					$output .= do_shortcode( sprintf( '[%1$s %2$s %3$s]%4$s[/%1$s]', $arguments['mla_alt_shortcode'], $mla_alt_shortcode_ids, $mla_alt_shortcode_args, $content ) );
				} else {
					$output .= do_shortcode( sprintf( '[%1$s %2$s %3$s]', $arguments['mla_alt_shortcode'], $mla_alt_shortcode_ids, $mla_alt_shortcode_args ) );
				}

				if ( $is_photonic  ) {
					remove_filter( 'wp_get_attachment_image_src', 'MLAShortcode_Support::_get_attachment_image_src' );
					remove_filter( 'pre_get_posts', 'MLAShortcode_Support::_photonic_pre_get_posts' );
				}

				do_action( 'mla_gallery_end_alt_shortcode' );
				return $output;
			} /* is_null( $mla_alt_ids_value ) */ else {
				/*
				 * If an alternate value has been specified we must delay alt shortcode execution
				 * and accumulate $mla_alt_shortcode_ids in the template Item section.
				 */
				$processing_alt_ids_value = true;
			}
		} // mla_alt_shortcode

		if ( 'icon' == strtolower( $size ) ) {
			if ( 'checked' == MLACore::mla_get_option( MLACoreOptions::MLA_ENABLE_MLA_ICONS ) ) {
				$size = array( 64, 64 );
			} else {
				$size = array( 60, 60 );
			}
		}

		// Feeds such as RSS, Atom or RDF do not require styled and formatted output
		if ( is_feed() ) {
			$output = "\n";
			foreach ( $attachments as $att_id => $attachment )
				$output .= wp_get_attachment_link( $att_id, $size, true ) . "\n";
			return $output;
		}

		// Check for Imagick thumbnail generation arguments
		$mla_viewer_required = false;
		if ( 'checked' == MLACore::mla_get_option( 'enable_mla_viewer' ) ) {
			if ( ! empty( $arguments['mla_viewer'] ) ) {
				// Split out the required suffix
				$mla_viewer_args = explode( ',', strtolower( $arguments['mla_viewer'] ) ) ;
				$mla_viewer_required = ( 1 < count( $mla_viewer_args ) && 'required' == $mla_viewer_args[1] );

				if ( 'single' == $mla_viewer_args[0] ) {
					$arguments['mla_single_thread'] = true;	
					$arguments['mla_viewer'] = true;
				} elseif ( 'true' == $mla_viewer_args[0] ) {
					$arguments['mla_viewer'] = true;
				} elseif ( 'required' == $mla_viewer_args[0] ) {
					$mla_viewer_required = true;
					$arguments['mla_viewer'] = true;
				} else {
					$arguments['mla_viewer'] = false;
				}
			}
		} else {
			$arguments['mla_viewer'] = false;
		}

		if ( $arguments['mla_viewer'] ) {
			// Test for Ghostscript here so debug messages can be recorded
			$ghostscript_path = MLACore::mla_get_option( 'ghostscript_path' );
			if ( self::mla_ghostscript_present( $ghostscript_path ) ) {
				$arguments['mla_viewer_extensions'] = array_filter( array_map( 'trim', explode( ',', $arguments['mla_viewer_extensions'] ) ) );
			} else {
				$arguments['mla_viewer_extensions'] = array();
			}

			// Convert limit (in MB) to float
			$arguments['mla_viewer_limit'] = abs( 0.0 + $arguments['mla_viewer_limit'] );

			// Fill width and/or height from explicit intermediate size
			if ( ( empty( $attr['mla_viewer_width'] ) && empty( $attr['mla_viewer_height'] ) ) && ! empty( $attr['size'] ) ) {
				$registered_dimensions = self::_registered_dimensions();
				if ( isset( $registered_dimensions[ $attr['size'] ] ) ) {
					$arguments['mla_viewer_width'] = absint( $registered_dimensions[ $attr['size'] ][0] );
					$arguments['mla_viewer_height'] = absint( $registered_dimensions[ $attr['size'] ][1] );

					if ( empty( $attr['mla_viewer_best_fit'] ) ) {
						$arguments['mla_viewer_best_fit'] = 'true';
					}
				}
			}

			$arguments['mla_viewer_width'] = absint( $arguments['mla_viewer_width'] );
			$arguments['mla_viewer_height'] = absint( $arguments['mla_viewer_height'] );
			$arguments['mla_viewer_page'] = absint( $arguments['mla_viewer_page'] );

			if ( isset( $arguments['mla_viewer_best_fit'] ) ) {
				$arguments['mla_viewer_best_fit'] = 'true' == strtolower( $arguments['mla_viewer_best_fit'] );
			}

			$arguments['mla_viewer_resolution'] = absint( $arguments['mla_viewer_resolution'] );
			$arguments['mla_viewer_quality'] = absint( $arguments['mla_viewer_quality'] );
		}

		/*
		 * The default MLA style template includes "margin: 1.5%" to put a bit of
		 * minimum space between the columns. "mla_margin" can be used to change
		 * this. "mla_itemwidth" can be used with "columns=0" to achieve a "responsive"
		 * layout.
		 */
		 
		$columns = absint( $arguments['columns'] );
		$margin_string = strtolower( trim( $arguments['mla_margin'] ) );

		if ( is_numeric( $margin_string ) && ( 0 != $margin_string) ) {
			$margin_string .= '%'; // Legacy values are always in percent
		}

		if ( '%' == substr( $margin_string, -1 ) ) {
			$margin_percent = (float) substr( $margin_string, 0, strlen( $margin_string ) - 1 );
		} else {
			$margin_percent = 0;
		}

		$width_string = strtolower( trim( $arguments['mla_itemwidth'] ) );
		if ( 'none' != $width_string ) {
			switch ( $width_string ) {
				case 'exact':
					$margin_percent = 0;
					// fallthru
				case 'calculate':
					$width_string = $columns > 0 ? (floor(1000/$columns)/10) - ( 2.0 * $margin_percent ) : 100 - ( 2.0 * $margin_percent );
					// fallthru
				default:
					if ( is_numeric( $width_string ) && ( 0 != $width_string) ) {
						$width_string .= '%'; // Legacy values are always in percent
					}
			}
		} // $use_width

		$float = strtolower( $arguments['mla_float'] );
		if ( ! in_array( $float, array( 'left', 'none', 'right' ) ) ) {
			$float = 'none';  // before v2.90: is_rtl() ? 'right' : 'left';
		}

		$style_values = array_merge( $page_values, array(
			'mla_style' => $arguments['mla_style'],
			'mla_markup' => $arguments['mla_markup'],
			'itemtag' => tag_escape( $arguments['itemtag'] ),
			'icontag' => tag_escape( $arguments['icontag'] ),
			'captiontag' => tag_escape( $arguments['captiontag'] ),
			'columns' => $columns,
			'itemwidth' => $width_string,
			'margin' => $margin_string,
			'float' => $float,
			'size_class' => sanitize_html_class( $size_class ),
			'found_rows' => $found_rows,
			'current_rows' => $current_rows,
			'max_num_pages' => $max_num_pages,
		) );

		$style_template = $gallery_style = '';

		if ( 'theme' == strtolower( $style_values['mla_style'] ) ) {
			$use_mla_gallery_style = apply_filters( 'use_default_gallery_style', ! $html5 );
		} else {
			$use_mla_gallery_style = ( 'none' != strtolower( $style_values['mla_style'] ) );
		}

		if ( apply_filters( 'use_mla_gallery_style', $use_mla_gallery_style, $style_values['mla_style'] ) ) {
			$style_template = MLATemplate_support::mla_fetch_custom_template( $style_values['mla_style'], 'gallery', 'style' );
			if ( empty( $style_template ) ) {
				$style_values['mla_style'] = 'default';
				$style_template = MLATemplate_support::mla_fetch_custom_template( 'default', 'gallery', 'style' );
			}

			if ( ! empty ( $style_template ) ) {
				// Look for 'query' and 'request' substitution parameters
				$style_values = MLAData::mla_expand_field_level_parameters( $style_template, $attr, $style_values );

				// Clean up the template to resolve width or margin == 'none'
				if ( 'none' == $margin_string ) {
					$style_values['margin'] = '0';
					$style_template = preg_replace( '/margin:[\s]*\[\+margin\+\][\%]*[\;]*/', '', $style_template );
				}

				if ( 'none' == $width_string ) {
					$style_values['itemwidth'] = 'auto';
					$style_template = preg_replace( '/width:[\s]*\[\+itemwidth\+\][\%]*[\;]*/', '', $style_template );
				}

				$style_values = apply_filters( 'mla_gallery_style_values', $style_values );
				$style_template = apply_filters( 'mla_gallery_style_template', $style_template );
				$gallery_style = MLAData::mla_parse_template( $style_template, $style_values );
				$gallery_style = apply_filters( 'mla_gallery_style_parse', $gallery_style, $style_template, $style_values );

				// Clean up the styles to resolve extra "%" suffixes on width or margin (pre v1.42 values)
				$preg_pattern = array( '/([margin|width]:[^\%]*)\%\%/', '/([margin|width]:.*)auto\%/', '/([margin|width]:.*)inherit\%/' );
				$preg_replacement = array( '${1}%', '${1}auto', '${1}inherit',  );
				$gallery_style = preg_replace( $preg_pattern, $preg_replacement, $gallery_style );
			} // !empty template
		} // use_mla_gallery_style

		$markup_values = $style_values;

		$open_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'gallery', 'markup', 'open' );
		if ( empty( $open_template ) ) {
			$open_template = '';
		}

		// Emulate [gallery] handling of row open markup for the default template only
		if ( $html5 && ( 'default' == $markup_values['mla_markup'] ) ) {
			$row_open_template = '';
		} else{
			$row_open_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'gallery', 'markup', 'row-open' );

			if ( empty( $row_open_template ) ) {
				$row_open_template = '';
			}
		}

		$item_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'gallery', 'markup', 'item' );
		if ( empty( $item_template ) ) {
			$item_template = '';
		}

		// Emulate [gallery] handling of row close markup for the default template only
		if ( $html5 && ( 'default' == $markup_values['mla_markup'] ) ) {
			$row_close_template = '';
		} else{
			$row_close_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'gallery', 'markup', 'row-close' );

			if ( empty( $row_close_template ) ) {
				$row_close_template = '';
			}
		}

		$close_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'gallery', 'markup', 'close' );
		if ( empty( $close_template ) ) {
			$close_template = '';
		}

		// Look for gallery-level markup substitution parameters
		$new_text = $open_template . $row_open_template . $row_close_template . $close_template;
		$markup_values = MLAData::mla_expand_field_level_parameters( $new_text, $attr, $markup_values );

		if ( self::$mla_debug ) {
			$output = MLACore::mla_debug_flush();
		} else {
			$output = '';
		}

		// These $markup_values are used for both pagination and gallery output
		$markup_values = apply_filters( 'mla_gallery_open_values', $markup_values );

		if ( $is_gallery ) {
			$open_template = apply_filters( 'mla_gallery_open_template', $open_template );
			if ( empty( $open_template ) ) {
				$gallery_open = '';
			} else {
				$gallery_open = MLAData::mla_parse_template( $open_template, $markup_values );
			}

			$gallery_open = apply_filters( 'mla_gallery_open_parse', $gallery_open, $open_template, $markup_values );
			if ( ! $processing_alt_ids_value ) {
				$output .= apply_filters( 'mla_gallery_style', $gallery_style . $gallery_open, $style_values, $markup_values, $style_template, $open_template );
			}
		} else {
			// Handle 'previous_page', 'next_page', and 'paginate_links'
			$pagination_result = self::_process_pagination_output_types( $output_parameters, $markup_values, $arguments, $attr, $found_rows, $output );
			if ( false !== $pagination_result ) {
				return $pagination_result;
			}
		}

		/*
		 * For "previous_link", "current_link" and "next_link",
		 * discard all of the $attachments except the appropriate choice
		 */
		if ( ! $is_gallery ) {
			$link_type = $output_parameters[0];

			if ( ! in_array( $link_type, array ( 'previous_link', 'current_link', 'next_link' ) ) ) {
				return ''; // unknown output type
			}

			$is_wrap = isset( $output_parameters[1] ) && 'wrap' == $output_parameters[1];
			$current_id = empty( $arguments['id'] ) ? $markup_values['id'] : $arguments['id'];

			$pagination_index = 1;
			foreach ( $attachments as $id => $attachment ) {
				if ( $attachment->ID == $current_id ) {
					break;
				}

				$pagination_index++;
			}

			$target = NULL;
			if ( isset( $id ) ) {
				switch ( $link_type ) {
					case 'previous_link':
						$target_id = $id - 1;
						break;
					case 'next_link':
						$target_id = $id + 1;
						break;
					case 'current_link':
					default:
						$target_id = $id;
				} // link_type

				if ( isset( $attachments[ $target_id ] ) ) {
					$target = $attachments[ $target_id ];
				} elseif ( $is_wrap ) {
					switch ( $link_type ) {
						case 'previous_link':
							$target = array_pop( $attachments );
							break;
						case 'next_link':
							$target = array_shift( $attachments );
					} // link_type
				} // is_wrap
			} // isset id

			if ( isset( $target ) ) {
				$attachments = array( $target );			
			} elseif ( ! empty( $arguments['mla_nolink_text'] ) ) {
				return self::_process_shortcode_parameter( $arguments['mla_nolink_text'], $markup_values ) . '</a>';
			} else {
				return '';
			}
		} else { // ! is_gallery
			$link_type= '';
		}

		$column_index = 0;
		foreach ( $attachments as $id => $attachment ) {
			$item_values = apply_filters( 'mla_gallery_item_initial_values', $markup_values, $attachment );

			// fill in item-specific elements
			$item_values['index'] = (string) $is_gallery ? 1 + $column_index : $pagination_index;
			$item_values['last_in_row'] = '';

			$item_values['excerpt'] = wptexturize( $attachment->post_excerpt );
			$item_values['attachment_ID'] = $attachment->ID;
			$item_values['mime_type'] = $attachment->post_mime_type;
			$item_values['menu_order'] = $attachment->menu_order;
			$item_values['date'] = $attachment->post_date;
			$item_values['modified'] = $attachment->post_modified;
			$item_values['parent'] = $attachment->post_parent;
			$item_values['parent_name'] = '';
			$item_values['parent_type'] = '';
			$item_values['parent_title'] = '(' . __( 'Unattached', 'media-library-assistant' ) . ')';
			$item_values['parent_date'] = '';
			$item_values['parent_permalink'] = '';
			$item_values['title'] = wptexturize( $attachment->post_title );
			$item_values['slug'] = $attachment->post_name;
			$item_values['width'] = '';
			$item_values['height'] = '';
			$item_values['orientation'] = '';
			$item_values['image_meta'] = '';
			$item_values['image_alt'] = '';
			$item_values['base_file'] = '';
			$item_values['path'] = '';
			$item_values['file'] = '';
			$item_values['description'] = wptexturize( $attachment->post_content );
			$item_values['file_url'] = wp_get_attachment_url( $attachment->ID );
			$item_values['author_id'] = $attachment->post_author;
			$item_values['author'] = '';
			$item_values['caption'] = '';
			$item_values['captiontag_content'] = '';

			$user = get_user_by( 'id', $attachment->post_author );
			if ( isset( $user->data->display_name ) ) {
				$item_values['author'] = wptexturize( $user->data->display_name );
			} else {
				$item_values['author'] = __( 'unknown', 'media-library-assistant' );
			}

			$post_meta = MLAQuery::mla_fetch_attachment_metadata( $attachment->ID );
			$base_file = isset( $post_meta['mla_wp_attached_file'] ) ? $post_meta['mla_wp_attached_file'] : '';
			$sizes = isset( $post_meta['mla_wp_attachment_metadata']['sizes'] ) ? $post_meta['mla_wp_attachment_metadata']['sizes'] : array();

			if ( ! empty( $post_meta['mla_wp_attachment_metadata']['width'] ) ) {
				$item_values['width'] = $post_meta['mla_wp_attachment_metadata']['width'];
				$width = absint( $item_values['width'] );
			} else {
				$width = 0;
			}

			if ( ! empty( $post_meta['mla_wp_attachment_metadata']['height'] ) ) {
				$item_values['height'] = $post_meta['mla_wp_attachment_metadata']['height'];
				$height = absint( $item_values['height'] );
			} else {
				$height = 0;
			}

			if ( $width && $height ) {
				$item_values['orientation'] = ( $height > $width ) ? 'portrait' : 'landscape';
			}

			if ( ! empty( $post_meta['mla_wp_attachment_metadata']['image_meta'] ) ) {
				$item_values['image_meta'] = var_export( $post_meta['mla_wp_attachment_metadata']['image_meta'], true );
			}

			if ( ! empty( $post_meta['mla_wp_attachment_image_alt'] ) ) {
				if ( is_array( $post_meta['mla_wp_attachment_image_alt'] ) ) {
					$item_values['image_alt'] = wptexturize( $post_meta['mla_wp_attachment_image_alt'][0] );
				} else {
					$item_values['image_alt'] = wptexturize( $post_meta['mla_wp_attachment_image_alt'] );
				}
			}

			if ( ! empty( $base_file ) ) {
				$last_slash = strrpos( $base_file, '/' );
				if ( false === $last_slash ) {
					$file_name = $base_file;
					$item_values['base_file'] = $base_file;
					$item_values['file'] = $base_file;
				} else {
					$file_name = substr( $base_file, $last_slash + 1 );
					$item_values['base_file'] = $base_file;;
					$item_values['path'] = substr( $base_file, 0, $last_slash + 1 );
					$item_values['file'] = $file_name;
				}
			} else {
				$file_name = '';
			}

			if ( 0 < $attachment->post_parent ) {
				$parent_info = MLAQuery::mla_fetch_attachment_parent_data( $attachment->post_parent );
				if ( isset( $parent_info['parent_name'] ) ) {
					$item_values['parent_name'] = $parent_info['parent_name'];
				}

				if ( isset( $parent_info['parent_type'] ) ) {
					$item_values['parent_type'] = wptexturize( $parent_info['parent_type'] );
				}

				if ( isset( $parent_info['parent_title'] ) ) {
					$item_values['parent_title'] = wptexturize( $parent_info['parent_title'] );
				}

				if ( isset( $parent_info['parent_date'] ) ) {
					$item_values['parent_date'] = wptexturize( $parent_info['parent_date'] );
				}

				$permalink = get_permalink( $attachment->post_parent );
				if ( false !== $permalink ) {
					$item_values['parent_permalink'] = $permalink;
				}
			} // has parent

			// Add attachment-specific field-level substitution parameters
			$new_text = isset( $item_template ) ? $item_template : '';
			foreach( $mla_item_specific_arguments as $index => $value ) {
				$new_text .= str_replace( '{+', '[+', str_replace( '+}', '+]', $arguments[ $index ] ) );
			}
			$item_values = MLAData::mla_expand_field_level_parameters( $new_text, $attr, $item_values, $attachment->ID );

			if ( $item_values['captiontag'] ) {
				$item_values['caption'] = wptexturize( $attachment->post_excerpt );
				if ( ! empty( $arguments['mla_caption'] ) ) {
					$item_values['caption'] = wptexturize( self::_process_shortcode_parameter( $arguments['mla_caption'], $item_values ) );
				}
			} else {
				$item_values['caption'] = '';
			}

			if ( ! empty( $arguments['mla_link_text'] ) ) {
				$link_text = self::_process_shortcode_parameter( $arguments['mla_link_text'], $item_values );
			} else {
				$link_text = false;
			}

			/*
			 * As of WP 3.7, this function returns "<a href='$url'>$link_text</a>", where
			 * $link_text can be an image thumbnail or a text link. The "title=" attribute
			 * was dropped. The function is defined in /wp-includes/post-template.php.
			 *
			 * As of WP 4.1, this function has an additional optional parameter, an "Array or
			 * string of attributes", used in the [gallery] shortcode to tie the link to a
			 * caption with 'aria-describedby'. The caption has a matching 'id' attribute
			 * "$selector-#id". See below for the MLA equivalent processing.
			 */
			if ( 'attachment' == $attachment->post_type ) {
				// Avoid native PDF thumbnails, if specified
				if ( $mla_viewer_required && in_array( $attachment->post_mime_type, array( 'application/pdf' ) ) ) {
					$item_values['pagelink'] = sprintf( '<a href=\'%1$s\'>%2$s</a>', get_permalink( $attachment->ID ), $attachment->post_title );
					$item_values['filelink'] = sprintf( '<a href=\'%1$s\'>%2$s</a>', $attachment->guid, $attachment->post_title );
				} else {
					add_filter( 'wp_get_attachment_image_src', 'MLAShortcode_Support::_get_attachment_image_src', 10, 4 );

					// The fourth argument, "show icon" is always false because we handle it in _get_attachment_image_src()
					$item_values['pagelink'] = wp_get_attachment_link($attachment->ID, $size, true, false, $link_text);
					$item_values['filelink'] = wp_get_attachment_link($attachment->ID, $size, false, false, $link_text);

					remove_filter( 'wp_get_attachment_image_src', 'MLAShortcode_Support::_get_attachment_image_src' );
				}
			} else {
				$item_values['pagelink'] = sprintf( '<a href=\'%1$s\'>%2$s</a>', $attachment->guid, $attachment->post_title );
				$item_values['filelink'] = sprintf( '<a href=\'%1$s\'>%2$s</a>', get_permalink( $attachment->ID ), $attachment->post_title );
			}

			if ( in_array( $attachment->post_mime_type, array( 'image/svg+xml' ) ) ) {
				$registered_dimensions = self::_registered_dimensions();
				if ( isset( $registered_dimensions[ $size_class ] ) ) {
					$dimensions = $registered_dimensions[ $size_class ];
				} else {
					$dimensions = $registered_dimensions['thumbnail'];
				}

				$thumb = preg_replace( '/width=\"[^\"]*\"/', sprintf( 'width="%1$d"', $dimensions[1] ), $item_values['pagelink'] );
				$item_values['pagelink'] = preg_replace( '/height=\"[^\"]*\"/', sprintf( 'height="%1$d"', $dimensions[0] ), $thumb );
				$thumb = preg_replace( '/width=\"[^\"]*\"/', sprintf( 'width="%1$d"', $dimensions[1] ), $item_values['filelink'] );
				$item_values['filelink'] = preg_replace( '/height=\"[^\"]*\"/', sprintf( 'height="%1$d"', $dimensions[0] ), $thumb );
			} // SVG thumbnail

			/*
			 * Apply the Gallery Display Content parameters.
			 * Note that $link_attributes and $rollover_text
			 * are used in the Google Viewer code below
			 */
			$link_attributes = '';
			if ( ! empty( $arguments['mla_rollover_text'] ) ) {
				$rollover_text = esc_attr( self::_process_shortcode_parameter( $arguments['mla_rollover_text'], $item_values ) );

				/*
				 * The "title=" attribute was removed in WP 3.7+, but look for it anyway.
				 * If it's not there, add the "title=" value to the link attributes.
				 */
				if ( false === strpos( $item_values['pagelink'], ' title=' ) ) {
					$link_attributes .= 'title="' . $rollover_text . '" ';
				}else {
					// Replace single- and double-quote delimited values
					$item_values['pagelink'] = preg_replace('# title=\'([^\']*)\'#', " title='{$rollover_text}'", $item_values['pagelink'] );
					$item_values['pagelink'] = preg_replace('# title=\"([^\"]*)\"#', " title=\"{$rollover_text}\"", $item_values['pagelink'] );
					$item_values['filelink'] = preg_replace('# title=\'([^\']*)\'#', " title='{$rollover_text}'", $item_values['filelink'] );
					$item_values['filelink'] = preg_replace('# title=\"([^\"]*)\"#', " title=\"{$rollover_text}\"", $item_values['filelink'] );
				}
			} else {
				$rollover_text = esc_attr( $item_values['title'] );
			}

			if ( ! empty( $arguments['mla_target'] ) ) {
				$link_attributes .= 'target="' . $arguments['mla_target'] . '" ';
			}

			if ( ! empty( $arguments['mla_link_attributes'] ) ) {
				$link_attributes .= self::_process_shortcode_parameter( $arguments['mla_link_attributes'], $item_values ) . ' ';
			}

			if ( ! empty( $arguments['mla_link_class'] ) ) {
				$link_attributes .= 'class="' . self::_process_shortcode_parameter( $arguments['mla_link_class'], $item_values ) . '" ';
			}

			if ( ! empty( $link_attributes ) ) {
				$item_values['pagelink'] = str_replace( '<a href=', '<a ' . $link_attributes . 'href=', $item_values['pagelink'] );
				$item_values['filelink'] = str_replace( '<a href=', '<a ' . $link_attributes . 'href=', $item_values['filelink'] );
			}

			/*
			 * Process the <img> tag, if present
			 * Note that $image_attributes, $image_class and $image_alt
			 * are used in the Google Viewer code below
			 */
			if ( ! empty( $arguments['mla_image_attributes'] ) ) {
				$image_attributes = self::_process_shortcode_parameter( $arguments['mla_image_attributes'], $item_values ) . ' ';
			} else {
				$image_attributes = '';
			}

			/*
			 * WordPress 4.1 ties the <img> tag to the caption with 'aria-describedby'
			 * has a matching 'id' attribute "$selector-#id".
			 */
			if ( trim( $item_values['caption'] ) && ( false === strpos( $image_attributes, 'aria-describedby=' ) ) && ( 'default' == $item_values['mla_markup'] ) ) {
				$image_attributes .= 'aria-describedby="' . $item_values['selector'] . '-' . $item_values['attachment_ID'] . '" ';
			}

			if ( ! empty( $arguments['mla_image_class'] ) ) {
				$image_class = esc_attr( self::_process_shortcode_parameter( $arguments['mla_image_class'], $item_values ) );
			} else {
				$image_class = '';
			}

			if ( ! empty( $arguments['mla_image_alt'] ) ) {
				$image_alt = esc_attr( self::_process_shortcode_parameter( $arguments['mla_image_alt'], $item_values ) );
			} else {
				$image_alt = '';
			}

			/*
			 * Look for alt= and class= attributes in $image_attributes. If found,
			 * they override and completely replace the corresponding values.
			 */
			$class_replace = false;
			if ( ! empty( $image_attributes ) ) {
				$match_count = preg_match( '#alt=(([\'\"])([^\']+?)\2)#', $image_attributes, $matches, PREG_OFFSET_CAPTURE );
				if ( 1 === $match_count ) {
					$image_alt = $matches[3][0];
					$image_attributes = substr_replace( $image_attributes, '', $matches[0][1], strlen( $matches[0][0] ) );
				}

				$match_count = preg_match( '#class=(([\'\"])([^\']+?)\2)#', $image_attributes, $matches, PREG_OFFSET_CAPTURE );
				if ( 1 === $match_count ) {
					$class_replace = true;
					$image_class = $matches[3][0];
					$image_attributes = substr_replace( $image_attributes, '', $matches[0][1], strlen( $matches[0][0] ) );
				}

				$image_attributes = trim( $image_attributes );
				if ( ! empty( $image_attributes ) ) {
					$image_attributes .= ' ';
				}
			}

			if ( false !== strpos( $item_values['pagelink'], '<img ' ) ) {
				if ( ! empty( $image_attributes ) ) {
					$item_values['pagelink'] = str_replace( '<img ', '<img ' . $image_attributes, $item_values['pagelink'] );
					$item_values['filelink'] = str_replace( '<img ', '<img ' . $image_attributes, $item_values['filelink'] );
				}

				// Extract existing class values and add to them
				if ( ! empty( $image_class ) ) {
					$match_count = preg_match_all( '# class=\"([^\"]+)\" #', $item_values['pagelink'], $matches, PREG_OFFSET_CAPTURE );
					if ( ! ( $class_replace || ( $match_count == false ) || ( $match_count == 0 ) ) ) {
						$class = $matches[1][0][0] . ' ' . $image_class;
					} else {
						$class = $image_class;
					}

					$item_values['pagelink'] = preg_replace('# class=\"([^\"]*)\"#', " class=\"{$class}\"", $item_values['pagelink'] );
					$item_values['filelink'] = preg_replace('# class=\"([^\"]*)\"#', " class=\"{$class}\"", $item_values['filelink'] );
				}

				if ( ! empty( $image_alt ) ) {
					$item_values['pagelink'] = preg_replace('# alt=\"([^\"]*)\"#', " alt=\"{$image_alt}\"", $item_values['pagelink'] );
					$item_values['filelink'] = preg_replace('# alt=\"([^\"]*)\"#', " alt=\"{$image_alt}\"", $item_values['filelink'] );
				}
			} // process <img> tag

			// Create download and named transfer links with all Content Parameters
			$match_count = preg_match( '#href=\'([^\']+)\'#', $item_values['filelink'], $matches, PREG_OFFSET_CAPTURE );
			if ( ! ( ( $match_count == false ) || ( $match_count == 0 ) ) ) {
				/*/ Forced download link - NO LONGER ALLOWED, SEE BELOW
				$args = array(
					'mla_download_file' => urlencode( $item_values['base_dir'] . '/' . $item_values['base_file'] ),
					'mla_download_type' => $item_values['mime_type']
				);

				if ( 'log' == $arguments['mla_debug'] ) {
					$args['mla_debug'] = 'log';
				}

				$item_values['downloadlink_url'] = add_query_arg( $args, MLA_PLUGIN_URL . 'includes/mla-file-downloader.php' );
				$item_values['downloadlink'] = preg_replace( '"' . $matches[0][0] . '"', sprintf( 'href=\'%1$s\'', $item_values['downloadlink_url'] ), $item_values['filelink'] ); // */

				// AJAX-based Named Transfer link
				$args = array(
					'action' => 'mla_named_transfer',
					'mla_item' => $attachment->post_name,
					'mla_disposition' => ( 'download' === $arguments['link'] ) ? 'attachment' : 'inline',
				);

				if ( 'log' == $arguments['mla_debug'] ) {
					$args['mla_debug'] = 'log';
				}

				$item_values['transferlink_url'] = add_query_arg( $args, admin_url( 'admin-ajax.php' ) );
				$item_values['transferlink'] = preg_replace( '"' . $matches[0][0] . '"', sprintf( 'href=\'%1$s\'', $item_values['transferlink_url'] ), $item_values['filelink'] );

				// AJAX-based Named Transfer link for forced downloads
				$args = array(
					'action' => 'mla_named_transfer',
					'mla_item' => $attachment->post_name,
					'mla_disposition' => 'attachment',
				);

				if ( 'log' == $arguments['mla_debug'] ) {
					$args['mla_debug'] = 'log';
				}

				$item_values['downloadlink_url'] = add_query_arg( $args, admin_url( 'admin-ajax.php' ) );
				$item_values['downloadlink'] = preg_replace( '"' . $matches[0][0] . '"', sprintf( 'href=\'%1$s\'', $item_values['transferlink_url'] ), $item_values['filelink'] );
			} else {
				$item_values['downloadlink_url'] = $item_values['filelink_url'];
				$item_values['downloadlink'] = $item_values['filelink'];

				$item_values['transferlink_url'] = $item_values['filelink_url'];
				$item_values['transferlink'] = $item_values['filelink'];
			}

			switch ( $arguments['link'] ) {
				case 'permalink':
				case 'post':
					$item_values['link'] = $item_values['pagelink'];
					break;
				case 'file':
				case 'full':
					$item_values['link'] = $item_values['filelink'];
					break;
				case 'download':
					$item_values['link'] = $item_values['downloadlink'];
					break;
				default:
					$item_values['link'] = $item_values['filelink'];

					// Check for link to specific (registered) file size, image types only
					if ( array_key_exists( $arguments['link'], $sizes ) ) {
						if ( 0 === strpos( $attachment->post_mime_type, 'image/' ) ) {
							$target_file = $sizes[ $arguments['link'] ]['file'];
							$item_values['link'] = str_replace( $file_name, $target_file, $item_values['filelink'] );
						}
					}
			} // switch 'link'

			// Replace link with AJAX-based item transfer using post slug
			if ( $arguments['mla_named_transfer'] ) {
				$item_values['link'] = $item_values['transferlink'];
			}

			// Extract target and thumbnail fields
			$match_count = preg_match_all( '#href=\'([^\']+)\'#', $item_values['pagelink'], $matches, PREG_OFFSET_CAPTURE );
 			if ( ! ( ( $match_count == false ) || ( $match_count == 0 ) ) ) {
				$item_values['pagelink_url'] = $matches[1][0][0];
			} else {
				$item_values['pagelink_url'] = '';
			}

			$match_count = preg_match_all( '#href=\'([^\']+)\'#', $item_values['filelink'], $matches, PREG_OFFSET_CAPTURE );
			if ( ! ( ( $match_count == false ) || ( $match_count == 0 ) ) ) {
				$item_values['filelink_url'] = $matches[1][0][0];
			} else {
				$item_values['filelink_url'] = '';
			}

			/*
			 * Override the link value; leave filelink and pagelink unchanged
			 * Note that $link_href is used in the Google Viewer code below
			 */
			if ( ! empty( $arguments['mla_link_href'] ) ) {
				$link_href = self::_process_shortcode_parameter( $arguments['mla_link_href'], $item_values );

				// Replace single- and double-quote delimited values
				$item_values['link'] = preg_replace('# href=\'([^\']*)\'#', " href='{$link_href}'", $item_values['link'] );
				$item_values['link'] = preg_replace('# href=\"([^\"]*)\"#', " href=\"{$link_href}\"", $item_values['link'] );
			} else {
				$link_href = '';
			}

			$match_count = preg_match_all( '#href=\'([^\']+)\'#', $item_values['link'], $matches, PREG_OFFSET_CAPTURE );
			if ( ! ( ( $match_count == false ) || ( $match_count == 0 ) ) ) {
				$item_values['link_url'] = $matches[1][0][0];
			} else {
				$item_values['link_url'] = '';
			}

			$match_count = preg_match_all( '#(\<a [^\>]+\>)(.*)\</a\>#', $item_values['link'], $matches, PREG_OFFSET_CAPTURE );
			if ( ! ( ( $match_count == false ) || ( $match_count == 0 ) ) ) {
				$link_tag = $matches[1][0][0];
				$item_values['thumbnail_content'] = $matches[2][0][0];
			} else {
				$link_tag = '';
				$item_values['thumbnail_content'] = '';
			}

			$match_count = preg_match_all( '# width=\"([^\"]+)\" height=\"([^\"]+)\" src=\"([^\"]+)\" #', $item_values['link'], $matches, PREG_OFFSET_CAPTURE );
			if ( ! ( ( $match_count == false ) || ( $match_count == 0 ) ) ) {
				$item_values['thumbnail_width'] = $matches[1][0][0];
				$item_values['thumbnail_height'] = $matches[2][0][0];
				$item_values['thumbnail_url'] = $matches[3][0][0];
			} else {
				$item_values['thumbnail_width'] = '';
				$item_values['thumbnail_height'] = '';
				$item_values['thumbnail_url'] = '';
				
				/* Replaced by logic in _get_attachment_image_src v2.90
				if ( ( 'none' !== $arguments['size'] ) && ( 'checked' == MLACore::mla_get_option( 'enable_featured_image' ) ) ) {
					// Look for the "Featured Image" as an alternate thumbnail for PDFs, etc.
					$feature = get_the_post_thumbnail( $attachment->ID, $size, array( 'class' => 'attachment-thumbnail' ) );
					$feature = apply_filters( 'mla_gallery_featured_image', $feature, $attachment, $size, $item_values );

					if ( ! empty( $feature ) ) {
						$match_count = preg_match_all( '# width=\"([^\"]+)\" height=\"([^\"]+)\" src=\"([^\"]+)\" #', $feature, $matches, PREG_OFFSET_CAPTURE );
						if ( ! ( ( $match_count == false ) || ( $match_count == 0 ) ) ) {
							$item_values['link'] = $link_tag . $feature . '</a>';
							$item_values['thumbnail_content'] = $feature;
							$item_values['thumbnail_width'] = $matches[1][0][0];
							$item_values['thumbnail_height'] = $matches[2][0][0];
							$item_values['thumbnail_url'] = $matches[3][0][0];
						}
					}
				} // enable_featured_image */
			}

			// Now that we have thumbnail_content we can check for 'span' and 'none'
			if ( 'none' == $arguments['link'] ) {
				$item_values['link'] = $item_values['thumbnail_content'];
			} elseif ( 'span' == $arguments['link'] ) {
				$item_values['link'] = sprintf( '<span %1$s>%2$s</span>', $link_attributes, $item_values['thumbnail_content'] );
			}

			/*
			 * Check for Imagick thumbnail generation, uses above-defined
			 * $link_attributes (includes target), $rollover_text, $link_href (link only),
			 * $image_attributes, $image_class, $image_alt
			 */
			if ( $arguments['mla_viewer'] && empty( $item_values['thumbnail_url'] ) ) {
				// Check for a match on file extension
				$last_dot = strrpos( $item_values['file'], '.' );
				if ( !( false === $last_dot) ) {
					$extension = substr( $item_values['file'], $last_dot + 1 );
					if ( in_array( $extension, $arguments['mla_viewer_extensions'] ) ) {
						// Default to an icon if thumbnail generation is not available
						$icon_url = wp_mime_type_icon( $attachment->ID );
						$upload_dir = wp_upload_dir();
						$args = array(
							'page' => MLACore::ADMIN_PAGE_SLUG,
							'mla_stream_file' => urlencode( $upload_dir['basedir'] . '/' . $item_values['base_file'] ),
						);

						if ( 'log' == $arguments['mla_debug'] ) {
							$args['mla_debug'] = 'log';
						}

						if ( $arguments['mla_single_thread'] ) {
							$args['mla_single_thread'] = 'true';
						}

						if ( $arguments['mla_viewer_width'] ) {
							$args['mla_stream_width'] = $arguments['mla_viewer_width'];
						}

						if ( $arguments['mla_viewer_height'] ) {
							$args['mla_stream_height'] = $arguments['mla_viewer_height'];
						}

						if ( isset( $arguments['mla_viewer_best_fit'] ) ) {
							$args['mla_stream_fit'] = $arguments['mla_viewer_best_fit'] ? '1' : '0';
						}

						/*
						 * Non-standard location, if not empty. Write the value to a file that can be
						 * found by the stand-alone (no WordPress) image stream processor.
						 */
						$ghostscript_path = MLACore::mla_get_option( 'ghostscript_path' );
						if ( ! empty( $ghostscript_path ) ) {
							if ( false !== @file_put_contents( dirname( __FILE__ ) . '/' . 'mla-ghostscript-path.txt', $ghostscript_path ) ) {
								$args['mla_ghostscript_path'] = 'custom';
							}
						}

						if ( self::mla_ghostscript_present( $ghostscript_path ) ) {
							// Optional upper limit (in MB) on file size
							if ( $limit = ( 1024 * 1024 ) * $arguments['mla_viewer_limit'] ) {
								$file_size = 0 + @filesize( $item_values['base_dir'] . '/' . $item_values['base_file'] );
								if ( ( 0 < $file_size ) && ( $file_size > $limit ) ) {
									$file_size = 0;
								}
							} else {
								$file_size = 1;
							}

							// Generate "real" thumbnail
							if ( $file_size ) {
								$frame = ( 0 < $arguments['mla_viewer_page'] ) ? $arguments['mla_viewer_page'] - 1 : 0;
								if ( $frame ) {
									$args['mla_stream_frame'] = $frame;
								}

								if ( $arguments['mla_viewer_resolution'] ) {
									$args['mla_stream_resolution'] = $arguments['mla_viewer_resolution'];
								}

								if ( $arguments['mla_viewer_quality'] ) {
									$args['mla_stream_quality'] = $arguments['mla_viewer_quality'];
								}

								if ( ! empty( $arguments['mla_viewer_type'] ) ) {
									$args['mla_stream_type'] = $arguments['mla_viewer_type'];
								}

								// For efficiency, image streaming is done outside WordPress
								$icon_url = add_query_arg( $args, MLACore::mla_nonce_url( MLA_PLUGIN_URL . 'includes/mla-stream-image.php', MLACore::MLA_ADMIN_NONCE_ACTION, MLACore::MLA_ADMIN_NONCE_NAME ) );
							}
						}

						// <img> tag (thumbnail_text)
						if ( ! empty( $image_class ) ) {
							$image_class = ' class="' . $image_class . '"';
						}

						if ( ! empty( $image_alt ) ) {
							$image_alt = ' alt="' . $image_alt . '"';
						} elseif ( ! empty( $item_values['caption'] ) ) {
							$image_alt = ' alt="' . $item_values['caption'] . '"';
						}

						$item_values['thumbnail_content'] = sprintf( '<img %1$ssrc="%2$s"%3$s%4$s>', $image_attributes, $icon_url, $image_class, $image_alt );

						// Filelink, pagelink and link. The "title=" attribute is in $link_attributes for WP 3.7+.
						if ( false === strpos( $link_attributes, 'title=' ) ) {
							$item_values['pagelink'] = sprintf( '<a %1$shref="%2$s" title="%3$s">%4$s</a>', $link_attributes, $item_values['pagelink_url'], $rollover_text, $item_values['thumbnail_content'] );
							$item_values['filelink'] = sprintf( '<a %1$shref="%2$s" title="%3$s">%4$s</a>', $link_attributes, $item_values['filelink_url'], $rollover_text, $item_values['thumbnail_content'] );
							$item_values['downloadlink'] = sprintf( '<a %1$shref="%2$s" title="%3$s">%4$s</a>', $link_attributes, $item_values['downloadlink_url'], $rollover_text, $item_values['thumbnail_content'] );
						} else {
							$item_values['pagelink'] = sprintf( '<a %1$shref="%2$s">%3$s</a>', $link_attributes, $item_values['pagelink_url'], $item_values['thumbnail_content'] );
							$item_values['filelink'] = sprintf( '<a %1$shref="%2$s">%3$s</a>', $link_attributes, $item_values['filelink_url'], $item_values['thumbnail_content'] );
							$item_values['downloadlink'] = sprintf( '<a %1$shref="%2$s">%3$s</a>', $link_attributes, $item_values['downloadlink_url'], $item_values['thumbnail_content'] );
						}
						if ( ! empty( $link_href ) ) {
							$item_values['link'] = sprintf( '<a %1$shref="%2$s" title="%3$s">%4$s</a>', $link_attributes, $link_href, $rollover_text, $item_values['thumbnail_content'] );
						} elseif ( 'permalink' == $arguments['link'] || 'post' == $arguments['link'] ) {
							$item_values['link'] = $item_values['pagelink'];
						} elseif ( 'file' == $arguments['link'] || 'full' == $arguments['link'] ) {
							$item_values['link'] = $item_values['filelink'];
						} elseif ( 'download' == $arguments['link'] ) {
							$item_values['link'] = $item_values['downloadlink'];
						} elseif ( 'span' == $arguments['link'] ) {
							$item_values['link'] = sprintf( '<a %1$s>%2$s</a>', $link_attributes, $item_values['thumbnail_content'] );
						} else {
							$item_values['link'] = $item_values['thumbnail_content'];
						}
					} // viewer extension
				} // has extension
			} // mla_viewer

			if ( $is_gallery ) {
				// Start of row markup
				if ( $markup_values['columns'] > 0 && $column_index % $markup_values['columns'] == 0 ) {
					$markup_values = apply_filters( 'mla_gallery_row_open_values', $markup_values );
					$row_open_template = apply_filters( 'mla_gallery_row_open_template', $row_open_template );
					$parse_value = MLAData::mla_parse_template( $row_open_template, $markup_values );
					if ( ! $processing_alt_ids_value ) {
						$output .= apply_filters( 'mla_gallery_row_open_parse', $parse_value, $row_open_template, $markup_values );
					}
				}

				// item markup
				$column_index++;
				if ( $item_values['columns'] > 0 && $column_index % $item_values['columns'] == 0 ) {
					$item_values['last_in_row'] = 'last_in_row';
				} else {
					$item_values['last_in_row'] = '';
				}

				// Conditional caption tag to replicate WP 4.1+, now used in the default markup template.
				if ( $item_values['captiontag'] && trim( $item_values['caption'] ) ) {
//					$item_values['captiontag_content'] = '<' . $item_values['captiontag'] . " class='wp-caption-text gallery-caption' id='" . $item_values['selector'] . '-' . $item_values['attachment_ID'] . "'>\n\t\t" . $item_values['caption'] . "\n\t</" . $item_values['captiontag'] . ">\n";
					$item_values['captiontag_content'] = '<' . $item_values['captiontag'] . " class='wp-caption-text gallery-caption' id='" . $item_values['selector'] . '-' . $item_values['attachment_ID'] . "'>\n\t" . $item_values['caption'] . "\n\t</" . $item_values['captiontag'] . ">";
				} else {
					$item_values['captiontag_content'] = '';
				}

				$item_values = apply_filters( 'mla_gallery_item_values', $item_values );

				// Accumulate mla_alt_shortcode_ids when mla_alt_ids_value present
				if ( $processing_alt_ids_value ) {
					$item_values = MLAData::mla_expand_field_level_parameters( $mla_alt_ids_value, $attr, $item_values );
					$mla_alt_shortcode_ids[] = MLAData::mla_parse_template( $mla_alt_ids_value, $item_values );
					continue;
				}

				$item_template = apply_filters( 'mla_gallery_item_template', $item_template );
				$parse_value = MLAData::mla_parse_template( $item_template, $item_values );
				$output .= apply_filters( 'mla_gallery_item_parse', $parse_value, $item_template, $item_values );

				// End of row markup
				if ( $markup_values['columns'] > 0 && $column_index % $markup_values['columns'] == 0 ) {
					$markup_values = apply_filters( 'mla_gallery_row_close_values', $markup_values );
					$row_close_template = apply_filters( 'mla_gallery_row_close_template', $row_close_template );
					$parse_value = MLAData::mla_parse_template( $row_close_template, $markup_values );
					$output .= apply_filters( 'mla_gallery_row_close_parse', $parse_value, $row_close_template, $markup_values );
				}
			} // is_gallery
			elseif ( ! empty( $link_type ) ) {
				return $item_values['link'];
			}
		} // foreach attachment

		// Execute the alternate gallery shortcode with the new parameters
		if ( $processing_alt_ids_value ) {
			// Apply the template when mla_alt_ids_template present
			if ( is_string( $mla_alt_ids_template ) ) {
				$markup_values['alt_ids'] = implode( ',', $mla_alt_shortcode_ids );
				$replacement_values = MLAData::mla_expand_field_level_parameters( $mla_alt_ids_template, $attr, $markup_values );
				$mla_alt_shortcode_ids = $arguments['mla_alt_ids_name'] . '="' . MLAData::mla_parse_template( $mla_alt_ids_template, $replacement_values ) . '"';
				unset( $markup_values['alt_ids'] );
			} else {
				$mla_alt_shortcode_ids = $arguments['mla_alt_ids_name'] . '="' . implode( ',', $mla_alt_shortcode_ids ) . '"';
			}

			$content = apply_filters( 'mla_gallery_final_content', $content );
			if ( ! empty( $content ) ) {
				return $output . do_shortcode( sprintf( '[%1$s %2$s %3$s]%4$s[/%1$s]', $arguments['mla_alt_shortcode'], $mla_alt_shortcode_ids, $mla_alt_shortcode_args, $content ) );
			} else {
				return $output . do_shortcode( sprintf( '[%1$s %2$s %3$s]', $arguments['mla_alt_shortcode'], $mla_alt_shortcode_ids, $mla_alt_shortcode_args ) );
			}
		}

		if ( $is_gallery ) {
			// Close out partial row
			if ( ! ($markup_values['columns'] > 0 && $column_index % $markup_values['columns'] == 0 ) ) {
				$markup_values = apply_filters( 'mla_gallery_row_close_values', $markup_values );
				$row_close_template = apply_filters( 'mla_gallery_row_close_template', $row_close_template );
				$parse_value = MLAData::mla_parse_template( $row_close_template, $markup_values );
				$output .= apply_filters( 'mla_gallery_row_close_parse', $parse_value, $row_close_template, $markup_values );
			}

			$markup_values = apply_filters( 'mla_gallery_close_values', $markup_values );
			$close_template = apply_filters( 'mla_gallery_close_template', $close_template );
			$parse_value = MLAData::mla_parse_template( $close_template, $markup_values );
			$output .= apply_filters( 'mla_gallery_close_parse', $parse_value, $close_template, $markup_values );
		} // is_gallery

		return $output;
	}

	/**
	 * The MLA Tag Cloud support function.
	 *
	 * This is an alternative to the WordPress wp_tag_cloud function, with additional
	 * options to customize the hyperlink behind each term.
	 *
	 * @since 2.20
	 *
	 * @param array $attr Attributes of the shortcode.
	 *
	 * @return string HTML content to display the tag cloud.
	 */
	public static function mla_tag_cloud( $attr ) {
		global $post;

		// Some do_shortcode callers may not have a specific post in mind
		if ( ! is_object( $post ) ) {
			$post = (object) self::$empty_post;
		}

		// $instance supports multiple clouds in one page/post	
		static $instance = 0;
		$instance++;

		// Some values are already known, and can be used in data selection parameters
		$upload_dir = wp_upload_dir();
		$page_values = array(
			'instance' => $instance,
			'selector' => "mla_tag_cloud-{$instance}",
			'site_url' => site_url(),
			'base_url' => $upload_dir['baseurl'],
			'base_dir' => $upload_dir['basedir'],
			'id' => $post->ID,
			'page_ID' => $post->ID,
			'page_author' => $post->post_author,
			'page_date' => $post->post_date,
			'page_content' => $post->post_content,
			'page_title' => $post->post_title,
			'page_excerpt' => $post->post_excerpt,
			'page_status' => $post->post_status,
			'page_name' => $post->post_name,
			'page_modified' => $post->post_modified,
			'page_guid' => $post->guid,
			'page_type' => $post->post_type,
			'page_url' => get_page_link(),
		);

		// These are the default parameters for tag cloud display
		$mla_item_specific_arguments = array(
			'mla_link_attributes' => '',
			'mla_link_class' => '',
			'mla_link_style' => '',
			'mla_link_href' => '',
			'mla_link_text' => '',
			'mla_nolink_text' => '',
			'mla_rollover_text' => '',
			'mla_caption' => ''
		);

		$defaults = array_merge(
			self::$mla_get_terms_parameters,
			array(
			'smallest' => 8,
			'largest' => 22,
			'unit' => 'pt',
			'separator' => "\n",
			'single_text' => '%d item',
			'multiple_text' => '%d items',

			'echo' => false,
			'link' => 'view',
			'current_item' => '',
			'current_item_class' => 'mla_current_item',

			'itemtag' => 'ul',
			'termtag' => 'li',
			'captiontag' => '',
			'columns' => MLACore::mla_get_option('mla_tag_cloud_columns'),

			'mla_output' => 'flat',
			'mla_style' => NULL,
			'mla_markup' => NULL,
			'mla_float' => is_rtl() ? 'right' : 'left',
			'mla_itemwidth' => MLACore::mla_get_option('mla_tag_cloud_itemwidth'),
			'mla_margin' => MLACore::mla_get_option('mla_tag_cloud_margin'),
			'mla_target' => '',
			'mla_debug' => false,

			// Pagination parameters
			'term_id' => NULL,
			'mla_end_size'=> 1,
			'mla_mid_size' => 2,
			'mla_prev_text' => '&laquo; ' . __( 'Previous', 'media-library-assistant' ),
			'mla_next_text' => __( 'Next', 'media-library-assistant' ) . ' &raquo;',
			'mla_page_parameter' => 'mla_cloud_current',
			'mla_cloud_current' => NULL,
			'mla_paginate_total' => NULL,
			'mla_paginate_type' => 'plain'),
			$mla_item_specific_arguments
		);

		// Filter the attributes before $mla_page_parameter and "request:" prefix processing.
		$attr = apply_filters( 'mla_tag_cloud_raw_attributes', $attr );

		/*
		 * The mla_paginate_current parameter can be changed to support
		 * multiple clouds per page.
		 */
		if ( ! isset( $attr['mla_page_parameter'] ) ) {
			$attr['mla_page_parameter'] = $defaults['mla_page_parameter'];
		}

		// The mla_page_parameter can contain page_level parameters like {+page_ID+}
		$attr_value = str_replace( '{+', '[+', str_replace( '+}', '+]', $attr['mla_page_parameter'] ) );
		$mla_page_parameter = MLAData::mla_parse_template( $attr_value, $page_values );
		 
		/*
		 * Special handling of mla_page_parameter to make "MLA pagination" easier.
		 * Look for this parameter in $_REQUEST if it's not present in the shortcode itself.
		 */
		if ( ! isset( $attr[ $mla_page_parameter ] ) ) {
			if ( isset( $_REQUEST[ $mla_page_parameter ] ) ) {
				$attr[ $mla_page_parameter ] = sanitize_text_field( wp_unslash( $_REQUEST[ $mla_page_parameter ] ) );
			}
		}
		 
		// Determine markup template to get default arguments
		$arguments = shortcode_atts( $defaults, $attr );
		if ( $arguments['mla_markup'] ) {
			$template = $arguments['mla_markup'];
			if ( ! MLATemplate_Support::mla_fetch_custom_template( $template, 'tag-cloud', 'markup', '[exists]' ) ) {
				$template = NULL;
			}
		} else {
			$template = NULL;
		}

		if ( empty( $template ) ) {
			$output_parameters = array_map( 'strtolower', array_map( 'trim', explode( ',', $arguments['mla_output'] ) ) );

			if ( !in_array( $output_parameters[0], array( 'flat', 'list', 'ulist', 'olist', 'dlist', 'grid', 'array', 'next_link', 'current_link', 'previous_link', 'next_page', 'previous_page', 'paginate_links' ) ) ) {
				$output_parameters[0] = 'flat';
			}

			if ( 'grid' == $output_parameters[0] ) {
				$template = MLACore::mla_get_option('default_tag_cloud_markup');
			} elseif ( in_array( $output_parameters[0], array( 'list', 'ulist', 'olist', 'dlist' ) ) ) {
				if ( ( 'dlist' == $output_parameters[0] ) || ('list' == $output_parameters[0] && 'dd' == $arguments['captiontag'] ) ) {
					$template = 'tag-cloud-dl';
				} else {
					$template = 'tag-cloud-ul';
				}
			} else {
				$template = NULL;
			}
		}

		// Apply default arguments set in the markup template
		if ( ! empty( $template ) ) {
			$arguments = MLATemplate_Support::mla_fetch_custom_template( $template, 'tag-cloud', 'markup', 'arguments' );
			if ( ! empty( $arguments ) ) {
				$attr = wp_parse_args( $attr, self::mla_validate_attributes( array(), $arguments ) );
			}
		}

		/*
		 * Look for page-level, 'request:' and 'query:' substitution parameters,
		 * which can be added to any input parameter
		 */
		foreach ( $attr as $attr_key => $attr_value ) {
			/*
			 * item-specific Display Content parameters must be evaluated
			 * later, when all of the information is available.
			 */
			if ( array_key_exists( $attr_key, $mla_item_specific_arguments ) ) {
				continue;
			}

			$attr_value = str_replace( '{+', '[+', str_replace( '+}', '+]', $attr_value ) );
			$replacement_values = MLAData::mla_expand_field_level_parameters( $attr_value, $attr, $page_values );
			$attr[ $attr_key ] = MLAData::mla_parse_template( $attr_value, $replacement_values );
		}

		$attr = apply_filters( 'mla_tag_cloud_attributes', $attr );
		$arguments = shortcode_atts( $defaults, $attr );

		/*
		 * $mla_page_parameter, if non-default, doesn't make it through the shortcode_atts
		 * filter, so we handle it separately
		 */
		if ( ! isset( $arguments[ $mla_page_parameter ] ) ) {
			if ( isset( $attr[ $mla_page_parameter ] ) ) {
				$arguments[ $mla_page_parameter ] = $attr[ $mla_page_parameter ];
			} else {
				$arguments[ $mla_page_parameter ] = $defaults['mla_cloud_current'];

			}
		}

		// Process the pagination parameter, if present
		if ( isset( $arguments[ $mla_page_parameter ] ) ) {
			$arguments['offset'] = $arguments['limit'] * ( $arguments[ $mla_page_parameter ] - 1);
		}

		// Clean up the current_item to separate term_id from slug
		if ( ! empty( $arguments['current_item'] ) && ctype_digit( $arguments['current_item'] ) ) {
			$arguments['current_item'] = absint( $arguments['current_item'] );
		}

		$arguments = apply_filters( 'mla_tag_cloud_arguments', $arguments );

		self::$mla_debug = ( ! empty( $arguments['mla_debug'] ) ) ? trim( strtolower( $arguments['mla_debug'] ) ) : false;
		if ( self::$mla_debug ) {
			if ( 'true' == self::$mla_debug ) {
				MLACore::mla_debug_mode( 'buffer' );
			} elseif ( 'log' == self::$mla_debug ) {
				MLACore::mla_debug_mode( 'log' );
			} else {
				self::$mla_debug = false;
			}
		}

		if ( self::$mla_debug ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug REQUEST', 'media-library-assistant' ) . '</strong> = ' . var_export( $_REQUEST, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug attributes', 'media-library-assistant' ) . '</strong> = ' . var_export( $attr, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug arguments', 'media-library-assistant' ) . '</strong> = ' . var_export( $arguments, true ) );
		}

		// Determine templates and output type
		if ( $arguments['mla_style'] && ( 'none' !== $arguments['mla_style'] ) ) {
			if ( !MLATemplate_Support::mla_fetch_custom_template( $arguments['mla_style'], 'tag-cloud', 'style', '[exists]' ) ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>mla_tag_cloud mla_style</strong> "' . $arguments['mla_style'] . '" ' . __( 'not found', 'media-library-assistant' ), MLACore::MLA_DEBUG_CATEGORY_ANY );
				$arguments['mla_style'] = NULL;
			}
		}

		if ( $arguments['mla_markup'] ) {
			if ( !MLATemplate_Support::mla_fetch_custom_template( $arguments['mla_markup'], 'tag-cloud', 'markup', '[exists]' ) ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>mla_tag_cloud mla_markup</strong> "' . $arguments['mla_markup'] . '" ' . __( 'not found', 'media-library-assistant' ), MLACore::MLA_DEBUG_CATEGORY_ANY );
				$arguments['mla_markup'] = NULL;
			}
		}

		$output_parameters = array_map( 'strtolower', array_map( 'trim', explode( ',', $arguments['mla_output'] ) ) );

		if ( !in_array( $output_parameters[0], array( 'flat', 'list', 'ulist', 'olist', 'dlist', 'grid', 'array', 'next_link', 'current_link', 'previous_link', 'next_page', 'previous_page', 'paginate_links' ) ) ) {
			$output_parameters[0] = 'flat';
		}

		if ( $is_grid = 'grid' == $output_parameters[0] ) {
			$default_style = MLACore::mla_get_option('default_tag_cloud_style');
			$default_markup = MLACore::mla_get_option('default_tag_cloud_markup');

			if ( NULL == $arguments['mla_style'] ) {
				$arguments['mla_style'] = $default_style;
			}

			if ( NULL == $arguments['mla_markup'] ) {
				$arguments['mla_markup'] = $default_markup;
			}

			if ( empty( $attr['itemtag'] ) ) {
				$arguments['itemtag'] = 'dl';
			}

			if ( empty( $attr['termtag'] ) ) {
				$arguments['termtag'] = 'dt';
			}

			if ( empty( $attr['captiontag'] ) ) {
				$arguments['captiontag'] = 'dd';
			}
		}

		if ( $is_list = in_array( $output_parameters[0], array( 'list', 'ulist', 'olist', 'dlist' ) ) ) {
			$default_style = 'none';

			if ( 'list' == $output_parameters[0] && 'dd' == $arguments['captiontag'] ) {
				$default_markup = 'tag-cloud-dl';
				$arguments['itemtag'] = 'dl';
				$arguments['termtag'] = 'dt';
			} else {
				$default_markup = 'tag-cloud-ul';
				$arguments['termtag'] = 'li';
				$arguments['captiontag'] = '';

				switch ( $output_parameters[0] ) {
					case 'ulist':
						$arguments['itemtag'] = 'ul';
						break;
					case 'olist':
						$arguments['itemtag'] = 'ol';
						break;
					case 'dlist':
						$default_markup = 'tag-cloud-dl';
						$arguments['itemtag'] = 'dl';
						$arguments['termtag'] = 'dt';
						$arguments['captiontag'] = 'dd';
					break;
					default:
						$arguments['itemtag'] = 'ul';
				}
			}

			if ( NULL == $arguments['mla_style'] ) {
				$arguments['mla_style'] = $default_style;
			}

			if ( NULL == $arguments['mla_markup'] ) {
				$arguments['mla_markup'] = $default_markup;
			}
		}

		$is_pagination = in_array( $output_parameters[0], array( 'previous_link', 'current_link', 'next_link', 'previous_page', 'next_page', 'paginate_links' ) ); 

		// Convert lists to arrays
		if ( is_string( $arguments['taxonomy'] ) ) {
			$arguments['taxonomy'] = explode( ',', $arguments['taxonomy'] );
		}

		if ( is_string( $arguments['post_type'] ) ) {
			$arguments['post_type'] = explode( ',', $arguments['post_type'] );
		}

		if ( is_string( $arguments['post_status'] ) ) {
			$arguments['post_status'] = explode( ',', $arguments['post_status'] );
		}

		$tags = self::mla_get_terms( $arguments );

		// Invalid taxonomy names return WP_Error
		if ( is_wp_error( $tags ) ) {
			$cloud =  '<strong>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . $tags->get_error_message() . '</strong>, ' . $tags->get_error_data( $tags->get_error_code() );

			if ( 'array' == $arguments['mla_output'] ) {
				return array( $cloud );
			}

			if ( empty($arguments['echo']) ) {
				return $cloud;
			}

			echo $cloud; // phpcs:ignore
			return;
		}

		// Fill in the item_specific link properties, calculate cloud parameters
		if ( isset( $tags['found_rows'] ) ) {
			$found_rows = $tags['found_rows'];
			unset( $tags['found_rows'] );
		} else {
			$found_rows = count( $tags );
		}

		if ( 0 == $found_rows ) {
			if ( self::$mla_debug ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug empty cloud', 'media-library-assistant' ) . '</strong>, query = ' . var_export( $arguments, true ) );
				$cloud = MLACore::mla_debug_flush();

				if ( '<p></p>' == $cloud ) {
					$cloud = '';
				}
			} else {
				$cloud = '';
			}

			$cloud .= $arguments['mla_nolink_text'];
			if ( 'array' == $arguments['mla_output'] ) {
				if ( empty( $cloud ) ) {
					return array();
				} else {
					return array( $cloud );
				}
			}

			if ( empty($arguments['echo']) ) {
				return $cloud;
			}

			echo $cloud; // phpcs:ignore
			return;
		}

		if ( self::$mla_debug ) {
			$cloud = MLACore::mla_debug_flush();
		} else {
			$cloud = '';
		}

		$min_count = 0x7FFFFFFF;
		$max_count = 0;
		$min_scaled_count = 0x7FFFFFFF;
		$max_scaled_count = 0;
		foreach ( $tags as $key => $tag ) {
			$tag_count = isset ( $tag->count ) ? $tag->count : 0;
			$tag->scaled_count = apply_filters( 'mla_tag_cloud_scale', round(log10($tag_count + 1) * 100), $attr, $arguments, $tag );

			if ( $tag_count < $min_count ) {
				$min_count = $tag_count;
			}

			if ( $tag_count > $max_count ) {
				$max_count = $tag_count;
			}

			if ( $tag->scaled_count < $min_scaled_count ) {
				$min_scaled_count = $tag->scaled_count;
			}

			if ( $tag->scaled_count > $max_scaled_count ) {
				$max_scaled_count = $tag->scaled_count;
			}

			$link = get_edit_tag_link( $tag->term_id, $tag->taxonomy );
			if ( ! is_wp_error( $link ) ) {
				$tags[ $key ]->edit_link = $link;
				$link = get_term_link( (int) $tag->term_id, $tag->taxonomy );
				$tags[ $key ]->term_link = $link;
			}

			if ( is_wp_error( $link ) ) {
				$cloud =  '<strong>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . $link->get_error_message() . '</strong>, ' . $link->get_error_data( $link->get_error_code() );

				if ( 'array' == $arguments['mla_output'] ) {
					return array( $cloud );
				}

				if ( empty($arguments['echo']) ) {
					return $cloud;
				}

				echo $cloud; // phpcs:ignore
				return;
			}

			if ( 'edit' == $arguments['link'] ) {
				$tags[ $key ]->link = $tags[ $key ]->edit_link;
			} else {
				$tags[ $key ]->link = $tags[ $key ]->term_link;
			}
		} // foreach tag

		/*
		 * The default MLA style template includes "margin: 1.5%" to put a bit of
		 * minimum space between the columns. "mla_margin" can be used to change
		 * this. "mla_itemwidth" can be used with "columns=0" to achieve a
		 * "responsive" layout.
		 */
		 
		$columns = absint( $arguments['columns'] );
		$margin_string = strtolower( trim( $arguments['mla_margin'] ) );

		if ( is_numeric( $margin_string ) && ( 0 != $margin_string) ) {
			$margin_string .= '%'; // Legacy values are always in percent
		}

		if ( '%' == substr( $margin_string, -1 ) ) {
			$margin_percent = (float) substr( $margin_string, 0, strlen( $margin_string ) - 1 );
		} else {
			$margin_percent = 0;
		}

		$width_string = strtolower( trim( $arguments['mla_itemwidth'] ) );
		if ( 'none' != $width_string ) {
			switch ( $width_string ) {
				case 'exact':
					$margin_percent = 0;
					// fallthru
				case 'calculate':
					$width_string = $columns > 0 ? (floor(1000/$columns)/10) - ( 2.0 * $margin_percent ) : 100 - ( 2.0 * $margin_percent );
					// fallthru
				default:
					if ( is_numeric( $width_string ) && ( 0 != $width_string) ) {
						$width_string .= '%'; // Legacy values are always in percent
					}
			}
		} // $use_width

		$float = strtolower( $arguments['mla_float'] );
		if ( ! in_array( $float, array( 'left', 'none', 'right' ) ) ) {
			$float = is_rtl() ? 'right' : 'left';
		}

		// Calculate cloud parameters
		$spread = $max_scaled_count - $min_scaled_count;
		if ( $spread <= 0 ) {
			$spread = 1;
		}

		$font_spread = $arguments['largest'] - $arguments['smallest'];
		if ( $font_spread < 0 ) {
			$font_spread = 1;
		}

		$font_step = $font_spread / $spread;

		$style_values = array_merge( $page_values, array(
			'mla_output' => $arguments['mla_output'],
			'mla_style' => $arguments['mla_style'],
			'mla_markup' => $arguments['mla_markup'],
			'taxonomy' => implode( '-', $arguments['taxonomy'] ),
			'current_item' => $arguments['current_item'],
			'itemtag' => tag_escape( $arguments['itemtag'] ),
			'termtag' => tag_escape( $arguments['termtag'] ),
			'captiontag' => tag_escape( $arguments['captiontag'] ),
			'columns' => $columns,
			'itemwidth' => $width_string,
			'margin' => $margin_string,
			'float' => $float,
			'found_rows' => $found_rows,
			'min_count' => $min_count,
			'max_count' => $max_count,
			'min_scaled_count' => $min_scaled_count,
			'max_scaled_count' => $max_scaled_count,
			'spread' => $spread,
			'smallest' => $arguments['smallest'],
			'largest' => $arguments['largest'],
			'unit' => $arguments['unit'],
			'font_spread' => $font_spread,
			'font_step' => $font_step,
			'separator' => $arguments['separator'],
			'single_text' => $arguments['single_text'],
			'multiple_text' => $arguments['multiple_text'],
			'echo' => $arguments['echo'],
			'link' => $arguments['link']
		) );

		$style_template = $gallery_style = '';
		$use_mla_tag_cloud_style = ( $is_grid || $is_list ) && ( 'none' != strtolower( $style_values['mla_style'] ) );
		if ( apply_filters( 'use_mla_tag_cloud_style', $use_mla_tag_cloud_style, $style_values['mla_style'] ) ) {
			$style_template = MLATemplate_support::mla_fetch_custom_template( $style_values['mla_style'], 'tag-cloud', 'style' );
			if ( empty( $style_template ) ) {
				$style_values['mla_style'] = $default_style;
				$style_template = MLATemplate_support::mla_fetch_custom_template( $default_style, 'tag-cloud', 'style' );
			}

			if ( ! empty ( $style_template ) ) {
				// Look for 'query' and 'request' substitution parameters
				$style_values = MLAData::mla_expand_field_level_parameters( $style_template, $attr, $style_values );

				// Clean up the template to resolve width or margin == 'none'
				if ( 'none' == $margin_string ) {
					$style_values['margin'] = '0';
					$style_template = preg_replace( '/margin:[\s]*\[\+margin\+\][\%]*[\;]*/', '', $style_template );
				}

				if ( 'none' == $width_string ) {
					$style_values['itemwidth'] = 'auto';
					$style_template = preg_replace( '/width:[\s]*\[\+itemwidth\+\][\%]*[\;]*/', '', $style_template );
				}

				$style_values = apply_filters( 'mla_tag_cloud_style_values', $style_values );
				$style_template = apply_filters( 'mla_tag_cloud_style_template', $style_template );
				$gallery_style = MLAData::mla_parse_template( $style_template, $style_values );
				$gallery_style = apply_filters( 'mla_tag_cloud_style_parse', $gallery_style, $style_template, $style_values );
			} // !empty template
		} // use_mla_tag_cloud_style

		$markup_values = $style_values;

		if ( $is_grid || $is_list ) {
			$open_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'tag-cloud', 'markup', 'open' );
			if ( false === $open_template ) {
				$markup_values['mla_markup'] = $default_markup;
				$open_template = MLATemplate_support::mla_fetch_custom_template( $default_markup, 'tag-cloud', 'markup', 'open' );
			}

			if ( empty( $open_template ) ) {
				$open_template = '';
			}

			if ( $is_grid ) {
				$row_open_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'tag-cloud', 'markup', 'row-open' );
				if ( empty( $row_open_template ) ) {
					$row_open_template = '';
				}
			} else {
				$row_open_template = '';
			}

			$item_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'tag-cloud', 'markup', 'item' );
			if ( empty( $item_template ) ) {
				$item_template = '';
			}

			if ( $is_grid ) {
				$row_close_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'tag-cloud', 'markup', 'row-close' );
				if ( empty( $row_close_template ) ) {
					$row_close_template = '';
					}
			} else {
				$row_close_template = '';
			}

			$close_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'tag-cloud', 'markup', 'close' );
			if ( empty( $close_template ) ) {
				$close_template = '';
			}

			// Look for gallery-level markup substitution parameters
			$new_text = $open_template . $row_open_template . $row_close_template . $close_template;
			$markup_values = MLAData::mla_expand_field_level_parameters( $new_text, $attr, $markup_values );

			$markup_values = apply_filters( 'mla_tag_cloud_open_values', $markup_values );
			$open_template = apply_filters( 'mla_tag_cloud_open_template', $open_template );
			if ( empty( $open_template ) ) {
				$gallery_open = '';
			} else {
				$gallery_open = MLAData::mla_parse_template( $open_template, $markup_values );
			}

			$gallery_open = apply_filters( 'mla_tag_cloud_open_parse', $gallery_open, $open_template, $markup_values );
			$cloud .= $gallery_style . $gallery_open;
		} // is_grid || is_list
		elseif ( $is_pagination ) {
			// Handle 'previous_page', 'next_page', and 'paginate_links'
			if ( isset( $attr['limit'] ) ) {
				$attr['posts_per_page'] = $attr['limit'];
			}

			$pagination_result = self::_process_pagination_output_types( $output_parameters, $markup_values, $arguments, $attr, $found_rows );
			if ( false !== $pagination_result ) {
				return $pagination_result;
			}

			// For "previous_link", "current_link" and "next_link", discard all of the $tags except the appropriate choice
			$link_type = $output_parameters[0];

			if ( ! in_array( $link_type, array ( 'previous_link', 'current_link', 'next_link' ) ) ) {
				return ''; // unknown output type
			}

			$is_wrap = isset( $output_parameters[1] ) && 'wrap' == $output_parameters[1];
			if ( empty( $arguments['term_id'] ) ) {
				$target_id = -2; // won't match anything
			} else {
				$current_id = $arguments['term_id'];

				foreach ( $tags as $id => $tag ) {
					if ( $tag->term_id == $current_id ) {
						break;
					}
				}

				switch ( $link_type ) {
					case 'previous_link':
						$target_id = $id - 1;
						break;
					case 'next_link':
						$target_id = $id + 1;
						break;
					case 'current_link':
					default:
						$target_id = $id;
				} // link_type
			}

			$target = NULL;
			if ( isset( $tags[ $target_id ] ) ) {
				$target = $tags[ $target_id ];
			} elseif ( $is_wrap ) {
				switch ( $link_type ) {
					case 'previous_link':
						$target = array_pop( $tags );
						break;
					case 'next_link':
						$target = array_shift( $tags );
				} // link_type
			} // is_wrap

			if ( isset( $target ) ) {
				$tags = array( $target );
			} elseif ( ! empty( $arguments['mla_nolink_text'] ) ) {
				return self::_process_shortcode_parameter( $arguments['mla_nolink_text'], $markup_values ) . '</a>';
			} else {
				return '';
			}
		} // is_pagination

		// Accumulate links for flat and array output
		$tag_links = array();

		// Find delimiter for currentlink, currentlink_url
		if ( strpos( $markup_values['page_url'], '?' ) ) {
			$current_item_delimiter = '&';
		} else {
			$current_item_delimiter = '?';
		}

		$column_index = 0;
		foreach ( $tags as $key => $tag ) {
			$item_values = $markup_values;

			// fill in item-specific elements
			$item_values['index'] = (string) 1 + $column_index;
			if ( $item_values['columns'] > 0 && ( 1 + $column_index ) % $item_values['columns'] == 0 ) {
				$item_values['last_in_row'] = 'last_in_row';
			} else {
				$item_values['last_in_row'] = '';
			}

			$item_values['key'] = $key;
			$item_values['term_id'] = $tag->term_id;
			$item_values['name'] = wptexturize( $tag->name );
			$item_values['slug'] = $tag->slug;
			$item_values['term_group'] = $tag->term_group;
			$item_values['term_taxonomy_id'] = $tag->term_taxonomy_id;
			$item_values['taxonomy'] = $tag->taxonomy;
			$item_values['description'] = wptexturize( $tag->description );
			$item_values['parent'] = $tag->parent;
			$item_values['count'] = isset ( $tag->count ) ? $tag->count : 0; 
			$item_values['scaled_count'] = $tag->scaled_count;
			$item_values['font_size'] = str_replace( ',', '.', ( $item_values['smallest'] + ( ( $item_values['scaled_count'] - $item_values['min_scaled_count'] ) * $item_values['font_step'] ) ) );
			$item_values['link_url'] = $tag->link;
			$item_values['currentlink_url'] = sprintf( '%1$s%2$scurrent_item=%3$d', $item_values['page_url'], $current_item_delimiter, $item_values['term_id'] );
			$item_values['editlink_url'] = $tag->edit_link;
			$item_values['termlink_url'] = $tag->term_link;
			// Added in the code below:
			$item_values['caption'] = '';
			$item_values['link_attributes'] = '';
			$item_values['current_item_class'] = '';
			$item_values['rollover_text'] = '';
			$item_values['link_style'] = '';
			$item_values['link_text'] = '';
			$item_values['currentlink'] = '';
			$item_values['editlink'] = '';
			$item_values['termlink'] = '';
			$item_values['thelink'] = '';

			if ( ! empty( $arguments['current_item'] ) ) {
				if ( is_integer( $arguments['current_item'] ) ) {
					if ( $arguments['current_item'] == $tag->term_id ) {
						$item_values['current_item_class'] = $arguments['current_item_class'];
					}
				} else {
//					if ( $arguments['current_item'] == $tag->slug ) {
					if ( $tag->slug == sanitize_title_for_query( $arguments['current_item'] ) ) {
						$item_values['current_item_class'] = $arguments['current_item_class'];
					}
				}
			}

			// Add item_specific field-level substitution parameters
			$new_text = isset( $item_template ) ? $item_template : '';
			foreach( $mla_item_specific_arguments as $index => $value ) {
				$new_text .= str_replace( '{+', '[+', str_replace( '+}', '+]', $arguments[ $index ] ) );
			}

			$item_values = MLAData::mla_expand_field_level_parameters( $new_text, $attr, $item_values );

			if ( $item_values['captiontag'] ) {
				$item_values['caption'] = wptexturize( $tag->description );
				if ( ! empty( $arguments['mla_caption'] ) ) {
					$item_values['caption'] = wptexturize( self::_process_shortcode_parameter( $arguments['mla_caption'], $item_values ) );
				}
			} else {
				$item_values['caption'] = '';
			}

			// Apply the Display Content parameters.
			if ( ! empty( $arguments['mla_target'] ) ) {
				$link_attributes = 'target="' . $arguments['mla_target'] . '" ';
			} else {
				$link_attributes = '';
			}

			if ( ! empty( $arguments['mla_link_attributes'] ) ) {
				$link_attributes .= self::_process_shortcode_parameter( $arguments['mla_link_attributes'], $item_values ) . ' ';
			}

			if ( ! empty( $arguments['mla_link_class'] ) ) {
				$class_attributes = self::_process_shortcode_parameter( $arguments['mla_link_class'], $item_values );
				if ( ! empty( $class_attributes ) ) {
					$link_attributes .= 'class="' . $class_attributes . '" ';
				}
			}

			$item_values['link_attributes'] = $link_attributes;

			$item_values['rollover_text'] = sprintf( _n( $item_values['single_text'], $item_values['multiple_text'], $item_values['count'], 'media-library-assistant' ), number_format_i18n( $item_values['count'] ) );
			if ( ! empty( $arguments['mla_rollover_text'] ) ) {
				$item_values['rollover_text'] = esc_attr( self::_process_shortcode_parameter( $arguments['mla_rollover_text'], $item_values ) );
			}

			if ( ! empty( $arguments['mla_link_href'] ) ) {
				$link_href = self::_process_shortcode_parameter( $arguments['mla_link_href'], $item_values );
				$item_values['link_url'] = $link_href;
			} else {
				$link_href = '';
			}

			if ( ! empty( $arguments['mla_link_style'] ) ) {
				$item_values['link_style'] = esc_attr( self::_process_shortcode_parameter( $arguments['mla_link_style'], $item_values ) );
			} else {
				$item_values['link_style'] = 'font-size: ' . $item_values['font_size'] . $item_values['unit'];
			}

			if ( ! empty( $arguments['mla_link_text'] ) ) {
				$item_values['link_text'] = esc_attr( self::_process_shortcode_parameter( $arguments['mla_link_text'], $item_values ) );
			} else {
				$item_values['link_text'] = $item_values['name'];
			}

			// Currentlink, editlink, termlink and thelink
			$item_values['currentlink'] = sprintf( '<a %1$shref="%2$s%3$scurrent_item=%4$d" title="%5$s" style="%6$s">%7$s</a>', $link_attributes, $item_values['page_url'], $current_item_delimiter, $item_values['term_id'], $item_values['rollover_text'], $item_values['link_style'], $item_values['link_text'] );
			$item_values['editlink'] = sprintf( '<a %1$shref="%2$s" title="%3$s" style="%4$s">%5$s</a>', $link_attributes, $item_values['editlink_url'], $item_values['rollover_text'], $item_values['link_style'], $item_values['link_text'] );
			$item_values['termlink'] = sprintf( '<a %1$shref="%2$s" title="%3$s" style="%4$s">%5$s</a>', $link_attributes, $item_values['termlink_url'], $item_values['rollover_text'], $item_values['link_style'], $item_values['link_text'] );

			if ( ! empty( $link_href ) ) {
				$item_values['thelink'] = sprintf( '<a %1$shref="%2$s" title="%3$s" style="%4$s">%5$s</a>', $link_attributes, $link_href, $item_values['rollover_text'], $item_values['link_style'], $item_values['link_text'] );
			} elseif ( 'current' == $arguments['link'] ) {
				$item_values['link_url'] = $item_values['currentlink_url'];
				$item_values['thelink'] = $item_values['currentlink'];
			} elseif ( 'edit' == $arguments['link'] ) {
				$item_values['thelink'] = $item_values['editlink'];
			} elseif ( 'view' == $arguments['link'] ) {
				$item_values['thelink'] = $item_values['termlink'];
			} elseif ( 'span' == $arguments['link'] ) {
				$item_values['thelink'] = sprintf( '<span %1$sstyle="%2$s">%3$s</span>', $link_attributes, $item_values['link_style'], $item_values['link_text'] );
			} else {
				$item_values['thelink'] = $item_values['link_text'];
			}

			if ( $is_grid || $is_list ) {
				// Start of row markup
				if ( $is_grid && ( $markup_values['columns'] > 0 && $column_index % $markup_values['columns'] == 0 ) ) {
					$markup_values = apply_filters( 'mla_tag_cloud_row_open_values', $markup_values );
					$row_open_template = apply_filters( 'mla_tag_cloud_row_open_template', $row_open_template );
					$parse_value = MLAData::mla_parse_template( $row_open_template, $markup_values );
					$cloud .= apply_filters( 'mla_tag_cloud_row_open_parse', $parse_value, $row_open_template, $markup_values );
				}

				// item markup
				$column_index++;
				$item_values = apply_filters( 'mla_tag_cloud_item_values', $item_values );
				$item_template = apply_filters( 'mla_tag_cloud_item_template', $item_template );
				$parse_value = MLAData::mla_parse_template( $item_template, $item_values );
				$cloud .= apply_filters( 'mla_tag_cloud_item_parse', $parse_value, $item_template, $item_values );

				// End of row markup
				if ( $is_grid && ( $markup_values['columns'] > 0 && $column_index % $markup_values['columns'] == 0 ) ) {
					$markup_values = apply_filters( 'mla_tag_cloud_row_close_values', $markup_values );
					$row_close_template = apply_filters( 'mla_tag_cloud_row_close_template', $row_close_template );
					$parse_value = MLAData::mla_parse_template( $row_close_template, $markup_values );
					$cloud .= apply_filters( 'mla_tag_cloud_row_close_parse', $parse_value, $row_close_template, $markup_values );
				}
			} // is_grid || is_list
			elseif ( $is_pagination ) {
				return $item_values['thelink'];
			} else {
				$column_index++;
				$item_values = apply_filters( 'mla_tag_cloud_item_values', $item_values );
				$tag_links[] = apply_filters( 'mla_tag_cloud_item_parse', $item_values['thelink'], NULL, $item_values );
			} 
		} // foreach tag

		if ($is_grid || $is_list ) {
			// Close out partial row
			if ( $is_grid && ( ! ($markup_values['columns'] > 0 && $column_index % $markup_values['columns'] == 0 ) ) ) {
				$markup_values = apply_filters( 'mla_tag_cloud_row_close_values', $markup_values );
				$row_close_template = apply_filters( 'mla_tag_cloud_row_close_template', $row_close_template );
				$parse_value = MLAData::mla_parse_template( $row_close_template, $markup_values );
				$cloud .= apply_filters( 'mla_tag_cloud_row_close_parse', $parse_value, $row_close_template, $markup_values );
			}

			$markup_values = apply_filters( 'mla_tag_cloud_close_values', $markup_values );
			$close_template = apply_filters( 'mla_tag_cloud_close_template', $close_template );
			$parse_value = MLAData::mla_parse_template( $close_template, $markup_values );
			$cloud .= apply_filters( 'mla_tag_cloud_close_parse', $parse_value, $close_template, $markup_values );
		} // is_grid || is_list
		else {
			switch ( $markup_values['mla_output'] ) {
			case 'array' :
				$cloud =& $tag_links;
				break;
			case 'flat' :
			default :
				$cloud .= join( $markup_values['separator'], $tag_links );
				break;
			} // switch format
		}

		if ( 'array' == $arguments['mla_output'] || empty($arguments['echo']) ) {
			return $cloud;
		}

		echo $cloud; // phpcs:ignore
	}

	/**
	 * The MLA Tag Cloud shortcode.
	 *
	 * This is an interface to the mla_tag_cloud function.
	 *
	 * @since 2.20
	 *
	 * @param array $attr Attributes of the shortcode.
	 * @param string $content Optional content for enclosing shortcodes
	 *
	 * @return string HTML content to display the tag cloud.
	 */
	public static function mla_tag_cloud_shortcode( $attr, $content = NULL ) {
		/*
		 * Make sure $attr is an array, even if it's empty,
		 * and repair damage caused by link-breaks in the source text
		 */
		$attr = self::mla_validate_attributes( $attr, $content );

		// The 'array' format makes no sense in a shortcode
		if ( isset( $attr['mla_output'] ) && 'array' == $attr['mla_output'] ) {
			$attr['mla_output'] = 'flat';
		}
			 
		// A shortcode must return its content to the caller, so "echo" makes no sense
		$attr['echo'] = false;
			 
		return self::mla_tag_cloud( $attr );
	}

	/**
	 * Compose one level of an mla_term_list
	 *
	 * Adds shortcode output text and term-specific links to arrays passed by reference.
	 *
	 * @since 2.25
	 *
	 * @param string $list Shortcode output text, by reference
	 * @param array $links Term-specific links for flat/array output, by reference
	 * @param array $terms Term objects, by reference
	 * @param array $markup_values Style and list-level substitution parameters, by reference
	 * @param array $arguments Shortcode parameters, including defaults, by reference
	 * @param array $attr Shortcode parameters, explicit, by reference
	 *
	 * @return boolean True if the list contains the "current_item"; appends to &$list, &$links
	 */
	public static function _compose_term_list( &$list, &$links, &$terms, &$markup_values, &$arguments, &$attr ) {
		$term = reset( $terms );
		$markup_values['current_level'] = $current_level = $term->level;
		if ( $current_level ) {
			$markup_values['itemtag_class'] = 'term-list term-list-taxonomy-' . $term->taxonomy . ' children'; 
			$markup_values['itemtag_id'] = $markup_values['selector'] . '-' . $term->parent;
		} else {
			$markup_values['itemtag_class'] = 'term-list term-list-taxonomy-' . $term->taxonomy; 
			$markup_values['itemtag_id'] = $markup_values['selector'];
		}

		$mla_item_parameter = $arguments['mla_item_parameter'];

		// Determine output type and templates
		$output_parameters = array_map( 'strtolower', array_map( 'trim', explode( ',', $arguments['mla_output'] ) ) );

		if ( !in_array( $output_parameters[0], array( 'flat', 'list', 'ulist', 'olist', 'dlist', 'dropdown', 'checklist', 'array' ) ) ) {
			$output_parameters[0] = 'ulist';
		}

		$is_list = in_array( $output_parameters[0], array( 'list', 'ulist', 'olist', 'dlist' ) );
		$is_dropdown = 'dropdown' == $output_parameters[0];
		$is_checklist = 'checklist' == $output_parameters[0];
		$is_hierarchical = !( 'false' === $arguments['hierarchical'] );
		$combine_hierarchical = 'combine' === $arguments['hierarchical'];

		// Using the slug is a common practice and affects current_item
		$current_is_slug = in_array( $arguments['mla_option_value'], array( '{+slug+}', '[+slug+]' ) );

		if ( $is_list || $is_dropdown || $is_checklist ) {
			if ( $term->parent ) {
				$open_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'term-list', 'markup', 'child-open' );
			} else {
				$open_template = false;
			}

			if ( false === $open_template ) {
				$open_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'term-list', 'markup', 'open' );
			}

			// Fall back to default template if no Open section
			if ( false === $open_template ) {
				$markup_values['mla_markup'] = $default_markup;

				if ( $term->parent ) {
					$open_template = MLATemplate_support::mla_fetch_custom_template( $default_markup, 'term-list', 'markup', 'child-open' );
				} else {
					$open_template = false;
				}

				if ( false === $open_template ) {
					$open_template = MLATemplate_support::mla_fetch_custom_template( $default_markup, 'term-list', 'markup', 'open' );
				}
			}

			if ( empty( $open_template ) ) {
				$open_template = '';
			}

			if ( $term->parent ) {
				$item_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'term-list', 'markup', 'child-item' );
			} else {
				$item_template = false;
			}

			if ( false === $item_template ) {
				$item_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'term-list', 'markup', 'item' );
			}

			if ( empty( $item_template ) ) {
				$item_template = '';
			}

			if ( $term->parent ) {
				$close_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'term-list', 'markup', 'child-close' );
			} else {
				$close_template = false;
			}

			if ( false === $close_template ) {
				$close_template = MLATemplate_support::mla_fetch_custom_template( $markup_values['mla_markup'], 'term-list', 'markup', 'close' );
			}

			if ( empty( $close_template ) ) {
				$close_template = '';
			}

			if ( $is_list || ( ( 0 == $current_level ) && $is_dropdown ) || $is_checklist ) {
				// Look for gallery-level markup substitution parameters
				$new_text = $open_template . $close_template;
				$markup_values = MLAData::mla_expand_field_level_parameters( $new_text, $attr, $markup_values );

				$markup_values = apply_filters( 'mla_term_list_open_values', $markup_values );
				$open_template = apply_filters( 'mla_term_list_open_template', $open_template );
				if ( empty( $open_template ) ) {
					$gallery_open = '';
				} else {
					$gallery_open = MLAData::mla_parse_template( $open_template, $markup_values );
				}

				$list .=  apply_filters( 'mla_term_list_open_parse', $gallery_open, $open_template, $markup_values );
			}
		} // is_list

		// Find delimiter for currentlink, currentlink_url
		if ( strpos( $markup_values['page_url'], '?' ) ) {
			$current_item_delimiter = '&';
		} else {
			$current_item_delimiter = '?';
		}

		$has_active = false;
		foreach ( $terms as $key => $term ) {
			$item_values = $markup_values;
			$is_active = false;

			// fill in item-specific elements
			$item_values['key'] = $key;
			$item_values['term_id'] = $term->term_id;
			$item_values['name'] = wptexturize( $term->name );
			$item_values['slug'] = $term->slug;
			$item_values['term_group'] = $term->term_group;
			$item_values['term_taxonomy_id'] = $term->term_taxonomy_id;
			$item_values['taxonomy'] = $term->taxonomy;
			$item_values['description'] = wptexturize( $term->description );
			$item_values['parent'] = $term->parent;
			$item_values['count'] = isset ( $term->count ) ? 0 + $term->count : 0; 
			$item_values['link_url'] = $term->link;
			$item_values['currentlink_url'] = sprintf( '%1$s%2$scurrent_item=%3$d', $item_values['page_url'], $current_item_delimiter, $item_values['term_id'] );
			$item_values['editlink_url'] = $term->edit_link;
			$item_values['termlink_url'] = $term->term_link;
			$item_values['children'] = '';
			$item_values['termtag_attributes'] = '';
			$item_values['termtag_class'] = $term->parent ? 'term-list-term children' : 'term-list-term';
			$item_values['termtag_id'] = sprintf( '%1$s-%2$d', $item_values['taxonomy'], $item_values['term_id'] );
			// Added in the code below:
			$item_values['caption'] = '';
			$item_values['link_attributes'] = '';
			$item_values['active_item_class'] = '';
			$item_values['current_item_class'] = '';
			$item_values['rollover_text'] = '';
			$item_values['link_style'] = '';
			$item_values['link_text'] = '';
			$item_values['currentlink'] = '';
			$item_values['editlink'] = '';
			$item_values['termlink'] = '';
			$item_values['thelink'] = '';

			if ( ! empty( $arguments[ $mla_item_parameter ] ) ) {
				foreach ( $arguments[ $mla_item_parameter ] as $current_item ) {
					// Check for multi-taxonomy taxonomy.term compound values
					$value = explode( '.', $current_item );
					if ( 2 === count( $value ) ) {
						if ( $value[0] !== $term->taxonomy ) {
							continue;
						}

						$current_item = $value[1];
					}

					if ( $current_is_slug || !( ctype_digit( $current_item ) || is_int( $current_item ) ) ) {
//						if ( $current_item == $term->slug ) {
						if ( $term->slug == sanitize_title_for_query( $current_item ) ) {
							$is_active = true;
							$item_values['current_item_class'] = $arguments['current_item_class'];
							break;
						}
					} else {
						if ( $current_item == $term->term_id ) {
							$is_active = true;
							$item_values['current_item_class'] = $arguments['current_item_class'];
							break;
						}
					}
				}
			}

			// Add item_specific field-level substitution parameters
			$new_text = isset( $item_template ) ? $item_template : '';
			foreach( self::$term_list_item_specific_arguments as $index => $value ) {
				$new_text .= str_replace( '{+', '[+', str_replace( '+}', '+]', $arguments[ $index ] ) );
			}

			$item_values = MLAData::mla_expand_field_level_parameters( $new_text, $attr, $item_values );

			if ( $item_values['captiontag'] ) {
				$item_values['caption'] = wptexturize( $term->description );
				if ( ! empty( $arguments['mla_caption'] ) ) {
					$item_values['caption'] = wptexturize( self::_process_shortcode_parameter( $arguments['mla_caption'], $item_values ) );
				}
			} else {
				$item_values['caption'] = '';
			}

			// Apply the Display Content parameters.
			if ( ! empty( $arguments['mla_target'] ) ) {
				$link_attributes = 'target="' . $arguments['mla_target'] . '" ';
			} else {
				$link_attributes = '';
			}

			if ( ! empty( $arguments['mla_link_attributes'] ) ) {
				$link_attributes .= self::_process_shortcode_parameter( $arguments['mla_link_attributes'], $item_values ) . ' ';
			}

			if ( ! empty( $arguments['mla_link_class'] ) ) {
				$link_attributes .= 'class="' . self::_process_shortcode_parameter( $arguments['mla_link_class'], $item_values ) . '" ';
			}

			$item_values['link_attributes'] = $link_attributes;

			$item_values['rollover_text'] = sprintf( _n( $item_values['single_text'], $item_values['multiple_text'], $item_values['count'], 'media-library-assistant' ), number_format_i18n( $item_values['count'] ) );
			if ( ! empty( $arguments['mla_rollover_text'] ) ) {
				$item_values['rollover_text'] = esc_attr( self::_process_shortcode_parameter( $arguments['mla_rollover_text'], $item_values ) );
			}

			if ( ! empty( $arguments['mla_link_href'] ) ) {
				$link_href = self::_process_shortcode_parameter( $arguments['mla_link_href'], $item_values );
				$item_values['link_url'] = $link_href;
			} else {
				$link_href = '';
			}

			if ( ! empty( $arguments['mla_link_text'] ) ) {
				$item_values['link_text'] = esc_attr( self::_process_shortcode_parameter( $arguments['mla_link_text'], $item_values ) );
			} else {
				$item_values['link_text'] = $item_values['name'];
			}

			if ( ! empty( $arguments['show_count'] ) && ( 'true' == strtolower( $arguments['show_count'] ) ) ) {
				// Ignore option-all
				if ( -1 !== $item_values['count'] ) {
					$item_values['link_text'] .= ' (' . $item_values['count'] . ')';
				}
			}

			if ( empty( $arguments['mla_item_value'] ) ) {
				$item_values['thevalue'] = $item_values['term_id'];
			} else {
				$item_values['thevalue'] = self::_process_shortcode_parameter( $arguments['mla_item_value'], $item_values );
			}

			// Currentlink, editlink, termlink and thelink  TODO - link style
			$item_values['currentlink'] = sprintf( '<a %1$shref="%2$s%3$s%4$s=%5$s" title="%6$s" style="%7$s">%8$s</a>', $link_attributes, $item_values['page_url'], $current_item_delimiter, $mla_item_parameter, $item_values['thevalue'], $item_values['rollover_text'], '', $item_values['link_text'] );
			$item_values['editlink'] = sprintf( '<a %1$shref="%2$s" title="%3$s" style="%4$s">%5$s</a>', $link_attributes, $item_values['editlink_url'], $item_values['rollover_text'], '', $item_values['link_text'] );
			$item_values['termlink'] = sprintf( '<a %1$shref="%2$s" title="%3$s" style="%4$s">%5$s</a>', $link_attributes, $item_values['termlink_url'], $item_values['rollover_text'], '', $item_values['link_text'] );

			if ( ! empty( $link_href ) ) {
				$item_values['thelink'] = sprintf( '<a %1$shref="%2$s" title="%3$s" style="%4$s">%5$s</a>', $link_attributes, $link_href, $item_values['rollover_text'], '', $item_values['link_text'] );
			} elseif ( 'current' == $arguments['link'] ) {
				$item_values['link_url'] = $item_values['currentlink_url'];
				$item_values['thelink'] = $item_values['currentlink'];
			} elseif ( 'edit' == $arguments['link'] ) {
				$item_values['thelink'] = $item_values['editlink'];
			} elseif ( 'view' == $arguments['link'] ) {
				$item_values['thelink'] = $item_values['termlink'];
			} elseif ( 'span' == $arguments['link'] ) {
				$item_values['thelink'] = sprintf( '<span %1$sstyle="%2$s">%3$s</span>', $link_attributes, '', $item_values['link_text'] );
			} else {
				$item_values['thelink'] = $item_values['link_text'];
			}

			if ( $is_dropdown || $is_checklist ) {
				// Indent the dropdown list
				if ( $is_dropdown && $current_level && $is_hierarchical ) {
					$pad = str_repeat('&nbsp;', $current_level * 3);
				} else {
					$pad = '';
				}

				if ( empty( $arguments['mla_option_text'] ) ) {
					$item_values['thelabel'] = $pad . $item_values['link_text'];
				} else {
					$item_values['thelabel'] = $pad . self::_process_shortcode_parameter( $arguments['mla_option_text'], $item_values );
				}

				if ( empty( $arguments['mla_option_value'] ) ) {
					$item_values['thevalue'] = $item_values['term_id'];

					// Combined hierarchical multi-taxonomy controls generate compound taxonomy.term values 
					if ( ( $is_dropdown || $is_checklist ) && 1 < count( $arguments['taxonomy'] ) ) {
						if ( !( $is_hierarchical && !$combine_hierarchical ) ) {
							$item_values['thevalue'] = $item_values['taxonomy'] . '.' . $item_values['term_id'];
						}
					}
				} else {
					$item_values['thevalue'] = self::_process_shortcode_parameter( $arguments['mla_option_value'], $item_values );
				}

				$item_values['popular'] = ''; // TODO Calculate 'term-list-popular'

				if ( $item_values['current_item_class'] == $arguments['current_item_class'] ) {
					if ( $is_dropdown ) {
						$item_values['selected'] = 'selected=selected';
					} else {
						$item_values['selected'] = 'checked=checked';
					}
				} else {
					$item_values['selected'] = '';
				}
			}

			$child_links = array();
			$child_active = false;
			if ( $is_hierarchical && ! empty( $term->children ) ) {
				$child_active = self::_compose_term_list( $item_values['children'], $child_links, $term->children, $markup_values, $arguments, $attr );
				$markup_values['current_level'] = $current_level; // Changed in _compose_term_list
			}

			if ( $is_active || $child_active ) {
				$has_active = true;
				$item_values['active_item_class'] = $arguments['active_item_class'];
			}

			if ( $is_list || $is_dropdown || $is_checklist ) {
				// item markup
				$item_values = apply_filters( 'mla_term_list_item_values', $item_values );
				$item_template = apply_filters( 'mla_term_list_item_template', $item_template );
				$parse_value = MLAData::mla_parse_template( $item_template, $item_values );
				$list .= apply_filters( 'mla_term_list_item_parse', $parse_value, $item_template, $item_values );
			} else {
				$item_values = apply_filters( 'mla_term_list_item_values', $item_values );
				$links[] = apply_filters( 'mla_term_list_item_parse', $item_values['thelink'], NULL, $item_values );

				if ( $is_hierarchical && ! empty( $child_links ) ) {
					$links = array_merge( $links, $child_links );
				}
			} 
		} // foreach tag

		if ( $is_list || $is_dropdown || $is_checklist ) {
			if ( $is_list || ( ( 0 == $current_level ) && $is_dropdown ) || $is_checklist ) {
				$markup_values = apply_filters( 'mla_term_list_close_values', $markup_values );
				$close_template = apply_filters( 'mla_term_list_close_template', $close_template );
				$parse_value = MLAData::mla_parse_template( $close_template, $markup_values );
				$list .= apply_filters( 'mla_term_list_close_parse', $parse_value, $close_template, $markup_values );
			}
		} else {
			switch ( $markup_values['mla_output'] ) {
			case 'array' :
				$list =& $links;
				break;
			case 'flat' :
			default :
				$list .= join( $markup_values['separator'], $links );
				break;
			} // switch format
		}

		return $has_active;
	}

	/**
	 * These are the default parameters for term list display
	 *
	 * @since 2.30
	 *
	 * @var	array
	 */
	private static $term_list_item_specific_arguments = array(
			'mla_link_attributes' => '',
			'mla_link_class' => '',
			'mla_link_href' => '',
			'mla_link_text' => '',
			'mla_rollover_text' => '',
			'mla_caption' => '',
			'mla_item_value' => '',

			'mla_control_name' => '',
			'mla_option_text' => '',
			'mla_option_value' => '',
		);

	/**
	 * The MLA Term List support function.
	 *
	 * This is an alternative to the WordPress wp_list_categories, wp_dropdown_categories
	 * and wp_terms_checklist functions, with additional options to customize the hyperlink
	 * behind each term.
	 *
	 * @since 2.25
	 *
	 * @param array $attr Attributes of the shortcode.
	 *
	 * @return string HTML content to display the term list, dropdown control or checklist.
	 */
	public static function mla_term_list( $attr ) {
		global $post;

		// Some do_shortcode callers may not have a specific post in mind
		if ( ! is_object( $post ) ) {
			$post = (object) self::$empty_post;
		}

		// $instance supports multiple lists in one page/post	
		static $instance = 0;
		$instance++;

		// Some values are already known, and can be used in data selection parameters
		$upload_dir = wp_upload_dir();
		$page_values = array(
			'instance' => $instance,
			'selector' => "mla_term_list-{$instance}",
			'site_url' => site_url(),
			'base_url' => $upload_dir['baseurl'],
			'base_dir' => $upload_dir['basedir'],
			'id' => $post->ID,
			'page_ID' => $post->ID,
			'page_author' => $post->post_author,
			'page_date' => $post->post_date,
			'page_content' => $post->post_content,
			'page_title' => $post->post_title,
			'page_excerpt' => $post->post_excerpt,
			'page_status' => $post->post_status,
			'page_name' => $post->post_name,
			'page_modified' => $post->post_modified,
			'page_guid' => $post->guid,
			'page_type' => $post->post_type,
			'page_url' => get_page_link(),
		);

		$defaults = array_merge(
			self::$mla_get_terms_parameters,
			array(
			'echo' => false,
			'mla_debug' => false,
			'mla_output' => 'ulist',
			'hierarchical' => 'true',

			'separator' => "\n",
			'single_text' => '%d item',
			'multiple_text' => '%d items',
			'link' => 'current',
			'current_item' => '',
			'active_item_class' => 'mla_active_item',
			'current_item_class' => 'mla_current_item',
			'mla_item_parameter' => 'current_item',
			'show_count' => false,

			'mla_style' => NULL,
			'mla_markup' => NULL,
			'itemtag' => 'ul',
			'termtag' => 'li',
			'captiontag' => '',
			'mla_multi_select' => '',

			'mla_nolink_text' => '',
			'mla_target' => '',
			'hide_if_empty' => false,
			'option_all_text' => '',
			'option_all_value' => NULL,
			'option_none_text' => '',
			'option_none_value' => NULL,

			'depth' => 0,
			'child_of' => 0,
			'include_tree' => NULL,
			'exclude_tree' => NULL,
			),
			self::$term_list_item_specific_arguments
		);

		// Filter the attributes before $mla_item_parameter and "request:" prefix processing.
		$attr = apply_filters( 'mla_term_list_raw_attributes', $attr );

		/*
		 * The current_item parameter can be changed to support
		 * multiple lists per page.
		 */
		if ( ! isset( $attr['mla_item_parameter'] ) ) {
			$attr['mla_item_parameter'] = $defaults['mla_item_parameter'];
		}

		// The mla_item_parameter can contain page_level parameters like {+page_ID+}
		$attr_value = str_replace( '{+', '[+', str_replace( '+}', '+]', $attr['mla_item_parameter'] ) );
		$mla_item_parameter = MLAData::mla_parse_template( $attr_value, $page_values );
		 
		/*
		 * Special handling of mla_item_parameter to make multiple lists per page easier.
		 * Look for this parameter in $_REQUEST if it's not present in the shortcode itself.
		 */
		if ( ! isset( $attr[ $mla_item_parameter ] ) ) {
			if ( isset( $_REQUEST[ $mla_item_parameter ] ) ) {
				$attr[ $mla_item_parameter ] = sanitize_text_field( wp_unslash( $_REQUEST[ $mla_item_parameter ] ) );
			}
		}
		 
		// Determine markup template to get default arguments
		$arguments = shortcode_atts( $defaults, $attr );

		/*
		 * $mla_item_parameter, if non-default, doesn't make it through the shortcode_atts
		 * filter, so we handle it separately
		 */
		if ( ! isset( $arguments[ $mla_item_parameter ] ) ) {
			if ( isset( $attr[ $mla_item_parameter ] ) ) {
				$arguments[ $mla_item_parameter ] = $attr[ $mla_item_parameter ];
			} else {
				$arguments[ $mla_item_parameter ] = $defaults['current_item'];
			}
		}

		if ( $arguments['mla_markup'] ) {
			$template = $arguments['mla_markup'];
			if ( ! MLATemplate_Support::mla_fetch_custom_template( $template, 'term-list', 'markup', '[exists]' ) ) {
				$template = NULL;
			}
		} else {
			$template = NULL;
		}

		if ( empty( $template ) ) {
			$output_parameters = array_map( 'strtolower', array_map( 'trim', explode( ',', $arguments['mla_output'] ) ) );

			if ( !in_array( $output_parameters[0], array( 'flat', 'list', 'ulist', 'olist', 'dlist', 'dropdown', 'checklist', 'array' ) ) ) {
				$output_parameters[0] = 'ulist';
			}

			if ( in_array( $output_parameters[0], array( 'list', 'ulist', 'olist', 'dlist' ) ) ) {
				if ( ( 'dlist' == $output_parameters[0] ) || ('list' == $output_parameters[0] && 'dd' == $arguments['captiontag'] ) ) {
					$template = 'term-list-dl';
				} else {
					$template = 'term-list-ul';
				}
			} elseif ( 'dropdown' == $output_parameters[0] ) {
				$template = 'term-list-dropdown';
			} elseif ( 'checklist' == $output_parameters[0] ) {
				$template = 'term-list-checklist';
			}
		}

		// Apply default arguments set in the markup template
		$arguments = MLATemplate_Support::mla_fetch_custom_template( $template, 'term-list', 'markup', 'arguments' );
		if ( ! empty( $arguments ) ) {
			$attr = wp_parse_args( $attr, self::mla_validate_attributes( array(), $arguments ) );
		}

		// Adjust data selection arguments; remove pagination-specific arguments
		unset( $attr['limit'] );
		unset( $attr['offset'] );

		/*
		 * Look for page-level, 'request:' and 'query:' substitution parameters,
		 * which can be added to any input parameter
		 */
		foreach ( $attr as $attr_key => $attr_value ) {
			/*
			 * item-specific Display Content parameters must be evaluated
			 * later, when all of the information is available.
			 */
			if ( array_key_exists( $attr_key, self::$term_list_item_specific_arguments ) ) {
				continue;
			}

			$attr_value = str_replace( '{+', '[+', str_replace( '+}', '+]', $attr_value ) );
			$replacement_values = MLAData::mla_expand_field_level_parameters( $attr_value, $attr, $page_values );
			$attr[ $attr_key ] = MLAData::mla_parse_template( $attr_value, $replacement_values );
		}

		$attr = apply_filters( 'mla_term_list_attributes', $attr );
		$arguments = shortcode_atts( $defaults, $attr );

		/*
		 * $mla_item_parameter, if non-default, doesn't make it through the shortcode_atts
		 * filter, so we handle it separately
		 */
		if ( ! isset( $arguments[ $mla_item_parameter ] ) ) {
			if ( isset( $attr[ $mla_item_parameter ] ) ) {
				$arguments[ $mla_item_parameter ] = $attr[ $mla_item_parameter ];
			} else {
				$arguments[ $mla_item_parameter ] = $defaults['current_item'];

			}
		}

		// Clean up the current_item(s) to separate term_id from slug
		if ( ! empty( $arguments[ $mla_item_parameter ] ) ) {
			if ( is_string( $arguments[ $mla_item_parameter ] ) ) {
				$arguments[ $mla_item_parameter ] = explode( ',', $arguments[ $mla_item_parameter ] );
			}

			foreach( $arguments[ $mla_item_parameter ] as $index => $value ) {
				if ( ctype_digit( $value ) ) {
					$arguments[ $mla_item_parameter ][ $index ] = absint( $value );
				}
			}
		}

		$arguments = apply_filters( 'mla_term_list_arguments', $arguments );

		// Clean up hierarchical parameter to simplify later processing
		$arguments['hierarchical'] = strtolower( trim( $arguments['hierarchical'] ) ) ;
		if ( !in_array( $arguments['hierarchical'], array( 'true', 'combine' ) ) ) {
			$arguments['hierarchical'] = 'false';
		}

		self::$mla_debug = ( ! empty( $arguments['mla_debug'] ) ) ? trim( strtolower( $arguments['mla_debug'] ) ) : false;
		if ( self::$mla_debug ) {
			if ( 'true' == self::$mla_debug ) {
				MLACore::mla_debug_mode( 'buffer' );
			} elseif ( 'log' == self::$mla_debug ) {
				MLACore::mla_debug_mode( 'log' );
			} else {
				self::$mla_debug = false;
			}
		}

		if ( self::$mla_debug ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug REQUEST', 'media-library-assistant' ) . '</strong> = ' . var_export( $_REQUEST, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug attributes', 'media-library-assistant' ) . '</strong> = ' . var_export( $attr, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug arguments', 'media-library-assistant' ) . '</strong> = ' . var_export( $arguments, true ) );
		}

		// Determine templates and output type
		if ( $arguments['mla_style'] && ( 'none' !== $arguments['mla_style'] ) ) {
			if ( !MLATemplate_Support::mla_fetch_custom_template( $arguments['mla_style'], 'term-list', 'style', '[exists]' ) ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>mla_term_list mla_style</strong> "' . $arguments['mla_style'] . '" ' . __( 'not found', 'media-library-assistant' ), MLACore::MLA_DEBUG_CATEGORY_ANY );
				$arguments['mla_style'] = NULL;
			}
		}

		if ( $arguments['mla_markup'] ) {
			if ( !MLATemplate_Support::mla_fetch_custom_template( $arguments['mla_markup'], 'term-list', 'markup', '[exists]' ) ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>mla_term_list mla_markup</strong> "' . $arguments['mla_markup'] . '" ' . __( 'not found', 'media-library-assistant' ), MLACore::MLA_DEBUG_CATEGORY_ANY );
				$arguments['mla_markup'] = NULL;
			}
		}

		$output_parameters = array_map( 'strtolower', array_map( 'trim', explode( ',', $arguments['mla_output'] ) ) );

		if ( !in_array( $output_parameters[0], array( 'flat', 'list', 'ulist', 'olist', 'dlist', 'dropdown', 'checklist', 'array' ) ) ) {
			$output_parameters[0] = 'ulist';
		}

		$default_style = 'term-list';
		$default_markup = 'term-list-ul';

		if ( $is_list = in_array( $output_parameters[0], array( 'list', 'ulist', 'olist', 'dlist' ) ) ) {

			if ( 'list' == $output_parameters[0] && 'dd' == $arguments['captiontag'] ) {
				$default_markup = 'term-list-dl';
				$arguments['itemtag'] = 'dl';
				$arguments['termtag'] = 'dt';
			} else {
				$default_markup = 'term-list-ul';
				$arguments['termtag'] = 'li';
				$arguments['captiontag'] = '';

				switch ( $output_parameters[0] ) {
					case 'ulist':
						$arguments['itemtag'] = 'ul';
						break;
					case 'olist':
						$arguments['itemtag'] = 'ol';
						break;
					case 'dlist':
						$default_markup = 'term-list-dl';
						$arguments['itemtag'] = 'dl';
						$arguments['termtag'] = 'dt';
						$arguments['captiontag'] = 'dd';
					break;
					default:
						$arguments['itemtag'] = 'ul';
				}
			}
		}

		if ( $is_dropdown = 'dropdown' == $output_parameters[0] ) {
			$default_markup = 'term-list-dropdown';
			$arguments['itemtag'] = empty( $attr['itemtag'] ) ? 'select' : $attr['itemtag'];
			$arguments['termtag'] = 'option';
		}

		if ( $is_checklist = 'checklist' == $output_parameters[0] ) {
			$default_markup = 'term-list-checklist';
			$arguments['termtag'] = 'li';
		}

		if ( NULL == $arguments['mla_style'] ) {
			$arguments['mla_style'] = $default_style;
		}

		if ( NULL == $arguments['mla_markup'] ) {
			$arguments['mla_markup'] = $default_markup;
		}

		$mla_multi_select = ! empty( $arguments['mla_multi_select'] ) && ( 'true' == strtolower( $arguments['mla_multi_select'] ) );

		$is_hierarchical = !( 'false' === $arguments['hierarchical'] );
		$combine_hierarchical = 'combine' === $arguments['hierarchical'];

		// Convert lists to arrays
		if ( is_string( $arguments['taxonomy'] ) ) {
			$arguments['taxonomy'] = explode( ',', $arguments['taxonomy'] );
		}

		if ( is_string( $arguments['post_type'] ) ) {
			$arguments['post_type'] = explode( ',', $arguments['post_type'] );
		}

		if ( is_string( $arguments['post_status'] ) ) {
			$arguments['post_status'] = explode( ',', $arguments['post_status'] );
		}

		// Hierarchical exclude is done in _get_term_tree to exclude children
		if ( $is_hierarchical && isset( $arguments['exclude'] ) ) {
			$exclude_later = $arguments['exclude'];
			unset( $arguments['exclude'] );
		} else {
			$exclude_later = NULL;
		}

		$tags = self::mla_get_terms( $arguments );
		if ( ! empty( $exclude_later ) ) {
			$arguments['exclude'] = $exclude_later;
		}

		// Invalid taxonomy names return WP_Error
		if ( is_wp_error( $tags ) ) {
			$list =  '<strong>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . $tags->get_error_message() . '</strong>, ' . $tags->get_error_data( $tags->get_error_code() );

			if ( 'array' == $arguments['mla_output'] ) {
				return array( $list );
			}

			if ( empty($arguments['echo']) ) {
				return $list;
			}

			echo $list; // phpcs:ignore
			return;
		}

		// Fill in the item_specific link properties, calculate list parameters
		if ( isset( $tags['found_rows'] ) ) {
			$found_rows = $tags['found_rows'];
			unset( $tags['found_rows'] );
		} else {
			$found_rows = count( $tags );
		}

		$show_empty = false;
		if ( 0 == $found_rows ) {
			if ( self::$mla_debug ) {
				MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug empty list', 'media-library-assistant' ) . '</strong>, query = ' . var_export( $arguments, true ) );
				$list = MLACore::mla_debug_flush();

				if ( '<p></p>' == $list ) {
					$list = '';
				}
			} else {
				$list = '';
			}

			if ( 'array' == $arguments['mla_output'] ) {
				$list .= $arguments['mla_nolink_text'];

				if ( empty( $list ) ) {
					return array();
				} else {
					return array( $list );
				}
			}

			$show_empty = empty( $arguments['hide_if_empty'] ) || ( 'true' !== strtolower( $arguments['hide_if_empty'] ) );
			if ( ( $is_checklist || $is_dropdown ) && $show_empty ) {
				if ( empty( $arguments['option_none_text'] ) ) {
					$arguments['option_none_text'] = __( 'no-terms', 'media-library-assistant' );
				}

				if ( ! empty( $arguments['option_none_value'] ) ) {
					$option_none_value = self::_process_shortcode_parameter( $arguments['option_none_value'], $page_values );
					if ( is_numeric( $option_none_value ) ) {
						$option_none_id = (int) $option_none_value;
						$option_none_slug = sanitize_title( $arguments['option_none_text'] );
					} else {
						$option_none_id = -1;
						$option_none_slug = sanitize_title( $option_none_value );
					}
				} else {
					$option_none_id = -1;
					$option_none_slug = sanitize_title( $arguments['option_none_text'] );
				}

				$tags[0] = ( object ) array(
					'term_id' => $option_none_id,
					'name' => $arguments['option_none_text'],
					'slug' => $option_none_slug,
					'term_group' => '0',
					'term_taxonomy_id' => $option_none_id,
					'taxonomy' => reset( $arguments['taxonomy'] ),
					'description' => '',
					'parent' => '0',
					'count' => 0,
					'level' => 0,
					'edit_link' => '',
					'term_link' => '',
					'link' => '',
				);

				$is_hierarchical = false;
				$found_rows = 1;
			} else {
				$list .= $arguments['mla_nolink_text'];

				if ( empty($arguments['echo']) ) {
					return $list;
				}

				echo $list; // phpcs:ignore
				return;
			}
		}

		if ( self::$mla_debug ) {
			$list = MLACore::mla_debug_flush();
		} else {
			$list = '';
		}

		$add_all_option = ( $is_checklist || $is_dropdown ) && ! empty( $arguments['option_all_text'] ) && !$show_empty;

		// Using the slug is a common practice and affects option_all_value
		if ( $add_all_option ) {
			if ( ! empty( $arguments['option_all_value'] ) ) {
				$option_all_value = self::_process_shortcode_parameter( $arguments['option_all_value'], $page_values );
				if ( is_numeric( $option_all_value ) ) {
					$option_all_id = (int) $option_all_value;
					$option_all_slug = sanitize_title( $arguments['option_all_text'] );
				} else {
					$option_all_id = 0;
					$option_all_slug = sanitize_title( $option_all_value );
				}
			} else {
				$option_all_id = 0;
				$option_all_slug = sanitize_title( $arguments['option_all_text'] );
			}
		} else {
			$option_all_id = 0;
			$option_all_slug = 'all';
		}

		if ( $is_hierarchical ) {
			$tags = self::_get_term_tree( $tags, $arguments );

			if ( is_wp_error( $tags ) ) {
				$list =  '<strong>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . $tags->get_error_message() . '</strong>, ' . $tags->get_error_data( $tags->get_error_code() );

				if ( 'array' == $arguments['mla_output'] ) {
					return array( $list );
				}

				if ( empty($arguments['echo']) ) {
					return $list;
				}

				echo $list; // phpcs:ignore
				return;
			}

			if ( isset( $tags['found_rows'] ) ) {
				$found_rows = $tags['found_rows'];
				unset( $tags['found_rows'] );
			} else {
				$found_rows = count( $tags );
			}
		} else {
			if ( !$show_empty ) {
				foreach ( $tags as $key => $tag ) {
					$tags[ $key ]->level = 0;
					$link = get_edit_tag_link( $tag->term_id, $tag->taxonomy );
					if ( ! is_wp_error( $link ) ) {
						$tags[ $key ]->edit_link = $link;
						$link = get_term_link( (int) $tag->term_id, $tag->taxonomy );
						$tags[ $key ]->term_link = $link;
					}

					if ( is_wp_error( $link ) ) {
						$list =  '<strong>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . $link->get_error_message() . '</strong>, ' . $link->get_error_data( $link->get_error_code() );

						if ( 'array' == $arguments['mla_output'] ) {
							return array( $list );
						}

						if ( empty($arguments['echo']) ) {
							return $list;
						}

						echo $list; // phpcs:ignore
						return;
					}

					if ( 'edit' == $arguments['link'] ) {
						$tags[ $key ]->link = $tags[ $key ]->edit_link;
					} else {
						$tags[ $key ]->link = $tags[ $key ]->term_link;
					}
				} // foreach tag
			}
		}

		if ( $add_all_option ) {
			$found_rows += 1;
		}

		$style_values = array_merge( $page_values, array(
			'mla_output' => $arguments['mla_output'],
			'mla_style' => $arguments['mla_style'],
			'mla_markup' => $arguments['mla_markup'],
			'taxonomy' => implode( '-', $arguments['taxonomy'] ),
			'current_item' => $arguments['current_item'],
			'itemtag' => tag_escape( $arguments['itemtag'] ),
			'termtag' => tag_escape( $arguments['termtag'] ),
			'captiontag' => tag_escape( $arguments['captiontag'] ),
			'multiple' => $arguments['mla_multi_select'] ? 'multiple' : '',
			'itemtag_attributes' => '',
			'itemtag_class' => 'term-list term-list-taxonomy-' . implode( '-', $arguments['taxonomy'] ), 
			'itemtag_id' => $page_values['selector'],
			'all_found_rows' => $found_rows,
			'found_rows' => $found_rows,
			'separator' => $arguments['separator'],
			'single_text' => $arguments['single_text'],
			'multiple_text' => $arguments['multiple_text'],
			'echo' => $arguments['echo'],
			'link' => $arguments['link']
		) );

		$style_template = $gallery_style = '';
		$use_mla_term_list_style = 'none' != strtolower( $style_values['mla_style'] );
		if ( apply_filters( 'use_mla_term_list_style', $use_mla_term_list_style, $style_values['mla_style'] ) ) {
			$style_template = MLATemplate_support::mla_fetch_custom_template( $style_values['mla_style'], 'term-list', 'style' );
			if ( empty( $style_template ) ) {
				$style_values['mla_style'] = $default_style;
				$style_template = MLATemplate_support::mla_fetch_custom_template( $default_style, 'term-list', 'style' );
			}

			if ( ! empty ( $style_template ) ) {
				// Look for 'query' and 'request' substitution parameters
				$style_values = MLAData::mla_expand_field_level_parameters( $style_template, $attr, $style_values );

				$style_values = apply_filters( 'mla_term_list_style_values', $style_values );
				$style_template = apply_filters( 'mla_term_list_style_template', $style_template );
				$gallery_style = MLAData::mla_parse_template( $style_template, $style_values );
				$gallery_style = apply_filters( 'mla_term_list_style_parse', $gallery_style, $style_template, $style_values );
			} // !empty template
		} // use_mla_term_list_style

		$list .= $gallery_style;
		$markup_values = $style_values;

		if ( empty( $arguments['mla_control_name'] ) ) {
			$mla_control_name = 'tax_input[[+taxonomy+]][]';
		} else {
			$mla_control_name = $arguments['mla_control_name'];;
		}

		// Accumulate links for flat and array output
		$tag_links = array();

		if ( $is_hierarchical ) {

			if ( $combine_hierarchical ) {
				$combined_tags = array();
				foreach( $tags as $taxonomy => $root_terms ) {
					$combined_tags = array_merge( $combined_tags, $root_terms );
				}
				$tags = array( $markup_values['taxonomy'] => $combined_tags );
			} // $combine_hierarchical

			foreach( $tags as $taxonomy => $root_terms ) {
				$markup_values['taxonomy'] = $taxonomy;
				$markup_values['thename'] = self::_process_shortcode_parameter( $mla_control_name, $markup_values );

				// Add the optional 'all-terms' option, if requested
				if ( $add_all_option ) {
					$option_all = ( object ) array(
						'term_id' => $option_all_id,
						'name' => $arguments['option_all_text'],
						'slug' => $option_all_slug,
						'term_group' => '0',
						'term_taxonomy_id' => $option_all_id,
						'taxonomy' => $taxonomy,
						'description' => '',
						'parent' => '0',
						'count' => -1,
						'level' => 0,
						'edit_link' => '',
						'term_link' => '',
						'link' => '',
					);

					array_unshift( $root_terms, $option_all );
					$add_to_found_rows = 1;
				} else {
					$add_to_found_rows = 0;
				}

				if ( isset( $root_terms['found_rows'] ) ) {
					$markup_values['found_rows'] = $add_to_found_rows + $root_terms['found_rows'];
					unset( $root_terms['found_rows'] );
				} else {
					$markup_values['found_rows'] = count( $root_terms );
				}

				if ( count( $root_terms ) ) {
					self::_compose_term_list( $list, $tag_links, $root_terms, $markup_values, $arguments, $attr );
				}
			}
		} else {
			$markup_values['thename'] = self::_process_shortcode_parameter( $mla_control_name, $markup_values );

			// Add the optional 'all-terms' option, if requested
			if ( $add_all_option ) {
				$option_all = ( object ) array(
					'term_id' => $option_all_id,
					'name' => $arguments['option_all_text'],
					'slug' => $option_all_slug,
					'term_group' => '0',
					'term_taxonomy_id' => $option_all_id,
					'taxonomy' => $markup_values['taxonomy'],
					'description' => '',
					'parent' => '0',
					'count' => -1,
					'level' => 0,
					'edit_link' => '',
					'term_link' => '',
					'link' => '',
				);

				array_unshift( $tags, $option_all );
			}

			if ( count( $tags ) ) {
				self::_compose_term_list( $list, $tag_links, $tags, $markup_values, $arguments, $attr );
			}
		}

		if ( 'array' == $arguments['mla_output'] || empty($arguments['echo']) ) {
			return $list;
		}

		echo $list; // phpcs:ignore
	}

	/**
	 * The MLA Term List shortcode.
	 *
	 * This is an interface to the mla_term_list function.
	 *
	 * @since 2.25
	 *
	 * @param array $attr Attributes of the shortcode.
	 * @param string $content Optional content for enclosing shortcodes
	 *
	 * @return string HTML content to display the term list.
	 */
	public static function mla_term_list_shortcode( $attr, $content = NULL ) {
		/*
		 * Make sure $attr is an array, even if it's empty,
		 * and repair damage caused by link-breaks in the source text
		 */
		$attr = self::mla_validate_attributes( $attr, $content );

		// The 'array' format makes no sense in a shortcode
		if ( isset( $attr['mla_output'] ) && 'array' == $attr['mla_output'] ) {
			$attr['mla_output'] = 'flat';
		}
			 
		// A shortcode must return its content to the caller, so "echo" makes no sense
		$attr['echo'] = false;

		if ( ! empty( $attr['mla_output'] ) ) {
			switch ( $attr['mla_output'] ) {
				case 'wp_list_categories':
					return wp_list_categories( $attr );
				case 'wp_dropdown_categories':
					return wp_dropdown_categories( $attr );
				case 'wp_terms_checklist':
					require_once( ABSPATH . 'wp-admin/includes/template.php' );
					return wp_terms_checklist( 0, $attr );
			}
		}

		return self::mla_term_list( $attr );
	}

	/**
	 * Computes image dimensions for scalable graphics, e.g., SVG 
	 *
	 * @since 2.20
	 *
	 * @return array 
	 */
	private static function _registered_dimensions() {
		global $_wp_additional_image_sizes;

		if ( 'checked' == MLACore::mla_get_option( MLACoreOptions::MLA_ENABLE_MLA_ICONS ) ) {
			$sizes = array( 'icon' => array( 64, 64 ) );
		} else {
			$sizes = array( 'icon' => array( 60, 60 ) );
		}

		foreach( get_intermediate_image_sizes() as $s ) {
			$sizes[ $s ] = array( 0, 0 );

			if( in_array( $s, array( 'thumbnail', 'medium', 'large' ) ) ) {
				$sizes[ $s ][0] = get_option( $s . '_size_w' );
				$sizes[ $s ][1] = get_option( $s . '_size_h' );
			} else {
				if( isset( $_wp_additional_image_sizes ) && isset( $_wp_additional_image_sizes[ $s ] ) ) {
					$sizes[ $s ] = array( $_wp_additional_image_sizes[ $s ]['width'], $_wp_additional_image_sizes[ $s ]['height'], );
				}
			}
		}
 
		return $sizes;
	}

	/**
	 * Handles brace/bracket escaping and parses template for a shortcode parameter
	 *
	 * @since 2.20
	 *
	 * @param string raw shortcode parameter, e.g., "text {+field+} {brackets} \\{braces\\}"
	 * @param string template substitution values, e.g., ('instance' => '1', ...  )
	 *
	 * @return string parameter with brackets, braces, substitution parameters and templates processed
	 */
	private static function _process_shortcode_parameter( $text, $markup_values ) {
		$new_text = str_replace( '{\+', '\[\+', str_replace( '+\}', '+\\\\]', $text ) );
		$new_text = str_replace( '{', '[', str_replace( '}', ']', $new_text ) );
		$new_text = str_replace( '\[', '{', str_replace( '\]', '}', $new_text ) );
		return MLAData::mla_parse_template( $new_text, $markup_values );
	}

	/**
	 * Replaces or removes a query argument, preserving url encoding
	 *
	 * @since 2.84
	 *
	 * @param string argument name
	 * @param mixed argument value (string) or false to remove argument
	 * @param string url
	 *
	 * @return string url with argument replaced
	 */
	private static function _replace_query_parameter( $key, $value, $url ) {
		$parts = wp_parse_url( $url );
		if ( empty( $parts['path'] ) ) {
			$parts['path'] = '';
		}

		// Fragments must come at the end of the URL and be preceded by a #
		if ( !empty( $parts['fragment'] ) ) {
			$parts['fragment'] = '#' . $parts['fragment'];
		} else {
			$parts['fragment'] = '';
		}

		$clean_query = array();
		if ( empty( $parts['query'] ) ) {
			// No existing query arguments; create query if requested
			if ( false !== $value ) {
				$clean_query[ $key ] = $value;
			}
		} else {
			parse_str( $parts['query'], $query );

			$add_it = true;
			foreach ( $query as $query_key => $query_value ) {
				// Query argument names cannot have URL special characters
				if ( $query_key === urldecode( $query_key ) ) {
					if ( $key === $query_key ) {
						$add_it = false;
						// Deleting argument?
						if ( false === $value ) {
							continue;
						}

						$query_value = $value;
					}

					$clean_query[ $query_key ] = $query_value;
				}
			}

			if ( $add_it && ( false !== $value ) ) {
				$clean_query[ $key ] = $value;
			}
		}

		$clean_query = urlencode_deep( $clean_query );
		$clean_query = build_query( $clean_query );

		// Query arguments must come before the fragment, if any
		if ( !empty( $clean_query ) ) {
			return $parts['scheme'] . '://' . $parts['host'] . $parts['path'] . '?' . $clean_query . $parts['fragment'];
		} else {
			return $parts['scheme'] . '://' . $parts['host'] . $parts['path'] . $parts['fragment'];
		}
	}

	/**
	 * Handles pagnation output types 'previous_page', 'next_page', and 'paginate_links'
	 *
	 * @since 2.20
	 *
	 * @param array	value(s) for mla_output_type parameter
	 * @param string template substitution values, e.g., ('instance' => '1', ...  )
	 * @param string merged default and passed shortcode parameter values
	 * @param integer number of attachments in the gallery, without pagination
	 * @param string output text so far, may include debug values
	 *
	 * @return mixed	false or string with HTML for pagination output types
	 */
	private static function _paginate_links( $output_parameters, $markup_values, $arguments, $found_rows, $output = '' ) {
		if ( 2 > $markup_values['last_page'] ) {
			return '';
		}

		$show_all = $prev_next = false;

		if ( isset ( $output_parameters[1] ) ) {
				switch ( $output_parameters[1] ) {
				case 'show_all':
					$show_all = true;
					break;
				case 'prev_next':
					$prev_next = true;
			}
		}

		$mla_page_parameter = $arguments['mla_page_parameter'];
		$current_page = $markup_values['current_page'];
		$last_page = $markup_values['last_page'];
		$end_size = absint( $arguments['mla_end_size'] );
		$mid_size = absint( $arguments['mla_mid_size'] );
		$posts_per_page = $markup_values['posts_per_page'];

		$new_target = ( ! empty( $arguments['mla_target'] ) ) ? 'target="' . $arguments['mla_target'] . '" ' : '';

		// these will add to the default classes
		$new_class = ( ! empty( $arguments['mla_link_class'] ) ) ? ' ' . esc_attr( self::_process_shortcode_parameter( $arguments['mla_link_class'], $markup_values ) ) : '';

		$new_attributes = ( ! empty( $arguments['mla_link_attributes'] ) ) ? esc_attr( self::_process_shortcode_parameter( $arguments['mla_link_attributes'], $markup_values ) ) . ' ' : '';

		$new_base =  ( ! empty( $arguments['mla_link_href'] ) ) ? self::_process_shortcode_parameter( $arguments['mla_link_href'], $markup_values ) : $markup_values['new_url'];

		// Build the array of page links
		$page_links = array();
		$dots = false;

		if ( $prev_next && $current_page && 1 < $current_page ) {
			$markup_values['new_page'] = $current_page - 1;
			$new_title = ( ! empty( $arguments['mla_rollover_text'] ) ) ? 'title="' . esc_attr( self::_process_shortcode_parameter( $arguments['mla_rollover_text'], $markup_values ) ) . '" ' : '';
			$new_url = self::_replace_query_parameter( $mla_page_parameter, $current_page - 1, $new_base );
			$prev_text = ( ! empty( $arguments['mla_prev_text'] ) ) ? esc_attr( self::_process_shortcode_parameter( $arguments['mla_prev_text'], $markup_values ) ) : '&laquo; ' . __( 'Previous', 'media-library-assistant' );
			$page_links[] = sprintf( '<a %1$sclass="prev page-numbers%2$s" %3$s%4$shref="%5$s">%6$s</a>',
				/* %1$s */ $new_target,
				/* %2$s */ $new_class,
				/* %3$s */ $new_attributes,
				/* %4$s */ $new_title,
				/* %5$s */ $new_url,
				/* %6$s */ $prev_text );
		}

		for ( $new_page = 1; $new_page <= $last_page; $new_page++ ) {
			$new_page_display = number_format_i18n( $new_page );
			$markup_values['new_page'] = $new_page;
			$new_title = ( ! empty( $arguments['mla_rollover_text'] ) ) ? 'title="' . esc_attr( self::_process_shortcode_parameter( $arguments['mla_rollover_text'], $markup_values ) ) . '" ' : '';

			if ( $new_page == $current_page ) {
				// build current page span
				$page_links[] = sprintf( '<span class="page-numbers current%1$s">%2$s</span>',
					/* %1$s */ $new_class,
					/* %2$s */ $new_page_display );
				$dots = true;
			} else {
				if ( $show_all || ( $new_page <= $end_size || ( $current_page && $new_page >= $current_page - $mid_size && $new_page <= $current_page + $mid_size ) || $new_page > $last_page - $end_size ) ) {
					// build link
					$new_url = self::_replace_query_parameter( $mla_page_parameter, $new_page, $new_base );
					$page_links[] = sprintf( '<a %1$sclass="page-numbers%2$s" %3$s%4$shref="%5$s">%6$s</a>',
						/* %1$s */ $new_target,
						/* %2$s */ $new_class,
						/* %3$s */ $new_attributes,
						/* %4$s */ $new_title,
						/* %5$s */ $new_url,
						/* %6$s */ $new_page_display );
					$dots = true;
				} elseif ( $dots && ! $show_all ) {
					// build link
					$page_links[] = sprintf( '<span class="page-numbers dots%1$s">&hellip;</span>',
						/* %1$s */ $new_class );
					$dots = false;
				}
			} // ! current
		} // for $new_page

		if ( $prev_next && $current_page && ( $current_page < $last_page || -1 == $last_page ) ) {
			// build next link
			$markup_values['new_page'] = $current_page + 1;
			$new_title = ( ! empty( $arguments['mla_rollover_text'] ) ) ? 'title="' . esc_attr( self::_process_shortcode_parameter( $arguments['mla_rollover_text'], $markup_values ) ) . '" ' : '';
			$new_url = self::_replace_query_parameter( $mla_page_parameter, $current_page + 1, $new_base );
			$next_text = ( ! empty( $arguments['mla_next_text'] ) ) ? esc_attr( self::_process_shortcode_parameter( $arguments['mla_next_text'], $markup_values ) ) : __( 'Next', 'media-library-assistant' ) . ' &raquo;';
			$page_links[] = sprintf( '<a %1$sclass="next page-numbers%2$s" %3$s%4$shref="%5$s">%6$s</a>',
				/* %1$s */ $new_target,
				/* %2$s */ $new_class,
				/* %3$s */ $new_attributes,
				/* %4$s */ $new_title,
				/* %5$s */ $new_url,
				/* %6$s */ $next_text );
		}

		switch ( strtolower( trim( $arguments['mla_paginate_type'] ) ) ) {
			case 'list':
				$results = "<ul class='page-numbers'>\n\t<li>";
				$results .= join("</li>\n\t<li>", $page_links);
				$results .= "</li>\n</ul>\n";
				break;
			case 'plain':
			default:
				$results = join("\n", $page_links);
		} // mla_paginate_type

		return $output . $results;
	}

	/**
	 * Handles pagnation output types 'previous_page', 'next_page', and 'paginate_links'
	 *
	 * @since 2.20
	 *
	 * @param array	value(s) for mla_output_type parameter
	 * @param string template substitution values, e.g., ('instance' => '1', ...  )
	 * @param string merged default and passed shortcode parameter values
	 * @param string raw passed shortcode parameter values
	 * @param integer number of attachments in the gallery, without pagination
	 * @param string output text so far, may include debug values
	 *
	 * @return mixed	false or string with HTML for pagination output types
	 */
	private static function _process_pagination_output_types( $output_parameters, $markup_values, $arguments, $attr, $found_rows, $output = '' ) {
		if ( ! in_array( $output_parameters[0], array( 'previous_page', 'next_page', 'paginate_links' ) ) ) {
			return false;
		}

		// Add data selection parameters to gallery-specific and mla_gallery-specific parameters
		$arguments = array_merge( $arguments, shortcode_atts( self::$mla_get_shortcode_attachments_parameters, $attr ) );
		$posts_per_page = absint( $arguments['posts_per_page'] );
		$mla_page_parameter = $arguments['mla_page_parameter'];

		/*
		 * $mla_page_parameter, if set, doesn't make it through the shortcode_atts filter,
		 * so we handle it separately
		 */
		if ( ! isset( $arguments[ $mla_page_parameter ] ) ) {
			if ( isset( $attr[ $mla_page_parameter ] ) ) {
				$arguments[ $mla_page_parameter ] = $attr[ $mla_page_parameter ];
			} else {
				$arguments[ $mla_page_parameter ] = '';
			}
		}

		if ( 0 == $posts_per_page ) {
			$posts_per_page = absint( $arguments['numberposts'] );
		}

		if ( 0 == $posts_per_page ) {
			$posts_per_page = absint( get_option('posts_per_page') );
		}

		if ( 0 < $posts_per_page ) {
			$max_page = floor( $found_rows / $posts_per_page );
			if ( $max_page < ( $found_rows / $posts_per_page ) ) {
				$max_page++;
			}
		} else {
			$max_page = 1;
		}

		if ( isset( $arguments['mla_paginate_total'] )  && $max_page > absint( $arguments['mla_paginate_total'] ) ) {
			$max_page = absint( $arguments['mla_paginate_total'] );
		}

		if ( isset( $arguments[ $mla_page_parameter ] ) ) {
			$paged = absint( $arguments[ $mla_page_parameter ] );
		} else {
			$paged = absint( $arguments['paged'] );
		}

		if ( 0 == $paged ) {
			$paged = 1;
		}

		if ( $max_page < $paged ) {
			$paged = $max_page;
		}

		switch ( $output_parameters[0] ) {
			case 'previous_page':
				if ( 1 < $paged ) {
					$new_page = $paged - 1;
				} else {
					$new_page = 0;

					if ( isset ( $output_parameters[1] ) ) {
						switch ( $output_parameters[1] ) {
							case 'wrap':
								$new_page = $max_page;
								break;
							case 'first':
								$new_page = 1;
						}
					}
				}

				break;
			case 'next_page':
				if ( $paged < $max_page ) {
					$new_page = $paged + 1;
				} else {
					$new_page = 0;

					if ( isset ( $output_parameters[1] ) ) {
						switch ( $output_parameters[1] ) {
							case 'last':
								$new_page = $max_page;
								break;
							case 'wrap':
								$new_page = 1;
						}
					}
				}

				break;
			case 'paginate_links':
				$new_page = 0;
		}

		$markup_values['current_page'] = $paged;
		$markup_values['new_page'] = $new_page;
		$markup_values['last_page'] = $max_page;
		$markup_values['posts_per_page'] = $posts_per_page;
		$markup_values['found_rows'] = $found_rows;

		if ( $paged ) {
			$markup_values['current_offset'] = ( $paged - 1 ) * $posts_per_page;
		} else {
			$markup_values['current_offset'] = 0;
		}

		if ( $new_page ) {
			$markup_values['new_offset'] = ( $new_page - 1 ) * $posts_per_page;
		} else {
			$markup_values['new_offset'] = 0;
		}

		$markup_values['current_page_text'] = 'mla_paginate_current="[+current_page+]"';
		$markup_values['new_page_text'] = 'mla_paginate_current="[+new_page+]"';
		$markup_values['last_page_text'] = 'mla_paginate_total="[+last_page+]"';
		$markup_values['posts_per_page_text'] = 'posts_per_page="[+posts_per_page+]"';

		if ( 'HTTPS' == substr( $_SERVER["SERVER_PROTOCOL"], 0, 5 ) ) { // phpcs:ignore
			$markup_values['scheme'] = 'https://';
		} else {
			$markup_values['scheme'] = 'http://';
		}

		$markup_values['http_host'] = $_SERVER['HTTP_HOST']; // phpcs:ignore

		$parts = wp_parse_url( $_SERVER['REQUEST_URI'] ); // phpcs:ignore
		$uri_path = empty( $parts['path'] ) ? '' : $parts['path'];
		$uri_query = empty( $parts['query'] ) ? '' : $parts['query'];

		// Add or replace the current page parameter
		if ( 0 < $new_page ) {
			$uri_query = remove_query_arg( $mla_page_parameter, $uri_query );
			$uri_query = add_query_arg( array(  $mla_page_parameter  => $new_page ), $uri_query );	
		}

		// Validate the query arguments to prevent cross-site scripting (reflection) attacks
		$test_query = array();
		parse_str( strval( $uri_query ), $test_query );
		
		$clean_query = array();
		foreach ( $test_query as $test_key => $test_value ) {
			// Query argument names cannot have URL special characters
			if ( $test_key === urldecode( $test_key ) ) {
				$clean_query[ $test_key ] = $test_value;
			}
		}

		$clean_query = urlencode_deep( $clean_query );
		$clean_query = build_query( $clean_query );
		$markup_values['query_string'] = $clean_query;

		if ( !empty( $clean_query ) ) {
			$markup_values['request_uri'] = $uri_path .  '?' . $clean_query;	
		} else {
			$markup_values['request_uri'] = $uri_path;
		}

		$markup_values['new_url'] = set_url_scheme( $markup_values['scheme'] . $markup_values['http_host'] . $markup_values['request_uri'] );
		$markup_values = apply_filters( 'mla_gallery_pagination_values', $markup_values );

		/*
		 * Expand pagination-specific Gallery Display Content parameters,
		 * which can contain request: and query: arguments.
		 */
		$pagination_arguments = array( 'mla_nolink_text', 'mla_link_class', 'mla_rollover_text', 'mla_link_attributes', 'mla_link_href', 'mla_link_text', 'mla_prev_text', 'mla_next_text' );

		$new_text = '';
		foreach( $pagination_arguments as $value ) {
			$new_text .= str_replace( '{+', '[+', str_replace( '+}', '+]', $arguments[ $value ] ) );
		}

		$markup_values = MLAData::mla_expand_field_level_parameters( $new_text, $attr, $markup_values );

		// Build the new link, applying Gallery Display Content parameters
		if ( 'paginate_links' == $output_parameters[0] ) {
			return self::_paginate_links( $output_parameters, $markup_values, $arguments, $found_rows, $output );
		}

		if ( 0 == $new_page ) {
			if ( ! empty( $arguments['mla_nolink_text'] ) ) {
				return self::_process_shortcode_parameter( $arguments['mla_nolink_text'], $markup_values );
			} else {
				return '';
			}
		}

		$new_link = '<a ';

		if ( ! empty( $arguments['mla_target'] ) ) {
			$new_link .= 'target="' . $arguments['mla_target'] . '" ';
		}

		if ( ! empty( $arguments['mla_link_class'] ) ) {
			$new_link .= 'class="' . esc_attr( self::_process_shortcode_parameter( $arguments['mla_link_class'], $markup_values ) ) . '" ';
		}

		if ( ! empty( $arguments['mla_rollover_text'] ) ) {
			$new_link .= 'title="' . esc_attr( self::_process_shortcode_parameter( $arguments['mla_rollover_text'], $markup_values ) ) . '" ';
		}

		if ( ! empty( $arguments['mla_link_attributes'] ) ) {
			$new_link .= esc_attr( self::_process_shortcode_parameter( $arguments['mla_link_attributes'], $markup_values ) ) . ' ';
		}

		if ( ! empty( $arguments['mla_link_href'] ) ) {
			$new_link .= 'href="' . self::_process_shortcode_parameter( $arguments['mla_link_href'], $markup_values ) . '" >';
		} else {
			$new_link .= 'href="' . $markup_values['new_url'] . '" >';
		}

		if ( ! empty( $arguments['mla_link_text'] ) ) {
			$new_link .= self::_process_shortcode_parameter( $arguments['mla_link_text'], $markup_values ) . '</a>';
		} else {
			if ( 'previous_page' == $output_parameters[0] ) {
				if ( isset( $arguments['mla_prev_text'] ) ) {
					$new_text = esc_attr( self::_process_shortcode_parameter( $arguments['mla_prev_text'], $markup_values ) );
				} else {
					$new_text = '&laquo; ' . __( 'Previous', 'media-library-assistant' );
				}
			} else {
				if ( isset( $arguments['mla_next_text'] ) ) {
					$new_text = esc_attr( self::_process_shortcode_parameter( $arguments['mla_next_text'], $markup_values ) );
				} else {
					$new_text = __( 'Next', 'media-library-assistant' ) . ' &raquo;';
				}
			}

			$new_link .= $new_text . '</a>';
		}

		return $new_link;
	}

	/**
	 * WP_Query filter "parameters"
	 *
	 * This array defines parameters for the query's join, where and orderby filters.
	 * The parameters are set up in the mla_get_shortcode_attachments function, and
	 * any further logic required to translate those values is contained in the filter.
	 *
	 * Array index values are: orderby, post_parent
	 *
	 * @since 2.20
	 *
	 * @var	array
	 */
	private static $query_parameters = array();

	/**
	 * Error details from _validate_array_specification()
	 *
	 * @since 2.94
	 *
	 * @var	string
	 */
	private static $array_specification_error = '';

	/**
	 * Checks for valid, perhaps nested PHP array specification
	 *
	 * @since 2.82
	 *
	 * @param string query specification; PHP nested arrays
	 *
	 * @return boolean true if specification is a valid PHP array else false
	 */
	private static function _validate_array_specification( $specification ) {
//error_log( __LINE__ . " _validate_array_specification() specification = " . var_export( $specification, true ), 0 );
		self::$array_specification_error = '';

		// Check for outer array specification(s) and reject anything else.
		if ( 1 !== preg_match( '/^array\s*\((.*)\)[\s\,]*$/', $specification, $matches ) ) {
//error_log( __LINE__ . " _validate_array_specification() specification = " . var_export( $specification, true ), 0 );
			self::$array_specification_error = " FAILED outer array = " . var_export( $specification, true );
			return false;
		}

		$interior = trim( $matches[1], ', ' );
		while ( strlen( $interior ) ) {
//error_log( __LINE__ . " _validate_array_specification() interior = " . var_export( $interior, true ), 0 );
			// Recursive matching required for nested and multiple arrays
			while ( preg_match_all( '/(?x)array\s*\( ( (?>[^()]+) | (?R) )* \)/', $interior, $matches ) ) {
//error_log( __LINE__ . " _validate_array_specification() recursion matches = " . var_export( $matches, true ), 0 );
				foreach ( $matches[0] as $search ) {
					// Replace valid arrays with a harmless literal value
					if ( false === self::_validate_array_specification( $search ) ) {
						self::$array_specification_error = " FAILED nested array = " . var_export( $search, true );
//error_log( __LINE__ . " _validate_array_specification() search = " . var_export( $search, true ), 0 );
						return false;
					}

					$interior = str_replace( $search, 'ARRAY', $interior );
				}
//error_log( __LINE__ . " _validate_array_specification() recursion interior = " . var_export( $interior, true ), 0 );
			}

			// Look for an already-validated array match
			if ( 0 === strpos( $interior, 'ARRAY' ) ) {
				$interior = trim( substr( $interior, 5 ), ' ,' );
				continue;
			}

			// Look for a nested array
			if ( 1 === preg_match( '/^(array\s*\(.*\))(.*)$/', $interior, $matches ) ) {
//error_log( __LINE__ . " _validate_array_specification() matches = " . var_export( $matches, true ), 0 );
				if ( false === self::_validate_array_specification( $matches[1] ) ) {
					return false;
				}

				$interior = trim( $matches[2], ' ,' );
				continue;
			}

			// PHP "undefined constant" pattern: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*

			// Look for 'key' => value
			if ( 1 === preg_match( '/^((([\'\"](.+?)[\'\"])|(\d+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))\s*\=\>\s*)(([\'\"](.*?)[\'\"])|(\d+)|(\w{4,5}))(.*)$/', $interior, $matches /*, PREG_OFFSET_CAPTURE */ ) ) {
//error_log( __LINE__ . " _validate_array_specification() key => value matches = " . var_export( $matches, true ), 0 );

				// Validate boolean and array() values
				if ( ! empty( $matches[11] ) ) {
//error_log( __LINE__ . " _validate_array_specification() boolean and array() matches = " . var_export( $matches[10], true ), 0 );
					if ( false === in_array( strtolower( $matches[11] ), array( 'false', 'true', 'array' ) ) ) {
//error_log( __LINE__ . " _validate_array_specification() FAILED boolean matches = " . var_export( $matches[7], true ), 0 );
						self::$array_specification_error = " FAILED boolean matches = " . var_export( $matches[7], true );
						return false;
					}
				}

				$interior = trim( $matches[12], ' ,' );
				continue;
			}

			// Look for simple quoted string, integer value or "undefined constant", e.g., in 'terms' =>
			if ( 1 === preg_match( '/^(([\'\"](.+?)[\'\"])|(\d+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))(.*)$/', $interior, $matches ) ) {
//error_log( __LINE__ . " _validate_array_specification() simple matches = " . var_export( $matches, true ), 0 );

				$interior = trim( $matches[6], ' ,' );
				continue;
			}

//error_log( __LINE__ . " _validate_array_specification() FAILED interior = " . var_export( $interior, true ), 0 );
			self::$array_specification_error = " FAILED interior = " . var_export( $interior, true );
			return false;
		}

//error_log( __LINE__ . " _validate_array_specification() GOOD interior = " . var_export( $interior, true ), 0 );
		return true;
	}

	/**
	 * Cleans up damage caused by the Visual Editor to the tax_query and meta_query specifications,
	 * then checks for valid PHP array specification to avoid remote code execution attacks.
	 *
	 * @since 2.20
	 *
	 * @param string query specification; PHP nested arrays
	 *
	 * @return string query specification with HTML escape sequences and line breaks removed
	 */
	private static function _sanitize_query_specification( $specification ) {
//error_log( __LINE__ . " _sanitize_query_specification() specification = " . var_export( $specification, true ), 0 );
		// Clean up the text
		$candidate = wp_specialchars_decode( $specification );

		$candidate = str_replace( array( '&#038;', '&#8216;', '&#8217;', '&#8220;', '&#8221;', '&#8242;', '&#8243;', '&amp;', '<br />', '<br>', '<p>', '</p>', "\r", "\n", "\t" ),
		                          array( '&',      '\'',      '\'',      '"',       '"',       '\'',      '"',       '&',     ' ',      ' ',    ' ',   ' ',    ' ',  ' ',  ' ' ), $candidate );

		$candidate = trim( $candidate, ' ,"\`' );
//error_log( __LINE__ . " _sanitize_query_specification() candidate = " . var_export( $candidate, true ), 0 );

		// Check for nested array specification(s) and reject anything else.
		$result = self::_validate_array_specification( $candidate );
		if ( true === $result ) {
			return $candidate;
		}

//error_log( __LINE__ . " _sanitize_query_specification() FAILED array_specification_error = " . var_export( self::$array_specification_error, true ), 0 );
		return 'false';
	}

	/**
	 * Translates query parameters to a valid SQL order by clause.
	 *
	 * Accepts one or more valid columns, with or without ASC/DESC.
	 * Enhanced version of /wp-includes/formatting.php function sanitize_sql_orderby().
	 *
	 * @since 2.20
	 *
	 * @param array Validated query parameters; 'order', 'orderby', 'meta_key', 'post__in'.
	 * @param string Optional. Database table prefix; can be empty. Default taken from $wpdb->posts.
	 * @param array Optional. Field names (keys) and database column equivalents (values). Defaults from [mla_gallery].
	 * @param array Optional. Field names (values) that require a BINARY prefix to preserve case order. Default array()
	 * @return string|bool Returns the orderby clause if present, false otherwise.
	 */
	private static function _validate_sql_orderby( $query_parameters, $table_prefix = NULL, $allowed_keys = NULL, $binary_keys = array() ){
		global $wpdb;

		$results = array ();
		$order = isset( $query_parameters['order'] ) ? ' ' . trim( strtoupper( $query_parameters['order'] ) ) : '';
		$orderby = isset( $query_parameters['orderby'] ) ? $query_parameters['orderby'] : '';
		$meta_key = isset( $query_parameters['meta_key'] ) ? $query_parameters['meta_key'] : '';

		if ( is_null( $table_prefix ) ) {
			$table_prefix = $wpdb->posts . '.';
		}

		if ( is_null( $allowed_keys ) ) {
			$allowed_keys = array(
				'empty_orderby_default' => 'post_date',
				'explicit_orderby_field' => 'post__in',
				'explicit_orderby_column' => 'ID',
				'id' => 'ID',
				'author' => 'post_author',
				'date' => 'post_date',
				'description' => 'post_content',
				'content' => 'post_content',
				'title' => 'post_title',
				'caption' => 'post_excerpt',
				'excerpt' => 'post_excerpt',
				'slug' => 'post_name',
				'name' => 'post_name',
				'modified' => 'post_modified',
				'parent' => 'post_parent',
				'menu_order' => 'menu_order',
				'mime_type' => 'post_mime_type',
				'comment_count' => 'post_content',
				'rand' => 'RAND()',
			);
		}

		if ( empty( $orderby ) ) {
			if ( ! empty( $allowed_keys['empty_orderby_default'] ) ) {
				return $table_prefix . $allowed_keys['empty_orderby_default'] . " {$order}";
			} else {
				return "{$table_prefix}post_date {$order}";
			}
		} elseif ( 'none' == $orderby ) {
			return '';
		} elseif ( ! empty( $allowed_keys['explicit_orderby_field'] ) ) {
			$explicit_field = $allowed_keys['explicit_orderby_field'];
			if ( $orderby == $explicit_field ) {
				if ( ! empty( $query_parameters[ $explicit_field ] ) ) {
					$explicit_order = implode(',', array_map( 'absint', $query_parameters[ $explicit_field ] ) );

					if ( ! empty( $explicit_order ) ) {
						$explicit_column = $allowed_keys['explicit_orderby_column'];
						return "FIELD( {$table_prefix}{$explicit_column}, {$explicit_order} )";
					} else {
						return '';
					}
				}
			}
		}

		if ( ! empty( $meta_key ) ) {
			$allowed_keys[ $meta_key ] = "$wpdb->postmeta.meta_value";
			$allowed_keys['meta_value'] = "$wpdb->postmeta.meta_value";
			$allowed_keys['meta_value_num'] = "$wpdb->postmeta.meta_value+0";
		}

		$obmatches = preg_split('/\s*,\s*/', trim($query_parameters['orderby']));
		foreach ( $obmatches as $index => $value ) {
			$count = preg_match('/([a-z0-9_]+)(\s+(ASC|DESC))?/i', $value, $matches);
			if ( $count && ( $value == $matches[0] ) ) {
				$matches[1] = strtolower( $matches[1] );
				if ( isset( $matches[2] ) ) {
					$matches[2] = strtoupper( $matches[2] );
				}

				if ( array_key_exists( $matches[1], $allowed_keys ) ) {
					if ( ( 'rand' == $matches[1] ) || ( 'random' == $matches[1] ) ){
							$results[] = 'RAND()';
					} else {
						switch ( $matches[1] ) {
							case $meta_key:
							case 'meta_value':
								$matches[1] = "$wpdb->postmeta.meta_value";
								break;
							case 'meta_value_num':
								$matches[1] = "$wpdb->postmeta.meta_value+0";
								break;
							default:
								if ( in_array( $matches[1], $binary_keys ) ) {
									$matches[1] = 'BINARY ' . $table_prefix . $allowed_keys[ $matches[1] ];
								} else {
									$matches[1] = $table_prefix . $allowed_keys[ $matches[1] ];
								}
						} // switch $matches[1]

						$results[] = isset( $matches[2] ) ? $matches[1] . $matches[2] : $matches[1] . $order;
					} // not 'rand'
				} // allowed key
			} // valid column specification
		} // foreach $obmatches

		$orderby = implode( ', ', $results );
		if ( empty( $orderby ) ) {
			return false;
		}

		return $orderby;
	}

	/**
	 * Data selection parameters for the WP_Query in [mla_gallery]
	 *
	 * @since 2.20
	 *
	 * @var	array
	 */
	private static $mla_get_shortcode_attachments_parameters = array(
			'order' => 'ASC', // or 'DESC' or 'RAND'
			'orderby' => 'menu_order,ID',
			'id' => NULL,
			'ids' => array(),
			'include' => array(),
			'exclude' => array(),
			// MLA extensions, from WP_Query
			// Force 'get_children' style query
			'post_parent' => NULL, // post/page ID, 'none', 'any', 'current' or 'all'
			// Author
			'author' => NULL,
			'author_name' => '',
			// Category
			'cat' => 0,
			'category_name' => '',
			'category__and' => array(),
			'category__in' => array(),
			'category__not_in' => array(),
			// Tag
			'tag' => '',
			'tag_id' => 0,
			'tag__and' => array(),
			'tag__in' => array(),
			'tag__not_in' => array(),
			'tag_slug__and' => array(),
			'tag_slug__in' => array(),
			// Taxonomy parameters are handled separately
			// {tax_slug} => 'term' | array ( 'term', 'term', ... )
			// 'tax_query' => ''
			// 'tax_input' => ''
			// 'tax_relation' => 'OR', 'AND' (default),
			// 'tax_operator' => 'OR' (default), 'IN', 'NOT IN', 'AND',
			// 'tax_include_children' => true (default), false
			// Post 
			'post_type' => 'attachment',
			'post_status' => 'inherit',
			'post_mime_type' => 'image',
			// Pagination - no default for most of these
			'nopaging' => true,
			'numberposts' => 0,
			'posts_per_page' => 0,
			'posts_per_archive_page' => 0,
			'paged' => NULL, // page number or 'current'
			'offset' => NULL,
			'mla_page_parameter' => 'mla_paginate_current',
			'mla_paginate_current' => NULL,
			'mla_paginate_total' => NULL,
			// Date and Time Queries
			'year' => '',
			'monthnum' => '',
			'w' => '',
			'day' => '',
			'm' => '',
			'date_query' => '',
			// Custom Field
			'meta_key' => '',
			'meta_value' => '',
			'meta_value_num' => NULL,
			'meta_compare' => '',
			'meta_query' => '',
			// Terms Search
			'mla_terms_phrases' => '',
			'mla_terms_taxonomies' => '',
			'mla_phrase_delimiter' => '',
			'mla_phrase_connector' => '',
			'mla_term_delimiter' => '',
			'mla_term_connector' => '',
			// Search
			's' => '',
			'mla_search_fields' => '',
			'mla_search_connector' => '',
			'whole_word' => '',
			'sentence' => '',
			'exact' => '',
			// Returned fields, for support topic "Adding 'fields' to function mla_get_shortcode_attachments" by leoloso
			'fields' => '',
			// Caching parameters, for support topic "Lag in attachment categories" by Ruriko
			'cache_results' => NULL,
			'update_post_meta_cache' => NULL,
			'update_post_term_cache' => NULL,
			'mla_allow_rml' => false,
			'mla_rml_folder' => NULL,
			'mla_rml_include_children' => false,
		);

	/**
	 * Data selection parameters for the WP_Query in [mla_gallery]
	 *
	 * @since 2.40
	 *
	 * @var	array
	 */
	private static $mla_get_shortcode_dynamic_attachments_parameters = array(
			// Taxonomy parameters are handled separately
			// {tax_slug} => 'term' | array ( 'term', 'term', ... )
			// 'tax_query' => ''
			// 'tax_relation' => 'OR', 'AND' (default),
			// 'tax_operator' => 'OR' (default), 'IN', 'NOT IN', 'AND',
			// 'tax_include_children' => true (default), false
		);

	/**
	 * For Paid Memberships Pro plugin, Hide the 'attachment' post type
	 * from searches and archives if membership is required to access.
	 *
	 * @since 2.84
	 *
	 * @param array Post types to be screened for membership
	 */

	public static function mla_pmp_hide_attachments_filter( $post_types ) {
		$post_types[] = 'attachment';
		return $post_types;

	}

	/**
	 * Parses shortcode parameters and returns the gallery objects
	 *
	 * @since 2.20
	 *
	 * @param int Post ID of the parent
	 * @param array Attributes of the shortcode
	 * @param boolean true to calculate and return ['found_rows', 'max_num_pages'] as array elements
	 *
	 * @return array List of attachments returned from WP_Query
	 */
	public static function mla_get_shortcode_attachments( $post_parent, $attr, $return_found_rows = NULL ) {
		global $wp_query;

		// Parameters passed to the join, where and orderby filter functions
		self::$query_parameters = array( MLAQuery::MLA_ALT_TEXT_SUBQUERY => false, MLAQuery::MLA_FILE_SUBQUERY => false, );

		// Parameters passed to the posts_search filter function in MLAData
		MLAQuery::$search_parameters = array( 'debug' => 'none' );

		// Make sure $attr is an array, even if it's empty
		if ( empty( $attr ) ) {
			$attr = array();
		} elseif ( is_string( $attr ) ) {
			$attr = shortcode_parse_atts( $attr );
		}

		/*
		 * The "where used" queries have no $_REQUEST context available to them,
		 * so tax_, date_ and meta_query evaluation will fail if they contain "{+request:"
		 * parameters. Ignore these errors.
		 */
		if ( isset( $attr['where_used_query'] ) && ( 'this-is-a-where-used-query' == $attr['where_used_query'] ) ) {
			$where_used_query = true;
			unset( $attr['where_used_query'] );

			// remove pagination parameters to get a complete result
			$attr['nopaging'] = true;
			unset( $attr['numberposts'] );
			unset( $attr['posts_per_page'] );
			unset( $attr['posts_per_archive_page'] );
			unset( $attr['paged'] );
			unset( $attr['offset'] );
			unset( $attr['mla_paginate_current'] );
			unset( $attr['mla_page_parameter'] );
			unset( $attr['mla_paginate_total'] );

			// There's no point in sorting the items
			$attr['orderby'] = 'none';
		} else {
			$where_used_query = false;
		}

		/*
		 * Merge input arguments with defaults, then extract the query arguments.
		 *
		 * $return_found_rows is used to indicate that the call comes from gallery_shortcode(),
		 * which is the only call that supplies it.
		 */
		if ( ! is_null( $return_found_rows ) ) {
			$attr = apply_filters( 'mla_gallery_query_attributes', $attr );
		}

		$arguments = shortcode_atts( self::$mla_get_shortcode_attachments_parameters, $attr );
		$mla_page_parameter = $arguments['mla_page_parameter'];
		unset( $arguments['mla_page_parameter'] );

		// Convert to boolean, detect mla_rml_folder
		$arguments['mla_allow_rml'] = 'true' === ( ( ! empty( $arguments['mla_allow_rml'] ) ) ? trim( strtolower( $arguments['mla_allow_rml'] ) ) : 'false' );
		$arguments['mla_rml_include_children'] = 'true' === ( ( ! empty( $arguments['mla_rml_include_children'] ) ) ? trim( strtolower( $arguments['mla_rml_include_children'] ) ) : 'false' );
		if ( ! empty( $arguments['mla_rml_folder'] ) ) {
			$arguments['mla_rml_folder'] = absint( $arguments['mla_rml_folder'] );
			$arguments['mla_allow_rml'] = 0 < $arguments['mla_rml_folder'];
		}

		/*
		 * $mla_page_parameter, if set, doesn't make it through the shortcode_atts filter,
		 * so we handle it separately
		 */
		if ( ! isset( $arguments[ $mla_page_parameter ] ) ) {
			if ( isset( $attr[ $mla_page_parameter ] ) ) {
				$arguments[ $mla_page_parameter ] = $attr[ $mla_page_parameter ];
			} else {
				$arguments[ $mla_page_parameter ] = NULL;
			}
		}

		if ( ! empty( $arguments['ids'] ) ) {
			// 'ids' is explicitly ordered, unless you specify otherwise.
			if ( empty( $attr['orderby'] ) ) {
				$arguments['orderby'] = 'post__in';
			}

			$arguments['include'] = $arguments['ids'];
		}
		unset( $arguments['ids'] );

		if ( ! is_null( $return_found_rows ) ) {
			$arguments = apply_filters( 'mla_gallery_query_arguments', $arguments );
		}

		// Extract taxonomy arguments
		self::$mla_get_shortcode_dynamic_attachments_parameters = array();
		$query_arguments = array();
		$no_terms_assigned_query = false;
		if ( ! empty( $attr ) ) {
			$all_taxonomies = get_taxonomies( array ( 'show_ui' => true ), 'names' );
			$simple_tax_queries = array();
			foreach ( $attr as $key => $value ) {
				if ( 'tax_query' === $key ) {
					// An empty query should be ignored, as is an empty "simple tax query"
					if ( empty( $value ) ) {
						continue;
					}

					if ( is_array( $value ) ) {
						$query_arguments[ $key ] = $value;
						self::$mla_get_shortcode_dynamic_attachments_parameters[ $key ] = $value;
					} else {
						$value = self::_sanitize_query_specification( $value );

						// Replace invalid queries from "where-used" callers with a harmless equivalent
						if ( $where_used_query && ( ( 'false' === $value ) || ( false !== strpos( $value, '{+' ) ) ) ) {
							$value = "array( array( 'taxonomy' => 'none', 'field' => 'slug', 'terms' => 'none' ) )";
						} else {
							// If sanitization fails, return an error message
							if ( 'false' === $value ) {
								return '<p>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . __( 'Invalid mla_gallery', 'media-library-assistant' ) . ' tax_query = ' . self::$array_specification_error . '</p>';
							}
						}

						try {
							$tax_query = @eval( 'return ' . $value . ';' );
						} catch ( Throwable $e ) { // PHP 7+
							$tax_query = NULL;
						} catch ( Exception $e ) { // PHP 5
							$tax_query = NULL;
						}

						if ( is_array( $tax_query ) ) {
							// Check for no.terms.assigned
							foreach ( $tax_query as $tax_query_key => $tax_query_element ) {
								if ( !is_array( $tax_query_element ) ) {
									continue;
								}

								if ( isset( $tax_query_element['taxonomy'] ) ) {
									$tax_query_taxonomy = $tax_query_element['taxonomy'];
								} else {
									continue;
								}

								if ( isset( $tax_query_element['terms'] ) && is_array( $tax_query_element['terms'] ) && in_array( 'no.terms.assigned', $tax_query_element['terms'] ) ) {
									$tax_query[ $tax_query_key ]['terms'] = MLAQuery::mla_wp_get_terms( $tax_query_taxonomy, array(
										'fields' => 'ids',
										'hide_empty' => false
									) );
								}
							}

							$query_arguments[ $key ] = $tax_query;
							self::$mla_get_shortcode_dynamic_attachments_parameters[ $key ] = $value;
							break; // Done - the tax_query overrides all other taxonomy parameters
						} else {
							return '<p>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . __( 'Invalid mla_gallery', 'media-library-assistant' ) . ' tax_query = ' . var_export( $value, true ) . '</p>';
						} // generated value is not an array
					} // $tax_query is a string, not array
				}  // attr is 'tax_query'
				elseif ( 'tax_input' == $key ) {
					$tax_queries = array();
					$compound_values = array_filter( array_map( 'trim', explode( ',', $value ) ) );
					foreach ( $compound_values as $compound_value ) {
						$value = explode( '.', $compound_value );
						if ( 2 === count( $value ) ) {
							if ( array_key_exists( $value[0], $all_taxonomies ) ) {
								$tax_queries[ $value[0] ][] = $value[1];
							} // valid taxonomy
						} // valid coumpound value
					} // foreach compound_value

					foreach( $tax_queries as $key => $value ) {
						$simple_tax_queries[ $key ] = implode(',', $value );
					}
				} // tax_input
				elseif ( array_key_exists( $key, $all_taxonomies ) ) {
					$simple_tax_queries[ $key ] = implode(',', array_filter( array_map( 'trim', explode( ',', $value ) ) ) );
					if ( 'no.terms.assigned' === $simple_tax_queries[ $key ] ) {
						$no_terms_assigned_query = true;
					}
				} // array_key_exists
			} //foreach $attr

			/*
			 * One of five outcomes:
			 * 1) An explicit tax_query was found; use it and ignore all other taxonomy parameters
			 * 2) No tax query is present; no further processing required
			 * 3) Two or more simple tax queries are present; compose a tax_query
			 * 4) One simple tax query and (tax_operator or tax_include_children) are present; compose a tax_query
			 * 5) One simple tax query is present; use it as-is or convert 'category' to 'category_name'
			 */
			if ( isset( $query_arguments['tax_query'] ) || empty( $simple_tax_queries ) ) {
				// No further action required
			} elseif ( ( 1 < count( $simple_tax_queries ) ) || isset( $attr['tax_operator'] ) || isset( $attr['tax_include_children'] ) || $no_terms_assigned_query ) {
				// Build a tax_query
				if  ( 1 < count( $simple_tax_queries ) ) {
					$tax_relation = 'AND';
					if ( isset( $attr['tax_relation'] ) ) {
						if ( 'OR' == strtoupper( $attr['tax_relation'] ) ) {
							$tax_relation = 'OR';
						}
					}
					$tax_query = array ('relation' => $tax_relation );
				} else {
					$tax_query = array ();
				}

				// Validate other tax_query parameters or set defaults
				$tax_operator = 'IN';
				if ( isset( $attr['tax_operator'] ) ) {
					$attr_value = strtoupper( $attr['tax_operator'] );
					if ( in_array( $attr_value, array( 'IN', 'NOT IN', 'AND' ) ) ) {
						$tax_operator = $attr_value;
					}
				}

				$tax_include_children = true;
				if ( isset( $attr['tax_include_children'] ) ) {
					if ( 'false' == strtolower( $attr['tax_include_children'] ) ) {
						$tax_include_children = false;
					}
				}

				foreach( $simple_tax_queries as $key => $value ) {
					if ( empty( $value ) ) {
						continue;
					}

					if ( 'no.terms.assigned' === $value ) {
						$term_list = MLAQuery::mla_wp_get_terms( $key, array(
							'fields' => 'ids',
							'hide_empty' => false
						) );

						$tax_query[] = array(
							'taxonomy' => $key,
							'field' => 'id',
							'terms' => $term_list,
							'operator' => 'NOT IN' 
						);

						continue;
					}

					$tax_query[] =	array( 'taxonomy' => $key, 'field' => 'slug', 'terms' => explode( ',', $value ), 'operator' => $tax_operator, 'include_children' => $tax_include_children );
				}

				$query_arguments['tax_query'] = $tax_query;
				self::$mla_get_shortcode_dynamic_attachments_parameters['tax_query'] = $tax_query;
			} else {
				// exactly one simple query is present
				if ( isset( $simple_tax_queries['category'] ) ) {
					$arguments['category_name'] = $simple_tax_queries['category'];
				} else {
					$query_arguments = $simple_tax_queries;
					self::$mla_get_shortcode_dynamic_attachments_parameters = $simple_tax_queries;
				}
			}
		} // ! empty

		// Finish building the dynamic parameters
		if ( isset( $attr['tax_relation'] ) ) {
			self::$mla_get_shortcode_dynamic_attachments_parameters['tax_relation'] = $attr['tax_relation'];
		}

		if ( isset( $attr['tax_operator'] ) ) {
			self::$mla_get_shortcode_dynamic_attachments_parameters['tax_operator'] = $attr['tax_operator'];
		}

		if ( isset( $attr['tax_include_children'] ) ) {
			self::$mla_get_shortcode_dynamic_attachments_parameters['tax_include_children'] = $attr['tax_include_children'];
		}

		// Convert lists to arrays, if they have more than one element
		if ( is_string( $arguments['post_type'] ) ) {
			$value = explode( ',', $arguments['post_type'] );
			if ( 1 < count( $value ) ) {
				$arguments['post_type'] = $value;
			}
		}

		if ( is_string( $arguments['post_status'] ) ) {
			$value = explode( ',', $arguments['post_status'] );
			if ( 1 < count( $value ) ) {
				$arguments['post_status'] = $value;
			}
		}

		// $query_arguments has been initialized in the taxonomy code above.
		$is_tax_query = ! ($use_children = empty( $query_arguments ));
		foreach ($arguments as $key => $value ) {
			/*
			 * There are several "fallthru" cases in this switch statement that decide 
			 * whether or not to limit the query to children of a specific post.
			 */
			$children_ok = true;
			switch ( $key ) {
			case 'post_parent':
				switch ( strtolower( $value ) ) {
				case 'all':
					$value = NULL;
					$use_children = false;
					break;
				case 'any':
					self::$query_parameters['post_parent'] = 'any';
					$value = NULL;
					$use_children = false;
					break;
				case 'current':
					$value = $post_parent;
					$use_children = true;
					break;
				case 'none':
					self::$query_parameters['post_parent'] = 'none';
					$value = NULL;
					$use_children = false;
					break;
				default:
					if ( false !== strpos( $value, ',' ) ) {
						self::$query_parameters['post_parent'] = array_filter( array_map( 'absint', explode( ',', $value ) ) );
						$value = NULL;
						$use_children = false;
					}
				}
				// fallthru
			case 'id':
				if ( is_numeric( $value ) ) {
					$query_arguments[ $key ] = (int) $value;
					if ( ! $children_ok ) {
						$use_children = false;
					}
				}
				unset( $arguments[ $key ] );
				break;
			case 'numberposts':
			case 'posts_per_page':
			case 'posts_per_archive_page':
				if ( is_numeric( $value ) ) {
					$value =  (int) $value;
					if ( ! empty( $value ) ) {
						$query_arguments[ $key ] = $value;
					}
				}
				unset( $arguments[ $key ] );
				break;
			case 'meta_value_num':
				$children_ok = false;
				// fallthru
			case 'offset':
				if ( is_numeric( $value ) ) {
					$query_arguments[ $key ] = (int) $value;
					if ( ! $children_ok ) {
						$use_children = false;
					}
				}
				unset( $arguments[ $key ] );
				break;
			case 'paged':
				if ( 'current' == strtolower( $value ) ) {
					/*
					 * Note: The query variable 'page' holds the pagenumber for a single paginated
					 * Post or Page that includes the <!--nextpage--> Quicktag in the post content. 
					 */
					if ( get_query_var('page') ) {
						$query_arguments[ $key ] = get_query_var('page');
					} else {
						$query_arguments[ $key ] = (get_query_var('paged')) ? get_query_var('paged') : 1;
					}
				} elseif ( is_numeric( $value ) ) {
					$query_arguments[ $key ] = (int) $value;
				} elseif ( '' === $value ) {
					$query_arguments[ $key ] = 1;
				}

				unset( $arguments[ $key ] );
				break;
			case  $mla_page_parameter :
			case 'mla_paginate_total':
				if ( is_numeric( $value ) ) {
					$query_arguments[ $key ] = (int) $value;
				} elseif ( '' === $value ) {
					$query_arguments[ $key ] = 1;
				}

				unset( $arguments[ $key ] );
				break;
			case 'author':
			case 'cat':
			case 'tag_id':
				if ( ! empty( $value ) ) {
					if ( is_array( $value ) ) {
						$query_arguments[ $key ] = array_filter( $value );
					} else {
						$query_arguments[ $key ] = array_filter( array_map( 'intval', explode( ",", $value ) ) );
					}

					if ( 1 == count( $query_arguments[ $key ] ) ) {
						$query_arguments[ $key ] = $query_arguments[ $key ][0];
					} else {
						$query_arguments[ $key ] = implode(',', $query_arguments[ $key ] );
					}

					$use_children = false;
				}
				unset( $arguments[ $key ] );
				break;
			case 'category__and':
			case 'category__in':
			case 'category__not_in':
			case 'tag__and':
			case 'tag__in':
			case 'tag__not_in':
			case 'include':
				$children_ok = false;
				// fallthru
			case 'exclude':
				if ( ! empty( $value ) ) {
					if ( is_array( $value ) ) {
						$value = array_filter( $value );
					} else {
						$value = array_filter( array_map( 'intval', explode( ",", $value ) ) );
					}

					if ( ! empty( $value ) ) {
						$query_arguments[ $key ] = $value;

						if ( ! $children_ok ) {
							$use_children = false;
						}
					}
				}
				unset( $arguments[ $key ] );
				break;
			case 'tag_slug__and':
			case 'tag_slug__in':
				if ( ! empty( $value ) ) {
					if ( is_array( $value ) ) {
						$query_arguments[ $key ] = $value;
					} else {
						$query_arguments[ $key ] = array_filter( array_map( 'trim', explode( ",", $value ) ) );
					}

					$use_children = false;
				}
				unset( $arguments[ $key ] );
				break;
			case 'nopaging': // boolean value, default false
				if ( ! empty( $value ) && ( 'false' != strtolower( $value ) ) ) {
					$query_arguments[ $key ] = true;
				}

				unset( $arguments[ $key ] );
				break;
			// boolean values, default true
			case 'cache_results':
			case 'update_post_meta_cache':
			case 'update_post_term_cache':
				if ( ! empty( $value ) && ( 'true' != strtolower( $value ) ) ) {
					$query_arguments[ $key ] = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'whole_word':
			case 'sentence':
			case 'exact':
				if ( ! empty( $value ) && ( 'true' == strtolower( $value ) ) ) {
					MLAQuery::$search_parameters[ $key ] = true;
				} else {
					MLAQuery::$search_parameters[ $key ] = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'mla_search_connector':
			case 'mla_phrase_connector':
			case 'mla_term_connector':
				if ( ! empty( $value ) && ( 'OR' == strtoupper( $value ) ) ) {
					MLAQuery::$search_parameters[ $key ] = 'OR';
				} else {
					MLAQuery::$search_parameters[ $key ] = 'AND';
				}

				unset( $arguments[ $key ] );
				break;
			case 'mla_phrase_delimiter':
			case 'mla_term_delimiter':
				if ( ! empty( $value ) ) {
					MLAQuery::$search_parameters[ $key ] = substr( $value, 0, 1 );
				}

				unset( $arguments[ $key ] );
				break;
			case 'mla_terms_phrases':
				$children_ok = false;
				$value = stripslashes( trim( $value ) );
				// fallthru
			case 'mla_terms_taxonomies':
			case 'mla_search_fields':
				if ( ! empty( $value ) ) {
					MLAQuery::$search_parameters[ $key ] = $value;

					if ( ! $children_ok ) {
						$use_children = false;
					}
				}

				unset( $arguments[ $key ] );
				break;
			case 's':
				MLAQuery::$search_parameters['s'] = stripslashes( trim( $value ) );
				// fallthru
			case 'author_name':
			case 'category_name':
			case 'tag':
			case 'meta_key':
			case 'meta_value':
			case 'meta_compare':
				$children_ok = false;
				// fallthru
			case 'post_type':
			case 'post_status':
			case 'post_mime_type':
			case 'orderby':
				if ( ! empty( $value ) ) {
					$query_arguments[ $key ] = $value;

					if ( ! $children_ok ) {
						$use_children = false;
					}
				}

				unset( $arguments[ $key ] );
				break;
			case 'order':
				if ( ! empty( $value ) ) {
					$value = strtoupper( $value );
					if ( in_array( $value, array( 'ASC', 'DESC' ) ) ) {
						$query_arguments[ $key ] = $value;
					}
				}

				unset( $arguments[ $key ] );
				break;
			case 'year': // 4 digit year, e.g., 2021
				if ( 4 === strlen( $value ) && is_numeric( $value ) ) {
					$query_arguments[ $key ] = (int) $value;
					$use_children = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'monthnum': // Month number (from 1 to 12)
				$value = absint( $value );
				if ( ( 0 < $value ) && ( 13 > $value ) ) {
					$query_arguments[ $key ] = $value;
					$use_children = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'w': // Week of the year (from 0 to 53). Uses MySQL WEEK command. The mode is dependent on the “start_of_week” option.
				$value = absint( $value );
				if ( ( 0 < $value ) && ( 54 > $value ) ) {
					$query_arguments[ $key ] = $value;
					$use_children = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'day': // Day of the month (from 1 to 31)
				$value = absint( $value );
				if ( ( 0 < $value ) && ( 32 > $value ) ) {
					$query_arguments[ $key ] = $value;
					$use_children = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'm': //YearMonth, e.g., 202101
				if ( 6 === strlen( $value ) && is_numeric( $value ) ) {
					$query_arguments[ $key ] = (int) $value;
					$use_children = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'date_query':
				if ( ! empty( $value ) ) {
					if ( is_array( $value ) ) {
						$query_arguments[ $key ] = $value;
					} else {
						$value = self::_sanitize_query_specification( $value );

						// Replace invalid queries from "where-used" callers with a harmless equivalent
						if ( $where_used_query && ( ( 'false' === $value ) || ( false !== strpos( $value, '{+' ) ) ) ) {
							$value = "array( array( 'key' => 'unlikely', 'value' => 'none or otherwise unlikely' ) )";
						} else {
							// If sanitization fails, return an error message
							if ( 'false' === $value ) {
								return '<p>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . __( 'Invalid mla_gallery', 'media-library-assistant' ) . ' date_query = ' . self::$array_specification_error . '</p>';
							}
						}

						try {
							$date_query = @eval( 'return ' . $value . ';' );
						} catch ( Throwable $e ) { // PHP 7+
							$date_query = NULL;
						} catch ( Exception $e ) { // PHP 5
							$date_query = NULL;
						}

						if ( is_array( $date_query ) ) {
							$query_arguments[ $key ] = $date_query;
						} else {
							return '<p>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . __( 'Invalid mla_gallery', 'media-library-assistant' ) . ' date_query = ' . var_export( $value, true ) . '</p>';
						}
					} // not array

					$use_children = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'meta_query':
				if ( ! empty( $value ) ) {
					if ( is_array( $value ) ) {
						$query_arguments[ $key ] = $value;
					} else {
						$value = self::_sanitize_query_specification( $value );

						// Replace invalid queries from "where-used" callers with a harmless equivalent
						if ( $where_used_query && ( ( 'false' === $value ) || ( false !== strpos( $value, '{+' ) ) ) ) {
							$value = "array( array( 'key' => 'unlikely', 'value' => 'none or otherwise unlikely' ) )";
						} else {
							// If sanitization fails, return an error message
							if ( 'false' === $value ) {
								return '<p>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . __( 'Invalid mla_gallery', 'media-library-assistant' ) . ' meta_query = ' . self::$array_specification_error . '</p>';
							}
						}

						try {
							$meta_query = @eval( 'return ' . $value . ';' );
						} catch ( Throwable $e ) { // PHP 7+
							$meta_query = NULL;
						} catch ( Exception $e ) { // PHP 5
							$meta_query = NULL;
						}

						if ( is_array( $meta_query ) ) {
							$query_arguments[ $key ] = $meta_query;
						} else {
							return '<p>' . __( 'ERROR', 'media-library-assistant' ) . ': ' . __( 'Invalid mla_gallery', 'media-library-assistant' ) . ' meta_query = ' . var_export( $value, true ) . '</p>';
						}
					} // not array

					$use_children = false;
				}

				unset( $arguments[ $key ] );
				break;
			case 'fields':
				if ( ! empty( $value ) ) {
					$value = strtolower( $value );
					if ( in_array( $value, array( 'ids', 'id=>parent' ) ) ) {
						$query_arguments[ $key ] = $value;
					}
				}

				unset( $arguments[ $key ] );
				break;
			default:
				// ignore anything else
			} // switch $key
		} // foreach $arguments 

		// Decide whether to use a "get_children" style query
		self::$query_parameters['disable_tax_join'] = $is_tax_query && ! $use_children;
		if ( $use_children && ! isset( $query_arguments['post_parent'] ) ) {
			if ( ! isset( $query_arguments['id'] ) ) {
				$query_arguments['post_parent'] = $post_parent;
			} else {
				$query_arguments['post_parent'] = $query_arguments['id'];
			}

			unset( $query_arguments['id'] );
		}

		if ( isset( $query_arguments['numberposts'] ) && ! isset( $query_arguments['posts_per_page'] )) {
			$query_arguments['posts_per_page'] = $query_arguments['numberposts'];
		}
		unset( $query_arguments['numberposts'] );

		/*
		 * Apply the archive/search tests here because WP_Query doesn't apply them to galleries within
		 * search results or archive pages.
		 */
		if ( self::$mla_debug ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>mla_debug is_archive()</strong> = ' . var_export( is_archive(), true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>mla_debug is_search()</strong> = ' . var_export( is_search(), true ) );
		}

		if ( isset( $query_arguments['posts_per_archive_page'] ) && ( is_archive() || is_search() ) ) {
			$query_arguments['posts_per_page'] = $query_arguments['posts_per_archive_page'];
		}
		unset( $query_arguments['posts_per_archive_page'] );

		// MLA pagination will override WordPress pagination
		if ( isset( $query_arguments[ $mla_page_parameter ] ) ) {
			unset( $query_arguments['nopaging'] );
			unset( $query_arguments['offset'] );
			unset( $query_arguments['paged'] );

			if ( isset( $query_arguments['mla_paginate_total'] ) && ( $query_arguments[ $mla_page_parameter ] > $query_arguments['mla_paginate_total'] ) ) {
				$query_arguments['offset'] = 0x7FFFFFFF; // suppress further output
			} else {
				$query_arguments['paged'] = $query_arguments[ $mla_page_parameter ];
			}
		} else {
			if ( isset( $query_arguments['posts_per_page'] ) || isset( $query_arguments['posts_per_archive_page'] ) ||
				isset( $query_arguments['paged'] ) || isset( $query_arguments['offset'] ) ) {
				unset( $query_arguments['nopaging'] );
			}
		}
		unset( $query_arguments[ $mla_page_parameter ] );
		unset( $query_arguments['mla_paginate_total'] );

		if ( isset( $query_arguments['post_mime_type'] ) && ('all' == strtolower( $query_arguments['post_mime_type'] ) ) ) {
			unset( $query_arguments['post_mime_type'] );
		}

		if ( ! empty($query_arguments['include']) ) {
			$incposts = wp_parse_id_list( $query_arguments['include'] );

			if ( ! ( isset( $query_arguments['posts_per_page'] ) && ( 0 < $query_arguments['posts_per_page'] ) ) ) {
				$query_arguments['posts_per_page'] = count($incposts);  // only the number of posts included
			}

			$query_arguments['post__in'] = $incposts;
			unset( $query_arguments['include'] );
		} elseif ( ! empty($query_arguments['exclude']) ) {
			$query_arguments['post__not_in'] = wp_parse_id_list( $query_arguments['exclude'] );
			unset( $query_arguments['exclude'] );
		}

		$query_arguments['ignore_sticky_posts'] = true;
		$query_arguments['no_found_rows'] = is_null( $return_found_rows ) ? true : ! $return_found_rows;

		// We will always handle "orderby" in our filter
		self::$query_parameters['orderby'] = self::_validate_sql_orderby( $query_arguments );
		if ( false === self::$query_parameters['orderby'] ) {
			unset( self::$query_parameters['orderby'] );
		}

		unset( $query_arguments['orderby'] );
		unset( $query_arguments['order'] );

		if ( self::$mla_debug ) {
			add_filter( 'posts_clauses', 'MLAShortcode_Support::mla_shortcode_query_posts_clauses_filter', 0x7FFFFFFF, 1 );
			add_filter( 'posts_clauses_request', 'MLAShortcode_Support::mla_shortcode_query_posts_clauses_request_filter', 0x7FFFFFFF, 1 );
		}

		add_filter( 'posts_join', 'MLAShortcode_Support::mla_shortcode_query_posts_join_filter', 0x7FFFFFFF, 1 );
		add_filter( 'posts_where', 'MLAShortcode_Support::mla_shortcode_query_posts_where_filter', 0x7FFFFFFF, 1 );
		add_filter( 'posts_orderby', 'MLAShortcode_Support::mla_shortcode_query_posts_orderby_filter', 0x7FFFFFFF, 1 );

		/*
		 * Handle the keyword and terms search in the posts_search filter.
		 * One or both of 'mla_terms_phrases' and 's' must be present to
		 * trigger the search.
		 */
		if ( empty( MLAQuery::$search_parameters['mla_terms_phrases'] ) && empty( MLAQuery::$search_parameters['s'] ) ) {
			MLAQuery::$search_parameters = array( 'debug' => 'none' );
		} else {
			/*
			 * Convert Terms Search parameters to the filter's requirements.
			 * mla_terms_taxonomies is shared with keyword search.
			 */
			if ( empty( MLAQuery::$search_parameters['mla_terms_taxonomies'] ) ) {
				MLAQuery::$search_parameters['mla_terms_search']['taxonomies'] = MLACore::mla_supported_taxonomies( 'term-search' );
			} else {
				MLAQuery::$search_parameters['mla_terms_search']['taxonomies'] = array_filter( array_map( 'trim', explode( ',', MLAQuery::$search_parameters['mla_terms_taxonomies'] ) ) );
			}

			if ( ! empty( MLAQuery::$search_parameters['mla_terms_phrases'] ) ) {
				MLAQuery::$search_parameters['mla_terms_search']['phrases'] = MLAQuery::$search_parameters['mla_terms_phrases'];

				if ( empty( MLAQuery::$search_parameters['mla_phrase_delimiter'] ) ) {
					MLAQuery::$search_parameters['mla_terms_search']['phrase_delimiter'] = ' ';
				} else {
					MLAQuery::$search_parameters['mla_terms_search']['phrase_delimiter'] = MLAQuery::$search_parameters['mla_phrase_delimiter'];
				}

				if ( empty( MLAQuery::$search_parameters['mla_phrase_connector'] ) ) {
					MLAQuery::$search_parameters['mla_terms_search']['radio_phrases'] = 'AND';
				} else {
					MLAQuery::$search_parameters['mla_terms_search']['radio_phrases'] = MLAQuery::$search_parameters['mla_phrase_connector'];
				}

				if ( empty( MLAQuery::$search_parameters['mla_term_delimiter'] ) ) {
					MLAQuery::$search_parameters['mla_terms_search']['term_delimiter'] = ',';
				} else {
					MLAQuery::$search_parameters['mla_terms_search']['term_delimiter'] = MLAQuery::$search_parameters['mla_term_delimiter'];
				}

				if ( empty( MLAQuery::$search_parameters['mla_term_connector'] ) ) {
					MLAQuery::$search_parameters['mla_terms_search']['radio_terms'] = 'OR';
				} else {
					MLAQuery::$search_parameters['mla_terms_search']['radio_terms'] = MLAQuery::$search_parameters['mla_term_connector'];
				}

				MLAQuery::$search_parameters['mla_terms_search']['whole_word'] = ! empty( MLAQuery::$search_parameters['whole_word'] );
				MLAQuery::$search_parameters['mla_terms_search']['exact'] = ! empty( MLAQuery::$search_parameters['exact'] );
				MLAQuery::$search_parameters['mla_terms_search']['sentence'] = ! empty( MLAQuery::$search_parameters['sentence'] );
			}

			// Remove terms-search-specific parameters
			unset( MLAQuery::$search_parameters['mla_terms_phrases'] );
			unset( MLAQuery::$search_parameters['mla_terms_taxonomies'] );
			unset( MLAQuery::$search_parameters['mla_phrase_connector'] );
			unset( MLAQuery::$search_parameters['mla_term_connector'] );
			unset( MLAQuery::$search_parameters['whole_word'] );

			if ( empty( MLAQuery::$search_parameters['mla_search_fields'] ) ) {
				MLAQuery::$search_parameters['mla_search_fields'] = array( 'title', 'content' );
			} else {
				MLAQuery::$search_parameters['mla_search_fields'] = array_filter( array_map( 'trim', explode( ',', MLAQuery::$search_parameters['mla_search_fields'] ) ) );
				MLAQuery::$search_parameters['mla_search_fields'] = array_intersect( array( 'title', 'name', 'excerpt', 'content', 'alt-text', 'file', 'terms' ), MLAQuery::$search_parameters['mla_search_fields'] );

				if ( in_array( 'alt-text', MLAQuery::$search_parameters['mla_search_fields'] ) ) {
					self::$query_parameters[MLAQuery::MLA_ALT_TEXT_SUBQUERY] = true;
				}

				if ( in_array( 'file', MLAQuery::$search_parameters['mla_search_fields'] ) ) {
					self::$query_parameters[MLAQuery::MLA_FILE_SUBQUERY] = true;
				}

				// Look for keyword search including 'terms' 
				foreach ( MLAQuery::$search_parameters['mla_search_fields'] as $index => $field ) {
					if ( 'terms' == $field ) {
						if ( isset( MLAQuery::$search_parameters['mla_terms_search']['phrases'] ) ) {
							// The Terms Search overrides any terms-based keyword search for now; too complicated.
							unset ( MLAQuery::$search_parameters['mla_search_fields'][ $index ] );
						} else {
							MLAQuery::$search_parameters['mla_search_taxonomies'] = MLAQuery::$search_parameters['mla_terms_search']['taxonomies'];
							unset( MLAQuery::$search_parameters['mla_terms_search']['taxonomies'] );
						}
					} // terms in search fields
				}
			} // mla_search_fields present

			if ( empty( MLAQuery::$search_parameters['mla_search_connector'] ) ) {
				MLAQuery::$search_parameters['mla_search_connector'] = 'AND';
			}

			if ( empty( MLAQuery::$search_parameters['whole_word'] ) ) {
				MLAQuery::$search_parameters['whole_word'] = false;
			}

			if ( empty( MLAQuery::$search_parameters['sentence'] ) ) {
				MLAQuery::$search_parameters['sentence'] = false;
			}

			if ( empty( MLAQuery::$search_parameters['exact'] ) ) {
				MLAQuery::$search_parameters['exact'] = false;
			}

			MLAQuery::$search_parameters['debug'] = self::$mla_debug ? 'shortcode' : 'none';

			add_filter( 'posts_search', 'MLAQuery::mla_query_posts_search_filter', 10, 2 );
			add_filter( 'posts_groupby', 'MLAQuery::mla_query_posts_groupby_filter' );
		}

		if ( self::$mla_debug ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>mla_debug $wp_filter[posts_where]</strong> = ' . MLACore::mla_decode_wp_filter('posts_where') );
			MLACore::mla_debug_add( __LINE__ . ' <strong>mla_debug $wp_filter[posts_orderby]</strong> = ' . MLACore::mla_decode_wp_filter('posts_orderby') );
		}

		/*
		 * Disable Relevanssi - A Better Search, v3.2 by Mikko Saari 
		 * relevanssi_prevent_default_request( $request, $query )
		 * apply_filters('relevanssi_admin_search_ok', $admin_search_ok, $query );
		 * apply_filters('relevanssi_prevent_default_request', $prevent, $query );
		 */
		if ( function_exists( 'relevanssi_prevent_default_request' ) ) {
			add_filter( 'relevanssi_admin_search_ok', 'MLAQuery::mla_query_relevanssi_admin_search_ok_filter' );
			add_filter( 'relevanssi_prevent_default_request', 'MLAQuery::mla_query_relevanssi_prevent_default_request_filter' );
		}

		if ( class_exists( 'MLA_Polylang' ) ) {
			$query_arguments = apply_filters( 'mla_get_shortcode_attachments_final_terms', $query_arguments, $return_found_rows );
		}

		// Paid Memberships Pro support
		if( defined('PMPRO_VERSION') ) {
			add_filter( 'pmpro_search_filter_post_types', 'MLAShortcode_Support::mla_pmp_hide_attachments_filter' );
		}

		if ( isset( $query_arguments['post_type'] ) && is_string( $query_arguments['post_type'] ) && ( 'attachment' === $query_arguments['post_type'] ) ) {
			if ( self::$query_parameters['disable_tax_join'] ) {
				// Suppress WordPress WP_Query LEFT JOIN on post_parent, etc.
				$query_arguments['post_type'] = 'mladisabletaxjoin';
			} 
			
			if ( defined('RML_FILE') ) {
				if ( $arguments['mla_allow_rml'] ) {
					add_filter( 'posts_clauses', 'MLAShortcode_Support::mla_shortcode_query_posts_clauses_rml_filter', 9, 2 );

					if ( ! empty( $arguments['mla_rml_folder'] ) ) {
						unset( $query_arguments['post_parent'] );
						$query_arguments['rml_folder'] = $arguments['mla_rml_folder'];

						if ( $arguments['mla_rml_include_children'] ) {
							$query_arguments['rml_include_children'] = $arguments['mla_rml_include_children'];
						}
					}
				} else {
					// Suppress RML additions to MLA queries
					$query_arguments['post_type'] = 'mladisablerml';
				}
			}
		} // post_type is attachment

		MLAShortcodes::$mla_gallery_wp_query_object = new WP_Query;
		$attachments = MLAShortcodes::$mla_gallery_wp_query_object->query( $query_arguments );

		// Paid Memberships Pro support
		if( defined('PMPRO_VERSION') ) {
			remove_filter( 'pmpro_search_filter_post_types', 'MLAShortcode_Support::mla_pmp_hide_attachments_filter' );
		}

		/*
		 * $return_found_rows is used to indicate that the call comes from gallery_shortcode(),
		 * which is the only call that supplies it.
		 */
		if ( is_null( $return_found_rows ) ) {
			$return_found_rows = false;
		} else  {
			do_action( 'mla_gallery_wp_query_object', $query_arguments );

			if ( $return_found_rows ) {
				$attachments['found_rows'] = absint( MLAShortcodes::$mla_gallery_wp_query_object->found_posts );
				$attachments['max_num_pages'] = absint( MLAShortcodes::$mla_gallery_wp_query_object->max_num_pages );
			}

			$filtered_attachments = apply_filters_ref_array( 'mla_gallery_the_attachments', array( NULL, &$attachments ) ) ;
			if ( !is_null( $filtered_attachments ) ) {
				$attachments = $filtered_attachments;
			}
		}

		if ( ! empty( MLAQuery::$search_parameters ) ) {
			remove_filter( 'posts_groupby', 'MLAQuery::mla_query_posts_groupby_filter' );
			remove_filter( 'posts_search', 'MLAQuery::mla_query_posts_search_filter' );
		}

		if ( function_exists( 'relevanssi_prevent_default_request' ) ) {
			remove_filter( 'relevanssi_admin_search_ok', 'MLAQuery::mla_query_relevanssi_admin_search_ok_filter' );
			remove_filter( 'relevanssi_prevent_default_request', 'MLAQuery::mla_query_relevanssi_prevent_default_request_filter' );
		}

		remove_filter( 'posts_join', 'MLAShortcode_Support::mla_shortcode_query_posts_join_filter', 0x7FFFFFFF );
		remove_filter( 'posts_where', 'MLAShortcode_Support::mla_shortcode_query_posts_where_filter', 0x7FFFFFFF );
		remove_filter( 'posts_orderby', 'MLAShortcode_Support::mla_shortcode_query_posts_orderby_filter', 0x7FFFFFFF );

		if ( self::$mla_debug ) {
			remove_filter( 'posts_clauses', 'MLAShortcode_Support::mla_shortcode_query_posts_clauses_filter', 0x7FFFFFFF );
			remove_filter( 'posts_clauses_request', 'MLAShortcode_Support::mla_shortcode_query_posts_clauses_request_filter', 0x7FFFFFFF );

			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug query', 'media-library-assistant' ) . '</strong> = ' . var_export( $query_arguments, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug request', 'media-library-assistant' ) . '</strong> = ' . var_export( MLAShortcodes::$mla_gallery_wp_query_object->request, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug query_vars', 'media-library-assistant' ) . '</strong> = ' . var_export( MLAShortcodes::$mla_gallery_wp_query_object->query_vars, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug post_count', 'media-library-assistant' ) . '</strong> = ' . var_export( MLAShortcodes::$mla_gallery_wp_query_object->post_count, true ) );
		}

		MLAQuery::$search_parameters = array( 'debug' => 'none' );
		MLAShortcodes::$mla_gallery_wp_query_object = NULL;
		return $attachments;
	}

	/**
	 * Filters the JOIN clause for shortcode queries
	 * 
	 * Defined as public because it's a filter.
	 *
	 * @since 2.20
	 *
	 * @param	string	query clause before modification
	 *
	 * @return	string	query clause after item modification
	 */
	public static function mla_shortcode_query_posts_join_filter( $join_clause ) {
		global $wpdb;

		if ( self::$mla_debug ) {
			$old_clause = $join_clause;
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug JOIN filter', 'media-library-assistant' ) . '</strong> = ' . var_export( $join_clause, true ) );
		}

		/*
		 * Set for taxonomy queries unless post_parent=current. If true, we must disable
		 * the LEFT JOIN clause that get_posts() adds to taxonomy queries.
		 * We leave the clause in because the WHERE clauses refer to "p2.".
		 * Replaced in MLA v2.94 by "post_type = 'mladisabletaxjoin'"
		 * /
		if ( self::$query_parameters['disable_tax_join'] ) {
			$join_clause = str_replace( " LEFT JOIN {$wpdb->posts} AS p2 ON ({$wpdb->posts}.post_parent = p2.ID) ", " LEFT JOIN {$wpdb->posts} AS p2 ON (p2.ID = p2.ID) ", $join_clause );
		} // */

		// These joins support the 'terms' search_field
		if ( isset( MLAQuery::$search_parameters['tax_terms_count'] ) ) {
			$tax_index = 0;
			$tax_clause = '';

			while ( $tax_index < MLAQuery::$search_parameters['tax_terms_count'] ) {
				$prefix = 'mlatt' . $tax_index++;
				$tax_clause .= sprintf( ' INNER JOIN %1$s AS %2$s ON (%3$s.ID = %2$s.object_id)', $wpdb->term_relationships, $prefix, $wpdb->posts );
			}

			$join_clause .= $tax_clause;
		}

		/*
		 * ALT Text and File Name searches use a subquery to build an intermediate table and
		 * modify the JOIN to include posts with no value for the metadata field.
		 */
		if ( self::$query_parameters[MLAQuery::MLA_ALT_TEXT_SUBQUERY] ) {
			$sub_query = sprintf( 'SELECT post_id, meta_value FROM %1$s WHERE %1$s.meta_key = \'%2$s\'', $wpdb->postmeta, '_wp_attachment_image_alt' );
			$join_clause .= sprintf( ' LEFT JOIN ( %1$s ) %2$s ON (%3$s.ID = %2$s.post_id)', $sub_query, MLAQuery::MLA_ALT_TEXT_SUBQUERY, $wpdb->posts );
		}

		if ( self::$query_parameters[MLAQuery::MLA_FILE_SUBQUERY] ) {
			$sub_query = sprintf( 'SELECT post_id, meta_value FROM %1$s WHERE %1$s.meta_key = \'%2$s\'', $wpdb->postmeta, '_wp_attached_file' );
			$join_clause .= sprintf( ' LEFT JOIN ( %1$s ) %2$s ON (%3$s.ID = %2$s.post_id)', $sub_query, MLAQuery::MLA_FILE_SUBQUERY, $wpdb->posts );
		}

		if ( self::$mla_debug && ( $old_clause != $join_clause ) ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug modified JOIN filter', 'media-library-assistant' ) . '</strong> = ' . var_export( $join_clause, true ) );
		}

		return $join_clause;
	}

	/**
	 * Filters the WHERE clause for shortcode queries
	 * 
	 * Captures debug information. Adds whitespace to the post_type = 'attachment'
	 * phrase to circumvent subsequent Role Scoper modification of the clause.
	 * Handles post_parent "any" and "none" cases.
	 * Defined as public because it's a filter.
	 *
	 * @since 2.20
	 *
	 * @param	string	query clause before modification
	 *
	 * @return	string	query clause after modification
	 */
	public static function mla_shortcode_query_posts_where_filter( $where_clause ) {
		global $table_prefix;

		if ( self::$mla_debug ) {
			$old_clause = $where_clause;
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug WHERE filter', 'media-library-assistant' ) . '</strong> = ' . var_export( $where_clause, true ) );
		}

		// Reverse post_type modification used to avoid redundant LEFT JOIN insertion or RML folder insertion
		if ( strpos( $where_clause, "post_type = 'mladisabletaxjoin'" ) ) {
			$where_clause = str_replace( "post_type = 'mladisabletaxjoin'", "post_type = 'attachment'", $where_clause );
		} elseif ( strpos( $where_clause, "post_type = 'mladisablerml'" ) ) {
			$where_clause = str_replace( "post_type = 'mladisablerml'", "post_type = 'attachment'", $where_clause );
		}

		// Add whitespace to prevent Role Scoper plugin clause modification
		if ( strpos( $where_clause, "post_type = 'attachment'" ) ) {
			$where_clause = str_replace( "post_type = 'attachment'", "post_type  =  'attachment'", $where_clause );
		}

		if ( isset( self::$query_parameters['post_parent'] ) ) {
			if ( is_array( self::$query_parameters['post_parent'] ) ) {
				$parent_list = implode( ',', self::$query_parameters['post_parent'] );
				$where_clause .= " AND {$table_prefix}posts.post_parent IN ({$parent_list})";
			} else {
				switch ( self::$query_parameters['post_parent'] ) {
				case 'any':
					$where_clause .= " AND {$table_prefix}posts.post_parent > 0";
					break;
				case 'none':
					$where_clause .= " AND {$table_prefix}posts.post_parent < 1";
					break;
				}
			}
		}

		// Support Plugin Name: Featured Image from URL, "Media Library" option setting
	    if ( function_exists('fifu_is_off') && fifu_is_off('fifu_media_library')) {
			$where_clause .= " AND {$table_prefix}posts.post_author <> 77777";
		}

		if ( self::$mla_debug && ( $old_clause != $where_clause ) ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug modified WHERE filter', 'media-library-assistant' ) . '</strong> = ' . var_export( $where_clause, true ) );
		}

		return $where_clause;
	}

	/**
	 * Filters the ORDERBY clause for shortcode queries
	 * 
	 * This is an enhanced version of the code found in wp-includes/query.php, function get_posts.
	 * Defined as public because it's a filter.
	 *
	 * @since 2.20
	 *
	 * @param	string	query clause before modification
	 *
	 * @return	string	query clause after modification
	 */
	public static function mla_shortcode_query_posts_orderby_filter( $orderby_clause ) {
		global $wpdb;

		if ( self::$mla_debug ) {
			$replacement = isset( self::$query_parameters['orderby'] ) ? var_export( self::$query_parameters['orderby'], true ) : 'none';
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug ORDER BY filter, incoming', 'media-library-assistant' ) . '</strong> = ' . var_export( $orderby_clause, true ) . '<br>' . __( 'Replacement ORDER BY clause', 'media-library-assistant' ) . ' = ' . $replacement );
		}

		if ( isset( self::$query_parameters['orderby'] ) ) {
			return self::$query_parameters['orderby'];
		}

		return $orderby_clause;
	}

	/**
	 * Reverse mladisabletaxjoin/mladisablerml overide for RML queries
	 * 
	 * @since 2.95
	 *
	 * @param	array	query clauses before modification
	 * @param	WP_Query query object
	 */
	public static function mla_shortcode_query_posts_clauses_rml_filter( $pieces, $query ) {
		remove_filter( 'posts_clauses', 'MLAShortcode_Support::mla_shortcode_query_posts_clauses_rml_filter', 9 );

        if ( $query->get( 'post_type' ) === 'mladisabletaxjoin' ) {
            $query->set( 'post_type', 'attachment' );
		}

		return $pieces;
	}

	/**
	 * Filters all clauses for shortcode queries, pre caching plugins
	 * 
	 * This is for debug purposes only.
	 * Defined as public because it's a filter.
	 *
	 * @since 2.20
	 *
	 * @param	array	query clauses before modification
	 *
	 * @return	array	query clauses after modification (none)
	 */
	public static function mla_shortcode_query_posts_clauses_filter( $pieces ) {
		MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug posts_clauses filter', 'media-library-assistant' ) . '</strong> = ' . var_export( $pieces, true ) );

		return $pieces;
	}

	/**
	 * Filters all clauses for shortcode queries, post caching plugins
	 * 
	 * This is for debug purposes only.
	 * Defined as public because it's a filter.
	 *
	 * @since 2.20
	 *
	 * @param	array	query clauses before modification
	 *
	 * @return	array	query clauses after modification (none)
	 */
	public static function mla_shortcode_query_posts_clauses_request_filter( $pieces ) {
		MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug posts_clauses_request filter', 'media-library-assistant' ) . '</strong> = ' . var_export( $pieces, true ) );

		return $pieces;
	}

	/**
	 * Data selection parameters for [mla_tag_cloud], [mla_term_list]
	 *
	 * @since 2.20
	 *
	 * @var	array
	 */
	private static $mla_get_terms_parameters = array(
		'taxonomy' => 'post_tag',
		'post_mime_type' => 'all',
		'post_type' => 'attachment',
		'post_status' => 'inherit',
		'ids' => array(),
		'fields' => 't.term_id, t.name, t.slug, t.term_group, tt.term_taxonomy_id, tt.taxonomy, tt.description, tt.parent, COUNT(p.ID) AS `count`',
		'include' => '',
		'exclude' => '',
		'parent' => '',
		'minimum' => 0,
		'no_count' => false,
		'number' => 0,
		'orderby' => 'name',
		'order' => 'ASC',
		'no_orderby' => false,
		'preserve_case' => false,
		'pad_counts' => false,
		'limit' => 0,
		'offset' => 0
	);


	/**
	 * Retrieve the terms in one or more taxonomies.
	 *
	 * Alternative to WordPress /wp-includes/taxonomy.php function get_terms() that provides
	 * an accurate count of attachments associated with each term.
	 *
	 * taxonomy - string containing one or more (comma-delimited) taxonomy names
	 * or an array of taxonomy names. Default 'post_tag'.
	 *
	 * post_mime_type - MIME type(s) of the items to include in the term-specific counts. Default 'all'.
	 *
	 * post_type - The post type(s) of the items to include in the term-specific counts.
	 * The default is "attachment". 
	 *
	 * post_status - The post status value(s) of the items to include in the term-specific counts.
	 * The default is "inherit".
	 *
	 * ids - A comma-separated list of attachment ID values for an item-specific cloud.
	 *
	 * include - An array, comma- or space-delimited string of term ids to include
	 * in the return array.
	 *
	 * exclude - An array, comma- or space-delimited string of term ids to exclude
	 * from the return array. If 'include' is non-empty, 'exclude' is ignored.
	 *
	 * parent - term_id of the terms' immediate parent; 0 for top-level terms.
	 *
	 * minimum - minimum number of attachments a term must have to be included. Default 0.
	 *
	 * no_count - 'true', 'false' (default) to suppress term-specific attachment-counting process.
	 *
	 * number - maximum number of term objects to return. Terms are ordered by count,
	 * descending and then by term_id before this value is applied. Default 0.
	 *
	 * orderby - 'count', 'id', 'name' (default), 'none', 'random', 'slug'
	 *
	 * order - 'ASC' (default), 'DESC'
	 *
	 * no_orderby - 'true', 'false' (default) to suppress ALL sorting clauses else false.
	 *
	 * preserve_case - 'true', 'false' (default) to make orderby case-sensitive.
	 *
	 * pad_counts - 'true', 'false' (default) to to include the count of all children in their parents' count.
	 *
	 * limit - final number of term objects to return, for pagination. Default 0.
	 *
	 * offset - number of term objects to skip, for pagination. Default 0.
	 *
	 * fields - string with fields for the SQL SELECT clause, e.g.,
	 *          't.term_id, t.name, t.slug, COUNT(p.ID) AS `count`'
	 *
	 * @since 2.20
	 *
	 * @param	array	taxonomies to search and query parameters
	 *
	 * @return	array	array of term objects, empty if none found
	 */
	public static function mla_get_terms( $attr ) {
		global $wpdb;

		// Make sure $attr is an array, even if it's empty
		if ( empty( $attr ) ) {
			$attr = array();
		} elseif ( is_string( $attr ) ) {
			$attr = shortcode_parse_atts( $attr );
		}

		// Merge input arguments with defaults
		$attr = apply_filters( 'mla_get_terms_query_attributes', $attr );
		$arguments = shortcode_atts( self::$mla_get_terms_parameters, $attr );
		$arguments = apply_filters( 'mla_get_terms_query_arguments', $arguments );

		// Build an array of individual clauses that can be filtered
		$clauses = array( 'fields' => '', 'join' => '', 'where' => '', 'orderby' => '', 'limits' => '', );

		/*
		 * If we're not counting attachments per term, strip
		 * post fields out of list and adjust the orderby value
		 */
		$no_count = true;
		$count_string = trim( strtolower( (string) $arguments['no_count'] ) );
		$field_array = explode( ',', $arguments['fields'] );

		switch ( $count_string ) {
			case 'true':
				foreach ( $field_array as $index => $field ) {
					if ( false !== strpos( $field, 'p.' ) ) {
						unset( $field_array[ $index ] );
					}
				}

				$arguments['minimum'] = 0;
				$arguments['post_mime_type'] = 'all';

				if ( 'count' == strtolower( $arguments['orderby'] ) ) {
					$arguments['orderby'] = 'none';
				}

				break;
			case 'internal':
				foreach ( $field_array as $index => $field ) {
					if ( false !== strpos( $field, 'p.' ) ) {
						unset( $field_array[ $index ] );
					}
				}

				$field_array[] = ' tt.count';
				$arguments['post_mime_type'] = 'all';
				break;
			default:
				$no_count = false;
		}

		// Support Simple Taxonomy Ordering plugin
		if ( 'tax_position' === strtolower( $arguments['orderby'] ) ) {
			if ( class_exists( 'Yikes_Custom_Taxonomy_Order', false ) ) {
				$field_array[] = ' term_meta.meta_value AS tax_position';
			} else {
				$arguments['orderby'] = 'name';
			}
		}

		// Support Simple Custom Post Order plugin
		if ( 'term_order' === strtolower( $arguments['orderby'] ) ) {
			if ( class_exists( 'SCPO_Engine', false ) ) {
				$field_array[] = ' t.term_order';
			} else {
				$arguments['orderby'] = 'name';
			}
		}

		$clauses['fields'] = implode( ',', $field_array );
		$clause = array ( 'INNER JOIN `' . $wpdb->term_taxonomy . '` AS tt ON t.term_id = tt.term_id' );
		$clause_parameters = array();

		if ( $no_count ) {
			// If no_count=internal we just omit the explicit count and use the WP-maintained count
			$no_count = 'true' === $count_string;

			if ( 'internal' === $count_string ) {
				// The ids parameter requires item-specific ID values
				if ( ! empty( $arguments['ids'] ) && empty( $arguments['include'] ) ) {
					$clause[] = 'LEFT JOIN `' . $wpdb->term_relationships . '` AS tr ON tt.term_taxonomy_id = tr.term_taxonomy_id';
				}
			}
		} else {
			$clause[] = 'LEFT JOIN `' . $wpdb->term_relationships . '` AS tr ON tt.term_taxonomy_id = tr.term_taxonomy_id';
			$clause[] = 'LEFT JOIN `' . $wpdb->posts . '` AS p ON ( tr.object_id = p.ID';

			// Add type and status constraints
			if ( is_array( $arguments['post_type'] ) ) {
				$post_types = $arguments['post_type'];
			} else {
				$post_types = array( $arguments['post_type'] );
			}

			$placeholders = array();
			foreach ( $post_types as $post_type ) {
				$placeholders[] = '%s';
				$clause_parameters[] = $post_type;
			}

			$clause[] = 'AND p.post_type IN (' . join( ',', $placeholders ) . ')';

			if ( is_array( $arguments['post_status'] ) ) {
				$post_stati = $arguments['post_status'];
			} else {
				$post_stati = array( $arguments['post_status'] );
			}

			$placeholders = array();
			foreach ( $post_stati as $post_status ) {
				if ( ( 'private' != $post_status ) || is_user_logged_in() ) {
					$placeholders[] = '%s';
					$clause_parameters[] = $post_status;
				}
			}

			$clause[] = 'AND p.post_status IN (' . join( ',', $placeholders ) . ') )';
		}

		$clause =  join(' ', $clause);
		if ( ! empty( $clause_parameters ) ) {
			$clauses['join'] = $wpdb->prepare( $clause, $clause_parameters );
		} else {
			$clauses['join'] = $clause;
		}

		// Start WHERE clause with a taxonomy constraint
		if ( is_array( $arguments['taxonomy'] ) ) {
			$taxonomies = $arguments['taxonomy'];
		} else {
			$taxonomies = array( $arguments['taxonomy'] );
		}

		foreach ( $taxonomies as $taxonomy ) {
			if ( ! taxonomy_exists( $taxonomy ) ) {
				$error = new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy', 'media-library-assistant' ), $taxonomy );
				return $error;
			}
		}

		$clause_parameters = array();
		$placeholders = array();
		foreach ($taxonomies as $taxonomy) {
		    $placeholders[] = '%s';
			$clause_parameters[] = $taxonomy;
		}

		$clause = array( 'tt.taxonomy IN (' . join( ',', $placeholders ) . ')' );

		/*
		 * The "ids" parameter can build an item-specific cloud.
		 * Compile a list of all the terms assigned to the items.
		 */
		if ( ! empty( $arguments['ids'] ) && empty( $arguments['include'] ) ) {
			$ids = wp_parse_id_list( $arguments['ids'] );
		    $placeholders = implode( "','", $ids );
			$clause[] = "AND tr.object_id IN ( '{$placeholders}' )";

			$includes = array();
			foreach ( $ids as $id ) {
				foreach ($taxonomies as $taxonomy) {
					$terms = get_the_terms( $id, $taxonomy );
					if ( is_array( $terms ) ) {
						foreach( $terms as $term ) {
							$includes[ $term->term_id ] = $term->term_id;
						} // terms
					}
				} // taxonomies
			} // ids

			// If there are no terms we want an empty cloud
			if ( empty( $includes ) ) {
				$arguments['include'] = (string) 0x7FFFFFFF;
			} else {
				ksort( $includes );
				$arguments['include'] = implode( ',', $includes );
			}
		}

		// Add include/exclude and parent constraints to WHERE cluse
		if ( ! empty( $arguments['include'] ) ) {
		    $placeholders = implode( "','", wp_parse_id_list( $arguments['include'] ) );
			$clause[] = "AND t.term_id IN ( '{$placeholders}' )";
		} elseif ( ! empty( $arguments['exclude'] ) ) {
		    $placeholders = implode( "','", wp_parse_id_list( $arguments['exclude'] ) );
			$clause[] = "AND t.term_id NOT IN ( '{$placeholders}' )";
		}

		if ( '' !== $arguments['parent'] ) {
			$parent = (int) $arguments['parent'];
			$clause[] = "AND tt.parent = '{$parent}'";
		}

		if ( 'all' !== strtolower( $arguments['post_mime_type'] ) ) {
			$where = str_replace( '%', '%%', wp_post_mime_type_where( $arguments['post_mime_type'], 'p' ) );

			if ( 0 == absint( $arguments['minimum'] ) ) {
				$clause[] = ' AND ( p.post_mime_type IS NULL OR ' . substr( $where, 6 );
			} else {
				$clause[] = $where;
			}
		}

		$clause =  join(' ', $clause);
		if ( ! empty( $clause_parameters ) ) {
			$clauses['where'] = $wpdb->prepare( $clause, $clause_parameters );
		} else {
			$clauses['where'] = $clause;
		}

		// For the inner/initial query, always select the most popular terms
		if ( $no_orderby = 'true' == (string) $arguments['no_orderby'] ) {
			$arguments['orderby'] = 'count';
			$arguments['order']  = 'DESC';
		}

		// Add sort order
		if ( 'none' !== strtolower( $arguments['orderby'] ) ) {
			if ( ( 'tax_position' === strtolower( $arguments['orderby'] ) ) && class_exists( 'Yikes_Custom_Taxonomy_Order', false ) ) {
				// Support Simple Taxonomy Ordering plugin
				$yikes_custom_taxonomy_order = Yikes_Custom_Taxonomy_Order::get_instance();
				$clauses = $yikes_custom_taxonomy_order->set_tax_order( $clauses, $taxonomies, array() );
				// Adjust the orderby clause to account for the subquery and the alias in the fields[] clause
				$clauses['orderby'] = 'ORDER BY CAST( tax_position AS UNSIGNED )';
			} elseif ( ( 'term_order' === strtolower( $arguments['orderby'] ) ) && class_exists( 'SCPO_Engine', false ) ) {
				// Support Simple Custom Post Order plugin
				$clauses['orderby'] = 'ORDER BY term_order';
			} else {
				if ( 'true' == strtolower( $arguments['preserve_case'] ) ) {
					$binary_keys = array( 'name', 'slug', );
				} else {
					$binary_keys = array();
				}

				$allowed_keys = array(
					'empty_orderby_default' => 'name',
					'count' => 'count',
					'id' => 'term_id',
					'name' => 'name',
					'random' => 'RAND()',
					'slug' => 'slug',
				);

				$clauses['orderby'] = 'ORDER BY ' . self::_validate_sql_orderby( $arguments, '', $allowed_keys, $binary_keys );
			} // not tax_position or term_order
		} else {
			$clauses['orderby'] = '';
		}

		// Add pagination
		$clauses['limits'] = '';
		$offset = absint( $arguments['offset'] );
		$limit = absint( $arguments['limit'] );
		if ( 0 < $offset && 0 < $limit ) {
			$clauses['limits'] = "LIMIT {$offset}, {$limit}";
		} elseif ( 0 < $limit ) {
			$clauses['limits'] = "LIMIT {$limit}";
		} elseif ( 0 < $offset ) {
			$clause_parameters = 0x7FFFFFFF;
			$clauses['limits'] = "LIMIT {$offset}, {$clause_parameters}";
		}

		$clauses = apply_filters( 'mla_get_terms_clauses', $clauses );

		// Build the final query
		$query = array( 'SELECT' );
		$query[] = $clauses['fields'];
		$query[] = 'FROM `' . $wpdb->terms . '` AS t';
		$query[] = $clauses['join'];
		$query[] = 'WHERE (';
		$query[] = $clauses['where'];
		$query[] = ') GROUP BY tt.term_taxonomy_id';

		$clause_parameters = absint( $arguments['minimum'] );
		if ( 0 < $clause_parameters ) {
			$query[] = "HAVING count >= {$clause_parameters}";
		}

		/*
		 * Unless specifically told to omit the ORDER BY clause or the COUNT,
		 * supply a sort order for the initial/inner query only
		 */
		if ( ! ( $no_orderby || $no_count ) ) {
			$query[] = 'ORDER BY count DESC, t.term_id ASC';
		}

		// Limit the total number of terms returned
		$terms_limit = absint( $arguments['number'] );
		if ( 0 < $terms_limit ) {
			$query[] = "LIMIT {$terms_limit}";
		}

		// $final_clauses, if present, require an SQL subquery
		$final_clauses = array();

		if ( ! empty( $clauses['orderby'] ) && 'ORDER BY count DESC' != $clauses['orderby'] ) {
			$final_clauses[] = $clauses['orderby'];
		}

		if ( '' !== $clauses['limits'] ) {
			$final_clauses[] = $clauses['limits'];
		}

		// If we're paginating the final results, we need to get an accurate total count first
		if ( ! $no_count && ( 0 < $offset || 0 < $limit ) ) {
			$count_query = 'SELECT COUNT(*) as count FROM (' . join(' ', $query) . ' ) as subQuery';
			$count = $wpdb->get_results( $count_query );
			$found_rows = $count[0]->count;
		}

		if ( ! empty( $final_clauses ) ) {
			if ( ! $no_count ) {
			    array_unshift($query, 'SELECT * FROM (');
			    $query[] = ') AS subQuery';
			}

			$query = array_merge( $query, $final_clauses );
		}

		$query =  join(' ', $query);
		$tags = $wpdb->get_results(	$query );
		if ( ! isset( $found_rows ) ) {
			$found_rows = $wpdb->num_rows;
		}

		if ( self::$mla_debug ) {
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug query arguments', 'media-library-assistant' ) . '</strong> = ' . var_export( $arguments, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug last_query', 'media-library-assistant' ) . '</strong> = ' . var_export( $wpdb->last_query, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug last_error', 'media-library-assistant' ) . '</strong> = ' . var_export( $wpdb->last_error, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug num_rows', 'media-library-assistant' ) . '</strong> = ' . var_export( $wpdb->num_rows, true ) );
			MLACore::mla_debug_add( __LINE__ . ' <strong>' . __( 'mla_debug found_rows', 'media-library-assistant' ) . '</strong> = ' . var_export( $found_rows, true ) );
		}

		if ( 'true' == strtolower( trim( $arguments['pad_counts'] ) ) ) {
			self::_pad_term_counts( $tags, reset( $taxonomies ), $post_types, $post_stati );
		}

		$tags['found_rows'] = $found_rows;
		$tags = apply_filters( 'mla_get_terms_query_results', $tags );

		return $tags;
	} // mla_get_terms

	/**
	 * Walk a list of terms and find hierarchy, preserving source order.
	 *
	 * @since 2.25
	 *
	 * @param	array	$terms Term objects, by reference
	 * @param	array	$arguments Shortcode arguments, including defaults
	 *
	 * @return	array	( [taxonomy] => array( [root terms] => array( [fields], array( 'children' => [child terms] )
	 */
	private static function _get_term_tree( &$terms, $arguments = array() ) {
		$term = current( $terms );

		if ( empty( $term ) or ! isset( $term->parent ) ) {
			return array();
		}

		// Set found_rows aside to be restored later
		if ( isset( $terms['found_rows'] ) ) {
			$found_rows = $terms['found_rows'];
			unset( $terms['found_rows'] );
		} else {
			$found_rows = NULL;
		}

		$child_of = ! empty( $arguments['child_of'] ) ? absint( $arguments['child_of'] ) : NULL;
		$include_tree = ! empty( $arguments['include_tree'] ) ? wp_parse_id_list( $arguments['include_tree'] ) : NULL;
		$exclude_tree = empty( $include_tree ) && ! empty( $arguments['exclude_tree'] ) ? wp_parse_id_list( $arguments['exclude_tree'] ) : NULL;

		$depth = ! empty( $arguments['depth'] ) ? absint( $arguments['depth'] ) : 0;
		$term_tree = array();
		$root_ids = array();
		$parents = array();
		$child_ids = array();
		foreach( $terms as $index => $term ) {
			// Preserve order for sorting later
			$term->original_index = $index;

			// TODO Make this conditional on $arguments['link']
			$link = get_edit_tag_link( $term->term_id, $term->taxonomy );
			if ( ! is_wp_error( $link ) ) {
				$term->edit_link = $link;
				$link = get_term_link( (int) $term->term_id, $term->taxonomy );
				$term->term_link = $link;
			}

			if ( is_wp_error( $link ) ) {
				return $link;
			}

			if ( 'edit' == $arguments['link'] ) {
				$term->link = $term->edit_link;
			} else {
				$term->link = $term->term_link;
			}

			$term->children = array();
			$parent = absint( $term->parent );
			if ( 0 == $parent ) {
				$term_tree[ $term->taxonomy ][] = $term;
				$root_ids[ $term->taxonomy ][ $term->term_id ] = count( $term_tree[ $term->taxonomy ] ) - 1;
			} else {
				$parents[ $term->taxonomy ][ $term->parent ][] = $term;
				$child_ids[ $term->taxonomy ][ $term->term_id ] = absint( $term->parent );
			}
		}

		// Collapse multi-level children
		foreach ( $parents as $taxonomy => $tax_parents ) {
			if ( ! isset( $term_tree[ $taxonomy ] ) ) {
				$term_tree[ $taxonomy ] = array();
				$root_ids[ $taxonomy ] = array();
			}

			while ( ! empty( $tax_parents ) ) {
				foreach( $tax_parents as $parent_id => $children ) {
					foreach( $children as $index => $child ) {
						if ( ! array_key_exists( $child->term_id, $tax_parents ) ) {

							if ( array_key_exists( $child->parent, $root_ids[ $taxonomy ] ) ) {
								// Found a root node - attach the leaf
								$term_tree[ $taxonomy ][ $root_ids[ $taxonomy ][ $child->parent ] ]->children[] = $child;
							} elseif ( isset( $child_ids[ $taxonomy ][ $child->parent ] ) ) {
								// Found a non-root parent node - attach the leaf
								$the_parent = $child_ids[ $taxonomy ][ $child->parent ];
								foreach( $tax_parents[ $the_parent ] as $candidate_index => $candidate ) {
									if ( $candidate->term_id == $child->parent ) {
										$parents[ $taxonomy ][ $the_parent ][ $candidate_index ]->children[] = $child;
										break;
									}
								} // foreach candidate
							} else {
								// No parent exists; make this a root node
								$term_tree[ $taxonomy ][] = $child;
								$root_ids[ $taxonomy ][ $child->term_id ] = count( $term_tree[ $taxonomy ] ) - 1;
							} // Move the leaf node

							unset( $tax_parents[ $parent_id ][ $index ] );
							if ( empty( $tax_parents[ $parent_id ] ) ) {
								unset( $tax_parents[ $parent_id ] );
							}
						} // leaf node; no children
					} // foreach child
				} // foreach parent_id
			} // has parents
		} // foreach taxonomy

		// Calculate and potentially trim parent/child tree
		$all_terms_count = 0;
		foreach ( array_keys( $term_tree ) as $taxonomy ) {
			if ( $child_of ) {
				$result = self::_find_child_of( $term_tree[ $taxonomy ], $child_of );
				if ( false !== $result ) {
					$term_tree[ $taxonomy ] = $result->children;
				} else {
					$term_tree[ $taxonomy ] = array();
					continue;
				}
			} // $child_of

			if ( $include_tree ) {
				$result = self::_find_include_tree( $term_tree[ $taxonomy ], $include_tree );
				if ( false !== $result ) {
					$term_tree[ $taxonomy ] = $result;
				} else {
					$term_tree[ $taxonomy ] = array();
					continue;
				}
			} // $include_tree

			if ( $exclude_tree ) {
				self::_remove_exclude_tree( $term_tree[ $taxonomy ], $exclude_tree );
			}

			$term_count = 0;
			$root_limit = count( $term_tree[ $taxonomy ] );

			if ( $root_limit ) {
				for ( $root_index = 0; $root_index < $root_limit; $root_index++ ) {
					if ( isset( $term_tree[ $taxonomy ][ $root_index ] ) ) {
						$term_count++;
						$term_tree[ $taxonomy ][ $root_index ]->level = 0;
						if ( ! empty( $term_tree[ $taxonomy ][ $root_index ]->children ) ) {
							$term_count += self::_count_term_children( $term_tree[ $taxonomy ][ $root_index ], $depth );
						}
					} else {
						$root_limit++;
					}
				}
			}

			// Restore original sort order
			$term_tree[ $taxonomy ] = self::_sort_by_original_index( $term_tree[ $taxonomy ] );

			$term_tree[ $taxonomy ]['found_rows'] = $term_count;
			$all_terms_count += $term_count;
		} // foreach taxonomy

		$term_tree['found_rows'] = $all_terms_count;
		return $term_tree;
	} // _get_term_tree

	/**
	 * Sort an array of terms (and their children) by original source order
	 *
	 * @since 2.84
	 *
	 * @param	array	$unsorted_tree unsorted term array, by reference
	 *
	 * @return	array	term array sorted by original source order
	 */
	private static function _sort_by_original_index( &$unsorted_tree ) {
		$sorted_tree = array();

		foreach ( $unsorted_tree as $unsorted_term ) {
			if ( ! empty( $unsorted_term->children ) ) {
				$unsorted_term->children = self::_sort_by_original_index( $unsorted_term->children );
			}

			$sorted_tree[ $unsorted_term->original_index ] = $unsorted_term;
		}

		ksort( $sorted_tree );		
		return $sorted_tree;
	} // _sort_by_original_index

	/**
	 * Find a term that matches $child_of
	 *
	 * @since 2.25
	 *
	 * @param	array	$parents Potential parent Term objects, by reference
	 * @param	integer	$parent_id Term_id of the desired parent
	 *
	 * @return	mixed	Term object of the desired parent else false
	 */
	private static function _find_child_of( &$parents, $parent_id ) {
		foreach( $parents as $parent ) {
			if ( $parent_id == $parent->term_id ) {
				return $parent;
			}

			$result = self::_find_child_of( $parent->children, $parent_id );
			if ( false !== $result ) {
				return $result;
			}
		}

		return false;
	} // _find_child_of

	/**
	 * Find the term(s) that match $include_tree
	 *
	 * @since 2.25
	 *
	 * @param	array	$terms Potential term objects, by reference
	 * @param	array	$include_tree term_id(s) of the desired terms
	 *
	 * @return	mixed	Term object(s) of the desired terms else false
	 */
	private static function _find_include_tree( &$terms, $include_tree ) {
		$new_tree = array();

		foreach( $terms as $term ) {
			if ( in_array( $term->term_id, $include_tree ) ) {
				$new_tree[] = $term;
			} elseif ( ! empty( $term->children ) ) {
				$result = self::_find_include_tree( $term->children, $include_tree );
				if ( false !== $result ) {
					$new_tree = array_merge( $new_tree, $result );
				}
			}
		}

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

		return $new_tree;
	} // _find_include_tree

	/**
	 * Remove the term(s) that match $exclude_tree
	 *
	 * @since 2.25
	 *
	 * @param	array	$terms Potential term objects, by reference
	 * @param	array	$exclude_tree term_id(s) of the desired terms
	 *
	 * @return	void	Term object(s) are removed from the &parents array
	 */
	private static function _remove_exclude_tree( &$terms, $exclude_tree ) {
		foreach( $terms as $index => $term ) {
			if ( in_array( $term->term_id, $exclude_tree ) ) {
				unset( $terms[ $index ] );
			} elseif ( ! empty( $term->children ) ) {
				self::_remove_exclude_tree( $term->children, $exclude_tree );
			}
		}
	} // _remove_exclude_tree

	/**
	 * Add level to term children and count them, all levels.
	 *
	 * Recalculates term counts by including items from child terms. Assumes all
	 * relevant children are already in the $terms argument.
	 *
	 * @since 2.25
	 *
	 * @param	object	$parent Parent Term objects, by reference
	 * @param	integer	$depth Maximum depth of parent/child relationship
	 *
	 * @return	integer	Total number of children, all levels
	 */
	private static function _count_term_children( &$parent, $depth = 0 ) {
		$term_count = 0;
		$child_level = $parent->level + 1;

		// level is zero-based, depth is one-based
		if ( $depth && $child_level >= $depth ) {
			$parent->children = array();
			return 0;
		}

		$child_limit = count( $parent->children );
		for ( $child_index = 0; $child_index < $child_limit; $child_index++ ) {
				if ( isset( $parent->children[ $child_index ] ) ) {
					$term_count++;
					$parent->children[ $child_index ]->level = $child_level;
					if ( ! empty( $parent->children[ $child_index ]->children ) ) {
						$term_count += self::_count_term_children( $parent->children[ $child_index ], $depth );
					}
				} else {
					$child_limit++;
				}
			}

		return $term_count;
	} // _count_term_children

	/**
	 * Add count of children to parent count.
	 *
	 * Recalculates term counts by including items from child terms. Assumes all
	 * relevant children are already in the $terms argument.
	 *
	 * @since 2.20
	 *
	 * @param	array	Array of Term objects, by reference
	 * @param	string	Term Context
	 * @param	array	Qualifying post type value(s)
	 * @param	array	Qualifying post status value(s)
	 * @return	null	Will break from function if conditions are not met.
	 */
	private static function _pad_term_counts( &$terms, $taxonomy, $post_types = NULL, $post_stati = NULL ) {
		global $wpdb;

		// This function only works for hierarchical taxonomies like post categories.
		if ( !is_taxonomy_hierarchical( $taxonomy ) ) {
			return;
		}

		// WordPress "private" function, in /wp-includes/taxonomy.php
		$term_hier = _get_term_hierarchy( $taxonomy );

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

		$terms_by_id = array(); // key term_id, value = reference to term object
		$term_ids = array(); // key term_taxonomy_id, value = term_id
		$term_items = array(); // key term_id

		foreach ( (array) $terms as $key => $term ) {
			if ( is_integer( $key ) ) {
				$terms_by_id[$term->term_id] = & $terms[$key];
				$term_ids[$term->term_taxonomy_id] = $term->term_id;
			}
		}

		if ( is_array( $post_stati ) ) {
			$post_stati = esc_sql( $post_stati );
		} else {
			$post_stati = array( 'inherit' );
		}

		if ( is_array( $post_types ) ) {
			$post_types = esc_sql( $post_types );
		} else {
			$tax_obj = get_taxonomy( $taxonomy );
			$post_types = esc_sql( $tax_obj->object_type );
		}

		// Get the object and term ids and stick them in a lookup table
		$results = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (" . implode( ',', array_keys($term_ids) ) . ") AND post_type IN ('" . implode( "', '", $post_types ) . "') AND post_status in ( '" . implode( "', '", $post_stati ) . "' )" );
		foreach ( $results as $row ) {
			$id = $term_ids[ $row->term_taxonomy_id ];
			$term_items[ $id ][ $row->object_id ] = isset( $term_items[ $id ][ $row->object_id ] ) ? ++$term_items[ $id ][ $row->object_id ] : 1;
		}

		// Touch every ancestor's lookup row for each post in each term
		foreach ( $term_ids as $term_id ) {
			$child = $term_id;
			while ( ! empty( $terms_by_id[ $child] ) && $parent = $terms_by_id[ $child ]->parent ) {
				if ( ! empty( $term_items[ $term_id ] ) ) {
					foreach ( $term_items[ $term_id ] as $item_id => $touches ) {
						$term_items[ $parent ][ $item_id ] = isset( $term_items[ $parent ][ $item_id ] ) ? ++$term_items[ $parent ][ $item_id ]: 1;
					}
				}

				$child = $parent;
			}
		}

		// Transfer the touched cells
		foreach ( (array) $term_items as $id => $items ) {
			if ( isset( $terms_by_id[ $id ] ) ) {
				$terms_by_id[ $id ]->count = count( $items );
			}
		}
	}
} // Class MLAShortcode_Support
?>