File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/DB.tar
Installer.php 0000644 00000003246 15154211254 0007217 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration\Migrator;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\FirstInstallInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\InstallableInterface;
defined( 'ABSPATH' ) || exit;
/**
* Class Installer
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB
*/
class Installer implements Service, FirstInstallInterface, InstallableInterface {
/**
* @var TableManager
*/
protected $table_manager;
/**
* @var Migrator
*/
protected $migrator;
/**
* Installer constructor.
*
* @param TableManager $table_manager
* @param Migrator $migrator
*/
public function __construct( TableManager $table_manager, Migrator $migrator ) {
$this->table_manager = $table_manager;
$this->migrator = $migrator;
}
/**
* Run installation logic for this class.
*
* @param string $old_version Previous version before updating.
* @param string $new_version Current version after updating.
*/
public function install( string $old_version, string $new_version ): void {
foreach ( $this->table_manager->get_tables() as $table ) {
$table->install();
}
// Run migrations.
$this->migrator->migrate( $old_version, $new_version );
}
/**
* Logic to run when the plugin is first installed.
*/
public function first_install(): void {
foreach ( $this->table_manager->get_tables() as $table ) {
if ( $table instanceof FirstInstallInterface ) {
$table->first_install();
}
}
}
}
Migration/AbstractMigration.php 0000644 00000001034 15154211254 0012621 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class AbstractMigration
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration
*
* @since 1.4.1
*/
abstract class AbstractMigration implements MigrationInterface {
/**
* @var wpdb
*/
protected $wpdb;
/**
* AbstractMigration constructor.
*
* @param wpdb $wpdb The wpdb object.
*/
public function __construct( wpdb $wpdb ) {
$this->wpdb = $wpdb;
}
}
Migration/Migration20211228T1640692399.php 0000644 00000005016 15154211254 0013130 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\ShippingRateTable;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class Migration20211228T1640692399
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration
*
* @since 1.12.2
*/
class Migration20211228T1640692399 extends AbstractMigration {
/**
* @var ShippingRateTable
*/
protected $shipping_rate_table;
/**
* @var OptionsInterface
*/
protected $options;
/**
* Migration constructor.
*
* @param wpdb $wpdb The wpdb object.
* @param ShippingRateTable $shipping_rate_table
* @param OptionsInterface $options
*/
public function __construct( wpdb $wpdb, ShippingRateTable $shipping_rate_table, OptionsInterface $options ) {
parent::__construct( $wpdb );
$this->shipping_rate_table = $shipping_rate_table;
$this->options = $options;
}
/**
* Returns the version to apply this migration for.
*
* @return string A version number. For example: 1.4.1
*/
public function get_applicable_version(): string {
return '1.12.2';
}
/**
* Apply the migrations.
*
* @return void
*/
public function apply(): void {
if ( $this->shipping_rate_table->exists() ) {
$mc_settings = $this->options->get( OptionsInterface::MERCHANT_CENTER );
if ( ! is_array( $mc_settings ) ) {
return;
}
if ( isset( $mc_settings['offers_free_shipping'] ) && false !== boolval( $mc_settings['offers_free_shipping'] ) && isset( $mc_settings['free_shipping_threshold'] ) ) {
// Move the free shipping threshold from the options to the shipping rate table.
$options_json = wp_json_encode( [ 'free_shipping_threshold' => (float) $mc_settings['free_shipping_threshold'] ] );
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->wpdb->query( $this->wpdb->prepare( "UPDATE `{$this->wpdb->_escape( $this->shipping_rate_table->get_name() )}` SET `options`=%s WHERE 1=1", $options_json ) );
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
// Remove the free shipping threshold from the options.
unset( $mc_settings['free_shipping_threshold'] );
unset( $mc_settings['offers_free_shipping'] );
$this->options->update( OptionsInterface::MERCHANT_CENTER, $mc_settings );
}
}
}
Migration/Migration20220524T1653383133.php 0000644 00000002160 15154211254 0013107 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\BudgetRecommendationTable;
defined( 'ABSPATH' ) || exit;
/**
* Class Migration20220524T1653383133
*
* Migration class to reload the default Ads budgets recommendations
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration
*
* @since 1.13.3
*/
class Migration20220524T1653383133 extends AbstractMigration {
/**
* @var BudgetRecommendationTable
*/
protected $budget_rate_table;
/**
* Migration constructor.
*
* @param BudgetRecommendationTable $budget_rate_table
*/
public function __construct( BudgetRecommendationTable $budget_rate_table ) {
$this->budget_rate_table = $budget_rate_table;
}
/**
* Returns the version to apply this migration for.
*
* @return string A version number. For example: 1.4.1
*/
public function get_applicable_version(): string {
return '1.13.3';
}
/**
* Apply the migrations.
*
* @return void
*/
public function apply(): void {
$this->budget_rate_table->reload_data();
}
}
Migration/Migration20231109T1653383133.php 0000644 00000003542 15154211254 0013115 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\BudgetRecommendationTable;
defined( 'ABSPATH' ) || exit;
/**
* Class Migration20231109T1653383133
*
* Migration class to reload the default Ads budgets recommendations provided by Google on 9 Nov 2023
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration
*
* @since 2.5.13
*/
class Migration20231109T1653383133 extends AbstractMigration {
/**
* @var BudgetRecommendationTable
*/
protected $budget_rate_table;
/**
* Migration constructor.
*
* @param \wpdb $wpdb
* @param BudgetRecommendationTable $budget_rate_table
*/
public function __construct( \wpdb $wpdb, BudgetRecommendationTable $budget_rate_table ) {
parent::__construct( $wpdb );
$this->budget_rate_table = $budget_rate_table;
}
/**
* Returns the version to apply this migration for.
*
* @return string A version number. For example: 1.4.1
*/
public function get_applicable_version(): string {
return '2.5.13';
}
/**
* Apply the migrations.
*
* @return void
*/
public function apply(): void {
if ( $this->budget_rate_table->exists() && $this->budget_rate_table->has_column( 'daily_budget_low' ) ) {
$this->wpdb->query( "ALTER TABLE `{$this->wpdb->_escape( $this->budget_rate_table->get_name() )}` DROP COLUMN `daily_budget_low`" ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
if ( $this->budget_rate_table->exists() && $this->budget_rate_table->has_column( 'daily_budget_high' ) ) {
$this->wpdb->query( "ALTER TABLE `{$this->wpdb->_escape( $this->budget_rate_table->get_name() )}` DROP COLUMN `daily_budget_high`" ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
$this->budget_rate_table->reload_data();
}
}
Migration/Migration20240813T1653383133.php 0000644 00000003215 15154211254 0013114 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\ShippingTimeTable;
defined( 'ABSPATH' ) || exit;
/**
* Class Migration20240813T1653383133
*
* Migration class to enable min and max time shippings.
*
* @see pcTzPl-2qP
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration
*
* @since 2.9.1
*/
class Migration20240813T1653383133 extends AbstractMigration {
/**
* @var ShippingTimeTable
*/
protected $shipping_time_table;
/**
* Migration constructor.
*
* @param \wpdb $wpdb
* @param ShippingTimeTable $shipping_time_table
*/
public function __construct( \wpdb $wpdb, ShippingTimeTable $shipping_time_table ) {
parent::__construct( $wpdb );
$this->shipping_time_table = $shipping_time_table;
}
/**
* Returns the version to apply this migration for.
*
* @return string A version number. For example: 1.4.1
*/
public function get_applicable_version(): string {
return '2.9.1';
}
/**
* Apply the migrations.
*
* @return void
*/
public function apply(): void {
if ( $this->shipping_time_table->exists() && ! $this->shipping_time_table->has_column( 'max_time' ) ) {
$this->wpdb->query( "ALTER TABLE `{$this->wpdb->_escape( $this->shipping_time_table->get_name() )}` Add COLUMN `max_time` bigint(20) NOT NULL default 0" ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
// Fill the new column with the current values
$this->wpdb->query( "UPDATE `{$this->wpdb->_escape( $this->shipping_time_table->get_name() )}` SET `max_time`=`time` WHERE 1=1" );
}
}
Migration/MigrationInterface.php 0000644 00000001062 15154211254 0012757 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration;
defined( 'ABSPATH' ) || exit;
/**
* Interface MigrationInterface
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration
*
* @since 1.4.1
*/
interface MigrationInterface {
/**
* Returns the version to apply this migration for.
*
* @return string A version number. For example: 1.4.1
*/
public function get_applicable_version(): string;
/**
* Apply the migrations.
*
* @return void
*/
public function apply(): void;
}
Migration/MigrationVersion141.php 0000644 00000002546 15154211254 0012742 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\MerchantIssueTable;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class MigrationVersion141
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration
*
* @since 1.4.1
*/
class MigrationVersion141 extends AbstractMigration {
/**
* @var MerchantIssueTable
*/
protected $mc_issues_table;
/**
* MigrationVersion141 constructor.
*
* @param wpdb $wpdb The wpdb object.
* @param MerchantIssueTable $mc_issues_table
*/
public function __construct( wpdb $wpdb, MerchantIssueTable $mc_issues_table ) {
parent::__construct( $wpdb );
$this->mc_issues_table = $mc_issues_table;
}
/**
* Returns the version to apply this migration for.
*
* @return string A version number. For example: 1.4.1
*/
public function get_applicable_version(): string {
return '1.4.1';
}
/**
* Apply the migrations.
*
* @return void
*/
public function apply(): void {
if ( $this->mc_issues_table->exists() && $this->mc_issues_table->has_index( 'product_issue' ) ) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->wpdb->query( "ALTER TABLE `{$this->wpdb->_escape( $this->mc_issues_table->get_name() )}` DROP INDEX `product_issue`" );
}
}
}
Migration/Migrator.php 0000644 00000003763 15154211254 0011003 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
defined( 'ABSPATH' ) || exit;
/**
* Class Migrator
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Migration
*
* @since 1.4.1
*/
class Migrator implements Service {
/**
* @var MigrationInterface[]
*/
protected $migrations;
/**
* Migrator constructor.
*
* @param MigrationInterface[] $migrations An array of all available migrations.
*/
public function __construct( array $migrations ) {
$this->migrations = $migrations;
// Sort migrations by version.
uasort(
$this->migrations,
function ( MigrationInterface $migration_a, MigrationInterface $migration_b ) {
return version_compare( $migration_a->get_applicable_version(), $migration_b->get_applicable_version() );
}
);
}
/**
* Run migrations.
*
* @param string $old_version Previous version before updating.
* @param string $new_version Current version after updating.
*/
public function migrate( string $old_version, string $new_version ): void {
// bail if both versions are equal.
if ( 0 === version_compare( $old_version, $new_version ) ) {
return;
}
foreach ( $this->migrations as $migration ) {
if ( $this->can_apply( $migration->get_applicable_version(), $old_version, $new_version ) ) {
$migration->apply();
}
}
}
/**
* @param string $migration_version The applicable version of the migration.
* @param string $old_version Previous version before updating.
* @param string $new_version Current version after updating.
*
* @return bool True if migration should be applied.
*/
protected function can_apply( string $migration_version, string $old_version, string $new_version ): bool {
return version_compare( $old_version, $new_version, '<' ) &&
version_compare( $old_version, $migration_version, '<' ) &&
version_compare( $migration_version, $new_version, '<=' );
}
}
ProductFeedQueryHelper.php 0000644 00000016760 15154211254 0011661 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidValue;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\ContainerAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ContainerAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantStatuses;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductMetaHandler;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductRepository;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncer;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\ChannelVisibility;
use WP_Query;
use WP_REST_Request;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class ProductFeedQueryHelper
*
* ContainerAware used to access:
* - MerchantCenterService
* - MerchantStatuses
* - ProductHelper
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB
*/
class ProductFeedQueryHelper implements ContainerAwareInterface, Service {
use ContainerAwareTrait;
use PluginHelper;
/**
* @var wpdb
*/
protected $wpdb;
/**
* @var WP_REST_Request
*/
protected $request;
/**
* @var ProductRepository
*/
protected $product_repository;
/**
* Meta key for total sales.
*/
protected const META_KEY_TOTAL_SALES = 'total_sales';
/**
* ProductFeedQueryHelper constructor.
*
* @param wpdb $wpdb
* @param ProductRepository $product_repository
*/
public function __construct( wpdb $wpdb, ProductRepository $product_repository ) {
$this->wpdb = $wpdb;
$this->product_repository = $product_repository;
}
/**
* Retrieve an array of product information using the request params.
*
* @param WP_REST_Request $request
*
* @return array
*
* @throws InvalidValue If the orderby value isn't valid.
* @throws Exception If the status data can't be retrieved from Google.
*/
public function get( WP_REST_Request $request ): array {
$this->request = $request;
$products = [];
$args = $this->prepare_query_args();
$refresh_status_data_job = null;
list( $limit, $offset ) = $this->prepare_query_pagination();
$mc_service = $this->container->get( MerchantCenterService::class );
if ( $mc_service->is_connected() ) {
$refresh_status_data_job = $this->container->get( MerchantStatuses::class )->maybe_refresh_status_data();
}
/** @var ProductHelper $product_helper */
$product_helper = $this->container->get( ProductHelper::class );
add_filter( 'posts_where', [ $this, 'title_filter' ], 10, 2 );
foreach ( $this->product_repository->find( $args, $limit, $offset ) as $product ) {
$id = $product->get_id();
$errors = $product_helper->get_validation_errors( $product );
$mc_status = $product_helper->get_mc_status( $product ) ?: $product_helper->get_sync_status( $product );
// If the refresh_status_data_job is scheduled, we don't know the status yet as it is being refreshed.
if ( $refresh_status_data_job && $refresh_status_data_job->is_scheduled() ) {
$mc_status = null;
}
$products[ $id ] = [
'id' => $id,
'title' => $product->get_name(),
'visible' => $product_helper->get_channel_visibility( $product ) !== ChannelVisibility::DONT_SYNC_AND_SHOW,
'status' => $mc_status,
'image_url' => wp_get_attachment_image_url( $product->get_image_id(), 'full' ),
'price' => $product->get_price(),
'errors' => array_values( $errors ),
];
}
remove_filter( 'posts_where', [ $this, 'title_filter' ] );
return array_values( $products );
}
/**
* Count the number of products (using title filter if present).
*
* @param WP_REST_Request $request
*
* @return int
*
* @throws InvalidValue If the orderby value isn't valid.
*/
public function count( WP_REST_Request $request ): int {
$this->request = $request;
$args = $this->prepare_query_args();
add_filter( 'posts_where', [ $this, 'title_filter' ], 10, 2 );
$ids = $this->product_repository->find_ids( $args );
remove_filter( 'posts_where', [ $this, 'title_filter' ] );
return count( $ids );
}
/**
* Prepare the args to be used to retrieve the products, namely orderby, meta_query and type.
*
* @return array
*
* @throws InvalidValue If the orderby value isn't valid.
*/
protected function prepare_query_args(): array {
$product_types = ProductSyncer::get_supported_product_types();
$product_types = array_diff( $product_types, [ 'variation' ] );
$args = [
'type' => $product_types,
'status' => 'publish',
'orderby' => [ 'title' => 'ASC' ],
];
if ( ! empty( $this->request['ids'] ) ) {
$args['include'] = explode( ',', $this->request['ids'] );
}
if ( ! empty( $this->request['search'] ) ) {
$args['gla_search'] = $this->request['search'];
}
if ( empty( $this->request['orderby'] ) ) {
return $args;
}
switch ( $this->request['orderby'] ) {
case 'title':
$args['orderby']['title'] = $this->get_order();
break;
case 'id':
$args['orderby'] = [ 'ID' => $this->get_order() ] + $args['orderby'];
break;
case 'visible':
$args['meta_key'] = $this->prefix_meta_key( ProductMetaHandler::KEY_VISIBILITY );
$args['orderby'] = [ 'meta_value' => $this->get_order() ] + $args['orderby'];
break;
case 'status':
$args['meta_key'] = $this->prefix_meta_key( ProductMetaHandler::KEY_MC_STATUS );
$args['orderby'] = [ 'meta_value' => $this->get_order() ] + $args['orderby'];
break;
case 'total_sales':
$args['meta_key'] = self::META_KEY_TOTAL_SALES;
$args['orderby'] = [ 'meta_value_num' => $this->get_order() ] + $args['orderby'];
break;
default:
throw InvalidValue::not_in_allowed_list( 'orderby', [ 'title', 'id', 'visible', 'status', 'total_sales' ] );
}
return $args;
}
/**
* Convert the per_page and page parameters into limit and offset values.
*
* @return array Containing limit and offset values.
*/
protected function prepare_query_pagination(): array {
$limit = -1;
$offset = 0;
if ( ! empty( $this->request['per_page'] ) ) {
$limit = intval( $this->request['per_page'] );
$page = max( 1, intval( $this->request['page'] ) );
$offset = $limit * ( $page - 1 );
}
return [ $limit, $offset ];
}
/**
* Filter for the posts_where hook, adds WHERE clause to search
* for the 'search' parameter in the product titles (when present).
*
* @param string $where The WHERE clause of the query.
* @param WP_Query $wp_query The WP_Query instance (passed by reference).
*
* @return string The updated WHERE clause.
*/
public function title_filter( string $where, WP_Query $wp_query ): string {
$gla_search = $wp_query->get( 'gla_search' );
if ( $gla_search ) {
$title_search = '%' . $this->wpdb->esc_like( $gla_search ) . '%';
$where .= $this->wpdb->prepare( " AND `{$this->wpdb->posts}`.`post_title` LIKE %s", $title_search ); // phpcs:ignore WordPress.DB.PreparedSQL
}
return $where;
}
/**
* Return the ORDER BY order based on the order request parameter value.
*
* @return string
*/
protected function get_order(): string {
return strtoupper( $this->request['order'] ?? '' ) === 'DESC' ? 'DESC' : 'ASC';
}
}
ProductMetaQueryHelper.php 0000644 00000004764 15154211254 0011705 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidMeta;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductMetaHandler;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class ProductMetaQueryHelper.
*
* @since 1.1.0
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB
*/
class ProductMetaQueryHelper implements Service {
use PluginHelper;
protected const BATCH_SIZE = 500;
/**
* @var wpdb
*/
protected $wpdb;
/**
* ProductMetaQueryHelper constructor.
*
* @param wpdb $wpdb
*/
public function __construct( wpdb $wpdb ) {
$this->wpdb = $wpdb;
}
/**
* Get all values for a given meta_key as post_id=>meta_value.
*
* @param string $meta_key The meta value to retrieve for all posts.
* @return array Meta values by post ID.
*
* @throws InvalidMeta If the meta key isn't valid.
*/
public function get_all_values( string $meta_key ): array {
self::validate_meta_key( $meta_key );
$query = "SELECT post_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$results = $this->wpdb->get_results( $this->wpdb->prepare( $query, $this->prefix_meta_key( $meta_key ) ) );
$return = [];
foreach ( $results as $r ) {
$return[ $r->post_id ] = maybe_unserialize( $r->meta_value );
}
return $return;
}
/**
* Delete all values for a given meta_key.
*
* @since 2.6.4
*
* @param string $meta_key The meta key to delete.
*
* @throws InvalidMeta If the meta key isn't valid.
*/
public function delete_all_values( string $meta_key ) {
self::validate_meta_key( $meta_key );
$meta_key = $this->prefix_meta_key( $meta_key );
$query = "DELETE FROM {$this->wpdb->postmeta} WHERE meta_key = %s";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$this->wpdb->query( $this->wpdb->prepare( $query, $meta_key ) );
}
/**
* @param string $meta_key The meta key to validate
*
* @throws InvalidMeta If the meta key isn't valid.
*/
protected static function validate_meta_key( string $meta_key ) {
if ( ! ProductMetaHandler::is_meta_key_valid( $meta_key ) ) {
do_action(
'woocommerce_gla_error',
sprintf( 'Product meta key is invalid: %s', $meta_key ),
__METHOD__
);
throw InvalidMeta::invalid_key( $meta_key );
}
}
}
Query/AttributeMappingRulesQuery.php 0000644 00000002727 15154211254 0013712 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\AttributeMappingRulesTable;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class defining the queries for the Attribute Mapping Rules Table
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Query
*/
class AttributeMappingRulesQuery extends Query {
/**
* AttributeMappingRulesQuery constructor.
*
* @param wpdb $wpdb
* @param AttributeMappingRulesTable $table
*/
public function __construct( wpdb $wpdb, AttributeMappingRulesTable $table ) {
parent::__construct( $wpdb, $table );
}
/**
* Sanitize a value for a given column before inserting it into the DB.
*
* @param string $column The column name.
* @param mixed $value The value to sanitize.
*
* @return mixed The sanitized value.
*/
protected function sanitize_value( string $column, $value ) {
if ( $column === 'attribute' || $column === 'source' ) {
return sanitize_text_field( $value );
}
if ( $column === 'categories' && is_null( $value ) ) {
return '';
}
return $value;
}
/**
* Gets a specific rule from Database
*
* @param int $rule_id The rule ID to get from Database
* @return array The rule from database
*/
public function get_rule( int $rule_id ): array {
return $this->where( 'id', $rule_id )->get_row();
}
}
Query/BudgetRecommendationQuery.php 0000644 00000002355 15154211254 0013514 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\BudgetRecommendationTable;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidQuery;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class BudgetRecommendationQuery
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Query
*/
class BudgetRecommendationQuery extends Query {
/**
* BudgetRecommendationQuery constructor.
*
* @param wpdb $wpdb
* @param BudgetRecommendationTable $table
*/
public function __construct( wpdb $wpdb, BudgetRecommendationTable $table ) {
parent::__construct( $wpdb, $table );
}
/**
* Sanitize a value for a given column before inserting it into the DB.
*
* @param string $column The column name.
* @param mixed $value The value to sanitize.
*
* @return mixed The sanitized value.
* @throws InvalidQuery When the code tries to set the ID column.
*/
protected function sanitize_value( string $column, $value ) {
if ( 'id' === $column ) {
throw InvalidQuery::cant_set_id( BudgetRecommendationTable::class );
}
return $value;
}
}
Query/MerchantIssueQuery.php 0000644 00000001710 15154211254 0012161 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\MerchantIssueTable;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class MerchantIssueQuery
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Query
*/
class MerchantIssueQuery extends Query {
/**
* MerchantIssueQuery constructor.
*
* @param wpdb $wpdb
* @param MerchantIssueTable $table
*/
public function __construct( wpdb $wpdb, MerchantIssueTable $table ) {
parent::__construct( $wpdb, $table );
}
/**
* Sanitize a value for a given column before inserting it into the DB.
*
* @param string $column The column name.
* @param mixed $value The value to sanitize.
*
* @return mixed The sanitized value.
*/
protected function sanitize_value( string $column, $value ) {
return $value;
}
}
Query/ShippingRateQuery.php 0000644 00000003243 15154211254 0012007 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\ShippingRateTable;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidQuery;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingRateQuery
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Query
*/
class ShippingRateQuery extends Query {
/**
* ShippingRateQuery constructor.
*
* @param wpdb $wpdb
* @param ShippingRateTable $table
*/
public function __construct( wpdb $wpdb, ShippingRateTable $table ) {
parent::__construct( $wpdb, $table );
}
/**
* Sanitize a value for a given column before inserting it into the DB.
*
* @param string $column The column name.
* @param mixed $value The value to sanitize.
*
* @return mixed The sanitized value.
* @throws InvalidQuery When the code tries to set the ID column.
*/
protected function sanitize_value( string $column, $value ) {
if ( 'id' === $column ) {
throw InvalidQuery::cant_set_id( ShippingRateTable::class );
}
if ( 'options' === $column ) {
if ( ! is_array( $value ) ) {
throw InvalidQuery::invalid_value( $column );
}
$value = wp_json_encode( $value );
}
return $value;
}
/**
* Perform the query and save it to the results.
*/
protected function query_results() {
parent::query_results();
$this->results = array_map(
function ( $row ) {
$row['options'] = ! empty( $row['options'] ) ? json_decode( $row['options'], true ) : $row['options'];
return $row;
},
$this->results
);
}
}
Query/ShippingTimeQuery.php 0000644 00000003126 15154211254 0012012 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\ShippingTimeTable;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidQuery;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingTimeQuery
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Query
*/
class ShippingTimeQuery extends Query {
/**
* ShippingTimeQuery constructor.
*
* @param wpdb $wpdb
* @param ShippingTimeTable $table
*/
public function __construct( wpdb $wpdb, ShippingTimeTable $table ) {
parent::__construct( $wpdb, $table );
}
/**
* Sanitize a value for a given column before inserting it into the DB.
*
* @param string $column The column name.
* @param mixed $value The value to sanitize.
*
* @return mixed The sanitized value.
* @throws InvalidQuery When the code tries to set the ID column.
*/
protected function sanitize_value( string $column, $value ) {
if ( 'id' === $column ) {
throw InvalidQuery::cant_set_id( ShippingTimeTable::class );
}
return $value;
}
/**
* Get all shipping times.
*
* @since 2.8.0
*
* @return array
*/
public function get_all_shipping_times() {
$times = $this->get_results();
$items = [];
foreach ( $times as $time ) {
$data = [
'country_code' => $time['country'],
'time' => $time['time'],
'max_time' => $time['max_time'] ?: $time['time'],
];
$items[ $time['country'] ] = $data;
}
return $items;
}
}
Query.php 0000644 00000027112 15154211254 0006365 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidQuery;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\PositiveInteger;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class Query
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB
*/
abstract class Query implements QueryInterface {
/** @var int */
protected $limit;
/** @var int */
protected $offset = 0;
/** @var array */
protected $orderby = [];
/** @var array */
protected $groupby = [];
/**
* The result of the query.
*
* @var mixed
*/
protected $results = null;
/**
* The number of rows returned by the query.
*
* @var int
*/
protected $count = null;
/**
* The last inserted ID (updated after a call to insert).
*
* @var int
*/
protected $last_insert_id = null;
/** @var TableInterface */
protected $table;
/**
* Where clauses for the query.
*
* @var array
*/
protected $where = [];
/**
* Where relation for multiple clauses.
*
* @var string
*/
protected $where_relation;
/** @var wpdb */
protected $wpdb;
/**
* Query constructor.
*
* @param wpdb $wpdb
* @param TableInterface $table
*/
public function __construct( wpdb $wpdb, TableInterface $table ) {
$this->wpdb = $wpdb;
$this->table = $table;
}
/**
* Add a where clause to the query.
*
* @param string $column The column name.
* @param mixed $value The where value.
* @param string $compare The comparison to use. Valid values are =, <, >, IN, NOT IN.
*
* @return $this
*/
public function where( string $column, $value, string $compare = '=' ): QueryInterface {
$this->validate_column( $column );
$this->validate_compare( $compare );
$this->where[] = [
'column' => $column,
'value' => $value,
'compare' => $compare,
];
return $this;
}
/**
* Add a group by clause to the query.
*
* @param string $column The column name.
*
* @return $this
*
* @since 1.12.0
*/
public function group_by( string $column ): QueryInterface {
$this->validate_column( $column );
$this->groupby[] = "`{$column}`";
return $this;
}
/**
* Set the where relation for the query.
*
* @param string $relation
*
* @return QueryInterface
*/
public function set_where_relation( string $relation ): QueryInterface {
$this->validate_where_relation( $relation );
$this->where_relation = $relation;
return $this;
}
/**
* Set ordering information for the query.
*
* @param string $column
* @param string $order
*
* @return QueryInterface
*/
public function set_order( string $column, string $order = 'ASC' ): QueryInterface {
$this->validate_column( $column );
$this->orderby[] = "`{$column}` {$this->normalize_order( $order )}";
return $this;
}
/**
* Limit the number of results for the query.
*
* @param int $limit
*
* @return QueryInterface
*/
public function set_limit( int $limit ): QueryInterface {
$this->limit = ( new PositiveInteger( $limit ) )->get();
return $this;
}
/**
* Set an offset for the results.
*
* @param int $offset
*
* @return QueryInterface
*/
public function set_offset( int $offset ): QueryInterface {
$this->offset = ( new PositiveInteger( $offset ) )->get();
return $this;
}
/**
* Get the results of the query.
*
* @return mixed
*/
public function get_results() {
if ( null === $this->results ) {
$this->query_results();
}
return $this->results;
}
/**
* Get the number of results returned by the query.
*
* @return int
*/
public function get_count(): int {
if ( null === $this->count ) {
$this->count_results();
}
return $this->count;
}
/**
* Gets the first result of the query.
*
* @return array
*/
public function get_row(): array {
if ( null === $this->results ) {
$old_limit = $this->limit ?? 0;
$this->set_limit( 1 );
$this->query_results();
$this->set_limit( $old_limit );
}
return $this->results[0] ?? [];
}
/**
* Perform the query and save it to the results.
*/
protected function query_results() {
$this->results = $this->wpdb->get_results(
$this->build_query(), // phpcs:ignore WordPress.DB.PreparedSQL
ARRAY_A
);
}
/**
* Count the results and save the result.
*/
protected function count_results() {
$this->count = (int) $this->wpdb->get_var( $this->build_query( true ) ); // phpcs:ignore WordPress.DB.PreparedSQL
}
/**
* Validate that a given column is valid for the current table.
*
* @param string $column
*
* @throws InvalidQuery When the given column is not valid for the current table.
*/
protected function validate_column( string $column ) {
if ( ! array_key_exists( $column, $this->table->get_columns() ) ) {
throw InvalidQuery::from_column( $column, get_class( $this->table ) );
}
}
/**
* Validate that a compare operator is valid.
*
* @param string $compare
*
* @throws InvalidQuery When the compare value is not valid.
*/
protected function validate_compare( string $compare ) {
switch ( $compare ) {
case '=':
case '>':
case '<':
case 'IN':
case 'NOT IN':
// These are all valid.
return;
default:
throw InvalidQuery::from_compare( $compare );
}
}
/**
* Validate that a where relation is valid.
*
* @param string $relation
*
* @throws InvalidQuery When the relation value is not valid.
*/
protected function validate_where_relation( string $relation ) {
switch ( $relation ) {
case 'AND':
case 'OR':
// These are all valid.
return;
default:
throw InvalidQuery::where_relation( $relation );
}
}
/**
* Normalize the string for the order.
*
* Converts the string to uppercase, and will return only DESC or ASC.
*
* @param string $order
*
* @return string
*/
protected function normalize_order( string $order ): string {
$order = strtoupper( $order );
return 'DESC' === $order ? $order : 'ASC';
}
/**
* Build the query and return the query string.
*
* @param bool $get_count False to build a normal query, true to build a COUNT(*) query.
*
* @return string
*/
protected function build_query( bool $get_count = false ): string {
$columns = $get_count ? 'COUNT(*)' : '*';
$pieces = [ "SELECT {$columns} FROM `{$this->table->get_name()}`" ];
$pieces = array_merge( $pieces, $this->generate_where_pieces() );
if ( ! empty( $this->groupby ) ) {
$pieces[] = 'GROUP BY ' . implode( ', ', $this->groupby );
}
if ( ! $get_count ) {
if ( $this->orderby ) {
$pieces[] = 'ORDER BY ' . implode( ', ', $this->orderby );
}
if ( $this->limit ) {
$pieces[] = "LIMIT {$this->limit}";
}
if ( $this->offset ) {
$pieces[] = "OFFSET {$this->offset}";
}
}
return join( "\n", $pieces );
}
/**
* Generate the pieces for the WHERE part of the query.
*
* @return string[]
*/
protected function generate_where_pieces(): array {
if ( empty( $this->where ) ) {
return [];
}
$where_pieces = [ 'WHERE' ];
foreach ( $this->where as $where ) {
$column = $where['column'];
$compare = $where['compare'];
if ( $compare === 'IN' || $compare === 'NOT IN' ) {
$value = sprintf(
"('%s')",
join(
"','",
array_map(
function ( $value ) {
return $this->wpdb->_escape( $value );
},
$where['value']
)
)
);
} else {
$value = "'{$this->wpdb->_escape( $where['value'] )}'";
}
if ( count( $where_pieces ) > 1 ) {
$where_pieces[] = $this->where_relation ?? 'AND';
}
$where_pieces[] = "{$column} {$compare} {$value}";
}
return $where_pieces;
}
/**
* Insert a row of data into the table.
*
* @param array $data
*
* @return int
* @throws InvalidQuery When there is an error inserting the data.
*/
public function insert( array $data ): int {
foreach ( $data as $column => &$value ) {
$this->validate_column( $column );
$value = $this->sanitize_value( $column, $value );
}
$result = $this->wpdb->insert( $this->table->get_name(), $data );
if ( false === $result ) {
throw InvalidQuery::from_insert( $this->wpdb->last_error ?: 'Error inserting data.' );
}
// Save a local copy of the last inserted ID.
$this->last_insert_id = $this->wpdb->insert_id;
return $result;
}
/**
* Returns the last inserted ID. Must be called after insert.
*
* @since 1.12.0
*
* @return int|null
*/
public function last_insert_id(): ?int {
return $this->last_insert_id;
}
/**
* Delete rows from the database.
*
* @param string $where_column Column to use when looking for values to delete.
* @param mixed $value Value to use when determining what rows to delete.
*
* @return int The number of rows deleted.
* @throws InvalidQuery When there is an error deleting data.
*/
public function delete( string $where_column, $value ): int {
$this->validate_column( $where_column );
$result = $this->wpdb->delete( $this->table->get_name(), [ $where_column => $value ] );
if ( false === $result ) {
throw InvalidQuery::from_delete( $this->wpdb->last_error ?: 'Error deleting data.' );
}
return $result;
}
/**
* Update data in the database.
*
* @param array $data Array of columns and their values.
* @param array $where Array of where conditions for updating values.
*
* @return int
* @throws InvalidQuery When there is an error updating data, or when an empty where array is provided.
*/
public function update( array $data, array $where ): int {
if ( empty( $where ) ) {
throw InvalidQuery::empty_where();
}
foreach ( $data as $column => &$value ) {
$this->validate_column( $column );
$value = $this->sanitize_value( $column, $value );
}
$result = $this->wpdb->update(
$this->table->get_name(),
$data,
$where
);
if ( false === $result ) {
throw InvalidQuery::from_update( $this->wpdb->last_error ?: 'Error updating data.' );
}
return $result;
}
/**
* Batch update or insert a set of records.
*
* @param array $records Array of records to be updated or inserted.
*
* @throws InvalidQuery If an invalid column name is provided.
*/
public function update_or_insert( array $records ): void {
if ( empty( $records ) ) {
return;
}
$update_values = [];
$columns = array_keys( reset( $records ) );
foreach ( $columns as $c ) {
$this->validate_column( $c );
$update_values[] = "`$c`=VALUES(`$c`)";
}
$single_placeholder = '(' . implode( ',', array_fill( 0, count( $columns ), "'%s'" ) ) . ')';
$chunk_size = 200;
$num_issues = count( $records );
for ( $i = 0; $i < $num_issues; $i += $chunk_size ) {
$all_values = [];
$all_placeholders = [];
foreach ( array_slice( $records, $i, $chunk_size ) as $issue ) {
if ( array_keys( $issue ) !== $columns ) {
throw new InvalidQuery( 'Not all records contain the same columns' );
}
$all_placeholders[] = $single_placeholder;
array_push( $all_values, ...array_values( $issue ) );
}
$column_names = '(`' . implode( '`, `', $columns ) . '`)';
$query = "INSERT INTO `{$this->table->get_name()}` $column_names VALUES ";
$query .= implode( ', ', $all_placeholders );
$query .= ' ON DUPLICATE KEY UPDATE ' . implode( ', ', $update_values );
$this->wpdb->query( $this->wpdb->prepare( $query, $all_values ) ); // phpcs:ignore WordPress.DB.PreparedSQL
}
}
/**
* Sanitize a value for a given column before inserting it into the DB.
*
* @param string $column The column name.
* @param mixed $value The value to sanitize.
*
* @return mixed The sanitized value.
*/
abstract protected function sanitize_value( string $column, $value );
}
QueryInterface.php 0000644 00000005776 15154211254 0010222 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidQuery;
defined( 'ABSPATH' ) || exit;
/**
* Interface QueryInterface
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB
*/
interface QueryInterface {
/**
* Set a where clause to query.
*
* @param string $column The column name.
* @param mixed $value The where value.
* @param string $compare The comparison to use. Valid values are =, <, >, IN, NOT IN.
*
* @return $this
*/
public function where( string $column, $value, string $compare = '=' ): QueryInterface;
/**
* Set the where relation for the query.
*
* @param string $relation
*
* @return QueryInterface
*/
public function set_where_relation( string $relation ): QueryInterface;
/**
* @param string $column
* @param string $order
*
* @return QueryInterface
*/
public function set_order( string $column, string $order = 'DESC' ): QueryInterface;
/**
* Limit the number of results for the query.
*
* @param int $limit
*
* @return QueryInterface
*/
public function set_limit( int $limit ): QueryInterface;
/**
* Set an offset for the results.
*
* @param int $offset
*
* @return QueryInterface
*/
public function set_offset( int $offset ): QueryInterface;
/**
* Get the results of the query.
*
* @return mixed
*/
public function get_results();
/**
* Get the number of results returned by the query.
*
* @return int
*/
public function get_count(): int;
/**
* Gets the first result of the query.
*
* @return array
*/
public function get_row(): array;
/**
* Insert a row of data into the table.
*
* @param array $data
*
* @return int
* @throws InvalidQuery When there is an error inserting the data.
*/
public function insert( array $data ): int;
/**
* Returns the last inserted ID. Must be called after insert.
*
* @since 1.12.0
*
* @return int|null
*/
public function last_insert_id(): ?int;
/**
* Delete rows from the database.
*
* @param string $where_column Column to use when looking for values to delete.
* @param mixed $value Value to use when determining what rows to delete.
*
* @return int The number of rows deleted.
* @throws InvalidQuery When there is an error deleting data.
*/
public function delete( string $where_column, $value ): int;
/**
* Update data in the database.
*
* @param array $data Array of columns and their values.
* @param array $where Array of where conditions for updating values.
*
* @return int
* @throws InvalidQuery When there is an error updating data, or when an empty where array is provided.
*/
public function update( array $data, array $where ): int;
/**
* Batch update or insert a set of records.
*
* @param array $records Array of records to be updated or inserted.
*
* @throws InvalidQuery If an invalid column name is provided.
*/
public function update_or_insert( array $records ): void;
}
Table/AttributeMappingRulesTable.php 0000644 00000002516 15154211254 0013552 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
defined( 'ABSPATH' ) || exit;
/**
* Definition class for the Attribute Mapping Rules Table
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Tables
*/
class AttributeMappingRulesTable extends Table {
/**
* Get the schema for the DB.
*
* This should be a SQL string for creating the DB table.
*
* @return string
*/
protected function get_install_query(): string {
return "
CREATE TABLE `{$this->get_sql_safe_name()}` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`attribute` varchar(255) NOT NULL,
`source` varchar(100) NOT NULL,
`category_condition_type` varchar(10) NOT NULL,
`categories` text NOT NULL,
PRIMARY KEY `id` (`id`)
) {$this->get_collation()};
";
}
/**
* Get the un-prefixed (raw) table name.
*
* @return string
*/
public static function get_raw_name(): string {
return 'attribute_mapping_rules';
}
/**
* Get the columns for the table.
*
* @return array
*/
public function get_columns(): array {
return [
'id' => true,
'attribute' => true,
'source' => true,
'category_condition_type' => true,
'categories' => true,
];
}
}
Table/BudgetRecommendationTable.php 0000644 00000007061 15154211254 0013357 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
defined( 'ABSPATH' ) || exit;
/**
* Class BudgetRecommendationTable
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Tables
*/
class BudgetRecommendationTable extends Table {
/**
* Whether the initial data has been loaded
*
* @var bool
*/
public $has_loaded_initial_data = false;
/**
* Get the schema for the DB.
*
* This should be a SQL string for creating the DB table.
*
* @return string
*/
protected function get_install_query(): string {
return "
CREATE TABLE `{$this->get_sql_safe_name()}` (
id bigint(20) NOT NULL AUTO_INCREMENT,
currency varchar(3) NOT NULL,
country varchar(2) NOT NULL,
daily_budget int(11) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY country_currency (country, currency)
) {$this->get_collation()};
";
}
/**
* Install the Database table.
*
* Add data if there is none.
*/
public function install(): void {
parent::install();
// Load the data if the table is empty.
// phpcs:ignore WordPress.DB.PreparedSQL
$result = $this->wpdb->get_row( "SELECT COUNT(*) AS count FROM `{$this->get_sql_safe_name()}`" );
if ( empty( $result->count ) ) {
$this->load_initial_data();
}
}
/**
* Reload initial data.
*
* @return void
*/
public function reload_data(): void {
if ( $this->exists() && ! $this->has_loaded_initial_data ) {
$this->truncate();
$this->load_initial_data();
}
}
/**
* Get the un-prefixed (raw) table name.
*
* @return string
*/
public static function get_raw_name(): string {
return 'budget_recommendations';
}
/**
* Get the columns for the table.
*
* @return array
*/
public function get_columns(): array {
return [
'id' => true,
'currency' => true,
'country' => true,
'daily_budget' => true,
];
}
/**
* Load packaged recommendation data on the first install of GLA.
*
* Inserts 500 records at a time.
*/
private function load_initial_data(): void {
$path = $this->get_root_dir() . '/data/budget-recommendations.csv';
$chunk_size = 500;
if ( file_exists( $path ) ) {
$csv = array_map(
function ( $row ) {
return str_getcsv( $row, ',', '"', '\\' );
},
file( $path )
);
// Remove the headers
array_shift( $csv );
if ( empty( $csv ) ) {
return;
}
$values = [];
$placeholders = [];
// Build placeholders for each row, and add values to data array
foreach ( $csv as $row ) {
if ( empty( $row ) ) {
continue;
}
$row_placeholders = [];
foreach ( $row as $value ) {
$values[] = $value;
$row_placeholders[] = is_numeric( $value ) ? '%d' : '%s';
}
$placeholders[] = '(' . implode( ', ', $row_placeholders ) . ')';
if ( count( $placeholders ) >= $chunk_size ) {
$this->insert_chunk( $placeholders, $values );
$placeholders = [];
$values = [];
}
}
$this->insert_chunk( $placeholders, $values );
}
$this->has_loaded_initial_data = true;
}
/**
* Insert a chunk of budget recommendations
*
* @param string[] $placeholders
* @param array $values
*/
private function insert_chunk( array $placeholders, array $values ): void {
$sql = "INSERT INTO `{$this->get_sql_safe_name()}` (country,daily_budget,currency) VALUES\n";
$sql .= implode( ",\n", $placeholders );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$this->wpdb->query( $this->wpdb->prepare( $sql, $values ) );
}
}
Table/MerchantIssueTable.php 0000644 00000005620 15154211254 0012031 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
use DateTime;
defined( 'ABSPATH' ) || exit;
/**
* Class MerchantIssueTable
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Tables
*/
class MerchantIssueTable extends Table {
/**
* Get the schema for the DB.
*
* This should be a SQL string for creating the DB table.
*
* @return string
*/
protected function get_install_query(): string {
return "
CREATE TABLE `{$this->get_sql_safe_name()}` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`product_id` bigint(20) NOT NULL,
`issue` varchar(200) NOT NULL,
`code` varchar(100) NOT NULL,
`severity` varchar(20) NOT NULL DEFAULT 'warning',
`product` varchar(100) NOT NULL,
`action` text NOT NULL,
`action_url` varchar(1024) NOT NULL,
`applicable_countries` text NOT NULL,
`source` varchar(10) NOT NULL DEFAULT 'mc',
`type` varchar(10) NOT NULL DEFAULT 'product',
`created_at` datetime NOT NULL,
PRIMARY KEY `id` (`id`)
) {$this->get_collation()};
";
}
/**
* Get the un-prefixed (raw) table name.
*
* @return string
*/
public static function get_raw_name(): string {
return 'merchant_issues';
}
/**
* Delete stale issue records.
*
* @param DateTime $created_before Delete all records created before this.
*/
public function delete_stale( DateTime $created_before ): void {
$query = "DELETE FROM `{$this->get_sql_safe_name()}` WHERE `created_at` < '%s'";
$this->wpdb->query( $this->wpdb->prepare( $query, $created_before->format( 'Y-m-d H:i:s' ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL
}
/**
* Delete product issues for specific products and source.
*
* @param array $products_ids Array of product IDs to delete issues for.
* @param string $source The source of the issues. Default is 'mc'.
*/
public function delete_specific_product_issues( array $products_ids, string $source = 'mc' ): void {
if ( empty( $products_ids ) ) {
return;
}
$placeholder = '(' . implode( ',', array_fill( 0, count( $products_ids ), '%d' ) ) . ')';
$this->wpdb->query( $this->wpdb->prepare( "DELETE FROM `{$this->get_sql_safe_name()}` WHERE `product_id` IN {$placeholder} AND `source` = %s", array_merge( $products_ids, [ $source ] ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL
}
/**
* Get the columns for the table.
*
* @return array
*/
public function get_columns(): array {
return [
'id' => true,
'product_id' => true,
'code' => true,
'severity' => true,
'issue' => true,
'product' => true,
'action' => true,
'action_url' => true,
'applicable_countries' => true,
'source' => true,
'type' => true,
'created_at' => true,
];
}
}
Table/ShippingRateTable.php 0000644 00000002347 15154211254 0011657 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingRateTable
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Tables
*/
class ShippingRateTable extends Table {
/**
* Get the schema for the DB.
*
* This should be a SQL string for creating the DB table.
*
* @return string
*/
protected function get_install_query(): string {
return "
CREATE TABLE `{$this->get_sql_safe_name()}` (
id bigint(20) NOT NULL AUTO_INCREMENT,
country varchar(2) NOT NULL,
currency varchar(3) NOT NULL,
rate double NOT NULL default 0,
options text DEFAULT NULL,
PRIMARY KEY (id),
KEY country (country),
KEY currency (currency)
) {$this->get_collation()};
";
}
/**
* Get the un-prefixed (raw) table name.
*
* @return string
*/
public static function get_raw_name(): string {
return 'shipping_rates';
}
/**
* Get the columns for the table.
*
* @return array
*/
public function get_columns(): array {
return [
'id' => true,
'country' => true,
'currency' => true,
'rate' => true,
'options' => true,
];
}
}
Table/ShippingTimeTable.php 0000644 00000002236 15154211254 0011657 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingTimeTable
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB\Table
*/
class ShippingTimeTable extends Table {
/**
* Get the schema for the DB.
*
* This should be a SQL string for creating the DB table.
*
* @return string
*/
protected function get_install_query(): string {
return "
CREATE TABLE `{$this->get_sql_safe_name()}` (
id bigint(20) NOT NULL AUTO_INCREMENT,
country varchar(2) NOT NULL,
time bigint(20) NOT NULL default 0,
max_time bigint(20) NOT NULL default 0,
PRIMARY KEY (id),
KEY country (country)
) {$this->get_collation()};
";
}
/**
* Get the un-prefixed (raw) table name.
*
* @return string
*/
public static function get_raw_name(): string {
return 'shipping_times';
}
/**
* Get the columns for the table.
*
* @return array
*/
public function get_columns(): array {
return [
'id' => true,
'country' => true,
'time' => true,
'max_time' => true,
];
}
}
Table.php 0000644 00000007744 15154211254 0006320 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WP;
use wpdb;
defined( 'ABSPATH' ) || exit;
/**
* Class Table
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB
*
* @see TableManager::VALID_TABLES contains a list of table classes that will be installed.
* @see \Automattic\WooCommerce\GoogleListingsAndAds\DB\Installer::install for installing tables.
*/
abstract class Table implements TableInterface {
use PluginHelper;
/** @var WP */
protected $wp;
/** @var wpdb */
protected $wpdb;
/**
* Table constructor.
*
* @param WP $wp The WP proxy object.
* @param wpdb $wpdb The wpdb object.
*/
public function __construct( WP $wp, wpdb $wpdb ) {
$this->wp = $wp;
$this->wpdb = $wpdb;
}
/**
* Install the Database table.
*/
public function install(): void {
$this->wp->db_delta( $this->get_install_query() );
}
/**
* Determine whether the table actually exists in the DB.
*
* @return bool
*/
public function exists(): bool {
$result = $this->wpdb->get_var(
"SHOW TABLES LIKE '{$this->wpdb->esc_like( $this->get_name() )}'" // phpcs:ignore WordPress.DB.PreparedSQL
);
return $result === $this->get_name();
}
/**
* Delete the Database table.
*/
public function delete(): void {
$this->wpdb->query( "DROP TABLE IF EXISTS `{$this->get_sql_safe_name()}`" ); // phpcs:ignore WordPress.DB.PreparedSQL
}
/**
* Truncate the Database table.
*/
public function truncate(): void {
$this->wpdb->query( "TRUNCATE TABLE `{$this->get_sql_safe_name()}`" ); // phpcs:ignore WordPress.DB.PreparedSQL
}
/**
* Get the SQL escaped version of the table name.
*
* @return string
*/
protected function get_sql_safe_name(): string {
return $this->wpdb->_escape( $this->get_name() );
}
/**
* Get the name of the Database table.
*
* The name is prefixed with the wpdb prefix, and our plugin prefix.
*
* @return string
*/
public function get_name(): string {
return "{$this->wpdb->prefix}{$this->get_slug()}_{$this->get_raw_name()}";
}
/**
* Get the primary column name for the table.
*
* @return string
*/
public function get_primary_column(): string {
return 'id';
}
/**
* Checks whether an index exists for the table.
*
* @param string $index_name The index name.
*
* @return bool True if the index exists on the table and False if not.
*
* @since 1.4.1
*/
public function has_index( string $index_name ): bool {
$result = $this->wpdb->get_results(
$this->wpdb->prepare( "SHOW INDEX FROM `{$this->get_sql_safe_name()}` WHERE Key_name = %s ", [ $index_name ] ) // phpcs:ignore WordPress.DB.PreparedSQL
);
return ! empty( $result );
}
/**
* Get the DB collation.
*
* @return string
*/
protected function get_collation(): string {
return $this->wpdb->has_cap( 'collation' ) ? $this->wpdb->get_charset_collate() : '';
}
/**
* Checks whether a column exists for the table.
*
* @param string $column_name The column name.
*
* @return bool True if the column exists on the table or False if not.
*
* @since 2.5.13
*/
public function has_column( string $column_name ): bool {
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->wpdb->get_results(
$this->wpdb->prepare( "SHOW COLUMNS FROM `{$this->get_sql_safe_name()}` WHERE Field = %s", [ $column_name ] )
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
return (bool) $this->wpdb->num_rows;
}
/**
* Get the schema for the DB.
*
* This should be a SQL string for creating the DB table.
*
* @return string
*/
abstract protected function get_install_query(): string;
/**
* Get the un-prefixed (raw) table name.
*
* @return string
*/
abstract public static function get_raw_name(): string;
}
TableInterface.php 0000644 00000002717 15154211254 0010134 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB;
defined( 'ABSPATH' ) || exit;
/**
* Interface TableInterface
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB
*/
interface TableInterface {
/**
* Install the Database table.
*/
public function install(): void;
/**
* Determine whether the table actually exists in the DB.
*
* @return bool
*/
public function exists(): bool;
/**
* Delete the Database table.
*/
public function delete(): void;
/**
* Truncate the Database table.
*/
public function truncate(): void;
/**
* Get the name of the Database table.
*
* @return string
*/
public function get_name(): string;
/**
* Get the columns for the table.
*
* @return array
*/
public function get_columns(): array;
/**
* Get the primary column name for the table.
*
* @return string
*/
public function get_primary_column(): string;
/**
* Checks whether an index exists for the table.
*
* @param string $index_name The index name.
*
* @return bool True if the index exists on the table and False if not.
*
* @since 1.4.1
*/
public function has_index( string $index_name ): bool;
/**
* Checks whether a column exists for the table.
*
* @param string $column_name The column name.
*
* @return bool True if the column exists on the table or False if not.
*
* @since 2.5.13
*/
public function has_column( string $column_name ): bool;
}
TableManager.php 0000644 00000004342 15154211254 0007602 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\DB;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\AttributeMappingRulesTable;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\BudgetRecommendationTable;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\MerchantIssueTable;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\ShippingRateTable;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Table\ShippingTimeTable;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ValidateInterface;
defined( 'ABSPATH' ) || exit;
/**
* Class TableManager
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\DB
*
* @since 1.3.0
*/
class TableManager {
use ValidateInterface;
protected const VALID_TABLES = [
AttributeMappingRulesTable::class => true,
BudgetRecommendationTable::class => true,
MerchantIssueTable::class => true,
ShippingRateTable::class => true,
ShippingTimeTable::class => true,
];
/**
* @var Table[]
*/
protected $tables;
/**
* TableManager constructor.
*
* @param Table[] $tables
*/
public function __construct( array $tables ) {
$this->setup_tables( $tables );
}
/**
* @return Table[]
*
* @see \Automattic\WooCommerce\GoogleListingsAndAds\DB\Installer::install for installing these tables.
*/
public function get_tables(): array {
return $this->tables;
}
/**
* Returns a list of table names to be installed.
*
* @return string[] Table names
*
* @see TableManager::VALID_TABLES for the list of valid table classes.
*/
public static function get_all_table_names(): array {
$tables = [];
foreach ( array_keys( self::VALID_TABLES ) as $table_class ) {
$table_name = call_user_func( [ $table_class, 'get_raw_name' ] );
$tables[ $table_name ] = $table_name;
}
return $tables;
}
/**
* Set up each of the table controllers.
*
* @param Table[] $tables
*/
protected function setup_tables( array $tables ) {
foreach ( $tables as $table ) {
$this->validate_instanceof( $table, Table::class );
// only include tables from the installable tables list.
if ( isset( self::VALID_TABLES[ get_class( $table ) ] ) ) {
$this->tables[] = $table;
}
}
}
}