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', '' ); } } // End of WPPFM_Feed_Master_Class. endif;