PluginProbe
Meow Gallery / 5.5.5
Meow Gallery v5.5.5
5.5.5 5.5.4 5.5.3 5.5.2 5.5.1 5.5.0 5.4.9 5.4.8 5.4.7 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 All 157 releases
← All changes | classes/core.php +255 -42 5.4.95.5.5 View file →
@@ -7,8 +7,10 @@
7 7 private $is_gallery_used = true; // TODO: Would be nice to detect if the gallery is actually used on the current page.
8 8 private $skeleton_handler;
9 9 private $pro_module = false;
10 10
11 + public $preview_cutoff = 12; // Limit the number of images to show in the preview (for performance reasons)
12 +
11 13 private static $plugin_option_name = 'mgl_options';
12 14 private $pro;
13 15 private $option_name = 'mgl_options';
14 16 private $infinite_layouts = [
@@ -21,8 +23,11 @@
21 23 ];
22 24
23 25 private $rewrittenMwlData = [];
24 26
27 + // Holds the image counts of the last preview render (used by the block editor).
28 + public $last_preview_counts = [ 'total' => 0, 'shown' => 0 ];
29 +
25 30 public function __construct() {
26 31 load_plugin_textdomain( MGL_DOMAIN, false, MGL_PATH . '/languages' );
27 32
28 33 //TODO: Move Skeleton into PRO
@@ -33,16 +38,19 @@
33 38 // Initializes the classes needed
34 39 MeowKit_MGL_Helpers::is_rest() && new Meow_MGL_Rest( $this );
35 40
36 41 // The gallery build process should only be enabled if the request is non-asynchronous
42 + add_filter( 'wp_get_attachment_image_attributes', array( $this, 'wp_get_attachment_image_attributes' ), 25, 3 );
43 +
37 44 if ( !MeowKit_MGL_Helpers::is_asynchronous_request() ) {
38 - add_filter( 'wp_get_attachment_image_attributes', array( $this, 'wp_get_attachment_image_attributes' ), 25, 3 );
45 +
39 46 if ( is_admin() || $this->is_gallery_used ) {
40 47 new Meow_MGL_Run( $this );
41 48 }
42 49 }
43 50
44 - // Load the Pro version *after* loading the Run class due to the JS file was gatherd into one file.
51 + // Load the Pro version *after* the Run class: both share the same JS bundle, and Run is
52 + // the one registering it (the Pro class only localizes extra data on it).
45 53
46 54 $this->pro_module = class_exists( 'MeowPro_MGL_Core' );
47 55 if ( $this->pro_module ) {
48 56 $this->pro = new MeowPro_MGL_Core( $this );
@@ -69,8 +77,34 @@
69 77 function collection() {
70 78 return "<b>Meow Collection</b>: This is only available in the Pro version. Please <a href='https://meowapps.com/products/meow-gallery-pro/'>upgrade to Meow Gallery Pro</a> to use this feature.";
71 79 }
72 80
81 + // Gallery and collection IDs are plain identifiers (generate_uniqid(), stored as varchar).
82 + // Anything else is rejected, and rejected rather than stripped: stripping could turn a crafted
83 + // ID into a different existing one. Security: esc_attr() does NOT escape "]", so an ID coming
84 + // from a request and concatenated into a shortcode string could close the tag and run
85 + // arbitrary shortcodes. Keep IDs on this charset and never build a shortcode string from them.
86 + public static function sanitize_id( $id ) {
87 + if ( !is_scalar( $id ) ) {
88 + return '';
89 + }
90 + $id = (string) $id;
91 + return preg_match( '/^[A-Za-z0-9_-]+$/', $id ) ? $id : '';
92 + }
93 +
94 + // Renders a collection from its ID. Deliberately calls the handler directly instead of going
95 + // through do_shortcode(): there is no shortcode string to inject into that way.
96 + public function render_collection( $id, $is_preview = false ) {
97 + $id = self::sanitize_id( $id );
98 + if ( $id === '' ) {
99 + return "<p class='meow-error'><b>Meow Gallery:</b> This collection ID is not valid.</p>";
100 + }
101 + if ( $this->pro_module && $this->pro ) {
102 + return $this->pro->collection( array( 'id' => $id ), $is_preview );
103 + }
104 + return $this->collection();
105 + }
106 +
73 107 public function can_access_settings() {
74 108 return apply_filters( 'mgl_allow_setup', current_user_can( 'manage_options' ) );
75 109 }
76 110
@@ -95,11 +129,14 @@
95 129 else if ( $this->gallery_layout === 'cascade' )
96 130 $sizes = '80vw';
97 131 else if ( $this->gallery_layout === 'justified' )
98 132 $sizes = '(max-width: 800px) 80vw, 50vw';
133 +
99 134 $sizes = apply_filters( 'mgl_sizes', $sizes, $this->gallery_layout, $attachment, $attr );
135 +
100 136 if ( !empty( $sizes ) )
101 137 $attr['sizes'] = $sizes;
138 +
102 139 return $attr;
103 140 }
104 141
105 142 function get_rewritten_mwl_data() {
@@ -105,8 +142,14 @@
105 142 function get_rewritten_mwl_data() {
106 143 return $this->rewrittenMwlData;
107 144 }
108 145
146 + // Get the IDs of the image attachments attached to the current post.
147 + private function get_attached_image_ids() {
148 + $attachments = get_attached_media( 'image' );
149 + return array_map( function( $x ) { return $x->ID; }, $attachments );
150 + }
151 +
109 152 function gallery( $atts, $options = [] ) {
110 153 $atts = apply_filters( 'shortcode_atts_gallery', $atts, null, $atts, 'gallery' );
111 154
112 155 $isPreview = isset( $options['isPreview'] ) ? $options['isPreview'] : false;
@@ -126,29 +169,33 @@
126 169 if ( isset( $atts['meow'] ) && $atts['meow'] === 'false' ) {
127 170 return gallery_shortcode( $atts );
128 171 }
129 172
130 - // If the attributes contain "collection" then use the collection shortcode instead
173 + // If the attributes contain "collection" then render that collection instead
131 174 if ( isset( $atts['collection'] ) && !empty( $atts['collection'] ) ) {
132 - return do_shortcode( '[meow-collection id="' . $atts['collection'] . '"]' );
175 + return $this->render_collection( $atts['collection'] );
133 176 }
134 177
135 178 $image_ids = array();
136 179 $layout = '';
137 180
138 - if ( isset( $atts['id'] ) && isset( $atts['ids'] ) ) {
181 + // All potential attributes that can be used to get the images for the gallery
182 + $has_id = isset( $atts['id'] ) && !empty( $atts['id'] );
183 + $has_ids = isset( $atts['ids'] ) && !empty( $atts['ids'] );
184 + $has_include = isset( $atts['include'] ) && !empty( $atts['include'] );
185 + $has_tags = isset( $atts['tags'] ) && !empty( $atts['tags'] );
186 + $has_posts = isset( $atts['posts'] ) && !empty( $atts['posts'] );
187 + $has_latest_posts = isset( $atts['latest_posts'] ) && !empty( $atts['latest_posts'] );
188 + $has_attachments = isset( $atts['attachments'] ) && ( $atts['attachments'] === 'true' || $atts['attachments'] === true || $atts['attachments'] === 1 || $atts['attachments'] === '1' );
139 189
140 - // Check if the ids are empty, then we can use the id
141 - if ( empty( $atts['ids'] ) ) {
142 - unset( $atts['ids'] );
143 - } else {
144 - error_log( "⚠️ Meow Gallery: in gallery $atts[id] both 'id' and 'ids' attributes are used in the same shortcode. 'id' will be ignored." );
145 - }
190 + if ( $has_id && $has_ids ) {
191 + unset( $atts['ids'] );
192 + error_log( "⚠️ Meow Gallery: in gallery $atts[id] both 'id' and 'ids' attributes are used in the same shortcode. 'id' will be ignored." );
146 193 }
147 194
148 195 // Get the IDs
149 196 #region media_ids
150 - if ( (isset( $atts['id'] ) && !empty($atts['id']) ) && !isset( $atts['ids'] ) ) {
197 + if ( $has_id && !$has_ids ) {
151 198 $shortcode_id = $atts['id'];
152 199
153 200 try {
154 201 $shortcode = $this->get_gallery_by_id( $shortcode_id );
@@ -153,9 +200,10 @@
153 200 try {
154 201 $shortcode = $this->get_gallery_by_id( $shortcode_id );
155 202 }
156 203 catch ( Exception $e ) {
157 - return "<p class='meow-error'><b>Meow Gallery:</b> This ID wasn't found in the Gallery Manager. (ID: $shortcode_id). " . $e->getMessage() . "</p>";
204 + $safe_id = esc_html( is_scalar( $shortcode_id ) ? $shortcode_id : '' );
205 + return "<p class='meow-error'><b>Meow Gallery:</b> This ID wasn't found in the Gallery Manager. (ID: $safe_id). " . esc_html( $e->getMessage() ) . "</p>";
158 206 }
159 207
160 208 if ( !isset( $shortcode['medias'] ) || !isset( $shortcode['medias']['thumbnail_ids'])) {
161 209 return "<p class='meow-error'><b>Meow Gallery:</b> Thumbnail IDs not found.</p>";
@@ -173,18 +221,32 @@
173 221
174 222 $atts = array_merge( $shortcode, $atts );
175 223 }
176 224
177 - if ( isset( $atts['ids'] ) ) {
225 + // Real Media Library folder path (e.g. rml="2025/France/Tours").
226 + // Re-evaluated here (not only from the top) so it also works when the value
227 + // comes from a saved gallery loaded via [gallery id="..."]. The logic lives
228 + // in Meow_MGL_RML::get_image_ids() to keep this method clean.
229 + $has_rml = isset( $atts['rml'] ) && !empty( $atts['rml'] );
230 + if ( $has_rml ) {
231 + $rml_ids = Meow_MGL_RML::get_image_ids( $atts['rml'] );
232 + if ( is_wp_error( $rml_ids ) ) {
233 + return "<p class='meow-error'><b>Meow Gallery:</b> " . esc_html( $rml_ids->get_error_message() ) . "</p>";
234 + }
235 + $image_ids = implode( ',', $rml_ids );
236 + }
237 +
238 + if ( $has_ids ) {
178 239 $image_ids = $atts['ids'];
179 240 }
180 - if ( isset( $atts['include'] ) ) {
241 +
242 + if ( $has_include ) {
181 243 $image_ids = is_array( $atts['include'] ) ? implode( ',', $atts['include'] ) : $atts['include'];
182 244 $atts['include'] = $image_ids;
183 245 }
184 246
185 247 // Tags support
186 - if ( isset( $atts['tags'] ) && !empty( $atts['tags'] ) ) {
248 + if ( $has_tags ) {
187 249 $tags = is_array( $atts['tags'] ) ? $atts['tags'] : array_map( 'trim', explode( ',', $atts['tags'] ) );
188 250
189 251 // Try multiple common taxonomies used for media tagging
190 252 $taxonomies_to_try = [ 'post_tag', 'media_tag', 'attachment_tag', 'attachment_category' ];
@@ -229,9 +291,9 @@
229 291 }
230 292 }
231 293 }
232 294
233 - if ( isset( $atts['latest_posts'] ) ) {
295 + if ( $has_latest_posts ) {
234 296 $num_posts = intval( $atts['latest_posts'] );
235 297
236 298 if ( $num_posts > 0 ) {
237 299
@@ -237,9 +299,9 @@
237 299
238 300 $latest_posts = get_posts( [ 'numberposts' => $num_posts ] );
239 301 $latest_posts_ids = array_map( function( $x ) { return $x->ID; }, $latest_posts );
240 302
241 - if ( isset( $atts['posts'] ) ) {
303 + if ( $has_posts ) {
242 304 error_log( "⚠️ Meow Gallery: in gallery $atts[id] both 'latest_posts' and 'posts' attributes are used in the same shortcode. 'latest_posts' will be merged with 'posts'.");
243 305 $atts['posts'] = array_merge( $latest_posts_ids, explode(',', $atts['posts']) );
244 306 }
245 307 else {
@@ -248,10 +310,17 @@
248 310 }
249 311
250 312 }
251 313
314 + if( $has_attachments ) {
315 + $attachmentIds = $this->get_attached_image_ids();
316 + if ( !empty( $attachmentIds ) ) {
317 + $image_ids = implode( ',', $attachmentIds );
318 + }
319 + }
320 +
252 321 $posts_ids = [];
253 - if (isset($atts['posts'])) {
322 + if ( $has_posts ) {
254 323
255 324 $posts_ids = is_array( $atts['posts'] ) ? $atts['posts'] : explode( ',', $atts['posts'] );
256 325 $featured_images = [];
257 326
@@ -279,23 +348,32 @@
279 348 $image_ids = implode( ',', $ids );
280 349
281 350 #endregion
282 351
283 - // Use attached images if still empty
284 - if ( empty( $image_ids ) ) {
285 - $attachments = get_attached_media( 'image' );
286 - $attachmentIds = array_map( function($x) { return $x->ID; }, $attachments );
352 + // Fall back to the attachments of the current post if the gallery is empty and the option is enabled.
353 + if ( empty( $image_ids ) && !$has_attachments && $this->get_option( 'use_attachments_on_empty', false ) ) {
354 + $attachmentIds = $this->get_attached_image_ids();
287 355 if ( !empty( $attachmentIds ) ) {
288 356 $image_ids = implode( ',', $attachmentIds );
357 + $has_attachments = true;
289 358 }
290 - else {
291 - return "<p class='meow-error'><b>Meow Gallery:</b> The gallery is empty.</p>";
359 + }
360 +
361 + // Use attached images if still empty
362 + if ( empty( $image_ids ) ) {
363 +
364 + if( $has_attachments ) {
365 + return "<p class='meow-error'><b>Meow Gallery:</b> No attached medias were found in the current post.</p>";
292 366 }
367 +
368 + return "<p class='meow-error'><b>Meow Gallery:</b> The gallery is empty.</p>";
293 369 }
294 370
295 371 if ( $isPreview ) {
296 372 $check = explode( ',', $image_ids );
297 - $check = array_slice( $check, 0, 40 );
373 + $total = count( $check );
374 + $check = array_slice( $check, 0, $this->preview_cutoff );
375 + $this->last_preview_counts = [ 'total' => $total, 'shown' => count( $check ) ];
298 376 $image_ids = implode( ',', $check );
299 377 }
300 378
301 379 // Limit images on archive/listing pages (not viewing the full single post)
@@ -336,11 +414,14 @@
336 414 if ( isset( $atts['order_by'] ) ) {
337 415 $orderby = $atts['order_by'];
338 416 }
339 417
340 - if( strpos( $orderby, '-' ) === 0 ) {
341 - $orderby = explode( '-', $orderby )[0];
342 - $order = explode( '-', $orderby )[1];
418 + if( strpos( $orderby, '-' ) != false ) {
419 + $left = explode( '-', $orderby )[0];
420 + $right = explode( '-', $orderby )[1];
421 +
422 + $orderby = $left;
423 + $order = $right;
343 424 }
344 425
345 426
346 427 $image_ids = explode( ',', $image_ids );
@@ -434,14 +515,21 @@
434 515 $data_gallery_images,
435 516 $data_atts
436 517 );
437 518
438 - // Run at /wp-includes/formatting.php on line 3501
439 - $textarr = preg_split( '/(<.*>)/U', $html , -1, PREG_SPLIT_DELIM_CAPTURE);
440 - if ( $textarr === false ) {
441 - $error = preg_last_error();
442 - error_log( "[MEOW GALLERY] Regex: " . preg_last_error_msg() . " (Code $error)" );
443 - return "<p class='meow-error'><b>Meow Gallery:</b> There was an error while building the gallery. Check your PHP Logs.</p>";
519 + // Run at /wp-includes/formatting.php on line 6037
520 + // WordPress returns early if the text is simple ASCII without emoji-like content,
521 + // so we only need to check preg_split if WordPress would actually run it.
522 + $needs_emoji_check = str_contains( $html, '&#x' ) ||
523 + !( ( function_exists( 'mb_check_encoding' ) && mb_check_encoding( $html, 'ASCII' ) ) || ! preg_match( '/[^\x00-\x7F]/', $html ) );
524 +
525 + if ( $needs_emoji_check ) {
526 + $textarr = preg_split( '/(<.*>)/U', $html , -1, PREG_SPLIT_DELIM_CAPTURE);
527 + if ( $textarr === false ) {
528 + $error = preg_last_error();
529 + error_log( "[MEOW GALLERY] Regex: " . preg_last_error_msg() . " (Code $error)" );
530 + return "<p class='meow-error'><b>Meow Gallery:</b> The gallery is too large for your Wordpress pcre.backtrack_limit, increase it in your server settings, or reduce your gallery size.";
531 + }
444 532 }
445 533
446 534 //The Gallery Container is where the images in the right layout will be rendered.
447 535 $html .= '<div class="mgl-gallery-container"></div>';
@@ -461,9 +549,11 @@
461 549
462 550 foreach ( $gallery_images as $image ) {
463 551 if ( !empty( $image['link_href'] ) ) {
464 552 // If there is a link, we will get the alt from the image id so we have a proper aria-label
465 - $aria = get_post_meta( $image['id'], '_wp_attachment_image_alt', true );
553 + $aria_label = __('Open image', MGL_DOMAIN);
554 + $aria_value = esc_attr( get_post_meta( $image['id'], '_wp_attachment_image_alt', true ) );
555 + $aria = !empty( $aria_value ) ? $aria_label . ': ' . $aria_value : $aria_label;
466 556
467 557 $custom_link_classes = apply_filters( 'mgl_custom_link_classes', '', $image );
468 558 $html .= '<a class="' . $custom_link_classes . '" href="' . $image['link_href'] . '" target="' . $image['link_target'] . '" rel="' . $image['link_rel'] .
469 559 '" aria-label="' . $aria . '">';
@@ -621,8 +711,26 @@
621 711 static function get_plugin_option_name() {
622 712 return self::$plugin_option_name;
623 713 }
624 714
715 + // Tiles density, from the shortcode attributes when they set one, from the options otherwise.
716 + // Shared by the front-end settings (Meow_MGL_Run) and the tiles CSS (Meow_MGL_Builders_Tiles).
717 + static function get_tiles_density( $atts = [] ) {
718 + if ( isset( $atts['density'] ) ) {
719 + return array(
720 + 'desktop' => $atts['density'],
721 + 'tablet' => $atts['density'],
722 + 'mobile' => $atts['density'],
723 + );
724 + }
725 + $options = get_option( self::$plugin_option_name, [] );
726 + return array(
727 + 'desktop' => $options['tiles_density'] ?? 'high',
728 + 'tablet' => $options['tiles_density_tablet'] ?? 'medium',
729 + 'mobile' => $options['tiles_density_mobile'] ?? 'low',
730 + );
731 + }
732 +
625 733 static function get_plugin_option( $option_name, $default = null ) {
626 734 $options = get_option( self::$plugin_option_name, null );
627 735 if ( !empty( $options ) && array_key_exists( $option_name, $options ) ) {
628 736 return $options[$option_name];
@@ -654,8 +762,10 @@
654 762 'animation' => false,
655 763 'image_size' => 'srcset',
656 764 'truncate_on_listing' => true,
657 765 'truncate_count' => 4,
766 + 'use_attachments_on_empty' => false,
767 + 'debug_logs' => false,
658 768
659 769 'rendering_mode' => 'dom', // Can be 'dom' or 'js'
660 770 'tiles_gutter' => 10,
661 771 'tiles_gutter_tablet' => 10,
@@ -696,10 +806,13 @@
696 806
697 807 // Stylish effect options
698 808 'stylish_enabled' => false,
699 809 'stylish_border_radius' => 6,
810 + 'stylish_border_width' => 0,
811 + 'stylish_border_color' => '#ffffff',
700 812 'stylish_shadow_opacity' => 0.08,
701 813 'stylish_shadow_opacity_hover' => 0.12,
814 + 'stylish_hover_lift' => 2,
702 815 'stylish_transition_speed' => 250,
703 816
704 817 //PRO OPTIONS
705 818 'infinite' => false,
@@ -807,8 +920,16 @@
807 920
808 921 function get_gallery_images( array $image_ids, array $atts, string $layout, string $size, array $posts_ids = []) {
809 922 global $wpdb;
810 923
924 + // Enable the image-attribute rewriting (and the 'mgl_sizes' filter) for the duration of this method.
925 + // This matters when get_gallery_images() is called directly (e.g. infinite scroll via REST) without
926 + // going through gallery(), which is what normally sets these on the initial page load.
927 + $previous_gallery_process = $this->gallery_process;
928 + $previous_gallery_layout = $this->gallery_layout;
929 + $this->gallery_process = true;
930 + $this->gallery_layout = $layout;
931 +
811 932 // Escape the array of IDs for SQL
812 933 $ids = array_map( 'intval', $image_ids );
813 934 $ids_str = implode( ',', $ids );
814 935
@@ -846,9 +967,12 @@
846 967 }
847 968 $ids = apply_filters( 'mgl_sort', $cleanIds, $images, $layout, $atts );
848 969
849 970 if ($layout === 'map') {
850 - return $this->get_map_images( $ids, $images, $atts );
971 + $map_result = $this->get_map_images( $ids, $images, $atts );
972 + $this->gallery_process = $previous_gallery_process;
973 + $this->gallery_layout = $previous_gallery_layout;
974 + return $map_result;
851 975 }
852 976
853 977 $result = [];
854 978 foreach ($ids as $index => $id) {
@@ -901,8 +1025,11 @@
901 1025
902 1026 $result[] = array_merge( $image, $mergedArray, $orientation );
903 1027 }
904 1028
1029 + $this->gallery_process = $previous_gallery_process;
1030 + $this->gallery_layout = $previous_gallery_layout;
1031 +
905 1032 return $result;
906 1033 }
907 1034
908 1035 private function get_image_class( $id, $layout, $noLightbox ) {
@@ -1111,8 +1238,60 @@
1111 1238 }
1112 1239 }
1113 1240
1114 1241
1242 + /**
1243 + * Reads the "medias" of a gallery. Only the ordered attachment IDs are stored (older versions
1244 + * also stored their URLs and mime types, which are ignored: they were a copy of the Media
1245 + * Library that went stale). This is also the shape that gets written back, and all the
1246 + * front-end needs. The Admin uses hydrate_medias() to get the URLs to display.
1247 + */
1248 + public static function normalize_medias( $medias ) {
1249 + $ids = ( is_array( $medias ) && isset( $medias['thumbnail_ids'] ) && is_array( $medias['thumbnail_ids'] ) )
1250 + ? array_values( $medias['thumbnail_ids'] ) : [];
1251 +
1252 + return [ 'thumbnail_ids' => $ids ];
1253 + }
1254 +
1255 + /**
1256 + * Adds the 'thumbnails' the Admin displays: one entry per ID, with its URLs and mime type
1257 + * resolved from the Media Library. Never stale, and a deleted attachment simply gets empty
1258 + * URLs (the Admin then shows a placeholder). Not used on the front-end, which only needs
1259 + * the IDs.
1260 + */
1261 + public static function hydrate_medias( $medias ) {
1262 + $ids = self::normalize_medias( $medias )['thumbnail_ids'];
1263 +
1264 + // One query for all the attachments instead of one per thumbnail. _prime_post_caches()
1265 + // only caches the attachments which exist, so anything still absent from the cache
1266 + // afterwards is gone: it's skipped instead of being queried on every request.
1267 + if ( !empty( $ids ) ) {
1268 + _prime_post_caches( array_values( array_unique( array_map( 'intval', $ids ) ) ), false, true );
1269 + }
1270 +
1271 + $thumbnails = [];
1272 + foreach ( $ids as $id ) {
1273 + $thumbnail = [ 'id' => $id, 'url' => '', 'zoom_url' => '', 'mime' => '' ];
1274 +
1275 + if ( !empty( $id ) && wp_cache_get( (int)$id, 'posts' ) ) {
1276 + $mime = get_post_mime_type( $id ) ?: '';
1277 + // Same rule as the latest_photos endpoint: videos have no image sizes, so their
1278 + // own URL is used for both the thumbnail and the zoom.
1279 + $is_video = strpos( $mime, 'video' ) !== false;
1280 + $url = $is_video ? wp_get_attachment_url( $id ) : wp_get_attachment_image_url( $id, 'thumbnail' );
1281 + $zoom = $is_video ? $url : wp_get_attachment_image_url( $id, 'large' );
1282 +
1283 + $thumbnail['url'] = $url ?: '';
1284 + $thumbnail['zoom_url'] = $zoom ?: $thumbnail['url'];
1285 + $thumbnail['mime'] = $mime;
1286 + }
1287 +
1288 + $thumbnails[] = $thumbnail;
1289 + }
1290 +
1291 + return [ 'thumbnail_ids' => $ids, 'thumbnails' => $thumbnails ];
1292 + }
1293 +
1115 1294 public function get_gallery_by_id( $id ) {
1116 1295 global $wpdb;
1117 1296 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1118 1297 $gallery = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $shortcodes_table WHERE id = %s", $id ), ARRAY_A );
@@ -1119,9 +1298,9 @@
1119 1298
1120 1299 if ( !$gallery ) {
1121 1300 throw new Exception( __( 'Gallery not found.', MGL_DOMAIN ));
1122 1301 }
1123 - $gallery['medias'] = maybe_unserialize( $gallery['medias'] );
1302 + $gallery['medias'] = self::normalize_medias( maybe_unserialize( $gallery['medias'] ) );
1124 1303 $gallery['posts'] = $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null;
1125 1304 $gallery['tags'] = $gallery['tags'] ? unserialize( $gallery['tags'] ) : null;
1126 1305
1127 1306 return $gallery;
@@ -1138,9 +1317,9 @@
1138 1317 $galleries[$gallery['id']] = [
1139 1318 'name' => $gallery['name'],
1140 1319 'description' => $gallery['description'],
1141 1320 'layout' => $gallery['layout'],
1142 - 'medias' => maybe_unserialize( $gallery['medias'] ),
1321 + 'medias' => self::normalize_medias( maybe_unserialize( $gallery['medias'] ) ),
1143 1322 'lead_image_id' => $gallery['lead_image_id'],
1144 1323 'order_by' => $gallery['order_by'],
1145 1324 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1146 1325 'dynamic_source' => $gallery['dynamic_source'],
@@ -1147,8 +1326,10 @@
1147 1326 'hero' => ( bool )$gallery['is_hero_mode'],
1148 1327 'posts' => $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null,
1149 1328 'latest_posts' => $gallery['latest_posts'],
1150 1329 'tags' => $gallery['tags'] ? unserialize( $gallery['tags'] ) : null,
1330 + 'dynamic_source' => $gallery['dynamic_source'],
1331 + 'rml' => $gallery['rml'] ?? null,
1151 1332 'updated' => strtotime( $gallery['updated_at'] )
1152 1333 ];
1153 1334 }
1154 1335 return $galleries;
@@ -1196,9 +1377,9 @@
1196 1377 $shortcodes[$gallery['id']] = [
1197 1378 'name' => $gallery['name'],
1198 1379 'description' => $gallery['description'],
1199 1380 'layout' => $gallery['layout'],
1200 - 'medias' => maybe_unserialize( $gallery['medias'] ),
1381 + 'medias' => self::hydrate_medias( maybe_unserialize( $gallery['medias'] ) ),
1201 1382 'lead_image_id' => $gallery['lead_image_id'],
1202 1383 'order_by' => $gallery['order_by'],
1203 1384 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1204 1385 'hero' => ( bool )$gallery['is_hero_mode'],
@@ -1205,8 +1386,9 @@
1205 1386 'posts' => $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null,
1206 1387 'latest_posts' => $gallery['latest_posts'],
1207 1388 'tags' => $gallery['tags'] ? unserialize( $gallery['tags'] ) : null,
1208 1389 'dynamic_source' => $gallery['dynamic_source'],
1390 + 'rml' => $gallery['rml'] ?? null,
1209 1391 'rank' => intval( $gallery['pref_rank'] ?? 0 ),
1210 1392 'updated' => strtotime( $gallery['updated_at'] )
1211 1393 ];
1212 1394 }
@@ -1216,8 +1398,39 @@
1216 1398 'galleries' => $shortcodes
1217 1399 ];
1218 1400 }
1219 1401
1402 + /**
1403 + * Just the id => name pairs, for the selectors (the Gutenberg block). They only need the
1404 + * names, so this avoids shipping every gallery's medias in the page and, unlike
1405 + * get_galleries(), it isn't paginated: the selectors used to be capped at 10 entries.
1406 + */
1407 + public function get_gallery_names() {
1408 + global $wpdb;
1409 + $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1410 + Meow_MGL_Migrations::check_db();
1411 +
1412 + $names = [];
1413 + $results = $wpdb->get_results( "SELECT id, name FROM $shortcodes_table ORDER BY name ASC", ARRAY_A );
1414 + foreach ( $results as $gallery ) {
1415 + $names[$gallery['id']] = [ 'name' => $gallery['name'] ];
1416 + }
1417 + return [ 'galleries' => $names ];
1418 + }
1419 +
1420 + public function get_collection_names() {
1421 + global $wpdb;
1422 + $collections_table = $wpdb->prefix . 'mgl_collections';
1423 + Meow_MGL_Migrations::check_db();
1424 +
1425 + $names = [];
1426 + $results = $wpdb->get_results( "SELECT id, name FROM $collections_table ORDER BY name ASC", ARRAY_A );
1427 + foreach ( $results as $collection ) {
1428 + $names[$collection['id']] = [ 'name' => $collection['name'] ];
1429 + }
1430 + return [ 'collections' => $names ];
1431 + }
1432 +
1220 1433 public function get_collection_by_id( $id ) {
1221 1434 global $wpdb;
1222 1435 $collections_table = $wpdb->prefix . 'mgl_collections';
1223 1436 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
@@ -1242,9 +1455,9 @@
1242 1455 'id' => $gallery['id'],
1243 1456 'name' => $gallery['name'],
1244 1457 'description' => $gallery['description'],
1245 1458 'layout' => $gallery['layout'],
1246 - 'medias' => unserialize( $gallery['medias'] ),
1459 + 'medias' => self::normalize_medias( maybe_unserialize( $gallery['medias'] ) ),
1247 1460 'lead_image_id' => $gallery['lead_image_id'],
1248 1461 'order_by' => $gallery['order_by'],
1249 1462 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1250 1463 'hero' => ( bool )$gallery['is_hero_mode'],
@@ -1304,9 +1517,9 @@
1304 1517 'id' => $gallery['id'],
1305 1518 'name' => $gallery['name'],
1306 1519 'description' => $gallery['description'],
1307 1520 'layout' => $gallery['layout'],
1308 - 'medias' => unserialize( $gallery['medias'] ),
1521 + 'medias' => self::hydrate_medias( maybe_unserialize( $gallery['medias'] ) ),
1309 1522 'lead_image_id' => $gallery['lead_image_id'],
1310 1523 'order_by' => $gallery['order_by'],
1311 1524 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1312 1525 'hero' => ( bool )$gallery['is_hero_mode'],