PluginProbe
Meow Gallery / 5.5.5
Meow Gallery v5.5.5
5.5.5 5.5.4 5.5.3 5.5.2 5.5.1 5.5.0 5.4.9 5.4.8 5.4.7 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 All 157 releases
← All changes | classes/core.php +139 -10 5.5.35.5.5 View file →
@@ -7,9 +7,9 @@
7 7 private $is_gallery_used = true; // TODO: Would be nice to detect if the gallery is actually used on the current page.
8 8 private $skeleton_handler;
9 9 private $pro_module = false;
10 10
11 - private $preview_cutoff = 12; // Limit the number of images to show in the preview (for performance reasons)
11 + public $preview_cutoff = 12; // Limit the number of images to show in the preview (for performance reasons)
12 12
13 13 private static $plugin_option_name = 'mgl_options';
14 14 private $pro;
15 15 private $option_name = 'mgl_options';
@@ -47,9 +47,10 @@
47 47 new Meow_MGL_Run( $this );
48 48 }
49 49 }
50 50
51 - // Load the Pro version *after* loading the Run class due to the JS file was gatherd into one file.
51 + // Load the Pro version *after* the Run class: both share the same JS bundle, and Run is
52 + // the one registering it (the Pro class only localizes extra data on it).
52 53
53 54 $this->pro_module = class_exists( 'MeowPro_MGL_Core' );
54 55 if ( $this->pro_module ) {
55 56 $this->pro = new MeowPro_MGL_Core( $this );
@@ -76,8 +77,34 @@
76 77 function collection() {
77 78 return "<b>Meow Collection</b>: This is only available in the Pro version. Please <a href='https://meowapps.com/products/meow-gallery-pro/'>upgrade to Meow Gallery Pro</a> to use this feature.";
78 79 }
79 80
81 + // Gallery and collection IDs are plain identifiers (generate_uniqid(), stored as varchar).
82 + // Anything else is rejected, and rejected rather than stripped: stripping could turn a crafted
83 + // ID into a different existing one. Security: esc_attr() does NOT escape "]", so an ID coming
84 + // from a request and concatenated into a shortcode string could close the tag and run
85 + // arbitrary shortcodes. Keep IDs on this charset and never build a shortcode string from them.
86 + public static function sanitize_id( $id ) {
87 + if ( !is_scalar( $id ) ) {
88 + return '';
89 + }
90 + $id = (string) $id;
91 + return preg_match( '/^[A-Za-z0-9_-]+$/', $id ) ? $id : '';
92 + }
93 +
94 + // Renders a collection from its ID. Deliberately calls the handler directly instead of going
95 + // through do_shortcode(): there is no shortcode string to inject into that way.
96 + public function render_collection( $id, $is_preview = false ) {
97 + $id = self::sanitize_id( $id );
98 + if ( $id === '' ) {
99 + return "<p class='meow-error'><b>Meow Gallery:</b> This collection ID is not valid.</p>";
100 + }
101 + if ( $this->pro_module && $this->pro ) {
102 + return $this->pro->collection( array( 'id' => $id ), $is_preview );
103 + }
104 + return $this->collection();
105 + }
106 +
80 107 public function can_access_settings() {
81 108 return apply_filters( 'mgl_allow_setup', current_user_can( 'manage_options' ) );
82 109 }
83 110
@@ -142,11 +169,11 @@
142 169 if ( isset( $atts['meow'] ) && $atts['meow'] === 'false' ) {
143 170 return gallery_shortcode( $atts );
144 171 }
145 172
146 - // If the attributes contain "collection" then use the collection shortcode instead
173 + // If the attributes contain "collection" then render that collection instead
147 174 if ( isset( $atts['collection'] ) && !empty( $atts['collection'] ) ) {
148 - return do_shortcode( '[meow-collection id="' . $atts['collection'] . '"]' );
175 + return $this->render_collection( $atts['collection'] );
149 176 }
150 177
151 178 $image_ids = array();
152 179 $layout = '';
@@ -173,9 +200,10 @@
173 200 try {
174 201 $shortcode = $this->get_gallery_by_id( $shortcode_id );
175 202 }
176 203 catch ( Exception $e ) {
177 - return "<p class='meow-error'><b>Meow Gallery:</b> This ID wasn't found in the Gallery Manager. (ID: $shortcode_id). " . $e->getMessage() . "</p>";
204 + $safe_id = esc_html( is_scalar( $shortcode_id ) ? $shortcode_id : '' );
205 + return "<p class='meow-error'><b>Meow Gallery:</b> This ID wasn't found in the Gallery Manager. (ID: $safe_id). " . esc_html( $e->getMessage() ) . "</p>";
178 206 }
179 207
180 208 if ( !isset( $shortcode['medias'] ) || !isset( $shortcode['medias']['thumbnail_ids'])) {
181 209 return "<p class='meow-error'><b>Meow Gallery:</b> Thumbnail IDs not found.</p>";
@@ -683,8 +711,26 @@
683 711 static function get_plugin_option_name() {
684 712 return self::$plugin_option_name;
685 713 }
686 714
715 + // Tiles density, from the shortcode attributes when they set one, from the options otherwise.
716 + // Shared by the front-end settings (Meow_MGL_Run) and the tiles CSS (Meow_MGL_Builders_Tiles).
717 + static function get_tiles_density( $atts = [] ) {
718 + if ( isset( $atts['density'] ) ) {
719 + return array(
720 + 'desktop' => $atts['density'],
721 + 'tablet' => $atts['density'],
722 + 'mobile' => $atts['density'],
723 + );
724 + }
725 + $options = get_option( self::$plugin_option_name, [] );
726 + return array(
727 + 'desktop' => $options['tiles_density'] ?? 'high',
728 + 'tablet' => $options['tiles_density_tablet'] ?? 'medium',
729 + 'mobile' => $options['tiles_density_mobile'] ?? 'low',
730 + );
731 + }
732 +
687 733 static function get_plugin_option( $option_name, $default = null ) {
688 734 $options = get_option( self::$plugin_option_name, null );
689 735 if ( !empty( $options ) && array_key_exists( $option_name, $options ) ) {
690 736 return $options[$option_name];
@@ -1192,8 +1238,60 @@
1192 1238 }
1193 1239 }
1194 1240
1195 1241
1242 + /**
1243 + * Reads the "medias" of a gallery. Only the ordered attachment IDs are stored (older versions
1244 + * also stored their URLs and mime types, which are ignored: they were a copy of the Media
1245 + * Library that went stale). This is also the shape that gets written back, and all the
1246 + * front-end needs. The Admin uses hydrate_medias() to get the URLs to display.
1247 + */
1248 + public static function normalize_medias( $medias ) {
1249 + $ids = ( is_array( $medias ) && isset( $medias['thumbnail_ids'] ) && is_array( $medias['thumbnail_ids'] ) )
1250 + ? array_values( $medias['thumbnail_ids'] ) : [];
1251 +
1252 + return [ 'thumbnail_ids' => $ids ];
1253 + }
1254 +
1255 + /**
1256 + * Adds the 'thumbnails' the Admin displays: one entry per ID, with its URLs and mime type
1257 + * resolved from the Media Library. Never stale, and a deleted attachment simply gets empty
1258 + * URLs (the Admin then shows a placeholder). Not used on the front-end, which only needs
1259 + * the IDs.
1260 + */
1261 + public static function hydrate_medias( $medias ) {
1262 + $ids = self::normalize_medias( $medias )['thumbnail_ids'];
1263 +
1264 + // One query for all the attachments instead of one per thumbnail. _prime_post_caches()
1265 + // only caches the attachments which exist, so anything still absent from the cache
1266 + // afterwards is gone: it's skipped instead of being queried on every request.
1267 + if ( !empty( $ids ) ) {
1268 + _prime_post_caches( array_values( array_unique( array_map( 'intval', $ids ) ) ), false, true );
1269 + }
1270 +
1271 + $thumbnails = [];
1272 + foreach ( $ids as $id ) {
1273 + $thumbnail = [ 'id' => $id, 'url' => '', 'zoom_url' => '', 'mime' => '' ];
1274 +
1275 + if ( !empty( $id ) && wp_cache_get( (int)$id, 'posts' ) ) {
1276 + $mime = get_post_mime_type( $id ) ?: '';
1277 + // Same rule as the latest_photos endpoint: videos have no image sizes, so their
1278 + // own URL is used for both the thumbnail and the zoom.
1279 + $is_video = strpos( $mime, 'video' ) !== false;
1280 + $url = $is_video ? wp_get_attachment_url( $id ) : wp_get_attachment_image_url( $id, 'thumbnail' );
1281 + $zoom = $is_video ? $url : wp_get_attachment_image_url( $id, 'large' );
1282 +
1283 + $thumbnail['url'] = $url ?: '';
1284 + $thumbnail['zoom_url'] = $zoom ?: $thumbnail['url'];
1285 + $thumbnail['mime'] = $mime;
1286 + }
1287 +
1288 + $thumbnails[] = $thumbnail;
1289 + }
1290 +
1291 + return [ 'thumbnail_ids' => $ids, 'thumbnails' => $thumbnails ];
1292 + }
1293 +
1196 1294 public function get_gallery_by_id( $id ) {
1197 1295 global $wpdb;
1198 1296 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1199 1297 $gallery = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $shortcodes_table WHERE id = %s", $id ), ARRAY_A );
@@ -1200,9 +1298,9 @@
1200 1298
1201 1299 if ( !$gallery ) {
1202 1300 throw new Exception( __( 'Gallery not found.', MGL_DOMAIN ));
1203 1301 }
1204 - $gallery['medias'] = maybe_unserialize( $gallery['medias'] );
1302 + $gallery['medias'] = self::normalize_medias( maybe_unserialize( $gallery['medias'] ) );
1205 1303 $gallery['posts'] = $gallery['posts'] ? maybe_unserialize( $gallery['posts'] ) : null;
1206 1304 $gallery['tags'] = $gallery['tags'] ? unserialize( $gallery['tags'] ) : null;
1207 1305
1208 1306 return $gallery;
@@ -1219,9 +1317,9 @@
1219 1317 $galleries[$gallery['id']] = [
1220 1318 'name' => $gallery['name'],
1221 1319 'description' => $gallery['description'],
1222 1320 'layout' => $gallery['layout'],
1223 - 'medias' => maybe_unserialize( $gallery['medias'] ),
1321 + 'medias' => self::normalize_medias( maybe_unserialize( $gallery['medias'] ) ),
1224 1322 'lead_image_id' => $gallery['lead_image_id'],
1225 1323 'order_by' => $gallery['order_by'],
1226 1324 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1227 1325 'dynamic_source' => $gallery['dynamic_source'],
@@ -1279,9 +1377,9 @@
1279 1377 $shortcodes[$gallery['id']] = [
1280 1378 'name' => $gallery['name'],
1281 1379 'description' => $gallery['description'],
1282 1380 'layout' => $gallery['layout'],
1283 - 'medias' => maybe_unserialize( $gallery['medias'] ),
1381 + 'medias' => self::hydrate_medias( maybe_unserialize( $gallery['medias'] ) ),
1284 1382 'lead_image_id' => $gallery['lead_image_id'],
1285 1383 'order_by' => $gallery['order_by'],
1286 1384 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1287 1385 'hero' => ( bool )$gallery['is_hero_mode'],
@@ -1300,8 +1398,39 @@
1300 1398 'galleries' => $shortcodes
1301 1399 ];
1302 1400 }
1303 1401
1402 + /**
1403 + * Just the id => name pairs, for the selectors (the Gutenberg block). They only need the
1404 + * names, so this avoids shipping every gallery's medias in the page and, unlike
1405 + * get_galleries(), it isn't paginated: the selectors used to be capped at 10 entries.
1406 + */
1407 + public function get_gallery_names() {
1408 + global $wpdb;
1409 + $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
1410 + Meow_MGL_Migrations::check_db();
1411 +
1412 + $names = [];
1413 + $results = $wpdb->get_results( "SELECT id, name FROM $shortcodes_table ORDER BY name ASC", ARRAY_A );
1414 + foreach ( $results as $gallery ) {
1415 + $names[$gallery['id']] = [ 'name' => $gallery['name'] ];
1416 + }
1417 + return [ 'galleries' => $names ];
1418 + }
1419 +
1420 + public function get_collection_names() {
1421 + global $wpdb;
1422 + $collections_table = $wpdb->prefix . 'mgl_collections';
1423 + Meow_MGL_Migrations::check_db();
1424 +
1425 + $names = [];
1426 + $results = $wpdb->get_results( "SELECT id, name FROM $collections_table ORDER BY name ASC", ARRAY_A );
1427 + foreach ( $results as $collection ) {
1428 + $names[$collection['id']] = [ 'name' => $collection['name'] ];
1429 + }
1430 + return [ 'collections' => $names ];
1431 + }
1432 +
1304 1433 public function get_collection_by_id( $id ) {
1305 1434 global $wpdb;
1306 1435 $collections_table = $wpdb->prefix . 'mgl_collections';
1307 1436 $shortcodes_table = $wpdb->prefix . 'mgl_gallery_shortcodes';
@@ -1326,9 +1455,9 @@
1326 1455 'id' => $gallery['id'],
1327 1456 'name' => $gallery['name'],
1328 1457 'description' => $gallery['description'],
1329 1458 'layout' => $gallery['layout'],
1330 - 'medias' => unserialize( $gallery['medias'] ),
1459 + 'medias' => self::normalize_medias( maybe_unserialize( $gallery['medias'] ) ),
1331 1460 'lead_image_id' => $gallery['lead_image_id'],
1332 1461 'order_by' => $gallery['order_by'],
1333 1462 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1334 1463 'hero' => ( bool )$gallery['is_hero_mode'],
@@ -1388,9 +1517,9 @@
1388 1517 'id' => $gallery['id'],
1389 1518 'name' => $gallery['name'],
1390 1519 'description' => $gallery['description'],
1391 1520 'layout' => $gallery['layout'],
1392 - 'medias' => unserialize( $gallery['medias'] ),
1521 + 'medias' => self::hydrate_medias( maybe_unserialize( $gallery['medias'] ) ),
1393 1522 'lead_image_id' => $gallery['lead_image_id'],
1394 1523 'order_by' => $gallery['order_by'],
1395 1524 'is_post_mode' => ( bool )$gallery['is_post_mode'],
1396 1525 'hero' => ( bool )$gallery['is_hero_mode'],