'factura',
'plural' => 'facturas',
'ajax' => false,
)
);
}
public function get_columns(): array {
return array(
'cb' => '',
'invoice_number' => __( 'Nº Factura', '8digits-factura-for-woocommerce' ),
'order_id' => __( 'Pedido', '8digits-factura-for-woocommerce' ),
'customer' => __( 'Cliente', '8digits-factura-for-woocommerce' ),
'invoice_date' => __( 'Fecha', '8digits-factura-for-woocommerce' ),
'total' => __( 'Total', '8digits-factura-for-woocommerce' ),
'tax' => __( 'IVA', '8digits-factura-for-woocommerce' ),
'status' => __( 'Estado', '8digits-factura-for-woocommerce' ),
'actions' => __( 'Acciones', '8digits-factura-for-woocommerce' ),
);
}
public function get_sortable_columns(): array {
return array(
'invoice_number' => array( 'invoice_number', false ),
'order_id' => array( 'order_id', false ),
'invoice_date' => array( 'invoice_date', true ),
'total' => array( 'total', false ),
);
}
protected function get_bulk_actions(): array {
return array(
'void' => __( 'Anular', '8digits-factura-for-woocommerce' ),
'regenerate' => __( 'Regenerar PDFs', '8digits-factura-for-woocommerce' ),
'zip' => __( 'Descargar ZIP', '8digits-factura-for-woocommerce' ),
'resend' => __( 'Reenviar emails', '8digits-factura-for-woocommerce' ),
);
}
public function prepare_items(): void {
$per_page = 20;
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only list filters and ordering.
$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
$status_filter = isset( $_GET['eightdigits_factura_status'] ) ? sanitize_text_field( wp_unslash( $_GET['eightdigits_factura_status'] ) ) : '';
$date_from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '';
$date_to = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '';
$paged = isset( $_GET['paged'] ) ? max( 1, absint( wp_unslash( $_GET['paged'] ) ) ) : 1;
$allowed_orderby = array( 'invoice_number', 'order_id', 'invoice_date', 'total' );
$orderby = isset( $_GET['orderby'] ) && in_array( sanitize_key( wp_unslash( $_GET['orderby'] ) ), $allowed_orderby, true )
? sanitize_key( wp_unslash( $_GET['orderby'] ) )
: 'invoice_date';
$order = isset( $_GET['order'] ) && 'asc' === strtolower( sanitize_key( wp_unslash( $_GET['order'] ) ) ) ? 'ASC' : 'DESC';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
list( $items, $total ) = self::query_invoices(
array(
'per_page' => $per_page,
'paged' => $paged,
'search' => $search,
'status_filter' => $status_filter,
'date_from' => $date_from,
'date_to' => $date_to,
'orderby' => $orderby,
'order' => $order,
)
);
$this->items = $items;
$this->set_pagination_args(
array(
'total_items' => $total,
'per_page' => $per_page,
'total_pages' => (int) ceil( $total / $per_page ),
)
);
$this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );
}
private static function query_invoices( array $args ): array {
global $wpdb;
$table = esc_sql( $wpdb->prefix . 'eightdigits_factura_invoices' );
$wheres = array( "invoice_type = 'invoice'" );
$params = array();
if ( '' !== $args['search'] ) {
$like = '%' . $wpdb->esc_like( $args['search'] ) . '%';
$wheres[] = '(invoice_number LIKE %s OR order_id LIKE %s)';
$params[] = $like;
$params[] = $like;
}
if ( '' !== $args['status_filter'] ) {
$wheres[] = 'status = %s';
$params[] = $args['status_filter'];
}
if ( '' !== $args['date_from'] ) {
$wheres[] = 'DATE(invoice_date) >= %s';
$params[] = $args['date_from'];
}
if ( '' !== $args['date_to'] ) {
$wheres[] = 'DATE(invoice_date) <= %s';
$params[] = $args['date_to'];
}
$where_sql = 'WHERE ' . implode( ' AND ', $wheres );
$orderby = in_array( $args['orderby'], array( 'invoice_number', 'order_id', 'invoice_date', 'total' ), true ) ? $args['orderby'] : 'invoice_date';
$order = 'ASC' === $args['order'] ? 'ASC' : 'DESC';
$per_page = (int) $args['per_page'];
$paged = max( 1, (int) $args['paged'] );
$offset = ( $paged - 1 ) * max( 0, $per_page );
$total = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Safe table and placeholder usage.
$wpdb->prepare(
"SELECT COUNT(*) FROM {$table} {$where_sql}",
$params
)
);
$item_params = $params;
$item_params[] = $per_page;
$item_params[] = $offset;
$items = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Safe table and allowlisted ordering.
$wpdb->prepare(
"SELECT * FROM {$table} {$where_sql} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d",
$item_params
),
ARRAY_A
);
return array( $items ?: array(), $total );
}
public static function get_export_items( array $args = array() ): array {
$defaults = array(
'search' => '',
'status_filter' => '',
'date_from' => '',
'date_to' => '',
'orderby' => 'invoice_date',
'order' => 'DESC',
'per_page' => 999999,
'paged' => 1,
);
$query_args = wp_parse_args( $args, $defaults );
list( $items ) = self::query_invoices( $query_args );
return $items;
}
protected function column_cb( $item ): string {
return sprintf( '', (int) $item['id'] );
}
protected function column_invoice_number( array $item ): string {
return '' . esc_html( $item['invoice_number'] ) . '';
}
protected function column_order_id( array $item ): string {
$order = wc_get_order( (int) $item['order_id'] );
if ( $order ) {
return sprintf( '#%s', esc_url( $order->get_edit_order_url() ), esc_html( (string) $order->get_id() ) );
}
return '#' . (int) $item['order_id'];
}
protected function column_customer( array $item ): string {
$order = wc_get_order( (int) $item['order_id'] );
if ( ! $order ) {
return '-';
}
$name = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() );
$email = $order->get_billing_email();
if ( $name ) {
return esc_html( $name ) . ( $email ? '
' . esc_html( $email ) . '' : '' );
}
return $email ? esc_html( $email ) : '-';
}
protected function column_invoice_date( array $item ): string {
return esc_html( wp_date( 'd/m/Y', strtotime( $item['invoice_date'] ) ) );
}
protected function column_total( array $item ): string {
return wc_price( (float) $item['total'] );
}
protected function column_tax( array $item ): string {
return wc_price( (float) $item['tax'] );
}
protected function column_status( array $item ): string {
$labels = array(
'generated' => __( 'Generada', '8digits-factura-for-woocommerce' ),
'sent' => __( 'Enviada', '8digits-factura-for-woocommerce' ),
'void' => __( 'Anulada', '8digits-factura-for-woocommerce' ),
);
$status = $item['status'];
$label = $labels[ $status ] ?? ucfirst( $status );
return sprintf( '%s', esc_attr( $status ), esc_html( $label ) );
}
protected function column_actions( array $item ): string {
$order = wc_get_order( (int) $item['order_id'] );
$download_url = EightDigits_Factura_Invoice::get_download_url( $item );
$resend_nonce = wp_create_nonce( 'eightdigits_factura_resend_' . $item['order_id'] );
$void_nonce = wp_create_nonce( 'eightdigits_factura_void_' . $item['id'] );
$regenerate_nonce = wp_create_nonce( 'eightdigits_factura_regenerate_' . $item['id'] );
$html = '
';
$html .= sprintf(
'
%s ',
esc_url( $download_url ),
esc_attr__( 'Descargar PDF', '8digits-factura-for-woocommerce' ),
esc_html__( 'PDF', '8digits-factura-for-woocommerce' )
);
if ( $order ) {
$html .= sprintf(
'
',
(int) $item['order_id'],
esc_attr( $resend_nonce ),
esc_attr__( 'Reenviar email con factura', '8digits-factura-for-woocommerce' ),
esc_html__( 'Reenviar', '8digits-factura-for-woocommerce' )
);
$html .= sprintf(
'
%s ',
esc_url( $order->get_edit_order_url() ),
esc_attr__( 'Ver pedido', '8digits-factura-for-woocommerce' ),
esc_html__( 'Pedido', '8digits-factura-for-woocommerce' )
);
}
if ( in_array( $item['status'], array( 'generated', 'sent' ), true ) ) {
$html .= sprintf(
'
',
(int) $item['id'],
esc_attr( $item['invoice_number'] ),
esc_attr( $void_nonce ),
esc_attr__( 'Anular factura', '8digits-factura-for-woocommerce' ),
esc_html__( 'Anular', '8digits-factura-for-woocommerce' )
);
}
$html .= sprintf(
'
',
(int) $item['id'],
esc_attr( $item['invoice_number'] ),
esc_attr( $regenerate_nonce ),
esc_attr__( 'Regenerar PDF', '8digits-factura-for-woocommerce' ),
esc_html__( 'Regenerar PDF', '8digits-factura-for-woocommerce' )
);
$html .= '
';
return $html;
}
protected function extra_tablenav( $which ): void {
if ( 'top' !== $which ) {
return;
}
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only filter params.
$status = isset( $_GET['eightdigits_factura_status'] ) ? sanitize_text_field( wp_unslash( $_GET['eightdigits_factura_status'] ) ) : '';
$from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '';
$to = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '';
$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
echo '';
echo '
';
printf( '
', esc_attr( $from ) );
printf( '
', esc_attr( $to ) );
submit_button( __( 'Filtrar', '8digits-factura-for-woocommerce' ), 'button', 'filter_action', false );
$export_url = wp_nonce_url(
add_query_arg(
array(
'action' => 'eightdigits_factura_export_csv',
'eightdigits_factura_status' => $status,
'date_from' => $from,
'date_to' => $to,
's' => $search,
),
admin_url( 'admin-ajax.php' )
),
'eightdigits_factura_export_csv',
'nonce'
);
printf(
'
%s',
esc_url( $export_url ),
esc_html__( 'Exportar CSV', '8digits-factura-for-woocommerce' )
);
echo '
';
}
protected function get_views(): array {
$base = admin_url( 'admin.php?page=factura-for-woocommerce' );
$current = isset( $_GET['eightdigits_factura_status'] ) ? sanitize_text_field( wp_unslash( $_GET['eightdigits_factura_status'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$total = EightDigits_Factura_Invoice::count();
$counts = EightDigits_Factura_Invoice::get_status_counts();
$views = array();
$views['all'] = sprintf(
'%s (%d)',
esc_url( $base ),
'' === $current ? ' class="current"' : '',
esc_html__( 'Todas', '8digits-factura-for-woocommerce' ),
(int) $total
);
foreach ( array(
'generated' => __( 'Generadas', '8digits-factura-for-woocommerce' ),
'sent' => __( 'Enviadas', '8digits-factura-for-woocommerce' ),
'void' => __( 'Anuladas', '8digits-factura-for-woocommerce' ),
) as $status => $label ) {
$views[ $status ] = sprintf(
'%s (%d)',
esc_url( add_query_arg( 'eightdigits_factura_status', $status, $base ) ),
$current === $status ? ' class="current"' : '',
esc_html( $label ),
(int) ( $counts[ $status ] ?? 0 )
);
}
return $views;
}
}
class EightDigits_Factura_Admin {
public function __construct() {
add_action( 'admin_menu', array( $this, 'register_menus' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widget' ) );
add_action( 'admin_post_eightdigits_factura_invoice_bulk', array( $this, 'handle_bulk_action_request' ) );
add_action( 'wp_ajax_eightdigits_factura_bulk_zip', array( $this, 'ajax_bulk_zip' ) );
add_action( 'wp_ajax_eightdigits_factura_void_invoice', array( $this, 'ajax_void_invoice' ) );
add_action( 'wp_ajax_eightdigits_factura_regenerate_pdf', array( $this, 'ajax_regenerate_pdf' ) );
add_action( 'wp_ajax_eightdigits_factura_export_csv', array( $this, 'ajax_export_csv' ) );
add_filter( 'plugin_action_links_' . EIGHTDIGITS_FACTURA_PLUGIN_BASENAME, array( $this, 'add_plugin_action_links' ) );
}
public function register_menus(): void {
add_menu_page(
__( '8Digits Factura', '8digits-factura-for-woocommerce' ),
__( '8Digits Factura', '8digits-factura-for-woocommerce' ),
'manage_woocommerce',
'factura-for-woocommerce',
array( $this, 'render_invoice_list' ),
'dashicons-media-spreadsheet',
56
);
add_submenu_page(
'factura-for-woocommerce',
__( 'Facturas', '8digits-factura-for-woocommerce' ),
__( 'Facturas', '8digits-factura-for-woocommerce' ),
'manage_woocommerce',
'factura-for-woocommerce',
array( $this, 'render_invoice_list' )
);
add_submenu_page(
'factura-for-woocommerce',
__( 'Configuracion', '8digits-factura-for-woocommerce' ),
__( 'Configuracion', '8digits-factura-for-woocommerce' ),
'manage_options',
'eightdigits-factura-settings',
array( $this, 'render_settings' )
);
}
public function enqueue_assets( string $hook ): void {
$plugin_pages = array( 'toplevel_page_factura-for-woocommerce', 'factura-for-woocommerce_page_eightdigits-factura-settings' );
if ( ! in_array( $hook, $plugin_pages, true ) && ! $this->is_order_page() ) {
return;
}
wp_enqueue_style( 'eightdigits-factura-admin', EIGHTDIGITS_FACTURA_PLUGIN_URL . 'assets/css/admin.css', array(), EIGHTDIGITS_FACTURA_VERSION );
wp_enqueue_script( 'eightdigits-factura-admin', EIGHTDIGITS_FACTURA_PLUGIN_URL . 'assets/js/admin.js', array( 'jquery' ), EIGHTDIGITS_FACTURA_VERSION, true );
wp_localize_script(
'eightdigits-factura-admin',
'eightdigitsFacturaAdmin',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'bulkZipNonce' => wp_create_nonce( 'eightdigits_factura_bulk_zip' ),
'strings' => array(
'resendSuccess' => __( 'Email reenviado correctamente.', '8digits-factura-for-woocommerce' ),
'resendError' => __( 'Error al reenviar el email.', '8digits-factura-for-woocommerce' ),
'generateSuccess' => __( 'Factura generada.', '8digits-factura-for-woocommerce' ),
'generateError' => __( 'Error al generar la factura.', '8digits-factura-for-woocommerce' ),
'zipError' => __( 'Error al generar el ZIP.', '8digits-factura-for-woocommerce' ),
'selectRows' => __( 'Seleccione al menos una factura.', '8digits-factura-for-woocommerce' ),
'voidError' => __( 'Error al anular la factura.', '8digits-factura-for-woocommerce' ),
'voidConfirm' => __( '¿Anular factura %s? Esta accion no se puede deshacer.', '8digits-factura-for-woocommerce' ),
'regenerateError' => __( 'Error al regenerar el PDF.', '8digits-factura-for-woocommerce' ),
'nifInvalid' => __( 'Formato de NIF/CIF/NIE no valido.', '8digits-factura-for-woocommerce' ),
'nifValid' => __( 'Formato correcto.', '8digits-factura-for-woocommerce' ),
),
)
);
}
private function is_order_page(): bool {
global $pagenow;
$screen = get_current_screen();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen detection.
if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) {
return 'shop_order' === get_post_type( absint( wp_unslash( $_GET['post'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
return $screen && in_array( $screen->id, array( 'woocommerce_page_wc-orders', 'shop_order' ), true );
}
public function render_invoice_list(): void {
require_once EIGHTDIGITS_FACTURA_PLUGIN_DIR . 'admin/views/invoice-list.php';
}
public function render_settings(): void {
require_once EIGHTDIGITS_FACTURA_PLUGIN_DIR . 'admin/views/settings-page.php';
}
public function handle_bulk_action_request(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'Sin permiso.', '8digits-factura-for-woocommerce' ) );
}
check_admin_referer( 'eightdigits_factura_bulk_action', 'eightdigits_factura_bulk_nonce' );
if ( empty( $_POST['invoice_ids'] ) ) {
$this->redirect_after_bulk_action( array() );
}
$bulk_action = '';
if ( isset( $_POST['action'] ) && '-1' !== $_POST['action'] ) {
$bulk_action = sanitize_text_field( wp_unslash( $_POST['action'] ) );
} elseif ( isset( $_POST['action2'] ) && '-1' !== $_POST['action2'] ) {
$bulk_action = sanitize_text_field( wp_unslash( $_POST['action2'] ) );
}
if ( '' === $bulk_action || 'zip' === $bulk_action ) {
$this->redirect_after_bulk_action( array() );
}
$invoice_ids = array_map( 'absint', (array) wp_unslash( $_POST['invoice_ids'] ) );
if ( 'resend' === $bulk_action ) {
$processed = $this->bulk_resend_invoices( $invoice_ids );
$this->redirect_after_bulk_action(
array(
'bulk_resent' => $processed > 0 ? '1' : '0',
)
);
}
if ( 'void' === $bulk_action ) {
$processed = $this->bulk_void_invoices( $invoice_ids );
$this->redirect_after_bulk_action(
array(
'bulk_voided' => $processed,
)
);
}
if ( 'regenerate' === $bulk_action ) {
$processed = $this->bulk_regenerate_invoices( $invoice_ids );
$this->redirect_after_bulk_action(
array(
'bulk_regenerated' => $processed,
)
);
}
}
private function bulk_resend_invoices( array $invoice_ids ): int {
$mailer = WC()->mailer();
$mails = $mailer->get_emails();
$email_id = 'customer_processing_order';
$processed = 0;
foreach ( $invoice_ids as $invoice_id ) {
$invoice = EightDigits_Factura_Invoice::get( $invoice_id );
if ( ! $invoice ) {
continue;
}
$order = wc_get_order( (int) $invoice['order_id'] );
if ( ! $order ) {
continue;
}
if ( isset( $mails[ $email_id ] ) ) {
$mails[ $email_id ]->trigger( $order->get_id(), $order );
}
global $wpdb;
$table = $wpdb->prefix . 'eightdigits_factura_invoices';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->update( $table, array( 'status' => 'sent' ), array( 'id' => $invoice_id ), array( '%s' ), array( '%d' ) );
$processed++;
}
return $processed;
}
private function bulk_void_invoices( array $invoice_ids ): int {
$processed = 0;
foreach ( $invoice_ids as $invoice_id ) {
$invoice = EightDigits_Factura_Invoice::get( $invoice_id );
if ( ! $invoice ) {
continue;
}
$result = EightDigits_Factura_Invoice::void( $invoice_id );
if ( is_wp_error( $result ) ) {
continue;
}
$order = wc_get_order( (int) $invoice['order_id'] );
if ( $order ) {
$order->add_order_note(
sprintf(
__( '8Digits Factura: Factura %s anulada.', '8digits-factura-for-woocommerce' ),
$invoice['invoice_number']
)
);
}
$processed++;
}
return $processed;
}
private function bulk_regenerate_invoices( array $invoice_ids ): int {
$processed = 0;
foreach ( $invoice_ids as $invoice_id ) {
$invoice = EightDigits_Factura_Invoice::get( $invoice_id );
if ( ! $invoice ) {
continue;
}
$result = EightDigits_Factura_Invoice::regenerate_pdf( $invoice_id );
if ( is_wp_error( $result ) ) {
continue;
}
$order = wc_get_order( (int) $invoice['order_id'] );
if ( $order ) {
$order->add_order_note(
sprintf(
__( '8Digits Factura: PDF de la factura %s regenerado.', '8digits-factura-for-woocommerce' ),
$invoice['invoice_number']
)
);
}
$processed++;
}
return $processed;
}
private function redirect_after_bulk_action( array $args ): void {
$query_args = array(
'page' => 'factura-for-woocommerce',
);
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is verified in handle_bulk_action_request() before redirect context is read.
foreach ( array( 's', 'orderby', 'order', 'paged', 'date_from', 'date_to', 'eightdigits_factura_status' ) as $key ) {
if ( isset( $_POST[ $key ] ) ) {
$query_args[ $key ] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) );
}
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
$redirect = add_query_arg( array_merge( $query_args, $args ), admin_url( 'admin.php' ) );
wp_safe_redirect( $redirect );
exit;
}
public function register_dashboard_widget(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
wp_add_dashboard_widget(
'eightdigits_factura_dashboard_widget',
__( '8Digits Factura', '8digits-factura-for-woocommerce' ),
array( $this, 'render_dashboard_widget' )
);
}
public function render_dashboard_widget(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
$stats = get_transient( 'eightdigits_factura_dashboard_stats' );
if ( false === $stats ) {
$stats = EightDigits_Factura_Invoice::get_dashboard_stats();
set_transient( 'eightdigits_factura_dashboard_stats', $stats, HOUR_IN_SECONDS );
}
$status_counts = $stats['status_counts'] ?? array();
$invoice_list_url = admin_url( 'admin.php?page=factura-for-woocommerce' );
?>
__( 'Sin permiso.', '8digits-factura-for-woocommerce' ) ) );
}
$ids = isset( $_POST['invoice_ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['invoice_ids'] ) ) : array();
if ( empty( $ids ) ) {
wp_send_json_error( array( 'message' => __( 'No se han seleccionado facturas.', '8digits-factura-for-woocommerce' ) ) );
}
if ( ! class_exists( 'ZipArchive' ) ) {
wp_send_json_error( array( 'message' => __( 'ZipArchive no disponible en este servidor.', '8digits-factura-for-woocommerce' ) ) );
}
$tmp_dir = get_temp_dir();
$zip_name = 'eightdigits-factura-' . gmdate( 'Ymd-His' ) . '.zip';
$zip_path = $tmp_dir . $zip_name;
$zip = new ZipArchive();
$opened = $zip->open( $zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE );
if ( true !== $opened ) {
wp_send_json_error( array( 'message' => __( 'No se pudo crear el archivo ZIP.', '8digits-factura-for-woocommerce' ) ) );
}
$added = 0;
foreach ( $ids as $invoice_id ) {
$invoice = EightDigits_Factura_Invoice::get( $invoice_id );
if ( ! $invoice || empty( $invoice['pdf_path'] ) || ! file_exists( $invoice['pdf_path'] ) ) {
continue;
}
$zip->addFile( $invoice['pdf_path'], basename( $invoice['pdf_path'] ) );
$added++;
}
$zip->close();
if ( 0 === $added ) {
wp_send_json_error( array( 'message' => __( 'No se encontraron archivos PDF para descargar.', '8digits-factura-for-woocommerce' ) ) );
}
$zip_data = base64_encode( file_get_contents( $zip_path ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reads a just-created local ZIP for download.
wp_delete_file( $zip_path );
wp_send_json_success(
array(
'filename' => $zip_name,
'data_b64' => $zip_data,
'message' => sprintf( __( 'ZIP con %d facturas listo.', '8digits-factura-for-woocommerce' ), $added ),
)
);
}
public function ajax_void_invoice(): void {
$invoice_id = absint( wp_unslash( $_POST['invoice_id'] ?? 0 ) );
check_ajax_referer( 'eightdigits_factura_void_' . $invoice_id, 'nonce' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_send_json_error( array( 'message' => __( 'Sin permiso.', '8digits-factura-for-woocommerce' ) ) );
}
$invoice = EightDigits_Factura_Invoice::get( $invoice_id );
if ( ! $invoice ) {
wp_send_json_error( array( 'message' => __( 'Factura no encontrada.', '8digits-factura-for-woocommerce' ) ) );
}
$result = EightDigits_Factura_Invoice::void( $invoice_id );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
}
wp_send_json_success(
array(
'message' => __( 'Factura anulada correctamente.', '8digits-factura-for-woocommerce' ),
'status' => __( 'Anulada', '8digits-factura-for-woocommerce' ),
)
);
}
public function ajax_regenerate_pdf(): void {
$invoice_id = absint( wp_unslash( $_POST['invoice_id'] ?? 0 ) );
check_ajax_referer( 'eightdigits_factura_regenerate_' . $invoice_id, 'nonce' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_send_json_error( array( 'message' => __( 'Sin permiso.', '8digits-factura-for-woocommerce' ) ) );
}
$result = EightDigits_Factura_Invoice::regenerate_pdf( $invoice_id );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
}
wp_send_json_success(
array(
'message' => __( 'PDF regenerado correctamente.', '8digits-factura-for-woocommerce' ),
'download_url' => EightDigits_Factura_Invoice::get_download_url( $result ),
)
);
}
public function add_plugin_action_links( array $links ): array {
$pro_link = sprintf(
'%s',
esc_url( 'https://8digits.es/factura-for-woocommerce/' ),
esc_html__( 'Go Pro', '8digits-factura-for-woocommerce' )
);
array_unshift( $links, $pro_link );
return $links;
}
public function ajax_export_csv(): void {
check_ajax_referer( 'eightdigits_factura_export_csv', 'nonce' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'Sin permiso.', '8digits-factura-for-woocommerce' ) );
}
$items = EightDigits_Factura_Invoice_List_Table::get_export_items(
array(
'search' => isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '',
'status_filter' => isset( $_GET['eightdigits_factura_status'] ) ? sanitize_text_field( wp_unslash( $_GET['eightdigits_factura_status'] ) ) : '',
'date_from' => isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '',
'date_to' => isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '',
)
);
$filename = 'eightdigits-factura-export-' . gmdate( 'Ymd-His' ) . '.csv';
nocache_headers();
header( 'Content-Type: text/csv; charset=UTF-8' );
header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( $filename ) . '"' );
$handle = fopen( 'php://output', 'w' );
if ( false === $handle ) {
wp_die( esc_html__( 'No se pudo generar el CSV.', '8digits-factura-for-woocommerce' ) );
}
echo "\xEF\xBB\xBF";
fputcsv(
$handle,
array(
__( 'Nº Factura', '8digits-factura-for-woocommerce' ),
__( 'Fecha', '8digits-factura-for-woocommerce' ),
__( 'Cliente', '8digits-factura-for-woocommerce' ),
__( 'NIF', '8digits-factura-for-woocommerce' ),
__( 'Base Imponible', '8digits-factura-for-woocommerce' ),
__( 'IVA %', '8digits-factura-for-woocommerce' ),
__( 'IVA Importe', '8digits-factura-for-woocommerce' ),
__( 'Total', '8digits-factura-for-woocommerce' ),
__( 'Estado', '8digits-factura-for-woocommerce' ),
),
';'
);
$status_labels = array(
'generated' => __( 'Generada', '8digits-factura-for-woocommerce' ),
'sent' => __( 'Enviada', '8digits-factura-for-woocommerce' ),
'void' => __( 'Anulada', '8digits-factura-for-woocommerce' ),
);
foreach ( $items as $invoice ) {
$order = wc_get_order( (int) $invoice['order_id'] );
$customer_name = '';
$nif = '';
if ( $order ) {
$customer_name = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() );
if ( '' === $customer_name ) {
$customer_name = $order->get_billing_email();
}
$nif = (string) ( $order->get_meta( 'billing_nif' ) ?: $order->get_meta( '_billing_nif' ) );
}
$tax_total = (float) $invoice['tax'];
$grand_total = (float) $invoice['total'];
$taxable_base = $grand_total - $tax_total;
$tax_rate = 0.0 !== $taxable_base ? round( ( $tax_total / $taxable_base ) * 100, 2 ) : 0.0;
fputcsv(
$handle,
array(
$invoice['invoice_number'],
wp_date( 'd/m/Y', strtotime( $invoice['invoice_date'] ) ),
$customer_name,
$nif,
number_format( $taxable_base, 2, ',', '' ),
number_format( $tax_rate, 2, ',', '' ),
number_format( $tax_total, 2, ',', '' ),
number_format( $grand_total, 2, ',', '' ),
$status_labels[ $invoice['status'] ] ?? $invoice['status'],
),
';'
);
}
fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closes the CSV stream.
exit;
}
}