Forum Replies Created

Viewing 3 replies - 1 through 3 (of 3 total)
  • Thread Starter riccardo.tasso

    (@riccardotasso)

    Thanks again David, a couple of fixes more were required, but now the plugin works as a charm!

    Here it is my final version:

    <?php
    /**
     * @package MLA Mapping Hooks Example
     * @version 1.02
     */
    
    /*
    Plugin Name: MLA Mapping Hooks Example
    Plugin URI: https://www.cross-library.com
    Description: Provides an example of the filters provided by the IPTC/EXIF and Custom Field mapping features.
    Author: Riccardo Tasso, David Lingren
    Version: 1.02
    Author URI: https://www.cross-library.com
    
    Copyright 2014 David Lingren
    
    	This program is free software; you can redistribute it and/or modify
    	it under the terms of the GNU General Public License as published by
    	the Free Software Foundation; either version 2 of the License, or
    	(at your option) any later version.
    
    	This program is distributed in the hope that it will be useful,
    	but WITHOUT ANY WARRANTY; without even the implied warranty of
    	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    	GNU General Public License for more details.
    
    	You can get a copy of the GNU General Public License by writing to the
    	Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
    */
    
    /**
     * Class MLA Mapping Hooks Example hooks all of the filters provided by the IPTC/EXIF and Custom Field mapping features
     *
     * Call it anything you want, but give it an unlikely and hopefully unique name. Hiding enerything
     * else inside a class means this is the only name you have to worry about.
     *
     * @package MLA Mapping Hooks Example
     * @since 1.00
     */
    class MLAMappingHooksExample {
    	/**
    	 * Initialization function, similar to __construct()
    	 *
    	 * Installs filters and actions that handle the MLA hooks for uploading and mapping.
    	 *
    	 * @since 1.00
    	 *
    	 * @return	void
    	 */
    	public static function initialize() {
    		/*
    		 * The filters are only useful in the admin section; exit if in the "front-end" posts/pages.
    		 */
    		if ( ! is_admin() )
    			return;
    
    		/*
    		 * add_filter parameters:
    		 * $tag - name of the hook you're filtering; defined by [mla_gallery]
    		 * $function_to_add - function to be called when [mla_gallery] applies the filter
    		 * $priority - default 10; lower runs earlier, higher runs later
    		 * $accepted_args - number of arguments your function accepts
    		 *
    		 * Comment out the filters you don't need; save them for future use
    		 */
    		add_filter( 'mla_upload_prefilter', 'MLAMappingHooksExample::mla_upload_prefilter_filter', 10, 2 );
    		add_filter( 'mla_upload_filter', 'MLAMappingHooksExample::mla_upload_filter_filter', 10, 2 );
    
    		add_action( 'mla_add_attachment', 'MLAMappingHooksExample::mla_add_attachment_action', 10, 1 );
    
    		add_filter( 'mla_update_attachment_metadata_options', 'MLAMappingHooksExample::mla_update_attachment_metadata_options_filter', 10, 3 );
    		add_filter( 'mla_update_attachment_metadata_prefilter', 'MLAMappingHooksExample::mla_update_attachment_metadata_prefilter_filter', 10, 3 );
    		add_filter( 'mla_update_attachment_metadata_postfilter', 'MLAMappingHooksExample::mla_update_attachment_metadata_postfilter_filter', 10, 3 );
    
    		add_filter( 'mla_mapping_settings', 'MLAMappingHooksExample::mla_mapping_settings_filter', 10, 4 );
    		add_filter( 'mla_mapping_rule', 'MLAMappingHooksExample::mla_mapping_rule_filter', 10, 4 );
    		add_filter( 'mla_mapping_custom_value', 'MLAMappingHooksExample::mla_mapping_custom_value_filter', 10, 5 );
    		add_filter( 'mla_mapping_iptc_value', 'MLAMappingHooksExample::mla_mapping_iptc_value_filter', 10, 5 );
    		add_filter( 'mla_mapping_exif_value', 'MLAMappingHooksExample::mla_mapping_exif_value_filter', 10, 5 );
    		add_filter( 'mla_mapping_updates', 'MLAMappingHooksExample::mla_mapping_updates_filter', 10, 5 );
    
    		add_filter( 'mla_get_options_tablist', 'MLAMappingHooksExample::mla_get_options_tablist_filter', 10, 3 );
    	}
    
    	/**
    	 * Save the original image metadata when a file is first uploaded
    	 *
    	 * Array elements are:
    	 * 		'post_id' => 0,
    	 *		'mla_iptc_metadata' => array(),
    	 *		'mla_exif_metadata' => array(),
    	 *		'wp_image_metadata' => array(),
    	 *
    	 * @since 1.00
    	 *
    	 * @var	array
    	 */
    	private static $image_metadata = array();
    	private static $raw_metadata = array();
    
    	/**
    	 * MLA Mapping Upload Prefilter
    	 *
    	 * This filter gives you an opportunity to record the original IPTC, EXIF and
    	 * WordPress image_metadata before the file is stored in the Media Library.
    	 * You can also modify the file name that will be used in the Media Library.
    	 *
    	 * Many plugins and image editing functions alter or destroy this information,
    	 * so this may be your last change to preserve it.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	the file name, type and location
    	 * @param	array	the IPTC, EXIF and WordPress image_metadata
    	 *
    	 * @return	array	updated file name and other information
    	 */
    	public static function mla_upload_prefilter_filter( $file, $image_metadata ) {
    		/*
    		 * Uncomment the error_log statements in any of the filters to see what's passed in
    		 */
    		//error_log( 'MLAMappingHooksExample::mla_upload_prefilter_filter $file = ' . var_export( $file, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_upload_prefilter_filter $image_metadata = ' . var_export( $image_metadata, true ), 0 );
    
    		/*
    		 * Save the information for use in the later filters
    		 */
    		self::$image_metadata = $image_metadata;
    		self::$image_metadata['preload_file'] = $file;
    
    		/*
    		 * Save the EXIF, XMP, IPTC and COM data from JPEG files
    		 */
    		if ( 'image/jpeg' == $file['type'] ) {
    			self::$raw_metadata = self::_extract_jpeg_metadata( $file['tmp_name'] );
    			//error_log( 'MLAMappingHooksExample::mla_upload_prefilter_filter $raw_metadata = ' . var_export( self::$raw_metadata, true ), 0 );
    		} else {
    			self::$raw_metadata = array();
    		}
    
    		return $file;
    	} // mla_upload_prefilter_filter
    
    	/**
    	 * MLA Mapping Upload Filter
    	 *
    	 * This filter gives you an opportunity to record some additional metadata
    	 * for audio and video media after the file is stored in the Media Library.
    	 *
    	 * Many plugins and other functions alter or destroy this information,
    	 * so this may be your last change to preserve it.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	the file name, type and location
    	 * @param	array	the ID3 metadata for audio and video files
    	 *
    	 * @return	array	updated file name, type and location
    	 */
    	public static function mla_upload_filter_filter( $file, $id3_data ) {
    		//error_log( 'MLAMappingHooksExample::mla_upload_filter_filter $file = ' . var_export( $file, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_upload_filter_filter $id3_data = ' . var_export( $id3_data, true ), 0 );
    
    		/*
    		 * Save the information for use in the later filters
    		 */
    		self::$image_metadata['postload_file'] = $file;
    		self::$image_metadata['id3_metadata'] = $id3_data;
    
    		return $file;
    	} // mla_upload_filter_filter
    
    	/**
    	 * MLA Add Attachment Action
    	 *
    	 * This filter is called at the end of the wp_insert_attachment() function,
    	 * after the file is in place and the post object has been created in the database.
    	 *
    	 * By this time, other plugins have probably run their own 'add_attachment' filters
    	 * and done their work/damage to metadata, etc.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	integer	The Post ID of the new attachment
    	 *
    	 * @return	void
    	 */
    	public static function mla_add_attachment_action( $post_ID ) {
    		//error_log( 'MLAMappingHooksExample::mla_add_attachment_action $post_ID = ' . var_export( $post_ID, true ), 0 );
    
    		/*
    		 * Save the information for use in the later filters
    		 */
    		self::$image_metadata['post_id'] = $post_ID;
    	} // mla_add_attachment_action
    
    	/**
    	 * MLA Update Attachment Metadata Options
    	 *
    	 * This filter lets you inspect or change the processing options that will
    	 * control the MLA mapping rules in the update_attachment_metadata filter.
    	 *
    	 * The options are:
    	 *		is_upload - true if this is part of the original file upload process
    	 *		enable_iptc_exif_mapping - true to apply IPTC/EXIF mapping to file uploads
    	 *		enable_custom_field_mapping - true to apply custom field mapping to file uploads
    	 *		enable_iptc_exif_update - true to apply IPTC/EXIF mapping to updates
    	 *		enable_custom_field_update - true to apply custom field mapping to updates
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	Processing options, e.g., 'is_upload'
    	 * @param	array	attachment metadata
    	 * @param	integer	The Post ID of the new/updated attachment
    	 *
    	 * @return	array	updated processing options
    	 */
    	public static function mla_update_attachment_metadata_options_filter( $options, $data, $post_ID ) {
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_options_filter $options = ' . var_export( $options, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_options_filter $data = ' . var_export( $data, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_options_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    
    		return $options;
    	} // mla_update_attachment_metadata_prefilter_filter
    
    	/**
    	 * MLA Update Attachment Metadata Prefilter
    	 *
    	 * This filter is called at the end of the wp_update_attachment_metadata() function,
    	 * BEFORE any MLA mapping rules are applied. The prefilter gives you an
    	 * opportunity to record or update the metadata before the mapping.
    	 *
    	 * The wp_update_attachment_metadata() function is called at the end of the file upload process and at
    	 * several later points, such as when an image attachment is edited or by
    	 * plugins that alter the attachment file.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	attachment metadata
    	 * @param	integer	The Post ID of the new/updated attachment
    	 * @param	array	Processing options, e.g., 'is_upload'
    	 *
    	 * @return	array	updated attachment metadata
    	 */
    	public static function mla_update_attachment_metadata_prefilter_filter( $data, $post_ID, $options ) {
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_prefilter_filter $data = ' . var_export( $data, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_prefilter_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_prefilter_filter $options = ' . var_export( $options, true ), 0 );
    
    		/*
    		 * If the metadata has been stripped, try to replace it
    		 * NOTE: Uncomment/comment the "self::" and "$data = " lines to activate/deactivate
    		 */
    		if ( isset( $data['image_meta']['created_timestamp'] )
    			&& empty( $data['image_meta']['created_timestamp'] ) ) {
    				//self::_replace_jpeg_metadata( self::$image_metadata['postload_file']['file'], self::$raw_metadata );
    				//$data = wp_generate_attachment_metadata( $post_ID, self::$image_metadata['postload_file']['file'] );
    				//error_log( 'regenerated data = ' . var_export( $data, true ), 0 );
    
    		}
    
    		return $data;
    	} // mla_update_attachment_metadata_prefilter_filter
    
    	/**
    	 * MLA Update Attachment Metadata Postfilter
    	 *
    	 * This filter is called AFTER MLA mapping rules are applied during
    	 * wp_update_attachment_metadata() processing. The postfilter gives you
    	 * an opportunity to record or update the metadata after the mapping.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	attachment metadata
    	 * @param	integer	The Post ID of the new/updated attachment
    	 * @param	array	Processing options, e.g., 'is_upload'
    	 *
    	 * @return	array	updated attachment metadata
    	 */
    	public static function mla_update_attachment_metadata_postfilter_filter( $data, $post_ID, $options ) {
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_postfilter_filter $data = ' . var_export( $data, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_postfilter_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_postfilter_filter $options = ' . var_export( $options, true ), 0 );
    
    		return $data;
    	} // mla_update_attachment_metadata_postfilter_filter
    
    	/**
    	 * MLA Mapping Settings Filter
    	 *
    	 * This filter is called before any mapping rules are executed.
    	 * You can add, change or delete rules from the array.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array 	mapping rules
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against, e.g., custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated mapping rules
    	 */
    	public static function mla_mapping_settings_filter( $settings, $post_ID, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_settings_filter $settings = ' . var_export( $settings, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_settings_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_settings_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_settings_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * $category gives the context in which the rule is applied:
    		 *		'custom_field_mapping' - mapping custom fields for ALL attachments
    		 *		'single_attachment_mapping' - mapping custom fields for ONE attachments
    		 *
    		 *		'iptc_exif_mapping' - mapping ALL IPTC/EXIF rules
    		 *		'iptc_exif_standard_mapping' - mapping standard field rules
    		 *		'iptc_exif_taxonomy_mapping' - mapping taxonomy term rules
    		 *		'iptc_exif_custom_mapping' - mapping IPTC/EXIF custom field rules
    		 *
    		 * NOTE: 'iptc_exif_mapping' will never be passed to the 'mla_mapping_rule' filter.
    		 * There, one of the three more specific values will be passed.
    		 */
    
    		/*
    		 * For Custom Field Mapping, $settings is an array indexed by
    		 * the custom field name.
    		 * Each array element is a mapping rule; an array containing:
    		 *		'name' => custom field name
    		 *		'data_source' => 'none', 'meta', 'template' or data source name
    		 *		'keep_existing' => boolean; true to preserve existing content
    		 *		'format' => 'native', 'commas'
    		 *		'mla_column' => boolean; not used
    		 *		'quick_edit' => boolean; not used
    		 *		'bulk_edit' => boolean; not used
    		 *		'meta_name' => attachment metadata element name or content template
    		 *		'option' => 'text', 'single', 'export', 'array', 'multi'
    		 *		'no_null' => boolean; true to delete empty custom field values
    		 *
    		 * For IPTC/EXIF Mapping, $settings is an array indexed by
    		 * the mapping category; 'standard', 'taxonomy' and 'custom'.
    		 * Each category is an array of rules, with slightly different formats.
    		 *
    		 * Each 'standard' category array element is a rule (array) containing:
    		 *		'name' => field slug; 'post_title', 'post_name', 'image_alt', 'post_excerpt', 'post_content'
    		 *		'iptc_value' => IPTC Identifier or friendly name
    		 *		'exif_value' => EXIF element name
    		 *		'iptc_first' => boolean; true to prefer IPTC value over EXIF value
    		 *		'keep_existing' => boolean; true to preserve existing content
    		 *
    		 * Each 'taxonomy' category array element is a rule (array) containing:
    		 *		'name' => taxonomy slug, e.g., 'post_tag', 'attachment_category'
    		 *		'hierarchical' => boolean; true for hierarchical taxonomies
    		 *		'iptc_value' => IPTC Identifier or friendly name
    		 *		'exif_value' => EXIF element name
    		 *		'iptc_first' => boolean; true to prefer IPTC value over EXIF value
    		 *		'keep_existing' => boolean; true to preserve existing content
    		 *		'parent' => zero for none or the term_id of the parent
    		 *		'delimiters' => term separator(s), e.g., ',;'
    		 *
    		 * Each 'custom' category array element is a rule (array) containing:
    		 *		'name' => custom field name
    		 *		'iptc_value' => IPTC Identifier or friendly name
    		 *		'exif_value' => EXIF element name
    		 *		'iptc_first' => boolean; true to prefer IPTC value over EXIF value
    		 *		'keep_existing' => boolean; true to preserve existing content
    		 */
    		return $settings;
    	} // mla_mapping_settings_filter
    
    	/**
    	 * MLA Mapping Rule Filter
    	 *
    	 * This filter is called once for each mapping rule, before the rule
    	 * is evaluated. You can change the rule parameters, or prevent rule
    	 * evaluation by returning $setting_value['data_source'] = 'none';
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array 	custom_field_mapping rule
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated custom_field_mapping rule
    	 */
    	public static function mla_mapping_rule_filter( $setting_value, $post_ID, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_rule_filter $setting_value = ' . var_export( $setting_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_rule_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_rule_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_rule_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * $setting_value is an array containing a mapping rule; see above
    		 * To stop this rule's evaluation and mapping, return NULL
    		 */
    		return $setting_value;
    	} // mla_mapping_rule_filter
    
    	/**
    	 * MLA Mapping Custom Field Value Filter
    	 *
    	 * This filter is called once for each custom field mapping rule, after the rule
    	 * is evaluated. You can change the new value produced by the rule.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	mixed 	value returned by the rule
    	 * @param	array 	custom_field_mapping rule
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated rule value
    	 */
    	public static function mla_mapping_custom_value_filter( $new_text, $setting_value, $post_id, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $new_text = ' . var_export( $new_text, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $setting_value = ' . var_export( $setting_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * You can use MLAOptions::mla_get_data_source() to get anything available;
    		 * for example:
    		 */
    		$my_setting = array(
    			'data_source' => 'size_names',
    			'option' => 'array'
    		);
    		//$size_names = MLAOptions::mla_get_data_source($post_id, $category, $my_setting, $attachment_metadata);
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $size_names = ' . var_export( $size_names, true ), 0 );
    
    		/*
    		 * For "empty" values, return ' '.
    		 */
    		return $new_text;
    	} // mla_mapping_custom_value_filter
    
    	/**
    	 * MLA Mapping IPTC Value Filter
    	 *
    	 * This filter is called once for each IPTC/EXIF mapping rule, after the IPTC
    	 * portion of the rule is evaluated. You can change the new value produced by
    	 * the rule.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	mixed 	IPTC value returned by the rule
    	 * @param	array 	custom_field_mapping rule
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated rule IPTC value
    	 */
    	public static function mla_mapping_iptc_value_filter( $iptc_value, $setting_value, $post_id, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $iptc_value = ' . var_export( $iptc_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $setting_value = ' . var_export( $setting_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * You can use MLAOptions::mla_get_data_source() to get anything available;
    		 * for example:
    		 */
    		$my_setting = array(
    			'data_source' => 'template',
    			'meta_name' => '([+iptc:keywords+])',
    			'option' => 'array'
    		);
    		//$keywords = MLAOptions::mla_get_data_source($post_id, $category, $my_setting, $attachment_metadata);
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $keywords = ' . var_export( $keywords, true ), 0 );
    
    		/*
    		 * For "empty" values, return ''.
    		 */
    		return $iptc_value;
    	} // mla_mapping_iptc_value_filter
    
    	/**
    	 * MLA Mapping EXIF Value Filter
    	 *
    	 * This filter is called once for each IPTC/EXIF mapping rule, after the EXIF
    	 * portion of the rule is evaluated. You can change the new value produced by
    	 * the rule.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	mixed 	EXIF/Template value returned by the rule
    	 * @param	array 	custom_field_mapping rule
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated rule EXIF/Template value
    	 */
    	public static function mla_mapping_exif_value_filter( $exif_value, $setting_value, $post_id, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $exif_value = ' . var_export( $exif_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $setting_value = ' . var_export( $setting_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * You can use MLAOptions::mla_get_data_source() to get anything available;
    		 * for example:
    		 */
    		$my_setting = array(
    			'data_source' => 'template',
    			'meta_name' => '([+exif:Copyright+])',
    			'option' => 'array'
    		);
    		//$copyright = MLAOptions::mla_get_data_source($post_id, $category, $my_setting, $attachment_metadata);
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $copyright = ' . var_export( $copyright, true ), 0 );
    
    		/*
    		 * For "empty" 'text' values, return ''.
    		 * For "empty" 'array' values, return NULL.
    		 */
    		return $exif_value;
    	} // mla_mapping_exif_value_filter
    
    	/**
    	 * MLA Mapping Updates Filter
    	 *
    	 * This filter is called AFTER all mapping rules are applied.
    	 * You can add, change or remove updates for the attachment's
    	 * standard fields, taxonomies and/or custom fields.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	updates for the attachment's standard fields, taxonomies and/or custom fields
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	mapping rules
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated attachment's updates
    	 */
    	public static function mla_mapping_updates_filter( $updates, $post_id, $category, $settings, $attachment_metadata ) {
    		foreach ($settings as $key => $setting ) {
    		    if ( 'exportmla' == sanitize_title( $key ) && 'none' == $setting['data_source'] && false !== strpos( $setting['meta_name'], '.xml' ) ) {
    		        self::_export_this_item( $post_id, $setting['meta_name'] );
    		    }
    		}
    
    		return $updates;
    	} // mla_mapping_updates_filter
    
    	/**
    	 * Export data for one or more items to an XML file
    	 *
    	 * This function is called from the mla_mapping_updates_filter(),
    	 * just above, when the "export" rule is defined. It writes information
    	 * about the item and its tags to an XML file.
    	 *
    	 * @since 1.01
    	 *
    	 * @param    integer    The ID value of the current item/attachment
    	 * @param    string    The name of the output file, e.g., "data.xml"
    	 *
    	 * @return    void
    	 */
    	private static function _export_this_item( $post_id, $file ) {
    	    static $filename = NULL, $file_pointer = NULL, $tail = "</items>\n";
    
    	    // create/open the file on first call
    	    if ( NULL == $filename ) {
    	        $filename = MLA_BACKUP_DIR . $file;
    
    	        // Make sure the directory exists and is writable
    	        if ( ! file_exists( MLA_BACKUP_DIR ) && ! @mkdir( MLA_BACKUP_DIR ) ) {
    	            return; // Does not exist and cannot create it
    	        } elseif ( ! is_writable( MLA_BACKUP_DIR ) && ! @chmod( MLA_BACKUP_DIR , '0777') ) {
    	            return; // Is not writable and cannot make it so
    	        }
    
    	        // Every directory should have an empty index.php file for security
    	        if ( ! file_exists( MLA_BACKUP_DIR . 'index.php') ) {
    	            @ touch( MLA_BACKUP_DIR . 'index.php');
    	        }
    
    	        // Open the file for write access
    	        $file_pointer = @fopen( $filename, 'w' );
    	        if ( ! $file_pointer ) {
    	            $file_pointer = NULL;
    	            return; // Cannot open a writable file
    	        }
    
    	        // Write the overall header and trailer
    	        if (false === @fwrite($file_pointer, "<items>\n" . $tail )) {
    	            fclose($file_pointer);
    	            $file_pointer = NULL;
    	            return;
    	        }
    	    } // First call
    
    	    if ( NULL == $file_pointer ) {
    	        return; // Don't have a writable file
    	    }
    
    	    // Back up over the old trailer
    	    if ( -1 === fseek( $file_pointer, (0 - strlen( $tail ) ), SEEK_END ) ) {
    	        fclose($file_pointer);
    	        $file_pointer = NULL;
    	        return;
    	    }
    
    	    $post = get_post( $post_id );
    	    $terms = wp_get_post_terms( $post_id, 'attachment_tag' );
    
    	    // Compose the item, line by line
    	    $item = array();
    	    $item[] = "\t<item>";
    	    $item[] = "\t\t<id>" . absint( $post_id ) . '</id>';
    	    $item[] = "\t\t<title>" . $post->post_title . '</title>';
    	    $item[] = "\t\t<url>" . get_attachment_link( $post_id ) . '</url>';
    	    //$item[] = "\t\t<file>" . wp_get_attachment_url( $post_id ) . '</file>';
    	    $item[] = "\t\t<post_date>" . $post->post_date . '</post_date>';
    
    	    foreach( $terms as $term ) {
    	        $item[] = "\t\t<tag>" . $term->name . '</tag>';
    	    }
    
    	    $item[] = "\t</item>";
    	    $item[] = $tail; // already has "\n" appended
    
    	    // Write the item to the file
    	    $item = implode( "\n", $item );
    	    if (false === @fwrite($file_pointer, $item )) {
    	        fclose($file_pointer);
    	        $file_pointer = NULL;
    	        return;
    	    }
    	} // _export_this_item
    
    	/**
    	 * MLA Mapping Updates Filter
    	 *
    	 * This filter is called AFTER all mapping rules are applied.
    	 * You can add, change or remove updates for the attachment's
    	 * standard fields, taxonomies and/or custom fields.
    	 *
    	 * @since 1.02
    	 *
    	 * @param	array|false	The entire tablist ( $tab = NULL ), a single tab entry or false if not found/not allowed.
    	 * @param	array		The entire tablist
    	 * @param	string|NULL	tab slug for single-element return or NULL to return entire tablist
    	 *
    	 * @return	array	updated attachment's updates
    	 */
    	public static function mla_get_options_tablist_filter( $results, $mla_tablist, $tab ) {
    		//error_log( 'MLAMappingHooksExample::mla_get_options_tablist_filter $results = ' . var_export( $results, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_get_options_tablist_filter $mla_tablist = ' . var_export( $mla_tablist, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_get_options_tablist_filter $tab = ' . var_export( $tab, true ), 0 );
    
    		/*
    		 * Return an updated $mla_tablist ( $tab = NULL ), an updated single element or false
    		 */
    		return $results;
    
    		/*
    		 * Comment out the above return statement to fall through to this example,
    		 * which removes the "Uploads" tab from the Settings/Media Library Assistant submenu
    		 */
    		if ( NULL == $tab ) {
    			unset( $results['upload'] );
    		} elseif ( 'upload' == $tab ) {
    			$results = false;
    		}
    
    		return $results;
    	} // mla_get_options_tablist_filter
    
    	/*
    	 * Selected JPEG Section Markers
    	 */
    	const SOF0	= 0xC0; // Baseline Encoding
    	const SOI	= 0xD8; // Start of image
    	const EOI	= 0xD9; // End of image
    	const SOS	= 0xDA; // Start of scan (image data)
    	const APP0	= 0xE0; // Application segment 0 JFIF Header
    	const APP1	= 0xE1; // Application segment 1 EXIF/XMP
    	const APP2	= 0xE2; // Application segment 2 EXIF Flashpix extensions
    	const APP13	= 0xED; // Application segment 13 IPTC
    	const COM	= 0xFE; // Comment
    
    	/**
    	 * Enumerate the sections of a JPEG file
     	 *
    	 * Returns an array of section descriptors, indexed by the section order, i.e., 0, 1, 2 ...
    	 *
    	 * Each array element is an array, containing:
    	 *		marker => section marker, e.g., 0xD8, 0xE0, 0xED
    	 *		offset => offset in the file of the "0xFF" marker introducing the section
    	 *		length => number of bytes in the section, including the "0xFF", marker byte and length field (if applicable)
    	 *
    	 * @since 1.01
    	 *
    	 * @param	string	File Contents
    	 *
    	 * @return	array	section list ( index => array( 'marker', 'offset', 'length' )
    	 */
    	private static function _enumerate_jpeg_sections( &$file_contents ) {
    		$file_length = strlen( $file_contents );
    		$file_offset = 0;
    		$section_array = array();
    
    		while ( $file_offset < $file_length ) {
    			$section_value = array();
    
    			// Find a marker
    			for ( $i = 0; $i < 7; $i++ ) {
    				if ( 0xFF != ord( $file_contents[ $file_offset + $i ] ) ) {
    					break;
    				}
    			}
    
    			$section_value['marker'] = $marker = ord( $file_contents[ $file_offset + $i ] );
    
    			if ( $marker >= self::SOF0 && $marker <= self::COM ) {
    				$section_value['offset'] = $file_offset + ( $i - 1);
    
    				if ( ( self::SOI == $marker ) || ( self::EOI == $marker ) ) {
    					$file_offset = $file_offset + ( $i + 1 );
    				} elseif ( self::SOS == $marker ) {
    					// Start of Scan precedes image data; skip to end of file/image
    					$file_offset = $file_length - 2;
    
    					// Scan backwards for End of Image marker
    					while ( ( 0xFF != ord( $file_contents[ $file_offset ] ) ) || ( self::EOI != ord( $file_contents[ $file_offset + 1 ] ) ) ) {
    						$file_offset--;
    						if ( $file_offset == $start_of_image ) {
    							// Give up - no End of Image marker
    							$file_offset = $file_length;
    							break;
    						}
    					}
    				} else {
    					// Big Endian length
    					$length = 256 * ord( $file_contents[ $file_offset + ++$i ] );
    					$length += ord( $file_contents[ $file_offset + ++$i ] );
    					$file_offset = $section_value['offset'] + 2 + $length;
    				}
    			}
    			else {
    				// No marker or invalid marker
    				if ( 0 < $i ) {
    					$section_value['offset'] = $file_offset + ( $i - 1 );
    				} else {
    					$section_value['offset'] = $file_offset + $i;
    				}
    				$file_offset = $file_offset + ( $i + 1 );
    
    				while ( $file_offset < $file_length ) {
    					if ( 0xFF == ord( $file_contents[ $file_offset ] ) ) {
    						break;
    					} else {
    						$file_offset++;
    					}
    				}
    			} // invalid marker
    
    			$section_value['length'] = $file_offset - $section_value['offset'];
    			$section_array[] = $section_value;
    			//error_log( 'MLAMappingHooksExample::_enumerate_jpeg_sections $section_value = ' . var_export( $section_value, true ), 0 );
    		} // while offset < length
    
    		return $section_array;
    	} // _enumerate_jpeg_sections
    
    	/**
    	 * Extract IPTC, EXIF/XMP and Comment data from a JPEG file
     	 *
    	 * Returns an array of section content, indexed by the section order
    	 *
    	 * Each array element is an array, containing:
    	 *		marker => section marker, e.g., 0xD8, 0xE0, 0xED
    	 *		content => data bytes in the section
    	 *
    	 * @since 1.01
    	 *
    	 * @param	string	Absolute path to the file
    	 *
    	 * @return	array	section list ( index => array( 'marker', 'content' )
    	 */
    	private static function _extract_jpeg_metadata( $path ) {
    		$metadata = array();
    		$file_contents = file_get_contents( $path, true );
    		 if ( $file_contents ) {
    			 $sections = self::_enumerate_jpeg_sections( $file_contents );
    			 foreach( $sections as $section ) {
    				 if ( in_array( $section['marker'], array( self::APP1, self::APP2, self::APP13, self::COM ) ) ) {
    					$metadata[] = array( 'marker' => $section['marker'],
    					 	'content' => substr( $file_contents, $section['offset'], $section['length'] )
    					);
    				 } // found metadata
    			 } // foreach section
    		 }
    
    		return $metadata;
    	} // _extract_jpeg_metadata
    
    	/**
    	 * Add/replace IPTC, EXIF/XMP and Comment data in a JPEG file
     	 *
    	 * @since 1.01
    	 *
    	 * @param	string	Absolute path to the destination file
    	 * @param	array	Metadata sections from _extract_jpeg_metadata
    	 *
    	 * @return	void
    	 */
    	private static function _replace_jpeg_metadata( $path, $metadata ) {
    		//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $path = ' . var_export( $path, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $marker = ' . var_export( $metadata[0]['marker'], true ), 0 );
    
    		$pathinfo = pathinfo( $path );
    		$temp_path = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '-MLA' . $pathinfo['extension'];
    		//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $temp_path = ' . var_export( $temp_path, true ), 0 );
    
    		/*
    		 * Default to the old COM section if the destination file lacks one
    		 */
    		$COM_section = NULL;
    		foreach ( $metadata as $section ) {
    			if ( self::COM == $section['marker'] ) {
    				$COM_section = $section['content'];
    			}
    		}
    
    		/*
    		 * Strip the destination "APP1, APP2, APP13" sections.
    		 * Separate out the SOI, APP0 and COM sections.
    		 */
    		$SOI_section = NULL;
    		$APP0_section = NULL;
    		$destination_sections = array ();
    		$file_contents = file_get_contents( $path, true );
    		 if ( $file_contents ) {
    			 $destination_sections = self::_enumerate_jpeg_sections( $file_contents );
    			foreach ( $destination_sections as $index => $value ) {
    				if ( self::SOI == $value['marker'] ) {
    					$SOI_section = substr( $file_contents, $value['offset'], $value['length'] );
    					unset( $destination_sections[ $index ] );
    				} elseif  ( self::APP0 == $value['marker'] ) {
    					$APP0_section = substr( $file_contents, $value['offset'], $value['length'] );
    					unset( $destination_sections[ $index ] );
    				} elseif  ( self::COM == $value['marker'] ) {
    					$COM_section = substr( $file_contents, $value['offset'], $value['length'] );
    					unset( $destination_sections[ $index ] );
    				} elseif ( ( self::APP1 == $value['marker'] ) || ( self::APP2 == $value['marker'] ) || ( self::APP13 == $value['marker'] ) ) {
    					unset( $destination_sections[ $index ] );
    				}
    			}
    			//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $SOI_section = ' . var_export( $SOI_section, true ), 0 );
    			//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $APP0_section = ' . var_export( $APP0_section, true ), 0 );
    			//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $COM_section = ' . var_export( $COM_section, true ), 0 );
    			//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $destination_sections = ' . var_export( $destination_sections, true ), 0 );
    
    			if ( ( NULL == $SOI_section ) || ( NULL == $APP0_section ) ) {
    				return;
    			}
    
    			@unlink( $temp_path );
    			$temp_handle = @fopen( $temp_path, 'wb' );
    			if ( false === $temp_handle ) {
    				//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fopen error = ' . var_export( error_get_last(), true ), 0 );
    				return;
    			}
    
    			if ( false === @fwrite( $temp_handle, $SOI_section ) ) {
    				//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite SOI error = ' . var_export( error_get_last(), true ), 0 );
    				@fclose( $temp_handle );
    				@unlink( $temp_path );
    				return;
    			}
    
    			if ( false === @fwrite( $temp_handle, $APP0_section ) ) {
    				//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite APP0 error = ' . var_export( error_get_last(), true ), 0 );
    				@fclose( $temp_handle );
    				@unlink( $temp_path );
    				return;
    			}
    
    			if ( ! empty( $COM_section ) ) {
    				if ( false === @fwrite( $temp_handle, $COM_section ) ) {
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite COM error = ' . var_export( error_get_last(), true ), 0 );
    					@fclose( $temp_handle );
    					@unlink( $temp_path );
    					return;
    				}
    			}
    
    			foreach ( $metadata as $section ) {
    				if ( false === @fwrite( $temp_handle, $section['content'] ) ) {
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite metadata marker = ' . var_export( $section['marker'], true ), 0 );
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite metadata error = ' . var_export( error_get_last(), true ), 0 );
    					@fclose( $temp_handle );
    					@unlink( $temp_path );
    					return;
    				}
    			}
    
    			foreach ( $destination_sections as $section ) {
    				if ( false === @fwrite( $temp_handle, substr( $file_contents, $section['offset'], $section['length'] ) ) ) {
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite destination_sections marker = ' . var_export( $section['marker'], true ), 0 );
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite destination_sections error = ' . var_export( error_get_last(), true ), 0 );
    					@fclose( $temp_handle );
    					@unlink( $temp_path );
    					return;
    				}
    			}
    
    			if ( false === @fclose( $temp_handle ) ) {
    				error_log( 'ERROR: MLAMappingHooksExample::_replace_jpeg_metadata fclose = ' . var_export( error_get_last(), true ), 0 );
    				return;
    			}
    
    			if ( false === @unlink( $path ) ) {
    				error_log( 'ERROR: MLAMappingHooksExample::_replace_jpeg_metadata unlink = ' . var_export( error_get_last(), true ), 0 );
    				return;
    			}
    
    			if ( false === @rename( $temp_path, $path ) ) {
    				error_log( 'ERROR: MLAMappingHooksExample::_replace_jpeg_metadata rename = ' . var_export( error_get_last(), true ), 0 );
    				return;
    			}
    		 } // if $file_contents
    	} // _replace_jpeg_metadata
    } //MLAMappingHooksExample
    
    /*
     * Install the filters at an early opportunity
     */
    add_action('init', 'MLAMappingHooksExample::initialize');
    ?>
    Thread Starter riccardo.tasso

    (@riccardotasso)

    Hi David and thank you so much for such a rich explanation!

    I followed carefully each one of your steps:

    1. created and saved the rule (named ExportMLA)
    2. created the plugin, wich is exactly as I found in wp-content/plugins/media-library-assistant/examples/mla-mapping-hooks-example.php.txt plus the snippets you gave me
    3. copied the plugin in the wp-content/plugins/exportMLA/index.php
    4. activated the new plugin from wordpress dashboard
    5. pressed the Map All Rules, All Attachments Now button (it gives me the message: “Custom field mapping completed; 290 attachment(s) examined, no changes detected.”)

    The problem is that i don’t found anything in wp-content/mla-backup except the settings backup (which I generated just to be sure that wordpress had write access to that directory).

    The code of my plugin is as follows:

    <?php
    /**
     * Provides an example of the filters provided by the IPTC/EXIF and Custom Field mapping features
     *
     * In this example...
     *
     * @package MLA Mapping Hooks Example
     * @version 1.02
     */
    
    /*
    Plugin Name: MLA Mapping Hooks Example
    Plugin URI: https://fairtradejudaica.org/media-library-assistant-a-wordpress-plugin/
    Description: Provides an example of the filters provided by the IPTC/EXIF and Custom Field mapping features.
    Author: David Lingren
    Version: 1.02
    Author URI: https://fairtradejudaica.org/our-story/staff/
    
    Copyright 2014 David Lingren
    
    	This program is free software; you can redistribute it and/or modify
    	it under the terms of the GNU General Public License as published by
    	the Free Software Foundation; either version 2 of the License, or
    	(at your option) any later version.
    
    	This program is distributed in the hope that it will be useful,
    	but WITHOUT ANY WARRANTY; without even the implied warranty of
    	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    	GNU General Public License for more details.
    
    	You can get a copy of the GNU General Public License by writing to the
    	Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
    */
    
    /**
     * Class MLA Mapping Hooks Example hooks all of the filters provided by the IPTC/EXIF and Custom Field mapping features
     *
     * Call it anything you want, but give it an unlikely and hopefully unique name. Hiding enerything
     * else inside a class means this is the only name you have to worry about.
     *
     * @package MLA Mapping Hooks Example
     * @since 1.00
     */
    class MLAMappingHooksExample {
    	/**
    	 * Initialization function, similar to __construct()
    	 *
    	 * Installs filters and actions that handle the MLA hooks for uploading and mapping.
    	 *
    	 * @since 1.00
    	 *
    	 * @return	void
    	 */
    	public static function initialize() {
    		/*
    		 * The filters are only useful in the admin section; exit if in the "front-end" posts/pages.
    		 */
    		if ( ! is_admin() )
    			return;
    
    		/*
    		 * add_filter parameters:
    		 * $tag - name of the hook you're filtering; defined by [mla_gallery]
    		 * $function_to_add - function to be called when [mla_gallery] applies the filter
    		 * $priority - default 10; lower runs earlier, higher runs later
    		 * $accepted_args - number of arguments your function accepts
    		 *
    		 * Comment out the filters you don't need; save them for future use
    		 */
    		add_filter( 'mla_upload_prefilter', 'MLAMappingHooksExample::mla_upload_prefilter_filter', 10, 2 );
    		add_filter( 'mla_upload_filter', 'MLAMappingHooksExample::mla_upload_filter_filter', 10, 2 );
    
    		add_action( 'mla_add_attachment', 'MLAMappingHooksExample::mla_add_attachment_action', 10, 1 );
    
    		add_filter( 'mla_update_attachment_metadata_options', 'MLAMappingHooksExample::mla_update_attachment_metadata_options_filter', 10, 3 );
    		add_filter( 'mla_update_attachment_metadata_prefilter', 'MLAMappingHooksExample::mla_update_attachment_metadata_prefilter_filter', 10, 3 );
    		add_filter( 'mla_update_attachment_metadata_postfilter', 'MLAMappingHooksExample::mla_update_attachment_metadata_postfilter_filter', 10, 3 );
    
    		add_filter( 'mla_mapping_settings', 'MLAMappingHooksExample::mla_mapping_settings_filter', 10, 4 );
    		add_filter( 'mla_mapping_rule', 'MLAMappingHooksExample::mla_mapping_rule_filter', 10, 4 );
    		add_filter( 'mla_mapping_custom_value', 'MLAMappingHooksExample::mla_mapping_custom_value_filter', 10, 5 );
    		add_filter( 'mla_mapping_iptc_value', 'MLAMappingHooksExample::mla_mapping_iptc_value_filter', 10, 5 );
    		add_filter( 'mla_mapping_exif_value', 'MLAMappingHooksExample::mla_mapping_exif_value_filter', 10, 5 );
    		add_filter( 'mla_mapping_updates', 'MLAMappingHooksExample::mla_mapping_updates_filter', 10, 5 );
    
    		add_filter( 'mla_get_options_tablist', 'MLAMappingHooksExample::mla_get_options_tablist_filter', 10, 3 );
    	}
    
    	/**
    	 * Save the original image metadata when a file is first uploaded
    	 *
    	 * Array elements are:
    	 * 		'post_id' => 0,
    	 *		'mla_iptc_metadata' => array(),
    	 *		'mla_exif_metadata' => array(),
    	 *		'wp_image_metadata' => array(),
    	 *
    	 * @since 1.00
    	 *
    	 * @var	array
    	 */
    	private static $image_metadata = array();
    	private static $raw_metadata = array();
    
    	/**
    	 * MLA Mapping Upload Prefilter
    	 *
    	 * This filter gives you an opportunity to record the original IPTC, EXIF and
    	 * WordPress image_metadata before the file is stored in the Media Library.
    	 * You can also modify the file name that will be used in the Media Library.
    	 *
    	 * Many plugins and image editing functions alter or destroy this information,
    	 * so this may be your last change to preserve it.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	the file name, type and location
    	 * @param	array	the IPTC, EXIF and WordPress image_metadata
    	 *
    	 * @return	array	updated file name and other information
    	 */
    	public static function mla_upload_prefilter_filter( $file, $image_metadata ) {
    		/*
    		 * Uncomment the error_log statements in any of the filters to see what's passed in
    		 */
    		//error_log( 'MLAMappingHooksExample::mla_upload_prefilter_filter $file = ' . var_export( $file, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_upload_prefilter_filter $image_metadata = ' . var_export( $image_metadata, true ), 0 );
    
    		/*
    		 * Save the information for use in the later filters
    		 */
    		self::$image_metadata = $image_metadata;
    		self::$image_metadata['preload_file'] = $file;
    
    		/*
    		 * Save the EXIF, XMP, IPTC and COM data from JPEG files
    		 */
    		if ( 'image/jpeg' == $file['type'] ) {
    			self::$raw_metadata = self::_extract_jpeg_metadata( $file['tmp_name'] );
    			//error_log( 'MLAMappingHooksExample::mla_upload_prefilter_filter $raw_metadata = ' . var_export( self::$raw_metadata, true ), 0 );
    		} else {
    			self::$raw_metadata = array();
    		}
    
    		return $file;
    	} // mla_upload_prefilter_filter
    
    	/**
    	 * MLA Mapping Upload Filter
    	 *
    	 * This filter gives you an opportunity to record some additional metadata
    	 * for audio and video media after the file is stored in the Media Library.
    	 *
    	 * Many plugins and other functions alter or destroy this information,
    	 * so this may be your last change to preserve it.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	the file name, type and location
    	 * @param	array	the ID3 metadata for audio and video files
    	 *
    	 * @return	array	updated file name, type and location
    	 */
    	public static function mla_upload_filter_filter( $file, $id3_data ) {
    		//error_log( 'MLAMappingHooksExample::mla_upload_filter_filter $file = ' . var_export( $file, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_upload_filter_filter $id3_data = ' . var_export( $id3_data, true ), 0 );
    
    		/*
    		 * Save the information for use in the later filters
    		 */
    		self::$image_metadata['postload_file'] = $file;
    		self::$image_metadata['id3_metadata'] = $id3_data;
    
    		return $file;
    	} // mla_upload_filter_filter
    
    	/**
    	 * MLA Add Attachment Action
    	 *
    	 * This filter is called at the end of the wp_insert_attachment() function,
    	 * after the file is in place and the post object has been created in the database.
    	 *
    	 * By this time, other plugins have probably run their own 'add_attachment' filters
    	 * and done their work/damage to metadata, etc.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	integer	The Post ID of the new attachment
    	 *
    	 * @return	void
    	 */
    	public static function mla_add_attachment_action( $post_ID ) {
    		//error_log( 'MLAMappingHooksExample::mla_add_attachment_action $post_ID = ' . var_export( $post_ID, true ), 0 );
    
    		/*
    		 * Save the information for use in the later filters
    		 */
    		self::$image_metadata['post_id'] = $post_ID;
    	} // mla_add_attachment_action
    
    	/**
    	 * MLA Update Attachment Metadata Options
    	 *
    	 * This filter lets you inspect or change the processing options that will
    	 * control the MLA mapping rules in the update_attachment_metadata filter.
    	 *
    	 * The options are:
    	 *		is_upload - true if this is part of the original file upload process
    	 *		enable_iptc_exif_mapping - true to apply IPTC/EXIF mapping to file uploads
    	 *		enable_custom_field_mapping - true to apply custom field mapping to file uploads
    	 *		enable_iptc_exif_update - true to apply IPTC/EXIF mapping to updates
    	 *		enable_custom_field_update - true to apply custom field mapping to updates
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	Processing options, e.g., 'is_upload'
    	 * @param	array	attachment metadata
    	 * @param	integer	The Post ID of the new/updated attachment
    	 *
    	 * @return	array	updated processing options
    	 */
    	public static function mla_update_attachment_metadata_options_filter( $options, $data, $post_ID ) {
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_options_filter $options = ' . var_export( $options, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_options_filter $data = ' . var_export( $data, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_options_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    
    		return $options;
    	} // mla_update_attachment_metadata_prefilter_filter
    
    	/**
    	 * MLA Update Attachment Metadata Prefilter
    	 *
    	 * This filter is called at the end of the wp_update_attachment_metadata() function,
    	 * BEFORE any MLA mapping rules are applied. The prefilter gives you an
    	 * opportunity to record or update the metadata before the mapping.
    	 *
    	 * The wp_update_attachment_metadata() function is called at the end of the file upload process and at
    	 * several later points, such as when an image attachment is edited or by
    	 * plugins that alter the attachment file.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	attachment metadata
    	 * @param	integer	The Post ID of the new/updated attachment
    	 * @param	array	Processing options, e.g., 'is_upload'
    	 *
    	 * @return	array	updated attachment metadata
    	 */
    	public static function mla_update_attachment_metadata_prefilter_filter( $data, $post_ID, $options ) {
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_prefilter_filter $data = ' . var_export( $data, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_prefilter_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_prefilter_filter $options = ' . var_export( $options, true ), 0 );
    
    		/*
    		 * If the metadata has been stripped, try to replace it
    		 * NOTE: Uncomment/comment the "self::" and "$data = " lines to activate/deactivate
    		 */
    		if ( isset( $data['image_meta']['created_timestamp'] )
    			&& empty( $data['image_meta']['created_timestamp'] ) ) {
    				//self::_replace_jpeg_metadata( self::$image_metadata['postload_file']['file'], self::$raw_metadata );
    				//$data = wp_generate_attachment_metadata( $post_ID, self::$image_metadata['postload_file']['file'] );
    				//error_log( 'regenerated data = ' . var_export( $data, true ), 0 );
    
    		}
    
    		return $data;
    	} // mla_update_attachment_metadata_prefilter_filter
    
    	/**
    	 * MLA Update Attachment Metadata Postfilter
    	 *
    	 * This filter is called AFTER MLA mapping rules are applied during
    	 * wp_update_attachment_metadata() processing. The postfilter gives you
    	 * an opportunity to record or update the metadata after the mapping.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	attachment metadata
    	 * @param	integer	The Post ID of the new/updated attachment
    	 * @param	array	Processing options, e.g., 'is_upload'
    	 *
    	 * @return	array	updated attachment metadata
    	 */
    	public static function mla_update_attachment_metadata_postfilter_filter( $data, $post_ID, $options ) {
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_postfilter_filter $data = ' . var_export( $data, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_postfilter_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_update_attachment_metadata_postfilter_filter $options = ' . var_export( $options, true ), 0 );
    
    		return $data;
    	} // mla_update_attachment_metadata_postfilter_filter
    
    	/**
    	 * MLA Mapping Settings Filter
    	 *
    	 * This filter is called before any mapping rules are executed.
    	 * You can add, change or delete rules from the array.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array 	mapping rules
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against, e.g., custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated mapping rules
    	 */
    	public static function mla_mapping_settings_filter( $settings, $post_ID, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_settings_filter $settings = ' . var_export( $settings, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_settings_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_settings_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_settings_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * $category gives the context in which the rule is applied:
    		 *		'custom_field_mapping' - mapping custom fields for ALL attachments
    		 *		'single_attachment_mapping' - mapping custom fields for ONE attachments
    		 *
    		 *		'iptc_exif_mapping' - mapping ALL IPTC/EXIF rules
    		 *		'iptc_exif_standard_mapping' - mapping standard field rules
    		 *		'iptc_exif_taxonomy_mapping' - mapping taxonomy term rules
    		 *		'iptc_exif_custom_mapping' - mapping IPTC/EXIF custom field rules
    		 *
    		 * NOTE: 'iptc_exif_mapping' will never be passed to the 'mla_mapping_rule' filter.
    		 * There, one of the three more specific values will be passed.
    		 */
    
    		/*
    		 * For Custom Field Mapping, $settings is an array indexed by
    		 * the custom field name.
    		 * Each array element is a mapping rule; an array containing:
    		 *		'name' => custom field name
    		 *		'data_source' => 'none', 'meta', 'template' or data source name
    		 *		'keep_existing' => boolean; true to preserve existing content
    		 *		'format' => 'native', 'commas'
    		 *		'mla_column' => boolean; not used
    		 *		'quick_edit' => boolean; not used
    		 *		'bulk_edit' => boolean; not used
    		 *		'meta_name' => attachment metadata element name or content template
    		 *		'option' => 'text', 'single', 'export', 'array', 'multi'
    		 *		'no_null' => boolean; true to delete empty custom field values
    		 *
    		 * For IPTC/EXIF Mapping, $settings is an array indexed by
    		 * the mapping category; 'standard', 'taxonomy' and 'custom'.
    		 * Each category is an array of rules, with slightly different formats.
    		 *
    		 * Each 'standard' category array element is a rule (array) containing:
    		 *		'name' => field slug; 'post_title', 'post_name', 'image_alt', 'post_excerpt', 'post_content'
    		 *		'iptc_value' => IPTC Identifier or friendly name
    		 *		'exif_value' => EXIF element name
    		 *		'iptc_first' => boolean; true to prefer IPTC value over EXIF value
    		 *		'keep_existing' => boolean; true to preserve existing content
    		 *
    		 * Each 'taxonomy' category array element is a rule (array) containing:
    		 *		'name' => taxonomy slug, e.g., 'post_tag', 'attachment_category'
    		 *		'hierarchical' => boolean; true for hierarchical taxonomies
    		 *		'iptc_value' => IPTC Identifier or friendly name
    		 *		'exif_value' => EXIF element name
    		 *		'iptc_first' => boolean; true to prefer IPTC value over EXIF value
    		 *		'keep_existing' => boolean; true to preserve existing content
    		 *		'parent' => zero for none or the term_id of the parent
    		 *		'delimiters' => term separator(s), e.g., ',;'
    		 *
    		 * Each 'custom' category array element is a rule (array) containing:
    		 *		'name' => custom field name
    		 *		'iptc_value' => IPTC Identifier or friendly name
    		 *		'exif_value' => EXIF element name
    		 *		'iptc_first' => boolean; true to prefer IPTC value over EXIF value
    		 *		'keep_existing' => boolean; true to preserve existing content
    		 */
    		return $settings;
    	} // mla_mapping_settings_filter
    
    	/**
    	 * MLA Mapping Rule Filter
    	 *
    	 * This filter is called once for each mapping rule, before the rule
    	 * is evaluated. You can change the rule parameters, or prevent rule
    	 * evaluation by returning $setting_value['data_source'] = 'none';
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array 	custom_field_mapping rule
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated custom_field_mapping rule
    	 */
    	public static function mla_mapping_rule_filter( $setting_value, $post_ID, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_rule_filter $setting_value = ' . var_export( $setting_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_rule_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_rule_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_rule_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * $setting_value is an array containing a mapping rule; see above
    		 * To stop this rule's evaluation and mapping, return NULL
    		 */
    		return $setting_value;
    	} // mla_mapping_rule_filter
    
    	/**
    	 * MLA Mapping Custom Field Value Filter
    	 *
    	 * This filter is called once for each custom field mapping rule, after the rule
    	 * is evaluated. You can change the new value produced by the rule.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	mixed 	value returned by the rule
    	 * @param	array 	custom_field_mapping rule
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated rule value
    	 */
    	public static function mla_mapping_custom_value_filter( $new_text, $setting_value, $post_id, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $new_text = ' . var_export( $new_text, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $setting_value = ' . var_export( $setting_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * You can use MLAOptions::mla_get_data_source() to get anything available;
    		 * for example:
    		 */
    		$my_setting = array(
    			'data_source' => 'size_names',
    			'option' => 'array'
    		);
    		//$size_names = MLAOptions::mla_get_data_source($post_id, $category, $my_setting, $attachment_metadata);
    		//error_log( 'MLAMappingHooksExample::mla_mapping_custom_value_filter $size_names = ' . var_export( $size_names, true ), 0 );
    
    		/*
    		 * For "empty" values, return ' '.
    		 */
    		return $new_text;
    	} // mla_mapping_custom_value_filter
    
    	/**
    	 * MLA Mapping IPTC Value Filter
    	 *
    	 * This filter is called once for each IPTC/EXIF mapping rule, after the IPTC
    	 * portion of the rule is evaluated. You can change the new value produced by
    	 * the rule.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	mixed 	IPTC value returned by the rule
    	 * @param	array 	custom_field_mapping rule
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated rule IPTC value
    	 */
    	public static function mla_mapping_iptc_value_filter( $iptc_value, $setting_value, $post_id, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $iptc_value = ' . var_export( $iptc_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $setting_value = ' . var_export( $setting_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * You can use MLAOptions::mla_get_data_source() to get anything available;
    		 * for example:
    		 */
    		$my_setting = array(
    			'data_source' => 'template',
    			'meta_name' => '([+iptc:keywords+])',
    			'option' => 'array'
    		);
    		//$keywords = MLAOptions::mla_get_data_source($post_id, $category, $my_setting, $attachment_metadata);
    		//error_log( 'MLAMappingHooksExample::mla_mapping_iptc_value_filter $keywords = ' . var_export( $keywords, true ), 0 );
    
    		/*
    		 * For "empty" values, return ''.
    		 */
    		return $iptc_value;
    	} // mla_mapping_iptc_value_filter
    
    	/**
    	 * MLA Mapping EXIF Value Filter
    	 *
    	 * This filter is called once for each IPTC/EXIF mapping rule, after the EXIF
    	 * portion of the rule is evaluated. You can change the new value produced by
    	 * the rule.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	mixed 	EXIF/Template value returned by the rule
    	 * @param	array 	custom_field_mapping rule
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated rule EXIF/Template value
    	 */
    	public static function mla_mapping_exif_value_filter( $exif_value, $setting_value, $post_id, $category, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $exif_value = ' . var_export( $exif_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $setting_value = ' . var_export( $setting_value, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * You can use MLAOptions::mla_get_data_source() to get anything available;
    		 * for example:
    		 */
    		$my_setting = array(
    			'data_source' => 'template',
    			'meta_name' => '([+exif:Copyright+])',
    			'option' => 'array'
    		);
    		//$copyright = MLAOptions::mla_get_data_source($post_id, $category, $my_setting, $attachment_metadata);
    		//error_log( 'MLAMappingHooksExample::mla_mapping_exif_value_filter $copyright = ' . var_export( $copyright, true ), 0 );
    
    		/*
    		 * For "empty" 'text' values, return ''.
    		 * For "empty" 'array' values, return NULL.
    		 */
    		return $exif_value;
    	} // mla_mapping_exif_value_filter
    
    	/**
    	 * MLA Mapping Updates Filter
    	 *
    	 * This filter is called AFTER all mapping rules are applied.
    	 * You can add, change or remove updates for the attachment's
    	 * standard fields, taxonomies and/or custom fields.
    	 *
    	 * @since 1.00
    	 *
    	 * @param	array	updates for the attachment's standard fields, taxonomies and/or custom fields
    	 * @param	integer post ID to be evaluated
    	 * @param	string 	category/scope to evaluate against: custom_field_mapping or single_attachment_mapping
    	 * @param	array 	mapping rules
    	 * @param	array 	attachment_metadata, default NULL
    	 *
    	 * @return	array	updated attachment's updates
    	 */
    	public static function mla_mapping_updates_filter( $updates, $post_ID, $category, $settings, $attachment_metadata ) {
    		//error_log( 'MLAMappingHooksExample::mla_mapping_updates_filter $updates = ' . var_export( $updates, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_updates_filter $post_ID = ' . var_export( $post_ID, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_updates_filter $category = ' . var_export( $category, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_updates_filter $settings = ' . var_export( $settings, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_mapping_updates_filter $attachment_metadata = ' . var_export( $attachment_metadata, true ), 0 );
    
    		/*
    		 * To stop this rule's updates, return an empty array, i.e., return array();
    		 */
    
    		/*
    		 * Look for the "file ExportMLA" rule; call the export function if there's a match.
    		 */
    		foreach ($settings as $key => $setting ) {
    		    if ( 'ExportMLA' == sanitize_title( $key ) && 'none' == $setting['data_source'] && false !== strpos( $setting['meta_name'], '.xml' ) ) {
    		        self::_export_this_item( $post_id, $setting['meta_name'] );
    		    }
    		}
    
    		return $updates;
    	} // mla_mapping_updates_filter
    
    	/**
    	 * Export data for one or more items to an XML file
    	 *
    	 * This function is called from the mla_mapping_updates_filter(),
    	 * just above, when the "export" rule is defined. It writes information
    	 * about the item and its tags to an XML file.
    	 *
    	 * @since 1.01
    	 *
    	 * @param    integer    The ID value of the current item/attachment
    	 * @param    string    The name of the output file, e.g., "data.xml"
    	 *
    	 * @return    void
    	 */
    	private static function _export_this_item( $post_id, $file ) {
    	    static $filename = NULL, $file_pointer = NULL, $tail = "</items>\n";
    
    	    // create/open the file on first call
    	    if ( NULL == $filename ) {
    	        $filename = MLA_BACKUP_DIR . $file;
    
    	        // Make sure the directory exists and is writable
    	        if ( ! file_exists( MLA_BACKUP_DIR ) && ! @mkdir( MLA_BACKUP_DIR ) ) {
    	            return; // Does not exist and cannot create it
    	        } elseif ( ! is_writable( MLA_BACKUP_DIR ) && ! @chmod( MLA_BACKUP_DIR , '0777') ) {
    	            return; // Is not writable and cannot make it so
    	        }
    
    	        // Every directory should have an empty index.php file for security
    	        if ( ! file_exists( MLA_BACKUP_DIR . 'index.php') ) {
    	            @ touch( MLA_BACKUP_DIR . 'index.php');
    	        }
    
    	        // Open the file for write access
    	        $file_pointer = @fopen( $filename, 'w' );
    	        if ( ! $file_pointer ) {
    	            $file_pointer = NULL;
    	            return; // Cannot open a writable file
    	        }
    
    	        // Write the overall header and trailer
    	        if (false === @fwrite($file_pointer, "<items>\n" . $tail )) {
    	            fclose($file_pointer);
    	            $file_pointer = NULL;
    	            return;
    	        }
    	    } // First call
    
    	    if ( NULL == $file_pointer ) {
    	        return; // Don't have a writable file
    	    }
    
    	    // Back up over the old trailer
    	    if ( -1 === fseek( $file_pointer, (0 - strlen( $tail ) ), SEEK_END ) ) {
    	        fclose($file_pointer);
    	        $file_pointer = NULL;
    	        return;
    	    }
    
    	    $post = get_post( $post_id );
    	    $terms = wp_get_post_terms( $post_id, 'post_tag' );
    
    	    // Compose the item, line by line
    	    $item = array();
    	    $item[] = "\t<item>";
    	    $item[] = "\t\t<id>" . absint( $post_id ) . '</id>';
    	    $item[] = "\t\t<title>" . $post->post_title . '</title>';
    	    $item[] = "\t\t<url>" . get_attachment_link( $post_id ) . '</url>';
    	    //$item[] = "\t\t<file>" . wp_get_attachment_url( $post_id ) . '</file>';
    	    $item[] = "\t\t<post_date>" . $post->post_date . '</post_date>';
    
    	    foreach( $terms as $term ) {
    	        $item[] = "\t\t<tag>" . $term->name . '</tag>';
    	    }
    
    	    $item[] = "\t</item>";
    	    $item[] = $tail; // already has "\n" appended
    
    	    // Write the item to the file
    	    $item = implode( "\n", $item );
    	    if (false === @fwrite($file_pointer, $item )) {
    	        fclose($file_pointer);
    	        $file_pointer = NULL;
    	        return;
    	    }
    	} // _export_this_item
    
    	/**
    	 * MLA Mapping Updates Filter
    	 *
    	 * This filter is called AFTER all mapping rules are applied.
    	 * You can add, change or remove updates for the attachment's
    	 * standard fields, taxonomies and/or custom fields.
    	 *
    	 * @since 1.02
    	 *
    	 * @param	array|false	The entire tablist ( $tab = NULL ), a single tab entry or false if not found/not allowed.
    	 * @param	array		The entire tablist
    	 * @param	string|NULL	tab slug for single-element return or NULL to return entire tablist
    	 *
    	 * @return	array	updated attachment's updates
    	 */
    	public static function mla_get_options_tablist_filter( $results, $mla_tablist, $tab ) {
    		//error_log( 'MLAMappingHooksExample::mla_get_options_tablist_filter $results = ' . var_export( $results, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_get_options_tablist_filter $mla_tablist = ' . var_export( $mla_tablist, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::mla_get_options_tablist_filter $tab = ' . var_export( $tab, true ), 0 );
    
    		/*
    		 * Return an updated $mla_tablist ( $tab = NULL ), an updated single element or false
    		 */
    		return $results;
    
    		/*
    		 * Comment out the above return statement to fall through to this example,
    		 * which removes the "Uploads" tab from the Settings/Media Library Assistant submenu
    		 */
    		if ( NULL == $tab ) {
    			unset( $results['upload'] );
    		} elseif ( 'upload' == $tab ) {
    			$results = false;
    		}
    
    		return $results;
    	} // mla_get_options_tablist_filter
    
    	/*
    	 * Selected JPEG Section Markers
    	 */
    	const SOF0	= 0xC0; // Baseline Encoding
    	const SOI	= 0xD8; // Start of image
    	const EOI	= 0xD9; // End of image
    	const SOS	= 0xDA; // Start of scan (image data)
    	const APP0	= 0xE0; // Application segment 0 JFIF Header
    	const APP1	= 0xE1; // Application segment 1 EXIF/XMP
    	const APP2	= 0xE2; // Application segment 2 EXIF Flashpix extensions
    	const APP13	= 0xED; // Application segment 13 IPTC
    	const COM	= 0xFE; // Comment
    
    	/**
    	 * Enumerate the sections of a JPEG file
     	 *
    	 * Returns an array of section descriptors, indexed by the section order, i.e., 0, 1, 2 ...
    	 *
    	 * Each array element is an array, containing:
    	 *		marker => section marker, e.g., 0xD8, 0xE0, 0xED
    	 *		offset => offset in the file of the "0xFF" marker introducing the section
    	 *		length => number of bytes in the section, including the "0xFF", marker byte and length field (if applicable)
    	 *
    	 * @since 1.01
    	 *
    	 * @param	string	File Contents
    	 *
    	 * @return	array	section list ( index => array( 'marker', 'offset', 'length' )
    	 */
    	private static function _enumerate_jpeg_sections( &$file_contents ) {
    		$file_length = strlen( $file_contents );
    		$file_offset = 0;
    		$section_array = array();
    
    		while ( $file_offset < $file_length ) {
    			$section_value = array();
    
    			// Find a marker
    			for ( $i = 0; $i < 7; $i++ ) {
    				if ( 0xFF != ord( $file_contents[ $file_offset + $i ] ) ) {
    					break;
    				}
    			}
    
    			$section_value['marker'] = $marker = ord( $file_contents[ $file_offset + $i ] );
    
    			if ( $marker >= self::SOF0 && $marker <= self::COM ) {
    				$section_value['offset'] = $file_offset + ( $i - 1);
    
    				if ( ( self::SOI == $marker ) || ( self::EOI == $marker ) ) {
    					$file_offset = $file_offset + ( $i + 1 );
    				} elseif ( self::SOS == $marker ) {
    					// Start of Scan precedes image data; skip to end of file/image
    					$file_offset = $file_length - 2;
    
    					// Scan backwards for End of Image marker
    					while ( ( 0xFF != ord( $file_contents[ $file_offset ] ) ) || ( self::EOI != ord( $file_contents[ $file_offset + 1 ] ) ) ) {
    						$file_offset--;
    						if ( $file_offset == $start_of_image ) {
    							// Give up - no End of Image marker
    							$file_offset = $file_length;
    							break;
    						}
    					}
    				} else {
    					// Big Endian length
    					$length = 256 * ord( $file_contents[ $file_offset + ++$i ] );
    					$length += ord( $file_contents[ $file_offset + ++$i ] );
    					$file_offset = $section_value['offset'] + 2 + $length;
    				}
    			}
    			else {
    				// No marker or invalid marker
    				if ( 0 < $i ) {
    					$section_value['offset'] = $file_offset + ( $i - 1 );
    				} else {
    					$section_value['offset'] = $file_offset + $i;
    				}
    				$file_offset = $file_offset + ( $i + 1 );
    
    				while ( $file_offset < $file_length ) {
    					if ( 0xFF == ord( $file_contents[ $file_offset ] ) ) {
    						break;
    					} else {
    						$file_offset++;
    					}
    				}
    			} // invalid marker
    
    			$section_value['length'] = $file_offset - $section_value['offset'];
    			$section_array[] = $section_value;
    			//error_log( 'MLAMappingHooksExample::_enumerate_jpeg_sections $section_value = ' . var_export( $section_value, true ), 0 );
    		} // while offset < length
    
    		return $section_array;
    	} // _enumerate_jpeg_sections
    
    	/**
    	 * Extract IPTC, EXIF/XMP and Comment data from a JPEG file
     	 *
    	 * Returns an array of section content, indexed by the section order
    	 *
    	 * Each array element is an array, containing:
    	 *		marker => section marker, e.g., 0xD8, 0xE0, 0xED
    	 *		content => data bytes in the section
    	 *
    	 * @since 1.01
    	 *
    	 * @param	string	Absolute path to the file
    	 *
    	 * @return	array	section list ( index => array( 'marker', 'content' )
    	 */
    	private static function _extract_jpeg_metadata( $path ) {
    		$metadata = array();
    		$file_contents = file_get_contents( $path, true );
    		 if ( $file_contents ) {
    			 $sections = self::_enumerate_jpeg_sections( $file_contents );
    			 foreach( $sections as $section ) {
    				 if ( in_array( $section['marker'], array( self::APP1, self::APP2, self::APP13, self::COM ) ) ) {
    					$metadata[] = array( 'marker' => $section['marker'],
    					 	'content' => substr( $file_contents, $section['offset'], $section['length'] )
    					);
    				 } // found metadata
    			 } // foreach section
    		 }
    
    		return $metadata;
    	} // _extract_jpeg_metadata
    
    	/**
    	 * Add/replace IPTC, EXIF/XMP and Comment data in a JPEG file
     	 *
    	 * @since 1.01
    	 *
    	 * @param	string	Absolute path to the destination file
    	 * @param	array	Metadata sections from _extract_jpeg_metadata
    	 *
    	 * @return	void
    	 */
    	private static function _replace_jpeg_metadata( $path, $metadata ) {
    		//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $path = ' . var_export( $path, true ), 0 );
    		//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $marker = ' . var_export( $metadata[0]['marker'], true ), 0 );
    
    		$pathinfo = pathinfo( $path );
    		$temp_path = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '-MLA' . $pathinfo['extension'];
    		//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $temp_path = ' . var_export( $temp_path, true ), 0 );
    
    		/*
    		 * Default to the old COM section if the destination file lacks one
    		 */
    		$COM_section = NULL;
    		foreach ( $metadata as $section ) {
    			if ( self::COM == $section['marker'] ) {
    				$COM_section = $section['content'];
    			}
    		}
    
    		/*
    		 * Strip the destination "APP1, APP2, APP13" sections.
    		 * Separate out the SOI, APP0 and COM sections.
    		 */
    		$SOI_section = NULL;
    		$APP0_section = NULL;
    		$destination_sections = array ();
    		$file_contents = file_get_contents( $path, true );
    		 if ( $file_contents ) {
    			 $destination_sections = self::_enumerate_jpeg_sections( $file_contents );
    			foreach ( $destination_sections as $index => $value ) {
    				if ( self::SOI == $value['marker'] ) {
    					$SOI_section = substr( $file_contents, $value['offset'], $value['length'] );
    					unset( $destination_sections[ $index ] );
    				} elseif  ( self::APP0 == $value['marker'] ) {
    					$APP0_section = substr( $file_contents, $value['offset'], $value['length'] );
    					unset( $destination_sections[ $index ] );
    				} elseif  ( self::COM == $value['marker'] ) {
    					$COM_section = substr( $file_contents, $value['offset'], $value['length'] );
    					unset( $destination_sections[ $index ] );
    				} elseif ( ( self::APP1 == $value['marker'] ) || ( self::APP2 == $value['marker'] ) || ( self::APP13 == $value['marker'] ) ) {
    					unset( $destination_sections[ $index ] );
    				}
    			}
    			//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $SOI_section = ' . var_export( $SOI_section, true ), 0 );
    			//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $APP0_section = ' . var_export( $APP0_section, true ), 0 );
    			//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $COM_section = ' . var_export( $COM_section, true ), 0 );
    			//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata $destination_sections = ' . var_export( $destination_sections, true ), 0 );
    
    			if ( ( NULL == $SOI_section ) || ( NULL == $APP0_section ) ) {
    				return;
    			}
    
    			@unlink( $temp_path );
    			$temp_handle = @fopen( $temp_path, 'wb' );
    			if ( false === $temp_handle ) {
    				//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fopen error = ' . var_export( error_get_last(), true ), 0 );
    				return;
    			}
    
    			if ( false === @fwrite( $temp_handle, $SOI_section ) ) {
    				//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite SOI error = ' . var_export( error_get_last(), true ), 0 );
    				@fclose( $temp_handle );
    				@unlink( $temp_path );
    				return;
    			}
    
    			if ( false === @fwrite( $temp_handle, $APP0_section ) ) {
    				//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite APP0 error = ' . var_export( error_get_last(), true ), 0 );
    				@fclose( $temp_handle );
    				@unlink( $temp_path );
    				return;
    			}
    
    			if ( ! empty( $COM_section ) ) {
    				if ( false === @fwrite( $temp_handle, $COM_section ) ) {
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite COM error = ' . var_export( error_get_last(), true ), 0 );
    					@fclose( $temp_handle );
    					@unlink( $temp_path );
    					return;
    				}
    			}
    
    			foreach ( $metadata as $section ) {
    				if ( false === @fwrite( $temp_handle, $section['content'] ) ) {
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite metadata marker = ' . var_export( $section['marker'], true ), 0 );
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite metadata error = ' . var_export( error_get_last(), true ), 0 );
    					@fclose( $temp_handle );
    					@unlink( $temp_path );
    					return;
    				}
    			}
    
    			foreach ( $destination_sections as $section ) {
    				if ( false === @fwrite( $temp_handle, substr( $file_contents, $section['offset'], $section['length'] ) ) ) {
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite destination_sections marker = ' . var_export( $section['marker'], true ), 0 );
    					//error_log( 'MLAMappingHooksExample::_replace_jpeg_metadata fwrite destination_sections error = ' . var_export( error_get_last(), true ), 0 );
    					@fclose( $temp_handle );
    					@unlink( $temp_path );
    					return;
    				}
    			}
    
    			if ( false === @fclose( $temp_handle ) ) {
    				error_log( 'ERROR: MLAMappingHooksExample::_replace_jpeg_metadata fclose = ' . var_export( error_get_last(), true ), 0 );
    				return;
    			}
    
    			if ( false === @unlink( $path ) ) {
    				error_log( 'ERROR: MLAMappingHooksExample::_replace_jpeg_metadata unlink = ' . var_export( error_get_last(), true ), 0 );
    				return;
    			}
    
    			if ( false === @rename( $temp_path, $path ) ) {
    				error_log( 'ERROR: MLAMappingHooksExample::_replace_jpeg_metadata rename = ' . var_export( error_get_last(), true ), 0 );
    				return;
    			}
    		 } // if $file_contents
    	} // _replace_jpeg_metadata
    } //MLAMappingHooksExample
    
    /*
     * Install the filters at an early opportunity
     */
    add_action('init', 'MLAMappingHooksExample::initialize');
    ?>

    I forgot to mention that I’m using MLA 1.83.

    Do you have any idea?
    Thank you again,
    Riccardo

    Thread Starter riccardo.tasso

    (@riccardotasso)

    Let’s say something in XML like:

    <items>
    <item>
       <id>123</id>
       <title>My Media File</title>
       <url>https://www.mydomain.com/?attachment_id=123</url>
       <post_date>2014-06-25 15:41:04</wp:post_date>
       <tag>a</tag>
       <tag>b</tag>
       <tag>c</tag>
    </item>
    ...
    </items>

    Thanks very much for the rapid response.
    Riccardo

Viewing 3 replies - 1 through 3 (of 3 total)