Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/motive-commerce-search/admin/partials/motive-admin-display.php
+++ b/motive-commerce-search/admin/partials/motive-admin-display.php
@@ -21,6 +21,10 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* Provide a admin area view for the plugin
*
--- a/motive-commerce-search/includes/class-motive-loader.php
+++ b/motive-commerce-search/includes/class-motive-loader.php
@@ -21,6 +21,10 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* Register all actions and filters for the plugin
*
--- a/motive-commerce-search/includes/class-motive.php
+++ b/motive-commerce-search/includes/class-motive.php
@@ -1,6 +1,4 @@
<?php
-use MotiveWoocommercePluginUpdateManager;
-use MotiveWoocommerceScriptLoader;
/**
* (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>
*
@@ -23,6 +21,13 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+use MotiveWoocommercePluginUpdateManager;
+use MotiveWoocommerceScriptLoader;
+
/**
* The file that defines the core plugin class
*
--- a/motive-commerce-search/motive.php
+++ b/motive-commerce-search/motive.php
@@ -34,15 +34,15 @@
* @package Motive
*
* @wordpress-plugin
- * Plugin Name: Motive Commerce Search
+ * Plugin Name: AI Product Search for WooCommerce - Motive Commerce Search
* Plugin URI: https://www.motive.co
* Description: Commerce search that you and your shoppers can trust. Incredible design. Intuitive interface. Powerful customisations. Tracker and cookie-free.
- * Version: 1.38.2
+ * Version: 1.38.3
* Author: Motive
* Author URI: https://www.motive.co/about-us
* License: Apache License, Version 2.0
* License URI: http://www.apache.org/licenses/LICENSE-2.0
- * Text Domain: motive-commmerce-search
+ * Text Domain: motive-commerce-search
* Domain Path: /languages
*/
require_once __DIR__ . '/vendor/autoload.php';
@@ -57,7 +57,7 @@
* Start at version 1.0.0 and use SemVer - https://semver.org
* Rename this for your plugin and update it as you release new versions.
*/
-define( 'MOTIVE_VERSION', '1.38.2' );
+define( 'MOTIVE_VERSION', '1.38.3' );
/**
* The code that runs during plugin activation.
--- a/motive-commerce-search/public/class-motive-public.php
+++ b/motive-commerce-search/public/class-motive-public.php
@@ -123,7 +123,7 @@
'info',
array(
'methods' => 'GET',
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'is_authorized_request' ),
'callback' => function () {
return InfoBuilder::build();
},
@@ -137,7 +137,7 @@
'(?P<lang>.+)/schema',
array(
'methods' => 'GET',
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'is_authorized_request' ),
'callback' => function ( $data ) {
$lang = LanguageManager::get_instance();
$lang->set_current_lang( $data->get_param( 'lang' ) );
@@ -154,7 +154,7 @@
'config',
array(
'methods' => 'GET',
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'is_authorized_request' ),
'callback' => function () {
// Using $_GET instead of $data->has_param to be compatible with wp < 5.3.0.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
@@ -170,7 +170,7 @@
'config',
array(
'methods' => 'POST',
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'is_authorized_request' ),
'callback' => function ( $data ) {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
return self::post_config( $data, isset( $_GET['if-unset'] ) );
@@ -186,7 +186,7 @@
'config-if-unset',
array(
'methods' => 'POST',
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( $this, 'is_authorized_request' ),
'callback' => function ( $data ) {
$only_if_unset = true;
return self::post_config( $data, $only_if_unset );
@@ -304,6 +304,21 @@
}
/**
+ * Permission callback used by protected endpoints.
+ *
+ * @param WP_REST_Request $request Request used to validate access.
+ *
+ * @return bool|WP_Error
+ */
+ public function is_authorized_request( $request ) {
+ if ( Token::check( $request->get_header( self::HEADER_MOTIVE_TOKEN ) ) ) {
+ return true;
+ }
+
+ return new WP_Error( 'rest_forbidden', 'Unauthorized. Invalid or missed security token', array( 'status' => 401 ) );
+ }
+
+ /**
* This function manages feed echoing, including headers.
*/
private static function write_feed( $lang_code, $query_params ) {
--- a/motive-commerce-search/public/js/index.php
+++ b/motive-commerce-search/public/js/index.php
@@ -0,0 +1,4 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
--- a/motive-commerce-search/public/js/motive-public.js.php
+++ b/motive-commerce-search/public/js/motive-public.js.php
@@ -1,25 +1,6 @@
<?php
-return <<<EOT
-(function(){"use strict";function u(t){const e=document.createElement("meta");e.id="motive_cart_info",e.dataset.productsCount="0",document.head.appendChild(e),document.addEventListener("DOMContentLoaded",function(){typeof jQuery>"u"||jQuery(document.body).on("wc_fragments_loaded",function(){jQuery(document.body).trigger("wc_fragment_refresh")})}),t.initParams.callbacks={UserClickedResultAddToCart:m,CartHandlerGettingCartInfo:f}}function f(){return{productsCount:parseInt(motive_cart_info.dataset.productsCount||"0")}}function m(t){if(!t.url)return;const e=new FormData;return e.append("quantity","1"),e.append("add-to-cart",t.id.toString()),fetch(t.url,{method:"POST",body:e}).then(()=>{motive_cart_info.dataset.productsCount=(parseInt(motive_cart_info.dataset.productsCount)+1).toString(),!(typeof jQuery>"u")&&jQuery(document.body).trigger("wc_fragment_refresh")})}let c=1,d=1;function p(t){if(t.options.shopperPrices){let e=function(r){const a=r.map(o=>o.variants?{id:o.id,variants:o.variants.map(h=>({id:h.id}))}:{id:o.id}),s=new URL(t.endpoints.shopperPrices);return s.searchParams.append("nonce",motiveShopperPricesNonce),fetch(s,{method:"PATCH",body:JSON.stringify(a)}).then(o=>o.json())};t.initParams.callbacks.AppendedResultsChanged=e,t.initParams.callbacks.RecommendationsChanged=e,t.initParams.transformPriceRange=(r,a)=>[r*c,a*d];const n=new URL(t.endpoints.shopperPrices);n.searchParams.append("nonce",motiveShopperPricesNonce),n.searchParams.append("action","price_rates"),fetch(n,{method:"PATCH"}).then(r=>r.json()).then(r=>[c,d]=r)}}function l(t){Array.from(document.querySelectorAll(t)).map(function(e){return e.closest("form")||e.querySelector("form")}).filter(function(e,n,r){return e&&r.indexOf(e)===n}).forEach(function(e){e&&(e.onsubmit=function(){return!1})})}/*
- * (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>
- *
- * This file is part of Motive Commerce Search.
- *
- * This file is licensed to you under the Apache License, Version 2.0 (the 'License');
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an 'AS IS' BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * @author Motive (motive.co)
- * @copyright (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>
- * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
- */typeof motive<"u"?i(motive):document.addEventListener("DOMContentLoaded",function(){if(typeof motive<"u")i(motive);else{const t=document.querySelector("#motive-config-url");if(!t)return;fetch(t.href,{method:"GET",credentials:"include",mode:"no-cors"}).then(e=>e.json()).then(e=>i(e))}});function i(t){if(u(t),p(t),window.initX=function(){return l(t.initParams.triggerSelector),t.initParams},{}.VITE_BUILD_TYPE!=="NOSCRIPT"){const e=document.querySelector("#motive-layer-js").href,n=document.createElement("script");n.setAttribute("src",e),n.setAttribute("type","module"),document.head.appendChild(n)}}})();
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
-EOT;
No newline at end of file
+return "(function(){"use strict";function u(t){const e=document.createElement("meta");e.id="motive_cart_info",e.dataset.productsCount="0",document.head.appendChild(e),document.addEventListener("DOMContentLoaded",function(){typeof jQuery>"u"||jQuery(document.body).on("wc_fragments_loaded",function(){jQuery(document.body).trigger("wc_fragment_refresh")})}),t.initParams.callbacks={UserClickedResultAddToCart:m,CartHandlerGettingCartInfo:f}}function f(){return{productsCount:parseInt(motive_cart_info.dataset.productsCount||"0")}}function m(t){if(!t.url)return;const e=new FormData;return e.append("quantity","1"),e.append("add-to-cart",t.id.toString()),fetch(t.url,{method:"POST",body:e}).then(()=>{motive_cart_info.dataset.productsCount=(parseInt(motive_cart_info.dataset.productsCount)+1).toString(),!(typeof jQuery>"u")&&jQuery(document.body).trigger("wc_fragment_refresh")})}let c=1,d=1;function p(t){if(t.options.shopperPrices){let e=function(r){const a=r.map(o=>o.variants?{id:o.id,variants:o.variants.map(h=>({id:h.id}))}:{id:o.id}),s=new URL(t.endpoints.shopperPrices);return s.searchParams.append("nonce",motiveShopperPricesNonce),fetch(s,{method:"PATCH",body:JSON.stringify(a)}).then(o=>o.json())};t.initParams.callbacks.AppendedResultsChanged=e,t.initParams.callbacks.RecommendationsChanged=e,t.initParams.transformPriceRange=(r,a)=>[r*c,a*d];const n=new URL(t.endpoints.shopperPrices);n.searchParams.append("nonce",motiveShopperPricesNonce),n.searchParams.append("action","price_rates"),fetch(n,{method:"PATCH"}).then(r=>r.json()).then(r=>[c,d]=r)}}function l(t){Array.from(document.querySelectorAll(t)).map(function(e){return e.closest("form")||e.querySelector("form")}).filter(function(e,n,r){return e&&r.indexOf(e)===n}).forEach(function(e){e&&(e.onsubmit=function(){return!1})})}/*n * (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>n *n * This file is part of Motive Commerce Search.n *n * This file is licensed to you under the Apache License, Version 2.0 (the 'License');n * you may not use this file except in compliance with the License.n * You may obtain a copy of the License atn *n * http://www.apache.org/licenses/LICENSE-2.0n *n * Unless required by applicable law or agreed to in writing, softwaren * distributed under the License is distributed on an 'AS IS' BASIS,n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.n * See the License for the specific language governing permissions andn * limitations under the License.n *n * @author Motive (motive.co)n * @copyright (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>n * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0n */typeof motive<"u"?i(motive):document.addEventListener("DOMContentLoaded",function(){if(typeof motive<"u")i(motive);else{const t=document.querySelector("#motive-config-url");if(!t)return;fetch(t.href,{method:"GET",credentials:"include",mode:"no-cors"}).then(e=>e.json()).then(e=>i(e))}});function i(t){if(u(t),p(t),window.initX=function(){return l(t.initParams.triggerSelector),t.initParams},{}.VITE_BUILD_TYPE!=="NOSCRIPT"){const e=document.querySelector("#motive-layer-js").href,n=document.createElement("script");n.setAttribute("src",e),n.setAttribute("type","module"),document.head.appendChild(n)}}})();n";
No newline at end of file
--- a/motive-commerce-search/public/js/motive_noscript-public.js.php
+++ b/motive-commerce-search/public/js/motive_noscript-public.js.php
@@ -1,25 +1,6 @@
<?php
-return <<<EOT
-(function(){"use strict";function u(t){const e=document.createElement("meta");e.id="motive_cart_info",e.dataset.productsCount="0",document.head.appendChild(e),document.addEventListener("DOMContentLoaded",function(){typeof jQuery>"u"||jQuery(document.body).on("wc_fragments_loaded",function(){jQuery(document.body).trigger("wc_fragment_refresh")})}),t.initParams.callbacks={UserClickedResultAddToCart:m,CartHandlerGettingCartInfo:f}}function f(){return{productsCount:parseInt(motive_cart_info.dataset.productsCount||"0")}}function m(t){if(!t.url)return;const e=new FormData;return e.append("quantity","1"),e.append("add-to-cart",t.id.toString()),fetch(t.url,{method:"POST",body:e}).then(()=>{motive_cart_info.dataset.productsCount=(parseInt(motive_cart_info.dataset.productsCount)+1).toString(),!(typeof jQuery>"u")&&jQuery(document.body).trigger("wc_fragment_refresh")})}let c=1,d=1;function p(t){if(t.options.shopperPrices){let e=function(n){const a=n.map(r=>r.variants?{id:r.id,variants:r.variants.map(l=>({id:l.id}))}:{id:r.id}),s=new URL(t.endpoints.shopperPrices);return s.searchParams.append("nonce",motiveShopperPricesNonce),fetch(s,{method:"PATCH",body:JSON.stringify(a)}).then(r=>r.json())};t.initParams.callbacks.AppendedResultsChanged=e,t.initParams.callbacks.RecommendationsChanged=e,t.initParams.transformPriceRange=(n,a)=>[n*c,a*d];const o=new URL(t.endpoints.shopperPrices);o.searchParams.append("nonce",motiveShopperPricesNonce),o.searchParams.append("action","price_rates"),fetch(o,{method:"PATCH"}).then(n=>n.json()).then(n=>[c,d]=n)}}function h(t){Array.from(document.querySelectorAll(t)).map(function(e){return e.closest("form")||e.querySelector("form")}).filter(function(e,o,n){return e&&n.indexOf(e)===o}).forEach(function(e){e&&(e.onsubmit=function(){return!1})})}/*
- * (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>
- *
- * This file is part of Motive Commerce Search.
- *
- * This file is licensed to you under the Apache License, Version 2.0 (the 'License');
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an 'AS IS' BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * @author Motive (motive.co)
- * @copyright (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>
- * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
- */typeof motive<"u"?i(motive):document.addEventListener("DOMContentLoaded",function(){if(typeof motive<"u")i(motive);else{const t=document.querySelector("#motive-config-url");if(!t)return;fetch(t.href,{method:"GET",credentials:"include",mode:"no-cors"}).then(e=>e.json()).then(e=>i(e))}});function i(t){u(t),p(t),window.initX=function(){return h(t.initParams.triggerSelector),t.initParams}}})();
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
-EOT;
No newline at end of file
+return "(function(){"use strict";function u(t){const e=document.createElement("meta");e.id="motive_cart_info",e.dataset.productsCount="0",document.head.appendChild(e),document.addEventListener("DOMContentLoaded",function(){typeof jQuery>"u"||jQuery(document.body).on("wc_fragments_loaded",function(){jQuery(document.body).trigger("wc_fragment_refresh")})}),t.initParams.callbacks={UserClickedResultAddToCart:m,CartHandlerGettingCartInfo:f}}function f(){return{productsCount:parseInt(motive_cart_info.dataset.productsCount||"0")}}function m(t){if(!t.url)return;const e=new FormData;return e.append("quantity","1"),e.append("add-to-cart",t.id.toString()),fetch(t.url,{method:"POST",body:e}).then(()=>{motive_cart_info.dataset.productsCount=(parseInt(motive_cart_info.dataset.productsCount)+1).toString(),!(typeof jQuery>"u")&&jQuery(document.body).trigger("wc_fragment_refresh")})}let c=1,d=1;function p(t){if(t.options.shopperPrices){let e=function(n){const a=n.map(r=>r.variants?{id:r.id,variants:r.variants.map(l=>({id:l.id}))}:{id:r.id}),s=new URL(t.endpoints.shopperPrices);return s.searchParams.append("nonce",motiveShopperPricesNonce),fetch(s,{method:"PATCH",body:JSON.stringify(a)}).then(r=>r.json())};t.initParams.callbacks.AppendedResultsChanged=e,t.initParams.callbacks.RecommendationsChanged=e,t.initParams.transformPriceRange=(n,a)=>[n*c,a*d];const o=new URL(t.endpoints.shopperPrices);o.searchParams.append("nonce",motiveShopperPricesNonce),o.searchParams.append("action","price_rates"),fetch(o,{method:"PATCH"}).then(n=>n.json()).then(n=>[c,d]=n)}}function h(t){Array.from(document.querySelectorAll(t)).map(function(e){return e.closest("form")||e.querySelector("form")}).filter(function(e,o,n){return e&&n.indexOf(e)===o}).forEach(function(e){e&&(e.onsubmit=function(){return!1})})}/*n * (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>n *n * This file is part of Motive Commerce Search.n *n * This file is licensed to you under the Apache License, Version 2.0 (the 'License');n * you may not use this file except in compliance with the License.n * You may obtain a copy of the License atn *n * http://www.apache.org/licenses/LICENSE-2.0n *n * Unless required by applicable law or agreed to in writing, softwaren * distributed under the License is distributed on an 'AS IS' BASIS,n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.n * See the License for the specific language governing permissions andn * limitations under the License.n *n * @author Motive (motive.co)n * @copyright (C) 2023 Motive Commerce Search Corp S.L. <info@motive.co>n * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0n */typeof motive<"u"?i(motive):document.addEventListener("DOMContentLoaded",function(){if(typeof motive<"u")i(motive);else{const t=document.querySelector("#motive-config-url");if(!t)return;fetch(t.href,{method:"GET",credentials:"include",mode:"no-cors"}).then(e=>e.json()).then(e=>i(e))}});function i(t){u(t),p(t),window.initX=function(){return h(t.initParams.triggerSelector),t.initParams}}})();n";
No newline at end of file
--- a/motive-commerce-search/public/shopper-prices/shopper-prices.php
+++ b/motive-commerce-search/public/shopper-prices/shopper-prices.php
@@ -21,6 +21,10 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
use MotiveWoocommerceModelXResultPrice;
use MotiveWoocommerceBuilderPriceBuilder;
use MotiveWoocommerceConfig;
--- a/motive-commerce-search/src/Builder/AttributeValueBuilder.php
+++ b/motive-commerce-search/src/Builder/AttributeValueBuilder.php
@@ -45,12 +45,15 @@
global $wpdb;
$raw_variation_attributes = $wpdb->get_results(
- "
+ $wpdb->prepare(
+ "
SELECT pm.meta_key, pm.meta_value
FROM {$wpdb->get_blog_prefix()}posts p
INNER JOIN {$wpdb->get_blog_prefix()}postmeta pm ON p.ID = pm.post_id
- WHERE p.post_type='PRODUCT_VARIATION' AND pm.meta_key like 'attribute_%' AND p.ID=$variation_id
- "
+ WHERE p.post_type='PRODUCT_VARIATION' AND pm.meta_key like 'attribute_%' AND p.ID=%d
+ ",
+ $variation_id
+ )
);
if ( ! $raw_variation_attributes ) {
return array();
@@ -75,17 +78,24 @@
* @return string label of the slug if found, slug otherwise
*/
public static function try_get_values_from_global_taxonomy( $term_name, $term_slug ) {
+ $term_slug = sanitize_title( $term_slug );
if ( isset( static::$cache_values[ $term_slug ] ) ) {
return static::$cache_values[ $term_slug ];
}
static::$cache_values[ $term_slug ] = $term_slug;
+ $term_name = sanitize_key( $term_name );
global $wpdb;
$term_value = $wpdb->get_var(
- "
+ $wpdb->prepare(
+ "
SELECT t.name
FROM {$wpdb->get_blog_prefix()}terms t
INNER JOIN {$wpdb->get_blog_prefix()}term_taxonomy tt ON tt.term_id = t.term_id
- WHERE tt.taxonomy='$term_name' AND t.slug='$term_slug'"
+ WHERE tt.taxonomy=%s AND t.slug=%s
+ ",
+ $term_name,
+ $term_slug
+ )
);
if ( $term_value ) {
static::$cache_values[ $term_slug ] = html_entity_decode( $term_value );
--- a/motive-commerce-search/src/Builder/FeatureValueBuilder.php
+++ b/motive-commerce-search/src/Builder/FeatureValueBuilder.php
@@ -96,14 +96,20 @@
*/
private static function get_values_from_global_taxonomy( $product_id, $name ) {
global $wpdb;
+ $product_id = (int) $product_id;
+ $name = sanitize_key( $name );
$values = array();
$applied_attributes = $wpdb->get_results(
- "
+ $wpdb->prepare(
+ "
SELECT t.name
FROM {$wpdb->get_blog_prefix()}terms t
INNER JOIN {$wpdb->get_blog_prefix()}term_taxonomy tt ON tt.term_id = t.term_id
INNER JOIN {$wpdb->get_blog_prefix()}term_relationships tr ON tr.term_taxonomy_id = tt.term_taxonomy_id
- WHERE tt.taxonomy='$name' AND tr.object_id=$product_id"
+ WHERE tt.taxonomy=%s AND tr.object_id=%d",
+ $name,
+ $product_id
+ )
);
foreach ( $applied_attributes as $applied_attribute ) {
$values[] = html_entity_decode( $applied_attribute->name );
--- a/motive-commerce-search/src/Builder/ImageBuilder.php
+++ b/motive-commerce-search/src/Builder/ImageBuilder.php
@@ -54,16 +54,26 @@
$results = array();
if ( ! empty( $attachment_ids ) ) {
- $attachment_ids_imploded = implode( ',', $attachment_ids );
+ $attachment_ids = array_map( 'intval', $attachment_ids );
+ $ids_count = count( $attachment_ids );
+ if ( 0 === $ids_count ) {
+ return array_values( $images );
+ }
+ $placeholders = implode( ',', array_fill( 0, $ids_count, '%d' ) );
+ $limit = (int) $this->image_limit;
global $wpdb;
- $results = $wpdb->get_results(
- "
+ $query_args = array_merge( $attachment_ids, $attachment_ids, array( $limit ) );
+ $results = $wpdb->get_results(
+ $wpdb->prepare(
+ "
SELECT p.post_name as legend, p.id as attachment_id
FROM {$wpdb->get_blog_prefix()}posts p
- WHERE (p.ID IN ($attachment_ids_imploded) AND p.post_type='attachment')
- ORDER BY FIELD(p.id, $attachment_ids_imploded)
- LIMIT $this->image_limit
- "
+ WHERE (p.ID IN ($placeholders) AND p.post_type='attachment')
+ ORDER BY FIELD(p.id, $placeholders)
+ LIMIT %d
+ ",
+ ...$query_args
+ )
);
}
--- a/motive-commerce-search/src/Builder/PostMetaSqlBuilder.php
+++ b/motive-commerce-search/src/Builder/PostMetaSqlBuilder.php
@@ -29,8 +29,15 @@
class PostMetaSqlBuilder {
private static function get_single_post_meta_sql( $meta_name, $db_prefix, $join = 'LEFT', $alias_prefix = 'p' ) {
- $select = "pm$meta_name.meta_value AS $alias_prefix$meta_name";
- $join = "$join JOIN {$db_prefix}postmeta AS pm$meta_name ON pm$meta_name.meta_id = (SELECT MAX(meta_id) FROM {$db_prefix}postmeta WHERE meta_key = '$meta_name' AND post_id = p.ID)";
+ $meta_name = sanitize_key( $meta_name );
+ $meta_alias = str_replace( '-', '_', $meta_name );
+ $join_type = strtoupper( $join );
+ $join_type = in_array( $join_type, array( 'LEFT', 'INNER' ), true ) ? $join_type : 'LEFT';
+ $alias_prefix = sanitize_key( $alias_prefix );
+ $db_prefix = preg_replace( '/[^A-Za-z0-9_]/', '', $db_prefix );
+
+ $select = "pm$meta_alias.meta_value AS {$alias_prefix}{$meta_alias}";
+ $join = "$join_type JOIN {$db_prefix}postmeta AS pm$meta_alias ON pm$meta_alias.meta_id = (SELECT MAX(meta_id) FROM {$db_prefix}postmeta WHERE meta_key = '$meta_name' AND post_id = p.ID)";
return (object) array(
'select' => $select,
--- a/motive-commerce-search/src/Builder/PriceBuilder.php
+++ b/motive-commerce-search/src/Builder/PriceBuilder.php
@@ -23,6 +23,10 @@
namespace MotiveWoocommerceBuilder;
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
use MotiveWoocommerceModelPrice;
use MotiveWoocommerceMotiveDateTools;
--- a/motive-commerce-search/src/Builder/Product/MultipleQueryProductBuilder.php
+++ b/motive-commerce-search/src/Builder/Product/MultipleQueryProductBuilder.php
@@ -47,12 +47,12 @@
$sql = $this->build_products_query( $from_id_product );
- $rows = $wpdb->get_results( $sql );
+ $rows = $wpdb->get_results( $wpdb->prepare( $sql->query, ...$sql->args ) );
$row = &$rows[ $row_count ];
$postmeta_sql = $this->get_postmeta_sql_from_rows( $rows, static::POSTMETA_KEYS );
- foreach ( $wpdb->get_results( $postmeta_sql ) as $postmeta_row ) {
+ foreach ( $wpdb->get_results( $wpdb->prepare( $postmeta_sql->query, ...$postmeta_sql->args ) ) as $postmeta_row ) {
while ( $row->p_id < $postmeta_row->pm_postid ) {
$from_id_product = $row->p_id;
yield self::create_missing_row_fields( $row, static::POSTMETA_KEYS );
@@ -97,17 +97,36 @@
private function get_postmeta_sql_from_rows( $rows, $postmeta_keys ) {
global $wpdb;
- $postmeta_keys_sql = "'" . join( "', '", $postmeta_keys ) . "'";
- $ids = join( ',', array_column( $rows, 'p_id' ) );
- return "
+ $ids = array_map( 'intval', array_column( $rows, 'p_id' ) );
+ if ( empty( $ids ) ) {
+ return (object) array(
+ 'query' => "
+ SELECT
+ pm.post_id AS pm_postid,
+ pm.meta_key AS pm_metakey,
+ pm.meta_value AS pm_metavalue
+ FROM {$wpdb->get_blog_prefix()}postmeta AS pm
+ WHERE 1 = 0
+ ",
+ 'args' => array(),
+ );
+ }
+
+ $postmeta_keys_placeholders = implode( ',', array_fill( 0, count( $postmeta_keys ), '%s' ) );
+ $ids_placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
+
+ return (object) array(
+ 'query' => "
SELECT
pm.post_id AS pm_postid,
pm.meta_key AS pm_metakey,
pm.meta_value AS pm_metavalue
FROM {$wpdb->get_blog_prefix()}postmeta AS pm
- WHERE pm.post_id IN ($ids) AND pm.meta_key IN ($postmeta_keys_sql)
+ WHERE pm.post_id IN ($ids_placeholders) AND pm.meta_key IN ($postmeta_keys_placeholders)
ORDER BY pm.post_id, pm.meta_key, pm.meta_id DESC
- ";
+ ",
+ 'args' => array_merge( $ids, $postmeta_keys ),
+ );
}
/**
@@ -121,7 +140,8 @@
$relevant_taxonomies = TaxonomyBuilder::get_relevant_taxonomies();
$inner_join_langs = $this->language_manager->get_inner_join_products_lang();
$from_id_product = (int) $from_id_product;
- return "
+ return (object) array(
+ 'query' => "
SELECT
p.ID AS p_id,
p.post_title AS p_name,
@@ -133,12 +153,14 @@
$relevant_taxonomies->join_statement
WHERE
p.post_type='PRODUCT'
- AND p.ID > $from_id_product
+ AND p.ID > %d
AND p.post_status = 'publish'
$relevant_taxonomies->where_statement
ORDER BY
p.ID
- LIMIT {$this->product_batch_size};
- ";
+ LIMIT %d;
+ ",
+ 'args' => array( $from_id_product, (int) $this->product_batch_size ),
+ );
}
}
--- a/motive-commerce-search/src/Builder/Product/SingleQueryProductBuilder.php
+++ b/motive-commerce-search/src/Builder/Product/SingleQueryProductBuilder.php
@@ -47,7 +47,7 @@
do {
$row_count = 0;
$sql = $this->build_query( $from_id_product, static::POSTMETA_KEYS );
- foreach ( $wpdb->get_results( $sql ) as $row ) {
+ foreach ( $wpdb->get_results( $wpdb->prepare( $sql->query, ...$sql->args ) ) as $row ) {
$from_id_product = $row->p_id;
yield $row;
++$row_count;
@@ -68,7 +68,8 @@
$relevant_taxonomies = TaxonomyBuilder::get_relevant_taxonomies();
$inner_join_langs = $this->language_manager->get_inner_join_products_lang();
$from_id_product = (int) $from_id_product;
- return "
+ return (object) array(
+ 'query' => "
SELECT
p.ID AS p_id,
p.post_title AS p_name,
@@ -82,12 +83,14 @@
$relevant_taxonomies->join_statement
WHERE
p.post_type='PRODUCT'
- AND p.ID > $from_id_product
+ AND p.ID > %d
AND p.post_status = 'publish'
$relevant_taxonomies->where_statement
ORDER BY
p.ID
- LIMIT {$this->product_batch_size};
- ";
+ LIMIT %d;
+ ",
+ 'args' => array( $from_id_product, (int) $this->product_batch_size ),
+ );
}
}
--- a/motive-commerce-search/src/Builder/SchemaBuilder.php
+++ b/motive-commerce-search/src/Builder/SchemaBuilder.php
@@ -76,23 +76,23 @@
* @return Field[]
*/
public function build_fields() {
- $this->language_manager->load_schemabuilder_textdomain();
+ $this->language_manager->load_motive_textdomain();
$fields = $this->build_shared_fields();
$fields[] = Field::build( 'name', FieldType::NAME )
- ->setLabel( __( 'Name', 'schemabuilder' ) )
+ ->setLabel( __( 'Name', 'motive-commerce-search' ) )
->setSearchable();
$fields[] = Field::build( 'short_description', FieldType::TEXT )
- ->setLabel( __( 'Short description', 'schemabuilder' ) )
+ ->setLabel( __( 'Short description', 'motive-commerce-search' ) )
->setSearchable();
$fields[] = Field::build( 'category', FieldType::CATEGORY )
- ->setLabel( __( 'Category', 'schemabuilder' ) )
+ ->setLabel( __( 'Category', 'motive-commerce-search' ) )
->setSearchable()
->setFacetable();
// Additional searchable fields
$fields[] = Field::build( 'tags', FieldType::TEXT )
- ->setLabel( __( 'Tags', 'schemabuilder' ) )
+ ->setLabel( __( 'Tags', 'motive-commerce-search' ) )
->setSearchable()
->setFacetable();
@@ -109,19 +109,19 @@
}
$fields[] = Field::build( 'is_bundle', FieldType::BOOLEAN )
- ->setLabel( __( 'Bundle', 'schemabuilder' ) )
+ ->setLabel( __( 'Bundle', 'motive-commerce-search' ) )
->setFacetable();
$fields[] = Field::build( 'is_virtual', FieldType::BOOLEAN )
- ->setLabel( __( 'Virtual', 'schemabuilder' ) )
+ ->setLabel( __( 'Virtual', 'motive-commerce-search' ) )
->setFacetable();
$fields[] = Field::build( 'is_featured', FieldType::BOOLEAN )
- ->setLabel( __( 'Featured', 'schemabuilder' ) )
+ ->setLabel( __( 'Featured', 'motive-commerce-search' ) )
->setFacetable();
$fields[] = Field::build( 'on_sale', FieldType::BOOLEAN )
- ->setLabel( __( 'On Sale', 'schemabuilder' ) )
+ ->setLabel( __( 'On Sale', 'motive-commerce-search' ) )
->setFacetable();
// Products variations/attributes
@@ -150,33 +150,33 @@
Field::build( $id_prefix . 'id', FieldType::ID, 'id' ),
Field::build( $id_prefix . 'url', FieldType::LINK, 'url' ),
Field::build( $id_prefix . 'description', FieldType::DESCRIPTION, 'description' )
- ->setLabel( __( 'Description', 'schemabuilder' ) )
+ ->setLabel( __( 'Description', 'motive-commerce-search' ) )
->setSearchable(),
Field::build( $id_prefix . 'images', FieldType::IMAGE, 'images' ),
Field::build( $id_prefix . 'availability', FieldType::AVAILABILITY, 'availability' )
- ->setLabel( __( 'In Stock', 'schemabuilder' ) )
+ ->setLabel( __( 'In Stock', 'motive-commerce-search' ) )
->setFacetable(),
Field::build( $id_prefix . 'price', FieldType::PRICE, 'price' )
- ->setLabel( __( 'Price', 'schemabuilder' ) )
+ ->setLabel( __( 'Price', 'motive-commerce-search' ) )
->setFacetable()
->setSortable(),
Field::build( $id_prefix . 'sku', FieldType::CODE_REFERENCE, 'sku' )
- ->setLabel( __( 'SKU', 'schemabuilder' ) )
+ ->setLabel( __( 'SKU', 'motive-commerce-search' ) )
->setSearchable(),
Field::build( $id_prefix . 'gtin', FieldType::CODE_GTIN, 'gtin' )
- ->setLabel( __( 'GTIN', 'schemabuilder' ) )
+ ->setLabel( __( 'GTIN', 'motive-commerce-search' ) )
->setSearchable(),
Field::build( $id_prefix . 'ean13', FieldType::CODE_EAN13, 'ean13' )
- ->setLabel( __( 'EAN-13', 'schemabuilder' ) )
+ ->setLabel( __( 'EAN-13', 'motive-commerce-search' ) )
->setSearchable(),
Field::build( $id_prefix . 'upc', FieldType::CODE_UPC, 'upc' )
- ->setLabel( __( 'UPC', 'schemabuilder' ) )
+ ->setLabel( __( 'UPC', 'motive-commerce-search' ) )
->setSearchable(),
Field::build( $id_prefix . 'isbn', FieldType::CODE_ISBN, 'isbn' )
- ->setLabel( __( 'ISBN', 'schemabuilder' ) )
+ ->setLabel( __( 'ISBN', 'motive-commerce-search' ) )
->setSearchable(),
Field::build( $id_prefix . 'labels', FieldType::PRODUCT_LABEL, 'labels' )
- ->setLabel( __( 'Product labels', 'schemabuilder' ) )
+ ->setLabel( __( 'Product labels', 'motive-commerce-search' ) )
->setRetrievable(),
);
}
--- a/motive-commerce-search/src/Builder/SchemaProductLabelBuilder.php
+++ b/motive-commerce-search/src/Builder/SchemaProductLabelBuilder.php
@@ -33,12 +33,12 @@
class SchemaProductLabelBuilder {
/**
- * Returns an array with the available labels in the platfor
+ * Returns an array with the available labels in the platform
* @return array
*/
public function get_available_labels() {
return array(
- SchemaProductLabel::build( 'on-sale', __( 'Sale!', 'woocommerce' ) ),
+ SchemaProductLabel::build( 'on-sale', __( 'Sale!', 'motive-commerce-search' ) ),
);
}
}
--- a/motive-commerce-search/src/Builder/TaxonomyBuilder.php
+++ b/motive-commerce-search/src/Builder/TaxonomyBuilder.php
@@ -53,12 +53,15 @@
private function fetch() {
global $wpdb;
return $wpdb->get_results(
- "
+ $wpdb->prepare(
+ "
SELECT t.term_id, t.name, tt.parent
FROM {$wpdb->get_blog_prefix()}term_taxonomy tt
INNER JOIN {$wpdb->get_blog_prefix()}terms t ON t.term_id = tt.term_id
- WHERE tt.taxonomy = '$this->taxonomy_name'
- "
+ WHERE tt.taxonomy = %s
+ ",
+ $this->taxonomy_name
+ )
);
}
@@ -71,15 +74,20 @@
*/
protected function fetch_for_product( $id_product ) {
global $wpdb;
+ $id_product = (int) $id_product;
return $wpdb->get_results(
- "
+ $wpdb->prepare(
+ "
SELECT t.term_id
FROM {$wpdb->get_blog_prefix()}posts p
INNER JOIN {$wpdb->get_blog_prefix()}term_relationships tr ON tr.object_id = p.ID
INNER JOIN {$wpdb->get_blog_prefix()}term_taxonomy tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
INNER JOIN {$wpdb->get_blog_prefix()}terms t ON t.term_id = tt.term_id
- WHERE p.post_type = 'PRODUCT' AND p.ID = $id_product AND tt.taxonomy = '$this->taxonomy_name'
- "
+ WHERE p.post_type = 'PRODUCT' AND p.ID = %d AND tt.taxonomy = %s
+ ",
+ $id_product,
+ $this->taxonomy_name
+ )
);
}
--- a/motive-commerce-search/src/Builder/VariationBuilder.php
+++ b/motive-commerce-search/src/Builder/VariationBuilder.php
@@ -52,6 +52,7 @@
*/
public function fetch_for( $product_id, $product_row ) {
global $wpdb;
+ $product_id = (int) $product_id;
$post_meta_keys = array(
'_sku',
@@ -72,7 +73,8 @@
$post_meta_sql = PostMetaSqlBuilder::get_post_meta_sql( $post_meta_keys, $wpdb->get_blog_prefix(), 'LEFT', 'v' );
$results = $wpdb->get_results(
- "
+ $wpdb->prepare(
+ "
SELECT
p.ID AS v_id,
p.post_title AS v_name,
@@ -81,9 +83,11 @@
$post_meta_sql->select_statement
FROM {$wpdb->get_blog_prefix()}posts AS p
$post_meta_sql->join_statement
- WHERE p.post_type='PRODUCT_VARIATION' AND p.post_parent = $product_id AND p.post_status = 'publish'
+ WHERE p.post_type='PRODUCT_VARIATION' AND p.post_parent = %d AND p.post_status = 'publish'
ORDER BY p.menu_order;
- "
+ ",
+ $product_id
+ )
);
$variations = array();
--- a/motive-commerce-search/src/LanguageManager.php
+++ b/motive-commerce-search/src/LanguageManager.php
@@ -94,11 +94,6 @@
return html_entity_decode( $this->handler->get_label( $id, $product_attribute, $this->current_lang ) );
}
- public function load_schemabuilder_textdomain() {
- $two_chars_lang = explode( '-', $this->current_lang )[0];
- load_textdomain( 'schemabuilder', plugin_dir_path( __FILE__ ) . "../languages/schemabuilder-$two_chars_lang.mo" );
- }
-
public function load_motive_textdomain() {
$two_chars_lang = explode( '-', $this->current_lang )[0];
load_textdomain( 'motive-commerce-search', plugin_dir_path( __FILE__ ) . "../languages/motive-commerce-search-$two_chars_lang.mo" );
--- a/motive-commerce-search/src/ScriptLoader.php
+++ b/motive-commerce-search/src/ScriptLoader.php
@@ -23,6 +23,10 @@
namespace MotiveWoocommerce;
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* ScriptLoader abstract class is the place where we implement in different ways the layer
* initialization scripts with its preloads, and also the interoperability script.
--- a/motive-commerce-search/src/ScriptLoaders/AbstractScriptEnqueuer.php
+++ b/motive-commerce-search/src/ScriptLoaders/AbstractScriptEnqueuer.php
@@ -24,6 +24,10 @@
namespace MotiveWoocommerceScriptLoaders;
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
use MotiveWoocommerceScriptLoader;
use MotiveWoocommerceConfig;
--- a/motive-commerce-search/src/ScriptLoaders/ScriptBodyPrinter.php
+++ b/motive-commerce-search/src/ScriptLoaders/ScriptBodyPrinter.php
@@ -21,9 +21,12 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
-
namespace MotiveWoocommerceScriptLoaders;
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* ScriptBodyPrinter loader will try to append scripts for layer using `wp_body_open` action, with a
* priority of 2 (should be less than the one used for preload links). This action is provided by
--- a/motive-commerce-search/src/ScriptLoaders/ScriptHeadPrinter.php
+++ b/motive-commerce-search/src/ScriptLoaders/ScriptHeadPrinter.php
@@ -24,6 +24,10 @@
namespace MotiveWoocommerceScriptLoaders;
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* ScriptHeadPrinter loader will try to append scripts for layer using `wp_head` action, with a
* priority of 2 (should be less than the one used for preload links). This action is provided by
--- a/motive-commerce-search/vendor/autoload.php
+++ b/motive-commerce-search/vendor/autoload.php
@@ -4,4 +4,4 @@
require_once __DIR__ . '/composer/autoload_real.php';
-return ComposerAutoloaderInit7bf400156d3f67db11660d274f60ac4b::getLoader();
+return ComposerAutoloaderInita8c23479cc9573d2d202f15d86747187::getLoader();
--- a/motive-commerce-search/vendor/composer/autoload_real.php
+++ b/motive-commerce-search/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
-class ComposerAutoloaderInit7bf400156d3f67db11660d274f60ac4b
+class ComposerAutoloaderInita8c23479cc9573d2d202f15d86747187
{
private static $loader;
@@ -22,15 +22,15 @@
return self::$loader;
}
- spl_autoload_register(array('ComposerAutoloaderInit7bf400156d3f67db11660d274f60ac4b', 'loadClassLoader'), true, true);
+ spl_autoload_register(array('ComposerAutoloaderInita8c23479cc9573d2d202f15d86747187', 'loadClassLoader'), true, true);
self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(dirname(__FILE__)));
- spl_autoload_unregister(array('ComposerAutoloaderInit7bf400156d3f67db11660d274f60ac4b', 'loadClassLoader'));
+ spl_autoload_unregister(array('ComposerAutoloaderInita8c23479cc9573d2d202f15d86747187', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
- call_user_func(ComposerAutoloadComposerStaticInit7bf400156d3f67db11660d274f60ac4b::getInitializer($loader));
+ call_user_func(ComposerAutoloadComposerStaticInita8c23479cc9573d2d202f15d86747187::getInitializer($loader));
} else {
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
--- a/motive-commerce-search/vendor/composer/autoload_static.php
+++ b/motive-commerce-search/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@
namespace ComposerAutoload;
-class ComposerStaticInit7bf400156d3f67db11660d274f60ac4b
+class ComposerStaticInita8c23479cc9573d2d202f15d86747187
{
public static $prefixLengthsPsr4 = array (
'M' =>
@@ -97,9 +97,9 @@
public static function getInitializer(ClassLoader $loader)
{
return Closure::bind(function () use ($loader) {
- $loader->prefixLengthsPsr4 = ComposerStaticInit7bf400156d3f67db11660d274f60ac4b::$prefixLengthsPsr4;
- $loader->prefixDirsPsr4 = ComposerStaticInit7bf400156d3f67db11660d274f60ac4b::$prefixDirsPsr4;
- $loader->classMap = ComposerStaticInit7bf400156d3f67db11660d274f60ac4b::$classMap;
+ $loader->prefixLengthsPsr4 = ComposerStaticInita8c23479cc9573d2d202f15d86747187::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInita8c23479cc9573d2d202f15d86747187::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInita8c23479cc9573d2d202f15d86747187::$classMap;
}, null, ClassLoader::class);
}