Integrations
6 years ago
Products
6 years ago
Utilities
6 years ago
test
6 years ago
AJAX.php
6 years ago
Admin.php
6 years ago
Lifecycle.php
6 years ago
Products.php
6 years ago
fbasync.php
6 years ago
fbbackground.php
6 years ago
fbgraph.php
6 years ago
fbinfobanner.php
6 years ago
fbproduct.php
6 years ago
fbproductfeed.php
6 years ago
fbutils.php
6 years ago
fbwpml.php
6 years ago
Admin.php
1122 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved |
| 4 | * |
| 5 | * This source code is licensed under the license found in the |
| 6 | * LICENSE file in the root directory of this source tree. |
| 7 | * |
| 8 | * @package FacebookCommerce |
| 9 | */ |
| 10 | |
| 11 | namespace SkyVerge\WooCommerce\Facebook; |
| 12 | |
| 13 | use SkyVerge\WooCommerce\PluginFramework\v5_5_4\SV_WC_Helper; |
| 14 | |
| 15 | defined( 'ABSPATH' ) or exit; |
| 16 | |
| 17 | /** |
| 18 | * Admin handler. |
| 19 | * |
| 20 | * @since 1.10.0 |
| 21 | */ |
| 22 | class Admin { |
| 23 | |
| 24 | |
| 25 | /** |
| 26 | * Admin constructor. |
| 27 | * |
| 28 | * @since 1.10.0 |
| 29 | */ |
| 30 | public function __construct() { |
| 31 | |
| 32 | // enqueue admin scripts |
| 33 | add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); |
| 34 | |
| 35 | $integration = facebook_for_woocommerce()->get_integration(); |
| 36 | |
| 37 | // only alter the admin UI if the plugin is connected to Facebook and ready to sync products |
| 38 | if ( ! $integration->get_product_catalog_id() ) { |
| 39 | return; |
| 40 | } |
| 41 | |
| 42 | // add a modal in admin product pages |
| 43 | add_action( 'admin_footer', [ $this, 'render_modal_template' ] ); |
| 44 | // may trigger the modal to open to warn the merchant about a conflict with the current product terms |
| 45 | add_action( 'admin_footer', [ $this, 'validate_product_excluded_terms' ] ); |
| 46 | |
| 47 | // add admin notification in case of site URL change |
| 48 | add_action( 'admin_notices', [ $this, 'validate_cart_url' ] ); |
| 49 | |
| 50 | // add admin notice to inform that product sync has changed |
| 51 | add_action( 'admin_notices', [ $this, 'add_product_sync_delay_notice' ] ); |
| 52 | // add admin notice to inform that the catalog visibility setting was removed |
| 53 | add_action( 'admin_notices', [ $this, 'add_catalog_visibility_settings_removed_notice' ] ); |
| 54 | // add admin notice if the user attempted to enable sync for virtual products using the bulk action |
| 55 | add_action( 'admin_notices', [ $this, 'add_enabling_virtual_products_sync_notice' ] ); |
| 56 | // add admin notice to inform sync has been automatically disabled for virtual products |
| 57 | add_action( 'admin_notices', [ $this, 'add_disabled_virtual_products_sync_notice' ] ); |
| 58 | |
| 59 | // handle dismissal of special notices |
| 60 | add_action( 'wc_' . facebook_for_woocommerce()->get_id(). '_dismiss_notice', [ $this, 'handle_dismiss_notice' ], 10, 2 ); |
| 61 | |
| 62 | // add columns for displaying Facebook sync enabled/disabled and catalog visibility status |
| 63 | add_filter( 'manage_product_posts_columns', [ $this, 'add_product_list_table_columns' ] ); |
| 64 | add_action( 'manage_product_posts_custom_column', [ $this, 'add_product_list_table_columns_content' ] ); |
| 65 | |
| 66 | // add input to filter products by Facebook sync enabled |
| 67 | add_action( 'restrict_manage_posts', [ $this, 'add_products_by_sync_enabled_input_filter' ], 40 ); |
| 68 | add_filter( 'request', [ $this, 'filter_products_by_sync_enabled' ] ); |
| 69 | |
| 70 | // add bulk actions to manage products sync |
| 71 | add_filter( 'bulk_actions-edit-product', [ $this, 'add_products_sync_bulk_actions' ], 40 ); |
| 72 | add_action( 'handle_bulk_actions-edit-product', [ $this, 'handle_products_sync_bulk_actions' ] ); |
| 73 | |
| 74 | // add Product data tab |
| 75 | add_filter( 'woocommerce_product_data_tabs', [ $this, 'add_product_settings_tab' ] ); |
| 76 | add_action( 'woocommerce_product_data_panels', [ $this, 'add_product_settings_tab_content' ] ); |
| 77 | |
| 78 | // add Variation edit fields |
| 79 | add_action( 'woocommerce_product_after_variable_attributes', [ $this, 'add_product_variation_edit_fields' ], 10, 3 ); |
| 80 | add_action( 'woocommerce_save_product_variation', [ $this, 'save_product_variation_edit_fields' ], 10, 2 ); |
| 81 | } |
| 82 | |
| 83 | |
| 84 | /** |
| 85 | * Enqueues admin scripts. |
| 86 | * |
| 87 | * @internal |
| 88 | * |
| 89 | * @since 1.10.0 |
| 90 | */ |
| 91 | public function enqueue_scripts() { |
| 92 | global $current_screen; |
| 93 | |
| 94 | $modal_screens = [ |
| 95 | 'product', |
| 96 | 'edit-product', |
| 97 | ]; |
| 98 | |
| 99 | if ( isset( $current_screen->id ) ) { |
| 100 | |
| 101 | if ( in_array( $current_screen->id, $modal_screens, true ) || facebook_for_woocommerce()->is_plugin_settings() ) { |
| 102 | |
| 103 | // enqueue modal functions |
| 104 | wp_enqueue_script( 'facebook-for-woocommerce-modal', plugins_url( '/facebook-for-woocommerce/assets/js/facebook-for-woocommerce-modal.min.js' ), [ 'jquery', 'wc-backbone-modal', 'jquery-blockui' ], \WC_Facebookcommerce::PLUGIN_VERSION ); |
| 105 | } |
| 106 | |
| 107 | if ( 'product' === $current_screen->id || 'edit-product' === $current_screen->id ) { |
| 108 | |
| 109 | wp_enqueue_style( 'facebook-for-woocommerce-products-admin', plugins_url( '/facebook-for-woocommerce/assets/css/admin/facebook-for-woocommerce-products-admin.css' ), [], \WC_Facebookcommerce::PLUGIN_VERSION ); |
| 110 | |
| 111 | wp_enqueue_script( 'facebook-for-woocommerce-products-admin', plugins_url( '/facebook-for-woocommerce/assets/js/admin/facebook-for-woocommerce-products-admin.min.js' ), [ 'jquery', 'wc-backbone-modal', 'jquery-blockui', 'facebook-for-woocommerce-modal' ], \WC_Facebookcommerce::PLUGIN_VERSION ); |
| 112 | |
| 113 | wp_localize_script( 'facebook-for-woocommerce-products-admin', 'facebook_for_woocommerce_products_admin', [ |
| 114 | 'ajax_url' => admin_url( 'admin-ajax.php' ), |
| 115 | 'set_product_visibility_nonce' => wp_create_nonce( 'set-products-visibility' ), |
| 116 | 'set_product_sync_prompt_nonce' => wp_create_nonce( 'set-product-sync-prompt' ), |
| 117 | 'set_product_sync_bulk_action_prompt_nonce' => wp_create_nonce( 'set-product-sync-bulk-action-prompt' ), |
| 118 | ] ); |
| 119 | } |
| 120 | |
| 121 | if ( facebook_for_woocommerce()->is_plugin_settings() ) { |
| 122 | |
| 123 | wp_enqueue_script( 'facebook-for-woocommerce-settings-sync', plugins_url( '/facebook-for-woocommerce/assets/js/admin/facebook-for-woocommerce-settings-sync.min.js' ), [ 'jquery', 'wc-backbone-modal', 'jquery-blockui', 'facebook-for-woocommerce-modal' ], \WC_Facebookcommerce::PLUGIN_VERSION ); |
| 124 | |
| 125 | /* translators: Placeholders %1$s - opening <strong> html tag, %2$s closing </strong> html tag, {count} number of remaining items */ |
| 126 | $sync_remaining_items_string = _n_noop( '%1$sProgress:%2$s {count} item remaining.', '%1$sProgress:%2$s {count} items remaining.', 'facebook-for-woocommerce' ); |
| 127 | |
| 128 | wp_localize_script( 'facebook-for-woocommerce-settings-sync', 'facebook_for_woocommerce_settings_sync', [ |
| 129 | 'ajax_url' => admin_url( 'admin-ajax.php' ), |
| 130 | 'set_excluded_terms_prompt_nonce' => wp_create_nonce( 'set-excluded-terms-prompt' ), |
| 131 | 'set_product_visibility_nonce' => wp_create_nonce( 'set-products-visibility' ), |
| 132 | 'i18n' => [ |
| 133 | /* translators: Placeholders %s - html code for a spinner icon */ |
| 134 | 'confirm_resync' => esc_html__( 'Your products will now be resynced to Facebook, this may take some time.', 'facebook-for-woocommerce' ), |
| 135 | 'confirm_sync_test' => esc_html__( 'Launch Test?', 'facebook-for-woocommerce' ), |
| 136 | 'confirm_sync' => esc_html__( "Facebook for WooCommerce automatically syncs your products on create/update. Are you sure you want to force product resync?\n\nThis will query all published products and may take some time. You only need to do this if your products are out of sync or some of your products did not sync.", 'facebook-for-woocommerce' ), |
| 137 | 'sync_in_progress' => sprintf( esc_html__( 'Syncing... Keep this browser open until sync is complete. %s', 'facebook-for-woocommerce' ), '<span class="spinner is-active"></span>' ), |
| 138 | 'sync_remaining_items_singular' => sprintf( esc_html( translate_nooped_plural( $sync_remaining_items_string, 1 ) ), '<strong>', '</strong>', '<span class="spinner is-active"></span>' ), |
| 139 | 'sync_remaining_items_plural' => sprintf( esc_html( translate_nooped_plural( $sync_remaining_items_string, 2 ) ), '<strong>', '</strong>', '<span class="spinner is-active"></span>' ), |
| 140 | /* translators: Placeholders %1$s - opening <strong> html tag, %2$s closing </strong> html tag */ |
| 141 | 'integration_test_sucessful' => sprintf( esc_html__( '%1$sStatus:%2$s Test Pass.', 'facebook-for-woocommerce' ), '<strong>', '</strong>' ), |
| 142 | /* translators: Placeholders %1$s - opening <strong> html tag, %2$s closing </strong> html tag */ |
| 143 | 'integration_test_in_progress' => sprintf( esc_html__( '%1$sStatus:%2$s Integration test in progress...', 'facebook-for-woocommerce' ), '<strong>', '</strong>' ), |
| 144 | /* translators: Placeholders %1$s - opening <strong> html tag, %2$s closing </strong> html tag */ |
| 145 | 'integration_test_failed' => sprintf( esc_html__( '%1$sStatus:%2$s Test Fail.', 'facebook-for-woocommerce' ), '<strong>', '</strong>' ), |
| 146 | 'general_error' => esc_html__( 'There was an error trying to sync the products to Facebook.', 'facebook-for-woocommerce' ), |
| 147 | 'feed_upload_error' => esc_html__( 'Something went wrong while uploading the product information, please try again.', 'facebook-for-woocommerce' ), |
| 148 | ], |
| 149 | ] ); |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | |
| 155 | /** |
| 156 | * Adds Facebook-related columns in the products edit screen. |
| 157 | * |
| 158 | * @internal |
| 159 | * |
| 160 | * @since 1.10.0 |
| 161 | * |
| 162 | * @param array $columns array of keys and labels |
| 163 | * @return array |
| 164 | */ |
| 165 | public function add_product_list_table_columns( $columns ) { |
| 166 | |
| 167 | $columns['facebook_sync_enabled'] = __( 'FB Sync Enabled', 'facebook-for-woocommerce' ); |
| 168 | // $columns['facebook_catalog_visibility'] = __( 'FB Catalog Visibility', 'facebook-for-woocommerce' ); |
| 169 | |
| 170 | return $columns; |
| 171 | } |
| 172 | |
| 173 | |
| 174 | /** |
| 175 | * Outputs sync information for products in the edit screen. |
| 176 | * |
| 177 | * @internal |
| 178 | * |
| 179 | * @since 1.10.0 |
| 180 | * |
| 181 | * @param string $column the current column in the posts table |
| 182 | */ |
| 183 | public function add_product_list_table_columns_content( $column ) { |
| 184 | global $post; |
| 185 | |
| 186 | if ( 'facebook_sync_enabled' === $column ) : |
| 187 | |
| 188 | $product = wc_get_product( $post ); |
| 189 | |
| 190 | if ( $product && Products::product_should_be_synced( $product ) ) : |
| 191 | esc_html_e( 'Enabled', 'facebook-for-woocommerce' ); |
| 192 | else : |
| 193 | esc_html_e( 'Disabled', 'facebook-for-woocommerce' ); |
| 194 | endif; |
| 195 | |
| 196 | elseif ( 'facebook_catalog_visibility' === $column ) : |
| 197 | |
| 198 | $integration = facebook_for_woocommerce()->get_integration(); |
| 199 | $product = wc_get_product( $post ); |
| 200 | $fb_product = new \WC_Facebook_Product( $post ); |
| 201 | $fb_product_group_id = $integration && $product && $integration->get_product_fbid( \WC_Facebookcommerce_Integration::FB_PRODUCT_GROUP_ID, $post->ID, $fb_product ); |
| 202 | |
| 203 | if ( ! $fb_product_group_id ) : |
| 204 | |
| 205 | ?> |
| 206 | <span |
| 207 | class="facebook-for-woocommerce-product-visibility-toggle" |
| 208 | style="cursor:default;" |
| 209 | title="<?php |
| 210 | /* translators: Points to a product that was never synced with Facebook */ |
| 211 | esc_attr_e( 'Never synced with Facebook.', 'facebook-for-woocommerce' ); ?>" |
| 212 | >–</span> |
| 213 | <?php |
| 214 | |
| 215 | else : |
| 216 | |
| 217 | $is_sync_enabled = Products::product_should_be_synced( $product ); |
| 218 | $is_visible = Products::is_product_visible( $product ); |
| 219 | $is_hidden = ! $is_visible; |
| 220 | |
| 221 | if ( $is_sync_enabled ) { |
| 222 | /* translators: Action to hide a product (currently synced with Facebook) from the Facebook catalog */ |
| 223 | $visible_tooltip_text = __( 'Hide from Facebook catalog. Currently synced with Facebook.', 'facebook-for-woocommerce' ); |
| 224 | /* translators: Action to publish a product (currently synced with Facebook) in the Facebook catalog */ |
| 225 | $hidden_tooltip_text = __( 'Publish in Facebook catalog. Currently synced with Facebook.', 'facebook-for-woocommerce' ); |
| 226 | } else { |
| 227 | /* translators: Action to hide a product (currently not synced with Facebook) from the Facebook catalog */ |
| 228 | $visible_tooltip_text = __( 'Hide from Facebook catalog. Not synced with Facebook.', 'facebook-for-woocommerce' ); |
| 229 | /* translators: Action to publish a product (currently not synced with Facebook) in the Facebook catalog */ |
| 230 | $hidden_tooltip_text = __( 'Publish in Facebook catalog. Not synced with Facebook.', 'facebook-for-woocommerce' ); |
| 231 | } |
| 232 | |
| 233 | ?> |
| 234 | <button |
| 235 | id="facebook-for-woocommerce-product-visibility-show-<?php echo esc_attr( $post->ID ); ?>" |
| 236 | class="button button-primary button-large facebook-for-woocommerce-product-visibility-toggle facebook-for-woocommerce-product-visibility-show" |
| 237 | style="<?php echo $is_hidden ? 'display:block;' : 'display:none;'; ?>" |
| 238 | data-action="show" |
| 239 | data-product-id="<?php echo esc_attr( $post->ID ); ?>" |
| 240 | title="<?php echo esc_attr( $hidden_tooltip_text ); ?>" |
| 241 | ><?php esc_html_e( 'Show', 'facebook-for-woocommerce' ); ?></button> |
| 242 | <button |
| 243 | id="facebook-for-woocommerce-product-visibility-hide-<?php echo esc_attr( $post->ID ); ?>" |
| 244 | class="button button-large facebook-for-woocommerce-product-visibility-toggle facebook-for-woocommerce-product-visibility-hide" |
| 245 | style="<?php echo $is_visible ? 'display:block;' : 'display:none;'; ?>" |
| 246 | data-action="hide" |
| 247 | data-product-id="<?php echo esc_attr( $post->ID ); ?>" |
| 248 | title="<?php echo esc_attr( $visible_tooltip_text ); ?>" |
| 249 | ><?php esc_html_e( 'Hide', 'facebook-for-woocommerce' ); ?></button> |
| 250 | <?php |
| 251 | |
| 252 | endif; |
| 253 | |
| 254 | endif; |
| 255 | } |
| 256 | |
| 257 | |
| 258 | /** |
| 259 | * Adds a dropdown input to let shop managers filter products by sync setting. |
| 260 | * |
| 261 | * @internal |
| 262 | * |
| 263 | * @since 1.10.0 |
| 264 | */ |
| 265 | public function add_products_by_sync_enabled_input_filter() { |
| 266 | global $typenow; |
| 267 | |
| 268 | if ( 'product' !== $typenow ) { |
| 269 | return; |
| 270 | } |
| 271 | |
| 272 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 273 | $choice = isset( $_GET['fb_sync_enabled'] ) ? (string) sanitize_text_field( wp_unslash( $_GET['fb_sync_enabled'] ) ) : ''; |
| 274 | |
| 275 | ?> |
| 276 | <select name="fb_sync_enabled"> |
| 277 | <option value="" <?php selected( $choice, '' ); ?>><?php esc_html_e( 'Filter by Facebook sync setting', 'facebook-for-woocommerce' ); ?></option> |
| 278 | <option value="yes" <?php selected( $choice, 'yes' ); ?>><?php esc_html_e( 'Facebook sync enabled', 'facebook-for-woocommerce' ); ?></option> |
| 279 | <option value="no" <?php selected( $choice, 'no' ); ?>><?php esc_html_e( 'Facebook sync disabled', 'facebook-for-woocommerce' ); ?></option> |
| 280 | </select> |
| 281 | <?php |
| 282 | } |
| 283 | |
| 284 | |
| 285 | /** |
| 286 | * Filters products by Facebook sync setting. |
| 287 | * |
| 288 | * @internal |
| 289 | * |
| 290 | * @since 1.10.0 |
| 291 | * |
| 292 | * @param array $query_vars product query vars for the edit screen |
| 293 | * @return array |
| 294 | */ |
| 295 | public function filter_products_by_sync_enabled( $query_vars ) { |
| 296 | |
| 297 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 298 | if ( isset( $_REQUEST['fb_sync_enabled'] ) && in_array( $_REQUEST['fb_sync_enabled'], [ 'yes', 'no' ], true ) ) { |
| 299 | |
| 300 | // by default use an "AND" clause if multiple conditions exist for a meta query |
| 301 | if ( ! empty( $query_vars['meta_query'] ) ) { |
| 302 | $query_vars['meta_query']['relation'] = 'AND'; |
| 303 | } else { |
| 304 | $query_vars['meta_query'] = []; |
| 305 | } |
| 306 | |
| 307 | // when checking for products with sync enabled we need to check both "yes" and meta not set, this requires adding an "OR" clause |
| 308 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 309 | if ( 'yes' === $_REQUEST['fb_sync_enabled'] ) { |
| 310 | |
| 311 | $query_vars = $this->add_query_vars_to_find_products_with_sync_enabled( $query_vars ); |
| 312 | |
| 313 | // since we record enabled status on child variations, we may need to query variable products found for their children to exclude them from query results |
| 314 | $exclude_products = []; |
| 315 | $found_ids = get_posts( array_merge( $query_vars, [ 'fields' => 'ids' ] ) ); |
| 316 | $found_products = empty( $found_ids ) ? [] : wc_get_products( [ |
| 317 | 'limit' => -1, |
| 318 | 'type' => 'variable', |
| 319 | 'include' => $found_ids, |
| 320 | ] ); |
| 321 | |
| 322 | /** @var \WC_Product[] $found_products */ |
| 323 | foreach ( $found_products as $product ) { |
| 324 | |
| 325 | if ( ! Products::is_sync_enabled_for_product( $product ) ) { |
| 326 | $exclude_products[] = $product->get_id(); |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | if ( ! empty( $exclude_products ) ) { |
| 331 | if ( ! empty( $query_vars['post__not_in'] ) ) { |
| 332 | $query_vars['post__not_in'] = array_merge( $query_vars['post__not_in'], $exclude_products ); |
| 333 | } else { |
| 334 | $query_vars['post__not_in'] = $exclude_products; |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | } else { |
| 339 | |
| 340 | $integration = facebook_for_woocommerce()->get_integration(); |
| 341 | $excluded_categories_ids = $integration ? $integration->get_excluded_product_category_ids() : []; |
| 342 | $excluded_tags_ids = $integration ? $integration->get_excluded_product_tag_ids() : []; |
| 343 | |
| 344 | if ( $excluded_categories_ids || $excluded_tags_ids ) { |
| 345 | |
| 346 | // find the IDs of products that have sync enabled |
| 347 | $products_query_vars = [ |
| 348 | 'post_type' => 'product', |
| 349 | 'post_status' => ! empty( $query_vars['post_status'] ) ? $query_vars['post_status'] : 'any', |
| 350 | 'no_found_rows' => true, |
| 351 | 'update_post_meta_cache' => false, |
| 352 | 'update_post_term_cache' => false, |
| 353 | 'fields' => 'ids', |
| 354 | 'nopaging' => true, |
| 355 | ]; |
| 356 | |
| 357 | $products_query_vars = $this->add_query_vars_to_find_products_with_sync_enabled( $products_query_vars ); |
| 358 | |
| 359 | // exclude products that have sync enabled from the current query |
| 360 | $query_vars['post__not_in'] = get_posts( $products_query_vars ); |
| 361 | |
| 362 | } else { |
| 363 | |
| 364 | $query_vars['meta_query'][] = [ |
| 365 | 'key' => Products::SYNC_ENABLED_META_KEY, |
| 366 | 'value' => 'no', |
| 367 | ]; |
| 368 | } |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | return $query_vars; |
| 373 | } |
| 374 | |
| 375 | |
| 376 | /** |
| 377 | * Adds query vars to limit the results to products that have sync enabled. |
| 378 | * |
| 379 | * @since 1.10.0 |
| 380 | * |
| 381 | * @param array $query_vars |
| 382 | * @return array |
| 383 | */ |
| 384 | private function add_query_vars_to_find_products_with_sync_enabled( array $query_vars ) { |
| 385 | |
| 386 | $query_vars['meta_query']['relation'] = 'OR'; |
| 387 | $query_vars['meta_query'][] = [ |
| 388 | 'key' => Products::SYNC_ENABLED_META_KEY, |
| 389 | 'value' => 'yes', |
| 390 | ]; |
| 391 | $query_vars['meta_query'][] = [ |
| 392 | 'key' => Products::SYNC_ENABLED_META_KEY, |
| 393 | 'compare' => 'NOT EXISTS', |
| 394 | ]; |
| 395 | |
| 396 | // check whether the product belongs to an excluded product category or tag |
| 397 | $query_vars = $this->maybe_add_tax_query_for_excluded_taxonomies( $query_vars ); |
| 398 | |
| 399 | return $query_vars; |
| 400 | } |
| 401 | |
| 402 | |
| 403 | /** |
| 404 | * Adds a tax query to filter out products in excluded product categories and product tags. |
| 405 | * |
| 406 | * @since 1.10.0 |
| 407 | * |
| 408 | * @param array $query_vars product query vars for the edit screen |
| 409 | * @return array |
| 410 | */ |
| 411 | private function maybe_add_tax_query_for_excluded_taxonomies( $query_vars ) { |
| 412 | |
| 413 | $integration = facebook_for_woocommerce()->get_integration(); |
| 414 | |
| 415 | if ( $integration ) { |
| 416 | |
| 417 | $tax_query = []; |
| 418 | $excluded_categories_ids = $integration->get_excluded_product_category_ids(); |
| 419 | |
| 420 | if ( $excluded_categories_ids ) { |
| 421 | $tax_query[] = [ |
| 422 | 'taxonomy' => 'product_cat', |
| 423 | 'terms' => $excluded_categories_ids, |
| 424 | 'field' => 'term_id', |
| 425 | 'operator' => 'NOT IN', |
| 426 | ]; |
| 427 | } |
| 428 | |
| 429 | $excluded_tags_ids = $integration->get_excluded_product_tag_ids(); |
| 430 | |
| 431 | if ( $excluded_tags_ids ) { |
| 432 | $tax_query[] = [ |
| 433 | 'taxonomy' => 'product_tag', |
| 434 | 'terms' => $excluded_tags_ids, |
| 435 | 'field' => 'term_id', |
| 436 | 'operator' => 'NOT IN', |
| 437 | ]; |
| 438 | } |
| 439 | |
| 440 | if ( $tax_query && empty( $query_vars['tax_query'] ) ) { |
| 441 | $query_vars['tax_query'] = $tax_query; |
| 442 | } elseif ( $tax_query && is_array( $query_vars ) ) { |
| 443 | $query_vars['tax_query'][] = $tax_query; |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | return $query_vars; |
| 448 | } |
| 449 | |
| 450 | |
| 451 | /** |
| 452 | * Adds bulk actions in the products edit screen. |
| 453 | * |
| 454 | * @internal |
| 455 | * |
| 456 | * @since 1.10.0 |
| 457 | * |
| 458 | * @param array $bulk_actions array of bulk action keys and labels |
| 459 | * @return array |
| 460 | */ |
| 461 | public function add_products_sync_bulk_actions( $bulk_actions ) { |
| 462 | |
| 463 | $bulk_actions['facebook_include'] = __( 'Include in Facebook sync', 'facebook-for-woocommerce' ); |
| 464 | $bulk_actions['facebook_exclude'] = __( 'Exclude from Facebook sync', 'facebook-for-woocommerce' ); |
| 465 | |
| 466 | return $bulk_actions; |
| 467 | } |
| 468 | |
| 469 | |
| 470 | /** |
| 471 | * Handles a Facebook product sync bulk action. |
| 472 | * |
| 473 | * @internal |
| 474 | * |
| 475 | * @since 1.10.0 |
| 476 | * |
| 477 | * @param string $redirect admin URL used by WordPress to redirect after performing the bulk action |
| 478 | * @return string |
| 479 | */ |
| 480 | public function handle_products_sync_bulk_actions( $redirect ) { |
| 481 | |
| 482 | // primary dropdown at the top of the list table |
| 483 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 484 | $action = isset( $_REQUEST['action'] ) && -1 !== (int) $_REQUEST['action'] ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : null; |
| 485 | |
| 486 | // secondary dropdown at the bottom of the list table |
| 487 | if ( ! $action ) { |
| 488 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 489 | $action = isset( $_REQUEST['action2'] ) && -1 !== (int) $_REQUEST['action2'] ? sanitize_text_field( wp_unslash( $_REQUEST['action2'] ) ) : null; |
| 490 | } |
| 491 | |
| 492 | if ( $action && in_array( $action, [ 'facebook_include', 'facebook_exclude' ], true ) ) { |
| 493 | |
| 494 | $products = []; |
| 495 | |
| 496 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 497 | $product_ids = isset( $_REQUEST['post'] ) && is_array( $_REQUEST['post'] ) ? array_map( 'absint', $_REQUEST['post'] ) : []; |
| 498 | |
| 499 | if ( ! empty( $product_ids ) ) { |
| 500 | |
| 501 | $is_enabling_sync_virtual_products = false; |
| 502 | $is_enabling_sync_virtual_variations = false; |
| 503 | |
| 504 | foreach ( $product_ids as $product_id ) { |
| 505 | |
| 506 | if ( $product = wc_get_product( $product_id ) ) { |
| 507 | |
| 508 | if ( 'facebook_include' === $action ) { |
| 509 | |
| 510 | if ( $product->is_virtual() ) { |
| 511 | |
| 512 | $is_enabling_sync_virtual_products = true; |
| 513 | |
| 514 | } else { |
| 515 | |
| 516 | // products with virtual variations are also added to the list, |
| 517 | // because they may have non-virtual variations as well |
| 518 | $products[] = $product; |
| 519 | |
| 520 | if ( $product->is_type( 'variable' ) ) { |
| 521 | |
| 522 | // check if the product has virtual variations, to display notice |
| 523 | foreach ( $product->get_children() as $variation_id ) { |
| 524 | |
| 525 | $variation = wc_get_product( $variation_id ); |
| 526 | |
| 527 | if ( $variation && $variation->is_virtual() ) { |
| 528 | |
| 529 | $is_enabling_sync_virtual_variations = true; |
| 530 | break; |
| 531 | } |
| 532 | } |
| 533 | } |
| 534 | } |
| 535 | } else { |
| 536 | |
| 537 | // add the product to the list of products to disable sync from |
| 538 | $products[] = $product; |
| 539 | } |
| 540 | } |
| 541 | } |
| 542 | |
| 543 | // display notice if enabling sync for virtual products or variations |
| 544 | if ( $is_enabling_sync_virtual_products || $is_enabling_sync_virtual_variations ) { |
| 545 | |
| 546 | $redirect = add_query_arg( [ 'enabling_virtual_products_sync' => 1 ], $redirect ); |
| 547 | } |
| 548 | |
| 549 | if ( 'facebook_include' === $action ) { |
| 550 | |
| 551 | Products::enable_sync_for_products( $products ); |
| 552 | |
| 553 | // re-sync each product |
| 554 | foreach ( $products as $product ) { |
| 555 | facebook_for_woocommerce()->get_integration()->on_product_publish( $product->get_id() ); |
| 556 | } |
| 557 | |
| 558 | } elseif ( 'facebook_exclude' === $action ) { |
| 559 | |
| 560 | Products::disable_sync_for_products( $products ); |
| 561 | } |
| 562 | } |
| 563 | } |
| 564 | |
| 565 | return $redirect; |
| 566 | } |
| 567 | |
| 568 | |
| 569 | /** |
| 570 | * Prints a notice on products page in case the current cart URL is not the original sync URL. |
| 571 | * |
| 572 | * @internal |
| 573 | * |
| 574 | * TODO: update this method to use the notice handler once we framework the plugin {CW 2020-01-09} |
| 575 | * |
| 576 | * @since 1.10.0 |
| 577 | */ |
| 578 | public function validate_cart_url() { |
| 579 | global $current_screen; |
| 580 | |
| 581 | if ( isset( $current_screen->id ) && in_array( $current_screen->id, [ 'edit-product', 'product' ], true ) ) : |
| 582 | |
| 583 | $cart_url = get_option( \WC_Facebookcommerce_Integration::FB_CART_URL, '' ); |
| 584 | |
| 585 | if ( ! empty( $cart_url ) && $cart_url !== wc_get_cart_url() ) : |
| 586 | |
| 587 | ?> |
| 588 | <div class="notice notice-warning"> |
| 589 | <?php printf( |
| 590 | /* translators: Placeholders: %1$s - Facebook for Woocommerce, %2$s - opening HTML <a> link tag, %3$s - closing HTML </a> link tag */ |
| 591 | '<p>' . esc_html__( '%1$s: One or more of your products is using a checkout URL that may be different than your shop checkout URL. %2$sRe-sync your products to update checkout URLs on Facebook%3$s.', 'facebook-for-woocommerce' ) . '</p>', |
| 592 | '<strong>' . esc_html__( 'Facebook for WooCommerce', 'facebook-for-woocommerce' ) . '</strong>', |
| 593 | '<a href="' . esc_url( WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL ) . '">', |
| 594 | '</a>' |
| 595 | ); ?> |
| 596 | </div> |
| 597 | <?php |
| 598 | |
| 599 | endif; |
| 600 | |
| 601 | endif; |
| 602 | } |
| 603 | |
| 604 | |
| 605 | /** |
| 606 | * Prints a notice on products page to inform users about changes in product sync. |
| 607 | * |
| 608 | * @internal |
| 609 | * |
| 610 | * @since 1.11.0 |
| 611 | */ |
| 612 | public function add_product_sync_delay_notice() { |
| 613 | global $current_screen; |
| 614 | |
| 615 | $transient_name = 'wc_' . facebook_for_woocommerce()->get_id() . '_show_product_sync_delay_notice_' . get_current_user_id(); |
| 616 | |
| 617 | if ( isset( $current_screen->id ) && in_array( $current_screen->id, [ 'edit-product', 'product' ], true ) && get_transient( $transient_name ) ) { |
| 618 | |
| 619 | if ( isset( $_GET['message'] ) || isset( $_GET['trashed'] ) || isset( $_GET['deleted'] ) ) { |
| 620 | |
| 621 | facebook_for_woocommerce()->get_admin_notice_handler()->add_admin_notice( |
| 622 | sprintf( |
| 623 | /* translators: Placeholders: %1$s - opening HTML <strong> tag, %2$s - closing HTML </strong> tag, %3$s - opening HTML <a> tag, %4$s - closing HTML </a> tag */ |
| 624 | esc_html__( '%1$sHeads up!%2$s Product sync is temporarily changed as we migrate to a more secure experience. An automated sync from Facebook will run every hour to update the catalog with any changes you\'ve made. %3$sLearn more%4$s', 'facebook-for-woocommerce' ), |
| 625 | '<strong>', |
| 626 | '</strong>', |
| 627 | '<a href="https://docs.woocommerce.com/document/facebook-for-woocommerce/#faq-security" target="_blank">', |
| 628 | '</a>' |
| 629 | ) |
| 630 | . '</p><p>' // close notice paragraph and open a new one for the button |
| 631 | . '<button class="button notice-dismiss-permanently" type="button">' . esc_html__( "Don't show this notice again", 'facebook-for-woocommerce' ) . '</button>', |
| 632 | 'wc-' . facebook_for_woocommerce()->get_id_dasherized() . '-product-sync-delay', |
| 633 | [ 'notice_class' => 'notice-info' ] |
| 634 | ); |
| 635 | } |
| 636 | |
| 637 | delete_transient( $transient_name ); |
| 638 | } |
| 639 | } |
| 640 | |
| 641 | |
| 642 | /** |
| 643 | * Handles dismissed notices. |
| 644 | * |
| 645 | * @internal |
| 646 | * |
| 647 | * @since 1.11.0 |
| 648 | * |
| 649 | * @param string $message_id the dismissed notice ID |
| 650 | * @param int $user_id the ID of the user the noticed was dismissed for |
| 651 | */ |
| 652 | public function handle_dismiss_notice( $message_id, $user_id = null ) { |
| 653 | |
| 654 | // undismiss product sync delay notice unless 'permanently' is included in the request |
| 655 | if ( ! SV_WC_Helper::get_requested_value( 'permanently' ) && 'wc-' . facebook_for_woocommerce()->get_id_dasherized() . '-product-sync-delay' === $message_id ) { |
| 656 | |
| 657 | facebook_for_woocommerce()->get_admin_notice_handler()->undismiss_notice( $message_id, $user_id ); |
| 658 | } |
| 659 | } |
| 660 | |
| 661 | |
| 662 | /** |
| 663 | * Prints a notice on products page to inform users that catalog visibility settings were removed. |
| 664 | * |
| 665 | * @internal |
| 666 | * |
| 667 | * @since 1.11.0 |
| 668 | */ |
| 669 | public function add_catalog_visibility_settings_removed_notice() { |
| 670 | global $current_screen; |
| 671 | |
| 672 | if ( isset( $current_screen->id ) && in_array( $current_screen->id, [ 'edit-product', 'product' ], true ) ) { |
| 673 | |
| 674 | facebook_for_woocommerce()->get_admin_notice_handler()->add_admin_notice( |
| 675 | sprintf( |
| 676 | /* translators: Placeholders: %1$s - opening HTML <strong> tag, %2$s - closing HTML </strong> tag, %3$s - opening HTML <a> tag, %4$s - closing HTML </a> tag */ |
| 677 | esc_html__( '%1$sHeads up!%2$s Catalog visibility settings have been temporarily removed as we migrate to a more secure experience. To remove products from your Facebook catalog, please disable syncing to Facebook for the product. %3$sLearn more%4$s', 'facebook-for-woocommerce' ), |
| 678 | '<strong>', |
| 679 | '</strong>', |
| 680 | '<a href="https://docs.woocommerce.com/document/facebook-for-woocommerce/#section-10" target="_blank">', // TODO: add link to FAQ entry {WV 2020-03-30} |
| 681 | '</a>' |
| 682 | ), |
| 683 | 'wc-' . facebook_for_woocommerce()->get_id_dasherized() . '-catalog-visibility-settings-removed', |
| 684 | [ 'notice_class' => 'notice-info' ] |
| 685 | ); |
| 686 | } |
| 687 | } |
| 688 | |
| 689 | |
| 690 | /** |
| 691 | * Prints a notice on products page to inform users that the virtual products selected for the Include bulk action will NOT have sync enabled. |
| 692 | * |
| 693 | * @internal |
| 694 | * |
| 695 | * @since 1.11.3-dev.2 |
| 696 | */ |
| 697 | public function add_enabling_virtual_products_sync_notice() { |
| 698 | global $current_screen; |
| 699 | |
| 700 | if ( isset( $_GET['enabling_virtual_products_sync'] ) && isset( $current_screen->id ) && 'edit-product' === $current_screen->id ) { |
| 701 | |
| 702 | facebook_for_woocommerce()->get_admin_notice_handler()->add_admin_notice( |
| 703 | sprintf( |
| 704 | /* translators: Placeholders: %1$s - opening HTML <strong> tag, %2$s - closing HTML </strong> tag, %3$s - opening HTML <a> tag, %4$s - closing HTML </a> tag */ |
| 705 | esc_html__( '%1$sHeads up!%2$s Facebook does not support selling virtual products, so we can\'t include virtual products in your catalog sync. %3$sClick here to read more about Facebook\'s policy%4$s.', 'facebook-for-woocommerce' ), |
| 706 | '<strong>', |
| 707 | '</strong>', |
| 708 | '<a href="https://www.facebook.com/help/130910837313345" target="_blank">', |
| 709 | '</a>' |
| 710 | ), |
| 711 | 'wc-' . facebook_for_woocommerce()->get_id_dasherized() . '-enabling-virtual-products-sync', |
| 712 | [ 'notice_class' => 'notice-info' ] |
| 713 | ); |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | |
| 718 | /** |
| 719 | * Prints a notice to inform sync has been automatically disabled for virtual products. |
| 720 | * |
| 721 | * @internal |
| 722 | * |
| 723 | * @since 1.11.3-dev.2 |
| 724 | */ |
| 725 | public function add_disabled_virtual_products_sync_notice() { |
| 726 | |
| 727 | if ( 'yes' === get_option( 'wc_facebook_sync_virtual_products_disabled', 'no' ) && |
| 728 | 'yes' !== get_option( 'wc_facebook_sync_virtual_products_disabled_skipped', 'no' ) ) { |
| 729 | |
| 730 | facebook_for_woocommerce()->get_admin_notice_handler()->add_admin_notice( |
| 731 | sprintf( |
| 732 | /* translators: Placeholders: %1$s - opening HTML <strong> tag, %2$s - closing HTML </strong> tag, %3$s - opening HTML <a> tag, %4$s - closing HTML </a> tag */ |
| 733 | esc_html__( '%1$sHeads up!%2$s Facebook does not support selling virtual products, so we have removed any previously synced virtual products from the catalog sync going forward. %3$sClick here to read more about Facebook\'s policy%4$s.', 'facebook-for-woocommerce' ), |
| 734 | '<strong>', |
| 735 | '</strong>', |
| 736 | '<a href="https://www.facebook.com/help/130910837313345" target="_blank">', |
| 737 | '</a>' |
| 738 | ), |
| 739 | 'wc-' . facebook_for_woocommerce()->get_id_dasherized() . '-disabled-virtual-products-sync', |
| 740 | [ 'notice_class' => 'notice-info' ] |
| 741 | ); |
| 742 | } |
| 743 | } |
| 744 | |
| 745 | |
| 746 | /** |
| 747 | * Adds a new tab to the Product edit page. |
| 748 | * |
| 749 | * @internal |
| 750 | * |
| 751 | * @since 1.10.0 |
| 752 | * |
| 753 | * @param array $tabs product tabs |
| 754 | * @return array |
| 755 | */ |
| 756 | public function add_product_settings_tab( $tabs ) { |
| 757 | |
| 758 | $tabs['fb_commerce_tab'] = [ |
| 759 | 'label' => __( 'Facebook', 'facebook-for-woocommerce' ), |
| 760 | 'target' => 'facebook_options', |
| 761 | 'class' => [ 'show_if_simple', 'hide_if_virtual' ], |
| 762 | ]; |
| 763 | |
| 764 | return $tabs; |
| 765 | } |
| 766 | |
| 767 | |
| 768 | /** |
| 769 | * Adds content to the new Facebook tab on the Product edit page. |
| 770 | * |
| 771 | * @internal |
| 772 | * |
| 773 | * @since 1.10.0 |
| 774 | */ |
| 775 | public function add_product_settings_tab_content() { |
| 776 | global $post; |
| 777 | |
| 778 | // all products have sync enabled unless explicitly disabled |
| 779 | $sync_enabled = 'no' !== get_post_meta( $post->ID, Products::SYNC_ENABLED_META_KEY, true ); |
| 780 | $description = get_post_meta( $post->ID, \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, true ); |
| 781 | $price = get_post_meta( $post->ID, \WC_Facebook_Product::FB_PRODUCT_PRICE, true ); |
| 782 | $image_source = get_post_meta( $post->ID, Products::PRODUCT_IMAGE_SOURCE_META_KEY, true ); |
| 783 | $image = get_post_meta( $post->ID, \WC_Facebook_Product::FB_PRODUCT_IMAGE, true ); |
| 784 | |
| 785 | // 'id' attribute needs to match the 'target' parameter set above |
| 786 | ?> |
| 787 | <div id='facebook_options' class='panel woocommerce_options_panel'> |
| 788 | <div class='options_group'> |
| 789 | <?php |
| 790 | |
| 791 | woocommerce_wp_checkbox( [ |
| 792 | 'id' => 'fb_sync_enabled', |
| 793 | 'label' => __( 'Include in Facebook sync', 'facebook-for-woocommerce' ), |
| 794 | 'value' => wc_bool_to_string( (bool) $sync_enabled ), |
| 795 | ] ); |
| 796 | |
| 797 | woocommerce_wp_textarea_input( [ |
| 798 | 'id' => \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, |
| 799 | 'label' => __( 'Facebook Description', 'facebook-for-woocommerce' ), |
| 800 | 'desc_tip' => true, |
| 801 | 'description' => __( 'Custom (plain-text only) description for product on Facebook. If blank, product description will be used. If product description is blank, shortname will be used.', 'facebook-for-woocommerce' ), |
| 802 | 'cols' => 40, |
| 803 | 'rows' => 20, |
| 804 | 'value' => $description, |
| 805 | 'class' => 'enable-if-sync-enabled', |
| 806 | ] ); |
| 807 | |
| 808 | woocommerce_wp_radio( [ |
| 809 | 'id' => 'fb_product_image_source', |
| 810 | 'label' => __( 'Facebook Product Image', 'facebook-for-woocommerce' ), |
| 811 | 'desc_tip' => true, |
| 812 | 'description' => __( 'Choose the product image that should be synced to the Facebook catalog for this product. If using a custom image, please enter an absolute URL (e.g. https://domain.com/image.jpg).', 'facebook-for-woocommerce' ), |
| 813 | 'options' => [ |
| 814 | Products::PRODUCT_IMAGE_SOURCE_PRODUCT => __( 'Use WooCommerce image', 'facebook-for-woocommerce' ), |
| 815 | Products::PRODUCT_IMAGE_SOURCE_CUSTOM => __( 'Use custom image', 'facebook-for-woocommerce' ), |
| 816 | ], |
| 817 | 'value' => $image_source ?: Products::PRODUCT_IMAGE_SOURCE_PRODUCT, |
| 818 | 'class' => 'short enable-if-sync-enabled js-fb-product-image-source', |
| 819 | 'wrapper_class' => 'fb-product-image-source-field', |
| 820 | ] ); |
| 821 | |
| 822 | woocommerce_wp_text_input( [ |
| 823 | 'id' => \WC_Facebook_Product::FB_PRODUCT_IMAGE, |
| 824 | 'label' => __( 'Custom Image URL', 'facebook-for-woocommerce' ), |
| 825 | 'value' => $image, |
| 826 | 'class' => sprintf( 'enable-if-sync-enabled product-image-source-field show-if-product-image-source-%s', Products::PRODUCT_IMAGE_SOURCE_CUSTOM ), |
| 827 | ] ); |
| 828 | |
| 829 | woocommerce_wp_text_input( [ |
| 830 | 'id' => \WC_Facebook_Product::FB_PRODUCT_PRICE, |
| 831 | 'label' => sprintf( |
| 832 | /* translators: Placeholders %1$s - WC currency symbol */ |
| 833 | __( 'Facebook Price (%1$s)', 'facebook-for-woocommerce' ), |
| 834 | get_woocommerce_currency_symbol() |
| 835 | ), |
| 836 | 'desc_tip' => true, |
| 837 | 'description' => __( 'Custom price for product on Facebook. Please enter in monetary decimal (.) format without thousand separators and currency symbols. If blank, product price will be used.', 'facebook-for-woocommerce' ), |
| 838 | 'cols' => 40, |
| 839 | 'rows' => 60, |
| 840 | 'value' => $price, |
| 841 | 'class' => 'enable-if-sync-enabled', |
| 842 | ] ); |
| 843 | |
| 844 | ?> |
| 845 | </div> |
| 846 | </div> |
| 847 | <?php |
| 848 | } |
| 849 | |
| 850 | |
| 851 | /** |
| 852 | * Outputs the Facebook settings fields for a single variation. |
| 853 | * |
| 854 | * @internal |
| 855 | * |
| 856 | * @since 1.10.0 |
| 857 | * |
| 858 | * @param int $index the index of the current variation |
| 859 | * @param array $variation_data unused |
| 860 | * @param \WC_Post $post the post type for the current variation |
| 861 | */ |
| 862 | public function add_product_variation_edit_fields( $index, $variation_data, $post ) { |
| 863 | |
| 864 | $variation = wc_get_product( $post ); |
| 865 | |
| 866 | if ( ! $variation instanceof \WC_Product_Variation ) { |
| 867 | return; |
| 868 | } |
| 869 | |
| 870 | $parent = wc_get_product( $variation->get_parent_id() ); |
| 871 | |
| 872 | if ( ! $parent instanceof \WC_Product ) { |
| 873 | return; |
| 874 | } |
| 875 | |
| 876 | $sync_enabled = $this->get_product_variation_meta( $variation, Products::SYNC_ENABLED_META_KEY, $parent ); |
| 877 | $description = $this->get_product_variation_meta( $variation, \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $parent ); |
| 878 | $price = $this->get_product_variation_meta( $variation, \WC_Facebook_Product::FB_PRODUCT_PRICE, $parent ); |
| 879 | $image_url = $this->get_product_variation_meta( $variation, \WC_Facebook_Product::FB_PRODUCT_IMAGE, $parent ); |
| 880 | $image_source = $variation->get_meta( Products::PRODUCT_IMAGE_SOURCE_META_KEY ); |
| 881 | |
| 882 | woocommerce_wp_checkbox( [ |
| 883 | 'id' => "variable_fb_sync_enabled$index", |
| 884 | 'name' => "variable_fb_sync_enabled[$index]", |
| 885 | 'label' => __( 'Include in Facebook sync', 'facebook-for-woocommerce' ), |
| 886 | 'value' => wc_bool_to_string( 'no' !== $sync_enabled ), |
| 887 | 'class' => 'checkbox js-variable-fb-sync-toggle', |
| 888 | 'wrapper_class' => 'fb-sync-enabled-field hide_if_variation_virtual', |
| 889 | ] ); |
| 890 | |
| 891 | woocommerce_wp_textarea_input( [ |
| 892 | 'id' => sprintf( 'variable_%s%s', \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $index ), |
| 893 | 'name' => sprintf( "variable_%s[$index]", \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION ), |
| 894 | 'label' => __( 'Facebook Description', 'facebook-for-woocommerce' ), |
| 895 | 'desc_tip' => true, |
| 896 | 'description' => __( 'Custom (plain-text only) description for product on Facebook. If blank, product description will be used. If product description is blank, shortname will be used.', 'facebook-for-woocommerce' ), |
| 897 | 'cols' => 40, |
| 898 | 'rows' => 5, |
| 899 | 'value' => $description, |
| 900 | 'class' => 'enable-if-sync-enabled', |
| 901 | 'wrapper_class' => 'form-row form-row-full hide_if_variation_virtual', |
| 902 | ] ); |
| 903 | |
| 904 | woocommerce_wp_radio( [ |
| 905 | 'id' => "variable_fb_product_image_source$index", |
| 906 | 'name' => "variable_fb_product_image_source[$index]", |
| 907 | 'label' => __( 'Facebook Product Image', 'facebook-for-woocommerce' ), |
| 908 | 'desc_tip' => true, |
| 909 | 'description' => __( 'Choose the product image that should be synced to the Facebook catalog for this product. If using a custom image, please enter an absolute URL (e.g. https://domain.com/image.jpg).', 'facebook-for-woocommerce' ), |
| 910 | 'options' => [ |
| 911 | Products::PRODUCT_IMAGE_SOURCE_PRODUCT => __( 'Use variation image', 'facebook-for-woocommerce' ), |
| 912 | Products::PRODUCT_IMAGE_SOURCE_PARENT_PRODUCT => __( 'Use parent image', 'facebook-for-woocommerce' ), |
| 913 | Products::PRODUCT_IMAGE_SOURCE_CUSTOM => __( 'Use custom image', 'facebook-for-woocommerce' ), |
| 914 | ], |
| 915 | 'value' => $image_source ?: Products::PRODUCT_IMAGE_SOURCE_PRODUCT, |
| 916 | 'class' => 'enable-if-sync-enabled js-fb-product-image-source', |
| 917 | 'wrapper_class' => 'fb-product-image-source-field hide_if_variation_virtual', |
| 918 | ] ); |
| 919 | |
| 920 | woocommerce_wp_text_input( [ |
| 921 | 'id' => sprintf( 'variable_%s%s', \WC_Facebook_Product::FB_PRODUCT_IMAGE, $index ), |
| 922 | 'name' => sprintf( "variable_%s[$index]", \WC_Facebook_Product::FB_PRODUCT_IMAGE ), |
| 923 | 'label' => __( 'Custom Image URL', 'facebook-for-woocommerce' ), |
| 924 | 'value' => $image_url, |
| 925 | 'class' => sprintf( 'enable-if-sync-enabled product-image-source-field show-if-product-image-source-%s', Products::PRODUCT_IMAGE_SOURCE_CUSTOM ), |
| 926 | 'wrapper_class' => 'form-row form-row-full hide_if_variation_virtual', |
| 927 | ] ); |
| 928 | |
| 929 | woocommerce_wp_text_input( [ |
| 930 | 'id' => sprintf( 'variable_%s%s', \WC_Facebook_Product::FB_PRODUCT_PRICE, $index ), |
| 931 | 'name' => sprintf( "variable_%s[$index]", \WC_Facebook_Product::FB_PRODUCT_PRICE ), |
| 932 | 'label' => sprintf( |
| 933 | /* translators: Placeholders %1$s - WC currency symbol */ |
| 934 | __( 'Facebook Price (%1$s)', 'facebook-for-woocommerce' ), |
| 935 | get_woocommerce_currency_symbol() |
| 936 | ), |
| 937 | 'desc_tip' => true, |
| 938 | 'description' => __( 'Custom price for product on Facebook. Please enter in monetary decimal (.) format without thousand separators and currency symbols. If blank, product price will be used.', 'facebook-for-woocommerce' ), |
| 939 | 'value' => wc_format_decimal( $price ), |
| 940 | 'class' => 'enable-if-sync-enabled', |
| 941 | 'wrapper_class' => 'form-row form-full hide_if_variation_virtual', |
| 942 | ] ); |
| 943 | } |
| 944 | |
| 945 | |
| 946 | /** |
| 947 | * Gets the stored value for the given meta of a product variation. |
| 948 | * |
| 949 | * If no value is found, we try to use the value stored in the parent product. |
| 950 | * |
| 951 | * @since 1.10.0 |
| 952 | * |
| 953 | * @param \WC_Product_Variation $variation the product variation |
| 954 | * @param string $key the name of the meta to retrieve |
| 955 | * @param \WC_Product $parent the parent product |
| 956 | * @return mixed |
| 957 | */ |
| 958 | private function get_product_variation_meta( $variation, $key, $parent ) { |
| 959 | |
| 960 | $value = $variation->get_meta( $key ); |
| 961 | |
| 962 | if ( '' === $value && $parent instanceof \WC_Product ) { |
| 963 | $value = $parent->get_meta( $key ); |
| 964 | } |
| 965 | |
| 966 | return $value; |
| 967 | } |
| 968 | |
| 969 | |
| 970 | /** |
| 971 | * Saves the submitted Facebook settings for each variation. |
| 972 | * |
| 973 | * @internal |
| 974 | * |
| 975 | * @since 1.10.0 |
| 976 | * |
| 977 | * @param int $variation_id the ID of the product variation being edited |
| 978 | * @param int $index the index of the current variation |
| 979 | */ |
| 980 | public function save_product_variation_edit_fields( $variation_id, $index ) { |
| 981 | |
| 982 | $variation = wc_get_product( $variation_id ); |
| 983 | |
| 984 | if ( ! $variation instanceof \WC_Product_Variation ) { |
| 985 | return; |
| 986 | } |
| 987 | |
| 988 | // phpcs:disable WordPress.Security.NonceVerification.Missing |
| 989 | if ( ! $variation->is_virtual() && isset( $_POST['variable_fb_sync_enabled'][ $index ] ) && 'yes' === $_POST['variable_fb_sync_enabled'][ $index ] ) { |
| 990 | |
| 991 | Products::enable_sync_for_products( [ $variation ] ); |
| 992 | |
| 993 | $posted_param = 'variable_' . \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION; |
| 994 | $description = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_text_field( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null; |
| 995 | |
| 996 | $posted_param = 'variable_fb_product_image_source'; |
| 997 | $image_source = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_key( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : ''; |
| 998 | |
| 999 | $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_IMAGE; |
| 1000 | $image_url = isset( $_POST[ $posted_param ][ $index ] ) ? esc_url_raw( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null; |
| 1001 | |
| 1002 | $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_PRICE; |
| 1003 | $price = isset( $_POST[ $posted_param ][ $index ] ) ? wc_format_decimal( $_POST[ $posted_param ][ $index ] ) : ''; |
| 1004 | |
| 1005 | $variation->update_meta_data( \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $description ); |
| 1006 | $variation->update_meta_data( Products::PRODUCT_IMAGE_SOURCE_META_KEY, $image_source ); |
| 1007 | $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_IMAGE, $image_url ); |
| 1008 | $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_PRICE, $price ); |
| 1009 | $variation->save_meta_data(); |
| 1010 | |
| 1011 | } else { |
| 1012 | |
| 1013 | Products::disable_sync_for_products( [ $variation ] ); |
| 1014 | |
| 1015 | } |
| 1016 | // phpcs:enable WordPress.Security.NonceVerification.Missing |
| 1017 | } |
| 1018 | |
| 1019 | |
| 1020 | /** |
| 1021 | * Outputs a modal template in admin product pages. |
| 1022 | * |
| 1023 | * @internal |
| 1024 | * |
| 1025 | * @since 1.10.0 |
| 1026 | */ |
| 1027 | public function render_modal_template() { |
| 1028 | global $current_screen; |
| 1029 | |
| 1030 | // bail if not on the products, product edit, or settings screen |
| 1031 | if ( ! $current_screen || ! in_array( $current_screen->id, [ 'edit-product', 'product', 'woocommerce_page_wc-settings' ], true ) ) { |
| 1032 | return; |
| 1033 | } |
| 1034 | |
| 1035 | ?> |
| 1036 | <script type="text/template" id="tmpl-facebook-for-woocommerce-modal"> |
| 1037 | <div class="wc-backbone-modal facebook-for-woocommerce-modal"> |
| 1038 | <div class="wc-backbone-modal-content"> |
| 1039 | <section class="wc-backbone-modal-main" role="main"> |
| 1040 | <header class="wc-backbone-modal-header"> |
| 1041 | <h1><?php esc_html_e( 'Facebook for WooCommerce', 'facebook-for-woocommerce' ); ?></h1> |
| 1042 | <button class="modal-close modal-close-link dashicons dashicons-no-alt"> |
| 1043 | <span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'facebook-for-woocommerce' ); ?></span> |
| 1044 | </button> |
| 1045 | </header> |
| 1046 | <article>{{{data.message}}}</article> |
| 1047 | <footer> |
| 1048 | <div class="inner">{{{data.buttons}}}</div> |
| 1049 | </footer> |
| 1050 | </section> |
| 1051 | </div> |
| 1052 | </div> |
| 1053 | <div class="wc-backbone-modal-backdrop modal-close"></div> |
| 1054 | </script> |
| 1055 | <?php |
| 1056 | } |
| 1057 | |
| 1058 | |
| 1059 | /** |
| 1060 | * Maybe triggers the modal to open on the product edit screen on page load. |
| 1061 | * |
| 1062 | * If the product is set to be synced in Facebook, but belongs to a term that is set to be excluded, the modal prompts the merchant for action. |
| 1063 | * |
| 1064 | * @internal |
| 1065 | * |
| 1066 | * @since 1.10.0 |
| 1067 | */ |
| 1068 | public function validate_product_excluded_terms() { |
| 1069 | global $current_screen, $post; |
| 1070 | |
| 1071 | if ( $post && $current_screen && $current_screen->id === 'product' ) : |
| 1072 | |
| 1073 | $product = wc_get_product( $post ); |
| 1074 | |
| 1075 | if ( $product instanceof \WC_Product |
| 1076 | && Products::is_sync_enabled_for_product( $product ) |
| 1077 | && Products::is_sync_excluded_for_product_terms( $product ) |
| 1078 | ) : |
| 1079 | |
| 1080 | ?> |
| 1081 | <script type="text/javascript"> |
| 1082 | jQuery( document ).ready( function( $ ) { |
| 1083 | |
| 1084 | var productID = parseInt( $( 'input#post_ID' ).val(), 10 ), |
| 1085 | productTag = $( 'textarea[name=\"tax_input[product_tag]\"]' ).val().split( ',' ), |
| 1086 | productCat = []; |
| 1087 | |
| 1088 | $( '#taxonomy-product_cat input[name=\"tax_input[product_cat][]\"]:checked' ).each( function() { |
| 1089 | productCat.push( parseInt( $( this ).val(), 10 ) ); |
| 1090 | } ); |
| 1091 | |
| 1092 | $.post( facebook_for_woocommerce_products_admin.ajax_url, { |
| 1093 | action: 'facebook_for_woocommerce_set_product_sync_prompt', |
| 1094 | security: facebook_for_woocommerce_products_admin.set_product_sync_prompt_nonce, |
| 1095 | sync_enabled: 'enabled', |
| 1096 | product: productID, |
| 1097 | categories: productCat, |
| 1098 | tags: productTag |
| 1099 | }, function( response ) { |
| 1100 | |
| 1101 | if ( response && ! response.success ) { |
| 1102 | |
| 1103 | $( '#wc-backbone-modal-dialog .modal-close' ).trigger( 'click' ); |
| 1104 | |
| 1105 | new $.WCBackboneModal.View( { |
| 1106 | target: 'facebook-for-woocommerce-modal', |
| 1107 | string: response.data |
| 1108 | } ); |
| 1109 | } |
| 1110 | } ); |
| 1111 | } ); |
| 1112 | </script> |
| 1113 | <?php |
| 1114 | |
| 1115 | endif; |
| 1116 | |
| 1117 | endif; |
| 1118 | } |
| 1119 | |
| 1120 | |
| 1121 | } |
| 1122 |