register_post_type(); flush_rewrite_rules(); } public static function deactivate() { flush_rewrite_rules(); } public static function defaults() { return array( 'default_currency' => 'INR', 'default_tax_rate' => '18', 'invoice_prefix' => 'INV', 'business_name' => get_bloginfo('name'), 'business_email' => get_bloginfo('admin_email'), 'business_phone' => '', 'business_gstin' => '', 'business_address' => '', 'payment_instructions' => '', 'default_terms' => __('Payment due as per due date.', 'aibill-maker'), 'default_notes' => __('Thank you for your business.', 'aibill-maker'), ); } public static function settings() { $saved = get_option(self::OPTION, array()); return wp_parse_args(is_array($saved) ? $saved : array(), self::defaults()); } public function register_post_type() { register_post_type(self::CPT, array( 'labels' => array( 'name' => __('AiBill Invoices', 'aibill-maker'), 'singular_name' => __('AiBill Invoice', 'aibill-maker'), ), 'public' => false, 'show_ui' => false, 'show_in_menu' => false, 'show_in_rest' => false, 'supports' => array('title', 'author'), 'capability_type' => 'post', 'map_meta_cap' => true, 'has_archive' => false, 'rewrite' => false, 'query_var' => false, )); } public function admin_menu() { add_menu_page( __('AiBill Maker', 'aibill-maker'), __('AiBill Maker', 'aibill-maker'), 'manage_options', 'aibima-maker', array($this, 'invoices_page'), 'dashicons-media-spreadsheet', 56 ); add_submenu_page( 'aibima-maker', __('Invoices', 'aibill-maker'), __('Invoices', 'aibill-maker'), 'manage_options', 'aibima-maker', array($this, 'invoices_page') ); add_submenu_page( 'aibima-maker', __('Create Invoice', 'aibill-maker'), __('Create Invoice', 'aibill-maker'), 'manage_options', 'aibima-create', array($this, 'create_invoice_page') ); add_submenu_page( 'aibima-maker', __('Settings', 'aibill-maker'), __('Settings', 'aibill-maker'), 'manage_options', 'aibima-settings', array($this, 'settings_page') ); } public function register_settings() { register_setting('aibima_settings_group', self::OPTION, array( 'type' => 'array', 'sanitize_callback' => array($this, 'sanitize_settings'), 'default' => self::defaults(), )); } public function sanitize_settings($input) { $input = is_array($input) ? wp_unslash($input) : array(); $old = self::settings(); $out = self::defaults(); $out['default_currency'] = strtoupper(sanitize_text_field($input['default_currency'] ?? $old['default_currency'])); if (!preg_match('/^[A-Z]{3}$/', $out['default_currency'])) { $out['default_currency'] = 'INR'; } $out['default_tax_rate'] = (string) max(0, min(100, (float) ($input['default_tax_rate'] ?? $old['default_tax_rate']))); $out['invoice_prefix'] = sanitize_key($input['invoice_prefix'] ?? $old['invoice_prefix']); if ($out['invoice_prefix'] === '') { $out['invoice_prefix'] = 'INV'; } $out['business_name'] = sanitize_text_field($input['business_name'] ?? ''); $out['business_email'] = sanitize_email($input['business_email'] ?? ''); $out['business_phone'] = sanitize_text_field($input['business_phone'] ?? ''); $out['business_gstin'] = strtoupper(sanitize_text_field($input['business_gstin'] ?? '')); $out['business_address'] = sanitize_textarea_field($input['business_address'] ?? ''); $out['payment_instructions'] = wp_kses_post($input['payment_instructions'] ?? ''); $out['default_terms'] = wp_kses_post($input['default_terms'] ?? ''); $out['default_notes'] = wp_kses_post($input['default_notes'] ?? ''); return $out; } public function enqueue_admin_assets($hook_suffix) { if (false === strpos((string) $hook_suffix, 'aibima')) { return; } wp_enqueue_style('aibima-admin', AIBIMA_URL . 'assets/css/admin.css', array(), AIBIMA_ASSET_VERSION); wp_enqueue_script('aibima-admin', AIBIMA_URL . 'assets/js/admin.js', array(), AIBIMA_ASSET_VERSION, true); wp_localize_script('aibima-admin', 'AibimaAdmin', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce(self::NONCE_ACTION), 'samplePrompt' => $this->sample_prompt(), 'strings' => array( 'generating' => __('Generating invoice…', 'aibill-maker'), 'generated' => __('Invoice generated and saved.', 'aibill-maker'), 'error' => __('Something went wrong. Please try again.', 'aibill-maker'), 'confirmDelete' => __('Delete this invoice? This cannot be undone.', 'aibill-maker'), 'copy' => __('Copy invoice text', 'aibill-maker'), 'copied' => __('Copied', 'aibill-maker'), 'viewInvoice' => __('View invoice', 'aibill-maker'), 'editInvoice' => __('Edit invoice', 'aibill-maker'), 'printInvoice' => __('Print invoice', 'aibill-maker'), 'downloadPdf' => __('Download PDF', 'aibill-maker'), ), )); } public function invoices_page() { if (!current_user_can('manage_options')) { return; } $action_raw = filter_input(INPUT_GET, 'action', FILTER_UNSAFE_RAW); $invoice_id_raw = filter_input(INPUT_GET, 'invoice_id', FILTER_VALIDATE_INT); $action = sanitize_key(null !== $action_raw ? $action_raw : 'list'); $invoice_id = absint(false !== $invoice_id_raw && null !== $invoice_id_raw ? $invoice_id_raw : 0); if ('view' === $action && $invoice_id) { $this->view_invoice_page($invoice_id); return; } if ('edit' === $action && $invoice_id) { $this->edit_invoice_page($invoice_id); return; } $this->list_invoices_page(false); } public function create_invoice_page() { if (!current_user_can('manage_options')) { return; } $this->list_invoices_page(true); } private function list_invoices_page($open_prompt = false) { $settings = self::settings(); $paged_raw = filter_input(INPUT_GET, 'paged', FILTER_VALIDATE_INT); $paged = max(1, absint(false !== $paged_raw && null !== $paged_raw ? $paged_raw : 1)); $per_page = 20; $query = new WP_Query(array( 'post_type' => self::CPT, 'post_status' => 'publish', 'posts_per_page' => $per_page, 'paged' => $paged, 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => false, )); $ai_supported = AIBIMA_AI::is_text_generation_supported(); ?>

admin_notice_from_query(); ?>

Connectors.', 'aibill-maker'); ?>

have_posts()): ?> have_posts()): $query->the_post(); $post_id = get_the_ID(); $invoice = $this->get_invoice($post_id); ?>
| | | |

pagination($query, $paged); ?>
prompt_modal(); ?>
get_invoice($post_id); if (!$invoice) { $this->not_found_page(); return; } ?>

←

get_invoice($post_id); if (!$invoice) { $this->not_found_page(); return; } $items = is_array($invoice['items'] ?? null) ? $invoice['items'] : array(); ?>

←

edit_input('invoice_number', __('Invoice number', 'aibill-maker'), $invoice['invoice_number'] ?? ''); ?> edit_input('issue_date', __('Issue date', 'aibill-maker'), $invoice['issue_date'] ?? '', 'date'); ?> edit_input('due_date', __('Due date', 'aibill-maker'), $invoice['due_date'] ?? '', 'date'); ?> edit_input('currency', __('Currency', 'aibill-maker'), $invoice['currency'] ?? 'INR', 'text', 3); ?>

edit_input('customer_name', __('Customer name', 'aibill-maker'), $customer['name'] ?? ''); ?> edit_input('customer_email', __('Customer email', 'aibill-maker'), $customer['email'] ?? '', 'email'); ?> edit_input('customer_phone', __('Customer phone', 'aibill-maker'), $customer['phone'] ?? ''); ?> edit_input('customer_gstin', __('Customer GSTIN', 'aibill-maker'), $customer['gstin'] ?? ''); ?> edit_textarea('customer_address', __('Customer address', 'aibill-maker'), $customer['address'] ?? ''); ?>

$item): ?> edit_item_row($index, $item); ?>

edit_input('business_name', __('Business name', 'aibill-maker'), $business['name'] ?? ''); ?> edit_input('business_email', __('Business email', 'aibill-maker'), $business['email'] ?? '', 'email'); ?> edit_input('business_phone', __('Business phone', 'aibill-maker'), $business['phone'] ?? ''); ?> edit_input('business_gstin', __('Business GSTIN', 'aibill-maker'), $business['gstin'] ?? ''); ?> edit_textarea('business_address', __('Business address', 'aibill-maker'), $business['address'] ?? ''); ?>

edit_textarea('notes', __('Notes', 'aibill-maker'), $invoice['notes'] ?? ''); ?> edit_textarea('terms', __('Terms', 'aibill-maker'), $invoice['terms'] ?? ''); ?> edit_textarea('payment_instructions', __('Payment instructions', 'aibill-maker'), $invoice['payment_instructions'] ?? ''); ?>

/>

__('You do not have permission to create invoices.', 'aibill-maker')), 403); } check_ajax_referer(self::NONCE_ACTION, 'nonce'); $prompt_raw = filter_input(INPUT_POST, 'prompt', FILTER_UNSAFE_RAW); $prompt = trim(wp_strip_all_tags((string) (null !== $prompt_raw ? $prompt_raw : ''))); if (strlen($prompt) < 3) { wp_send_json_error(array('message' => __('Please write invoice details first.', 'aibill-maker')), 422); } if (strlen($prompt) > 10000) { $prompt = substr($prompt, 0, 10000); } $settings = self::settings(); $invoice = AIBIMA_Parser::parse($prompt, $settings); if (null === $invoice && AIBIMA_AI::is_text_generation_supported()) { $invoice = AIBIMA_AI::invoice_from_prompt($prompt, $settings); } if (null === $invoice) { wp_send_json_error(array('message' => __('Could not detect invoice items or amount from the prompt. Add an item and amount, for example: Website design ₹15000 GST 18% for Rajesh Kumar.', 'aibill-maker')), 422); } if (is_wp_error($invoice)) { wp_send_json_error(array('message' => $invoice->get_error_message()), 422); } $post_id = $this->save_invoice_record($invoice, $prompt); if (!$post_id) { wp_send_json_error(array('message' => __('Invoice could not be saved.', 'aibill-maker')), 500); } $invoice['post_id'] = $post_id; wp_send_json_success(array( 'message' => __('Invoice generated and saved.', 'aibill-maker'), 'id' => $post_id, 'viewUrl' => $this->invoice_url($post_id, 'view'), 'editUrl' => $this->invoice_url($post_id, 'edit'), 'printUrl' => $this->print_url($post_id), 'downloadUrl' => $this->download_url($post_id), 'html' => AIBIMA_Renderer::invoice($invoice, false), )); } public function handle_save_invoice() { $post_id_raw = filter_input(INPUT_POST, 'invoice_id', FILTER_VALIDATE_INT); $post_id = absint(false !== $post_id_raw && null !== $post_id_raw ? $post_id_raw : 0); if (!$post_id || !current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to save this invoice.', 'aibill-maker')); } check_admin_referer('aibima_save_invoice_' . $post_id); $post_data = filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW); $invoice = $this->invoice_from_post(is_array($post_data) ? $post_data : array()); $saved_id = $this->save_invoice_record($invoice, get_post_meta($post_id, '_aibima_prompt', true), $post_id); if (!$saved_id) { wp_die(esc_html__('Invoice could not be saved.', 'aibill-maker')); } wp_safe_redirect(add_query_arg(array('page' => 'aibima-maker', 'action' => 'view', 'invoice_id' => $saved_id, 'aibima_notice' => 'saved'), admin_url('admin.php'))); exit; } public function handle_delete_invoice() { $post_id_raw = filter_input(INPUT_GET, 'invoice_id', FILTER_VALIDATE_INT); $post_id = absint(false !== $post_id_raw && null !== $post_id_raw ? $post_id_raw : 0); if (!$post_id || !current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to delete this invoice.', 'aibill-maker')); } check_admin_referer('aibima_delete_invoice_' . $post_id); wp_trash_post($post_id); wp_safe_redirect(add_query_arg(array('page' => 'aibima-maker', 'aibima_notice' => 'deleted'), admin_url('admin.php'))); exit; } public function handle_print_invoice() { $post_id_raw = filter_input(INPUT_GET, 'invoice_id', FILTER_VALIDATE_INT); $post_id = absint(false !== $post_id_raw && null !== $post_id_raw ? $post_id_raw : 0); if (!$post_id || !current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to print this invoice.', 'aibill-maker')); } check_admin_referer('aibima_print_invoice_' . $post_id); $invoice = $this->get_invoice($post_id); if (!$invoice) { wp_die(esc_html__('Invoice not found.', 'aibill-maker')); } nocache_headers(); header('Content-Type: text/html; charset=' . get_option('blog_charset')); wp_enqueue_style('aibima-print', AIBIMA_URL . 'assets/css/print.css', array(), AIBIMA_ASSET_VERSION); wp_enqueue_script('aibima-print', AIBIMA_URL . 'assets/js/print.js', array(), AIBIMA_ASSET_VERSION, false); ?> > <?php echo esc_html($invoice['invoice_number'] ?? __('Invoice', 'aibill-maker')); ?>
get_invoice($post_id); if (!$invoice) { wp_die(esc_html__('Invoice not found.', 'aibill-maker')); } $filename = sanitize_file_name(($invoice['invoice_number'] ?? 'invoice') . '.pdf'); $pdf = AIBIMA_PDF::invoice($invoice); nocache_headers(); header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . strlen($pdf)); echo $pdf; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary PDF output. exit; } private function invoice_from_post($post) { $settings = self::settings(); $currency = strtoupper(sanitize_text_field($post['currency'] ?? 'INR')); if (!preg_match('/^[A-Z]{3}$/', $currency)) { $currency = 'INR'; } $tax_component = sanitize_key($post['tax_component'] ?? 'cgst_sgst'); if (!in_array($tax_component, array('cgst_sgst', 'igst', 'none'), true)) { $tax_component = 'cgst_sgst'; } $items = array(); $raw_items = is_array($post['items'] ?? null) ? $post['items'] : array(); foreach ($raw_items as $raw) { if (!is_array($raw)) { continue; } $name = sanitize_text_field($raw['name'] ?? ''); if ($name === '') { continue; } $items[] = array( 'name' => $name, 'description' => sanitize_text_field($raw['description'] ?? ''), 'quantity' => max(0, (float) ($raw['quantity'] ?? 1)), 'unit' => sanitize_text_field($raw['unit'] ?? 'pcs'), 'unit_price' => max(0, (float) ($raw['unit_price'] ?? 0)), 'tax_rate' => max(0, min(100, (float) ($raw['tax_rate'] ?? 0))), 'tax_inclusive' => !empty($raw['tax_inclusive']), 'hsn_sac' => sanitize_text_field($raw['hsn_sac'] ?? ''), ); } if (empty($items)) { $items[] = array( 'name' => __('Item', 'aibill-maker'), 'description' => '', 'quantity' => 1, 'unit' => 'pcs', 'unit_price' => 0, 'tax_rate' => (float) ($settings['default_tax_rate'] ?? 0), 'tax_inclusive' => false, 'hsn_sac' => '', ); } $invoice = array( 'source' => sanitize_text_field($post['source'] ?? 'edited'), 'invoice_type' => 'invoice', 'invoice_number' => sanitize_text_field($post['invoice_number'] ?? ''), 'issue_date' => $this->sanitize_date($post['issue_date'] ?? current_time('Y-m-d')), 'due_date' => $this->sanitize_date($post['due_date'] ?? gmdate('Y-m-d', current_time('timestamp') + WEEK_IN_SECONDS)), 'currency' => $currency, 'customer' => array( 'name' => sanitize_text_field($post['customer_name'] ?? ''), 'email' => sanitize_email($post['customer_email'] ?? ''), 'phone' => sanitize_text_field($post['customer_phone'] ?? ''), 'gstin' => strtoupper(sanitize_text_field($post['customer_gstin'] ?? '')), 'address' => sanitize_textarea_field($post['customer_address'] ?? ''), ), 'business' => array( 'name' => sanitize_text_field($post['business_name'] ?? ''), 'email' => sanitize_email($post['business_email'] ?? ''), 'phone' => sanitize_text_field($post['business_phone'] ?? ''), 'gstin' => strtoupper(sanitize_text_field($post['business_gstin'] ?? '')), 'address' => sanitize_textarea_field($post['business_address'] ?? ''), ), 'tax_component' => $tax_component, 'tax_inclusive' => false, 'items' => $items, 'notes' => wp_kses_post($post['notes'] ?? ''), 'terms' => wp_kses_post($post['terms'] ?? ''), 'payment_instructions' => wp_kses_post($post['payment_instructions'] ?? ''), ); if ($invoice['invoice_number'] === '') { $invoice['invoice_number'] = $this->next_invoice_number(); } return AIBIMA_Parser::calculate($invoice); } private function save_invoice_record($invoice, $prompt = '', $post_id = 0) { $customer = is_array($invoice['customer'] ?? null) ? $invoice['customer'] : array(); $invoice_number = sanitize_text_field($invoice['invoice_number'] ?? $this->next_invoice_number()); $title = trim($invoice_number . ' - ' . ($customer['name'] ?? __('Customer', 'aibill-maker'))); $postarr = array( 'post_type' => self::CPT, 'post_status' => 'publish', 'post_title' => $title, 'post_author' => get_current_user_id() ?: 0, ); if ($post_id) { $postarr['ID'] = $post_id; $saved = wp_update_post($postarr, true); } else { $saved = wp_insert_post($postarr, true); } if (is_wp_error($saved)) { return 0; } $post_id = (int) $saved; update_post_meta($post_id, '_aibima_invoice_json', $invoice); update_post_meta($post_id, '_aibima_prompt', sanitize_textarea_field($prompt)); update_post_meta($post_id, '_aibima_invoice_number', $invoice_number); update_post_meta($post_id, '_aibima_customer_name', sanitize_text_field($customer['name'] ?? '')); update_post_meta($post_id, '_aibima_total', (float) ($invoice['grand_total'] ?? 0)); update_post_meta($post_id, '_aibima_currency', sanitize_text_field($invoice['currency'] ?? 'INR')); update_post_meta($post_id, '_aibima_source', sanitize_text_field($invoice['source'] ?? 'local')); return $post_id; } private function get_invoice($post_id) { $post = get_post($post_id); if (!$post || self::CPT !== $post->post_type || 'trash' === $post->post_status) { return null; } $invoice = get_post_meta($post_id, '_aibima_invoice_json', true); if (!is_array($invoice)) { return null; } return $invoice; } public function settings_page() { if (!current_user_can('manage_options')) { return; } $settings = self::settings(); $ai_supported = AIBIMA_AI::is_text_generation_supported(); $test_notice = get_transient('aibima_test_notice_' . get_current_user_id()); if ($test_notice) { delete_transient('aibima_test_notice_' . get_current_user_id()); } ?>

Connectors.', 'aibill-maker'); ?>

Connectors and add an AI provider before using natural-language prompts.', 'aibill-maker'); ?>

settings_text_field('business_name', __('Business name', 'aibill-maker'), $settings['business_name']); ?> settings_text_field('business_email', __('Business email', 'aibill-maker'), $settings['business_email'], 'email'); ?> settings_text_field('business_phone', __('Business phone', 'aibill-maker'), $settings['business_phone']); ?> settings_text_field('business_gstin', __('Business GSTIN', 'aibill-maker'), $settings['business_gstin']); ?> settings_textarea_field('business_address', __('Business address', 'aibill-maker'), $settings['business_address']); ?> settings_textarea_field('payment_instructions', __('Payment instructions', 'aibill-maker'), $settings['payment_instructions']); ?> settings_textarea_field('default_terms', __('Default terms', 'aibill-maker'), $settings['default_terms']); ?> settings_textarea_field('default_notes', __('Default notes', 'aibill-maker'), $settings['default_notes']); ?>

ai_client_help_box(); ?>
'error', 'message' => $result->get_error_message()), 60); } else { set_transient('aibima_test_notice_' . get_current_user_id(), array('type' => 'success', 'message' => __('WordPress AI Client text generation is available.', 'aibill-maker')), 60); } wp_safe_redirect(admin_url('admin.php?page=aibima-settings')); exit; } private function ai_client_help_box() { ?>

  1. Connectors in WordPress admin.', 'aibill-maker'); ?>

publish + 1; return $prefix . '-' . current_time('Ymd') . '-' . str_pad((string) $count, 4, '0', STR_PAD_LEFT); } private function sanitize_date($value) { $value = sanitize_text_field($value); if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { return current_time('Y-m-d'); } $parts = explode('-', $value); return checkdate((int) $parts[1], (int) $parts[2], (int) $parts[0]) ? $value : current_time('Y-m-d'); } private function invoice_url($post_id, $action) { return add_query_arg(array('page' => 'aibima-maker', 'action' => sanitize_key($action), 'invoice_id' => absint($post_id)), admin_url('admin.php')); } private function print_url($post_id) { return wp_nonce_url(add_query_arg(array('action' => 'aibima_print_invoice', 'invoice_id' => absint($post_id)), admin_url('admin-post.php')), 'aibima_print_invoice_' . absint($post_id)); } private function download_url($post_id) { return wp_nonce_url(add_query_arg(array('action' => 'aibima_download_pdf', 'invoice_id' => absint($post_id)), admin_url('admin-post.php')), 'aibima_download_pdf_' . absint($post_id)); } private function delete_url($post_id) { return wp_nonce_url(add_query_arg(array('action' => 'aibima_delete_invoice', 'invoice_id' => absint($post_id)), admin_url('admin-post.php')), 'aibima_delete_invoice_' . absint($post_id)); } private function pagination($query, $paged) { if ($query->max_num_pages <= 1) { return; } echo '
'; echo wp_kses_post(paginate_links(array( 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'current' => max(1, $paged), 'total' => (int) $query->max_num_pages, ))); echo '
'; } private function admin_notice_from_query() { $notice_raw = filter_input(INPUT_GET, 'aibima_notice', FILTER_UNSAFE_RAW); $notice = sanitize_key(null !== $notice_raw ? $notice_raw : ''); if ('saved' === $notice) { echo '

' . esc_html__('Invoice saved.', 'aibill-maker') . '

'; } elseif ('deleted' === $notice) { echo '

' . esc_html__('Invoice moved to Trash.', 'aibill-maker') . '

'; } } private function not_found_page() { ?>

' . esc_html__('Settings', 'aibill-maker') . ''; $invoices_link = '' . esc_html__('Invoices', 'aibill-maker') . ''; array_unshift($links, $settings_link, $invoices_link); return $links; } }