diff --git a/.travis.yml b/.travis.yml index 33fed981d380..91a6651bf13f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -114,6 +114,8 @@ after_script: - mysql -u root -e 'select * from credits;' ninja - mysql -u root -e 'select * from expenses;' ninja - mysql -u root -e 'select * from accounts;' ninja + - mysql -u root -e 'select * from fonts;' ninja + - mysql -u root -e 'select * from banks;' ninja - cat storage/logs/laravel-error.log - cat storage/logs/laravel-info.log - FILES=$(find tests/_output -type f -name '*.png' | sort -nr) diff --git a/app/Console/Commands/InitLookup.php b/app/Console/Commands/InitLookup.php index aa52ec4f4a35..f37de078be2f 100644 --- a/app/Console/Commands/InitLookup.php +++ b/app/Console/Commands/InitLookup.php @@ -21,7 +21,7 @@ class InitLookup extends Command * * @var string */ - protected $signature = 'ninja:init-lookup {--truncate=} {--validate=} {--update=} {--company_id=} {--page_size=100} {--database=db-ninja-1}'; + protected $signature = 'ninja:init-lookup {--truncate=} {--subdomain} {--validate=} {--update=} {--company_id=} {--page_size=100} {--database=db-ninja-1}'; /** * The console command description. @@ -57,9 +57,12 @@ class InitLookup extends Command $database = $this->option('database'); $dbServer = DbServer::whereName($database)->first(); - if ($this->option('truncate')) { + if ($this->option('subdomain')) { + $this->logMessage('Updating subdomains...'); + $this->popuplateSubdomains(); + } else if ($this->option('truncate')) { + $this->logMessage('Truncating data...'); $this->truncateTables(); - $this->logMessage('Truncated'); } else { config(['database.default' => $this->option('database')]); @@ -87,6 +90,30 @@ class InitLookup extends Command } } + private function popuplateSubdomains() + { + $data = []; + + config(['database.default' => $this->option('database')]); + + $accounts = DB::table('accounts') + ->orderBy('id') + ->where('subdomain', '!=', '') + ->get(['account_key', 'subdomain']); + foreach ($accounts as $account) { + $data[$account->account_key] = $account->subdomain; + } + + config(['database.default' => DB_NINJA_LOOKUP]); + + $validate = $this->option('validate'); + $update = $this->option('update'); + + foreach ($data as $accountKey => $subdomain) { + LookupAccount::whereAccountKey($accountKey)->update(['subdomain' => $subdomain]); + } + } + private function initCompanies($dbServerId, $offset = 0) { $data = []; @@ -340,6 +367,7 @@ class InitLookup extends Command protected function getOptions() { return [ + ['subdomain', null, InputOption::VALUE_OPTIONAL, 'Subdomain', null], ['truncate', null, InputOption::VALUE_OPTIONAL, 'Truncate', null], ['company_id', null, InputOption::VALUE_OPTIONAL, 'Company Id', null], ['page_size', null, InputOption::VALUE_OPTIONAL, 'Page Size', null], diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index c7d32473ee4c..b608a092055d 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -116,8 +116,10 @@ class SendRecurringInvoices extends Command try { $invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice); if ($invoice && ! $invoice->isPaid()) { - $this->info('Sending Invoice'); + $this->info('Not billed - Sending Invoice'); $this->mailer->sendInvoice($invoice); + } elseif ($invoice) { + $this->info('Successfully billed invoice'); } } catch (Exception $exception) { $this->info('Error: ' . $exception->getMessage()); diff --git a/app/Console/Commands/SendReminders.php b/app/Console/Commands/SendReminders.php index 9347b69f525d..cd691a768243 100644 --- a/app/Console/Commands/SendReminders.php +++ b/app/Console/Commands/SendReminders.php @@ -2,12 +2,18 @@ namespace App\Console\Commands; +use Carbon; +use Str; use App\Models\Invoice; use App\Ninja\Mailers\ContactMailer as Mailer; +use App\Ninja\Mailers\UserMailer; use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\InvoiceRepository; +use App\Models\ScheduledReport; use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; +use App\Jobs\ExportReportResults; +use App\Jobs\RunReport; /** * Class SendReminders. @@ -46,13 +52,14 @@ class SendReminders extends Command * @param InvoiceRepository $invoiceRepo * @param accountRepository $accountRepo */ - public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo) + public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, UserMailer $userMailer) { parent::__construct(); $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; $this->accountRepo = $accountRepo; + $this->userMailer = $userMailer; } public function fire() @@ -63,6 +70,23 @@ class SendReminders extends Command config(['database.default' => $database]); } + $this->chargeLateFees(); + $this->setReminderEmails(); + $this->sendScheduledReports(); + + $this->info('Done'); + + if ($errorEmail = env('ERROR_EMAIL')) { + \Mail::raw('EOM', function ($message) use ($errorEmail, $database) { + $message->to($errorEmail) + ->from(CONTACT_EMAIL) + ->subject("SendReminders [{$database}]: Finished successfully"); + }); + } + } + + private function chargeLateFees() + { $accounts = $this->accountRepo->findWithFees(); $this->info(count($accounts) . ' accounts found with fees'); @@ -79,17 +103,20 @@ class SendReminders extends Command $this->info('Charge fee: ' . $invoice->id); $account->loadLocalizationSettings($invoice->client); // support trans to add fee line item $number = preg_replace('/[^0-9]/', '', $reminder); + $amount = $account->account_email_settings->{"late_fee{$number}_amount"}; $percent = $account->account_email_settings->{"late_fee{$number}_percent"}; $this->invoiceRepo->setLateFee($invoice, $amount, $percent); } } } + } + private function setReminderEmails() + { $accounts = $this->accountRepo->findWithReminders(); $this->info(count($accounts) . ' accounts found with reminders'); - /** @var \App\Models\Account $account */ foreach ($accounts as $account) { if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) { continue; @@ -98,7 +125,6 @@ class SendReminders extends Command $invoices = $this->invoiceRepo->findNeedingReminding($account); $this->info($account->name . ': ' . count($invoices) . ' invoices found'); - /** @var Invoice $invoice */ foreach ($invoices as $invoice) { if ($reminder = $account->getInvoiceReminder($invoice)) { $this->info('Send email: ' . $invoice->id); @@ -106,15 +132,34 @@ class SendReminders extends Command } } } + } - $this->info('Done'); + private function sendScheduledReports() + { + $scheduledReports = ScheduledReport::where('send_date', '<=', date('Y-m-d')) + ->with('user', 'account.company') + ->get(); + $this->info(count($scheduledReports) . ' scheduled reports'); - if ($errorEmail = env('ERROR_EMAIL')) { - \Mail::raw('EOM', function ($message) use ($errorEmail, $database) { - $message->to($errorEmail) - ->from(CONTACT_EMAIL) - ->subject("SendReminders [{$database}]: Finished successfully"); - }); + foreach ($scheduledReports as $scheduledReport) { + $user = $scheduledReport->user; + $account = $scheduledReport->account; + + if (! $account->hasFeature(FEATURE_REPORTS)) { + continue; + } + + $config = (array) json_decode($scheduledReport->config); + $reportType = $config['report_type']; + + $report = dispatch(new RunReport($scheduledReport->user, $reportType, $config, true)); + $file = dispatch(new ExportReportResults($scheduledReport->user, $config['export_format'], $reportType, $report->exportParams)); + + if ($file) { + $this->userMailer->sendScheduledReport($scheduledReport, $file); + } + + $scheduledReport->updateSendDate(); } } diff --git a/app/Console/Commands/UpdateKey.php b/app/Console/Commands/UpdateKey.php index 5625822488a9..4bf51c28ef2e 100644 --- a/app/Console/Commands/UpdateKey.php +++ b/app/Console/Commands/UpdateKey.php @@ -7,11 +7,11 @@ use Symfony\Component\Console\Input\InputOption; use App\Models\AccountGateway; use App\Models\BankAccount; use Artisan; -use Crypt; use Illuminate\Encryption\Encrypter; +use Laravel\LegacyEncrypter\McryptEncrypter; /** - * Class PruneData. + * Class UpdateKey */ class UpdateKey extends Command { @@ -34,16 +34,29 @@ class UpdateKey extends Command exit; } + $legacy = false; + if ($this->option('legacy') == 'true') { + $legacy = new McryptEncrypter(env('APP_KEY')); + } + // load the current values $gatewayConfigs = []; $bankUsernames = []; foreach (AccountGateway::all() as $gateway) { - $gatewayConfigs[$gateway->id] = $gateway->getConfig(); + if ($legacy) { + $gatewayConfigs[$gateway->id] = json_decode($legacy->decrypt($gateway->config)); + } else { + $gatewayConfigs[$gateway->id] = $gateway->getConfig(); + } } foreach (BankAccount::all() as $bank) { - $bankUsernames[$bank->id] = $bank->getUsername(); + if ($legacy) { + $bankUsernames[$bank->id] = $legacy->decrypt($bank->username); + } else { + $bankUsernames[$bank->id] = $bank->getUsername(); + } } // check if we can write to the .env file @@ -57,7 +70,8 @@ class UpdateKey extends Command $key = str_random(32); } - $crypt = new Encrypter($key, config('app.cipher')); + $cipher = $legacy ? 'AES-256-CBC' : config('app.cipher'); + $crypt = new Encrypter($key, $cipher); // update values using the new key/encrypter foreach (AccountGateway::all() as $gateway) { @@ -72,11 +86,21 @@ class UpdateKey extends Command $bank->save(); } + $message = date('r') . ' Successfully updated '; if ($envWriteable) { - $this->info(date('r') . ' Successfully update the key'); + if ($legacy) { + $message .= 'the key, set the cipher in the .env file to AES-256-CBC'; + } else { + $message .= 'the key'; + } } else { - $this->info(date('r') . ' Successfully update data, make sure to set the new app key: ' . $key); + if ($legacy) { + $message .= 'the data, make sure to set the new cipher/key: AES-256-CBC/' . $key; + } else { + $message .= 'the data, make sure to set the new key: ' . $key; + } } + $this->info($message); } /** @@ -92,6 +116,8 @@ class UpdateKey extends Command */ protected function getOptions() { - return []; + return [ + ['legacy', null, InputOption::VALUE_OPTIONAL, 'Legacy', null], + ]; } } diff --git a/app/Constants.php b/app/Constants.php index bfacde203d42..e55e00c3185b 100644 --- a/app/Constants.php +++ b/app/Constants.php @@ -2,6 +2,7 @@ if (! defined('APP_NAME')) { define('APP_NAME', env('APP_NAME', 'Invoice Ninja')); + define('APP_DOMAIN', env('APP_DOMAIN', 'invoiceninja.com')); define('CONTACT_EMAIL', env('MAIL_FROM_ADDRESS', env('MAIL_USERNAME'))); define('CONTACT_NAME', env('MAIL_FROM_NAME')); define('SITE_URL', env('APP_URL')); @@ -39,6 +40,7 @@ if (! defined('APP_NAME')) { define('ENTITY_PROJECT', 'project'); define('ENTITY_RECURRING_EXPENSE', 'recurring_expense'); define('ENTITY_CUSTOMER', 'customer'); + define('ENTITY_SUBSCRIPTION', 'subscription'); define('INVOICE_TYPE_STANDARD', 1); define('INVOICE_TYPE_QUOTE', 2); @@ -228,6 +230,11 @@ if (! defined('APP_NAME')) { define('FREQUENCY_SIX_MONTHS', 8); define('FREQUENCY_ANNUALLY', 9); + define('REPORT_FREQUENCY_DAILY', 'daily'); + define('REPORT_FREQUENCY_WEEKLY', 'weekly'); + define('REPORT_FREQUENCY_BIWEEKLY', 'biweekly'); + define('REPORT_FREQUENCY_MONTHLY', 'monthly'); + define('SESSION_TIMEZONE', 'timezone'); define('SESSION_CURRENCY', 'currency'); define('SESSION_CURRENCY_DECORATOR', 'currency_decorator'); @@ -310,7 +317,7 @@ if (! defined('APP_NAME')) { define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest')); define('NINJA_DATE', '2000-01-01'); - define('NINJA_VERSION', '3.9.2' . env('NINJA_VERSION_SUFFIX')); + define('NINJA_VERSION', '4.0.0' . env('NINJA_VERSION_SUFFIX')); define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); @@ -426,6 +433,7 @@ if (! defined('APP_NAME')) { define('GATEWAY_TYPE_SOFORT', 8); define('GATEWAY_TYPE_SEPA', 9); define('GATEWAY_TYPE_GOCARDLESS', 10); + define('GATEWAY_TYPE_APPLE_PAY', 11); define('GATEWAY_TYPE_TOKEN', 'token'); define('TEMPLATE_INVOICE', 'invoice'); @@ -451,6 +459,9 @@ if (! defined('APP_NAME')) { define('FILTER_INVOICE_DATE', 'invoice_date'); define('FILTER_PAYMENT_DATE', 'payment_date'); + define('ADDRESS_BILLING', 'billing_address'); + define('ADDRESS_SHIPPING', 'shipping_address'); + define('SOCIAL_GOOGLE', 'Google'); define('SOCIAL_FACEBOOK', 'Facebook'); define('SOCIAL_GITHUB', 'GitHub'); diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 6aa8054d38f0..8922c26ba5ab 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -4,6 +4,7 @@ namespace App\Exceptions; use Crawler; use Exception; +use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; @@ -28,7 +29,7 @@ class Handler extends ExceptionHandler */ protected $dontReport = [ TokenMismatchException::class, - //ModelNotFoundException::class, + ModelNotFoundException::class, //AuthorizationException::class, //HttpException::class, //ValidationException::class, @@ -150,4 +151,31 @@ class Handler extends ExceptionHandler return parent::render($request, $e); } } + + /** + * Convert an authentication exception into an unauthenticated response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Auth\AuthenticationException $exception + * @return \Illuminate\Http\Response + */ + protected function unauthenticated($request, AuthenticationException $exception) + { + if ($request->expectsJson()) { + return response()->json(['error' => 'Unauthenticated.'], 401); + } + + $guard = array_get($exception->guards(), 0); + + switch ($guard) { + case 'client': + $url = '/client/login'; + break; + default: + $url = '/login'; + break; + } + + return redirect()->guest($url); + } } diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index cf6b3647559e..dc6c335cc887 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -16,6 +16,7 @@ use Auth; use Cache; use Exception; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; use Response; use Socialite; use Utils; @@ -91,7 +92,7 @@ class AccountApiController extends BaseAPIController return $this->response($data); } - + public function show(Request $request) { $account = Auth::user()->account; @@ -118,7 +119,13 @@ class AccountApiController extends BaseAPIController public function getUserAccounts(Request $request) { - return $this->processLogin($request); + $user = Auth::user(); + + $users = $this->accountRepo->findUsers($user, 'account.account_tokens'); + $transformer = new UserAccountTransformer($user->account, $request->serializer, $request->token_name); + $data = $this->createCollection($users, $transformer, 'user_account'); + + return $this->response($data); } public function update(UpdateAccountRequest $request) @@ -140,7 +147,7 @@ class AccountApiController extends BaseAPIController $devices = json_decode($account->devices, true); for ($x = 0; $x < count($devices); $x++) { - if ($devices[$x]['email'] == Auth::user()->username) { + if ($devices[$x]['email'] == $request->email) { $devices[$x]['token'] = $request->token; //update $devices[$x]['device'] = $request->device; $account->devices = json_encode($devices); @@ -171,6 +178,26 @@ class AccountApiController extends BaseAPIController return $this->response($newDevice); } + public function removeDeviceToken(Request $request) { + + $account = Auth::user()->account; + + $devices = json_decode($account->devices, true); + + foreach($devices as $key => $value) + { + + if($request->token == $value['token']) + unset($devices[$key]); + + } + + $account->devices = json_encode(array_values($devices)); + $account->save(); + + return $this->response(['success']); + } + public function updatePushNotifications(Request $request) { $account = Auth::user()->account; @@ -220,4 +247,11 @@ class AccountApiController extends BaseAPIController return $this->errorResponse(['message' => 'Invalid credentials'], 401); } + + public function iosSubscriptionStatus() { + + //stubbed for iOS callbacks + + } + } diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 97e35c140149..ffdb3094af77 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -494,6 +494,8 @@ class AccountController extends BaseController 'account' => Auth::user()->account, 'title' => trans('texts.tax_rates'), 'taxRates' => TaxRate::scope()->whereIsInclusive(false)->get(), + 'countInvoices' => Invoice::scope()->withTrashed()->count(), + 'hasInclusiveTaxRates' => TaxRate::scope()->whereIsInclusive(true)->count() ? true : false, ]; return View::make('accounts.tax_rates', $data); @@ -769,11 +771,20 @@ class AccountController extends BaseController */ public function saveClientPortalSettings(SaveClientPortalSettings $request) { - $account = $request->user()->account; - if($account->subdomain !== $request->subdomain) + // check subdomain is unique in the lookup tables + if (request()->subdomain) { + if (! \App\Models\LookupAccount::validateField('subdomain', request()->subdomain, $account)) { + return Redirect::to('settings/' . ACCOUNT_CLIENT_PORTAL) + ->withError(trans('texts.subdomain_taken')) + ->withInput(); + } + } + + if ($account->subdomain !== $request->subdomain) { event(new SubdomainWasUpdated($account)); + } $account->fill($request->all()); $account->client_view_css = $request->client_view_css; diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index 09a17ede065e..559840caaa2b 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -17,6 +17,7 @@ use Utils; use Validator; use View; use WePay; +use File; class AccountGatewayController extends BaseController { @@ -119,9 +120,9 @@ class AccountGatewayController extends BaseController $creditCards = []; foreach ($creditCardsArray as $card => $name) { if ($selectedCards > 0 && ($selectedCards & $card) == $card) { - $creditCards[$name['text']] = ['value' => $card, 'data-imageUrl' => asset($name['card']), 'checked' => 'checked']; + $creditCards['
t |
p,v=i.minHeight&&i.minHeight>f;i.grid=c,b&&(p+=l),v&&(f+=u),m&&(p-=l),g&&(f-=u),/^(se|s|e)$/.test(r)?(n.size.width=p,n.size.height=f):/^(ne)$/.test(r)?(n.size.width=p,n.size.height=f,n.position.top=s.top-d):/^(sw)$/.test(r)?(n.size.width=p,n.size.height=f,n.position.left=s.left-h):((f-u<=0||p-l<=0)&&(e=n._getPaddingPlusBorderDimensions(this)),f-u>0?(n.size.height=f,n.position.top=s.top-d):(f=u-e.height,n.size.height=f,n.position.top=s.top+a.height-f),p-l>0?(n.size.width=p,n.position.left=s.left-h):(p=u-e.height,n.size.width=p,n.position.left=s.left+a.width-p))}});t.ui.resizable,t.widget("ui.dialog",{version:"1.11.2",options:{appendTo:"body",autoOpen:!0,buttons:[],closeOnEscape:!0,closeText:"Close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(e){var n=t(this).css(e).offset().top;n<0&&t(this).css("top",e.top-n)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),this.options.title=this.options.title||this.originalTitle,this._createWrapper(),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&t.fn.draggable&&this._makeDraggable(),this.options.resizable&&t.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var e=this.options.appendTo;return e&&(e.jquery||e.nodeType)?t(e):this.document.find(e||"body").eq(0)},_destroy:function(){var t,e=this.originalPosition;this._destroyOverlay(),this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach(),this.uiDialog.stop(!0,!0).remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),t=e.parent.children().eq(e.index),t.length&&t[0]!==this.element[0]?t.before(this.element):e.parent.append(this.element)},widget:function(){return this.uiDialog},disable:t.noop,enable:t.noop,close:function(e){var n,i=this;if(this._isOpen&&this._trigger("beforeClose",e)!==!1){if(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),!this.opener.filter(":focusable").focus().length)try{n=this.document[0].activeElement,n&&"body"!==n.nodeName.toLowerCase()&&t(n).blur()}catch(o){}this._hide(this.uiDialog,this.options.hide,function(){i._trigger("close",e)})}},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(e,n){var i=!1,o=this.uiDialog.siblings(".ui-front:visible").map(function(){return+t(this).css("z-index")}).get(),a=Math.max.apply(null,o);return a>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",a+1),i=!0),i&&!n&&this._trigger("focus",e),i},open:function(){var e=this;return this._isOpen?void(this._moveToTop()&&this._focusTabbable()):(this._isOpen=!0,this.opener=t(this.document[0].activeElement),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){e._focusTabbable(),e._trigger("focus")}),this._makeFocusTarget(),void this._trigger("open"))},_focusTabbable:function(){var t=this._focusedElement;t||(t=this.element.find("[autofocus]")),t.length||(t=this.element.find(":tabbable")),t.length||(t=this.uiDialogButtonPane.find(":tabbable")),t.length||(t=this.uiDialogTitlebarClose.filter(":tabbable")),t.length||(t=this.uiDialog),t.eq(0).focus()},_keepFocus:function(e){function n(){var e=this.document[0].activeElement,n=this.uiDialog[0]===e||t.contains(this.uiDialog[0],e);n||this._focusTabbable()}e.preventDefault(),n.call(this),this._delay(n)},_createWrapper:function(){this.uiDialog=t("
1&&M.splice.apply(M,[1,0].concat(M.splice(y,f+1))),s.dequeue()},t.effects.effect.clip=function(e,n){var i,o,a,s=t(this),r=["position","top","bottom","left","right","height","width"],c=t.effects.setMode(s,e.mode||"hide"),l="show"===c,u=e.direction||"vertical",h="vertical"===u,d=h?"height":"width",p=h?"top":"left",f={};t.effects.save(s,r),s.show(),i=t.effects.createWrapper(s).css({overflow:"hidden"}),o="IMG"===s[0].tagName?i:s,a=o[d](),l&&(o.css(d,0),o.css(p,a/2)),f[d]=l?a:0,f[p]=l?0:a/2,o.animate(f,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){l||s.hide(),t.effects.restore(s,r),t.effects.removeWrapper(s),n()}})},t.effects.effect.drop=function(e,n){var i,o=t(this),a=["position","top","bottom","left","right","opacity","height","width"],s=t.effects.setMode(o,e.mode||"hide"),r="show"===s,c=e.direction||"left",l="up"===c||"down"===c?"top":"left",u="up"===c||"left"===c?"pos":"neg",h={opacity:r?1:0};t.effects.save(o,a),o.show(),t.effects.createWrapper(o),i=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0)/2,r&&o.css("opacity",0).css(l,"pos"===u?-i:i),h[l]=(r?"pos"===u?"+=":"-=":"pos"===u?"-=":"+=")+i,o.animate(h,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===s&&o.hide(),t.effects.restore(o,a),t.effects.removeWrapper(o),n()}})},t.effects.effect.explode=function(e,n){function i(){M.push(this),M.length===h*d&&o()}function o(){p.css({visibility:"visible"}),t(M).remove(),m||p.hide(),n()}var a,s,r,c,l,u,h=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=h,p=t(this),f=t.effects.setMode(p,e.mode||"hide"),m="show"===f,g=p.show().css("visibility","hidden").offset(),b=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/h),M=[];for(a=0;a
t<"F"ip>'),C.renderer?i.isPlainObject(C.renderer)&&!C.renderer.header&&(C.renderer.header="jqueryui"):C.renderer="jqueryui"):i.extend(O,Yt.ext.classes,m.oClasses),i(this).addClass(O.sTable),""===C.oScroll.sX&&""===C.oScroll.sY||(C.oScroll.iBarWidth=wt()),C.oScroll.sX===!0&&(C.oScroll.sX="100%"),C.iInitDisplayStart===n&&(C.iInitDisplayStart=m.iDisplayStart,C._iDisplayStart=m.iDisplayStart),null!==m.iDeferLoading){C.bDeferLoading=!0;var N=i.isArray(m.iDeferLoading);C._iRecordsDisplay=N?m.iDeferLoading[0]:m.iDeferLoading,C._iRecordsTotal=N?m.iDeferLoading[1]:m.iDeferLoading}var S=C.oLanguage;i.extend(!0,S,m.oLanguage),""!==S.sUrl&&(i.ajax({dataType:"json",url:S.sUrl,success:function(t){s(t),a(_.oLanguage,t),i.extend(!0,S,t),rt(C)},error:function(){rt(C)}}),v=!0),null===m.asStripeClasses&&(C.asStripeClasses=[O.sStripeOdd,O.sStripeEven]);var x=C.asStripeClasses,L=i("tbody tr:eq(0)",this);i.inArray(!0,i.map(x,function(t,e){return L.hasClass(t)}))!==-1&&(i("tbody tr",this).removeClass(x.join(" ")),C.asDestroyStripes=x.slice());var D,q=[],W=this.getElementsByTagName("thead");if(0!==W.length&&(R(C.aoHeader,W[0]),q=F(C)),null===m.aoColumns)for(D=[],g=0,p=q.length;g
").appendTo(this)),C.nTHead=X[0];var H=i(this).children("tbody");0===H.length&&(H=i("
").appendTo(this)),C.nTBody=H[0];var j=i(this).children("tfoot");if(0===j.length&&P.length>0&&(""!==C.oScroll.sX||""!==C.oScroll.sY)&&(j=i("").appendTo(this)),0===j.length||0===j.children().length?i(this).addClass(O.sNoFooter):j.length>0&&(C.nTFoot=j[0],R(C.aoFooter,C.nTFoot)),m.aaData)for(g=0;g