877 lines
32 KiB
PHP
877 lines
32 KiB
PHP
<?php
|
|
|
|
/** @noinspection PhpUndefinedMethodInspection */
|
|
|
|
/**
|
|
* WP Product Feed Master Class.
|
|
*
|
|
* @package WP Product Feed Manager/Application/Classes
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
if ( ! class_exists( 'WPPFM_Feed_Master_Class' ) ) :
|
|
|
|
/**
|
|
* Feed Master Class.
|
|
*/
|
|
class WPPFM_Feed_Master_Class {
|
|
|
|
use WPPFM_Processing_Support;
|
|
|
|
/**
|
|
* Contains the general feed data.
|
|
*
|
|
* @var object
|
|
*/
|
|
protected $_feed = null;
|
|
|
|
/**
|
|
* Instantiation of global background process class.
|
|
*
|
|
* @var stdClass
|
|
*/
|
|
protected $_background_process;
|
|
|
|
/**
|
|
* Placeholder for the correct channel class.
|
|
*
|
|
* @var stdClass
|
|
*/
|
|
protected $_channel_class;
|
|
|
|
/**
|
|
* Placeholder for the WPPFM_Data_Class.
|
|
*
|
|
* @var stdClass
|
|
*/
|
|
protected $_data_class;
|
|
|
|
/**
|
|
* Path and name of the feed file.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_feed_file_path;
|
|
|
|
/**
|
|
* Constructor of the feed master class. Instantiates the correct background_process for the selected feed and data class.
|
|
*
|
|
* @param string $feed_id The id of the feed. Default 0.
|
|
*
|
|
* @global stdClass $wppfm_background_process
|
|
*/
|
|
public function __construct( $feed_id = '0' ) {
|
|
// Get the correct feed type class. Possible outcomes are WPPFM_Feed_Processor, WPPPFM_Promotions_Feed_Processor or WPPRFM_Review_Feed_Processor.
|
|
$background_process_class = $this->get_background_process_class_name( $feed_id );
|
|
|
|
if ( class_exists( $background_process_class ) ) {
|
|
$this->_background_process = new $background_process_class();
|
|
} else {
|
|
$this->_background_process = new WPPFM_Feed_Processor();
|
|
}
|
|
|
|
$this->_data_class = new WPPFM_Data();
|
|
}
|
|
|
|
/**
|
|
* Starts the update process.
|
|
*
|
|
* @param bool $silent Sets whether process messages should be shown or not. Default true.
|
|
*
|
|
* @return false|void or false
|
|
*/
|
|
public function update_feed_file( $silent = true ) {
|
|
|
|
$feed_id = WPPFM_Feed_Controller::get_next_id_from_feed_queue();
|
|
|
|
do_action( 'wppfm_feed_process_prepared', $feed_id, $silent );
|
|
|
|
if ( false === $feed_id ) {
|
|
return false;
|
|
}
|
|
|
|
$startup_lock_acquired = WPPFM_Feed_Controller::acquire_feed_startup_lock( $feed_id );
|
|
|
|
if ( ! $startup_lock_acquired ) {
|
|
do_action(
|
|
'wppfm_feed_generation_message',
|
|
$feed_id,
|
|
'Skipped duplicate feed start while another request is already preparing this feed.',
|
|
'WARNING'
|
|
);
|
|
|
|
if ( ! $silent ) {
|
|
$queued_products = max( 0, intval( WPPFM_Feed_Controller::nr_ids_remaining_in_product_queue() ) );
|
|
echo 'started_processing-' . esc_html( $queued_products );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
if ( $silent ) {
|
|
set_transient( 'wppfm_running_silent', true, WPPFM_TRANSIENT_LIVE );
|
|
}
|
|
|
|
$feed_data = $this->_data_class->get_feed_data( $feed_id );
|
|
|
|
if ( ! $feed_data ) {
|
|
do_action( 'wppfm_feed_generation_message', $feed_id, 'The update_feed_file function failed to get the feed data', 'ERROR' );
|
|
if ( ! $silent ) {
|
|
esc_attr_e( '1428 - Failed to load the feed data, please try to generate a feed in the foreground mode (see Settings page) and then try the background mode again.', 'wp-product-feed-manager' );
|
|
}
|
|
|
|
echo 'activation_error';
|
|
|
|
return false;
|
|
}
|
|
|
|
// Store the feed data in a property.
|
|
$this->_feed = $feed_data;
|
|
|
|
// Guard the startup path with the existing processing flag so a second request
|
|
// cannot prepare a new batch for the same feed while a worker hand-off is active.
|
|
if ( WPPFM_Feed_Controller::feed_is_processing() ) {
|
|
do_action(
|
|
'wppfm_feed_generation_message',
|
|
$this->_feed->feedId,
|
|
'Skipped feed start because a feed generation process is already active.',
|
|
'WARNING'
|
|
);
|
|
|
|
if ( ! $silent ) {
|
|
$queued_products = max( 0, intval( WPPFM_Feed_Controller::nr_ids_remaining_in_product_queue() ) );
|
|
echo 'started_processing-' . esc_html( $queued_products );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Only one feed can be processing, so if other feeds than the current feed are on a processing
|
|
// status, set these to an error status.
|
|
$this->_data_class->check_for_failed_feeds( $this->_feed->feedId );
|
|
|
|
// Hook for performance monitoring - feed preparation starting
|
|
do_action( 'wppfm_feed_generation_preparing', $this->_feed->feedId );
|
|
|
|
$prepare_update = $this->prepare_feed_file_update();
|
|
|
|
if ( true !== $prepare_update ) {
|
|
if ( ! $silent ) {
|
|
echo esc_attr( $prepare_update );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
WPPFM_Feed_Controller::set_feed_processing_flag( true );
|
|
|
|
$this->prepare_background_process();
|
|
|
|
$this->fill_the_background_queue();
|
|
|
|
// Hook for performance monitoring - feed processing starting
|
|
do_action( 'wppfm_feed_generation_ready_to_start', $this->_feed->feedId );
|
|
|
|
$this->activate_feed_file_update( $this->_feed->feedId );
|
|
|
|
// Note: Do NOT delete wppfm_running_silent here. The transient must persist until the
|
|
// feed actually completes (success or failure), so that failure detection can send the
|
|
// notice email when running in silent/automatic mode. The transient is cleared in the
|
|
// background process complete() method when the batch finishes.
|
|
|
|
$nr_of_products_in_feed = $this->_background_process->nr_of_products_in_queue();
|
|
|
|
// Store the queued total so the UI can render progress as soon as processing starts.
|
|
set_transient( 'wppfm_nr_of_products_to_process_' . $this->_feed->feedId, intval( $nr_of_products_in_feed ), HOUR_IN_SECONDS );
|
|
|
|
if ( ! $silent ) {
|
|
echo 'started_processing-' . esc_html( $nr_of_products_in_feed );
|
|
}
|
|
} finally {
|
|
WPPFM_Feed_Controller::release_feed_startup_lock( $feed_id );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets triggered by the wppfm-ajax-get-feed-status http call from the JavaScript side. Checks if the feed is still processed correctly. If not, it will change the feed status to fail.
|
|
*
|
|
* Watchdog failure email for automatic runs is deferred (see wppfm_schedule_deferred_feed_failure_notice)
|
|
* so notices match a stable final state after generation has had time to finish.
|
|
*
|
|
* @param string $feed_id The id of the feed to be checked.
|
|
*
|
|
* @return array with feed status data.
|
|
*/
|
|
public function feed_status_check( $feed_id ) {
|
|
$queries_class = new WPPFM_Queries();
|
|
|
|
$current_feed_status = $queries_class->get_feed_status_data( $feed_id );
|
|
|
|
// Missing row (invalid id or race with deletion): skip logic that assumes feed columns exist.
|
|
if ( ! is_array( $current_feed_status ) ) {
|
|
return array();
|
|
}
|
|
|
|
$feed_type_labels = wppfm_list_feed_type_text();
|
|
$raw_type_id = array_key_exists( 'feed_type_id', $current_feed_status ) ? $current_feed_status['feed_type_id'] : null;
|
|
// Schema default is 1, but empty string or null can still appear from legacy or partial data.
|
|
$type_key = ( null === $raw_type_id || '' === $raw_type_id ) ? '1' : (string) $raw_type_id;
|
|
|
|
if ( array_key_exists( $type_key, $feed_type_labels ) ) {
|
|
$current_feed_status['feed_type_name'] = $feed_type_labels[ $type_key ];
|
|
} else {
|
|
$current_feed_status['feed_type_name'] = __( 'Unknown feed type', 'wp-product-feed-manager' );
|
|
}
|
|
$current_feed_status['feed_type'] = $current_feed_status['feed_type_name'];
|
|
|
|
if ( array_key_exists( 'status_id', $current_feed_status ) && '3' === $current_feed_status['status_id'] ) { // Status still processing.
|
|
// Get file name, including a path.
|
|
$file_extension = function_exists( 'wppfm_get_file_type' ) ? wppfm_get_file_type( $current_feed_status['channel_id'] ) : 'xml';
|
|
$feed_file = wppfm_get_file_path( $current_feed_status['title'] . '.' . $file_extension );
|
|
$current_feed_status['products_in_queue'] = get_transient( 'wppfm_nr_of_processed_products' );
|
|
$current_feed_status['products_to_process'] = get_transient( 'wppfm_nr_of_products_to_process_' . $feed_id );
|
|
|
|
// If it is, set the feed status to fail and change the $current_feed_status['status_id'] to 6.
|
|
if ( WPPFM_Feed_Controller::feed_processing_failed( $feed_file ) ) {
|
|
|
|
do_action( 'wppfm_feed_processing_failed_file_size_stopped_increasing', $feed_id, WPPFM_Feed_Controller::nr_ids_remaining_in_product_queue() );
|
|
do_action( 'wppfm_register_feed_url', $feed_id, $feed_file );
|
|
|
|
// Change the status of the feed to failed processing.
|
|
$this->_data_class->update_feed_status( $feed_id, 6 ); // Feed status to fail.
|
|
|
|
// Update the current_feed_status variable before returning.
|
|
$current_feed_status['status_id'] = '6';
|
|
|
|
// Clear this feed from the feed queue.
|
|
WPPFM_Feed_Controller::remove_id_from_feed_queue( $feed_id );
|
|
WPPFM_Feed_Controller::set_feed_processing_flag();
|
|
|
|
// Automatic runs: queue a delayed notice only if the feed is still failed after a quiet
|
|
// period (see wppfm_send_deferred_feed_failure_notice_cb). Immediate email was removed
|
|
// to avoid false positives when generation completes successfully shortly after.
|
|
if ( get_transient( 'wppfm_running_silent' ) && function_exists( 'wppfm_schedule_deferred_feed_failure_notice' ) ) {
|
|
wppfm_schedule_deferred_feed_failure_notice( $feed_id, time() );
|
|
}
|
|
|
|
if ( ! WPPFM_Feed_Controller::feed_queue_is_empty() ) {
|
|
$this->initiate_update_next_feed_in_queue();
|
|
}
|
|
}
|
|
}
|
|
|
|
return $current_feed_status;
|
|
}
|
|
|
|
/**
|
|
* Initiates the update process of the next feed in the feed queue.
|
|
*/
|
|
public function initiate_update_next_feed_in_queue() {
|
|
$next_feed_id = WPPFM_Feed_Controller::get_next_id_from_feed_queue();
|
|
|
|
if ( $next_feed_id ) {
|
|
$feed_master_class = new WPPFM_Feed_Master_Class( $next_feed_id );
|
|
$feed_master_class->update_feed_file();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform all preparations for the feed update.
|
|
*
|
|
* @return bool true if the preparations are successful.
|
|
*/
|
|
private function prepare_feed_file_update() {
|
|
// Prepare the folder structure to support saving feed files.
|
|
if ( ! file_exists( WPPFM_FEEDS_DIR ) ) {
|
|
WPPFM_Folders::make_feed_support_folder();
|
|
}
|
|
|
|
$wp_filesystem = wppfm_get_wp_filesystem();
|
|
|
|
if ( ! $wp_filesystem->is_writable( WPPFM_FEEDS_DIR ) ) {
|
|
/* translators: %s: Folder where the feeds are stored */
|
|
return sprintf( __( '1430 - %s is not a writable folder. Make sure you have admin rights to this folder.', 'wp-product-feed-manager' ), WPPFM_FEEDS_DIR );
|
|
}
|
|
|
|
$initial_feed_status = $this->_data_class->get_feed_status( $this->_feed->feedId );
|
|
|
|
if ( ! $this->set_properties() ) {
|
|
$message = sprintf( 'Failed to set the properties of feed %s.', $this->_feed->feedId );
|
|
do_action( 'wppfm_feed_generation_message', $this->_feed->feedId, $message, 'ERROR' );
|
|
|
|
return false;
|
|
}
|
|
|
|
$this->_data_class->set_nr_of_feed_products( $this->_feed->feedId, '0' ); // 0 products.
|
|
$this->_data_class->update_feed_status( $this->_feed->feedId, 3 ); // Set status to "Processing".
|
|
|
|
$file_extension = function_exists( 'wppfm_get_file_type' ) ? wppfm_get_file_type( $this->_feed->channel ) : 'xml';
|
|
|
|
$this->_feed_file_path = wppfm_get_file_path( $this->_feed->title . '.' . $file_extension );
|
|
|
|
// Clear the existing feed.
|
|
$wp_filesystem->put_contents( $this->_feed_file_path, '', FS_CHMOD_FILE );
|
|
|
|
// Clear the file size checker.
|
|
delete_transient( 'wppfm_feed_file_size' );
|
|
|
|
// clear the list of processed products @since 2.10.0.
|
|
delete_option( 'wppfm_processed_products' );
|
|
|
|
$channel_class = new WPPFM_Channel();
|
|
$channel_name = $channel_class->get_channel_short_name( $this->_feed->channel );
|
|
|
|
$logger_message = sprintf( 'Feed %s is a %s feed stored as %s, with an original feed status %s.', $this->_feed->feedId, $channel_name, $this->_feed_file_path, $initial_feed_status );
|
|
do_action( 'wppfm_feed_generation_message', $this->_feed->feedId, $logger_message );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Prepares the background process. Stores common product metadata in the Background Process properties.
|
|
*/
|
|
private function prepare_background_process() {
|
|
// Start counting from zero.
|
|
delete_option( 'wppfm_processed_products' );
|
|
|
|
$this->_background_process->set_feed_data( $this->_feed );
|
|
$this->_background_process->set_file_path( $this->_feed_file_path );
|
|
$this->_background_process->set_pre_data( $this->get_required_pre_data() );
|
|
$this->_background_process->set_channel_details( $this->get_channel_details() );
|
|
$this->_background_process->set_relations_table( $this->channel_to_woocommerce_field_relations() );
|
|
}
|
|
|
|
/**
|
|
* Fills the product background queue with products that are to be processed.
|
|
*/
|
|
private function fill_the_background_queue() {
|
|
// Start with an empty queue.
|
|
$this->_background_process->clear_the_queue();
|
|
$sw_status_control = 30 * 3.3;
|
|
$product_counter = 0;
|
|
|
|
// Add the header to the queue.
|
|
$header_string = $this->get_feed_start_line();
|
|
$this->_background_process->push_to_queue( array( 'file_format_line' => $header_string ) );
|
|
|
|
do {
|
|
$product_ids = $this->get_product_ids_for_feed();
|
|
|
|
// Add the product ids to the queue.
|
|
foreach ( $product_ids as $product_id ) {
|
|
$this->_background_process->push_to_queue( $product_id );
|
|
|
|
$product_counter++;
|
|
|
|
if ( $product_counter > $sw_status_control ) {
|
|
break;
|
|
}
|
|
}
|
|
} while ( ! empty( $product_ids ) && $sw_status_control > $product_counter );
|
|
|
|
delete_transient( 'wppfm_start_product_id' );
|
|
set_transient( 'wppfm_nr_of_processed_products', 0 ); // (Re)set the processed product counter for the progress bar.
|
|
|
|
// implement the wppfm_feed_ids_in_queue filter on the queue.
|
|
$this->_background_process->apply_filter_to_queue( $this->_feed->feedId );
|
|
|
|
do_action( 'wppfm_feed_queue_filled', $this->_feed->feedId, $product_counter );
|
|
|
|
$product_ids = null;
|
|
|
|
$file_extension = function_exists( 'wppfm_get_file_type' ) ? wppfm_get_file_type( $this->_feed->channel ) : 'xml';
|
|
|
|
// Add the XML footer to the queue, except when it's a promotion feed.
|
|
if ( 'xml' === $file_extension && '3' !== $this->_feed->feedTypeId ) {
|
|
$this->_background_process->push_to_queue(
|
|
array(
|
|
'file_format_line' => apply_filters(
|
|
'wppfm_footer_string',
|
|
$this->_channel_class->footer(),
|
|
$this->_feed->feedId,
|
|
$this->_feed->feedTypeId
|
|
),
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initiates the feed update process in the background.
|
|
*
|
|
* @param string $feed_id The id of the feed that is to be updated.
|
|
*/
|
|
private function activate_feed_file_update( $feed_id ) {
|
|
// Save the queue data and then run the wppfm-background-process dispatch function.
|
|
$this->_background_process->save( $this->_feed->feedId )->dispatch( $feed_id );
|
|
}
|
|
|
|
/**
|
|
* Set all class properties.
|
|
*
|
|
* @return bool true if the properties are set correctly.
|
|
*/
|
|
private function set_properties() {
|
|
// Some channels do not use channels and leave the main category empty, which causes issues.
|
|
if ( function_exists( 'wppfm_channel_uses_own_category' ) && ! wppfm_channel_uses_own_category( $this->_feed->channel ) ) {
|
|
$this->_feed->mainCategory = 'No Category Required';
|
|
}
|
|
|
|
// Some channels only accept category id numbers, for these channels retrieve the category numbers.
|
|
if ( stripos( strrev( $this->_feed->mainCategory ), ')' ) === 0 ) {
|
|
$start = stripos( $this->_feed->mainCategory, '(' ) + 1;
|
|
$end = stripos( $this->_feed->mainCategory, '(' ) - $start;
|
|
$this->_feed->mainCategory = substr( $this->_feed->mainCategory, $start, $end );
|
|
}
|
|
|
|
// instantiate the correct channel class.
|
|
$this->_channel_class = new WPPFM_Google_Feed_Class();
|
|
|
|
// Reset the feed status in case the previous status was stuck in processing.
|
|
if ( '5' === $this->_feed->status || '6' === $this->_feed->status ) {
|
|
$this->_feed->status = '2';
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the correct first line for the selected feed type.
|
|
*
|
|
* @return string with the first feed line.
|
|
*/
|
|
private function get_feed_start_line() {
|
|
$header_string = '';
|
|
|
|
if ( $this->_feed->channel ) {
|
|
$file_extension = function_exists( 'wppfm_get_file_type' ) ? wppfm_get_file_type( $this->_feed->channel ) : 'xml';
|
|
if ( '1' === $this->_feed->channel && ! empty( $this->_feed->feedTitle ) ) {
|
|
$header_string = $this->_channel_class->header( $this->_feed->feedTitle, $this->_feed->feedDescription );
|
|
} elseif ( 'xml' === $file_extension ) {
|
|
$header_string = $this->_channel_class->header( $this->_feed->title );
|
|
} elseif ( 'txt' === $file_extension ) {
|
|
$txt_sep = apply_filters( 'wppfm_txt_separator', wppfm_get_correct_txt_separator( $this->_feed->channel ) );
|
|
$header_string = $this->make_feed_string_from_product_placeholder( $this->get_active_fields(), $txt_sep );
|
|
} elseif ( 'csv' === $file_extension ) {
|
|
$csv_sep = apply_filters( 'wppfm_csv_separator', wppfm_get_correct_csv_header_separator( $this->_feed->channel ) );
|
|
$string = $this->make_custom_header_string( $this->get_active_fields(), $csv_sep );
|
|
|
|
$header_string = $this->_channel_class->header( $string );
|
|
} elseif ( 'tsv' === $file_extension ) {
|
|
$string = $this->make_custom_header_string( $this->get_active_fields(), "\t" );
|
|
$header_string = $this->_channel_class->header( $string );
|
|
}
|
|
}
|
|
|
|
return apply_filters( 'wppfm_header_string', $header_string, $this->_feed->feedId, $this->_feed->feedTypeId );
|
|
}
|
|
|
|
/**
|
|
* Sets the activity status of a specific attribute to true or false depending on its level.
|
|
* 'Active' (true) means the attribute will be added to the feed, 'not active' (false) means it will not be added to the feed.
|
|
* ALERT! Has a JavaScript equivalent in channel-functions.js called setAttributeStatus().
|
|
*
|
|
* @param int $field_level The level of the field.
|
|
* @param string $field_value The value of the field.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
protected function set_attribute_status( $field_level, $field_value ) {
|
|
if ( $field_level > 0 && $field_level < 3 ) {
|
|
return true;
|
|
}
|
|
$clean_field_value = trim( $field_value );
|
|
if ( ! empty( $clean_field_value ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Produces an array with the ids of all products that should be added into the feed.
|
|
*
|
|
* @return array with ids.
|
|
*/
|
|
private function get_product_ids_for_feed() {
|
|
$queries_class = new WPPFM_Queries();
|
|
|
|
$selected_categories = apply_filters( 'wppfm_selected_categories', $this->make_category_selection_string(), $this->_feed->feedId );
|
|
|
|
$include_variations = '1' === $this->_feed->includeVariations;
|
|
|
|
$products = $queries_class->get_post_ids( $selected_categories, $include_variations );
|
|
|
|
array_filter( $products ); // Just to make sure, remove all empty elements.
|
|
$unique_products = array_unique( $products ); // Remove doubles.
|
|
|
|
return apply_filters( 'wppfm_products_in_feed_queue', $unique_products, $this->_feed->feedId );
|
|
}
|
|
|
|
/**
|
|
* Returns a comma separated string with selected category numbers to be used as part of a query.
|
|
*
|
|
* @return string with selected category ids.
|
|
*/
|
|
private function make_category_selection_string() {
|
|
$category_selection_string = '';
|
|
$category_mapping = json_decode( $this->_feed->categoryMapping );
|
|
|
|
if ( ! empty( $category_mapping ) ) {
|
|
foreach ( $category_mapping as $category ) {
|
|
$category_selection_string .= $category->shopCategoryId . ', '; // phpcs:ignore
|
|
}
|
|
}
|
|
|
|
return $category_selection_string ? substr( $category_selection_string, 0, - 2 ) : '';
|
|
}
|
|
|
|
/**
|
|
* Get all general data required to make a feed.
|
|
*
|
|
* @return array with arrays containing required data to make a feed.
|
|
*/
|
|
private function get_required_pre_data() {
|
|
// Get the feed query string if the user has added to filter out specific products from the feed (Paid version only).
|
|
$feed_filter = $this->_data_class->get_filter_query( $this->_feed->feedId );
|
|
|
|
// Should the feed include product variations?
|
|
$include_variations = '1' === $this->_feed->includeVariations;
|
|
|
|
// Get an array with all the field names that are required to make the feed (including the source fields, fields for the queries and fields for static data).
|
|
$required_column_names = '3' !== $this->_feed->feedTypeId ? $this->get_column_names_required_for_feed( $feed_filter ) : array();
|
|
|
|
// Get the fields that are active and have to go into the feed.
|
|
$active_fields = $this->get_active_fields();
|
|
|
|
$database_fields = $this->get_database_fields( $required_column_names );
|
|
|
|
return array(
|
|
'filters' => $feed_filter,
|
|
'include_vars' => $include_variations,
|
|
'column_names' => $required_column_names,
|
|
'active_fields' => $active_fields,
|
|
'database_fields' => $database_fields,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the correct background feed processor class name.
|
|
* Also sets the wppfm_set_global_background_process transient.
|
|
*
|
|
* @param string $feed_id The id of the feed that is currently processing.
|
|
*
|
|
* @since 2.33.0.
|
|
* @since 2.34.0. Improved the stability of the correct selection of the class name.
|
|
* @since 2.37.0. Added a check if the Review Feed Manager is selected on or not, before using the WPPRFM_Review_Feed_Processor as background class.
|
|
*
|
|
* @return string containing the correct background feed processor class.
|
|
*/
|
|
protected function get_background_process_class_name( $feed_id ) {
|
|
$query_class = new WPPFM_Queries();
|
|
|
|
if ( intval( $feed_id ) > 0 ) {
|
|
set_transient( 'wppfm_active_feed_id', $feed_id, WPPFM_TRANSIENT_LIVE );
|
|
$feed_type_id = $query_class->get_feed_type_id( $feed_id );
|
|
} else {
|
|
$feed_id = get_transient( 'wppfm_active_feed_id' );
|
|
$feed_type_id = intval( $feed_id ) > 0 ? $query_class->get_feed_type_id( $feed_id ) : '1';
|
|
}
|
|
|
|
// Set the wppfm_set_global_background_process transient for use in the global background_process variable.
|
|
switch ( $feed_type_id ) {
|
|
case '2':
|
|
$active_tab = 'google-product-review-feed';
|
|
break;
|
|
|
|
case '3':
|
|
$active_tab = 'google-merchant-promotions-feed';
|
|
break;
|
|
|
|
default: // 1
|
|
$active_tab = 'product-feed';
|
|
}
|
|
|
|
set_transient( 'wppfm_set_global_background_process', $active_tab, WPPFM_TRANSIENT_LIVE );
|
|
|
|
switch ( $feed_type_id ) {
|
|
case '2':
|
|
return 'WPPRFM_Review_Feed_Processor';
|
|
|
|
case '3':
|
|
return 'WPPPFM_Promotions_Feed_Processor';
|
|
|
|
default:
|
|
return 'WPPFM_Feed_Processor';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the category name and description name from the active channel.
|
|
*
|
|
* @return array containing the channel details.
|
|
*/
|
|
private function get_channel_details() {
|
|
return function_exists( 'wppfm_channel_file_text_data' ) ? wppfm_channel_file_text_data( $this->_feed->channel ) :
|
|
array(
|
|
'channel_id' => $this->_feed->channel,
|
|
'category_name' => 'google_product_category',
|
|
'description_name' => 'description',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the column names from the database that are required to get the data necessary to make the feed.
|
|
*
|
|
* @param object $feed_filter_object The feed filter object.
|
|
*
|
|
* @return array with column names.
|
|
*/
|
|
private function get_column_names_required_for_feed( $feed_filter_object ) {
|
|
$support_class = new WPPFM_Feed_Support();
|
|
|
|
$fields = array();
|
|
$filter_columns = $support_class->get_column_names_from_feed_filter_array( $feed_filter_object );
|
|
|
|
foreach ( $this->_feed->attributes as $attribute ) {
|
|
if ( 'category_mapping' !== $attribute->fieldName ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
|
$column_names = $this->get_db_column_name_from_attribute( $attribute );
|
|
foreach ( $column_names as $name ) {
|
|
if ( ! empty( $name ) ) {
|
|
/** @noinspection PhpArrayPushWithOneElementInspection */
|
|
array_push( $fields, $name );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$result = array_unique( array_merge( $fields, $filter_columns ) ); // Remove doubles.
|
|
|
|
if ( empty( $result ) ) {
|
|
wppfm_write_log_file( 'Function get_column_names_required_for_feed returned zero columns' );
|
|
}
|
|
|
|
return array_merge( $result ); // And resort the result before returning.
|
|
}
|
|
|
|
/**
|
|
* Returns all active column names that are stored in the feed attributes.
|
|
*
|
|
* @param object|string $attribute The attribute array.
|
|
*
|
|
* @return array
|
|
* @noinspection PhpArrayPushWithOneElementInspection
|
|
* @noinspection PhpStrFunctionsInspection
|
|
*/
|
|
public function get_db_column_name_from_attribute( $attribute ) {
|
|
$column_names = array();
|
|
|
|
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
|
if ( property_exists( $attribute, 'isActive' ) && $attribute->isActive ) { // Only select the active attributes.
|
|
// Source columns.
|
|
if ( ! empty( $attribute->value ) ) {
|
|
$source_columns = $this->get_source_columns_from_attribute_value( $attribute->value );
|
|
$condition_columns = $this->get_condition_columns_from_attribute_value( $attribute->value );
|
|
$query_columns = $this->get_queries_columns_from_attribute_value( $attribute->value );
|
|
|
|
// TODO: I think the first $column_names array can be removed from the array_merge.
|
|
$column_names = array_merge( $column_names, $source_columns, $condition_columns, $query_columns );
|
|
}
|
|
|
|
// Advised sources.
|
|
if ( ! empty( $attribute->advisedSource )
|
|
&& strpos( $attribute->advisedSource, __( 'Fill with a static value', 'wp-product-feed-manager' ) ) === false
|
|
&& strpos( $attribute->advisedSource, __( 'Use the settings in the Merchant Center', 'wp-product-feed-manager' ) ) === false ) {
|
|
|
|
// Add the relevant advised sources.
|
|
array_push( $column_names, $attribute->advisedSource );
|
|
} elseif ( property_exists( $attribute, 'advisedSource' )
|
|
&& strpos( $attribute->advisedSource, __( 'Use the settings in the Merchant Center', 'wp-product-feed-manager' ) ) !== false ) {
|
|
|
|
array_push( $column_names, 'woo_shipping' );
|
|
}
|
|
}
|
|
|
|
return $column_names;
|
|
}
|
|
|
|
/**
|
|
* Extract the active fields from the attributes.
|
|
*
|
|
* @return array with active field strings.
|
|
*/
|
|
private function get_active_fields() {
|
|
$active_fields = array();
|
|
|
|
|
|
foreach ( $this->_feed->attributes as $attribute ) {
|
|
if ( $attribute->isActive && 'category_mapping' !== $attribute->fieldName ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
|
$push = false;
|
|
|
|
if ( '1' === $attribute->fieldLevel || 1 === $attribute->fieldLevel ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
|
$push = true;
|
|
} else {
|
|
$value_object = property_exists( $attribute, 'value' ) ? json_decode( $attribute->value ) : new stdClass();
|
|
|
|
if ( empty( $value_object ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ! empty( $attribute->value )
|
|
&& is_object( $value_object )
|
|
&& property_exists( $value_object, 'm' )
|
|
&& ! empty( $value_object->m[0] )
|
|
&& property_exists( $value_object->m[0], 's' ) ) {
|
|
$push = true;
|
|
} elseif ( ! empty( $attribute->advisedSource ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
|
$push = true;
|
|
} elseif ( ! empty( $attribute->value ) && is_object( $value_object ) && property_exists( $value_object, 't' ) ) {
|
|
$push = true;
|
|
} elseif ( ! empty( $attribute->value ) && is_object( $value_object ) && property_exists( $value_object, 'v' ) ) {
|
|
$push = true;
|
|
} elseif ( ! empty( $attribute->value ) && ! is_object( $value_object ) ) {
|
|
$push = true;
|
|
}
|
|
}
|
|
|
|
if ( true === $push ) {
|
|
$active_fields[] = $attribute->fieldName; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( empty( $active_fields ) ) {
|
|
wppfm_write_log_file( 'Function get_active_fields returned zero fields.' );
|
|
}
|
|
|
|
return $active_fields;
|
|
}
|
|
|
|
/**
|
|
* Returns an array with fields that are handled procedurally in the add_procedural_data() function.
|
|
*
|
|
* @since 2.36.0.
|
|
* @return array with procedural field strings.
|
|
*/
|
|
private function procedural_fields() {
|
|
return array(
|
|
'_regular_price',
|
|
'_sale_price',
|
|
'shipping_class',
|
|
'permalink',
|
|
'attachment_url',
|
|
'product_main_image_url',
|
|
'product_cat',
|
|
'product_cat_string',
|
|
'last_update',
|
|
'_wp_attachement_metadata',
|
|
'product_tags',
|
|
'wc_currency',
|
|
'_min_variation_price',
|
|
'_max_variation_price',
|
|
'_min_variation_regular_price',
|
|
'_max_variation_regular_price',
|
|
'_min_variation_sale_price',
|
|
'_max_variation_sale_price',
|
|
'item_group_id',
|
|
'_stock',
|
|
'empty',
|
|
'product_type',
|
|
'product_category_primary',
|
|
'product_variation_title_without_attributes',
|
|
'_variation_parent_id',
|
|
'_product_parent_id',
|
|
'_max_group_price',
|
|
'_min_group_price',
|
|
'_regular_price_with_tax',
|
|
'_regular_price_without_tax',
|
|
'_sale_price_with_tax',
|
|
'_sale_price_without_tax',
|
|
'_product_parent_description',
|
|
'_woocs_currency',
|
|
'_low_stock_amount',
|
|
'wppfm_performance_tier',
|
|
'wppfm_performance_revenue',
|
|
'wppfm_performance_orders',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gather all required column names from the database that hold data that .
|
|
*
|
|
* @param array $active_field_names Array with the names of the active fields.
|
|
*
|
|
* @return array
|
|
*/
|
|
private function get_database_fields( $active_field_names ) {
|
|
$queries_class = new WPPFM_Queries();
|
|
|
|
$post_fields = array();
|
|
$meta_fields = array();
|
|
$custom_fields = array();
|
|
$active_custom_fields = array();
|
|
$procedural_fields = $this->procedural_fields();
|
|
$post_columns_string = '';
|
|
|
|
$columns_in_post_table = $queries_class->get_columns_from_post_table(); // Get all post-table column names.
|
|
$all_custom_columns = $queries_class->get_custom_product_attributes(); // Get all custom name labels.
|
|
$third_party_custom_fields = $this->_data_class->get_third_party_custom_fields();
|
|
|
|
// Convert the query results to an array with only the name labels.
|
|
foreach ( $columns_in_post_table as $column ) {
|
|
$post_fields[] = $column->Field; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
|
} // $post_fields containing the required names from the post-table.
|
|
foreach ( $all_custom_columns as $custom ) {
|
|
$custom_fields[] = $custom->attribute_name;
|
|
} // $custom_fields containing the custom names.
|
|
// Filter the post-columns, the meta columns and the custom columns to only those that are actually in use.
|
|
|
|
foreach ( $active_field_names as $column ) {
|
|
if ( in_array( $column, $post_fields, true ) && 'ID' !== $column ) { // Because ID is always required, it's excluded here and hard coded in the query.
|
|
$post_columns_string .= $column . ', '; // Here a string is required to push in the query.
|
|
} elseif ( in_array( $column, $custom_fields, true ) ) {
|
|
$active_custom_fields[] = $column;
|
|
} else {
|
|
if ( ! in_array( $column, $procedural_fields, true ) ) { // Skip the procedural fields
|
|
$meta_fields[] = $column;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array(
|
|
'post_column_string' => $post_columns_string,
|
|
'meta_fields' => $meta_fields,
|
|
'active_custom_fields' => $active_custom_fields,
|
|
'third_party_custom_fields' => $third_party_custom_fields,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Feed header text, override this function in the class-feed.php if required for a channel-specific header.
|
|
*
|
|
* @param string $title Title string.
|
|
*
|
|
* @return string with the header text.
|
|
*/
|
|
protected function header( $title ) {
|
|
return apply_filters( 'wppfm_xml_header', $title );
|
|
}
|
|
|
|
/**
|
|
* Feed footer text, override if required for a channel-specific footer.
|
|
*
|
|
* @return string with the footer text.
|
|
*/
|
|
protected function footer() {
|
|
return apply_filters( 'wppfm_xml_footer', '</products></rss>' );
|
|
}
|
|
}
|
|
|
|
// End of WPPFM_Feed_Master_Class.
|
|
|
|
endif;
|