Completed
Push — refact-v2 ( 0317e0 )
by mw
04:47
created

ExifPropertyAnnotator::addAnnotation()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 39
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 20
nc 6
nop 2
dl 0
loc 39
rs 6.7272
c 0
b 0
f 0
ccs 0
cts 26
cp 0
crap 56
1
<?php
2
3
namespace SESP\PropertyAnnotators;
4
5
use SESP\PropertyAnnotator;
6
use SESP\AppFactory;
7
use SMW\DIProperty;
8
use SMW\DIWikiPage;
9
use SMWContainerSemanticData as ContainerSemanticData;
10
use SMW\SemanticData;
11
use SMWDataItem as DataItem;
12
use SMWDIContainer as DIContainer;
13
use SMWDITime as DITime;
14
use SMWDINumber as DINumber;
15
use SMWDIBlob as DIBlob;
16
use FormatMetadata;
17
use RuntimeException;
18
19
/**
20
 * @private
21
 * @ingroup SESP
22
 *
23
 * @see http://www.exiv2.org/tags.html
24
 *
25
 * @license GNU GPL v2+
26
 * @since 2.0
27
 *
28
 * @author mwjames
29
 * @author rotsee
30
 * @author Stephan Gambke
31
 */
32
class ExifPropertyAnnotator implements PropertyAnnotator {
33
34
	/**
35
	 * @var AppFactory
36
	 */
37
	private $appFactory;
38
39
	/**
40
	 * @since 2.0
41
	 *
42
	 * @param AppFactory $appFactory
43
	 */
44
	public function __construct( AppFactory $appFactory ) {
45
		$this->appFactory = $appFactory;
46
	}
47
48
	/**
49
	 * @since 2.0
50
	 *
51
	 * {@inheritDoc}
52
	 */
53
	public function isAnnotatorFor( DIProperty $property ) {
54
		return $property->getKey() === '___EXIFDATA' ;
55
	}
56
57
	/**
58
	 * @since 2.0
59
	 *
60
	 * {@inheritDoc}
61
	 */
62
	public function addAnnotation( DIProperty $property, SemanticData $semanticData ) {
63
64
		$subject = $semanticData->getSubject();
65
		$title = $subject->getTitle();
66
67
		if ( !$title->inNamespace( NS_FILE ) ) {
68
			return;
69
		}
70
71
		$page = $this->appFactory->newWikiPage( $title );
0 ignored issues
show
Bug introduced by
It seems like $title defined by $subject->getTitle() on line 65 can be null; however, SESP\AppFactory::newWikiPage() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
72
		$file = $page->getFile();
73
74
		if ( !$file->exists() ) {
75
			return;
76
		}
77
78
		// #66
79
		$meta = $file->getMetadata();
80
81
		if ( !$meta ) {
82
			return false;
83
		}
84
85
		// Guard against "Error at offset 0 of 1 bytes"
86
		$exif = @unserialize( $meta );
87
88
		if ( !is_array( $exif ) || count( $exif ) === 0 ) {
89
			return;
90
		}
91
92
		$exif[ 'ImageWidth' ]  = $file->getWidth();
93
		$exif[ 'ImageLength' ] = $file->getHeight();
94
95
		$dataItem = $this->getDataItemFromExifData( $subject, $exif );
96
97
		if ( $dataItem instanceof DataItem ) {
98
			$semanticData->addPropertyObjectValue( $property, $dataItem );
99
		}
100
	}
101
102
	protected function getDataItemFromExifData( $subject, $rawExif ) {
103
104
		$containerSemanticData = $this->newContainerSemanticData(
105
			$subject
106
		);
107
108
		$this->addExifDataTo( $containerSemanticData, $rawExif );
109
110
		if ( $containerSemanticData->isEmpty() ) {
111
			return;
112
		}
113
114
		return new DIContainer( $containerSemanticData );
115
	}
116
117
	private function newContainerSemanticData( $subject ) {
118
119
		$subject = new DIWikiPage(
120
			$subject->getDBkey(),
121
			$subject->getNamespace(),
122
			$subject->getInterwiki(),
123
			'_EXIFDATA'
124
		);
125
126
		return new ContainerSemanticData( $subject );
127
	}
128
129
	private function addExifDataTo( $containerSemanticData, $rawExif ) {
130
131
		$exifDefinitions = $this->appFactory->getPropertyDefinitions()->safeGet( '_EXIF' );
132
		$formattedExif = FormatMetadata::getFormattedData( $rawExif );
133
134
		foreach ( $formattedExif as $key => $value ) {
135
136
			$dataItem = null;
137
			$upKey = strtoupper( $key );
138
139
			if ( !isset( $exifDefinitions[$upKey] ) || !isset( $exifDefinitions[$upKey]['id'] ) ) {
140
				continue;
141
			}
142
143
			$id = $exifDefinitions[$upKey]['id'];
144
			$type = $exifDefinitions[$upKey]['type'];
145
146
			switch ( $type ) {
147
				case '_num':
148
					$dataItem = is_numeric( $rawExif[$key] ) ? new DINumber( $rawExif[$key] ) : null;
149
					break;
150
				case '_txt':
151
					$dataItem = new DIBlob( $value );
152
					break;
153
				case '_dat':
154
					$dataItem = $this->makeDataItemTime( $rawExif[$key] );
155
			}
156
157
			if ( $dataItem instanceof DataItem ) {
158
				$containerSemanticData->addPropertyObjectValue( new DIProperty( $id ), $dataItem );
159
			}
160
		}
161
	}
162
163
	private function makeDataItemTime( $exifValue ) {
164
		$datetime = $this->convertExifDate( $exifValue );
165
166
		if ( $datetime ) {
167
			return new DITime(
168
				DITime::CM_GREGORIAN,
169
				$datetime->format('Y'),
170
				$datetime->format('n'),
0 ignored issues
show
Documentation introduced by
$datetime->format('n') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
171
				$datetime->format('j'),
0 ignored issues
show
Documentation introduced by
$datetime->format('j') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
172
				$datetime->format('G'),
0 ignored issues
show
Documentation introduced by
$datetime->format('G') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
173
				$datetime->format('i')
0 ignored issues
show
Documentation introduced by
$datetime->format('i') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
174
			);
175
		}
176
	}
177
178
	private function convertExifDate( $exifString ) {
179
180
		// Unknown date
181
		if ( $exifString == '0000:00:00 00:00:00' || $exifString == '    :  :     :  :  ' ) {
182
			return false;
183
		}
184
185
		// Full date
186
		if ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $exifString ) ) {
187
			return new \DateTime( $exifString );
188
		}
189
190
		// No second field, timeanddate doesn't include seconds but second still available in api
191
		if ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $exifString ) ) {
192
			return new \DateTime( $exifString . ':00' );
193
		}
194
195
		// Only the date but not the time
196
		if (  preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $exifString ) ) {
197
			return new \DateTime(
198
				substr( $exifString, 0, 4 ) . ':' .
199
				substr( $exifString, 5, 2 ) . ':' .
200
				substr( $exifString, 8, 2 ) . ' 00:00:00'
201
			);
202
		}
203
204
		return false;
205
	}
206
207
}
208