PluginProbe
Image Source Control Lite – Show Image Credits and Captions / 3.12.0
Image Source Control Lite – Show Image Credits and Captions v3.12.0
3.12.0 3.11.0 trunk 1.1 1.1.1 1.1.2 1.1.2.1 1.1.3 1.10 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.2 1.2.0.1 1.2.0.2 1.2.0.3 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.4.1 1.3.5 All 110 releases
image-source-control-isc / includes / image-sources / analyze-html.php

analyze-html.php in Image Source Control Lite – Show Image Credits and Captions 3.12.0, at includes/image-sources/analyze-html.php

224 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ISC\Image_Sources;
4
5 use ISC_Log;
6
7 /**
8 * Analyze chunks of HTML
9 */
10 class Analyze_HTML {
11
12 /**
13 * Extract image-related markup from larger chunks of HTML.
14 *
15 * @param string $html HTML to extract images from.
16 * @return array Array of image markup.
17 */
18 public function extract_images_from_html( string $html ): array {
19 /**
20 * Removed [caption], because this check runs after the hook that interprets shortcodes
21 * img tag is checked individually since there is a different order of attributes when images are used in gallery or individually
22 *
23 * 0 – full match
24 * 1 - <figure> if set and having a class attribute
25 * 2 – classes from figure tag
26 * 3 – inner code starting with <a>
27 * 4 – opening link tag
28 * 5 – "rel" attribute from link tag
29 * 6 – image id from link wp-att- value in "rel" attribute
30 * 7 – full img tag
31 * 8 – image URL
32 * 9 – closing link tag
33 *
34 * tested with:
35 * * with and without [caption]
36 * * with and without link attribute
37 *
38 * potential issues:
39 * * line breaks in the code – use \s* where potential line breaks could appear
40 *
41 * Use (\x20|\x9|\xD|\xA)+ to match whitespace following HTML starting tag name according to W3C REC 3.1. See issue PR #136
42 */
43 $pattern = apply_filters( 'isc_public_caption_regex', '#(?:<figure[^>]*class=["\']([^"\']*)["\'][^>]*>\s*)?((<a[\x20|\x9|\xD|\xA]+[^>]*>)?\s*(<img[\x20|\x9|\xD|\xA]+[^>]*[^>]*src=["\'](.+)["\'].*\/?>).*(\s*</a>)??[^<]*)#isU', $html );
44 preg_match_all( $pattern, $html, $matches, PREG_SET_ORDER );
45
46 /**
47 * Convert the matches into a multidimensional array with string keys to simplify usage and manipulation
48 */
49 $matches_new = array_map(
50 function ( $match ) {
51 return [
52 'full' => $match[0] ?? '',
53 'figure_class' => $match[1] ?? '',
54 'inner_code' => $match[2] ?? '',
55 'img_src' => $match[5] ?? '',
56 ];
57 },
58 $matches
59 );
60
61 /**
62 * Filter matches from regex
63 */
64 return apply_filters( 'isc_extract_images_from_html', $matches_new, $matches, $pattern );
65 }
66
67 /**
68 * Extract image ID from HTML
69 *
70 * @param string $html HTML to extract an image ID from.
71 * @return int Image ID.
72 */
73 public function extract_image_id( string $html ): int {
74 if ( ! $html ) {
75 return 0;
76 }
77
78 $id = 0;
79
80 /**
81 * Image IDs seem to be in the following elements:
82 * - img tag with class "wp-image-123"
83 * - img tag with data-id="123"
84 */
85 $success = preg_match( '#wp-image-(\d+)|data-id=["\'](\d+)#is', $html, $matches_id );
86 if ( $success ) {
87 $id = $matches_id[1] ? intval( $matches_id[1] ) : intval( $matches_id[2] );
88 ISC_Log::log( sprintf( 'found ID "%s"', $id ) );
89 } else {
90 ISC_Log::log( sprintf( 'no ID found for "%s"', $html ) );
91 }
92
93 return $id;
94 }
95
96 /**
97 * Extract the image URL from HTML, namely, the value of the src attribute
98 *
99 * @param string $html HTML to extract an image URL from.
100 * @return string Image URL.
101 */
102 public function extract_image_src( string $html ): string {
103 if ( ! $html ) {
104 return '';
105 }
106
107 $src = '';
108
109 $success = preg_match( '#src=["\']([^"\']+)["\']#is', $html, $matches_src );
110 if ( $success ) {
111 $src = $matches_src[1];
112 ISC_Log::log( sprintf( 'found src "%s"', $src ) );
113 } else {
114 ISC_Log::log( sprintf( 'no src found for "%s"', $html ) );
115 }
116
117 return $src;
118 }
119
120 /**
121 * Extract image URLs from HTML.
122 * One image URL per HTML attribute will be found.
123 *
124 * Limitations:
125 * - retrieves the first valid image URL from any HTML tag
126 * - if an IMG tag has a SRC attribute with a valid image URL, all other tags with valid URLs are ignored
127 * - technically, one could generate tags with different images from the Media Library, e.g., having a differently cut URL in a data-attribute
128 * that then shows dynamically using JavaScript
129 * this would cause one of the images not being listed with a source or a used image
130 * since I haven’t seen that in the wild, this case is deliberately ignored
131 *
132 * @param string $html Any HTML code.
133 * @return array List of image URLs.
134 */
135 public static function extract_image_urls_from_html_tags( $html = '' ): array {
136 $urls = [];
137
138 if ( empty( $html ) ) {
139 ISC_Log::log( 'Exit due to missing empty HTML' );
140 return $urls;
141 }
142
143 ISC_Log::log( 'look for image IDs within the HTML' );
144
145 $types = implode( '|', \ISC\Image_Sources\Image_Sources::get_allowed_extensions() );
146 /**
147 * Look for any URLs
148 * - starting with http, or https
149 * - ending with a valid image format or " or '
150 * - or any URL in the SRC attribute of IMG tags, where we ignore the extension
151 *
152 * What it does
153 * (?<!, ) – needed to prevent multiple images from `srcset` attribute to being considered here
154 * (http[s]?:) - start with http or https
155 * [^\'^"^ ] - stop at one of these chars since they don't belong into URLs: ", ', or space
156 * or looks for any URL in the SRC attribute
157 *
158 * Limitations
159 * - we don't include images that don't have a full path, e.g., url("../img/image.png") or url("image.png"); they are likely not in the Media library
160 */
161 $pattern = '#((?<!, )((http[s]?:)[^\'^"^ ]*\.(' . $types . ')))|(<img[\x20|\x9|\xD|\xA]+[^>]*[^>]*src=["\'](.+)["\'].*\/?>)#isU';
162
163 /**
164 * Match index
165 * 0 - full match (including first char)
166 * 1 - URLs with valid image file extension
167 * 2 - ignore
168 * 3 – protocol
169 * 4 - file format (e.g. "jpg")
170 * 5 – full img tag
171 * 6 - URLs from img tags – the file extension is ignored here
172 */
173 preg_match_all( $pattern, $html, $matches );
174
175 // merge matches from both conditions and remove empty ones
176 $urls = array_filter( array_merge( $matches[1], $matches[6] ) );
177 // remove duplicate URLs
178 return array_values( array_unique( $urls ) );
179 }
180
181 /**
182 * Extract any image URLs from HTML.
183 *
184 * @param string $html Any HTML code.
185 *
186 * @return array List of image URLs.
187 */
188 public static function extract_image_urls( string $html = '' ): array {
189 $urls = [];
190
191 if ( empty( $html ) ) {
192 \ISC_Log::log( 'Exit due to empty HTML' );
193 return $urls;
194 }
195
196 \ISC_Log::log( 'Looking for valid image URLs within HTML' );
197
198 // Get allowed image extensions as a pipe-separated string.
199 $types = implode( '|', \ISC\Image_Sources\Image_Sources::get_allowed_extensions() );
200
201 /**
202 * The regex below matches:
203 * - URLs starting with "http://" or "https://"
204 * - Followed by one or more non-whitespace characters (non-greedy)
205 * - Ending with a dot and one of the allowed extensions (case-insensitive)
206 * - Optionally followed by a query string
207 * - The URL is expected to be wrapped in either:
208 * -- single or double quotes
209 * -- whitespace
210 * -- brackets "()"
211 */
212 $pattern = '#https?://\S+?\.(?:' . $types . ')(?:\?\S*)?(?=["\'\s\)])#i';
213
214 preg_match_all( $pattern, $html, $matches );
215
216 if ( ! empty( $matches[0] ) ) {
217 // Remove duplicate URLs.
218 return array_values( array_unique( $matches[0] ) );
219 }
220
221 return $urls;
222 }
223 }
224