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();
?>
←
|
|
|
|
|
|
|
|
/>
__('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);
?>
>
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'); ?>
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() {
?>
- 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;
}
}