PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.6.8
TinyPNG – JPEG, PNG & WebP image compression v3.6.8
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 5 months ago config 5 months ago css 5 months ago data 4 years ago images 4 years ago js 5 months ago vendor 1 year ago views 5 months ago class-tiny-bulk-optimization.php 5 months ago class-tiny-cli.php 5 months ago class-tiny-compress-client.php 5 months ago class-tiny-compress-fopen.php 5 months ago class-tiny-compress.php 5 months ago class-tiny-diagnostics.php 5 months ago class-tiny-exception.php 5 months ago class-tiny-helpers.php 5 months ago class-tiny-image-size.php 5 months ago class-tiny-image.php 5 months ago class-tiny-logger.php 5 months ago class-tiny-notices.php 5 months ago class-tiny-php.php 5 months ago class-tiny-picture.php 5 months ago class-tiny-plugin.php 5 months ago class-tiny-settings.php 5 months ago class-tiny-source-base.php 5 months ago class-tiny-source-image.php 5 months ago class-tiny-source-picture.php 5 months ago class-tiny-wp-base.php 5 months ago
class-tiny-source-base.php
304 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 * @return array{path: string, size: string}[] The image sources
128 */
129 protected function get_image_srcsets( $html ) {
130 $result = array();
131 $srcset = $this::get_attribute_value( $html, 'srcset' );
132
133 if ( $srcset ) {
134 // Split the srcset to get individual entries
135 $srcset_entries = explode( ',', $srcset );
136
137 foreach ( $srcset_entries as $entry ) {
138 // Trim whitespace
139 $entry = trim( $entry );
140
141 // Split by whitespace to separate path and size/density descriptor
142 $parts = preg_split( '/\s+/', $entry, 2 );
143
144 if ( count( $parts ) === 2 ) {
145 // We have both path and size
146 $result[] = array(
147 'path' => $parts[0],
148 'size' => $parts[1],
149 );
150 } elseif ( count( $parts ) === 1 ) {
151 // We only have a path, will be interpreted as pixel
152 // density 1x (unusual in srcset)
153 $result[] = array(
154 'path' => $parts[0],
155 'size' => '',
156 );
157 }
158 }
159 }
160 return $result;
161 }
162
163 /**
164 * Retrieves the sources from the <img> or <source> element
165 *
166 * @return array{path: string, size: string}[] The image sources
167 */
168 private function get_image_src( $html ) {
169 $source = $this::get_attribute_value( $html, 'src' );
170 if ( ! empty( $source ) ) {
171 // No srcset, but we have a src attribute
172 return array(
173 'path' => $source,
174 'size' => '',
175 );
176 }
177 return array();
178 }
179
180
181 /**
182 * Creates one or more <source> elements if alternative formats
183 * are available.
184 *
185 * @param string $original_source_html, either <source> or <img>
186 * @return array{string} array of <source> html
187 */
188 protected function create_alternative_sources( $original_source_html ) {
189 $srcsets = $this->get_image_srcsets( $original_source_html );
190 if ( empty( $srcsets ) ) {
191 // no srcset, try src attribute
192 $srcsets[] = $this->get_image_src( $original_source_html );
193 }
194
195 if ( empty( $srcsets ) ) {
196 return array();
197 }
198
199 $is_source_tag = (bool) preg_match( '#<source\b#i', $original_source_html );
200
201 $sources = array();
202 $width_descriptor = $this->get_largest_width_descriptor( $srcsets );
203
204 foreach ( $this->valid_mimetypes as $mimetype ) {
205 $srcset_parts = array();
206
207 foreach ( $srcsets as $srcset ) {
208 $alt_source = $this->get_formatted_source( $srcset, $mimetype );
209 if ( $alt_source ) {
210 $srcset_parts[] = trim( $alt_source['src'] . ' ' . $alt_source['size'] );
211 }
212 }
213
214 if (
215 $width_descriptor &&
216 ! self::srcset_contains_width_descriptor(
217 $srcset_parts,
218 $width_descriptor
219 )
220 ) {
221 continue;
222 }
223
224 if ( empty( $srcset_parts ) ) {
225 continue;
226 }
227
228 $source_attr_parts = array();
229
230 $srcset_attr = implode( ', ', $srcset_parts );
231 $source_attr_parts['srcset'] = $srcset_attr;
232
233 if ( $is_source_tag ) {
234 foreach ( array( 'sizes', 'media', 'width', 'height' ) as $attr ) {
235 $attr_value = $this->get_attribute_value( $original_source_html, $attr );
236 if ( $attr_value ) {
237 $source_attr_parts[ $attr ] = $attr_value;
238 }
239 }
240 }
241
242 $source_attr_parts['type'] = $mimetype;
243 $source_parts = array( '<source' );
244 foreach ( $source_attr_parts as $source_attr_name => $source_attr_val ) {
245 $source_parts[] = $source_attr_name . '="' . esc_attr( $source_attr_val ) . '"';
246 }
247 $source_parts[] = '/>';
248 $sources[] = implode( ' ', $source_parts );
249 } // End foreach().
250
251 return $sources;
252 }
253
254 /**
255 * Returns the largest numeric width descriptor
256 * (e.g. 2000 from "2000w") found in the srcset data.
257 *
258 * @param array<array{path: string, size: string}> $srcsets
259 * @return int
260 */
261 public static function get_largest_width_descriptor( $srcsets ) {
262 $largest = 0;
263
264 foreach ( $srcsets as $srcset ) {
265 if ( empty( $srcset['size'] ) ) {
266 continue;
267 }
268
269 if ( preg_match( '/(\d+)w/', $srcset['size'], $matches ) ) {
270 $width = (int) $matches[1];
271 if ( $width > $largest ) {
272 $largest = $width;
273 }
274 }
275 }
276
277 return $largest;
278 }
279
280 /**
281 * Determines whether a srcset list contains the provided width descriptor.
282 *
283 * @param string[] $srcset_parts
284 * @param int $width_descriptor
285 * @return bool true if width is in srcset
286 */
287 public static function srcset_contains_width_descriptor( $srcset_parts, $width_descriptor ) {
288 if ( empty( $srcset_parts ) || $width_descriptor <= 0 ) {
289 return false;
290 }
291
292 $suffix = ' ' . $width_descriptor . 'w';
293 $suffix_length = strlen( $suffix );
294
295 foreach ( $srcset_parts as $srcset_part ) {
296 if ( substr( $srcset_part, -$suffix_length ) === $suffix ) {
297 return true;
298 }
299 }
300
301 return false;
302 }
303 }
304