Files
wordpress-preseed/wp-content/plugins/media-library-assistant/includes/class-mla-data.php
2019-10-24 00:12:05 +02:00

4445 lines
157 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Database and template file access for MLA needs
*
* @package Media Library Assistant
* @since 0.1
*/
/**
* Class MLA (Media Library Assistant) Data provides database and template file access for MLA needs
*
* The _template functions are inspired by the book "WordPress 3 Plugin Development Essentials."
* Templates separate HTML markup from PHP code for easier maintenance and localization.
*
* @package Media Library Assistant
* @since 0.1
*/
class MLAData {
/**
* Initialization function, similar to __construct()
*
* @since 0.1
*/
public static function initialize() {
self::$search_parameters =& MLAQuery::$search_parameters;
self::$query_parameters =& MLAQuery::$query_parameters;
add_action( 'save_post', 'MLAData::mla_save_post_action', 10, 1);
add_action( 'edit_attachment', 'MLAData::mla_save_post_action', 10, 1);
add_action( 'add_attachment', 'MLAData::mla_save_post_action', 10, 1);
}
/**
* Find a complete template, balancing opening and closing delimiters
*
* @since 1.50
*
* @param string A string possibly starting with '[+template:'
*
* @return string '' or template string starting with '[+template:' and ending with the matching '+]'
*/
private static function _find_template_substring( $tpl ) {
if ( '[+template:' == substr( $tpl, 0, 11 ) ) {
$nest = 11;
$level = 1;
do {
$template_end = strpos( $tpl, '+]', $nest );
if ( false === $template_end ) {
/* translators: 1: ERROR tag 2: template excerpt */
MLACore::mla_debug_add( __LINE__ . sprintf( _x( '%1$s: _find_template_substring no template end delimiter, tail = "%2$s".', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), substr( $tpl, $nest ) ), MLACore::MLA_DEBUG_CATEGORY_ANY );
return '';
}
$nest = strpos( $tpl, '[+', $nest );
if ( false === $nest ) {
$nest = $template_end + 2;
$level--;
} elseif ( $nest < $template_end ) {
$nest += 2;
$level++;
} else {
$nest = $template_end + 2;
$level--;
}
} while ( $level );
$template_length = $template_end + 2;
$template_content = substr( $tpl, 0, $template_length );
return $template_content;
} // found template
return '';
}
/**
* Expand a template, replacing placeholders with their values
*
* Will return an array of values if one or more of the placeholders returns an array.
*
* @since 1.50
*
* @param string A formatting string containing [+placeholders+]
* @param array An associative array containing keys and values e.g. array('key' => 'value')
*
* @return mixed string or array, depending on placeholder values. Placeholders corresponding
* to the keys of the markup_values will be replaced with their values.
*/
public static function mla_parse_array_template( $tpl, $markup_values ) {
$result = array();
$offset = 0;
while ( false !== $start = strpos( $tpl, '[+', $offset ) ) {
if ( $offset < $start ) {
$result[] = substr( $tpl, $offset, ( $start - $offset ) );
}
if ( $template_content = self::_find_template_substring( substr( $tpl, $start ) ) ) {
$template_length = strlen( $template_content );
$template_content = substr( $template_content, 11, $template_length - (11 + 2) );
$template_content = self::_expand_field_level_template( $template_content, $markup_values, true );
foreach ( $template_content as $value )
$result[] = $value;
$offset = $start + $template_length;
} else { // found template
if ( false === $end = strpos( $tpl, '+]', $offset ) ) {
/* translators: 1: ERROR tag 2: template excerpt */
MLACore::mla_debug_add( __LINE__ . sprintf( _x( '%1$s: mla_parse_array_template no template end delimiter, tail = "%2$s".', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), substr( $tpl, $offset ) ), MLACore::MLA_DEBUG_CATEGORY_ANY );
return $tpl;
} // no end delimiter
$key = substr( $tpl, $start + 2, $end - $start - 2 );
if ( isset( $markup_values[ $key ] ) ) {
$result[] = $markup_values[ $key ];
} else { // found key and scalar value
$result[] = substr( $tpl, $start, ( $end + 2 ) - $start );
}
$offset = $end + 2;
} // simple substitution
} // while substitution parameter present
if ( $offset < strlen( $tpl ) ) {
$result[] = substr( $tpl, $offset );
}
/*
* Build a final result, eliminating empty elements and expanding array elements
*/
$final = array();
foreach ( $result as $element ) {
if ( is_scalar( $element ) ) {
$element = trim( $element );
if ( ! empty( $element ) ) {
$final[] = $element;
}
} elseif ( is_array( $element ) ) {
foreach ($element as $key => $value ) {
if ( is_scalar( $value ) ) {
$value = trim( $value );
} elseif ( ! empty( $value ) ) {
$value = var_export( $value, true );
}
/*
* Preserve any keys with string values
*/
if ( ! empty( $value ) ) {
if ( is_integer( $key ) ) {
$final[] = $value;
} else {
$final[ $key ] = $value;
}
}
}
} elseif ( ! empty( $element ) ) {
$final[] = var_export( $element, true );
}
}
if ( 1 == count( $final ) ) {
$final = $final[0];
}
return $final;
}
/**
* Expand a template, replacing placeholders with their values
*
* A simple parsing function for basic templating.
*
* @since 0.1
*
* @param string A formatting string containing [+placeholders+]
* @param array An associative array containing keys and values e.g. array('key' => 'value')
*
* @return strng Placeholders corresponding to the keys of the markup_values will be replaced with their values.
*/
public static function mla_parse_template( $tpl, $markup_values ) {
// If templates are present we must step through $tpl and expand them
if ( isset( $markup_values['[+template_count+]'] ) ) {
$offset = 0;
while ( false !== $start = strpos( $tpl, '[+', $offset ) ) {
if ( $template_content = self::_find_template_substring( substr( $tpl, $start ) ) ) {
$template_length = strlen( $template_content );
$template_content = substr( $template_content, 11, $template_length - (11 + 2) );
$template_content = self::_expand_field_level_template( $template_content, $markup_values );
$tpl = substr_replace( $tpl, $template_content, $start, $template_length );
$offset = $start;
} else { // found template
if ( false === $end = strpos( $tpl, '+]', $offset ) ) {
/* translators: 1: ERROR tag 2: template excerpt */
MLACore::mla_debug_add( __LINE__ . ' ' . sprintf( _x( '%1$s: mla_parse_template no end delimiter, tail = "%2$s".', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), substr( $tpl, $offset ) ), MLACore::MLA_DEBUG_CATEGORY_ANY );
return $tpl;
} // no end delimiter
$key = substr( $tpl, $start + 2, $end - $start - 2 );
if ( isset( $markup_values[ $key ] ) && is_scalar( $markup_values[ $key ] ) ) {
$tpl = substr_replace( $tpl, $markup_values[ $key ], $start, strlen( $key ) + 4 );
$offset = $start;
} else { // found key and scalar value
$offset += strlen( $key ) + 4;
}
} // simple substitution
} // while substitution parameter present
} else { // template(s) present
// No templates means a simple string substitution will suffice
foreach ( $markup_values as $key => $value ) {
if ( is_scalar( $value ) ) {
$tpl = str_replace( '[+' . $key . '+]', $value, $tpl );
}
}
}
return $tpl;
}
/**
* Find a complete delimited element, balancing opening and closing delimiters
*
* @since 1.50
*
* @param string $tpl A string possibly starting with the $open_delimiter
* @param string $open_delimiter Optional opening delimiter, default '('
* @param string $close_delimiter Optional closing delimiter, default ')'
*
* @return string '' or template substring including the opening and closing delimiters
*/
private static function _find_delimited_substring( $tpl, $open_delimiter = '(', $close_delimiter = ')' ) {
if ( $open_delimiter == $tpl[0] ) {
$nest = 1;
$level = 1;
do {
$test_end = strpos( $tpl, $close_delimiter, $nest );
if ( false === $test_end ) {
/* translators: 1: ERROR tag 2: template string */
MLACore::mla_debug_add( __LINE__ . ' ' . sprintf( _x( '%1$s: _find_delimited_substring no end delimiter, tail = "%2$s".', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), substr( $tpl, $nest ) ), MLACore::MLA_DEBUG_CATEGORY_ANY );
return '';
}
$nest = strpos( $tpl, $open_delimiter, $nest );
if ( false === $nest ) {
$nest = $test_end + 1;
$level--;
} elseif ( $nest < $test_end ) {
$nest += 1;
$level++;
} else {
$nest = $test_end + 1;
$level--;
}
} while ( $level );
$test_length = $test_end + 1;
$test_content = substr( $tpl, 0, $test_length );
return $test_content;
} // found test element
return '';
}
/**
* Convert field-level "template:" string into its component parts
*
* @since 1.50
*
* @param string Template content with string, test and choice elements
*
* @return array ( node => array( type => "string | test | choice | template", length => bytes, value => string | node(s) ) )
*/
private static function _parse_field_level_template( $tpl ) {
$index = 0;
$max_length = strlen( $tpl );
$test_level = 0;
$output = '';
$output_values = array();
$choice_values = array();
while ( $index < $max_length ) {
$byte = $tpl[ $index++ ];
if ( '\\' == $byte ) {
if ( $index == $max_length ) {
$output .= $byte;
continue;
} // template ends with a backslash
switch ( $tpl[ $index ] ) {
case 'n':
$output .= chr( 0x0A );
break;
case 'r':
$output .= chr( 0x0D );
break;
case 't':
$output .= chr( 0x09 );
break;
case 'b':
$output .= chr( 0x08 );
break;
case 'f':
$output .= chr( 0x0C );
break;
default: // could be a 1- to 3-digit octal value
if ( $max_length < ( $digit_limit = $index + 3 ) ) {
$digit_limit = $max_length;
}
$digit_index = $index;
while ( $digit_index < $digit_limit )
if ( ! ctype_digit( $tpl[ $digit_index ] ) ) {
break;
} else {
$digit_index++;
}
if ( $digit_count = $digit_index - $index ) {
$output .= chr( octdec( substr( $tpl, $index, $digit_count ) ) );
$index += $digit_count - 1;
} else { // accept the character following the backslash
$output .= $tpl[ $index ];
}
} // switch
$index++;
} // REVERSE SOLIDUS (backslash)
elseif ( '(' == $byte ) {
if ( ! empty( $output ) ) {
$output_values[] = array( 'type' => 'string', 'value' => $output, 'length' => strlen( $output ) );
$output = '';
}
$test_content = self::_find_delimited_substring( substr( $tpl, $index - 1 ) );
if ( 2 < $test_length = strlen( $test_content ) ) {
$values = self::_parse_field_level_template( substr( $test_content, 1, strlen( $test_content ) - 2 ) );
$output_values[] = array( 'type' => 'test', 'value' => $values, 'length' => strlen( $test_content ) );
$index += strlen( $test_content ) - 1;
} // found a value
elseif ( 2 == $test_length ) {
$index++; // empty test string
} else {
$test_content = __( 'ERROR', 'media-library-assistant' ) . ': ' . __( 'Test; no closing parenthesis ', 'media-library-assistant' );
$output_values[] = array( 'type' => 'string', 'value' => $test_content, 'length' => strlen( $test_content ) );
} // bad test string
} // (test) element
elseif ( '|' == $byte ) {
/*
* Turn each alternative within a choice element into a conditional
*/
if ( ! empty( $output ) ) {
$output_values[] = array( 'type' => 'string', 'value' => $output, 'length' => strlen( $output ) );
$output = '';
}
$length = 0;
foreach ( $output_values as $value )
if ( isset( $value['length'] ) ) {
$length += $value['length'];
}
$choice_values[] = array( 'type' => 'test', 'value' => $output_values, 'length' => $length );
$output_values = array();
} // choice element
elseif ( '[' == $byte && '+template:' == substr( $tpl, $index, 10 ) ) {
if ( ! empty( $output ) ) {
$output_values[] = array( 'type' => 'string', 'value' => $output, 'length' => strlen( $output ) );
$output = '';
}
$template_content = self::_find_template_substring( substr( $tpl, $index - 1 ) );
$values = self::_parse_field_level_template( substr( $template_content, 11, strlen( $template_content ) - (11 + 2) ) );
if ( 'template' == $values['type'] ) {
$output_values = array_merge( $output_values, $values['value'] );
} else {
$output_values[] = $values;
}
$index += strlen( $template_content ) - 1;
} // nested template
elseif ( '[' == $byte ) {
$match_count = preg_match( '/\[\+.+?\+\]/', $tpl, $matches, 0, $index - 1 );
if ( $match_count ) {
// found substitution parameter
$output .= $matches[0];
$index += strlen( $matches[0] ) - 1;
} else {
$output .= $byte;
}
} // maybe substitution parameter
else {
$output .= $byte;
}
} // $index < $max_length
if ( ! empty( $output ) ) {
$output_values[] = array( 'type' => 'string', 'value' => $output, 'length' => strlen( $output ) );
}
if ( ! empty( $choice_values ) ) {
if ( ! empty( $output_values ) ) {
$length = 0;
foreach ( $output_values as $value )
if ( isset( $value['length'] ) ) {
$length += $value['length'];
}
$choice_values[] = array( 'type' => 'test', 'value' => $output_values, 'length' => $length );
}
return array( 'type' => 'choice', 'value' => $choice_values, 'length' => $max_length );
}
if ( 1 == count( $output_values ) ) {
return $output_values[0];
}
return array ( 'type' => 'template', 'value' => $output_values, 'length' => $max_length );
}
/**
* Analyze a field-level "template:" element, expanding Field-level Markup Substitution Parameters
*
* Will return an array of values if one or more of the placeholders returns an array.
*
* @since 1.50
*
* @param array A field-level template element node
* @param array An array of markup substitution values
*
* @return mixed string or array, depending on placeholder values. Placeholders corresponding to the keys of the markup_values will be replaced with their values.
*/
private static function _evaluate_template_array_node( $node, $markup_values = array() ) {
$result = array();
/*
* Check for an array of sub-nodes
*/
if ( ! isset( $node['type'] ) ) {
foreach ( $node as $value ) {
$node_result = self::_evaluate_template_array_node( $value, $markup_values );
foreach ( $node_result as $value )
$result[] = $value;
}
} else { // array of sub-nodes
switch ( $node['type'] ) {
case 'string':
$result[] = self::mla_parse_array_template( $node['value'], $markup_values );
break;
case 'test':
$node_value = $node['value'];
if ( isset( $node_value['type'] ) ) {
$node_result = self::_evaluate_template_array_node( $node_value, $markup_values );
foreach ( $node_result as $value )
$result[] = $value;
} else { // single node
foreach ( $node_value as $value ) {
$node_result = self::_evaluate_template_array_node( $value, $markup_values );
foreach ( $node_result as $value )
$result[] = $value;
}
} // array of nodes
foreach ($result as $element )
if ( is_scalar( $element ) && false !== strpos( $element, '[+' ) ) {
$result = array();
break;
} elseif ( is_array( $element ) ) {
foreach ( $element as $value )
if ( is_scalar( $value ) && false !== strpos( $value, '[+' ) ) {
$result = array();
break;
}
} // is_array
break;
case 'choice':
foreach ( $node['value'] as $value ) {
$node_result = self::_evaluate_template_array_node( $value, $markup_values );
if ( ! empty( $node_result ) ) {
foreach ( $node_result as $value )
$result[] = $value;
break;
}
}
break;
case 'template':
foreach ( $node['value'] as $value ) {
$node_result = self::_evaluate_template_array_node( $value, $markup_values );
foreach ( $node_result as $value )
$result[] = $value;
}
break;
default:
/* translators: 1: ERROR tag 2: node type, e.g., template */
MLACore::mla_debug_add( __LINE__ . sprintf( _x( '%1$s: _evaluate_template_array_node unknown type "%2$s".', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), $node ), MLACore::MLA_DEBUG_CATEGORY_ANY );
} // node type
} // isset node type
return $result;
}
/**
* Analyze a field-level "template:" element, expanding Field-level Markup Substitution Parameters
*
* @since 1.50
*
* @param array A field-level template element node
* @param array An array of markup substitution values
*
* @return string String with expanded values, if any
*/
private static function _evaluate_template_node( $node, $markup_values = array() ) {
$results = '';
/*
* Check for an array of sub-nodes
*/
if ( ! isset( $node['type'] ) ) {
foreach ( $node as $value )
$results .= self::_evaluate_template_node( $value, $markup_values );
return $results;
} // array of sub-nodes
switch ( $node['type'] ) {
case 'string':
return self::mla_parse_template( $node['value'], $markup_values );
case 'test':
$node_value = $node['value'];
if ( isset( $node_value['type'] ) ) {
$results = self::_evaluate_template_node( $node_value, $markup_values );
} else { // single node
foreach ( $node_value as $value )
$results .= self::_evaluate_template_node( $value, $markup_values );
} // array of nodes
if ( false === strpos( $results, '[+' ) ) {
return $results;
}
break;
case 'choice':
foreach ( $node['value'] as $value ) {
$results = self::_evaluate_template_node( $value, $markup_values );
if ( ! empty( $results ) ) {
return $results;
}
}
break;
case 'template':
foreach ( $node['value'] as $value )
$results .= self::_evaluate_template_node( $value, $markup_values );
return $results;
default:
/* translators: 1: ERROR tag 2: node type, e.g., template */
MLACore::mla_debug_add( __LINE__ . sprintf( _x( '%1$s: _evaluate_template_node unknown type "%2$s".', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), $node ), MLACore::MLA_DEBUG_CATEGORY_ANY );
} // node type
return '';
}
/**
* Analyze a field-level "template:" element, expanding Field-level Markup Substitution Parameters
*
* @since 1.50
*
* @param string A formatting string containing [+placeholders+]
* @param array An array of markup substitution values
* @param boolean True to return array value(s), false to return a string
*
* @return mixed Element with expanded string/array values, if any
*/
private static function _expand_field_level_template( $tpl, $markup_values = array(), $return_arrays = false ) {
/*
* Step 1: parse the template and build the tree of its elements
* root node => array( type => "string | test | choice | template", value => string | node(s) )
*/
$root_element = self::_parse_field_level_template( $tpl );
unset( $markup_values['[+template_count+]'] );
/*
* Step 2: Remove all the empty elements from the $markup_values,
* so the evaluation of conditional and choice elements is simplified.
*/
foreach ( $markup_values as $key => $value ) {
if ( is_scalar( $value ) ) {
$value = trim( $value );
}
if ( empty( $value ) ) {
unset( $markup_values[ $key ] );
}
}
/*
* Step 3: walk the element tree and process each node
*/
if ( $return_arrays ) {
$results = self::_evaluate_template_array_node( $root_element, $markup_values );
} else {
$results = self::_evaluate_template_node( $root_element, $markup_values );
}
return $results;
}
/**
* Process an markup field array value according to the supplied data-format option
*
* @since 1.50
*
* @param array an array of scalar values
* @param string data option; 'text'|'single'|'export'|'array'|'multi'
* @param boolean Optional: for option 'multi', retain existing values
*
* @return array ( parameter => value ) for all field-level parameters and anything in $markup_values
*/
private static function _process_field_level_array( $record, $option = 'text', $keep_existing = false ) {
switch ( $option ) {
case 'single':
$text = sanitize_text_field( current( $record ) );
break;
case 'export':
$text = sanitize_text_field( var_export( $record, true ) );
break;
case 'unpack':
if ( is_array( $record ) ) {
$clean_data = array();
foreach ( $record as $key => $value ) {
if ( is_array( $value ) ) {
$clean_data[ $key ] = '(ARRAY)';
} elseif ( is_string( $value ) ) {
$clean_data[ $key ] = self::_bin_to_utf8( substr( $value, 0, 256 ) );
} else {
$clean_data[ $key ] = $value;
}
}
$text = sanitize_text_field( var_export( $clean_data, true) );
} else {
$text = sanitize_text_field( var_export( $record, true ) );
}
break;
case 'multi':
$record[0x80000000] = 'multi';
$record[0x80000001] = $keep_existing;
// fallthru
case 'array':
$text = $record;
break;
default: // or 'text'
$text = '';
foreach ( $record as $term ) {
$term_name = sanitize_text_field( $term );
$text .= strlen( $text ) ? ', ' . $term_name : $term_name;
}
} // $option
return $text;
}
/**
* Process an argument list within a field-level parameter format specification
*
* @since 2.02
*
* @param string arguments, e.g., ('d/m/Y H:i:s' , "arg, \" two" ) without parens
*
* @return array individual arguments, e.g. array( 0 => 'd/m/Y H:i:s', 1 => 'arg, " two' )
*/
private static function _parse_arguments( $argument_string ) {
$argument_string = trim( $argument_string, " \n\t\r\0\x0B," );
$arguments = array();
while ( strlen( $argument_string ) ) {
$argument = '';
$index = 0;
// Check for array or enclosing quotes
$delimiter = $argument_string[0];
// Check for array
if ( '{' == $delimiter ) {
$array = self::_find_delimited_substring( substr( $argument_string, $index ), '{', '}' );
if ( 2 < strlen( $array ) ) {
$content = substr( $array, 1, strlen( $array ) - 2 );
$argument = self::_parse_arguments( $content );
$index += strlen( $array );
} else {
// Bad array format, skip the rest
$argument = '';
$index = strlen( $argument_string );
}
} else {
// Check for enclosing quotes
if ( '\'' == $delimiter || '"' == $delimiter ) {
$index++;
} else {
$delimiter = '';
}
while ( $index < strlen( $argument_string ) ) {
$byte = $argument_string[ $index++ ];
if ( '\\' == $byte ) {
switch ( $argument_string[ $index ] ) {
case 'n':
$argument .= chr( 0x0A );
break;
case 'r':
$argument .= chr( 0x0D );
break;
case 't':
$argument .= chr( 0x09 );
break;
case 'b':
$argument .= chr( 0x08 );
break;
case 'f':
$argument .= chr( 0x0C );
break;
default: // could be a 1- to 3-digit octal value
$digit_limit = $index + 3;
$digit_index = $index;
while ( $digit_index < $digit_limit ) {
if ( ! ctype_digit( $argument_string[ $digit_index ] ) ) {
break;
} else {
$digit_index++;
}
}
if ( $digit_count = $digit_index - $index ) {
$argument .= chr( octdec( substr( $argument_string, $index, $digit_count ) ) );
$index += $digit_count - 1;
} else { // accept the character following the backslash
$argument .= $argument_string[ $index ];
}
} // switch
$index++;
} else { // backslash
if ( $delimiter == $byte || ( empty( $delimiter ) && ',' == $byte ) ) {
break;
}
$argument .= $byte;
} // just another 8-bit value, but check for closing delimiter
} // index < strlen
} // non-array
$arguments[] = $argument;
$argument_string = trim( substr( $argument_string, $index ), " \n\t\r\0\x0B," );
} // strlen( $argument_string )
return $arguments;
}
/**
* Regular expression pattern/subpattern matches
*
* This array contains values matched in the "match" and "extract" format/option functions,
* making them available for the "matches:" data source prefix.
*
* @since 2.71
*
* @var array
*/
public static $regex_matches = array();
/**
* Clear out the matches: prefix values
*
* @since 2.71
*
* @param int current attachment ID
*/
public static function mla_reset_regex_matches( $post_id ) {
static $current_id = 0;
// Do this once per post.
if ( $current_id != $post_id ) {
$current_id = $post_id;
MLAData::$regex_matches = array();
}
}
/**
* Intercept regex pattern matching errors
*
* @since 2.54
*
* @param int the level of the error raised
* @param string the error message
* @param string the filename that the error was raised in
* @param int the line number the error was raised at
*
* @return boolean true, to bypass PHP error handler
*/
public static function preg_error_handler( $type, $string, $file, $line ) {
MLACore::mla_debug_add( __LINE__ . " MLAData::preg_error_handler( $type, $string, $file, $line )", MLACore::MLA_DEBUG_CATEGORY_ANY );
// Don't execute PHP internal error handler
return true;
}
/**
* Apply field-level format options to field-level content
*
* @since 2.10
*
* @param string field-level content
* @param array format code and aguments
*
* @return string formatted field-level content
*/
public static function mla_apply_field_level_format( $value, $args ) {
if ( empty( $value ) ) {
return $value;
}
switch ( $args['format'] ) {
case 'native':
case 'raw':
break;
case 'attr':
$value = esc_attr( $value );
break;
case 'url':
$value = urlencode( $value );
break;
case 'commas':
if ( is_numeric( $value ) ) {
$value = number_format( (float)$value );
}
break;
case 'kbmb':
if ( is_numeric( $value ) ) {
$number = (string) $value;
} else {
$index = 0;
$number = '';
while ( $index < strlen( $value ) ) {
if ( ctype_digit( $value[ $index ] ) || ( '.' == $value[ $index ] ) ) {
$number .= $value[ $index ];
}
$index++;
}
}
$threshold = 10240;
$kb_suffix = ' KB';
$mb_suffix = ' MB';
$precision = 3;
if ( is_array( $args['args'] ) ) {
$precision = isset( $args['args'][3] ) ? absint( $args['args'][3] ) : $precision;
$mb_suffix = isset( $args['args'][2] ) ? $args['args'][2] : $mb_suffix;
$kb_suffix = isset( $args['args'][1] ) ? $args['args'][1] : $kb_suffix;
$args['args'] = $args['args'][0];
}
if ( is_numeric( $args['args'] ) ) {
$threshold = absint( $args['args'] );
}
$number = (float) $number;
if ( 1048576 < $number ) {
$value = number_format( ( $number/1048576 ), $precision ) . $mb_suffix;
} elseif ( $threshold < $number ) {
$value = number_format( ( $number/1024 ), $precision ) . $kb_suffix;
} else {
$value = number_format( $number );
}
break;
case 'timestamp':
if ( is_numeric( $value ) ) {
$modifier = '';
$format = empty( $args['args'] ) ? 'd/m/Y H:i:s' : $args['args'];
if ( is_array( $format ) ) {
$modifier = strtolower( trim( $format[1] ) );
$format = $format[0];
}
switch ( $modifier ) {
case 'i18n':
$value = date_i18n( $format, (integer) $value );
break;
case 'age':
$value = sprintf( $format, human_time_diff( (integer) $value ) );
break;
default:
// date "Returns a string formatted according to the given format string using the given integer"
$value = date( $format, (integer) $value );
}
}
break;
case 'date':
/*
* strtotime will "Parse about any English textual datetime description into a Unix timestamp"
* If it succeeds we can format the timestamp for display
*/
$timestamp = strtotime( $value );
if( false !== $timestamp ) {
$modifier = '';
$format = empty( $args['args'] ) ? 'd/m/Y H:i:s' : $args['args'];
if ( is_array( $format ) ) {
$modifier = strtolower( trim( $format[1] ) );
$format = $format[0];
}
switch ( $modifier ) {
case 'i18n':
$value = date_i18n( $format, $timestamp );
break;
case 'age':
$value = sprintf( $format, human_time_diff( $timestamp ) );
break;
default:
$value = date( $format, $timestamp );
}
}
break;
case 'fraction':
$show_fractions = true;
if ( ! empty( $args['args'] ) ) {
if ( is_array( $args['args'] ) ) {
if ( is_numeric( $args['args'][0] ) ) {
$format = '%1$+.' . absint( $args['args'][0] ) . 'f';
} else {
$format = $args['args'][0];
}
$show_fractions = ( 'false' !== strtolower( trim( $args['args'][1] ) ) );
} else {
if ( is_numeric( $args['args'] ) ) {
$format = '%1$+.' . absint( $args['args'] ) . 'f';
} else {
$format = $args['args'];
}
}
} else {
$format = '%1$+.2f';
}
$fragments = array_map( 'intval', explode( '/', $value ) );
if ( 1 == count( $fragments ) ) {
$value = trim( $value );
if ( ! empty( $value ) ) {
$value = $value;
}
} else {
if ( $fragments[0] ) {
if ( 1 == $fragments[1] ) {
$value = sprintf( '%1$+d', $fragments[0] );
} elseif ( 0 != $fragments[1] ) {
$value = $fragments[0] / $fragments[1];
if ( $show_fractions && ( -1 <= $value ) && ( 1 >= $value ) ) {
$value = sprintf( '%1$+d/%2$d', $fragments[0], $fragments[1] );
} else {
$value = sprintf( $format, $value );
}
} // fractional value
} // non-zero numerator
} // valid denominator
break;
case 'substr':
$start = 0;
$length = strlen( $value );
if ( ! empty( $args['args'] ) ) {
if ( is_array( $args['args'] ) ) {
$start = intval( $args['args'][0] );
if ( 1 < count( $args['args'] ) ) {
$length = intval( $args['args'][1] );
}
} else {
$start = intval( $args['args'] );
}
}
if ( false === $value = substr( $value, $start, $length ) ) {
$value = '';
}
break;
case 'str_replace':
if ( is_array( $args['args'] ) && ( 2 === count( $args['args'] ) ) ) {
$value = str_replace( $args['args'][0], $args['args'][1], $value );
}
break;
case 'match':
$pattern = NULL;
if ( ! empty( $args['args'] ) ) {
if ( is_array( $args['args'] ) ) {
$pattern = trim( $args['args'][0] );
} else {
$pattern = trim( $args['args'] );
}
}
if ( empty( $pattern ) ) {
// No pattern - return empty value
$value = '';
break;
}
set_error_handler( 'MLAData::preg_error_handler' );
try {
$count = preg_match( $pattern, $value, $matches );
} catch ( Throwable $e ) { // PHP 7
} catch ( Exception $e ) { // PHP 5
}
restore_error_handler();
if ( $count ) {
// array_merge won't work because it handles numeric keys differently
foreach ( $matches as $matches_key => $matches_value ) {
MLAData::$regex_matches[ $matches_key ] = $matches_value;
}
$value = MLAData::$regex_matches[0];
} else {
// No pattern or no match
$value = '';
}
break;
case 'extract':
$pattern = NULL;
$return_value = NULL;
if ( ! empty( $args['args'] ) ) {
if ( is_array( $args['args'] ) ) {
$pattern = trim( $args['args'][0] );
if ( 1 < count( $args['args'] ) ) {
$return_value = $args['args'][1];
if ( is_numeric( $return_value ) ) {
$return_value = intval( $return_value );
}
}
} else {
$pattern = trim( $args['args'] );
}
}
if ( empty( $pattern ) ) {
// No pattern - return empty value
$value = '';
break;
}
set_error_handler( 'MLAData::preg_error_handler' );
try {
$count = preg_match( $pattern, $value, $matches );
} catch ( Throwable $e ) { // PHP 7
} catch ( Exception $e ) { // PHP 5
}
restore_error_handler();
if ( $count ) {
// array_merge won't work because it handles numeric keys differently
foreach ( $matches as $matches_key => $matches_value ) {
MLAData::$regex_matches[ $matches_key ] = $matches_value;
}
} else {
// No pattern or no match
$value = '';
break;
}
if ( !is_null( $return_value ) && isset( MLAData::$regex_matches[ $return_value ] ) ) {
$value = MLAData::$regex_matches[ $return_value ];
} else {
$value = '';
}
break;
case 'replace':
$pattern = NULL;
$replacement = NULL;
$return_value = false;
if ( ! empty( $args['args'] ) ) {
if ( is_array( $args['args'] ) ) {
$pattern = trim( $args['args'][0] );
if ( 1 < count( $args['args'] ) ) {
$replacement = trim( $args['args'][1] );
}
if ( 2 < count( $args['args'] ) ) {
$return_value = 'true' === strtolower( trim( $args['args'][2] ) );
}
} else {
$pattern = trim( $args['args'] );
}
}
if ( empty( $pattern ) || empty( $replacement ) ) {
// No pattern or no replacement - return unaltered value
break;
}
// Save original value in case replacement fails
$old_value = $value;
// If $return_value is true we return only the matched portion with the modifications applied
if ( $return_value ) {
set_error_handler( 'MLAData::preg_error_handler' );
try {
$count = preg_match( $pattern, $value, $matches );
} catch ( Throwable $e ) { // PHP 7
} catch ( Exception $e ) { // PHP 5
}
restore_error_handler();
if ( $count ) {
// Keep only the matched portion of the original value
$value = $matches[0];
} else {
// No match - return unaltered value
break;
}
}
set_error_handler( 'MLAData::preg_error_handler' );
try {
$value = preg_replace( $pattern, $replacement, $value );
} catch ( Throwable $e ) { // PHP 7
} catch ( Exception $e ) { // PHP 5
}
restore_error_handler();
// Check or error, i.e., bad $replacement pattern
if ( is_null( $value ) ) {
$value = $old_value;
}
break;
default:
$value = apply_filters( 'mla_apply_custom_format', $value, $args );
}
return $value;
}
/**
* Analyze a template, expanding Field-level Markup Substitution Parameters
*
* Field-level parameters must have one of the following prefix values:
* template, meta, query, request, terms, custom, iptc, exif, xmp, id3, pdf, matches.
* All but request and query require an attachment ID.
*
* @since 1.50
*
* @param string A formatting string containing [+placeholders+]
* @param array Optional: an array of values from the query, if any, e.g. shortcode parameters
* @param array Optional: an array of values to add to the returned array
* @param integer Optional: attachment ID for attachment-specific placeholders
* @param boolean Optional: for option 'multi', retain existing values
* @param string Optional: default option value
* @param array Optional: attachment_metadata, required during item uploads
*
* @return array ( parameter => value ) for all field-level parameters and anything in $markup_values
*/
public static function mla_expand_field_level_parameters( $tpl, $query = NULL, $markup_values = array(), $post_id = 0, $keep_existing = false, $default_option = 'text', $upload_metadata = NULL ) {
static $cached_post_id = 0, $item_metadata = NULL, $attachment_metadata = NULL, $id3_metadata = NULL;
if ( $cached_post_id != $post_id ) {
$item_metadata = NULL;
$attachment_metadata = NULL;
$id3_metadata = NULL;
MLAData::mla_reset_regex_matches( $post_id );
$cached_post_id = $post_id;
}
$template_count = 0;
$placeholders = self::mla_get_template_placeholders( $tpl, $default_option );
foreach ($placeholders as $key => $value ) {
// Braces in the key must become brackets for template parsing
$key = str_replace( '{', '[', str_replace( '}', ']', $key ) );
if ( isset( $markup_values[ $key ] ) ) {
continue;
}
switch ( $value['prefix'] ) {
case 'template':
$markup_values = self::mla_expand_field_level_parameters( $value['value'], $query , $markup_values, $post_id, $keep_existing, $default_option, $upload_metadata );
$template_count++;
break;
case 'meta':
if ( is_null( $item_metadata ) ) {
if ( is_array( $upload_metadata ) ) {
$item_metadata = $upload_metadata;
} elseif ( 0 < $post_id ) {
$item_metadata = get_metadata( 'post', $post_id, '_wp_attachment_metadata', true );
} else {
break;
}
}
$markup_values[ $key ] = self::mla_find_array_element( $value['value'], $item_metadata, $value['option'] );
break;
case 'query':
if ( isset( $query ) && isset( $query[ $value['value'] ] ) ) {
$markup_values[ $key ] = $query[ $value['value'] ];
} else {
$markup_values[ $key ] = '';
}
break;
case 'request':
if ( isset( $_REQUEST[ $value['value'] ] ) ) {
$record = $_REQUEST[ $value['value'] ];
} else {
// Look for compound names, e.g., tax_input.attachment_category
$key_array = explode( '.', $value['value'] );
if ( 1 < count( $key_array ) && isset( $_REQUEST[ $key_array[0] ] ) ) {
$array_value = array( $key_array[0] => $_REQUEST[ $key_array[0] ] );
$record = MLAData::mla_find_array_element( $value['value'], $array_value, $value['option'], false, ',' );
} else {
$record = '';
}
}
if ( 'raw' == $value['format'] ) {
$text = $record;
} elseif ( is_scalar( $record ) ) {
$text = sanitize_text_field( (string) $record );
} elseif ( is_array( $record ) ) {
if ( 'export' == $value['option'] ) {
$text = sanitize_text_field( var_export( $record, true ) );
} else {
$text = '';
foreach ( $record as $term ) {
$term_name = sanitize_text_field( $term );
$text .= strlen( $text ) ? ',' . $term_name : $term_name;
}
}
} // is_array
$markup_values[ $key ] = $text;
break;
case 'terms':
// Look for field specification
$match_count = preg_match( '/^(.+)\((.+)\)/', $value['value'], $matches );
if ( $match_count ) {
$taxonomy = $matches[1];
$field = $matches[2];
} else {
$taxonomy = $value['value'];
$field = 'name';
}
// Look for compound taxonomy.slug notation
$matches = explode( '.', $taxonomy );
if ( 2 === count( $matches ) ) {
$slug = str_replace( '{+', '[+', str_replace( '+}', '+]', stripslashes( $matches[1] ) ) );
$replacement_values = MLAData::mla_expand_field_level_parameters( $slug, $query, $markup_values );
$slug = MLAData::mla_parse_template( $slug, $replacement_values );
$term = get_term_by( 'slug', $slug, $matches[0] );
if ( false === $term ) {
break;
}
$terms = array( $term );
} else {
if ( 0 < $post_id ) {
$terms = get_object_term_cache( $post_id, $taxonomy );
if ( false === $terms ) {
$terms = wp_get_object_terms( $post_id, $taxonomy );
wp_cache_add( $post_id, $terms, $taxonomy . '_relationships' );
}
} else {
break;
}
}
$text = '';
if ( is_wp_error( $terms ) ) {
$text = implode( ',', $terms->get_error_messages() );
} elseif ( ! empty( $terms ) ) {
if ( 'single' == $value['option'] || 1 == count( $terms ) ) {
reset( $terms );
$term = current( $terms );
$fields = get_object_vars( $term );
$text = isset( $fields[ $field ] ) ? $fields[ $field ] : $fields['name'];
$text = sanitize_term_field( $field, $text, $term->term_id, $taxonomy, 'display' );
} elseif ( ( 'export' == $value['option'] ) || ( 'unpack' == $value['option'] ) ) {
$text = sanitize_text_field( var_export( $terms, true ) );
} else {
foreach ( $terms as $term ) {
$fields = get_object_vars( $term );
$field_value = isset( $fields[ $field ] ) ? $fields[ $field ] : $fields['name'];
$field_value = sanitize_term_field( $field, $field_value, $term->term_id, $taxonomy, 'display' );
$text .= strlen( $text ) ? ', ' . $field_value : $field_value;
}
}
}
$markup_values[ $key ] = $text;
break;
case 'custom':
if ( 0 < $post_id ) {
$record = get_metadata( 'post', $post_id, $value['value'], 'single' == $value['option'] );
if ( empty( $record ) && 'ALL_CUSTOM' == $value['value'] ) {
$meta_values = MLAQuery::mla_fetch_attachment_metadata( $post_id );
$clean_data = array();
foreach( $meta_values as $meta_key => $meta_value ) {
if ( 0 !== strpos( $meta_key, 'mla_item_' ) ) {
continue;
}
$meta_key = substr( $meta_key, 9 );
if ( is_array( $meta_value ) ) {
$clean_data[ $meta_key ] = '(ARRAY)';
} elseif ( is_string( $meta_value ) ) {
$clean_data[ $meta_key ] = self::_bin_to_utf8( substr( $meta_value, 0, 256 ) );
} else {
$clean_data[ $meta_key ] = $meta_value;
}
} // foreach value
/*
* Convert the array to text, strip the outer "array( ... ,)" literal,
* the interior linefeed/space/space separators and backslashes.
*/
$record = var_export( $clean_data, true);
$record = substr( $record, 7, strlen( $record ) - 10 );
$record = str_replace( chr(0x0A).' ', ' ', $record );
$record = str_replace( '\\', '', $record );
} // ALL_CUSTOM
} else {
break;
}
$text = '';
if ( is_wp_error( $record ) ) {
$text = implode( ',', $record->get_error_messages() );
} elseif ( ! empty( $record ) ) {
if ( is_scalar( $record ) ) {
$text = ( 'raw' == $value['format'] ) ? (string) $record : sanitize_text_field( (string) $record );
} elseif ( is_array( $record ) ) {
if ( 'export' == $value['option'] ) {
$text = ( 'raw' == $value['format'] ) ? var_export( $record, true ) : sanitize_text_field( var_export( $record, true ) );
} else {
$text = '';
foreach ( $record as $term ) {
$term_name = ( 'raw' == $value['format'] ) ? $term : sanitize_text_field( $term );
$text .= strlen( $text ) ? ', ' . $term_name : $term_name;
}
}
} // is_array
} // ! empty
$markup_values[ $key ] = $text;
break;
case 'iptc':
if ( is_null( $attachment_metadata ) ) {
if ( 0 < $post_id ) {
$attachment_metadata = self::mla_fetch_attachment_image_metadata( $post_id );
} else {
break;
}
}
$markup_values[ $key ] = self::mla_iptc_metadata_value( $value['value'], $attachment_metadata, $value['option'], $keep_existing );
break;
case 'exif':
if ( is_null( $attachment_metadata ) ) {
if ( 0 < $post_id ) {
$attachment_metadata = self::mla_fetch_attachment_image_metadata( $post_id );
} else {
break;
}
}
$record = self::mla_exif_metadata_value( $value['value'], $attachment_metadata, $value['option'], $keep_existing );
if ( is_array( $record ) ) {
$markup_values[ $key ] = self::_process_field_level_array( $record, $value['option'], $keep_existing );
} else {
$markup_values[ $key ] = $record;
}
break;
case 'xmp':
if ( is_null( $attachment_metadata ) ) {
if ( 0 < $post_id ) {
$attachment_metadata = self::mla_fetch_attachment_image_metadata( $post_id );
} else {
break;
}
}
$markup_values[ $key ] = self::mla_xmp_metadata_value( $value['value'], $attachment_metadata['mla_xmp_metadata'], $value['option'], $keep_existing );
break;
case 'id3':
if ( is_null( $id3_metadata ) ) {
if ( 0 < $post_id ) {
$id3_metadata = self::mla_fetch_attachment_id3_metadata( $post_id );
} else {
break;
}
}
$markup_values[ $key ] = self::mla_id3_metadata_value( $value['value'], $id3_metadata, $value['option'], $keep_existing );
break;
case 'pdf':
if ( is_null( $attachment_metadata ) ) {
if ( 0 < $post_id ) {
$attachment_metadata = self::mla_fetch_attachment_image_metadata( $post_id );
} else {
break;
}
}
$record = self::mla_pdf_metadata_value( $value['value'], $attachment_metadata );
if ( is_array( $record ) ) {
$markup_values[ $key ] = self::_process_field_level_array( $record, $value['option'], $keep_existing );
} else {
$markup_values[ $key ] = $record;
}
break;
case 'matches':
$markup_values[ $key ] = isset( MLAData::$regex_matches[ $value['value'] ] ) ? MLAData::$regex_matches[ $value['value'] ] : '';
break;
case '':
$candidate = str_replace( '{', '[', str_replace( '}', ']', $value['value'] ) );
if ( MLAShortcodes::mla_is_data_source( $candidate ) ) {
$data_value = array(
'data_source' => $candidate,
'keep_existing' => false,
'format' => 'raw',
'option' => $value['option'] ); // single, export, text for array values, e.g., alt_text
$markup_values[ $key ] = MLAShortcodes::mla_get_data_source( $post_id, 'single_attachment_mapping', $data_value );
} elseif ( isset( $markup_values[ $value['value'] ] ) ) {
// A standard element can have a format modifier, e.g., commas, attr
$markup_values[ $key ] = $markup_values[ $value['value'] ];
} else {
$custom_value = apply_filters( 'mla_expand_custom_data_source', NULL, $key, $candidate, $value, $query, $markup_values, $post_id, $keep_existing, $default_option );
if ( !is_null( $custom_value ) ) {
$markup_values[ $key ] = $custom_value;
}
}
break;
default:
$custom_value = apply_filters( 'mla_expand_custom_prefix', NULL, $key, $value, $query, $markup_values, $post_id, $keep_existing, $default_option );
if ( !is_null( $custom_value ) ) {
$markup_values[ $key ] = $custom_value;
}
} // switch
if ( isset( $markup_values[ $key ] ) ) {
$markup_values[ $key ] = self::mla_apply_field_level_format( $markup_values[ $key ], $value );
} // isset( $markup_values[ $key ] )
} // foreach placeholder
if ( $template_count ) {
$markup_values['[+template_count+]'] = $template_count;
}
return $markup_values;
}
/**
* Analyze a template, returning an array of the placeholders it contains
*
* @since 0.90
*
* @param string A formatting string containing [+placeholders+]
* @param string Optional: default option value
*
* @return array Placeholder information: each entry is an array with
* ['prefix'] => string, ['value'] => string, ['option'] => string 'text'|single'|'export'|'array'|'multi'
*/
public static function mla_get_template_placeholders( $tpl, $default_option = 'text' ) {
$results = array();
/*
* Look for and process templates, removing them from the input so substitution parameters within
* the template are not expanded. They will be expanded when the template itself is expanded.
*/
while ( false !== ( $template_offset = strpos( $tpl, '[+template:' ) ) ) {
$nest = $template_offset + 11;
$level = 1;
do {
$template_end = strpos( $tpl, '+]', $nest );
if ( false === $template_end ) {
/* translators: 1: ERROR tag 2: template excerpt */
MLACore::mla_debug_add( __LINE__ . ' ' . sprintf( _x( '%1$s: mla_get_template_placeholders no template-end delimiter dump = "%2$s".', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), self::mla_hex_dump( substr( $tpl, $template_offset, 128 ), 128, 16 ) ), MLACore::MLA_DEBUG_CATEGORY_ANY );
return array();
}
$nest = strpos( $tpl, '[+', $nest );
if ( false === $nest ) {
$nest = $template_end + 2;
$level--;
} elseif ( $nest < $template_end ) {
$nest += 2;
$level++;
} else {
$nest = $template_end + 2;
$level--;
}
} while ( $level );
$template_length = $template_end + 2 - $template_offset;
$template_content = substr( $tpl, $template_offset + 11, $template_length - (11 + 2) );
$placeholders = self::mla_get_template_placeholders( $template_content );
$result = array( 'template:' . $template_content => array( 'prefix' => 'template', 'value' => $template_content, 'option' => $default_option, 'format' => 'native' ) );
$results = array_merge( $results, $result, $placeholders );
$tpl = substr_replace( $tpl, '', $template_offset, $template_length );
} // found a template
$match_count = preg_match_all( '/\[\+.+?\+\]/', $tpl, $matches );
if ( ( $match_count == false ) || ( $match_count == 0 ) ) {
return $results;
}
foreach ( $matches[0] as $match ) {
$key = substr( $match, 2, (strlen( $match ) - 4 ) );
$result = array( 'prefix' => '', 'value' => '', 'option' => $default_option, 'format' => 'native' );
$match_count = preg_match( '/\[\+([^,:]+):(.+)\+\]/', $match, $matches );
if ( 1 == $match_count ) {
$result['prefix'] = $matches[1];
$tail = $matches[2];
} else {
$tail = $key;
}
if ( false !== strpos( $tail, ',' ) ) {
$match_count = preg_match( '/([^,]+)(,(text|single|export|unpack|array|multi|commas|raw|attr|url|kbmb|timestamp|date|fraction|substr|str_replace|match|extract|replace))(\((.*)\)$)*/', $tail, $matches );
if ( 1 == $match_count ) {
$result['value'] = $matches[1];
if ( ! empty( $matches[5] ) ) {
$args = self::_parse_arguments( $matches[5] );
if ( 1 == count( $args ) ) {
$args = $args[0];
}
} else {
$args = '';
}
if ( in_array( $matches[3], array( 'commas', 'raw', 'attr', 'url', 'kbmb', 'timestamp', 'date', 'fraction', 'substr', 'str_replace', 'match', 'extract', 'replace' ) ) ) {
$result['option'] = 'text';
$result['format'] = $matches[3];
$result['args'] = $args;
} else {
$result['option'] = $matches[3];
}
} else {
$match_count = preg_match( '/([^,]+),([^(]+)(\(([^)]+)\))*/', $tail, $matches );
if ( 1 == $match_count ) {
$result['value'] = $matches[1];
$result['format'] = $matches[2];
if ( ! empty( $matches[4] ) ) {
$args = self::_parse_arguments( $matches[4] );
if ( 1 == count( $args ) ) {
$args = $args[0];
}
$result['args'] = $args;
}
} else {
$result['value'] = $tail;
}
}
} else {
$result['value'] = $tail;
}
$results[ $key ] = $result;
} // foreach
return $results;
}
/**
* WP_Query filter "parameters"
*
* This array defines parameters for the query's join, where and orderby filters.
* The parameters are set up in the _prepare_list_table_query function, and
* any further logic required to translate those values is contained in the filters.
*
* Array index values are: use_alt_text_view, use_postmeta_view, use_orderby_view,
* alt_text_value, postmeta_key, postmeta_value, patterns, detached,
* orderby, order, mla-metavalue, debug (also in search_parameters)
*
* @since 0.30
*
* @var array
*/
public static $query_parameters = array();
/**
* WP_Query 'posts_search' filter "parameters"
*
* This array defines parameters for the query's posts_search filter, which uses
* 'search_string' to add a clause to the query's WHERE clause. It is shared between
* the list_table-query functions here and the mla_get_shortcode_attachments function
* in class-mla-shortcodes.php. This array passes the relevant parameters to the filter.
*
* Array index values are:
* ['mla_terms_search']['phrases']
* ['mla_terms_search']['taxonomies']
* ['mla_terms_search']['radio_phrases'] => AND/OR
* ['mla_terms_search']['radio_terms'] => AND/OR
* ['s'] => numeric for ID/parent search
* ['mla_search_fields'] => 'title', 'name', 'alt-text', 'excerpt', 'content', 'file' ,'terms'
* Note: 'alt-text' and 'file' are not supported in [mla_gallery]
* ['mla_search_connector'] => AND/OR
* ['sentence'] => entire string must match as one "keyword"
* ['exact'] => entire string must match entire field value
* ['debug'] => internal element, console/log/shortcode/none
* ['tax_terms_count'] => internal element, shared with JOIN and GROUP BY filters
*
* @since 2.00
*
* @var array
*/
public static $search_parameters = array();
/**
* Get the total number of attachment posts
*
* Compatibility shim for MLAQuery::mla_count_list_table_items
*
* @since 0.30
*
* @param array Query variables, e.g., from $_REQUEST
* @param int (optional) number of rows to skip over to reach desired page
* @param int (optional) number of rows on each page
*
* @return integer Number of attachment posts
*/
public static function mla_count_list_table_items( $request, $offset = NULL, $count = NULL ) {
return MLAQuery::mla_count_list_table_items( $request, $offset, $count );
}
/**
* Retrieve attachment objects for list table display
*
* Compatibility shim for MLAQuery::mla_query_list_table_items
*
* @since 0.1
*
* @param array query parameters from web page, usually found in $_REQUEST
* @param int number of rows to skip over to reach desired page
* @param int number of rows on each page
*
* @return array attachment objects (posts) including parent data, meta data and references
*/
public static function mla_query_list_table_items( $request, $offset, $count ) {
return MLAQuery::mla_query_list_table_items( $request, $offset, $count );
}
/**
* Retrieve an Attachment array given a $post_id
*
* The (associative) array will contain every field that can be found in
* the posts and postmeta tables, and all references to the attachment.
*
* @since 0.1
* @uses $post WordPress global variable
*
* @param integer The ID of the attachment post
* @param boolean True to add references, false to skip references
*
* @return NULL|array NULL on failure else associative array
*/
public static function mla_get_attachment_by_id( $post_id, $add_references = true ) {
global $post;
static $save_id = -1, $post_data;
if ( $post_id == $save_id ) {
return $post_data;
} elseif ( $post_id == -1 ) {
$save_id = -1;
return NULL;
}
$item = get_post( $post_id );
if ( empty( $item ) ) {
/* translators: 1: ERROR tag 2: post ID */
MLACore::mla_debug_add( __LINE__ . sprintf( _x( '%1$s: mla_get_attachment_by_id(%2$d) not found.', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), $post_id ), MLACore::MLA_DEBUG_CATEGORY_ANY );
return NULL;
}
if ( $item->post_type != 'attachment' ) {
/* translators: 1: ERROR tag 2: post ID 3: post_type */
MLACore::mla_debug_add( __LINE__ . sprintf( _x( '%1$s: mla_get_attachment_by_id(%2$d) wrong post_type "%3$s".', 'error_log', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), $post_id, $item->post_type ), MLACore::MLA_DEBUG_CATEGORY_ANY );
return NULL;
}
$post_data = (array) $item;
$post = $item;
setup_postdata( $item );
/*
* Add parent data
*/
$post_data = array_merge( $post_data, MLAQuery::mla_fetch_attachment_parent_data( $post_data['post_parent'] ) );
/*
* Add meta data
*/
$post_data = array_merge( $post_data, MLAQuery::mla_fetch_attachment_metadata( $post_id ) );
/*
* Add references, if requested, or "empty" references array
*/
$post_data['mla_references'] = MLAQuery::mla_fetch_attachment_references( $post_id, $post_data['post_parent'], $add_references );
$save_id = $post_id;
return $post_data;
}
/**
* Adds or replaces the value of a key in a possibly nested array structure
*
* @since 1.51
*
* @param string key value, e.g. array1.array2.element
* @param mixed replacement value, string or array, by reference
* @param array PHP nested arrays, by reference
*
* @return boolean true if $needle element set, false if not
*/
private static function _set_array_element( $needle, &$value, &$haystack ) {
$key_array = explode( '.', $needle );
$key = array_shift( $key_array );
if ( empty( $key_array ) ) {
$haystack[ $key ] = $value;
return true;
} // lowest level
/*
* If an intermediate key is not an array, leave it alone and fail.
* If an intermediate key does not exist, create an empty array for it.
*/
if ( isset( $haystack[ $key ] ) ) {
if ( ! is_array( $haystack[ $key ] ) ) {
return false;
}
} else {
$haystack[ $key ] = array();
}
return self::_set_array_element( implode( $key_array, '.' ), $value, $haystack[ $key ] );
}
/**
* Deletes the value of a key in a possibly nested array structure
*
* @since 1.51
*
* @param string key value, e.g. array1.array2.element
* @param array PHP nested arrays, by reference
*
* @return boolean true if $needle element found, false if not
*/
private static function _unset_array_element( $needle, &$haystack ) {
$key_array = explode( '.', $needle );
$key = array_shift( $key_array );
if ( empty( $key_array ) ) {
if ( isset( $haystack[ $key ] ) ) {
unset( $haystack[ $key ] );
return true;
}
return false;
} // lowest level
if ( isset( $haystack[ $key ] ) ) {
return self::_unset_array_element( implode( $key_array, '.' ), $haystack[ $key ] );
}
return false;
}
/**
* Finds the value of a key in a possibly nested array structure
*
* Used primarily to extract fields from the _wp_attachment_metadata custom field.
* Also used with the audio/video ID3 metadata exposed in WordPress 3.6 and later.
*
* @since 1.30
*
* @param string key value, e.g. array1.array2.element
* @param array PHP nested arrays
* @param string data option; 'text'|'single'|'export'|'array'|'multi'
* @param boolean keep existing values - for 'multi' option
* @param string inter-element glue for text implode
*
* @return mixed string or array value matching key(.key ...) or ''
*/
public static function mla_find_array_element( $needle, $haystack, $option, $keep_existing = false, $glue = ', ' ) {
$key_array = explode( '.', $needle );
if ( is_array( $key_array ) ) {
foreach ( $key_array as $key ) {
/*
* The '*' key means:
* 1) needle.* => accept any value, or
* 2) needle.*.tail => search each sub-array using "tail"
* and build an array of results.
*/
if ( '*' == $key ) {
if ( false !== ( $tail = strpos( $needle, '*.' ) ) ) {
$tail = substr( $needle, $tail + 2 );
if ( ! empty( $tail ) ) {
if ( is_array( $haystack ) ) {
$results = array();
foreach ( $haystack as $substack ) {
$results[] = self::mla_find_array_element( $tail, $substack, $option, $keep_existing );
}
if ( 1 == count( $results ) ) {
$haystack = $results[0];
} else {
$haystack = $results;
}
} else {
$haystack = '';
}
} // found tail
} // found .*.
break;
} else {
if ( is_array( $haystack ) ) {
if ( isset( $haystack[ $key ] ) ) {
$haystack = $haystack[ $key ];
} else {
$haystack = '';
}
} else {
$haystack = '';
}
} // * != key
} // foreach $key
} else {
$haystack = '';
}
if ( is_array( $haystack ) ) {
switch ( $option ) {
case 'single':
$haystack = current( $haystack );
break;
case 'export':
$haystack = var_export( $haystack, true );
break;
case 'unpack':
if ( is_array( $haystack ) ) {
$clean_data = array();
foreach ( $haystack as $key => $value ) {
if ( is_array( $value ) ) {
$clean_data[ $key ] = '(ARRAY)';
} elseif ( is_string( $value ) ) {
$clean_data[ $key ] = self::_bin_to_utf8( substr( $value, 0, 256 ) );
} else {
$clean_data[ $key ] = $value;
}
}
$haystack = var_export( $clean_data, true);
} else {
$haystack = var_export( $record, true );
}
break;
case 'multi':
$haystack[0x80000000] = $option;
$haystack[0x80000001] = $keep_existing;
// fallthru
case 'array':
return $haystack;
break;
default:
$haystack = self::_bin_to_utf8( @implode( $glue, $haystack ) );
} // $option
} // is_array
return self::_bin_to_utf8( $haystack );
} // mla_find_array_element
/**
* Invalidates $mla_galleries and $galleries arrays and cached values after post, page or attachment updates
*
* @since 1.00
*
* @param integer ID of post/page/attachment; not used at this time
*
* @return void
*/
public static function mla_save_post_action( $post_id ) {
MLAQuery::mla_flush_mla_galleries( MLACoreOptions::MLA_GALLERY_IN_TUNING );
MLAQuery::mla_flush_mla_galleries( MLACoreOptions::MLA_MLA_GALLERY_IN_TUNING );
}
/**
* Parse a PDF date string
*
* @since 1.50
*
* @param string PDF date string of the form D:YYYYMMDDHHmmSSOHH'mm
*
* @return string formatted date string YYYY-MM-DD HH:mm:SS
*/
public static function mla_parse_pdf_date( $source_string ) {
if ( 'D:' == substr( $source_string, 0, 2) && ctype_digit( substr( $source_string, 2, 12 ) ) ) {
return sprintf( '%1$s-%2$s-%3$s %4$s:%5$s:%6$s',
substr( $source_string, 2, 4),
substr( $source_string, 6, 2),
substr( $source_string, 8, 2),
substr( $source_string, 10, 2),
substr( $source_string, 12, 2),
substr( $source_string, 14, 2) );
}
return $source_string;
}
/**
* Parse a ISO 8601 Timestamp
*
* @since 1.50
*
* @param string ISO string of the form YYYY-MM-DDTHH:MM:SS-HH:MM (inc time zone)
*
* @return string formatted date string YYYY-MM-DD HH:mm:SS
*/
private static function _parse_iso8601_date( $source_string ) {
if ( 1 == preg_match( '/^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(Z|[+-]\\d\\d:\\d\\d)/', $source_string ) ) {
return sprintf( '%1$s-%2$s-%3$s %4$s:%5$s:%6$s',
substr( $source_string, 0, 4),
substr( $source_string, 5, 2),
substr( $source_string, 8, 2),
substr( $source_string, 11, 2),
substr( $source_string, 14, 2),
substr( $source_string, 17, 2) );
}
return $source_string;
}
/**
* Parse an XMP array value, stripping namespace prefixes and Seq/Alt/Bag arrays
*
* @since 2.10
*
* @param array XMP multi-valued element
*
* @return mixed Simplified array or string value
*/
private static function _parse_xmp_array( $values ) {
if ( is_scalar( $values ) ) {
return $values;
}
if ( isset( $values['rdf:Alt'] ) ) {
return self::_parse_xmp_array( $values['rdf:Alt'] );
}
if ( isset( $values['rdf:Bag'] ) ) {
return self::_parse_xmp_array( $values['rdf:Bag'] );
}
if ( isset( $values['rdf:Seq'] ) ) {
return self::_parse_xmp_array( $values['rdf:Seq'] );
}
$results = array();
foreach( $values as $key => $value ) {
if ( false !== ($colon = strpos( $key, ':' ) ) ) {
$new_key = substr( $key, $colon + 1 );
} else {
$new_key = $key;
}
if ( is_array( $value ) ) {
$results[ $new_key ] = self::_parse_xmp_array( $value );
} else {
$results[ $new_key ] = self::_parse_iso8601_date( $value );
}
}
return $results;
}
/**
* Search the namespace array for a non-empty value
*
* @since 2.10
*
* @param array namespace array
* @param string namespace
* @param string key
*
* @return string trimmed value of the key within the namespace
*/
private static function _nonempty_value( &$namespace_array, $namespace, $key ) {
$result = '';
if ( isset( $namespace_array[ $namespace ] ) && isset( $namespace_array[ $namespace ][ $key ] ) ) {
if ( is_array( $namespace_array[ $namespace ][ $key ] ) ) {
$result = @implode( ',', $namespace_array[ $namespace ][ $key ] );
} else {
$result = (string) $namespace_array[ $namespace ][ $key ];
}
}
$trim_value = trim( $result, " \n\t\r\0\x0B," );
if ( empty( $trim_value ) ) {
$result = '';
}
return $result;
}
/**
* Extract XMP meta data from a file
*
* @since 2.10
*
* @param string full path and file name
* @param integer offset within the file of the search start point
*
* @return mixed array of metadata values or NULL on failure
*/
public static function mla_parse_xmp_metadata( $file_name, $file_offset ) {
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata( {$file_name}, {$file_offset} ) ", 0 );
$chunksize = 16384;
$xmp_chunk = file_get_contents( $file_name, true, NULL, $file_offset, $chunksize );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata( {$file_offset} ) chunk = \r\n" . MLAData::mla_hex_dump( $xmp_chunk ), 0 );
/*
* If necessary and possible, advance the $xmp_chunk through the file until it contains the start tag
*/
if ( false === ( $start_tag = strpos( $xmp_chunk, '<x:xmpmeta' ) ) && ( $chunksize == strlen( $xmp_chunk ) ) ) {
$new_offset = $file_offset + ( $chunksize - 16 );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata( {$new_offset} ) ", 0 );
$xmp_chunk = file_get_contents( $file_name, true, NULL, $new_offset, $chunksize );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata( {$new_offset} ) chunk = \r\n" . MLAData::mla_hex_dump( $xmp_chunk ), 0 );
while ( false === ( $start_tag = strpos( $xmp_chunk, '<x:xmpmeta' ) ) && ( $chunksize == strlen( $xmp_chunk ) ) ) {
$new_offset = $new_offset + ( $chunksize - 16 );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata( {$new_offset} ) ", 0 );
$xmp_chunk = file_get_contents( $file_name, true, NULL, $new_offset, $chunksize );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata( {$new_offset} ) chunk = \r\n" . MLAData::mla_hex_dump( $xmp_chunk ), 0 );
} // while not found
} else { // if not found
$new_offset = $file_offset;
}
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata( {$start_tag} ) ", 0 );
if ( false === $start_tag ) {
return NULL;
}
/*
* If necessary and possible, expand the $xmp_chunk until it contains the start tag
*/
if ( false === ( $end_tag = strpos( $xmp_chunk, '</x:xmpmeta>', $start_tag ) ) && ( $chunksize == strlen( $xmp_chunk ) ) ) {
$new_offset = $new_offset + $start_tag;
$start_tag = 0;
$new_chunksize = $chunksize + $chunksize;
$xmp_chunk = file_get_contents( $file_name, true, NULL, $new_offset, $new_chunksize );
while ( false === ( $end_tag = strpos( $xmp_chunk, '</x:xmpmeta>' ) ) && ( $new_chunksize == strlen( $xmp_chunk ) ) ) {
$new_chunksize = $new_chunksize + $chunksize;
$xmp_chunk = file_get_contents( $file_name, true, NULL, $new_offset, $new_chunksize );
} // while not found
} // if not found
if ( false === $end_tag ) {
return NULL;
}
$xmp_string = "<?xml version='1.0'?>\n" . substr($xmp_chunk, $start_tag, ( $end_tag + 12 ) - $start_tag );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_string = " . var_export( $xmp_string, true ), 0 );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_string = \r\n" . MLAData::mla_hex_dump( $xmp_string ), 0 );
// experimental damage repair for GodsHillPC
$xmp_string = str_replace( "\000", '0', $xmp_string );
$xmp_values = array();
$xml_parser = xml_parser_create('UTF-8');
if ( xml_parser_set_option( $xml_parser, XML_OPTION_SKIP_WHITE, 0 ) && xml_parser_set_option( $xml_parser, XML_OPTION_CASE_FOLDING, 0 ) ) {
if ( 0 == xml_parse_into_struct( $xml_parser, $xmp_string, $xmp_values ) ) {
MLACore::mla_debug_add( __LINE__ . __( 'ERROR', 'media-library-assistant' ) . ': ' . _x( 'mla_parse_xmp_metadata xml_parse_into_struct failed.', 'error_log', 'media-library-assistant' ), MLACore::MLA_DEBUG_CATEGORY_ANY );
$xmp_values = array();
}
} else {
MLACore::mla_debug_add( __LINE__ . __( 'ERROR', 'media-library-assistant' ) . ': ' . _x( 'mla_parse_xmp_metadata set option failed.', 'error_log', 'media-library-assistant' ), MLACore::MLA_DEBUG_CATEGORY_ANY );
}
xml_parser_free($xml_parser);
if ( empty( $xmp_values ) ) {
return NULL;
}
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values = " . var_export( $xmp_values, true ), 0 );
$levels = array();
$current_level = 0;
$results = array();
$xmlns = array();
foreach ( $xmp_values as $index => $value ) {
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$index} ) value = " . var_export( $value, true ), 0 );
$language = 'x-default';
$node_attributes = array();
if ( isset( $value['attributes'] ) ) {
foreach ( $value['attributes'] as $att_tag => $att_value ) {
$att_value = self::_bin_to_utf8( $att_value );
if ( 'xmlns:' == substr( $att_tag, 0, 6 ) ) {
$xmlns[ substr( $att_tag, 6 ) ] = $att_value;
} elseif ( 'x:xmptk' == $att_tag ) {
$results['xmptk'] = $att_value;
} elseif ( 'xml:lang' == $att_tag ) {
$language = $att_value;
} else {
$key = explode( ':', $att_tag );
switch ( count( $key ) ) {
case 1:
$att_ns = 'unknown';
$att_name = $key[0];
break;
case 2:
$att_ns = $key[0];
$att_name = $key[1];
break;
default:
$att_ns = array_shift( $key );
$att_name = implode( ':', $key );
break;
} // count
if ( ! in_array( $att_tag, array( 'rdf:about', 'rdf:parseType' ) ) ) {
$node_attributes[ $att_tag ] = self::_bin_to_utf8( $att_value );
}
}
}
} // attributes
switch ( $value['type'] ) {
case 'open':
$levels[ ++$current_level ] = array( 'key' => $value['tag'], 'values' => $node_attributes );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$current_level}, {$index} ) case 'open': top_level = " . var_export( $levels[ $current_level ], true ), 0 );
break;
case 'close':
if ( 0 < --$current_level ) {
$top_level = array_pop( $levels );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$current_level}, {$index} ) case 'close': top_level = " . var_export( $top_level, true ), 0 );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$current_level}, {$index} ) case 'close': levels( {$current_level} ) before = " . var_export( $levels, true ), 0 );
if ( 'rdf:li' == $top_level['key'] ) {
$levels[ $current_level ]['values'][] = $top_level['values'];
} else {
if ( isset( $levels[ $current_level ]['values'][ $top_level['key'] ] ) ) {
$levels[ $current_level ]['values'][ $top_level['key'] ] = array_merge( (array) $levels[ $current_level ]['values'][ $top_level['key'] ], $top_level['values'] );
} else {
$levels[ $current_level ]['values'][ $top_level['key'] ] = $top_level['values'];
}
}
}
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$current_level}, {$index} ) case 'close': levels( {$current_level} ) after = " . var_export( $levels, true ), 0 );
break;
case 'complete':
if ( 'x-default' != $language ) {
break;
}
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$index} ) case 'complete': node_attributes = " . var_export( $node_attributes, true ), 0 );
if ( empty( $node_attributes ) ) {
if ( isset( $value['value'] ) ) {
$complete_value = self::_bin_to_utf8( $value['value'] );
} else {
$complete_value = '';
}
} else {
$complete_value = $node_attributes;
}
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$index} ) case 'complete': value = " . var_export( $value, true ), 0 );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$index} ) case 'complete': complete_value = " . var_export( $complete_value, true ), 0 );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$index} ) case 'complete': (array) complete_value = " . var_export( (array) $complete_value, true ), 0 );
if ( 'rdf:li' == $value['tag'] ) {
$levels[ $current_level ]['values'][] = $complete_value;
} else {
if ( isset( $levels[ $current_level ]['values'][ $value['tag'] ] ) ) {
$new_value = (array) $levels[ $current_level ]['values'][ $value['tag'] ];
$levels[ $current_level ]['values'][ $value['tag'] ] = array_merge( $new_value, (array) $complete_value );
} else {
$levels[ $current_level ]['values'][ $value['tag'] ] = $complete_value;
}
}
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$index}, {$current_level} ) case 'complete': values = " . var_export( $levels[ $current_level ]['values'], true ), 0 );
break;
default:
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$index}, {$current_level} ) ignoring type = " . var_export( $value['type'], true ), 0 );
} // switch on type
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmp_values( {$index}, {$current_level} ) levels = " . var_export( $levels, true ), 0 );
} // foreach value
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata levels = " . var_export( $levels, true ), 0 );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata xmlns = " . var_export( $xmlns, true ), 0 );
/*
* Parse "namespace:name" names into arrays of simple names
* NOTE: The string "XAP" or "xap" appears in some namespaces, keywords,
* and related names in stored XMP data. It reflects an early internal
* code name for XMP; the names have been preserved for compatibility purposes.
*/
$namespace_arrays = array();
if ( isset( $levels[1] ) && isset( $levels[1]['values'] ) && isset( $levels[1]['values']['rdf:RDF'] ) && isset( $levels[1]['values']['rdf:RDF']['rdf:Description'] ) ) {
foreach ( $levels[1]['values']['rdf:RDF']['rdf:Description'] as $key => $value ) {
if ( is_string( $value ) ) {
$value = self::_parse_iso8601_date( self::mla_parse_pdf_date( $value ) );
} elseif ( is_array( $value ) ) {
$value = self::_parse_xmp_array( $value );
}
if ( false !== ($colon = strpos( $key, ':' ) ) ) {
$array_name = substr( $key, 0, $colon );
$array_index = substr( $key, $colon + 1 );
$namespace_arrays[ $array_name ][ $array_index ] = $value;
if ( ! isset( $results[ $array_index ] ) && in_array( $array_name, array( 'xmp', 'xmpMM', 'xmpRights', 'xap', 'xapMM', 'dc', 'pdf', 'pdfx', 'mwg-rs' ) ) ) {
if ( is_array( $value ) && 1 == count( $value ) && isset( $value[0] ) ) {
$results[ $array_index ] = $value[0];
} else {
$results[ $array_index ] = $value;
}
}
} // found namespace
} // foreach Description
}
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata results = " . var_export( $results, true ), 0 );
/*
* Try to populate all the PDF-standard keys (except Trapped)
* Title - The document's title
* Author - The name of the person who created the document
* Subject - The subject of the document
* Keywords - Keywords associated with the document
* Creator - the name of the conforming product that created the original document
* Producer - the name of the conforming product that converted it to PDF
* CreationDate - The date and time the document was created
* ModDate - The date and time the document was most recently modified
*/
if ( ! isset( $results['Title'] ) ) {
$replacement = self::_nonempty_value( $namespace_arrays, 'dc', 'title' );
if ( ! empty( $replacement ) ) {
$results['Title'] = $replacement;
}
}
if ( ! isset( $results['Author'] ) ) {
$replacement = self::_nonempty_value( $namespace_arrays, 'dc', 'creator' );
if ( ! empty( $replacement ) ) {
$results['Author'] = $replacement;
}
}
if ( ! isset( $results['Subject'] ) ) {
$replacement = self::_nonempty_value( $namespace_arrays, 'dc', 'description' );
if ( ! empty( $replacement ) ) {
$results['Subject'] = $replacement;
}
}
/*
* Keywords are special, since they are often assigned to taxonomy terms.
* Build or preserve an array if there are multiple values; string for single values.
* "pdf:Keywords" uses a ';' delimiter, "dc:subject" uses an array.
*/
$keywords = array();
if ( isset( $results['Keywords'] ) ) {
if ( false !== strpos( $results['Keywords'], ';' ) ) {
$terms = array_map( 'trim', explode( ';', $results['Keywords'] ) );
foreach ( $terms as $term )
if ( ! empty( $term ) ) {
$keywords[ $term ] = $term;
}
} elseif ( false !== strpos( $results['Keywords'], ',' ) ) {
$terms = array_map( 'trim', explode( ',', $results['Keywords'] ) );
foreach ( $terms as $term )
if ( ! empty( $term ) ) {
$keywords[ $term ] = $term;
}
} else {
$term = trim( $results['Keywords'] );
if ( ! empty( $term ) ) {
$keywords[ $term ] = $term;
}
}
} // Keywords
if ( isset( $namespace_arrays['dc'] ) && isset( $namespace_arrays['dc']['subject'] ) ) {
if ( is_array( $namespace_arrays['dc']['subject'] ) ) {
foreach ( $namespace_arrays['dc']['subject'] as $term ) {
$term = trim( $term, " \n\t\r\0\x0B," );
if ( ! empty( $term ) ) {
$keywords[ $term ] = $term;
}
}
} elseif ( is_string( $namespace_arrays['dc']['subject'] ) ) {
$term = trim ( $namespace_arrays['dc']['subject'], " \n\t\r\0\x0B," );
if ( ! empty( $term ) ) {
$keywords[ $term ] = $term;
}
}
} // dc:subject
if ( ! empty( $keywords ) ) {
if ( 1 == count( $keywords ) ) {
$results['Keywords'] = array_shift( $keywords );
} else {
$results['Keywords'] = array();
foreach ( $keywords as $term ) {
$results['Keywords'][] = $term;
}
}
}
// if ( ! isset( $results['Producer'] ) ) {
// }
if ( ! isset( $results['Creator'] ) ) {
$replacement = self::_nonempty_value( $namespace_arrays, 'xmp', 'CreatorTool' );
if ( ! empty( $replacement ) ) {
$results['Creator'] = $replacement;
} else {
$replacement = self::_nonempty_value( $namespace_arrays, 'xap', 'CreatorTool' );
if ( ! empty( $replacement ) ) {
$results['Creator'] = $replacement;
} elseif ( ! empty( $results['Producer'] ) ) {
$results['Creator'] = $results['Producer'];
}
}
}
if ( ! isset( $results['CreationDate'] ) ) {
$replacement = self::_nonempty_value( $namespace_arrays, 'xmp', 'CreateDate' );
if ( ! empty( $replacement ) ) {
$results['CreationDate'] = $replacement;
} else {
$replacement = self::_nonempty_value( $namespace_arrays, 'xap', 'CreateDate' );
if ( ! empty( $replacement ) ) {
$results['CreationDate'] = $replacement;
}
}
}
if ( ! isset( $results['ModDate'] ) ) {
$replacement = self::_nonempty_value( $namespace_arrays, 'xmp', 'ModifyDate' );
if ( ! empty( $replacement ) ) {
$results['ModDate'] = $replacement;
} else {
$replacement = self::_nonempty_value( $namespace_arrays, 'xap', 'ModifyDate' );
if ( ! empty( $replacement ) ) {
$results['ModDate'] = $replacement;
}
}
}
if ( ! empty( $xmlns ) ) {
$results['xmlns'] = $xmlns;
}
$results = array_merge( $results, $namespace_arrays );
//error_log( __LINE__ . " MLAData::mla_parse_xmp_metadata results = " . var_export( $results, true ), 0 );
return $results;
}
/**
* UTF-8 replacements for invalid SQL characters
*
* @since 1.41
*
* @var array
*/
public static $utf8_chars = array(
"\xC2\x80", "\xC2\x81", "\xC2\x82", "\xC2\x83", "\xC2\x84", "\xC2\x85", "\xC2\x86", "\xC2\x87",
"\xC2\x88", "\xC2\x89", "\xC2\x8A", "\xC2\x8B", "\xC2\x8C", "\xC2\x8D", "\xC2\x8E", "\xC2\x8F",
"\xC2\x90", "\xC2\x91", "\xC2\x92", "\xC2\x93", "\xC2\x94", "\xC2\x95", "\xC2\x96", "\xC2\x97",
"\xC2\x98", "\xC2\x99", "\xC2\x9A", "\xC2\x9B", "\xC2\x9C", "\xC2\x9D", "\xC2\x9E", "\xC2\x9F",
"\xC2\xA0", "\xC2\xA1", "\xC2\xA2", "\xC2\xA3", "\xC2\xA4", "\xC2\xA5", "\xC2\xA6", "\xC2\xA7",
"\xC2\xA8", "\xC2\xA9", "\xC2\xAA", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC2\xAF",
"\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xC2\xB4", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7",
"\xC2\xB8", "\xC2\xB9", "\xC2\xBA", "\xC2\xBB", "\xC2\xBC", "\xC2\xBD", "\xC2\xBE", "\xC2\xBF",
"\xC3\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC3\x87",
"\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F",
"\xC3\x90", "\xC3\x91", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xC3\x97",
"\xC3\x98", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC3\x9D", "\xC3\x9E", "\xC3\x9F",
"\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC3\xA7",
"\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF",
"\xC3\xB0", "\xC3\xB1", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xC3\xB7",
"\xC3\xB8", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC3\xBD", "\xC3\xBE", "\xC3\xBF"
);
/**
* Replace SQL incorrect characters (0x80 - 0xFF) with their UTF-8 equivalents
*
* @since 1.41
*
* @param string unencoded string
*
* @return string UTF-8 encoded string
*/
private static function _bin_to_utf8( $string ) {
if ( seems_utf8( $string ) ) {
return $string;
}
if (function_exists('utf8_encode')) {
return utf8_encode( $string );
}
$output = '';
for ($index = 0; $index < strlen( $string ); $index++ ) {
$value = ord( $string[ $index ] );
if ( $value < 0x80 ) {
$output .= chr( $value );
} else {
$output .= self::$utf8_chars[ $value - 0x80 ];
}
}
return $output;
}
/**
* IPTC Dataset identifiers and names
*
* This array contains the identifiers and names of Datasets defined in
* the "IPTC-NAA Information Interchange Model Version No. 4.1".
*
* @since 0.90
*
* @var array
*/
public static $mla_iptc_records = array(
// Envelope Record
"1#000" => "Model Version",
"1#005" => "Destination",
"1#020" => "File Format",
"1#022" => "File Format Version",
"1#030" => "Service Identifier",
"1#040" => "Envelope Number",
"1#050" => "Product ID",
"1#060" => "Envelope Priority",
"1#070" => "Date Sent",
"1#080" => "Time Sent",
"1#090" => "Coded Character Set",
"1#100" => "UNO",
"1#120" => "ARM Identifier",
"1#122" => "ARM Version",
// Application Record
"2#000" => "Record Version",
"2#003" => "Object Type Reference",
"2#004" => "Object Attribute Reference",
"2#005" => "Object Name",
"2#007" => "Edit Status",
"2#008" => "Editorial Update",
"2#010" => "Urgency",
"2#012" => "Subject Reference",
"2#015" => "Category",
"2#020" => "Supplemental Category",
"2#022" => "Fixture Identifier",
"2#025" => "Keywords",
"2#026" => "Content Location Code",
"2#027" => "Content Location Name",
"2#030" => "Release Date",
"2#035" => "Release Time",
"2#037" => "Expiration Date",
"2#038" => "Expiration Time",
"2#040" => "Special Instructions",
"2#042" => "Action Advised",
"2#045" => "Reference Service",
"2#047" => "Reference Date",
"2#050" => "Reference Number",
"2#055" => "Date Created",
"2#060" => "Time Created",
"2#062" => "Digital Creation Date",
"2#063" => "Digital Creation Time",
"2#065" => "Originating Program",
"2#070" => "Program Version",
"2#075" => "Object Cycle",
"2#080" => "By-line",
"2#085" => "By-line Title",
"2#090" => "City",
"2#092" => "Sub-location",
"2#095" => "Province or State",
"2#100" => "Country or Primary Location Code",
"2#101" => "Country or Primary Location Name",
"2#103" => "Original Transmission Reference",
"2#105" => "Headline",
"2#110" => "Credit",
"2#115" => "Source",
"2#116" => "Copyright Notice",
"2#118" => "Contact",
"2#120" => "Caption or Abstract",
"2#122" => "Caption Writer or Editor",
"2#125" => "Rasterized Caption",
"2#130" => "Image Type",
"2#131" => "Image Orientation",
"2#135" => "Language Identifier",
"2#150" => "Audio Type",
"2#151" => "Audio Sampling Rate",
"2#152" => "Audio Sampling Resolution",
"2#153" => "Audio Duration",
"2#154" => "Audio Outcue",
"2#200" => "ObjectData Preview File Format",
"2#201" => "ObjectData Preview File Format Version",
"2#202" => "ObjectData Preview Data",
// Pre ObjectData Descriptor Record
"7#010" => "Size Mode",
"7#020" => "Max Subfile Size",
"7#090" => "ObjectData Size Announced",
"7#095" => "Maximum ObjectData Size",
// ObjectData Record
"8#010" => "Subfile",
// Post ObjectData Descriptor Record
"9#010" => "Confirmed ObjectData Size"
);
/**
* IPTC Dataset friendly name/slug and identifiers
*
* This array contains the sanitized names and identifiers of Datasets defined in
* the "IPTC-NAA Information Interchange Model Version No. 4.1".
*
* @since 0.90
*
* @var array
*/
public static $mla_iptc_keys = array(
// Envelope Record
'model-version' => '1#000',
'destination' => '1#005',
'file-format' => '1#020',
'file-format-version' => '1#022',
'service-identifier' => '1#030',
'envelope-number' => '1#040',
'product-id' => '1#050',
'envelope-priority' => '1#060',
'date-sent' => '1#070',
'time-sent' => '1#080',
'coded-character-set' => '1#090',
'uno' => '1#100',
'arm-identifier' => '1#120',
'arm-version' => '1#122',
// Application Record
'record-version' => '2#000',
'object-type-reference' => '2#003',
'object-attribute-reference' => '2#004',
'object-name' => '2#005',
'edit-status' => '2#007',
'editorial-update' => '2#008',
'urgency' => '2#010',
'subject-reference' => '2#012',
'category' => '2#015',
'supplemental-category' => '2#020',
'fixture-identifier' => '2#022',
'keywords' => '2#025',
'content-location-code' => '2#026',
'content-location-name' => '2#027',
'release-date' => '2#030',
'release-time' => '2#035',
'expiration-date' => '2#037',
'expiration-time' => '2#038',
'special-instructions' => '2#040',
'action-advised' => '2#042',
'reference-service' => '2#045',
'reference-date' => '2#047',
'reference-number' => '2#050',
'date-created' => '2#055',
'time-created' => '2#060',
'digital-creation-date' => '2#062',
'digital-creation-time' => '2#063',
'originating-program' => '2#065',
'program-version' => '2#070',
'object-cycle' => '2#075',
'by-line' => '2#080',
'by-line-title' => '2#085',
'city' => '2#090',
'sub-location' => '2#092',
'province-or-state' => '2#095',
'country-or-primary-location-code' => '2#100',
'country-or-primary-location-name' => '2#101',
'original-transmission-reference' => '2#103',
'headline' => '2#105',
'credit' => '2#110',
'source' => '2#115',
'copyright-notice' => '2#116',
'contact' => '2#118',
'caption-or-abstract' => '2#120',
'caption-writer-or-editor' => '2#122',
'rasterized-caption' => '2#125',
'image-type' => '2#130',
'image-orientation' => '2#131',
'language-identifier' => '2#135',
'audio-type' => '2#150',
'audio-sampling-rate' => '2#151',
'audio-sampling-resolution' => '2#152',
'audio-duration' => '2#153',
'audio-outcue' => '2#154',
'objectdata-preview-file-format' => '2#200',
'objectdata-preview-file-format-version' => '2#201',
'objectdata-preview-data' => '2#202',
// Pre ObjectData Descriptor Record
'size-mode' => '7#010',
'max-subfile-size' => '7#020',
'objectdata-size-announced' => '7#090',
'maximum-objectdata-size' => '7#095',
// ObjectData Record
'subfile' => '8#010',
// Post ObjectData Descriptor Record
'confirmed-objectdata-size' => '9#010'
);
/**
* IPTC Dataset descriptions
*
* This array contains the descriptions of Datasets defined in
* the "IPTC-NAA Information Interchange Model Version No. 4.1".
*
* @since 0.90
*
* @var array
*/
private static $mla_iptc_descriptions = array(
// Envelope Record
"1#000" => "2 octet binary IIM version number",
"1#005" => "Max 1024 characters of Destination (ISO routing information); repeatable",
"1#020" => "2 octet binary file format number, see IPTC-NAA V4 Appendix A",
"1#022" => "2 octet binary file format version number",
"1#030" => "Max 10 characters of Service Identifier and product",
"1#040" => "8 Character Envelope Number",
"1#050" => "Max 32 characters subset of provider's overall service; repeatable",
"1#060" => "1 numeric character of envelope handling priority (not urgency)",
"1#070" => "8 numeric characters of Date Sent by service - CCYYMMDD",
"1#080" => "11 characters of Time Sent by service - HHMMSS±HHMM",
"1#090" => "Max 32 characters of control functions, etc.",
"1#100" => "14 to 80 characters of eternal, globally unique identification for objects",
"1#120" => "2 octet binary Abstract Relationship Model Identifier",
"1#122" => "2 octet binary Abstract Relationship Model Version",
// Application Record
"2#000" => "2 octet binary Information Interchange Model, Part II version number",
"2#003" => "3 to 67 Characters of Object Type Reference number and optional text",
"2#004" => "3 to 67 Characters of Object Attribute Reference number and optional text; repeatable",
"2#005" => "Max 64 characters of the object name or shorthand reference",
"2#007" => "Max 64 characters of the status of the objectdata",
"2#008" => "2 numeric characters of the type of update this object provides",
"2#010" => "1 numeric character of the editorial urgency of content",
"2#012" => "13 to 236 characters of a structured definition of the subject matter; repeatable",
"2#015" => "Max 3 characters of the subject of the objectdata, DEPRECATED",
"2#020" => "Max 32 characters (each) of further refinement of subject, DEPRECATED; repeatable",
"2#022" => "Max 32 characters identifying recurring, predictable content",
"2#025" => "Max 64 characters (each) of tags; repeatable",
"2#026" => "3 characters of ISO3166 country code or IPTC-assigned code; repeatable",
"2#027" => "Max 64 characters of publishable country/geographical location name; repeatable",
"2#030" => "8 numeric characters of Release Date - CCYYMMDD",
"2#035" => "11 characters of Release Time (earliest use) - HHMMSS±HHMM",
"2#037" => "8 numeric characters of Expiration Date (latest use) - CCYYMDD",
"2#038" => "11 characters of Expiration Time (latest use) - HHMMSS±HHMM",
"2#040" => "Max 256 Characters of editorial instructions, e.g., embargoes and warnings",
"2#042" => "2 numeric characters of type of action this object provides to a previous object",
"2#045" => "Max 10 characters of the Service ID (1#030) of a prior envelope; repeatable",
"2#047" => "8 numeric characters of prior envelope Reference Date (1#070) - CCYYMMDD; repeatable",
"2#050" => "8 characters of prior envelope Reference Number (1#040); repeatable",
"2#055" => "8 numeric characters of intellectual content Date Created - CCYYMMDD",
"2#060" => "11 characters of intellectual content Time Created - HHMMSS±HHMM",
"2#062" => "8 numeric characters of digital representation creation date - CCYYMMDD",
"2#063" => "11 characters of digital representation creation time - HHMMSS±HHMM",
"2#065" => "Max 32 characters of the program used to create the objectdata",
"2#070" => "Program Version - Max 10 characters of the version of the program used to create the objectdata",
"2#075" => "1 character where a=morning, p=evening, b=both",
"2#080" => "Max 32 Characters of the name of the objectdata creator, e.g., the writer, photographer; repeatable",
"2#085" => "Max 32 characters of the title of the objectdata creator; repeatable",
"2#090" => "Max 32 Characters of the city of objectdata origin",
"2#092" => "Max 32 Characters of the location within the city of objectdata origin",
"2#095" => "Max 32 Characters of the objectdata origin Province or State",
"2#100" => "3 characters of ISO3166 or IPTC-assigned code for Country of objectdata origin",
"2#101" => "Max 64 characters of publishable country/geographical location name of objectdata origin",
"2#103" => "Max 32 characters of a code representing the location of original transmission",
"2#105" => "Max 256 Characters of a publishable entry providing a synopsis of the contents of the objectdata",
"2#110" => "Max 32 Characters that identifies the provider of the objectdata (Vs the owner/creator)",
"2#115" => "Max 32 Characters that identifies the original owner of the intellectual content",
"2#116" => "Max 128 Characters that contains any necessary copyright notice",
"2#118" => "Max 128 characters that identifies the person or organisation which can provide further background information; repeatable",
"2#120" => "Max 2000 Characters of a textual description of the objectdata",
"2#122" => "Max 32 Characters that the identifies the person involved in the writing, editing or correcting the objectdata or caption/abstract; repeatable",
"2#125" => "7360 binary octets of the rasterized caption - 1 bit per pixel, 460x128-pixel image",
"2#130" => "2 characters of color composition type and information",
"2#131" => "1 alphabetic character indicating the image area layout - P=portrait, L=landscape, S=square",
"2#135" => "2 or 3 aphabetic characters containing the major national language of the object, according to the ISO 639:1988 codes",
"2#150" => "2 characters identifying monaural/stereo and exact type of audio content",
"2#151" => "6 numeric characters representing the audio sampling rate in hertz (Hz)",
"2#152" => "2 numeric characters representing the number of bits in each audio sample",
"2#153" => "6 numeric characters of the Audio Duration - HHMMSS",
"2#154" => "Max 64 characters of the content of the end of an audio objectdata",
"2#200" => "2 octet binary file format of the ObjectData Preview",
"2#201" => "2 octet binary particular version of the ObjectData Preview File Format",
"2#202" => "Max 256000 binary octets containing the ObjectData Preview data",
// Pre ObjectData Descriptor Record
"7#010" => "1 numeric character - 0=objectdata size not known, 1=objectdata size known at beginning of transfer",
"7#020" => "4 octet binary maximum subfile dataset(s) size",
"7#090" => "4 octet binary objectdata size if known at beginning of transfer",
"7#095" => "4 octet binary largest possible objectdata size",
// ObjectData Record
"8#010" => "Subfile DataSet containing the objectdata itself; repeatable",
// Post ObjectData Descriptor Record
"9#010" => "4 octet binary total objectdata size"
);
/**
* IPTC file format identifiers and descriptions
*
* This array contains the file format identifiers and descriptions defined in
* the "IPTC-NAA Information Interchange Model Version No. 4.1" for dataset 1#020.
*
* @since 0.90
*
* @var array
*/
private static $mla_iptc_formats = array(
0 => "No ObjectData",
1 => "IPTC-NAA Digital Newsphoto Parameter Record",
2 => "IPTC7901 Recommended Message Format",
3 => "Tagged Image File Format (Adobe/Aldus Image data)",
4 => "Illustrator (Adobe Graphics data)",
5 => "AppleSingle (Apple Computer Inc)",
6 => "NAA 89-3 (ANPA 1312)",
7 => "MacBinary II",
8 => "IPTC Unstructured Character Oriented File Format (UCOFF)",
9 => "United Press International ANPA 1312 variant",
10 => "United Press International Down-Load Message",
11 => "JPEG File Interchange (JFIF)",
12 => "Photo-CD Image-Pac (Eastman Kodak)",
13 => "Microsoft Bit Mapped Graphics File [*.BMP]",
14 => "Digital Audio File [*.WAV] (Microsoft & Creative Labs)",
15 => "Audio plus Moving Video [*.AVI] (Microsoft)",
16 => "PC DOS/Windows Executable Files [*.COM][*.EXE]",
17 => "Compressed Binary File [*.ZIP] (PKWare Inc)",
18 => "Audio Interchange File Format AIFF (Apple Computer Inc)",
19 => "RIFF Wave (Microsoft Corporation)",
20 => "Freehand (Macromedia/Aldus)",
21 => "Hypertext Markup Language - HTML (The Internet Society)",
22 => "MPEG 2 Audio Layer 2 (Musicom), ISO/IEC",
23 => "MPEG 2 Audio Layer 3, ISO/IEC",
24 => "Portable Document File (*.PDF) Adobe",
25 => "News Industry Text Format (NITF)",
26 => "Tape Archive (*.TAR)",
27 => "Tidningarnas Telegrambyrå NITF version (TTNITF DTD)",
28 => "Ritzaus Bureau NITF version (RBNITF DTD)",
29 => "Corel Draw [*.CDR]"
);
/**
* IPTC image type identifiers and descriptions
*
* This array contains the image type identifiers and descriptions defined in
* the "IPTC-NAA Information Interchange Model Version No. 4.1" for dataset 2#130, octet 2.
*
* @since 0.90
*
* @var array
*/
private static $mla_iptc_image_types = array(
"M" => "Monochrome",
"Y" => "Yellow Component",
"M" => "Magenta Component",
"C" => "Cyan Component",
"K" => "Black Component",
"R" => "Red Component",
"G" => "Green Component",
"B" => "Blue Component",
"T" => "Text Only",
"F" => "Full colour composite, frame sequential",
"L" => "Full colour composite, line sequential",
"P" => "Full colour composite, pixel sequential",
"S" => "Full colour composite, special interleaving"
);
/**
* Parse one IPTC metadata field
*
* @since 1.41
*
* @param string field name - IPTC Identifier or friendly name/slug
* @param array metadata array containing iptc, exif, xmp and pdf metadata arrays
* @param string data option; 'text'|'single'|'export'|'array'|'multi'
* @param boolean Optional: for option 'multi', retain existing values
*
* @return mixed string/array representation of metadata value or an empty string
*/
public static function mla_iptc_metadata_value( $iptc_key, $item_metadata, $option = 'text', $keep_existing = false ) {
// convert friendly name/slug to identifier
if ( array_key_exists( $iptc_key, self::$mla_iptc_keys ) ) {
$iptc_key = self::$mla_iptc_keys[ $iptc_key ];
}
if ( 'ALL_IPTC' == $iptc_key ) {
$clean_data = array();
if ( isset( $item_metadata['mla_iptc_metadata'] ) && is_array( $item_metadata['mla_iptc_metadata'] ) ) {
foreach ( $item_metadata['mla_iptc_metadata'] as $key => $value ) {
if ( is_array( $value ) ) {
foreach ($value as $text_key => $text )
$value[ $text_key ] = self::_bin_to_utf8( $text );
$clean_data[ $key ] = 'ARRAY(' . implode( ',', $value ) . ')';
} elseif ( is_string( $value ) ) {
$clean_data[ $key ] = self::_bin_to_utf8( substr( $value, 0, 256 ) );
} else {
$clean_data[ $key ] = self::_bin_to_utf8( $value );
}
}
}
return var_export( $clean_data, true);
}
return self::mla_find_array_element( $iptc_key, $item_metadata['mla_iptc_metadata'], $option, $keep_existing );
}
/**
* Parse one EXIF metadata field
*
* Also handles the special pseudo-values 'ALL_EXIF' and 'ALL_IPTC'.
*
* @since 1.13
*
* @param string field name
* @param array metadata array containing iptc, exif, xmp and pdf metadata arrays
* @param string data option; 'text'|'single'|'export'|'array'|'multi'
* @param boolean Optional: for option 'multi', retain existing values
*
* @return mixed string/array representation of metadata value or an empty string
*/
public static function mla_exif_metadata_value( $exif_key, $item_metadata, $option = 'text', $keep_existing = false ) {
if ( 'ALL_EXIF' == $exif_key ) {
$clean_data = array();
if ( isset( $item_metadata['mla_exif_metadata'] ) && is_array( $item_metadata['mla_exif_metadata'] ) ) {
foreach ( $item_metadata['mla_exif_metadata'] as $key => $value ) {
if ( is_array( $value ) ) {
$clean_data[ $key ] = '(ARRAY)';
} elseif ( is_string( $value ) ) {
$clean_data[ $key ] = self::_bin_to_utf8( substr( $value, 0, 256 ) );
} else {
$clean_data[ $key ] = $value;
}
}
}
return var_export( $clean_data, true);
} elseif ( 'ALL_IPTC' == $exif_key ) {
$clean_data = array();
if ( isset( $item_metadata['mla_iptc_metadata'] ) && is_array( $item_metadata['mla_iptc_metadata'] ) ) {
foreach ( $item_metadata['mla_iptc_metadata'] as $key => $value ) {
if ( is_array( $value ) ) {
foreach ($value as $text_key => $text )
$value[ $text_key ] = self::_bin_to_utf8( $text );
$clean_data[ $key ] = 'ARRAY(' . implode( ',', $value ) . ')';
} elseif ( is_string( $value ) ) {
$clean_data[ $key ] = self::_bin_to_utf8( substr( $value, 0, 256 ) );
} else {
$clean_data[ $key ] = self::_bin_to_utf8( $value );
}
}
}
return var_export( $clean_data, true);
}
return self::mla_find_array_element( $exif_key, $item_metadata['mla_exif_metadata'], $option, $keep_existing );
}
/**
* Parse one XMP metadata field
*
* Also handles the special pseudo-value 'ALL_XMP'.
*
* @since 2.10
*
* @param string field name
* @param array XMP metadata array
* @param string data option; 'text'|'single'|'export'|'array'|'multi'
* @param boolean Optional: for option 'multi', retain existing values
*
* @return mixed string/array representation of metadata value or an empty string
*/
public static function mla_xmp_metadata_value( $xmp_key, $xmp_metadata, $option = 'text', $keep_existing = false ) {
if ( 'ALL_XMP' == $xmp_key ) {
$clean_data = array();
if ( is_array( $xmp_metadata ) ) {
foreach ( $xmp_metadata as $key => $value ) {
if ( is_array( $value ) ) {
$clean_data[ $key ] = '(ARRAY)';
} elseif ( is_string( $value ) ) {
$clean_data[ $key ] = self::_bin_to_utf8( substr( $value, 0, 256 ) );
} else {
$clean_data[ $key ] = $value;
}
}
}
return var_export( $clean_data, true);
}
return self::mla_find_array_element($xmp_key, $xmp_metadata, $option, $keep_existing );
}
/**
* Parse one ID3 (audio/visual) metadata field
*
* Also handles the special pseudo-value 'ALL_ID3'.
*
* @since 2.13
*
* @param string field name
* @param array ID3 metadata array
* @param string data option; 'text'|'single'|'export'|'array'|'multi'
* @param boolean Optional: for option 'multi', retain existing values
*
* @return mixed string/array representation of metadata value or an empty string
*/
public static function mla_id3_metadata_value( $id3_key, $id3_metadata, $option, $keep_existing ) {
if ( 'ALL_ID3' == $id3_key ) {
$clean_data = array();
if ( is_array( $id3_metadata ) ) {
foreach ( $id3_metadata as $key => $value ) {
if ( is_array( $value ) ) {
$clean_data[ $key ] = '(ARRAY)';
} elseif ( is_string( $value ) ) {
$clean_data[ $key ] = self::_bin_to_utf8( substr( $value, 0, 256 ) );
} else {
$clean_data[ $key ] = $value;
}
}
}
return var_export( $clean_data, true);
}
return self::mla_find_array_element($id3_key, $id3_metadata, $option, $keep_existing );
}
/**
* Parse one PDF metadata field
*
* Also handles the special pseudo-value 'ALL_PDF'.
*
* @since 1.50
*
* @param string field name
* @param string metadata array containing iptc, exif, xmp and pdf metadata arrays
*
* @return mixed string/array representation of metadata value or an empty string
*/
public static function mla_pdf_metadata_value( $pdf_key, $item_metadata ) {
$text = '';
if ( array_key_exists( $pdf_key, $item_metadata['mla_pdf_metadata'] ) ) {
$text = $item_metadata['mla_pdf_metadata'][ $pdf_key ];
if ( is_array( $text ) ) {
foreach ($text as $key => $value ) {
if ( is_array( $value ) ) {
$text[ $key ] = self::_bin_to_utf8( var_export( $value, true ) );
} else {
$text[ $key ] = self::_bin_to_utf8( $value );
}
}
} elseif ( is_string( $text ) ) {
$text = self::_bin_to_utf8( $text );
}
} elseif ( 'ALL_PDF' == $pdf_key ) {
$clean_data = array();
if ( isset( $item_metadata['mla_pdf_metadata'] ) && is_array( $item_metadata['mla_pdf_metadata'] ) ) {
foreach ( $item_metadata['mla_pdf_metadata'] as $key => $value ) {
if ( is_array( $value ) ) {
$clean_data[ $key ] = '(ARRAY)';
} elseif ( is_string( $value ) ) {
$clean_data[ $key ] = self::_bin_to_utf8( substr( $value, 0, 256 ) );
} else {
$clean_data[ $key ] = $value;
}
}
}
$text = var_export( $clean_data, true);
} // ALL_PDF
return $text;
}
/**
* Convert an EXIF GPS rational value to a PHP float value
*
* @since 1.50
*
* @param array array( 0 => numerator, 1 => denominator )
*
* @return float numerator/denominator
*/
private static function _rational_to_decimal( $rational ) {
$parts = explode('/', $rational);
return $parts[0] / ( $parts[1] ? $parts[1] : 1);
}
/**
* Convert an EXIF rational value to a formatted string
*
* @since 2.02
*
* @param string numerator/denominator
* @param string format for integer values
* @param string format for fractional values from -1 to +1
* @param string format for integer.fraction values
*
* @return mixed formatted value or boolean false if no value available
*/
private static function _rational_to_string( $rational, $integer_format, $fraction_format, $mixed_format ) {
$fragments = array_map( 'intval', explode( '/', $rational ) );
if ( 1 == count( $fragments ) ) {
$value = trim( $rational );
if ( ! empty( $value ) ) {
return $value;
}
} else {
if ( $fragments[0] ) {
if ( 1 == $fragments[1] ) {
return sprintf( $integer_format, $fragments[0] );
} elseif ( 0 != $fragments[1] ) {
$value = $fragments[0] / $fragments[1];
if ( ( -1 <= $value ) && ( 1 >= $value ) ) {
return sprintf( $fraction_format, $fragments[0], $fragments[1] );
} else {
if ( $value == intval( $value ) ) {
return sprintf( $integer_format, $value );
}else {
return sprintf( $mixed_format, $value );
}
} // mixed value
} // fractional or mixed value
} // non-zero numerator
} // valid denominator
return false;
}
/**
* Passes IPTC/EXIF parse errors between mla_IPTC_EXIF_error_handler
* and mla_fetch_attachment_image_metadata
*
* @since 1.81
*
* @var array
*/
private static $mla_IPTC_EXIF_errors = array();
/**
* Intercept IPTC and EXIF parse errors
*
* @since 1.81
*
* @param int the level of the error raised
* @param string the error message
* @param string the filename that the error was raised in
* @param int the line number the error was raised at
*
* @return boolean true, to bypass PHP error handler
*/
public static function mla_IPTC_EXIF_error_handler( $type, $string, $file, $line ) {
//error_log( 'DEBUG: mla_IPTC_EXIF_error_handler $type = ' . var_export( $type, true ), 0 );
//error_log( 'DEBUG: mla_IPTC_EXIF_error_handler $string = ' . var_export( $string, true ), 0 );
//error_log( 'DEBUG: mla_IPTC_EXIF_error_handler $file = ' . var_export( $file, true ), 0 );
//error_log( 'DEBUG: mla_IPTC_EXIF_error_handler $line = ' . var_export( $line, true ), 0 );
switch ( $type ) {
case E_ERROR:
$level = 'E_ERROR';
break;
case E_WARNING:
$level = 'E_WARNING';
break;
case E_NOTICE:
$level = 'E_NOTICE';
break;
default:
$level = 'OTHER';
}
$path_info = pathinfo( $file );
$file_name = $path_info['basename'];
MLAData::$mla_IPTC_EXIF_errors[] = "{$level} ({$type}) {$string}";
/* Don't execute PHP internal error handler */
return true;
}
/**
* Fetch and filter ID3 metadata for an audio or video attachment
*
* Adapted from /wp-admin/includes/media.php functions wp_add_id3_tag_data,
* wp_read_video_metadata and wp_read_audio_metadata
*
* @since 2.13
*
* @param int post ID of attachment
* @param string optional; if $post_id is zero, path to the image file.
*
* @return array Meta data variables, including 'audio' and 'video'
*/
public static function mla_fetch_attachment_id3_metadata( $post_id, $path = '' ) {
static $id3 = NULL;
if ( 0 != $post_id ) {
$path = get_attached_file($post_id);
}
if ( ! empty( $path ) ) {
if ( ! class_exists( 'getID3' ) ) {
require( ABSPATH . WPINC . '/ID3/getid3.php' );
}
if ( NULL == $id3 ) {
$id3 = new getID3();
}
$data = $id3->analyze( $path );
}
if ( ! empty( $data['filesize'] ) )
$data['filesize'] = (int) $data['filesize'];
if ( ! empty( $data['playtime_seconds'] ) )
$data['length'] = (int) round( $data['playtime_seconds'] );
// from wp_read_video_metadata
if ( ! empty( $data['video'] ) ) {
if ( ! empty( $data['video']['bitrate'] ) )
$data['bitrate'] = (int) $data['video']['bitrate'];
if ( ! empty( $data['video']['resolution_x'] ) )
$data['width'] = (int) $data['video']['resolution_x'];
if ( ! empty( $data['video']['resolution_y'] ) )
$data['height'] = (int) $data['video']['resolution_y'];
}
// from wp_read_audio_metadata
if ( ! empty( $data['audio'] ) ) {
unset( $data['audio']['streams'] );
}
// from wp_add_id3_tag_data
foreach ( array( 'id3v2', 'id3v1' ) as $version ) {
if ( ! empty( $data[ $version ]['comments'] ) ) {
foreach ( $data[ $version ]['comments'] as $key => $list ) {
if ( 'length' !== $key && ! empty( $list ) ) {
$data[ $key ] = reset( $list );
// Fix bug in byte stream analysis.
if ( 'terms_of_use' === $key && 0 === strpos( $metadata[ $key ], 'yright notice.' ) )
$metadata[ $key ] = 'Cop' . $metadata[$key];
}
}
break;
}
}
unset( $data['id3v2']['comments'] );
unset( $data['id3v1']['comments'] );
if ( ! empty( $data['id3v2']['APIC'] ) ) {
$image = reset( $data['id3v2']['APIC']);
if ( ! empty( $image['data'] ) ) {
$data['image'] = array(
'data' => $image['data'],
'mime' => $image['image_mime'],
'width' => $image['image_width'],
'height' => $image['image_height']
);
}
unset( $data['id3v2']['APIC'] );
} elseif ( ! empty( $data['comments']['picture'] ) ) {
$image = reset( $data['comments']['picture'] );
if ( ! empty( $image['data'] ) ) {
$data['image'] = array(
'data' => $image['data'],
'mime' => $image['image_mime']
);
}
unset( $data['comments']['picture'] );
}
$data['post_id'] = $post_id;
return $data;
}
/**
* Fetch and filter IPTC and EXIF, XMP or PDF metadata for an image attachment
*
* @since 0.90
*
* @param int post ID of attachment
* @param string optional; if $post_id is zero, path to the image file.
*
* @return array Meta data variables, IPTC and EXIF or PDF
*/
public static function mla_fetch_attachment_image_metadata( $post_id, $path = '' ) {
$results = array(
'post_id' => $post_id,
'mla_iptc_metadata' => array(),
'mla_exif_metadata' => array(),
'mla_xmp_metadata' => array(),
'mla_pdf_metadata' => array()
);
if ( 0 != $post_id ) {
$path = get_attached_file($post_id);
}
if ( ! empty( $path ) ) {
if ( !file_exists( $path ) ) {
MLACore::mla_debug_add( __LINE__ . ' ' . __( 'ERROR', 'media-library-assistant' ) . ': ' . "mla_fetch_attachment_image_metadata( {$post_id}, {$path} ) not found", MLACore::MLA_DEBUG_CATEGORY_ANY );
return $results;
}
if ( 'pdf' == strtolower( pathinfo( $path, PATHINFO_EXTENSION ) ) ) {
if ( !class_exists( 'MLAPDF' ) ) {
require_once( MLA_PLUGIN_PATH . 'includes/class-mla-data-pdf.php' );
}
$pdf_metadata = MLAPDF::mla_extract_pdf_metadata( $path );
$results['mla_xmp_metadata'] = $pdf_metadata['xmp'];
$results['mla_pdf_metadata'] = $pdf_metadata['pdf'];
MLACore::mla_debug_add( __LINE__ . ' mla_fetch_attachment_image_metadata results = ' . var_export( $results, true ), MLACore::MLA_DEBUG_CATEGORY_METADATA );
return $results;
}
$size = getimagesize( $path, $info );
MLACore::mla_debug_add( __LINE__ . ' mla_fetch_attachment_image_metadata getimagesize returns ' . var_export( $size, true ), MLACore::MLA_DEBUG_CATEGORY_METADATA );
MLACore::mla_debug_add( __LINE__ . ' mla_fetch_attachment_image_metadata getimagesize info keys = ' . var_export( array_keys( $info ), true ), MLACore::MLA_DEBUG_CATEGORY_METADATA );
if ( is_callable( 'iptcparse' ) ) {
if ( ! empty( $info['APP13'] ) ) {
set_error_handler( 'MLAData::mla_IPTC_EXIF_error_handler' );
try {
$exception = NULL;
$iptc_values = iptcparse( $info['APP13'] );
} catch ( Throwable $e ) { // PHP 7
$exception = $e;
$iptc_values = NULL;
} catch ( Exception $e ) { // PHP 5
$exception = $e;
$iptc_values = NULL;
}
restore_error_handler();
MLACore::mla_debug_add( __LINE__ . ' mla_fetch_attachment_image_metadata iptc_values = ' . var_export( $iptc_values, true ), MLACore::MLA_DEBUG_CATEGORY_METADATA );
if ( ! empty( $exception ) ) {
MLAData::$mla_IPTC_EXIF_errors[] = sprintf( '(%1$s) %2$s', $exception->getCode(), $exception->getMessage() );
}
// Combine exceptions with PHP notice/warning/error messages
if ( ! empty( MLAData::$mla_IPTC_EXIF_errors ) ) {
$results['mla_iptc_errors'] = MLAData::$mla_IPTC_EXIF_errors;
MLACore::mla_debug_add( __LINE__ . ' ' . __( 'ERROR', 'media-library-assistant' ) . ': ' . '$results[mla_iptc_errors] = ' . var_export( $results['mla_iptc_errors'], true ), MLACore::MLA_DEBUG_CATEGORY_ANY );
MLAData::$mla_IPTC_EXIF_errors = array();
}
if ( ! is_array( $iptc_values ) ) {
$iptc_values = array();
}
foreach ( $iptc_values as $key => $value ) {
if ( in_array( $key, array( '1#000', '1#020', '1#022', '1#120', '1#122', '2#000', '2#200', '2#201' ) ) ) {
$value = unpack( 'nbinary', $value[0] );
$results['mla_iptc_metadata'][ $key ] = (string) $value['binary'];
} elseif ( 1 == count( $value ) ) {
$results['mla_iptc_metadata'][ $key ] = $value[0];
} else {
$results['mla_iptc_metadata'][ $key ] = $value;
}
} // foreach $value
} // ! empty
} // iptcparse
if ( is_callable( 'exif_read_data' ) && in_array( $size[2], array( IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM ) ) ) {
set_error_handler( 'MLAData::mla_IPTC_EXIF_error_handler' );
try {
$exception = NULL;
$exif_data = exif_read_data( $path, NULL, true );
} catch ( Throwable $e ) { // PHP 7
$exception = $e;
$exif_data = NULL;
} catch ( Exception $e ) { // PHP 5
$exception = $e;
$exif_data = NULL;
}
restore_error_handler();
MLACore::mla_debug_add( __LINE__ . ' mla_fetch_attachment_image_metadata (PHP ' . phpversion() . ') exif_data = ' . var_export( $exif_data, true ), MLACore::MLA_DEBUG_CATEGORY_METADATA );
if ( ! empty( $exception ) ) {
MLAData::$mla_IPTC_EXIF_errors[] = sprintf( '(%1$s) %2$s', $exception->getCode(), $exception->getMessage() );
}
// Combine exceptions with PHP notice/warning/error messages
if ( ! empty( MLAData::$mla_IPTC_EXIF_errors ) ) {
$results['mla_exif_errors'] = MLAData::$mla_IPTC_EXIF_errors;
MLACore::mla_debug_add( __LINE__ . ' ' . __( 'ERROR', 'media-library-assistant' ) . ': ' . '$results[mla_exif_errors] = ' . var_export( $results['mla_exif_errors'], true ), MLACore::MLA_DEBUG_CATEGORY_ANY );
MLAData::$mla_IPTC_EXIF_errors = array();
}
if ( ! is_array( $exif_data ) ) {
$exif_data = array();
}
// Promote most array elements to top-level simple values, emulating $arrays = false
foreach ( $exif_data as $section_name => $section_data ) {
/*
* The sections COMPUTED, THUMBNAIL, and COMMENT always become arrays as
* they may contain values whose names conflict with other sections.
* The WINXP section usually contains garbage that overwrites IFD0 values.
*/
if ( in_array( $section_name, array ( 'COMPUTED', 'THUMBNAIL', 'COMMENT', 'WINXP' ) ) ) {
$results['mla_exif_metadata'][ $section_name ] = $section_data;
continue;
}
foreach ( $section_data as $element_name => $element_value ) {
// JPEG standard defines 0xEA1C as "padding"
if ( 'UndefinedTag:0xEA1C' === $element_name ) {
continue;
}
// Decode some non-string values
if ( 'IFD0' === $section_name ) {
/*
* PixelUnit (1 byte) Units for the PixelPerUnitX and PixelPerUnitY densities
* 0: no units, PixelPerUnitX and PixelPerUnitY specify the pixel aspect ratio
* 1: PixelPerUnitX and PixelPerUnitY are dots per inch
* 2: PixelPerUnitX and PixelPerUnitY are dots per cm
*/
if ( 'PixelUnit' === $element_name ) {
$element_value = (string) ord( $element_value );
}
// Problem with values edited through Windows right-click properties.
if ( in_array( $element_name, array( 'Title', 'Keywords', 'Subject' ) ) ) {
$element_value = str_replace( "\000", '', $element_value );
}
}
$results['mla_exif_metadata'][ $element_name ] = $element_value;
} // foreach $section_data
} // foreach $exif_data
// $exif_data is used for enhanced values below
$exif_data = $results['mla_exif_metadata'];
} // exif_read_data
$results['mla_xmp_metadata'] = self::mla_parse_xmp_metadata( $path, 0 );
if ( NULL == $results['mla_xmp_metadata'] ) {
$results['mla_xmp_metadata'] = array();
}
// experimental damage repair for Robert O'Conner (Rufus McDufus)
if ( isset( $exif_data['DateTimeOriginal'] ) && ( 8 > strlen( $exif_data['DateTimeOriginal'] ) ) ) {
if ( isset( $results['mla_xmp_metadata']['CreateDate'] )&& ( is_numeric( strtotime( $results['mla_xmp_metadata']['CreateDate'] ) ) ) ) {
$exif_data['BadDateTimeOriginal'] = $exif_data['DateTimeOriginal'];
$results['mla_exif_metadata']['BadDateTimeOriginal'] = $exif_data['DateTimeOriginal'];
$exif_data['DateTimeOriginal'] = $results['mla_xmp_metadata']['CreateDate'];
$results['mla_exif_metadata']['DateTimeOriginal'] = $results['mla_xmp_metadata']['CreateDate'];
}
}
// experimental damage repair for Elsie Gilmore (earthnutvt)
if ( isset( $exif_data['Keywords'] ) && ( '????' == substr( $exif_data['Keywords'], 0, 4 ) ) ) {
if ( isset( $results['mla_xmp_metadata']['Keywords'] ) ) {
$exif_data['Keywords'] = $results['mla_xmp_metadata']['Keywords'];
$results['mla_exif_metadata']['Keywords'] = $results['mla_xmp_metadata']['Keywords'];
} else {
unset( $exif_data['Keywords'] );
unset( $results['mla_exif_metadata']['Keywords'] );
}
}
}
/*
* Expand EXIF Camera-related values:
*
* ExposureBiasValue
* ExposureTime
* Flash
* FNumber
* FocalLength
* ShutterSpeed from ExposureTime
*/
$new_data = array();
if ( isset( $exif_data['FNumber'] ) ) {
if ( false !== ( $value = self::_rational_to_string( $exif_data['FNumber'], '%1$d', '%1$d/%2$d', '%1$.1f' ) ) ) {
$new_data['FNumber'] = $value;
}
} // FNumber
if ( isset( $exif_data['ExposureBiasValue'] ) ) {
$fragments = array_map( 'intval', explode( '/', $exif_data['ExposureBiasValue'] ) );
if ( ! is_null( $fragments[1] ) ) {
$numerator = $fragments[0];
$denominator = $fragments[1];
// Clean up some common format issues, e.g. 4/6, 2/4
while ( ( 0 == ( $numerator & 0x1 ) ) && ( 0 == ( $denominator & 0x1 ) ) ) {
$numerator = ( $numerator >> 1 );
$denominator = ( $denominator >> 1 );
}
// Remove excess precision
if ( ( $denominator > $numerator) && ( 1000 < $numerator ) && ( 1000 < $denominator ) ) {
$exif_data['ExposureBiasValue'] = sprintf( '%1$+.3f', ( $numerator/$denominator ) );
} else {
$fragments[0] = $numerator;
$fragments[1] = $denominator;
$exif_data['ExposureBiasValue'] = $numerator . '/' . $denominator;
}
}
if ( false !== ( $value = self::_rational_to_string( $exif_data['ExposureBiasValue'], '%1$+d', '%1$+d/%2$d', '%1$+.2f' ) ) ) {
$new_data['ExposureBiasValue'] = $value;
}
} // ExposureBiasValue
if ( isset( $exif_data['Flash'] ) ) {
$value = ( absint( $exif_data['Flash'] ) );
if ( $value & 0x1 ) {
$new_data['Flash'] = __( 'Yes', 'media-library-assistant' );
} else {
$new_data['Flash'] = __( 'No', 'media-library-assistant' );
}
} // Flash
if ( isset( $exif_data['FocalLength'] ) ) {
if ( false !== ( $value = self::_rational_to_string( $exif_data['FocalLength'], '%1$d', '%1$d/%2$d', '%1$.2f' ) ) ) {
$new_data['FocalLength'] = $value;
}
} // FocalLength
if ( isset( $exif_data['ExposureTime'] ) ) {
if ( false !== ( $value = self::_rational_to_string( $exif_data['ExposureTime'], '%1$d', '%1$d/%2$d', '%1$.2f' ) ) ) {
$new_data['ExposureTime'] = $value;
}
} // ExposureTime
/*
* ShutterSpeed in "1/" format, from ExposureTime
* Special logic for "fractional shutter speed" values 1.3, 1.5, 1.6, 2.5
*/
if ( isset( $exif_data['ExposureTime'] ) ) {
$fragments = array_map( 'intval', explode( '/', $exif_data['ExposureTime'] ) );
if ( ! is_null( $fragments[1] && $fragments[0] ) ) {
if ( 1 == $fragments[1] ) {
$new_data['ShutterSpeed'] = $new_data['ExposureTime'] = sprintf( '%1$d', $fragments[0] );
} elseif ( 0 != $fragments[1] ) {
$value = $fragments[0] / $fragments[1];
if ( ( 0 < $value ) && ( 1 > $value ) ) {
// Convert to "1/" value for shutter speed
if ( 1 == $fragments[0] ) {
$new_data['ShutterSpeed'] = $new_data['ExposureTime'];
} else {
$test = (float) number_format( 1.0 / $value, 1, '.', '');
if ( in_array( $test, array( 1.3, 1.5, 1.6, 2.5 ) ) ) {
$new_data['ShutterSpeed'] = '1/' . number_format( 1.0 / $value, 1, '.', '' );
} else {
$new_data['ShutterSpeed'] = '1/' . number_format( 1.0 / $value, 0, '.', '' );
}
}
} else {
$new_data['ShutterSpeed'] = $new_data['ExposureTime'] = sprintf( '%1$.2f', $value );
}
} // fractional value
} // valid denominator and non-zero numerator
} // ShutterSpeed
if ( isset( $exif_data['UndefinedTag:0xA420'] ) ) {
$new_data['ImageUniqueID'] = $exif_data['UndefinedTag:0xA420'];
}
if ( isset( $exif_data['UndefinedTag:0xA430'] ) ) {
$new_data['CameraOwnerName'] = $exif_data['UndefinedTag:0xA430'];
}
if ( isset( $exif_data['UndefinedTag:0xA431'] ) ) {
$new_data['BodySerialNumber'] = $exif_data['UndefinedTag:0xA431'];
}
if ( isset( $exif_data['UndefinedTag:0xA432'] ) && is_array( $exif_data['UndefinedTag:0xA432'] ) ) {
$array = $new_data['LensSpecification'] = $exif_data['UndefinedTag:0xA432'];
if ( isset ( $array[0] ) ) {
if ( false !== ( $value = self::_rational_to_string( $array[0], '%1$d', '%1$d/%2$d', '%1$.2f' ) ) ) {
$new_data['LensMinFocalLength'] = $value;
}
}
if ( isset ( $array[1] ) ) {
if ( false !== ( $value = self::_rational_to_string( $array[1], '%1$d', '%1$d/%2$d', '%1$.2f' ) ) ) {
$new_data['LensMaxFocalLength'] = $value;
}
}
if ( isset ( $array[2] ) ) {
if ( false !== ( $value = self::_rational_to_string( $array[2], '%1$d', '%1$d/%2$d', '%1$.1f' ) ) ) {
$new_data['LensMinFocalLengthFN'] = $value;
}
}
if ( isset ( $array[3] ) ) {
if ( false !== ( $value = self::_rational_to_string( $array[3], '%1$d', '%1$d/%2$d', '%1$.1f' ) ) ) {
$new_data['LensMaxFocalLengthFN'] = $value;
}
}
}
if ( isset( $exif_data['UndefinedTag:0xA433'] ) ) {
$new_data['LensMake'] = $exif_data['UndefinedTag:0xA433'];
}
if ( isset( $exif_data['UndefinedTag:0xA434'] ) ) {
$new_data['LensModel'] = $exif_data['UndefinedTag:0xA434'];
}
if ( isset( $exif_data['UndefinedTag:0xA435'] ) ) {
$new_data['LensSerialNumber'] = $exif_data['UndefinedTag:0xA435'];
}
if ( ! empty( $new_data ) ) {
$results['mla_exif_metadata']['CAMERA'] = $new_data;
}
/*
* Expand EXIF GPS values
*/
$new_data = array();
if ( isset( $exif_data['GPSVersion'] ) ) {
$new_data['Version'] = sprintf( '%1$d.%2$d.%3$d.%4$d', ord( $exif_data['GPSVersion'][0] ), ord( $exif_data['GPSVersion'][1] ), ord( $exif_data['GPSVersion'][2] ), ord( $exif_data['GPSVersion'][3] ) );
}
if ( isset( $exif_data['GPSLatitudeRef'] ) ) {
$new_data['LatitudeRef'] = $exif_data['GPSLatitudeRef'];
$new_data['LatitudeRefS'] = ( 'N' == $exif_data['GPSLatitudeRef'] ) ? '' : '-';
$ref = $new_data['LatitudeRef'];
$refs = $new_data['LatitudeRefS'];
} else {
$ref = '';
$refs = '';
}
if ( isset( $exif_data['GPSLatitude'] ) ) {
$rational = $exif_data['GPSLatitude'];
$new_data['LatitudeD'] = $degrees = self::_rational_to_decimal( $rational[0] );
$new_data['LatitudeM'] = $minutes = self::_rational_to_decimal( $rational[1] );
$new_data['LatitudeS'] = sprintf( '%1$01.4f', $seconds = self::_rational_to_decimal( $rational[2] ) );
$decimal_minutes = $minutes + ( $seconds / 60 );
$decimal_degrees = ( $decimal_minutes / 60 );
$new_data['Latitude'] = sprintf( '%1$dd %2$d\' %3$01.4f" %4$s', $degrees, $minutes, $seconds, $ref );
$new_data['LatitudeDM'] = sprintf( '%1$d %2$01.4f', $degrees, $decimal_minutes );
$new_data['LatitudeDD'] = sprintf( '%1$01f', $degrees + $decimal_degrees );
$new_data['LatitudeMinDec'] = substr( $new_data['LatitudeDM'], strpos( $new_data['LatitudeDM'], ' ' ) + 1 );
$new_data['LatitudeDegDec'] = substr( $new_data['LatitudeDD'], strpos( $new_data['LatitudeDD'], '.' ) );
$new_data['LatitudeSDM'] = $refs . $new_data['LatitudeDM'];
$new_data['LatitudeSDD'] = $refs . $new_data['LatitudeDD'];
$new_data['LatitudeDM'] = $new_data['LatitudeDM'] . $ref;
$new_data['LatitudeDD'] = $new_data['LatitudeDD'] . $ref;
}
if ( isset( $exif_data['GPSLongitudeRef'] ) ) {
$new_data['LongitudeRef'] = $exif_data['GPSLongitudeRef'];
$new_data['LongitudeRefS'] = ( 'E' == $exif_data['GPSLongitudeRef'] ) ? '' : '-';
$ref = $new_data['LongitudeRef'];
$refs = $new_data['LongitudeRefS'];
} else {
$ref = '';
$refs = '';
}
if ( isset( $exif_data['GPSLongitude'] ) ) {
$rational = $exif_data['GPSLongitude'];
$new_data['LongitudeD'] = $degrees = self::_rational_to_decimal( $rational[0] );
$new_data['LongitudeM'] = $minutes = self::_rational_to_decimal( $rational[1] );
$new_data['LongitudeS'] = sprintf( '%1$01.4f', $seconds = self::_rational_to_decimal( $rational[2] ) );
$decimal_minutes = $minutes + ( $seconds / 60 );
$decimal_degrees = ( $decimal_minutes / 60 );
$new_data['Longitude'] = sprintf( '%1$dd %2$d\' %3$01.4f" %4$s', $degrees, $minutes, $seconds, $ref );
$new_data['LongitudeDM'] = sprintf( '%1$d %2$01.4f', $degrees, $decimal_minutes );
$new_data['LongitudeDD'] = sprintf( '%1$01f', $degrees + $decimal_degrees );
$new_data['LongitudeMinDec'] = substr( $new_data['LongitudeDM'], strpos( $new_data['LongitudeDM'], ' ' ) + 1 );
$new_data['LongitudeDegDec'] = substr( $new_data['LongitudeDD'], strpos( $new_data['LongitudeDD'], '.' ) );
$new_data['LongitudeSDM'] = $refs . $new_data['LongitudeDM'];
$new_data['LongitudeSDD'] = $refs . $new_data['LongitudeDD'];
$new_data['LongitudeDM'] = $new_data['LongitudeDM'] . $ref;
$new_data['LongitudeDD'] = $new_data['LongitudeDD'] . $ref;
}
if ( isset( $exif_data['GPSAltitudeRef'] ) ) {
$new_data['AltitudeRef'] = sprintf( '%1$d', ord( $exif_data['GPSAltitudeRef'][0] ) );
$new_data['AltitudeRefS'] = ( '0' == $new_data['AltitudeRef'] ) ? '' : '-';
$refs = $new_data['AltitudeRefS'];
} else {
$refs = '';
}
if ( isset( $exif_data['GPSAltitude'] ) ) {
$new_data['Altitude'] = sprintf( '%1$s%2$01.4f', $refs, $meters = self::_rational_to_decimal( $exif_data['GPSAltitude'] ) );
$new_data['AltitudeFeet'] = sprintf( '%1$s%2$01.2f', $refs, $meters * 3.280839895013 );
}
if ( isset( $exif_data['GPSTimeStamp'] ) ) {
$rational = $exif_data['GPSTimeStamp'];
$new_data['TimeStampH'] = sprintf( '%1$02d', $hours = self::_rational_to_decimal( $rational[0] ) );
$new_data['TimeStampM'] = sprintf( '%1$02d', $minutes = self::_rational_to_decimal( $rational[1] ) );
$new_data['TimeStampS'] = sprintf( '%1$02d', $seconds = self::_rational_to_decimal( $rational[2] ) );
$new_data['TimeStamp'] = sprintf( '%1$02d:%2$02d:%3$02d', $hours, $minutes, $seconds );
}
if ( isset( $exif_data['GPSDateStamp'] ) ) {
$parts = explode( ':', $exif_data['GPSDateStamp'] );
$new_data['DateStampY'] = $parts[0];
$new_data['DateStampM'] = $parts[1];
$new_data['DateStampD'] = $parts[2];
$new_data['DateStamp'] = $exif_data['GPSDateStamp'];
}
if ( isset( $exif_data['GPSMapDatum'] ) ) {
$new_data['MapDatum'] = $exif_data['GPSMapDatum'];
}
if ( ! empty( $new_data ) ) {
$results['mla_exif_metadata']['GPS'] = $new_data;
}
MLACore::mla_debug_add( __LINE__ . ' mla_fetch_attachment_image_metadata results = ' . var_export( $results, true ), MLACore::MLA_DEBUG_CATEGORY_METADATA );
return $results;
}
/**
* Update "meta:" data for a single attachment
*
* @since 1.51
*
* @param array The current wp_attachment_metadata value
* @param array Field name => value pairs
*
* @return string success/failure message(s); empty string if no changes.
*/
public static function mla_update_wp_attachment_metadata( &$current_values, $new_meta ) {
$message = '';
foreach( $new_meta as $key => $value ) {
/*
* The "Multi" option has no meaning for attachment_metadata;
* convert to a simple array or string
*/
if ( isset( $value[0x80000000] ) ) {
unset( $value[0x80000000] );
unset( $value[0x80000001] );
unset( $value[0x80000002] );
if ( 1 == count( $value ) ) {
foreach ( $value as $single_key => $single_value ) {
if ( is_integer( $single_key ) ) {
$value = $single_value;
}
}
} // one-element array
} // Multi-key value
$value = sanitize_text_field( $value );
$old_value = self::mla_find_array_element( $key, $current_values, 'array' );
if ( ! empty( $old_value ) ) {
if ( empty( $value ) ) {
if ( self::_unset_array_element( $key, $current_values ) ) {
/* translators: 1: meta_key */
$message .= sprintf( __( 'Deleting %1$s', 'media-library-assistant' ) . '<br>', $key );
} else {
/* translators: 1: ERROR tag 2: meta_key */
$message .= sprintf( __( '%1$s: meta:%2$s not found', 'media-library-assistant' ) . '<br>', __( 'ERROR', 'media-library-assistant' ), $key );
}
continue;
}
} else { // old_value present
if ( ! empty( $value ) ) {
if ( self::_set_array_element( $key, $value, $current_values ) ) {
/* translators: 1: meta_key 2: meta_value */
$message .= sprintf( __( 'Adding %1$s = %2$s', 'media-library-assistant' ) . '<br>', $key,
( is_array( $value ) ) ? var_export( $value, true ) : $value );
} else {
/* translators: 1: ERROR tag 2: meta_key */
$message .= sprintf( __( '%1$s: Adding meta:%2$s; not found', 'media-library-assistant' ) . '<br>', __( 'ERROR', 'media-library-assistant' ), $key );
}
continue;
} elseif ( NULL == $value ) {
if ( self::_unset_array_element( $key, $current_values ) ) {
/* translators: 1: meta_key */
$message .= sprintf( __( 'Deleting Null meta:%1$s', 'media-library-assistant' ) . '<br>', $key );
}
continue;
}
} // old_value empty
if ( $old_value !== $value ) {
if ( self::_set_array_element( $key, $value, $current_values ) ) {
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', 'meta:' . $key,
( is_array( $old_value ) ) ? var_export( $old_value, true ) : $old_value,
( is_array( $value ) ) ? var_export( $value, true ) : $value );
} else {
/* translators: 1: ERROR tag 2: meta_key */
$message .= sprintf( __( '%1$s: Changing meta:%2$s; not found', 'media-library-assistant' ) . '<br>', __( 'ERROR', 'media-library-assistant' ), $key );
}
}
} // foreach new_meta
return $message;
}
/**
* Update custom field and "meta:" data for a single attachment
*
* @since 1.40
*
* @param int The ID of the attachment to be updated
* @param array Field name => value pairs
*
* @return string success/failure message(s)
*/
public static function mla_update_item_postmeta( $post_id, $new_meta ) {
$post_data = MLAQuery::mla_fetch_attachment_metadata( $post_id );
$message = '';
$attachment_meta_values = array();
foreach ( $new_meta as $meta_key => $meta_value ) {
if ( 'meta:' == substr( $meta_key, 0, 5 ) ) {
$meta_key = substr( $meta_key, 5 );
$attachment_meta_values[ $meta_key ] = $meta_value;
continue;
}
if ( $multi_key = isset( $meta_value[0x80000000] ) ) {
unset( $meta_value[0x80000000] );
}
if ( $keep_existing = isset( $meta_value[0x80000001] ) ) {
$keep_existing = (boolean) $meta_value[0x80000001];
unset( $meta_value[0x80000001] );
}
if ( $no_null = isset( $meta_value[0x80000002] ) ) {
$no_null = (boolean) $meta_value[0x80000002];
unset( $meta_value[0x80000002] );
}
// mla_fetch_attachment_metadata doesn't return "hidden" fields
if ( '_' === $meta_key{0} ) {
$old_meta_value = get_post_meta( $post_id, $meta_key );
if ( !empty( $old_meta_value ) ) {
if ( is_array( $old_meta_value ) ) {
if ( count( $old_meta_value ) == 1 ) {
$old_meta_value = maybe_unserialize( current( $old_meta_value ) );
} else {
foreach ( $old_meta_value as $single_key => $single_value ) {
$old_meta_value[ $single_key ] = maybe_unserialize( $single_value );
}
}
}
$post_data[ 'mla_item_' . $meta_key ] = $old_meta_value;
}
}
if ( isset( $post_data[ 'mla_item_' . $meta_key ] ) ) {
$old_meta_value = $post_data[ 'mla_item_' . $meta_key ];
if ( $multi_key && $no_null ) {
if ( is_string( $old_meta_value ) ) {
$old_meta_value = trim( $old_meta_value );
}
$delete = empty( $old_meta_value );
} else {
$delete = NULL === $meta_value;
}
if ( $delete) {
if ( delete_post_meta( $post_id, $meta_key ) ) {
/* translators: 1: meta_key */
$message .= sprintf( __( 'Deleting %1$s', 'media-library-assistant' ) . '<br>', $meta_key );
}
continue;
}
} else {
if ( NULL !== $meta_value ) {
if ( $multi_key ) {
foreach ( $meta_value as $new_value ) {
if ( add_post_meta( $post_id, $meta_key, $new_value ) ) {
/* translators: 1: meta_key 2: new_value */
$message .= sprintf( __( 'Adding %1$s = %2$s', 'media-library-assistant' ) . '<br>', $meta_key, '[' . $new_value . ']' );
}
}
} else {
if ( add_post_meta( $post_id, $meta_key, $meta_value ) ) {
if ( is_array( $meta_value ) ) {
$new_text = var_export( $meta_value, true );
} else {
$new_text = $meta_value;
}
/* translators: 1: meta_key 2: meta_value */
$message .= sprintf( __( 'Adding %1$s = %2$s', 'media-library-assistant' ) . '<br>', $meta_key, $new_text );
}
}
}
continue; // no change or message if old and new are both NULL
} // no old value
$old_text = ( is_array( $old_meta_value ) ) ? var_export( $old_meta_value, true ) : $old_meta_value;
/*
* Multi-key change from existing values to new values
*/
if ( $multi_key ) {
/*
* Test for "no changes"
*/
if ( $meta_value == (array) $old_meta_value ) {
continue;
}
if ( ! $keep_existing ) {
if ( delete_post_meta( $post_id, $meta_key ) ) {
/* translators: 1: meta_key */
$message .= sprintf( __( 'Deleting old %1$s values', 'media-library-assistant' ) . '<br>', $meta_key );
}
$old_meta_value = array();
} elseif ( $old_text == $old_meta_value ) { // single value
$old_meta_value = array( $old_meta_value );
}
$updated = 0;
foreach ( $meta_value as $new_value ) {
if ( ! in_array( $new_value, $old_meta_value ) ) {
add_post_meta( $post_id, $meta_key, $new_value );
$old_meta_value[] = $new_value; // prevent duplicates
$updated++;
}
}
if ( $updated ) {
$meta_value = get_post_meta( $post_id, $meta_key );
if ( is_array( $meta_value ) ) {
if ( 1 == count( $meta_value ) ) {
$new_text = $meta_value[0];
} else {
$new_text = var_export( $meta_value, true );
}
} else {
$new_text = $meta_value;
}
/* translators: 1: meta_key 2: old_value 3: new_value 4: update count*/
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"; %4$d updates', 'media-library-assistant' ) . '<br>', 'meta:' . $meta_key, $old_text, $new_text, $updated );
}
} elseif ( $old_meta_value !== $meta_value ) {
if ( is_array( $old_meta_value ) ) {
delete_post_meta( $post_id, $meta_key );
}
if ( is_array( $meta_value ) ) {
$new_text = var_export( $meta_value, true );
} else {
$new_text = $meta_value;
}
if ( update_post_meta( $post_id, $meta_key, $meta_value ) ) {
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', 'meta:' . $meta_key, $old_text, $new_text );
}
}
} // foreach $new_meta
/*
* Process the "meta:" updates, if any
*/
if ( ! empty( $attachment_meta_values ) ) {
if ( isset( $post_data['mla_wp_attachment_metadata'] ) ) {
$current_values = $post_data['mla_wp_attachment_metadata'];
} else {
$current_values = array();
}
$results = self::mla_update_wp_attachment_metadata( $current_values, $attachment_meta_values );
if ( ! empty( $results ) ) {
if ( update_post_meta( $post_id, '_wp_attachment_metadata', $current_values ) ) {
$message .= $results;
}
}
}
return $message;
}
/**
* Update a single item; change the "post" data, taxonomy terms
* and meta data for a single attachment
*
* @since 0.1
*
* @param int The ID of the attachment to be updated
* @param array Field name => value pairs
* @param array Optional taxonomy term values, default null
* @param array Optional taxonomy actions (add, remove, replace), default null
*
* @return array success/failure message and NULL content
*/
public static function mla_update_single_item( $post_id, $new_data, $tax_input = NULL, $tax_actions = NULL ) {
$post_data = self::mla_get_attachment_by_id( $post_id, false );
if ( !isset( $post_data ) ) {
return array(
'message' => __( 'ERROR', 'media-library-assistant' ) . ': ' . __( 'Could not retrieve Attachment.', 'media-library-assistant' ),
'body' => ''
);
}
$updates = apply_filters( 'mla_update_single_item', compact( array( 'new_data', 'tax_input', 'tax_actions' ) ), $post_id, $post_data );
$new_data = isset( $updates['new_data'] ) ? $updates['new_data'] : array();
$tax_input = isset( $updates['tax_input'] ) ? $updates['tax_input'] : NULL;
$tax_actions = isset( $updates['tax_actions'] ) ? $updates['tax_actions'] : NULL;
$message = '';
$updates = array( 'ID' => $post_id );
$new_data = stripslashes_deep( $new_data );
$new_meta = NULL;
foreach ( $new_data as $key => $value ) {
switch ( $key ) {
case 'post_title':
if ( $value == $post_data[ $key ] ) {
break;
}
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Title', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
$updates[ $key ] = $value;
break;
case 'post_name':
if ( $value == $post_data[ $key ] ) {
break;
}
$value = sanitize_title( $value );
/*
* Make sure new slug is unique
*/
$args = array(
'name' => $value,
'post_type' => 'attachment',
'post_status' => 'inherit',
'showposts' => 1
);
$my_posts = get_posts( $args );
if ( $my_posts ) {
/* translators: 1: ERROR tag 2: old_value */
$message .= sprintf( __( '%1$s: Could not change Name/Slug "%2$s"; name already exists', 'media-library-assistant' ) . '<br>', __( 'ERROR', 'media-library-assistant' ), $value );
} else {
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Name/Slug', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
$updates[ $key ] = $value;
}
break;
/*
* bulk_image_alt requires a separate key because some attachment types
* should not get a value, e.g., text or PDF documents
*/
case 'bulk_image_alt':
// wp_attachment_is_image( $post_id );
if ( 'image/' !== substr( $post_data[ 'post_mime_type' ], 0, 6 ) ) {
break;
}
// fallthru
case 'image_alt':
$key = 'mla_wp_attachment_image_alt';
if ( !isset( $post_data[ $key ] ) ) {
$post_data[ $key ] = NULL;
}
if ( $value == $post_data[ $key ] ) {
break;
}
if ( empty( $value ) ) {
if ( delete_post_meta( $post_id, '_wp_attachment_image_alt' ) ) {
/* translators: 1: old_value */
$message .= sprintf( __( 'Deleting ALT Text, was "%1$s"', 'media-library-assistant' ) . '<br>', esc_attr( $post_data[ $key ] ) );
} else {
/* translators: 1: ERROR tag 2: old_value */
$message .= sprintf( __( '%1$s: Could not delete ALT Text, remains "%2$s"', 'media-library-assistant' ) . '<br>', __( 'ERROR', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ) );
}
} else {
/*
* ALT Text isn't supposed to have multiple values, but it happens.
* Delete multiple values and start over.
*/
if ( is_array( $post_data[ $key ] ) ) {
delete_post_meta( $post_id, '_wp_attachment_image_alt' );
}
if ( update_post_meta( $post_id, '_wp_attachment_image_alt', $value ) ) {
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'ALT Text', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
} else {
/* translators: 1: ERROR tag 2: old_value 3: new_value */
$message .= sprintf( __( '%1$s: Could not change ALT Text from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'ERROR', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
}
}
break;
case 'post_excerpt':
if ( $value == $post_data[ $key ] ) {
break;
}
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Caption', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
$updates[ $key ] = $value;
break;
case 'post_content':
if ( $value == $post_data[ $key ] ) {
break;
}
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Description', 'media-library-assistant' ), esc_textarea( $post_data[ $key ] ), esc_textarea( $value ) );
$updates[ $key ] = $value;
break;
case 'post_parent':
if ( $value == $post_data[ $key ] ) {
break;
}
$value = absint( $value );
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Parent', 'media-library-assistant' ), $post_data[ $key ], $value );
$updates[ $key ] = $value;
break;
case 'menu_order':
if ( $value == $post_data[ $key ] ) {
break;
}
$value = absint( $value );
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Menu Order', 'media-library-assistant' ), $post_data[ $key ], $value );
$updates[ $key ] = $value;
break;
case 'post_author':
if ( $value == $post_data[ $key ] ) {
break;
}
$value = absint( $value );
$from_user = get_userdata( $post_data[ $key ] );
$to_user = get_userdata( $value );
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Author', 'media-library-assistant' ), $from_user->display_name, $to_user->display_name );
$updates[ $key ] = $value;
break;
case 'comment_status':
if ( $value == $post_data[ $key ] ) {
break;
}
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Comments', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
$updates[ $key ] = $value;
break;
case 'ping_status':
if ( $value == $post_data[ $key ] ) {
break;
}
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Pings', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
$updates[ $key ] = $value;
break;
case 'post_date':
if ( $value == $post_data[ $key ] ) {
break;
}
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Uploaded on', 'media-library-assistant' ), esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
$updates[ $key ] = $value;
break;
case 'post_date_gmt':
if ( $value == $post_data[ $key ] ) {
break;
}
/* translators: 1: element name 2: old_value 3: new_value */
$message .= sprintf( __( 'Changing %1$s from "%2$s" to "%3$s"', 'media-library-assistant' ) . '<br>', __( 'Uploaded on', 'media-library-assistant' ) . ' GMT', esc_attr( $post_data[ $key ] ), esc_attr( $value ) );
$updates[ $key ] = $value;
break;
case 'taxonomy_updates':
$tax_input = $value['inputs'];
$tax_actions = $value['actions'];
break;
case 'custom_updates':
$new_meta = $value;
break;
default:
// Ignore anything else
} // switch $key
} // foreach $new_data
if ( ! empty( $tax_input ) ) {
foreach ( $tax_input as $taxonomy => $tags ) {
if ( ! empty( $tax_actions ) ) {
$tax_action = $tax_actions[ $taxonomy ];
} else {
$tax_action = 'replace';
}
$taxonomy_obj = get_taxonomy( $taxonomy );
if ( ! current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
/* translators: 1: taxonomy */
$message .= sprintf( __( 'You cannot assign "%1$s" terms', 'media-library-assistant' ) . '<br>', $taxonomy );
continue;
}
// array of int = hierarchical, comma-delimited string = flat.
if ( is_array( $tags ) ) {
$tags = array_filter( $tags );
} else {
/*
* Convert flat taxonomy input to term IDs, to avoid ambiguity.
* Adapted from edit_post() in /wp-admin/includes/post.php
*/
$comma = _x( ',', 'tag delimiter' );
if ( ',' !== $comma ) {
$tags = str_replace( $comma, ',', $tags );
}
$tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
$clean_terms = array();
foreach ( $tags as $tag ) {
// Empty terms are invalid input.
if ( empty( $tag ) ) {
continue;
}
$_term = MLAQuery::mla_wp_get_terms( $taxonomy, array(
'name' => $tag,
'fields' => 'ids',
'hide_empty' => false,
) );
if ( ! empty( $_term ) ) {
$clean_terms[] = intval( $_term[0] );
} else {
// No existing term was found, so pass the string. A new term will be created.
$clean_terms[] = $tag;
}
}
$tags = $clean_terms;
}
switch ( $tax_action ) {
case 'add':
if ( ! empty( $tags ) ) {
$action_name = __( 'Adding', 'media-library-assistant' );
$result = wp_set_post_terms( $post_id, $tags, $taxonomy, true );
}
break;
case 'remove':
$action_name = __( 'Removing', 'media-library-assistant' );
$tags = self::_remove_terms( $post_id, $tags, $taxonomy_obj );
$result = wp_set_post_terms( $post_id, $tags, $taxonomy );
if ( empty( $tags ) ) {
$result = true;
}
break;
case 'replace':
$action_name = __( 'Replacing', 'media-library-assistant' );
$result = wp_set_post_terms( $post_id, $tags, $taxonomy );
if ( empty( $tags ) ) {
$result = true;
}
break;
default:
$action_name = __( 'Ignoring', 'media-library-assistant' );
$result = NULL;
// ignore anything else
}
/*
* Definitive results check would use:
* do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids );
* in /wp_includes/taxonomy.php function wp_set_object_terms()
*/
if ( ! empty( $result ) ) {
delete_transient( MLA_OPTION_PREFIX . 't_term_counts_' . $taxonomy );
/* translators: 1: action_name, 2: taxonomy */
$message .= sprintf( __( '%1$s "%2$s" terms', 'media-library-assistant' ) . '<br>', $action_name, $taxonomy );
}
} // foreach $tax_input
} // ! empty $tax_input
if ( is_array( $new_meta ) ) {
$message .= self::mla_update_item_postmeta( $post_id, $new_meta );
}
if ( empty( $message ) ) {
return array(
/* translators: 1: post ID */
'message' => sprintf( __( 'Item %1$d, no changes detected.', 'media-library-assistant' ), $post_id ),
'body' => ''
);
} else {
// invalidate the cached item
self::mla_get_attachment_by_id( -1 );
MLAQuery::mla_fetch_attachment_parent_data( -1 );
MLAQuery::mla_fetch_attachment_metadata( -1 );
MLAQuery::mla_fetch_attachment_references( -1, 0 );
// See if anything else has changed
if ( 1 < count( $updates ) ) {
$result = wp_update_post( $updates );
} else {
$result = $post_id;
}
/*
* Allow Jordy Meow's Media File Renamer plugin to do its work
* https://wordpress.org/support/topic/media-file-rename-media-library-assistant/
*/
if ( class_exists( 'Meow_MFRH_Core' ) && isset( $updates['post_title'] ) ) {
global $mfrh_core;
$mfrh_core->rename( $post_id );
}
do_action( 'mla_updated_single_item', $post_id, $result );
if ( $result ) {
/* translators: 1: post ID */
$final_message = sprintf( __( 'Item %1$d updated.', 'media-library-assistant' ), $post_id );
/*
* Uncomment this for debugging.
*/
// $final_message .= '<br>' . $message;
//error_log( __LINE__ . ' MLAData::mla_update_single_item message = ' . var_export( $message, true ), 0 );
return array(
'message' => $final_message,
'body' => ''
);
} else {
return array(
/* translators: 1: ERROR tag 2: post ID */
'message' => sprintf( __( '%1$s: Item %2$d update failed.', 'media-library-assistant' ), __( 'ERROR', 'media-library-assistant' ), $post_id ),
'body' => ''
);
}
}
}
/**
* Remove terms from an attachment's assignments
*
* @since 0.40
*
* @param integer The ID of the attachment to be updated
* @param array The term ids (integer array) or names (string array) to remove
* @param object The taxonomy object
*
* @return array Term ids/names of the surviving terms
*/
private static function _remove_terms( $post_id, $terms, $taxonomy_obj ) {
$taxonomy = $taxonomy_obj->name;
$hierarchical = $taxonomy_obj->hierarchical;
/*
* Get the current terms for the terms_after check
*/
$current_terms = get_object_term_cache( $post_id, $taxonomy );
if ( false === $current_terms ) {
$current_terms = wp_get_object_terms( $post_id, $taxonomy );
wp_cache_add( $post_id, $current_terms, $taxonomy . '_relationships' );
}
$terms_before = array();
foreach( $current_terms as $term ) {
$terms_before[ $term->term_id ] = $term->name;
}
$terms_after = array();
if ( $hierarchical || MLACore::mla_taxonomy_support( $taxonomy, 'flat-checklist' )) {
$terms = array_map( 'intval', $terms );
$terms = array_unique( $terms );
foreach( $terms_before as $index => $term ) {
if ( ! in_array( $index, $terms ) ) {
$terms_after[] = $index;
}
}
} else {
// WordPress encodes special characters, e.g., "&" as HTML entities in term names
array_map( '_wp_specialchars', $terms );
foreach( $terms_before as $index => $term ) {
if ( ! in_array( $term, $terms ) ) {
$terms_after[] = $term;
}
}
}
return $terms_after;
}
/**
* Format printable version of binary data
*
* @since 0.90
*
* @param string Binary data
* @param integer Bytes to format, default = 0 (all bytes)
* @param intger Bytes to format on each line
* @param integer offset of initial byte, or -1 to suppress printing offset information
*
* @return string Printable representation of $data
*/
public static function mla_hex_dump( $data, $limit = 0, $bytes_per_row = 16, $offset = -1 ) {
if ( 0 == $limit ) {
$limit = strlen( $data );
}
$position = 0;
$output = "\r\n";
$print_offset = ( 0 <= $offset );
if ( $print_offset ) {
$print_length = $bytes_per_row + 5;
} else {
$print_length = $bytes_per_row;
}
while ( $position < $limit ) {
$row_length = strlen( substr( $data, $position ) );
if ( 0 == $row_length ) {
break;
}
if ( $row_length > ( $limit - $position ) ) {
$row_length = $limit - $position;
}
if ( $row_length > $bytes_per_row ) {
$row_length = $bytes_per_row;
}
$row_data = substr( $data, $position, $row_length );
if ( $print_offset ) {
$print_string = sprintf( '%04X ', $position + $offset );
} else {
$print_string = '';
}
$hex_string = '';
for ( $index = 0; $index < $row_length; $index++ ) {
$char = ord( substr( $row_data, $index, 1 ) );
if ( ( 31 < $char ) && ( 127 > $char ) ) {
$print_string .= chr($char);
} else {
$print_string .= '.';
}
$hex_string .= ' ' . bin2hex( chr($char) );
} // for
$output .= str_pad( $print_string, $print_length, ' ', STR_PAD_RIGHT ) . $hex_string . "\r\n";
$position += $row_length;
} // while
return $output;
}
} // class MLAData
?>