PluginProbe
Meow Gallery / 5.4.8
Meow Gallery v5.4.8
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.8, at classes/core.php

1,337 lines 43.4 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 // Stylish effect options
692 'stylish_enabled' => false,
693 'stylish_border_radius' => 6,
694 'stylish_shadow_opacity' => 0.08,
695 'stylish_shadow_opacity_hover' => 0.12,
696 'stylish_transition_speed' => 250,
697
698 //PRO OPTIONS
699 'infinite' => false,
700 'infinite_buffer' => 0,
701 'right_click' => false,
702 'gallery_shortcode_override_disabled' => false,
703 'skeleton_loading' => false,
704 );
705 }
706
707 function list_pro_options() {
708 return array(
709 'infinite' => false,
710 'infinite_buffer' => 0,
711 'right_click' => false,
712 'gallery_shortcode_override_disabled' => false,
713 'skeleton_loading' => false,
714 );
715 }
716
717 function get_all_options() {
718 $options = get_option( $this->option_name, null );
719 $options = $this->check_options( $options );
720 return $options;
721 }
722
723 // Upgrade from the old way of storing options to the new way.
724
725 function check_options( $options = [] ) {
726 $plugin_options = $this->list_options();
727 $pro_options = $this->list_pro_options();
728
729 $options = empty( $options ) ? [] : $options;
730 $hasChanges = false;
731
732 foreach ( $plugin_options as $option => $default ) {
733 // The option already exists
734 if ( isset( $options[$option] ) ) {
735 continue;
736 }
737 // The option does not exist, so we need to add it.
738 // Let's use the old value if any, or the default value.
739 $options[$option] = get_option( 'mgl_' . $option, $default );
740 delete_option( 'mgl_' . $option );
741 $hasChanges = true;
742 }
743
744 if( !$this->pro_module ) {
745 foreach ( $pro_options as $pro_option => $default ) {
746 if ( $options[$pro_option] !== $default ) {
747 $options[$pro_option] = $default;
748 $hasChanges = true;
749 }
750 }
751 }
752
753 if ( $hasChanges ) {
754 update_option( $this->option_name , $options );
755 }
756 return $options;
757 }
758
759 function update_options( $options ) {
760 if ( !update_option( $this->option_name, $options, false ) ) {
761 return false;
762 }
763 $options = $this->sanitize_options();
764 return $options;
765 }
766
767
768 // Validate and keep the options clean and logical.
769 function sanitize_options() {
770 $options = $this->get_all_options();
771 // something to do
772 return $options;
773 }
774
775 # endregion
776
777 function get_caption_from_source( $image ) {
778 $caption_source = $this->get_option( 'caption_source', 'caption' );
779 $caption = '';
780
781 switch ( $caption_source ) {
782 case 'title':
783 $caption = $image->title;
784 break;
785 case 'caption':
786 $caption = $image->caption;
787 break;
788 case 'description':
789 $caption = $image->description;
790 break;
791 case 'alt':
792 $caption = $image->alt;
793 break;
794 default:
795 $caption = $image->caption;
796 break;
797 }
798
799 return $caption;
800 }
801
802 function get_gallery_images( array $image_ids, array $atts, string $layout, string $size, array $posts_ids = []) {
803 global $wpdb;
804
805 // Escape the array of IDs for SQL
806 $ids = array_map( 'intval', $image_ids );
807 $ids_str = implode( ',', $ids );
808
809 $query = "SELECT
810 p.ID id,
811 p.post_title title,
812 p.post_content description,
813 p.post_excerpt caption,
814 pm.meta_value alt,
815 pm2.meta_value meta
816
817 FROM $wpdb->posts p
818 LEFT JOIN $wpdb->postmeta pm ON pm.post_id = p.ID AND pm.meta_key = '_wp_attachment_image_alt'
819 LEFT JOIN $wpdb->postmeta pm2 ON pm2.post_id = p.ID AND pm2.meta_key = '_wp_attachment_metadata'
820
821 WHERE p.post_type = 'attachment'
822
823 AND p.ID IN (" . $ids_str . ")
824 ";
825
826 $res = $wpdb->get_results( $query );
827
828 $ids = explode( ',', $ids_str );
829 $images = [];
830 foreach ( $res as $r ) {
831 $images[$r->id] = [
832 'caption' => $this->get_caption_from_source( $r ),
833 'meta' => unserialize( $r->meta ),
834 ];
835 }
836 $cleanIds = [];
837 foreach ( $ids as $id ) {
838 if ( isset( $images[$id] ) )
839 array_push( $cleanIds, $id );
840 }
841 $ids = apply_filters( 'mgl_sort', $cleanIds, $images, $layout, $atts );
842
843 if ($layout === 'map') {
844 return $this->get_map_images( $ids, $images, $atts );
845 }
846
847 $result = [];
848 foreach ($ids as $index => $id) {
849 $image = $images[$id];
850
851 // Determine orientation if layout is 'tiles'
852 $orientation = [];
853 if ($layout === 'tiles') {
854 $orientation = [
855 'orientation' => ($image['meta']['width'] > $image['meta']['height'] ? 'o' : 'i')
856 ];
857 }
858
859 $default_link = $this->get_option( 'link', null );
860 $link_attr = $this->get_link_attributes( $id, $atts['link'] ?? $default_link, $image );
861 $no_lightbox = $link_attr['type'] === 'link';
862
863 $mergedArray = [
864 'id' => $id,
865 'caption' => wp_kses(
866 html_entity_decode(apply_filters( 'mgl_caption', $image['caption'], $id ), ENT_QUOTES),
867 [
868 'strong' => [], // Bold
869 'b' => [], // Bold alternative
870 'em' => [], // Italic
871 'i' => [] // Italic alternative
872 ]
873 ),
874 'img_html' => apply_filters( 'mgl_gallery_written',
875 $this->get_img_html( $id, $size, $layout, $atts, $image, $no_lightbox ),
876 $layout
877 ),
878 'link_href' => $link_attr['href'] ?? null,
879 'link_target' => $link_attr['target'] ?? null,
880 'link_rel' => $link_attr['rel'] ?? null,
881 'attributes' => $this->get_attributes( $id, $image, $layout ),
882 ];
883
884 if( !empty( $posts_ids ) && isset( $atts['hero'] ) && $atts['hero'] ) {
885
886 $post_id = $posts_ids[$index];
887 $post = get_post( $post_id );
888
889 $mergedArray['featured_post_id'] = $post_id;
890 $mergedArray['featured_post_title'] = $post->post_title;
891 $mergedArray['featured_post_excerpt'] = $post->post_excerpt;
892 $mergedArray['featured_post_url'] = get_permalink( $post_id );
893
894 }
895
896 $result[] = array_merge( $image, $mergedArray, $orientation );
897 }
898
899 return $result;
900 }
901
902 private function get_image_class( $id, $layout, $noLightbox ) {
903 $base_class = 'wp-image-' . $id;
904
905 if( $layout === 'carousel' ) {
906 $base_class .= ' skip-lazy';
907 }
908
909 if ( $noLightbox ) {
910 $base_class .= ' no-lightbox';
911 }
912 return $base_class;
913 }
914
915 private function get_img_html( $id, $size, $layout, $atts, $data, $noLightbox ) {
916
917 //check if the media is a video
918 $media_type = get_post_mime_type( $id );
919 if ( strpos( $media_type, 'video' ) !== false ) {
920 $video = wp_get_attachment_url( $id );
921 $video = apply_filters( 'mgl_video', $video, $id, $data );
922 if ( !empty( $video ) ) {
923 return '<video class="wp-video-'. $id .'" controls="controls" onclick="() => this.play();"><source src="' . $video . '" type="' . $media_type . '"></video>';
924 }
925 }
926
927 $image_size = $this->get_option( 'image_size', 'srcset' );
928 $img_html = null;
929 if ( empty( $image_size ) || $image_size === 'srcset' ) {
930 $img_html = wp_get_attachment_image( $id, $size, false, [
931 'class' => $this->get_image_class( $id, $layout, $noLightbox ),
932 'draggable' => $layout === 'carousel' ? 'false' : null,
933 ]);
934 }
935 else {
936 $info = wp_get_attachment_image_src( $id, $image_size );
937 $alt_text = get_post_meta( $id, '_wp_attachment_image_alt', true );
938
939 $img_html = '<img loading="lazy" src="' . $info[0] . '" class="' . $this->get_image_class( $id, $layout, $noLightbox ) . '" alt="' . esc_attr( $alt_text ) . '" />';
940 }
941
942 if ( $layout === 'masonry' ) {
943 $masonry_column = $this->get_option( 'masonry_column', 3 );
944 $columns = ( isset( $atts['columns'] ) ? $atts['columns'] : $masonry_column ) + 1;
945 $img_html = str_replace( '100vw', 100 / $columns . 'vw', $img_html );
946 }
947 else if ( $layout === 'square' ) {
948 $square_column = $this->get_option( 'square_columns', 5 );
949 $columns = ( isset( $atts['columns'] ) ? $atts['columns'] : $square_column ) + 1;
950 $img_html = str_replace( '100vw', 100 / $columns . 'vw', $img_html );
951 }
952 else if ( $layout === 'cascade' ) {
953 $img_html = str_replace( '100vw', 100 / 3 . 'vw', $img_html );
954 }
955
956 return wp_kses( $img_html, [
957 'img' => [
958 'src' => true,
959 'srcset' => true,
960 'loading' => true,
961 'tabindex' => true,
962 'sizes' => true,
963 'class' => true,
964 'id' => true,
965 'width' => true,
966 'height' => true,
967 'alt' => true,
968 'align' => true,
969 'draggable' => true,
970 ]
971 ]
972 );
973 }
974
975 private function get_link_attributes( $id, $link, $data ) {
976 $link_url = null;
977 $type = 'media';
978 $rel = null;
979 $target = '_self';
980
981 if ( $link === 'attachment' ) {
982 $link_url = get_permalink( (int)$id );
983 }
984 else if ( $link === 'media' || $link === 'file' ) {
985 $wpUploadDir = wp_upload_dir();
986 $updir = trailingslashit( $wpUploadDir['baseurl'] );
987 if ( isset( $data['meta']['file'] ) ) {
988 $link_url = $updir . $data['meta']['file'];
989 } else {
990 $link_url = get_permalink( (int)$id );
991 }
992 }
993 else if ( $link === null ){
994 $link_url = get_post_meta( $id, '_gallery_link_url', true );
995 if ( !empty( $link_url ) ) {
996 $type = 'link';
997 $target = get_post_meta( $id, '_gallery_link_target', true );
998 }
999 }
1000
1001 $link_attr = [
1002 'href' => !empty( $link_url ) ? esc_url( $link_url ) : null,
1003 'target' => $target,
1004 'type' => $type,
1005 'rel' => $rel,
1006 ];
1007
1008
1009 return apply_filters( 'mgl_link_attributes', $link_attr, (int)$id, $data );
1010 }
1011
1012 private function get_attributes( $id, $data, $layout ) {
1013 $attributes = '';
1014 if ( $layout === 'raw' ) {
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 elseif ( $layout === 'tiles' ) {
1020 if ( isset( $data['meta'] ) && isset( $data['meta']['width'] ) && isset( $data['meta']['height'] ) ) {
1021 $attributes = 'data-mgl-id=' . $id . ' data-mgl-width=' . $data['meta']['width'] . ' data-mgl-height=' . $data['meta']['height'];
1022 }
1023 }
1024 $attributes = apply_filters( 'mgl_attributes', $attributes, $id, $data );
1025 if ( $attributes === '' ) {
1026 return [];
1027 }
1028
1029 $attribute_list = explode( ' ', $attributes );
1030 $attributes = [];
1031 foreach ( $attribute_list as $attribute ) {
1032 list( $key, $value ) = explode( '=', $attribute );
1033 $attributes[$key] = $value;
1034 }
1035 return $attributes;
1036 }
1037
1038 private function get_map_images( $ids, $images, $atts = [] ) {
1039 $map_images = array_map( function ( $id ) use ( $images, $atts ) {
1040
1041 $image = $images[$id];
1042 $default_link = $this->get_option( 'link', null );
1043 $link_attr = $this->get_link_attributes( $id, $atts['link'] ?? $default_link, $image );
1044
1045 $geo_coordinates = MeowPro_MGL_Exif::get_gps_data( $id, $image['meta'] );
1046 if ( empty( $geo_coordinates ) ) {
1047 return null;
1048 }
1049 $callback = function ( &$value, $key ) use ( $id ) {
1050 $imgsrc = wp_get_attachment_image_src( $id, $key );
1051 $value = $imgsrc[0];
1052 };
1053 array_walk( $image['meta']['sizes'], $callback );
1054 return array_merge(
1055 $image,
1056 [
1057 'id' => $id,
1058 'file' => $image['meta']['file'],
1059 'file_full' => wp_get_attachment_url( $id ),
1060 'file_srcset' => wp_get_attachment_image_srcset( $id, 'full' ),
1061 'file_sizes' => wp_get_attachment_image_sizes( $id, 'full' ),
1062 'dimension' => [
1063 'width' => $image['meta']['width'],
1064 'height' => $image['meta']['height'],
1065 ],
1066 'sizes' => $image['meta']['sizes'],
1067 'data' => [
1068 'caption' => $image['meta']['image_meta']['caption'],
1069 'gps' => $geo_coordinates,
1070 ],
1071 'link' => $link_attr,
1072 ]
1073 );
1074 }, $ids );
1075 return array_values( array_filter( $map_images ) );
1076 }
1077
1078 public function get_mgl_root_class( $atts, $classes = ['mgl-root'] ) {
1079 $classes[] = isset( $atts['align'] ) ? 'align' . $atts['align'] : '';
1080 return trim( implode( ' ', $classes ) );
1081 }
1082
1083 public function get_data_as_json( $data ) {
1084 return esc_attr( htmlspecialchars( wp_json_encode( $data ), ENT_QUOTES, 'UTF-8' ) );
1085 }
1086
1087 public function generate_uniqid($length = 13) {
1088 // Use WordPress function
1089 if ( function_exists( 'wp_unique_id' ) ) {
1090 $prefix = uniqid();
1091 return wp_unique_id( $prefix );
1092 }
1093 // Fall back
1094 else {
1095 if ( function_exists( "random_bytes" ) ) {
1096 $bytes = random_bytes( ceil( $length / 2 ) );
1097 }
1098 elseif ( function_exists( "openssl_random_pseudo_bytes" ) ) {
1099 $bytes = openssl_random_pseudo_bytes(ceil($length / 2));
1100 }
1101 else {
1102 throw new Exception( "No cryptographically secure random function available." );
1103 }
1104 return substr( bin2hex( $bytes ), 0, $length );
1105 }
1106 }
1107
1108
1109 public function get_gallery_by_id( $id ) {
1110 global $wpdb;
1111 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1112 $gallery = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $shortcodes_table WHERE id = %s", $id ), ARRAY_A );
1113
1114 if ( !$gallery ) {
1115 throw new Exception( __( 'Gallery not found.', MGL_DOMAIN ));
1116 }
1117 $gallery['medias'] = maybe_unserialize( $gallery['medias'] );
1118 $gallery['posts'] = $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null;
1119 $gallery['tags'] = $gallery['tags'] ? unserialize( $gallery['tags'] ) : null;
1120
1121 return $gallery;
1122 }
1123
1124 public function get_galleries_by_ids( $ids ){
1125 global $wpdb;
1126 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1127 $galleries = [];
1128 $ids_str = "'" . implode( "','", array_map( 'esc_sql', $ids )) . "'";
1129 $query = "SELECT * FROM $shortcodes_table WHERE id IN ( $ids_str )";
1130 $results = $wpdb->get_results( $query, ARRAY_A );
1131 foreach ( $results as $gallery ) {
1132 $galleries[$gallery['id']] = [
1133 'name' => $gallery['name'],
1134 'description' => $gallery['description'],
1135 'layout' => $gallery['layout'],
1136 'medias' => maybe_unserialize( $gallery['medias'] ),
1137 'lead_image_id' => $gallery['lead_image_id'],
1138 'order_by' => $gallery['order_by'],
1139 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1140 'dynamic_source' => $gallery['dynamic_source'],
1141 'hero' => ( bool )$gallery['is_hero_mode'],
1142 'posts' => $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null,
1143 'latest_posts' => $gallery['latest_posts'],
1144 'tags' => $gallery['tags'] ? unserialize( $gallery['tags'] ) : null,
1145 'updated' => strtotime( $gallery['updated_at'] )
1146 ];
1147 }
1148 return $galleries;
1149 }
1150
1151 public function get_galleries( $offset = 0, $limit = 10, $order = 'DESC', $sort = null, $page = 1, $search = '' ) {
1152 global $wpdb;
1153 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1154 Meow_MGL_Migrations::check_db();
1155
1156 // Get total count
1157 $total = 0;
1158 if ( empty( $search ) ) {
1159 $total = $wpdb->get_var( "SELECT COUNT( * ) FROM $shortcodes_table" );
1160 } else {
1161 $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $shortcodes_table WHERE name LIKE %s", '%' . $wpdb->esc_like( $search ) . '%' ) );
1162 }
1163
1164 // Calculate offset based on page if provided
1165 if ($page > 1 && $offset === 0) {
1166 $offset = ($page - 1) * $limit;
1167 }
1168
1169 // Get shortcodes with pagination and sorting
1170 $sort_accessor_to_column = [
1171 'info' => 'name',
1172 'updated' => 'updated_at',
1173 'rank' => 'pref_rank',
1174 ];
1175
1176 $sort = $sort_accessor_to_column[$sort] ?? 'pref_rank';
1177 $order = in_array( strtoupper( $order ), ['ASC', 'DESC'] ) ? strtoupper( $order ) : 'DESC';
1178
1179 // Sort by rank DESC first, then by updated_at for equal ranks
1180 $query = $wpdb->prepare(
1181 "SELECT * FROM $shortcodes_table WHERE name LIKE %s ORDER BY $sort $order, updated_at DESC LIMIT %d, %d",
1182 '%' . $wpdb->esc_like( $search ) . '%', $offset, $limit
1183 );
1184
1185 $results = $wpdb->get_results( $query, ARRAY_A );
1186 $shortcodes = [];
1187
1188 foreach ( $results as $gallery ) {
1189 // Transform database format to match expected format
1190 $shortcodes[$gallery['id']] = [
1191 'name' => $gallery['name'],
1192 'description' => $gallery['description'],
1193 'layout' => $gallery['layout'],
1194 'medias' => maybe_unserialize( $gallery['medias'] ),
1195 'lead_image_id' => $gallery['lead_image_id'],
1196 'order_by' => $gallery['order_by'],
1197 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1198 'hero' => ( bool )$gallery['is_hero_mode'],
1199 'posts' => $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null,
1200 'latest_posts' => $gallery['latest_posts'],
1201 'tags' => $gallery['tags'] ? unserialize( $gallery['tags'] ) : null,
1202 'dynamic_source' => $gallery['dynamic_source'],
1203 'rank' => intval( $gallery['pref_rank'] ?? 0 ),
1204 'updated' => strtotime( $gallery['updated_at'] )
1205 ];
1206 }
1207
1208 return [
1209 'total' => $total,
1210 'galleries' => $shortcodes
1211 ];
1212 }
1213
1214 public function get_collection_by_id( $id ) {
1215 global $wpdb;
1216 $collections_table = $wpdb->prefix . 'mgl_collections';
1217 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1218
1219 // Get the collection
1220 $collection = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $collections_table WHERE id = %d", $id ), ARRAY_A );
1221 if ( !$collection ) {
1222 throw new Exception( __( 'Collection not found.', MGL_DOMAIN ));
1223 }
1224 $collection['galleries_ids'] = unserialize( $collection['galleries_ids'] );
1225
1226 // Get the associated galleries
1227 $galleries = [];
1228 if ( !empty( $collection['galleries_ids'] )) {
1229 $galleries_ids_str = "'" . implode( "','", array_map( 'esc_sql', $collection['galleries_ids'] )) . "'";
1230 $galleries_query = "SELECT * FROM $shortcodes_table WHERE id IN ( $galleries_ids_str )";
1231 $galleries_data = $wpdb->get_results( $galleries_query, ARRAY_A );
1232
1233 foreach ( $galleries_data as $gallery ) {
1234 // Transform database format to match expected format
1235 $galleries[] = [
1236 'id' => $gallery['id'],
1237 'name' => $gallery['name'],
1238 'description' => $gallery['description'],
1239 'layout' => $gallery['layout'],
1240 'medias' => unserialize( $gallery['medias'] ),
1241 'lead_image_id' => $gallery['lead_image_id'],
1242 'order_by' => $gallery['order_by'],
1243 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1244 'hero' => ( bool )$gallery['is_hero_mode'],
1245 'posts' => $gallery['posts'] ? unserialize( $gallery['posts'] ) : null,
1246 'latest_posts' => $gallery['latest_posts'],
1247 'updated' => strtotime( $gallery['updated_at'] )
1248 ];
1249 }
1250
1251 // Format collection data
1252 $collection['galleries'] = $galleries;
1253 }
1254
1255 return $collection;
1256 }
1257
1258 public function get_collections( $offset = 0, $limit = 10, $order = 'DESC', $page = 1, $search = '' ) {
1259 global $wpdb;
1260 $collections_table = $wpdb->prefix . 'mgl_collections';
1261 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1262 Meow_MGL_Migrations::check_db();
1263
1264 // Get total count
1265 if( empty( $search ) ) {
1266 $total = $wpdb->get_var( "SELECT COUNT( * ) FROM $collections_table" );
1267 } else {
1268 $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $collections_table WHERE name LIKE %s", '%' . $wpdb->esc_like( $search ) . '%' ) );
1269 }
1270 // Calculate offset based on page if provided
1271 if ($page > 1 && $offset === 0) {
1272 $offset = ($page - 1) * $limit;
1273 }
1274
1275 // Get collections with pagination and sorting
1276 $query = $wpdb->prepare(
1277 "SELECT * FROM $collections_table WHERE name LIKE %s ORDER BY updated_at $order LIMIT %d, %d",
1278 '%' . $wpdb->esc_like( $search ) . '%', $offset, $limit
1279 );
1280
1281 $collections = $wpdb->get_results( $query, ARRAY_A );
1282 $result = [];
1283
1284 foreach ( $collections as $collection ) {
1285 $collection_id = $collection['id'];
1286 $galleries_ids = unserialize( $collection['galleries_ids'] );
1287
1288 // Get the associated galleries
1289 $galleries = [];
1290 if ( !empty( $galleries_ids )) {
1291 $galleries_ids_str = "'" . implode( "','", array_map( 'esc_sql', $galleries_ids )) . "'";
1292 $galleries_query = "SELECT * FROM $shortcodes_table WHERE id IN ( $galleries_ids_str )";
1293 $galleries_data = $wpdb->get_results( $galleries_query, ARRAY_A );
1294
1295 foreach ( $galleries_data as $gallery ) {
1296 // Transform database format to match expected format
1297 $gallery_item = [
1298 'id' => $gallery['id'],
1299 'name' => $gallery['name'],
1300 'description' => $gallery['description'],
1301 'layout' => $gallery['layout'],
1302 'medias' => unserialize( $gallery['medias'] ),
1303 'lead_image_id' => $gallery['lead_image_id'],
1304 'order_by' => $gallery['order_by'],
1305 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1306 'hero' => ( bool )$gallery['is_hero_mode'],
1307 'posts' => $gallery['posts'] ? unserialize( $gallery['posts'] ) : null,
1308 'latest_posts' => $gallery['latest_posts'],
1309 'tags' => $gallery['tags'] ? unserialize( $gallery['tags'] ) : null,
1310 'dynamic_source' => $gallery['dynamic_source'],
1311 'updated' => strtotime( $gallery['updated_at'] )
1312 ];
1313 $galleries[] = $gallery_item;
1314 }
1315 }
1316
1317 // Format collection data
1318 $result[$collection_id] = [
1319 'name' => $collection['name'],
1320 'description' => $collection['description'],
1321 'layout' => $collection['layout'],
1322 'galleries_ids' => $galleries_ids,
1323 'galleries' => $galleries,
1324 'updated' => strtotime( $collection['updated_at'] )
1325 ];
1326 }
1327
1328 return [
1329 'total' => $total,
1330 'collections' => $result
1331 ];
1332 }
1333
1334 }
1335
1336 ?>
1337