PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.8.0
TinyPNG – JPEG, PNG & WebP image compression v3.8.0
3.8.0 3.7.0 3.6.14 trunk 1.0.0 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.4.0 1.5.0 1.6.0 1.7.0 1.7.1 1.7.2 2.0.0 2.0.1 2.0.2 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 3.0.0 3.0.1 3.1.0 3.2.0 3.2.1 3.3 3.4 3.4.1 3.4.2 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.5.2 3.6.0 3.6.1 3.6.10 3.6.11 3.6.12 3.6.13 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9
tiny-compress-images / src / class-tiny-source-base.php
tiny-compress-images / src Last commit date
compatibility 2 months ago config 2 months ago css 7 months ago data 4 years ago images 4 years ago js 1 week ago vendor 6 months ago views 1 week ago class-tiny-apache-rewrite.php 2 months ago class-tiny-bulk-optimization.php 2 months ago class-tiny-cli.php 2 months ago class-tiny-compress-client.php 1 week ago class-tiny-compress-fopen.php 2 months ago class-tiny-compress.php 1 week ago class-tiny-conversion.php 4 months ago class-tiny-diagnostics.php 7 months ago class-tiny-exception.php 7 months ago class-tiny-helpers.php 2 months ago class-tiny-image-size.php 1 week ago class-tiny-image.php 1 week ago class-tiny-logger.php 2 months ago class-tiny-migrate.php 2 months ago class-tiny-notices.php 2 months ago class-tiny-php.php 2 months ago class-tiny-picture.php 2 months ago class-tiny-plugin.php 1 week ago class-tiny-settings.php 1 week ago class-tiny-source-base.php 1 week ago class-tiny-source-image.php 7 months ago class-tiny-source-picture.php 7 months ago class-tiny-wp-base.php 2 months ago
class-tiny-source-base.php
313 lines
1 <?php
2
3 /*
4 * Tiny Compress Images - WordPress plugin.
5 * Copyright (C) 2015-2018 Tinify B.V.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc., 51
19 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 abstract class Tiny_Source_Base {
23
24 public $raw_html;
25 protected $base_dir;
26 protected $allowed_domains;
27 protected $valid_mimetypes;
28
29 public function __construct( $html, $base_dir, $domains ) {
30 $this->raw_html = $html;
31 $this->base_dir = $base_dir;
32 $this->allowed_domains = $domains;
33 $this->valid_mimetypes = array( 'image/avif', 'image/webp' );
34 }
35
36 protected static function get_attribute_value( $element, $name ) {
37 // Match the exact attribute name (not part of data-media, mediaType, etc.)
38 // and capture a single- or double-quoted value.
39 $delim = '~';
40 $attr = preg_quote( $name, $delim );
41 $regex = $delim . '(?<![\w:-])' . $attr . '\s*=\s*(["\'])(.*?)\1' . $delim . 'is';
42
43 if ( preg_match( $regex, $element, $m ) ) {
44 return $m[2];
45 }
46 return null;
47 }
48
49 /**
50 * Extract elements by tag name from an HTML string (regex-based).
51 *
52 * @param string $html The HTML string to search in.
53 * @param string $tagname The tag name (e.g., 'div', 'source', 'img').
54 * @return array Array of matched elements as strings.
55 */
56 protected function get_element_by_tag( $html, $tagname ) {
57 $results = array();
58
59 // Self-closing / void tag (e.g. <source />, <img />, <br />)
60 if ( preg_match_all(
61 '~<' . preg_quote( $tagname, '~' ) . '\b(?:[^>"\']+|"[^"]*"|\'[^\']*\')*/?>~i',
62 $html,
63 $matches
64 ) ) {
65 $results = array_merge( $results, $matches[0] );
66 }
67
68 // Normal paired tags (e.g. <div>…</div>)
69 $regex_tag = preg_quote( $tagname, '~' );
70 if ( preg_match_all(
71 '~<' . $regex_tag .
72 '\b(?:[^>"\']+|"[^"]*"|\'[^\']*\')*>.*?</' .
73 $regex_tag .
74 '>~is',
75 $html,
76 $matches
77 ) ) {
78 $results = array_merge( $results, $matches[0] );
79 }
80
81 return $results;
82 }
83
84 protected function get_local_path( $url ) {
85 if ( strpos( $url, 'http' ) === 0 ) {
86 $matched_domain = null;
87
88 foreach ( $this->allowed_domains as $domain ) {
89 if ( strpos( $url, $domain ) === 0 ) {
90 $matched_domain = $domain;
91 break;
92 }
93 }
94
95 if ( null === $matched_domain ) {
96 return '';
97 }
98
99 $url = substr( $url, strlen( $matched_domain ) );
100 }
101 $url = $this->base_dir . $url;
102
103 return $url;
104 }
105
106 protected function get_formatted_source( $image_source_data, $mimetype ) {
107 $format_url = Tiny_Helpers::replace_file_extension( $mimetype, $image_source_data['path'] );
108 $local_path = $this->get_local_path( $format_url );
109 if ( empty( $local_path ) ) {
110 return null;
111 }
112
113 $exists_local = file_exists( $local_path );
114 if ( $exists_local ) {
115 return array(
116 'src' => $format_url,
117 'size' => $image_source_data['size'],
118 'type' => $mimetype,
119 );
120 }
121 return null;
122 }
123
124 /**
125 * Retrieves the sources from the <img> or <source> element
126 *
127 * @param string $html string containg html
128 * @return array{path: string, size: string}[] The image sources
129 */
130 protected function get_image_srcsets( $html ) {
131 $result = array();
132 $srcset = $this::get_attribute_value( $html, 'srcset' );
133
134 if ( $srcset ) {
135 // Split the srcset to get individual entries
136 $srcset_entries = explode( ',', $srcset );
137
138 foreach ( $srcset_entries as $entry ) {
139 // Trim whitespace
140 $entry = trim( $entry );
141
142 // Split by whitespace to separate path and size/density descriptor
143 $parts = preg_split( '/\s+/', $entry, 2 );
144
145 if ( count( $parts ) === 2 ) {
146 // We have both path and size
147 $result[] = array(
148 'path' => $parts[0],
149 'size' => $parts[1],
150 );
151 } elseif ( count( $parts ) === 1 ) {
152 // We only have a path, will be interpreted as pixel
153 // density 1x (unusual in srcset)
154 $result[] = array(
155 'path' => $parts[0],
156 'size' => '',
157 );
158 }
159 }
160 }
161 return $result;
162 }
163
164 /**
165 * Retrieves the sources from the <img> or <source> element
166 *
167 * @return array{path: string, size: string}[] The image sources
168 */
169 private function get_image_src( $html ) {
170 $source = $this::get_attribute_value( $html, 'src' );
171 if ( ! empty( $source ) ) {
172 // No srcset, but we have a src attribute
173 return array(
174 'path' => $source,
175 'size' => '',
176 );
177 }
178 return array();
179 }
180
181
182 /**
183 * Creates one or more <source> elements if alternative formats
184 * are available.
185 *
186 * @param string $original_source_html, either <source> or <img>
187 * @return array{string} array of <source> html
188 */
189 protected function create_alternative_sources( $original_source_html ) {
190 $srcsets = $this->get_image_srcsets( $original_source_html );
191 if ( empty( $srcsets ) ) {
192 // no srcset, try src attribute
193 $src = $this->get_image_src( $original_source_html );
194 if ( ! empty( $src ) ) {
195 $srcsets[] = $src;
196 }
197 }
198
199 if ( empty( $srcsets ) ) {
200 return array();
201 }
202
203 $is_source_tag = (bool) preg_match( '#<source\b#i', $original_source_html );
204
205 $sources = array();
206 $width_descriptor = $this->get_largest_width_descriptor( $srcsets );
207
208 foreach ( $this->valid_mimetypes as $mimetype ) {
209 $srcset_parts = array();
210
211 foreach ( $srcsets as $srcset ) {
212 $alt_source = $this->get_formatted_source( $srcset, $mimetype );
213 if ( $alt_source ) {
214 $srcset_parts[] = trim( $alt_source['src'] . ' ' . $alt_source['size'] );
215 }
216 }
217
218 if (
219 $width_descriptor &&
220 ! self::srcset_contains_width_descriptor(
221 $srcset_parts,
222 $width_descriptor
223 )
224 ) {
225 continue;
226 }
227
228 if ( empty( $srcset_parts ) ) {
229 continue;
230 }
231
232 $source_attr_parts = array();
233
234 $srcset_attr = implode( ', ', $srcset_parts );
235 $source_attr_parts['srcset'] = $srcset_attr;
236
237 if ( $is_source_tag ) {
238 foreach ( array( 'sizes', 'media', 'width', 'height' ) as $attr ) {
239 $attr_value = $this->get_attribute_value( $original_source_html, $attr );
240 if ( $attr_value ) {
241 $source_attr_parts[ $attr ] = $attr_value;
242 }
243 }
244 } else {
245 $sizes_value = $this->get_attribute_value( $original_source_html, 'sizes' );
246 if ( $sizes_value ) {
247 $source_attr_parts['sizes'] = $sizes_value;
248 }
249 }
250
251 $source_attr_parts['type'] = $mimetype;
252 $source_parts = array( '<source' );
253 foreach ( $source_attr_parts as $source_attr_name => $source_attr_val ) {
254 $source_parts[] = $source_attr_name . '="' . esc_attr( $source_attr_val ) . '"';
255 }
256 $source_parts[] = '/>';
257 $sources[] = implode( ' ', $source_parts );
258 } // End foreach().
259
260 return $sources;
261 }
262
263 /**
264 * Returns the largest numeric width descriptor
265 * (e.g. 2000 from "2000w") found in the srcset data.
266 *
267 * @param array<array{path: string, size: string}> $srcsets
268 * @return int
269 */
270 public static function get_largest_width_descriptor( $srcsets ) {
271 $largest = 0;
272
273 foreach ( $srcsets as $srcset ) {
274 if ( empty( $srcset['size'] ) ) {
275 continue;
276 }
277
278 if ( preg_match( '/(\d+)w/', $srcset['size'], $matches ) ) {
279 $width = (int) $matches[1];
280 if ( $width > $largest ) {
281 $largest = $width;
282 }
283 }
284 }
285
286 return $largest;
287 }
288
289 /**
290 * Determines whether a srcset list contains the provided width descriptor.
291 *
292 * @param string[] $srcset_parts
293 * @param int $width_descriptor
294 * @return bool true if width is in srcset
295 */
296 public static function srcset_contains_width_descriptor( $srcset_parts, $width_descriptor ) {
297 if ( empty( $srcset_parts ) || $width_descriptor <= 0 ) {
298 return false;
299 }
300
301 $suffix = ' ' . $width_descriptor . 'w';
302 $suffix_length = strlen( $suffix );
303
304 foreach ( $srcset_parts as $srcset_part ) {
305 if ( substr( $srcset_part, -$suffix_length ) === $suffix ) {
306 return true;
307 }
308 }
309
310 return false;
311 }
312 }
313