Forum Replies Created

Viewing 15 replies - 1 through 15 (of 23 total)
  • Sounds promising! An AJAX callback to only search for specific posts given a search term would resolve the problem.

    Any ETA on the new release? We’re trying to determine how to move forward with the project.

    Another issue touching on the same topic:
    https://github.com/woocommerce/woocommerce/issues/33988

    The cause for this are the WooCommerce Onboarding Tasks. Possibly only in combination with translation plugins like WPML.

    The Onboarding Tasks are performing slow queries to determine e.g. whether the shop has any physical products.

    Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Shipping::has_physical_products() looks innocent like this:

    
    			count(
    				wc_get_products(
    					array(
    						'virtual' => false,
    						'limit'   => 1,
    					)
    				)
    			)
    

    But the query turns into this monster:

    
    SELECT wp_posts.ID
    FROM wp_posts
    LEFT JOIN wp_term_relationships
    ON (wp_posts.ID = wp_term_relationships.object_id) JOIN wp_icl_translations wpml_translations
    ON wp_posts.ID = wpml_translations.element_id
    AND wpml_translations.element_type = CONCAT('post_', wp_posts.post_type)
    WHERE 1=1
    AND ( wp_term_relationships.term_taxonomy_id IN (102,103,104,105) )
    AND wp_posts.post_type = 'product'
    AND ((wp_posts.post_status = 'publish'))
    AND ( ( ( wpml_translations.language_code = 'de'
    OR 0 )
    AND wp_posts.post_type IN ('post','page','attachment','wp_block','wp_template','wp_template_part','wp_navigation','event','reference-highlight','reference','acf-field-group','product','product_variation','ep-pointer' ) )
    OR wp_posts.post_type NOT IN ('post','page','attachment','wp_block','wp_template','wp_template_part','wp_navigation','event','reference-highlight','reference','acf-field-group','product','product_variation','ep-pointer' ) )
    GROUP BY wp_posts.ID
    ORDER BY wp_posts.post_date DESC
    LIMIT 0, 1
    

    Each of these queries takes roughly 0.5 seconds on an (highly optimized) larger database.

    They are executed multiple times with different conditions for different tasks, so WooCommerce slows down the backend page rendering of all backend pages by several seconds in total.

    A recent version fixed the case of the Shipping Onboarding Task, but two other cases are still remaining:

    Products::has_products() – which is invoked twice!

    TaskLists::setup_tasks_remaining() invokes the Products onboarding task two times, so the same expensive query is run twice.

    The problem is also documented in this blog post: https://www.wpintense.com/2022/03/11/how-to-fix-slow-woocommerce-6-01-for-wp-admin-pages/

    As all of those queries appear to be count queries, maybe WooCommerce should look into using wp_count_posts() instead. At least WPML seems to implement special handling for count queries (as opposed to regular queries).

    What’s worst about this is that the WooCommerce Onboarding isn’t even visible on all backend pages – to my knowledge it only appears on the WordPress Dashboard.

    So in addition to fixing the problematic queries themselves, Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists::init() should really limit all of this to the Dashboard page only.

    In fact, it’s not clear to me why the starting point for TaskLists are the init and admin_init hooks – instead of the WooCommerce Onboarding admin meta box.

    The whole Onboarding code should not run at all if the admin meta box is not enabled.

    The problem still exists after updating all plugins to the latest versions.

    A subscription paid with SEPA / direct debit is immediately moved to cancelled instead of pending-cancellation.

    This is happening with the WooCommerce Subscriptions plugin.

    It seems like the Stripe plugin supports the subscription cancellation, but only for the credit card method thus far. The SEPA / direct debit method does not seem to use the trait for subscriptions.

    https://github.com/woocommerce/woocommerce-gateway-stripe/search?q=subscription_cancellation

    Wondering whether you would just need to add the trait to the direct debit class in the Stripe plugin?

    • This reply was modified 4 years, 3 months ago by sun. Reason: Added technical details

    In our case, it was the order-delivery-date plugin. It changes the sort order on the administrative orders page to be based on the custom delivery date for each order.

    However, the plugin provides a configuration option on its own settings page to enable or disable the custom sorting.

    We were able to identify the causing code by looking at the actual query being executed using the query-monitor plugin.

    👍

    https://ww.wp.xz.cn/support/topic/php-7-1-warning-4/ is a duplicate of this issue.

    You can apply the following simple change to fix the problem:

    
    diff --git a/acf-search.php b/acf-search.php
    index e42da27f..68c4ad76 100644
    --- a/acf-search.php
    +++ b/acf-search.php
    @@ -88,7 +88,7 @@ function acf_search_list_searcheable_acf(){
      * see https://vzurczak.wordpress.com/2013/06/15/extend-the-default-wordpress-search/
      * credits to Vincent Zurczak for the base query structure/spliting tags section
      */
    - function acf_search_advanced_custom_search( $where, &$wp_query ) {
    + function acf_search_advanced_custom_search( $where, $wp_query ) {
          global $wpdb;
     
          if ( empty( $where ))
    

    To prevent the error “Invalid or duplicated SKU.” in the WooCommerce product import disable the mapping for the field “ID” by setting it to “Do not import” in the field mapping form of the 2nd step in the WooCommerce product import.

    By attempting to import and map the (post) ID of your products by default, WooCommerce assumes that the database you’re exporting from and the database you’re importing into were the same databases at some point — which is most probably not the case in your case (and in 99% of all cases).

    In order for the import to work without IDs, all of your products need to have a SKU. The WooCommerce product import falls back to using the SKUs of the products to map e.g. variations to their parent variable products.

    Also, all of your SKUs need to be unique. SKUs of variations need to differ from their parents. To double-check that your product data is actually correct, execute the following database queries and ensure that they do not produce any results:

    
    -- Most basic check within postmeta (must pass)
    SELECT COUNT(p.ID) AS count, p.post_title 
    FROM wp_posts p 
    INNER JOIN wp_postmeta pm ON pm.post_id = p.ID AND pm.meta_key = '_sku' 
    GROUP BY pm.meta_value 
    HAVING count > 1;
    
    -- More granular results with actual product IDs to check
    SELECT p.ID, sku.meta_value AS sku, p.post_title, p.post_type 
    FROM wp_posts p 
    INNER JOIN wp_postmeta sku ON sku.post_id = p.ID AND sku.meta_key = '_sku' 
    INNER JOIN wp_postmeta dupe ON dupe.post_id <> sku.post_id AND dupe.meta_key = '_sku' 
    WHERE dupe.meta_value = sku.meta_value 
    GROUP BY p.ID;
    

    Also make sure to use the action “Remove orphan product variations from database” in the Tools screen of the WooCommerce Settings page. The action has been introduced in a recent version and removes product variations whose parent variable products no longer exist in the database, but which still exist for unknown reasons (invisibly) in the database and cannot be seen or reached through the backend user interface.

    To prevent the error “Invalid or duplicated SKU.” in the WooCommerce product import disable the mapping for the field “ID” by setting it to “Do not import” in the field mapping form of the 2nd step in the WooCommerce product import.

    By attempting to import and map the (post) ID of your products by default, WooCommerce assumes that the database you’re exporting from and the database you’re importing into were the same databases at some point — which is most probably not the case in your case (and in 99% of all cases).

    In order for the import to work without IDs, all of your products need to have a SKU. The WooCommerce product import falls back to using the SKUs of the products to map e.g. variations to their parent variable products.

    Also, all of your SKUs need to be unique. SKUs of variations need to differ from their parents. To double-check that your product data is actually correct, execute the following database queries and ensure that they do not produce any results:

    
    -- Most basic check within postmeta (must pass)
    SELECT COUNT(p.ID) AS count, p.post_title 
    FROM wp_posts p 
    INNER JOIN wp_postmeta pm ON pm.post_id = p.ID AND pm.meta_key = '_sku' 
    GROUP BY pm.meta_value 
    HAVING count > 1;
    
    -- More granular results with actual product IDs to check
    SELECT p.ID, sku.meta_value AS sku, p.post_title, p.post_type 
    FROM wp_posts p 
    INNER JOIN wp_postmeta sku ON sku.post_id = p.ID AND sku.meta_key = '_sku' 
    INNER JOIN wp_postmeta dupe ON dupe.post_id <> sku.post_id AND dupe.meta_key = '_sku' 
    WHERE dupe.meta_value = sku.meta_value 
    GROUP BY p.ID;
    

    Also make sure to use the action “Remove orphan product variations from database” in the Tools screen of the WooCommerce Settings page. The action has been introduced in a recent version and removes product variations whose parent variable products no longer exist in the database, but which still exist for unknown reasons (invisibly) in the database and cannot be seen or reached through the backend user interface.

    Thread Starter sun

    (@tha_sun)

    The following patch resolves the issue by giving other code access to the class instance. It would be great if you could include this change in the next release.

    
    diff --git a/language-fallback.php b/language-fallback.php
    index bf363a1..7b7d27d 100644
    --- a/language-fallback.php
    +++ b/language-fallback.php
    @@ -13,6 +13,8 @@
     
     class Language_Fallback {
     
    +       private static = $instance;
    +
            /**
             * used to store the current locale e.g. "de_DE"
             *
    @@ -28,6 +30,7 @@ class Language_Fallback {
            private $fallback_locale;
     
            function __construct() {
    +               self::$instance = $this;
     
                    // get current locale
                    $this->locale = get_locale();
    @@ -48,6 +51,13 @@ class Language_Fallback {
                    load_plugin_textdomain( 'language-fallback', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
            }
     
    +       public static function getInstance() {
    +               if (!isset(self::$instance)) {
    +                       new self();
    +               }
    +               return self::$instance;
    +       }
    +
            /**
             * A function to check if the requested mofile exists and if not, it checks if a mofile for the fallback locale exists
             *
    
    • This reply was modified 9 years, 3 months ago by sun. Reason: Removed windows line ending markers from diff
    Thread Starter sun

    (@tha_sun)

    Any feedback?

    Thread Starter sun

    (@tha_sun)

    diff --git a/wp-content/plugins/etsy-shop/etsy-shop.php b/wp-content/plugins/etsy-shop/etsy-shop.php
    index 4f289e0..b44dd39 100644
    --- a/wp-content/plugins/etsy-shop/etsy-shop.php
    +++ b/wp-content/plugins/etsy-shop/etsy-shop.php
    @@ -28,7 +28,6 @@ Version: 0.18
      */
    
     /* Roadmap to version 1.x
    - * TODO: touch() file in tmp folder
      * TODO: reset cache function
      * TODO: edit cache life
      * TODO: allow more than 100 items
    @@ -39,7 +38,7 @@ Version: 0.18
      */
    
     define( 'ETSY_SHOP_VERSION',  '0.18');
    -define( 'ETSY_SHOP_CACHE_LIFE',  21600 ); // 6 hours in seconds
    +define( 'ETSY_SHOP_CACHE_LIFE', 6 * HOUR_IN_SECONDS );
    
     // load translation
     add_action( 'init', 'etsy_shop_load_translation_file' );
    @@ -264,38 +263,28 @@ function etsy_shop_shortcode( $atts ) {
     add_shortcode( 'etsy-shop', 'etsy_shop_shortcode' );
    
     function etsy_shop_getShopSectionListings( $etsy_shop_id, $etsy_section_id, $language ) {
    -    $etsy_cache_file = etsy_shop_get_data_dir().'/'.$etsy_shop_id.'-'.$etsy_section_id.'_cache.json';
    -
    -    // if no cache file exist
    -    if (!file_exists( $etsy_cache_file ) or ( time() - filemtime( $etsy_cache_file ) >= ETSY_SHOP_CACHE_LIFE ) or get_option( 'etsy_shop_debug_mode' ) ) {
    -        // if language set
    -        if ($language != null) {
    -            $reponse = etsy_shop_api_request( "shops/$etsy_shop_id/sections/$etsy_section_id/listings/active", '&limit=100&includes=Images&language='.$language );
    -        } else {
    -            $reponse = etsy_shop_api_request( "shops/$etsy_shop_id/sections/$etsy_section_id/listings/active", '&limit=100&includes=Images' );
    +    $transient_id = 'etsy-shop_' . $etsy_shop_id . '-' . $etsy_section_id;
    +    if ($language) {
    +        $transient_id .= '-' . $language;
    +    }
    +    if (get_option('etsy_shop_debug_mode') || !$response = get_transient($transient_id)) {
    +        $api_parameters = '&limit=100&includes=Images';
    +        if ($language) {
    +            $api_parameters .= '&language=' . $language;
             }
    -
    -        if ( !is_wp_error( $reponse ) ) {
    -            // if request OK
    -            $tmp_file = $etsy_cache_file.rand().'.tmp';
    -            file_put_contents( $tmp_file, $reponse );
    -            rename( $tmp_file, $etsy_cache_file );
    -        } else {
    -            // return WP_Error
    -            return $reponse;
    +        $response = etsy_shop_api_request("shops/$etsy_shop_id/sections/$etsy_section_id/listings/active", $api_parameters);
    +        if (is_wp_error($response)) {
    +            return $response;
             }
    -    } else {
    -        // read cache file
    -        $reponse = file_get_contents( $etsy_cache_file );
    +        set_transient($transient_id, $response, ETSY_SHOP_CACHE_LIFE);
         }
    
         if ( get_option( 'etsy_shop_debug_mode' ) ) {
    -        $file_content = file_get_contents( $etsy_cache_file );
    -        print_r( '<h3>--- Etsy Cache File:' . $etsy_cache_file . ' ---</h3>' );
    -        print_r( $file_content );
    +        print_r( '<h3>--- Etsy Transient: ' . $transient_id . ' ---</h3>' );
    +        print_r( $response );
         }
    
    -    $data = json_decode( $reponse );
    +    $data = json_decode( $response );
         return $data;
     }
    
    @@ -426,11 +415,14 @@ function etsy_shop_menu() {
     }
    
     function etsy_shop_options_page() {
    +    global $wpdb;
    +
         // did the user is allowed?
         if ( !current_user_can( 'manage_options' ) )  {
             wp_die( __( 'You do not have sufficient permissions to access this page.', 'etsyshop' ) );
         }
    
    +    $updated = FALSE;
         if ( isset( $_POST['submit'] ) ) {
             // did the user enter an API Key?
             if ( isset( $_POST['etsy_shop_api_key'] ) ) {
    @@ -485,30 +477,11 @@ function etsy_shop_options_page() {
             }
         }
    
    -    // delete cache file
    -    if ( isset( $_GET['delete'] ) ) {
    -
    -        // did a file was choosed?
    -        if ( isset( $_GET['file'] ) ) {
    -            $tmp_directory = etsy_shop_get_data_dir() . '/';
    -
    -            // REGEX for security!
    -            $filename = str_replace( '.json', '', $_GET['file'] );
    -            $filename = preg_replace( '/[^a-zA-Z0-9-_]/', '', $filename );
    -
    -            $fullpath_filename = $tmp_directory . $filename . '.json';
    -            if ( file_exists( $fullpath_filename ) ) {
    -                $deletion = unlink( $fullpath_filename );
    -            } else {
    -                $deletion = false;
    -            }
    -
    -            if ( $deletion ) {
    -                // and remember to note deletion to user
    -                $deleted = true;
    -                $deleted_file = $fullpath_filename;
    -            }
    -        }
    +    // delete cache
    +    $deleted = FALSE;
    +    if (isset($_GET['delete']) && isset($_GET['transient_id'])) {
    +        delete_transient($_GET['transient_id']);
    +        $deleted = TRUE;
         }
    
         // grab the Etsy API key
    @@ -544,7 +517,7 @@ function etsy_shop_options_page() {
         }
    
         if ( $deleted ) {
    -        echo '<div class="updated fade"><p><strong>'. __( 'Cache file deleted:', 'etsyshop' ) . ' ' . $deleted_file . '</strong></p></div>';
    +        echo '<div class="updated fade"><p><strong>'. __( 'Cache deleted:', 'etsyshop' ) . ' ' . $_GET['transient_id'] . '</strong></p></div>';
         }
    
         // print the Options Page
    @@ -616,29 +589,35 @@ function etsy_shop_options_page() {
                                             <thead id="EtsyShopCacheTableHead">
                                             <tr>
                                                 <th><?php _e('Shop Section', 'etsyshop'); ?></th>
    -                                            <th><?php _e('Filename', 'etsyshop'); ?></th>
    -                                            <th><?php _e('Last update', 'etsyshop'); ?></th>
    +                                            <th><?php _e('Transient ID', 'etsyshop'); ?></th>
    +                                            <th><?php _e('Expires', 'etsyshop'); ?></th>
                                                 <th></th>
                                             </tr>
                                             </thead>
                                             <?php
    -                                $files = glob( etsy_shop_get_data_dir().'/*.json' );
    -                                $time_zone = get_option('timezone_string');
    -                                date_default_timezone_set( $time_zone );
    -                                foreach ($files as $file) {
    -                                    // downgrade to support PHP 5.2.4
    -                                    //$etsy_shop_section = explode( "-", strstr(basename( $file ), '_cache.json', true ) );
    -                                    $etsy_shop_section = explode( "-", substr( basename( $file ), 0, strpos( basename( $file ), '_cache.json' ) ) );
    -                                    $etsy_shop_section_info = etsy_shop_getShopSection($etsy_shop_section[0], $etsy_shop_section[1]);
    +                                $transients = $wpdb->get_results("SELECT * FROM {$wpdb->options} WHERE option_name LIKE '_transient_etsy-shop_%'");
    +                                foreach ($transients as $transient) {
    +                                    $transient_id = substr($transient->option_name, strlen('_transient_'));
    +                                    @list($shop_name, $shop_section_id, $language) = explode('-', substr($transient_id, strlen('etsy-shop_')), 3);
    +                                    $etsy_shop_section_info = etsy_shop_getShopSection($shop_name, $shop_section_id, $language);
    +                                    $expiration = $wpdb->get_var("SELECT option_value FROM {$wpdb->options} WHERE option_name = '_transient_timeout_{$transient_id}'");
                                         if ( !is_wp_error( $etsy_shop_section_info ) ) {
    -                                       echo '<tr><td>' . $etsy_shop_section[0] . ' / ' . $etsy_shop_section_info->results[0]->title . '</td><td>' . basename( $file ) . '</td><td>' .  date( "Y-m-d H:i:s", filemtime( $file ) ) . '</td><td><a href="options-general.php?page=etsy-shop.php&delete&file=' . basename( $file ) . '" title="'. __('Delete cache file', 'etsyshop') .'"><span class="dashicons dashicons-trash"></span></a></td></tr>';
    -                                       // echo '<tr><td>' . $etsy_shop_section[0] . ' / ' . $etsy_shop_section_info->results[0]->title . '</td><td>' . basename( $file ) . '</td><td>' .  date( "Y-m-d H:i:s", filemtime( $file ) ) . '</td><td><a href="" title="' . _e('Delete cache file', 'etsyshop'); . '"><span class="dashicons dashicons-trash"></span></a></td></tr>';
    +                                       echo '<tr>
    +<td>' . $shop_name . ' / ' . $etsy_shop_section_info->results[0]->title . '</td>
    +<td>' . $transient_id . '</td>
    +<td>' . date_i18n( "Y-m-d H:i:s", $expiration ) . '</td>
    +<td><a href="options-general.php?page=etsy-shop.php&delete&transient_id=' . urlencode($transient_id) . '" title="'. __('Delete cache', 'etsyshop') .'"><span class="dashicons dashicons-trash"></span></a></td>
    +</tr>';
                                         } else {
    -                                        echo '<tr><td>' . $etsy_shop_section[0] . ' / <span style="color:red;">Error on API Request</span>' . '</td><td>' . basename( $file ) . '</td><td>' .  date( "Y-m-d H:i:s", filemtime( $file ) ) . '</td><td></td></tr>';
    +                                        echo '<tr>
    +<td>' . $shop_name . ' / <span style="color:red;">Error on API Request</span>' . '</td>
    +<td>' . $transient_id . '</td>
    +<td>' . ($expiration ? date_i18n( "Y-m-d H:i:s", $expiration ) : $expiration ) . '</td>
    +<td></td>
    +</tr>';
                                         }
                                     }
                                         ?></table><?php } else { _e('You must enter your Etsy API Key to view cache status!', 'etsyshop'); } ?>
    -                                <p class="description"><?php _e( 'You may reset cache a any time by deleting files in tmp folder of the plugin.', 'etsyshop' ); ?></p>
                                     </td>
                             </tr>
             </table>
    @@ -681,12 +660,3 @@ function etsy_shop_plugin_action_links( $links, $file ) {
         return $links;
     }
    
    -function etsy_shop_get_data_dir() {
    -    $uploads_config = wp_upload_dir(NULL, FALSE);
    -    $dir = $uploads_config['basedir'] . '/etsy-shop/tmp';
    -    if ( !file_exists($dir) ) {
    -        wp_mkdir_p($dir);
    -    }
    -    return $dir;
    -}
    -
    Thread Starter sun

    (@tha_sun)

    Or even better, use WordPress’s Transient API, which has been built exactly for jobs like this, as also explained here:
    https://css-tricks.com/the-deal-with-wordpress-transients/

    My next comment will contain a patch that replaces the file-based cache with a transient-based cache.

Viewing 15 replies - 1 through 15 (of 23 total)