load_dependencies();
// Check required plugins
add_action('admin_init', array($this, 'check_required_plugins'));
// Add admin menu items
add_action('admin_menu', array($this, 'add_admin_menu'), 99);
// Register admin assets
add_action('admin_enqueue_scripts', array($this, 'register_admin_assets'));
// Initialize components
$this->init_components();
// Declare WooCommerce HPOS compatibility
add_action('before_woocommerce_init', array($this, 'declare_hpos_compatibility'));
}
/**
* Declare compatibility with WooCommerce HPOS (High-Performance Order Storage)
*/
public function declare_hpos_compatibility() {
if (class_exists('Automattic\WooCommerce\Utilities\FeaturesUtil')) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
'custom_order_tables',
__FILE__,
true
);
}
}
/**
* Load required dependencies
*/
private function load_dependencies() {
require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-db.php';
require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-export.php';
require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-import.php';
require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-manipulator.php';
require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-db.php';
require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-import.php';
require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-export.php';
}
/**
* Initialize components
*/
private function init_components() {
$this->db = new Studiou_WC_Product_Cat_Manage_DB();
$this->exporter = new Studiou_WC_Product_Cat_Manage_Export($this->db);
$this->importer = new Studiou_WC_Product_Cat_Manage_Import($this->db);
$this->manipulator = new Studiou_WC_Product_Cat_Manage_Manipulator($this->db);
$this->product_db = new Studiou_WC_Product_Manage_Product_DB();
$this->product_importer = new Studiou_WC_Product_Manage_Product_Import($this->product_db);
$this->product_exporter = new Studiou_WC_Product_Manage_Product_Export($this->product_db);
// Register AJAX handlers
add_action('wp_ajax_studiou_wcpcm_product_import_parse', array($this, 'handle_product_import_parse'));
add_action('wp_ajax_studiou_wcpcm_product_import_batch', array($this, 'handle_product_import_batch'));
add_action('wp_ajax_studiou_wcpcm_product_export', array($this, 'handle_product_export'));
}
/**
* Check if required plugins are active
*/
public function check_required_plugins() {
if (!is_admin() || isset($_GET['activate-multi'])) {
return;
}
// Check for WooCommerce
if (!class_exists('WooCommerce') || !defined('WC_VERSION') || version_compare(WC_VERSION, '9.8', '<')) {
add_action('admin_notices', array($this, 'woocommerce_missing_notice'));
deactivate_plugins(STUDIOU_WCPCM_PLUGIN_BASENAME);
if (isset($_GET['activate'])) {
unset($_GET['activate']);
}
}
}
/**
* WooCommerce missing notice
*/
public function woocommerce_missing_notice() {
?>
get_cache_busted_asset_url('assets/css/admin', 'css'),
array(),
STUDIOU_WCPCM_VERSION
);
wp_enqueue_script(
'studiou-wcpcm-admin',
$this->get_cache_busted_asset_url('assets/js/admin', 'js'),
array('jquery'),
STUDIOU_WCPCM_VERSION,
true
);
wp_localize_script('studiou-wcpcm-admin', 'studiouWcpcm', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('studiou-wcpcm-nonce'),
'i18n' => array(
'importSuccess' => __('Import successful', 'studiou-wc-product-cat-manage'),
'importError' => __('Import error', 'studiou-wc-product-cat-manage'),
'exportSuccess' => __('Export successful', 'studiou-wc-product-cat-manage'),
'exportError' => __('Export error', 'studiou-wc-product-cat-manage'),
'moveSuccess' => __('Move operation successful', 'studiou-wc-product-cat-manage'),
'moveError' => __('Move operation error', 'studiou-wc-product-cat-manage'),
'selectDifferentCategories' => __('Please select different source and target categories', 'studiou-wc-product-cat-manage'),
'batchDescriptionSuccess' => __('Batch description operation successful', 'studiou-wc-product-cat-manage'),
'batchDescriptionError' => __('Batch description operation error', 'studiou-wc-product-cat-manage'),
'deleteSuccess' => __('Delete operation successful', 'studiou-wc-product-cat-manage'),
'deleteError' => __('Delete operation error', 'studiou-wc-product-cat-manage'),
'confirmDelete' => __('Are you sure you want to permanently delete ALL products in the selected categories? This action cannot be undone!', 'studiou-wc-product-cat-manage'),
'selectAtLeastOneCategory' => __('Please select at least one category', 'studiou-wc-product-cat-manage'),
'noProductsFound' => __('No products found in selected categories', 'studiou-wc-product-cat-manage'),
'confirmClearMedia' => __('Are you sure you want to permanently delete ALL media files in the selected media categories? This action cannot be undone!', 'studiou-wc-product-cat-manage'),
'clearMediaSuccess' => __('Clear media operation successful', 'studiou-wc-product-cat-manage'),
'clearMediaError' => __('Clear media operation error', 'studiou-wc-product-cat-manage'),
'noMediaFound' => __('No media files found in selected categories', 'studiou-wc-product-cat-manage'),
'confirmRemoveUnassigned' => __('Are you sure you want to permanently delete ALL unused uncategorized media files? This action cannot be undone!', 'studiou-wc-product-cat-manage'),
'removeUnassignedSuccess' => __('Remove unassigned media operation successful', 'studiou-wc-product-cat-manage'),
'removeUnassignedError' => __('Remove unassigned media operation error', 'studiou-wc-product-cat-manage'),
'noUnassignedMedia' => __('No unused uncategorized media files found', 'studiou-wc-product-cat-manage'),
'upvpSelectAttribute' => __('-- Select attribute --', 'studiou-wc-product-cat-manage'),
'upvpSelectValue' => __('-- Select value --', 'studiou-wc-product-cat-manage'),
'upvpInvalidPrice' => __('Please enter a valid new price', 'studiou-wc-product-cat-manage'),
'upvpLoadingAttributes' => __('Loading attributes...', 'studiou-wc-product-cat-manage'),
'upvpLoadingValues' => __('Loading values...', 'studiou-wc-product-cat-manage'),
'upvpDryRunComplete' => __('Dry run complete: %d variations would be updated to %s.', 'studiou-wc-product-cat-manage'),
'upvpApplySuccess' => __('Variant prices updated successfully', 'studiou-wc-product-cat-manage'),
'upvpApplyError' => __('Variant prices update error', 'studiou-wc-product-cat-manage'),
'upvpNoMatches' => __('No variations match the current filter.', 'studiou-wc-product-cat-manage'),
'upvpConfirmApply' => __('Are you sure you want to update %d variations?', 'studiou-wc-product-cat-manage'),
'tabLoading' => __('Loading...', 'studiou-wc-product-cat-manage'),
'tabLoadError' => __('Failed to load tab', 'studiou-wc-product-cat-manage'),
)
));
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('STUDIOU WC: Admin assets loaded successfully');
}
}
}
/**
* Build a cache-busting URL for a plugin asset.
*
* studiou.cz (and similar host optimisers) strip the `?ver=` query string
* from static assets, so a version bump alone does not bust the browser
* cache. To work around that, this serves the asset under a filename that
* embeds the plugin version, e.g. assets/js/admin-1.6.0.js. The versioned
* copy is generated automatically from the source file and refreshed
* whenever the source changes, so no manual file renaming is needed.
*
* Falls back to the plain source URL if the copy cannot be written.
*
* @param string $rel_no_ext Asset path relative to the plugin dir, without
* extension, e.g. 'assets/js/admin'.
* @param string $ext File extension without the dot, e.g. 'js'.
* @return string Absolute URL to the asset.
*/
private function get_cache_busted_asset_url($rel_no_ext, $ext) {
$source_rel = $rel_no_ext . '.' . $ext;
$source_abs = STUDIOU_WCPCM_PLUGIN_DIR . $source_rel;
// Source missing - nothing we can do, return the plain URL.
if (!file_exists($source_abs)) {
return STUDIOU_WCPCM_PLUGIN_URL . $source_rel;
}
$versioned_rel = $rel_no_ext . '-' . STUDIOU_WCPCM_VERSION . '.' . $ext;
$versioned_abs = STUDIOU_WCPCM_PLUGIN_DIR . $versioned_rel;
// Regenerate the versioned copy if it is missing or out of date.
if (!file_exists($versioned_abs) || filemtime($source_abs) > filemtime($versioned_abs)) {
// Remove stale versioned copies of this asset.
$old_files = glob(STUDIOU_WCPCM_PLUGIN_DIR . $rel_no_ext . '-*.' . $ext);
if (is_array($old_files)) {
foreach ($old_files as $old_file) {
@unlink($old_file);
}
}
// Copy failed (e.g. read-only filesystem) - fall back to plain URL.
if (!@copy($source_abs, $versioned_abs)) {
return STUDIOU_WCPCM_PLUGIN_URL . $source_rel;
}
}
return STUDIOU_WCPCM_PLUGIN_URL . $versioned_rel;
}
/**
* Render import page
*/
public function render_import_page() {
include STUDIOU_WCPCM_PLUGIN_DIR . 'views/import.php';
}
/**
* Render export page
*/
public function render_export_page() {
include STUDIOU_WCPCM_PLUGIN_DIR . 'views/export.php';
}
/**
* Render manipulations page
*/
public function render_manipulations_page() {
include STUDIOU_WCPCM_PLUGIN_DIR . 'views/manipulations.php';
}
/**
* Render product import page
*/
public function render_product_import_page() {
include STUDIOU_WCPCM_PLUGIN_DIR . 'views/product-import.php';
}
/**
* Render product export page
*/
public function render_product_export_page() {
include STUDIOU_WCPCM_PLUGIN_DIR . 'views/product-export.php';
}
/**
* Handle product import parse AJAX request (step 1: parse CSV)
*/
public function handle_product_import_parse() {
// Clean any output buffers
while (ob_get_level()) {
ob_end_clean();
}
// Verify nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
return;
}
// Check permissions
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
return;
}
// Check if file was uploaded
if (!isset($_FILES['import_file']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
wp_send_json_error(array('message' => __('No file uploaded or upload error', 'studiou-wc-product-cat-manage')));
return;
}
try {
// Parse CSV and store in transient
$result = $this->product_importer->parse_csv_for_batch($_FILES['import_file']['tmp_name']);
if (isset($result['error'])) {
wp_send_json_error(array('message' => $result['error']));
return;
}
wp_send_json_success(array(
'transient_key' => $result['transient_key'],
'total' => $result['total']
));
} catch (Exception $e) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('STUDIOU WC Product Import Parse Error: ' . $e->getMessage());
}
wp_send_json_error(array('message' => $e->getMessage()));
}
}
/**
* Handle product import batch AJAX request (step 2: process batch)
*/
public function handle_product_import_batch() {
// Clean any output buffers
while (ob_get_level()) {
ob_end_clean();
}
// Verify nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
return;
}
// Check permissions
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
return;
}
// Get parameters
$transient_key = isset($_POST['transient_key']) ? sanitize_text_field($_POST['transient_key']) : '';
$offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
if (empty($transient_key)) {
wp_send_json_error(array('message' => __('Invalid request', 'studiou-wc-product-cat-manage')));
return;
}
try {
// Process batch
$result = $this->product_importer->process_batch($transient_key, $offset, 5);
if (isset($result['error'])) {
wp_send_json_error(array('message' => $result['error']));
return;
}
// Generate failed CSV if this is the last batch and there are failed items
$failed_csv = '';
if ($result['processed'] >= $result['total'] && !empty($result['failed_items'])) {
$failed_csv = $this->product_importer->generate_failed_csv($result['failed_items']);
}
wp_send_json_success(array(
'success' => $result['success'],
'failed' => $result['failed'],
'skipped' => $result['skipped'],
'messages' => $result['messages'],
'processed' => $result['processed'],
'total' => $result['total'],
'failed_csv' => $failed_csv
));
} catch (Exception $e) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('STUDIOU WC Product Import Batch Error: ' . $e->getMessage());
}
wp_send_json_error(array('message' => $e->getMessage()));
}
}
/**
* Handle product export AJAX request
*/
public function handle_product_export() {
// Clean any output buffers
while (ob_get_level()) {
ob_end_clean();
}
// Verify nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
return;
}
// Check permissions
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
return;
}
// Get selected categories
$category_ids = isset($_POST['export_categories']) ? array_map('intval', $_POST['export_categories']) : array();
if (empty($category_ids)) {
wp_send_json_error(array('message' => __('No categories selected', 'studiou-wc-product-cat-manage')));
return;
}
try {
// Generate export file
$result = $this->product_exporter->generate_export_file($category_ids);
if ($result['success']) {
wp_send_json_success(array(
'message' => $result['message'],
'download_url' => $result['download_url']
));
} else {
wp_send_json_error(array('message' => $result['message']));
}
} catch (Exception $e) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('STUDIOU WC Product Export Error: ' . $e->getMessage());
}
wp_send_json_error(array('message' => $e->getMessage()));
}
}
}
/**
* Begin execution of the plugin
*/
function run_studiou_wc_product_cat_manage() {
// Only initialize once
static $initialized = false;
if ($initialized) {
return;
}
$initialized = true;
// Load text domain first
load_plugin_textdomain(
'studiou-wc-product-cat-manage',
false,
dirname(plugin_basename(__FILE__)) . '/languages/'
);
// Create instance
new Studiou_WC_Product_Cat_Manage();
}
// Run the plugin after plugins are loaded to ensure WooCommerce is available
add_action('plugins_loaded', 'run_studiou_wc_product_cat_manage');