PluginProbe
Meow Gallery / 5.4.7
Meow Gallery v5.4.7
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.4.7, at classes/core.php

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