1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 20:22:42 +01:00

Improved handling of various document types; better documents zip

This commit is contained in:
Joshua Dwire 2016-03-24 11:33:28 -04:00
parent e6056104bd
commit 1c5c45a1e1
11 changed files with 183 additions and 117 deletions

View File

@ -46,7 +46,7 @@ class DocumentController extends BaseController
if($stream){ if($stream){
$headers = [ $headers = [
'Content-Type' => $document->type, 'Content-Type' => Document::$types[$document->type]['mime'],
'Content-Length' => $document->size, 'Content-Length' => $document->size,
]; ];
@ -56,7 +56,7 @@ class DocumentController extends BaseController
} }
else{ else{
$response = Response::make($document->getRaw(), 200); $response = Response::make($document->getRaw(), 200);
$response->header('content-type', $document->type); $response->header('content-type', Document::$types[$document->type]['mime']);
} }
return $response; return $response;
@ -80,9 +80,9 @@ class DocumentController extends BaseController
return redirect($direct_url); return redirect($direct_url);
} }
$extension = pathinfo($document->preview, PATHINFO_EXTENSION); $previewType = pathinfo($document->preview, PATHINFO_EXTENSION);
$response = Response::make($document->getRawPreview(), 200); $response = Response::make($document->getRawPreview(), 200);
$response->header('content-type', Document::$extensions[$extension]); $response->header('content-type', Document::$types[$previewType]['mime']);
return $response; return $response;
} }
@ -99,7 +99,7 @@ class DocumentController extends BaseController
return $response; return $response;
} }
if(substr($document->type, 0, 6) != 'image/'){ if(!$document->isPDFEmbeddable()){
return Response::view('error', array('error'=>'Image does not exist!'), 404); return Response::view('error', array('error'=>'Image does not exist!'), 404);
} }

View File

@ -19,8 +19,7 @@ use App\Ninja\Repositories\ActivityRepository;
use App\Events\InvoiceInvitationWasViewed; use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed; use App\Events\QuoteInvitationWasViewed;
use App\Services\PaymentService; use App\Services\PaymentService;
use League\Flysystem\Filesystem; use Barracuda\ArchiveStream\ZipArchive;
use League\Flysystem\ZipArchive\ZipArchiveAdapter;
class PublicClientController extends BaseController class PublicClientController extends BaseController
{ {
@ -138,8 +137,13 @@ class PublicClientController extends BaseController
'phantomjs' => Input::has('phantomjs'), 'phantomjs' => Input::has('phantomjs'),
); );
if($account->isPro() && $this->canCreateInvoiceDocsZip($invoice)){ if($account->isPro() && $this->canCreateZip()){
$zipDocs = $this->getInvoiceZipDocuments($invoice, $size);
if(count($zipDocs) > 1){
$data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}"); $data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}");
$data['documentsZipSize'] = $size;
}
} }
return View::make('invoices.view', $data); return View::make('invoices.view', $data);
@ -173,6 +177,12 @@ class PublicClientController extends BaseController
return $paymentTypes; return $paymentTypes;
} }
protected function humanFilesize($bytes, $decimals = 2) {
$size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$size[$factor];
}
public function download($invitationKey) public function download($invitationKey)
{ {
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
@ -391,7 +401,7 @@ class PublicClientController extends BaseController
$document = Document::scope($publicId, $invitation->account_id)->first(); $document = Document::scope($publicId, $invitation->account_id)->first();
if(!$document || substr($document->type, 0, 6) != 'image/'){ if(!$document->isPDFEmbeddable()){
return Response::view('error', array('error'=>'Image does not exist!'), 404); return Response::view('error', array('error'=>'Image does not exist!'), 404);
} }
@ -423,19 +433,43 @@ class PublicClientController extends BaseController
return function_exists('gmp_init'); return function_exists('gmp_init');
} }
protected function canCreateInvoiceDocsZip($invoice){ protected function getInvoiceZipDocuments($invoice, &$size=0){
if(!$this->canCreateZip())return false; $documents = $invoice->documents->sortBy('size');
if(count($invoice->documents) == 1)return false;
$size = 0;
$maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000; $maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000;
$i = 0; $toZip = array();
foreach($invoice->documents as $document){ foreach($documents as $document){
if($document->size <= $maxSize)$i++; if($size + $document->size > $maxSize)break;
if($i > 1){
return true; if(!empty($toZip[$document->name])){
// This name is taken
if($toZip[$document->name]->hash != $document->hash){
// 2 different files with the same name
$nameInfo = pathinfo($document->name);
for($i = 1;; $i++){
$name = $nameInfo['filename'].' ('.$i.').'.$nameInfo['extension'];
if(empty($toZip[$name])){
$toZip[$name] = $document;
$size += $document->size;
break;
} else if ($toZip[$name]->hash == $document->hash){
// We're not adding this after all
break;
} }
} }
return false;
}
}
else{
$toZip[$document->name] = $document;
$size += $document->size;
}
}
return $toZip;
} }
public function getInvoiceDocumentsZip($invitationKey){ public function getInvoiceDocumentsZip($invitationKey){
@ -451,58 +485,19 @@ class PublicClientController extends BaseController
return Response::view('error', array('error'=>'No documents'), 404); return Response::view('error', array('error'=>'No documents'), 404);
} }
$documents = $invoice->documents->sortBy('size'); $toZip = $this->getInvoiceZipDocuments($invoice);
$size = 0;
$maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000;
$toZip = array();
foreach($documents as $document){
$size += $document->size;
if($size > $maxSize)break;
if(!empty($toZip[$document->name])){
$hasSameHash = false;
foreach($toZip[$document->name] as $sameName){
if($sameName->hash == $document->hash){
$hasSameHash = true;
break;
}
}
if(!$hasSameHash){
// 2 different files with the same name
$toZip[$document->name][] = $document;
}
else{
// We're not adding this after all
$size -= $document->size;
}
}
else{
$toZip[$document->name] = array($document);
}
}
if(!count($toZip)){ if(!count($toZip)){
return Response::view('error', array('error'=>'No documents small enough'), 404); return Response::view('error', array('error'=>'No documents small enough'), 404);
} }
$zip = new \Barracuda\ArchiveStream\ZipArchive($invitation->account->name.' Invoice '.$invoice->invoice_number.'.zip'); $zip = new ZipArchive($invitation->account->name.' Invoice '.$invoice->invoice_number.'.zip');
return Response::stream(function() use ($toZip, $zip) { return Response::stream(function() use ($toZip, $zip) {
foreach($toZip as $documentsSameName){ foreach($toZip as $name=>$document){
$i = 0;
foreach($documentsSameName as $document){
$name = $document->name;
if($i){
$nameInfo = pathinfo($document->name);
$name = $nameInfo['filename'].' ('.$i.').'.$nameInfo['extension'];
}
$fileStream = $document->getStream(); $fileStream = $document->getStream();
if($fileStream){ if($fileStream){
$zip->init_file_stream_transfer($name, $document->size); $zip->init_file_stream_transfer($name, $document->size, array('time'=>$document->created_at->timestamp));
while ($buffer = fread($fileStream, 8192))$zip->stream_file_part($buffer); while ($buffer = fread($fileStream, 256000))$zip->stream_file_part($buffer);
fclose($fileStream); fclose($fileStream);
$zip->complete_file_stream(); $zip->complete_file_stream();
} }
@ -510,7 +505,6 @@ class PublicClientController extends BaseController
$zip->add_file($name, $document->getRaw()); $zip->add_file($name, $document->getRaw());
} }
} }
}
$zip->finish(); $zip->finish();
}, 200); }, 200);
} }

View File

@ -5,31 +5,68 @@ use DB;
class Document extends EntityModel class Document extends EntityModel
{ {
public static $extensions = array( public static $extraExtensions = array(
'png' => 'image/png', 'jpg' => 'jpeg',
'jpg' => 'image/jpeg', 'tif' => 'tiff',
'jpeg' => 'image/jpeg', );
'tiff' => 'image/tiff',
'tif' => 'image/tiff', public static $allowedMimes = array(// Used by Dropzone.js; does not affect what the server accepts
'pdf' => 'application/pdf', 'image/png', 'image/jpeg', 'image/tiff', 'application/pdf', 'image/gif', 'image/vnd.adobe.photoshop', 'text/plain',
'gif' => 'image/gif' 'application/zip', 'application/msword',
'application/excel', 'application/vnd.ms-excel', 'application/x-excel', 'application/x-msexcel',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','application/postscript', 'image/svg+xml',
'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.ms-powerpoint',
); );
public static $types = array( public static $types = array(
'image/png' => array( 'png' => array(
'extension' => 'png', 'mime' => 'image/png',
), ),
'image/jpeg' => array( 'ai' => array(
'extension' => 'jpeg', 'mime' => 'application/postscript',
), ),
'image/tiff' => array( 'svg' => array(
'extension' => 'tiff', 'mime' => 'image/svg+xml',
), ),
'image/gif' => array( 'jpeg' => array(
'extension' => 'gif', 'mime' => 'image/jpeg',
), ),
'application/pdf' => array( 'tiff' => array(
'extension' => 'pdf', 'mime' => 'image/tiff',
),
'pdf' => array(
'mime' => 'application/pdf',
),
'gif' => array(
'mime' => 'image/gif',
),
'psd' => array(
'mime' => 'image/vnd.adobe.photoshop',
),
'txt' => array(
'mime' => 'text/plain',
),
'zip' => array(
'mime' => 'application/zip',
),
'doc' => array(
'mime' => 'application/msword',
),
'xls' => array(
'mime' => 'application/vnd.ms-excel',
),
'ppt' => array(
'mime' => 'application/vnd.ms-powerpoint',
),
'xlsx' => array(
'mime' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
),
'docx' => array(
'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
),
'pptx' => array(
'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
), ),
); );
@ -142,11 +179,17 @@ class Document extends EntityModel
return url('client/document/'.$invitation->invitation_key.'/'.$this->public_id.'/'.$this->name); return url('client/document/'.$invitation->invitation_key.'/'.$this->public_id.'/'.$this->name);
} }
public function isPDFEmbeddable(){
return $this->type == 'jpeg' || $this->type == 'png' || $this->preview;
}
public function getVFSJSUrl(){ public function getVFSJSUrl(){
if(!$this->isPDFEmbeddable())return null;
return url('document/js/'.$this->public_id.'/'.$this->name.'.js'); return url('document/js/'.$this->public_id.'/'.$this->name.'.js');
} }
public function getClientVFSJSUrl(){ public function getClientVFSJSUrl(){
if(!$this->isPDFEmbeddable())return null;
return url('client/document/js/'.$this->public_id.'/'.$this->name.'.js'); return url('client/document/js/'.$this->public_id.'/'.$this->name.'.js');
} }

View File

@ -61,14 +61,22 @@ class DocumentRepository extends BaseRepository
$uploaded = $input['file']; $uploaded = $input['file'];
$extension = strtolower($uploaded->extension()); $extension = strtolower($uploaded->extension());
if(empty(Document::$extensions[$extension])){ if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){
$documentType = Document::$extraExtensions[$extension];
}
else{
$documentType = $extension;
}
if(empty(Document::$types[$documentType])){
return Response::json([ return Response::json([
'error' => 'Unsupported extension', 'error' => 'Unsupported extension',
'code' => 400 'code' => 400
], 400); ], 400);
} }
$documentType = Document::$extensions[$extension]; $documentTypeData = Document::$types[$documentType];
$filePath = $uploaded->path(); $filePath = $uploaded->path();
$name = $uploaded->getClientOriginalName(); $name = $uploaded->getClientOriginalName();
$size = filesize($filePath); $size = filesize($filePath);
@ -80,10 +88,10 @@ class DocumentRepository extends BaseRepository
], 400); ], 400);
} }
$documentTypeData = Document::$types[$documentType];
$hash = sha1_file($filePath); $hash = sha1_file($filePath);
$filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentTypeData['extension']; $filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType;
$document = Document::createNew(); $document = Document::createNew();
$disk = $document->getDisk(); $disk = $document->getDisk();
@ -94,20 +102,20 @@ class DocumentRepository extends BaseRepository
} }
// This is an image; check if we need to create a preview // This is an image; check if we need to create a preview
if(in_array($documentType, array('image/jpeg','image/png','image/gif','image/bmp','image/tiff'))){ if(in_array($documentType, array('jpeg','png','gif','bmp','tiff','psd'))){
$makePreview = false; $makePreview = false;
$imageSize = getimagesize($filePath); $imageSize = getimagesize($filePath);
$width = $imageSize[0]; $width = $imageSize[0];
$height = $imageSize[1]; $height = $imageSize[1];
$imgManagerConfig = array(); $imgManagerConfig = array();
if(in_array($documentType, array('image/gif','image/bmp','image/tiff'))){ if(in_array($documentType, array('gif','bmp','tiff','psd'))){
// Needs to be converted // Needs to be converted
$makePreview = true; $makePreview = true;
} else if($width > DOCUMENT_PREVIEW_SIZE || $height > DOCUMENT_PREVIEW_SIZE){ } else if($width > DOCUMENT_PREVIEW_SIZE || $height > DOCUMENT_PREVIEW_SIZE){
$makePreview = true; $makePreview = true;
} }
if($documentType == 'image/bmp' || $documentType == 'image/tiff'){ if(in_array($documentType,array('bmp','tiff','psd'))){
if(!class_exists('Imagick')){ if(!class_exists('Imagick')){
// Cant't read this // Cant't read this
$makePreview = false; $makePreview = false;
@ -117,13 +125,13 @@ class DocumentRepository extends BaseRepository
} }
if($makePreview){ if($makePreview){
$previewType = 'jpg'; $previewType = 'jpeg';
if(in_array($documentType, array('image/png','image/gif','image/bmp','image/tiff'))){ if(in_array($documentType, array('png','gif','tiff','psd'))){
// Has transparency // Has transparency
$previewType = 'png'; $previewType = 'png';
} }
$document->preview = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentTypeData['extension'].'.x'.DOCUMENT_PREVIEW_SIZE.'.'.$previewType; $document->preview = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType.'.x'.DOCUMENT_PREVIEW_SIZE.'.'.$previewType;
if(!$disk->exists($document->preview)){ if(!$disk->exists($document->preview)){
// We haven't created a preview yet // We haven't created a preview yet
$imgManager = new ImageManager($imgManagerConfig); $imgManager = new ImageManager($imgManagerConfig);
@ -170,7 +178,7 @@ class DocumentRepository extends BaseRepository
$doc_array = $document->toArray(); $doc_array = $document->toArray();
if(!empty($base64)){ if(!empty($base64)){
$mime = !empty($previewType)?Document::$extensions[$previewType]:$documentType; $mime = Document::$types[!empty($previewType)?$previewType:$documentType]['mime'];
$doc_array['base64'] = 'data:'.$mime.';base64,'.$base64; $doc_array['base64'] = 'data:'.$mime.';base64,'.$base64;
} }

View File

@ -159,6 +159,12 @@ class AppServiceProvider extends ServiceProvider {
return $str . '</ol>'; return $str . '</ol>';
}); });
Form::macro('human_filesize', function($bytes, $decimals = 1) {
$size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor];
});
Validator::extend('positive', function($attribute, $value, $parameters) { Validator::extend('positive', function($attribute, $value, $parameters) {
return Utils::parseFloat($value) >= 0; return Utils::parseFloat($value) >= 0;
}); });

View File

@ -31404,10 +31404,10 @@ NINJA.invoiceDocuments = function(invoice) {
for (var i = 0; i < invoice.documents.length; i++) { for (var i = 0; i < invoice.documents.length; i++) {
var document = invoice.documents[i]; var document = invoice.documents[i];
var path = document.base64; var path = document.base64;
if(!path && (document.preview_url || document.type == 'image/png' || document.type == 'image/jpeg')){
path = 'docs/'+document.public_id+'/'+document.name; if(!path)path = 'docs/'+document.public_id+'/'+document.name;
}
if(path && (window.pdfMake.vfs[path] || document.base64)){ if(path && (window.pdfMake.vfs[path] || document.base64)){
// Only embed if we actually have an image for it
if(j%3==0){ if(j%3==0){
stackItem = {columns:[]}; stackItem = {columns:[]};
stack.push(stackItem); stack.push(stackItem);

View File

@ -412,10 +412,10 @@ NINJA.invoiceDocuments = function(invoice) {
for (var i = 0; i < invoice.documents.length; i++) { for (var i = 0; i < invoice.documents.length; i++) {
var document = invoice.documents[i]; var document = invoice.documents[i];
var path = document.base64; var path = document.base64;
if(!path && (document.preview_url || document.type == 'image/png' || document.type == 'image/jpeg')){
path = 'docs/'+document.public_id+'/'+document.name; if(!path)path = 'docs/'+document.public_id+'/'+document.name;
}
if(path && (window.pdfMake.vfs[path] || document.base64)){ if(path && (window.pdfMake.vfs[path] || document.base64)){
// Only embed if we actually have an image for it
if(j%3==0){ if(j%3==0){
stackItem = {columns:[]}; stackItem = {columns:[]};
stack.push(stackItem); stack.push(stackItem);

View File

@ -1107,7 +1107,7 @@ $LANG = array(
'invoice_embed_documents' => 'Embed Documents', 'invoice_embed_documents' => 'Embed Documents',
'invoice_embed_documents_help' => 'Include attached images in the invoice.', 'invoice_embed_documents_help' => 'Include attached images in the invoice.',
'document_email_attachment' => 'Attach Documents', 'document_email_attachment' => 'Attach Documents',
'download_documents' => 'Download Documents', 'download_documents' => 'Download Documents (:size)',
); );
return $LANG; return $LANG;

View File

@ -930,7 +930,7 @@
params:{ params:{
_token:"{{ Session::getToken() }}" _token:"{{ Session::getToken() }}"
}, },
acceptedFiles:{!! json_encode(implode(',',array_keys(\App\Models\Document::$types))) !!}, acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!},
addRemoveLinks:true, addRemoveLinks:true,
maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}},
dictDefaultMessage:{!! json_encode(trans('texts.document_upload_message')) !!} dictDefaultMessage:{!! json_encode(trans('texts.document_upload_message')) !!}
@ -955,9 +955,12 @@
dropzone.emit('addedfile', mockFile); dropzone.emit('addedfile', mockFile);
dropzone.emit('complete', mockFile); dropzone.emit('complete', mockFile);
if(document.type().match(/image.*/)){ if(document.preview_url()){
dropzone.emit('thumbnail', mockFile, document.preview_url()||document.url()); dropzone.emit('thumbnail', mockFile, document.preview_url()||document.url());
} }
else if(document.type()=='jpeg' || document.type()=='png' || document.type()=='svg'){
dropzone.emit('thumbnail', mockFile, document.url());
}
dropzone.files.push(mockFile); dropzone.files.push(mockFile);
} }
@endif @endif
@ -1005,7 +1008,9 @@
} }
function createInvoiceModel() { function createInvoiceModel() {
var invoice = ko.toJS(window.model).invoice; var model = ko.toJS(window.model);
if(!model)return;
var invoice = model.invoice;
invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }}; invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }};
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }}; invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true}); invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
@ -1335,13 +1340,19 @@
file.public_id = response.document.public_id file.public_id = response.document.public_id
model.invoice().documents()[file.index].update(response.document); model.invoice().documents()[file.index].update(response.document);
refreshPDF(true); refreshPDF(true);
if(response.document.preview_url){
dropzone.emit('thumbnail', file, response.document.preview_url);
}
} }
@endif @endif
</script> </script>
@if ($account->isPro() && $account->invoice_embed_documents) @if ($account->isPro() && $account->invoice_embed_documents)
@foreach ($invoice->documents as $document) @foreach ($invoice->documents as $document)
@if($document->isPDFEmbeddable())
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script> <script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>
@endif
@endforeach @endforeach
@endif @endif

View File

@ -59,7 +59,9 @@
@if (Auth::user()->account->isPro() && Auth::user()->account->invoice_embed_documents) @if (Auth::user()->account->isPro() && Auth::user()->account->invoice_embed_documents)
@foreach ($invoice->documents as $document) @foreach ($invoice->documents as $document)
@if($document->isPDFEmbeddable())
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script> <script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>
@endif
@endforeach @endforeach
@endif @endif
@stop @stop

View File

@ -43,7 +43,7 @@
</div> </div>
<div class="pull-left"> <div class="pull-left">
@if(!empty($documentsZipURL)) @if(!empty($documentsZipURL))
{!! Button::normal(trans('texts.download_documents'))->asLinkTo($documentsZipURL)->large() !!} {!! Button::normal(trans('texts.download_documents', array('size'=>Form::human_filesize($documentsZipSize))))->asLinkTo($documentsZipURL)->large() !!}
@endif @endif
</div> </div>
@endif @endif
@ -54,7 +54,7 @@
<h3>{{ trans('texts.documents_header') }}</h3> <h3>{{ trans('texts.documents_header') }}</h3>
<ul> <ul>
@foreach ($invoice->documents as $document) @foreach ($invoice->documents as $document)
<li><a target="_blank" href="{{ $document->getClientUrl($invitation) }}">{{$document->name}}</a></li> <li><a target="_blank" href="{{ $document->getClientUrl($invitation) }}">{{$document->name}} ({{Form::human_filesize($document->size)}})</a></li>
@endforeach @endforeach
</ul> </ul>
</div> </div>
@ -62,7 +62,9 @@
@if ($account->isPro() && $account->invoice_embed_documents) @if ($account->isPro() && $account->invoice_embed_documents)
@foreach ($invoice->documents as $document) @foreach ($invoice->documents as $document)
@if($document->isPDFEmbeddable())
<script src="{{ $document->getClientVFSJSUrl() }}" type="text/javascript" {{ Input::has('phantomjs')?'':'async' }}></script> <script src="{{ $document->getClientVFSJSUrl() }}" type="text/javascript" {{ Input::has('phantomjs')?'':'async' }}></script>
@endif
@endforeach @endforeach
@endif @endif
<script type="text/javascript"> <script type="text/javascript">