get(Pages::class); $filter = $container->get(Filter::class); $orderColumn = $container->get(OrderColumn::class); $orderMetaBox = $container->get(OrderMetaBox::class); $orderStatuses = $container->get(OrderStatuses::class); $actions = $container->get(Actions::class); $bulkEdit = $container->get(BulkEdit::class); // Initialize Admin Actions $container->get(RegisterActions::class); // Admin Menu \add_action("admin_menu", [$pages, "registerMenus"]); \add_filter("script_loader_tag", [ChatWidget::class, "addTokenToWidgetScript"], 10, 3); // Admin Scripts & Styles \add_action("admin_enqueue_scripts", [self::class, "adminEnqueueStyles"]); \add_action("admin_enqueue_scripts", [self::class, "adminEnqueueScripts"]); \add_action("admin_enqueue_scripts", [self::class, "financialManagementEnqueueAssets"]); \add_action("admin_footer", [AnnouncementCenter::class, 'renderPanel']); // Financial Management (مدیریت مالی) FinancialManagementHistory::register(); BalanceSettlement::register(); // Product Columns \add_filter("manage_edit-product_columns", [StatusColumn::class, "registerStatusColumn"]); \add_action("manage_product_posts_custom_column", [StatusColumn::class, "renderStatusColumnContent"], 10, 2); // Product Meta Boxes \add_action("add_meta_boxes_product", [MetaBox::class, "registerMetaBox"]); // Basalam Product Settings Tab \add_filter('woocommerce_product_data_tabs', [Tab::class, 'registerTab']); \add_action('woocommerce_product_data_panels', [Tab::class, 'renderTabContent']); \add_action('woocommerce_process_product_meta', [Tab::class, 'saveTabData']); // Basalam Product Video Meta Box (sidebar, like product image/gallery) \add_action('add_meta_boxes_product', [VideoField::class, 'registerMetaBox']); \add_action('woocommerce_process_product_meta', [VideoField::class, 'save']); // Product Filter \add_action("restrict_manage_posts", [$filter, "renderFilterDropdown"]); \add_action("pre_get_posts", [$filter, "applyFilterToQuery"]); // Order Columns (HPOS) \add_action("manage_woocommerce_page_wc-orders_custom_column", [$orderColumn, "renderColumn"], 10, 2); \add_filter("manage_woocommerce_page_wc-orders_columns", [$orderColumn, "addColumn"]); // Order Columns (Traditional CPT - non-HPOS) \add_action("manage_shop_order_posts_custom_column", [$orderColumn, "renderColumn"], 10, 2); \add_filter("manage_edit-shop_order_columns", [$orderColumn, "addColumn"]); // Order Meta Boxes \add_action("add_meta_boxes", [$orderMetaBox, "registerMetaBox"], 10); // Order Meta Boxes (Traditional CPT - non-HPOS) \add_action("add_meta_boxes_shop_order", [$orderMetaBox, "registerMetaBox"], 10); // Order Statuses \add_filter("wc_order_statuses", [$orderStatuses, "registerorderStatuses"]); // Order Stock Levels \add_action("woocommerce_order_status_bslm-rejected", "wc_maybe_increase_stock_levels"); \add_action("woocommerce_order_status_bslm-preparation", "wc_maybe_reduce_stock_levels"); \add_action("woocommerce_order_status_bslm-shipping", "wc_maybe_reduce_stock_levels"); \add_action("woocommerce_order_status_bslm-completed", "wc_maybe_reduce_stock_levels"); // Bulk Actions \add_filter("bulk_actions-edit-product", [$actions, "registerBulkActions"]); \add_filter("handle_bulk_actions-edit-product", [$actions, "handleBulkAction"], 10, 3); \add_action('bulk_edit_custom_box', [$bulkEdit, 'renderFields'], 10, 2); \add_action('admin_action_sync_basalam_bulk_edit', [$bulkEdit, 'save']); // Product Duplicate // نسخه کپی یک محصول تازه است و نباید هیچ داده ووسلامی از محصول اصلی به ارث ببرد؛ // مخصوصا شناسه اتصال، وگرنه بروزرسانی نسخه کپی، محصول اصلی را در باسلام تغییر می‌دهد. \add_filter("woocommerce_duplicate_product_exclude_meta", [self::class, "excludePluginMetaFromDuplicate"], 10, 2); \add_action("woocommerce_product_duplicate", function ($newProduct) { if (!is_object($newProduct) || !method_exists($newProduct, "get_id")) return; ProductConnection::purgeAll($newProduct->get_id()); }, 10, 1); // Product Duplicate (third party plugins, e.g. Yoast Duplicate Post) foreach (["dp_duplicate_post", "dp_duplicate_page", "duplicate_post_post_copy"] as $duplicateHook) { \add_action($duplicateHook, function ($newPostId, $sourcePost = null) { if (!is_scalar($newPostId) || \get_post_type((int) $newPostId) !== "product") return; // اگر هوک، پست مبدا را هم بدهد مطمئنیم شناسه اول نسخه کپی است. if (is_object($sourcePost) && !empty($sourcePost->ID) && (int) $sourcePost->ID !== (int) $newPostId) { ProductConnection::purgeAll((int) $newPostId); return; } // در غیر این صورت محافظه‌کارانه عمل می‌کنیم تا داده محصول اصلی پاک نشود. ProductConnection::purgeIfCopy((int) $newPostId); }, 10, 2); } // Duplicate connection report (admin notice + dismiss handler) DuplicateConnectionReport::registerHooks(); // Order Check Buttons (HPOS) \add_action("woocommerce_order_list_table_extra_tablenav", [OrderPageComponents::class, "renderCheckOrdersButton"], 20, 1); // Order Check Buttons (Traditional CPT - uses same hook as product filter) \add_action("restrict_manage_posts", [OrderPageComponents::class, "renderCheckOrdersButtonTraditional"]); // Initialize AJAX handlers $connectProduct = $container->get(ConnectProduct::class); add_action('wp_ajax_basalam_search_products', [$connectProduct, 'handleSearchProducts']); add_action('wp_ajax_sync_basalam_mark_pointer_onboarding_completed', [PointerTour::class, 'markPointerOnboardingCompleted']); add_action('wp_ajax_' . AnnouncementCenter::MARK_SEEN_ACTION, [AnnouncementCenter::class, 'markAllSeen']); add_action('wp_ajax_' . AnnouncementCenter::FETCH_PAGE_ACTION, [AnnouncementCenter::class, 'fetchPage']); // Tasks per minute calculation handler add_action('wp_ajax_basalam_calculate_tasks_per_minute', function () { check_ajax_referer('basalam_update_setting_nonce', 'nonce', true); $monitor = syncBasalamContainer()->get(SystemResourceMonitor::class); $optimal = $monitor->calculateOptimalTasksPerMinute(); wp_send_json_success([ 'optimal_tasks_per_minute' => $optimal ]); }); } /** * حذف همه متاهای ووسلام از نسخه کپی، پیش از ذخیره شدن آن توسط ووکامرس. * ووکامرس همین فهرست را روی تنوع‌های کپی شده هم اعمال می‌کند. * * @param array $excludedMeta کلیدهایی که تا اینجا برای حذف انتخاب شده‌اند. * @param array $productMetaKeys کلیدهای متای محصول اصلی. */ public static function excludePluginMetaFromDuplicate($excludedMeta, $productMetaKeys = []) { $excludedMeta = is_array($excludedMeta) ? $excludedMeta : []; foreach ((array) $productMetaKeys as $metaKey) { if (ProductConnection::isPluginMetaKey($metaKey)) $excludedMeta[] = $metaKey; } // کلیدهای تنوع‌ها در فهرست متای محصول والد نیستند و باید دستی اضافه شوند. $excludedMeta = array_merge( $excludedMeta, ProductMetaKey::basalamProductMetaKeys(), [ProductMetaKey::basalamVariationId(), ProductMetaKey::DISCOUNTED] ); return array_values(array_unique($excludedMeta)); } public static function assetsUrl($path = null) { return plugin_dir_url(dirname(__FILE__, 2)) . "assets/" . $path; } public static function isWoosalamRelatedAdminScreen($hook = ''): bool { return self::isPluginAdminPage($hook) || self::isProductAdminScreen($hook) || self::isOrderAdminScreen($hook); } private static function isPluginAdminPage($hook = ''): bool { $page = self::currentAdminPage(); if ($page !== '' && in_array($page, self::pluginAdminPages(), true)) { return true; } return is_string($hook) && strpos($hook, 'sync_basalam') !== false; } private static function isProductAdminScreen($hook = ''): bool { $screen = function_exists('get_current_screen') ? get_current_screen() : null; if ($screen && isset($screen->post_type) && $screen->post_type === 'product') { return true; } $postType = isset($_GET['post_type']) ? sanitize_key(wp_unslash($_GET['post_type'])) : ''; if ($postType === 'product') { return true; } $postId = isset($_GET['post']) ? absint($_GET['post']) : 0; if ($postId > 0 && function_exists('get_post_type') && get_post_type($postId) === 'product') { return true; } return in_array((string) $hook, ['edit-product', 'product'], true); } private static function isOrderAdminScreen($hook = ''): bool { $screen = function_exists('get_current_screen') ? get_current_screen() : null; if ($screen) { $screenId = isset($screen->id) ? (string) $screen->id : ''; $postType = isset($screen->post_type) ? (string) $screen->post_type : ''; if ($postType === 'shop_order' || $screenId === 'woocommerce_page_wc-orders') { return true; } } $page = self::currentAdminPage(); if ($page === 'wc-orders') { return true; } $postType = isset($_GET['post_type']) ? sanitize_key(wp_unslash($_GET['post_type'])) : ''; if ($postType === 'shop_order') { return true; } $postId = isset($_GET['post']) ? absint($_GET['post']) : 0; if ($postId > 0 && function_exists('get_post_type') && get_post_type($postId) === 'shop_order') { return true; } return in_array((string) $hook, ['edit-shop_order', 'shop_order', 'woocommerce_page_wc-orders'], true); } private static function isProductEditScreen($hook = ''): bool { $screen = function_exists('get_current_screen') ? get_current_screen() : null; if ($screen && isset($screen->post_type, $screen->base)) { return $screen->post_type === 'product' && in_array($screen->base, ['post', 'post-new'], true); } return self::isProductAdminScreen($hook) && in_array((string) $hook, ['post.php', 'post-new.php', 'product'], true); } private static function isPluginPage(string $page): bool { return self::currentAdminPage() === $page; } private static function isTicketPage(): bool { return in_array(self::currentAdminPage(), [ 'sync_basalam_tickets', 'sync_basalam_ticket', 'sync_basalam_new_ticket', ], true); } private static function currentAdminPage(): string { return isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; } private static function pluginAdminPages(): array { return [ 'sync_basalam', 'sync_basalam_logs', 'sync_basalam_vendor_info', 'sync_basalam_help', 'sync_basalam_category_mapping', FinancialManagementMenu::PAGE_SLUG, 'basalam-onboarding', 'basalam-save-token', 'basalam-show-products', 'sync_basalam_tickets', 'sync_basalam_ticket', 'sync_basalam_new_ticket', ]; } public static function adminEnqueueStyles($hook = '') { if (!self::isWoosalamRelatedAdminScreen($hook)) { return; } $version = syncbasalamplugin()->getVersion(); wp_enqueue_style( "basalam-admin-style", self::assetsUrl("css/style.css"), array(), $version ); wp_enqueue_style( "basalam-admin-font-style", self::assetsUrl("css/font.css"), array(), $version ); if (self::isPluginPage('sync_basalam_vendor_info')) { wp_enqueue_style( "basalam-admin-social-style", self::assetsUrl("css/social.css"), array(), $version ); } if (self::isPluginPage('sync_basalam_logs')) { wp_enqueue_style( "basalam-admin-logs-style", self::assetsUrl("css/logs.css"), array(), $version ); } if (self::isPluginPage('basalam-onboarding')) { wp_enqueue_style( "basalam-admin-onboarding-style", self::assetsUrl("css/onboarding.css"), array(), $version ); } wp_enqueue_style( "basalam-admin-toast-style", self::assetsUrl("css/toast.css"), array(), $version ); if (PointerTour::shouldLoadPointerTour((string) $hook)) { wp_enqueue_style('wp-pointer'); } } public static function adminEnqueueScripts($hook = '') { $version = syncbasalamplugin()->getVersion(); if (ChatWidget::shouldLoadWidget()) { wp_enqueue_script( "basalam-chat-widget-script", self::assetsUrl("chat/widget-loader.js"), [], $version, true ); } $shouldLoadPointerTour = PointerTour::shouldLoadPointerTour((string) $hook); $shouldLoadAssets = self::isWoosalamRelatedAdminScreen($hook); if (!$shouldLoadAssets && !$shouldLoadPointerTour) { return; } $isPluginPage = self::isPluginAdminPage($hook); $isMainPage = self::isPluginPage('sync_basalam'); $isProductScreen = self::isProductAdminScreen($hook); $isProductEditScreen = self::isProductEditScreen($hook); $isOrderScreen = self::isOrderAdminScreen($hook); if ($shouldLoadPointerTour) { wp_enqueue_script('wp-pointer'); } wp_enqueue_script( "basalam-admin-toast-script", self::assetsUrl("js/toast.js"), [], $version, true ); $vendorStatusScriptPath = syncbasalamplugin()->pluginPath() . '/assets/js/vendor-status.js'; $vendorStatusScriptVersion = file_exists($vendorStatusScriptPath) ? $version . '-' . filemtime($vendorStatusScriptPath) : $version; wp_enqueue_script( "basalam-vendor-status-script", self::assetsUrl("js/vendor-status.js"), ["basalam-admin-toast-script"], $vendorStatusScriptVersion, true ); $ticketNotice = TicketFlashNotice::pull(); if ($ticketNotice !== null) { wp_add_inline_script( "basalam-admin-toast-script", sprintf( 'window.addEventListener("DOMContentLoaded",function(){if(window.BasalamToast){window.BasalamToast.show(%s,%s);}});', wp_json_encode($ticketNotice['message'], JSON_UNESCAPED_UNICODE), wp_json_encode($ticketNotice['type']) ), 'after' ); } $settingsValidationError = SettingsPageHandler::pullValidationError(); if ($settingsValidationError !== '') { wp_add_inline_script( "basalam-admin-toast-script", sprintf( 'window.addEventListener("DOMContentLoaded",function(){if(window.BasalamToast){window.BasalamToast.error(%s);}});', wp_json_encode($settingsValidationError, JSON_UNESCAPED_UNICODE) ), 'after' ); } if (self::isPluginPage('sync_basalam_logs')) { wp_enqueue_script( "basalam-admin-logs-script", self::assetsUrl("js/logs.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); } if (self::isPluginPage('sync_basalam_help')) { wp_enqueue_script( "basalam-admin-help-script", self::assetsUrl("js/help.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); } if ($isProductEditScreen) { wp_enqueue_script( "basalam-admin-product-fields-script", self::assetsUrl("js/product-fields.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); wp_enqueue_script( "basalam-admin-connect-modal-script", self::assetsUrl("js/connect-modal.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); wp_enqueue_script( "basalam-get-category-script", self::assetsUrl("js/get-category.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); wp_enqueue_script( "basalam-generate-product-variation-script", self::assetsUrl("js/generate-product-variation.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); } if ($isMainPage || $isProductEditScreen) { wp_enqueue_script( "basalam-admin-manage-box-script", self::assetsUrl("js/manage-box.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); } if ($isMainPage) { wp_enqueue_script( "basalam-map-category-option-script", self::assetsUrl("js/map-category-option.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); } if ($isMainPage || $isProductScreen) { wp_enqueue_script( "basalam-round-script", self::assetsUrl("js/round.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); } if ($isOrderScreen) { wp_enqueue_script( "basalam-order-script", self::assetsUrl("js/order.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); } if ($isMainPage || $isOrderScreen) { wp_enqueue_script( "basalam-check-sync-script", self::assetsUrl("js/check-sync.js"), ["jquery", "basalam-admin-toast-script"], $version, true ); } if ($isPluginPage || $isProductScreen || $isOrderScreen || $shouldLoadPointerTour) { $adminScriptPath = syncBasalamPlugin()->pluginPath() . '/assets/js/admin.js'; $adminScriptVersion = file_exists($adminScriptPath) ? $version . '-' . filemtime($adminScriptPath) : $version; wp_enqueue_script( "basalam-admin-script", self::assetsUrl("js/admin.js"), $shouldLoadPointerTour ? ["jquery", "wp-pointer", "basalam-admin-toast-script"] : ["jquery", "basalam-admin-toast-script"], $adminScriptVersion, true ); } if (self::isTicketPage()) { $ticketScriptPath = syncBasalamPlugin()->pluginPath() . '/assets/js/ticket.js'; $ticketScriptVersion = file_exists($ticketScriptPath) ? $version . '-' . filemtime($ticketScriptPath) : $version; wp_enqueue_script( "basalam-ticket-script", self::assetsUrl("js/ticket.js"), ["basalam-admin-toast-script"], $ticketScriptVersion, true ); } if ($shouldLoadPointerTour) { wp_localize_script('basalam-admin-script', 'basalamPointerTour', PointerTour::getPointerTourConfig()); } if (wp_script_is('basalam-admin-script', 'enqueued') && AnnouncementCenter::shouldLoadAnnouncement()) { wp_localize_script('basalam-admin-script', 'basalamAnnouncements', AnnouncementCenter::getConfig()); } } public static function financialManagementEnqueueAssets($hook = '') { $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : ''; if ($page !== FinancialManagementMenu::PAGE_SLUG) { return; } $version = syncbasalamplugin()->getVersion(); wp_enqueue_style( 'sync-basalam-finance-style', self::assetsUrl('css/finance.css'), [], $version ); wp_enqueue_script( 'sync-basalam-finance-history-pagination', self::assetsUrl('js/finance-history-pagination.js'), [], $version, true ); wp_localize_script('sync-basalam-finance-history-pagination', 'syncBasalamHistoryPagination', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'action' => FinancialManagementHistory::AJAX_ACTION, 'nonce' => wp_create_nonce(FinancialManagementHistory::AJAX_ACTION), 'pageSlug' => FinancialManagementMenu::PAGE_SLUG, 'errorMessage' => 'بارگذاری تاریخچه تسویه انجام نشد.', ]); wp_enqueue_script( 'sync-basalam-finance-balance-settlement', self::assetsUrl('js/finance-balance-settlement.js'), [], $version, true ); wp_localize_script('sync-basalam-finance-balance-settlement', 'syncBasalamBalanceSettlement', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'action' => BalanceSettlement::AJAX_ACTION, 'bankAccountsAction' => BalanceSettlement::AJAX_ACTION_BANK_ACCOUNTS, 'nonce' => wp_create_nonce(BalanceSettlement::AJAX_ACTION), 'submitText' => 'ثبت درخواست', 'loadingText' => 'در حال ارسال...', 'errorMessage' => 'خطا در ثبت درخواست تسویه.', 'successMessage' => 'درخواست تسویه با موفقیت ثبت شد.', 'amountError' => 'لطفاً مبلغ معتبری وارد کنید.', 'walletTitle' => 'انتقال به کیف پول', 'bankTitle' => 'انتقال به حساب بانکی', 'bankAccountsError' => 'خطا در دریافت لیست حساب‌های بانکی.', ]); } }