'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, '', $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, '' ) ) && ( $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 = "\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' ) . '
', $key );
} else {
/* translators: 1: ERROR tag 2: meta_key */
$message .= sprintf( __( '%1$s: meta:%2$s not found', 'media-library-assistant' ) . '
', __( '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' ) . '
', $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' ) . '
', __( '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' ) . '
', $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' ) . '
', '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' ) . '
', __( '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' ) . '
', $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' ) . '
', $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' ) . '
', $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' ) . '
', $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' ) . '
', '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' ) . '
', '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', 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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', __( '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' ) . '
', $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' ) . '
', $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 .= '
' . $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
?>