Newer
Older
itcca-allievi / plugin / itcca-allievi / includes / class-export-csv.php
<?php

declare(strict_types=1);

namespace ItccaAllievi;

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

final class ExportCsv
{
    public static function register(): void
    {
        if (defined('WP_CLI') && WP_CLI) {
            \WP_CLI::add_command('itcca import-csv', [self::class, 'cli_import']);
            \WP_CLI::add_command('itcca export-csv', [self::class, 'cli_export']);
        }
    }

    public static function handle_export(): void
    {
        if (!current_user_can('edit_users')) {
            wp_die(__('Permessi insufficienti.', 'itcca-allievi'));
        }
        check_admin_referer('itcca_export_csv');

        $filename = 'INSARRI-' . date('Y-m-d') . '.csv';
        nocache_headers();
        header('Content-Type: text/csv; charset=UTF-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');

        $out = fopen('php://output', 'w');
        if ($out === false) {
            exit;
        }
        // BOM per Excel
        fwrite($out, "\xEF\xBB\xBF");
        $header = self::csv_header();
        fputcsv($out, $header);

        $users = get_users(['role' => ITCCA_ROLE, 'orderby' => 'meta_value', 'meta_key' => ITCCA_META_PREFIX . 'cognome']);
        foreach ($users as $user) {
            $row = [];
            foreach ($header as $col) {
                $field = Fields::by_csv($col);
                $row[] = $field === null ? '' : Fields::read_value($field, $user);
            }
            fputcsv($out, $row);
        }
        fclose($out);
        exit;
    }

    /**
     * Header del CSV INSARRI: esclude i campi rimossi dallo schema (i loro dati
     * restano in user_meta ma non vengono esportati).
     *
     * @return array<int, string>
     */
    private static function csv_header(): array
    {
        $cols = [];
        foreach (Fields::all() as $f) {
            if (!empty($f['deleted'])) continue;
            $csv = (string) ($f['csv'] ?? '');
            if ($csv === '') continue;
            $cols[] = $csv;
        }
        return $cols;
    }

    /**
     * WP-CLI: import CSV in formato INSARRI come allievi.
     * Usage: wp itcca import-csv /path/to/INSARRI.csv [--dry-run]
     */
    public static function cli_import(array $args, array $assoc): void
    {
        if (!defined('WP_CLI') || !WP_CLI) return;
        $path = $args[0] ?? '';
        if ($path === '' || !file_exists($path)) {
            \WP_CLI::error('File non trovato: ' . $path);
        }
        $dry = !empty($assoc['dry-run']);

        $fh = fopen($path, 'r');
        if ($fh === false) {
            \WP_CLI::error('Impossibile aprire il file.');
        }
        $header = fgetcsv($fh);
        if (!is_array($header)) {
            \WP_CLI::error('Header CSV mancante.');
        }

        $col_idx = [];
        foreach ($header as $i => $h) {
            $field = Fields::by_csv((string) $h);
            if ($field) $col_idx[$field['key']] = $i;
        }
        if (!isset($col_idx['cf'])) {
            \WP_CLI::error('Colonna CF mancante.');
        }

        $created = 0; $updated = 0; $skipped = 0;
        while (($row = fgetcsv($fh)) !== false) {
            $data = [];
            foreach ($col_idx as $key => $idx) {
                $data[$key] = (string) ($row[$idx] ?? '');
            }
            $cf = Validators::normalize_cf((string) ($data['cf'] ?? ''));
            if ($cf === '' || !Validators::is_valid_cf($cf)) {
                $skipped++;
                continue;
            }
            $existing = get_users([
                'meta_key' => ITCCA_META_PREFIX . 'cf',
                'meta_value' => $cf,
                'fields' => 'ID',
                'number' => 1,
            ]);
            if (!empty($existing)) {
                $uid = (int) $existing[0];
                if (!$dry) self::apply_row($uid, $data);
                $updated++;
                continue;
            }
            if ($dry) {
                $created++;
                continue;
            }
            $uid = self::create_user_from_row($data);
            if ($uid > 0) $created++;
            else $skipped++;
        }
        fclose($fh);
        \WP_CLI::success(sprintf('Creati: %d, aggiornati: %d, saltati: %d%s', $created, $updated, $skipped, $dry ? ' (dry-run)' : ''));
    }

    public static function cli_export(array $args, array $assoc): void
    {
        if (!defined('WP_CLI') || !WP_CLI) return;
        $path = $args[0] ?? '';
        if ($path === '') {
            \WP_CLI::error('Specifica il path di output.');
        }
        $fh = fopen($path, 'w');
        if ($fh === false) {
            \WP_CLI::error('Impossibile scrivere su ' . $path);
        }
        fwrite($fh, "\xEF\xBB\xBF");
        $header = self::csv_header();
        fputcsv($fh, $header);
        $users = get_users(['role' => ITCCA_ROLE]);
        foreach ($users as $user) {
            $row = [];
            foreach ($header as $col) {
                $field = Fields::by_csv($col);
                $row[] = $field === null ? '' : Fields::read_value($field, $user);
            }
            fputcsv($fh, $row);
        }
        fclose($fh);
        \WP_CLI::success('Export scritto in ' . $path);
    }

    private static function apply_row(int $user_id, array $data): void
    {
        foreach (Fields::storable() as $field) {
            $key = $field['key'];
            if (!array_key_exists($key, $data)) continue;
            $val = self::normalize_for_storage($field, (string) $data[$key]);
            if (!empty($field['native'])) {
                if ($field['key'] === 'user_email' && $val !== '') {
                    wp_update_user(['ID' => $user_id, 'user_email' => $val]);
                }
                continue;
            }
            $mk = Fields::meta_key($field);
            if ($val === '' && $field['type'] !== 'flag') {
                delete_user_meta($user_id, $mk);
            } else {
                update_user_meta($user_id, $mk, $val);
            }
        }
    }

    private static function create_user_from_row(array $data): int
    {
        $email = sanitize_email((string) ($data['user_email'] ?? ''));
        $cf = Validators::normalize_cf((string) ($data['cf'] ?? ''));
        if ($email === '') {
            $email = strtolower($cf) . '@no-email.itcca.local';
        }
        if (email_exists($email)) {
            return 0;
        }
        $login_base = sanitize_user(
            sanitize_title(remove_accents(((string) ($data['nome'] ?? '')) . '.' . ((string) ($data['cognome'] ?? '')))),
            true
        );
        if ($login_base === '') $login_base = strtolower($cf) ?: 'allievo';
        $login = $login_base;
        $i = 1;
        while (username_exists($login)) {
            $login = $login_base . $i;
            $i++;
        }
        $uid = wp_insert_user([
            'user_login' => $login,
            'user_email' => $email,
            'user_pass'  => wp_generate_password(20, true, true),
            'first_name' => (string) ($data['nome'] ?? ''),
            'last_name'  => (string) ($data['cognome'] ?? ''),
            'display_name' => trim(((string) ($data['nome'] ?? '')) . ' ' . ((string) ($data['cognome'] ?? ''))),
            'role'       => ITCCA_ROLE,
        ]);
        if (is_wp_error($uid)) return 0;
        self::apply_row((int) $uid, $data);
        return (int) $uid;
    }

    private static function normalize_for_storage(array $field, string $value): string
    {
        $value = trim($value);
        if ($value === '') return '';
        switch ($field['type']) {
            case 'date':
                $d = Validators::parse_date($value);
                return $d ? $d->format('Y-m-d') : '';
            case 'cf':
                return Validators::normalize_cf($value);
            case 'cap':
                return preg_replace('/\D/', '', $value) ?? '';
            case 'tel':
                return Validators::normalize_phone($value);
            case 'flag':
                $v = strtolower($value);
                return in_array($v, ['1', 'x', 'sì', 'si', 'yes', 'true'], true) ? '1' : '0';
            case 'decimal':
                $n = (float) str_replace([',', '€', ' '], ['.', '', ''], $value);
                return number_format($n, 2, '.', '');
            default:
                return sanitize_text_field($value);
        }
    }
}