Changeset 3427516
- Timestamp:
- 12/26/2025 03:30:29 AM (5 weeks ago)
- Location:
- avif-local-support
- Files:
-
- 6 added
- 2 deleted
- 12 edited
- 1 copied
-
tags/0.5.3 (copied) (copied from avif-local-support/trunk)
-
tags/0.5.3/assets/thumbhash-decoder.min.js (modified) (1 diff)
-
tags/0.5.3/avif-local-support.php (modified) (4 diffs)
-
tags/0.5.3/cursor.md (deleted)
-
tags/0.5.3/includes/LQIP_CLI.php (modified) (6 diffs)
-
tags/0.5.3/includes/ThumbHash.php (modified) (2 diffs)
-
tags/0.5.3/lib (added)
-
tags/0.5.3/lib/Thumbhash (added)
-
tags/0.5.3/lib/Thumbhash/Thumbhash.php (added)
-
tags/0.5.3/readme.txt (modified) (2 diffs)
-
tags/0.5.3/uninstall.php (modified) (5 diffs)
-
trunk/assets/thumbhash-decoder.min.js (modified) (1 diff)
-
trunk/avif-local-support.php (modified) (4 diffs)
-
trunk/cursor.md (deleted)
-
trunk/includes/LQIP_CLI.php (modified) (6 diffs)
-
trunk/includes/ThumbHash.php (modified) (2 diffs)
-
trunk/lib (added)
-
trunk/lib/Thumbhash (added)
-
trunk/lib/Thumbhash/Thumbhash.php (added)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/uninstall.php (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
avif-local-support/tags/0.5.3/assets/thumbhash-decoder.min.js
r3427506 r3427516 6 6 * @license MIT 7 7 */ 8 (function () { 9 'use strict'; 10 11 // Cached references for performance 12 var doc = document, 13 M = Math, 14 PI = M.PI, 15 cos = M.cos, 16 round = M.round, 17 max = M.max, 18 min = M.min; 19 20 // Cache for decoded data URLs 21 var cache = {}; 22 23 // Reusable canvas for encoding 24 var canvas, ctx; 25 26 function decode(hash) { 27 // Decode base64 only once 28 var bin = atob(hash), 29 len = bin.length, 30 bytes = new Uint8Array(len), 31 i; 32 33 for (i = 0; i < len; i++) bytes[i] = bin.charCodeAt(i); 34 35 var h24 = bytes[0] | bytes[1] << 8 | bytes[2] << 16, 36 h16 = bytes[3] | bytes[4] << 8, 37 l_dc = (h24 & 63) / 63, 38 p_dc = ((h24 >> 6) & 63) / 31.5 - 1, 39 q_dc = ((h24 >> 12) & 63) / 31.5 - 1, 40 l_scale = ((h24 >> 18) & 31) / 31, 41 hasAlpha = h24 >> 23 !== 0, 42 p_scale = ((h16 >> 3) & 63) / 63, 43 q_scale = ((h16 >> 9) & 63) / 63, 44 isLandscape = h16 >> 15 !== 0, 45 lx = max(3, isLandscape ? (hasAlpha ? 5 : 7) : h16 & 7), 46 ly = max(3, isLandscape ? h16 & 7 : (hasAlpha ? 5 : 7)), 47 a_dc = 1, 48 a_scale = 0, 49 ac_start = 5, 50 ac_idx = 0; 51 52 if (hasAlpha) { 53 a_dc = (bytes[5] & 15) / 15; 54 a_scale = (bytes[5] >> 4 & 15) / 15; 55 ac_start = 6; 56 } 57 58 function readAC(nx, ny, scale) { 59 var ac = [], cy, cx, nibble; 60 for (cy = 0; cy < ny; cy++) { 61 for (cx = 0; cx < nx; cx++) { 62 if (cx || cy) { 63 nibble = ac_idx & 1 ? bytes[ac_start + (ac_idx >> 1)] >> 4 : bytes[ac_start + (ac_idx >> 1)] & 15; 64 ac.push((nibble / 7.5 - 1) * scale); 65 ac_idx++; 66 } 67 } 68 } 69 return ac; 70 } 71 72 var l_ac = readAC(lx, ly, l_scale), 73 p_ac = readAC(3, 3, p_scale * 1.25), 74 q_ac = readAC(3, 3, q_scale * 1.25), 75 a_ac = hasAlpha ? readAC(5, 5, a_scale) : null; 76 77 // Calculate dimensions from header (inline aspect ratio calc) 78 var alpha = ((bytes[0] | bytes[1] << 8 | bytes[2] << 16) >> 23 & 1) !== 0, 79 landscape = ((bytes[3] | bytes[4] << 8) >> 15 & 1) !== 0, 80 _lx = landscape ? (alpha ? 5 : 7) : (bytes[3] | bytes[4] << 8 >> 8) & 7, 81 _ly = landscape ? (bytes[3] | bytes[4] << 8 >> 8) & 7 : (alpha ? 5 : 7), 82 ratio = (landscape ? 32 : _lx) / (landscape ? _ly : 32), 83 w = round(ratio > 1 ? 32 : 32 * ratio), 84 h = round(ratio > 1 ? 32 / ratio : 32), 85 rgba = new Uint8Array(w * h * 4), 86 x, y, l, p, q, a, j, cx, cy, fx, fy, fy_l, fy_p, fy_a, idx, b_, r, g, b, 87 piH = PI / h, 88 piW = PI / w; 89 90 for (y = 0; y < h; y++) { 91 var yFactor = (y + 0.5); 92 93 // Precompute fy values for luminance 94 fy_l = []; 95 for (i = 0; i < ly; i++) fy_l[i] = cos(piH * yFactor * i); 96 97 // Precompute fy values for PQ (always 3) 98 fy_p = [cos(0), cos(piH * yFactor), cos(piH * yFactor * 2)]; 99 100 // Precompute fy values for alpha if needed 101 if (hasAlpha) { 102 fy_a = []; 103 for (i = 0; i < 5; i++) fy_a[i] = cos(piH * yFactor * i); 104 } 105 106 for (x = 0; x < w; x++) { 107 var xFactor = (x + 0.5); 108 l = l_dc; p = p_dc; q = q_dc; a = a_dc; 109 110 // Luminance 111 j = 0; 112 for (cy = 0; cy < ly; cy++) { 113 fy = fy_l[cy]; 114 for (cx = 0; cx < lx; cx++) { 115 if (cx || cy) l += l_ac[j++] * cos(piW * xFactor * cx) * fy; 116 } 117 } 118 119 // P and Q channels 120 j = 0; 121 for (cy = 0; cy < 3; cy++) { 122 for (cx = 0; cx < 3; cx++) { 123 if (cx || cy) { 124 fx = cos(piW * xFactor * cx); 125 p += p_ac[j] * fx * fy_p[cy]; 126 q += q_ac[j] * fx * fy_p[cy]; 127 j++; 128 } 129 } 130 } 131 132 // Alpha channel 133 if (hasAlpha) { 134 j = 0; 135 for (cy = 0; cy < 5; cy++) { 136 for (cx = 0; cx < 5; cx++) { 137 if (cx || cy) a += a_ac[j++] * cos(piW * xFactor * cx) * fy_a[cy]; 138 } 139 } 140 } 141 142 // Convert LPQ to RGB 143 b_ = l - 2 / 3 * p; 144 r = (3 * l - b_ + q) / 2; 145 g = r - q; 146 b = b_; 147 148 idx = (y * w + x) * 4; 149 rgba[idx] = max(0, min(255, round(255 * r))); 150 rgba[idx + 1] = max(0, min(255, round(255 * g))); 151 rgba[idx + 2] = max(0, min(255, round(255 * b))); 152 rgba[idx + 3] = max(0, min(255, round(255 * a))); 153 } 154 } 155 156 return { w: w, h: h, rgba: rgba }; 157 } 158 159 function toDataURL(hash) { 160 if (cache[hash]) return cache[hash]; 161 try { 162 var d = decode(hash); 163 // Reuse canvas 164 if (!canvas) { 165 canvas = doc.createElement('canvas'); 166 ctx = canvas.getContext('2d'); 167 } 168 canvas.width = d.w; 169 canvas.height = d.h; 170 var img = ctx.createImageData(d.w, d.h); 171 img.data.set(d.rgba); 172 ctx.putImageData(img, 0, 0); 173 return cache[hash] = canvas.toDataURL(); 174 } catch (e) { 175 return ''; 176 } 177 } 178 179 function apply(img) { 180 if (img._th) return; 181 img._th = 1; 182 183 var hash = img.getAttribute('data-thumbhash'); 184 if (!hash) return; 185 186 var url = toDataURL(hash); 187 if (!url) return; 188 189 var el = img.closest('picture') || img, 190 s = el.style; 191 192 s.backgroundImage = 'url(' + url + ')'; 193 s.backgroundSize = 'cover'; 194 s.backgroundPosition = 'center'; 195 s.backgroundRepeat = 'no-repeat'; 196 if (el.tagName === 'PICTURE') s.display = 'block'; 197 198 // Add loading class for CSS targeting (e.g. blur) 199 if (el.classList) el.classList.add('thumbhash-loading'); 200 201 function clear() { 202 s.backgroundImage = s.backgroundSize = s.backgroundPosition = s.backgroundRepeat = ''; 203 if (el.classList) el.classList.remove('thumbhash-loading'); 204 } 205 206 if (img.complete && img.naturalWidth) { 207 clear(); 208 } else { 209 img.addEventListener('load', clear, { once: true }); 210 img.addEventListener('error', clear, { once: true }); 211 } 212 } 213 214 function process(node) { 215 if (!node) return; 216 if (node.nodeType === 1 && node.tagName === 'IMG' && node.hasAttribute('data-thumbhash')) { 217 apply(node); 218 } 219 if (node.querySelectorAll) { 220 var imgs = node.querySelectorAll('img[data-thumbhash]'), i; 221 for (i = 0; i < imgs.length; i++) apply(imgs[i]); 222 } 223 } 224 225 // Start MutationObserver immediately 226 new MutationObserver(function (muts) { 227 for (var i = 0; i < muts.length; i++) { 228 var nodes = muts[i].addedNodes; 229 for (var j = 0; j < nodes.length; j++) process(nodes[j]); 230 } 231 }).observe(doc.documentElement, { childList: true, subtree: true }); 232 233 // Handle cached/already-loaded pages 234 if (doc.readyState !== 'loading') { 235 process(doc.body || doc.documentElement); 236 } else { 237 doc.addEventListener('DOMContentLoaded', function () { process(doc.body); }); 238 } 239 })(); 8 !function(){"use strict";var t,e,a=document,r=Math,n=r.PI,o=r.cos,i=r.round,d=r.max,u=r.min,c={};function s(r){if(c[r])return c[r];try{var s=function(t){var e,a=atob(t),r=a.length,c=new Uint8Array(r);for(e=0;e<r;e++)c[e]=a.charCodeAt(e);var s=c[0]|c[1]<<8|c[2]<<16,h=c[3]|c[4]<<8,f=(63&s)/63,l=(s>>6&63)/31.5-1,g=(s>>12&63)/31.5-1,b=(s>>18&31)/31,v=!!(s>>23),m=(h>>3&63)/63,y=(h>>9&63)/63,L=!!(h>>15),k=d(3,L?v?5:7:7&h),p=d(3,L?7&h:v?5:7),w=1,A=0,E=5,I=0;function S(t,e,a){var r,n,o,i=[];for(r=0;r<e;r++)for(n=0;n<t;n++)(n||r)&&(o=1&I?c[E+(I>>1)]>>4:15&c[E+(I>>1)],i.push((o/7.5-1)*a),I++);return i}v&&(w=(15&c[5])/15,A=(c[5]>>4&15)/15,E=6);var C,D,M,P,R,U,N,q,x,z,O,T,_,G,W,j,B,F,H,J=S(k,p,b),K=S(3,3,1.25*m),Q=S(3,3,1.25*y),V=v?S(5,5,A):null,X=!!((c[0]|c[1]<<8|c[2]<<16)>>23&1),Y=!!((c[3]|c[4]<<8)>>15&1),Z=Y?X?5:7:7&(c[3]|c[4]<<8>>8),$=Y?7&(c[3]|c[4]<<8>>8):X?5:7,tt=(Y?32:Z)/(Y?$:32),et=i(tt>1?32:32*tt),at=i(tt>1?32/tt:32),rt=new Uint8Array(et*at*4),nt=n/at,ot=n/et;for(D=0;D<at;D++){var it=D+.5;for(T=[],e=0;e<p;e++)T[e]=o(nt*it*e);if(_=[o(0),o(nt*it),o(nt*it*2)],v)for(G=[],e=0;e<5;e++)G[e]=o(nt*it*e);for(C=0;C<et;C++){var dt=C+.5;for(M=f,P=l,R=g,U=w,N=0,x=0;x<p;x++)for(O=T[x],q=0;q<k;q++)(q||x)&&(M+=J[N++]*o(ot*dt*q)*O);for(N=0,x=0;x<3;x++)for(q=0;q<3;q++)(q||x)&&(z=o(ot*dt*q),P+=K[N]*z*_[x],R+=Q[N]*z*_[x],N++);if(v)for(N=0,x=0;x<5;x++)for(q=0;q<5;q++)(q||x)&&(U+=V[N++]*o(ot*dt*q)*G[x]);F=(B=(3*M-(j=M-2/3*P)+R)/2)-R,H=j,rt[W=4*(D*et+C)]=d(0,u(255,i(255*B))),rt[W+1]=d(0,u(255,i(255*F))),rt[W+2]=d(0,u(255,i(255*H))),rt[W+3]=d(0,u(255,i(255*U)))}}return{w:et,h:at,rgba:rt}}(r);t||(t=a.createElement("canvas"),e=t.getContext("2d")),t.width=s.w,t.height=s.h;var h=e.createImageData(s.w,s.h);return h.data.set(s.rgba),e.putImageData(h,0,0),c[r]=t.toDataURL()}catch(t){return""}}function h(t){if(!t._th){t._th=1;var e=t.getAttribute("data-thumbhash");if(e){var a=s(e);if(a){var r=t.closest("picture")||t,n=r.style;n.backgroundImage="url("+a+")",n.backgroundSize="cover",n.backgroundPosition="center",n.backgroundRepeat="no-repeat","PICTURE"===r.tagName&&(n.display="block"),r.classList&&r.classList.add("thumbhash-loading"),t.complete&&t.naturalWidth?o():(t.addEventListener("load",o,{once:!0}),t.addEventListener("error",o,{once:!0}))}}}function o(){n.backgroundImage=n.backgroundSize=n.backgroundPosition=n.backgroundRepeat="",r.classList&&r.classList.remove("thumbhash-loading")}}function f(t){if(t&&(1===t.nodeType&&"IMG"===t.tagName&&t.hasAttribute("data-thumbhash")&&h(t),t.querySelectorAll)){var e,a=t.querySelectorAll("img[data-thumbhash]");for(e=0;e<a.length;e++)h(a[e])}}new MutationObserver(function(t){for(var e=0;e<t.length;e++)for(var a=t[e].addedNodes,r=0;r<a.length;r++)f(a[r])}).observe(a.documentElement,{childList:!0,subtree:!0}),"loading"!==a.readyState?f(a.body||a.documentElement):a.addEventListener("DOMContentLoaded",function(){f(a.body)})}(); -
avif-local-support/tags/0.5.3/avif-local-support.php
r3427508 r3427516 7 7 * Plugin URI: https://github.com/ddegner/avif-local-support 8 8 * Description: Unified AVIF support and conversion. Local-first processing with a strong focus on image quality when converting JPEGs. 9 * Version: 0.5. 19 * Version: 0.5.3 10 10 * Author: David Degner 11 11 * Author URI: https://www.DavidDegner.com … … 22 22 23 23 // Define constants 24 \define('AVIFLOSU_VERSION', '0.5. 1');24 \define('AVIFLOSU_VERSION', '0.5.3'); 25 25 \define('AVIFLOSU_PLUGIN_FILE', __FILE__); 26 26 \define('AVIFLOSU_PLUGIN_DIR', plugin_dir_path(__FILE__)); … … 28 28 \define('AVIFLOSU_INC_DIR', AVIFLOSU_PLUGIN_DIR . 'includes'); 29 29 30 // Composer autoloader for third-party dependencies 30 // Load bundled ThumbHash library 31 $thumbhashLib = AVIFLOSU_PLUGIN_DIR . 'lib/Thumbhash/Thumbhash.php'; 32 if (file_exists($thumbhashLib)) { 33 require_once $thumbhashLib; 34 } 35 36 // Composer autoloader for other third-party dependencies (if needed) 31 37 $composerAutoloader = AVIFLOSU_PLUGIN_DIR . 'vendor/autoload.php'; 32 38 if (file_exists($composerAutoloader)) { … … 105 111 add_option('aviflosu_engine_mode', 'auto'); 106 112 add_option('aviflosu_cli_path', ''); 107 // Beta features (off by default)113 // LQIP (ThumbHash) settings 108 114 add_option('aviflosu_thumbhash_enabled', false); 115 add_option('aviflosu_thumbhash_size', 100); 116 add_option('aviflosu_lqip_generate_on_upload', true); 117 add_option('aviflosu_lqip_generate_via_schedule', true); 109 118 } 110 119 -
avif-local-support/tags/0.5.3/includes/LQIP_CLI.php
r3427508 r3427516 82 82 * : Show what would be generated without actually generating 83 83 * 84 * [--limit=<number>] 85 * : Limit the number of images to process (useful for testing) 86 * 87 * [--verbose] 88 * : Show detailed output for each image being processed 89 * 84 90 * ## EXAMPLES 85 91 * … … 103 109 } 104 110 111 if (!ThumbHash::isLibraryAvailable()) { 112 \WP_CLI::error('ThumbHash library not found. Please run "composer install" in the plugin directory to install dependencies.'); 113 return; 114 } 115 105 116 $all = isset($assoc_args['all']); 106 117 $dryRun = isset($assoc_args['dry-run']); 118 $limit = isset($assoc_args['limit']) ? (int) $assoc_args['limit'] : 0; 119 $verbose = isset($assoc_args['verbose']); 107 120 $attachmentId = !empty($args[0]) ? (int) $args[0] : 0; 108 121 … … 117 130 } 118 131 119 $this->generateAll($dryRun );132 $this->generateAll($dryRun, $limit, $verbose); 120 133 } 121 134 … … 212 225 /** 213 226 * Generate LQIP for all attachments missing them. 214 */ 215 private function generateAll(bool $dryRun): void 227 * 228 * @param bool $dryRun Whether this is a dry run. 229 * @param int $limit Maximum number of images to process (0 = no limit). 230 * @param bool $verbose Show detailed output for each image. 231 */ 232 private function generateAll(bool $dryRun, int $limit = 0, bool $verbose = false): void 216 233 { 217 234 $stats = ThumbHash::getStats(); … … 262 279 $skipped = 0; 263 280 $failed = 0; 264 265 foreach ($query->posts as $attachmentId) { 281 $postsToProcess = $query->posts; 282 283 // Apply limit if specified 284 if ($limit > 0 && count($postsToProcess) > $limit) { 285 $postsToProcess = array_slice($postsToProcess, 0, $limit); 286 \WP_CLI::line(sprintf('Limiting processing to first %d images...', $limit)); 287 \WP_CLI::line(''); 288 } 289 290 $totalToProcess = count($postsToProcess); 291 292 foreach ($postsToProcess as $index => $attachmentId) { 266 293 // Skip if already has LQIP 267 294 $existing = get_post_meta($attachmentId, ThumbHash::getMetaKey(), true); 268 295 if (!empty($existing) && is_array($existing)) { 269 296 ++$skipped; 297 ++$processed; 298 $this->printProgress($processed, $totalMissing, $startTime); 270 299 continue; 271 300 } 272 301 273 ThumbHash::generateForAttachment((int) $attachmentId); 302 // Show which image we're processing (helps identify hanging images) 303 $currentNum = $index + 1; 304 if ($verbose || $currentNum % 10 === 0 || $currentNum === 1) { 305 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- WP-CLI progress output. 306 fwrite(STDERR, "\r" . str_repeat(' ', 80) . "\r"); 307 \WP_CLI::line(sprintf('Processing image %d/%d (ID: %d)...', $currentNum, $totalToProcess, $attachmentId)); 308 } 309 310 // Clear any previous error 311 ThumbHash::getLastError(); 312 313 // Try to generate with error handling 314 try { 315 $memoryBefore = memory_get_usage(true); 316 ThumbHash::generateForAttachment((int) $attachmentId); 317 $memoryAfter = memory_get_usage(true); 318 319 // Check for memory issues 320 if ($memoryAfter - $memoryBefore > 50 * 1024 * 1024) { // More than 50MB 321 \WP_CLI::warning(sprintf('High memory usage for attachment ID %d: %s', $attachmentId, size_format($memoryAfter - $memoryBefore))); 322 } 323 } catch (\Throwable $e) { 324 \WP_CLI::warning(sprintf('Exception generating LQIP for attachment ID %d: %s', $attachmentId, $e->getMessage())); 325 ++$failed; 326 ++$processed; 327 $this->printProgress($processed, $totalMissing, $startTime); 328 continue; 329 } 274 330 275 331 // Verify it was generated … … 281 337 } else { 282 338 ++$failed; 339 ++$processed; 340 $error = ThumbHash::getLastError(); 341 if ($error && ($currentNum % 10 === 0 || $currentNum <= 5)) { 342 \WP_CLI::warning(sprintf('Failed to generate LQIP for attachment ID %d: %s', $attachmentId, $error)); 343 } 344 $this->printProgress($processed, $totalMissing, $startTime); 345 } 346 347 // Force garbage collection every 50 images to prevent memory buildup 348 if ($processed % 50 === 0) { 349 gc_collect_cycles(); 283 350 } 284 351 } -
avif-local-support/tags/0.5.3/includes/ThumbHash.php
r3427508 r3427516 61 61 62 62 /** 63 * Check if the ThumbHash library is available. 64 * 65 * @return bool True if the library class exists, false otherwise. 66 */ 67 public static function isLibraryAvailable(): bool 68 { 69 return class_exists('Thumbhash\Thumbhash'); 70 } 71 72 /** 63 73 * Generate ThumbHash string for an image file. 64 74 * … … 68 78 public static function generate(string $imagePath): ?string 69 79 { 80 // Check if the ThumbHash library is available 81 if (!self::isLibraryAvailable()) { 82 self::$lastError = 'ThumbHash library not found. Please run "composer install" in the plugin directory to install dependencies.'; 83 if (class_exists(Logger::class)) { 84 (new Logger())->addLog('error', 'ThumbHash library not available', array( 85 'path' => $imagePath, 86 'error' => 'Thumbhash\Thumbhash class not found. Composer dependencies may not be installed.' 87 )); 88 } 89 return null; 90 } 91 70 92 if (!file_exists($imagePath) || !is_readable($imagePath)) { 71 93 self::$lastError = "File not found or unreadable: $imagePath"; -
avif-local-support/tags/0.5.3/readme.txt
r3427508 r3427516 4 4 Requires at least: 6.8 5 5 Tested up to: 6.9 6 Stable tag: 0.5. 16 Stable tag: 0.5.3 7 7 Requires PHP: 8.3 8 8 License: GPLv2 or later … … 198 198 199 199 == Changelog == 200 201 = 0.5.3 = 202 203 - Fix: Added missing LQIP options to plugin activation (thumbhash_size, generate_on_upload, generate_via_schedule) 204 - Fix: Added missing LQIP options to uninstall cleanup for complete data removal 205 - Fix: Properly minified thumbhash-decoder.min.js (62% size reduction) 206 - Fix: Excluded developer documentation from WordPress plugin distribution 207 208 = 0.5.2 = 209 210 - Feature: Bundled ThumbHash library — no Composer dependency required on deployment 211 - Enhancement: Improved LQIP generation with better error handling, progress reporting, and memory management 212 - Enhancement: Added `--limit` and `--verbose` options to `wp lqip generate` command 213 - Fix: Resolved hanging issue in `wp lqip generate --all` command with better error handling and progress output 214 - Fix: Clear error messages when ThumbHash library is unavailable 200 215 201 216 = 0.5.1 = -
avif-local-support/tags/0.5.3/uninstall.php
r3427506 r3427516 3 3 declare(strict_types=1); 4 4 5 if ( ! defined( 'WP_UNINSTALL_PLUGIN' )) {5 if (!defined('WP_UNINSTALL_PLUGIN')) { 6 6 exit; 7 7 } … … 22 22 'aviflosu_cli_args', 23 23 'aviflosu_cli_env', 24 // Beta features24 // LQIP (ThumbHash) settings 25 25 'aviflosu_thumbhash_enabled', 26 'aviflosu_thumbhash_size', 27 'aviflosu_lqip_generate_on_upload', 28 'aviflosu_lqip_generate_via_schedule', 26 29 // legacy options left behind in older versions 27 30 'aviflosu_preserve_metadata', … … 30 33 ); 31 34 32 foreach ( $aviflosu_options as $aviflosu_option) {33 if ( get_option( $aviflosu_option ) !== false) {34 delete_option( $aviflosu_option);35 foreach ($aviflosu_options as $aviflosu_option) { 36 if (get_option($aviflosu_option) !== false) { 37 delete_option($aviflosu_option); 35 38 } 36 39 } 37 40 38 41 // Delete transients using WordPress functions. 39 delete_transient( 'aviflosu_file_cache');40 delete_transient( 'aviflosu_logs');41 delete_transient( 'aviflosu_stop_conversion');42 delete_transient('aviflosu_file_cache'); 43 delete_transient('aviflosu_logs'); 44 delete_transient('aviflosu_stop_conversion'); 42 45 43 46 // Delete ImageMagick CLI cache transients (with wildcard pattern). … … 45 48 // Note: Direct DB queries won't clear object cache entries (Redis/Memcached), 46 49 // but those will naturally expire based on their TTL. 47 if ( ! wp_using_ext_object_cache()) {50 if (!wp_using_ext_object_cache()) { 48 51 global $wpdb; 49 52 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 50 $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_aviflosu_imc_%' OR option_name LIKE '_transient_timeout_aviflosu_imc_%'");53 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_aviflosu_imc_%' OR option_name LIKE '_transient_timeout_aviflosu_imc_%'"); 51 54 } 52 55 … … 54 57 global $wpdb; 55 58 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 56 $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_aviflosu_thumbhash' ));59 $wpdb->delete($wpdb->postmeta, array('meta_key' => '_aviflosu_thumbhash')); -
avif-local-support/trunk/assets/thumbhash-decoder.min.js
r3427506 r3427516 6 6 * @license MIT 7 7 */ 8 (function () { 9 'use strict'; 10 11 // Cached references for performance 12 var doc = document, 13 M = Math, 14 PI = M.PI, 15 cos = M.cos, 16 round = M.round, 17 max = M.max, 18 min = M.min; 19 20 // Cache for decoded data URLs 21 var cache = {}; 22 23 // Reusable canvas for encoding 24 var canvas, ctx; 25 26 function decode(hash) { 27 // Decode base64 only once 28 var bin = atob(hash), 29 len = bin.length, 30 bytes = new Uint8Array(len), 31 i; 32 33 for (i = 0; i < len; i++) bytes[i] = bin.charCodeAt(i); 34 35 var h24 = bytes[0] | bytes[1] << 8 | bytes[2] << 16, 36 h16 = bytes[3] | bytes[4] << 8, 37 l_dc = (h24 & 63) / 63, 38 p_dc = ((h24 >> 6) & 63) / 31.5 - 1, 39 q_dc = ((h24 >> 12) & 63) / 31.5 - 1, 40 l_scale = ((h24 >> 18) & 31) / 31, 41 hasAlpha = h24 >> 23 !== 0, 42 p_scale = ((h16 >> 3) & 63) / 63, 43 q_scale = ((h16 >> 9) & 63) / 63, 44 isLandscape = h16 >> 15 !== 0, 45 lx = max(3, isLandscape ? (hasAlpha ? 5 : 7) : h16 & 7), 46 ly = max(3, isLandscape ? h16 & 7 : (hasAlpha ? 5 : 7)), 47 a_dc = 1, 48 a_scale = 0, 49 ac_start = 5, 50 ac_idx = 0; 51 52 if (hasAlpha) { 53 a_dc = (bytes[5] & 15) / 15; 54 a_scale = (bytes[5] >> 4 & 15) / 15; 55 ac_start = 6; 56 } 57 58 function readAC(nx, ny, scale) { 59 var ac = [], cy, cx, nibble; 60 for (cy = 0; cy < ny; cy++) { 61 for (cx = 0; cx < nx; cx++) { 62 if (cx || cy) { 63 nibble = ac_idx & 1 ? bytes[ac_start + (ac_idx >> 1)] >> 4 : bytes[ac_start + (ac_idx >> 1)] & 15; 64 ac.push((nibble / 7.5 - 1) * scale); 65 ac_idx++; 66 } 67 } 68 } 69 return ac; 70 } 71 72 var l_ac = readAC(lx, ly, l_scale), 73 p_ac = readAC(3, 3, p_scale * 1.25), 74 q_ac = readAC(3, 3, q_scale * 1.25), 75 a_ac = hasAlpha ? readAC(5, 5, a_scale) : null; 76 77 // Calculate dimensions from header (inline aspect ratio calc) 78 var alpha = ((bytes[0] | bytes[1] << 8 | bytes[2] << 16) >> 23 & 1) !== 0, 79 landscape = ((bytes[3] | bytes[4] << 8) >> 15 & 1) !== 0, 80 _lx = landscape ? (alpha ? 5 : 7) : (bytes[3] | bytes[4] << 8 >> 8) & 7, 81 _ly = landscape ? (bytes[3] | bytes[4] << 8 >> 8) & 7 : (alpha ? 5 : 7), 82 ratio = (landscape ? 32 : _lx) / (landscape ? _ly : 32), 83 w = round(ratio > 1 ? 32 : 32 * ratio), 84 h = round(ratio > 1 ? 32 / ratio : 32), 85 rgba = new Uint8Array(w * h * 4), 86 x, y, l, p, q, a, j, cx, cy, fx, fy, fy_l, fy_p, fy_a, idx, b_, r, g, b, 87 piH = PI / h, 88 piW = PI / w; 89 90 for (y = 0; y < h; y++) { 91 var yFactor = (y + 0.5); 92 93 // Precompute fy values for luminance 94 fy_l = []; 95 for (i = 0; i < ly; i++) fy_l[i] = cos(piH * yFactor * i); 96 97 // Precompute fy values for PQ (always 3) 98 fy_p = [cos(0), cos(piH * yFactor), cos(piH * yFactor * 2)]; 99 100 // Precompute fy values for alpha if needed 101 if (hasAlpha) { 102 fy_a = []; 103 for (i = 0; i < 5; i++) fy_a[i] = cos(piH * yFactor * i); 104 } 105 106 for (x = 0; x < w; x++) { 107 var xFactor = (x + 0.5); 108 l = l_dc; p = p_dc; q = q_dc; a = a_dc; 109 110 // Luminance 111 j = 0; 112 for (cy = 0; cy < ly; cy++) { 113 fy = fy_l[cy]; 114 for (cx = 0; cx < lx; cx++) { 115 if (cx || cy) l += l_ac[j++] * cos(piW * xFactor * cx) * fy; 116 } 117 } 118 119 // P and Q channels 120 j = 0; 121 for (cy = 0; cy < 3; cy++) { 122 for (cx = 0; cx < 3; cx++) { 123 if (cx || cy) { 124 fx = cos(piW * xFactor * cx); 125 p += p_ac[j] * fx * fy_p[cy]; 126 q += q_ac[j] * fx * fy_p[cy]; 127 j++; 128 } 129 } 130 } 131 132 // Alpha channel 133 if (hasAlpha) { 134 j = 0; 135 for (cy = 0; cy < 5; cy++) { 136 for (cx = 0; cx < 5; cx++) { 137 if (cx || cy) a += a_ac[j++] * cos(piW * xFactor * cx) * fy_a[cy]; 138 } 139 } 140 } 141 142 // Convert LPQ to RGB 143 b_ = l - 2 / 3 * p; 144 r = (3 * l - b_ + q) / 2; 145 g = r - q; 146 b = b_; 147 148 idx = (y * w + x) * 4; 149 rgba[idx] = max(0, min(255, round(255 * r))); 150 rgba[idx + 1] = max(0, min(255, round(255 * g))); 151 rgba[idx + 2] = max(0, min(255, round(255 * b))); 152 rgba[idx + 3] = max(0, min(255, round(255 * a))); 153 } 154 } 155 156 return { w: w, h: h, rgba: rgba }; 157 } 158 159 function toDataURL(hash) { 160 if (cache[hash]) return cache[hash]; 161 try { 162 var d = decode(hash); 163 // Reuse canvas 164 if (!canvas) { 165 canvas = doc.createElement('canvas'); 166 ctx = canvas.getContext('2d'); 167 } 168 canvas.width = d.w; 169 canvas.height = d.h; 170 var img = ctx.createImageData(d.w, d.h); 171 img.data.set(d.rgba); 172 ctx.putImageData(img, 0, 0); 173 return cache[hash] = canvas.toDataURL(); 174 } catch (e) { 175 return ''; 176 } 177 } 178 179 function apply(img) { 180 if (img._th) return; 181 img._th = 1; 182 183 var hash = img.getAttribute('data-thumbhash'); 184 if (!hash) return; 185 186 var url = toDataURL(hash); 187 if (!url) return; 188 189 var el = img.closest('picture') || img, 190 s = el.style; 191 192 s.backgroundImage = 'url(' + url + ')'; 193 s.backgroundSize = 'cover'; 194 s.backgroundPosition = 'center'; 195 s.backgroundRepeat = 'no-repeat'; 196 if (el.tagName === 'PICTURE') s.display = 'block'; 197 198 // Add loading class for CSS targeting (e.g. blur) 199 if (el.classList) el.classList.add('thumbhash-loading'); 200 201 function clear() { 202 s.backgroundImage = s.backgroundSize = s.backgroundPosition = s.backgroundRepeat = ''; 203 if (el.classList) el.classList.remove('thumbhash-loading'); 204 } 205 206 if (img.complete && img.naturalWidth) { 207 clear(); 208 } else { 209 img.addEventListener('load', clear, { once: true }); 210 img.addEventListener('error', clear, { once: true }); 211 } 212 } 213 214 function process(node) { 215 if (!node) return; 216 if (node.nodeType === 1 && node.tagName === 'IMG' && node.hasAttribute('data-thumbhash')) { 217 apply(node); 218 } 219 if (node.querySelectorAll) { 220 var imgs = node.querySelectorAll('img[data-thumbhash]'), i; 221 for (i = 0; i < imgs.length; i++) apply(imgs[i]); 222 } 223 } 224 225 // Start MutationObserver immediately 226 new MutationObserver(function (muts) { 227 for (var i = 0; i < muts.length; i++) { 228 var nodes = muts[i].addedNodes; 229 for (var j = 0; j < nodes.length; j++) process(nodes[j]); 230 } 231 }).observe(doc.documentElement, { childList: true, subtree: true }); 232 233 // Handle cached/already-loaded pages 234 if (doc.readyState !== 'loading') { 235 process(doc.body || doc.documentElement); 236 } else { 237 doc.addEventListener('DOMContentLoaded', function () { process(doc.body); }); 238 } 239 })(); 8 !function(){"use strict";var t,e,a=document,r=Math,n=r.PI,o=r.cos,i=r.round,d=r.max,u=r.min,c={};function s(r){if(c[r])return c[r];try{var s=function(t){var e,a=atob(t),r=a.length,c=new Uint8Array(r);for(e=0;e<r;e++)c[e]=a.charCodeAt(e);var s=c[0]|c[1]<<8|c[2]<<16,h=c[3]|c[4]<<8,f=(63&s)/63,l=(s>>6&63)/31.5-1,g=(s>>12&63)/31.5-1,b=(s>>18&31)/31,v=!!(s>>23),m=(h>>3&63)/63,y=(h>>9&63)/63,L=!!(h>>15),k=d(3,L?v?5:7:7&h),p=d(3,L?7&h:v?5:7),w=1,A=0,E=5,I=0;function S(t,e,a){var r,n,o,i=[];for(r=0;r<e;r++)for(n=0;n<t;n++)(n||r)&&(o=1&I?c[E+(I>>1)]>>4:15&c[E+(I>>1)],i.push((o/7.5-1)*a),I++);return i}v&&(w=(15&c[5])/15,A=(c[5]>>4&15)/15,E=6);var C,D,M,P,R,U,N,q,x,z,O,T,_,G,W,j,B,F,H,J=S(k,p,b),K=S(3,3,1.25*m),Q=S(3,3,1.25*y),V=v?S(5,5,A):null,X=!!((c[0]|c[1]<<8|c[2]<<16)>>23&1),Y=!!((c[3]|c[4]<<8)>>15&1),Z=Y?X?5:7:7&(c[3]|c[4]<<8>>8),$=Y?7&(c[3]|c[4]<<8>>8):X?5:7,tt=(Y?32:Z)/(Y?$:32),et=i(tt>1?32:32*tt),at=i(tt>1?32/tt:32),rt=new Uint8Array(et*at*4),nt=n/at,ot=n/et;for(D=0;D<at;D++){var it=D+.5;for(T=[],e=0;e<p;e++)T[e]=o(nt*it*e);if(_=[o(0),o(nt*it),o(nt*it*2)],v)for(G=[],e=0;e<5;e++)G[e]=o(nt*it*e);for(C=0;C<et;C++){var dt=C+.5;for(M=f,P=l,R=g,U=w,N=0,x=0;x<p;x++)for(O=T[x],q=0;q<k;q++)(q||x)&&(M+=J[N++]*o(ot*dt*q)*O);for(N=0,x=0;x<3;x++)for(q=0;q<3;q++)(q||x)&&(z=o(ot*dt*q),P+=K[N]*z*_[x],R+=Q[N]*z*_[x],N++);if(v)for(N=0,x=0;x<5;x++)for(q=0;q<5;q++)(q||x)&&(U+=V[N++]*o(ot*dt*q)*G[x]);F=(B=(3*M-(j=M-2/3*P)+R)/2)-R,H=j,rt[W=4*(D*et+C)]=d(0,u(255,i(255*B))),rt[W+1]=d(0,u(255,i(255*F))),rt[W+2]=d(0,u(255,i(255*H))),rt[W+3]=d(0,u(255,i(255*U)))}}return{w:et,h:at,rgba:rt}}(r);t||(t=a.createElement("canvas"),e=t.getContext("2d")),t.width=s.w,t.height=s.h;var h=e.createImageData(s.w,s.h);return h.data.set(s.rgba),e.putImageData(h,0,0),c[r]=t.toDataURL()}catch(t){return""}}function h(t){if(!t._th){t._th=1;var e=t.getAttribute("data-thumbhash");if(e){var a=s(e);if(a){var r=t.closest("picture")||t,n=r.style;n.backgroundImage="url("+a+")",n.backgroundSize="cover",n.backgroundPosition="center",n.backgroundRepeat="no-repeat","PICTURE"===r.tagName&&(n.display="block"),r.classList&&r.classList.add("thumbhash-loading"),t.complete&&t.naturalWidth?o():(t.addEventListener("load",o,{once:!0}),t.addEventListener("error",o,{once:!0}))}}}function o(){n.backgroundImage=n.backgroundSize=n.backgroundPosition=n.backgroundRepeat="",r.classList&&r.classList.remove("thumbhash-loading")}}function f(t){if(t&&(1===t.nodeType&&"IMG"===t.tagName&&t.hasAttribute("data-thumbhash")&&h(t),t.querySelectorAll)){var e,a=t.querySelectorAll("img[data-thumbhash]");for(e=0;e<a.length;e++)h(a[e])}}new MutationObserver(function(t){for(var e=0;e<t.length;e++)for(var a=t[e].addedNodes,r=0;r<a.length;r++)f(a[r])}).observe(a.documentElement,{childList:!0,subtree:!0}),"loading"!==a.readyState?f(a.body||a.documentElement):a.addEventListener("DOMContentLoaded",function(){f(a.body)})}(); -
avif-local-support/trunk/avif-local-support.php
r3427508 r3427516 7 7 * Plugin URI: https://github.com/ddegner/avif-local-support 8 8 * Description: Unified AVIF support and conversion. Local-first processing with a strong focus on image quality when converting JPEGs. 9 * Version: 0.5. 19 * Version: 0.5.3 10 10 * Author: David Degner 11 11 * Author URI: https://www.DavidDegner.com … … 22 22 23 23 // Define constants 24 \define('AVIFLOSU_VERSION', '0.5. 1');24 \define('AVIFLOSU_VERSION', '0.5.3'); 25 25 \define('AVIFLOSU_PLUGIN_FILE', __FILE__); 26 26 \define('AVIFLOSU_PLUGIN_DIR', plugin_dir_path(__FILE__)); … … 28 28 \define('AVIFLOSU_INC_DIR', AVIFLOSU_PLUGIN_DIR . 'includes'); 29 29 30 // Composer autoloader for third-party dependencies 30 // Load bundled ThumbHash library 31 $thumbhashLib = AVIFLOSU_PLUGIN_DIR . 'lib/Thumbhash/Thumbhash.php'; 32 if (file_exists($thumbhashLib)) { 33 require_once $thumbhashLib; 34 } 35 36 // Composer autoloader for other third-party dependencies (if needed) 31 37 $composerAutoloader = AVIFLOSU_PLUGIN_DIR . 'vendor/autoload.php'; 32 38 if (file_exists($composerAutoloader)) { … … 105 111 add_option('aviflosu_engine_mode', 'auto'); 106 112 add_option('aviflosu_cli_path', ''); 107 // Beta features (off by default)113 // LQIP (ThumbHash) settings 108 114 add_option('aviflosu_thumbhash_enabled', false); 115 add_option('aviflosu_thumbhash_size', 100); 116 add_option('aviflosu_lqip_generate_on_upload', true); 117 add_option('aviflosu_lqip_generate_via_schedule', true); 109 118 } 110 119 -
avif-local-support/trunk/includes/LQIP_CLI.php
r3427508 r3427516 82 82 * : Show what would be generated without actually generating 83 83 * 84 * [--limit=<number>] 85 * : Limit the number of images to process (useful for testing) 86 * 87 * [--verbose] 88 * : Show detailed output for each image being processed 89 * 84 90 * ## EXAMPLES 85 91 * … … 103 109 } 104 110 111 if (!ThumbHash::isLibraryAvailable()) { 112 \WP_CLI::error('ThumbHash library not found. Please run "composer install" in the plugin directory to install dependencies.'); 113 return; 114 } 115 105 116 $all = isset($assoc_args['all']); 106 117 $dryRun = isset($assoc_args['dry-run']); 118 $limit = isset($assoc_args['limit']) ? (int) $assoc_args['limit'] : 0; 119 $verbose = isset($assoc_args['verbose']); 107 120 $attachmentId = !empty($args[0]) ? (int) $args[0] : 0; 108 121 … … 117 130 } 118 131 119 $this->generateAll($dryRun );132 $this->generateAll($dryRun, $limit, $verbose); 120 133 } 121 134 … … 212 225 /** 213 226 * Generate LQIP for all attachments missing them. 214 */ 215 private function generateAll(bool $dryRun): void 227 * 228 * @param bool $dryRun Whether this is a dry run. 229 * @param int $limit Maximum number of images to process (0 = no limit). 230 * @param bool $verbose Show detailed output for each image. 231 */ 232 private function generateAll(bool $dryRun, int $limit = 0, bool $verbose = false): void 216 233 { 217 234 $stats = ThumbHash::getStats(); … … 262 279 $skipped = 0; 263 280 $failed = 0; 264 265 foreach ($query->posts as $attachmentId) { 281 $postsToProcess = $query->posts; 282 283 // Apply limit if specified 284 if ($limit > 0 && count($postsToProcess) > $limit) { 285 $postsToProcess = array_slice($postsToProcess, 0, $limit); 286 \WP_CLI::line(sprintf('Limiting processing to first %d images...', $limit)); 287 \WP_CLI::line(''); 288 } 289 290 $totalToProcess = count($postsToProcess); 291 292 foreach ($postsToProcess as $index => $attachmentId) { 266 293 // Skip if already has LQIP 267 294 $existing = get_post_meta($attachmentId, ThumbHash::getMetaKey(), true); 268 295 if (!empty($existing) && is_array($existing)) { 269 296 ++$skipped; 297 ++$processed; 298 $this->printProgress($processed, $totalMissing, $startTime); 270 299 continue; 271 300 } 272 301 273 ThumbHash::generateForAttachment((int) $attachmentId); 302 // Show which image we're processing (helps identify hanging images) 303 $currentNum = $index + 1; 304 if ($verbose || $currentNum % 10 === 0 || $currentNum === 1) { 305 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- WP-CLI progress output. 306 fwrite(STDERR, "\r" . str_repeat(' ', 80) . "\r"); 307 \WP_CLI::line(sprintf('Processing image %d/%d (ID: %d)...', $currentNum, $totalToProcess, $attachmentId)); 308 } 309 310 // Clear any previous error 311 ThumbHash::getLastError(); 312 313 // Try to generate with error handling 314 try { 315 $memoryBefore = memory_get_usage(true); 316 ThumbHash::generateForAttachment((int) $attachmentId); 317 $memoryAfter = memory_get_usage(true); 318 319 // Check for memory issues 320 if ($memoryAfter - $memoryBefore > 50 * 1024 * 1024) { // More than 50MB 321 \WP_CLI::warning(sprintf('High memory usage for attachment ID %d: %s', $attachmentId, size_format($memoryAfter - $memoryBefore))); 322 } 323 } catch (\Throwable $e) { 324 \WP_CLI::warning(sprintf('Exception generating LQIP for attachment ID %d: %s', $attachmentId, $e->getMessage())); 325 ++$failed; 326 ++$processed; 327 $this->printProgress($processed, $totalMissing, $startTime); 328 continue; 329 } 274 330 275 331 // Verify it was generated … … 281 337 } else { 282 338 ++$failed; 339 ++$processed; 340 $error = ThumbHash::getLastError(); 341 if ($error && ($currentNum % 10 === 0 || $currentNum <= 5)) { 342 \WP_CLI::warning(sprintf('Failed to generate LQIP for attachment ID %d: %s', $attachmentId, $error)); 343 } 344 $this->printProgress($processed, $totalMissing, $startTime); 345 } 346 347 // Force garbage collection every 50 images to prevent memory buildup 348 if ($processed % 50 === 0) { 349 gc_collect_cycles(); 283 350 } 284 351 } -
avif-local-support/trunk/includes/ThumbHash.php
r3427508 r3427516 61 61 62 62 /** 63 * Check if the ThumbHash library is available. 64 * 65 * @return bool True if the library class exists, false otherwise. 66 */ 67 public static function isLibraryAvailable(): bool 68 { 69 return class_exists('Thumbhash\Thumbhash'); 70 } 71 72 /** 63 73 * Generate ThumbHash string for an image file. 64 74 * … … 68 78 public static function generate(string $imagePath): ?string 69 79 { 80 // Check if the ThumbHash library is available 81 if (!self::isLibraryAvailable()) { 82 self::$lastError = 'ThumbHash library not found. Please run "composer install" in the plugin directory to install dependencies.'; 83 if (class_exists(Logger::class)) { 84 (new Logger())->addLog('error', 'ThumbHash library not available', array( 85 'path' => $imagePath, 86 'error' => 'Thumbhash\Thumbhash class not found. Composer dependencies may not be installed.' 87 )); 88 } 89 return null; 90 } 91 70 92 if (!file_exists($imagePath) || !is_readable($imagePath)) { 71 93 self::$lastError = "File not found or unreadable: $imagePath"; -
avif-local-support/trunk/readme.txt
r3427508 r3427516 4 4 Requires at least: 6.8 5 5 Tested up to: 6.9 6 Stable tag: 0.5. 16 Stable tag: 0.5.3 7 7 Requires PHP: 8.3 8 8 License: GPLv2 or later … … 198 198 199 199 == Changelog == 200 201 = 0.5.3 = 202 203 - Fix: Added missing LQIP options to plugin activation (thumbhash_size, generate_on_upload, generate_via_schedule) 204 - Fix: Added missing LQIP options to uninstall cleanup for complete data removal 205 - Fix: Properly minified thumbhash-decoder.min.js (62% size reduction) 206 - Fix: Excluded developer documentation from WordPress plugin distribution 207 208 = 0.5.2 = 209 210 - Feature: Bundled ThumbHash library — no Composer dependency required on deployment 211 - Enhancement: Improved LQIP generation with better error handling, progress reporting, and memory management 212 - Enhancement: Added `--limit` and `--verbose` options to `wp lqip generate` command 213 - Fix: Resolved hanging issue in `wp lqip generate --all` command with better error handling and progress output 214 - Fix: Clear error messages when ThumbHash library is unavailable 200 215 201 216 = 0.5.1 = -
avif-local-support/trunk/uninstall.php
r3427506 r3427516 3 3 declare(strict_types=1); 4 4 5 if ( ! defined( 'WP_UNINSTALL_PLUGIN' )) {5 if (!defined('WP_UNINSTALL_PLUGIN')) { 6 6 exit; 7 7 } … … 22 22 'aviflosu_cli_args', 23 23 'aviflosu_cli_env', 24 // Beta features24 // LQIP (ThumbHash) settings 25 25 'aviflosu_thumbhash_enabled', 26 'aviflosu_thumbhash_size', 27 'aviflosu_lqip_generate_on_upload', 28 'aviflosu_lqip_generate_via_schedule', 26 29 // legacy options left behind in older versions 27 30 'aviflosu_preserve_metadata', … … 30 33 ); 31 34 32 foreach ( $aviflosu_options as $aviflosu_option) {33 if ( get_option( $aviflosu_option ) !== false) {34 delete_option( $aviflosu_option);35 foreach ($aviflosu_options as $aviflosu_option) { 36 if (get_option($aviflosu_option) !== false) { 37 delete_option($aviflosu_option); 35 38 } 36 39 } 37 40 38 41 // Delete transients using WordPress functions. 39 delete_transient( 'aviflosu_file_cache');40 delete_transient( 'aviflosu_logs');41 delete_transient( 'aviflosu_stop_conversion');42 delete_transient('aviflosu_file_cache'); 43 delete_transient('aviflosu_logs'); 44 delete_transient('aviflosu_stop_conversion'); 42 45 43 46 // Delete ImageMagick CLI cache transients (with wildcard pattern). … … 45 48 // Note: Direct DB queries won't clear object cache entries (Redis/Memcached), 46 49 // but those will naturally expire based on their TTL. 47 if ( ! wp_using_ext_object_cache()) {50 if (!wp_using_ext_object_cache()) { 48 51 global $wpdb; 49 52 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 50 $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_aviflosu_imc_%' OR option_name LIKE '_transient_timeout_aviflosu_imc_%'");53 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_aviflosu_imc_%' OR option_name LIKE '_transient_timeout_aviflosu_imc_%'"); 51 54 } 52 55 … … 54 57 global $wpdb; 55 58 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 56 $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_aviflosu_thumbhash' ));59 $wpdb->delete($wpdb->postmeta, array('meta_key' => '_aviflosu_thumbhash'));
Note: See TracChangeset
for help on using the changeset viewer.