SHET23
English

آپدیت نشدن لینک دانلود محصولات ووکامرس؛ علت و راه‌حل

آپدیت نشدن لینک دانلود محصولات ووکامرس؛ علت و راه‌حل

تصور کنید یه افزونه یا قالب وردپرسی می‌فروشید. نسخه جدید رو با کلی رفع باگ و قابلیت تازه آماده کردید، فایلش رو توی محصول ووکامرس آپلود می‌کنید، ذخیره می‌زنید و خیالتون راحته که مشتری‌ها آپدیت رو می‌گیرن. چند ساعت بعد اولین تیکت میاد: «سلام، من دیروز خریدم ولی لینک دانلود هنوز نسخه قبلیه.» و بعد دومی. و سومی.

این یکی از رایج‌ترین دردسرهای فروشگاه‌های دانلودی با ووکامرسه. ولی قبل از اینکه بریم سراغ کد و راه‌حل، باید یه چیزی رو بهتون بگم که شاید کل مشکلتون رو همین‌جا حل کنه — و متأسفانه تقریباً هیچ‌کدوم از مقاله‌های فارسی (و خیلی از انگلیسی‌ها) بهش اشاره نکردن.

آنچه در این مقاله می‌خوانید

اول ببینیم ووکامرس دقیقاً چطور کار می‌کنه

وقتی یه مشتری محصول دانلودی می‌خره و سفارشش تکمیل می‌شه، ووکامرس یه ردیف توی جدول wp_woocommerce_downloadable_product_permissions می‌سازه. توی این ردیف اینا ذخیره می‌شه:

  • order_id — شناسه سفارش
  • product_id — شناسه محصول
  • download_id — شناسه فایل دانلودی
  • user_email و user_id — مشخصات خریدار
  • downloads_remaining — تعداد دفعات باقی‌مونده
  • access_expires — تاریخ انقضای دسترسی

حالا نکته کلیدی اینه که آدرس فایل توی این جدول ذخیره نمی‌شه. فقط download_id ذخیره می‌شه. آدرس واقعی فایل جای دیگه‌ای نگهداری می‌شه؛ توی خود محصول. یعنی وقتی مشتری روی دکمه دانلود کلیک می‌کنه، ووکامرس این مسیر رو طی می‌کنه:

سفارش → download_id → محصول → آدرس فعلی فایل

و اینجا همون جاییه که همه‌چیز عوض شد

توی ووکامرس ۲.۶ و قبل‌تر، download_id برابر بود با هش MD5 آدرس فایل. یعنی آدرس فایل که عوض می‌شد، شناسه هم عوض می‌شد و ارتباط سفارش قدیمی با فایل جدید قطع می‌شد. دقیقاً همون مشکلی که همه ازش شکایت داشتن.

ولی از ووکامرس ۳.۰ به بعد، این رفتار عوض شده. حالا download_id یه شناسه یکتای تصادفی (UUID) هست که موقع ساخته شدن ردیف تولید می‌شه و هیچ ربطی به آدرس فایل نداره. یعنی می‌تونید آدرس فایل رو ده بار عوض کنید، شناسه سر جاش می‌مونه و همه خریداران قبلی به فایل جدید وصل می‌شن.

ووکامرس حتی توی نسخه ۳.۳ اون کد قدیمی مدیریت مسیرها (WC_Post_Data::process_product_file_download_paths) رو منسوخ اعلام کرد و دلیلش رو هم صراحتاً نوشت: چون download_id دیگه بر پایه هش فایل نیست، این کد لازم نیست.

سه سناریو، سه نتیجه کاملاً متفاوت

این جدول رو یه جایی سیو کنید، چون تمام ماجرا همینه:

کاری که انجام می‌دیدخریداران قبلینیاز به کد؟
ردیف موجود رو ویرایش می‌کنید (تغییر نام یا آدرس فایل)✅ فایل جدید رو می‌گیرنخیر
فایل روی سرور رو با همون نام جایگزین می‌کنید✅ فایل جدید رو می‌گیرنخیر
یه ردیف دانلود جدید اضافه می‌کنید❌ نمی‌بیننشبله
ردیف قدیمی رو حذف و ردیف جدید می‌سازید⚠️ دسترسیشون کلاً قطع می‌شهبله (یا اصلاً این کار رو نکنید)

حالا احتمالاً فهمیدید مشکل اکثر فروشگاه‌ها از کجاست: موقع آپدیت محصول، ردیف قدیمی رو حذف می‌کنن و یه ردیف تازه می‌سازن. از نظر ووکامرس، اون فایل قدیمی «حذف شده» و این یه «فایل کاملاً جدید»ه که خریداران قبلی حقی نسبت بهش ندارن.

راه‌حل اول: اصلاً کد نمی‌خواد (و برای ۹۰٪ شما همینه)

اگه کاری که می‌کنید آپدیت نسخه یه محصوله؛ نه اضافه کردن یه فایل جانبی، این روش رو دنبال کنید:

  1. وارد ویرایش محصول بشید و به تب «دانلودی» توی باکس داده‌های محصول برید.
  2. ردیف موجود رو حذف نکنید. این مهم‌ترین جمله کل مقاله‌ست.
  3. روی خونه «آدرس فایل» همون ردیف کلیک کنید و آدرس فایل نسخه جدید رو جایگزین کنید (یا از دکمه انتخاب فایل استفاده کنید).
  4. اسم فایل رو هم می‌تونید عوض کنید، مشکلی نداره.
  5. محصول رو به‌روزرسانی کنید. تمام.

حالا وارد حساب یه مشتری قدیمی بشید. لینک دانلودش سر جاشه، ولی به فایل نسخه جدید اشاره می‌کنه. تعداد دانلود باقی‌مونده و تاریخ انقضاش هم دست‌نخورده مونده.

یه ترفند عملی: اسم فایل‌هاتون رو نسخه‌دار نکنید. به‌جای rakhsh-plugin-v2.4.zip از rakhsh-plugin.zip استفاده کنید و هر بار همون فایل رو روی سرور بازنویسی کنید. اینطوری حتی نیازی به ویرایش محصول هم ندارید — فایل جدید رو با FTP جایگزین می‌کنید و کار تمومه. شماره نسخه رو توی نام پوشه داخل زیپ یا فایل readme بذارید.

راه‌حل دوم: ابزارهای خود ووکامرس برای موارد تکی

اگه فقط چند تا سفارش خاص مشکل دارن، اصلاً سراغ کد نرید. ووکامرس دو تا ابزار داخلی داره:

الف) اعطای دسترسی از داخل صفحه سفارش

سفارش موردنظر رو باز کنید. پایین صفحه یه باکس به اسم «محصولات دانلودی» هست. از منوی کشویی، محصول رو انتخاب کنید و روی «اعطای دسترسی» بزنید. همین‌جا می‌تونید تعداد دفعات مجاز دانلود و تاریخ انقضا رو هم دستی تنظیم کنید.

ب) بازتولید گروهی دسترسی‌ها

توی لیست سفارش‌ها، سفارش‌های موردنظر رو تیک بزنید و از منوی «اقدامات دسته‌جمعی»، گزینه «بازتولید مجوزهای دانلود» رو انتخاب کنید. ووکامرس دسترسی‌ها رو بر اساس فایل‌های فعلی محصول از نو می‌سازه.

ولی حواستون باشه: این گزینه دسترسی‌ها رو ریست می‌کنه. یعنی تعداد دانلود باقی‌مونده و تاریخ انقضا به مقدار پیش‌فرض محصول برمی‌گرده. اگه محدودیت دانلود گذاشتید و مشتری قبلاً ۳ بار از ۵ بار مجازش رو استفاده کرده، بعد از این کار دوباره ۵ بار کامل داره. برای بعضی کسب‌وکارها اهمیتی نداره، برای بعضی‌ها داره.

راه‌حل سوم: کد، برای وقتی که واقعاً لازمه

خب، حالا فرض کنیم سناریوی شما واقعاً این نیست که یه فایل رو آپدیت کنید. مثلاً:

  • یه فایل بونوس به محصول اضافه کردید و می‌خواید همه خریداران قبلی هم بگیرنش.
  • محصولتون یه پکیجه و به مرور بهش فایل اضافه می‌کنید.
  • مدل فروشتون «دسترسی مادام‌العمر به همه فایل‌های آینده»ست.

برای این حالت‌ها باید ووکامرس رو مجبور کنیم بعد از اضافه شدن فایل جدید، بره سراغ همه سفارش‌های قبلی اون محصول و دسترسی جدید رو بهشون بده.

⚠️ چرا خود ووکامرس این قابلیت رو حذف کرد؟
این رفتار تا نسخه ۲.۶ توی هسته ووکامرس وجود داشت و بعد حذف شد. تیم ووکامرس دلیلش رو صریح گفته: کوئری‌های سنگین دیتابیس که مقیاس‌پذیر نیست. روی محصولی با ۵۰ سفارش، این کد چشم به هم بزنید تموم می‌شه. روی محصولی با ۲۰ هزار سفارش، صفحه ذخیره محصول تایم‌اوت می‌ده و ممکنه سایت رو زمین بزنه.

کدی که پایین می‌بینید نسخه بازنویسی‌شده‌ایه که این مشکل رو با پردازش دسته‌ای در پس‌زمینه حل کرده. ولی باز هم: اول روی سایت آزمایشی تست کنید.

کد نهایی (سازگار با ووکامرس مدرن و HPOS)

<?php
/**
 * Plugin Name: اعطای دسترسی دانلود به سفارش‌های قبلی
 * Description: پس از افزودن فایل دانلودی جدید به یک محصول، دسترسی را به سفارش‌های قبلی همان محصول هم می‌دهد.
 * Version:     1.0.0
 * Author:      میهن وردپرس
 * Requires PHP: 7.4
 * License:     GPLv2 or later
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * اعلام سازگاری با HPOS (ذخیره‌سازی پرسرعت سفارش‌ها)
 */
add_action( 'before_woocommerce_init', function () {
    if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
            'custom_order_tables',
            __FILE__,
            true
        );
    }
} );

class MWP_Grant_Download_Permissions {

    /**
     * تا این تعداد سفارش، کار همان لحظه انجام می‌شود.
     * بیشتر از این، به صف پس‌زمینه سپرده می‌شود.
     */
    const IMMEDIATE_LIMIT = 200;

    /** تعداد سفارش در هر دسته پس‌زمینه */
    const BATCH_SIZE = 100;

    protected static $instance = null;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        if ( ! class_exists( 'WooCommerce' ) ) {
            return;
        }

        add_action( 'woocommerce_process_product_file_download_paths', array( $this, 'handle_new_files' ), 10, 3 );
        add_action( 'mwp_grant_download_batch', array( $this, 'process_batch' ), 10, 3 );
    }

    /**
     * وقتی فایل‌های دانلودی محصول ذخیره می‌شوند اجرا می‌شود.
     */
    public function handle_new_files( $product_id, $variation_id, $downloadable_files ) {

        // اگر محصول متغیر است، شناسه متغیر ملاک است.
        if ( $variation_id ) {
            $product_id = $variation_id;
        }

        $product = wc_get_product( $product_id );
        if ( ! $product ) {
            return;
        }

        $existing_ids = array_keys( (array) $product->get_downloads() );
        $updated_ids  = array_keys( (array) $downloadable_files );

        // فقط فایل‌هایی که تازه اضافه شده‌اند برایمان مهم است.
        $new_ids = array_filter( array_diff( $updated_ids, $existing_ids ) );

        if ( empty( $new_ids ) ) {
            return;
        }

        $order_ids = $this->get_orders_for_product( $product_id );

        if ( empty( $order_ids ) ) {
            return;
        }

        // فروشگاه‌های بزرگ: کار را به Action Scheduler بسپار تا صفحه تایم‌اوت ندهد.
        if ( count( $order_ids ) > self::IMMEDIATE_LIMIT && function_exists( 'as_schedule_single_action' ) ) {

            $chunks = array_chunk( $order_ids, self::BATCH_SIZE );

            foreach ( $chunks as $index => $chunk ) {
                as_schedule_single_action(
                    time() + ( $index * 60 ),
                    'mwp_grant_download_batch',
                    array( $chunk, $product_id, array_values( $new_ids ) )
                );
            }

            return;
        }

        $this->process_batch( $order_ids, $product_id, array_values( $new_ids ) );
    }

    /**
     * یک دسته از سفارش‌ها را پردازش می‌کند.
     */
    public function process_batch( $order_ids, $product_id, $new_ids ) {

        foreach ( (array) $order_ids as $order_id ) {

            $order = wc_get_order( $order_id );
            if ( ! $order ) {
                continue;
            }

            foreach ( (array) $new_ids as $download_id ) {

                // اجازه بده توسعه‌دهنده‌های دیگر جلوی این کار را بگیرند.
                if ( ! apply_filters( 'mwp_grant_access_to_new_file', true, $download_id, $product_id, $order ) ) {
                    continue;
                }

                if ( ! $this->permission_exists( $order->get_id(), $product_id, $download_id ) ) {
                    wc_downloadable_file_permission( $download_id, $product_id, $order );
                }
            }
        }
    }

    /**
     * سفارش‌هایی که این محصول را خریده‌اند.
     */
    private function get_orders_for_product( $product_id ) {
        global $wpdb;

        return $wpdb->get_col(
            $wpdb->prepare(
                "SELECT DISTINCT order_id
                 FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
                 WHERE product_id = %d",
                $product_id
            )
        );
    }

    /**
     * آیا این دسترسی از قبل وجود دارد؟
     */
    private function permission_exists( $order_id, $product_id, $download_id ) {
        global $wpdb;

        return (bool) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT permission_id
                 FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
                 WHERE order_id = %d AND product_id = %d AND download_id = %s
                 LIMIT 1",
                $order_id,
                $product_id,
                $download_id
            )
        );
    }
}

add_action( 'admin_init', array( 'MWP_Grant_Download_Permissions', 'get_instance' ) );

این کد چه فرقی با نسخه‌های قدیمی داره؟

اگه قبلاً نسخه‌های قدیمی این کد رو دیدید، این تفاوت‌ها رو بدونید:

  • خط remove_action حذف شده. نسخه‌های قدیمی سعی می‌کردن هوک پیش‌فرض ووکامرس رو بردارن، ولی از ووکامرس ۳.۳ اون تابع منسوخ شده و اصلاً به این هوک وصل نیست. اون خط الان فقط کد اضافه‌ست.
  • سازگاری با HPOS اعلام شده. بدون این بخش، ووکامرس ممکنه هشدار ناسازگاری نشون بده.
  • پردازش دسته‌ای اضافه شده. بالای ۲۰۰ سفارش، کار به Action Scheduler (که خودش با ووکامرس میاد) سپرده می‌شه و توی پس‌زمینه انجام می‌شه. این دقیقاً همون چیزیه که نبودش باعث شد ووکامرس این قابلیت رو از هسته بردارد.
  • حذف دسترسی فایل‌های پاک‌شده، برداشته شده. نسخه اصلی وقتی فایلی رو حذف می‌کردید، دسترسی خریداران قبلی رو هم قطع می‌کرد. تجربه نشون داده این رفتار بیشتر باعث دردسر می‌شه تا فایده — یه اشتباه کوچیک موقع ویرایش محصول و دسترسی صدها مشتری می‌پره. اگه واقعاً می‌خواید این رفتار رو داشته باشید، بهتره دستی و آگاهانه انجامش بدید.
  • یه فیلتر اضافه شده (mwp_grant_access_to_new_file) که اگه خواستید برای بعضی محصول‌ها یا سفارش‌ها استثنا بذارید، بتونید.

کد رو کجا بذاریم؟ (این بخش رو رد نکنید)

خیلی از آموزش‌های قدیمی می‌گن: «برید به نمایش ← ویرایشگر قالب و کد رو ته functions.php بچسبونید.» لطفاً این کار رو نکنید. سه تا دلیل:

  1. یه پرانتز جا‌افتاده توی اون ویرایشگر یعنی سفید شدن کل سایت، و چون از همون ویرایشگر بیرون افتادید، برای درست کردنش باید برید سراغ FTP.
  2. قالبتون رو که عوض کنید، کد می‌پره. قالبتون که آپدیت بشه (اگه قالب فرزند نساخته باشید)، باز هم می‌پره.
  3. توی وردپرس‌های جدید با قالب‌های بلوکی، این ویرایشگر اصلاً نمایش داده نمی‌شه.

کد بالا رو من عمداً به شکل یه افزونه مستقل نوشتم. سه تا راه درست دارید:

روش اول (توصیه‌شده): افزونه اختصاصی

یه فایل به اسم mwp-grant-download-permissions.php بسازید، کد بالا رو داخلش بذارید، توی یه فایل ZIP فشرده‌ش کنید و از مسیر افزونه‌ها ← افزودن ← بارگذاری افزونه، نصبش کنید. حالا مستقل از قالبه و هر وقت خواستید با یه کلیک غیرفعالش می‌کنید.

روش دوم: افزونه Code Snippets

اگه با فایل و FTP راحت نیستید، افزونه Code Snippets رو نصب کنید و کد رو (بدون خط <?php و بدون هدر افزونه) به‌عنوان یه اسنیپت جدید ذخیره کنید. مزیتش اینه که اگه کد خطا بده، خود افزونه جلوی از کار افتادن سایت رو می‌گیره.

روش سوم: قالب فرزند

اگه از قبل قالب فرزند دارید، می‌تونید بدنه کلاس رو به functions.php اون اضافه کنید. ولی بازم روش اول تمیزتره.

قبل از هر چیز، بک‌آپ. از فایل‌ها و دیتابیس. این کد مستقیم با جدول دسترسی‌های دانلود کار می‌کنه و اگه چیزی اشتباه پیش بره، برگردوندن دستی‌ش کار سختیه.

یه مشکل که ممکنه کل زحمتتون رو بی‌اثر کنه: کش

این سناریو رو زیاد دیدیم که همه‌چیز رو درست انجام دادید، دسترسی‌ها توی دیتابیس هم درست ثبت شده، ولی مشتری هنوز داد می‌زنه که لینک جدید رو نمی‌بینه. می‌رید توی دیتابیس چک می‌کنید، همه‌چیز درسته. چرا؟

چون صفحه «حساب کاربری من» کش شده. افزونه کش سایتتون یه نسخه HTML ثابت از صفحه دانلودها ساخته و داره همون نسخه قدیمی رو به همه نشون می‌ده. صفحاتی که هیچ‌وقت نباید کش بشن:

  • حساب کاربری من (و همه زیرصفحه‌هاش، مخصوصاً «دانلودها»)
  • سبد خرید
  • تسویه‌حساب
  • صفحه ورود
  • و به‌طور کلی، هر صفحه‌ای برای کاربران وارد‌شده

اگه دنبال یه راه‌حل فارسی هستید که این استثناها رو از قبل بلد باشه، افزونه رخش به‌صورت پیش‌فرض سبد خرید، تسویه‌حساب و ورود رو از کش عبور می‌ده و برای کاربران واردشده اصلاً صفحه رو کش نمی‌کنه — یعنی دقیقاً همون چیزی که برای یه فروشگاه دانلودی حیاتیه. کنارش می‌تونید آدرس‌ها و کوکی‌های مستثنی دلخواه خودتون رو هم تعریف کنید و بعد از هر بار پاکسازی، کش دوباره به‌صورت خودکار گرم بشه تا اولین بازدیدکننده گیر صفحه سرد نیفته.

یه قابلیت دیگه‌اش که برای همین مقاله کاربردیه، پایش کوئری‌های کند دیتابیسه. اگه بعد از اضافه کردن کد بالا احساس کردید صفحه ذخیره محصول کند شده، به‌جای حدس زدن می‌تونید دقیقاً ببینید کدوم کوئری داره وقت می‌بره و چقدر طول می‌کشه.

راه‌حل‌های جایگزین (که شاید بهتر از کد باشن)

قبل از اینکه کد رو نصب کنید، ببینید کدوم یکی از اینا به مدل کسب‌وکار شما نزدیک‌تره:

همه فایل‌ها رو توی یک ZIP بذارید

ساده‌ترین راه‌حل دنیا. یه ردیف دانلود، یه فایل ZIP که همه‌چیز توشه. هر بار محتوای ZIP رو عوض می‌کنید و همون فایل رو جایگزین می‌کنید. صفر خط کد، صفر مشکل.

سرور آپدیت اختصاصی (برای فروشندگان افزونه و قالب)

اگه افزونه یا قالب وردپرسی می‌فروشید، بهترین کار اینه که اصلاً مشتری رو مجبور به دانلود دستی نکنید. یه سیستم آپدیت راه بندازید که آپدیت‌ها مستقیم توی پیشخوان وردپرس مشتری نمایش داده بشن. این کاریه که همه فروشگاه‌های جدی نرم‌افزار می‌کنن و تجربه کاربریش با «برو به حساب کاربری، دانلود کن، دستی جایگزین کن» قابل مقایسه نیست.

مدل عضویت یا اشتراک

اگه مدل فروشتون «دسترسی به همه فایل‌های حال و آینده»ست، در واقع شما محصول دانلودی نمی‌فروشید، عضویت می‌فروشید. افزونه‌های عضویت یا اشتراک این سناریو رو به‌صورت بومی مدیریت می‌کنن و لازم نیست با جدول دسترسی‌ها کلنجار برید.

آدرس فایل بدون شماره نسخه

همون ترفندی که بالاتر گفتم. یه آدرس ثابت مثل /downloads/rakhsh-latest.zip که همیشه به آخرین نسخه اشاره می‌کنه.

تجربه میهن وردپرس در انتقال از EDD به ووکامرس

اگه اخبار سایت رو دنبال کرده باشید، می‌دونید که ما زیرساخت فروشمون رو از افزونه Easy Digital Downloads به ووکامرس منتقل کردیم. توی EDD، این رفتار پیش‌فرض بود: فایل محصول رو که به‌روز می‌کردید، همه خریداران قبلی خودکار نسخه جدید رو می‌گرفتن.

موقع انتقال، اولین چیزی که فکر کردیم بهش برخوردیم همین تفاوت بود. ولی بعد از بررسی دقیق‌تر متوجه شدیم ماجرا کمی متفاوته: ووکامرس هم این کار رو می‌کنه، فقط به شرطی که ردیف دانلود رو حذف نکنیم. مشکل واقعی ما این بود که توی فرآیند مهاجرت، فایل‌ها با آدرس‌های جدید و به‌عنوان ردیف‌های تازه ساخته می‌شدن.

بعد از اصلاح فرآیند و اضافه کردن کدی مشابه چیزی که بالا دیدید (برای موارد خاصی که واقعاً فایل جدید اضافه می‌شد)، مشکل کاملاً حل شد. یکی از کاربران عزیز سایت هم دقیقاً همین سؤال رو توی مرکز پشتیبانی مطرح کرده بود که انگیزه نوشتن این مقاله شد. در ضمن با دوره آموزش ووکامرس میتونید به صورت رایگان آموزش صفر تا صد کار با ووکامرس رو ببینید و یه سایت فروشگاهی کامل راه بندازید!

عیب‌یابی خطاهای رایج

کد رو گذاشتم ولی هیچ اتفاقی نمی‌افته

چند تا مورد رو چک کنید:

  • مطمئن شید که واقعاً یه ردیف جدید اضافه کردید. اگه ردیف موجود رو ویرایش کرده باشید، کد اصلاً اجرا نمی‌شه — چون نیازی بهش نیست و ووکامرس خودش کار رو کرده.
  • محصول رو ذخیره کردید یا فقط پیش‌نویس رو به‌روز کردید؟ این هوک موقع ذخیره داده‌های محصول اجرا می‌شه.
  • اگه محصول متغیر (Variable) دارید، دانلودها روی خود متغیرها تنظیم می‌شن، نه محصول والد.

موقع ذخیره محصول، صفحه تایم‌اوت می‌ده

یعنی تعداد سفارش‌های اون محصول از حد آستانه بیشتره. مقدار IMMEDIATE_LIMIT رو توی کد کمتر کنید (مثلاً ۵۰) تا زودتر به صف پس‌زمینه سپرده بشه. بعدش می‌تونید از مسیر ووکامرس ← وضعیت ← کارهای زمان‌بندی‌شده، پیشرفت کار رو ببینید.

پیغام «امکان اعطای دسترسی وجود ندارد»

معمولاً یعنی ایمیل صورتحساب سفارش خالیه یا کاربر از قبل به همون فایل دسترسی داره. سفارش رو باز کنید و مطمئن شید فیلد ایمیل پر شده باشه.

مشتری می‌گه «لینک دانلود نامعتبر است»

این یعنی download_id توی جدول دسترسی‌ها هست، ولی توی محصول دیگه وجود نداره — یعنی ردیف رو حذف کردید. راه‌حلش: یا ردیف رو با همون شناسه برگردونید (سخته)، یا برای اون سفارش‌ها از «بازتولید مجوزهای دانلود» استفاده کنید.

فایل روی سرور عوض شده ولی مشتری هنوز نسخه قدیمی رو می‌گیره

احتمالاً CDN یا کش مرورگر. اگه از CDN استفاده می‌کنید، کش اون مسیر رو پاک کنید. مطمئن شید مسیر فایل‌های دانلودی (woocommerce_uploads) از کش CDN مستثنی شده باشه.

سؤالات متداول

آیا این کد با آپدیت ووکامرس از بین می‌ره؟

نه. اگه به‌شکل افزونه مستقل نصبش کرده باشید، آپدیت ووکامرس یا وردپرس بهش کاری نداره. فقط بعد از هر آپدیت بزرگ ووکامرس، یه بار تستش کنید که هوک هنوز درست کار می‌کنه.

روی سرعت سایت تأثیر می‌ذاره؟

روی صفحات سایت برای بازدیدکننده‌ها، نه — این کد فقط توی پیشخوان و اون هم فقط لحظه ذخیره محصول اجرا می‌شه. ولی روی سرعت ذخیره کردن محصول بله، مخصوصاً روی محصولات پرفروش. برای همین بخش پردازش دسته‌ای رو اضافه کردیم.

با محصولات متغیر کار می‌کنه؟

بله. کد شناسه متغیر رو تشخیص می‌ده و به‌عنوان شناسه اصلی در نظر می‌گیره.

با ووکامرس ساب‌اسکریپشن چطور؟

اینجا داستان فرق می‌کنه. محصولات اشتراکی منطق دسترسی خودشون رو دارن و این کد لزوماً روشون جواب نمی‌ده. برای اون سناریو باید سراغ هوک‌های خود افزونه اشتراک برید.

اگه قالبم رو عوض کنم چی می‌شه؟

اگه کد رو مثل توصیه ما به‌شکل افزونه نصب کرده باشید، هیچی. اگه توی functions.php قالب گذاشته باشید، با تعویض قالب کد از دست می‌ره — که دقیقاً یکی از دلایلیه که روش افزونه رو پیشنهاد می‌کنیم.

می‌تونم دسترسی رو فقط به سفارش‌های بعد از یه تاریخ خاص بدم؟

بله. از فیلتر mwp_grant_access_to_new_file استفاده کنید و توی تابعتون تاریخ سفارش رو با $order->get_date_created() چک کنید و برای سفارش‌های قدیمی‌تر false برگردونید.

حداقل نسخه ووکامرس برای این کد چیه؟

روی ووکامرس ۸ به بالا تست شده. روی نسخه‌های قدیمی‌تر هم احتمالاً کار می‌کنه ولی بخش اعلام سازگاری HPOS بی‌اثر خواهد بود.

جمع‌بندی

اگه بخوایم کل این مقاله رو توی یه توصیه خلاصه کنیم: ردیف دانلود رو حذف نکنید، ویرایشش کنید. نود درصد کسایی که دنبال راه‌حل این مشکل می‌گردن، با همین یه جمله مشکلشون حل می‌شه و اصلاً به کد نیاز ندارن.

اگه واقعاً سناریوی شما اضافه کردن فایل جدید به محصولات فروخته‌شده‌ست، کد این مقاله کارتون رو راه می‌ندازه — با این تفاوت که برخلاف نسخه‌های قدیمی که توی اینترنت پیدا می‌کنید، سازگار با HPOSه و روی فروشگاه‌های بزرگ هم زمین نمی‌خوره.

و آخرین توصیه: قبل از هر تغییری روی ساختار محصولات دانلودی، یه بار روی سایت آزمایشی تست کنید. مشتری‌هایی که نمی‌تونن محصول خریداری‌شده‌شون رو دانلود کنن، سریع‌ترین راه از دست دادن اعتماد یه فروشگاه دانلودی هستن.

شما با این مشکل برخورد کردید؟ راه‌حل دیگه‌ای پیدا کردید که به درد بقیه بخوره؟ توی بخش نظرات بنویسید، حتماً می‌خونیم و جواب می‌دیم. 🙂

نظر شما در این مورد چیه؟

  1. U331578۱ آذر ۱۴۰۳

    عرض سلام و احترام.
    دوستانی که از php 8.1 استفاده می‌کنن. من کد php که در افزونه grant-download-permissions-for-past-woocommerce-orders وجود داشت رو با توابع مدرن‌تر به صورت خاص برای php 8.1 بازنویسی کردم که کارشناسان میهن وردپرس هم میتونن بررسی کنن. این کد به طور خاص برای php 8.1 بهینه شده ولی در php 7.4 هم به درستی کار میکنه و بعد از آپدیت محصول، یک رکورد جدید با تاریخ به‌روز در جدول woocommerce_downloadable_product_permissions برای خریداران قبلی ثبت میکنه و همین ثبت رکورد جدید برای خریداران قبلی باعث میشه که خریداران قبلی به آپدیت محصولات دانلودی دسترسی داشته باشن:
    کد php:

    if ( ! defined( ‘ABSPATH’ ) ) {
    exit; // Exit if accessed directly.
    }

    /**
    * WooCommerce_Legacy_Grant_Download_Permissions class.
    */
    class WooCommerce_Legacy_Grant_Download_Permissions {

    /**
    * Instance of this class.
    *
    * @var object|null
    */
    protected static $instance = null;

    /**
    * Initialize the plugin actions.
    */
    private function __construct() {
    // Stop if WooCommerce isn’t activated.
    if ( ! class_exists( ‘WC_Admin_Post_Types’, false ) ) {
    return;
    }

    // Remove WooCommerce 3.0 download permission action.
    remove_action( ‘woocommerce_process_product_file_download_paths’, [ ‘WC_Admin_Post_Types’, ‘process_product_file_download_paths’ ], 10, 3 );

    // Backwards compatibility method.
    add_action( ‘woocommerce_process_product_file_download_paths’, [ $this, ‘grant_download_permissions’ ], 10, 3 );
    }

    /**
    * Return an instance of this class.
    *
    * @return object A single instance of this class.
    */
    public static function get_instance(): self {
    // If the single instance hasn’t been set, set it now.
    return self::$instance ?? ( self::$instance = new self );
    }

    /**
    * Grant download permissions like WooCommerce 2.6.
    *
    * This method performs a heavy query and should be used cautiously.
    *
    * @param int $product_id Product identifier.
    * @param int $variation_id Optional product variation identifier.
    * @param array $downloadable_files Newly set files.
    */
    public function grant_download_permissions( int $product_id, int $variation_id, array $downloadable_files ) {
    global $wpdb;

    // Ensure correct product_id in case of variation.
    $product_id = $variation_id ?: $product_id;

    $product = wc_get_product( $product_id );
    if ( ! $product ) {
    return;
    }

    $existing_download_ids = array_keys( (array) $product->get_downloads() );
    $updated_download_ids = array_keys( (array) $downloadable_files );

    // Determine new and removed download IDs
    $new_download_ids = array_diff( $updated_download_ids, $existing_download_ids );
    $removed_download_ids = array_diff( $existing_download_ids, $updated_download_ids );

    if ( empty( $new_download_ids ) && empty( $removed_download_ids ) ) {
    return;
    }

    // Fetch all orders associated with this product.
    $existing_orders = $wpdb->get_col( $wpdb->prepare(
    “SELECT order_id FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE product_id = %d GROUP BY order_id”,
    $product_id
    ) );

    foreach ( $existing_orders as $order_id ) {
    $order = wc_get_order( $order_id );
    if ( ! $order ) {
    continue;
    }

    // Remove permissions for old files
    foreach ( $removed_download_ids as $download_id ) {
    if ( apply_filters( ‘woocommerce_process_product_file_download_paths_remove_access_to_old_file’, true, $download_id, $product_id, $order ) ) {
    $wpdb->query( $wpdb->prepare(
    “DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE order_id = %d AND product_id = %d AND download_id = %s”,
    $order->get_id(),
    $product_id,
    $download_id
    ) );
    }
    }

    // Add permissions for new files
    foreach ( $new_download_ids as $download_id ) {
    if ( apply_filters( ‘woocommerce_process_product_file_download_paths_grant_access_to_new_file’, true, $download_id, $product_id, $order ) ) {
    // Grant permission if it doesn’t already exist
    $exists = $wpdb->get_var( $wpdb->prepare(
    “SELECT 1 FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE order_id = %d AND product_id = %d AND download_id = %s”,
    $order->get_id(),
    $product_id,
    $download_id
    ) );

    if ( ! $exists ) {
    wc_downloadable_file_permission( $download_id, $product_id, $order );
    }
    }
    }
    }
    }
    }

    // Initialize the plugin
    add_action( ‘admin_init’, [ ‘WooCommerce_Legacy_Grant_Download_Permissions’, ‘get_instance’ ] );

  2. U315296۲۱ شهریور ۱۴۰۳

    سلام
    سوالی که برایم هست اینه که چرا این قطعه کد را در بروزرسانی های اهورا وارد نمیکنید؟
    تا هر دفعه کاربران مجبور نشوند محتویات فایل function را ویرایش کنند.
    با احترام

    • رضا راد۲۱ شهریور ۱۴۰۳

      سلام همه نیاز به این مورد ندارند.

  3. U41451۱۹ مهر ۱۴۰۲

    سلام و درود
    من یک سایت آموزشی دارم و یکی از دانشجوها یک آموزشی رو خریداری کرده ولی در پنل کاربریش وارد نشده، و فقط محصول قبلیش در پروفایلش نشان داده میشه.

    • رضا راد۲۲ مهر ۱۴۰۲

      سلام باید خرید رو به حسابشون متصل کنید.

      • U41451۲۲ مهر ۱۴۰۲

        چه شکلی؟
        متوجه این نکته نشدم
        لطفا بیشتر راهنماییم کنید
        سپاسگزارم

        • رضا راد۲۳ مهر ۱۴۰۲

          توی ویرایش سفارش باید خرید متصل بشه به حساب کاربری خریدار

  4. U4852۲۸ دی ۱۴۰۱

    سلام و درود
    قالبی که فایل functions.php اون کد گذاری شده و امکان اضافه کردن کد بهش رو نداریم
    چطوری از این کد استفاده کنیم؟
    ممنون میشم راهنماییم کنید

    • رضا راد۵ بهمن ۱۴۰۱

      سلام طبیعتا باید کدها رو ابتدا decrypt کنید با ابزارهای موجود که معمولا کار سختیه و توسط کسی که کدگذاری شده باید decrypt بشه.

  5. U319896۶ دی ۱۴۰۱

    سلام و احترام ممنون از آموزش مفیدتون ،من استفاده کردم کد مشکلی نداشت و برای محصولاتی که از قبل در پنل کاربران بودند عمل کرد ، من درخواستم درباره این است : محصولی که هنگام ایجاد فایل دانلودیش وارد نشده بود در ووکامرس و منتشر شده و خریدار خرید کرده ، و به طبع در پنلش فایلی قرار نگرفته ، الان راهی هست ( کد یا افزونه ) که برای این مورد هم کارایی داشته باشد و با قرار گیری لینک ها به پنل کاربری افراد اضافه شود ؟
    پیشاپیش ممنون از پاسخگوییتون .

    • رضا راد۱۰ دی ۱۴۰۱

      سلام سپاس از شما. همین کد رو بعد از ویرایش محصول باید اجرا کنید تا برای این مدل محصولات هم کار کنه.

      • U319896۱۱ دی ۱۴۰۱

        ممنون از شما ولی امتحان کردم نشد ، محصولی که فایلی نداشت ، فایل ها را بارگزاری کردم ، بعد مجدد کد را به حالت قبل برگرداندم و مجدد این کد را اضافه کردم و بعد همان محصول را ویرایش کردم( فایلش را یکبار حذف و اضافه و به روزرسانی کردم ، ولی در پنل کاربری افراد اضافه نشد .)

        • رضا راد۱۴ دی ۱۴۰۱

          یکبار یکی از صفحات پنل مدیریت رو باز کنید کد باید اجرا بشه. بارها تست شده و کار می‌کنه. مطمئن بشید جای درستی استفادش میکنید.

  6. U25649۶ تیر ۱۴۰۰

    با سلام و خسته نباشید خددمت شما
    من میخوام وقتی کاربر محصولی رو خرید میکنه از طریق ووکامرس به جای اینکه فایلی رو دانلود کنه یک لینک بهش داده بشه …
    برای مثال وقتی محصول رو خرید و وارد حساب کاربری خودش شد یک لینک https://mihanwp.com مثل این باشه که کلیک کنه و وارد این سایت بشه…
    من وقتی لینک گذاشتم در اون قسمت برای کاربرا میخواد چیزی رو دانلود کنه و نمیتونن وارد اون لینک بشن…
    ممنون میشم اگر امکان داره این کار من رو راهنمایی بفرمایید

    • رضا راد۶ تیر ۱۴۰۰

      درود بر شما باید صفحه تکمیل پرداخت ووکامرس رو ویرایش کنید توی قالب سایتتون

  7. U37224۱۰ اسفند ۱۳۹۹

    سلام-وقت بخیر-من یه مشکلی پبدا کرده سایتم-محصولات دانلودی رو که مشتری خرید میکنه داخل پروفایل کاربریش که میره برا دانلود روی هر گزینه ای که میزنه صفحه عوض نمیشه و نمیتونه سفارشات خودش رو ببینه تا دانلود کنه-فکر میکنم برگه این مورد از ووکامرس غیر فعال هست -ممنون میشم راهنمایی که چیکار کنم؟البته صفحه حساب کاربری در ووکامرس روی my account قرار داره و این مشکل پا برجاست

    • رضا راد۱۲ اسفند ۱۳۹۹

      سلام بستگی به افزونه‌ای داره که برای نمایش سفارشات استفاده می‌کنید. در همون صفحه حساب من ووکامرس این مشکل رو دارید؟

  8. U8071۱۳ بهمن ۱۳۹۹

    سلام!
    برای لایسنس گذاری میشه آموزش بذارین؟

    • رضا راد۱۴ بهمن ۱۳۹۹

      سلام باید php رو ابتدا یاد بگیرید. در دوره php ثبت نام کنید بعد از این دوره یک دوره پلاگین نویسی وردپرس خواهیم داشت.

  9. U17845۹ دی ۱۳۹۹

    من اضافه کردم سایتم از دسترس خارج شد

    • تیم پشتیبانیتیم پشتیبانی۹ دی ۱۳۹۹

      سلام
      چه اروری رو الان مشاهده میکنید؟

  10. U25787۴ دی ۱۳۹۹

    سلام دمتون گرم پست خیلی کاربردی و عالی ای بود حرف ندارین
    فقط یک نکته اضافه کنم اگر قبلا فایل ها رو آپدیت کرده باشن و این کد رو اضافه کنن باز هم نشون نمیده فایل های جدید رو
    باید قبل از اپدیت کردن این کد قرار بگیره و یا اپدیت ها رو که قبلا اضافه کردن لینک هارو از داخل محصول پاک کنن به روزرسانی کنن محصول رو به حالت قبل و بعد دوباره بعد از اضافه کردن کد لینک هارو اضافه کنن تا کار کنه

    • رضا راد۵ دی ۱۳۹۹

      سپاس بله دقیقا

      • U7572۲۰ خرداد ۱۴۰۰

        سلام. وقت بخیر. با توجه به توضیحات دوستمون تو کامنت بالا عمل کردم اما اصلا لینک ها تغییر نمی کنه.
        یعنی اول کد رو گذاشتم . بعد لینک محصول دانلودی رو حذف کردم و بعدش بروزرسانیش کردم. بعد از اون دوباره لینک ها رو قرار دادم. ولی لینک ها اصلا تغییر نکرد
        میشه بیشتر توضیح دهید که چطور باید این مورد رو حل کنم؟

        • رضا راد۲۲ خرداد ۱۴۰۰

          درود بر شما مراحل موجود در آموزش رو انجام دادین؟

          • U7572۲۳ خرداد ۱۴۰۰
            • رضا راد۲۷ خرداد ۱۴۰۰

              توی بخش ویرایش یکی از سفارشات بروزرسانی مجوز دانلود رو بزنید اگر مشکل حل نشد پس با این کد هم حل نمیشه و باید قالب و افزونه‌های سایتتون رو چک کنید.

  11. U17441۴ دی ۱۳۹۹

    سلام. با چه اصولی باید برگه سیاست حریم خصوصی و قوانین و مقررات رو بنویسیم؟

    • تیم پشتیبانیتیم پشتیبانی۴ دی ۱۳۹۹

      سلام
      اطلاعاتی که هنگام نوشتن نظرات نوشته می شوند (مثل نام، نام کاربری، آدرس ایمیل و…). این اطلاعات اغلب قبل از نوشتن نظرات توسط افراد، دریافت می شود.
      به چه روش‌هایی اطلاعات را جمع‌آوری می کنید؟ (مثل : کوکی‌ها، فرم‌های ثبت‌نام، فرم نظرات و…)
      شما باید تمامی اطلاعاتی که شرکت‌های تبلیغاتی دیگر (مثل Google Adsense) جمع‌آوری می‌کنند را نیز در این صفحه بنویسید.
      قرار دادن توضیحات، همراه با لینک های تمامی ” سیاست‌های حفظ حریم خصوصی دیگر سایت ها ” که از آنها استفاده می‌کنید.
      اطلاعات تماس شما یا پشتیبانی سایت، برای مواردی که خوانندگان این صفحه، سوالی در این رابطه داشته

  12. U33979۴ دی ۱۳۹۹

    سلام و درود خدمت میهن وردپرس، ببخشید از حضورتون یه سوال داشتم ممنون میشم جواب بدین، از وقتی که مشکل ایندکس سرچ کنسول رفع شده و بازم می‌تونیم مطلب را به صورت دستی وارد و ایندکس کنیم، مطالب سایت من دیر ایندکس میشه یعنی تا یک هفته طول میکشه، ولی قبل از اون تو یک دقیقه و فوری ایندکس میشد، یعنی این مشل بعد از راه اندازی مجدد ایندکس دستی سرچ کنسول اتفاق افتاده

    • رضا راد۴ دی ۱۳۹۹

      سلام بر شما. بله ظاهرا کمی تغییرات ایجاد کردند. مشکل از سایت شما نیست.

      • U33979۵ دی ۱۳۹۹

        سپاس از پاسختان، راه حلی پیشنهاد میدین که سریع‌تر ایندکس بشه

        • رضا راد۵ دی ۱۳۹۹

          سپاس از شما. برنامه‌ریزی منظم برای انتشار مطالب سایت

پشتیبانپشتیبانپشتیبانپشتیبانپشتیبان
تیم میهن وردپرس
پشتیبانپشتیبانپشتیبانپشتیبانپشتیبان
تیم میهن وردپرسآنلاین و پاسخگوی شما هستیم.آنلاین

در حال بارگذاری...

هر سوالی دارید بپرسید.پاسخگوی شما هستیم.