PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / templates / quotes / builder.php

builder.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.0, at templates/quotes/builder.php

412 lines 22.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Exit if accessed directly
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6 use EasyInvoice\Providers\ClientServiceProvider;
7 use EasyInvoice\Providers\QuoteServiceProvider;
8
9 // Enqueue CSS and JS files for the builder page
10 wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), EASY_INVOICE_VERSION);
11 wp_enqueue_script('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/js/invoice-form.js', array('jquery'), EASY_INVOICE_VERSION, true);
12 wp_enqueue_script('easy-client-manager', plugin_dir_url(__FILE__) . '../../assets/js/client-manager.js', array('jquery'), EASY_INVOICE_VERSION, true);
13
14 // Create nonce for AJAX calls
15 $ajax_nonce = wp_create_nonce('easy_invoice_nonce');
16
17 // Localize client manager script
18 wp_localize_script('easy-client-manager', 'easyInvoice', array(
19 'ajaxUrl' => admin_url('admin-ajax.php'),
20 'nonce' => $ajax_nonce
21 ));
22
23 // Get quote ID if present in URL
24 $quote_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
25 if ($quote_id) {
26 $post = get_post($quote_id);
27 if (!$post || $post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE) {
28 wp_die(esc_html__('That quote does not exist or has been deleted.', 'easy-invoice'), esc_html__('Quote not found', 'easy-invoice'), array('back_link' => true, 'response' => 404));
29 }
30 }
31 $quote = null;
32
33 // Check if this is a new quote (no ID)
34 $is_new_quote = ($quote_id === 0);
35
36 // Default values for new quote
37 $quote_number_service = function_exists('easy_invoice_get_quote_number_service') ? easy_invoice_get_quote_number_service() : null;
38
39 // Get global quote settings
40 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
41 $quote_prefix = $settings_controller::getQuotePrefix();
42 $quote_terms = $settings_controller::getQuoteTermsConditions();
43 $quote_footer = $settings_controller::getQuoteFooterText();
44 $quote_accept_button = get_option('easy_invoice_quote_accept_button', 'yes');
45 $quote_accept_action = get_option('easy_invoice_quote_accept_action', 'email');
46 $quote_accept_text = get_option('easy_invoice_quote_accept_text', __('Accept Quote', 'easy-invoice'));
47 $quote_accepted_message = get_option('easy_invoice_quote_accepted_message', __('Thank you for accepting our quote!', 'easy-invoice'));
48 $quote_declined_message = get_option('easy_invoice_quote_declined_message', __('Thank you for your consideration.', 'easy-invoice'));
49
50 $quote_data = array(
51 'number' => $quote_number_service ? $quote_number_service->getNextNumber() : 'QT-1',
52 'date' => current_time('Y-m-d'),
53 'expiry_date' => wp_date('Y-m-d', strtotime('+30 days')),
54 'client_id' => 0,
55 'client_name' => '',
56 'client_email' => '',
57 'client_phone' => '',
58 'client_address' => '',
59 'items' => array(),
60 'notes' => '',
61 'internal_notes' => '',
62 'discount' => 0,
63 'discount_type' => 'percentage',
64 'calculation_method' => 'before_tax',
65 'tax_rate' => easy_invoice_get_tax_rate(),
66 'prices_include_tax' => 'no',
67 'status' => 'draft',
68 'currency' => 'USD',
69 'currency_symbol' => '$',
70 'title' => '',
71 'description' => '',
72 'terms' => $quote_terms, // Use global terms setting
73 'footer_text' => $quote_footer, // Use global footer setting
74 'accept_button' => $quote_accept_button, // Use global accept button setting
75 'accept_action' => $quote_accept_action, // Use global accept action setting
76 'accept_text' => $quote_accept_text, // Use global accept text setting
77 'accepted_message' => $quote_accepted_message, // Use global accepted message setting
78 'declined_message' => $quote_declined_message, // Use global declined message setting
79 );
80
81 // Get clients from repository
82 $client_repository = ClientServiceProvider::getClientRepository();
83 // The hidden #select-client mirror only needs the document's own client; the picker
84 // searches over AJAX. Loading every client here built a model per user on each open.
85 $clients = [];
86
87 // Load client data directly in PHP if quote has a client
88 $client_data = null;
89 if ($quote && $quote->getClientId()) {
90 $client = $client_repository->find($quote->getClientId());
91 if ($client) {
92 $client_data = array(
93 'id' => $client->getId(),
94 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
95 'email' => $client->getEmail() ?: '',
96 'phone' => $client->getExtraInfo() ?: '',
97 'company' => $client->getBusinessClientName() ?: '',
98 'address' => $client->getAddress() ?: '',
99 'website' => $client->getWebsite() ?: '',
100 );
101 }
102 }
103
104 // If editing an existing quote, load its data
105 if ($quote_id > 0) {
106 // Get the quote from repository
107 $quote_repository = QuoteServiceProvider::getQuoteRepository();
108 $quote = $quote_repository->find($quote_id);
109
110 if ($quote && $quote->getId()) {
111 // Quote loaded successfully
112 } else {
113 // Failed to load quote
114 }
115 } else {
116 // Create a temporary WP_Post object for new quote
117 $empty_post = new WP_Post((object) array(
118 'ID' => 0,
119 'post_author' => get_current_user_id(),
120 'post_date' => current_time('mysql'),
121 'post_date_gmt' => current_time('mysql', 1),
122 'post_title' => $quote_data['number'],
123 'post_status' => 'auto-draft',
124 'comment_status' => 'closed',
125 'ping_status' => 'closed',
126 'post_name' => '',
127 'post_modified' => current_time('mysql'),
128 'post_modified_gmt' => current_time('mysql', 1),
129 'post_parent' => 0,
130 'guid' => '',
131 'menu_order' => 0,
132 'post_type' => \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
133 'post_mime_type' => '',
134 'comment_count' => 0,
135 'filter' => 'raw',
136 ));
137
138 $quote = new \EasyInvoice\Models\Quote($empty_post);
139
140 // Set default values on the quote object
141 foreach ($quote_data as $key => $value) {
142 $setter = 'set' . easy_invoice_str_replace('_', '', ucwords($key, '_'));
143 if (method_exists($quote, $setter)) {
144 // Handle type-specific setters
145 switch ($setter) {
146 case 'setClientId':
147 $quote->setClientId((int) $value);
148 break;
149 case 'setItems':
150 $quote->setItems((array) $value);
151 break;
152 case 'setSubtotal':
153 case 'setTaxAmount':
154 case 'setDiscountAmount':
155 case 'setTotal':
156 case 'setDiscountValue':
157 case 'setTaxRate':
158 $quote->$setter((float) $value);
159 break;
160 case 'setPricesIncludeTax':
161 $quote->$setter((bool) $value);
162 break;
163 default:
164 $quote->$setter((string) $value);
165 break;
166 }
167 }
168 }
169
170 // Initialize empty items array
171 $quote->setItems([]);
172 }
173
174 // Only load custom meta that's not handled by Quote object
175 if ($quote) {
176 // These are still accessed from meta as they don't have model methods yet
177 $quote_data['terms'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_terms', true) : $quote_data['terms'];
178 $quote_data['internal_notes'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_internal_notes', true) : $quote_data['internal_notes'];
179 $quote_data['currency'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_currency_code', true) : $quote_data['currency'];
180 $quote_data['currency_symbol'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_currency_position', true) : $quote_data['currency_symbol'];
181 $quote_data['calculation_method'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_calculation_method', true) : $quote_data['calculation_method'];
182 }
183
184 // Prepare quote items JSON for JavaScript
185 $quote_items_json = json_encode($quote ? $quote->getItems() : []);
186
187 // Create nonce for AJAX calls
188 $admin_nonce = wp_create_nonce('easy_invoice_admin_nonce');
189
190 // Initialize quote form manager for field configuration
191 $quote_form_manager = new \EasyInvoice\Forms\Quote\QuoteFormManager();
192 $quote_field_config = $quote_form_manager->getFieldConfigForJavaScript();
193
194 // Remove the following code that preloads all templates into JS
195 // $templates = $quote_form_manager->getTemplates();
196 // $template_htmls = [];
197 // $formatter = new \EasyInvoice\Helpers\QuoteFormatter($quote);
198 // foreach ($templates as $template_id => $template_config) {
199 // $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/quote-templates/' . $template_id . '.php';
200 // if (file_exists($template_file)) {
201 // ob_start();
202 // include $template_file;
203 // $template_htmls[$template_id] = ob_get_clean();
204 // } else {
205 // $template_htmls[$template_id] = '<div class="template-not-found"><h3>Template Not Found</h3><p>The template "' . esc_html($template_id) . '" does not exist.</p></div>';
206 // }
207 // }
208
209 if ( $quote_id && $quote ) {
210 $ei_header_display_title = $quote->title ?: $quote->number ?: __( 'Untitled quote', 'easy-invoice' );
211 } else {
212 $ei_header_display_title = __( 'Create new quote', 'easy-invoice' );
213 }
214
215 ?>
216
217 <script>
218 window.isQuoteForm = true;
219 </script>
220
221 <script>
222 jQuery(function ($) {
223 window.easyInvoice = $.extend(true, {}, typeof window.easyInvoice === 'object' ? window.easyInvoice : {}, {
224 fieldConfig: <?php echo wp_json_encode( $quote_field_config ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
225 clientData: <?php echo wp_json_encode( $client_data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
226 ajaxUrl: '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>',
227 nonce: '<?php echo esc_js( wp_create_nonce( 'easy_invoice_nonce' ) ); ?>',
228 isPro: <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>
229 });
230
231 function t(key, fb) {
232 return (window.easyInvoice && easyInvoice.i18n && easyInvoice.i18n[key]) ? easyInvoice.i18n[key] : fb;
233 }
234 var spin = '<svg class="ei-btn-spinner h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
235 var pendingSend = false;
236
237 $(document).on('easy-quote-saved', function (e, response) {
238 if (!pendingSend || !response || !response.success) {
239 return;
240 }
241 pendingSend = false;
242 if ($('#quote-id').val() !== '0') {
243 sendQuoteEmail();
244 }
245 });
246 $(document).on('easy-quote-save-failed', function () {
247 pendingSend = false;
248 });
249
250 $('#send-quote-btn').on('click', function (e) {
251 e.preventDefault();
252 if ($('#quote-id').val() === '0') {
253 pendingSend = true;
254 $('#save-quote-btn').trigger('click');
255 return;
256 }
257 sendQuoteEmail();
258 });
259
260 function sendQuoteEmail() {
261 var quoteId = $('#quote-id').val();
262 if (quoteId === '0') {
263 if (typeof EasyInvoiceToast !== 'undefined') {
264 EasyInvoiceToast.warning(t('save_first_quote', 'Please save the quote before sending.'));
265 }
266 return;
267 }
268 var nonce = $('#send-quote-btn').data('send-nonce') || '<?php echo esc_js( wp_create_nonce( 'easy_invoice_send_quote_email' ) ); ?>';
269
270 var runAjax = function () {
271 var $btn = $('#send-quote-btn');
272 var original = $btn.html();
273 $btn.prop('disabled', true).attr('aria-busy', 'true').html('<span class="inline-flex items-center gap-2">' + spin + '<span>' + t('sending', 'Sending…') + '</span></span>');
274 $.ajax({
275 url: window.easyInvoice.ajaxUrl,
276 type: 'POST',
277 data: {
278 action: 'easy_invoice_send_quote_email',
279 quote_id: quoteId,
280 nonce: nonce
281 },
282 success: function (response) {
283 if (response.success) {
284 if (typeof EasyInvoiceToast !== 'undefined') {
285 EasyInvoiceToast.success(t('email_sent_quote', 'Quote email sent successfully.'));
286 }
287 } else {
288 var msg = (response.data && response.data.message) ? response.data.message : (response.data || t('email_error', 'Error sending email.'));
289 if (typeof EasyInvoiceToast !== 'undefined') {
290 EasyInvoiceToast.error(msg);
291 }
292 }
293 },
294 error: function () {
295 if (typeof EasyInvoiceToast !== 'undefined') {
296 EasyInvoiceToast.error(t('network_error', 'Error connecting to server.'));
297 }
298 },
299 complete: function () {
300 $btn.prop('disabled', false).removeAttr('aria-busy').html(original);
301 }
302 });
303 };
304
305 if (typeof EasyInvoiceConfirmation !== 'undefined') {
306 EasyInvoiceConfirmation.confirmSendDocument('quote', runAjax);
307 } else if (window.confirm(t('send_quote_confirm_browser', 'Send this quote by email?'))) {
308 runAjax();
309 }
310 }
311 });
312 </script>
313
314 <form id="quote-form" method="post">
315 <input type="hidden" id="quote-id" name="quote_id" value="<?php echo (int) $quote_id; ?>">
316 <input type="hidden" id="quote_nonce" name="quote_nonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_nonce')); ?>">
317
318 <div id="easy-invoice-content" class="h-screen flex flex-col bg-gray-50">
319 <header class="ei-quote-builder-header ei-app-header-sync bg-white border-b border-gray-200 shadow-sm w-full z-10 box-border">
320 <div class="max-w-full mx-auto h-full px-4 sm:px-5">
321 <div class="ei-app-header-inner flex flex-col gap-3 py-3 sm:gap-4 lg:flex-row lg:items-center lg:justify-between lg:gap-6 lg:py-0 lg:h-full">
322 <div class="ei-app-header-leading flex min-w-0 flex-1 flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
323 <a href="<?php echo esc_url( admin_url( 'admin.php?page=easy-quote-all' ) ); ?>"
324 class="inline-flex shrink-0 items-center gap-2 self-start rounded-md text-sm font-medium text-gray-600 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
325 <svg class="h-4 w-4 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
326 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
327 </svg>
328 <span><?php esc_html_e( 'Back to quotes', 'easy-invoice' ); ?></span>
329 </a>
330 <div class="min-w-0 flex-1 border-l-0 sm:border-l sm:border-gray-200 sm:pl-4">
331 <p class="text-xs font-medium uppercase tracking-wide text-gray-500"><?php esc_html_e( 'Quote', 'easy-invoice' ); ?></p>
332 <h1 id="ei-builder-header-title" class="mt-0.5 truncate text-lg font-semibold leading-tight text-gray-900 sm:text-xl" data-ei-default="<?php echo esc_attr( $ei_header_display_title ); ?>">
333 <?php echo esc_html( $ei_header_display_title ); ?>
334 </h1>
335 <p class="sr-only"><?php esc_html_e( 'The title above matches the document title field in the editor.', 'easy-invoice' ); ?></p>
336 </div>
337 </div>
338 <div class="ei-builder-header-actions flex flex-wrap items-center gap-2 lg:shrink-0">
339 <button type="button" id="save-quote-btn" class="inline-flex items-center gap-2 px-3 sm:px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" aria-busy="false">
340 <svg class="h-4 w-4 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
341 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path>
342 </svg>
343 <span><?php echo isset( $_GET['id'] ) ? esc_html__( 'Update quote', 'easy-invoice' ) : esc_html__( 'Save quote', 'easy-invoice' ); ?></span>
344 </button>
345 <button type="button" id="send-quote-btn" class="inline-flex items-center gap-2 px-3 sm:px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-send-nonce="<?php echo esc_attr( wp_create_nonce( 'easy_invoice_send_quote_email' ) ); ?>">
346 <svg class="h-4 w-4 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
347 <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"></path>
348 </svg>
349 <span><?php esc_html_e( 'Send quote', 'easy-invoice' ); ?></span>
350 </button>
351 </div>
352 </div>
353 </div>
354 </header>
355
356 <div class="flex-grow overflow-y-auto min-h-0">
357 <div class="max-w-full mx-auto px-4 sm:px-5 py-4 sm:py-5">
358 <div class="ei-builder-tabs lg:hidden flex rounded-lg bg-gray-100 p-1 gap-1 mb-4" role="tablist" aria-label="<?php echo esc_attr__( 'Editor panels', 'easy-invoice' ); ?>">
359 <button type="button" role="tab" class="ei-builder-tab flex-1 rounded-md py-2 text-sm font-medium transition bg-white shadow-sm text-indigo-700" data-tab="editor" aria-selected="true"><?php esc_html_e( 'Editor', 'easy-invoice' ); ?></button>
360 <button type="button" role="tab" class="ei-builder-tab flex-1 rounded-md py-2 text-sm font-medium transition text-gray-600 hover:text-gray-800" data-tab="preview" aria-selected="false"><?php esc_html_e( 'Preview', 'easy-invoice' ); ?></button>
361 </div>
362 <div id="ei-builder-shell-hint" class="ei-builder-shell-hint mb-4 hidden rounded-md border border-indigo-100 bg-indigo-50/90 p-3 text-sm text-gray-700 shadow-sm lg:hidden" role="status" hidden>
363 <div class="flex items-start gap-3">
364 <span class="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-100 text-indigo-700" aria-hidden="true">
365 <svg class="h-3.5 w-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
366 </span>
367 <p class="min-w-0 flex-1 leading-snug"><?php esc_html_e( 'On smaller screens, use the tabs to switch between the editor and the live preview.', 'easy-invoice' ); ?></p>
368 <button type="button" id="ei-builder-shell-hint-dismiss" class="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-indigo-700 hover:bg-indigo-100 focus:outline-none focus:ring-2 focus:ring-indigo-500">
369 <?php esc_html_e( 'Got it', 'easy-invoice' ); ?>
370 </button>
371 </div>
372 </div>
373 <div id="ei-quote-builder-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
374 <div id="ei-builder-editor" class="min-w-0 ei-builder-panel-editor">
375 <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/form.php'; ?>
376 </div>
377 <div id="ei-builder-preview" class="min-w-0 ei-builder-panel-preview hidden lg:block">
378 <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/live-preview.php'; ?>
379 </div>
380 </div>
381
382 <div id="add_client_modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50" role="dialog" aria-modal="true" aria-labelledby="ei-quote-add-client-title">
383 <div class="relative top-20 mx-auto p-5 border w-11/12 md:w-2/3 lg:w-1/2 shadow-lg rounded-md bg-white">
384 <div class="flex justify-between items-center mb-4">
385 <h3 id="ei-quote-add-client-title" class="text-lg font-medium text-gray-900"><?php esc_html_e( 'Add new client', 'easy-invoice' ); ?></h3>
386 <button type="button" class="close-modal rounded-md p-1 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500">
387 <span class="sr-only"><?php esc_html_e( 'Close', 'easy-invoice' ); ?></span>
388 <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
389 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
390 </svg>
391 </button>
392 </div>
393
394 <?php
395 // Unset the $client variable from the foreach loop to ensure clean add form
396 unset($client);
397 // Also unset any client variable that might have been set in form.php
398 if (isset($client)) {
399 unset($client);
400 }
401 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
402 ?>
403 </div>
404 </div>
405 </div>
406 </div>
407 </div>
408 </form>
409
410
411
412