Plugin Directory

Changeset 3726224


Ignore:
Timestamp:
10/03/2026 12:04:00 PM (3 weeks ago)
Author:
misticuantum
Message:

1.3.3

Fixes Slash Markup label detection to prevent slashes in ordinary text or URLs from breaking video and image markup.

Location:
misticease
Files:
295 added
11 edited

Legend:

Unmodified
Added
Removed
  • misticease/trunk/changelog-misticease.txt

    r3707712 r3726224  
     1= 1.3.2 =
     2
     3* Added what the plugin does to the plugin name.
     4* Characters such as `. , ! ? )` directly after a URL are no longer treated as the end of the URL. When they follow with no space, mark the end with `///`.
     5* Fixed image URLs with a double extension (for example, `photo.jpg.webp`) being cut off partway.
     6
     7= 1.3.1 =
     8
     9* Minor bug fixes and stability improvements.
     10
    111= 1.3.0 =
    212
  • misticease/trunk/inc/app/css/misticease.css

    r3652182 r3726224  
    1818  height: auto;
    1919  object-fit: cover;
     20}
     21
     22.misticease-thumbnail--shorts {
     23  width: min(100%, 47.8125vh);
     24  aspect-ratio: 9 / 16;
     25}
     26
     27.misticease-thumbnail--shorts > img {
     28  position: absolute;
     29  inset: 0;
     30  height: 100%;
     31  margin: 0;
     32  object-position: center;
    2033}
    2134
    … …  
    179192  border: none;
    180193  background: #000;
     194}
     195
     196.misticease-popup-content--shorts iframe {
     197  aspect-ratio: 9 / 16;
     198  flex-shrink: 0;
     199}
     200
     201.misticease-popup-content--shorts .misticease-popup-caption {
     202  flex-shrink: 0;
     203  overflow-y: auto;
     204  overflow-wrap: anywhere;
    181205}
    182206
  • misticease/trunk/inc/app/js/misticease-sml-parser.js

    r3707712 r3726224  
    103103    // Protect the complete /label/ before interpreting any control-like suffix.
    104104    // Keep the existing reverse control order, including directly joined controls.
    105     var suffix = s.match(/(?:\/([^\/<>'"\n\r]+)\/((?:(?:ttl[ \u3000][^\/<>'"\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)*)|(?:^|[^A-Za-z0-9_])((?:(?:ttl[ \u3000][^\/<>'"\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)+))$/i);
     105    var suffix = s.match(/(?:(?:^|[\s\u00A0\u3000])\/([^\/<>'"\n\r]+)\/((?:(?:ttl[ \u3000][^\/<>'"\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)*)|(?:^|[^A-Za-z0-9_])((?:(?:ttl[ \u3000][^\/<>'"\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)+))$/i);
    106106    if (!suffix) return null;
    107107
    108108    var hasLabel = typeof suffix[1] !== 'undefined';
    109109    var controls = hasLabel ? suffix[2] : suffix[3];
    110     var preserved = s.slice(0, suffix.index + (hasLabel ? 0 : suffix[0].length - controls.length));
     110    var preserved = s.slice(0, suffix.index + (hasLabel ? suffix[0].indexOf('/') : suffix[0].length - controls.length));
    111111    s = controls;
    112112
    … …  
    185185  }
    186186
    187   function setThumbImageSize(img, width, videoRatio) {
     187  function setThumbImageSize(img, width, videoRatio, isShorts) {
    188188    if (!img || !width) return;
    189189    var height = null;
    190190
    191191    if (videoRatio) {
    192       height = Math.max(1, Math.round(width * 9 / 16));
     192      height = Math.max(1, Math.round(width * (isShorts ? 16 / 9 : 9 / 16)));
    193193    } else {
    194194      var currentWidth = parseInt(img.getAttribute('width') || '', 10);
    … …  
    206206      img.style.width = '100%';
    207207      img.style.maxWidth = width + 'px';
    208       img.style.height = 'auto';
     208      img.style.height = isShorts ? '100%' : 'auto';
    209209    }
    210210  }
    … …  
    214214
    215215    if (spec.popupWidth) {
    216       try { el.setAttribute('data-popup-width', String(spec.popupWidth)); } catch (_e) {}
     216      try {
     217        el.setAttribute('data-popup-width', String(spec.popupWidth));
     218        el.removeAttribute('data-popup-height');
     219      } catch (_e) {}
    217220    }
    218221
    … …  
    223226    if (spec.thumbWidth && el.classList && el.classList.contains('misticease-thumbnail')) {
    224227      el.style.maxWidth = spec.thumbWidth + 'px';
    225       setThumbImageSize(firstImage(el), spec.thumbWidth, true);
     228      var isShorts = el.getAttribute('data-video-type') === 'youtube' && el.getAttribute('data-video-shorts') === '1';
     229      setThumbImageSize(firstImage(el), spec.thumbWidth, true, isShorts);
    226230    } else if (spec.thumbWidth && !(spec.label || spec.textLink)) {
    227231      setThumbImageSize(firstImage(el), spec.thumbWidth, false);
    … …  
    272276    a.textContent = spec.label || defaultLinkText(el);
    273277
    274     var attrs = ['data-video-id', 'data-video-type', 'data-popup-width', 'data-misticease-title'];
     278    var attrs = ['data-video-id', 'data-video-type', 'data-video-shorts', 'data-popup-width', 'data-popup-height', 'data-misticease-title'];
    275279    for (var i = 0; i < attrs.length; i++) {
    276280      var v = el.getAttribute(attrs[i]);
    … …  
    278282    }
    279283
    280     if (spec.popupWidth) a.setAttribute('data-popup-width', String(spec.popupWidth));
     284    if (spec.popupWidth) {
     285      a.setAttribute('data-popup-width', String(spec.popupWidth));
     286      a.removeAttribute('data-popup-height');
     287    }
    281288    if (spec.title) a.setAttribute('data-misticease-title', spec.title);
    282289
    … …  
    438445      var prefix = match[1] || '';
    439446      var url = match[2] || '';
     447      if (prefix.charAt(0) === '/' && match.index > 0 && !/[\s\u00A0\u3000]/.test(text.charAt(match.index - 1))) {
     448        continue;
     449      }
    440450      var spec = parsePrefix(prefix);
    441451
  • misticease/trunk/inc/app/js/misticease.js

    r3707712 r3726224  
    7474  }
    7575
    76   function getClampedPopupWidth(widthPx) {
     76  function getClampedPopupWidth(widthPx, isShorts) {
    7777    var viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    7878    var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
     79
     80    if (isShorts) {
     81      var shortsMaxWidth = Math.max(1, Math.min(viewportWidth - 48, (viewportHeight - 48 - 20) * (9 / 16) + 40));
     82      return (!widthPx || widthPx < 1) ? shortsMaxWidth : Math.min(widthPx, shortsMaxWidth);
     83    }
    7984
    8085    var sideMargin = 48;
    … …  
    98103  }
    99104
    100   function fitVideoPopupToViewport(popup, iframe, requestedWidthPx) {
     105  function fitVideoPopupToViewport(popup, iframe, requestedWidthPx, isShorts, requestedHeightPx) {
     106    if (isShorts) {
     107      var shortsViewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
     108      var shortsStyle = window.getComputedStyle(popup);
     109      var shortsPaddingX = (parseFloat(shortsStyle.paddingLeft) || 0) + (parseFloat(shortsStyle.paddingRight) || 0) +
     110        (parseFloat(shortsStyle.borderLeftWidth) || 0) + (parseFloat(shortsStyle.borderRightWidth) || 0);
     111      var shortsPaddingY = (parseFloat(shortsStyle.paddingTop) || 0) + (parseFloat(shortsStyle.paddingBottom) || 0) +
     112        (parseFloat(shortsStyle.borderTopWidth) || 0) + (parseFloat(shortsStyle.borderBottomWidth) || 0);
     113      var shortsMaxHeight = Math.max(1, Math.min(shortsViewportHeight - 48, shortsViewportHeight * 0.9));
     114      if (requestedHeightPx > 0) shortsMaxHeight = Math.min(shortsMaxHeight, requestedHeightPx);
     115      var shortsWidth = Math.max(1, getClampedPopupWidth(requestedHeightPx > 0 ? 0 : requestedWidthPx, true) - shortsPaddingX);
     116      var shortsCaption = popup.querySelector('.misticease-popup-caption');
     117      if (shortsCaption) shortsCaption.style.maxHeight = Math.max(1, shortsMaxHeight * 0.25) + 'px';
     118
     119      for (var shortsPass = 0; shortsPass < 3; shortsPass++) {
     120        popup.style.maxWidth = (shortsWidth + shortsPaddingX) + 'px';
     121        popup.style.width = (shortsWidth + shortsPaddingX) + 'px';
     122        var shortsCaptionHeight = shortsCaption ? shortsCaption.offsetHeight : 0;
     123        var shortsAvailableHeight = Math.max(1, shortsMaxHeight - shortsPaddingY - shortsCaptionHeight);
     124        shortsWidth = Math.min(shortsWidth, shortsAvailableHeight * (9 / 16));
     125      }
     126      if (shortsCaption) shortsCaption.style.maxHeight = shortsCaptionHeight + 'px';
     127      popup.style.maxWidth = (shortsWidth + shortsPaddingX) + 'px';
     128      popup.style.width = (shortsWidth + shortsPaddingX) + 'px';
     129      iframe.style.height = (shortsWidth * 16 / 9) + 'px';
     130      return;
     131    }
     132
    101133    var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    102134    var popupPaddingX = 40; // 20px left + 20px right
    … …  
    211243  }
    212244
    213   function createVideoWindow(url, widthPx, popupTitle) {
     245  function createVideoWindow(url, widthPx, popupTitle, isShorts, requestedHeightPx) {
    214246    var popup = document.createElement('div');
    215247    popup.className = 'misticease-popup-content';
     248    if (isShorts) popup.className += ' misticease-popup-content--shorts';
    216249    popup.setAttribute('role', 'dialog');
    217250    popup.setAttribute('aria-modal', 'true');
    218251
    219     var clampedWidth = getClampedPopupWidth(widthPx);
     252    var clampedWidth = getClampedPopupWidth(widthPx, isShorts);
    220253    popup.style.maxWidth = clampedWidth + 'px';
    221254    popup.style.width = clampedWidth + 'px';
    … …  
    247280
    248281    function fit() {
    249       fitVideoPopupToViewport(popup, iframe, widthPx);
     282      fitVideoPopupToViewport(popup, iframe, widthPx, isShorts, requestedHeightPx);
    250283    }
    251284
    … …  
    271304  }
    272305
    273   function openVideoPopup(url, widthPx, popupTitle) {
     306  function openVideoPopup(url, widthPx, popupTitle, isShorts, requestedHeightPx) {
    274307    if (mgr && typeof mgr.requestOpen === 'function') {
    275308      mgr.requestOpen({
    … …  
    278311        floatingControls: true,
    279312        create: function () {
    280           return createVideoWindow(url, widthPx, popupTitle);
     313          return createVideoWindow(url, widthPx, popupTitle, isShorts, requestedHeightPx);
    281314        }
    282315      });
    … …  
    298331    var popup = document.createElement('div');
    299332    popup.className = 'misticease-popup-content';
    300 
    301     var clampedWidth = getClampedPopupWidth(widthPx);
     333    if (isShorts) popup.className += ' misticease-popup-content--shorts';
     334
     335    var clampedWidth = getClampedPopupWidth(widthPx, isShorts);
    302336    popup.style.maxWidth = clampedWidth + 'px';
    303337    popup.style.width = clampedWidth + 'px';
    … …  
    332366
    333367    function fit() {
    334       fitVideoPopupToViewport(popup, iframe, widthPx);
     368      fitVideoPopupToViewport(popup, iframe, widthPx, isShorts, requestedHeightPx);
    335369    }
    336370
    … …  
    11711205      var w = parseInt(trigger.getAttribute('data-popup-width') || '0', 10) || 0;
    11721206      var popupTitle = trigger.getAttribute('data-misticease-title') || '';
    1173       openVideoPopup(url, w, popupTitle);
     1207      var isShorts = type === 'youtube' && trigger.getAttribute('data-video-shorts') === '1';
     1208      var requestedHeight = isShorts ? (parseInt(trigger.getAttribute('data-popup-height') || '0', 10) || 0) : 0;
     1209      openVideoPopup(url, w, popupTitle, isShorts, requestedHeight);
    11741210    }, true);
    11751211
  • misticease/trunk/inc/app/misticease-cache.php

    r3652182 r3726224  
    9292        $bytes = misticease_calc_dir_size_bytes( $dir );
    9393        misticease_opts_update( [ 'used_cache_bytes' => (int) $bytes ] );
    94         set_transient( 'misticease_used_cache_bytes_t', (int) $bytes, 10 * MINUTE_IN_SECONDS );
    9594
    9695        if ( is_multisite() && function_exists( 'update_metadata' ) ) {
  • misticease/trunk/inc/app/misticease-frontend.php

    r3652182 r3726224  
    1212                return false;
    1313        }
    14         if ( preg_match( '#(?:youtu\.be/|(?:(?:www|m)\.)?youtube\.com/(?:watch\?|shorts/|embed/|live/|v/)|(?:www\.)?youtube-nocookie\.com/embed/|(?:www\.)?vimeo\.com/|player\.vimeo\.com/video/|(?:www\.)?dailymotion\.com/(?:video|embed/video)/|dai\.ly/)#i', $content ) ) {
    15                 return true;
     14        $content = (string) preg_replace( '#<!--.*?-->|<(pre|code|script|style|textarea|kbd|samp|var)\b[^>]*>.*?</\1\s*>#is', ' ', $content );
     15        $options = misticease_opts_get();
     16        $images_enabled = ! isset( $options['image_popup'] ) || 'on' === $options['image_popup'];
     17        $cdn_hosts = $images_enabled ? misticease_cdn_hosts_effective() : [];
     18
     19        if ( preg_match_all( '#<[a-z][a-z0-9:-]*\b(?:[^>"\']|"[^"]*"|\'[^\']*\')*?\sclass\s*=\s*(["\'])(.*?)\1(?:[^>"\']|"[^"]*"|\'[^\']*\')*>#is', $content, $classes, PREG_SET_ORDER ) ) {
     20                foreach ( $classes as $class ) {
     21                        if ( preg_match( '/(?:^|\s)misticease-(?:thumbnail|n-link)(?:\s|$)/', (string) $class[2] ) || ( $images_enabled && preg_match( '/(?:^|\s)misticease-img-popup(?:\s|$)/', (string) $class[2] ) ) ) {
     22                                return true;
     23                        }
     24                }
    1625        }
    17         if ( false !== stripos( $content, 'wp-image-' ) && false !== stripos( $content, '<a' ) ) {
    18                 return true;
     26
     27        if ( $images_enabled && preg_match_all( '#<a\b(?:[^>"\']|"[^"]*"|\'[^\']*\')*?\shref\s*=\s*(["\'])(.*?)\1(?:[^>"\']|"[^"]*"|\'[^\']*\')*>#is', $content, $anchors, PREG_SET_ORDER ) ) {
     28                foreach ( $anchors as $anchor ) {
     29                        if ( misticease_is_supported_media_image_href( (string) $anchor[2], $cdn_hosts ) ) {
     30                                return true;
     31                        }
     32                }
     33        }
     34
     35        $content = misticease_convert_anchored_ncode( $content );
     36        $content = (string) preg_replace( '#<a\b[^>]*>.*?</a>#is', ' ', $content );
     37        $content = (string) preg_replace( '/(<!--.*?-->|<!\[CDATA\[.*?\]\]>|<[^>"\']*(?:"[^"]*"[^>"\']*|\'[^\']*\'[^>"\']*)*>)/s', ' ', $content );
     38        $content = wp_strip_all_tags( $content );
     39
     40        if ( preg_match_all( misticease_video_service_candidate_pattern(), $content, $videos, PREG_SET_ORDER ) ) {
     41                foreach ( $videos as $video ) {
     42                        [ $url ] = misticease_split_pasted_video_service_url( (string) $video['url'] );
     43                        if ( misticease_is_supported_video_service_url( $url ) ) {
     44                                return true;
     45                        }
     46                }
     47        }
     48
     49        if ( $images_enabled && preg_match_all( misticease_direct_image_url_pattern(), $content, $images, PREG_SET_ORDER ) ) {
     50                foreach ( $images as $image ) {
     51                        $spec = misticease_parse_slash_markup_prefix( (string) ( $image['prefix'] ?? '' ) );
     52                        [ $url ] = misticease_split_pasted_direct_image_url( (string) $image['url'] );
     53                        if ( ! empty( $spec['has_markup'] ) && ( ! empty( $spec['label'] ) || ! empty( $spec['thumb_width'] ) || ! empty( $spec['no_popup'] ) ) && misticease_is_supported_media_image_href( $url, $cdn_hosts ) ) {
     54                                return true;
     55                        }
     56                }
    1957        }
    2058        return false;
    … …  
    2563                return false;
    2664        }
    27         if ( is_singular() ) {
     65        if ( wp_script_is( 'misticease', 'enqueued' ) ) {
    2866                return true;
    2967        }
    3068        global $wp_query;
    3169        if ( ! isset( $wp_query ) || ! isset( $wp_query->posts ) || ! is_array( $wp_query->posts ) ) {
    32                 return true;
     70                return false;
    3371        }
    3472        foreach ( $wp_query->posts as $post ) {
    … …  
    4785 * ---------------------------------------------------------------------- */
    4886
    49 add_action(
    50         'wp_enqueue_scripts',
    51         static function (): void {
    52                 if ( ! misticease_should_enqueue_frontend_assets() ) {
     87function misticease_enqueue_frontend_assets( bool $force = false ): void {
     88                if ( is_admin() || is_feed() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ( ! $force && ! misticease_should_enqueue_frontend_assets() ) ) {
     89                        return;
     90                }
     91                if ( wp_script_is( 'misticease-sml-parser', 'enqueued' ) ) {
    5392                        return;
    5493                }
    … …  
    102141                wp_enqueue_script( 'misticease' );
    103142                wp_enqueue_script( 'misticease-sml-parser' );
     143}
     144add_action( 'wp_enqueue_scripts', 'misticease_enqueue_frontend_assets', 20 );
     145
     146function misticease_enqueue_assets_for_rendered_content( string $content ): string {
     147        if ( is_admin() || is_feed() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || wp_script_is( 'misticease-sml-parser', 'enqueued' ) ) {
     148                return $content;
     149        }
     150        if ( misticease_content_may_need_assets( $content ) ) {
     151                misticease_enqueue_frontend_assets( true );
     152        }
     153        return $content;
     154}
     155add_filter( 'the_content', 'misticease_enqueue_assets_for_rendered_content', PHP_INT_MAX );
     156add_filter( 'render_block', 'misticease_enqueue_assets_for_rendered_content', PHP_INT_MAX );
     157
     158add_filter(
     159        'pre_render_block',
     160        static function ( $pre_render ) {
     161                return is_string( $pre_render ) ? misticease_enqueue_assets_for_rendered_content( $pre_render ) : $pre_render;
    104162        },
    105         20
     163        PHP_INT_MAX
    106164);
     165
     166add_action(
     167        'wp_footer',
     168        static function (): void {
     169                if ( wp_style_is( 'misticease', 'enqueued' ) && ! wp_style_is( 'misticease', 'done' ) ) {
     170                        wp_print_styles( 'misticease' );
     171                }
     172        },
     173        5
     174);
  • misticease/trunk/inc/app/misticease-render.php

    r3725318 r3726224  
    1414function misticease_video_service_candidate_pattern(): string {
    1515        return '/(?<!["\'=])
    16                    (?P<prefix>(?:\/[^\/<>"\'\n\r]+\/)?(?:(?:ttl[ \x{3000}][^\/<>"\'\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)*)?
     16                   (?P<prefix>(?:(?<![^\s\x{00A0}\x{3000}])\/[^\/<>"\'\n\r]+\/)?(?:(?:ttl[ \x{3000}][^\/<>"\'\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)*)?
    1717                   (?P<url>https?:\/\/(?:(?:(?:www|m)\.)?youtube\.com|(?:www\.)?youtube-nocookie\.com|youtu\.be|(?:www\.)?vimeo\.com|player\.vimeo\.com|(?:www\.)?dailymotion\.com|dai\.ly)(?:\/{3}|\/(?:(?!\/{3})[^\s<>"\' ])+(?:\/{3})?))
    1818                   /ixu';
    … …  
    107107        }
    108108
    109         return '' === $video_id ? [] : [ 'type' => 'youtube', 'id' => $video_id ];
     109        if ( '' === $video_id ) {
     110                return [];
     111        }
     112
     113        $parsed = [ 'type' => 'youtube', 'id' => $video_id ];
     114        if ( preg_match( '/^(?:www\.|m\.)?youtube\.com$/', $host ) && preg_match( '~^/shorts/[A-Za-z0-9_-]{11}/?$~', $path ) ) {
     115                $parsed['is_shorts'] = true;
     116        }
     117
     118        return $parsed;
    110119}
    111120
    … …  
    195204        $images_enabled = ! isset( $opts['image_popup'] ) || 'on' === $opts['image_popup'];
    196205        $pattern        = '/(?P<prefix>
    197                 (?:\/[^\/<>"\'\n\r]+\/(?:(?:ttl[ \x{3000}][^\/<>"\'\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)*)
     206                (?:(?<![^\s\x{00A0}\x{3000}])\/[^\/<>"\'\n\r]+\/(?:(?:ttl[ \x{3000}][^\/<>"\'\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)*)
    198207                |(?:(?<![A-Za-z0-9_])(?:(?:ttl[ \x{3000}][^\/<>"\'\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)+)
    199208        )?<a\b[^>]*\bhref=(?P<quote>["\'])(?P<href>[^"\']+)(?P=quote)[^>]*>(?P<text>.*?)<\/a>/isux';
    … …  
    398407}
    399408
    400 function misticease_video_text_link_html( string $url, string $video_id, string $type, int $popup_width, string $text, string $title = '' ): string {
     409function misticease_video_text_link_html( string $url, string $video_id, string $type, int $popup_width, string $text, string $title = '', bool $is_shorts = false, bool $popup_width_is_explicit = false ): string {
    401410        $attrs = '';
    402411        if ( '' !== $title ) {
    403412                $attrs = ' data-misticease-title="' . esc_attr( $title ) . '"';
     413        }
     414        if ( 'youtube' === $type && $is_shorts && ! is_feed() ) {
     415                $attrs .= ' data-video-shorts="1"';
     416                if ( ! $popup_width_is_explicit ) {
     417                        $attrs .= ' data-popup-height="' . (int) $popup_width . '"';
     418                }
    404419        }
    405420
    … …  
    451466                                }
    452467
    453                                 return misticease_video_text_link_html( $url, $video_id, $type, $popup_width, $link_text, $title ) . $tail;
    454                         }
    455 
    456                         return misticease_generate_thumbnail( $url, $video_id, $thumb_width, $popup_width, $loading_attr, $title, $thumb_radius ) . $tail;
     468                                return misticease_video_text_link_html( $url, $video_id, $type, $popup_width, $link_text, $title, ! empty( $parsed['is_shorts'] ), is_int( $spec['popup_width'] ) ) . $tail;
     469                        }
     470
     471                        return misticease_generate_thumbnail( $url, $video_id, $thumb_width, $popup_width, $loading_attr, $title, $thumb_radius, is_int( $spec['thumb_width'] ), is_int( $spec['popup_width'] ) ) . $tail;
    457472                },
    458473                $text
    … …  
    561576}
    562577
    563 function misticease_generate_thumbnail( string $video_url, string $video_id, int $thumb_width, int $popup_width, string $loading_attr, string $sml_title = '', int $thumb_radius = 0 ): string {
     578function misticease_generate_thumbnail( string $video_url, string $video_id, int $thumb_width, int $popup_width, string $loading_attr, string $sml_title = '', int $thumb_radius = 0, bool $thumb_width_is_explicit = false, bool $popup_width_is_explicit = false ): string {
    564579        $parsed = misticease_parse_video_service_url( $video_url );
    565580        if ( empty( $parsed['type'] ) || empty( $parsed['id'] ) ) {
    … …  
    577592        $recovery_fallback     = '';
    578593        $recovery_token        = '';
    579         if ( $thumbnail_url === misticease_local_thumb_url( $video_type, $video_id ) ) {
     594        if ( ! is_feed() && $thumbnail_url === misticease_local_thumb_url( $video_type, $video_id ) ) {
    580595                $recovery_fallback = misticease_remote_fallback( $video_type, $video_id );
    581596                $recovery_token    = misticease_hd_thumb_prefetch_token( $video_type, $video_id );
    … …  
    602617        }
    603618
     619        if ( is_feed() ) {
     620                $feed_alt = '' !== $sml_title ? $sml_title : $alt_text;
     621                return '<a href="' . esc_url( $video_url ) . '">'
     622                        . '<img src="' . esc_url( $thumbnail_url ) . '" alt="' . esc_attr( $feed_alt ) . '"'
     623                        . ' width="' . (int) $thumb_width . '" height="' . (int) $thumb_height . '" />'
     624                        . esc_html( $feed_alt )
     625                        . '</a>';
     626        }
     627
    604628        $classes = 'misticease-thumbnail';
     629        $is_shorts = 'youtube' === $video_type && ! empty( $parsed['is_shorts'] );
     630        if ( $is_shorts ) {
     631                $classes .= ' misticease-thumbnail--shorts';
     632                if ( ! $thumb_width_is_explicit ) {
     633                        $thumb_width = max( 1, (int) floor( $thumb_width * 9 / 16 ) );
     634                }
     635                $thumb_height = max( 1, (int) round( $thumb_width * 16 / 9 ) );
     636        }
    605637        if ( $is_vimeo_placeholder ) {
    606638                $classes .= ' misticease-thumbnail--vimeo-fallback';
    … …  
    619651
    620652        ob_start(); ?>
    621         <div class="<?php echo esc_attr( $classes ); ?>"
     653        <span class="<?php echo esc_attr( $classes ); ?>"
    622654             role="button"
    623655             tabindex="0"
    … …  
    625657             data-video-id="<?php echo esc_attr( $video_id ); ?>"
    626658             data-video-type="<?php echo esc_attr( $video_type ); ?>"
     659             <?php if ( $is_shorts ) : ?>
     660             data-video-shorts="1"
     661             <?php endif; ?>
    627662             data-popup-width="<?php echo esc_attr( $popup_width ); ?>"
     663             <?php if ( $is_shorts && ! $popup_width_is_explicit ) : ?>
     664             data-popup-height="<?php echo esc_attr( $popup_width ); ?>"
     665             <?php endif; ?>
    628666             <?php if ( '' !== $sml_title ) : ?>
    629667             data-misticease-title="<?php echo esc_attr( $sml_title ); ?>"
    … …  
    655693                        <img decoding="async" src="<?php echo esc_url( plugins_url( 'inc/app/img/play.png', misticease_basefile() ) ); ?>" width="60" height="60" alt="" aria-hidden="true" />
    656694                </span>
    657         </div>
     695        </span>
    658696        <?php
    659697        return (string) ob_get_clean();
    … …  
    900938function misticease_direct_image_url_pattern(): string {
    901939        return '/(?<!["\'=])
    902                    (?P<prefix>(?:\/[^\/<>":\'\n\r]+\/)?(?:(?:ttl[ \x{3000}][^\/<>":\'\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)*)?
     940                   (?P<prefix>(?:(?<![^\s\x{00A0}\x{3000}])\/[^\/<>":\'\n\r]+\/)?(?:(?:ttl[ \x{3000}][^\/<>":\'\n\r]+|t\d{1,4}|p\d{1,4}|r\d{1,4}|np|n)\/)*)?
    903941                   (?P<url>https?:\/\/(?:(?!\/{3})[^\s<>"\' ])+?\.(?:jpe?g|png|gif|webp|avif)(?:[?#](?:(?!\/{3})[^\s<>"\' ])*)?(?:\/{3}|(?=[\s<>"\' ]|$)))
    904942                   /ixu';
  • misticease/trunk/inc/app/misticease-thumbnails.php

    r3707712 r3726224  
    134134
    135135function misticease_touch_file( string $file ): void {
    136         if ( '' === $file ) {
    137                 return;
    138         }
    139 
    140         global $wp_filesystem;
    141         if ( ! isset( $wp_filesystem ) || ! $wp_filesystem || ! is_object( $wp_filesystem ) ) {
    142                 require_once ABSPATH . 'wp-admin/includes/file.php';
    143                 WP_Filesystem();
    144         }
    145 
    146         if ( ! $wp_filesystem || ! is_object( $wp_filesystem ) || ! method_exists( $wp_filesystem, 'touch' ) ) {
    147                 return;
    148         }
    149 
    150         if ( method_exists( $wp_filesystem, 'exists' ) && ! $wp_filesystem->exists( $file ) ) {
    151                 return;
    152         }
    153 
    154         if ( method_exists( $wp_filesystem, 'is_file' ) && ! $wp_filesystem->is_file( $file ) ) {
     136        if ( '' === $file || ! is_file( $file ) || is_link( $file ) || ! misticease_path_is_under_uploads( $file ) ) {
    155137                return;
    156138        }
    157139
    158140        $now = time();
    159         $wp_filesystem->touch( $file, $now, $now );
     141        $mtime = filemtime( $file );
     142        if ( false === $mtime || ( $now - (int) $mtime ) < DAY_IN_SECONDS ) {
     143                return;
     144        }
     145
     146        [ $cache_dir ] = misticease_cache_base();
     147        if ( is_link( $cache_dir ) ) {
     148                return;
     149        }
     150        $real_file    = realpath( $file );
     151        $real_cache   = realpath( $cache_dir );
     152        $real_uploads = realpath( untrailingslashit( misticease_uploads_root_normalized() ) );
     153        if ( false === $real_file || false === $real_cache || false === $real_uploads ) {
     154                return;
     155        }
     156        $real_cache = trailingslashit( wp_normalize_path( $real_cache ) );
     157        if ( 0 !== strpos( $real_cache, trailingslashit( wp_normalize_path( $real_uploads ) ) ) || 0 !== strpos( wp_normalize_path( $real_file ), $real_cache ) ) {
     158                return;
     159        }
     160
     161        if ( @touch( $file, $now, $now ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_touch -- This cache uses local uploads paths; avoid changing the shared filesystem connection.
     162                clearstatcache( true, $file );
     163        }
    160164}
    161165
    … …  
    642646        if ( ! is_dir( $dir ) ) {
    643647                misticease_opts_update( [ 'used_cache_bytes' => 0 ] );
    644                 set_transient( 'misticease_used_cache_bytes_t', 0, 10 * MINUTE_IN_SECONDS );
    645648                return;
    646649        }
    … …  
    654657        if ( $total <= $cap_bytes ) {
    655658                misticease_opts_update( [ 'used_cache_bytes' => (int) $total ] );
    656                 set_transient( 'misticease_used_cache_bytes_t', (int) $total, 10 * MINUTE_IN_SECONDS );
    657659                return;
    658660        }
    … …  
    680682        $final = misticease_calc_dir_size_bytes( $dir );
    681683        misticease_opts_update( [ 'used_cache_bytes' => (int) $final ] );
    682         set_transient( 'misticease_used_cache_bytes_t', (int) $final, 10 * MINUTE_IN_SECONDS );
    683684}
    684685
    … …  
    687688        if ( ! is_dir( $dir ) ) {
    688689                misticease_opts_update( [ 'used_cache_bytes' => 0 ] );
    689                 set_transient( 'misticease_used_cache_bytes_t', 0, 10 * MINUTE_IN_SECONDS );
    690690                return;
    691691        }
  • misticease/trunk/inc/bootstrap.php

    r3725318 r3726224  
    99
    1010if ( ! defined( 'MISTICEASE_VER' ) ) {
    11         define( 'MISTICEASE_VER', defined( 'MISTICEASE_VERSION' ) ? MISTICEASE_VERSION : '1.3.2' );
     11        define( 'MISTICEASE_VER', defined( 'MISTICEASE_VERSION' ) ? MISTICEASE_VERSION : '1.3.3' );
    1212}
    1313
    … …  
    2323}
    2424
     25add_action( 'init', static function (): void {
     26        // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- The bundled translation path is needed for inline documentation, option descriptions, and usage notices.
     27        load_plugin_textdomain( 'misticease', false, dirname( plugin_basename( misticease_basefile() ) ) . '/languages' );
     28}, 0 );
     29
    2530/* -------------------------------------------------------------------------
    2631 * Module loader
  • misticease/trunk/misticease.php

    r3725318 r3726224  
    33 * Plugin Name: MisticEase – Video Thumbnails & Draggable Popups
    44 * Plugin URI:  https://mitum.me/misticease
    5  * Description: Pasted YouTube or Vimeo URLs become thumbnails on publish to keep pages fast. A click opens a popup visitors can drag anywhere and watch as they read.
    6  * Version:     1.3.2
     5 * Description: Pasted YouTube or Vimeo URLs become thumbnails to keep pages fast. A click opens a popup visitors can drag anywhere and watch as they read.
     6 * Version:     1.3.3
    77 * Author:      MistiCuantum
    88 * Author URI:  https://mitum.me
    … …  
    8383    }
    8484    if ( !defined( 'MISTICEASE_VERSION' ) ) {
    85         define( 'MISTICEASE_VERSION', '1.3.2' );
     85        define( 'MISTICEASE_VERSION', '1.3.3' );
    8686    }
    8787    if ( !defined( 'MISTICEASE_PACKAGE_SLUG' ) ) {
  • misticease/trunk/readme.txt

    r3725318 r3726224  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.3.2
     7Stable tag: 1.3.3
    88License: GPLv2
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    106106* No space, line break, or HTML tag can come between the prefix and the URL.
    107107* When text comes before the prefix, put a space before the prefix.
     108* A `/Your text/` label must start at the beginning of the text or immediately after whitespace.
    108109* The text in `/Your text/` and `ttl` cannot contain `/`, `<`, `>`, or quotation marks (`"`, `'`).
    109110
    … …  
    264265== Upgrade Notice ==
    265266
    266 = 1.3.2 =
    267 The plugin name now says what the plugin does.
     267= 1.3.3 =
     268Fixes Slash Markup label detection to prevent slashes in ordinary text or URLs from breaking video and image markup.
    268269
    269270== Changelog ==
    270271
    271 = 1.3.2 =
    272 
    273 * Added what the plugin does to the plugin name.
    274 * Characters such as `. , ! ? )` directly after a URL are no longer treated as the end of the URL. When they follow with no space, mark the end with `///`.
    275 * Fixed image URLs with a double extension (for example, `photo.jpg.webp`) being cut off partway.
    276 
    277 = 1.3.1 =
    278 
    279 * Minor bug fixes and stability improvements.
     272= 1.3.3 =
     273
     274* Fixed Slash Markup label detection so slashes in ordinary text or other URLs no longer disrupt video and image markup.
    280275
    281276Older changelog entries are in `changelog-misticease.txt`.
Note: See TracChangeset for help on using the changeset viewer.