Plugin Directory

Changeset 3427516


Ignore:
Timestamp:
12/26/2025 03:30:29 AM (5 weeks ago)
Author:
ddegner
Message:

Update to version 0.5.3 from GitHub

Location:
avif-local-support
Files:
6 added
2 deleted
12 edited
1 copied

Legend:

Unmodified
Added
Removed
  • avif-local-support/tags/0.5.3/assets/thumbhash-decoder.min.js

    r3427506 r3427516  
    66 * @license MIT
    77 */
    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  
    77 * Plugin URI: https://github.com/ddegner/avif-local-support
    88 * Description: Unified AVIF support and conversion. Local-first processing with a strong focus on image quality when converting JPEGs.
    9  * Version: 0.5.1
     9 * Version: 0.5.3
    1010 * Author: David Degner
    1111 * Author URI: https://www.DavidDegner.com
     
    2222
    2323// Define constants
    24 \define('AVIFLOSU_VERSION', '0.5.1');
     24\define('AVIFLOSU_VERSION', '0.5.3');
    2525\define('AVIFLOSU_PLUGIN_FILE', __FILE__);
    2626\define('AVIFLOSU_PLUGIN_DIR', plugin_dir_path(__FILE__));
     
    2828\define('AVIFLOSU_INC_DIR', AVIFLOSU_PLUGIN_DIR . 'includes');
    2929
    30 // Composer autoloader for third-party dependencies
     30// Load bundled ThumbHash library
     31$thumbhashLib = AVIFLOSU_PLUGIN_DIR . 'lib/Thumbhash/Thumbhash.php';
     32if (file_exists($thumbhashLib)) {
     33    require_once $thumbhashLib;
     34}
     35
     36// Composer autoloader for other third-party dependencies (if needed)
    3137$composerAutoloader = AVIFLOSU_PLUGIN_DIR . 'vendor/autoload.php';
    3238if (file_exists($composerAutoloader)) {
     
    105111    add_option('aviflosu_engine_mode', 'auto');
    106112    add_option('aviflosu_cli_path', '');
    107     // Beta features (off by default)
     113    // LQIP (ThumbHash) settings
    108114    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);
    109118}
    110119
  • avif-local-support/tags/0.5.3/includes/LQIP_CLI.php

    r3427508 r3427516  
    8282     * : Show what would be generated without actually generating
    8383     *
     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     *
    8490     * ## EXAMPLES
    8591     *
     
    103109        }
    104110
     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
    105116        $all = isset($assoc_args['all']);
    106117        $dryRun = isset($assoc_args['dry-run']);
     118        $limit = isset($assoc_args['limit']) ? (int) $assoc_args['limit'] : 0;
     119        $verbose = isset($assoc_args['verbose']);
    107120        $attachmentId = !empty($args[0]) ? (int) $args[0] : 0;
    108121
     
    117130        }
    118131
    119         $this->generateAll($dryRun);
     132        $this->generateAll($dryRun, $limit, $verbose);
    120133    }
    121134
     
    212225    /**
    213226     * 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
    216233    {
    217234        $stats = ThumbHash::getStats();
     
    262279        $skipped = 0;
    263280        $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) {
    266293            // Skip if already has LQIP
    267294            $existing = get_post_meta($attachmentId, ThumbHash::getMetaKey(), true);
    268295            if (!empty($existing) && is_array($existing)) {
    269296                ++$skipped;
     297                ++$processed;
     298                $this->printProgress($processed, $totalMissing, $startTime);
    270299                continue;
    271300            }
    272301
    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            }
    274330
    275331            // Verify it was generated
     
    281337            } else {
    282338                ++$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();
    283350            }
    284351        }
  • avif-local-support/tags/0.5.3/includes/ThumbHash.php

    r3427508 r3427516  
    6161
    6262    /**
     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    /**
    6373     * Generate ThumbHash string for an image file.
    6474     *
     
    6878    public static function generate(string $imagePath): ?string
    6979    {
     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
    7092        if (!file_exists($imagePath) || !is_readable($imagePath)) {
    7193            self::$lastError = "File not found or unreadable: $imagePath";
  • avif-local-support/tags/0.5.3/readme.txt

    r3427508 r3427516  
    44Requires at least: 6.8
    55Tested up to: 6.9
    6 Stable tag: 0.5.1
     6Stable tag: 0.5.3
    77Requires PHP: 8.3
    88License: GPLv2 or later
     
    198198
    199199== 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
    200215
    201216= 0.5.1 =
  • avif-local-support/tags/0.5.3/uninstall.php

    r3427506 r3427516  
    33declare(strict_types=1);
    44
    5 if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
     5if (!defined('WP_UNINSTALL_PLUGIN')) {
    66    exit;
    77}
     
    2222    'aviflosu_cli_args',
    2323    'aviflosu_cli_env',
    24     // Beta features
     24    // LQIP (ThumbHash) settings
    2525    'aviflosu_thumbhash_enabled',
     26    'aviflosu_thumbhash_size',
     27    'aviflosu_lqip_generate_on_upload',
     28    'aviflosu_lqip_generate_via_schedule',
    2629    // legacy options left behind in older versions
    2730    'aviflosu_preserve_metadata',
     
    3033);
    3134
    32 foreach ( $aviflosu_options as $aviflosu_option ) {
    33     if ( get_option( $aviflosu_option ) !== false ) {
    34         delete_option( $aviflosu_option );
     35foreach ($aviflosu_options as $aviflosu_option) {
     36    if (get_option($aviflosu_option) !== false) {
     37        delete_option($aviflosu_option);
    3538    }
    3639}
    3740
    3841// Delete transients using WordPress functions.
    39 delete_transient( 'aviflosu_file_cache' );
    40 delete_transient( 'aviflosu_logs' );
    41 delete_transient( 'aviflosu_stop_conversion' );
     42delete_transient('aviflosu_file_cache');
     43delete_transient('aviflosu_logs');
     44delete_transient('aviflosu_stop_conversion');
    4245
    4346// Delete ImageMagick CLI cache transients (with wildcard pattern).
     
    4548// Note: Direct DB queries won't clear object cache entries (Redis/Memcached),
    4649// but those will naturally expire based on their TTL.
    47 if ( ! wp_using_ext_object_cache() ) {
     50if (!wp_using_ext_object_cache()) {
    4851    global $wpdb;
    4952    // 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_%'");
    5154}
    5255
     
    5457global $wpdb;
    5558// 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  
    66 * @license MIT
    77 */
    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  
    77 * Plugin URI: https://github.com/ddegner/avif-local-support
    88 * Description: Unified AVIF support and conversion. Local-first processing with a strong focus on image quality when converting JPEGs.
    9  * Version: 0.5.1
     9 * Version: 0.5.3
    1010 * Author: David Degner
    1111 * Author URI: https://www.DavidDegner.com
     
    2222
    2323// Define constants
    24 \define('AVIFLOSU_VERSION', '0.5.1');
     24\define('AVIFLOSU_VERSION', '0.5.3');
    2525\define('AVIFLOSU_PLUGIN_FILE', __FILE__);
    2626\define('AVIFLOSU_PLUGIN_DIR', plugin_dir_path(__FILE__));
     
    2828\define('AVIFLOSU_INC_DIR', AVIFLOSU_PLUGIN_DIR . 'includes');
    2929
    30 // Composer autoloader for third-party dependencies
     30// Load bundled ThumbHash library
     31$thumbhashLib = AVIFLOSU_PLUGIN_DIR . 'lib/Thumbhash/Thumbhash.php';
     32if (file_exists($thumbhashLib)) {
     33    require_once $thumbhashLib;
     34}
     35
     36// Composer autoloader for other third-party dependencies (if needed)
    3137$composerAutoloader = AVIFLOSU_PLUGIN_DIR . 'vendor/autoload.php';
    3238if (file_exists($composerAutoloader)) {
     
    105111    add_option('aviflosu_engine_mode', 'auto');
    106112    add_option('aviflosu_cli_path', '');
    107     // Beta features (off by default)
     113    // LQIP (ThumbHash) settings
    108114    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);
    109118}
    110119
  • avif-local-support/trunk/includes/LQIP_CLI.php

    r3427508 r3427516  
    8282     * : Show what would be generated without actually generating
    8383     *
     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     *
    8490     * ## EXAMPLES
    8591     *
     
    103109        }
    104110
     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
    105116        $all = isset($assoc_args['all']);
    106117        $dryRun = isset($assoc_args['dry-run']);
     118        $limit = isset($assoc_args['limit']) ? (int) $assoc_args['limit'] : 0;
     119        $verbose = isset($assoc_args['verbose']);
    107120        $attachmentId = !empty($args[0]) ? (int) $args[0] : 0;
    108121
     
    117130        }
    118131
    119         $this->generateAll($dryRun);
     132        $this->generateAll($dryRun, $limit, $verbose);
    120133    }
    121134
     
    212225    /**
    213226     * 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
    216233    {
    217234        $stats = ThumbHash::getStats();
     
    262279        $skipped = 0;
    263280        $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) {
    266293            // Skip if already has LQIP
    267294            $existing = get_post_meta($attachmentId, ThumbHash::getMetaKey(), true);
    268295            if (!empty($existing) && is_array($existing)) {
    269296                ++$skipped;
     297                ++$processed;
     298                $this->printProgress($processed, $totalMissing, $startTime);
    270299                continue;
    271300            }
    272301
    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            }
    274330
    275331            // Verify it was generated
     
    281337            } else {
    282338                ++$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();
    283350            }
    284351        }
  • avif-local-support/trunk/includes/ThumbHash.php

    r3427508 r3427516  
    6161
    6262    /**
     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    /**
    6373     * Generate ThumbHash string for an image file.
    6474     *
     
    6878    public static function generate(string $imagePath): ?string
    6979    {
     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
    7092        if (!file_exists($imagePath) || !is_readable($imagePath)) {
    7193            self::$lastError = "File not found or unreadable: $imagePath";
  • avif-local-support/trunk/readme.txt

    r3427508 r3427516  
    44Requires at least: 6.8
    55Tested up to: 6.9
    6 Stable tag: 0.5.1
     6Stable tag: 0.5.3
    77Requires PHP: 8.3
    88License: GPLv2 or later
     
    198198
    199199== 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
    200215
    201216= 0.5.1 =
  • avif-local-support/trunk/uninstall.php

    r3427506 r3427516  
    33declare(strict_types=1);
    44
    5 if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
     5if (!defined('WP_UNINSTALL_PLUGIN')) {
    66    exit;
    77}
     
    2222    'aviflosu_cli_args',
    2323    'aviflosu_cli_env',
    24     // Beta features
     24    // LQIP (ThumbHash) settings
    2525    'aviflosu_thumbhash_enabled',
     26    'aviflosu_thumbhash_size',
     27    'aviflosu_lqip_generate_on_upload',
     28    'aviflosu_lqip_generate_via_schedule',
    2629    // legacy options left behind in older versions
    2730    'aviflosu_preserve_metadata',
     
    3033);
    3134
    32 foreach ( $aviflosu_options as $aviflosu_option ) {
    33     if ( get_option( $aviflosu_option ) !== false ) {
    34         delete_option( $aviflosu_option );
     35foreach ($aviflosu_options as $aviflosu_option) {
     36    if (get_option($aviflosu_option) !== false) {
     37        delete_option($aviflosu_option);
    3538    }
    3639}
    3740
    3841// Delete transients using WordPress functions.
    39 delete_transient( 'aviflosu_file_cache' );
    40 delete_transient( 'aviflosu_logs' );
    41 delete_transient( 'aviflosu_stop_conversion' );
     42delete_transient('aviflosu_file_cache');
     43delete_transient('aviflosu_logs');
     44delete_transient('aviflosu_stop_conversion');
    4245
    4346// Delete ImageMagick CLI cache transients (with wildcard pattern).
     
    4548// Note: Direct DB queries won't clear object cache entries (Redis/Memcached),
    4649// but those will naturally expire based on their TTL.
    47 if ( ! wp_using_ext_object_cache() ) {
     50if (!wp_using_ext_object_cache()) {
    4851    global $wpdb;
    4952    // 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_%'");
    5154}
    5255
     
    5457global $wpdb;
    5558// 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.