'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 ?>