PluginProbe
Meow Gallery / 5.5.3
Meow Gallery v5.5.3
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
meow-gallery / classes / core.php

core.php in Meow Gallery 5.5.3, at classes/core.php

1,427 lines 47.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class Meow_MGL_Core {
4
5 private $gallery_process = false;
6 private $gallery_layout = 'tiles';
7 private $is_gallery_used = true; // TODO: Would be nice to detect if the gallery is actually used on the current page.
8 private $skeleton_handler;
9 private $pro_module = false;
10
11 private $preview_cutoff = 12; // Limit the number of images to show in the preview (for performance reasons)
12
13 private static $plugin_option_name = 'mgl_options';
14 private $pro;
15 private $option_name = 'mgl_options';
16 private $infinite_layouts = [
17 'tiles',
18 'masonry',
19 'justified',
20 'square',
21 'cascade',
22 // 'carousel', Added dynamically if the option is enabled
23 ];
24
25 private $rewrittenMwlData = [];
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
30 public function __construct() {
31 load_plugin_textdomain( MGL_DOMAIN, false, MGL_PATH . '/languages' );
32
33 //TODO: Move Skeleton into PRO
34 // Initialize skeleton handler
35 require_once( MGL_PATH . '/classes/skeleton.php' );
36 $this->skeleton_handler = new Meow_MGL_Skeleton();
37
38 // Initializes the classes needed
39 MeowKit_MGL_Helpers::is_rest() && new Meow_MGL_Rest( $this );
40
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
44 if ( !MeowKit_MGL_Helpers::is_asynchronous_request() ) {
45
46 if ( is_admin() || $this->is_gallery_used ) {
47 new Meow_MGL_Run( $this );
48 }
49 }
50
51 // Load the Pro version *after* loading the Run class due to the JS file was gatherd into one file.
52
53 $this->pro_module = class_exists( 'MeowPro_MGL_Core' );
54 if ( $this->pro_module ) {
55 $this->pro = new MeowPro_MGL_Core( $this );
56 } else {
57 add_shortcode( 'meow-collection', array( $this, 'collection' ) );
58 }
59
60 // Initialize the Admin if needed
61 add_action( 'init', array( $this, 'init' ) );
62 }
63
64 function init() {
65 is_admin() && new Meow_MGL_Admin( $this );
66
67 global $wpmgl;
68 $wpmgl = $this;
69
70 if ( $this->pro_module ) {
71 global $wpmgl_pro;
72 $wpmgl_pro = $this->pro;
73 }
74 }
75
76 function collection() {
77 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.";
78 }
79
80 public function can_access_settings() {
81 return apply_filters( 'mgl_allow_setup', current_user_can( 'manage_options' ) );
82 }
83
84 public function can_access_features() {
85 return apply_filters( 'mgl_allow_usage', current_user_can( 'upload_files' ) );
86 }
87
88 // Use by the Gutenberg block
89
90 // Rewrite the sizes attributes of the src-set for each image
91 function wp_get_attachment_image_attributes( $attr, $attachment, $size ) {
92 if (!$this->gallery_process)
93 return $attr;
94
95 $sizes = null;
96 if ( $this->gallery_layout === 'tiles' )
97 $sizes = '50vw';
98 else if ( $this->gallery_layout === 'masonry' )
99 $sizes = '50vw';
100 else if ( $this->gallery_layout === 'square' )
101 $sizes = '33vw';
102 else if ( $this->gallery_layout === 'cascade' )
103 $sizes = '80vw';
104 else if ( $this->gallery_layout === 'justified' )
105 $sizes = '(max-width: 800px) 80vw, 50vw';
106
107 $sizes = apply_filters( 'mgl_sizes', $sizes, $this->gallery_layout, $attachment, $attr );
108
109 if ( !empty( $sizes ) )
110 $attr['sizes'] = $sizes;
111
112 return $attr;
113 }
114
115 function get_rewritten_mwl_data() {
116 return $this->rewrittenMwlData;
117 }
118
119 // Get the IDs of the image attachments attached to the current post.
120 private function get_attached_image_ids() {
121 $attachments = get_attached_media( 'image' );
122 return array_map( function( $x ) { return $x->ID; }, $attachments );
123 }
124
125 function gallery( $atts, $options = [] ) {
126 $atts = apply_filters( 'shortcode_atts_gallery', $atts, null, $atts, 'gallery' );
127
128 $isPreview = isset( $options['isPreview'] ) ? $options['isPreview'] : false;
129 $isRest = isset( $options['isRest'] ) ? $options['isRest'] : false;
130
131 // Sanitize the atts to avoid XSS
132 $atts = array_map( function( $x ) {
133 if ( is_array( $x ) ) {
134 // In case it contains an array, we need to sanitize each element, and avoid a string conversion issue
135 return array_map( function( $y ) { return is_null( $y ) ? $y : esc_attr( $y ); }, $x );
136 } else {
137 // We don't sanitize null value, as it would convert it to a empty string
138 return is_null( $x ) ? $x : esc_attr( $x );
139 }
140 }, $atts );
141
142 if ( isset( $atts['meow'] ) && $atts['meow'] === 'false' ) {
143 return gallery_shortcode( $atts );
144 }
145
146 // If the attributes contain "collection" then use the collection shortcode instead
147 if ( isset( $atts['collection'] ) && !empty( $atts['collection'] ) ) {
148 return do_shortcode( '[meow-collection id="' . $atts['collection'] . '"]' );
149 }
150
151 $image_ids = array();
152 $layout = '';
153
154 // All potential attributes that can be used to get the images for the gallery
155 $has_id = isset( $atts['id'] ) && !empty( $atts['id'] );
156 $has_ids = isset( $atts['ids'] ) && !empty( $atts['ids'] );
157 $has_include = isset( $atts['include'] ) && !empty( $atts['include'] );
158 $has_tags = isset( $atts['tags'] ) && !empty( $atts['tags'] );
159 $has_posts = isset( $atts['posts'] ) && !empty( $atts['posts'] );
160 $has_latest_posts = isset( $atts['latest_posts'] ) && !empty( $atts['latest_posts'] );
161 $has_attachments = isset( $atts['attachments'] ) && ( $atts['attachments'] === 'true' || $atts['attachments'] === true || $atts['attachments'] === 1 || $atts['attachments'] === '1' );
162
163 if ( $has_id && $has_ids ) {
164 unset( $atts['ids'] );
165 error_log( "⚠️ Meow Gallery: in gallery $atts[id] both 'id' and 'ids' attributes are used in the same shortcode. 'id' will be ignored." );
166 }
167
168 // Get the IDs
169 #region media_ids
170 if ( $has_id && !$has_ids ) {
171 $shortcode_id = $atts['id'];
172
173 try {
174 $shortcode = $this->get_gallery_by_id( $shortcode_id );
175 }
176 catch ( Exception $e ) {
177 return "<p class='meow-error'><b>Meow Gallery:</b> This ID wasn't found in the Gallery Manager. (ID: $shortcode_id). " . $e->getMessage() . "</p>";
178 }
179
180 if ( !isset( $shortcode['medias'] ) || !isset( $shortcode['medias']['thumbnail_ids'])) {
181 return "<p class='meow-error'><b>Meow Gallery:</b> Thumbnail IDs not found.</p>";
182 }
183
184 $image_ids = $shortcode['medias']['thumbnail_ids'];
185 unset( $shortcode['medias'] );
186
187 //* We merge $atts into $shortcode ( not $shortcode into $atts ) so we can override the gallery settings even when using the ID.
188 // Override specifics: "default" layout from atts should be the layout from shortcode
189 if( array_key_exists('layout', $atts) && $atts['layout'] == 'default' )
190 {
191 $atts['layout'] = $shortcode['layout'];
192 }
193
194 $atts = array_merge( $shortcode, $atts );
195 }
196
197 // Real Media Library folder path (e.g. rml="2025/France/Tours").
198 // Re-evaluated here (not only from the top) so it also works when the value
199 // comes from a saved gallery loaded via [gallery id="..."]. The logic lives
200 // in Meow_MGL_RML::get_image_ids() to keep this method clean.
201 $has_rml = isset( $atts['rml'] ) && !empty( $atts['rml'] );
202 if ( $has_rml ) {
203 $rml_ids = Meow_MGL_RML::get_image_ids( $atts['rml'] );
204 if ( is_wp_error( $rml_ids ) ) {
205 return "<p class='meow-error'><b>Meow Gallery:</b> " . esc_html( $rml_ids->get_error_message() ) . "</p>";
206 }
207 $image_ids = implode( ',', $rml_ids );
208 }
209
210 if ( $has_ids ) {
211 $image_ids = $atts['ids'];
212 }
213
214 if ( $has_include ) {
215 $image_ids = is_array( $atts['include'] ) ? implode( ',', $atts['include'] ) : $atts['include'];
216 $atts['include'] = $image_ids;
217 }
218
219 // Tags support
220 if ( $has_tags ) {
221 $tags = is_array( $atts['tags'] ) ? $atts['tags'] : array_map( 'trim', explode( ',', $atts['tags'] ) );
222
223 // Try multiple common taxonomies used for media tagging
224 $taxonomies_to_try = [ 'post_tag', 'media_tag', 'attachment_tag', 'attachment_category' ];
225 $taxonomies_to_try = apply_filters( 'mgl_tags_taxonomies', $taxonomies_to_try );
226
227 $tagged_media_ids = [];
228
229 foreach ( $taxonomies_to_try as $taxonomy ) {
230 if ( !taxonomy_exists( $taxonomy ) ) {
231 continue;
232 }
233
234 $args = [
235 'post_type' => 'attachment',
236 'post_status' => 'inherit',
237 'posts_per_page' => -1,
238 'fields' => 'ids',
239 'tax_query' => [
240 [
241 'taxonomy' => $taxonomy,
242 'field' => 'slug',
243 'terms' => $tags,
244 ]
245 ]
246 ];
247
248 $query = new WP_Query( $args );
249 if ( !empty( $query->posts ) ) {
250 $tagged_media_ids = array_merge( $tagged_media_ids, $query->posts );
251 }
252 }
253
254 $tagged_media_ids = array_unique( $tagged_media_ids );
255
256 if ( !empty( $tagged_media_ids ) ) {
257 if ( !empty( $image_ids ) ) {
258 // Merge with existing IDs
259 $existing_ids = is_array( $image_ids ) ? $image_ids : explode( ',', $image_ids );
260 $image_ids = implode( ',', array_unique( array_merge( $existing_ids, $tagged_media_ids ) ) );
261 } else {
262 $image_ids = implode( ',', $tagged_media_ids );
263 }
264 }
265 }
266
267 if ( $has_latest_posts ) {
268 $num_posts = intval( $atts['latest_posts'] );
269
270 if ( $num_posts > 0 ) {
271
272 $latest_posts = get_posts( [ 'numberposts' => $num_posts ] );
273 $latest_posts_ids = array_map( function( $x ) { return $x->ID; }, $latest_posts );
274
275 if ( $has_posts ) {
276 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'.");
277 $atts['posts'] = array_merge( $latest_posts_ids, explode(',', $atts['posts']) );
278 }
279 else {
280 $atts['posts'] = implode( ',', $latest_posts_ids );
281 }
282 }
283
284 }
285
286 if( $has_attachments ) {
287 $attachmentIds = $this->get_attached_image_ids();
288 if ( !empty( $attachmentIds ) ) {
289 $image_ids = implode( ',', $attachmentIds );
290 }
291 }
292
293 $posts_ids = [];
294 if ( $has_posts ) {
295
296 $posts_ids = is_array( $atts['posts'] ) ? $atts['posts'] : explode( ',', $atts['posts'] );
297 $featured_images = [];
298
299 foreach ($posts_ids as $key => $post_id) {
300 $image_id = get_post_thumbnail_id($post_id);
301 if ($image_id === false || $image_id == 0) {
302 unset($posts_ids[$key]);
303 } else {
304 $featured_images[] = $image_id;
305 }
306 }
307
308 if ( count( $posts_ids ) !== count( $featured_images ) ) {
309 return "<p class='meow-error'><b>Meow Gallery:</b> The number of featured images and posts id should be the same.</p>";
310 }
311
312 $image_ids = implode(',', $featured_images);
313 $posts_ids = array_values($posts_ids);
314 }
315
316
317 // Filter the IDs
318 $ids = is_array( $image_ids ) ? $image_ids : explode( ',', $image_ids );
319 $ids = apply_filters( 'mgl_ids', $ids, $atts );
320 $image_ids = implode( ',', $ids );
321
322 #endregion
323
324 // Fall back to the attachments of the current post if the gallery is empty and the option is enabled.
325 if ( empty( $image_ids ) && !$has_attachments && $this->get_option( 'use_attachments_on_empty', false ) ) {
326 $attachmentIds = $this->get_attached_image_ids();
327 if ( !empty( $attachmentIds ) ) {
328 $image_ids = implode( ',', $attachmentIds );
329 $has_attachments = true;
330 }
331 }
332
333 // Use attached images if still empty
334 if ( empty( $image_ids ) ) {
335
336 if( $has_attachments ) {
337 return "<p class='meow-error'><b>Meow Gallery:</b> No attached medias were found in the current post.</p>";
338 }
339
340 return "<p class='meow-error'><b>Meow Gallery:</b> The gallery is empty.</p>";
341 }
342
343 if ( $isPreview ) {
344 $check = explode( ',', $image_ids );
345 $total = count( $check );
346 $check = array_slice( $check, 0, $this->preview_cutoff );
347 $this->last_preview_counts = [ 'total' => $total, 'shown' => count( $check ) ];
348 $image_ids = implode( ',', $check );
349 }
350
351 // Limit images on archive/listing pages (not viewing the full single post)
352 $should_truncate = $this->get_option( 'truncate_on_listing', true );
353 $is_archive_context = !is_singular() && !is_admin() && !$isPreview && !$isRest && $should_truncate;
354 $is_archive_context = apply_filters( 'mgl_is_archive_context', $is_archive_context, $atts );
355 if ( $is_archive_context ) {
356 $archive_limit = intval( $this->get_option( 'truncate_count', 4 ) );
357
358 if( $archive_limit === 0 ) {
359 return ""; // If the user does not want the gallery to be shown at all on listing pages
360 }
361
362 if ( $archive_limit > 0 ) {
363 $check = explode( ',', $image_ids );
364 if ( count( $check ) > $archive_limit ) {
365 $check = array_slice( $check, 0, $archive_limit );
366 $image_ids = implode( ',', $check );
367 $atts['is_truncated'] = true; // Flag to potentially show "view more" indicator
368 }
369 }
370 }
371
372 // Ordering
373 if ( isset( $atts['orderby'] ) || isset( $atts['order_by'] ) ) {
374
375 $orderby = '';
376 $order = 'asc';
377
378 if ( isset( $atts['order'] ) ) {
379 $order = $atts['order'];
380 }
381
382 if ( isset( $atts['orderby'] ) ) {
383 $orderby = $atts['orderby'];
384 }
385
386 if ( isset( $atts['order_by'] ) ) {
387 $orderby = $atts['order_by'];
388 }
389
390 if( strpos( $orderby, '-' ) != false ) {
391 $left = explode( '-', $orderby )[0];
392 $right = explode( '-', $orderby )[1];
393
394 $orderby = $left;
395 $order = $right;
396 }
397
398
399 $image_ids = explode( ',', $image_ids );
400 $image_ids = Meow_MGL_OrderBy::run( $image_ids, $orderby, $order );
401 $image_ids = implode( ',', $image_ids );
402 }
403
404 // Layout
405 if ( isset( $atts['layout'] ) && $atts['layout'] != 'default' ) {
406 $layout = $atts['layout'];
407 }
408 else if ( isset( $atts['mgl-layout'] ) && $atts['mgl-layout'] != 'default' ) {
409 $layout = $atts['mgl-layout'];
410 $atts['layout'] = $layout;
411 } else {
412 $layout = $this->get_option( 'layout', 'tiles' );
413 $atts['layout'] = $layout;
414 }
415
416
417 if ( $layout === 'none' || $layout === '' ) {
418 $layout = $this->get_option( 'layout', 'tiles' );
419 }
420
421
422 $layoutClass = 'Meow_MGL_Builders_' . ucfirst( $layout );
423 if ( !class_exists( $layoutClass ) ) {
424 error_log( "Meow Gallery: Class $layoutClass does not exist." );
425 return "<p class='meow-error'><b>Meow Gallery:</b> The layout $layout is not available in this version.</p>";
426 }
427
428 // Captions
429 if ( isset( $atts['captions'] ) && ( $atts['captions'] === false || $atts['captions'] === 'false' ) ) {
430 // This is to avoid issues linked to the old block editor for the Meow Gallery
431 $atts['captions'] = 'none';
432 }
433
434 // apply filter 'mgl_sort_ahead' to sort the images before anything else
435 if ( !empty( $image_ids ) ){
436 $image_ids = implode( ',', apply_filters( 'mgl_sort_ahead', explode( ',', $image_ids ), $layout, $atts ) );
437 }
438
439 //DEBUG: Display $atts
440 //error_log( print_r( $atts, 1 ) );
441
442 // Start the process of building the gallery
443 $this->gallery_process = true;
444 $this->gallery_layout = $layout;
445
446 // This should be probably removed.
447 // wp_enqueue_style( 'mgl-css' );
448
449 $infinite = $this->get_option( 'infinite', false ) && class_exists( 'MeowPro_MGL_Core' );
450
451 // $gen = new $layoutClass( $atts, !$isPreview && $infinite, $isPreview );
452 // $result = $gen->build( $image_ids );
453
454 do_action( 'mgl_' . $layout . '_gallery_created', $layout );
455 //$result = apply_filters( 'post_gallery', $result, $atts, null );
456
457 $this->rewrittenMwlData = apply_filters('mgl_force_rewrite_mwl_data', explode( ',', $image_ids ) );
458 do_action( 'mgl_gallery_created', $atts, explode( ',', $image_ids ), $layout );
459
460 $gallery_options = $this->get_gallery_options( $image_ids, $atts, $infinite, $isPreview, $layout );
461
462 // If infinite scroll option was enabled, get the images up to 12 at first.
463 $loading_image_ids = explode(',', $image_ids);
464 $loading_image_ids = apply_filters( 'mgl_sort', $loading_image_ids, [], $layout, $atts );
465
466 // Only add the carousel to the infinite layouts if the option is enabled
467 if( $infinite && $this->get_option( 'carousel_infinite', false ) ) {
468 $this->infinite_layouts[] = 'carousel';
469 }
470
471 if (!$isPreview && $infinite && in_array( $layout, $this->infinite_layouts ) ) {
472 $loading_image_ids = array_slice( $loading_image_ids, 0, 12 );
473 }
474
475 $gallery_images = $this->get_gallery_images( $loading_image_ids, $atts, $layout, $gallery_options['size'], $posts_ids );
476
477 // Get the class and data attributes
478 $class = $this->get_mgl_root_class( $atts );
479 $data_atts = $this->get_data_as_json( $atts );
480 $data_gallery_options = $this->get_data_as_json( $gallery_options );
481 $data_gallery_images = $this->get_data_as_json( $gallery_images );
482
483 $html = sprintf(
484 '<div class="%s" data-gallery-options="%s" data-gallery-images="%s" data-atts="%s">',
485 esc_attr( $class ),
486 $data_gallery_options,
487 $data_gallery_images,
488 $data_atts
489 );
490
491 // Run at /wp-includes/formatting.php on line 6037
492 // WordPress returns early if the text is simple ASCII without emoji-like content,
493 // so we only need to check preg_split if WordPress would actually run it.
494 $needs_emoji_check = str_contains( $html, '&#x' ) ||
495 !( ( function_exists( 'mb_check_encoding' ) && mb_check_encoding( $html, 'ASCII' ) ) || ! preg_match( '/[^\x00-\x7F]/', $html ) );
496
497 if ( $needs_emoji_check ) {
498 $textarr = preg_split( '/(<.*>)/U', $html , -1, PREG_SPLIT_DELIM_CAPTURE);
499 if ( $textarr === false ) {
500 $error = preg_last_error();
501 error_log( "[MEOW GALLERY] Regex: " . preg_last_error_msg() . " (Code $error)" );
502 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.";
503 }
504 }
505
506 //The Gallery Container is where the images in the right layout will be rendered.
507 $html .= '<div class="mgl-gallery-container"></div>';
508
509 // Add skeleton loading placeholder to prevent layout shift
510 $skeleton_loading = $this->get_option( 'skeleton_loading', true );
511 if ( $skeleton_loading ) {
512 $image_count = count( $gallery_images );
513 $html .= $this->skeleton_handler->get_skeleton_html( $layout, $gallery_options, $image_count );
514 }
515
516 // Use the DOM to generate the images (so that lightboxes can hook into them, and for better SEO)
517 // If there are no images, the JS will look for the img_html and build the gallery from there.
518 // TODO: We should check why it's not working with the carousel (for map, it's normal).
519 if ( $layout !== 'map' && $layout !== 'carousel' && $this->get_option( 'rendering_mode', 'dom' ) === 'dom' ) {
520 $html .= '<div class="mgl-gallery-images">';
521
522 foreach ( $gallery_images as $image ) {
523 if ( !empty( $image['link_href'] ) ) {
524 // If there is a link, we will get the alt from the image id so we have a proper aria-label
525 $aria_label = __('Open image', MGL_DOMAIN);
526 $aria_value = esc_attr( get_post_meta( $image['id'], '_wp_attachment_image_alt', true ) );
527 $aria = !empty( $aria_value ) ? $aria_label . ': ' . $aria_value : $aria_label;
528
529 $custom_link_classes = apply_filters( 'mgl_custom_link_classes', '', $image );
530 $html .= '<a class="' . $custom_link_classes . '" href="' . $image['link_href'] . '" target="' . $image['link_target'] . '" rel="' . $image['link_rel'] .
531 '" aria-label="' . $aria . '">';
532 $html .= $image['img_html'];
533 $html .= '</a>';
534 }
535 else {
536 $html .= $image['img_html'];
537 }
538 }
539 $html .= '</div>';
540 }
541
542 $html .= '</div>';
543
544 $this->gallery_process = false;
545
546 return $html;
547 }
548
549 public function get_gallery_options(string $image_ids, array $atts, bool $infinite, bool $is_preview, string $layout) {
550 $image_ids = explode(',', $image_ids);
551 $wp_upload_dir = wp_upload_dir();
552 $options = $this->get_all_options();
553 $id = uniqid();
554 $size = isset( $atts['size'] ) ? $atts['size'] : 'large';
555 $size = apply_filters( 'mgl_media_size', $size );
556 $custom_class = isset( $atts['custom-class'] ) ? $atts['custom-class'] : null;
557 $link = isset( $atts['link'] ) ? $atts['link'] : ( $options['link'] ?? null );
558 $updir = trailingslashit( $wp_upload_dir['baseurl'] );
559 $captions = isset( $atts['captions'] ) ? $atts['captions'] : ( $options['captions'] ?? 'none' );
560 $animation = null;
561 if ( isset( $atts['animation'] ) && $atts['animation'] != 'default' ) {
562 $animation = $atts['animation'];
563 } else {
564 $animation = $options['animation'] ?? null;
565 }
566 $class_id = 'mgl-gallery-' . $id;
567 $layouts = [];
568
569 // Justified
570 $justified_row_height = $options['justified_row_height'];
571 $justified_gutter = $options['justified_gutter'];
572 if ( $layout === 'justified' ) {
573 $justified_row_height = $atts['row-height'] ?? $options['justified_row_height'];
574 $justified_gutter = $atts['gutter'] ?? $options['justified_gutter'];
575 }
576 // Masonry
577 $masonry_gutter = $options['masonry_gutter'];
578 $masonry_columns = $options['masonry_columns'];
579 if ( $layout === 'masonry' ) {
580 $masonry_gutter = $atts['gutter'] ?? $options['masonry_gutter'];
581 $masonry_columns = $atts['columns'] ?? $options['masonry_columns'];
582 }
583 // Square
584 $square_gutter = $options['square_gutter'];
585 $square_columns = $options['square_columns'];
586 if ( $layout === 'square' ) {
587 $square_gutter = $atts['gutter'] ?? $options['square_gutter'];
588 $square_columns = $atts['columns'] ?? $options['square_columns'];
589 }
590 // Cascade
591 $cascade_gutter = $options['cascade_gutter'];
592 if ( $layout === 'cascade' ) {
593 $layouts = [ 'o', 'i', 'ii' ];
594 $cascade_gutter = $atts['gutter'] ?? $options['cascade_gutter'];
595 }
596 // Tiles
597 $tiles_gutter = $options['tiles_gutter'];
598 $tiles_gutter_tablet = $options['tiles_gutter_tablet'];
599 $tiles_gutter_mobile = $options['tiles_gutter_mobile'];
600 $tiles_density = $options['tiles_density'];
601 $tiles_density_tablet = $options['tiles_density_tablet'];
602 $tiles_density_mobile = $options['tiles_density_mobile'];
603 if ( $layout === 'tiles' ) {
604 $tiles_gutter = $atts['gutter'] ?? $options['tiles_gutter'];
605 $tiles_gutter_tablet = $atts['gutter'] ?? $options['tiles_gutter_tablet'];
606 $tiles_gutter_mobile = $atts['gutter'] ?? $options['tiles_gutter_mobile'];
607 $tiles_density = $atts['density'] ?? $options['tiles_density'];
608 $tiles_density_tablet = $atts['density'] ?? $options['tiles_density_tablet'];
609 $tiles_density_mobile = $atts['density'] ?? $options['tiles_density_mobile'];
610 }
611 // Horizontal
612 $horizontal_gutter = $options['horizontal_gutter'];
613 $horizontal_image_height = $options['horizontal_image_height'];
614 $horizontal_hide_scrollbar = $options['horizontal_hide_scrollbar'];
615 if ( $layout === 'horizontal' ) {
616 $horizontal_gutter = $atts['gutter'] ?? $options['horizontal_gutter'];
617 $horizontal_image_height = $atts['image_height'] ?? $options['horizontal_image_height'];
618 $horizontal_hide_scrollbar = $atts['hide_scrollbar'] ?? $options['horizontal_hide_scrollbar'];
619 }
620 // Carousel
621 $carousel_gutter = $options['carousel_gutter'];
622 $carousel_arrow_nav_enabled = $options['carousel_arrow_nav_enabled'];
623 $carousel_dot_nav_enabled = $options['carousel_dot_nav_enabled'];
624 $carousel_image_height = $options['carousel_image_height'];
625 $carousel_keep_aspect_ratio = $options['carousel_aspect_ratio'] ?? false;
626 if ( $layout === 'carousel' ) {
627 $carousel_gutter = $atts['gutter'] ?? $options['carousel_gutter'];
628 $carousel_arrow_nav_enabled = $atts['arrow_nav_enabled'] ?? $options['carousel_arrow_nav_enabled'];
629 $carousel_dot_nav_enabled = $atts['dot_nav_enabled'] ?? $options['carousel_dot_nav_enabled'];
630 $carousel_image_height = $atts['image_height'] ?? $options['carousel_image_height'];
631 $carousel_keep_aspect_ratio = array_key_exists( 'keep-aspect-ratio', $atts ) ? $atts['keep-aspect-ratio'] : 1;
632 }
633 // Map
634 $map_gutter = $options['map_gutter'];
635 $map_height = $options['map_height'];
636 if ( $layout === 'map' ) {
637 $map_gutter = $atts['gutter'] ?? $options['map_gutter'];
638 $map_height = $atts['map_height'] ?? $options['map_height'];
639 }
640
641 return compact(
642 'image_ids',
643 'id',
644 'size',
645 'infinite',
646 'custom_class',
647 'link',
648 'is_preview',
649 'updir',
650 'captions',
651 'animation',
652 'layout',
653 'justified_row_height',
654 'justified_gutter',
655 'masonry_gutter',
656 'masonry_columns',
657 'square_gutter',
658 'square_columns',
659 'cascade_gutter',
660 'class_id',
661 'layouts',
662 'tiles_gutter',
663 'tiles_gutter_tablet',
664 'tiles_gutter_mobile',
665 'tiles_density',
666 'tiles_density_tablet',
667 'tiles_density_mobile',
668 'horizontal_gutter',
669 'horizontal_image_height',
670 'horizontal_hide_scrollbar',
671 'carousel_gutter',
672 'carousel_arrow_nav_enabled',
673 'carousel_dot_nav_enabled',
674 'carousel_image_height',
675 'carousel_keep_aspect_ratio',
676 'map_gutter',
677 'map_height',
678 );
679 }
680
681 // #region Options
682
683 static function get_plugin_option_name() {
684 return self::$plugin_option_name;
685 }
686
687 static function get_plugin_option( $option_name, $default = null ) {
688 $options = get_option( self::$plugin_option_name, null );
689 if ( !empty( $options ) && array_key_exists( $option_name, $options ) ) {
690 return $options[$option_name];
691 }
692 return $default;
693 }
694
695 function get_option( $option_name, $default = null ) {
696 $options = $this->get_all_options();
697 if ( array_key_exists( $option_name, $options ) ) {
698 return $options[$option_name];
699 }
700 return $default;
701 }
702
703 function reset_options() {
704 delete_option( 'mgl_db_version');
705 delete_option( $this->option_name );
706 }
707
708 function list_options() {
709 return array(
710 'layout' => 'tiles',
711 'ç' => 'none',
712 'link' => null,
713 'caption_source' => 'caption',
714 'captions_alignment' => 'center',
715 'captions_background' => 'fade-black',
716 'animation' => false,
717 'image_size' => 'srcset',
718 'truncate_on_listing' => true,
719 'truncate_count' => 4,
720 'use_attachments_on_empty' => false,
721 'debug_logs' => false,
722
723 'rendering_mode' => 'dom', // Can be 'dom' or 'js'
724 'tiles_gutter' => 10,
725 'tiles_gutter_tablet' => 10,
726 'tiles_gutter_mobile' => 10,
727 'tiles_density' => 'high',
728 'tiles_density_tablet' => 'medium',
729 'tiles_density_mobile' => 'low',
730
731 'justified_density' => 'low',
732 'justified_density_tablet' => 'medium',
733 'justified_density_mobile' => 'low',
734
735 'masonry_gutter' => 5,
736 'masonry_columns' => 3,
737 'masonry_left_to_right' => false,
738 'justified_gutter' => 5,
739 'justified_row_height' => 200,
740 'square_gutter' => 5,
741 'square_columns' => 5,
742 'cascade_gutter' => 10,
743 'horizontal_gutter' => 10,
744 'horizontal_image_height' => 500,
745 'horizontal_hide_scrollbar' => false,
746 'carousel_gutter' => 5,
747 'carousel_image_height' => 500,
748 'carousel_arrow_nav_enabled' => true,
749 'carousel_dot_nav_enabled' => true,
750 'carousel_infinite' => false,
751 'map_engine' => 'googlemaps',
752 'map_height' => 500,
753 'map_zoom' => 10,
754 'map_gutter' => 10,
755 'googlemaps_token' => '',
756 'googlemaps_style' => '[]',
757 'mapbox_token' => '',
758 'mapbox_style' => '{"username":"", "style_id":""}',
759 'maptiler_token' => '',
760
761 // Stylish effect options
762 'stylish_enabled' => false,
763 'stylish_border_radius' => 6,
764 'stylish_border_width' => 0,
765 'stylish_border_color' => '#ffffff',
766 'stylish_shadow_opacity' => 0.08,
767 'stylish_shadow_opacity_hover' => 0.12,
768 'stylish_hover_lift' => 2,
769 'stylish_transition_speed' => 250,
770
771 //PRO OPTIONS
772 'infinite' => false,
773 'infinite_buffer' => 0,
774 'right_click' => false,
775 'gallery_shortcode_override_disabled' => false,
776 'skeleton_loading' => false,
777 );
778 }
779
780 function list_pro_options() {
781 return array(
782 'infinite' => false,
783 'infinite_buffer' => 0,
784 'right_click' => false,
785 'gallery_shortcode_override_disabled' => false,
786 'skeleton_loading' => false,
787 );
788 }
789
790 function get_all_options() {
791 $options = get_option( $this->option_name, null );
792 $options = $this->check_options( $options );
793 return $options;
794 }
795
796 // Upgrade from the old way of storing options to the new way.
797
798 function check_options( $options = [] ) {
799 $plugin_options = $this->list_options();
800 $pro_options = $this->list_pro_options();
801
802 $options = empty( $options ) ? [] : $options;
803 $hasChanges = false;
804
805 foreach ( $plugin_options as $option => $default ) {
806 // The option already exists
807 if ( isset( $options[$option] ) ) {
808 continue;
809 }
810 // The option does not exist, so we need to add it.
811 // Let's use the old value if any, or the default value.
812 $options[$option] = get_option( 'mgl_' . $option, $default );
813 delete_option( 'mgl_' . $option );
814 $hasChanges = true;
815 }
816
817 if( !$this->pro_module ) {
818 foreach ( $pro_options as $pro_option => $default ) {
819 if ( $options[$pro_option] !== $default ) {
820 $options[$pro_option] = $default;
821 $hasChanges = true;
822 }
823 }
824 }
825
826 if ( $hasChanges ) {
827 update_option( $this->option_name , $options );
828 }
829 return $options;
830 }
831
832 function update_options( $options ) {
833 if ( !update_option( $this->option_name, $options, false ) ) {
834 return false;
835 }
836 $options = $this->sanitize_options();
837 return $options;
838 }
839
840
841 // Validate and keep the options clean and logical.
842 function sanitize_options() {
843 $options = $this->get_all_options();
844 // something to do
845 return $options;
846 }
847
848 # endregion
849
850 function get_caption_from_source( $image ) {
851 $caption_source = $this->get_option( 'caption_source', 'caption' );
852 $caption = '';
853
854 switch ( $caption_source ) {
855 case 'title':
856 $caption = $image->title;
857 break;
858 case 'caption':
859 $caption = $image->caption;
860 break;
861 case 'description':
862 $caption = $image->description;
863 break;
864 case 'alt':
865 $caption = $image->alt;
866 break;
867 default:
868 $caption = $image->caption;
869 break;
870 }
871
872 return $caption;
873 }
874
875 function get_gallery_images( array $image_ids, array $atts, string $layout, string $size, array $posts_ids = []) {
876 global $wpdb;
877
878 // Enable the image-attribute rewriting (and the 'mgl_sizes' filter) for the duration of this method.
879 // This matters when get_gallery_images() is called directly (e.g. infinite scroll via REST) without
880 // going through gallery(), which is what normally sets these on the initial page load.
881 $previous_gallery_process = $this->gallery_process;
882 $previous_gallery_layout = $this->gallery_layout;
883 $this->gallery_process = true;
884 $this->gallery_layout = $layout;
885
886 // Escape the array of IDs for SQL
887 $ids = array_map( 'intval', $image_ids );
888 $ids_str = implode( ',', $ids );
889
890 $query = "SELECT
891 p.ID id,
892 p.post_title title,
893 p.post_content description,
894 p.post_excerpt caption,
895 pm.meta_value alt,
896 pm2.meta_value meta
897
898 FROM $wpdb->posts p
899 LEFT JOIN $wpdb->postmeta pm ON pm.post_id = p.ID AND pm.meta_key = '_wp_attachment_image_alt'
900 LEFT JOIN $wpdb->postmeta pm2 ON pm2.post_id = p.ID AND pm2.meta_key = '_wp_attachment_metadata'
901
902 WHERE p.post_type = 'attachment'
903
904 AND p.ID IN (" . $ids_str . ")
905 ";
906
907 $res = $wpdb->get_results( $query );
908
909 $ids = explode( ',', $ids_str );
910 $images = [];
911 foreach ( $res as $r ) {
912 $images[$r->id] = [
913 'caption' => $this->get_caption_from_source( $r ),
914 'meta' => unserialize( $r->meta ),
915 ];
916 }
917 $cleanIds = [];
918 foreach ( $ids as $id ) {
919 if ( isset( $images[$id] ) )
920 array_push( $cleanIds, $id );
921 }
922 $ids = apply_filters( 'mgl_sort', $cleanIds, $images, $layout, $atts );
923
924 if ($layout === 'map') {
925 $map_result = $this->get_map_images( $ids, $images, $atts );
926 $this->gallery_process = $previous_gallery_process;
927 $this->gallery_layout = $previous_gallery_layout;
928 return $map_result;
929 }
930
931 $result = [];
932 foreach ($ids as $index => $id) {
933 $image = $images[$id];
934
935 // Determine orientation if layout is 'tiles'
936 $orientation = [];
937 if ($layout === 'tiles') {
938 $orientation = [
939 'orientation' => ($image['meta']['width'] > $image['meta']['height'] ? 'o' : 'i')
940 ];
941 }
942
943 $default_link = $this->get_option( 'link', null );
944 $link_attr = $this->get_link_attributes( $id, $atts['link'] ?? $default_link, $image );
945 $no_lightbox = $link_attr['type'] === 'link';
946
947 $mergedArray = [
948 'id' => $id,
949 'caption' => wp_kses(
950 html_entity_decode(apply_filters( 'mgl_caption', $image['caption'], $id ), ENT_QUOTES),
951 [
952 'strong' => [], // Bold
953 'b' => [], // Bold alternative
954 'em' => [], // Italic
955 'i' => [] // Italic alternative
956 ]
957 ),
958 'img_html' => apply_filters( 'mgl_gallery_written',
959 $this->get_img_html( $id, $size, $layout, $atts, $image, $no_lightbox ),
960 $layout
961 ),
962 'link_href' => $link_attr['href'] ?? null,
963 'link_target' => $link_attr['target'] ?? null,
964 'link_rel' => $link_attr['rel'] ?? null,
965 'attributes' => $this->get_attributes( $id, $image, $layout ),
966 ];
967
968 if( !empty( $posts_ids ) && isset( $atts['hero'] ) && $atts['hero'] ) {
969
970 $post_id = $posts_ids[$index];
971 $post = get_post( $post_id );
972
973 $mergedArray['featured_post_id'] = $post_id;
974 $mergedArray['featured_post_title'] = $post->post_title;
975 $mergedArray['featured_post_excerpt'] = $post->post_excerpt;
976 $mergedArray['featured_post_url'] = get_permalink( $post_id );
977
978 }
979
980 $result[] = array_merge( $image, $mergedArray, $orientation );
981 }
982
983 $this->gallery_process = $previous_gallery_process;
984 $this->gallery_layout = $previous_gallery_layout;
985
986 return $result;
987 }
988
989 private function get_image_class( $id, $layout, $noLightbox ) {
990 $base_class = 'wp-image-' . $id;
991
992 if( $layout === 'carousel' ) {
993 $base_class .= ' skip-lazy';
994 }
995
996 if ( $noLightbox ) {
997 $base_class .= ' no-lightbox';
998 }
999 return $base_class;
1000 }
1001
1002 private function get_img_html( $id, $size, $layout, $atts, $data, $noLightbox ) {
1003
1004 //check if the media is a video
1005 $media_type = get_post_mime_type( $id );
1006 if ( strpos( $media_type, 'video' ) !== false ) {
1007 $video = wp_get_attachment_url( $id );
1008 $video = apply_filters( 'mgl_video', $video, $id, $data );
1009 if ( !empty( $video ) ) {
1010 return '<video class="wp-video-'. $id .'" controls="controls" onclick="() => this.play();"><source src="' . $video . '" type="' . $media_type . '"></video>';
1011 }
1012 }
1013
1014 $image_size = $this->get_option( 'image_size', 'srcset' );
1015 $img_html = null;
1016 if ( empty( $image_size ) || $image_size === 'srcset' ) {
1017 $img_html = wp_get_attachment_image( $id, $size, false, [
1018 'class' => $this->get_image_class( $id, $layout, $noLightbox ),
1019 'draggable' => $layout === 'carousel' ? 'false' : null,
1020 ]);
1021 }
1022 else {
1023 $info = wp_get_attachment_image_src( $id, $image_size );
1024 $alt_text = get_post_meta( $id, '_wp_attachment_image_alt', true );
1025
1026 $img_html = '<img loading="lazy" src="' . $info[0] . '" class="' . $this->get_image_class( $id, $layout, $noLightbox ) . '" alt="' . esc_attr( $alt_text ) . '" />';
1027 }
1028
1029 if ( $layout === 'masonry' ) {
1030 $masonry_column = $this->get_option( 'masonry_column', 3 );
1031 $columns = ( isset( $atts['columns'] ) ? $atts['columns'] : $masonry_column ) + 1;
1032 $img_html = str_replace( '100vw', 100 / $columns . 'vw', $img_html );
1033 }
1034 else if ( $layout === 'square' ) {
1035 $square_column = $this->get_option( 'square_columns', 5 );
1036 $columns = ( isset( $atts['columns'] ) ? $atts['columns'] : $square_column ) + 1;
1037 $img_html = str_replace( '100vw', 100 / $columns . 'vw', $img_html );
1038 }
1039 else if ( $layout === 'cascade' ) {
1040 $img_html = str_replace( '100vw', 100 / 3 . 'vw', $img_html );
1041 }
1042
1043 return wp_kses( $img_html, [
1044 'img' => [
1045 'src' => true,
1046 'srcset' => true,
1047 'loading' => true,
1048 'tabindex' => true,
1049 'sizes' => true,
1050 'class' => true,
1051 'id' => true,
1052 'width' => true,
1053 'height' => true,
1054 'alt' => true,
1055 'align' => true,
1056 'draggable' => true,
1057 ]
1058 ]
1059 );
1060 }
1061
1062 private function get_link_attributes( $id, $link, $data ) {
1063 $link_url = null;
1064 $type = 'media';
1065 $rel = null;
1066 $target = '_self';
1067
1068 if ( $link === 'attachment' ) {
1069 $link_url = get_permalink( (int)$id );
1070 }
1071 else if ( $link === 'media' || $link === 'file' ) {
1072 $wpUploadDir = wp_upload_dir();
1073 $updir = trailingslashit( $wpUploadDir['baseurl'] );
1074 if ( isset( $data['meta']['file'] ) ) {
1075 $link_url = $updir . $data['meta']['file'];
1076 } else {
1077 $link_url = get_permalink( (int)$id );
1078 }
1079 }
1080 else if ( $link === null ){
1081 $link_url = get_post_meta( $id, '_gallery_link_url', true );
1082 if ( !empty( $link_url ) ) {
1083 $type = 'link';
1084 $target = get_post_meta( $id, '_gallery_link_target', true );
1085 }
1086 }
1087
1088 $link_attr = [
1089 'href' => !empty( $link_url ) ? esc_url( $link_url ) : null,
1090 'target' => $target,
1091 'type' => $type,
1092 'rel' => $rel,
1093 ];
1094
1095
1096 return apply_filters( 'mgl_link_attributes', $link_attr, (int)$id, $data );
1097 }
1098
1099 private function get_attributes( $id, $data, $layout ) {
1100 $attributes = '';
1101 if ( $layout === 'raw' ) {
1102 if ( isset( $data['meta'] ) && isset( $data['meta']['width'] ) && isset( $data['meta']['height'] ) ) {
1103 $attributes = 'data-mgl-id=' . $id . ' data-mgl-width=' . $data['meta']['width'] . ' data-mgl-height=' . $data['meta']['height'];
1104 }
1105 }
1106 elseif ( $layout === 'tiles' ) {
1107 if ( isset( $data['meta'] ) && isset( $data['meta']['width'] ) && isset( $data['meta']['height'] ) ) {
1108 $attributes = 'data-mgl-id=' . $id . ' data-mgl-width=' . $data['meta']['width'] . ' data-mgl-height=' . $data['meta']['height'];
1109 }
1110 }
1111 $attributes = apply_filters( 'mgl_attributes', $attributes, $id, $data );
1112 if ( $attributes === '' ) {
1113 return [];
1114 }
1115
1116 $attribute_list = explode( ' ', $attributes );
1117 $attributes = [];
1118 foreach ( $attribute_list as $attribute ) {
1119 list( $key, $value ) = explode( '=', $attribute );
1120 $attributes[$key] = $value;
1121 }
1122 return $attributes;
1123 }
1124
1125 private function get_map_images( $ids, $images, $atts = [] ) {
1126 $map_images = array_map( function ( $id ) use ( $images, $atts ) {
1127
1128 $image = $images[$id];
1129 $default_link = $this->get_option( 'link', null );
1130 $link_attr = $this->get_link_attributes( $id, $atts['link'] ?? $default_link, $image );
1131
1132 $geo_coordinates = MeowPro_MGL_Exif::get_gps_data( $id, $image['meta'] );
1133 if ( empty( $geo_coordinates ) ) {
1134 return null;
1135 }
1136 $callback = function ( &$value, $key ) use ( $id ) {
1137 $imgsrc = wp_get_attachment_image_src( $id, $key );
1138 $value = $imgsrc[0];
1139 };
1140 array_walk( $image['meta']['sizes'], $callback );
1141 return array_merge(
1142 $image,
1143 [
1144 'id' => $id,
1145 'file' => $image['meta']['file'],
1146 'file_full' => wp_get_attachment_url( $id ),
1147 'file_srcset' => wp_get_attachment_image_srcset( $id, 'full' ),
1148 'file_sizes' => wp_get_attachment_image_sizes( $id, 'full' ),
1149 'dimension' => [
1150 'width' => $image['meta']['width'],
1151 'height' => $image['meta']['height'],
1152 ],
1153 'sizes' => $image['meta']['sizes'],
1154 'data' => [
1155 'caption' => $image['meta']['image_meta']['caption'],
1156 'gps' => $geo_coordinates,
1157 ],
1158 'link' => $link_attr,
1159 ]
1160 );
1161 }, $ids );
1162 return array_values( array_filter( $map_images ) );
1163 }
1164
1165 public function get_mgl_root_class( $atts, $classes = ['mgl-root'] ) {
1166 $classes[] = isset( $atts['align'] ) ? 'align' . $atts['align'] : '';
1167 return trim( implode( ' ', $classes ) );
1168 }
1169
1170 public function get_data_as_json( $data ) {
1171 return esc_attr( htmlspecialchars( wp_json_encode( $data ), ENT_QUOTES, 'UTF-8' ) );
1172 }
1173
1174 public function generate_uniqid($length = 13) {
1175 // Use WordPress function
1176 if ( function_exists( 'wp_unique_id' ) ) {
1177 $prefix = uniqid();
1178 return wp_unique_id( $prefix );
1179 }
1180 // Fall back
1181 else {
1182 if ( function_exists( "random_bytes" ) ) {
1183 $bytes = random_bytes( ceil( $length / 2 ) );
1184 }
1185 elseif ( function_exists( "openssl_random_pseudo_bytes" ) ) {
1186 $bytes = openssl_random_pseudo_bytes(ceil($length / 2));
1187 }
1188 else {
1189 throw new Exception( "No cryptographically secure random function available." );
1190 }
1191 return substr( bin2hex( $bytes ), 0, $length );
1192 }
1193 }
1194
1195
1196 public function get_gallery_by_id( $id ) {
1197 global $wpdb;
1198 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1199 $gallery = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $shortcodes_table WHERE id = %s", $id ), ARRAY_A );
1200
1201 if ( !$gallery ) {
1202 throw new Exception( __( 'Gallery not found.', MGL_DOMAIN ));
1203 }
1204 $gallery['medias'] = maybe_unserialize( $gallery['medias'] );
1205 $gallery['posts'] = $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null;
1206 $gallery['tags'] = $gallery['tags'] ? unserialize( $gallery['tags'] ) : null;
1207
1208 return $gallery;
1209 }
1210
1211 public function get_galleries_by_ids( $ids ){
1212 global $wpdb;
1213 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1214 $galleries = [];
1215 $ids_str = "'" . implode( "','", array_map( 'esc_sql', $ids )) . "'";
1216 $query = "SELECT * FROM $shortcodes_table WHERE id IN ( $ids_str )";
1217 $results = $wpdb->get_results( $query, ARRAY_A );
1218 foreach ( $results as $gallery ) {
1219 $galleries[$gallery['id']] = [
1220 'name' => $gallery['name'],
1221 'description' => $gallery['description'],
1222 'layout' => $gallery['layout'],
1223 'medias' => maybe_unserialize( $gallery['medias'] ),
1224 'lead_image_id' => $gallery['lead_image_id'],
1225 'order_by' => $gallery['order_by'],
1226 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1227 'dynamic_source' => $gallery['dynamic_source'],
1228 'hero' => ( bool )$gallery['is_hero_mode'],
1229 'posts' => $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null,
1230 'latest_posts' => $gallery['latest_posts'],
1231 'tags' => $gallery['tags'] ? unserialize( $gallery['tags'] ) : null,
1232 'dynamic_source' => $gallery['dynamic_source'],
1233 'rml' => $gallery['rml'] ?? null,
1234 'updated' => strtotime( $gallery['updated_at'] )
1235 ];
1236 }
1237 return $galleries;
1238 }
1239
1240 public function get_galleries( $offset = 0, $limit = 10, $order = 'DESC', $sort = null, $page = 1, $search = '' ) {
1241 global $wpdb;
1242 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1243 Meow_MGL_Migrations::check_db();
1244
1245 // Get total count
1246 $total = 0;
1247 if ( empty( $search ) ) {
1248 $total = $wpdb->get_var( "SELECT COUNT( * ) FROM $shortcodes_table" );
1249 } else {
1250 $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $shortcodes_table WHERE name LIKE %s", '%' . $wpdb->esc_like( $search ) . '%' ) );
1251 }
1252
1253 // Calculate offset based on page if provided
1254 if ($page > 1 && $offset === 0) {
1255 $offset = ($page - 1) * $limit;
1256 }
1257
1258 // Get shortcodes with pagination and sorting
1259 $sort_accessor_to_column = [
1260 'info' => 'name',
1261 'updated' => 'updated_at',
1262 'rank' => 'pref_rank',
1263 ];
1264
1265 $sort = $sort_accessor_to_column[$sort] ?? 'pref_rank';
1266 $order = in_array( strtoupper( $order ), ['ASC', 'DESC'] ) ? strtoupper( $order ) : 'DESC';
1267
1268 // Sort by rank DESC first, then by updated_at for equal ranks
1269 $query = $wpdb->prepare(
1270 "SELECT * FROM $shortcodes_table WHERE name LIKE %s ORDER BY $sort $order, updated_at DESC LIMIT %d, %d",
1271 '%' . $wpdb->esc_like( $search ) . '%', $offset, $limit
1272 );
1273
1274 $results = $wpdb->get_results( $query, ARRAY_A );
1275 $shortcodes = [];
1276
1277 foreach ( $results as $gallery ) {
1278 // Transform database format to match expected format
1279 $shortcodes[$gallery['id']] = [
1280 'name' => $gallery['name'],
1281 'description' => $gallery['description'],
1282 'layout' => $gallery['layout'],
1283 'medias' => maybe_unserialize( $gallery['medias'] ),
1284 'lead_image_id' => $gallery['lead_image_id'],
1285 'order_by' => $gallery['order_by'],
1286 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1287 'hero' => ( bool )$gallery['is_hero_mode'],
1288 'posts' => $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null,
1289 'latest_posts' => $gallery['latest_posts'],
1290 'tags' => $gallery['tags'] ? unserialize( $gallery['tags'] ) : null,
1291 'dynamic_source' => $gallery['dynamic_source'],
1292 'rml' => $gallery['rml'] ?? null,
1293 'rank' => intval( $gallery['pref_rank'] ?? 0 ),
1294 'updated' => strtotime( $gallery['updated_at'] )
1295 ];
1296 }
1297
1298 return [
1299 'total' => $total,
1300 'galleries' => $shortcodes
1301 ];
1302 }
1303
1304 public function get_collection_by_id( $id ) {
1305 global $wpdb;
1306 $collections_table = $wpdb->prefix . 'mgl_collections';
1307 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1308
1309 // Get the collection
1310 $collection = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $collections_table WHERE id = %d", $id ), ARRAY_A );
1311 if ( !$collection ) {
1312 throw new Exception( __( 'Collection not found.', MGL_DOMAIN ));
1313 }
1314 $collection['galleries_ids'] = unserialize( $collection['galleries_ids'] );
1315
1316 // Get the associated galleries
1317 $galleries = [];
1318 if ( !empty( $collection['galleries_ids'] )) {
1319 $galleries_ids_str = "'" . implode( "','", array_map( 'esc_sql', $collection['galleries_ids'] )) . "'";
1320 $galleries_query = "SELECT * FROM $shortcodes_table WHERE id IN ( $galleries_ids_str )";
1321 $galleries_data = $wpdb->get_results( $galleries_query, ARRAY_A );
1322
1323 foreach ( $galleries_data as $gallery ) {
1324 // Transform database format to match expected format
1325 $galleries[] = [
1326 'id' => $gallery['id'],
1327 'name' => $gallery['name'],
1328 'description' => $gallery['description'],
1329 'layout' => $gallery['layout'],
1330 'medias' => unserialize( $gallery['medias'] ),
1331 'lead_image_id' => $gallery['lead_image_id'],
1332 'order_by' => $gallery['order_by'],
1333 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1334 'hero' => ( bool )$gallery['is_hero_mode'],
1335 'posts' => $gallery['posts'] ? unserialize( $gallery['posts'] ) : null,
1336 'latest_posts' => $gallery['latest_posts'],
1337 'updated' => strtotime( $gallery['updated_at'] )
1338 ];
1339 }
1340
1341 // Format collection data
1342 $collection['galleries'] = $galleries;
1343 }
1344
1345 return $collection;
1346 }
1347
1348 public function get_collections( $offset = 0, $limit = 10, $order = 'DESC', $page = 1, $search = '' ) {
1349 global $wpdb;
1350 $collections_table = $wpdb->prefix . 'mgl_collections';
1351 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1352 Meow_MGL_Migrations::check_db();
1353
1354 // Get total count
1355 if( empty( $search ) ) {
1356 $total = $wpdb->get_var( "SELECT COUNT( * ) FROM $collections_table" );
1357 } else {
1358 $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $collections_table WHERE name LIKE %s", '%' . $wpdb->esc_like( $search ) . '%' ) );
1359 }
1360 // Calculate offset based on page if provided
1361 if ($page > 1 && $offset === 0) {
1362 $offset = ($page - 1) * $limit;
1363 }
1364
1365 // Get collections with pagination and sorting
1366 $query = $wpdb->prepare(
1367 "SELECT * FROM $collections_table WHERE name LIKE %s ORDER BY updated_at $order LIMIT %d, %d",
1368 '%' . $wpdb->esc_like( $search ) . '%', $offset, $limit
1369 );
1370
1371 $collections = $wpdb->get_results( $query, ARRAY_A );
1372 $result = [];
1373
1374 foreach ( $collections as $collection ) {
1375 $collection_id = $collection['id'];
1376 $galleries_ids = unserialize( $collection['galleries_ids'] );
1377
1378 // Get the associated galleries
1379 $galleries = [];
1380 if ( !empty( $galleries_ids )) {
1381 $galleries_ids_str = "'" . implode( "','", array_map( 'esc_sql', $galleries_ids )) . "'";
1382 $galleries_query = "SELECT * FROM $shortcodes_table WHERE id IN ( $galleries_ids_str )";
1383 $galleries_data = $wpdb->get_results( $galleries_query, ARRAY_A );
1384
1385 foreach ( $galleries_data as $gallery ) {
1386 // Transform database format to match expected format
1387 $gallery_item = [
1388 'id' => $gallery['id'],
1389 'name' => $gallery['name'],
1390 'description' => $gallery['description'],
1391 'layout' => $gallery['layout'],
1392 'medias' => unserialize( $gallery['medias'] ),
1393 'lead_image_id' => $gallery['lead_image_id'],
1394 'order_by' => $gallery['order_by'],
1395 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1396 'hero' => ( bool )$gallery['is_hero_mode'],
1397 'posts' => $gallery['posts'] ? unserialize( $gallery['posts'] ) : null,
1398 'latest_posts' => $gallery['latest_posts'],
1399 'tags' => $gallery['tags'] ? unserialize( $gallery['tags'] ) : null,
1400 'dynamic_source' => $gallery['dynamic_source'],
1401 'updated' => strtotime( $gallery['updated_at'] )
1402 ];
1403 $galleries[] = $gallery_item;
1404 }
1405 }
1406
1407 // Format collection data
1408 $result[$collection_id] = [
1409 'name' => $collection['name'],
1410 'description' => $collection['description'],
1411 'layout' => $collection['layout'],
1412 'galleries_ids' => $galleries_ids,
1413 'galleries' => $galleries,
1414 'updated' => strtotime( $collection['updated_at'] )
1415 ];
1416 }
1417
1418 return [
1419 'total' => $total,
1420 'collections' => $result
1421 ];
1422 }
1423
1424 }
1425
1426 ?>
1427