Files
2026-04-28 15:13:50 +02:00

311 lines
8.9 KiB
PHP

<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package MetaCommerce
*/
namespace WooCommerce\Facebook\Admin;
defined( 'ABSPATH' ) || exit;
use WooCommerce\Facebook\ProductAttributeMapper;
/**
* Global Attributes Banner handler.
*
* Shows informational banners when users create global attributes
* that don't have direct mappings to Meta catalog fields.
*
* @since 3.5.4
*/
class Global_Attributes_Banner {
/**
* Constructor.
*/
public function __construct() {
// Hook into WooCommerce attribute creation (better than generic term creation)
add_action( 'woocommerce_attribute_added', array( $this, 'check_new_woocommerce_attribute' ), 10, 2 );
// Also hook into generic term creation as fallback
add_action( 'created_term', array( $this, 'check_new_attribute_mapping' ), 20, 3 );
// Hook into attribute page display
add_action( 'admin_notices', array( $this, 'display_unmapped_attribute_banner' ) );
// AJAX handler for dismissing banner
add_action( 'wp_ajax_dismiss_fb_unmapped_attribute_banner', array( $this, 'dismiss_banner' ) );
// Add test action for debugging (remove in production)
add_action( 'wp_ajax_test_fb_banner', array( $this, 'ajax_test_banner' ) );
// Add URL parameter trigger for testing
add_action( 'admin_init', array( $this, 'maybe_trigger_test_banner' ) );
}
/**
* Check for URL parameter to trigger test banner.
*/
public function maybe_trigger_test_banner() {
if ( isset( $_GET['test_fb_banner'] ) && current_user_can( 'manage_woocommerce' ) ) {
$attribute_name = sanitize_text_field( wp_unslash( $_GET['test_fb_banner'] ) );
if ( empty( $attribute_name ) ) {
$attribute_name = 'escobar';
}
$this->test_banner( $attribute_name );
// Add an admin notice to confirm
add_action(
'admin_notices',
function () use ( $attribute_name ) {
echo '<div class="notice notice-success"><p>Test banner triggered for: ' . esc_html( $attribute_name ) . '</p></div>';
}
);
}
}
/**
* Check if a newly created WooCommerce attribute has a direct mapping to Meta.
*
* @param int $id Attribute ID.
* @param array $data Attribute data.
*/
public function check_new_woocommerce_attribute( $id, $data ) {
// Only show to users who can manage WooCommerce
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
// Get the attribute name from the data
$attribute_name = isset( $data['attribute_name'] ) ? $data['attribute_name'] : '';
if ( empty( $attribute_name ) ) {
return;
}
// Check if this attribute maps to any Meta field
$maps_to_meta = $this->attribute_maps_to_meta( $attribute_name );
if ( ! $maps_to_meta ) {
$this->queue_unmapped_attribute_banner( $attribute_name );
}
}
/**
* Check if a newly created attribute has a direct mapping to Meta.
*
* @param int $term_id Term ID.
* @param int $tt_id Term taxonomy ID.
* @param string $taxonomy Taxonomy slug.
*/
public function check_new_attribute_mapping( $term_id, $tt_id, $taxonomy ) {
// Only check for attribute taxonomies
if ( ! $this->is_attribute_taxonomy( $taxonomy ) ) {
return;
}
// Only show to users who can manage WooCommerce
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
// Get the attribute name from taxonomy
$attribute_name = str_replace( 'pa_', '', $taxonomy );
// Check if this attribute maps to any Meta field
$maps_to_meta = $this->attribute_maps_to_meta( $attribute_name );
if ( ! $maps_to_meta ) {
$this->queue_unmapped_attribute_banner( $attribute_name );
}
}
/**
* Check if a taxonomy is an attribute taxonomy.
*
* @param string $taxonomy Taxonomy name.
* @return bool
*/
private function is_attribute_taxonomy( $taxonomy ) {
return strpos( $taxonomy, 'pa_' ) === 0;
}
/**
* Check if an attribute name maps to a Meta field.
*
* @param string $attribute_name Attribute name (without pa_ prefix).
* @return bool
*/
private function attribute_maps_to_meta( $attribute_name ) {
if ( ! class_exists( 'WooCommerce\Facebook\ProductAttributeMapper' ) ) {
return false;
}
// Use the same comprehensive logic as the ProductAttributeMapper
$mapped_field = ProductAttributeMapper::check_attribute_mapping( 'pa_' . $attribute_name );
// If we get a mapping result, the attribute is mapped
return false !== $mapped_field;
}
/**
* Queue a banner for an unmapped attribute.
*
* @param string $attribute_name The attribute name.
*/
private function queue_unmapped_attribute_banner( $attribute_name ) {
$banner_data = array(
'attribute_name' => $attribute_name,
'timestamp' => time(),
);
// Increase duration to 30 minutes to account for page redirects
set_transient( 'fb_new_unmapped_attribute_banner', $banner_data, 1800 );
// Also store a flag to show the banner immediately on the current page
set_transient( 'fb_show_banner_now', true, 300 );
}
/**
* Display the unmapped attribute banner.
*/
public function display_unmapped_attribute_banner() {
// Check if we should force show the banner now (for immediate display)
$show_now = get_transient( 'fb_show_banner_now' );
$should_show = $this->should_show_banner();
if ( ! $should_show && ! $show_now ) {
return;
}
$banner_data = get_transient( 'fb_new_unmapped_attribute_banner' );
if ( ! $banner_data || ! isset( $banner_data['attribute_name'] ) ) {
return;
}
// Clear the immediate show flag if it was set
if ( $show_now ) {
delete_transient( 'fb_show_banner_now' );
}
$attribute_name = $banner_data['attribute_name'];
$display_name = ucfirst( str_replace( array( '_', '-' ), ' ', $attribute_name ) );
// Build the mapper URL
$mapper_url = add_query_arg(
array(
'page' => 'wc-facebook',
'tab' => 'product-attributes',
),
admin_url( 'admin.php' )
);
?>
<div class="notice notice-info is-dismissible fb-unmapped-attribute-banner" style="position: relative;">
<p>
<strong><?php esc_html_e( 'Meta for WooCommerce', 'facebook-for-woocommerce' ); ?></strong>
</p>
<p>
<?php
printf(
/* translators: %1$s - attribute name, %2$s - link start, %3$s - link end */
esc_html__( 'Your new "%1$s" attribute doesn\'t directly map to a Meta catalog field. %2$sMap it to Facebook%3$s to improve product visibility in Meta ads and help customers find your products more easily.', 'facebook-for-woocommerce' ),
esc_html( $display_name ),
'<a href="' . esc_url( $mapper_url ) . '">',
'</a>'
);
?>
</p>
<button type="button" class="notice-dismiss" data-attribute="<?php echo esc_attr( $attribute_name ); ?>">
<span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice', 'facebook-for-woocommerce' ); ?></span>
</button>
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('.fb-unmapped-attribute-banner .notice-dismiss').on('click', function() {
var attributeName = $(this).data('attribute');
$.post(ajaxurl, {
action: 'dismiss_fb_unmapped_attribute_banner',
attribute: attributeName,
nonce: '<?php echo esc_attr( wp_create_nonce( 'dismiss_fb_banner' ) ); ?>'
});
$(this).closest('.notice').fadeOut();
});
});
</script>
<?php
}
/**
* Check if we should show the banner.
*
* @return bool
*/
private function should_show_banner() {
// Only show to users who can manage WooCommerce
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return false;
}
// Show only on the attributes page & Facebook settings page to reduce noise
$screen = get_current_screen();
if ( ! $screen ) {
return false;
}
// Show only on: attributes page & Facebook settings page
$allowed_screens = array(
'product_page_product_attributes', // Global attributes page
'woocommerce_page_wc-facebook', // Facebook settings page
);
return in_array( $screen->id, $allowed_screens, true );
}
/**
* Dismiss the banner via AJAX.
*/
public function dismiss_banner() {
check_ajax_referer( 'dismiss_fb_banner', 'nonce' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( -1 );
}
delete_transient( 'fb_new_unmapped_attribute_banner' );
wp_die();
}
/**
* Manual method to test the banner (for debugging).
*
* @param string $attribute_name The attribute name to test.
*/
public function test_banner( $attribute_name = 'escobar' ) {
$this->queue_unmapped_attribute_banner( $attribute_name );
}
/**
* AJAX handler to test the banner.
*/
public function ajax_test_banner() {
check_ajax_referer( 'test_fb_banner', 'nonce' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( -1 );
}
$attribute_name = isset( $_POST['attribute'] ) ? sanitize_text_field( wp_unslash( $_POST['attribute'] ) ) : 'escobar';
$this->test_banner( $attribute_name );
wp_send_json_success( 'Banner queued for: ' . $attribute_name );
}
}