<exclude-pattern>*/includes/specials/SpecialMostinterwikis\.php</exclude-pattern>
<exclude-pattern>*/includes/cache/CacheDependency\.php</exclude-pattern>
<exclude-pattern>*/includes/cache/CacheHelper\.php</exclude-pattern>
+ <exclude-pattern>*/includes/compat/XMPReader\.php</exclude-pattern>
<exclude-pattern>*/includes/diff/DairikiDiff\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/SpecialAncientpages\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/SpecialBrokenRedirects\.php</exclude-pattern>
<exclude-pattern>*/includes/AuthPlugin\.php</exclude-pattern>
<exclude-pattern>*/includes/cache/CacheDependency\.php</exclude-pattern>
<exclude-pattern>*/includes/cache/CacheHelper\.php</exclude-pattern>
+ <exclude-pattern>*/includes/compat/XMPReader\.php</exclude-pattern>
<exclude-pattern>*/includes/deferred/CdnCacheUpdate\.php</exclude-pattern>
<exclude-pattern>*/includes/diff/DairikiDiff\.php</exclude-pattern>
<exclude-pattern>*/includes/diff/DiffEngine\.php</exclude-pattern>
** ScopedCallback objects can no longer be serialized.
==== New external libraries ====
+* Added wikimedia/xmp-reader 0.5.1
* …
==== Removed and replaced external libraries ====
constant INTL_ICU_VERSION directly in all versions that MediaWiki supports.
* Parser::fetchFile() is deprecated. Use ::fetchFileAndTitle() instead.
* The ApiQueryContributions class has been renamed to ApiQueryUserContribs.
+* The XMPInfo, XMPReader, and XMPValidate classes have been deprecated in favor
+ of the namespaced classes provided by the wikimedia/xmp-reader library.
=== Other changes in 1.32 ===
* …
'WrapOldPasswords' => __DIR__ . '/maintenance/wrapOldPasswords.php',
'XCFHandler' => __DIR__ . '/includes/media/XCF.php',
'XMLRCFeedFormatter' => __DIR__ . '/includes/rcfeed/XMLRCFeedFormatter.php',
- 'XMPInfo' => __DIR__ . '/includes/libs/xmp/XMPInfo.php',
- 'XMPReader' => __DIR__ . '/includes/libs/xmp/XMP.php',
- 'XMPValidate' => __DIR__ . '/includes/libs/xmp/XMPValidate.php',
+ 'XMPInfo' => __DIR__ . '/includes/compat/XMPReader.php',
+ 'XMPReader' => __DIR__ . '/includes/compat/XMPReader.php',
+ 'XMPValidate' => __DIR__ . '/includes/compat/XMPReader.php',
'Xhprof' => __DIR__ . '/includes/libs/Xhprof.php',
'XhprofData' => __DIR__ . '/includes/libs/XhprofData.php',
'Xml' => __DIR__ . '/includes/Xml.php',
"wikimedia/timestamp": "1.0.0",
"wikimedia/wait-condition-loop": "1.0.1",
"wikimedia/wrappedstring": "3.0.0",
+ "wikimedia/xmp-reader": "0.5.1",
"zordius/lightncandy": "0.23"
},
"require-dev": {
--- /dev/null
+<?php
+/**
+ * Back-compat for pre-librarized XMP classes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+use Wikimedia\XMPReader\Info;
+use Wikimedia\XMPReader\Reader;
+use Wikimedia\XMPReader\Validate;
+
+/**
+ * @deprecated since 1.32
+ */
+class XMPInfo extends Info {
+}
+
+/**
+ * @deprecated since 1.32
+ */
+class XMPReader extends Reader {
+}
+
+/**
+ * @deprecated since 1.32
+ */
+class XMPValidate extends Validate {
+}
+++ /dev/null
-<?php
-/**
- * Reader for XMP data containing properties relevant to images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use Wikimedia\ScopedCallback;
-
-/**
- * Class for reading xmp data containing properties relevant to
- * images, and spitting out an array that FormatMetadata accepts.
- *
- * Note, this is not meant to recognize every possible thing you can
- * encode in XMP. It should recognize all the properties we want.
- * For example it doesn't have support for structures with multiple
- * nesting levels, as none of the properties we're supporting use that
- * feature. If it comes across properties it doesn't recognize, it should
- * ignore them.
- *
- * The public methods one would call in this class are
- * - parse( $content )
- * Reads in xmp content.
- * Can potentially be called multiple times with partial data each time.
- * - parseExtended( $content )
- * Reads XMPExtended blocks (jpeg files only).
- * - getResults
- * Outputs a results array.
- *
- * Note XMP kind of looks like rdf. They are not the same thing - XMP is
- * encoded as a specific subset of rdf. This class can read XMP. It cannot
- * read rdf.
- */
-class XMPReader implements LoggerAwareInterface {
- /** @var array XMP item configuration array */
- protected $items;
-
- /** @var array Array to hold the current element (and previous element, and so on) */
- private $curItem = [];
-
- /** @var bool|string The structure name when processing nested structures. */
- private $ancestorStruct = false;
-
- /** @var bool|string Temporary holder for character data that appears in xmp doc. */
- private $charContent = false;
-
- /** @var array Stores the state the xmpreader is in (see MODE_FOO constants) */
- private $mode = [];
-
- /** @var array Array to hold results */
- private $results = [];
-
- /** @var bool If we're doing a seq or bag. */
- private $processingArray = false;
-
- /** @var bool|string Used for lang alts only */
- private $itemLang = false;
-
- /** @var resource A resource handle for the XML parser */
- private $xmlParser;
-
- /** @var bool|string Character set like 'UTF-8' */
- private $charset = false;
-
- /** @var int */
- private $extendedXMPOffset = 0;
-
- /** @var int Flag determining if the XMP is safe to parse **/
- private $parsable = 0;
-
- /** @var string Buffer of XML to parse **/
- private $xmlParsableBuffer = '';
-
- /**
- * These are various mode constants.
- * they are used to figure out what to do
- * with an element when its encountered.
- *
- * For example, MODE_IGNORE is used when processing
- * a property we're not interested in. So if a new
- * element pops up when we're in that mode, we ignore it.
- */
- const MODE_INITIAL = 0;
- const MODE_IGNORE = 1;
- const MODE_LI = 2;
- const MODE_LI_LANG = 3;
- const MODE_QDESC = 4;
-
- // The following MODE constants are also used in the
- // $items array to denote what type of property the item is.
- const MODE_SIMPLE = 10;
- const MODE_STRUCT = 11; // structure (associative array)
- const MODE_SEQ = 12; // ordered list
- const MODE_BAG = 13; // unordered list
- const MODE_LANG = 14;
- const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
- const MODE_BAGSTRUCT = 16; // A BAG of Structs.
-
- const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
- const NS_XML = 'http://www.w3.org/XML/1998/namespace';
-
- // States used while determining if XML is safe to parse
- const PARSABLE_UNKNOWN = 0;
- const PARSABLE_OK = 1;
- const PARSABLE_BUFFERING = 2;
- const PARSABLE_NO = 3;
-
- /**
- * @var LoggerInterface
- */
- private $logger;
-
- /**
- * @var string
- */
- private $filename;
-
- /**
- * Primary job is to initialize the XMLParser
- * @param LoggerInterface|null $logger
- * @param string $filename
- */
- function __construct( LoggerInterface $logger = null, $filename = 'unknown' ) {
- if ( !function_exists( 'xml_parser_create_ns' ) ) {
- // this should already be checked by this point
- throw new RuntimeException( 'XMP support requires XML Parser' );
- }
- if ( $logger ) {
- $this->setLogger( $logger );
- } else {
- $this->setLogger( new NullLogger() );
- }
- $this->filename = $filename;
-
- $this->items = XMPInfo::getItems();
-
- $this->resetXMLParser();
- }
-
- public function setLogger( LoggerInterface $logger ) {
- $this->logger = $logger;
- }
-
- /**
- * free the XML parser.
- *
- * @note It is unclear to me if we really need to do this ourselves
- * or if php garbage collection will automatically free the xmlParser
- * when it is no longer needed.
- */
- private function destroyXMLParser() {
- if ( $this->xmlParser ) {
- xml_parser_free( $this->xmlParser );
- $this->xmlParser = null;
- }
- }
-
- /**
- * Main use is if a single item has multiple xmp documents describing it.
- * For example in jpeg's with extendedXMP
- */
- private function resetXMLParser() {
- $this->destroyXMLParser();
-
- $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' );
- xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 );
- xml_parser_set_option( $this->xmlParser, XML_OPTION_SKIP_WHITE, 1 );
-
- xml_set_element_handler( $this->xmlParser,
- [ $this, 'startElement' ],
- [ $this, 'endElement' ] );
-
- xml_set_character_data_handler( $this->xmlParser, [ $this, 'char' ] );
-
- $this->parsable = self::PARSABLE_UNKNOWN;
- $this->xmlParsableBuffer = '';
- }
-
- /**
- * Check if this instance supports using this class
- * @return bool
- */
- public static function isSupported() {
- return function_exists( 'xml_parser_create_ns' ) && class_exists( 'XMLReader' );
- }
-
- /** Get the result array. Do some post-processing before returning
- * the array, and transform any metadata that is special-cased.
- *
- * @return array Array of results as an array of arrays suitable for
- * FormatMetadata::getFormattedData().
- */
- public function getResults() {
- // xmp-special is for metadata that affects how stuff
- // is extracted. For example xmpNote:HasExtendedXMP.
-
- // It is also used to handle photoshop:AuthorsPosition
- // which is weird and really part of another property,
- // see 2:85 in IPTC. See also pg 21 of IPTC4XMP standard.
- // The location fields also use it.
-
- $data = $this->results;
-
- if ( isset( $data['xmp-special']['AuthorsPosition'] )
- && is_string( $data['xmp-special']['AuthorsPosition'] )
- && isset( $data['xmp-general']['Artist'][0] )
- ) {
- // Note, if there is more than one creator,
- // this only applies to first. This also will
- // only apply to the dc:Creator prop, not the
- // exif:Artist prop.
-
- $data['xmp-general']['Artist'][0] =
- $data['xmp-special']['AuthorsPosition'] . ', '
- . $data['xmp-general']['Artist'][0];
- }
-
- // Go through the LocationShown and LocationCreated
- // changing it to the non-hierarchal form used by
- // the other location fields.
-
- if ( isset( $data['xmp-special']['LocationShown'][0] )
- && is_array( $data['xmp-special']['LocationShown'][0] )
- ) {
- // the is_array is just paranoia. It should always
- // be an array.
- foreach ( $data['xmp-special']['LocationShown'] as $loc ) {
- if ( !is_array( $loc ) ) {
- // To avoid copying over the _type meta-fields.
- continue;
- }
- foreach ( $loc as $field => $val ) {
- $data['xmp-general'][$field . 'Dest'][] = $val;
- }
- }
- }
- if ( isset( $data['xmp-special']['LocationCreated'][0] )
- && is_array( $data['xmp-special']['LocationCreated'][0] )
- ) {
- // the is_array is just paranoia. It should always
- // be an array.
- foreach ( $data['xmp-special']['LocationCreated'] as $loc ) {
- if ( !is_array( $loc ) ) {
- // To avoid copying over the _type meta-fields.
- continue;
- }
- foreach ( $loc as $field => $val ) {
- $data['xmp-general'][$field . 'Created'][] = $val;
- }
- }
- }
-
- // We don't want to return the special values, since they're
- // special and not info to be stored about the file.
- unset( $data['xmp-special'] );
-
- // Convert GPSAltitude to negative if below sea level.
- if ( isset( $data['xmp-exif']['GPSAltitudeRef'] )
- && isset( $data['xmp-exif']['GPSAltitude'] )
- ) {
- // Must convert to a real before multiplying by -1
- // XMPValidate guarantees there will always be a '/' in this value.
- list( $nom, $denom ) = explode( '/', $data['xmp-exif']['GPSAltitude'] );
- $data['xmp-exif']['GPSAltitude'] = $nom / $denom;
-
- if ( $data['xmp-exif']['GPSAltitudeRef'] == '1' ) {
- $data['xmp-exif']['GPSAltitude'] *= -1;
- }
- unset( $data['xmp-exif']['GPSAltitudeRef'] );
- }
-
- return $data;
- }
-
- /**
- * Main function to call to parse XMP. Use getResults to
- * get results.
- *
- * Also catches any errors during processing, writes them to
- * debug log, blanks result array and returns false.
- *
- * @param string $content XMP data
- * @param bool $allOfIt If this is all the data (true) or if its split up (false). Default true
- * @throws RuntimeException
- * @return bool Success.
- */
- public function parse( $content, $allOfIt = true ) {
- if ( !$this->xmlParser ) {
- $this->resetXMLParser();
- }
- try {
-
- // detect encoding by looking for BOM which is supposed to be in processing instruction.
- // see page 12 of http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
- if ( !$this->charset ) {
- $bom = [];
- if ( preg_match( '/\xEF\xBB\xBF|\xFE\xFF|\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFF\xFE/',
- $content, $bom )
- ) {
- switch ( $bom[0] ) {
- case "\xFE\xFF":
- $this->charset = 'UTF-16BE';
- break;
- case "\xFF\xFE":
- $this->charset = 'UTF-16LE';
- break;
- case "\x00\x00\xFE\xFF":
- $this->charset = 'UTF-32BE';
- break;
- case "\xFF\xFE\x00\x00":
- $this->charset = 'UTF-32LE';
- break;
- case "\xEF\xBB\xBF":
- $this->charset = 'UTF-8';
- break;
- default:
- // this should be impossible to get to
- throw new RuntimeException( "Invalid BOM" );
- }
- } else {
- // standard specifically says, if no bom assume utf-8
- $this->charset = 'UTF-8';
- }
- }
- if ( $this->charset !== 'UTF-8' ) {
- // don't convert if already utf-8
- Wikimedia\suppressWarnings();
- $content = iconv( $this->charset, 'UTF-8//IGNORE', $content );
- Wikimedia\restoreWarnings();
- }
-
- // Ensure the XMP block does not have an xml doctype declaration, which
- // could declare entities unsafe to parse with xml_parse (T85848/T71210).
- if ( $this->parsable !== self::PARSABLE_OK ) {
- if ( $this->parsable === self::PARSABLE_NO ) {
- throw new RuntimeException( 'Unsafe doctype declaration in XML.' );
- }
-
- $content = $this->xmlParsableBuffer . $content;
- if ( !$this->checkParseSafety( $content ) ) {
- if ( !$allOfIt && $this->parsable !== self::PARSABLE_NO ) {
- // parse wasn't Unsuccessful yet, so return true
- // in this case.
- return true;
- }
- $msg = ( $this->parsable === self::PARSABLE_NO ) ?
- 'Unsafe doctype declaration in XML.' :
- 'No root element found in XML.';
- throw new RuntimeException( $msg );
- }
- }
-
- $ok = xml_parse( $this->xmlParser, $content, $allOfIt );
- if ( !$ok ) {
- $code = xml_get_error_code( $this->xmlParser );
- $error = xml_error_string( $code );
- $line = xml_get_current_line_number( $this->xmlParser );
- $col = xml_get_current_column_number( $this->xmlParser );
- $offset = xml_get_current_byte_index( $this->xmlParser );
-
- $this->logger->info(
- '{method} : Error reading XMP content: {error} ' .
- '(file: {file}, line: {line} column: {column} ' .
- 'byte offset: {offset})',
- [
- 'method' => __METHOD__,
- 'error_code' => $code,
- 'error' => $error,
- 'file' => $this->filename,
- 'line' => $line,
- 'column' => $col,
- 'offset' => $offset,
- 'content' => $content,
- ] );
- $this->results = []; // blank if error.
- $this->destroyXMLParser();
- return false;
- }
- } catch ( Exception $e ) {
- $this->logger->warning(
- '{method} {exception}',
- [
- 'method' => __METHOD__,
- 'exception' => $e,
- 'file' => $this->filename,
- 'content' => $content,
- ]
- );
- $this->results = [];
- return false;
- }
- if ( $allOfIt ) {
- $this->destroyXMLParser();
- }
-
- return true;
- }
-
- /** Entry point for XMPExtended blocks in jpeg files
- *
- * @todo In serious need of testing
- * @see http://www.adobe.ge/devnet/xmp/pdfs/XMPSpecificationPart3.pdf XMP spec part 3 page 20
- * @param string $content XMPExtended block minus the namespace signature
- * @return bool If it succeeded.
- */
- public function parseExtended( $content ) {
- // @todo FIXME: This is untested. Hard to find example files
- // or programs that make such files..
- $guid = substr( $content, 0, 32 );
- if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
- || $this->results['xmp-special']['HasExtendedXMP'] !== $guid
- ) {
- $this->logger->info( __METHOD__ .
- " Ignoring XMPExtended block due to wrong guid (guid= '{guid}')",
- [
- 'guid' => $guid,
- 'file' => $this->filename,
- ]
- );
-
- return false;
- }
- $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
-
- if ( !$len ||
- $len['length'] < 4 ||
- $len['offset'] < 0 ||
- $len['offset'] > $len['length']
- ) {
- $this->logger->info(
- __METHOD__ . 'Error reading extended XMP block, invalid length or offset.',
- [ 'file' => $this->filename ]
- );
-
- return false;
- }
-
- // we're not very robust here. we should accept it in the wrong order.
- // To quote the XMP standard:
- // "A JPEG writer should write the ExtendedXMP marker segments in order,
- // immediately following the StandardXMP. However, the JPEG standard
- // does not require preservation of marker segment order. A robust JPEG
- // reader should tolerate the marker segments in any order."
- // On the other hand, the probability that an image will have more than
- // 128k of metadata is rather low... so the probability that it will have
- // > 128k, and be in the wrong order is very low...
-
- if ( $len['offset'] !== $this->extendedXMPOffset ) {
- $this->logger->info( __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
- . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')',
- [ 'file' => $this->filename ]
- );
-
- return false;
- }
-
- if ( $len['offset'] === 0 ) {
- // if we're starting the extended block, we've probably already
- // done the XMPStandard block, so reset.
- $this->resetXMLParser();
- }
-
- $this->extendedXMPOffset += $len['length'];
-
- $actualContent = substr( $content, 40 );
-
- if ( $this->extendedXMPOffset === strlen( $actualContent ) ) {
- $atEnd = true;
- } else {
- $atEnd = false;
- }
-
- $this->logger->debug(
- __METHOD__ . 'Parsing a XMPExtended block',
- [ 'file' => $this->filename ]
- );
-
- return $this->parse( $actualContent, $atEnd );
- }
-
- /**
- * Character data handler
- * Called whenever character data is found in the xmp document.
- *
- * does nothing if we're in MODE_IGNORE or if the data is whitespace
- * throws an error if we're not in MODE_SIMPLE (as we're not allowed to have character
- * data in the other modes).
- *
- * As an example, this happens when we encounter XMP like:
- * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
- * and are processing the 0/10 bit.
- *
- * @param resource $parser XMLParser reference to the xml parser
- * @param string $data Character data
- * @throws RuntimeException On invalid data
- */
- function char( $parser, $data ) {
- $data = trim( $data );
- if ( trim( $data ) === "" ) {
- return;
- }
-
- if ( !isset( $this->mode[0] ) ) {
- throw new RuntimeException( 'Unexpected character data before first rdf:Description element' );
- }
-
- if ( $this->mode[0] === self::MODE_IGNORE ) {
- return;
- }
-
- if ( $this->mode[0] !== self::MODE_SIMPLE
- && $this->mode[0] !== self::MODE_QDESC
- ) {
- throw new RuntimeException( 'character data where not expected. (mode ' . $this->mode[0] . ')' );
- }
-
- // to check, how does this handle w.s.
- if ( $this->charContent === false ) {
- $this->charContent = $data;
- } else {
- $this->charContent .= $data;
- }
- }
-
- /**
- * Check if a block of XML is safe to pass to xml_parse, i.e. doesn't
- * contain a doctype declaration which could contain a dos attack if we
- * parse it and expand internal entities (T85848).
- *
- * @param string $content xml string to check for parse safety
- * @return bool true if the xml is safe to parse, false otherwise
- */
- private function checkParseSafety( $content ) {
- $reader = new XMLReader();
- $result = null;
-
- // For XMLReader to parse incomplete/invalid XML, it has to be open()'ed
- // instead of using XML().
- $reader->open(
- 'data://text/plain,' . urlencode( $content ),
- null,
- LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET
- );
-
- $oldDisable = libxml_disable_entity_loader( true );
- /** @noinspection PhpUnusedLocalVariableInspection */
- $reset = new ScopedCallback(
- 'libxml_disable_entity_loader',
- [ $oldDisable ]
- );
- $reader->setParserProperty( XMLReader::SUBST_ENTITIES, false );
-
- // Even with LIBXML_NOWARNING set, XMLReader::read gives a warning
- // when parsing truncated XML, which causes unit tests to fail.
- Wikimedia\suppressWarnings();
- while ( $reader->read() ) {
- if ( $reader->nodeType === XMLReader::ELEMENT ) {
- // Reached the first element without hitting a doctype declaration
- $this->parsable = self::PARSABLE_OK;
- $result = true;
- break;
- }
- if ( $reader->nodeType === XMLReader::DOC_TYPE ) {
- $this->parsable = self::PARSABLE_NO;
- $result = false;
- break;
- }
- }
- Wikimedia\restoreWarnings();
-
- if ( !is_null( $result ) ) {
- return $result;
- }
-
- // Reached the end of the parsable xml without finding an element
- // or doctype. Buffer and try again.
- $this->parsable = self::PARSABLE_BUFFERING;
- $this->xmlParsableBuffer = $content;
- return false;
- }
-
- /** When we hit a closing element in MODE_IGNORE
- * Check to see if this is the element we started to ignore,
- * in which case we get out of MODE_IGNORE
- *
- * @param string $elm Namespace of element followed by a space and then tag name of element.
- */
- private function endElementModeIgnore( $elm ) {
- if ( $this->curItem[0] === $elm ) {
- array_shift( $this->curItem );
- array_shift( $this->mode );
- }
- }
-
- /**
- * Hit a closing element when in MODE_SIMPLE.
- * This generally means that we finished processing a
- * property value, and now have to save the result to the
- * results array
- *
- * For example, when processing:
- * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
- * this deals with when we hit </exif:DigitalZoomRatio>.
- *
- * Or it could be if we hit the end element of a property
- * of a compound data structure (like a member of an array).
- *
- * @param string $elm Namespace, space, and tag name.
- */
- private function endElementModeSimple( $elm ) {
- if ( $this->charContent !== false ) {
- if ( $this->processingArray ) {
- // if we're processing an array, use the original element
- // name instead of rdf:li.
- list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
- } else {
- list( $ns, $tag ) = explode( ' ', $elm, 2 );
- }
- $this->saveValue( $ns, $tag, $this->charContent );
-
- $this->charContent = false; // reset
- }
- array_shift( $this->curItem );
- array_shift( $this->mode );
- }
-
- /**
- * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
- * generally means we've finished processing a nested structure.
- * resets some internal variables to indicate that.
- *
- * Note this means we hit the closing element not the "</rdf:Seq>".
- *
- * @par For example, when processing:
- * @code{,xml}
- * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
- * </rdf:Seq> </exif:ISOSpeedRatings>
- * @endcode
- *
- * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
- *
- * @param string $elm Namespace . space . tag name.
- * @throws RuntimeException
- */
- private function endElementNested( $elm ) {
- /* cur item must be the same as $elm, unless if in MODE_STRUCT
- * in which case it could also be rdf:Description */
- if ( $this->curItem[0] !== $elm
- && !( $elm === self::NS_RDF . ' Description'
- && $this->mode[0] === self::MODE_STRUCT )
- ) {
- throw new RuntimeException( "nesting mismatch. got a </$elm> but expected a </" .
- $this->curItem[0] . '>' );
- }
-
- // Validate structures.
- list( $ns, $tag ) = explode( ' ', $elm, 2 );
- if ( isset( $this->items[$ns][$tag]['validate'] ) ) {
- $info =& $this->items[$ns][$tag];
- $finalName = $info['map_name'] ?? $tag;
-
- if ( is_array( $info['validate'] ) ) {
- $validate = $info['validate'];
- } else {
- $validator = new XMPValidate( $this->logger );
- $validate = [ $validator, $info['validate'] ];
- }
-
- if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
- // This can happen if all the members of the struct failed validation.
- $this->logger->debug(
- __METHOD__ . " <$ns:$tag> has no valid members.",
- [ 'file' => $this->filename ]
- );
- } elseif ( is_callable( $validate ) ) {
- $val =& $this->results['xmp-' . $info['map_group']][$finalName];
- call_user_func_array( $validate, [ $info, &$val, false ] );
- if ( is_null( $val ) ) {
- // the idea being the validation function will unset the variable if
- // its invalid.
- $this->logger->info(
- __METHOD__ . " <$ns:$tag> failed validation.",
- [ 'file' => $this->filename ]
- );
- unset( $this->results['xmp-' . $info['map_group']][$finalName] );
- }
- } else {
- $this->logger->warning(
- __METHOD__ . " Validation function for $finalName (" .
- $validate[0] . '::' . $validate[1] . '()) is not callable.',
- [ 'file' => $this->filename ]
- );
- }
- }
-
- array_shift( $this->curItem );
- array_shift( $this->mode );
- $this->ancestorStruct = false;
- $this->processingArray = false;
- $this->itemLang = false;
- }
-
- /**
- * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
- * Add information about what type of element this is.
- *
- * Note we still have to hit the outer "</property>"
- *
- * @par For example, when processing:
- * @code{,xml}
- * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
- * </rdf:Seq> </exif:ISOSpeedRatings>
- * @endcode
- *
- * This method is called when we hit the "</rdf:Seq>".
- * (For comparison, we call endElementModeSimple when we
- * hit the "</rdf:li>")
- *
- * @param string $elm Namespace . ' ' . element name
- * @throws RuntimeException
- */
- private function endElementModeLi( $elm ) {
- list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
- $info = $this->items[$ns][$tag];
- $finalName = $info['map_name'] ?? $tag;
-
- array_shift( $this->mode );
-
- if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
- $this->logger->debug(
- __METHOD__ . " Empty compund element $finalName.",
- [ 'file' => $this->filename ]
- );
-
- return;
- }
-
- if ( $elm === self::NS_RDF . ' Seq' ) {
- $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ol';
- } elseif ( $elm === self::NS_RDF . ' Bag' ) {
- $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ul';
- } elseif ( $elm === self::NS_RDF . ' Alt' ) {
- // extra if needed as you could theoretically have a non-language alt.
- if ( $info['mode'] === self::MODE_LANG ) {
- $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
- }
- } else {
- throw new RuntimeException(
- __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm."
- );
- }
- }
-
- /**
- * End element while in MODE_QDESC
- * mostly when ending an element when we have a simple value
- * that has qualifiers.
- *
- * Qualifiers aren't all that common, and we don't do anything
- * with them.
- *
- * @param string $elm Namespace and element
- */
- private function endElementModeQDesc( $elm ) {
- if ( $elm === self::NS_RDF . ' value' ) {
- list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
- $this->saveValue( $ns, $tag, $this->charContent );
-
- return;
- } else {
- array_shift( $this->mode );
- array_shift( $this->curItem );
- }
- }
-
- /**
- * Handler for hitting a closing element.
- *
- * generally just calls a helper function depending on what
- * mode we're in.
- *
- * Ignores the outer wrapping elements that are optional in
- * xmp and have no meaning.
- *
- * @param resource $parser
- * @param string $elm Namespace . ' ' . element name
- * @throws RuntimeException
- */
- function endElement( $parser, $elm ) {
- if ( $elm === ( self::NS_RDF . ' RDF' )
- || $elm === 'adobe:ns:meta/ xmpmeta'
- || $elm === 'adobe:ns:meta/ xapmeta'
- ) {
- // ignore these.
- return;
- }
-
- if ( $elm === self::NS_RDF . ' type' ) {
- // these aren't really supported properly yet.
- // However, it appears they almost never used.
- $this->logger->info(
- __METHOD__ . ' encountered <rdf:type>',
- [ 'file' => $this->filename ]
- );
- }
-
- if ( strpos( $elm, ' ' ) === false ) {
- // This probably shouldn't happen.
- // However, there is a bug in an adobe product
- // that forgets the namespace on some things.
- // (Luckily they are unimportant things).
- $this->logger->info(
- __METHOD__ . " Encountered </$elm> which has no namespace. Skipping.",
- [ 'file' => $this->filename ]
- );
-
- return;
- }
-
- if ( count( $this->mode ) === 0 ) {
- // This should never ever happen and means
- // there is a pretty major bug in this class.
- throw new RuntimeException( 'Encountered end element with no mode' );
- }
-
- if ( count( $this->curItem ) == 0 && $this->mode[0] !== self::MODE_INITIAL ) {
- // just to be paranoid. Should always have a curItem, except for initially
- // (aka during MODE_INITAL).
- throw new RuntimeException( "Hit end element </$elm> but no curItem" );
- }
-
- switch ( $this->mode[0] ) {
- case self::MODE_IGNORE:
- $this->endElementModeIgnore( $elm );
- break;
- case self::MODE_SIMPLE:
- $this->endElementModeSimple( $elm );
- break;
- case self::MODE_STRUCT:
- case self::MODE_SEQ:
- case self::MODE_BAG:
- case self::MODE_LANG:
- case self::MODE_BAGSTRUCT:
- $this->endElementNested( $elm );
- break;
- case self::MODE_INITIAL:
- if ( $elm === self::NS_RDF . ' Description' ) {
- array_shift( $this->mode );
- } else {
- throw new RuntimeException( 'Element ended unexpectedly while in MODE_INITIAL' );
- }
- break;
- case self::MODE_LI:
- case self::MODE_LI_LANG:
- $this->endElementModeLi( $elm );
- break;
- case self::MODE_QDESC:
- $this->endElementModeQDesc( $elm );
- break;
- default:
- $this->logger->info(
- __METHOD__ . " no mode (elm = $elm)",
- [ 'file' => $this->filename ]
- );
- break;
- }
- }
-
- /**
- * Hit an opening element while in MODE_IGNORE
- *
- * XMP is extensible, so ignore any tag we don't understand.
- *
- * Mostly ignores, unless we encounter the element that we are ignoring.
- * in which case we add it to the item stack, so we can ignore things
- * that are nested, correctly.
- *
- * @param string $elm Namespace . ' ' . tag name
- */
- private function startElementModeIgnore( $elm ) {
- if ( $elm === $this->curItem[0] ) {
- array_unshift( $this->curItem, $elm );
- array_unshift( $this->mode, self::MODE_IGNORE );
- }
- }
-
- /**
- * Start element in MODE_BAG (unordered array)
- * this should always be <rdf:Bag>
- *
- * @param string $elm Namespace . ' ' . tag
- * @throws RuntimeException If we have an element that's not <rdf:Bag>
- */
- private function startElementModeBag( $elm ) {
- if ( $elm === self::NS_RDF . ' Bag' ) {
- array_unshift( $this->mode, self::MODE_LI );
- } else {
- throw new RuntimeException( "Expected <rdf:Bag> but got $elm." );
- }
- }
-
- /**
- * Start element in MODE_SEQ (ordered array)
- * this should always be <rdf:Seq>
- *
- * @param string $elm Namespace . ' ' . tag
- * @throws RuntimeException If we have an element that's not <rdf:Seq>
- */
- private function startElementModeSeq( $elm ) {
- if ( $elm === self::NS_RDF . ' Seq' ) {
- array_unshift( $this->mode, self::MODE_LI );
- } elseif ( $elm === self::NS_RDF . ' Bag' ) {
- # T29105
- $this->logger->info(
- __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending' .
- ' it is a Seq, since some buggy software is known to screw this up.',
- [ 'file' => $this->filename ]
- );
- array_unshift( $this->mode, self::MODE_LI );
- } else {
- throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
- }
- }
-
- /**
- * Start element in MODE_LANG (language alternative)
- * this should always be <rdf:Alt>
- *
- * This tag tends to be used for metadata like describe this
- * picture, which can be translated into multiple languages.
- *
- * XMP supports non-linguistic alternative selections,
- * which are really only used for thumbnails, which
- * we don't care about.
- *
- * @param string $elm Namespace . ' ' . tag
- * @throws RuntimeException If we have an element that's not <rdf:Alt>
- */
- private function startElementModeLang( $elm ) {
- if ( $elm === self::NS_RDF . ' Alt' ) {
- array_unshift( $this->mode, self::MODE_LI_LANG );
- } else {
- throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
- }
- }
-
- /**
- * Handle an opening element when in MODE_SIMPLE
- *
- * This should not happen often. This is for if a simple element
- * already opened has a child element. Could happen for a
- * qualified element.
- *
- * For example:
- * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
- * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
- * </exif:DigitalZoomRatio>
- *
- * This method is called when processing the <rdf:Description> element
- *
- * @param string $elm Namespace and tag names separated by space.
- * @param array $attribs Attributes of the element.
- * @throws RuntimeException
- */
- private function startElementModeSimple( $elm, $attribs ) {
- if ( $elm === self::NS_RDF . ' Description' ) {
- // If this value has qualifiers
- array_unshift( $this->mode, self::MODE_QDESC );
- array_unshift( $this->curItem, $this->curItem[0] );
-
- if ( isset( $attribs[self::NS_RDF . ' value'] ) ) {
- list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
- $this->saveValue( $ns, $tag, $attribs[self::NS_RDF . ' value'] );
- }
- } elseif ( $elm === self::NS_RDF . ' value' ) {
- // This should not be here.
- throw new RuntimeException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
- } else {
- // something else we don't recognize, like a qualifier maybe.
- $this->logger->info( __METHOD__ .
- " Encountered element <{element}> where only expecting character data as value of {curitem}",
- [
- 'element' => $elm,
- 'curitem' => $this->curItem[0],
- 'file' => $this->filename,
- ]
- );
- array_unshift( $this->mode, self::MODE_IGNORE );
- array_unshift( $this->curItem, $elm );
- }
- }
-
- /**
- * Start an element when in MODE_QDESC.
- * This generally happens when a simple element has an inner
- * rdf:Description to hold qualifier elements.
- *
- * For example in:
- * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
- * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
- * </exif:DigitalZoomRatio>
- * Called when processing the <rdf:value> or <foo:someQualifier>.
- *
- * @param string $elm Namespace and tag name separated by a space.
- */
- private function startElementModeQDesc( $elm ) {
- if ( $elm === self::NS_RDF . ' value' ) {
- return; // do nothing
- } else {
- // otherwise its a qualifier, which we ignore
- array_unshift( $this->mode, self::MODE_IGNORE );
- array_unshift( $this->curItem, $elm );
- }
- }
-
- /**
- * Starting an element when in MODE_INITIAL
- * This usually happens when we hit an element inside
- * the outer rdf:Description
- *
- * This is generally where most properties start.
- *
- * @param string $ns Namespace
- * @param string $tag Tag name (without namespace prefix)
- * @param array $attribs Array of attributes
- * @throws RuntimeException
- */
- private function startElementModeInitial( $ns, $tag, $attribs ) {
- if ( $ns !== self::NS_RDF ) {
- if ( isset( $this->items[$ns][$tag] ) ) {
- if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
- // If this element is supposed to appear only as
- // a child of a structure, but appears here (not as
- // a child of a struct), then something weird is
- // happening, so ignore this element and its children.
-
- $this->logger->info(
- 'Encountered <{element}> outside of its expected parent. Ignoring.',
- [ 'element' => "$ns:$tag", 'file' => $this->filename ]
- );
-
- array_unshift( $this->mode, self::MODE_IGNORE );
- array_unshift( $this->curItem, $ns . ' ' . $tag );
-
- return;
- }
- $mode = $this->items[$ns][$tag]['mode'];
- array_unshift( $this->mode, $mode );
- array_unshift( $this->curItem, $ns . ' ' . $tag );
- if ( $mode === self::MODE_STRUCT ) {
- $this->ancestorStruct = $this->items[$ns][$tag]['map_name'] ?? $tag;
- }
- if ( $this->charContent !== false ) {
- // Something weird.
- // Should not happen in valid XMP.
- throw new RuntimeException( 'tag nested in non-whitespace characters.' );
- }
- } else {
- // This element is not on our list of allowed elements so ignore.
- $this->logger->debug( __METHOD__ . ' Ignoring unrecognized element <{element}>.',
- [ 'element' => "$ns:$tag", 'file' => $this->filename ] );
- array_unshift( $this->mode, self::MODE_IGNORE );
- array_unshift( $this->curItem, $ns . ' ' . $tag );
-
- return;
- }
- }
- // process attributes
- $this->doAttribs( $attribs );
- }
-
- /**
- * Hit an opening element when in a Struct (MODE_STRUCT)
- * This is generally for fields of a compound property.
- *
- * Example of a struct (abbreviated; flash has more properties):
- *
- * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
- * <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
- *
- * or:
- *
- * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
- * <exif:Mode>1</exif:Mode></exif:Flash>
- *
- * @param string $ns Namespace
- * @param string $tag Tag name (no ns)
- * @param array $attribs Array of attribs w/ values.
- * @throws RuntimeException
- */
- private function startElementModeStruct( $ns, $tag, $attribs ) {
- if ( $ns !== self::NS_RDF ) {
- if ( isset( $this->items[$ns][$tag] ) ) {
- if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
- && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
- ) {
- // This assumes that we don't have inter-namespace nesting
- // which we don't in all the properties we're interested in.
- throw new RuntimeException( " <$tag> appeared nested in <" . $this->ancestorStruct
- . "> where it is not allowed." );
- }
- array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
- array_unshift( $this->curItem, $ns . ' ' . $tag );
- if ( $this->charContent !== false ) {
- // Something weird.
- // Should not happen in valid XMP.
- throw new RuntimeException( "tag <$tag> nested in non-whitespace characters (" .
- $this->charContent . ")." );
- }
- } else {
- array_unshift( $this->mode, self::MODE_IGNORE );
- array_unshift( $this->curItem, $ns . ' ' . $tag );
-
- return;
- }
- }
-
- if ( $ns === self::NS_RDF && $tag === 'Description' ) {
- $this->doAttribs( $attribs );
- array_unshift( $this->mode, self::MODE_STRUCT );
- array_unshift( $this->curItem, $this->curItem[0] );
- }
- }
-
- /**
- * opening element in MODE_LI
- * process elements of arrays.
- *
- * Example:
- * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
- * </rdf:Seq> </exif:ISOSpeedRatings>
- * This method is called when we hit the <rdf:li> element.
- *
- * @param string $elm Namespace . ' ' . tagname
- * @param array $attribs Attributes. (needed for BAGSTRUCTS)
- * @throws RuntimeException If gets a tag other than <rdf:li>
- */
- private function startElementModeLi( $elm, $attribs ) {
- if ( ( $elm ) !== self::NS_RDF . ' li' ) {
- throw new RuntimeException( "<rdf:li> expected but got $elm." );
- }
-
- if ( !isset( $this->mode[1] ) ) {
- // This should never ever ever happen. Checking for it
- // to be paranoid.
- throw new RuntimeException( 'In mode Li, but no 2xPrevious mode!' );
- }
-
- if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
- // This list item contains a compound (STRUCT) value.
- array_unshift( $this->mode, self::MODE_STRUCT );
- array_unshift( $this->curItem, $elm );
- $this->processingArray = true;
-
- if ( !isset( $this->curItem[1] ) ) {
- // be paranoid.
- throw new RuntimeException( 'Can not find parent of BAGSTRUCT.' );
- }
- list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
- $this->ancestorStruct = $this->items[$curNS][$curTag]['map_name'] ?? $curTag;
-
- $this->doAttribs( $attribs );
- } else {
- // Normal BAG or SEQ containing simple values.
- array_unshift( $this->mode, self::MODE_SIMPLE );
- // need to add curItem[0] on again since one is for the specific item
- // and one is for the entire group.
- array_unshift( $this->curItem, $this->curItem[0] );
- $this->processingArray = true;
- }
- }
-
- /**
- * Opening element in MODE_LI_LANG.
- * process elements of language alternatives
- *
- * Example:
- * <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">My house
- * </rdf:li> </rdf:Alt> </dc:title>
- *
- * This method is called when we hit the <rdf:li> element.
- *
- * @param string $elm Namespace . ' ' . tag
- * @param array $attribs Array of elements (most importantly xml:lang)
- * @throws RuntimeException If gets a tag other than <rdf:li> or if no xml:lang
- */
- private function startElementModeLiLang( $elm, $attribs ) {
- if ( $elm !== self::NS_RDF . ' li' ) {
- throw new RuntimeException( __METHOD__ . " <rdf:li> expected but got $elm." );
- }
- if ( !isset( $attribs[self::NS_XML . ' lang'] )
- || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
- ) {
- throw new RuntimeException( __METHOD__
- . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
- }
-
- // Lang is case-insensitive.
- $this->itemLang = strtolower( $attribs[self::NS_XML . ' lang'] );
-
- // need to add curItem[0] on again since one is for the specific item
- // and one is for the entire group.
- array_unshift( $this->curItem, $this->curItem[0] );
- array_unshift( $this->mode, self::MODE_SIMPLE );
- $this->processingArray = true;
- }
-
- /**
- * Hits an opening element.
- * Generally just calls a helper based on what MODE we're in.
- * Also does some initial set up for the wrapper element
- *
- * @param resource $parser
- * @param string $elm Namespace "<space>" element
- * @param array $attribs Attribute name => value
- * @throws RuntimeException
- */
- function startElement( $parser, $elm, $attribs ) {
- if ( $elm === self::NS_RDF . ' RDF'
- || $elm === 'adobe:ns:meta/ xmpmeta'
- || $elm === 'adobe:ns:meta/ xapmeta'
- ) {
- /* ignore. */
- return;
- } elseif ( $elm === self::NS_RDF . ' Description' ) {
- if ( count( $this->mode ) === 0 ) {
- // outer rdf:desc
- array_unshift( $this->mode, self::MODE_INITIAL );
- }
- } elseif ( $elm === self::NS_RDF . ' type' ) {
- // This doesn't support rdf:type properly.
- // In practise I have yet to see a file that
- // uses this element, however it is mentioned
- // on page 25 of part 1 of the xmp standard.
- // Also it seems as if exiv2 and exiftool do not support
- // this either (That or I misunderstand the standard)
- $this->logger->info(
- __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported',
- [ 'file' => $this->filename ]
- );
- }
-
- if ( strpos( $elm, ' ' ) === false ) {
- // This probably shouldn't happen.
- $this->logger->info(
- __METHOD__ . " Encountered <$elm> which has no namespace. Skipping.",
- [ 'file' => $this->filename ]
- );
-
- return;
- }
-
- list( $ns, $tag ) = explode( ' ', $elm, 2 );
-
- if ( count( $this->mode ) === 0 ) {
- // This should not happen.
- throw new RuntimeException( 'Error extracting XMP, '
- . "encountered <$elm> with no mode" );
- }
-
- switch ( $this->mode[0] ) {
- case self::MODE_IGNORE:
- $this->startElementModeIgnore( $elm );
- break;
- case self::MODE_SIMPLE:
- $this->startElementModeSimple( $elm, $attribs );
- break;
- case self::MODE_INITIAL:
- $this->startElementModeInitial( $ns, $tag, $attribs );
- break;
- case self::MODE_STRUCT:
- $this->startElementModeStruct( $ns, $tag, $attribs );
- break;
- case self::MODE_BAG:
- case self::MODE_BAGSTRUCT:
- $this->startElementModeBag( $elm );
- break;
- case self::MODE_SEQ:
- $this->startElementModeSeq( $elm );
- break;
- case self::MODE_LANG:
- $this->startElementModeLang( $elm );
- break;
- case self::MODE_LI_LANG:
- $this->startElementModeLiLang( $elm, $attribs );
- break;
- case self::MODE_LI:
- $this->startElementModeLi( $elm, $attribs );
- break;
- case self::MODE_QDESC:
- $this->startElementModeQDesc( $elm );
- break;
- default:
- throw new RuntimeException( 'StartElement in unknown mode: ' . $this->mode[0] );
- }
- }
-
- // phpcs:disable Generic.Files.LineLength
- /**
- * Process attributes.
- * Simple values can be stored as either a tag or attribute
- *
- * Often the initial "<rdf:Description>" tag just has all the simple
- * properties as attributes.
- *
- * @par Example:
- * @code
- * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
- * @endcode
- *
- * @param array $attribs Array attribute=>value
- * @throws RuntimeException
- */
- // phpcs:enable
- private function doAttribs( $attribs ) {
- // first check for rdf:parseType attribute, as that can change
- // how the attributes are interperted.
-
- if ( isset( $attribs[self::NS_RDF . ' parseType'] )
- && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
- && $this->mode[0] === self::MODE_SIMPLE
- ) {
- // this is equivalent to having an inner rdf:Description
- $this->mode[0] = self::MODE_QDESC;
- }
- foreach ( $attribs as $name => $val ) {
- if ( strpos( $name, ' ' ) === false ) {
- // This shouldn't happen, but so far some old software forgets namespace
- // on rdf:about.
- $this->logger->info(
- __METHOD__ . ' Encountered non-namespaced attribute: ' .
- " $name=\"$val\". Skipping. ",
- [ 'file' => $this->filename ]
- );
- continue;
- }
- list( $ns, $tag ) = explode( ' ', $name, 2 );
- if ( $ns === self::NS_RDF ) {
- if ( $tag === 'value' || $tag === 'resource' ) {
- // resource is for url.
- // value attribute is a weird way of just putting the contents.
- $this->char( $this->xmlParser, $val );
- }
- } elseif ( isset( $this->items[$ns][$tag] ) ) {
- if ( $this->mode[0] === self::MODE_SIMPLE ) {
- throw new RuntimeException( __METHOD__
- . " $ns:$tag found as attribute where not allowed" );
- }
- $this->saveValue( $ns, $tag, $val );
- } else {
- $this->logger->debug(
- __METHOD__ . " Ignoring unrecognized element <$ns:$tag>.",
- [ 'file' => $this->filename ]
- );
- }
- }
- }
-
- /**
- * Given an extracted value, save it to results array
- *
- * note also uses $this->ancestorStruct and
- * $this->processingArray to determine what name to
- * save the value under. (in addition to $tag).
- *
- * @param string $ns Namespace of tag this is for
- * @param string $tag Tag name
- * @param string $val Value to save
- */
- private function saveValue( $ns, $tag, $val ) {
- $info =& $this->items[$ns][$tag];
- $finalName = $info['map_name'] ?? $tag;
- if ( isset( $info['validate'] ) ) {
- if ( is_array( $info['validate'] ) ) {
- $validate = $info['validate'];
- } else {
- $validator = new XMPValidate( $this->logger );
- $validate = [ $validator, $info['validate'] ];
- }
-
- if ( is_callable( $validate ) ) {
- call_user_func_array( $validate, [ $info, &$val, true ] );
- // the reasoning behind using &$val instead of using the return value
- // is to be consistent between here and validating structures.
- if ( is_null( $val ) ) {
- $this->logger->info(
- __METHOD__ . " <$ns:$tag> failed validation.",
- [ 'file' => $this->filename ]
- );
-
- return;
- }
- } else {
- $this->logger->warning(
- __METHOD__ . " Validation function for $finalName (" .
- $validate[0] . '::' . $validate[1] . '()) is not callable.',
- [ 'file' => $this->filename ]
- );
- }
- }
-
- if ( $this->ancestorStruct && $this->processingArray ) {
- // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
- $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
- } elseif ( $this->ancestorStruct ) {
- $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
- } elseif ( $this->processingArray ) {
- if ( $this->itemLang === false ) {
- // normal array
- $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
- } else {
- // lang array.
- $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
- }
- } else {
- $this->results['xmp-' . $info['map_group']][$finalName] = $val;
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * Definitions for XMPReader class.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * This class is just a container for a big array
- * used by XMPReader to determine which XMP items to
- * extract.
- */
-class XMPInfo {
- /** Get the items array
- * @return array XMP item configuration array.
- */
- public static function getItems() {
- return self::$items;
- }
-
- /**
- * XMPInfo::$items keeps a list of all the items
- * we are interested to extract, as well as
- * information about the item like what type
- * it is.
- *
- * Format is an array of namespaces,
- * each containing an array of tags
- * each tag is an array of information about the
- * tag, including:
- * * map_group - What group (used for precedence during conflicts).
- * * mode - What type of item (self::MODE_SIMPLE usually, see above for
- * all values).
- * * validate - Method to validate input. Could also post-process the
- * input. A string value is assumed to be a method of
- * XMPValidate. Can also take a array( 'className', 'methodName' ).
- * * choices - Array of potential values (format of 'value' => true ).
- * Only used with validateClosed.
- * * rangeLow and rangeHigh - Alternative to choices for numeric ranges.
- * Again for validateClosed only.
- * * children - For MODE_STRUCT items, allowed children.
- * * structPart - Indicates that this element can only appear as a member
- * of a structure.
- *
- * Currently this just has a bunch of EXIF values as this class is only half-done.
- */
- static private $items = [
- 'http://ns.adobe.com/exif/1.0/' => [
- 'ApertureValue' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'BrightnessValue' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'CompressedBitsPerPixel' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'DigitalZoomRatio' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'ExposureBiasValue' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'ExposureIndex' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'ExposureTime' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'FlashEnergy' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
- ],
- 'FNumber' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'FocalLength' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'FocalPlaneXResolution' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'FocalPlaneYResolution' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'GPSAltitude' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
- ],
- 'GPSDestBearing' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'GPSDestDistance' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'GPSDOP' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'GPSImgDirection' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'GPSSpeed' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'GPSTrack' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'MaxApertureValue' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'ShutterSpeedValue' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- 'SubjectDistance' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational'
- ],
- /* Flash */
- 'Flash' => [
- 'mode' => XMPReader::MODE_STRUCT,
- 'children' => [
- 'Fired' => true,
- 'Function' => true,
- 'Mode' => true,
- 'RedEyeMode' => true,
- 'Return' => true,
- ],
- 'validate' => 'validateFlash',
- 'map_group' => 'exif',
- ],
- 'Fired' => [
- 'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'Function' => [
- 'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'Mode' => [
- 'map_group' => 'exif',
- 'validate' => 'validateClosed',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'choices' => [ '0' => true, '1' => true,
- '2' => true, '3' => true ],
- 'structPart' => true,
- ],
- 'Return' => [
- 'map_group' => 'exif',
- 'validate' => 'validateClosed',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'choices' => [ '0' => true,
- '2' => true, '3' => true ],
- 'structPart' => true,
- ],
- 'RedEyeMode' => [
- 'map_group' => 'exif',
- 'validate' => 'validateBoolean',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- /* End Flash */
- 'ISOSpeedRatings' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger'
- ],
- /* end rational things */
- 'ColorSpace' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '1' => true, '65535' => true ],
- ],
- 'ComponentsConfiguration' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateClosed',
- 'choices' => [ '1' => true, '2' => true, '3' => true, '4' => true,
- '5' => true, '6' => true ]
- ],
- 'Contrast' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '0' => true, '1' => true, '2' => true ]
- ],
- 'CustomRendered' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '0' => true, '1' => true ]
- ],
- 'DateTimeOriginal' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
- ],
- 'DateTimeDigitized' => [ /* xmp:CreateDate */
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
- ],
- /* todo: there might be interesting information in
- * exif:DeviceSettingDescription, but need to find an
- * example
- */
- 'ExifVersion' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'ExposureMode' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
- 'rangeHigh' => 2,
- ],
- 'ExposureProgram' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
- 'rangeHigh' => 8,
- ],
- 'FileSource' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '3' => true ]
- ],
- 'FlashpixVersion' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'FocalLengthIn35mmFilm' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- ],
- 'FocalPlaneResolutionUnit' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '2' => true, '3' => true ],
- ],
- 'GainControl' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
- 'rangeHigh' => 4,
- ],
- /* this value is post-processed out later */
- 'GPSAltitudeRef' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '0' => true, '1' => true ],
- ],
- 'GPSAreaInformation' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'GPSDestBearingRef' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ 'T' => true, 'M' => true ],
- ],
- 'GPSDestDistanceRef' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ 'K' => true, 'M' => true,
- 'N' => true ],
- ],
- 'GPSDestLatitude' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
- ],
- 'GPSDestLongitude' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
- ],
- 'GPSDifferential' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '0' => true, '1' => true ],
- ],
- 'GPSImgDirectionRef' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ 'T' => true, 'M' => true ],
- ],
- 'GPSLatitude' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
- ],
- 'GPSLongitude' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateGPS',
- ],
- 'GPSMapDatum' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'GPSMeasureMode' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '2' => true, '3' => true ]
- ],
- 'GPSProcessingMethod' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'GPSSatellites' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'GPSSpeedRef' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ 'K' => true, 'M' => true,
- 'N' => true ],
- ],
- 'GPSStatus' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ 'A' => true, 'V' => true ]
- ],
- 'GPSTimeStamp' => [
- 'map_group' => 'exif',
- // Note: in exif, GPSDateStamp does not include
- // the time, where here it does.
- 'map_name' => 'GPSDateStamp',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
- ],
- 'GPSTrackRef' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ 'T' => true, 'M' => true ]
- ],
- 'GPSVersionID' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'ImageUniqueID' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'LightSource' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- /* can't use a range, as it skips... */
- 'choices' => [ '0' => true, '1' => true,
- '2' => true, '3' => true, '4' => true,
- '9' => true, '10' => true, '11' => true,
- '12' => true, '13' => true,
- '14' => true, '15' => true,
- '17' => true, '18' => true,
- '19' => true, '20' => true,
- '21' => true, '22' => true,
- '23' => true, '24' => true,
- '255' => true,
- ],
- ],
- 'MeteringMode' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
- 'rangeHigh' => 6,
- 'choices' => [ '255' => true ],
- ],
- /* Pixel(X|Y)Dimension are rather useless, but for
- * completeness since we do it with exif.
- */
- 'PixelXDimension' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- ],
- 'PixelYDimension' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- ],
- 'Saturation' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
- 'rangeHigh' => 2,
- ],
- 'SceneCaptureType' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
- 'rangeHigh' => 3,
- ],
- 'SceneType' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '1' => true ],
- ],
- // Note, 6 is not valid SensingMethod.
- 'SensingMethod' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 1,
- 'rangeHigh' => 5,
- 'choices' => [ '7' => true, 8 => true ],
- ],
- 'Sharpness' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
- 'rangeHigh' => 2,
- ],
- 'SpectralSensitivity' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- // This tag should perhaps be displayed to user better.
- 'SubjectArea' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
- ],
- 'SubjectDistanceRange' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'rangeLow' => 0,
- 'rangeHigh' => 3,
- ],
- 'SubjectLocation' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
- ],
- 'UserComment' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
- ],
- 'WhiteBalance' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '0' => true, '1' => true ]
- ],
- ],
- 'http://ns.adobe.com/tiff/1.0/' => [
- 'Artist' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'BitsPerSample' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateInteger',
- ],
- 'Compression' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '1' => true, '6' => true ],
- ],
- /* this prop should not be used in XMP. dc:rights is the correct prop */
- 'Copyright' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
- ],
- 'DateTime' => [ /* proper prop is xmp:ModifyDate */
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
- ],
- 'ImageDescription' => [ /* proper one is dc:description */
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_LANG,
- ],
- 'ImageLength' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- ],
- 'ImageWidth' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- ],
- 'Make' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'Model' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- /**** Do not extract this property
- * It interferes with auto exif rotation.
- * 'Orientation' => array(
- * 'map_group' => 'exif',
- * 'mode' => XMPReader::MODE_SIMPLE,
- * 'validate' => 'validateClosed',
- * 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
- * '6' => true, '7' => true, '8' => true ),
- *),
- ******/
- 'PhotometricInterpretation' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '2' => true, '6' => true ],
- ],
- 'PlanerConfiguration' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '1' => true, '2' => true ],
- ],
- 'PrimaryChromaticities' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
- ],
- 'ReferenceBlackWhite' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
- ],
- 'ResolutionUnit' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '2' => true, '3' => true ],
- ],
- 'SamplesPerPixel' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- ],
- 'Software' => [ /* see xmp:CreatorTool */
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- /* ignore TransferFunction */
- 'WhitePoint' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
- ],
- 'XResolution' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
- ],
- 'YResolution' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRational',
- ],
- 'YCbCrCoefficients' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateRational',
- ],
- 'YCbCrPositioning' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateClosed',
- 'choices' => [ '1' => true, '2' => true ],
- ],
- /********
- * Disable extracting this property (T33944)
- * Several files have a string instead of a Seq
- * for this property. XMPReader doesn't handle
- * mismatched types very gracefully (it marks
- * the entire file as invalid, instead of just
- * the relavent prop). Since this prop
- * doesn't communicate all that useful information
- * just disable this prop for now, until such
- * XMPReader is more graceful (T34172)
- * 'YCbCrSubSampling' => array(
- * 'map_group' => 'exif',
- * 'mode' => XMPReader::MODE_SEQ,
- * 'validate' => 'validateClosed',
- * 'choices' => array( '1' => true, '2' => true ),
- * ),
- */
- ],
- 'http://ns.adobe.com/exif/1.0/aux/' => [
- 'Lens' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'SerialNumber' => [
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'OwnerName' => [
- 'map_group' => 'exif',
- 'map_name' => 'CameraOwnerName',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- ],
- 'http://purl.org/dc/elements/1.1/' => [
- 'title' => [
- 'map_group' => 'general',
- 'map_name' => 'ObjectName',
- 'mode' => XMPReader::MODE_LANG
- ],
- 'description' => [
- 'map_group' => 'general',
- 'map_name' => 'ImageDescription',
- 'mode' => XMPReader::MODE_LANG
- ],
- 'contributor' => [
- 'map_group' => 'general',
- 'map_name' => 'dc-contributor',
- 'mode' => XMPReader::MODE_BAG
- ],
- 'coverage' => [
- 'map_group' => 'general',
- 'map_name' => 'dc-coverage',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'creator' => [
- 'map_group' => 'general',
- 'map_name' => 'Artist', // map with exif Artist, iptc byline (2:80)
- 'mode' => XMPReader::MODE_SEQ,
- ],
- 'date' => [
- 'map_group' => 'general',
- // Note, not mapped with other date properties, as this type of date is
- // non-specific: "A point or period of time associated with an event in
- // the lifecycle of the resource"
- 'map_name' => 'dc-date',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateDate',
- ],
- /* Do not extract dc:format, as we've got better ways to determine MIME type */
- 'identifier' => [
- 'map_group' => 'deprecated',
- 'map_name' => 'Identifier',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'language' => [
- 'map_group' => 'general',
- 'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
- 'mode' => XMPReader::MODE_BAG,
- 'validate' => 'validateLangCode',
- ],
- 'publisher' => [
- 'map_group' => 'general',
- 'map_name' => 'dc-publisher',
- 'mode' => XMPReader::MODE_BAG,
- ],
- // for related images/resources
- 'relation' => [
- 'map_group' => 'general',
- 'map_name' => 'dc-relation',
- 'mode' => XMPReader::MODE_BAG,
- ],
- 'rights' => [
- 'map_group' => 'general',
- 'map_name' => 'Copyright',
- 'mode' => XMPReader::MODE_LANG,
- ],
- // Note: source is not mapped with iptc source, since iptc
- // source describes the source of the image in terms of a person
- // who provided the image, where this is to describe an image that the
- // current one is based on.
- 'source' => [
- 'map_group' => 'general',
- 'map_name' => 'dc-source',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'subject' => [
- 'map_group' => 'general',
- 'map_name' => 'Keywords', /* maps to iptc 2:25 */
- 'mode' => XMPReader::MODE_BAG,
- ],
- 'type' => [
- 'map_group' => 'general',
- 'map_name' => 'dc-type',
- 'mode' => XMPReader::MODE_BAG,
- ],
- ],
- 'http://ns.adobe.com/xap/1.0/' => [
- 'CreateDate' => [
- 'map_group' => 'general',
- 'map_name' => 'DateTimeDigitized',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateDate',
- ],
- 'CreatorTool' => [
- 'map_group' => 'general',
- 'map_name' => 'Software',
- 'mode' => XMPReader::MODE_SIMPLE
- ],
- 'Identifier' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- ],
- 'Label' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'ModifyDate' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'DateTime',
- 'validate' => 'validateDate',
- ],
- 'MetadataDate' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- // map_name to be consistent with other date names.
- 'map_name' => 'DateTimeMetadata',
- 'validate' => 'validateDate',
- ],
- 'Nickname' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'Rating' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateRating',
- ],
- ],
- 'http://ns.adobe.com/xap/1.0/rights/' => [
- 'Certificate' => [
- 'map_group' => 'general',
- 'map_name' => 'RightsCertificate',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'Marked' => [
- 'map_group' => 'general',
- 'map_name' => 'Copyrighted',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateBoolean',
- ],
- 'Owner' => [
- 'map_group' => 'general',
- 'map_name' => 'CopyrightOwner',
- 'mode' => XMPReader::MODE_BAG,
- ],
- // this seems similar to dc:rights.
- 'UsageTerms' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_LANG,
- ],
- 'WebStatement' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- ],
- // XMP media management.
- 'http://ns.adobe.com/xap/1.0/mm/' => [
- // if we extract the exif UniqueImageID, might
- // as well do this too.
- 'OriginalDocumentID' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- // It might also be useful to do xmpMM:LastURL
- // and xmpMM:DerivedFrom as you can potentially,
- // get the url of this document/source for this
- // document. However whats more likely is you'd
- // get a file:// url for the path of the doc,
- // which is somewhat of a privacy issue.
- ],
- 'http://creativecommons.org/ns#' => [
- 'license' => [
- 'map_name' => 'LicenseUrl',
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'morePermissions' => [
- 'map_name' => 'MorePermissionsUrl',
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'attributionURL' => [
- 'map_group' => 'general',
- 'map_name' => 'AttributionUrl',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'attributionName' => [
- 'map_group' => 'general',
- 'map_name' => 'PreferredAttributionName',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- ],
- // Note, this property affects how jpeg metadata is extracted.
- 'http://ns.adobe.com/xmp/note/' => [
- 'HasExtendedXMP' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- ],
- /* Note, in iptc schemas, the legacy properties are denoted
- * as deprecated, since other properties should used instead,
- * and properties marked as deprecated in the standard are
- * are marked as general here as they don't have replacements
- */
- 'http://ns.adobe.com/photoshop/1.0/' => [
- 'City' => [
- 'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CityDest',
- ],
- 'Country' => [
- 'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CountryDest',
- ],
- 'State' => [
- 'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'ProvinceOrStateDest',
- ],
- 'DateCreated' => [
- 'map_group' => 'deprecated',
- // marking as deprecated as the xmp prop preferred
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'DateTimeOriginal',
- 'validate' => 'validateDate',
- // note this prop is an XMP, not IPTC date
- ],
- 'CaptionWriter' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'Writer',
- ],
- 'Instructions' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'SpecialInstructions',
- ],
- 'TransmissionReference' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'OriginalTransmissionRef',
- ],
- 'AuthorsPosition' => [
- /* This corresponds with 2:85
- * By-line Title, which needs to be
- * handled weirdly to correspond
- * with iptc/exif. */
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE
- ],
- 'Credit' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'Source' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'Urgency' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'Category' => [
- // Note, this prop is deprecated, but in general
- // group since it doesn't have a replacement.
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'iimCategory',
- ],
- 'SupplementalCategories' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'iimSupplementalCategory',
- ],
- 'Headline' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE
- ],
- ],
- 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' => [
- 'CountryCode' => [
- 'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'CountryCodeDest',
- ],
- 'IntellectualGenre' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- // Note, this is a six digit code.
- // See: http://cv.iptc.org/newscodes/scene/
- // Since these aren't really all that common,
- // we just show the number.
- 'Scene' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'validate' => 'validateInteger',
- 'map_name' => 'SceneCode',
- ],
- /* Note: SubjectCode should be an 8 ascii digits.
- * it is not really an integer (has leading 0's,
- * cannot have a +/- sign), but validateInteger
- * will let it through.
- */
- 'SubjectCode' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'SubjectNewsCode',
- 'validate' => 'validateInteger'
- ],
- 'Location' => [
- 'map_group' => 'deprecated',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'map_name' => 'SublocationDest',
- ],
- 'CreatorContactInfo' => [
- /* Note this maps to 2:118 in iim
- * (Contact) field. However those field
- * types are slightly different - 2:118
- * is free form text field, where this
- * is more structured.
- */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_STRUCT,
- 'map_name' => 'Contact',
- 'children' => [
- 'CiAdrExtadr' => true,
- 'CiAdrCity' => true,
- 'CiAdrCtry' => true,
- 'CiEmailWork' => true,
- 'CiTelWork' => true,
- 'CiAdrPcode' => true,
- 'CiAdrRegion' => true,
- 'CiUrlWork' => true,
- ],
- ],
- 'CiAdrExtadr' => [ /* address */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CiAdrCity' => [ /* city */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CiAdrCtry' => [ /* country */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CiEmailWork' => [ /* email (possibly separated by ',') */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CiTelWork' => [ /* telephone */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CiAdrPcode' => [ /* postal code */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CiAdrRegion' => [ /* province/state */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CiUrlWork' => [ /* url. Multiple may be separated by comma. */
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- /* End contact info struct properties */
- ],
- 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/' => [
- 'Event' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- ],
- 'OrganisationInImageName' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- 'map_name' => 'OrganisationInImage'
- ],
- 'PersonInImage' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_BAG,
- ],
- 'MaxAvailHeight' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- 'map_name' => 'OriginalImageHeight',
- ],
- 'MaxAvailWidth' => [
- 'map_group' => 'general',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'validate' => 'validateInteger',
- 'map_name' => 'OriginalImageWidth',
- ],
- // LocationShown and LocationCreated are handled
- // specially because they are hierarchical, but we
- // also want to merge with the old non-hierarchical.
- 'LocationShown' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_BAGSTRUCT,
- 'children' => [
- 'WorldRegion' => true,
- 'CountryCode' => true, /* iso code */
- 'CountryName' => true,
- 'ProvinceState' => true,
- 'City' => true,
- 'Sublocation' => true,
- ],
- ],
- 'LocationCreated' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_BAGSTRUCT,
- 'children' => [
- 'WorldRegion' => true,
- 'CountryCode' => true, /* iso code */
- 'CountryName' => true,
- 'ProvinceState' => true,
- 'City' => true,
- 'Sublocation' => true,
- ],
- ],
- 'WorldRegion' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CountryCode' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'CountryName' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- 'map_name' => 'Country',
- ],
- 'ProvinceState' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- 'map_name' => 'ProvinceOrState',
- ],
- 'City' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
- 'Sublocation' => [
- 'map_group' => 'special',
- 'mode' => XMPReader::MODE_SIMPLE,
- 'structPart' => true,
- ],
-
- /* Other props that might be interesting but
- * Not currently extracted:
- * ArtworkOrObject, (info about objects in picture)
- * DigitalSourceType
- * RegistryId
- */
- ],
-
- /* Plus props we might want to consider:
- * (Note: some of these have unclear/incomplete definitions
- * from the iptc4xmp standard).
- * ImageSupplier (kind of like iptc source field)
- * ImageSupplierId (id code for image from supplier)
- * CopyrightOwner
- * ImageCreator
- * Licensor
- * Various model release fields
- * Property release fields.
- */
- ];
-}
+++ /dev/null
-<?php
-/**
- * Methods for validating XMP properties.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-use Psr\Log\LoggerInterface;
-use Psr\Log\LoggerAwareInterface;
-use Wikimedia\Timestamp\ConvertibleTimestamp;
-
-/**
- * This contains some static methods for
- * validating XMP properties. See XMPInfo and XMPReader classes.
- *
- * Each of these functions take the same parameters
- * * an info array which is a subset of the XMPInfo::items array
- * * A value (passed as reference) to validate. This can be either a
- * simple value or an array
- * * A boolean to determine if this is validating a simple or complex values
- *
- * It should be noted that when an array is being validated, typically the validation
- * function is called once for each value, and then once at the end for the entire array.
- *
- * These validation functions can also be used to modify the data. See the gps and flash one's
- * for example.
- *
- * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf starting at pg 28
- * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf starting at pg 11
- */
-class XMPValidate implements LoggerAwareInterface {
-
- /**
- * @var LoggerInterface
- */
- private $logger;
-
- public function __construct( LoggerInterface $logger ) {
- $this->setLogger( $logger );
- }
-
- public function setLogger( LoggerInterface $logger ) {
- $this->logger = $logger;
- }
- /**
- * Function to validate boolean properties ( True or False )
- *
- * @param array $info Information about current property
- * @param mixed &$val Current value to validate
- * @param bool $standalone If this is a simple property or array
- */
- public function validateBoolean( $info, &$val, $standalone ) {
- if ( !$standalone ) {
- // this only validates standalone properties, not arrays, etc
- return;
- }
- if ( $val !== 'True' && $val !== 'False' ) {
- $this->logger->info( __METHOD__ . " Expected True or False but got $val" );
- $val = null;
- }
- }
-
- /**
- * function to validate rational properties ( 12/10 )
- *
- * @param array $info Information about current property
- * @param mixed &$val Current value to validate
- * @param bool $standalone If this is a simple property or array
- */
- public function validateRational( $info, &$val, $standalone ) {
- if ( !$standalone ) {
- // this only validates standalone properties, not arrays, etc
- return;
- }
- if ( !preg_match( '/^(?:-?\d+)\/(?:\d+[1-9]|[1-9]\d*)$/D', $val ) ) {
- $this->logger->info( __METHOD__ . " Expected rational but got $val" );
- $val = null;
- }
- }
-
- /**
- * function to validate rating properties -1, 0-5
- *
- * if its outside of range put it into range.
- *
- * @see MWG spec
- * @param array $info Information about current property
- * @param mixed &$val Current value to validate
- * @param bool $standalone If this is a simple property or array
- */
- public function validateRating( $info, &$val, $standalone ) {
- if ( !$standalone ) {
- // this only validates standalone properties, not arrays, etc
- return;
- }
- if ( !preg_match( '/^[-+]?\d*(?:\.?\d*)$/D', $val )
- || !is_numeric( $val )
- ) {
- $this->logger->info( __METHOD__ . " Expected rating but got $val" );
- $val = null;
-
- return;
- } else {
- $nVal = (float)$val;
- if ( $nVal < 0 ) {
- // We do < 0 here instead of < -1 here, since
- // the values between 0 and -1 are also illegal
- // as -1 is meant as a special reject rating.
- $this->logger->info( __METHOD__ . " Rating too low, setting to -1 (Rejected)" );
- $val = '-1';
-
- return;
- }
- if ( $nVal > 5 ) {
- $this->logger->info( __METHOD__ . " Rating too high, setting to 5" );
- $val = '5';
-
- return;
- }
- }
- }
-
- /**
- * function to validate integers
- *
- * @param array $info Information about current property
- * @param mixed &$val Current value to validate
- * @param bool $standalone If this is a simple property or array
- */
- public function validateInteger( $info, &$val, $standalone ) {
- if ( !$standalone ) {
- // this only validates standalone properties, not arrays, etc
- return;
- }
- if ( !preg_match( '/^[-+]?\d+$/D', $val ) ) {
- $this->logger->info( __METHOD__ . " Expected integer but got $val" );
- $val = null;
- }
- }
-
- /**
- * function to validate properties with a fixed number of allowed
- * choices. (closed choice)
- *
- * @param array $info Information about current property
- * @param mixed &$val Current value to validate
- * @param bool $standalone If this is a simple property or array
- */
- public function validateClosed( $info, &$val, $standalone ) {
- if ( !$standalone ) {
- // this only validates standalone properties, not arrays, etc
- return;
- }
-
- // check if its in a numeric range
- $inRange = false;
- if ( isset( $info['rangeLow'] )
- && isset( $info['rangeHigh'] )
- && is_numeric( $val )
- && ( intval( $val ) <= $info['rangeHigh'] )
- && ( intval( $val ) >= $info['rangeLow'] )
- ) {
- $inRange = true;
- }
-
- if ( !isset( $info['choices'][$val] ) && !$inRange ) {
- $this->logger->info( __METHOD__ . " Expected closed choice, but got $val" );
- $val = null;
- }
- }
-
- /**
- * function to validate and modify flash structure
- *
- * @param array $info Information about current property
- * @param mixed &$val Current value to validate
- * @param bool $standalone If this is a simple property or array
- */
- public function validateFlash( $info, &$val, $standalone ) {
- if ( $standalone ) {
- // this only validates flash structs, not individual properties
- return;
- }
- if ( !( isset( $val['Fired'] )
- && isset( $val['Function'] )
- && isset( $val['Mode'] )
- && isset( $val['RedEyeMode'] )
- && isset( $val['Return'] )
- ) ) {
- $this->logger->info( __METHOD__ . " Flash structure did not have all the required components" );
- $val = null;
- } else {
- $val = ( 0 | ( $val['Fired'] === 'True' )
- | ( intval( $val['Return'] ) << 1 )
- | ( intval( $val['Mode'] ) << 3 )
- | ( ( $val['Function'] === 'True' ) << 5 )
- | ( ( $val['RedEyeMode'] === 'True' ) << 6 ) );
- }
- }
-
- /**
- * function to validate LangCode properties ( en-GB, etc )
- *
- * This is just a naive check to make sure it somewhat looks like a lang code.
- *
- * @see BCP 47
- * @see https://wwwimages2.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/
- * XMP%20SDK%20Release%20cc-2014-12/XMPSpecificationPart1.pdf page 22 (section 8.2.2.4)
- *
- * @param array $info Information about current property
- * @param mixed &$val Current value to validate
- * @param bool $standalone If this is a simple property or array
- */
- public function validateLangCode( $info, &$val, $standalone ) {
- if ( !$standalone ) {
- // this only validates standalone properties, not arrays, etc
- return;
- }
- if ( !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $val ) ) {
- // this is a rather naive check.
- $this->logger->info( __METHOD__ . " Expected Lang code but got $val" );
- $val = null;
- }
- }
-
- /**
- * function to validate date properties, and convert to (partial) Exif format.
- *
- * Dates can be one of the following formats:
- * YYYY
- * YYYY-MM
- * YYYY-MM-DD
- * YYYY-MM-DDThh:mmTZD
- * YYYY-MM-DDThh:mm:ssTZD
- * YYYY-MM-DDThh:mm:ss.sTZD
- *
- * @param array $info Information about current property
- * @param mixed &$val Current value to validate. Converts to TS_EXIF as a side-effect.
- * in cases where there's only a partial date, it will give things like
- * 2011:04.
- * @param bool $standalone If this is a simple property or array
- */
- public function validateDate( $info, &$val, $standalone ) {
- if ( !$standalone ) {
- // this only validates standalone properties, not arrays, etc
- return;
- }
- $res = [];
- if ( !preg_match(
- /* ahh! scary regex... */
- // phpcs:ignore Generic.Files.LineLength
- '/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D',
- $val, $res )
- ) {
- $this->logger->info( __METHOD__ . " Expected date but got $val" );
- $val = null;
- } else {
- /*
- * $res is formatted as follows:
- * 0 -> full date.
- * 1 -> year, 2-> month, 3-> day, 4-> hour, 5-> minute, 6->second
- * 7-> Timezone specifier (Z or something like +12:30 )
- * many parts are optional, some aren't. For example if you specify
- * minute, you must specify hour, day, month, and year but not second or TZ.
- */
-
- /*
- * First of all, if year = 0000, Something is wrongish,
- * so don't extract. This seems to happen when
- * some programs convert between metadata formats.
- */
- if ( $res[1] === '0000' ) {
- $this->logger->info( __METHOD__ . " Invalid date (year 0): $val" );
- $val = null;
-
- return;
- }
-
- if ( !isset( $res[4] ) ) { // hour
- // just have the year month day (if that)
- $val = $res[1];
- if ( isset( $res[2] ) ) {
- $val .= ':' . $res[2];
- }
- if ( isset( $res[3] ) ) {
- $val .= ':' . $res[3];
- }
-
- return;
- }
-
- if ( !isset( $res[7] ) || $res[7] === 'Z' ) {
- // if hour is set, then minute must also be or regex above will fail.
- $val = $res[1] . ':' . $res[2] . ':' . $res[3]
- . ' ' . $res[4] . ':' . $res[5];
- if ( isset( $res[6] ) && $res[6] !== '' ) {
- $val .= ':' . $res[6];
- }
-
- return;
- }
-
- // Extra check for empty string necessary due to TZ but no second case.
- $stripSeconds = false;
- if ( !isset( $res[6] ) || $res[6] === '' ) {
- $res[6] = '00';
- $stripSeconds = true;
- }
-
- // Do timezone processing. We've already done the case that tz = Z.
-
- // We know that if we got to this step, year, month day hour and min must be set
- // by virtue of regex not failing.
-
- $unix = ConvertibleTimestamp::convert( TS_UNIX,
- $res[1] . $res[2] . $res[3] . $res[4] . $res[5] . $res[6]
- );
- $offset = intval( substr( $res[7], 1, 2 ) ) * 60 * 60;
- $offset += intval( substr( $res[7], 4, 2 ) ) * 60;
- if ( substr( $res[7], 0, 1 ) === '-' ) {
- $offset = -$offset;
- }
- $val = ConvertibleTimestamp::convert( TS_EXIF, $unix + $offset );
-
- if ( $stripSeconds ) {
- // If seconds weren't specified, remove the trailing ':00'.
- $val = substr( $val, 0, -3 );
- }
- }
- }
-
- /** function to validate, and more importantly
- * translate the XMP DMS form of gps coords to
- * the decimal form we use.
- *
- * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf
- * section 1.2.7.4 on page 23
- *
- * @param array $info Unused (info about prop)
- * @param string &$val GPS string in either DDD,MM,SSk or
- * or DDD,MM.mmk form
- * @param bool $standalone If its a simple prop (should always be true)
- */
- public function validateGPS( $info, &$val, $standalone ) {
- if ( !$standalone ) {
- return;
- }
-
- $m = [];
- if ( preg_match(
- '/(\d{1,3}),(\d{1,2}),(\d{1,2})([NWSE])/D',
- $val, $m )
- ) {
- $coord = intval( $m[1] );
- $coord += intval( $m[2] ) * ( 1 / 60 );
- $coord += intval( $m[3] ) * ( 1 / 3600 );
- if ( $m[4] === 'S' || $m[4] === 'W' ) {
- $coord = -$coord;
- }
- $val = $coord;
-
- return;
- } elseif ( preg_match(
- '/(\d{1,3}),(\d{1,2}(?:.\d*)?)([NWSE])/D',
- $val, $m )
- ) {
- $coord = intval( $m[1] );
- $coord += floatval( $m[2] ) * ( 1 / 60 );
- if ( $m[3] === 'S' || $m[3] === 'W' ) {
- $coord = -$coord;
- }
- $val = $coord;
-
- return;
- } else {
- $this->logger->info( __METHOD__
- . " Expected GPSCoordinate, but got $val." );
- $val = null;
-
- return;
- }
- }
-}
*/
use MediaWiki\Logger\LoggerFactory;
+use Wikimedia\XMPReader\Reader as XMPReader;
/**
* Class to deal with reconciling and extracting metadata from bitmap images.
* @ingroup Media
*/
+use Wikimedia\XMPReader\Reader as XMPReader;
+
/**
* Class for reading jpegs and extracting metadata.
* see also BitmapMetadataHandler.
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => '9'
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- exif:DigitalZoomRatio="0/10">
-<exif:Flash rdf:parseType='Resource'>
-<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => '9'
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- exif:DigitalZoomRatio="0/10">
-<exif:Flash>
-<rdf:Description exif:Return="0">
-<exif:Fired>True</exif:Fired> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></rdf:Description></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<!--
-This file has an invalid flash compoenent (one of the values are a qualifier)
--->
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
->
-<exif:DigitalZoomRatio>
-
-<rdf:Description>
-<rdf:value>
-0/10
-</rdf:value>
-<exif:foobarbaz>fred</exif:foobarbaz>
-
-</rdf:Description>
-
-</exif:DigitalZoomRatio>
-
-<exif:Flash>
-<rdf:Description exif:Return="0">
-<exif:Mode><rdf:Description>
-<rdf:value>1</rdf:value>
-<exif:Fired>False</exif:Fired> <!-- qualifier. should be ignored-->
-</rdf:Description>
-</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></rdf:Description></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => '9'
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
->
-<exif:DigitalZoomRatio>
-
-<rdf:Description>
-<rdf:value>
-0/10
-</rdf:value>
-<exif:foobarbaz>fred</exif:foobarbaz>
-
-</rdf:Description>
-
-</exif:DigitalZoomRatio>
-
-<exif:Flash>
-<rdf:Description exif:Return="0">
-<exif:Fired>True</exif:Fired>
-<exif:Mode><rdf:Description>
-<rdf:value>1</rdf:value>
-<exif:Fired>False</exif:Fired> <!-- qualifier. should be ignored-->
-</rdf:Description>
-</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></rdf:Description></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<!-- Valid output is just the DigitalZoomRatio
-as the flash is a qualifier
--->
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/">
- <exif:DigitalZoomRatio>
-<rdf:Description>
-<rdf:value>
-0/10
-</rdf:value>
-<exif:Flash rdf:parseType='Resource'>
-<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash>
-</rdf:Description>
-</exif:DigitalZoomRatio>
-</rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/">
- <exif:DigitalZoomRatio>
-<rdf:Description rdf:value="0/10">
-<exif:Flash rdf:parseType='Resource'>
-<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash>
-</rdf:Description>
-</exif:DigitalZoomRatio>
-</rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => '9'
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/">
-<exif:DigitalZoomRatio>
-0/10
-</exif:DigitalZoomRatio>
-</rdf:Description>
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/">
-
-<exif:Flash rdf:parseType='Resource'>
-<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-$result = [
- 'xmp-exif' =>
- [
- 'CameraOwnerName' => 'Me!',
- ],
- 'xmp-general' =>
- [
- 'LicenseUrl' => 'http://creativecommons.com/cc-by-2.9',
- 'ImageDescription' =>
- [
- 'x-default' => 'Test image for the cc: xmp: xmpRights: namespaces in xmp',
- '_type' => 'lang',
- ],
- 'ObjectName' =>
- [
- 'x-default' => 'xmp core/xmp rights/cc ns test',
- '_type' => 'lang',
- ],
- 'DateTimeDigitized' => '2005:04:03',
- 'Software' => 'The one true editor: Vi (ok i used gimp)',
- 'Identifier' =>
- [
- 0 => 'http://example.com/identifierurl',
- 1 => 'urn:sha1:342524abcdef',
- '_type' => 'ul',
- ],
- 'Label' => 'Test image',
- 'DateTimeMetadata' => '2011:05:12',
- 'DateTime' => '2007:03:04 06:34:10',
- 'Nickname' => 'My little xmp test image',
- 'Rating' => '5',
- 'RightsCertificate' => 'http://example.com/rights-certificate/',
- 'Copyrighted' => 'True',
- 'CopyrightOwner' =>
- [
- 0 => 'Bawolff is copyright owner',
- '_type' => 'ul',
- ],
- 'UsageTerms' =>
- [
- 'x-default' => 'do whatever you want',
- 'en-gb' => 'Do whatever you want in british english',
- '_type' => 'lang',
- ],
- 'WebStatement' => 'http://example.com/web_statement',
- ],
- 'xmp-deprecated' =>
- [
- 'Identifier' => 'http://example.com/identifierurl/wrong',
- ],
-];
+++ /dev/null
-<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
-<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
-<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
-
- <rdf:Description rdf:about=''
- xmlns:aux='http://ns.adobe.com/exif/1.0/aux/'>
- <aux:OwnerName>Me!</aux:OwnerName>
- </rdf:Description>
-
- <rdf:Description rdf:about=''
- xmlns:cc='http://creativecommons.org/ns#'>
- <cc:license>http://creativecommons.com/cc-by-2.9</cc:license>
- </rdf:Description>
-
- <rdf:Description rdf:about=''
- xmlns:dc='http://purl.org/dc/elements/1.1/'>
- <dc:description>
- <rdf:Alt>
- <rdf:li xml:lang='x-default'>Test image for the cc: xmp: xmpRights: namespaces in xmp</rdf:li>
- </rdf:Alt>
- </dc:description>
- <dc:identifier>http://example.com/identifierurl/wrong</dc:identifier>
- <dc:title>
- <rdf:Alt>
- <rdf:li xml:lang='x-default'>xmp core/xmp rights/cc ns test</rdf:li>
- </rdf:Alt>
- </dc:title>
- </rdf:Description>
-
- <rdf:Description rdf:about=''
- xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
- <xmp:CreateDate>2005-04-03</xmp:CreateDate>
- <xmp:CreatorTool>The one true editor: Vi (ok i used gimp)</xmp:CreatorTool>
- <xmp:Identifier>
- <rdf:Bag>
- <rdf:li>http://example.com/identifierurl
-</rdf:li>
- <rdf:li>urn:sha1:342524abcdef</rdf:li>
- </rdf:Bag>
- </xmp:Identifier>
- <xmp:Label>Test image</xmp:Label>
- <xmp:MetadataDate>2011-05-12</xmp:MetadataDate>
- <xmp:ModifyDate>2007-03-04T12:34:10-06:00</xmp:ModifyDate>
- <xmp:Nickname>My little xmp test image</xmp:Nickname>
- <xmp:Rating>7</xmp:Rating>
- </rdf:Description>
-
- <rdf:Description rdf:about=''
- xmlns:xmpRights='http://ns.adobe.com/xap/1.0/rights/'>
- <xmpRights:Certificate>http://example.com/rights-certificate/</xmpRights:Certificate>
- <xmpRights:Marked>True</xmpRights:Marked>
- <xmpRights:Owner>
- <rdf:Bag>
- <rdf:li>Bawolff is copyright owner</rdf:li>
- </rdf:Bag>
- </xmpRights:Owner>
- <xmpRights:UsageTerms>
- <rdf:Alt>
- <rdf:li xml:lang='x-default'>do whatever you want</rdf:li>
- <rdf:li xml:lang='en-GB'>Do whatever you want in british english</rdf:li>
- </rdf:Alt>
- </xmpRights:UsageTerms>
- <xmpRights:WebStatement>http://example.com/web_statement</xmpRights:WebStatement>
- </rdf:Description>
-</rdf:RDF>
-</x:xmpmeta>
-<?xpacket end='r'?>
+++ /dev/null
-This directory contains a bunch of XMP files
-as well as a bunch of php files containing what the
-parsed version of the XMP looks like.
+++ /dev/null
-<?php
-
-$result = [
- 'xmp-general' => [
- 'Artist' => [
- '_type' => 'ul',
- 0 => 'The author',
- ]
- ]
-];
+++ /dev/null
-<?xpacket begin=""?> <x:xmpmeta xmlns:x="adobe:ns:meta/"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"> <dc:creator> <rdf:Bag> <rdf:li>The author</rdf:li> </rdf:Bag> </dc:creator> </rdf:Description> </rdf:RDF> </x:xmpmeta>
+++ /dev/null
-<?php
-
-$result = [];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <!DOCTYPE x:xmpmeta [ <!ENTITY lol "lollollollollollollollollollollol"> ]>
-<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- exif:DigitalZoomRatio="0/10">
-<exif:Flash rdf:parseType='Resource'>
-<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- exif:DigitalZoomRatio="0/10">
-<exif:Flash rdf:parseType='Resource'>
-<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => '127'
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- exif:DigitalZoomRatio="0/10">
-<exif:Flash rdf:parseType='Resource'>
-<exif:Fired>True</exif:Fired> <exif:Return>3</exif:Return> <exif:Mode>3</exif:Mode> <exif:Function>True</exif:Function> <exif:RedEyeMode>True</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'GPSAltitude' => -3.14159265301,
- 'GPSDOP' => '5/1',
- 'GPSLatitude' => 88.51805555,
- 'GPSLongitude' => -21.12356945,
- 'GPSVersionID' => '2.2.0.0'
- ]
-];
+++ /dev/null
-<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
-<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
-<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
-
- <rdf:Description rdf:about=''
- xmlns:exif='http://ns.adobe.com/exif/1.0/'>
- <exif:GPSAltitude>103993/33102</exif:GPSAltitude>
- <exif:GPSAltitudeRef>1</exif:GPSAltitudeRef>
- <exif:GPSDOP>5/1</exif:GPSDOP>
- <exif:GPSLatitude>88,31.083333N</exif:GPSLatitude>
- <exif:GPSLongitude>21,7.414167W</exif:GPSLongitude>
- <exif:GPSVersionID>2.2.0.0</exif:GPSVersionID>
- </rdf:Description>
-
-</rdf:RDF>
-</x:xmpmeta>
-<?xpacket end='w'?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- exif:DigitalZoomRatio="0/10">
-<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode>
-
- </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'FNumber' => '28/10',
- ]
-];
+++ /dev/null
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<!-- Testing it handles random non-namespaced properties in files ok.
- Some older photoshop's did not include the rdf: prefix on about. -->
-<rdf:Description
- about=""
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- exif:FNumber="28/10">
-</rdf:Description>
-</rdf:RDF>
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-$result = [];
+++ /dev/null
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/not-exif-namespace"
- exif:FNumber="2/10">
-</rdf:Description>
-</rdf:RDF>
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-$result = [
- 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- ],
- 'xmp-general' =>
- [
- 'Label' => ''
- ],
-];
+++ /dev/null
-<?php
-
-$result = [
- 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- ],
- 'xmp-general' =>
- [
- 'Label' => ''
- ],
-];
+++ /dev/null
-<?php
-
-$result = [
- 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- ],
- 'xmp-general' =>
- [
- 'Label' => ''
- ],
-];
+++ /dev/null
-<?php
-
-$result = [
- 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- ],
- 'xmp-general' =>
- [
- 'Label' => ''
- ],
-];
+++ /dev/null
-<?php
-
-$result = [ 'xmp-exif' =>
- [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => '9'
- ]
-];
+++ /dev/null
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
- 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- xmlns:xmpNote="http://ns.adobe.com/xmp/note/"
- exif:DigitalZoomRatio="0/10"
- xmpNote:HasExtendedXMP="28C74E0AC2D796886759006FBE2E57B7">
-<exif:Flash rdf:parseType='Resource'>
-<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
-
-<?xpacket end="w"?>
+++ /dev/null
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-<rdf:Description
- rdf:about=""
- xmlns:exif="http://ns.adobe.com/exif/1.0/"
- exif:FNumber="2/10">
-</rdf:Description>
-</rdf:RDF>
-<?xpacket end="w"?>
+++ /dev/null
-<?php
-
-/**
- * @group Media
- * @covers XMPReader
- */
-class XMPTest extends PHPUnit\Framework\TestCase {
-
- use MediaWikiCoversValidator;
-
- protected function setUp() {
- parent::setUp();
- # Requires libxml to do XMP parsing
- if ( !extension_loaded( 'exif' ) ) {
- $this->markTestSkipped( "PHP extension 'exif' is not loaded, skipping." );
- }
- }
-
- /**
- * Put XMP in, compare what comes out...
- *
- * @param string $xmp The actual xml data.
- * @param array $expected Expected result of parsing the xmp.
- * @param string $info Short sentence on what's being tested.
- *
- * @throws Exception
- * @dataProvider provideXMPParse
- *
- * @covers XMPReader::parse
- */
- public function testXMPParse( $xmp, $expected, $info ) {
- if ( !is_string( $xmp ) || !is_array( $expected ) ) {
- throw new Exception( "Invalid data provided to " . __METHOD__ );
- }
- $reader = new XMPReader;
- $reader->parse( $xmp );
- $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 );
- }
-
- public static function provideXMPParse() {
- $xmpPath = __DIR__ . '/../../../data/xmp/';
- $data = [];
-
- // $xmpFiles format: array of arrays with first arg file base name,
- // with the actual file having .xmp on the end for the xmp
- // and .result.php on the end for a php file containing the result
- // array. Second argument is some info on what's being tested.
- $xmpFiles = [
- [ '1', 'parseType=Resource test' ],
- [ '2', 'Structure with mixed attribute and element props' ],
- [ '3', 'Extra qualifiers (that should be ignored)' ],
- [ '3-invalid', 'Test ignoring qualifiers that look like normal props' ],
- [ '4', 'Flash as qualifier' ],
- [ '5', 'Flash as qualifier 2' ],
- [ '6', 'Multiple rdf:Description' ],
- [ '7', 'Generic test of several property types' ],
- [ 'flash', 'Test of Flash property' ],
- [ 'invalid-child-not-struct', 'Test child props not in struct or ignored' ],
- [ 'no-recognized-props', 'Test namespace and no recognized props' ],
- [ 'no-namespace', 'Test non-namespaced attributes are ignored' ],
- [ 'bag-for-seq', "Allow bag's instead of seq's. (T29105)" ],
- [ 'utf16BE', 'UTF-16BE encoding' ],
- [ 'utf16LE', 'UTF-16LE encoding' ],
- [ 'utf32BE', 'UTF-32BE encoding' ],
- [ 'utf32LE', 'UTF-32LE encoding' ],
- [ 'xmpExt', 'Extended XMP missing second part' ],
- [ 'gps', 'Handling of exif GPS parameters in XMP' ],
- ];
-
- $xmpFiles[] = [ 'doctype-included', 'XMP includes doctype' ];
-
- foreach ( $xmpFiles as $file ) {
- $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' );
- // I'm not sure if this is the best way to handle getting the
- // result array, but it seems kind of big to put directly in the test
- // file.
- $result = null;
- include $xmpPath . $file[0] . '.result.php';
- $data[] = [ $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] ];
- }
-
- return $data;
- }
-
- /** Test ExtendedXMP block support. (Used when the XMP has to be split
- * over multiple jpeg segments, due to 64k size limit on jpeg segments.
- *
- * @todo This is based on what the standard says. Need to find a real
- * world example file to double check the support for this is right.
- *
- * @covers XMPReader::parseExtended
- */
- public function testExtendedXMP() {
- $xmpPath = __DIR__ . '/../../../data/xmp/';
- $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
- $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
- $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
- $length = pack( 'N', strlen( $extendedXMP ) );
- $offset = pack( 'N', 0 );
- $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
- $reader = new XMPReader();
- $reader->parse( $standardXMP );
- $reader->parseExtended( $extendedPacket );
- $actual = $reader->getResults();
-
- $expected = [
- 'xmp-exif' => [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => 9,
- 'FNumber' => '2/10',
- ]
- ];
-
- $this->assertEquals( $expected, $actual );
- }
-
- /**
- * This test has an extended XMP block with a wrong guid (md5sum)
- * and thus should only return the StandardXMP, not the ExtendedXMP.
- *
- * @covers XMPReader::parseExtended
- */
- public function testExtendedXMPWithWrongGUID() {
- $xmpPath = __DIR__ . '/../../../data/xmp/';
- $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
- $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
- $md5sum = '28C74E0AC2D796886759006FBE2E57B9'; // Note last digit.
- $length = pack( 'N', strlen( $extendedXMP ) );
- $offset = pack( 'N', 0 );
- $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
- $reader = new XMPReader();
- $reader->parse( $standardXMP );
- $reader->parseExtended( $extendedPacket );
- $actual = $reader->getResults();
-
- $expected = [
- 'xmp-exif' => [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => 9,
- ]
- ];
-
- $this->assertEquals( $expected, $actual );
- }
-
- /**
- * Have a high offset to simulate a missing packet,
- * which should cause it to ignore the ExtendedXMP packet.
- *
- * @covers XMPReader::parseExtended
- */
- public function testExtendedXMPMissingPacket() {
- $xmpPath = __DIR__ . '/../../../data/xmp/';
- $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
- $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
- $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
- $length = pack( 'N', strlen( $extendedXMP ) );
- $offset = pack( 'N', 2048 );
- $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
- $reader = new XMPReader();
- $reader->parse( $standardXMP );
- $reader->parseExtended( $extendedPacket );
- $actual = $reader->getResults();
-
- $expected = [
- 'xmp-exif' => [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => 9,
- ]
- ];
-
- $this->assertEquals( $expected, $actual );
- }
-
- /**
- * Test for multi-section, hostile XML
- * @covers XMPReader::checkParseSafety
- */
- public function testCheckParseSafety() {
- // Test for detection
- $xmpPath = __DIR__ . '/../../../data/xmp/';
- $file = fopen( $xmpPath . 'doctype-included.xmp', 'rb' );
- $valid = false;
- $reader = new XMPReader();
- do {
- $chunk = fread( $file, 10 );
- $valid = $reader->parse( $chunk, feof( $file ) );
- } while ( !feof( $file ) );
- $this->assertFalse( $valid, 'Check that doctype is detected in fragmented XML' );
- $this->assertEquals(
- [],
- $reader->getResults(),
- 'Check that doctype is detected in fragmented XML'
- );
- fclose( $file );
- unset( $reader );
-
- // Test for false positives
- $file = fopen( $xmpPath . 'doctype-not-included.xmp', 'rb' );
- $valid = false;
- $reader = new XMPReader();
- do {
- $chunk = fread( $file, 10 );
- $valid = $reader->parse( $chunk, feof( $file ) );
- } while ( !feof( $file ) );
- $this->assertTrue(
- $valid,
- 'Check for false-positive detecting doctype in fragmented XML'
- );
- $this->assertEquals(
- [
- 'xmp-exif' => [
- 'DigitalZoomRatio' => '0/10',
- 'Flash' => '9'
- ]
- ],
- $reader->getResults(),
- 'Check that doctype is detected in fragmented XML'
- );
- }
-}
+++ /dev/null
-<?php
-
-use Psr\Log\NullLogger;
-
-/**
- * @group Media
- */
-class XMPValidateTest extends PHPUnit\Framework\TestCase {
-
- use MediaWikiCoversValidator;
-
- /**
- * @dataProvider provideDates
- * @covers XMPValidate::validateDate
- */
- public function testValidateDate( $value, $expected ) {
- // The method should modify $value.
- $validate = new XMPValidate( new NullLogger() );
- $validate->validateDate( [], $value, true );
- $this->assertEquals( $expected, $value );
- }
-
- public static function provideDates() {
- /* For reference valid date formats are:
- * YYYY
- * YYYY-MM
- * YYYY-MM-DD
- * YYYY-MM-DDThh:mmTZD
- * YYYY-MM-DDThh:mm:ssTZD
- * YYYY-MM-DDThh:mm:ss.sTZD
- * (Time zone is optional)
- */
- return [
- [ '1992', '1992' ],
- [ '1992-04', '1992:04' ],
- [ '1992-02-01', '1992:02:01' ],
- [ '2011-09-29', '2011:09:29' ],
- [ '1982-12-15T20:12', '1982:12:15 20:12' ],
- [ '1982-12-15T20:12Z', '1982:12:15 20:12' ],
- [ '1982-12-15T20:12+02:30', '1982:12:15 22:42' ],
- [ '1982-12-15T01:12-02:30', '1982:12:14 22:42' ],
- [ '1982-12-15T20:12:11', '1982:12:15 20:12:11' ],
- [ '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ],
- [ '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ],
- [ '2045-12-15T20:12:11', '2045:12:15 20:12:11' ],
- [ '1867-06-01T15:00:00', '1867:06:01 15:00:00' ],
- /* some invalid ones */
- [ '2001--12', null ],
- [ '2001-5-12', null ],
- [ '2001-5-12TZ', null ],
- [ '2001-05-12T15', null ],
- [ '2001-12T15:13', null ],
- ];
- }
-}