Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wplr-sync/classes/api.php
+++ b/wplr-sync/classes/api.php
@@ -182,10 +182,24 @@
*/
function sync( $args ) {
global $wplr;
+
if ( !$_FILES || !isset( $_FILES['file'] ) ) {
$wplr->log( 'Parameter missing.' );
return $this->response( null, false, 'Parameter missing.' );
}
+
+ $filename = isset( $args['file'] ) ? $args['file'] : '';
+
+ $allowed_mimes = get_allowed_mime_types();
+ $filetype = wp_check_filetype( $filename, $allowed_mimes );
+
+ $is_valid = $filetype['type'] && $filetype['ext'] && $filetype['ext'] !== false;
+
+ if ( !$is_valid ) {
+ $wplr->log( 'File type validation failed: ' . $filetype['type'] );
+ return $this->response( null, false, 'File type validation failed: ' . $filetype['type'] );
+ }
+
$lrinfo = new Meow_WPLR_Sync_LRInfo();
$lrinfo->lr_id = $args["id"];
$lrinfo->lr_file = $args["file"];
--- a/wplr-sync/classes/rest.php
+++ b/wplr-sync/classes/rest.php
@@ -201,6 +201,11 @@
'permission_callback' => array( $this->core, 'can_access_features' ),
'callback' => array( $this, 'rest_update_featured_image' )
) );
+ register_rest_route( $this->namespace, '/reorder_media', array(
+ 'methods' => 'POST',
+ 'permission_callback' => array( $this->core, 'can_access_features' ),
+ 'callback' => array( $this, 'rest_reorder_media' )
+ ) );
}
catch (Exception $e) {
var_dump($e);
@@ -1099,6 +1104,43 @@
return new WP_REST_Response([
'success' => true,
+ ], 200 );
+ }
+ catch (Exception $e) {
+ return new WP_REST_Response([
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ ], 500 );
+ }
+ }
+
+ public function rest_reorder_media($request) {
+ $params = $request->get_json_params();
+ $collection_id = isset( $params['collection_id'] ) ? $params['collection_id'] : '';
+ $media_ids = isset( $params['media_ids'] ) ? $params['media_ids'] : [];
+ if (!$collection_id || !is_array($media_ids) || count($media_ids) === 0) {
+ return new WP_REST_Response([
+ 'success' => false,
+ 'message' => 'The collection id or media ids are missing.',
+ ], 400 );
+ }
+ try {
+ global $wpdb;
+ $tbl_r = $wpdb->prefix . 'lrsync_relations';
+
+ // Update the sort order for each media in the collection
+ foreach ($media_ids as $index => $media_id) {
+ $wpdb->update(
+ $tbl_r,
+ array( 'sort' => $index ),
+ array( 'wp_col_id' => $collection_id, 'wp_id' => $media_id ),
+ array( '%d' ),
+ array( '%d', '%d' )
+ );
+ }
+
+ return new WP_REST_Response([
+ 'success' => true,
], 200 );
}
catch (Exception $e) {
--- a/wplr-sync/common/admin.php
+++ b/wplr-sync/common/admin.php
@@ -6,6 +6,8 @@
public static $loaded = false;
public static $version = '4.0';
public static $admin_version = '4.0';
+ public static $network_license_modal_added = false;
+ public static $network_license_plugins = [];
/**
* Storage for instances that need deferred initialization.
@@ -197,19 +199,191 @@
}
$isIssue = $this->isPro && !$this->is_registered();
if ( strpos( $pathName, $thisPathName ) !== false ) {
- $new_links = [
- 'settings' =>
- sprintf( __( '<a href="admin.php?page=%s_settings">Settings</a>', $this->domain ), $this->prefix ),
- 'license' =>
- $this->is_registered() ?
- ( '<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>' ) :
- ( $isIssue ? ( sprintf( '<span style="color: #ff3434;">' . __( 'License Issue', $this->domain ), $this->prefix ) . '</span>' ) : ( sprintf( '<span>' . __( '<a target="_blank" href="https://meowapps.com">Get the <u>Pro Version</u></a>', $this->domain ), $this->prefix ) . '</span>' ) ),
- ];
+ // In network admin, handle differently (no settings page available)
+ if ( is_network_admin() ) {
+ if ( $this->isPro && !$this->is_registered() ) {
+ // Show "Register License" link for unregistered Pro plugins
+ $new_links = [
+ 'license' => sprintf(
+ '<a href="#" class="meowapps-network-license-link" data-prefix="%s" data-plugin="%s" style="color: #d63638;">%s</a>',
+ esc_attr( $this->prefix ),
+ esc_attr( $this->nice_name_from_file( $this->mainfile ) ),
+ __( 'Register License', $this->domain )
+ ),
+ ];
+ // Track this plugin for the modal
+ self::$network_license_plugins[ $this->prefix ] = $this->nice_name_from_file( $this->mainfile );
+ // Add modal output hook (only once)
+ if ( !self::$network_license_modal_added ) {
+ add_action( 'admin_footer', [ __CLASS__, 'output_network_license_modal' ] );
+ self::$network_license_modal_added = true;
+ }
+ }
+ elseif ( $this->isPro && $this->is_registered() ) {
+ // Pro plugin is registered
+ $new_links = [
+ 'license' => '<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>',
+ ];
+ }
+ else {
+ // Free plugin
+ $new_links = [
+ 'license' => sprintf( '<span>' . __( '<a target="_blank" href="https://meowapps.com">Get the <u>Pro Version</u></a>', $this->domain ), $this->prefix ) . '</span>',
+ ];
+ }
+ }
+ else {
+ // Regular admin - show settings and license status
+ $new_links = [
+ 'settings' =>
+ sprintf( __( '<a href="admin.php?page=%s_settings">Settings</a>', $this->domain ), $this->prefix ),
+ 'license' =>
+ $this->is_registered() ?
+ ( '<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>' ) :
+ ( $isIssue ? ( sprintf( '<span style="color: #ff3434;">' . __( 'License Issue', $this->domain ), $this->prefix ) . '</span>' ) : ( sprintf( '<span>' . __( '<a target="_blank" href="https://meowapps.com">Get the <u>Pro Version</u></a>', $this->domain ), $this->prefix ) . '</span>' ) ),
+ ];
+ }
$links = array_merge( $new_links, $links );
}
return $links;
}
+ /**
+ * Output the network license registration modal.
+ * Called via admin_footer hook in network admin.
+ */
+ public static function output_network_license_modal() {
+ $rest_url = esc_url( rest_url() );
+ $nonce = wp_create_nonce( 'wp_rest' );
+ ?>
+ <div id="meowapps-network-license-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:100000; align-items:center; justify-content:center;">
+ <div style="background:#fff; padding:24px; border-radius:8px; max-width:450px; width:90%; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
+ <h2 style="margin:0 0 8px 0; font-size:18px;">Register License</h2>
+ <p id="meowapps-license-plugin-name" style="margin:0 0 16px 0; color:#666;"></p>
+ <input type="text" id="meowapps-license-key-input" placeholder="Enter your license key" style="width:100%; padding:10px; font-size:14px; border:1px solid #8c8f94; border-radius:4px; box-sizing:border-box;" />
+ <p id="meowapps-license-message" style="margin:12px 0 0 0; padding:10px; border-radius:4px; display:none;"></p>
+ <div style="margin-top:16px; display:flex; gap:10px; justify-content:flex-end;">
+ <button type="button" id="meowapps-license-cancel" class="button">Cancel</button>
+ <button type="button" id="meowapps-license-submit" class="button button-primary">Validate & Register</button>
+ </div>
+ </div>
+ </div>
+ <script>
+ (function() {
+ var modal = document.getElementById('meowapps-network-license-modal');
+ var input = document.getElementById('meowapps-license-key-input');
+ var message = document.getElementById('meowapps-license-message');
+ var pluginName = document.getElementById('meowapps-license-plugin-name');
+ var submitBtn = document.getElementById('meowapps-license-submit');
+ var cancelBtn = document.getElementById('meowapps-license-cancel');
+ var currentPrefix = '';
+
+ function showMessage(text, isError) {
+ message.textContent = text;
+ message.style.display = 'block';
+ message.style.background = isError ? '#fcf0f1' : '#edfaef';
+ message.style.color = isError ? '#d63638' : '#1e7e34';
+ message.style.border = '1px solid ' + (isError ? '#d63638' : '#1e7e34');
+ }
+
+ function hideMessage() {
+ message.style.display = 'none';
+ }
+
+ function openModal(prefix, plugin) {
+ currentPrefix = prefix;
+ pluginName.textContent = plugin;
+ input.value = '';
+ hideMessage();
+ submitBtn.disabled = false;
+ submitBtn.textContent = 'Validate & Register';
+ modal.style.display = 'flex';
+ input.focus();
+ }
+
+ function closeModal() {
+ modal.style.display = 'none';
+ currentPrefix = '';
+ }
+
+ // Handle click on "Register License" links
+ document.addEventListener('click', function(e) {
+ if (e.target.classList.contains('meowapps-network-license-link')) {
+ e.preventDefault();
+ var prefix = e.target.getAttribute('data-prefix');
+ var plugin = e.target.getAttribute('data-plugin');
+ openModal(prefix, plugin);
+ }
+ });
+
+ // Close modal on cancel or clicking outside
+ cancelBtn.addEventListener('click', closeModal);
+ modal.addEventListener('click', function(e) {
+ if (e.target === modal) closeModal();
+ });
+
+ // Handle escape key
+ document.addEventListener('keydown', function(e) {
+ if (e.key === 'Escape' && modal.style.display === 'flex') {
+ closeModal();
+ }
+ });
+
+ // Handle enter key in input
+ input.addEventListener('keydown', function(e) {
+ if (e.key === 'Enter') {
+ submitBtn.click();
+ }
+ });
+
+ // Submit license
+ submitBtn.addEventListener('click', function() {
+ var licenseKey = input.value.trim();
+ if (!licenseKey) {
+ showMessage('Please enter a license key.', true);
+ return;
+ }
+
+ submitBtn.disabled = true;
+ submitBtn.textContent = 'Validating...';
+ hideMessage();
+
+ var restUrl = '<?php echo $rest_url; ?>meow-licenser/' + currentPrefix + '/v1/set_license/';
+
+ fetch(restUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': '<?php echo $nonce; ?>'
+ },
+ body: JSON.stringify({ serialKey: licenseKey })
+ })
+ .then(function(response) { return response.json(); })
+ .then(function(data) {
+ if (data.success && data.data && !data.data.issue) {
+ showMessage('License registered successfully! Reloading...', false);
+ setTimeout(function() { location.reload(); }, 1500);
+ } else {
+ var errorMsg = 'License validation failed.';
+ if (data.data && data.data.issue) {
+ errorMsg = 'License issue: ' + data.data.issue;
+ }
+ showMessage(errorMsg, true);
+ submitBtn.disabled = false;
+ submitBtn.textContent = 'Validate & Register';
+ }
+ })
+ .catch(function(error) {
+ showMessage('Error: ' + error.message, true);
+ submitBtn.disabled = false;
+ submitBtn.textContent = 'Validate & Register';
+ });
+ });
+ })();
+ </script>
+ <?php
+ }
+
public function request_verify_ssl() {
return get_option( 'force_sslverify', false );
}
--- a/wplr-sync/wplr-sync.php
+++ b/wplr-sync/wplr-sync.php
@@ -3,7 +3,7 @@
Plugin Name: Photo Engine (WP/LR Sync)
Plugin URI: https://meowapps.com
Description: Synchronize and maintain your photos, collections, keywords and metadata between Lightroom and WordPress.
-Version: 6.4.9
+Version: 6.5.0
Author: Jordy Meow
Author URI: https://meowapps.com
Text Domain: wplr-sync
@@ -14,7 +14,7 @@
- Haikyo (https://haikyo.org)
*/
-define( 'WPLR_SYNC_VERSION', '6.4.9' );
+define( 'WPLR_SYNC_VERSION', '6.5.0' );
define( 'WPLR_SYNC_PREFIX', 'wplr_sync' );
define( 'WPLR_SYNC_DOMAIN', 'wplr-sync' );
define( 'WPLR_SYNC_ENTRY', __FILE__ );