Completed
Push — add/amp-pwa-experiment ( efea12 )
by
unknown
11:53
created

AMP_Image_Dimension_Extractor   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 117
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 117
rs 10
c 0
b 0
f 0
wmc 22
lcom 1
cbo 1

5 Methods

Rating   Name   Duplication   Size   Complexity  
A extract() 0 12 3
C normalize_url() 0 27 7
A register_callbacks() 0 8 1
A extract_from_attachment_metadata() 0 18 4
C extract_by_downloading_image() 0 44 7
1
<?php
2
3
class AMP_Image_Dimension_Extractor {
4
	static $callbacks_registered = false;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $callbacks_registered.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
5
6
	static public function extract( $url ) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
7
		if ( ! self::$callbacks_registered ) {
8
			self::register_callbacks();
9
		}
10
11
		$url = self::normalize_url( $url );
12
		if ( false === $url ) {
13
			return false;
14
		}
15
16
		return apply_filters( 'amp_extract_image_dimensions', false, $url );
17
	}
18
19
	public static function normalize_url( $url ) {
20
		if ( empty( $url ) ) {
21
			return false;
22
		}
23
24
		if ( 0 === strpos( $url, 'data:' ) ) {
25
			return false;
26
		}
27
28
		if ( 0 === strpos( $url, '//' ) ) {
29
			return set_url_scheme( $url, 'http' );
30
		}
31
32
		$parsed = parse_url( $url );
33
		if ( ! isset( $parsed['host'] ) ) {
34
			$path = '';
35
			if ( isset( $parsed['path'] ) ) {
36
				$path .= $parsed['path'];
37
			}
38
			if ( isset( $parsed['query'] ) ) {
39
				$path .= '?' . $parsed['query'];
40
			}
41
			$url = site_url( $path );
42
		}
43
44
		return $url;
45
	}
46
47
	private static function register_callbacks() {
48
		self::$callbacks_registered = true;
49
50
		add_filter( 'amp_extract_image_dimensions', array( __CLASS__, 'extract_from_attachment_metadata' ), 10, 2 );
51
		add_filter( 'amp_extract_image_dimensions', array( __CLASS__, 'extract_by_downloading_image' ), 999, 2 ); // Run really late since this is our last resort
52
53
		do_action( 'amp_extract_image_dimensions_callbacks_registered' );
54
	}
55
56
	public static function extract_from_attachment_metadata( $dimensions, $url ) {
57
		if ( is_array( $dimensions ) ) {
58
			return $dimensions;
59
		}
60
61
		$url = strtok( $url, '?' );
62
		$attachment_id = attachment_url_to_postid( $url );
63
		if ( empty( $attachment_id ) ) {
64
			return false;
65
		}
66
67
		$metadata = wp_get_attachment_metadata( $attachment_id );
68
		if ( ! $metadata ) {
69
			return false;
70
		}
71
72
		return array( $metadata['width'], $metadata['height'] );
73
	}
74
75
	public static function extract_by_downloading_image( $dimensions, $url ) {
76
		if ( is_array( $dimensions ) ) {
77
			return $dimensions;
78
		}
79
80
		$url_hash = md5( $url );
81
		$transient_name = sprintf( 'amp_img_%s', $url_hash );
82
		$transient_expiry = 30 * DAY_IN_SECONDS;
83
		$transient_fail = 'fail';
84
85
		$dimensions = get_transient( $transient_name );
86
87
		if ( is_array( $dimensions ) ) {
88
			return $dimensions;
89
		} elseif ( $transient_fail === $dimensions ) {
90
			return false;
91
		}
92
93
		// Very simple lock to prevent stampedes
94
		$transient_lock_name = sprintf( 'amp_lock_%s', $url_hash );
95
		if ( false !== get_transient( $transient_lock_name ) ) {
96
			return false;
97
		}
98
		set_transient( $transient_lock_name, 1, MINUTE_IN_SECONDS );
99
100
		// Note to other developers: please don't use this class directly as it may not stick around forever...
101
		if ( ! class_exists( 'FastImage' ) ) {
102
			require_once( AMP__ROOT__ . '/includes/lib/class-fastimage.php' );
103
		}
104
105
		// TODO: look into using curl+stream (https://github.com/willwashburn/FasterImage)
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
106
		$image = new FastImage( $url );
107
		$dimensions = $image->getSize();
108
109
		if ( ! is_array( $dimensions ) ) {
110
			set_transient( $transient_name, $transient_fail, $transient_expiry );
111
			delete_transient( $transient_lock_name );
112
			return false;
113
		}
114
115
		set_transient( $transient_name, $dimensions, $transient_expiry );
116
		delete_transient( $transient_lock_name );
117
		return $dimensions;
118
	}
119
}
120