1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-19 16:01:34 +02:00

Add Files

This commit is contained in:
Jeramy Simpson 2015-03-17 07:45:25 +10:00
parent 77922893d2
commit 04c392136e
476 changed files with 138012 additions and 5 deletions

View File

@ -0,0 +1,266 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
/*
##################################################################
WARNING: Please backup your database before running this script
##################################################################
Since the application was released a number of bugs have (inevitably) been found.
Although the bugs have always been fixed in some cases they've caused the client's
balance, paid to date and/or activity records to become inaccurate. This script will
check for errors and correct the data.
If you have any questions please email us at contact@invoiceninja.com
Usage:
php artisan ninja:check-data
Options:
--client_id:<value>
Limits the script to a single client
--fix=true
By default the script only checks for errors, adding this option
makes the script apply the fixes.
*/
class CheckData extends Command {
protected $name = 'ninja:check-data';
protected $description = 'Check/fix data';
public function fire()
{
$this->info(date('Y-m-d') . ' Running CheckData...');
$today = new DateTime();
if (!$this->option('client_id')) {
// update client deletion activities with the client's current balance
$activities = DB::table('activities')
->join('clients', 'clients.id', '=', 'activities.client_id')
->where('activities.activity_type_id', '=', ACTIVITY_TYPE_DELETE_CLIENT)
->where('activities.balance', '=', 0)
->where('clients.balance', '!=', 0)
->get(['activities.id', 'clients.balance']);
$this->info(count($activities) . ' delete client activities with zero balance');
if ($this->option('fix') == 'true') {
foreach ($activities as $activity) {
DB::table('activities')
->where('id', $activity->id)
->update(['balance' => $activity->balance]);
}
}
// update client paid_to_date value
$clients = DB::table('clients')
->join('payments', 'payments.client_id', '=', 'clients.id')
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
->where('payments.is_deleted', '=', 0)
->where('invoices.is_deleted', '=', 0)
->groupBy('clients.id')
->havingRaw('clients.paid_to_date != sum(payments.amount) and clients.paid_to_date != 999999999.9999')
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]);
$this->info(count($clients) . ' clients with incorrect paid to date');
if ($this->option('fix') == 'true') {
foreach ($clients as $client) {
DB::table('clients')
->where('id', $client->id)
->update(['paid_to_date' => $client->amount]);
}
}
}
// find all clients where the balance doesn't equal the sum of the outstanding invoices
$clients = DB::table('clients')
->join('invoices', 'invoices.client_id', '=', 'clients.id')
->join('accounts', 'accounts.id', '=', 'clients.account_id');
if ($this->option('client_id')) {
$clients->where('clients.id', '=', $this->option('client_id'));
} else {
$clients->where('invoices.is_deleted', '=', 0)
->where('invoices.is_quote', '=', 0)
->where('invoices.is_recurring', '=', 0)
->havingRaw('abs(clients.balance - sum(invoices.balance)) > .01 and clients.balance != 999999999.9999');
}
$clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at')
->orderBy('clients.id', 'DESC')
->get(['clients.id', 'clients.balance', 'clients.paid_to_date']);
$this->info(count($clients) . ' clients with incorrect balance/activities');
foreach ($clients as $client) {
$this->info("=== Client:{$client->id} Balance:{$client->balance} ===");
$foundProblem = false;
$lastBalance = 0;
$clientFix = false;
$activities = DB::table('activities')
->where('client_id', '=', $client->id)
->orderBy('activities.id')
->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.message', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']);
//$this->info(var_dump($activities));
foreach ($activities as $activity) {
$activityFix = false;
if ($activity->invoice_id) {
$invoice = DB::table('invoices')
->where('id', '=', $activity->invoice_id)
->first(['invoices.amount', 'invoices.is_recurring', 'invoices.is_quote', 'invoices.deleted_at', 'invoices.id', 'invoices.is_deleted']);
// Check if this invoice was once set as recurring invoice
if (!$invoice->is_recurring && DB::table('invoices')
->where('recurring_invoice_id', '=', $activity->invoice_id)
->first(['invoices.id'])) {
$invoice->is_recurring = 1;
// **Fix for enabling a recurring invoice to be set as non-recurring**
if ($this->option('fix') == 'true') {
DB::table('invoices')
->where('id', $invoice->id)
->update(['is_recurring' => 1]);
}
}
}
if ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_INVOICE
|| $activity->activity_type_id == ACTIVITY_TYPE_CREATE_QUOTE) {
// Get original invoice amount
$update = DB::table('activities')
->where('invoice_id', '=', $activity->invoice_id)
->where('activity_type_id', '=', ACTIVITY_TYPE_UPDATE_INVOICE)
->orderBy('id')
->first(['json_backup']);
if ($update) {
$backup = json_decode($update->json_backup);
$invoice->amount = floatval($backup->amount);
}
$noAdjustment = $activity->activity_type_id == ACTIVITY_TYPE_CREATE_INVOICE
&& $activity->adjustment == 0
&& $invoice->amount > 0;
// **Fix for allowing converting a recurring invoice to a normal one without updating the balance**
if ($noAdjustment && !$invoice->is_quote && !$invoice->is_recurring) {
$this->info("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} isQuote:{$invoice->is_quote} isRecurring:{$invoice->is_recurring}");
$foundProblem = true;
$clientFix += $invoice->amount;
$activityFix = $invoice->amount;
// **Fix for updating balance when creating a quote or recurring invoice**
} elseif ($activity->adjustment != 0 && ($invoice->is_quote || $invoice->is_recurring)) {
$this->info("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} isQuote:{$invoice->is_quote} isRecurring:{$invoice->is_recurring}");
$foundProblem = true;
$clientFix -= $activity->adjustment;
$activityFix = 0;
}
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_INVOICE) {
// **Fix for updating balance when deleting a recurring invoice**
if ($activity->adjustment != 0 && $invoice->is_recurring) {
$this->info("Incorrect adjustment for deleted invoice adjustment:{$activity->adjustment}");
$foundProblem = true;
if ($activity->balance != $lastBalance) {
$clientFix -= $activity->adjustment;
}
$activityFix = 0;
}
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_ARCHIVE_INVOICE) {
// **Fix for updating balance when archiving an invoice**
if ($activity->adjustment != 0 && !$invoice->is_recurring) {
$this->info("Incorrect adjustment for archiving invoice adjustment:{$activity->adjustment}");
$foundProblem = true;
$activityFix = 0;
$clientFix += $activity->adjustment;
}
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_INVOICE) {
// **Fix for updating balance when updating recurring invoice**
if ($activity->adjustment != 0 && $invoice->is_recurring) {
$this->info("Incorrect adjustment for updated recurring invoice adjustment:{$activity->adjustment}");
$foundProblem = true;
$clientFix -= $activity->adjustment;
$activityFix = 0;
}
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_QUOTE) {
// **Fix for updating balance when updating a quote**
if ($activity->balance != $lastBalance) {
$this->info("Incorrect adjustment for updated quote adjustment:{$activity->adjustment}");
$foundProblem = true;
$clientFix += $lastBalance - $activity->balance;
$activityFix = 0;
}
} else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) {
// **Fix for delting payment after deleting invoice**
if ($activity->adjustment != 0 && $invoice->is_deleted && $activity->created_at > $invoice->deleted_at) {
$this->info("Incorrect adjustment for deleted payment adjustment:{$activity->adjustment}");
$foundProblem = true;
$activityFix = 0;
$clientFix -= $activity->adjustment;
}
}
if ($activityFix !== false || $clientFix !== false) {
$data = [
'balance' => $activity->balance + $clientFix
];
if ($activityFix !== false) {
$data['adjustment'] = $activityFix;
}
if ($this->option('fix') == 'true') {
DB::table('activities')
->where('id', $activity->id)
->update($data);
}
}
$lastBalance = $activity->balance;
}
if ($clientFix !== false) {
$balance = $activity->balance + $clientFix;
$data = ['balance' => $balance];
$this->info("Corrected balance:{$balance}");
if ($this->option('fix') == 'true') {
DB::table('clients')
->where('id', $client->id)
->update($data);
}
}
}
$this->info('Done');
}
protected function getArguments()
{
return array(
//array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions()
{
return array(
array('fix', null, InputOption::VALUE_OPTIONAL, 'Fix data', null),
array('client_id', null, InputOption::VALUE_OPTIONAL, 'Client id', null),
);
}
}

View File

@ -0,0 +1,88 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class CreateRandomData extends Command {
protected $name = 'ninja:create-data';
protected $description = 'Create random data';
public function fire()
{
$this->info(date('Y-m-d') . ' Running CreateRandomData...');
$user = User::first();
if (!$user) {
$this->error("Error: please create user account by logging in");
return;
}
$productNames = ['Arkansas', 'New York', 'Arizona', 'California', 'Colorado', 'Alabama', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'Alaska', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
$clientNames = ['IBM', 'Nestle', 'Mitsubishi UFJ Financial', 'Vodafone', 'Eni', 'Procter & Gamble', 'Johnson & Johnson', 'American International Group', 'Banco Santander', 'BHP Billiton', 'Pfizer', 'Itaú Unibanco Holding', 'Ford Motor', 'BMW Group', 'Commonwealth Bank', 'EDF', 'Statoil', 'Google', 'Siemens', 'Novartis', 'Royal Bank of Canada', 'Sumitomo Mitsui Financial', 'Comcast', 'Sberbank', 'Goldman Sachs Group', 'Westpac Banking Group', 'Nippon Telegraph & Tel', 'Ping An Insurance Group', 'Banco Bradesco', 'Anheuser-Busch InBev', 'Bank of Communications', 'China Life Insurance', 'General Motors', 'Telefónica', 'MetLife', 'Honda Motor', 'Enel', 'BASF', 'Softbank', 'National Australia Bank', 'ANZ', 'ConocoPhillips', 'TD Bank Group', 'Intel', 'UBS', 'Hewlett-Packard', 'Coca-Cola', 'Cisco Systems', 'UnitedHealth Group', 'Boeing', 'Zurich Insurance Group', 'Hyundai Motor', 'Sanofi', 'Credit Agricole', 'United Technologies', 'Roche Holding', 'Munich Re', 'PepsiCo', 'Oracle', 'Bank of Nova Scotia'];
foreach ($productNames as $i => $value) {
$product = Product::createNew($user);
$product->id = $i+1;
$product->product_key = $value;
$product->save();
}
foreach ($clientNames as $i => $value) {
$client = Client::createNew($user);
$client->name = $value;
$client->save();
$contact = Contact::createNew($user);
$contact->email = "client@aol.com";
$contact->is_primary = 1;
$client->contacts()->save($contact);
$numInvoices = rand(1, 25);
if ($numInvoices == 4 || $numInvoices == 10 || $numInvoices == 25) {
// leave these
} else if ($numInvoices % 3 == 0) {
$numInvoices = 1;
} else if ($numInvoices > 10) {
$numInvoices = $numInvoices % 2;
}
$paidUp = rand(0, 1) == 1;
for ($j=1; $j<=$numInvoices; $j++) {
$price = rand(10, 1000);
if ($price < 900) {
$price = rand(10, 150);
}
$invoice = Invoice::createNew($user);
$invoice->invoice_number = $user->account->getNextInvoiceNumber();
$invoice->amount = $invoice->balance = $price;
$invoice->created_at = date('Y-m-d', strtotime(date("Y-m-d") . ' - ' . rand(1, 100) . ' days'));
$client->invoices()->save($invoice);
$productId = rand(0, 40);
if ($productId > 20) {
$productId = ($productId % 2) + rand(0, 2);
}
$invoiceItem = InvoiceItem::createNew($user);
$invoiceItem->product_id = $productId+1;
$invoiceItem->product_key = $productNames[$invoiceItem->product_id];
$invoiceItem->cost = $invoice->amount;
$invoiceItem->qty = 1;
$invoice->invoice_items()->save($invoiceItem);
if ($paidUp || rand(0,2) > 1) {
$payment = Payment::createNew($user);
$payment->invoice_id = $invoice->id;
$payment->amount = $invoice->amount;
$client->payments()->save($payment);
}
}
}
}
}

View File

@ -0,0 +1,335 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use PHPBenchTime\Timer;
class ImportTimesheetData extends Command {
protected $name = 'ninja:import-timesheet-data';
protected $description = 'Import timesheet data';
public function fire() {
$this->info(date('Y-m-d') . ' Running ImportTimesheetData...');
// Seems we are using the console timezone
DB::statement("SET SESSION time_zone = '+00:00'");
// Get the Unix epoch
$unix_epoch = new DateTime('1970-01-01T00:00:01', new DateTimeZone("UTC"));
// Create some initial sources we can test with
$user = User::first();
if (!$user) {
$this->error("Error: please create user account by logging in");
return;
}
// TODO: Populate with own test data until test data has been created
// Truncate the tables
/*$this->info("Truncate tables");
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
DB::table('projects')->truncate();
DB::table('project_codes')->truncate();
DB::table('timesheet_event_sources')->truncate();
DB::table('timesheet_events')->truncate();
DB::statement('SET FOREIGN_KEY_CHECKS=1;'); */
if (!Project::find(1)) {
$this->info("Import old project codes");
$oldcodes = json_decode(file_get_contents("/home/tlb/git/itktime/codes.json"), true);
foreach ($oldcodes as $name => $options) {
$project = Project::createNew($user);
$project->name = $options['description'];
$project->save();
$code = ProjectCode::createNew($user);
$code->name = $name;
$project->codes()->save($code);
}
}
if (!TimesheetEventSource::find(1)) {
$this->info("Import old event sources");
$oldevent_sources = json_decode(file_get_contents("/home/tlb/git/itktime/employes.json"), true);
foreach ($oldevent_sources as $source) {
$event_source = TimesheetEventSource::createNew($user);
$event_source->name = $source['name'];
$event_source->url = $source['url'];
$event_source->owner = $source['owner'];
$event_source->type = 'ical';
//$event_source->from_date = new DateTime("2009-01-01");
$event_source->save();
}
}
// Add all URL's to Curl
$this->info("Download ICAL feeds");
$T = new Timer;
$T->start();
$T->lap("Get Event Sources");
$event_sources = TimesheetEventSource::all(); // TODO: Filter based on ical feeds
$T->lap("Get ICAL responses");
$urls = [];
$event_sources->map(function($item) use(&$urls) {
$urls[] = $item->url;
});
$icalresponses = TimesheetUtils::curlGetUrls($urls);
$T->lap("Fetch all codes so we can do a quick lookup");
$codes = array();
ProjectCode::all()->map(function($item) use(&$codes) {
$codes[$item->name] = $item;
});
$this->info("Start parsing ICAL files");
foreach ($event_sources as $i => $event_source) {
if (!is_array($icalresponses[$i])) {
$this->info("Find events in " . $event_source->name);
file_put_contents("/tmp/" . $event_source->name . ".ical", $icalresponses[$i]); // FIXME: Remove
$T->lap("Split on events for ".$event_source->name);
// Check if the file is complete
if(!preg_match("/^\s*BEGIN:VCALENDAR/", $icalresponses[$i]) || !preg_match("/END:VCALENDAR\s*$/", $icalresponses[$i])) {
$this->error("Missing start or end of ical file");
continue;
}
// Extract all events from ical file
if (preg_match_all('/BEGIN:VEVENT\r?\n(.+?)\r?\nEND:VEVENT/s', $icalresponses[$i], $icalmatches)) {
$this->info("Found ".(count($icalmatches[1])-1)." events");
$T->lap("Fetch all uids and last updated at so we can do a quick lookup to find out if the event needs to be updated in the database".$event_source->name);
$uids = [];
$org_deleted = []; // Create list of events we know are deleted on the source, but still have in the db
$event_source->events()->withTrashed()->get(['uid', 'org_updated_at', 'updated_data_at', 'org_deleted_at'])->map(function($item) use(&$uids, &$org_deleted) {
if($item->org_updated_at > $item->updated_data_at) {
$uids[$item->uid] = $item->org_updated_at;
} else {
$uids[$item->uid] = $item->updated_data_at;
}
if($item->org_deleted_at > '0000-00-00 00:00:00') {
$org_deleted[$item->uid] = $item->updated_data_at;
}
});
$deleted = $uids;
// Loop over all the found events
$T->lap("Parse events for ".$event_source->name);
foreach ($icalmatches[1] as $eventstr) {
//print "---\n";
//print $eventstr."\n";
//print "---\n";
//$this->info("Match event");
# Fix lines broken by 76 char limit
$eventstr = preg_replace('/\r?\n\s/s', '', $eventstr);
//$this->info("Parse data");
$data = TimesheetUtils::parseICALEvent($eventstr);
if ($data) {
// Extract code for summary so we only import events we use
list($codename, $tags, $title) = TimesheetUtils::parseEventSummary($data['summary']);
if ($codename != null) {
$event = TimesheetEvent::createNew($user);
// Copy data to new object
$event->uid = $data['uid'];
$event->summary = $title;
$event->org_data = $eventstr;
$event->org_code = $codename;
if(isset($data['description'])) {
$event->description = $data['description'];
}
$event->owner = $event_source->owner;
$event->timesheet_event_source_id = $event_source->id;
if (isset($codes[$codename])) {
$event->project_id = $codes[$codename]->project_id;
$event->project_code_id = $codes[$codename]->id;
}
if (isset($data['location'])) {
$event->location = $data['location'];
}
# Add RECURRENCE-ID to the UID to make sure the event is unique
if (isset($data['recurrence-id'])) {
$event->uid .= "::".$data['recurrence-id'];
}
//TODO: Add support for recurring event, make limit on number of events created : https://github.com/tplaner/When
// Bail on RRULE as we don't support that
if(isset($event['rrule'])) {
die("Recurring event not supported: {$event['summary']} - {$event['dtstart']}");
}
// Convert to DateTime objects
foreach (['dtstart', 'dtend', 'created', 'last-modified'] as $key) {
// Parse and create DataTime object from ICAL format
list($dt, $timezone) = TimesheetUtils::parseICALDate($data[$key]);
// Handle bad dates in created and last-modified
if ($dt == null || $dt < $unix_epoch) {
if ($key == 'created' || $key == 'last-modified') {
$dt = $unix_epoch; // Default to UNIX epoch
$event->import_warning = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
} else {
$event->import_error = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
// TODO: Bail on this event or write to error table
die("Could not parse date for $key: '" . $data[$key] . "'\n");
}
}
// Assign DateTime object to
switch ($key) {
case 'dtstart':
$event->start_date = $dt;
if($timezone) {
$event->org_start_date_timezone = $timezone;
}
break;
case 'dtend':
$event->end_date = $dt;
if($timezone) {
$event->org_end_date_timezone = $timezone;
}
break;
case 'created':
$event->org_created_at = $dt;
break;
case 'last-modified':
$event->org_updated_at = $dt;
break;
}
}
// Check that we are witin the range
if ($event_source->from_date != null) {
$from_date = new DateTime($event_source->from_date, new DateTimeZone('UTC'));
if ($from_date > $event->end_date) {
// Skip this event
echo "Skiped: $codename: $title\n";
continue;
}
}
// Calculate number of hours
$di = $event->end_date->diff($event->start_date);
$event->hours = $di->h + $di->i / 60;
// Check for events we already have
if (isset($uids[$event->uid])) {
// Remove from deleted list
unset($deleted[$event->uid]);
// See if the event has been updated compared to the one in the database
$db_event_org_updated_at = new DateTime($uids[$event->uid], new DateTimeZone('UTC'));
// Check if same or older version of new event then skip
if($event->org_updated_at <= $db_event_org_updated_at) {
// SKIP
// Updated version of the event
} else {
// Get the old event from the database
/* @var $db_event TimesheetEvent */
$db_event = $event_source->events()->where('uid', $event->uid)->firstOrFail();
$changes = $db_event->toChangesArray($event);
// Make sure it's more than the org_updated_at that has been changed
if (count($changes) > 1) {
// Check if we have manually changed the event in the database or used it in a timesheet
if ($db_event->manualedit || $db_event->timesheet) {
$this->info("Updated Data");
$db_event->updated_data = $event->org_data;
$db_event->updated_data_at = $event->org_updated_at;
// Update the db_event with the changes
} else {
$this->info("Updated Event");
foreach ($changes as $key => $value) {
if($value == null) {
unset($db_event->$key);
} else {
$db_event->$key = $value;
}
}
}
} else {
$this->info("Nothing Changed");
// Nothing has been changed so update the org_updated_at
$db_event->org_updated_at = $changes['org_updated_at'];
}
$db_event->save();
}
} else {
try {
$this->info("New event: " . $event->summary);
$event->save();
} catch (Exception $ex) {
echo "'" . $event->summary . "'\n";
var_dump($data);
echo $ex->getMessage();
echo $ex->getTraceAsString();
exit(0);
}
}
// Add new uid to know uids
$uids[$event->uid] = $event->org_updated_at;
}
}
}
// Delete events in database that no longer exists in the source
foreach($deleted as $uid => $lastupdated_date) {
// Skip we already marked this a deleted
if(isset($org_deleted[$uid])) {
unset($deleted[$uid]);
continue;
}
// Delete or update event in db
$db_event = $event_source->events()->where('uid', $uid)->firstOrFail();
if($db_event->timesheet_id === null && !$db_event->manualedit) {
// Hard delete if this event has not been assigned to a timesheet or have been manually edited
$db_event->forceDelete();
} else {
// Mark as deleted in source
$db_event->org_deleted_at = new DateTime('now', new DateTimeZone('UTC'));
$db_event->save();
}
}
$this->info("Deleted ".count($deleted). " events");
} else {
// TODO: Parse error
}
} else {
// TODO: Curl Error
}
}
foreach($T->end()['laps'] as $lap) {
echo number_format($lap['total'], 3)." : {$lap['name']}\n";
}
$this->info('Done');
}
protected function getArguments() {
return array(
);
}
protected function getOptions() {
return array(
);
}
}

View File

@ -0,0 +1,24 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class ResetData extends Command {
protected $name = 'ninja:reset-data';
protected $description = 'Reset data';
public function fire()
{
$this->info(date('Y-m-d') . ' Running ResetData...');
if (!Utils::isNinjaDev()) {
return;
}
Artisan::call('migrate:reset');
Artisan::call('migrate');
Artisan::call('db:seed');
}
}

View File

@ -0,0 +1,113 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use ninja\mailers\ContactMailer as Mailer;
class SendRecurringInvoices extends Command
{
protected $name = 'ninja:send-invoices';
protected $description = 'Send recurring invoices';
protected $mailer;
public function __construct(Mailer $mailer)
{
parent::__construct();
$this->mailer = $mailer;
}
public function fire()
{
$this->info(date('Y-m-d').' Running SendRecurringInvoices...');
$today = new DateTime();
$invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user')
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))->get();
$this->info(count($invoices).' recurring invoice(s) found');
foreach ($invoices as $recurInvoice) {
if ($recurInvoice->client->deleted_at) {
continue;
}
if (!$recurInvoice->user->confirmed) {
continue;
}
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
if (!$recurInvoice->shouldSendToday()) {
continue;
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = 'R'.$recurInvoice->account->getNextInvoiceNumber();
$invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->discount = $recurInvoice->discount;
$invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = $recurInvoice->public_notes;
$invoice->terms = $recurInvoice->terms;
$invoice->invoice_footer = $recurInvoice->invoice_footer;
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
$invoice->custom_value1 = $recurInvoice->custom_value1;
$invoice->custom_value2 = $recurInvoice->custom_value2;
$invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
$invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
$invoice->is_amount_discount = $recurInvoice->is_amount_discount;
if ($invoice->client->payment_terms) {
$invoice->due_date = date_create()->modify($invoice->client->payment_terms.' day')->format('Y-m-d');
}
$invoice->save();
foreach ($recurInvoice->invoice_items as $recurItem) {
$item = InvoiceItem::createNew($recurItem);
$item->product_id = $recurItem->product_id;
$item->qty = $recurItem->qty;
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
$item->tax_name = $recurItem->tax_name;
$item->tax_rate = $recurItem->tax_rate;
$invoice->invoice_items()->save($item);
}
foreach ($recurInvoice->invitations as $recurInvitation) {
$invitation = Invitation::createNew($recurInvitation);
$invitation->contact_id = $recurInvitation->contact_id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invoice->invitations()->save($invitation);
}
$this->mailer->sendInvoice($invoice);
$recurInvoice->last_sent_date = Carbon::now()->toDateTimeString();
$recurInvoice->save();
}
$this->info('Done');
}
protected function getArguments()
{
return array(
//array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions()
{
return array(
//array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
);
}
}

View File

@ -0,0 +1,51 @@
<?php
use ninja\mailers\UserMailer;
use ninja\mailers\ContactMailer;
class InvoiceEventHandler
{
protected $userMailer;
protected $contactMailer;
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer)
{
$this->userMailer = $userMailer;
$this->contactMailer = $contactMailer;
}
public function subscribe($events)
{
$events->listen('invoice.sent', 'InvoiceEventHandler@onSent');
$events->listen('invoice.viewed', 'InvoiceEventHandler@onViewed');
$events->listen('invoice.paid', 'InvoiceEventHandler@onPaid');
}
public function onSent($invoice)
{
$this->sendNotifications($invoice, 'sent');
}
public function onViewed($invoice)
{
$this->sendNotifications($invoice, 'viewed');
}
public function onPaid($payment)
{
$this->contactMailer->sendPaymentConfirmation($payment);
$this->sendNotifications($payment->invoice, 'paid', $payment);
}
private function sendNotifications($invoice, $type, $payment = null)
{
foreach ($invoice->account->users as $user)
{
if ($user->{'notify_' . $type})
{
$this->userMailer->sendNotification($user, $invoice, $type, $payment);
}
}
}
}

View File

@ -0,0 +1,30 @@
<?php
class UserEventHandler
{
public function subscribe($events)
{
$events->listen('user.signup', 'UserEventHandler@onSignup');
$events->listen('user.login', 'UserEventHandler@onLogin');
$events->listen('user.refresh', 'UserEventHandler@onRefresh');
}
public function onSignup()
{
}
public function onLogin()
{
$account = Auth::user()->account;
$account->last_login = Carbon::now()->toDateTimeString();
$account->save();
Event::fire('user.refresh');
}
public function onRefresh()
{
Auth::user()->account->loadLocalizationSettings();
}
}

View File

View File

@ -0,0 +1,715 @@
<?php
use ninja\repositories\AccountRepository;
use ninja\mailers\UserMailer;
use ninja\mailers\ContactMailer;
class AccountController extends \BaseController
{
protected $accountRepo;
protected $userMailer;
protected $contactMailer;
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer)
{
parent::__construct();
$this->accountRepo = $accountRepo;
$this->userMailer = $userMailer;
$this->contactMailer = $contactMailer;
}
public function demo()
{
$demoAccountId = Utils::getDemoAccountId();
if (!$demoAccountId) {
return Redirect::to('/');
}
$account = Account::find($demoAccountId);
$user = $account->users()->first();
Auth::login($user, true);
return Redirect::to('invoices/create');
}
public function getStarted()
{
if (Auth::check()) {
return Redirect::to('invoices/create');
} elseif (!Utils::isNinja() && Account::count() > 0) {
return Redirect::to('/login');
}
$user = false;
$guestKey = Input::get('guest_key');
if ($guestKey) {
$user = User::where('password', '=', $guestKey)->first();
if ($user && $user->registered) {
return Redirect::to('/');
}
}
if (!$user) {
$account = $this->accountRepo->create();
$user = $account->users()->first();
Session::forget(RECENTLY_VIEWED);
}
Auth::login($user, true);
Event::fire('user.login');
return Redirect::to('invoices/create')->with('sign_up', Input::get('sign_up'));
}
public function enableProPlan()
{
$invitation = $this->accountRepo->enableProPlan();
/*
if ($invoice)
{
$this->contactMailer->sendInvoice($invoice);
}
*/
return $invitation->invitation_key;
}
public function setTrashVisible($entityType, $visible)
{
Session::put("show_trash:{$entityType}", $visible == 'true');
if ($entityType == 'user') {
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_USER_MANAGEMENT);
} elseif ($entityType == 'token') {
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_TOKEN_MANAGEMENT);
} else {
return Redirect::to("{$entityType}s");
}
}
public function getSearchData()
{
$data = $this->accountRepo->getSearchData();
return Response::json($data);
}
public function showSection($section = ACCOUNT_DETAILS, $subSection = false)
{
if ($section == ACCOUNT_DETAILS) {
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'timezones' => Timezone::remember(DEFAULT_QUERY_CACHE)->orderBy('location')->get(),
'dateFormats' => DateFormat::remember(DEFAULT_QUERY_CACHE)->get(),
'datetimeFormats' => DatetimeFormat::remember(DEFAULT_QUERY_CACHE)->get(),
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'languages' => Language::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'showUser' => Auth::user()->id === Auth::user()->account->users()->first()->id,
];
return View::make('accounts.details', $data);
} elseif ($section == ACCOUNT_PAYMENTS) {
$account = Auth::user()->account;
$account->load('account_gateways');
$count = count($account->account_gateways);
if ($count == 0) {
return Redirect::to('gateways/create');
} else {
return View::make('accounts.payments', ['showAdd' => $count < 2]);
}
} elseif ($section == ACCOUNT_NOTIFICATIONS) {
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
];
return View::make('accounts.notifications', $data);
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return View::make('accounts.import_export');
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
$account = Auth::user()->account;
$data = [
'account' => $account,
'feature' => $subSection,
];
if ($subSection == ACCOUNT_INVOICE_DESIGN) {
$invoice = new stdClass();
$client = new stdClass();
$invoiceItem = new stdClass();
$client->name = 'Sample Client';
$client->address1 = '';
$client->city = '';
$client->state = '';
$client->postal_code = '';
$client->work_phone = '';
$client->work_email = '';
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber();
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->account = json_decode(Auth::user()->account->toJson());
$invoice->amount = $invoice->balance = 100;
$invoiceItem->cost = 100;
$invoiceItem->qty = 1;
$invoiceItem->notes = 'Notes';
$invoiceItem->product_key = 'Item';
$invoice->client = $client;
$invoice->invoice_items = [$invoiceItem];
$data['invoice'] = $invoice;
$data['invoiceDesigns'] = InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId())
->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get();
} else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
$data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE);
$data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE);
$data['paymentEmail'] = $account->getEmailTemplate(ENTITY_PAYMENT);
$data['emailFooter'] = $account->getEmailFooter();
}
return View::make("accounts.{$subSection}", $data);
} elseif ($section == ACCOUNT_PRODUCTS) {
$data = [
'account' => Auth::user()->account,
];
return View::make('accounts.products', $data);
}
}
public function doSection($section = ACCOUNT_DETAILS, $subSection = false)
{
if ($section == ACCOUNT_DETAILS) {
return AccountController::saveDetails();
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return AccountController::importFile();
} elseif ($section == ACCOUNT_MAP) {
return AccountController::mapFile();
} elseif ($section == ACCOUNT_NOTIFICATIONS) {
return AccountController::saveNotifications();
} elseif ($section == ACCOUNT_EXPORT) {
return AccountController::export();
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
if ($subSection == ACCOUNT_INVOICE_SETTINGS) {
return AccountController::saveInvoiceSettings();
} elseif ($subSection == ACCOUNT_INVOICE_DESIGN) {
return AccountController::saveInvoiceDesign();
} elseif ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
return AccountController::saveEmailTemplates();
}
} elseif ($section == ACCOUNT_PRODUCTS) {
return AccountController::saveProducts();
}
}
private function saveEmailTemplates()
{
if (Auth::user()->account->isPro()) {
$account = Auth::user()->account;
$account->email_template_invoice = Input::get('email_template_invoice', $account->getEmailTemplate(ENTITY_INVOICE));
$account->email_template_quote = Input::get('email_template_quote', $account->getEmailTemplate(ENTITY_QUOTE));
$account->email_template_payment = Input::get('email_template_payment', $account->getEmailTemplate(ENTITY_PAYMENT));
$account->save();
Session::flash('message', trans('texts.updated_settings'));
}
return Redirect::to('company/advanced_settings/email_templates');
}
private function saveProducts()
{
$account = Auth::user()->account;
$account->fill_products = Input::get('fill_products') ? true : false;
$account->update_products = Input::get('update_products') ? true : false;
$account->save();
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('company/products');
}
private function saveInvoiceSettings()
{
if (Auth::user()->account->isPro()) {
$account = Auth::user()->account;
$account->custom_label1 = trim(Input::get('custom_label1'));
$account->custom_value1 = trim(Input::get('custom_value1'));
$account->custom_label2 = trim(Input::get('custom_label2'));
$account->custom_value2 = trim(Input::get('custom_value2'));
$account->custom_client_label1 = trim(Input::get('custom_client_label1'));
$account->custom_client_label2 = trim(Input::get('custom_client_label2'));
$account->custom_invoice_label1 = trim(Input::get('custom_invoice_label1'));
$account->custom_invoice_label2 = trim(Input::get('custom_invoice_label2'));
$account->custom_invoice_taxes1 = Input::get('custom_invoice_taxes1') ? true : false;
$account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false;
$account->invoice_number_prefix = Input::get('invoice_number_prefix');
$account->invoice_number_counter = Input::get('invoice_number_counter');
$account->quote_number_prefix = Input::get('quote_number_prefix');
$account->share_counter = Input::get('share_counter') ? true : false;
if (!$account->share_counter) {
$account->quote_number_counter = Input::get('quote_number_counter');
}
if (!$account->share_counter && $account->invoice_number_prefix == $account->quote_number_prefix) {
Session::flash('error', trans('texts.invalid_counter'));
return Redirect::to('company/advanced_settings/invoice_settings')->withInput();
} else {
$account->save();
Session::flash('message', trans('texts.updated_settings'));
}
}
return Redirect::to('company/advanced_settings/invoice_settings');
}
private function saveInvoiceDesign()
{
if (Auth::user()->account->isPro()) {
$account = Auth::user()->account;
$account->hide_quantity = Input::get('hide_quantity') ? true : false;
$account->hide_paid_to_date = Input::get('hide_paid_to_date') ? true : false;
$account->primary_color = Input::get('primary_color');
$account->secondary_color = Input::get('secondary_color');
$account->invoice_design_id = Input::get('invoice_design_id');
$account->save();
Session::flash('message', trans('texts.updated_settings'));
}
return Redirect::to('company/advanced_settings/invoice_design');
}
private function export()
{
$output = fopen('php://output', 'w') or Utils::fatalError();
header('Content-Type:application/csv');
header('Content-Disposition:attachment;filename=export.csv');
$clients = Client::scope()->get();
AccountController::exportData($output, $clients->toArray());
$contacts = Contact::scope()->get();
AccountController::exportData($output, $contacts->toArray());
$invoices = Invoice::scope()->get();
AccountController::exportData($output, $invoices->toArray());
$invoiceItems = InvoiceItem::scope()->get();
AccountController::exportData($output, $invoiceItems->toArray());
$payments = Payment::scope()->get();
AccountController::exportData($output, $payments->toArray());
$credits = Credit::scope()->get();
AccountController::exportData($output, $credits->toArray());
fclose($output);
exit;
}
private function exportData($output, $data)
{
if (count($data) > 0) {
fputcsv($output, array_keys($data[0]));
}
foreach ($data as $record) {
fputcsv($output, $record);
}
fwrite($output, "\n");
}
private function importFile()
{
$data = Session::get('data');
Session::forget('data');
$map = Input::get('map');
$count = 0;
$hasHeaders = Input::get('header_checkbox');
$countries = Country::remember(DEFAULT_QUERY_CACHE)->get();
$countryMap = [];
foreach ($countries as $country) {
$countryMap[strtolower($country->name)] = $country->id;
}
foreach ($data as $row) {
if ($hasHeaders) {
$hasHeaders = false;
continue;
}
$client = Client::createNew();
$contact = Contact::createNew();
$contact->is_primary = true;
$contact->send_invoice = true;
$count++;
foreach ($row as $index => $value) {
$field = $map[$index];
$value = trim($value);
if ($field == Client::$fieldName && !$client->name) {
$client->name = $value;
} elseif ($field == Client::$fieldPhone && !$client->work_phone) {
$client->work_phone = $value;
} elseif ($field == Client::$fieldAddress1 && !$client->address1) {
$client->address1 = $value;
} elseif ($field == Client::$fieldAddress2 && !$client->address2) {
$client->address2 = $value;
} elseif ($field == Client::$fieldCity && !$client->city) {
$client->city = $value;
} elseif ($field == Client::$fieldState && !$client->state) {
$client->state = $value;
} elseif ($field == Client::$fieldPostalCode && !$client->postal_code) {
$client->postal_code = $value;
} elseif ($field == Client::$fieldCountry && !$client->country_id) {
$value = strtolower($value);
$client->country_id = isset($countryMap[$value]) ? $countryMap[$value] : null;
} elseif ($field == Client::$fieldNotes && !$client->private_notes) {
$client->private_notes = $value;
} elseif ($field == Contact::$fieldFirstName && !$contact->first_name) {
$contact->first_name = $value;
} elseif ($field == Contact::$fieldLastName && !$contact->last_name) {
$contact->last_name = $value;
} elseif ($field == Contact::$fieldPhone && !$contact->phone) {
$contact->phone = $value;
} elseif ($field == Contact::$fieldEmail && !$contact->email) {
$contact->email = strtolower($value);
}
}
$client->save();
$client->contacts()->save($contact);
Activity::createClient($client, false);
}
$message = Utils::pluralize('created_client', $count);
Session::flash('message', $message);
return Redirect::to('clients');
}
private function mapFile()
{
$file = Input::file('file');
if ($file == null) {
Session::flash('error', trans('texts.select_file'));
return Redirect::to('company/import_export');
}
$name = $file->getRealPath();
require_once app_path().'/includes/parsecsv.lib.php';
$csv = new parseCSV();
$csv->heading = false;
$csv->auto($name);
if (count($csv->data) + Client::scope()->count() > Auth::user()->getMaxNumClients()) {
$message = trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()]);
Session::flash('error', $message);
return Redirect::to('company/import_export');
}
Session::put('data', $csv->data);
$headers = false;
$hasHeaders = false;
$mapped = array();
$columns = array('',
Client::$fieldName,
Client::$fieldPhone,
Client::$fieldAddress1,
Client::$fieldAddress2,
Client::$fieldCity,
Client::$fieldState,
Client::$fieldPostalCode,
Client::$fieldCountry,
Client::$fieldNotes,
Contact::$fieldFirstName,
Contact::$fieldLastName,
Contact::$fieldPhone,
Contact::$fieldEmail,
);
if (count($csv->data) > 0) {
$headers = $csv->data[0];
foreach ($headers as $title) {
if (strpos(strtolower($title), 'name') > 0) {
$hasHeaders = true;
break;
}
}
for ($i = 0; $i<count($headers); $i++) {
$title = strtolower($headers[$i]);
$mapped[$i] = '';
if ($hasHeaders) {
$map = array(
'first' => Contact::$fieldFirstName,
'last' => Contact::$fieldLastName,
'email' => Contact::$fieldEmail,
'mobile' => Contact::$fieldPhone,
'phone' => Client::$fieldPhone,
'name|organization' => Client::$fieldName,
'street|address|address1' => Client::$fieldAddress1,
'street2|address2' => Client::$fieldAddress2,
'city' => Client::$fieldCity,
'state|province' => Client::$fieldState,
'zip|postal|code' => Client::$fieldPostalCode,
'country' => Client::$fieldCountry,
'note' => Client::$fieldNotes,
);
foreach ($map as $search => $column) {
foreach (explode("|", $search) as $string) {
if (strpos($title, 'sec') === 0) {
continue;
}
if (strpos($title, $string) !== false) {
$mapped[$i] = $column;
break(2);
}
}
}
}
}
}
$data = array(
'data' => $csv->data,
'headers' => $headers,
'hasHeaders' => $hasHeaders,
'columns' => $columns,
'mapped' => $mapped,
);
return View::make('accounts.import_map', $data);
}
private function saveNotifications()
{
$account = Auth::user()->account;
$account->invoice_terms = Input::get('invoice_terms');
$account->invoice_footer = Input::get('invoice_footer');
$account->email_footer = Input::get('email_footer');
$account->save();
$user = Auth::user();
$user->notify_sent = Input::get('notify_sent');
$user->notify_viewed = Input::get('notify_viewed');
$user->notify_paid = Input::get('notify_paid');
$user->save();
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('company/notifications');
}
private function saveDetails()
{
$rules = array(
'name' => 'required',
);
$user = Auth::user()->account->users()->first();
if (Auth::user()->id === $user->id) {
$rules['email'] = 'email|required|unique:users,email,'.$user->id.',id';
}
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('company/details')
->withErrors($validator)
->withInput();
} else {
$account = Auth::user()->account;
$account->name = trim(Input::get('name'));
$account->id_number = trim(Input::get('id_number'));
$account->vat_number = trim(Input::get('vat_number'));
$account->work_email = trim(Input::get('work_email'));
$account->work_phone = trim(Input::get('work_phone'));
$account->address1 = trim(Input::get('address1'));
$account->address2 = trim(Input::get('address2'));
$account->city = trim(Input::get('city'));
$account->state = trim(Input::get('state'));
$account->postal_code = trim(Input::get('postal_code'));
$account->country_id = Input::get('country_id') ? Input::get('country_id') : null;
$account->size_id = Input::get('size_id') ? Input::get('size_id') : null;
$account->industry_id = Input::get('industry_id') ? Input::get('industry_id') : null;
$account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null;
$account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null;
$account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null;
$account->currency_id = Input::get('currency_id') ? Input::get('currency_id') : 1; // US Dollar
$account->language_id = Input::get('language_id') ? Input::get('language_id') : 1; // English
$account->save();
if (Auth::user()->id === $user->id) {
$user->first_name = trim(Input::get('first_name'));
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(strtolower(Input::get('email')));
$user->phone = trim(Input::get('phone'));
$user->save();
}
/* Logo image file */
if ($file = Input::file('logo')) {
$path = Input::file('logo')->getRealPath();
File::delete('logo/'.$account->account_key.'.jpg');
$image = Image::make($path);
$mimeType = $file->getMimeType();
if ($image->width == 200 && $mimeType == 'image/jpeg') {
$file->move('logo/', $account->account_key . '.jpg');
} else {
$image->resize(200, 120, true, false);
Image::canvas($image->width, $image->height, '#FFFFFF')->insert($image)->save($account->getLogoPath());
}
//$image = Image::make($path)->resize(200, 120, true, false);
//Image::canvas($image->width, $image->height, '#FFFFFF')->insert($image)->save($account->getLogoPath());
}
Event::fire('user.refresh');
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('company/details');
}
}
public function removeLogo()
{
File::delete('logo/'.Auth::user()->account->account_key.'.jpg');
Session::flash('message', trans('texts.removed_logo'));
return Redirect::to('company/details');
}
public function checkEmail()
{
$email = User::withTrashed()->where('email', '=', Input::get('email'))->where('id', '<>', Auth::user()->id)->first();
if ($email) {
return "taken";
} else {
return "available";
}
}
public function submitSignup()
{
$rules = array(
'new_first_name' => 'required',
'new_last_name' => 'required',
'new_password' => 'required|min:6',
'new_email' => 'email|required|unique:users,email,'.Auth::user()->id.',id',
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return '';
}
$user = Auth::user();
$user->first_name = trim(Input::get('new_first_name'));
$user->last_name = trim(Input::get('new_last_name'));
$user->email = trim(strtolower(Input::get('new_email')));
$user->username = $user->email;
$user->password = trim(Input::get('new_password'));
$user->password_confirmation = trim(Input::get('new_password'));
$user->registered = true;
$user->amend();
if (Utils::isNinja()) {
$this->userMailer->sendConfirmation($user);
} else {
$this->accountRepo->registerUser($user);
}
$activities = Activity::scope()->get();
foreach ($activities as $activity) {
$activity->message = str_replace('Guest', $user->getFullName(), $activity->message);
$activity->save();
}
if (Input::get('go_pro') == 'true') {
Session::set(REQUESTED_PRO_PLAN, true);
}
Session::set(SESSION_COUNTER, -1);
return "{$user->first_name} {$user->last_name}";
}
public function doRegister()
{
$affiliate = Affiliate::where('affiliate_key', '=', SELF_HOST_AFFILIATE_KEY)->first();
$license = new License();
$license->first_name = Input::get('first_name');
$license->last_name = Input::get('last_name');
$license->email = Input::get('email');
$license->transaction_reference = Request::getClientIp();
$license->license_key = Utils::generateLicense();
$license->affiliate_id = $affiliate->id;
$license->product_id = PRODUCT_SELF_HOST;
$license->is_claimed = 1;
$license->save();
return '';
}
public function cancelAccount()
{
if ($reason = trim(Input::get('reason'))) {
$email = Auth::user()->email;
$name = Auth::user()->getDisplayName();
$data = [
'text' => $reason,
];
$this->userMailer->sendTo(CONTACT_EMAIL, $email, $name, 'Invoice Ninja Feedback [Canceled Account]', 'contact', $data);
}
$account = Auth::user()->account;
$account->forceDelete();
Confide::logout();
return Redirect::to('/')->with('clearGuestKey', true);
}
}

View File

@ -0,0 +1,292 @@
<?php
/*
|--------------------------------------------------------------------------
| Confide Controller Template
|--------------------------------------------------------------------------
|
| This is the default Confide controller template for controlling user
| authentication. Feel free to change to your needs.
|
*/
use ninja\repositories\AccountRepository;
class AccountGatewayController extends BaseController
{
public function getDatatable()
{
$query = \DB::table('account_gateways')
->join('gateways', 'gateways.id', '=', 'account_gateways.gateway_id')
->where('account_gateways.deleted_at', '=', null)
->where('account_gateways.account_id', '=', \Auth::user()->account_id)
->select('account_gateways.public_id', 'gateways.name', 'account_gateways.deleted_at');
return Datatable::query($query)
->addColumn('name', function ($model) { return link_to('gateways/'.$model->public_id.'/edit', $model->name); })
->addColumn('dropdown', function ($model) {
$actions = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at) {
$actions .= '<li><a href="'.URL::to('gateways/'.$model->public_id).'/edit">'.uctrans('texts.edit_gateway').'</a></li>
<li class="divider"></li>
<li><a href="javascript:deleteAccountGateway('.$model->public_id.')">'.uctrans('texts.delete_gateway').'</a></li>';
}
$actions .= '</ul>
</div>';
return $actions;
})
->orderColumns(['name'])
->make();
}
public function edit($publicId)
{
$accountGateway = AccountGateway::scope($publicId)->firstOrFail();
$config = $accountGateway->config;
$selectedCards = $accountGateway->accepted_credit_cards;
$configFields = json_decode($config);
foreach ($configFields as $configField => $value) {
$configFields->$configField = str_repeat('*', strlen($value));
}
$data = self::getViewModel($accountGateway);
$data['url'] = 'gateways/'.$publicId;
$data['method'] = 'PUT';
$data['title'] = trans('texts.edit_gateway') . ' - ' . $accountGateway->gateway->name;
$data['config'] = $configFields;
return View::make('accounts.account_gateway', $data);
}
public function update($publicId)
{
return $this->save($publicId);
}
public function store()
{
return $this->save();
}
/**
* Displays the form for account creation
*
*/
public function create()
{
$data = self::getViewModel();
$data['url'] = 'gateways';
$data['method'] = 'POST';
$data['title'] = trans('texts.add_gateway');
return View::make('accounts.account_gateway', $data);
}
private function getViewModel($accountGateway = false)
{
$selectedCards = $accountGateway ? $accountGateway->accepted_credit_cards : 0;
$account = Auth::user()->account;
$recommendedGateways = Gateway::remember(DEFAULT_QUERY_CACHE)
->where('recommended', '=', '1')
->orderBy('sort_order')
->get();
$recommendedGatewayArray = array();
foreach ($recommendedGateways as $recommendedGateway) {
$arrayItem = array(
'value' => $recommendedGateway->id,
'other' => 'false',
'data-imageUrl' => asset($recommendedGateway->getLogoUrl()),
'data-siteUrl' => $recommendedGateway->site_url,
);
$recommendedGatewayArray[$recommendedGateway->name] = $arrayItem;
}
$creditCardsArray = unserialize(CREDIT_CARDS);
$creditCards = [];
foreach ($creditCardsArray as $card => $name) {
if ($selectedCards > 0 && ($selectedCards & $card) == $card) {
$creditCards[$name['text']] = ['value' => $card, 'data-imageUrl' => asset($name['card']), 'checked' => 'checked'];
} else {
$creditCards[$name['text']] = ['value' => $card, 'data-imageUrl' => asset($name['card'])];
}
}
$otherItem = array(
'value' => 1000000,
'other' => 'true',
'data-imageUrl' => '',
'data-siteUrl' => '',
);
$recommendedGatewayArray['Other Options'] = $otherItem;
$account->load('account_gateways');
$currentGateways = $account->account_gateways;
$gateways = Gateway::where('payment_library_id', '=', 1)->orderBy('name');
$onlyPayPal = false;
if (!$accountGateway) {
if (count($currentGateways) > 0) {
$currentGateway = $currentGateways[0];
if ($currentGateway->isPayPal()) {
$gateways->where('id', '!=', GATEWAY_PAYPAL_EXPRESS);
} else {
$gateways->where('id', '=', GATEWAY_PAYPAL_EXPRESS);
$onlyPayPal = true;
}
}
}
$gateways = $gateways->get();
foreach ($gateways as $gateway) {
$paymentLibrary = $gateway->paymentlibrary;
$gateway->fields = $gateway->getFields();
if ($accountGateway && $accountGateway->gateway_id == $gateway->id) {
$accountGateway->fields = $gateway->fields;
}
}
$tokenBillingOptions = [];
for ($i=1; $i<=4; $i++) {
$tokenBillingOptions[$i] = trans("texts.token_billing_{$i}");
}
return [
'account' => $account,
'accountGateway' => $accountGateway,
'config' => false,
'gateways' => $gateways,
'recommendedGateways' => $recommendedGatewayArray,
'creditCardTypes' => $creditCards,
'tokenBillingOptions' => $tokenBillingOptions,
'showBreadcrumbs' => false,
'onlyPayPal' => $onlyPayPal,
'countGateways' => count($currentGateways)
];
}
public function delete()
{
$accountGatewayPublicId = Input::get('accountGatewayPublicId');
$gateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
$gateway->delete();
Session::flash('message', trans('texts.deleted_gateway'));
return Redirect::to('company/payments');
}
/**
* Stores new account
*
*/
public function save($accountGatewayPublicId = false)
{
$rules = array();
$recommendedId = Input::get('recommendedGateway_id');
$gatewayId = ($recommendedId == 1000000 ? Input::get('gateway_id') : $recommendedId);
if (!$gatewayId) {
Session::flash('error', trans('validation.required', ['attribute' => 'gateway']));
return Redirect::to('gateways/create')
->withInput();
}
$gateway = Gateway::findOrFail($gatewayId);
$paymentLibrary = $gateway->paymentlibrary;
$fields = $gateway->getFields();
foreach ($fields as $field => $details) {
if (!in_array($field, ['testMode', 'developerMode', 'headerImageUrl', 'solutionType', 'landingPage', 'brandName', 'logoImageUrl', 'borderColor'])) {
if (strtolower($gateway->name) == 'beanstream') {
if (in_array($field, ['merchant_id', 'passCode'])) {
$rules[$gateway->id.'_'.$field] = 'required';
}
} else {
$rules[$gateway->id.'_'.$field] = 'required';
}
}
}
$creditcards = Input::get('creditCardTypes');
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('gateways/create')
->withErrors($validator)
->withInput();
} else {
$account = Account::with('account_gateways')->findOrFail(Auth::user()->account_id);
if ($accountGatewayPublicId) {
$accountGateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
} else {
$accountGateway = AccountGateway::createNew();
$accountGateway->gateway_id = $gatewayId;
}
$isMasked = false;
$config = new stdClass();
foreach ($fields as $field => $details) {
$value = trim(Input::get($gateway->id.'_'.$field));
if ($value && $value === str_repeat('*', strlen($value))) {
$isMasked = true;
}
$config->$field = $value;
}
$cardCount = 0;
if ($creditcards) {
foreach ($creditcards as $card => $value) {
$cardCount += intval($value);
}
}
// if the values haven't changed don't update the config
if ($isMasked && $accountGatewayPublicId) {
$accountGateway->accepted_credit_cards = $cardCount;
$accountGateway->save();
// if there's an existing config for this gateway update it
} elseif (!$isMasked && $accountGatewayPublicId && $accountGateway->gateway_id == $gatewayId) {
$accountGateway->accepted_credit_cards = $cardCount;
$accountGateway->config = json_encode($config);
$accountGateway->save();
// otherwise, create a new gateway config
} else {
$accountGateway->config = json_encode($config);
$accountGateway->accepted_credit_cards = $cardCount;
$account->account_gateways()->save($accountGateway);
}
if (Input::get('token_billing_type_id')) {
$account->token_billing_type_id = Input::get('token_billing_type_id');
$account->save();
}
if ($accountGatewayPublicId) {
$message = trans('texts.updated_gateway');
} else {
$message = trans('texts.created_gateway');
}
Session::flash('message', $message);
return Redirect::to('company/payments');
}
}
}

View File

@ -0,0 +1,20 @@
<?php
class ActivityController extends \BaseController
{
public function getDatatable($clientPublicId)
{
$query = DB::table('activities')
->join('clients', 'clients.id', '=', 'activities.client_id')
->where('clients.public_id', '=', $clientPublicId)
->where('activities.account_id', '=', Auth::user()->account_id)
->select('activities.id', 'activities.message', 'activities.created_at', 'clients.currency_id', 'activities.balance', 'activities.adjustment');
return Datatable::query($query)
->addColumn('id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
->addColumn('message', function ($model) { return Utils::decodeActivity($model->message); })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? Utils::formatMoney($model->adjustment, $model->currency_id) : ''; })
->make();
}
}

View File

@ -0,0 +1,171 @@
<?php
use ninja\mailers\Mailer;
use ninja\repositories\AccountRepository;
class AppController extends BaseController
{
protected $accountRepo;
protected $mailer;
public function __construct(AccountRepository $accountRepo, Mailer $mailer)
{
parent::__construct();
$this->accountRepo = $accountRepo;
$this->mailer = $mailer;
}
public function showSetup()
{
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
return Redirect::to('/');
}
return View::make('setup');
}
public function doSetup()
{
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
return Redirect::to('/');
}
$valid = false;
$test = Input::get('test');
$app = Input::get('app');
$app['key'] = str_random(RANDOM_KEY_LENGTH);
$app['debug'] = false;
$database = Input::get('database');
$dbType = $database['default'];
$database['connections'] = [$dbType => $database['type']];
unset($database['type']);
$mail = Input::get('mail');
$email = $mail['username'];
$mail['from']['address'] = $email;
if ($test == 'mail') {
return self::testMail($mail);
}
$valid = self::testDatabase($database);
if ($test == 'db') {
return $valid === true ? 'Success' : $valid;
} elseif (!$valid) {
return Redirect::to('/setup')->withInput();
}
$content = "<?php return 'production';";
$fp = fopen(base_path()."/bootstrap/environment.php", 'w');
fwrite($fp, $content);
fclose($fp);
$configDir = app_path().'/config/production';
if (!file_exists($configDir)) {
mkdir($configDir);
}
foreach (['app' => $app, 'database' => $database, 'mail' => $mail] as $key => $config) {
$content = '<?php return '.var_export($config, true).';';
$fp = fopen(app_path()."/config/production/{$key}.php", 'w');
fwrite($fp, $content);
fclose($fp);
}
Artisan::call('migrate');
Artisan::call('db:seed');
$account = $this->accountRepo->create();
$user = $account->users()->first();
$user->first_name = trim(Input::get('first_name'));
$user->last_name = trim(Input::get('last_name'));
$user->email = trim(strtolower(Input::get('email')));
$user->username = $user->email;
$user->password = trim(Input::get('password'));
$user->password_confirmation = trim(Input::get('password'));
$user->registered = true;
$user->amend();
//Auth::login($user, true);
$this->accountRepo->registerUser($user);
return Redirect::to('/invoices/create');
}
private function testDatabase($database)
{
$dbType = $database['default'];
Config::set('database.default', $dbType);
foreach ($database['connections'][$dbType] as $key => $val) {
Config::set("database.connections.{$dbType}.{$key}", $val);
}
try {
$valid = DB::connection()->getDatabaseName() ? true : false;
} catch (Exception $e) {
return $e->getMessage();
}
return $valid;
}
private function testMail($mail)
{
$email = $mail['username'];
$fromName = $mail['from']['name'];
foreach ($mail as $key => $val) {
Config::set("mail.{$key}", $val);
}
Config::set('mail.from.address', $email);
Config::set('mail.from.name', $fromName);
$data = [
'text' => 'Test email',
];
try {
$this->mailer->sendTo($email, $email, $fromName, 'Test email', 'contact', $data);
return 'Sent';
} catch (Exception $e) {
return $e->getMessage();
}
}
public function install()
{
if (!Utils::isNinja() && !Utils::isDatabaseSetup()) {
try {
Artisan::call('migrate');
Artisan::call('db:seed');
} catch (Exception $e) {
Response::make($e->getMessage(), 500);
}
}
return Redirect::to('/');
}
public function update()
{
if (!Utils::isNinja()) {
try {
Artisan::call('migrate');
Cache::flush();
} catch (Exception $e) {
Response::make($e->getMessage(), 500);
}
}
return Redirect::to('/');
}
}

View File

@ -0,0 +1,21 @@
<?php namespace App\Http\Controllers;
class BaseController extends Controller
{
/**
* Setup the layout used by the controller.
*
* @return void
*/
protected function setupLayout()
{
if (! is_null($this->layout)) {
$this->layout = View::make($this->layout);
}
}
public function __construct()
{
$this->beforeFilter('csrf', array('on' => array('post', 'delete', 'put')));
}
}

View File

@ -0,0 +1,51 @@
<?php
use ninja\repositories\ClientRepository;
class ClientApiController extends Controller
{
protected $clientRepo;
public function __construct(ClientRepository $clientRepo)
{
$this->clientRepo = $clientRepo;
}
public function ping()
{
$headers = Utils::getApiHeaders();
return Response::make('', 200, $headers);
}
public function index()
{
$clients = Client::scope()->with('contacts')->orderBy('created_at', 'desc')->get();
$clients = Utils::remapPublicIds($clients->toArray());
$response = json_encode($clients, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($clients));
return Response::make($response, 200, $headers);
}
public function store()
{
$data = Input::all();
$error = $this->clientRepo->getErrors($data);
if ($error) {
$headers = Utils::getApiHeaders();
return Response::make($error, 500, $headers);
} else {
$client = $this->clientRepo->save(false, $data, false);
$client->load('contacts');
$client = Utils::remapPublicIds($client->toArray());
$response = json_encode($client, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
}
}

View File

@ -0,0 +1,278 @@
<?php
use ninja\repositories\ClientRepository;
class ClientController extends \BaseController
{
protected $clientRepo;
public function __construct(ClientRepository $clientRepo)
{
parent::__construct();
$this->clientRepo = $clientRepo;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return View::make('list', array(
'entityType' => ENTITY_CLIENT,
'title' => trans('texts.clients'),
'columns' => Utils::trans(['checkbox', 'client', 'contact', 'email', 'date_created', 'last_login', 'balance', 'action']),
));
}
public function getDatatable()
{
$clients = $this->clientRepo->find(Input::get('sSearch'));
return Datatable::query($clients)
->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; })
->addColumn('name', function ($model) { return link_to('clients/'.$model->public_id, $model->name); })
->addColumn('first_name', function ($model) { return link_to('clients/'.$model->public_id, $model->first_name.' '.$model->last_name); })
->addColumn('email', function ($model) { return link_to('clients/'.$model->public_id, $model->email); })
->addColumn('created_at', function ($model) { return Utils::timestampToDateString(strtotime($model->created_at)); })
->addColumn('last_login', function ($model) { return Utils::timestampToDateString(strtotime($model->last_login)); })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('dropdown', function ($model) {
if ($model->is_deleted) {
return '<div style="height:38px"/>';
}
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="'.URL::to('clients/'.$model->public_id.'/edit').'">'.trans('texts.edit_client').'</a></li>
<li class="divider"></li>
<li><a href="'.URL::to('invoices/create/'.$model->public_id).'">'.trans('texts.new_invoice').'</a></li>
<li><a href="'.URL::to('payments/create/'.$model->public_id).'">'.trans('texts.new_payment').'</a></li>
<li><a href="'.URL::to('credits/create/'.$model->public_id).'">'.trans('texts.new_credit').'</a></li>
<li class="divider"></li>
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_client').'</a></li>';
} else {
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_client').'</a></li>';
}
return $str.'<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_client').'</a></li></ul>
</div>';
})
->make();
}
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store()
{
return $this->save();
}
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($publicId)
{
$client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail();
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
$actionLinks = [
[trans('texts.create_invoice'), URL::to('invoices/create/'.$client->public_id)],
[trans('texts.enter_payment'), URL::to('payments/create/'.$client->public_id)],
[trans('texts.enter_credit'), URL::to('credits/create/'.$client->public_id)],
];
if (Utils::isPro()) {
array_unshift($actionLinks, [trans('texts.create_quote'), URL::to('quotes/create/'.$client->public_id)]);
}
$data = array(
'actionLinks' => $actionLinks,
'showBreadcrumbs' => false,
'client' => $client,
'credit' => $client->getTotalCredit(),
'title' => trans('texts.view_client'),
'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0,
'gatewayLink' => $client->getGatewayLink(),
);
return View::make('clients.show', $data);
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
if (Client::scope()->count() > Auth::user()->getMaxNumClients()) {
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]);
}
$data = [
'client' => null,
'method' => 'POST',
'url' => 'clients',
'title' => trans('texts.new_client'),
];
$data = array_merge($data, self::getViewModel());
return View::make('clients.edit', $data);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit($publicId)
{
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
$data = [
'client' => $client,
'method' => 'PUT',
'url' => 'clients/'.$publicId,
'title' => trans('texts.edit_client'),
];
$data = array_merge($data, self::getViewModel());
return View::make('clients.edit', $data);
}
private static function getViewModel()
{
return [
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'customLabel1' => Auth::user()->account->custom_client_label1,
'customLabel2' => Auth::user()->account->custom_client_label2,
];
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($publicId)
{
return $this->save($publicId);
}
private function save($publicId = null)
{
$rules = array(
'email' => 'required',
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
$url = $publicId ? 'clients/'.$publicId.'/edit' : 'clients/create';
return Redirect::to($url)
->withErrors($validator)
->withInput(Input::except('password'));
} else {
if ($publicId) {
$client = Client::scope($publicId)->firstOrFail();
} else {
$client = Client::createNew();
}
$client->name = trim(Input::get('name'));
$client->id_number = trim(Input::get('id_number'));
$client->vat_number = trim(Input::get('vat_number'));
$client->work_phone = trim(Input::get('work_phone'));
$client->custom_value1 = trim(Input::get('custom_value1'));
$client->custom_value2 = trim(Input::get('custom_value2'));
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
$client->postal_code = trim(Input::get('postal_code'));
$client->country_id = Input::get('country_id') ?: null;
$client->private_notes = trim(Input::get('private_notes'));
$client->size_id = Input::get('size_id') ?: null;
$client->industry_id = Input::get('industry_id') ?: null;
$client->currency_id = Input::get('currency_id') ?: null;
$client->payment_terms = Input::get('payment_terms') ?: 0;
$client->website = trim(Input::get('website'));
$client->save();
$data = json_decode(Input::get('data'));
$contactIds = [];
$isPrimary = true;
foreach ($data->contacts as $contact) {
if (isset($contact->public_id) && $contact->public_id) {
$record = Contact::scope($contact->public_id)->firstOrFail();
} else {
$record = Contact::createNew();
}
$record->email = trim(strtolower($contact->email));
$record->first_name = trim($contact->first_name);
$record->last_name = trim($contact->last_name);
$record->phone = trim($contact->phone);
$record->is_primary = $isPrimary;
$isPrimary = false;
$client->contacts()->save($record);
$contactIds[] = $record->public_id;
}
foreach ($client->contacts as $contact) {
if (!in_array($contact->public_id, $contactIds)) {
$contact->delete();
}
}
if ($publicId) {
Session::flash('message', trans('texts.updated_client'));
} else {
Activity::createClient($client);
Session::flash('message', trans('texts.created_client'));
}
return Redirect::to('clients/'.$client->public_id);
}
}
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
$count = $this->clientRepo->bulk($ids, $action);
$message = Utils::pluralize($action.'d_client', $count);
Session::flash('message', $message);
if ($action == 'restore' && $count == 1) {
return Redirect::to('clients/'.$ids[0]);
} else {
return Redirect::to('clients');
}
}
}

View File

@ -0,0 +1,11 @@
<?php namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesCommands;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
abstract class Controller extends BaseController {
use DispatchesCommands, ValidatesRequests;
}

View File

@ -0,0 +1,149 @@
<?php
use ninja\repositories\CreditRepository;
class CreditController extends \BaseController
{
protected $creditRepo;
public function __construct(CreditRepository $creditRepo)
{
parent::__construct();
$this->creditRepo = $creditRepo;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return View::make('list', array(
'entityType' => ENTITY_CREDIT,
'title' => trans('texts.credits'),
'columns' => Utils::trans(['checkbox', 'client', 'credit_amount', 'credit_balance', 'credit_date', 'private_notes', 'action']),
));
}
public function getDatatable($clientPublicId = null)
{
$credits = $this->creditRepo->find($clientPublicId, Input::get('sSearch'));
$table = Datatable::query($credits);
if (!$clientPublicId) {
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; })
->addColumn('client_name', function ($model) { return link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)); });
}
return $table->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id).'<span '.Utils::getEntityRowClass($model).'/>'; })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('credit_date', function ($model) { return Utils::fromSqlDate($model->credit_date); })
->addColumn('private_notes', function ($model) { return $model->private_notes; })
->addColumn('dropdown', function ($model) {
if ($model->is_deleted) {
return '<div style="height:38px"/>';
}
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_credit').'</a></li>';
} else {
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_credit').'</a></li>';
}
return $str.'<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_credit').'</a></li></ul>
</div>';
})
->make();
}
public function create($clientPublicId = 0)
{
$data = array(
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
//'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
'credit' => null,
'method' => 'POST',
'url' => 'credits',
'title' => trans('texts.new_credit'),
//'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
//'invoices' => Invoice::scope()->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
return View::make('credits.edit', $data);
}
public function edit($publicId)
{
$credit = Credit::scope($publicId)->firstOrFail();
$credit->credit_date = Utils::fromSqlDate($credit->credit_date);
$data = array(
'client' => null,
'credit' => $credit,
'method' => 'PUT',
'url' => 'credits/'.$publicId,
'title' => 'Edit Credit',
//'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
return View::make('credit.edit', $data);
}
public function store()
{
return $this->save();
}
public function update($publicId)
{
return $this->save($publicId);
}
private function save($publicId = null)
{
$rules = array(
'client' => 'required',
'amount' => 'required|positive',
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
$url = $publicId ? 'credits/'.$publicId.'/edit' : 'credits/create';
return Redirect::to($url)
->withErrors($validator)
->withInput();
} else {
$this->creditRepo->save($publicId, Input::all());
$message = trans('texts.created_credit');
Session::flash('message', $message);
return Redirect::to('clients/'.Input::get('client'));
}
}
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
$count = $this->creditRepo->bulk($ids, $action);
if ($count > 0) {
$message = Utils::pluralize($action.'d_credit', $count);
Session::flash('message', $message);
}
return Redirect::to('credits');
}
}

View File

@ -0,0 +1,79 @@
<?php
class DashboardController extends \BaseController
{
public function index()
{
// total_income, billed_clients, invoice_sent and active_clients
$select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients,
SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent,
COUNT(DISTINCT clients.id) active_clients');
$metrics = DB::table('accounts')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('invoices.is_recurring', '=', false)
->where('invoices.is_quote', '=', false)
->groupBy('accounts.id')
->first();
$select = DB::raw('SUM(clients.paid_to_date) as value, clients.currency_id as currency_id');
$paidToDate = DB::table('accounts')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get();
$select = DB::raw('AVG(invoices.amount) as invoice_avg, clients.currency_id as currency_id');
$averageInvoice = DB::table('accounts')
->select($select)
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id')
->where('accounts.id', '=', Auth::user()->account_id)
->where('clients.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->groupBy('accounts.id')
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
->get();
$activities = Activity::where('activities.account_id', '=', Auth::user()->account_id)
->orderBy('created_at', 'desc')->take(6)->get();
$pastDue = Invoice::scope()
->where('due_date', '<', date('Y-m-d'))
->where('balance', '>', 0)
->where('is_recurring', '=', false)
->where('is_quote', '=', false)
->where('is_deleted', '=', false)
->orderBy('due_date', 'asc')->take(6)->get();
$upcoming = Invoice::scope()
->where('due_date', '>=', date('Y-m-d'))
->where('balance', '>', 0)
->where('is_recurring', '=', false)
->where('is_quote', '=', false)
->where('is_deleted', '=', false)
->orderBy('due_date', 'asc')->take(6)->get();
$data = [
'paidToDate' => $paidToDate,
'averageInvoice' => $averageInvoice,
'billedClients' => $metrics ? $metrics->billed_clients : 0,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0,
'activities' => $activities,
'pastDue' => $pastDue,
'upcoming' => $upcoming,
];
return View::make('dashboard', $data);
}
}

View File

@ -1,5 +1,6 @@
<?php
<?php namespace App\Http\Controllers;
use App\libraries\Utils;
use ninja\mailers\Mailer;
class HomeController extends BaseController

View File

@ -0,0 +1,26 @@
<?php
class IntegrationController extends Controller
{
public function subscribe()
{
$eventId = Utils::lookupEventId(trim(Input::get('event')));
if (!$eventId) {
return Response::json('', 500);
}
$subscription = Subscription::where('account_id', '=', Auth::user()->account_id)->where('event_id', '=', $eventId)->first();
if (!$subscription) {
$subscription = new Subscription();
$subscription->account_id = Auth::user()->account_id;
$subscription->event_id = $eventId;
}
$subscription->target_url = trim(Input::get('target_url'));
$subscription->save();
return Response::json('{"id":'.$subscription->id.'}', 201);
}
}

View File

@ -0,0 +1,192 @@
<?php
use ninja\repositories\InvoiceRepository;
use ninja\mailers\ContactMailer as Mailer;
class InvoiceApiController extends Controller
{
protected $invoiceRepo;
public function __construct(InvoiceRepository $invoiceRepo, Mailer $mailer)
{
$this->invoiceRepo = $invoiceRepo;
$this->mailer = $mailer;
}
public function index()
{
$invoices = Invoice::scope()->where('invoices.is_quote', '=', false)->orderBy('created_at', 'desc')->get();
$invoices = Utils::remapPublicIds($invoices->toArray());
$response = json_encode($invoices, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($invoices));
return Response::make($response, 200, $headers);
}
public function store()
{
$data = Input::all();
$error = null;
// check if the invoice number is set and unique
if (!isset($data['invoice_number'])) {
$data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber();
} else {
$invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first();
if ($invoice) {
$error = trans('validation.unique', ['attribute' => 'texts.invoice_number']);
}
}
// check the client id is set and exists
if (!isset($data['client_id'])) {
$error = trans('validation.required', ['attribute' => 'client_id']);
} else {
$client = Client::scope($data['client_id'])->first();
if (!$client) {
$error = trans('validation.not_in', ['attribute' => 'client_id']);
}
}
if ($error) {
$response = json_encode($error, JSON_PRETTY_PRINT);
} else {
$data = self::prepareData($data);
$invoice = $this->invoiceRepo->save(false, $data, false);
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
$invitation->contact_id = $client->contacts[0]->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
// prepare the return data
$invoice->load('invoice_items');
$invoice = $invoice->toArray();
$invoice['link'] = $invitation->getLink();
unset($invoice['account']);
unset($invoice['client']);
$invoice = Utils::remapPublicIds($invoice);
$invoice['client_id'] = $client->public_id;
$response = json_encode($invoice, JSON_PRETTY_PRINT);
}
$headers = Utils::getApiHeaders();
return Response::make($response, $error ? 400 : 200, $headers);
}
private function prepareData($data)
{
$account = Auth::user()->account;
$account->loadLocalizationSettings();
// set defaults for optional fields
$fields = [
'discount' => 0,
'is_amount_discount' => false,
'terms' => '',
'invoice_footer' => '',
'public_notes' => '',
'po_number' => '',
'invoice_design_id' => $account->invoice_design_id,
'invoice_items' => [],
'custom_value1' => 0,
'custom_value2' => 0,
'custom_taxes1' => false,
'custom_taxes2' => false,
];
if (!isset($data['invoice_date'])) {
$fields['invoice_date_sql'] = date_create()->format('Y-m-d');
}
if (!isset($data['due_date'])) {
$fields['due_date_sql'] = false;
}
foreach ($fields as $key => $val) {
if (!isset($data[$key])) {
$data[$key] = $val;
}
}
// hardcode some fields
$fields = [
'is_recurring' => false
];
foreach ($fields as $key => $val) {
$data[$key] = $val;
}
// initialize the line items
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
$data['invoice_items'] = [self::prepareItem($data)];
} else {
foreach ($data['invoice_items'] as $index => $item) {
$data['invoice_items'][$index] = self::prepareItem($item);
}
}
return $data;
}
private function prepareItem($item)
{
$fields = [
'cost' => 0,
'product_key' => '',
'notes' => '',
'qty' => 1
];
foreach ($fields as $key => $val) {
if (!isset($item[$key])) {
$item[$key] = $val;
}
}
// if only the product key is set we'll load the cost and notes
if ($item['product_key'] && (!$item['cost'] || !$item['notes'])) {
$product = Product::findProductByKey($item['product_key']);
if ($product) {
if (!$item['cost']) {
$item['cost'] = $product->cost;
}
if (!$item['notes']) {
$item['notes'] = $product->notes;
}
}
}
return $item;
}
public function emailInvoice()
{
$data = Input::all();
$error = null;
if (!isset($data['id'])) {
$error = trans('validation.required', ['attribute' => 'id']);
} else {
$invoice = Invoice::scope($data['id'])->first();
if (!$invoice) {
$error = trans('validation.not_in', ['attribute' => 'id']);
} else {
$this->mailer->sendInvoice($invoice);
}
}
if ($error) {
$response = json_encode($error, JSON_PRETTY_PRINT);
} else {
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
}
$headers = Utils::getApiHeaders();
return Response::make($response, $error ? 400 : 200, $headers);
}
}

View File

@ -0,0 +1,542 @@
<?php
use ninja\mailers\ContactMailer as Mailer;
use ninja\repositories\InvoiceRepository;
use ninja\repositories\ClientRepository;
use ninja\repositories\TaxRateRepository;
class InvoiceController extends \BaseController
{
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
protected $taxRateRepo;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
{
parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
$this->taxRateRepo = $taxRateRepo;
}
public function index()
{
$data = [
'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE,
'columns' => Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action']),
];
$recurringInvoices = Invoice::scope()->where('is_recurring', '=', true);
if (Session::get('show_trash:invoice')) {
$recurringInvoices->withTrashed();
}
if ($recurringInvoices->count() > 0) {
$data['secEntityType'] = ENTITY_RECURRING_INVOICE;
$data['secColumns'] = Utils::trans(['checkbox', 'frequency', 'client', 'start_date', 'end_date', 'invoice_total', 'action']);
}
return View::make('list', $data);
}
public function clientIndex()
{
$invitationKey = Session::get('invitation_key');
if (!$invitationKey) {
return Redirect::to('/setup');
}
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => Session::get('white_label'),
'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE,
'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']),
];
return View::make('public_list', $data);
}
public function getDatatable($clientPublicId = null)
{
$accountId = Auth::user()->account_id;
$search = Input::get('sSearch');
return $this->invoiceRepo->getDatatable($accountId, $clientPublicId, ENTITY_INVOICE, $search);
}
public function getClientDatatable()
{
//$accountId = Auth::user()->account_id;
$search = Input::get('sSearch');
$invitationKey = Session::get('invitation_key');
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation || $invitation->is_deleted) {
return [];
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return [];
}
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, $search);
}
public function getRecurringDatatable($clientPublicId = null)
{
$query = $this->invoiceRepo->getRecurringInvoices(Auth::user()->account_id, $clientPublicId, Input::get('sSearch'));
$table = Datatable::query($query);
if (!$clientPublicId) {
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; });
}
$table->addColumn('frequency', function ($model) { return link_to('invoices/'.$model->public_id, $model->frequency); });
if (!$clientPublicId) {
$table->addColumn('client_name', function ($model) { return link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)); });
}
return $table->addColumn('start_date', function ($model) { return Utils::fromSqlDate($model->start_date); })
->addColumn('end_date', function ($model) { return Utils::fromSqlDate($model->end_date); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('dropdown', function ($model) {
if ($model->is_deleted) {
return '<div style="height:38px"/>';
}
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="'.URL::to('invoices/'.$model->public_id.'/edit').'">'.trans('texts.edit_invoice').'</a></li>
<li class="divider"></li>
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_invoice').'</a></li>
<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_invoice').'</a></li>';
} else {
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_invoice').'</a></li>';
}
return $str.'</ul>
</div>';
})
->make();
}
public function view($invitationKey)
{
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return View::make('invoices.deleted');
}
$invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country');
$client = $invoice->client;
if (!$client || $client->is_deleted) {
return View::make('invoices.deleted');
}
if (!Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
Activity::viewInvoice($invitation);
Event::fire('invoice.viewed', $invoice);
}
Session::set($invitationKey, true);
Session::set('invitation_key', $invitationKey);
Session::set('white_label', $client->account->isWhiteLabel());
$client->account->loadLocalizationSettings();
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = $client->account->isPro();
$contact = $invitation->contact;
$contact->setVisible([
'first_name',
'last_name',
'email',
'phone', ]);
$data = array(
'isConverted' => $invoice->quote_invoice_id ? true : false,
'showBreadcrumbs' => false,
'hideLogo' => $client->account->isWhiteLabel(),
'invoice' => $invoice->hidePrivateFields(),
'invitation' => $invitation,
'invoiceLabels' => $client->account->getInvoiceLabels(),
'contact' => $contact,
'hasToken' => $client->getGatewayToken(),
'countGateways' => AccountGateway::scope(false, $client->account->id)->count(),
);
return View::make('invoices.view', $data);
}
public function edit($publicId, $clone = false)
{
$invoice = Invoice::scope($publicId)->withTrashed()->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items')->firstOrFail();
$entityType = $invoice->getEntityType();
$contactIds = DB::table('invitations')
->join('contacts', 'contacts.id', '=', 'invitations.contact_id')
->where('invitations.invoice_id', '=', $invoice->id)
->where('invitations.account_id', '=', Auth::user()->account_id)
->where('invitations.deleted_at', '=', null)
->select('contacts.public_id')->lists('public_id');
if ($clone) {
$invoice->id = null;
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber($invoice->is_quote);
$invoice->balance = $invoice->amount;
$invoice->invoice_status_id = 0;
$invoice->invoice_date = date_create()->format('Y-m-d');
$method = 'POST';
$url = "{$entityType}s";
} else {
Utils::trackViewed($invoice->invoice_number.' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
$method = 'PUT';
$url = "{$entityType}s/{$publicId}";
}
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->start_date = Utils::fromSqlDate($invoice->start_date);
$invoice->end_date = Utils::fromSqlDate($invoice->end_date);
$invoice->is_pro = Auth::user()->isPro();
$data = array(
'entityType' => $entityType,
'showBreadcrumbs' => $clone,
'account' => $invoice->account,
'invoice' => $invoice,
'data' => false,
'method' => $method,
'invitationContactIds' => $contactIds,
'url' => $url,
'title' => trans("texts.edit_{$entityType}"),
'client' => $invoice->client, );
$data = array_merge($data, self::getViewModel());
// Set the invitation link on the client's contacts
if (!$clone) {
$clients = $data['clients'];
foreach ($clients as $client) {
if ($client->id == $invoice->client->id) {
foreach ($invoice->invitations as $invitation) {
foreach ($client->contacts as $contact) {
if ($invitation->contact_id == $contact->id) {
$contact->invitation_link = $invitation->getLink();
}
}
}
break;
}
}
}
return View::make('invoices.edit', $data);
}
public function create($clientPublicId = 0)
{
$client = null;
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber();
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
}
$data = array(
'entityType' => ENTITY_INVOICE,
'account' => $account,
'invoice' => null,
'data' => Input::old('data'),
'invoiceNumber' => $invoiceNumber,
'method' => 'POST',
'url' => 'invoices',
'title' => trans('texts.new_invoice'),
'client' => $client, );
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
}
private static function getViewModel()
{
$recurringHelp = '';
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) {
$parts = explode("=>", $line);
if (count($parts) > 1) {
$line = $parts[0].' => '.Utils::processVariables($parts[0]);
$recurringHelp .= '<li>'.strip_tags($line).'</li>';
} else {
$recurringHelp .= $line;
}
}
return [
'account' => Auth::user()->account,
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId())
->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
'frequencies' => array(
1 => 'Weekly',
2 => 'Two weeks',
3 => 'Four weeks',
4 => 'Monthly',
5 => 'Three months',
6 => 'Six months',
7 => 'Annually',
),
'recurringHelp' => $recurringHelp
];
}
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store()
{
return InvoiceController::save();
}
private function save($publicId = null)
{
$action = Input::get('action');
$entityType = Input::get('entityType');
if (in_array($action, ['archive', 'delete', 'mark', 'restore'])) {
return InvoiceController::bulk($entityType);
}
$input = json_decode(Input::get('data'));
$invoice = $input->invoice;
if ($errors = $this->invoiceRepo->getErrors($invoice)) {
Session::flash('error', trans('texts.invoice_error'));
return Redirect::to("{$entityType}s/create")
->withInput()->withErrors($errors);
} else {
$this->taxRateRepo->save($input->tax_rates);
$clientData = (array) $invoice->client;
$client = $this->clientRepo->save($invoice->client->public_id, $clientData);
$invoiceData = (array) $invoice;
$invoiceData['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save($publicId, $invoiceData, $entityType);
$account = Auth::user()->account;
if ($account->invoice_taxes != $input->invoice_taxes
|| $account->invoice_item_taxes != $input->invoice_item_taxes
|| $account->invoice_design_id != $input->invoice->invoice_design_id) {
$account->invoice_taxes = $input->invoice_taxes;
$account->invoice_item_taxes = $input->invoice_item_taxes;
$account->invoice_design_id = $input->invoice->invoice_design_id;
$account->save();
}
$client->load('contacts');
$sendInvoiceIds = [];
foreach ($client->contacts as $contact) {
if ($contact->send_invoice || count($client->contacts) == 1) {
$sendInvoiceIds[] = $contact->id;
}
}
foreach ($client->contacts as $contact) {
$invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first();
if (in_array($contact->id, $sendInvoiceIds) && !$invitation) {
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
$invitation->contact_id = $contact->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
} elseif (!in_array($contact->id, $sendInvoiceIds) && $invitation) {
$invitation->delete();
}
}
$message = trans($publicId ? "texts.updated_{$entityType}" : "texts.created_{$entityType}");
if ($input->invoice->client->public_id == '-1') {
$message = $message.' '.trans('texts.and_created_client');
$url = URL::to('clients/'.$client->public_id);
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT, $url);
}
if ($action == 'clone') {
return $this->cloneInvoice($publicId);
} elseif ($action == 'convert') {
return $this->convertQuote($publicId);
} elseif ($action == 'email') {
if (Auth::user()->confirmed && !Auth::user()->isDemo()) {
$message = trans("texts.emailed_{$entityType}");
$this->mailer->sendInvoice($invoice);
Session::flash('message', $message);
} else {
$errorMessage = trans(Auth::user()->registered ? 'texts.confirmation_required' : 'texts.registration_required');
Session::flash('error', $errorMessage);
Session::flash('message', $message);
}
} else {
Session::flash('message', $message);
}
$url = "{$entityType}s/".$invoice->public_id.'/edit';
return Redirect::to($url);
}
}
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($publicId)
{
Session::reflash();
return Redirect::to('invoices/'.$publicId.'/edit');
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($publicId)
{
return InvoiceController::save($publicId);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function bulk($entityType = ENTITY_INVOICE)
{
$action = Input::get('action');
$statusId = Input::get('statusId', INVOICE_STATUS_SENT);
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
$count = $this->invoiceRepo->bulk($ids, $action, $statusId);
if ($count > 0) {
$key = $action == 'mark' ? "updated_{$entityType}" : "{$action}d_{$entityType}";
$message = Utils::pluralize($key, $count);
Session::flash('message', $message);
}
if ($action == 'restore' && $count == 1) {
return Redirect::to("{$entityType}s/".$ids[0]);
} else {
return Redirect::to("{$entityType}s");
}
}
public function convertQuote($publicId)
{
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail();
$clone = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
Session::flash('message', trans('texts.converted_to_invoice'));
return Redirect::to('invoices/'.$clone->public_id);
}
public function cloneInvoice($publicId)
{
/*
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail();
$clone = $this->invoiceRepo->cloneInvoice($invoice);
$entityType = $invoice->getEntityType();
Session::flash('message', trans('texts.cloned_invoice'));
return Redirect::to("{$entityType}s/" . $clone->public_id);
*/
return self::edit($publicId, true);
}
public function invoiceHistory($publicId)
{
$invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail();
$invoice->load('user', 'invoice_items', 'account.country', 'client.contacts', 'client.country');
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = Auth::user()->isPro();
$invoice->is_quote = intval($invoice->is_quote);
$activityTypeId = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
$activities = Activity::scope(false, $invoice->account_id)
->where('activity_type_id', '=', $activityTypeId)
->where('invoice_id', '=', $invoice->id)
->orderBy('id', 'desc')
->get(['id', 'created_at', 'user_id', 'json_backup', 'message']);
$versionsJson = [];
$versionsSelect = [];
$lastId = false;
foreach ($activities as $activity) {
$backup = json_decode($activity->json_backup);
$backup->invoice_date = Utils::fromSqlDate($backup->invoice_date);
$backup->due_date = Utils::fromSqlDate($backup->due_date);
$backup->is_pro = Auth::user()->isPro();
$backup->is_quote = isset($backup->is_quote) && intval($backup->is_quote);
$backup->account = $invoice->account->toArray();
$versionsJson[$activity->id] = $backup;
$key = Utils::timestampToDateTimeString(strtotime($activity->created_at)) . ' - ' . Utils::decodeActivity($activity->message);
$versionsSelect[$lastId ? $lastId : 0] = $key;
$lastId = $activity->id;
}
$versionsSelect[$lastId] = Utils::timestampToDateTimeString(strtotime($invoice->created_at)) . ' - ' . $invoice->user->getDisplayName();
$data = [
'invoice' => $invoice,
'versionsJson' => json_encode($versionsJson),
'versionsSelect' => $versionsSelect,
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId())->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
];
return View::make('invoices.history', $data);
}
}

View File

@ -0,0 +1,36 @@
<?php
use ninja\repositories\PaymentRepository;
class PaymentApiController extends Controller
{
protected $paymentRepo;
public function __construct(PaymentRepository $paymentRepo)
{
$this->paymentRepo = $paymentRepo;
}
public function index()
{
$payments = Payment::scope()->orderBy('created_at', 'desc')->get();
$payments = Utils::remapPublicIds($payments->toArray());
$response = json_encode($payments, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($payments));
return Response::make($response, 200, $headers);
}
/*
public function store()
{
$data = Input::all();
$invoice = $this->invoiceRepo->save(false, $data, false);
$response = json_encode($invoice, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
*/
}

View File

@ -0,0 +1,748 @@
<?php
use ninja\repositories\PaymentRepository;
use ninja\repositories\InvoiceRepository;
use ninja\repositories\AccountRepository;
use ninja\mailers\ContactMailer;
class PaymentController extends \BaseController
{
protected $creditRepo;
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer)
{
parent::__construct();
$this->paymentRepo = $paymentRepo;
$this->invoiceRepo = $invoiceRepo;
$this->accountRepo = $accountRepo;
$this->contactMailer = $contactMailer;
}
public function index()
{
return View::make('list', array(
'entityType' => ENTITY_PAYMENT,
'title' => trans('texts.payments'),
'columns' => Utils::trans(['checkbox', 'invoice', 'client', 'transaction_reference', 'method', 'payment_amount', 'payment_date', 'action']),
));
}
public function clientIndex()
{
$invitationKey = Session::get('invitation_key');
if (!$invitationKey) {
return Redirect::to('/setup');
}
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => Session::get('white_label'),
'entityType' => ENTITY_PAYMENT,
'title' => trans('texts.payments'),
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
];
return View::make('public_list', $data);
}
public function getDatatable($clientPublicId = null)
{
$payments = $this->paymentRepo->find($clientPublicId, Input::get('sSearch'));
$table = Datatable::query($payments);
if (!$clientPublicId) {
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; });
}
$table->addColumn('invoice_number', function ($model) { return $model->invoice_public_id ? link_to('invoices/'.$model->invoice_public_id.'/edit', $model->invoice_number, ['class' => Utils::getEntityRowClass($model)]) : ''; });
if (!$clientPublicId) {
$table->addColumn('client_name', function ($model) { return link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)); });
}
$table->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); });
return $table->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
->addColumn('dropdown', function ($model) {
if ($model->is_deleted || $model->invoice_is_deleted) {
return '<div style="height:38px"/>';
}
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="payments/'.$model->public_id.'/edit">'.trans('texts.edit_payment').'</a></li>
<li class="divider"></li>
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_payment').'</a></li>';
} else {
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_payment').'</a></li>';
}
return $str.'<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_payment').'</a></li></ul>
</div>';
})
->make();
}
public function getClientDatatable()
{
$search = Input::get('sSearch');
$invitationKey = Session::get('invitation_key');
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->with('contact.client')->first();
if (!$invitation) {
return [];
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return [];
}
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
return Datatable::query($payments)
->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })
->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
->make();
}
public function create($clientPublicId = 0, $invoicePublicId = 0)
{
$data = array(
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
'invoice' => null,
'invoices' => Invoice::scope()->where('is_recurring', '=', false)->where('is_quote', '=', false)
->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
'payment' => null,
'method' => 'POST',
'url' => "payments",
'title' => trans('texts.new_payment'),
//'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'paymentTypes' => PaymentType::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
return View::make('payments.edit', $data);
}
public function edit($publicId)
{
$payment = Payment::scope($publicId)->firstOrFail();
$payment->payment_date = Utils::fromSqlDate($payment->payment_date);
$data = array(
'client' => null,
'invoice' => null,
'invoices' => Invoice::scope()->where('is_recurring', '=', false)->where('is_quote', '=', false)
->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
'payment' => $payment,
'method' => 'PUT',
'url' => 'payments/'.$publicId,
'title' => trans('texts.edit_payment'),
//'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'paymentTypes' => PaymentType::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
return View::make('payments.edit', $data);
}
private function createGateway($accountGateway)
{
$gateway = Omnipay::create($accountGateway->gateway->provider);
$config = json_decode($accountGateway->config);
/*
$gateway->setSolutionType("Sole");
$gateway->setLandingPage("Billing");
*/
foreach ($config as $key => $val) {
if (!$val) {
continue;
}
$function = "set".ucfirst($key);
$gateway->$function($val);
}
if (Utils::isNinjaDev()) {
$gateway->setTestMode(true);
}
return $gateway;
}
private function getLicensePaymentDetails($input, $affiliate)
{
$data = self::convertInputForOmnipay($input);
$card = new CreditCard($data);
return [
'amount' => $affiliate->price,
'card' => $card,
'currency' => 'USD',
'returnUrl' => URL::to('license_complete'),
'cancelUrl' => URL::to('/')
];
}
private function convertInputForOmnipay($input)
{
return [
'firstName' => $input['first_name'],
'lastName' => $input['last_name'],
'number' => $input['card_number'],
'expiryMonth' => $input['expiration_month'],
'expiryYear' => $input['expiration_year'],
'cvv' => $input['cvv'],
'billingAddress1' => $input['address1'],
'billingAddress2' => $input['address2'],
'billingCity' => $input['city'],
'billingState' => $input['state'],
'billingPostcode' => $input['postal_code'],
'shippingAddress1' => $input['address1'],
'shippingAddress2' => $input['address2'],
'shippingCity' => $input['city'],
'shippingState' => $input['state'],
'shippingPostcode' => $input['postal_code']
];
}
private function getPaymentDetails($invitation, $input = null)
{
$invoice = $invitation->invoice;
$key = $invoice->invoice_number.'_details';
$gateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'))->gateway;
$paymentLibrary = $gateway->paymentlibrary;
$currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD');
if ($input && $paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
$data = self::convertInputForOmnipay($input);
Session::put($key, $data);
} elseif ($input && $paymentLibrary->id == PAYMENT_LIBRARY_PHP_PAYMENTS) {
$input = Input::all();
$data = [
'first_name' => $input['first_name'],
'last_name' => $input['last_name'],
'cc_number' => $input['card_number'],
'cc_exp' => $input['expiration_month'].$input['expiration_year'],
'cc_code' => $input['cvv'],
'street' => $input['address1'],
'street2' => $input['address2'],
'city' => $input['city'],
'state' => $input['state'],
'postal_code' => $input['postal_code'],
'amt' => $invoice->balance,
'ship_to_street' => $input['address1'],
'ship_to_city' => $input['city'],
'ship_to_state' => $input['state'],
'ship_to_postal_code' => $input['postal_code'],
'currency_code' => $currencyCode,
];
switch ($gateway->id) {
case GATEWAY_BEANSTREAM:
$data['phone'] = $input['phone'];
$data['email'] = $input['email'];
$data['country'] = $input['country'];
$data['ship_to_country'] = $input['country'];
break;
case GATEWAY_BRAINTREE:
$data['ship_to_state'] = 'Ohio'; //$input['state'];
break;
}
if (strlen($data['cc_exp']) == 5) {
$data['cc_exp'] = '0'.$data['cc_exp'];
}
Session::put($key, $data);
return $data;
} elseif (Session::get($key)) {
$data = Session::get($key);
} else {
$data = [];
}
if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
$card = new CreditCard($data);
return [
'amount' => $invoice->balance,
'card' => $card,
'currency' => $currencyCode,
'returnUrl' => URL::to('complete'),
'cancelUrl' => $invitation->getLink(),
'description' => trans('texts.' . $invoice->getEntityType()) . " {$invoice->invoice_number}",
];
} else {
return $data;
}
}
public function show_payment($invitationKey)
{
// Handle token billing
if (Input::get('use_token') == 'true') {
return self::do_payment($invitationKey, false, true);
}
if (Input::has('use_paypal')) {
Session::put('payment_type', Input::get('use_paypal') == 'true' ? PAYMENT_TYPE_PAYPAL : PAYMENT_TYPE_CREDIT_CARD);
} elseif (!Session::has('payment_type')) {
Session::put('payment_type', PAYMENT_TYPE_ANY);
}
// For PayPal we redirect straight to their site
$usePayPal = false;
if ($usePayPal = Input::get('use_paypal')) {
$usePayPal = $usePayPal == 'true';
} else {
$invitation = Invitation::with('invoice.client.account', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$account = $invitation->invoice->client->account;
if (count($account->account_gateways) == 1 && $account->getGatewayByType(PAYMENT_TYPE_PAYPAL)) {
$usePayPal = true;
}
}
if ($usePayPal) {
if (Session::has('error')) {
Session::reflash();
return Redirect::to('view/'.$invitationKey);
} else {
return self::do_payment($invitationKey, false);
}
}
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
$gateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'))->gateway;
$paymentLibrary = $gateway->paymentlibrary;
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
$data = [
'showBreadcrumbs' => false,
'url' => 'payment/'.$invitationKey,
'amount' => $invoice->balance,
'invoiceNumber' => $invoice->invoice_number,
'client' => $client,
'contact' => $invitation->contact,
'paymentLibrary' => $paymentLibrary,
'gateway' => $gateway,
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'currencyId' => $client->currency_id,
'account' => $client->account
];
return View::make('payments.payment', $data);
}
public function show_license_payment()
{
if (Input::has('return_url')) {
Session::set('return_url', Input::get('return_url'));
}
if (Input::has('affiliate_key')) {
if ($affiliate = Affiliate::where('affiliate_key', '=', Input::get('affiliate_key'))->first()) {
Session::set('affiliate_id', $affiliate->id);
}
}
Session::set('product_id', Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL));
if (!Session::get('affiliate_id')) {
return Utils::fatalError();
}
if (Utils::isNinjaDev() && Input::has('test_mode')) {
Session::set('test_mode', Input::get('test_mode'));
}
$account = $this->accountRepo->getNinjaAccount();
$account->load('account_gateways.gateway');
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
$gateway = $accountGateway->gateway;
$paymentLibrary = $gateway->paymentlibrary;
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
$affiliate = Affiliate::find(Session::get('affiliate_id'));
$data = [
'showBreadcrumbs' => false,
'hideHeader' => true,
'url' => 'license',
'amount' => $affiliate->price,
'client' => false,
'contact' => false,
'paymentLibrary' => $paymentLibrary,
'gateway' => $gateway,
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'currencyId' => 1,
'paymentTitle' => $affiliate->payment_title,
'paymentSubtitle' => $affiliate->payment_subtitle,
];
return View::make('payments.payment', $data);
}
public function do_license_payment()
{
$testMode = Session::get('test_mode') === 'true';
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('license')
->withErrors($validator);
}
$account = $this->accountRepo->getNinjaAccount();
$account->load('account_gateways.gateway');
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD);
try {
$affiliate = Affiliate::find(Session::get('affiliate_id'));
if ($testMode) {
$ref = 'TEST_MODE';
} else {
$gateway = self::createGateway($accountGateway);
$details = self::getLicensePaymentDetails(Input::all(), $affiliate);
$response = $gateway->purchase($details)->send();
$ref = $response->getTransactionReference();
if (!$ref) {
Session::flash('error', $response->getMessage());
return Redirect::to('license')->withInput();
}
if (!$response->isSuccessful()) {
Session::flash('error', $response->getMessage());
Utils::logError($response->getMessage());
return Redirect::to('license')->withInput();
}
}
$licenseKey = Utils::generateLicense();
$license = new License();
$license->first_name = Input::get('first_name');
$license->last_name = Input::get('last_name');
$license->email = Input::get('email');
$license->transaction_reference = $ref;
$license->license_key = $licenseKey;
$license->affiliate_id = Session::get('affiliate_id');
$license->product_id = Session::get('product_id');
$license->save();
$data = [
'message' => $affiliate->payment_subtitle,
'license' => $licenseKey,
'hideHeader' => true,
];
$name = "{$license->first_name} {$license->last_name}";
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, $affiliate->price, $license->license_key, $license->product_id);
if (Session::has('return_url')) {
return Redirect::away(Session::get('return_url')."?license_key={$license->license_key}&product_id=".Session::get('product_id'));
} else {
return View::make('public.license', $data);
}
} catch (\Exception $e) {
$errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage);
Utils::logError(Utils::getErrorString($e));
return Redirect::to('license')->withInput();
}
}
public function claim_license()
{
$licenseKey = Input::get('license_key');
$productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL);
$license = License::where('license_key', '=', $licenseKey)
->where('is_claimed', '=', false)
->where('product_id', '=', $productId)
->first();
if ($license) {
if ($license->transaction_reference != 'TEST_MODE') {
$license->is_claimed = true;
$license->save();
}
return $productId == PRODUCT_INVOICE_DESIGNS ? $_ENV['INVOICE_DESIGNS'] : 'valid';
} else {
return 'invalid';
}
}
public function do_payment($invitationKey, $onSite = true, $useToken = false)
{
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
);
if ($onSite) {
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('payment/'.$invitationKey)
->withErrors($validator);
}
}
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
$paymentLibrary = $accountGateway->gateway->paymentlibrary;
if ($onSite) {
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
$client->postal_code = trim(Input::get('postal_code'));
$client->save();
}
try {
if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
$gateway = self::createGateway($accountGateway);
$details = self::getPaymentDetails($invitation, $useToken || !$onSite ? false : Input::all());
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
if ($useToken) {
$details['cardReference'] = $client->getGatewayToken();
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) {
$tokenResponse = $gateway->createCard($details)->send();
$cardReference = $tokenResponse->getCardReference();
$details['cardReference'] = $cardReference;
$token = AccountGatewayToken::where('client_id', '=', $client->id)
->where('account_gateway_id', '=', $accountGateway->id)->first();
if (!$token) {
$token = new AccountGatewayToken();
$token->account_id = $account->id;
$token->contact_id = $invitation->contact_id;
$token->account_gateway_id = $accountGateway->id;
$token->client_id = $client->id;
}
$token->token = $cardReference;
$token->save();
}
}
$response = $gateway->purchase($details)->send();
$ref = $response->getTransactionReference();
if (!$ref) {
Session::flash('error', $response->getMessage());
if ($onSite) {
return Redirect::to('payment/'.$invitationKey)->withInput();
} else {
return Redirect::to('view/'.$invitationKey);
}
}
if ($response->isSuccessful()) {
$payment = self::createPayment($invitation, $ref);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/'.$payment->invitation->invitation_key);
} elseif ($response->isRedirect()) {
$invitation->transaction_reference = $ref;
$invitation->save();
Session::save();
$response->redirect();
} else {
Session::flash('error', $response->getMessage());
return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>', $response->getMessage());
}
}
} catch (\Exception $e) {
$errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage."<p>".$e->getMessage());
Utils::logError(Utils::getErrorString($e));
if ($onSite) {
return Redirect::to('payment/'.$invitationKey)->withInput();
} else {
return Redirect::to('view/'.$invitationKey);
}
}
}
private function createPayment($invitation, $ref, $payerId = null)
{
$invoice = $invitation->invoice;
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY) {
$account = Account::find($invoice->client->public_id);
$account->pro_plan_paid = date_create()->format('Y-m-d');
$account->save();
}
$payment = Payment::createNew($invitation);
$payment->invitation_id = $invitation->id;
$payment->account_gateway_id = $accountGateway->id;
$payment->invoice_id = $invoice->id;
$payment->amount = $invoice->balance;
$payment->client_id = $invoice->client_id;
$payment->contact_id = $invitation->contact_id;
$payment->transaction_reference = $ref;
$payment->payment_date = date_create()->format('Y-m-d');
if ($payerId) {
$payment->payer_id = $payerId;
}
$payment->save();
Event::fire('invoice.paid', $payment);
return $payment;
}
public function offsite_payment()
{
$payerId = Request::query('PayerID');
$token = Request::query('token');
$invitation = Invitation::with('invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('transaction_reference', '=', $token)->firstOrFail();
$invoice = $invitation->invoice;
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
$gateway = self::createGateway($accountGateway);
try {
$details = self::getPaymentDetails($invitation);
$response = $gateway->completePurchase($details)->send();
$ref = $response->getTransactionReference();
if ($response->isSuccessful()) {
$payment = self::createPayment($invitation, $ref, $payerId);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/'.$invitation->invitation_key);
} else {
$errorMessage = trans('texts.payment_error')."\n\n".$response->getMessage();
Session::flash('error', $errorMessage);
Utils::logError($errorMessage);
return Redirect::to('view/'.$invitation->invitation_key);
}
} catch (\Exception $e) {
$errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage);
Utils::logError($errorMessage."\n\n".$e->getMessage());
return Redirect::to('view/'.$invitation->invitation_key);
}
}
public function store()
{
return $this->save();
}
public function update($publicId)
{
return $this->save($publicId);
}
private function save($publicId = null)
{
if (!$publicId && $errors = $this->paymentRepo->getErrors(Input::all())) {
$url = $publicId ? 'payments/'.$publicId.'/edit' : 'payments/create';
return Redirect::to($url)
->withErrors($errors)
->withInput();
} else {
$this->paymentRepo->save($publicId, Input::all());
if ($publicId) {
Session::flash('message', trans('texts.updated_payment'));
return Redirect::to('payments/');
} else {
Session::flash('message', trans('texts.created_payment'));
return Redirect::to('clients/'.Input::get('client'));
}
}
}
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
$count = $this->paymentRepo->bulk($ids, $action);
if ($count > 0) {
$message = Utils::pluralize($action.'d_payment', $count);
Session::flash('message', $message);
}
return Redirect::to('payments');
}
}

View File

@ -0,0 +1,96 @@
<?php
class ProductController extends \BaseController
{
public function getDatatable()
{
$query = DB::table('products')
->where('products.account_id', '=', Auth::user()->account_id)
->where('products.deleted_at', '=', null)
->select('products.public_id', 'products.product_key', 'products.notes', 'products.cost');
return Datatable::query($query)
->addColumn('product_key', function ($model) { return link_to('products/'.$model->public_id.'/edit', $model->product_key); })
->addColumn('notes', function ($model) { return nl2br(Str::limit($model->notes, 100)); })
->addColumn('cost', function ($model) { return Utils::formatMoney($model->cost); })
->addColumn('dropdown', function ($model) {
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="'.URL::to('products/'.$model->public_id).'/edit">'.uctrans('texts.edit_product').'</a></li>
<li class="divider"></li>
<li><a href="'.URL::to('products/'.$model->public_id).'/archive">'.uctrans('texts.archive_product').'</a></li>
</ul>
</div>';
})
->orderColumns(['cost', 'product_key', 'cost'])
->make();
}
public function edit($publicId)
{
$data = [
'showBreadcrumbs' => false,
'product' => Product::scope($publicId)->firstOrFail(),
'method' => 'PUT',
'url' => 'products/'.$publicId,
'title' => trans('texts.edit_product'),
];
return View::make('accounts.product', $data);
}
public function create()
{
$data = [
'showBreadcrumbs' => false,
'product' => null,
'method' => 'POST',
'url' => 'products',
'title' => trans('texts.create_product'),
];
return View::make('accounts.product', $data);
}
public function store()
{
return $this->save();
}
public function update($publicId)
{
return $this->save($publicId);
}
private function save($productPublicId = false)
{
if ($productPublicId) {
$product = Product::scope($productPublicId)->firstOrFail();
} else {
$product = Product::createNew();
}
$product->product_key = trim(Input::get('product_key'));
$product->notes = trim(Input::get('notes'));
$product->cost = trim(Input::get('cost'));
$product->save();
$message = $productPublicId ? trans('texts.updated_product') : trans('texts.created_product');
Session::flash('message', $message);
return Redirect::to('company/products');
}
public function archive($publicId)
{
$product = Product::scope($publicId)->firstOrFail();
$product->delete();
Session::flash('message', trans('texts.archived_product'));
return Redirect::to('company/products');
}
}

View File

@ -0,0 +1,36 @@
<?php
use ninja\repositories\InvoiceRepository;
class QuoteApiController extends Controller
{
protected $invoiceRepo;
public function __construct(InvoiceRepository $invoiceRepo)
{
$this->invoiceRepo = $invoiceRepo;
}
public function index()
{
$invoices = Invoice::scope()->where('invoices.is_quote', '=', true)->orderBy('created_at', 'desc')->get();
$invoices = Utils::remapPublicIds($invoices->toArray());
$response = json_encode($invoices, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($invoices));
return Response::make($response, 200, $headers);
}
/*
public function store()
{
$data = Input::all();
$invoice = $this->invoiceRepo->save(false, $data, false);
$response = json_encode($invoice, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
*/
}

View File

@ -0,0 +1,187 @@
<?php
use ninja\mailers\ContactMailer as Mailer;
use ninja\repositories\InvoiceRepository;
use ninja\repositories\ClientRepository;
use ninja\repositories\TaxRateRepository;
class QuoteController extends \BaseController
{
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
protected $taxRateRepo;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
{
parent::__construct();
$this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
$this->taxRateRepo = $taxRateRepo;
}
public function index()
{
if (!Utils::isPro()) {
return Redirect::to('/invoices/create');
}
$data = [
'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE,
'columns' => Utils::trans(['checkbox', 'quote_number', 'client', 'quote_date', 'quote_total', 'due_date', 'status', 'action']),
];
/*
if (Invoice::scope()->where('is_recurring', '=', true)->count() > 0)
{
$data['secEntityType'] = ENTITY_RECURRING_INVOICE;
$data['secColumns'] = Utils::trans(['checkbox', 'frequency', 'client', 'start_date', 'end_date', 'quote_total', 'action']);
}
*/
return View::make('list', $data);
}
public function clientIndex()
{
$invitationKey = Session::get('invitation_key');
if (!$invitationKey) {
return Redirect::to('/setup');
}
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => Session::get('white_label'),
'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE,
'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
];
return View::make('public_list', $data);
}
public function getDatatable($clientPublicId = null)
{
$accountId = Auth::user()->account_id;
$search = Input::get('sSearch');
return $this->invoiceRepo->getDatatable($accountId, $clientPublicId, ENTITY_QUOTE, $search);
}
public function getClientDatatable()
{
$search = Input::get('sSearch');
$invitationKey = Session::get('invitation_key');
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation || $invitation->is_deleted) {
return [];
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return [];
}
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, $search);
}
public function create($clientPublicId = 0)
{
if (!Utils::isPro()) {
return Redirect::to('/invoices/create');
}
$client = null;
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber(true);
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
if ($clientPublicId) {
$client = Client::scope($clientPublicId)->firstOrFail();
}
$data = array(
'account' => $account,
'invoice' => null,
'data' => Input::old('data'),
'invoiceNumber' => $invoiceNumber,
'method' => 'POST',
'url' => 'invoices',
'title' => trans('texts.new_quote'),
'client' => $client, );
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
}
private static function getViewModel()
{
return [
'entityType' => ENTITY_QUOTE,
'account' => Auth::user()->account,
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId())
->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels()
];
}
public function bulk()
{
$action = Input::get('action');
if ($action == 'convert') {
$invoice = Invoice::with('invoice_items')->scope(Input::get('id'))->firstOrFail();
$clone = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
Session::flash('message', trans('texts.converted_to_invoice'));
return Redirect::to('invoices/'.$clone->public_id);
}
$statusId = Input::get('statusId');
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
$count = $this->invoiceRepo->bulk($ids, $action, $statusId);
if ($count > 0) {
$key = $action == 'mark' ? "updated_quote" : "{$action}d_quote";
$message = Utils::pluralize($key, $count);
Session::flash('message', $message);
}
return Redirect::to('quotes');
}
public function approve($invitationKey)
{
$invitation = Invitation::with('invoice.invoice_items', 'invoice.invitations')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
if ($invoice->is_quote && !$invoice->quote_invoice_id) {
Activity::approveQuote($invitation);
$invoice = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
Session::flash('message', trans('texts.converted_to_invoice'));
foreach ($invoice->invitations as $invitationClone) {
if ($invitation->contact_id == $invitationClone->contact_id) {
$invitationKey = $invitationClone->invitation_key;
}
}
}
return Redirect::to("view/{$invitationKey}");
}
}

View File

@ -0,0 +1,127 @@
<?php
class ReportController extends \BaseController
{
public function d3()
{
$message = '';
if (Auth::user()->account->isPro()) {
$account = Account::where('id', '=', Auth::user()->account->id)->with(['clients.invoices.invoice_items', 'clients.contacts'])->first();
$account = $account->hideFieldsForViz();
$clients = $account->clients->toJson();
} elseif (isset($_ENV['DATA_VIZ_SAMPLE'])) {
$clients = $_ENV['DATA_VIZ_SAMPLE'];
$message = trans('texts.sample_data');
} else {
$clients = '[]';
}
$data = [
'feature' => ACCOUNT_DATA_VISUALIZATIONS,
'clients' => $clients,
'message' => $message,
];
return View::make('reports.d3', $data);
}
public function report()
{
if (Input::all()) {
$groupBy = Input::get('group_by');
$chartType = Input::get('chart_type');
$startDate = Utils::toSqlDate(Input::get('start_date'), false);
$endDate = Utils::toSqlDate(Input::get('end_date'), false);
} else {
$groupBy = 'MONTH';
$chartType = 'Bar';
$startDate = Utils::today(false)->modify('-3 month');
$endDate = Utils::today(false);
}
$padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month');
$endDate->modify('+1 '.$padding);
$datasets = [];
$labels = [];
$maxTotals = 0;
$width = 10;
if (Auth::user()->account->isPro()) {
foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType) {
$records = DB::table($entityType.'s')
->select(DB::raw('sum(amount) as total, '.$groupBy.'('.$entityType.'_date) as '.$groupBy))
->where('account_id', '=', Auth::user()->account_id)
->where($entityType.'s.is_deleted', '=', false)
->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d'))
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d'))
->groupBy($groupBy);
if ($entityType == ENTITY_INVOICE) {
$records->where('is_quote', '=', false)
->where('is_recurring', '=', false);
}
$totals = $records->lists('total');
$dates = $records->lists($groupBy);
$data = array_combine($dates, $totals);
$interval = new DateInterval('P1'.substr($groupBy, 0, 1));
$period = new DatePeriod($startDate, $interval, $endDate);
$totals = [];
foreach ($period as $d) {
$dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n');
$date = $d->format($dateFormat);
$totals[] = isset($data[$date]) ? $data[$date] : 0;
if ($entityType == ENTITY_INVOICE) {
$labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F');
$label = $d->format($labelFormat);
$labels[] = $label;
}
}
$max = max($totals);
if ($max > 0) {
$datasets[] = [
'totals' => $totals,
'colors' => $entityType == ENTITY_INVOICE ? '78,205,196' : ($entityType == ENTITY_CREDIT ? '199,244,100' : '255,107,107'),
];
$maxTotals = max($max, $maxTotals);
}
}
$width = (ceil($maxTotals / 100) * 100) / 10;
$width = max($width, 10);
}
$dateTypes = [
'DAYOFYEAR' => 'Daily',
'WEEK' => 'Weekly',
'MONTH' => 'Monthly',
];
$chartTypes = [
'Bar' => 'Bar',
'Line' => 'Line',
];
$params = [
'labels' => $labels,
'datasets' => $datasets,
'scaleStepWidth' => $width,
'dateTypes' => $dateTypes,
'chartTypes' => $chartTypes,
'chartType' => $chartType,
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)),
'endDate' => $endDate->modify('-1'.$padding)->format(Session::get(SESSION_DATE_FORMAT)),
'groupBy' => $groupBy,
'feature' => ACCOUNT_CHART_BUILDER,
];
return View::make('reports.report_builder', $params);
}
}

View File

@ -0,0 +1,93 @@
<?php
class TimesheetController extends \BaseController {
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$data = [
'showBreadcrumbs' => false,
'timesheet' => [
'timesheet_number' => 1
]
];
return View::make('timesheets.edit', $data);
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store()
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function destroy($id)
{
//
}
}

View File

@ -0,0 +1,161 @@
<?php
/*
|--------------------------------------------------------------------------
| Confide Controller Template
|--------------------------------------------------------------------------
|
| This is the default Confide controller template for controlling user
| authentication. Feel free to change to your needs.
|
*/
use ninja\repositories\AccountRepository;
class TokenController extends BaseController
{
public function getDatatable()
{
$query = DB::table('account_tokens')
->where('account_tokens.account_id', '=', Auth::user()->account_id);
if (!Session::get('show_trash:token')) {
$query->where('account_tokens.deleted_at', '=', null);
}
$query->select('account_tokens.public_id', 'account_tokens.name', 'account_tokens.token', 'account_tokens.public_id', 'account_tokens.deleted_at');
return Datatable::query($query)
->addColumn('name', function ($model) { return link_to('tokens/'.$model->public_id.'/edit', $model->name); })
->addColumn('token', function ($model) { return $model->token; })
->addColumn('dropdown', function ($model) {
$actions = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at) {
$actions .= '<li><a href="'.URL::to('tokens/'.$model->public_id).'/edit">'.uctrans('texts.edit_token').'</a></li>
<li class="divider"></li>
<li><a href="javascript:deleteToken('.$model->public_id.')">'.uctrans('texts.delete_token').'</a></li>';
}
$actions .= '</ul>
</div>';
return $actions;
})
->orderColumns(['name', 'token'])
->make();
}
public function edit($publicId)
{
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $publicId)->firstOrFail();
$data = [
'showBreadcrumbs' => false,
'token' => $token,
'method' => 'PUT',
'url' => 'tokens/'.$publicId,
'title' => trans('texts.edit_token'),
];
return View::make('accounts.token', $data);
}
public function update($publicId)
{
return $this->save($publicId);
}
public function store()
{
return $this->save();
}
/**
* Displays the form for account creation
*
*/
public function create()
{
if (!Auth::user()->confirmed) {
Session::flash('error', trans('texts.register_to_add_user'));
return Redirect::to('company/advanced_settings/user_management');
}
$data = [
'showBreadcrumbs' => false,
'token' => null,
'method' => 'POST',
'url' => 'tokens',
'title' => trans('texts.add_token'),
];
return View::make('accounts.token', $data);
}
public function delete()
{
$tokenPublicId = Input::get('tokenPublicId');
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $tokenPublicId)->firstOrFail();
$token->delete();
Session::flash('message', trans('texts.deleted_token'));
return Redirect::to('company/advanced_settings/token_management');
}
/**
* Stores new account
*
*/
public function save($tokenPublicId = false)
{
if (Auth::user()->account->isPro()) {
$rules = [
'name' => 'required',
];
if ($tokenPublicId) {
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $tokenPublicId)->firstOrFail();
}
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to($tokenPublicId ? 'tokens/edit' : 'tokens/create')->withInput()->withErrors($validator);
}
if ($tokenPublicId) {
$token->name = trim(Input::get('name'));
} else {
$lastToken = AccountToken::withTrashed()->where('account_id', '=', Auth::user()->account_id)
->orderBy('public_id', 'DESC')->first();
$token = AccountToken::createNew();
$token->name = trim(Input::get('name'));
$token->token = str_random(RANDOM_KEY_LENGTH);
$token->public_id = $lastToken ? $lastToken->public_id + 1 : 1;
}
$token->save();
if ($tokenPublicId) {
$message = trans('texts.updated_token');
} else {
$message = trans('texts.created_token');
}
Session::flash('message', $message);
}
return Redirect::to('company/advanced_settings/token_management');
}
}

View File

@ -0,0 +1,507 @@
<?php
/*
|--------------------------------------------------------------------------
| Confide Controller Template
|--------------------------------------------------------------------------
|
| This is the default Confide controller template for controlling user
| authentication. Feel free to change to your needs.
|
*/
use ninja\repositories\AccountRepository;
use ninja\mailers\ContactMailer;
use ninja\mailers\UserMailer;
class UserController extends BaseController
{
protected $accountRepo;
protected $contactMailer;
protected $userMailer;
public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer, UserMailer $userMailer)
{
parent::__construct();
$this->accountRepo = $accountRepo;
$this->contactMailer = $contactMailer;
$this->userMailer = $userMailer;
}
public function getDatatable()
{
$query = DB::table('users')
->where('users.account_id', '=', Auth::user()->account_id);
if (!Session::get('show_trash:user')) {
$query->where('users.deleted_at', '=', null);
}
$query->where('users.public_id', '>', 0)
->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at');
return Datatable::query($query)
->addColumn('first_name', function ($model) { return link_to('users/'.$model->public_id.'/edit', $model->first_name.' '.$model->last_name); })
->addColumn('email', function ($model) { return $model->email; })
->addColumn('confirmed', function ($model) { return $model->deleted_at ? trans('texts.deleted') : ($model->confirmed ? trans('texts.active') : trans('texts.pending')); })
->addColumn('dropdown', function ($model) {
$actions = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if ($model->deleted_at) {
$actions .= '<li><a href="'.URL::to('restore_user/'.$model->public_id).'">'.uctrans('texts.restore_user').'</a></li>';
} else {
$actions .= '<li><a href="'.URL::to('users/'.$model->public_id).'/edit">'.uctrans('texts.edit_user').'</a></li>';
if (!$model->confirmed) {
$actions .= '<li><a href="'.URL::to('send_confirmation/'.$model->public_id).'">'.uctrans('texts.send_invite').'</a></li>';
}
$actions .= '<li class="divider"></li>
<li><a href="javascript:deleteUser('.$model->public_id.')">'.uctrans('texts.delete_user').'</a></li>';
}
$actions .= '</ul>
</div>';
return $actions;
})
->orderColumns(['first_name', 'email', 'confirmed'])
->make();
}
public function setTheme()
{
$user = User::find(Auth::user()->id);
$user->theme_id = Input::get('theme_id');
$user->save();
return Redirect::to(Input::get('path'));
}
public function forcePDFJS()
{
$user = Auth::user();
$user->force_pdfjs = true;
$user->save();
Session::flash('message', trans('texts.confide.updated_settings'));
return Redirect::to('/dashboard');
}
public function edit($publicId)
{
$user = User::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $publicId)->firstOrFail();
$data = [
'showBreadcrumbs' => false,
'user' => $user,
'method' => 'PUT',
'url' => 'users/'.$publicId,
'title' => trans('texts.edit_user'),
];
return View::make('users.edit', $data);
}
public function update($publicId)
{
return $this->save($publicId);
}
public function store()
{
return $this->save();
}
/**
* Displays the form for account creation
*
*/
public function create()
{
if (!Auth::user()->confirmed) {
Session::flash('error', trans('texts.register_to_add_user'));
return Redirect::to('company/advanced_settings/user_management');
}
if (Utils::isNinja()) {
$count = User::where('account_id', '=', Auth::user()->account_id)->count();
if ($count >= MAX_NUM_USERS) {
Session::flash('error', trans('texts.limit_users'));
return Redirect::to('company/advanced_settings/user_management');
}
}
$data = [
'showBreadcrumbs' => false,
'user' => null,
'method' => 'POST',
'url' => 'users',
'title' => trans('texts.add_user'),
];
return View::make('users.edit', $data);
}
public function delete()
{
$userPublicId = Input::get('userPublicId');
$user = User::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $userPublicId)->firstOrFail();
$user->delete();
Session::flash('message', trans('texts.deleted_user'));
return Redirect::to('company/advanced_settings/user_management');
}
public function restoreUser($userPublicId)
{
$user = User::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $userPublicId)
->withTrashed()->firstOrFail();
$user->restore();
Session::flash('message', trans('texts.restored_user'));
return Redirect::to('company/advanced_settings/user_management');
}
/**
* Stores new account
*
*/
public function save($userPublicId = false)
{
if (Auth::user()->account->isPro()) {
$rules = [
'first_name' => 'required',
'last_name' => 'required',
];
if ($userPublicId) {
$user = User::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $userPublicId)->firstOrFail();
$rules['email'] = 'required|email|unique:users,email,'.$user->id.',id';
} else {
$rules['email'] = 'required|email|unique:users';
}
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to($userPublicId ? 'users/edit' : 'users/create')->withInput()->withErrors($validator);
}
if ($userPublicId) {
$user->first_name = trim(Input::get('first_name'));
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email'));
} else {
$lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
->orderBy('public_id', 'DESC')->first();
$user = new User();
$user->account_id = Auth::user()->account_id;
$user->first_name = trim(Input::get('first_name'));
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email'));
$user->registered = true;
$user->password = str_random(RANDOM_KEY_LENGTH);
$user->password_confirmation = $user->password;
$user->public_id = $lastUser->public_id + 1;
}
$user->save();
if (!$user->confirmed) {
$this->userMailer->sendConfirmation($user, Auth::user());
$message = trans('texts.sent_invite');
} else {
$message = trans('texts.updated_user');
}
Session::flash('message', $message);
}
return Redirect::to('company/advanced_settings/user_management');
}
public function sendConfirmation($userPublicId)
{
$user = User::where('account_id', '=', Auth::user()->account_id)
->where('public_id', '=', $userPublicId)->firstOrFail();
$this->userMailer->sendConfirmation($user, Auth::user());
Session::flash('message', trans('texts.sent_invite'));
return Redirect::to('company/advanced_settings/user_management');
}
/**
* Displays the login form
*
*/
public function login()
{
if (Confide::user()) {
Event::fire('user.login');
Session::reflash();
return Redirect::to('/dashboard');
/*
$invoice = Invoice::scope()->orderBy('id', 'desc')->first();
if ($invoice)
{
return Redirect::to('/invoices/' . $invoice->public_id);
}
else
{
return Redirect::to('/dashboard');
}
*/
} else {
return View::make(Config::get('confide::login_form'));
}
}
/**
* Attempt to do login
*
*/
public function do_login()
{
$input = array(
'email' => Input::get('login_email'), // May be the username too
'username' => Input::get('login_email'), // so we have to pass both
'password' => Input::get('login_password'),
'remember' => true,
);
// If you wish to only allow login from confirmed users, call logAttempt
// with the second parameter as true.
// logAttempt will check if the 'email' perhaps is the username.
// Get the value from the config file instead of changing the controller
if (Input::get('login_email') && Confide::logAttempt($input, false)) {
Event::fire('user.login');
// Redirect the user to the URL they were trying to access before
// caught by the authentication filter IE Redirect::guest('user/login').
// Otherwise fallback to '/'
// Fix pull #145
return Redirect::intended('/dashboard'); // change it to '/admin', '/dashboard' or something
} else {
//$user = new User;
// Check if there was too many login attempts
if (Confide::isThrottled($input)) {
$err_msg = trans('texts.confide.too_many_attempts');
}
/*
elseif( $user->checkUserExists( $input ) and ! $user->isConfirmed( $input ) )
{
$err_msg = Lang::get('confide::confide.alerts.not_confirmed');
}
*/
else {
$err_msg = trans('texts.confide.wrong_credentials');
}
return Redirect::action('UserController@login')
->withInput(Input::except('login_password'))
->with('error', $err_msg);
}
}
/**
* Attempt to confirm account with code
*
* @param string $code
*/
public function confirm($code)
{
if (Confide::confirm($code)) {
$notice_msg = trans('texts.confide.confirmation');
$user = User::where('confirmation_code', '=', $code)->get()->first();
$user->confirmation_code = '';
$user->save();
if ($user->public_id) {
Auth::login($user);
return Redirect::to('user/reset');
} else {
if (Session::has(REQUESTED_PRO_PLAN)) {
Session::forget(REQUESTED_PRO_PLAN);
$invitation = $this->accountRepo->enableProPlan();
return Redirect::to($invitation->getLink());
} else {
return Redirect::action('UserController@login')->with('message', $notice_msg);
}
}
} else {
$error_msg = trans('texts.confide.wrong_confirmation');
return Redirect::action('UserController@login')->with('error', $error_msg);
}
}
/**
* Displays the forgot password form
*
*/
public function forgot_password()
{
return View::make(Config::get('confide::forgot_password_form'));
}
/**
* Attempt to send change password link to the given email
*
*/
public function do_forgot_password()
{
Confide::forgotPassword(Input::get('email'));
$notice_msg = trans('texts.confide.password_forgot');
return Redirect::action('UserController@login')
->with('notice', $notice_msg);
/*
if( Confide::forgotPassword( Input::get( 'email' ) ) )
{
$notice_msg = Lang::get('confide::confide.alerts.password_forgot');
return Redirect::action('UserController@login')
->with( 'notice', $notice_msg );
}
else
{
$error_msg = Lang::get('confide::confide.alerts.wrong_password_forgot');
return Redirect::action('UserController@forgot_password')
->withInput()
->with( 'error', $error_msg );
}
*/
}
/**
* Shows the change password form with the given token
*
*/
public function reset_password($token = false)
{
return View::make(Config::get('confide::reset_password_form'))
->with('token', $token);
}
/**
* Attempt change password of the user
*
*/
public function do_reset_password()
{
if (Auth::check()) {
$rules = [
'password' => 'required|between:4,11|confirmed',
'password_confirmation' => 'between:4,11',
];
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('user/reset')->withInput()->withErrors($validator);
}
$user = Auth::user();
$user->password = Input::get('password');
$user->save();
Session::flash('message', trans('texts.confide.password_reset'));
return Redirect::to('/dashboard');
} else {
$input = array(
'token' => Input::get('token'),
'password' => Input::get('password'),
'password_confirmation' => Input::get('password_confirmation'),
);
// By passing an array with the token, password and confirmation
if (Confide::resetPassword($input)) {
$notice_msg = trans('texts.confide.password_reset');
return Redirect::action('UserController@login')
->with('notice', $notice_msg);
} else {
$error_msg = trans('texts.confide.wrong_password_reset');
return Redirect::action('UserController@reset_password', array('token' => $input['token']))
->withInput()
->with('error', $error_msg);
}
}
}
/**
* Log the user out of the application.
*
*/
public function logout()
{
if (Auth::check()) {
if (!Auth::user()->registered) {
$account = Auth::user()->account;
$account->forceDelete();
}
}
Session::forget('news_feed_id');
Session::forget('news_feed_message');
Confide::logout();
return Redirect::to('/')->with('clearGuestKey', true);
}
public function changePassword()
{
// check the current password is correct
if (!Auth::validate([
'email' => Auth::user()->email,
'password' => Input::get('current_password')
])) {
return trans('texts.password_error_incorrect');
}
// validate the new password
$password = Input::get('new_password');
$confirm = Input::get('confirm_password');
if (strlen($password) < 6 || $password != $confirm) {
return trans('texts.password_error_invalid');
}
// save the new password
$user = Auth::user();
$user->password = $password;
$user->save();
return RESULT_SUCCESS;
}
}

View File

@ -0,0 +1,38 @@
<?php namespace InvoiceNinja\Http\Controllers\Auth;
use InvoiceNinja\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Registrar;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller {
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use AuthenticatesAndRegistersUsers;
/**
* Create a new authentication controller instance.
*
* @param \Illuminate\Contracts\Auth\Guard $auth
* @param \Illuminate\Contracts\Auth\Registrar $registrar
* @return void
*/
public function __construct(Guard $auth, Registrar $registrar)
{
$this->auth = $auth;
$this->registrar = $registrar;
$this->middleware('guest', ['except' => 'getLogout']);
}
}

View File

@ -0,0 +1,38 @@
<?php namespace InvoiceNinja\Http\Controllers\Auth;
use InvoiceNinja\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\PasswordBroker;
use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller {
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Create a new password controller instance.
*
* @param \Illuminate\Contracts\Auth\Guard $auth
* @param \Illuminate\Contracts\Auth\PasswordBroker $passwords
* @return void
*/
public function __construct(Guard $auth, PasswordBroker $passwords)
{
$this->auth = $auth;
$this->passwords = $passwords;
$this->middleware('guest');
}
}

View File

@ -0,0 +1,36 @@
<?php namespace InvoiceNinja\Http\Controllers;
class HomeController extends Controller {
/*
|--------------------------------------------------------------------------
| Home Controller
|--------------------------------------------------------------------------
|
| This controller renders your application's "dashboard" for users that
| are authenticated. Of course, you are free to change or remove the
| controller as you wish. It is just here to get your app started!
|
*/
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the application dashboard to the user.
*
* @return Response
*/
public function index()
{
return view('home');
}
}

View File

@ -0,0 +1,36 @@
<?php namespace InvoiceNinja\Http\Controllers;
class WelcomeController extends Controller {
/*
|--------------------------------------------------------------------------
| Welcome Controller
|--------------------------------------------------------------------------
|
| This controller renders the "marketing page" for the application and
| is configured to only allow guests. Like most of the other sample
| controllers, you are free to modify or remove it as you desire.
|
*/
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Show the application welcome screen to the user.
*
* @return Response
*/
public function index()
{
return view('welcome');
}
}

View File

@ -64,7 +64,7 @@ Route::get('user/reset/{token?}', 'UserController@reset_password');
Route::post('user/reset', 'UserController@do_reset_password');
Route::get('logout', 'UserController@logout');
if (Utils::isNinja()) {
if (\App\libraries\Utils::isNinja()) {
Route::post('/signup/register', 'AccountController@doRegister');
Route::get('/news_feed/{user_type}/{version}/', 'HomeController@newsFeed');
Route::get('/demo', 'AccountController@demo');

605
app/Libraries/Utils.php Normal file
View File

@ -0,0 +1,605 @@
<?php namespace App\Libraries;
class Utils
{
public static function isRegistered()
{
return Auth::check() && Auth::user()->registered;
}
public static function isConfirmed()
{
return Auth::check() && Auth::user()->confirmed;
}
public static function isDatabaseSetup()
{
try {
if (Schema::hasTable('accounts')) {
return true;
}
} catch (Exception $e) {
return false;
}
}
public static function isProd()
{
return App::environment() == ENV_PRODUCTION;
}
public static function isNinja()
{
return self::isNinjaProd() || self::isNinjaDev();
}
public static function isNinjaProd()
{
return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'];
}
public static function isNinjaDev()
{
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'];
}
public static function isPro()
{
return Auth::check() && Auth::user()->isPro();
}
public static function getUserType()
{
if (Utils::isNinja()) {
return USER_TYPE_CLOUD_HOST;
} else {
return USER_TYPE_SELF_HOST;
}
}
public static function getDemoAccountId()
{
return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false;
}
public static function isDemo()
{
return Auth::check() && Auth::user()->isDemo();
}
public static function getNewsFeedResponse($userType = false)
{
if (!$userType) {
$userType = Utils::getUserType();
}
$response = new stdClass();
$response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : '';
$response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : '';
$response->version = NINJA_VERSION;
return $response;
}
public static function getProLabel($feature)
{
if (Auth::check()
&& !Auth::user()->isPro()
&& $feature == ACCOUNT_ADVANCED_SETTINGS) {
return '&nbsp;<sup class="pro-label">PRO</sup>';
} else {
return '';
}
}
public static function basePath()
{
return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1);
}
public static function trans($input)
{
$data = [];
foreach ($input as $field) {
if ($field == "checkbox") {
$data[] = $field;
} else {
$data[] = trans("texts.$field");
}
}
return $data;
}
public static function fatalError($message = false, $exception = false)
{
if (!$message) {
$message = "An error occurred, please try again later.";
}
static::logError($message.' '.$exception);
$data = [
'showBreadcrumbs' => false,
'hideHeader' => true,
];
return View::make('error', $data)->with('error', $message);
}
public static function getErrorString($exception)
{
return "{$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
}
public static function logError($error, $context = 'PHP')
{
$count = Session::get('error_count', 0);
Session::put('error_count', ++$count);
if ($count > 100) {
return 'logged';
}
$data = [
'context' => $context,
'user_id' => Auth::check() ? Auth::user()->id : 0,
'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '',
'url' => Input::get('url', Request::url()),
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
'ip' => Request::getClientIp(),
'count' => Session::get('error_count', 0),
];
Log::error($error."\n", $data);
/*
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
{
$message->to($email)->subject($subject);
});
*/
}
public static function parseFloat($value)
{
$value = preg_replace('/[^0-9\.\-]/', '', $value);
return floatval($value);
}
public static function formatPhoneNumber($phoneNumber)
{
$phoneNumber = preg_replace('/[^0-9a-zA-Z]/', '', $phoneNumber);
if (!$phoneNumber) {
return '';
}
if (strlen($phoneNumber) > 10) {
$countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10);
$areaCode = substr($phoneNumber, -10, 3);
$nextThree = substr($phoneNumber, -7, 3);
$lastFour = substr($phoneNumber, -4, 4);
$phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour;
} elseif (strlen($phoneNumber) == 10 && in_array(substr($phoneNumber, 0, 3), array(653, 656, 658, 659))) {
/**
* SG country code are 653, 656, 658, 659
* US area code consist of 650, 651 and 657
* @see http://en.wikipedia.org/wiki/Telephone_numbers_in_Singapore#Numbering_plan
* @see http://www.bennetyee.org/ucsd-pages/area.html
*/
$countryCode = substr($phoneNumber, 0, 2);
$nextFour = substr($phoneNumber, 2, 4);
$lastFour = substr($phoneNumber, 6, 4);
$phoneNumber = '+'.$countryCode.' '.$nextFour.' '.$lastFour;
} elseif (strlen($phoneNumber) == 10) {
$areaCode = substr($phoneNumber, 0, 3);
$nextThree = substr($phoneNumber, 3, 3);
$lastFour = substr($phoneNumber, 6, 4);
$phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour;
} elseif (strlen($phoneNumber) == 7) {
$nextThree = substr($phoneNumber, 0, 3);
$lastFour = substr($phoneNumber, 3, 4);
$phoneNumber = $nextThree.'-'.$lastFour;
}
return $phoneNumber;
}
public static function formatMoney($value, $currencyId = false)
{
if (!$currencyId) {
$currencyId = Session::get(SESSION_CURRENCY);
}
$currency = Currency::remember(DEFAULT_QUERY_CACHE)->find($currencyId);
if (!$currency) {
$currency = Currency::remember(DEFAULT_QUERY_CACHE)->find(1);
}
return $currency->symbol.number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
}
public static function pluralize($string, $count)
{
$field = $count == 1 ? $string : $string.'s';
$string = trans("texts.$field", ['count' => $count]);
return $string;
}
public static function toArray($data)
{
return json_decode(json_encode((array) $data), true);
}
public static function toSpaceCase($camelStr)
{
return preg_replace('/([a-z])([A-Z])/s', '$1 $2', $camelStr);
}
public static function timestampToDateTimeString($timestamp)
{
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
return Utils::timestampToString($timestamp, $timezone, $format);
}
public static function timestampToDateString($timestamp)
{
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
return Utils::timestampToString($timestamp, $timezone, $format);
}
public static function dateToString($date)
{
$dateTime = new DateTime($date);
$timestamp = $dateTime->getTimestamp();
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
return Utils::timestampToString($timestamp, false, $format);
}
public static function timestampToString($timestamp, $timezone = false, $format)
{
if (!$timestamp) {
return '';
}
$date = Carbon::createFromTimeStamp($timestamp);
if ($timezone) {
$date->tz = $timezone;
}
if ($date->year < 1900) {
return '';
}
return $date->format($format);
}
public static function toSqlDate($date, $formatResult = true)
{
if (!$date) {
return;
}
$timezone = Session::get(SESSION_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT);
$dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone));
return $formatResult ? $dateTime->format('Y-m-d') : $dateTime;
}
public static function fromSqlDate($date, $formatResult = true)
{
if (!$date || $date == '0000-00-00') {
return '';
}
$timezone = Session::get(SESSION_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT);
$dateTime = DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone));
return $formatResult ? $dateTime->format($format) : $dateTime;
}
public static function today($formatResult = true)
{
$timezone = Session::get(SESSION_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT);
$date = date_create(null, new DateTimeZone($timezone));
if ($formatResult) {
return $date->format($format);
} else {
return $date;
}
}
public static function trackViewed($name, $type, $url = false)
{
if (!$url) {
$url = Request::url();
}
$viewed = Session::get(RECENTLY_VIEWED);
if (!$viewed) {
$viewed = [];
}
$object = new stdClass();
$object->url = $url;
$object->name = ucwords($type).': '.$name;
$data = [];
for ($i = 0; $i<count($viewed); $i++) {
$item = $viewed[$i];
if ($object->url == $item->url || $object->name == $item->name) {
continue;
}
array_unshift($data, $item);
}
array_unshift($data, $object);
if (count($data) > RECENTLY_VIEWED_LIMIT) {
array_pop($data);
}
Session::put(RECENTLY_VIEWED, $data);
}
public static function processVariables($str)
{
if (!$str) {
return '';
}
$variables = ['MONTH', 'QUARTER', 'YEAR'];
for ($i = 0; $i<count($variables); $i++) {
$variable = $variables[$i];
$regExp = '/:'.$variable.'[+-]?[\d]*/';
preg_match_all($regExp, $str, $matches);
$matches = $matches[0];
if (count($matches) == 0) {
continue;
}
foreach ($matches as $match) {
$offset = 0;
$addArray = explode('+', $match);
$minArray = explode('-', $match);
if (count($addArray) > 1) {
$offset = intval($addArray[1]);
} elseif (count($minArray) > 1) {
$offset = intval($minArray[1]) * -1;
}
$val = Utils::getDatePart($variable, $offset);
$str = str_replace($match, $val, $str);
}
}
return $str;
}
private static function getDatePart($part, $offset)
{
$offset = intval($offset);
if ($part == 'MONTH') {
return Utils::getMonth($offset);
} elseif ($part == 'QUARTER') {
return Utils::getQuarter($offset);
} elseif ($part == 'YEAR') {
return Utils::getYear($offset);
}
}
private static function getMonth($offset)
{
$months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December", ];
$month = intval(date('n')) - 1;
$month += $offset;
$month = $month % 12;
if ($month < 0) {
$month += 12;
}
return $months[$month];
}
private static function getQuarter($offset)
{
$month = intval(date('n')) - 1;
$quarter = floor(($month + 3) / 3);
$quarter += $offset;
$quarter = $quarter % 4;
if ($quarter == 0) {
$quarter = 4;
}
return 'Q'.$quarter;
}
private static function getYear($offset)
{
$year = intval(date('Y'));
return $year + $offset;
}
public static function getEntityName($entityType)
{
return ucwords(str_replace('_', ' ', $entityType));
}
public static function getClientDisplayName($model)
{
if ($model->client_name) {
return $model->client_name;
} elseif ($model->first_name || $model->last_name) {
return $model->first_name.' '.$model->last_name;
} else {
return $model->email;
}
}
public static function encodeActivity($person = null, $action, $entity = null, $otherPerson = null)
{
$person = $person ? $person->getDisplayName() : '<i>System</i>';
$entity = $entity ? '['.$entity->getActivityKey().']' : '';
$otherPerson = $otherPerson ? 'to '.$otherPerson->getDisplayName() : '';
$token = Session::get('token_id') ? ' ('.trans('texts.token').')' : '';
return trim("$person $token $action $entity $otherPerson");
}
public static function decodeActivity($message)
{
$pattern = '/\[([\w]*):([\d]*):(.*)\]/i';
preg_match($pattern, $message, $matches);
if (count($matches) > 0) {
$match = $matches[0];
$type = $matches[1];
$publicId = $matches[2];
$name = $matches[3];
$link = link_to($type.'s/'.$publicId, $name);
$message = str_replace($match, "$type $link", $message);
}
return $message;
}
public static function generateLicense()
{
$parts = [];
for ($i = 0; $i<5; $i++) {
$parts[] = strtoupper(str_random(4));
}
return implode('-', $parts);
}
public static function lookupEventId($eventName)
{
if ($eventName == 'create_client') {
return EVENT_CREATE_CLIENT;
} elseif ($eventName == 'create_invoice') {
return EVENT_CREATE_INVOICE;
} elseif ($eventName == 'create_quote') {
return EVENT_CREATE_QUOTE;
} elseif ($eventName == 'create_payment') {
return EVENT_CREATE_PAYMENT;
} else {
return false;
}
}
public static function notifyZapier($subscription, $data)
{
$curl = curl_init();
$jsonEncodedData = json_encode($data->toJson());
$opts = [
CURLOPT_URL => $subscription->target_url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $jsonEncodedData,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
];
curl_setopt_array($curl, $opts);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($status == 410) {
$subscription->delete();
}
}
public static function remapPublicIds(array $data)
{
$return = [];
foreach ($data as $key => $val) {
if ($key === 'public_id') {
$key = 'id';
} elseif (strpos($key, '_id')) {
continue;
}
if (is_array($val)) {
$val = Utils::remapPublicIds($val);
}
$return[$key] = $val;
}
return $return;
}
public static function getApiHeaders($count = 0)
{
return [
'Content-Type' => 'application/json',
//'Access-Control-Allow-Origin' => '*',
//'Access-Control-Allow-Methods' => 'GET',
//'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With',
//'Access-Control-Allow-Credentials' => 'true',
'X-Total-Count' => $count,
//'X-Rate-Limit-Limit' - The number of allowed requests in the current period
//'X-Rate-Limit-Remaining' - The number of remaining requests in the current period
//'X-Rate-Limit-Reset' - The number of seconds left in the current period,
];
}
public static function startsWith($haystack, $needle)
{
return $needle === "" || strpos($haystack, $needle) === 0;
}
public static function endsWith($haystack, $needle)
{
return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
}
public static function getEntityRowClass($model)
{
$str = $model->is_deleted || ($model->deleted_at && $model->deleted_at != '0000-00-00') ? 'DISABLED ' : '';
if ($model->is_deleted) {
$str .= 'ENTITY_DELETED ';
}
if ($model->deleted_at && $model->deleted_at != '0000-00-00') {
$str .= 'ENTITY_ARCHIVED ';
}
return $str;
}
}

8
app/Libraries/entity.php Normal file
View File

@ -0,0 +1,8 @@
<?
class Entity
{
public $id;
public $type;
}

View File

@ -0,0 +1,119 @@
<?php
class TimesheetUtils
{
public static function parseEventSummary($summary) {
if (preg_match('/^\s*([^\s:\/]+)(?:\/([^:]+))?\s*:\s*([^)].*$|$)$/s', $summary, $matches)) {
return [strtoupper($matches[1]), strtolower($matches[2]), $matches[3]];
} else {
return false;
}
}
public static function parseICALEvent($eventstr) {
if (preg_match_all('/(?:^|\r?\n)([^;:]+)[;:]([^\r\n]+)/s', $eventstr, $matches)) {
// Build ICAL event array
$data = ['summary' => ''];
foreach ($matches[1] as $i => $key) {
# Convert escaped linebreakes to linebreak
$value = preg_replace("/\r?\n\s/", "", $matches[2][$i]);
# Unescape , and ;
$value = preg_replace('/\\\\([,;])/s', '$1', $value);
$data[strtolower($key)] = $value;
}
return $data;
} else {
return false;
}
}
public static function parseICALDate($datestr) {
$dt = null;
$timezone = null;
if (preg_match('/^TZID=(.+?):([12]\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/', $datestr, $m)) {
$timezone = $m[1];
$dt = new DateTime("{$m[2]}-{$m[3]}-{$m[4]}T{$m[5]}:{$m[6]}:{$m[7]}", new DateTimeZone($m[1]));
} else if (preg_match('/^VALUE=DATE:([12]\d\d\d)(\d\d)(\d\d)$/', $datestr, $m)) {
$dt = new DateTime("{$m[1]}-{$m[2]}-{$m[3]}T00:00:00", new DateTimeZone("UTC"));
} else if (preg_match('/^([12]\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z$/', $datestr, $m)) {
$dt = new DateTime("{$m[1]}-{$m[2]}-{$m[3]}T{$m[4]}:{$m[5]}:{$m[6]}", new DateTimeZone("UTC"));
} else {
return false;
}
// Convert all to UTC
if($dt->getTimezone()->getName() != 'UTC') {
$dt->setTimezone(new DateTimeZone('UTC'));
}
return [$dt, $timezone];
}
public static function curlGetUrls($urls = [], $timeout = 30) {
// Create muxer
$results = [];
$multi = curl_multi_init();
$handles = [];
$ch2idx = [];
try {
foreach ($urls as $i => $url) {
// Create new handle and add to muxer
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); //timeout in seconds
curl_multi_add_handle($multi, $ch);
$handles[(int) $ch] = $ch;
$ch2idx[(int) $ch] = $i;
}
// Do initial connect
$still_running = true;
while ($still_running) {
// Do curl stuff
while (($mrc = curl_multi_exec($multi, $still_running)) === CURLM_CALL_MULTI_PERFORM);
if ($mrc !== CURLM_OK) {
break;
}
// Try to read from handles that are ready
while ($info = curl_multi_info_read($multi)) {
if ($info["result"] == CURLE_OK) {
$results[$ch2idx[(int) $info["handle"]]] = curl_multi_getcontent($info["handle"]);
} else {
if (CURLE_UNSUPPORTED_PROTOCOL == $info["result"]) {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Unsupported protocol"];
} else if (CURLE_URL_MALFORMAT == $info["result"]) {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Malform url"];
} else if (CURLE_COULDNT_RESOLVE_HOST == $info["result"]) {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Could not resolve host"];
} else if (CURLE_OPERATION_TIMEDOUT == $info["result"]) {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Timed out waiting for operations to finish"];
} else {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Unknown curl error code"];
}
}
}
// Sleep until
if (($rs = curl_multi_select($multi)) === -1) {
usleep(20); // select failed for some reason, so we sleep for 20ms and run some more curl stuff
}
}
} finally {
foreach ($handles as $chi => $ch) {
curl_multi_remove_handle($multi, $ch);
}
curl_multi_close($multi);
}
return $results;
}
}

363
app/Models/Account.php Normal file
View File

@ -0,0 +1,363 @@
<?php
class Account extends Eloquent
{
protected $softDelete = true;
public function users()
{
return $this->hasMany('User');
}
public function clients()
{
return $this->hasMany('Client');
}
public function invoices()
{
return $this->hasMany('Invoice');
}
public function account_gateways()
{
return $this->hasMany('AccountGateway');
}
public function tax_rates()
{
return $this->hasMany('TaxRate');
}
public function country()
{
return $this->belongsTo('Country');
}
public function timezone()
{
return $this->belongsTo('Timezone');
}
public function language()
{
return $this->belongsTo('Language');
}
public function date_format()
{
return $this->belongsTo('DateFormat');
}
public function datetime_format()
{
return $this->belongsTo('DatetimeFormat');
}
public function size()
{
return $this->belongsTo('Size');
}
public function industry()
{
return $this->belongsTo('Industry');
}
public function isGatewayConfigured($gatewayId = 0)
{
$this->load('account_gateways');
if ($gatewayId) {
return $this->getGatewayConfig($gatewayId) != false;
} else {
return count($this->account_gateways) > 0;
}
}
public function getDisplayName()
{
if ($this->name) {
return $this->name;
}
$this->load('users');
$user = $this->users()->first();
return $user->getDisplayName();
}
public function getTimezone()
{
if ($this->timezone) {
return $this->timezone->name;
} else {
return 'US/Eastern';
}
}
public function getGatewayByType($type = PAYMENT_TYPE_ANY)
{
foreach ($this->account_gateways as $gateway) {
if ($type == PAYMENT_TYPE_ANY) {
return $gateway;
} elseif ($gateway->isPayPal() && $type == PAYMENT_TYPE_PAYPAL) {
return $gateway;
} elseif (!$gateway->isPayPal() && $type == PAYMENT_TYPE_CREDIT_CARD) {
return $gateway;
}
}
return false;
}
public function getGatewayConfig($gatewayId)
{
foreach ($this->account_gateways as $gateway) {
if ($gateway->gateway_id == $gatewayId) {
return $gateway;
}
}
return false;
}
public function getLogoPath()
{
return 'logo/'.$this->account_key.'.jpg';
}
public function getLogoWidth()
{
$path = $this->getLogoPath();
if (!file_exists($path)) {
return 0;
}
list($width, $height) = getimagesize($path);
return $width;
}
public function getLogoHeight()
{
$path = $this->getLogoPath();
if (!file_exists($path)) {
return 0;
}
list($width, $height) = getimagesize($path);
return $height;
}
public function getNextInvoiceNumber($isQuote = false)
{
$counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
$prefix = $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
return $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT);
}
public function incrementCounter($invoiceNumber, $isQuote = false, $isRecurring)
{
// check if the user modified the invoice number
if (!$isRecurring && $invoiceNumber != $this->getNextInvoiceNumber($isQuote)) {
$number = intval(preg_replace('/[^0-9]/', '', $invoiceNumber));
if ($isQuote && !$this->share_counter) {
$this->quote_number_counter = $number + 1;
} else {
$this->invoice_number_counter = $number + 1;
}
// otherwise, just increment the counter
} else {
if ($isQuote && !$this->share_counter) {
$this->quote_number_counter += 1;
} else {
$this->invoice_number_counter += 1;
}
}
$this->save();
}
public function getLocale()
{
$language = Language::remember(DEFAULT_QUERY_CACHE)->where('id', '=', $this->account->language_id)->first();
return $language->locale;
}
public function loadLocalizationSettings()
{
$this->load('timezone', 'date_format', 'datetime_format', 'language');
Session::put(SESSION_TIMEZONE, $this->timezone ? $this->timezone->name : DEFAULT_TIMEZONE);
Session::put(SESSION_DATE_FORMAT, $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT);
Session::put(SESSION_DATE_PICKER_FORMAT, $this->date_format ? $this->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT);
Session::put(SESSION_DATETIME_FORMAT, $this->datetime_format ? $this->datetime_format->format : DEFAULT_DATETIME_FORMAT);
Session::put(SESSION_CURRENCY, $this->currency_id ? $this->currency_id : DEFAULT_CURRENCY);
Session::put(SESSION_LOCALE, $this->language_id ? $this->language->locale : DEFAULT_LOCALE);
}
public function getInvoiceLabels()
{
$data = [];
$fields = [
'invoice',
'invoice_date',
'due_date',
'invoice_number',
'po_number',
'discount',
'taxes',
'tax',
'item',
'description',
'unit_cost',
'quantity',
'line_total',
'subtotal',
'paid_to_date',
'balance_due',
'terms',
'your_invoice',
'quote',
'your_quote',
'quote_date',
'quote_number',
'total',
'invoice_issued_to',
];
foreach ($fields as $field) {
$data[$field] = trans("texts.$field");
}
return $data;
}
public function isPro()
{
if (!Utils::isNinjaProd()) {
return true;
}
if ($this->account_key == NINJA_ACCOUNT_KEY) {
return true;
}
$datePaid = $this->pro_plan_paid;
if (!$datePaid || $datePaid == '0000-00-00') {
return false;
} elseif ($datePaid == NINJA_DATE) {
return true;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $datePaid);
$interval = $today->diff($datePaid);
return $interval->y == 0;
}
public function isWhiteLabel()
{
if (Utils::isNinjaProd()) {
return false;
}
return $this->pro_plan_paid == NINJA_DATE;
}
public function getSubscription($eventId)
{
return Subscription::where('account_id', '=', $this->id)->where('event_id', '=', $eventId)->first();
}
public function hideFieldsForViz()
{
foreach ($this->clients as $client) {
$client->setVisible([
'public_id',
'name',
'balance',
'paid_to_date',
'invoices',
'contacts',
]);
foreach ($client->invoices as $invoice) {
$invoice->setVisible([
'public_id',
'invoice_number',
'amount',
'balance',
'invoice_status_id',
'invoice_items',
'created_at',
]);
foreach ($invoice->invoice_items as $invoiceItem) {
$invoiceItem->setVisible([
'product_key',
'cost',
'qty',
]);
}
}
foreach ($client->contacts as $contact) {
$contact->setVisible([
'public_id',
'first_name',
'last_name',
'email', ]);
}
}
return $this;
}
public function getEmailTemplate($entityType, $message = false)
{
$field = "email_template_$entityType";
$template = $this->$field;
if ($template) {
return $template;
}
$template = "\$client,<p/>\r\n\r\n" .
trans("texts.{$entityType}_message", ['amount' => '$amount']) . "<p/>\r\n\r\n";
if ($entityType != ENTITY_PAYMENT) {
$template .= "<a href=\"\$link\">\$link</a><p/>\r\n\r\n";
}
if ($message) {
$template .= "$message<p/>\r\n\r\n";
}
return $template . "\$footer";
}
public function getEmailFooter()
{
if ($this->email_footer) {
return $this->email_footer;
} else {
return "<p>" . trans('texts.email_signature') . "<br>\$account</p>";
}
}
public function showTokenCheckbox()
{
return $this->token_billing_type_id == TOKEN_BILLING_OPT_IN
|| $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT;
}
public function selectTokenCheckbox()
{
return $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT;
}
}

View File

@ -0,0 +1,28 @@
<?php
class AccountGateway extends EntityModel
{
public function gateway()
{
return $this->belongsTo('Gateway');
}
public function getCreditcardTypes()
{
$flags = unserialize(CREDIT_CARDS);
$arrayOfImages = [];
foreach ($flags as $card => $name) {
if (($this->accepted_credit_cards & $card) == $card) {
$arrayOfImages[] = ['source' => asset($name['card']), 'alt' => $name['text']];
}
}
return $arrayOfImages;
}
public function isPayPal() {
return $this->gateway_id == GATEWAY_PAYPAL_EXPRESS;
}
}

View File

@ -0,0 +1,7 @@
<?php
class AccountGatewayToken extends Eloquent
{
protected $softDelete = true;
public $timestamps = true;
}

View File

@ -0,0 +1,9 @@
<?php
class AccountToken extends EntityModel
{
public function account()
{
return $this->belongsTo('Account');
}
}

470
app/Models/Activity.php Normal file
View File

@ -0,0 +1,470 @@
<?php
class Activity extends Eloquent
{
public $timestamps = true;
protected $softDelete = false;
public function scopeScope($query)
{
return $query->whereAccountId(Auth::user()->account_id);
}
public function account()
{
return $this->belongsTo('Account');
}
public function user()
{
return $this->belongsTo('User');
}
private static function getBlank($entity = false)
{
$activity = new Activity();
if ($entity) {
$activity->user_id = $entity instanceof User ? $entity->id : $entity->user_id;
$activity->account_id = $entity->account_id;
} elseif (Auth::check()) {
$activity->user_id = Auth::user()->id;
$activity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
$activity->token_id = Session::get('token_id', null);
$activity->ip = Request::getClientIp();
return $activity;
}
public static function createClient($client, $notify = true)
{
$activity = Activity::getBlank();
$activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CLIENT;
$activity->message = Utils::encodeActivity(Auth::user(), 'created', $client);
$activity->save();
if ($notify) {
Activity::checkSubscriptions(EVENT_CREATE_CLIENT, $client);
}
}
public static function updateClient($client)
{
if ($client->is_deleted && !$client->getOriginal('is_deleted')) {
$activity = Activity::getBlank();
$activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_DELETE_CLIENT;
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted', $client);
$activity->balance = $client->balance;
$activity->save();
}
}
public static function archiveClient($client)
{
if (!$client->is_deleted) {
$activity = Activity::getBlank();
$activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CLIENT;
$activity->message = Utils::encodeActivity(Auth::user(), 'archived', $client);
$activity->balance = $client->balance;
$activity->save();
}
}
public static function restoreClient($client)
{
$activity = Activity::getBlank();
$activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_RESTORE_CLIENT;
$activity->message = Utils::encodeActivity(Auth::user(), 'restored', $client);
$activity->balance = $client->balance;
$activity->save();
}
public static function createInvoice($invoice)
{
if (Auth::check()) {
$message = Utils::encodeActivity(Auth::user(), 'created', $invoice);
} else {
$message = Utils::encodeActivity(null, 'created', $invoice);
}
$adjustment = 0;
$client = $invoice->client;
if (!$invoice->is_quote && !$invoice->is_recurring) {
$adjustment = $invoice->amount;
$client->balance = $client->balance + $adjustment;
$client->save();
}
$activity = Activity::getBlank($invoice);
$activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id;
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_CREATE_QUOTE : ACTIVITY_TYPE_CREATE_INVOICE;
$activity->message = $message;
$activity->balance = $client->balance;
$activity->adjustment = $adjustment;
$activity->save();
Activity::checkSubscriptions($invoice->is_quote ? EVENT_CREATE_QUOTE : EVENT_CREATE_INVOICE, $invoice);
}
public static function archiveInvoice($invoice)
{
if (!$invoice->is_deleted) {
$activity = Activity::getBlank();
$activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id;
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_ARCHIVE_QUOTE : ACTIVITY_TYPE_ARCHIVE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'archived', $invoice);
$activity->balance = $invoice->client->balance;
$activity->save();
}
}
public static function restoreInvoice($invoice)
{
$activity = Activity::getBlank();
$activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id;
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_RESTORE_QUOTE : ACTIVITY_TYPE_RESTORE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'restored', $invoice);
$activity->balance = $invoice->client->balance;
$activity->save();
}
public static function emailInvoice($invitation)
{
$adjustment = 0;
$client = $invitation->invoice->client;
$activity = Activity::getBlank($invitation);
$activity->client_id = $invitation->invoice->client_id;
$activity->invoice_id = $invitation->invoice_id;
$activity->contact_id = $invitation->contact_id;
$activity->activity_type_id = $invitation->invoice ? ACTIVITY_TYPE_EMAIL_QUOTE : ACTIVITY_TYPE_EMAIL_INVOICE;
$activity->message = Utils::encodeActivity(Auth::check() ? Auth::user() : null, 'emailed', $invitation->invoice, $invitation->contact);
$activity->balance = $client->balance;
$activity->save();
}
public static function updateInvoice($invoice)
{
$client = $invoice->client;
if ($invoice->is_deleted && !$invoice->getOriginal('is_deleted')) {
$adjustment = 0;
if (!$invoice->is_quote && !$invoice->is_recurring) {
$adjustment = $invoice->balance * -1;
$client->balance = $client->balance - $invoice->balance;
$client->paid_to_date = $client->paid_to_date - ($invoice->amount - $invoice->balance);
$client->save();
}
$activity = Activity::getBlank();
$activity->client_id = $invoice->client_id;
$activity->invoice_id = $invoice->id;
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_DELETE_QUOTE : ACTIVITY_TYPE_DELETE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted', $invoice);
$activity->balance = $invoice->client->balance;
$activity->adjustment = $adjustment;
$activity->save();
} else {
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
$fieldChanged = false;
foreach (['invoice_number', 'po_number', 'invoice_date', 'due_date', 'terms', 'public_notes', 'invoice_footer'] as $field) {
if ($invoice->$field != $invoice->getOriginal($field)) {
$fieldChanged = true;
break;
}
}
if ($diff > 0 || $fieldChanged) {
$backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($invoice->id);
if ($diff > 0 && !$invoice->is_quote && !$invoice->is_recurring) {
$client->balance = $client->balance + $diff;
$client->save();
}
$activity = Activity::getBlank($invoice);
$activity->client_id = $invoice->client_id;
$activity->invoice_id = $invoice->id;
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
$activity->balance = $client->balance;
$activity->adjustment = $invoice->is_quote || $invoice->is_recurring ? 0 : $diff;
$activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
$activity->save();
if ($invoice->isPaid() && $invoice->balance > 0) {
$invoice->invoice_status_id = INVOICE_STATUS_PARTIAL;
$invoice->save();
}
}
}
}
public static function viewInvoice($invitation)
{
if (Session::get($invitation->invitation_key)) {
return;
}
Session::put($invitation->invitation_key, true);
$invoice = $invitation->invoice;
if (!$invoice->isViewed()) {
$invoice->invoice_status_id = INVOICE_STATUS_VIEWED;
$invoice->save();
}
$now = Carbon::now()->toDateTimeString();
$invitation->viewed_date = $now;
$invitation->save();
$client = $invoice->client;
$client->last_login = $now;
$client->save();
$activity = Activity::getBlank($invitation);
$activity->client_id = $invitation->invoice->client_id;
$activity->invitation_id = $invitation->id;
$activity->contact_id = $invitation->contact_id;
$activity->invoice_id = $invitation->invoice_id;
$activity->activity_type_id = $invitation->invoice->is_quote ? ACTIVITY_TYPE_VIEW_QUOTE : ACTIVITY_TYPE_VIEW_INVOICE;
$activity->message = Utils::encodeActivity($invitation->contact, 'viewed', $invitation->invoice);
$activity->balance = $invitation->invoice->client->balance;
$activity->save();
}
public static function approveQuote($invitation) {
$activity = Activity::getBlank($invitation);
$activity->client_id = $invitation->invoice->client_id;
$activity->invitation_id = $invitation->id;
$activity->contact_id = $invitation->contact_id;
$activity->invoice_id = $invitation->invoice_id;
$activity->activity_type_id = ACTIVITY_TYPE_APPROVE_QUOTE;
$activity->message = Utils::encodeActivity($invitation->contact, 'approved', $invitation->invoice);
$activity->balance = $invitation->invoice->client->balance;
$activity->save();
}
public static function createPayment($payment)
{
$client = $payment->client;
$client->balance = $client->balance - $payment->amount;
$client->paid_to_date = $client->paid_to_date + $payment->amount;
$client->save();
if ($payment->contact_id) {
$activity = Activity::getBlank($client);
$activity->contact_id = $payment->contact_id;
$activity->message = Utils::encodeActivity($payment->invitation->contact, 'entered '.$payment->getName().' for ', $payment->invoice);
} else {
$activity = Activity::getBlank($client);
$message = $payment->payment_type_id == PAYMENT_TYPE_CREDIT ? 'applied credit for ' : 'entered '.$payment->getName().' for ';
$activity->message = Utils::encodeActivity(Auth::user(), $message, $payment->invoice);
}
$activity->payment_id = $payment->id;
if ($payment->invoice_id) {
$activity->invoice_id = $payment->invoice_id;
$invoice = $payment->invoice;
$invoice->balance = $invoice->balance - $payment->amount;
$invoice->invoice_status_id = ($invoice->balance > 0) ? INVOICE_STATUS_PARTIAL : INVOICE_STATUS_PAID;
$invoice->save();
}
$activity->payment_id = $payment->id;
$activity->client_id = $payment->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_PAYMENT;
$activity->balance = $client->balance;
$activity->adjustment = $payment->amount * -1;
$activity->save();
Activity::checkSubscriptions(EVENT_CREATE_PAYMENT, $payment);
}
public static function updatePayment($payment)
{
if ($payment->is_deleted && !$payment->getOriginal('is_deleted')) {
$client = $payment->client;
$client->balance = $client->balance + $payment->amount;
$client->paid_to_date = $client->paid_to_date - $payment->amount;
$client->save();
$invoice = $payment->invoice;
$invoice->balance = $invoice->balance + $payment->amount;
$invoice->save();
$activity = Activity::getBlank();
$activity->payment_id = $payment->id;
$activity->client_id = $invoice->client_id;
$activity->invoice_id = $invoice->id;
$activity->activity_type_id = ACTIVITY_TYPE_DELETE_PAYMENT;
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted '.$payment->getName());
$activity->balance = $client->balance;
$activity->adjustment = $payment->amount;
$activity->save();
} else {
/*
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
if ($diff == 0)
{
return;
}
$client = $invoice->client;
$client->balance = $client->balance + $diff;
$client->save();
$activity = Activity::getBlank($invoice);
$activity->client_id = $invoice->client_id;
$activity->invoice_id = $invoice->id;
$activity->activity_type_id = ACTIVITY_TYPE_UPDATE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
$activity->balance = $client->balance;
$activity->adjustment = $diff;
$activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
$activity->save();
*/
}
}
public static function archivePayment($payment)
{
if ($payment->is_deleted) {
return;
}
$client = $payment->client;
$invoice = $payment->invoice;
$activity = Activity::getBlank();
$activity->payment_id = $payment->id;
$activity->invoice_id = $invoice->id;
$activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_PAYMENT;
$activity->message = Utils::encodeActivity(Auth::user(), 'archived '.$payment->getName());
$activity->balance = $client->balance;
$activity->adjustment = 0;
$activity->save();
}
public static function restorePayment($payment)
{
$client = $payment->client;
$invoice = $payment->invoice;
$activity = Activity::getBlank();
$activity->payment_id = $payment->id;
$activity->invoice_id = $invoice->id;
$activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_RESTORE_PAYMENT;
$activity->message = Utils::encodeActivity(Auth::user(), 'restored '.$payment->getName());
$activity->balance = $client->balance;
$activity->adjustment = 0;
$activity->save();
}
public static function createCredit($credit)
{
$activity = Activity::getBlank();
$activity->message = Utils::encodeActivity(Auth::user(), 'entered '.Utils::formatMoney($credit->amount, $credit->client->currency_id).' credit');
$activity->credit_id = $credit->id;
$activity->client_id = $credit->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT;
$activity->balance = $credit->client->balance;
$activity->save();
}
public static function updateCredit($credit)
{
if ($credit->is_deleted && !$credit->getOriginal('is_deleted')) {
$activity = Activity::getBlank();
$activity->credit_id = $credit->id;
$activity->client_id = $credit->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_DELETE_CREDIT;
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit');
$activity->balance = $credit->client->balance;
$activity->save();
} else {
/*
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
if ($diff == 0)
{
return;
}
$client = $invoice->client;
$client->balance = $client->balance + $diff;
$client->save();
$activity = Activity::getBlank($invoice);
$activity->client_id = $invoice->client_id;
$activity->invoice_id = $invoice->id;
$activity->activity_type_id = ACTIVITY_TYPE_UPDATE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
$activity->balance = $client->balance;
$activity->adjustment = $diff;
$activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
$activity->save();
*/
}
}
public static function archiveCredit($credit)
{
if ($credit->is_deleted) {
return;
}
$activity = Activity::getBlank();
$activity->client_id = $credit->client_id;
$activity->credit_id = $credit->id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CREDIT;
$activity->message = Utils::encodeActivity(Auth::user(), 'archived '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit');
$activity->balance = $credit->client->balance;
$activity->save();
}
public static function restoreCredit($credit)
{
$activity = Activity::getBlank();
$activity->client_id = $credit->client_id;
$activity->credit_id = $credit->id;
$activity->activity_type_id = ACTIVITY_TYPE_RESTORE_CREDIT;
$activity->message = Utils::encodeActivity(Auth::user(), 'restored '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit');
$activity->balance = $credit->client->balance;
$activity->save();
}
private static function checkSubscriptions($event, $data)
{
if (!Auth::check()) {
return;
}
$subscription = Auth::user()->account->getSubscription($event);
if ($subscription) {
Utils::notifyZapier($subscription, $data);
}
}
}

7
app/Models/Affiliate.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Affiliate extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
}

268
app/Models/Client.php Normal file
View File

@ -0,0 +1,268 @@
<?php
class Client extends EntityModel
{
public static $fieldName = 'Client - Name';
public static $fieldPhone = 'Client - Phone';
public static $fieldAddress1 = 'Client - Street';
public static $fieldAddress2 = 'Client - Apt/Floor';
public static $fieldCity = 'Client - City';
public static $fieldState = 'Client - State';
public static $fieldPostalCode = 'Client - Postal Code';
public static $fieldNotes = 'Client - Notes';
public static $fieldCountry = 'Client - Country';
public function account()
{
return $this->belongsTo('Account');
}
public function invoices()
{
return $this->hasMany('Invoice');
}
public function payments()
{
return $this->hasMany('Payment');
}
public function contacts()
{
return $this->hasMany('Contact');
}
public function projects()
{
return $this->hasMany('Project');
}
public function country()
{
return $this->belongsTo('Country');
}
public function currency()
{
return $this->belongsTo('Currency');
}
public function size()
{
return $this->belongsTo('Size');
}
public function industry()
{
return $this->belongsTo('Industry');
}
public function getTotalCredit()
{
return DB::table('credits')
->where('client_id', '=', $this->id)
->whereNull('deleted_at')
->sum('balance');
}
public function getName()
{
return $this->getDisplayName();
}
public function getDisplayName()
{
if ($this->name) {
return $this->name;
}
$this->load('contacts');
$contact = $this->contacts()->first();
return $contact->getDisplayName();
}
public function getEntityType()
{
return ENTITY_CLIENT;
}
public function getAddress()
{
$str = '';
if ($this->address1) {
$str .= $this->address1.'<br/>';
}
if ($this->address2) {
$str .= $this->address2.'<br/>';
}
if ($this->city) {
$str .= $this->city.', ';
}
if ($this->state) {
$str .= $this->state.' ';
}
if ($this->postal_code) {
$str .= $this->postal_code;
}
if ($this->country) {
$str .= '<br/>'.$this->country->name;
}
if ($str) {
$str = '<p>'.$str.'</p>';
}
return $str;
}
public function getPhone()
{
$str = '';
if ($this->work_phone) {
$str .= '<i class="fa fa-phone" style="width: 20px"></i>'.Utils::formatPhoneNumber($this->work_phone);
}
return $str;
}
public function getIdNumber()
{
$str = '';
if ($this->id_number) {
$str .= '<i class="fa fa-id-number" style="width: 20px"></i>'.trans('texts.id_number').': '.$this->id_number;
}
return $str;
}
public function getVatNumber()
{
$str = '';
if ($this->vat_number) {
$str .= '<i class="fa fa-vat-number" style="width: 20px"></i>'.trans('texts.vat_number').': '.$this->vat_number;
}
return $str;
}
public function getNotes()
{
$str = '';
if ($this->private_notes) {
$str .= '<i>'.$this->private_notes.'</i>';
}
return $str;
}
public function getIndustry()
{
$str = '';
if ($this->client_industry) {
$str .= $this->client_industry->name.' ';
}
if ($this->client_size) {
$str .= $this->client_size->name;
}
return $str;
}
public function getCustomFields()
{
$str = '';
$account = $this->account;
if ($account->custom_client_label1 && $this->custom_value1) {
$str .= "{$account->custom_client_label1}: {$this->custom_value1}<br/>";
}
if ($account->custom_client_label2 && $this->custom_value2) {
$str .= "{$account->custom_client_label2}: {$this->custom_value2}<br/>";
}
return $str;
}
public function getWebsite()
{
if (!$this->website) {
return '';
}
$link = $this->website;
$title = $this->website;
$prefix = 'http://';
if (strlen($link) > 7 && substr($link, 0, 7) === $prefix) {
$title = substr($title, 7);
} else {
$link = $prefix.$link;
}
return link_to($link, $title, array('target' => '_blank'));
}
public function getDateCreated()
{
if ($this->created_at == '0000-00-00 00:00:00') {
return '---';
} else {
return $this->created_at->format('m/d/y h:i a');
}
}
public function getGatewayToken()
{
$this->account->load('account_gateways');
if (!count($this->account->account_gateways)) {
return false;
}
$accountGateway = $this->account->getGatewayConfig(GATEWAY_STRIPE);
if (!$accountGateway) {
return false;
}
$token = AccountGatewayToken::where('client_id', '=', $this->id)
->where('account_gateway_id', '=', $accountGateway->id)->first();
return $token ? $token->token : false;
}
public function getGatewayLink()
{
$token = $this->getGatewayToken();
return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
}
}
/*
Client::created(function($client)
{
Activity::createClient($client);
});
*/
Client::updating(function ($client) {
Activity::updateClient($client);
});
Client::deleting(function ($client) {
Activity::archiveClient($client);
});
Client::restoring(function ($client) {
Activity::restoreClient($client);
});

74
app/Models/Contact.php Normal file
View File

@ -0,0 +1,74 @@
<?php
class Contact extends EntityModel
{
public static $fieldFirstName = 'Contact - First Name';
public static $fieldLastName = 'Contact - Last Name';
public static $fieldEmail = 'Contact - Email';
public static $fieldPhone = 'Contact - Phone';
public function client()
{
return $this->belongsTo('Client');
}
public function getPersonType()
{
return PERSON_CONTACT;
}
/*
public function getLastLogin()
{
if ($this->last_login == '0000-00-00 00:00:00')
{
return '---';
}
else
{
return $this->last_login->format('m/d/y h:i a');
}
}
*/
public function getDisplayName()
{
if ($this->getFullName()) {
return $this->getFullName();
} else {
return $this->email;
}
}
public function getFullName()
{
if ($this->first_name || $this->last_name) {
return $this->first_name.' '.$this->last_name;
} else {
return '';
}
}
public function getDetails()
{
$str = '';
if ($this->first_name || $this->last_name) {
$str .= '<b>'.$this->first_name.' '.$this->last_name.'</b><br/>';
}
if ($this->email) {
$str .= '<i class="fa fa-envelope" style="width: 20px"></i>'.HTML::mailto($this->email, $this->email).'<br/>';
}
if ($this->phone) {
$str .= '<i class="fa fa-phone" style="width: 20px"></i>'.Utils::formatPhoneNumber($this->phone);
}
if ($str) {
$str = '<p>'.$str.'</p>';
}
return $str;
}
}

9
app/Models/Country.php Normal file
View File

@ -0,0 +1,9 @@
<?php
class Country extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
protected $visible = ['id', 'name'];
}

55
app/Models/Credit.php Normal file
View File

@ -0,0 +1,55 @@
<?php
class Credit extends EntityModel
{
public function invoice()
{
return $this->belongsTo('Invoice')->withTrashed();
}
public function client()
{
return $this->belongsTo('Client')->withTrashed();
}
public function getName()
{
return '';
}
public function getEntityType()
{
return ENTITY_CREDIT;
}
public function apply($amount)
{
if ($amount > $this->balance) {
$applied = $this->balance;
$this->balance = 0;
} else {
$applied = $amount;
$this->balance = $this->balance - $amount;
}
$this->save();
return $applied;
}
}
Credit::created(function ($credit) {
Activity::createCredit($credit);
});
Credit::updating(function ($credit) {
Activity::updateCredit($credit);
});
Credit::deleting(function ($credit) {
Activity::archiveCredit($credit);
});
Credit::restoring(function ($credit) {
Activity::restoreCredit($credit);
});

7
app/Models/Currency.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Currency extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

View File

@ -0,0 +1,7 @@
<?php
class DateFormat extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

View File

@ -0,0 +1,7 @@
<?php
class DatetimeFormat extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

View File

@ -0,0 +1,78 @@
<?php
class EntityModel extends Eloquent
{
protected $softDelete = true;
public $timestamps = true;
protected $hidden = ['id'];
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
$lastEntity = $className::withTrashed()->scope(false, $entity->account_id)->orderBy('public_id', 'DESC')->first();
if ($lastEntity) {
$entity->public_id = $lastEntity->public_id + 1;
} else {
$entity->public_id = 1;
}
return $entity;
}
public static function getPrivateId($publicId)
{
$className = get_called_class();
return $className::scope($publicId)->pluck('id');
}
public function getActivityKey()
{
return $this->getEntityType().':'.$this->public_id.':'.$this->getName();
}
/*
public function getEntityType()
{
return '';
}
public function getNmae()
{
return '';
}
*/
public function scopeScope($query, $publicId = false, $accountId = false)
{
if (!$accountId) {
$accountId = Auth::user()->account_id;
}
$query->whereAccountId($accountId);
if ($publicId) {
if (is_array($publicId)) {
$query->whereIn('public_id', $publicId);
} else {
$query->wherePublicId($publicId);
}
}
return $query;
}
}

7
app/Models/Frequency.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Frequency extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

52
app/Models/Gateway.php Normal file
View File

@ -0,0 +1,52 @@
<?php
class Gateway extends Eloquent
{
public $timestamps = true;
protected $softDelete = false;
public function paymentlibrary()
{
return $this->belongsTo('PaymentLibrary', 'payment_library_id');
}
public function getLogoUrl()
{
return '/images/gateways/logo_'.$this->provider.'.png';
}
public function getHelp()
{
$link = '';
if ($this->id == GATEWAY_AUTHORIZE_NET || $this->id == GATEWAY_AUTHORIZE_NET_SIM) {
$link = 'http://reseller.authorize.net/application/?id=5560364';
} elseif ($this->id == GATEWAY_PAYPAL_EXPRESS) {
$link = 'https://www.paypal.com/us/cgi-bin/webscr?cmd=_login-api-run';
} elseif ($this->id == GATEWAY_TWO_CHECKOUT) {
$link = 'https://www.2checkout.com/referral?r=2c37ac2298';
}
$key = 'texts.gateway_help_'.$this->id;
$str = trans($key, ['link' => "<a href='$link' target='_blank'>Click here</a>"]);
return $key != $str ? $str : '';
}
public function getFields()
{
$paymentLibrary = $this->paymentlibrary;
if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
$fields = Omnipay::create($this->provider)->getDefaultParameters();
} else {
$fields = Payment_Utility::load('config', 'drivers/'.strtolower($this->provider));
}
if ($fields == null) {
$fields = array();
}
return $fields;
}
}

7
app/Models/Industry.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Industry extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

29
app/Models/Invitation.php Normal file
View File

@ -0,0 +1,29 @@
<?php
class Invitation extends EntityModel
{
public function invoice()
{
return $this->belongsTo('Invoice');
}
public function contact()
{
return $this->belongsTo('Contact')->withTrashed();
}
public function user()
{
return $this->belongsTo('User')->withTrashed();
}
public function account()
{
return $this->belongsTo('Account');
}
public function getLink()
{
return SITE_URL.'/view/'.$this->invitation_key;
}
}

230
app/Models/Invoice.php Normal file
View File

@ -0,0 +1,230 @@
<?php
class Invoice extends EntityModel
{
public function account()
{
return $this->belongsTo('Account');
}
public function user()
{
return $this->belongsTo('User');
}
public function client()
{
return $this->belongsTo('Client')->withTrashed();
}
public function invoice_items()
{
return $this->hasMany('InvoiceItem')->orderBy('id');
}
public function invoice_status()
{
return $this->belongsTo('InvoiceStatus');
}
public function invoice_design()
{
return $this->belongsTo('InvoiceDesign');
}
public function invitations()
{
return $this->hasMany('Invitation')->orderBy('invitations.contact_id');
}
public function getName()
{
return $this->invoice_number;
}
public function getLink()
{
return link_to('invoices/'.$this->public_id, $this->invoice_number);
}
public function getEntityType()
{
return $this->is_quote ? ENTITY_QUOTE : ENTITY_INVOICE;
}
public function isSent()
{
return $this->invoice_status_id >= INVOICE_STATUS_SENT;
}
public function isViewed()
{
return $this->invoice_status_id >= INVOICE_STATUS_VIEWED;
}
public function isPaid()
{
return $this->invoice_status_id >= INVOICE_STATUS_PAID;
}
public function hidePrivateFields()
{
$this->setVisible([
'invoice_number',
'discount',
'is_amount_discount',
'po_number',
'invoice_date',
'due_date',
'terms',
'invoice_footer',
'public_notes',
'amount',
'balance',
'invoice_items',
'client',
'tax_name',
'tax_rate',
'account',
'invoice_design',
'invoice_design_id',
'is_pro',
'is_quote',
'custom_value1',
'custom_value2',
'custom_taxes1',
'custom_taxes2', ]);
$this->client->setVisible([
'name',
'id_number',
'vat_number',
'address1',
'address2',
'city',
'state',
'postal_code',
'work_phone',
'payment_terms',
'contacts',
'country',
'currency_id',
'custom_value1',
'custom_value2', ]);
$this->account->setVisible([
'name',
'id_number',
'vat_number',
'address1',
'address2',
'city',
'state',
'postal_code',
'work_phone',
'work_email',
'country',
'currency_id',
'custom_label1',
'custom_value1',
'custom_label2',
'custom_value2',
'custom_client_label1',
'custom_client_label2',
'primary_color',
'secondary_color',
'hide_quantity',
'hide_paid_to_date',
'custom_invoice_label1',
'custom_invoice_label2', ]);
foreach ($this->invoice_items as $invoiceItem) {
$invoiceItem->setVisible([
'product_key',
'notes',
'cost',
'qty',
'tax_name',
'tax_rate', ]);
}
foreach ($this->client->contacts as $contact) {
$contact->setVisible([
'first_name',
'last_name',
'email',
'phone', ]);
}
return $this;
}
public function shouldSendToday()
{
if (!$this->start_date || strtotime($this->start_date) > strtotime('now')) {
return false;
}
if ($this->end_date && strtotime($this->end_date) < strtotime('now')) {
return false;
}
$dayOfWeekToday = date('w');
$dayOfWeekStart = date('w', strtotime($this->start_date));
$dayOfMonthToday = date('j');
$dayOfMonthStart = date('j', strtotime($this->start_date));
if (!$this->last_sent_date) {
return true;
} else {
$date1 = new DateTime($this->last_sent_date);
$date2 = new DateTime();
$diff = $date2->diff($date1);
$daysSinceLastSent = $diff->format("%a");
$monthsSinceLastSent = ($diff->format('%y') * 12) + $diff->format('%m');
if ($daysSinceLastSent == 0) {
return false;
}
}
switch ($this->frequency_id) {
case FREQUENCY_WEEKLY:
return $daysSinceLastSent >= 7;
case FREQUENCY_TWO_WEEKS:
return $daysSinceLastSent >= 14;
case FREQUENCY_FOUR_WEEKS:
return $daysSinceLastSent >= 28;
case FREQUENCY_MONTHLY:
return $monthsSinceLastSent >= 1;
case FREQUENCY_THREE_MONTHS:
return $monthsSinceLastSent >= 3;
case FREQUENCY_SIX_MONTHS:
return $monthsSinceLastSent >= 6;
case FREQUENCY_ANNUALLY:
return $monthsSinceLastSent >= 12;
default:
return false;
}
return false;
}
}
Invoice::created(function ($invoice) {
$invoice->account->incrementCounter($invoice->invoice_number, $invoice->is_quote, $invoice->recurring_invoice_id);
Activity::createInvoice($invoice);
});
Invoice::updating(function ($invoice) {
Activity::updateInvoice($invoice);
});
Invoice::deleting(function ($invoice) {
Activity::archiveInvoice($invoice);
});
Invoice::restoring(function ($invoice) {
Activity::restoreInvoice($invoice);
});

View File

@ -0,0 +1,7 @@
<?php
class InvoiceDesign extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

View File

@ -0,0 +1,14 @@
<?php
class InvoiceItem extends EntityModel
{
public function invoice()
{
return $this->belongsTo('Invoice');
}
public function product()
{
return $this->belongsTo('Product');
}
}

View File

@ -0,0 +1,7 @@
<?php
class InvoiceStatus extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

7
app/Models/Language.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Language extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

7
app/Models/License.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class License extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
}

60
app/Models/Payment.php Normal file
View File

@ -0,0 +1,60 @@
<?php
class Payment extends EntityModel
{
public function invoice()
{
return $this->belongsTo('Invoice')->withTrashed();
}
public function invitation()
{
return $this->belongsTo('Invitation');
}
public function client()
{
return $this->belongsTo('Client')->withTrashed();
}
public function account()
{
return $this->belongsTo('Account');
}
public function contact()
{
return $this->belongsTo('Contact');
}
public function getAmount()
{
return Utils::formatMoney($this->amount, $this->client->currency_id);
}
public function getName()
{
return trim("payment {$this->transaction_reference}");
}
public function getEntityType()
{
return ENTITY_PAYMENT;
}
}
Payment::created(function ($payment) {
Activity::createPayment($payment);
});
Payment::updating(function ($payment) {
Activity::updatePayment($payment);
});
Payment::deleting(function ($payment) {
Activity::archivePayment($payment);
});
Payment::restoring(function ($payment) {
Activity::restorePayment($payment);
});

View File

@ -0,0 +1,12 @@
<?php
class PaymentLibrary extends Eloquent
{
protected $table = 'payment_libraries';
public $timestamps = true;
public function gateways()
{
return $this->hasMany('Gateway', 'payment_library_id');
}
}

View File

@ -0,0 +1,7 @@
<?php
class PaymentTerm extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

View File

@ -0,0 +1,7 @@
<?php
class PaymentType extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

9
app/Models/Product.php Normal file
View File

@ -0,0 +1,9 @@
<?php
class Product extends EntityModel
{
public static function findProductByKey($key)
{
return Product::scope()->where('product_key', '=', $key)->first();
}
}

45
app/Models/Project.php Normal file
View File

@ -0,0 +1,45 @@
<?php
class Project extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
public function account()
{
return $this->belongsTo('Account');
}
public function user()
{
return $this->belongsTo('User');
}
public function client()
{
return $this->belongsTo('Client');
}
public function codes()
{
return $this->hasMany('ProjectCode');
}
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
return $entity;
}
}

View File

@ -0,0 +1,45 @@
<?php
class ProjectCode extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
public function account()
{
return $this->belongsTo('Account');
}
public function user()
{
return $this->belongsTo('User');
}
public function project()
{
return $this->belongsTo('Project');
}
public function events()
{
return $this->hasMany('TimesheetEvent');
}
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
return $entity;
}
}

7
app/Models/Size.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Size extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

View File

@ -0,0 +1,7 @@
<?php
class Subscription extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
}

5
app/Models/TaxRate.php Normal file
View File

@ -0,0 +1,5 @@
<?php
class TaxRate extends EntityModel
{
}

7
app/Models/Theme.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Theme extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

22
app/Models/Timesheet.php Normal file
View File

@ -0,0 +1,22 @@
<?php
class Timesheet extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
public function account()
{
return $this->belongsTo('Account');
}
public function user()
{
return $this->belongsTo('User');
}
public function timesheet_events()
{
return $this->hasMany('TimeSheetEvent');
}
}

View File

@ -0,0 +1,122 @@
<?php
class TimesheetEvent extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
/* protected $dates = array('org_updated_at');
public function getDates() {
return array('created_at', 'updated_at', 'deleted_at');
} */
/* public function setOrgUpdatedAtAttribute($value)
{
var_dump($value);
$this->attributes['org_updated_at'] = $value->getTimestamp();
}*/
public function account()
{
return $this->belongsTo('Account');
}
public function user()
{
return $this->belongsTo('User');
}
public function source()
{
return $this->belongsTo('TimesheetEventSource');
}
public function timesheet()
{
return $this->belongsTo('Timesheet');
}
public function project()
{
return $this->belongsTo('Project');
}
public function project_code()
{
return $this->belongsTo('ProjectCode');
}
/**
* @return TimesheetEvent
*/
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
return $entity;
}
public function toChangesArray(TimesheetEvent $other)
{
$attributes_old = parent::toArray();
$attributes_new = $other->toArray();
$skip_keys = ['id' => 1, 'created_at' => 1, 'updated_at' => 1, 'deleted_at' => 1, 'org_data' => 1, 'update_data' => 1];
$zeroisempty_keys = ['discount' => 1];
$result = [];
// Find all the values that where changed or deleted
foreach ($attributes_old as $key => $value) {
// Skip null values, keys we don't care about and 0 value keys that means they are not used
if (empty($value) || isset($skip_keys[$key]) || (isset($zeroisempty_keys[$key]) && $value)) {
continue;
}
// Compare values if it exists in the new array
if (isset($attributes_new[$key]) || array_key_exists($key, $attributes_new)) {
if ($value instanceof \DateTime && $attributes_new[$key] instanceof \DateTime) {
if ($value != $attributes_new[$key]) {
$result[$key] = $attributes_new[$key]->format("Y-m-d H:i:s");
}
} elseif ($value instanceof \DateTime && is_string($attributes_new[$key])) {
if ($value->format("Y-m-d H:i:s") != $attributes_new[$key]) {
$result[$key] = $attributes_new[$key];
}
} elseif (is_string($value) && $attributes_new[$key] instanceof \DateTime) {
if ($attributes_new[$key]->format("Y-m-d H:i:s") != $value) {
$result[$key] = $attributes_new[$key]->format("Y-m-d H:i:s");
}
} elseif ($value != $attributes_new[$key]) {
$result[$key] = $attributes_new[$key];
}
} else {
$result[$key] = null;
}
}
// Find all the values that where deleted
foreach ($attributes_new as $key => $value) {
if (isset($skip_keys[$key])) {
continue;
}
if (!isset($attributes_old[$key])) {
$result[$key] = $value;
}
}
return $result;
}
}

View File

@ -0,0 +1,40 @@
<?php
class TimesheetEventSource extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
public function account()
{
return $this->belongsTo('Account');
}
public function user()
{
return $this->belongsTo('User');
}
public function events()
{
return $this->hasMany('TimesheetEvent');
}
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
return $entity;
}
}

7
app/Models/Timezone.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Timezone extends Eloquent
{
public $timestamps = false;
protected $softDelete = false;
}

170
app/Models/User.php Normal file
View File

@ -0,0 +1,170 @@
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
use Zizaco\Confide\ConfideUser;
class User extends ConfideUser implements UserInterface, RemindableInterface
{
protected $softDelete = true;
public static $rules = array(
/*
'username' => 'required|unique:users',
'password' => 'required|between:6,32|confirmed',
'password_confirmation' => 'between:6,32',
*/
);
protected $updateRules = array(
/*
'email' => 'required|unique:users',
'username' => 'required|unique:users',
*/
);
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'users';
public function account()
{
return $this->belongsTo('Account');
}
public function theme()
{
return $this->belongsTo('Theme');
}
public function getPersonType()
{
return PERSON_USER;
}
/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* @return string
*/
public function getReminderEmail()
{
return $this->email;
}
public function isPro()
{
return $this->account->isPro();
}
public function isDemo()
{
return $this->account->id == Utils::getDemoAccountId();
}
public function maxInvoiceDesignId()
{
return $this->isPro() ? 10 : COUNT_FREE_DESIGNS;
}
public function getDisplayName()
{
if ($this->getFullName()) {
return $this->getFullName();
} elseif ($this->email) {
return $this->email;
} else {
return 'Guest';
}
}
public function getFullName()
{
if ($this->first_name || $this->last_name) {
return $this->first_name.' '.$this->last_name;
} else {
return '';
}
}
public function showGreyBackground()
{
return !$this->theme_id || in_array($this->theme_id, [2, 3, 5, 6, 7, 8, 10, 11, 12]);
}
public function getRequestsCount()
{
return Session::get(SESSION_COUNTER, 0);
}
public function getPopOverText()
{
if (!Utils::isNinja() || !Auth::check() || Session::has('error')) {
return false;
}
$count = self::getRequestsCount();
if ($count == 1 || $count % 5 == 0) {
if (!Utils::isRegistered()) {
return trans('texts.sign_up_to_save');
} elseif (!Auth::user()->account->name) {
return trans('texts.set_name');
}
}
return false;
}
public function afterSave($success = true, $forced = false)
{
if ($this->email) {
return parent::afterSave($success = true, $forced = false);
} else {
return true;
}
}
public function getMaxNumClients()
{
return $this->isPro() ? MAX_NUM_CLIENTS_PRO : MAX_NUM_CLIENTS;
}
public function getRememberToken()
{
return $this->remember_token;
}
public function setRememberToken($value)
{
$this->remember_token = $value;
}
public function getRememberTokenName()
{
return 'remember_token';
}
}

View File

@ -0,0 +1,303 @@
<?php namespace App\Providers;
use App;
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider {
/**
* This namespace is applied to the controller routes in your routes file.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function boot(Router $router)
{
parent::boot($router);
App::before(function($request)
{
// Ensure all request are over HTTPS in production
if (App::environment() == ENV_PRODUCTION)
{
if (!Request::secure())
{
return Redirect::secure(Request::getRequestUri());
}
}
// If the database doens't yet exist we'll skip the rest
if (!Utils::isNinja() && !Utils::isDatabaseSetup())
{
return;
}
// check the application is up to date and for any news feed messages
if (Auth::check())
{
$count = Session::get(SESSION_COUNTER, 0);
Session::put(SESSION_COUNTER, ++$count);
if (!Utils::startsWith($_SERVER['REQUEST_URI'], '/news_feed') && !Session::has('news_feed_id')) {
$data = false;
if (Utils::isNinja()) {
$data = Utils::getNewsFeedResponse();
} else {
$file = @file_get_contents(NINJA_APP_URL . '/news_feed/' . Utils::getUserType() . '/' . NINJA_VERSION);
$data = @json_decode($file);
}
if ($data) {
if ($data->version != NINJA_VERSION) {
$params = [
'user_version' => NINJA_VERSION,
'latest_version'=> $data->version,
'releases_link' => link_to(RELEASES_URL, 'Invoice Ninja', ['target' => '_blank'])
];
Session::put('news_feed_id', NEW_VERSION_AVAILABLE);
Session::put('news_feed_message', trans('texts.new_version_available', $params));
} else {
Session::put('news_feed_id', $data->id);
if ($data->message && $data->id > Auth::user()->news_feed_id) {
Session::put('news_feed_message', $data->message);
}
}
} else {
Session::put('news_feed_id', true);
}
}
}
// Check if we're requesting to change the account's language
if (Input::has('lang'))
{
$locale = Input::get('lang');
App::setLocale($locale);
Session::set(SESSION_LOCALE, $locale);
if (Auth::check())
{
if ($language = Language::whereLocale($locale)->first())
{
$account = Auth::user()->account;
$account->language_id = $language->id;
$account->save();
}
}
}
else if (Auth::check())
{
$locale = Session::get(SESSION_LOCALE, DEFAULT_LOCALE);
App::setLocale($locale);
}
// Make sure the account/user localization settings are in the session
if (Auth::check() && !Session::has(SESSION_TIMEZONE))
{
Event::fire('user.refresh');
}
// Check if the user is claiming a license (ie, additional invoices, white label, etc.)
$claimingLicense = Utils::startsWith($_SERVER['REQUEST_URI'], '/claim_license');
if (!$claimingLicense && Input::has('license_key') && Input::has('product_id'))
{
$licenseKey = Input::get('license_key');
$productId = Input::get('product_id');
$data = trim(file_get_contents((Utils::isNinjaDev() ? 'http://ninja.dev' : NINJA_APP_URL) . "/claim_license?license_key={$licenseKey}&product_id={$productId}"));
if ($productId == PRODUCT_INVOICE_DESIGNS)
{
if ($data = json_decode($data))
{
foreach ($data as $item)
{
$design = new InvoiceDesign();
$design->id = $item->id;
$design->name = $item->name;
$design->javascript = $item->javascript;
$design->save();
}
if (!Utils::isNinjaProd()) {
Cache::forget('invoice_designs_cache_' . Auth::user()->maxInvoiceDesignId());
}
Session::flash('message', trans('texts.bought_designs'));
}
}
else if ($productId == PRODUCT_WHITE_LABEL)
{
if ($data == 'valid')
{
$account = Auth::user()->account;
$account->pro_plan_paid = NINJA_DATE;
$account->save();
Session::flash('message', trans('texts.bought_white_label'));
}
}
}
});
/*
|--------------------------------------------------------------------------
| Authentication Filters
|--------------------------------------------------------------------------
|
| The following filters are used to verify that the user of the current
| session is logged into this application. The "basic" filter easily
| integrates HTTP Basic authentication for quick, simple checking.
|
*/
$router->filter('auth', function()
{
if (Auth::guest())
{
if (Utils::isNinja() || Account::count() == 0)
{
return Redirect::guest('/');
}
else
{
return Redirect::guest('/login');
}
}
});
$router->filter('auth.basic', function()
{
return Auth::basic();
});
$router->filter('api.access', function()
{
$headers = Utils::getApiHeaders();
// check for a valid token
$token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
if ($token) {
Auth::loginUsingId($token->user_id);
Session::set('token_id', $token->id);
} else {
sleep(3);
return Response::make('Invalid token', 403, $headers);
}
if (!Utils::isNinja()) {
return null;
}
if (!Utils::isPro()) {
return Response::make('API requires pro plan', 403, $headers);
} else {
$accountId = Auth::user()->account->id;
// http://stackoverflow.com/questions/1375501/how-do-i-throttle-my-sites-api-users
$hour = 60 * 60;
$hour_limit = 100; # users are limited to 100 requests/hour
$hour_throttle = Cache::get("hour_throttle:{$accountId}", null);
$last_api_request = Cache::get("last_api_request:{$accountId}", 0);
$last_api_diff = time() - $last_api_request;
if (is_null($hour_throttle)) {
$new_hour_throttle = 0;
} else {
$new_hour_throttle = $hour_throttle - $last_api_diff;
$new_hour_throttle = $new_hour_throttle < 0 ? 0 : $new_hour_throttle;
$new_hour_throttle += $hour / $hour_limit;
$hour_hits_remaining = floor(( $hour - $new_hour_throttle ) * $hour_limit / $hour);
$hour_hits_remaining = $hour_hits_remaining >= 0 ? $hour_hits_remaining : 0;
}
if ($new_hour_throttle > $hour) {
$wait = ceil($new_hour_throttle - $hour);
sleep(1);
return Response::make("Please wait {$wait} second(s)", 403, $headers);
}
Cache::put("hour_throttle:{$accountId}", $new_hour_throttle, 10);
Cache::put("last_api_request:{$accountId}", time(), 10);
}
return null;
});
/*
|--------------------------------------------------------------------------
| Guest Filter
|--------------------------------------------------------------------------
|
| The "guest" filter is the counterpart of the authentication filters as
| it simply checks that the current user is not logged in. A redirect
| response will be issued if they are, which you may freely change.
|
*/
$router->filter('guest', function()
{
if (Auth::check()) return Redirect::to('/');
});
/*
|--------------------------------------------------------------------------
| CSRF Protection Filter
|--------------------------------------------------------------------------
|
| The CSRF filter is responsible for protecting your application against
| cross-site request forgery attacks. If this special token in a user
| session does not match the one given in this request, we'll bail.
|
*/
$router->filter('csrf', function()
{
if ($_SERVER['REQUEST_URI'] != '/signup/register')
{
$token = Request::ajax() ? Request::header('X-CSRF-Token') : Input::get('_token');
if (Session::token() != $token)
{
Session::flash('warning', trans('texts.session_expired'));
return Redirect::to('/');
//throw new Illuminate\Session\TokenMismatchException;
}
}
});
}
/**
* Define the routes for the application.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function map(Router $router)
{
$router->group(['namespace' => $this->namespace], function($router)
{
require app_path('Http/routes.php');
});
}
}

View File

@ -0,0 +1,695 @@
<?php
class parseCSV {
/*
Class: parseCSV v0.3.2
http://code.google.com/p/parsecsv-for-php/
Fully conforms to the specifications lined out on wikipedia:
- http://en.wikipedia.org/wiki/Comma-separated_values
Based on the concept of Ming Hong Ng's CsvFileParser class:
- http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
Copyright (c) 2007 Jim Myhrberg (jim@zydev.info).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Code Examples
----------------
# general usage
$csv = new parseCSV('data.csv');
print_r($csv->data);
----------------
# tab delimited, and encoding conversion
$csv = new parseCSV();
$csv->encoding('UTF-16', 'UTF-8');
$csv->delimiter = "\t";
$csv->parse('data.tsv');
print_r($csv->data);
----------------
# auto-detect delimiter character
$csv = new parseCSV();
$csv->auto('data.csv');
print_r($csv->data);
----------------
# modify data in a csv file
$csv = new parseCSV();
$csv->sort_by = 'id';
$csv->parse('data.csv');
# "4" is the value of the "id" column of the CSV row
$csv->data[4] = array('firstname' => 'John', 'lastname' => 'Doe', 'email' => 'john@doe.com');
$csv->save();
----------------
# add row/entry to end of CSV file
# - only recommended when you know the extact sctructure of the file
$csv = new parseCSV();
$csv->save('data.csv', array('1986', 'Home', 'Nowhere', ''), true);
----------------
# convert 2D array to csv data and send headers
# to browser to treat output as a file and download it
$csv = new parseCSV();
$csv->output (true, 'movies.csv', $array);
----------------
*/
/**
* Configuration
* - set these options with $object->var_name = 'value';
*/
# use first line/entry as field names
var $heading = true;
# override field names
var $fields = array();
# sort entries by this field
var $sort_by = null;
var $sort_reverse = false;
# delimiter (comma) and enclosure (double quote)
var $delimiter = ',';
var $enclosure = '"';
# basic SQL-like conditions for row matching
var $conditions = null;
# number of rows to ignore from beginning of data
var $offset = null;
# limits the number of returned rows to specified amount
var $limit = null;
# number of rows to analyze when attempting to auto-detect delimiter
var $auto_depth = 15;
# characters to ignore when attempting to auto-detect delimiter
var $auto_non_chars = "a-zA-Z0-9\n\r";
# preferred delimiter characters, only used when all filtering method
# returns multiple possible delimiters (happens very rarely)
var $auto_preferred = ",;\t.:|";
# character encoding options
var $convert_encoding = false;
var $input_encoding = 'ISO-8859-1';
var $output_encoding = 'ISO-8859-1';
# used by unparse(), save(), and output() functions
var $linefeed = "\r\n";
# only used by output() function
var $output_delimiter = ',';
var $output_filename = 'data.csv';
/**
* Internal variables
*/
# current file
var $file;
# loaded file contents
var $file_data;
# array of field values in data parsed
var $titles = array();
# two dimentional array of CSV data
var $data = array();
/**
* Constructor
* @param input CSV file or string
* @return nothing
*/
function parseCSV ($input = null, $offset = null, $limit = null, $conditions = null) {
if ( $offset !== null ) $this->offset = $offset;
if ( $limit !== null ) $this->limit = $limit;
if ( count($conditions) > 0 ) $this->conditions = $conditions;
if ( !empty($input) ) $this->parse($input);
}
// ==============================================
// ----- [ Main Functions ] ---------------------
// ==============================================
/**
* Parse CSV file or string
* @param input CSV file or string
* @return nothing
*/
function parse ($input = null, $offset = null, $limit = null, $conditions = null) {
if ( !empty($input) ) {
if ( $offset !== null ) $this->offset = $offset;
if ( $limit !== null ) $this->limit = $limit;
if ( count($conditions) > 0 ) $this->conditions = $conditions;
if ( is_readable($input) ) {
$this->data = $this->parse_file($input);
} else {
$this->file_data = &$input;
$this->data = $this->parse_string();
}
if ( $this->data === false ) return false;
}
return true;
}
/**
* Save changes, or new file and/or data
* @param file file to save to
* @param data 2D array with data
* @param append append current data to end of target CSV if exists
* @param fields field names
* @return true or false
*/
function save ($file = null, $data = array(), $append = false, $fields = array()) {
if ( empty($file) ) $file = &$this->file;
$mode = ( $append ) ? 'at' : 'wt' ;
$is_php = ( preg_match('/\.php$/i', $file) ) ? true : false ;
return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode);
}
/**
* Generate CSV based string for output
* @param output if true, prints headers and strings to browser
* @param filename filename sent to browser in headers if output is true
* @param data 2D array with data
* @param fields field names
* @param delimiter delimiter used to separate data
* @return CSV data using delimiter of choice, or default
*/
function output ($output = true, $filename = null, $data = array(), $fields = array(), $delimiter = null) {
if ( empty($filename) ) $filename = $this->output_filename;
if ( $delimiter === null ) $delimiter = $this->output_delimiter;
$data = $this->unparse($data, $fields, null, null, $delimiter);
if ( $output ) {
header('Content-type: application/csv');
header('Content-Disposition: inline; filename="'.$filename.'"');
echo $data;
}
return $data;
}
/**
* Convert character encoding
* @param input input character encoding, uses default if left blank
* @param output output character encoding, uses default if left blank
* @return nothing
*/
function encoding ($input = null, $output = null) {
$this->convert_encoding = true;
if ( $input !== null ) $this->input_encoding = $input;
if ( $output !== null ) $this->output_encoding = $output;
}
/**
* Auto-Detect Delimiter: Find delimiter by analyzing a specific number of
* rows to determine most probable delimiter character
* @param file local CSV file
* @param parse true/false parse file directly
* @param search_depth number of rows to analyze
* @param preferred preferred delimiter characters
* @param enclosure enclosure character, default is double quote (").
* @return delimiter character
*/
function auto ($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
if ( $file === null ) $file = $this->file;
if ( empty($search_depth) ) $search_depth = $this->auto_depth;
if ( $enclosure === null ) $enclosure = $this->enclosure;
if ( $preferred === null ) $preferred = $this->auto_preferred;
if ( empty($this->file_data) ) {
if ( $this->_check_data($file) ) {
$data = &$this->file_data;
} else return false;
} else {
$data = &$this->file_data;
}
$chars = array();
$strlen = strlen($data);
$enclosed = false;
$n = 1;
$to_end = true;
// walk specific depth finding posssible delimiter characters
for ( $i=0; $i < $strlen; $i++ ) {
$ch = $data{$i};
$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
// open and closing quotes
if ( $ch == $enclosure && (!$enclosed || $nch != $enclosure) ) {
$enclosed = ( $enclosed ) ? false : true ;
// inline quotes
} elseif ( $ch == $enclosure && $enclosed ) {
$i++;
// end of row
} elseif ( ($ch == "\n" && $pch != "\r" || $ch == "\r") && !$enclosed ) {
if ( $n >= $search_depth ) {
$strlen = 0;
$to_end = false;
} else {
$n++;
}
// count character
} elseif (!$enclosed) {
if ( !preg_match('/['.preg_quote($this->auto_non_chars, '/').']/i', $ch) ) {
if ( !isset($chars[$ch][$n]) ) {
$chars[$ch][$n] = 1;
} else {
$chars[$ch][$n]++;
}
}
}
}
// filtering
$depth = ( $to_end ) ? $n-1 : $n ;
$filtered = array();
foreach( $chars as $char => $value ) {
if ( $match = $this->_check_count($char, $value, $depth, $preferred) ) {
$filtered[$match] = $char;
}
}
// capture most probable delimiter
ksort($filtered);
$delimiter = reset($filtered);
$this->delimiter = $delimiter;
// parse data
if ( $parse ) $this->data = $this->parse_string();
return $delimiter;
}
// ==============================================
// ----- [ Core Functions ] ---------------------
// ==============================================
/**
* Read file to string and call parse_string()
* @param file local CSV file
* @return 2D array with CSV data, or false on failure
*/
function parse_file ($file = null) {
if ( $file === null ) $file = $this->file;
if ( empty($this->file_data) ) $this->load_data($file);
return ( !empty($this->file_data) ) ? $this->parse_string() : false ;
}
/**
* Parse CSV strings to arrays
* @param data CSV string
* @return 2D array with CSV data, or false on failure
*/
function parse_string ($data = null) {
if ( empty($data) ) {
if ( $this->_check_data() ) {
$data = &$this->file_data;
} else return false;
}
$rows = array();
$row = array();
$row_count = 0;
$current = '';
$head = ( !empty($this->fields) ) ? $this->fields : array() ;
$col = 0;
$enclosed = false;
$was_enclosed = false;
$strlen = strlen($data);
// walk through each character
for ( $i=0; $i < $strlen; $i++ ) {
$ch = $data{$i};
$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
// open and closing quotes
if ( $ch == $this->enclosure && (!$enclosed || $nch != $this->enclosure) ) {
$enclosed = ( $enclosed ) ? false : true ;
if ( $enclosed ) $was_enclosed = true;
// inline quotes
} elseif ( $ch == $this->enclosure && $enclosed ) {
$current .= $ch;
$i++;
// end of field/row
} elseif ( ($ch == $this->delimiter || ($ch == "\n" && $pch != "\r") || $ch == "\r") && !$enclosed ) {
if ( !$was_enclosed ) $current = trim($current);
$key = ( !empty($head[$col]) ) ? $head[$col] : $col ;
$row[$key] = $current;
$current = '';
$col++;
// end of row
if ( $ch == "\n" || $ch == "\r" ) {
if ( $this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions) ) {
if ( $this->heading && empty($head) ) {
$head = $row;
} elseif ( empty($this->fields) || (!empty($this->fields) && (($this->heading && $row_count > 0) || !$this->heading)) ) {
if ( !empty($this->sort_by) && !empty($row[$this->sort_by]) ) {
if ( isset($rows[$row[$this->sort_by]]) ) {
$rows[$row[$this->sort_by].'_0'] = &$rows[$row[$this->sort_by]];
unset($rows[$row[$this->sort_by]]);
for ( $sn=1; isset($rows[$row[$this->sort_by].'_'.$sn]); $sn++ ) {}
$rows[$row[$this->sort_by].'_'.$sn] = $row;
} else $rows[$row[$this->sort_by]] = $row;
} else $rows[] = $row;
}
}
$row = array();
$col = 0;
$row_count++;
if ( $this->sort_by === null && $this->limit !== null && count($rows) == $this->limit ) {
$i = $strlen;
}
}
// append character to current field
} else {
$current .= $ch;
}
}
$this->titles = $head;
if ( !empty($this->sort_by) ) {
( $this->sort_reverse ) ? krsort($rows) : ksort($rows) ;
if ( $this->offset !== null || $this->limit !== null ) {
$rows = array_slice($rows, ($this->offset === null ? 0 : $this->offset) , $this->limit, true);
}
}
return $rows;
}
/**
* Create CSV data from array
* @param data 2D array with data
* @param fields field names
* @param append if true, field names will not be output
* @param is_php if a php die() call should be put on the first
* line of the file, this is later ignored when read.
* @param delimiter field delimiter to use
* @return CSV data (text string)
*/
function unparse ( $data = array(), $fields = array(), $append = false , $is_php = false, $delimiter = null) {
if ( !is_array($data) || empty($data) ) $data = &$this->data;
if ( !is_array($fields) || empty($fields) ) $fields = &$this->titles;
if ( $delimiter === null ) $delimiter = $this->delimiter;
$string = ( $is_php ) ? "<?php header('Status: 403'); die(' '); ?>".$this->linefeed : '' ;
$entry = array();
// create heading
if ( $this->heading && !$append ) {
foreach( $fields as $key => $value ) {
$entry[] = $this->_enclose_value($value);
}
$string .= implode($delimiter, $entry).$this->linefeed;
$entry = array();
}
// create data
foreach( $data as $key => $row ) {
foreach( $row as $field => $value ) {
$entry[] = $this->_enclose_value($value);
}
$string .= implode($delimiter, $entry).$this->linefeed;
$entry = array();
}
return $string;
}
/**
* Load local file or string
* @param input local CSV file
* @return true or false
*/
function load_data ($input = null) {
$data = null;
$file = null;
if ( $input === null ) {
$file = $this->file;
} elseif ( file_exists($input) ) {
$file = $input;
} else {
$data = $input;
}
if ( !empty($data) || $data = $this->_rfile($file) ) {
if ( $this->file != $file ) $this->file = $file;
if ( preg_match('/\.php$/i', $file) && preg_match('/<\?.*?\?>(.*)/ims', $data, $strip) ) {
$data = ltrim($strip[1]);
}
if ( $this->convert_encoding ) $data = iconv($this->input_encoding, $this->output_encoding, $data);
if ( substr($data, -1) != "\n" ) $data .= "\n";
$this->file_data = &$data;
return true;
}
return false;
}
// ==============================================
// ----- [ Internal Functions ] -----------------
// ==============================================
/**
* Validate a row against specified conditions
* @param row array with values from a row
* @param conditions specified conditions that the row must match
* @return true of false
*/
function _validate_row_conditions ($row = array(), $conditions = null) {
if ( !empty($row) ) {
if ( !empty($conditions) ) {
$conditions = (strpos($conditions, ' OR ') !== false) ? explode(' OR ', $conditions) : array($conditions) ;
$or = '';
foreach( $conditions as $key => $value ) {
if ( strpos($value, ' AND ') !== false ) {
$value = explode(' AND ', $value);
$and = '';
foreach( $value as $k => $v ) {
$and .= $this->_validate_row_condition($row, $v);
}
$or .= (strpos($and, '0') !== false) ? '0' : '1' ;
} else {
$or .= $this->_validate_row_condition($row, $value);
}
}
return (strpos($or, '1') !== false) ? true : false ;
}
return true;
}
return false;
}
/**
* Validate a row against a single condition
* @param row array with values from a row
* @param condition specified condition that the row must match
* @return true of false
*/
function _validate_row_condition ($row, $condition) {
$operators = array(
'=', 'equals', 'is',
'!=', 'is not',
'<', 'is less than',
'>', 'is greater than',
'<=', 'is less than or equals',
'>=', 'is greater than or equals',
'contains',
'does not contain',
);
$operators_regex = array();
foreach( $operators as $value ) {
$operators_regex[] = preg_quote($value, '/');
}
$operators_regex = implode('|', $operators_regex);
if ( preg_match('/^(.+) ('.$operators_regex.') (.+)$/i', trim($condition), $capture) ) {
$field = $capture[1];
$op = $capture[2];
$value = $capture[3];
if ( preg_match('/^([\'\"]{1})(.*)([\'\"]{1})$/i', $value, $capture) ) {
if ( $capture[1] == $capture[3] ) {
$value = $capture[2];
$value = str_replace("\\n", "\n", $value);
$value = str_replace("\\r", "\r", $value);
$value = str_replace("\\t", "\t", $value);
$value = stripslashes($value);
}
}
if ( array_key_exists($field, $row) ) {
if ( ($op == '=' || $op == 'equals' || $op == 'is') && $row[$field] == $value ) {
return '1';
} elseif ( ($op == '!=' || $op == 'is not') && $row[$field] != $value ) {
return '1';
} elseif ( ($op == '<' || $op == 'is less than' ) && $row[$field] < $value ) {
return '1';
} elseif ( ($op == '>' || $op == 'is greater than') && $row[$field] > $value ) {
return '1';
} elseif ( ($op == '<=' || $op == 'is less than or equals' ) && $row[$field] <= $value ) {
return '1';
} elseif ( ($op == '>=' || $op == 'is greater than or equals') && $row[$field] >= $value ) {
return '1';
} elseif ( $op == 'contains' && preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) {
return '1';
} elseif ( $op == 'does not contain' && !preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) {
return '1';
} else {
return '0';
}
}
}
return '1';
}
/**
* Validates if the row is within the offset or not if sorting is disabled
* @param current_row the current row number being processed
* @return true of false
*/
function _validate_offset ($current_row) {
if ( $this->sort_by === null && $this->offset !== null && $current_row < $this->offset ) return false;
return true;
}
/**
* Enclose values if needed
* - only used by unparse()
* @param value string to process
* @return Processed value
*/
function _enclose_value ($value = null) {
if ( $value !== null && $value != '' ) {
$delimiter = preg_quote($this->delimiter, '/');
$enclosure = preg_quote($this->enclosure, '/');
if ( preg_match("/".$delimiter."|".$enclosure."|\n|\r/i", $value) || ($value{0} == ' ' || substr($value, -1) == ' ') ) {
$value = str_replace($this->enclosure, $this->enclosure.$this->enclosure, $value);
$value = $this->enclosure.$value.$this->enclosure;
}
}
return $value;
}
/**
* Check file data
* @param file local filename
* @return true or false
*/
function _check_data ($file = null) {
if ( empty($this->file_data) ) {
if ( $file === null ) $file = $this->file;
return $this->load_data($file);
}
return true;
}
/**
* Check if passed info might be delimiter
* - only used by find_delimiter()
* @return special string used for delimiter selection, or false
*/
function _check_count ($char, $array, $depth, $preferred) {
if ( $depth == count($array) ) {
$first = null;
$equal = null;
$almost = false;
foreach( $array as $key => $value ) {
if ( $first == null ) {
$first = $value;
} elseif ( $value == $first && $equal !== false) {
$equal = true;
} elseif ( $value == $first+1 && $equal !== false ) {
$equal = true;
$almost = true;
} else {
$equal = false;
}
}
if ( $equal ) {
$match = ( $almost ) ? 2 : 1 ;
$pref = strpos($preferred, $char);
$pref = ( $pref !== false ) ? str_pad($pref, 3, '0', STR_PAD_LEFT) : '999' ;
return $pref.$match.'.'.(99999 - str_pad($first, 5, '0', STR_PAD_LEFT));
} else return false;
}
}
/**
* Read local file
* @param file local filename
* @return Data from file, or false on failure
*/
function _rfile ($file = null) {
if ( is_readable($file) ) {
if ( !($fh = fopen($file, 'r')) ) return false;
$data = fread($fh, filesize($file));
fclose($fh);
return $data;
}
return false;
}
/**
* Write to local file
* @param file local filename
* @param string data to write to file
* @param mode fopen() mode
* @param lock flock() mode
* @return true or false
*/
function _wfile ($file, $string = '', $mode = 'wb', $lock = 2) {
if ( $fp = fopen($file, $mode) ) {
flock($fp, $lock);
$re = fwrite($fp, $string);
$re2 = fclose($fp);
if ( $re != false && $re2 != false ) return true;
}
return false;
}
}
?>

604
app/libraries/Utils.php Normal file
View File

@ -0,0 +1,604 @@
<?php namespace App\libraries;
class Utils
{
public static function isRegistered()
{
return Auth::check() && Auth::user()->registered;
}
public static function isConfirmed()
{
return Auth::check() && Auth::user()->confirmed;
}
public static function isDatabaseSetup()
{
try {
if (Schema::hasTable('accounts')) {
return true;
}
} catch (Exception $e) {
return false;
}
}
public static function isProd()
{
return App::environment() == ENV_PRODUCTION;
}
public static function isNinja()
{
return self::isNinjaProd() || self::isNinjaDev();
}
public static function isNinjaProd()
{
return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'];
}
public static function isNinjaDev()
{
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'];
}
public static function isPro()
{
return Auth::check() && Auth::user()->isPro();
}
public static function getUserType()
{
if (Utils::isNinja()) {
return USER_TYPE_CLOUD_HOST;
} else {
return USER_TYPE_SELF_HOST;
}
}
public static function getDemoAccountId()
{
return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false;
}
public static function isDemo()
{
return Auth::check() && Auth::user()->isDemo();
}
public static function getNewsFeedResponse($userType = false)
{
if (!$userType) {
$userType = Utils::getUserType();
}
$response = new stdClass();
$response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : '';
$response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : '';
$response->version = NINJA_VERSION;
return $response;
}
public static function getProLabel($feature)
{
if (Auth::check()
&& !Auth::user()->isPro()
&& $feature == ACCOUNT_ADVANCED_SETTINGS) {
return '&nbsp;<sup class="pro-label">PRO</sup>';
} else {
return '';
}
}
public static function basePath()
{
return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1);
}
public static function trans($input)
{
$data = [];
foreach ($input as $field) {
if ($field == "checkbox") {
$data[] = $field;
} else {
$data[] = trans("texts.$field");
}
}
return $data;
}
public static function fatalError($message = false, $exception = false)
{
if (!$message) {
$message = "An error occurred, please try again later.";
}
static::logError($message.' '.$exception);
$data = [
'showBreadcrumbs' => false,
'hideHeader' => true,
];
return View::make('error', $data)->with('error', $message);
}
public static function getErrorString($exception)
{
return "{$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
}
public static function logError($error, $context = 'PHP')
{
$count = Session::get('error_count', 0);
Session::put('error_count', ++$count);
if ($count > 100) {
return 'logged';
}
$data = [
'context' => $context,
'user_id' => Auth::check() ? Auth::user()->id : 0,
'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '',
'url' => Input::get('url', Request::url()),
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
'ip' => Request::getClientIp(),
'count' => Session::get('error_count', 0),
];
Log::error($error."\n", $data);
/*
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
{
$message->to($email)->subject($subject);
});
*/
}
public static function parseFloat($value)
{
$value = preg_replace('/[^0-9\.\-]/', '', $value);
return floatval($value);
}
public static function formatPhoneNumber($phoneNumber)
{
$phoneNumber = preg_replace('/[^0-9a-zA-Z]/', '', $phoneNumber);
if (!$phoneNumber) {
return '';
}
if (strlen($phoneNumber) > 10) {
$countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10);
$areaCode = substr($phoneNumber, -10, 3);
$nextThree = substr($phoneNumber, -7, 3);
$lastFour = substr($phoneNumber, -4, 4);
$phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour;
} elseif (strlen($phoneNumber) == 10 && in_array(substr($phoneNumber, 0, 3), array(653, 656, 658, 659))) {
/**
* SG country code are 653, 656, 658, 659
* US area code consist of 650, 651 and 657
* @see http://en.wikipedia.org/wiki/Telephone_numbers_in_Singapore#Numbering_plan
* @see http://www.bennetyee.org/ucsd-pages/area.html
*/
$countryCode = substr($phoneNumber, 0, 2);
$nextFour = substr($phoneNumber, 2, 4);
$lastFour = substr($phoneNumber, 6, 4);
$phoneNumber = '+'.$countryCode.' '.$nextFour.' '.$lastFour;
} elseif (strlen($phoneNumber) == 10) {
$areaCode = substr($phoneNumber, 0, 3);
$nextThree = substr($phoneNumber, 3, 3);
$lastFour = substr($phoneNumber, 6, 4);
$phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour;
} elseif (strlen($phoneNumber) == 7) {
$nextThree = substr($phoneNumber, 0, 3);
$lastFour = substr($phoneNumber, 3, 4);
$phoneNumber = $nextThree.'-'.$lastFour;
}
return $phoneNumber;
}
public static function formatMoney($value, $currencyId = false)
{
if (!$currencyId) {
$currencyId = Session::get(SESSION_CURRENCY);
}
$currency = Currency::remember(DEFAULT_QUERY_CACHE)->find($currencyId);
if (!$currency) {
$currency = Currency::remember(DEFAULT_QUERY_CACHE)->find(1);
}
return $currency->symbol.number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
}
public static function pluralize($string, $count)
{
$field = $count == 1 ? $string : $string.'s';
$string = trans("texts.$field", ['count' => $count]);
return $string;
}
public static function toArray($data)
{
return json_decode(json_encode((array) $data), true);
}
public static function toSpaceCase($camelStr)
{
return preg_replace('/([a-z])([A-Z])/s', '$1 $2', $camelStr);
}
public static function timestampToDateTimeString($timestamp)
{
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
return Utils::timestampToString($timestamp, $timezone, $format);
}
public static function timestampToDateString($timestamp)
{
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
return Utils::timestampToString($timestamp, $timezone, $format);
}
public static function dateToString($date)
{
$dateTime = new DateTime($date);
$timestamp = $dateTime->getTimestamp();
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
return Utils::timestampToString($timestamp, false, $format);
}
public static function timestampToString($timestamp, $timezone = false, $format)
{
if (!$timestamp) {
return '';
}
$date = Carbon::createFromTimeStamp($timestamp);
if ($timezone) {
$date->tz = $timezone;
}
if ($date->year < 1900) {
return '';
}
return $date->format($format);
}
public static function toSqlDate($date, $formatResult = true)
{
if (!$date) {
return;
}
$timezone = Session::get(SESSION_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT);
$dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone));
return $formatResult ? $dateTime->format('Y-m-d') : $dateTime;
}
public static function fromSqlDate($date, $formatResult = true)
{
if (!$date || $date == '0000-00-00') {
return '';
}
$timezone = Session::get(SESSION_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT);
$dateTime = DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone));
return $formatResult ? $dateTime->format($format) : $dateTime;
}
public static function today($formatResult = true)
{
$timezone = Session::get(SESSION_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT);
$date = date_create(null, new DateTimeZone($timezone));
if ($formatResult) {
return $date->format($format);
} else {
return $date;
}
}
public static function trackViewed($name, $type, $url = false)
{
if (!$url) {
$url = Request::url();
}
$viewed = Session::get(RECENTLY_VIEWED);
if (!$viewed) {
$viewed = [];
}
$object = new stdClass();
$object->url = $url;
$object->name = ucwords($type).': '.$name;
$data = [];
for ($i = 0; $i<count($viewed); $i++) {
$item = $viewed[$i];
if ($object->url == $item->url || $object->name == $item->name) {
continue;
}
array_unshift($data, $item);
}
array_unshift($data, $object);
if (count($data) > RECENTLY_VIEWED_LIMIT) {
array_pop($data);
}
Session::put(RECENTLY_VIEWED, $data);
}
public static function processVariables($str)
{
if (!$str) {
return '';
}
$variables = ['MONTH', 'QUARTER', 'YEAR'];
for ($i = 0; $i<count($variables); $i++) {
$variable = $variables[$i];
$regExp = '/:'.$variable.'[+-]?[\d]*/';
preg_match_all($regExp, $str, $matches);
$matches = $matches[0];
if (count($matches) == 0) {
continue;
}
foreach ($matches as $match) {
$offset = 0;
$addArray = explode('+', $match);
$minArray = explode('-', $match);
if (count($addArray) > 1) {
$offset = intval($addArray[1]);
} elseif (count($minArray) > 1) {
$offset = intval($minArray[1]) * -1;
}
$val = Utils::getDatePart($variable, $offset);
$str = str_replace($match, $val, $str);
}
}
return $str;
}
private static function getDatePart($part, $offset)
{
$offset = intval($offset);
if ($part == 'MONTH') {
return Utils::getMonth($offset);
} elseif ($part == 'QUARTER') {
return Utils::getQuarter($offset);
} elseif ($part == 'YEAR') {
return Utils::getYear($offset);
}
}
private static function getMonth($offset)
{
$months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December", ];
$month = intval(date('n')) - 1;
$month += $offset;
$month = $month % 12;
if ($month < 0) {
$month += 12;
}
return $months[$month];
}
private static function getQuarter($offset)
{
$month = intval(date('n')) - 1;
$quarter = floor(($month + 3) / 3);
$quarter += $offset;
$quarter = $quarter % 4;
if ($quarter == 0) {
$quarter = 4;
}
return 'Q'.$quarter;
}
private static function getYear($offset)
{
$year = intval(date('Y'));
return $year + $offset;
}
public static function getEntityName($entityType)
{
return ucwords(str_replace('_', ' ', $entityType));
}
public static function getClientDisplayName($model)
{
if ($model->client_name) {
return $model->client_name;
} elseif ($model->first_name || $model->last_name) {
return $model->first_name.' '.$model->last_name;
} else {
return $model->email;
}
}
public static function encodeActivity($person = null, $action, $entity = null, $otherPerson = null)
{
$person = $person ? $person->getDisplayName() : '<i>System</i>';
$entity = $entity ? '['.$entity->getActivityKey().']' : '';
$otherPerson = $otherPerson ? 'to '.$otherPerson->getDisplayName() : '';
$token = Session::get('token_id') ? ' ('.trans('texts.token').')' : '';
return trim("$person $token $action $entity $otherPerson");
}
public static function decodeActivity($message)
{
$pattern = '/\[([\w]*):([\d]*):(.*)\]/i';
preg_match($pattern, $message, $matches);
if (count($matches) > 0) {
$match = $matches[0];
$type = $matches[1];
$publicId = $matches[2];
$name = $matches[3];
$link = link_to($type.'s/'.$publicId, $name);
$message = str_replace($match, "$type $link", $message);
}
return $message;
}
public static function generateLicense()
{
$parts = [];
for ($i = 0; $i<5; $i++) {
$parts[] = strtoupper(str_random(4));
}
return implode('-', $parts);
}
public static function lookupEventId($eventName)
{
if ($eventName == 'create_client') {
return EVENT_CREATE_CLIENT;
} elseif ($eventName == 'create_invoice') {
return EVENT_CREATE_INVOICE;
} elseif ($eventName == 'create_quote') {
return EVENT_CREATE_QUOTE;
} elseif ($eventName == 'create_payment') {
return EVENT_CREATE_PAYMENT;
} else {
return false;
}
}
public static function notifyZapier($subscription, $data)
{
$curl = curl_init();
$jsonEncodedData = json_encode($data->toJson());
$opts = [
CURLOPT_URL => $subscription->target_url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $jsonEncodedData,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
];
curl_setopt_array($curl, $opts);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($status == 410) {
$subscription->delete();
}
}
public static function remapPublicIds(array $data)
{
$return = [];
foreach ($data as $key => $val) {
if ($key === 'public_id') {
$key = 'id';
} elseif (strpos($key, '_id')) {
continue;
}
if (is_array($val)) {
$val = Utils::remapPublicIds($val);
}
$return[$key] = $val;
}
return $return;
}
public static function getApiHeaders($count = 0)
{
return [
'Content-Type' => 'application/json',
//'Access-Control-Allow-Origin' => '*',
//'Access-Control-Allow-Methods' => 'GET',
//'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With',
//'Access-Control-Allow-Credentials' => 'true',
'X-Total-Count' => $count,
//'X-Rate-Limit-Limit' - The number of allowed requests in the current period
//'X-Rate-Limit-Remaining' - The number of remaining requests in the current period
//'X-Rate-Limit-Reset' - The number of seconds left in the current period,
];
}
public static function startsWith($haystack, $needle)
{
return $needle === "" || strpos($haystack, $needle) === 0;
}
public static function endsWith($haystack, $needle)
{
return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
}
public static function getEntityRowClass($model)
{
$str = $model->is_deleted || ($model->deleted_at && $model->deleted_at != '0000-00-00') ? 'DISABLED ' : '';
if ($model->is_deleted) {
$str .= 'ENTITY_DELETED ';
}
if ($model->deleted_at && $model->deleted_at != '0000-00-00') {
$str .= 'ENTITY_ARCHIVED ';
}
return $str;
}
}

View File

@ -0,0 +1,104 @@
<?php namespace ninja\mailers;
use Invoice;
use Payment;
use Contact;
use Invitation;
use Activity;
use Utils;
class ContactMailer extends Mailer
{
public function sendInvoice(Invoice $invoice)
{
$invoice->load('invitations', 'client', 'account');
$entityType = $invoice->getEntityType();
$view = 'invoice';
$subject = trans("texts.{$entityType}_subject", ['invoice' => $invoice->invoice_number, 'account' => $invoice->account->getDisplayName()]);
$accountName = $invoice->account->getDisplayName();
$emailTemplate = $invoice->account->getEmailTemplate($entityType);
$invoiceAmount = Utils::formatMoney($invoice->amount, $invoice->client->currency_id);
foreach ($invoice->invitations as $invitation) {
if (!$invitation->user || !$invitation->user->email) {
return false;
}
if (!$invitation->contact || !$invitation->contact->email) {
return false;
}
$invitation->sent_date = \Carbon::now()->toDateTimeString();
$invitation->save();
$variables = [
'$footer' => $invoice->account->getEmailFooter(),
'$link' => $invitation->getLink(),
'$client' => $invoice->client->getDisplayName(),
'$account' => $accountName,
'$contact' => $invitation->contact->getDisplayName(),
'$amount' => $invoiceAmount
];
$data['body'] = str_replace(array_keys($variables), array_values($variables), $emailTemplate);
$data['link'] = $invitation->getLink();
$data['entityType'] = $entityType;
$fromEmail = $invitation->user->email;
$this->sendTo($invitation->contact->email, $fromEmail, $accountName, $subject, $view, $data);
Activity::emailInvoice($invitation);
}
if (!$invoice->isSent()) {
$invoice->invoice_status_id = INVOICE_STATUS_SENT;
$invoice->save();
}
\Event::fire('invoice.sent', $invoice);
}
public function sendPaymentConfirmation(Payment $payment)
{
$invoice = $payment->invoice;
$view = 'payment_confirmation';
$subject = trans('texts.payment_subject', ['invoice' => $invoice->invoice_number]);
$accountName = $payment->account->getDisplayName();
$emailTemplate = $invoice->account->getEmailTemplate(ENTITY_PAYMENT);
$variables = [
'$footer' => $payment->account->getEmailFooter(),
'$client' => $payment->client->getDisplayName(),
'$account' => $accountName,
'$amount' => Utils::formatMoney($payment->amount, $payment->client->currency_id)
];
$data = ['body' => str_replace(array_keys($variables), array_values($variables), $emailTemplate)];
$user = $payment->invitation->user;
$this->sendTo($payment->contact->email, $user->email, $accountName, $subject, $view, $data);
}
public function sendLicensePaymentConfirmation($name, $email, $amount, $license, $productId)
{
$view = 'license_confirmation';
$subject = trans('texts.payment_subject');
if ($productId == PRODUCT_ONE_CLICK_INSTALL) {
$license = "Softaculous install license: $license";
} elseif ($productId == PRODUCT_INVOICE_DESIGNS) {
$license = "Invoice designs license: $license";
} elseif ($productId == PRODUCT_WHITE_LABEL) {
$license = "White label license: $license";
}
$data = [
'account' => trans('texts.email_from'),
'client' => $name,
'amount' => Utils::formatMoney($amount, 1),
'license' => $license
];
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
}
}

View File

@ -0,0 +1,27 @@
<?php namespace ninja\mailers;
use Mail;
use Utils;
class Mailer
{
public function sendTo($toEmail, $fromEmail, $fromName, $subject, $view, $data = [])
{
$views = [
'emails.'.$view.'_html',
'emails.'.$view.'_text',
];
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject) {
$replyEmail = $fromEmail;
// http://stackoverflow.com/questions/2421234/gmail-appearing-to-ignore-reply-to
if (Utils::isNinja() && $toEmail != CONTACT_EMAIL) {
$fromEmail = NINJA_FROM_EMAIL;
}
//$message->setEncoder(\Swift_Encoding::get8BitEncoding());
$message->to($toEmail)->from($fromEmail, $fromName)->replyTo($replyEmail, $fromName)->subject($subject);
});
}
}

View File

@ -0,0 +1,62 @@
<?php namespace ninja\mailers;
use Invoice;
use Payment;
use User;
use Utils;
class UserMailer extends Mailer
{
public function sendConfirmation(User $user, User $invitor = null)
{
if (!$user->email) {
return;
}
$view = 'confirm';
$subject = trans('texts.confirmation_subject');
$data = [
'user' => $user,
'invitationMessage' => $invitor ? trans('texts.invitation_message', ['invitor' => $invitor->getDisplayName()]) : '',
];
if ($invitor) {
$fromEmail = $invitor->email;
$fromName = $invitor->getDisplayName();
} else {
$fromEmail = CONTACT_EMAIL;
$fromName = CONTACT_NAME;
}
$this->sendTo($user->email, $fromEmail, $fromName, $subject, $view, $data);
}
public function sendNotification(User $user, Invoice $invoice, $notificationType, Payment $payment = null)
{
if (!$user->email) {
return;
}
$view = 'invoice_'.$notificationType;
$entityType = $invoice->getEntityType();
$data = [
'entityType' => $entityType,
'clientName' => $invoice->client->getDisplayName(),
'accountName' => $invoice->account->getDisplayName(),
'userName' => $user->getDisplayName(),
'invoiceAmount' => Utils::formatMoney($invoice->amount, $invoice->client->currency_id),
'invoiceNumber' => $invoice->invoice_number,
'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}",
];
if ($payment) {
$data['paymentAmount'] = Utils::formatMoney($payment->amount, $invoice->client->currency_id);
}
$subject = trans("texts.notification_{$entityType}_{$notificationType}_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->getDisplayName()]);
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
}
}

View File

@ -0,0 +1,234 @@
<?php namespace ninja\repositories;
use Client;
use Contact;
use Account;
use Request;
use Session;
use Language;
use User;
use Auth;
use Invitation;
use Invoice;
use InvoiceItem;
use AccountGateway;
use Utils;
class AccountRepository
{
public function create()
{
$account = new Account();
$account->ip = Request::getClientIp();
$account->account_key = str_random(RANDOM_KEY_LENGTH);
if (Session::has(SESSION_LOCALE)) {
$locale = Session::get(SESSION_LOCALE);
if ($language = Language::whereLocale($locale)->first()) {
$account->language_id = $language->id;
}
}
$account->save();
$random = str_random(RANDOM_KEY_LENGTH);
$user = new User();
$user->password = $random;
$user->password_confirmation = $random;
$user->username = $random;
$user->confirmed = !Utils::isNinja();
$account->users()->save($user, []);
return $account;
}
public function getSearchData()
{
$clients = \DB::table('clients')
->where('clients.deleted_at', '=', null)
->where('clients.account_id', '=', \Auth::user()->account_id)
->whereRaw("clients.name <> ''")
->select(\DB::raw("'Clients' as type, clients.public_id, clients.name, '' as token"));
$contacts = \DB::table('clients')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('clients.deleted_at', '=', null)
->where('clients.account_id', '=', \Auth::user()->account_id)
->whereRaw("CONCAT(contacts.first_name, contacts.last_name, contacts.email) <> ''")
->select(\DB::raw("'Contacts' as type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ' ', contacts.email) as name, '' as token"));
$invoices = \DB::table('clients')
->join('invoices', 'invoices.client_id', '=', 'clients.id')
->where('clients.account_id', '=', \Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('invoices.deleted_at', '=', null)
->select(\DB::raw("'Invoices' as type, invoices.public_id, CONCAT(invoices.invoice_number, ': ', clients.name) as name, invoices.invoice_number as token"));
$data = [];
foreach ($clients->union($contacts)->union($invoices)->get() as $row) {
$type = $row->type;
if (!isset($data[$type])) {
$data[$type] = [];
}
$tokens = explode(' ', $row->name);
$tokens[] = $type;
if ($type == 'Invoices') {
$tokens[] = intVal($row->token).'';
}
$data[$type][] = [
'value' => $row->name,
'public_id' => $row->public_id,
'tokens' => $tokens,
];
}
return $data;
}
public function enableProPlan()
{
if (Auth::user()->isPro()) {
return false;
}
$ninjaAccount = $this->getNinjaAccount();
$lastInvoice = Invoice::withTrashed()->whereAccountId($ninjaAccount->id)->orderBy('public_id', 'DESC')->first();
$publicId = $lastInvoice ? ($lastInvoice->public_id + 1) : 1;
$ninjaClient = $this->getNinjaClient($ninjaAccount);
$invitation = $this->createNinjaInvoice($publicId, $ninjaAccount, $ninjaClient);
return $invitation;
}
private function createNinjaInvoice($publicId, $account, $client)
{
$invoice = new Invoice();
$invoice->account_id = $account->id;
$invoice->user_id = $account->users()->first()->id;
$invoice->public_id = $publicId;
$invoice->client_id = $client->id;
$invoice->invoice_number = $account->getNextInvoiceNumber();
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->amount = PRO_PLAN_PRICE;
$invoice->balance = PRO_PLAN_PRICE;
$invoice->save();
$item = new InvoiceItem();
$item->account_id = $account->id;
$item->user_id = $account->users()->first()->id;
$item->public_id = $publicId;
$item->qty = 1;
$item->cost = PRO_PLAN_PRICE;
$item->notes = trans('texts.pro_plan_description');
$item->product_key = trans('texts.pro_plan_product');
$invoice->invoice_items()->save($item);
$invitation = new Invitation();
$invitation->account_id = $account->id;
$invitation->user_id = $account->users()->first()->id;
$invitation->public_id = $publicId;
$invitation->invoice_id = $invoice->id;
$invitation->contact_id = $client->contacts()->first()->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
return $invitation;
}
public function getNinjaAccount()
{
$account = Account::whereAccountKey(NINJA_ACCOUNT_KEY)->first();
if ($account) {
return $account;
} else {
$account = new Account();
$account->name = 'Invoice Ninja';
$account->work_email = 'contact@invoiceninja.com';
$account->work_phone = '(800) 763-1948';
$account->account_key = NINJA_ACCOUNT_KEY;
$account->save();
$random = str_random(RANDOM_KEY_LENGTH);
$user = new User();
$user->registered = true;
$user->confirmed = true;
$user->email = 'contact@invoiceninja.com';
$user->password = $random;
$user->password_confirmation = $random;
$user->username = $random;
$user->first_name = 'Invoice';
$user->last_name = 'Ninja';
$user->notify_sent = true;
$user->notify_paid = true;
$account->users()->save($user);
$accountGateway = new AccountGateway();
$accountGateway->user_id = $user->id;
$accountGateway->gateway_id = NINJA_GATEWAY_ID;
$accountGateway->public_id = 1;
$accountGateway->config = NINJA_GATEWAY_CONFIG;
$account->account_gateways()->save($accountGateway);
}
return $account;
}
private function getNinjaClient($ninjaAccount)
{
$client = Client::whereAccountId($ninjaAccount->id)->wherePublicId(Auth::user()->account_id)->first();
if (!$client) {
$client = new Client();
$client->public_id = Auth::user()->account_id;
$client->user_id = $ninjaAccount->users()->first()->id;
$client->currency_id = 1;
foreach (['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'work_phone'] as $field) {
$client->$field = Auth::user()->account->$field;
}
$ninjaAccount->clients()->save($client);
$contact = new Contact();
$contact->user_id = $ninjaAccount->users()->first()->id;
$contact->account_id = $ninjaAccount->id;
$contact->public_id = Auth::user()->account_id;
$contact->is_primary = true;
foreach (['first_name', 'last_name', 'email', 'phone'] as $field) {
$contact->$field = Auth::user()->$field;
}
$client->contacts()->save($contact);
}
return $client;
}
public function registerUser($user)
{
$url = NINJA_APP_URL.'/signup/register';
$data = '';
$fields = [
'first_name' => urlencode($user->first_name),
'last_name' => urlencode($user->last_name),
'email' => urlencode($user->email),
];
foreach ($fields as $key => $value) {
$data .= $key.'='.$value.'&';
}
rtrim($data, '&');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_exec($ch);
curl_close($ch);
}
}

View File

@ -0,0 +1,202 @@
<?php namespace ninja\repositories;
use Client;
use Contact;
class ClientRepository
{
public function find($filter = null)
{
$query = \DB::table('clients')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('clients.account_id', '=', \Auth::user()->account_id)
->where('contacts.is_primary', '=', true)
->where('contacts.deleted_at', '=', null)
->select('clients.public_id', 'clients.name', 'contacts.first_name', 'contacts.last_name', 'clients.balance', 'clients.last_login', 'clients.created_at', 'clients.work_phone', 'contacts.email', 'clients.currency_id', 'clients.deleted_at', 'clients.is_deleted');
if (!\Session::get('show_trash:client')) {
$query->where('clients.deleted_at', '=', null);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('contacts.email', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function getErrors($data)
{
$contact = isset($data['contacts']) ? (array) $data['contacts'][0] : (isset($data['contact']) ? $data['contact'] : []);
$validator = \Validator::make($contact, ['email' => 'required|email']);
if ($validator->fails()) {
return $validator->messages();
}
return false;
}
public function save($publicId, $data, $notify = true)
{
if (!$publicId || $publicId == "-1") {
$client = Client::createNew();
$client->currency_id = 1;
$contact = Contact::createNew();
$contact->is_primary = true;
$contact->send_invoice = true;
} else {
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
$contact = $client->contacts()->where('is_primary', '=', true)->firstOrFail();
}
if (isset($data['name'])) {
$client->name = trim($data['name']);
}
if (isset($data['id_number'])) {
$client->id_number = trim($data['id_number']);
}
if (isset($data['vat_number'])) {
$client->vat_number = trim($data['vat_number']);
}
if (isset($data['work_phone'])) {
$client->work_phone = trim($data['work_phone']);
}
if (isset($data['custom_value1'])) {
$client->custom_value1 = trim($data['custom_value1']);
}
if (isset($data['custom_value2'])) {
$client->custom_value2 = trim($data['custom_value2']);
}
if (isset($data['address1'])) {
$client->address1 = trim($data['address1']);
}
if (isset($data['address2'])) {
$client->address2 = trim($data['address2']);
}
if (isset($data['city'])) {
$client->city = trim($data['city']);
}
if (isset($data['state'])) {
$client->state = trim($data['state']);
}
if (isset($data['postal_code'])) {
$client->postal_code = trim($data['postal_code']);
}
if (isset($data['country_id'])) {
$client->country_id = $data['country_id'] ? $data['country_id'] : null;
}
if (isset($data['private_notes'])) {
$client->private_notes = trim($data['private_notes']);
}
if (isset($data['size_id'])) {
$client->size_id = $data['size_id'] ? $data['size_id'] : null;
}
if (isset($data['industry_id'])) {
$client->industry_id = $data['industry_id'] ? $data['industry_id'] : null;
}
if (isset($data['currency_id'])) {
$client->currency_id = $data['currency_id'] ? $data['currency_id'] : null;
}
if (isset($data['payment_terms'])) {
$client->payment_terms = $data['payment_terms'];
}
if (isset($data['website'])) {
$client->website = trim($data['website']);
}
$client->save();
$isPrimary = true;
$contactIds = [];
if (isset($data['contact'])) {
$info = $data['contact'];
if (isset($info['email'])) {
$contact->email = trim(strtolower($info['email']));
}
if (isset($info['first_name'])) {
$contact->first_name = trim($info['first_name']);
}
if (isset($info['last_name'])) {
$contact->last_name = trim($info['last_name']);
}
if (isset($info['phone'])) {
$contact->phone = trim($info['phone']);
}
$contact->is_primary = true;
$contact->send_invoice = true;
$client->contacts()->save($contact);
} else {
foreach ($data['contacts'] as $record) {
$record = (array) $record;
if ($publicId != "-1" && isset($record['public_id']) && $record['public_id']) {
$contact = Contact::scope($record['public_id'])->firstOrFail();
} else {
$contact = Contact::createNew();
}
if (isset($record['email'])) {
$contact->email = trim(strtolower($record['email']));
}
if (isset($record['first_name'])) {
$contact->first_name = trim($record['first_name']);
}
if (isset($record['last_name'])) {
$contact->last_name = trim($record['last_name']);
}
if (isset($record['phone'])) {
$contact->phone = trim($record['phone']);
}
$contact->is_primary = $isPrimary;
$contact->send_invoice = isset($record['send_invoice']) ? $record['send_invoice'] : true;
$isPrimary = false;
$client->contacts()->save($contact);
$contactIds[] = $contact->public_id;
}
foreach ($client->contacts as $contact) {
if (!in_array($contact->public_id, $contactIds)) {
$contact->delete();
}
}
}
$client->save();
if (!$publicId || $publicId == "-1") {
\Activity::createClient($client, $notify);
}
return $client;
}
public function bulk($ids, $action)
{
$clients = Client::withTrashed()->scope($ids)->get();
foreach ($clients as $client) {
if ($action == 'restore') {
$client->restore();
$client->is_deleted = false;
$client->save();
} else {
if ($action == 'delete') {
$client->is_deleted = true;
$client->save();
}
$client->delete();
}
}
return count($clients);
}
}

View File

@ -0,0 +1,77 @@
<?php namespace ninja\repositories;
use Credit;
use Client;
use Utils;
class CreditRepository
{
public function find($clientPublicId = null, $filter = null)
{
$query = \DB::table('credits')
->join('clients', 'clients.id', '=', 'credits.client_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('clients.account_id', '=', \Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->select('credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'credits.amount', 'credits.balance', 'credits.credit_date', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'credits.private_notes', 'credits.deleted_at', 'credits.is_deleted');
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
if (!\Session::get('show_trash:credit')) {
$query->where('credits.deleted_at', '=', null);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function save($publicId = null, $input)
{
if ($publicId) {
$credit = Credit::scope($publicId)->firstOrFail();
} else {
$credit = Credit::createNew();
}
$credit->client_id = Client::getPrivateId($input['client']);
$credit->credit_date = Utils::toSqlDate($input['credit_date']);
$credit->amount = Utils::parseFloat($input['amount']);
$credit->balance = Utils::parseFloat($input['amount']);
$credit->private_notes = trim($input['private_notes']);
$credit->save();
return $credit;
}
public function bulk($ids, $action)
{
if (!$ids) {
return 0;
}
$credits = Credit::withTrashed()->scope($ids)->get();
foreach ($credits as $credit) {
if ($action == 'restore') {
$credit->restore();
} else {
if ($action == 'delete') {
$credit->is_deleted = true;
$credit->save();
}
$credit->delete();
}
}
return count($credits);
}
}

View File

@ -0,0 +1,490 @@
<?php namespace ninja\repositories;
use Invoice;
use InvoiceItem;
use Invitation;
use Product;
use Utils;
class InvoiceRepository
{
public function getInvoices($accountId, $clientPublicId = false, $entityType = ENTITY_INVOICE, $filter = false)
{
$query = \DB::table('invoices')
->join('clients', 'clients.id', '=', 'invoices.client_id')
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', $accountId)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
->where('contacts.is_primary', '=', true)
->select('clients.public_id as client_public_id', 'invoice_number', 'invoice_status_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'quote_id', 'quote_invoice_id', 'invoices.deleted_at', 'invoices.is_deleted');
if (!\Session::get('show_trash:'.$entityType)) {
$query->where('invoices.deleted_at', '=', null);
}
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%')
->orWhere('invoice_statuses.name', 'like', '%'.$filter.'%')
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('contacts.email', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function getRecurringInvoices($accountId, $clientPublicId = false, $filter = false)
{
$query = \DB::table('invoices')
->join('clients', 'clients.id', '=', 'invoices.client_id')
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', $accountId)
->where('invoices.is_quote', '=', false)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true)
->where('contacts.is_primary', '=', true)
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'invoices.deleted_at', 'invoices.is_deleted');
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
if (!\Session::get('show_trash:invoice')) {
$query->where('invoices.deleted_at', '=', null);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function getClientDatatable($contactId, $entityType, $search)
{
$query = \DB::table('invitations')
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
->join('clients', 'clients.id', '=', 'invoices.client_id')
->where('invitations.contact_id', '=', $contactId)
->where('invitations.deleted_at', '=', null)
->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE)
->where('invoices.is_deleted', '=', false)
->where('clients.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
->select('invitation_key', 'invoice_number', 'invoice_date', 'invoices.balance as balance', 'due_date', 'clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'start_date', 'end_date', 'clients.currency_id');
$table = \Datatable::query($query)
->addColumn('invoice_number', function ($model) use ($entityType) { return link_to('/view/'.$model->invitation_key, $model->invoice_number); })
->addColumn('invoice_date', function ($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); });
if ($entityType == ENTITY_INVOICE) {
$table->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); });
}
return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); })
->make();
}
public function getDatatable($accountId, $clientPublicId = null, $entityType, $search)
{
$query = $this->getInvoices($accountId, $clientPublicId, $entityType, $search)
->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false);
$table = \Datatable::query($query);
if (!$clientPublicId) {
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; });
}
$table->addColumn("invoice_number", function ($model) use ($entityType) { return link_to("{$entityType}s/".$model->public_id.'/edit', $model->invoice_number, ['class' => Utils::getEntityRowClass($model)]); });
if (!$clientPublicId) {
$table->addColumn('client_name', function ($model) { return link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)); });
}
$table->addColumn("invoice_date", function ($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); });
if ($entityType == ENTITY_INVOICE) {
$table->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); });
}
return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); })
->addColumn('invoice_status_name', function ($model) { return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted')) : $model->invoice_status_name; })
->addColumn('dropdown', function ($model) use ($entityType) {
if ($model->is_deleted) {
return '<div style="height:38px"/>';
}
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="'.\URL::to("{$entityType}s/".$model->public_id.'/edit').'">'.trans("texts.edit_{$entityType}").'</a></li>
<li><a href="'.\URL::to("{$entityType}s/".$model->public_id.'/clone').'">'.trans("texts.clone_{$entityType}").'</a></li>
<li><a href="' . \URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}") . '">' . trans("texts.view_history") . '</a></li>
<li class="divider"></li>';
if ($model->invoice_status_id < INVOICE_STATUS_SENT) {
$str .= '<li><a href="javascript:markEntity('.$model->public_id.', '.INVOICE_STATUS_SENT.')">'.trans("texts.mark_sent").'</a></li>';
}
if ($entityType == ENTITY_INVOICE) {
if ($model->invoice_status_id < INVOICE_STATUS_PAID) {
$str .= '<li><a href="'.\URL::to('payments/create/'.$model->client_public_id.'/'.$model->public_id).'">'.trans('texts.enter_payment').'</a></li>';
}
if ($model->quote_id) {
$str .= '<li><a href="'.\URL::to("quotes/{$model->quote_id}/edit").'">'.trans("texts.view_quote").'</a></li>';
}
} elseif ($entityType == ENTITY_QUOTE) {
if ($model->quote_invoice_id) {
$str .= '<li><a href="'.\URL::to("invoices/{$model->quote_invoice_id}/edit").'">'.trans("texts.view_invoice").'</a></li>';
} else {
$str .= '<li><a href="javascript:convertEntity('.$model->public_id.')">'.trans("texts.convert_to_invoice").'</a></li>';
}
}
$str .= '<li class="divider"></li>
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans("texts.archive_{$entityType}").'</a></li>
<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans("texts.delete_{$entityType}").'</a></li>';
} else {
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans("texts.restore_{$entityType}").'</a></li>
<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans("texts.delete_{$entityType}").'</a></li>';
}
return $str.'</ul>
</div>';
})
->make();
}
public function getErrors($input)
{
$contact = (array) $input->client->contacts[0];
$rules = ['email' => 'required|email'];
$validator = \Validator::make($contact, $rules);
if ($validator->fails()) {
return $validator;
}
$invoice = (array) $input;
$invoiceId = isset($invoice['public_id']) && $invoice['public_id'] ? Invoice::getPrivateId($invoice['public_id']) : null;
$rules = [
'invoice_number' => 'required|unique:invoices,invoice_number,'.$invoiceId.',id,account_id,'.\Auth::user()->account_id,
'discount' => 'positive',
];
if ($invoice['is_recurring'] && $invoice['start_date'] && $invoice['end_date']) {
$rules['end_date'] = 'after:'.$invoice['start_date'];
}
$validator = \Validator::make($invoice, $rules);
if ($validator->fails()) {
return $validator;
}
return false;
}
public function save($publicId, $data, $entityType)
{
if ($publicId) {
$invoice = Invoice::scope($publicId)->firstOrFail();
} else {
$invoice = Invoice::createNew();
if ($entityType == ENTITY_QUOTE) {
$invoice->is_quote = true;
}
}
$account = \Auth::user()->account;
$invoice->client_id = $data['client_id'];
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
$invoice->invoice_number = trim($data['invoice_number']);
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
if ($invoice->is_recurring) {
$invoice->frequency_id = $data['frequency_id'] ? $data['frequency_id'] : 0;
$invoice->start_date = Utils::toSqlDate($data['start_date']);
$invoice->end_date = Utils::toSqlDate($data['end_date']);
$invoice->due_date = null;
} else {
$invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']);
$invoice->frequency_id = 0;
$invoice->start_date = null;
$invoice->end_date = null;
}
$invoice->terms = trim($data['terms']) ? trim($data['terms']) : ($account->invoice_terms ? $account->invoice_terms : '');
$invoice->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : $account->invoice_footer;
$invoice->public_notes = trim($data['public_notes']);
$invoice->po_number = trim($data['po_number']);
$invoice->invoice_design_id = $data['invoice_design_id'];
if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) {
$invoice->tax_rate = Utils::parseFloat($data['tax_rate']);
$invoice->tax_name = trim($data['tax_name']);
} else {
$invoice->tax_rate = 0;
$invoice->tax_name = '';
}
$total = 0;
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) {
continue;
}
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
$invoiceItemTaxRate = 0;
if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
}
$lineTotal = $invoiceItemCost * $invoiceItemQty;
$total += round($lineTotal + ($lineTotal * $invoiceItemTaxRate / 100), 2);
}
if ($invoice->discount > 0) {
if ($invoice->is_amount_discount) {
$total -= $invoice->discount;
} else {
$total *= (100 - $invoice->discount) / 100;
}
}
$invoice->custom_value1 = round($data['custom_value1'], 2);
$invoice->custom_value2 = round($data['custom_value2'], 2);
$invoice->custom_taxes1 = $data['custom_taxes1'] ? true : false;
$invoice->custom_taxes2 = $data['custom_taxes2'] ? true : false;
// custom fields charged taxes
if ($invoice->custom_value1 && $invoice->custom_taxes1) {
$total += $invoice->custom_value1;
}
if ($invoice->custom_value2 && $invoice->custom_taxes2) {
$total += $invoice->custom_value2;
}
$total += $total * $invoice->tax_rate / 100;
$total = round($total, 2);
// custom fields not charged taxes
if ($invoice->custom_value1 && !$invoice->custom_taxes1) {
$total += $invoice->custom_value1;
}
if ($invoice->custom_value2 && !$invoice->custom_taxes2) {
$total += $invoice->custom_value2;
}
if ($publicId) {
$invoice->balance = $total - ($invoice->amount - $invoice->balance);
} else {
$invoice->balance = $total;
}
$invoice->amount = $total;
$invoice->save();
if ($publicId) {
$invoice->invoice_items()->forceDelete();
}
foreach ($data['invoice_items'] as $item) {
$item = (array) $item;
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) {
continue;
}
if ($item['product_key']) {
$product = Product::findProductByKey(trim($item['product_key']));
if (!$product) {
$product = Product::createNew();
$product->product_key = trim($item['product_key']);
}
if (\Auth::user()->account->update_products) {
$product->notes = $item['notes'];
$product->cost = $item['cost'];
}
$product->save();
}
$invoiceItem = InvoiceItem::createNew();
$invoiceItem->product_id = isset($product) ? $product->id : null;
$invoiceItem->product_key = trim($invoice->is_recurring ? $item->product_key : Utils::processVariables($item['product_key']));
$invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes']));
$invoiceItem->cost = Utils::parseFloat($item['cost']);
$invoiceItem->qty = Utils::parseFloat($item['qty']);
$invoiceItem->tax_rate = 0;
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
$invoiceItem['tax_name'] = trim($item['tax_name']);
}
$invoice->invoice_items()->save($invoiceItem);
}
if ((isset($data['set_default_terms']) && $data['set_default_terms'])
|| (isset($data['set_default_footer']) && $data['set_default_footer'])) {
if (isset($data['set_default_terms']) && $data['set_default_terms']) {
$account->invoice_terms = trim($data['terms']);
}
if (isset($data['set_default_footer']) && $data['set_default_footer']) {
$account->invoice_footer = trim($data['invoice_footer']);
}
$account->save();
}
return $invoice;
}
public function cloneInvoice($invoice, $quotePublicId = null)
{
$invoice->load('invitations', 'invoice_items');
$account = $invoice->account;
$clone = Invoice::createNew($invoice);
$clone->balance = $invoice->amount;
// if the invoice prefix is diff than quote prefix, use the same number for the invoice
if (($account->invoice_number_prefix || $account->quote_number_prefix)
&& $account->invoice_number_prefix != $account->quote_number_prefix
&& $account->share_counter) {
$invoiceNumber = $invoice->invoice_number;
if (strpos($invoiceNumber, $account->quote_number_prefix) === 0) {
$invoiceNumber = substr($invoiceNumber, strlen($account->quote_number_prefix));
}
$clone->invoice_number = $account->invoice_number_prefix.$invoiceNumber;
} else {
$clone->invoice_number = $account->getNextInvoiceNumber();
}
foreach ([
'client_id',
'discount',
'is_amount_discount',
'invoice_date',
'po_number',
'due_date',
'is_recurring',
'frequency_id',
'start_date',
'end_date',
'terms',
'invoice_footer',
'public_notes',
'invoice_design_id',
'tax_name',
'tax_rate',
'amount',
'is_quote',
'custom_value1',
'custom_value2',
'custom_taxes1',
'custom_taxes2', ] as $field) {
$clone->$field = $invoice->$field;
}
if ($quotePublicId) {
$clone->is_quote = false;
$clone->quote_id = $quotePublicId;
}
$clone->save();
if ($quotePublicId) {
$invoice->quote_invoice_id = $clone->public_id;
$invoice->save();
}
foreach ($invoice->invoice_items as $item) {
$cloneItem = InvoiceItem::createNew($invoice);
foreach ([
'product_id',
'product_key',
'notes',
'cost',
'qty',
'tax_name',
'tax_rate', ] as $field) {
$cloneItem->$field = $item->$field;
}
$clone->invoice_items()->save($cloneItem);
}
foreach ($invoice->invitations as $invitation) {
$cloneInvitation = Invitation::createNew($invoice);
$cloneInvitation->contact_id = $invitation->contact_id;
$cloneInvitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$clone->invitations()->save($cloneInvitation);
}
return $clone;
}
public function bulk($ids, $action, $statusId = false)
{
if (!$ids) {
return 0;
}
$invoices = Invoice::withTrashed()->scope($ids)->get();
foreach ($invoices as $invoice) {
if ($action == 'mark') {
$invoice->invoice_status_id = $statusId;
$invoice->save();
} elseif ($action == 'restore') {
$invoice->restore();
} else {
if ($action == 'delete') {
$invoice->is_deleted = true;
$invoice->save();
}
$invoice->delete();
}
}
return count($invoices);
}
}

View File

@ -0,0 +1,154 @@
<?php namespace ninja\repositories;
use Payment;
use Credit;
use Invoice;
use Client;
use Utils;
class PaymentRepository
{
public function find($clientPublicId = null, $filter = null)
{
$query = \DB::table('payments')
->join('clients', 'clients.id', '=', 'payments.client_id')
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
->where('payments.account_id', '=', \Auth::user()->account_id)
->where('clients.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->where('contacts.deleted_at', '=', null)
->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted', 'invoices.is_deleted as invoice_is_deleted');
if (!\Session::get('show_trash:payment')) {
$query->where('payments.deleted_at', '=', null)
->where('invoices.deleted_at', '=', null);
}
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function findForContact($contactId = null, $filter = null)
{
$query = \DB::table('payments')
->join('clients', 'clients.id', '=', 'payments.client_id')
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->leftJoin('invitations', function ($join) {
$join->on('invitations.invoice_id', '=', 'invoices.id')
->on('invitations.contact_id', '=', 'contacts.id');
})
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
->where('clients.is_deleted', '=', false)
->where('payments.is_deleted', '=', false)
->where('invitations.deleted_at', '=', null)
->where('invoices.deleted_at', '=', null)
->where('invitations.contact_id', '=', $contactId)
->select('invitations.invitation_key', 'payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id');
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function getErrors($input)
{
$rules = array(
'client' => 'required',
'invoice' => 'required',
'amount' => 'required',
);
if ($input['payment_type_id'] == PAYMENT_TYPE_CREDIT) {
$rules['payment_type_id'] = 'has_credit:'.$input['client'].','.$input['amount'];
}
$validator = \Validator::make($input, $rules);
if ($validator->fails()) {
return $validator;
}
return false;
}
public function save($publicId = null, $input)
{
if ($publicId) {
$payment = Payment::scope($publicId)->firstOrFail();
} else {
$payment = Payment::createNew();
}
$paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null;
$payment->payment_type_id = $paymentTypeId;
$payment->payment_date = Utils::toSqlDate($input['payment_date']);
$payment->transaction_reference = trim($input['transaction_reference']);
if (!$publicId) {
$clientId = Client::getPrivateId($input['client']);
$amount = Utils::parseFloat($input['amount']);
if ($paymentTypeId == PAYMENT_TYPE_CREDIT) {
$credits = Credit::scope()->where('client_id', '=', $clientId)
->where('balance', '>', 0)->orderBy('created_at')->get();
$applied = 0;
foreach ($credits as $credit) {
$applied += $credit->apply($amount);
if ($applied >= $amount) {
break;
}
}
}
$payment->client_id = $clientId;
$payment->invoice_id = isset($input['invoice']) && $input['invoice'] != "-1" ? Invoice::getPrivateId($input['invoice']) : null;
$payment->amount = $amount;
}
$payment->save();
return $payment;
}
public function bulk($ids, $action)
{
if (!$ids) {
return 0;
}
$payments = Payment::withTrashed()->scope($ids)->get();
foreach ($payments as $payment) {
if ($action == 'restore') {
$payment->restore();
} else {
if ($action == 'delete') {
$payment->is_deleted = true;
$payment->save();
}
$payment->delete();
}
}
return count($payments);
}
}

View File

@ -0,0 +1,42 @@
<?php namespace ninja\repositories;
use TaxRate;
use Utils;
class TaxRateRepository
{
public function save($taxRates)
{
$taxRateIds = [];
foreach ($taxRates as $record) {
if (!isset($record->rate) || (isset($record->is_deleted) && $record->is_deleted)) {
continue;
}
if (!isset($record->name) || !trim($record->name)) {
continue;
}
if ($record->public_id) {
$taxRate = TaxRate::scope($record->public_id)->firstOrFail();
} else {
$taxRate = TaxRate::createNew();
}
$taxRate->rate = Utils::parseFloat($record->rate);
$taxRate->name = trim($record->name);
$taxRate->save();
$taxRateIds[] = $taxRate->public_id;
}
$taxRates = TaxRate::scope()->get();
foreach ($taxRates as $taxRate) {
if (!in_array($taxRate->public_id, $taxRateIds)) {
$taxRate->delete();
}
}
}
}

View File

@ -57,9 +57,9 @@
"autoload": {
"classmap": [
"app/Console/Commands",
"app/libraries",
"app/Http/Controllers",
"app/Models",
"app/libraries",
"app/ninja",
"vendor/calvinfroedge/PHP-Payments/lib",
"database"

6220
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -171,7 +171,44 @@ return [
'aliases' => [
'App' => 'Illuminate\Support\Facades\App',
'App' => 'Illuminate\Support\Facades\App',
'Artisan' => 'Illuminate\Support\Facades\Artisan',
'Auth' => 'Illuminate\Support\Facades\Auth',
'Blade' => 'Illuminate\Support\Facades\Blade',
'Cache' => 'Illuminate\Support\Facades\Cache',
'ClassLoader' => 'Illuminate\Support\ClassLoader',
'Config' => 'Illuminate\Support\Facades\Config',
'Controller' => 'Illuminate\Routing\Controller',
'Cookie' => 'Illuminate\Support\Facades\Cookie',
'Crypt' => 'Illuminate\Support\Facades\Crypt',
'DB' => 'Illuminate\Support\Facades\DB',
'Eloquent' => 'Illuminate\Database\Eloquent\Model',
'Event' => 'Illuminate\Support\Facades\Event',
'File' => 'Illuminate\Support\Facades\File',
//'Form' => 'Illuminate\Support\Facades\Form',
'Hash' => 'Illuminate\Support\Facades\Hash',
'HTML' => 'Illuminate\Support\Facades\HTML',
'Input' => 'Illuminate\Support\Facades\Input',
'Lang' => 'Illuminate\Support\Facades\Lang',
'Log' => 'Illuminate\Support\Facades\Log',
'Mail' => 'Illuminate\Support\Facades\Mail',
//'Paginator' => 'Illuminate\Support\Facades\Paginator',
'Password' => 'Illuminate\Support\Facades\Password',
'Queue' => 'Illuminate\Support\Facades\Queue',
'Redirect' => 'Illuminate\Support\Facades\Redirect',
'Redis' => 'Illuminate\Support\Facades\Redis',
'Request' => 'Illuminate\Support\Facades\Request',
'Response' => 'Illuminate\Support\Facades\Response',
'Route' => 'Illuminate\Support\Facades\Route',
'Schema' => 'Illuminate\Support\Facades\Schema',
'Seeder' => 'Illuminate\Database\Seeder',
'Session' => 'Illuminate\Support\Facades\Session',
'Str' => 'Illuminate\Support\Str',
'URL' => 'Illuminate\Support\Facades\URL',
'Validator' => 'Illuminate\Support\Facades\Validator',
'View' => 'Illuminate\Support\Facades\View',
/*'App' => 'Illuminate\Support\Facades\App',
'Artisan' => 'Illuminate\Support\Facades\Artisan',
'Auth' => 'Illuminate\Support\Facades\Auth',
'Blade' => 'Illuminate\Support\Facades\Blade',
@ -202,7 +239,7 @@ return [
'Storage' => 'Illuminate\Support\Facades\Storage',
'URL' => 'Illuminate\Support\Facades\URL',
'Validator' => 'Illuminate\Support\Facades\Validator',
'View' => 'Illuminate\Support\Facades\View',
'View' => 'Illuminate\Support\Facades\View',*/
// Added Class Aliases

View File

@ -0,0 +1,550 @@
<?php
use Illuminate\Database\Migrations\Migration;
class ConfideSetupUsersTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('payment_terms');
Schema::dropIfExists('themes');
Schema::dropIfExists('credits');
Schema::dropIfExists('activities');
Schema::dropIfExists('invitations');
Schema::dropIfExists('payments');
Schema::dropIfExists('account_gateways');
Schema::dropIfExists('invoice_items');
Schema::dropIfExists('products');
Schema::dropIfExists('tax_rates');
Schema::dropIfExists('contacts');
Schema::dropIfExists('invoices');
Schema::dropIfExists('password_reminders');
Schema::dropIfExists('clients');
Schema::dropIfExists('users');
Schema::dropIfExists('accounts');
Schema::dropIfExists('currencies');
Schema::dropIfExists('invoice_statuses');
Schema::dropIfExists('countries');
Schema::dropIfExists('timezones');
Schema::dropIfExists('frequencies');
Schema::dropIfExists('date_formats');
Schema::dropIfExists('datetime_formats');
Schema::dropIfExists('sizes');
Schema::dropIfExists('industries');
Schema::dropIfExists('gateways');
Schema::dropIfExists('payment_types');
Schema::create('countries', function($table)
{
$table->increments('id');
$table->string('capital', 255)->nullable();
$table->string('citizenship', 255)->nullable();
$table->string('country_code', 3)->default('');
$table->string('currency', 255)->nullable();
$table->string('currency_code', 255)->nullable();
$table->string('currency_sub_unit', 255)->nullable();
$table->string('full_name', 255)->nullable();
$table->string('iso_3166_2', 2)->default('');
$table->string('iso_3166_3', 3)->default('');
$table->string('name', 255)->default('');
$table->string('region_code', 3)->default('');
$table->string('sub_region_code', 3)->default('');
$table->boolean('eea')->default(0);
});
Schema::create('themes', function($t)
{
$t->increments('id');
$t->string('name');
});
Schema::create('payment_types', function($t)
{
$t->increments('id');
$t->string('name');
});
Schema::create('payment_terms', function($t)
{
$t->increments('id');
$t->integer('num_days');
$t->string('name');
});
Schema::create('timezones', function($t)
{
$t->increments('id');
$t->string('name');
$t->string('location');
});
Schema::create('date_formats', function($t)
{
$t->increments('id');
$t->string('format');
$t->string('picker_format');
$t->string('label');
});
Schema::create('datetime_formats', function($t)
{
$t->increments('id');
$t->string('format');
$t->string('label');
});
Schema::create('currencies', function($t)
{
$t->increments('id');
$t->string('name');
$t->string('symbol');
$t->string('precision');
$t->string('thousand_separator');
$t->string('decimal_separator');
$t->string('code');
});
Schema::create('sizes', function($t)
{
$t->increments('id');
$t->string('name');
});
Schema::create('industries', function($t)
{
$t->increments('id');
$t->string('name');
});
Schema::create('accounts', function($t)
{
$t->increments('id');
$t->unsignedInteger('timezone_id')->nullable();
$t->unsignedInteger('date_format_id')->nullable();
$t->unsignedInteger('datetime_format_id')->nullable();
$t->unsignedInteger('currency_id')->nullable();
$t->timestamps();
$t->softDeletes();
$t->string('name')->nullable();
$t->string('ip');
$t->string('account_key')->unique();
$t->timestamp('last_login')->nullable();
$t->string('address1')->nullable();
$t->string('address2')->nullable();
$t->string('city')->nullable();
$t->string('state')->nullable();
$t->string('postal_code')->nullable();
$t->unsignedInteger('country_id')->nullable();
$t->text('invoice_terms')->nullable();
$t->text('email_footer')->nullable();
$t->unsignedInteger('industry_id')->nullable();
$t->unsignedInteger('size_id')->nullable();
$t->boolean('invoice_taxes')->default(true);
$t->boolean('invoice_item_taxes')->default(false);
$t->foreign('timezone_id')->references('id')->on('timezones');
$t->foreign('date_format_id')->references('id')->on('date_formats');
$t->foreign('datetime_format_id')->references('id')->on('datetime_formats');
$t->foreign('country_id')->references('id')->on('countries');
$t->foreign('currency_id')->references('id')->on('currencies');
$t->foreign('industry_id')->references('id')->on('industries');
$t->foreign('size_id')->references('id')->on('sizes');
});
Schema::create('gateways', function($t)
{
$t->increments('id');
$t->timestamps();
$t->string('name');
$t->string('provider');
$t->boolean('visible')->default(true);
});
Schema::create('users', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id')->index();
$t->timestamps();
$t->softDeletes();
$t->string('first_name')->nullable();
$t->string('last_name')->nullable();
$t->string('phone')->nullable();
$t->string('username')->unique();
$t->string('email')->nullable();
$t->string('password');
$t->string('confirmation_code');
$t->boolean('registered')->default(false);
$t->boolean('confirmed')->default(false);
$t->integer('theme_id')->nullable();
$t->boolean('notify_sent')->default(true);
$t->boolean('notify_viewed')->default(false);
$t->boolean('notify_paid')->default(true);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->unsignedInteger('public_id')->nullable();
$t->unique( array('account_id','public_id') );
});
Schema::create('account_gateways', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('gateway_id');
$t->timestamps();
$t->softDeletes();
$t->text('config');
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('gateway_id')->references('id')->on('gateways');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
Schema::create('password_reminders', function($t)
{
$t->string('email');
$t->timestamps();
$t->string('token');
});
Schema::create('clients', function($t)
{
$t->increments('id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('currency_id')->nullable();
$t->timestamps();
$t->softDeletes();
$t->string('name')->nullable();
$t->string('address1')->nullable();
$t->string('address2')->nullable();
$t->string('city')->nullable();
$t->string('state')->nullable();
$t->string('postal_code')->nullable();
$t->unsignedInteger('country_id')->nullable();
$t->string('work_phone')->nullable();
$t->text('private_notes')->nullable();
$t->decimal('balance', 13, 2)->nullable();
$t->decimal('paid_to_date', 13, 2)->nullable();
$t->timestamp('last_login')->nullable();
$t->string('website')->nullable();
$t->unsignedInteger('industry_id')->nullable();
$t->unsignedInteger('size_id')->nullable();
$t->boolean('is_deleted')->default(false);
$t->integer('payment_terms')->nullable();
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$t->foreign('country_id')->references('id')->on('countries');
$t->foreign('industry_id')->references('id')->on('industries');
$t->foreign('size_id')->references('id')->on('sizes');
$t->foreign('currency_id')->references('id')->on('currencies');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
Schema::create('contacts', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('client_id')->index();
$t->timestamps();
$t->softDeletes();
$t->boolean('is_primary')->default(0);
$t->boolean('send_invoice')->default(0);
$t->string('first_name')->nullable();
$t->string('last_name')->nullable();
$t->string('email')->nullable();
$t->string('phone')->nullable();
$t->timestamp('last_login')->nullable();
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$t->unsignedInteger('public_id')->nullable();
$t->unique( array('account_id','public_id') );
});
Schema::create('invoice_statuses', function($t)
{
$t->increments('id');
$t->string('name');
});
Schema::create('frequencies', function($t)
{
$t->increments('id');
$t->string('name');
});
Schema::create('invoices', function($t)
{
$t->increments('id');
$t->unsignedInteger('client_id')->index();
$t->unsignedInteger('user_id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('invoice_status_id')->default(1);
$t->timestamps();
$t->softDeletes();
$t->string('invoice_number');
$t->float('discount');
$t->string('po_number');
$t->date('invoice_date')->nullable();
$t->date('due_date')->nullable();
$t->text('terms');
$t->text('public_notes');
$t->boolean('is_deleted')->default(false);
$t->boolean('is_recurring');
$t->unsignedInteger('frequency_id');
$t->date('start_date')->nullable();
$t->date('end_date')->nullable();
$t->timestamp('last_sent_date')->nullable();
$t->unsignedInteger('recurring_invoice_id')->index()->nullable();
$t->string('tax_name');
$t->decimal('tax_rate', 13, 2);
$t->decimal('amount', 13, 2);
$t->decimal('balance', 13, 2);
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$t->foreign('invoice_status_id')->references('id')->on('invoice_statuses');
$t->foreign('recurring_invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
$t->unique( array('account_id','invoice_number') );
});
Schema::create('invitations', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('contact_id');
$t->unsignedInteger('invoice_id')->index();
$t->string('invitation_key')->index()->unique();
$t->timestamps();
$t->softDeletes();
$t->string('transaction_reference')->nullable();
$t->timestamp('sent_date');
$t->timestamp('viewed_date');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$t->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
Schema::create('tax_rates', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('user_id');
$t->timestamps();
$t->softDeletes();
$t->string('name');
$t->decimal('rate', 13, 2);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$t->unsignedInteger('public_id');
$t->unique( array('account_id','public_id') );
});
Schema::create('products', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('user_id');
$t->timestamps();
$t->softDeletes();
$t->string('product_key');
$t->text('notes');
$t->decimal('cost', 13, 2);
$t->decimal('qty', 13, 2)->nullable();
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$t->unsignedInteger('public_id');
$t->unique( array('account_id','public_id') );
});
Schema::create('invoice_items', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('invoice_id')->index();
$t->unsignedInteger('product_id')->nullable();
$t->timestamps();
$t->softDeletes();
$t->string('product_key');
$t->text('notes');
$t->decimal('cost', 13, 2);
$t->decimal('qty', 13, 2)->nullable();
$t->string('tax_name')->nullable();
$t->decimal('tax_rate', 13, 2)->nullable();
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$t->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$t->unsignedInteger('public_id');
$t->unique( array('account_id','public_id') );
});
Schema::create('payments', function($t)
{
$t->increments('id');
$t->unsignedInteger('invoice_id')->nullable();
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('client_id')->index();
$t->unsignedInteger('contact_id')->nullable();
$t->unsignedInteger('invitation_id')->nullable();
$t->unsignedInteger('user_id')->nullable();
$t->unsignedInteger('account_gateway_id')->nullable();
$t->unsignedInteger('payment_type_id')->nullable();
$t->timestamps();
$t->softDeletes();
$t->boolean('is_deleted')->default(false);
$t->decimal('amount', 13, 2);
$t->date('payment_date')->nullable();
$t->string('transaction_reference')->nullable();
$t->string('payer_id')->nullable();
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
$t->foreign('account_gateway_id')->references('id')->on('account_gateways')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$t->foreign('payment_type_id')->references('id')->on('payment_types');
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
Schema::create('credits', function($t)
{
$t->increments('id');
$t->unsignedInteger('account_id')->index();
$t->unsignedInteger('client_id')->index();
$t->unsignedInteger('user_id');
$t->timestamps();
$t->softDeletes();
$t->boolean('is_deleted')->default(false);
$t->decimal('amount', 13, 2);
$t->decimal('balance', 13, 2);
$t->date('credit_date')->nullable();
$t->string('credit_number')->nullable();
$t->text('private_notes');
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$t->unsignedInteger('public_id')->index();
$t->unique( array('account_id','public_id') );
});
Schema::create('activities', function($t)
{
$t->increments('id');
$t->timestamps();
$t->unsignedInteger('account_id');
$t->unsignedInteger('client_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('contact_id')->nullable();
$t->unsignedInteger('payment_id')->nullable();
$t->unsignedInteger('invoice_id')->nullable();
$t->unsignedInteger('credit_id')->nullable();
$t->unsignedInteger('invitation_id')->nullable();
$t->text('message')->nullable();
$t->text('json_backup')->nullable();
$t->integer('activity_type_id');
$t->decimal('adjustment', 13, 2)->nullable();
$t->decimal('balance', 13, 2)->nullable();
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('payment_terms');
Schema::dropIfExists('themes');
Schema::dropIfExists('credits');
Schema::dropIfExists('activities');
Schema::dropIfExists('invitations');
Schema::dropIfExists('payments');
Schema::dropIfExists('account_gateways');
Schema::dropIfExists('invoice_items');
Schema::dropIfExists('products');
Schema::dropIfExists('tax_rates');
Schema::dropIfExists('contacts');
Schema::dropIfExists('invoices');
Schema::dropIfExists('password_reminders');
Schema::dropIfExists('clients');
Schema::dropIfExists('users');
Schema::dropIfExists('accounts');
Schema::dropIfExists('currencies');
Schema::dropIfExists('invoice_statuses');
Schema::dropIfExists('countries');
Schema::dropIfExists('timezones');
Schema::dropIfExists('frequencies');
Schema::dropIfExists('date_formats');
Schema::dropIfExists('datetime_formats');
Schema::dropIfExists('sizes');
Schema::dropIfExists('industries');
Schema::dropIfExists('gateways');
Schema::dropIfExists('payment_types');
}
}

View File

@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
class SetupCountriesTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// Creates the users table
/*
Schema::create('countries', function($table)
{
$table->integer('id')->index();
$table->string('capital', 255)->nullable();
$table->string('citizenship', 255)->nullable();
$table->string('country_code', 3)->default('');
$table->string('currency', 255)->nullable();
$table->string('currency_code', 255)->nullable();
$table->string('currency_sub_unit', 255)->nullable();
$table->string('full_name', 255)->nullable();
$table->string('iso_3166_2', 2)->default('');
$table->string('iso_3166_3', 3)->default('');
$table->string('name', 255)->default('');
$table->string('region_code', 3)->default('');
$table->string('sub_region_code', 3)->default('');
$table->boolean('eea')->default(0);
$table->primary('id');
});
*/
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//Schema::drop('countries');
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCascaseDrops extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function($table)
{
$table->dropForeign('invoices_account_id_foreign');
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -0,0 +1,72 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddSupportForInvoiceDesigns extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('invoice_designs', function($table)
{
$table->increments('id');
$table->string('name');
});
DB::table('invoice_designs')->insert(['name' => 'Clean']);
DB::table('invoice_designs')->insert(['name' => 'Bold']);
DB::table('invoice_designs')->insert(['name' => 'Modern']);
DB::table('invoice_designs')->insert(['name' => 'Plain']);
Schema::table('invoices', function($table)
{
$table->unsignedInteger('invoice_design_id')->default(1);
});
Schema::table('accounts', function($table)
{
$table->unsignedInteger('invoice_design_id')->default(1);
});
DB::table('invoices')->update(['invoice_design_id' => 1]);
DB::table('accounts')->update(['invoice_design_id' => 1]);
Schema::table('invoices', function($table)
{
$table->foreign('invoice_design_id')->references('id')->on('invoice_designs');
});
Schema::table('accounts', function($table)
{
$table->foreign('invoice_design_id')->references('id')->on('invoice_designs');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoices', function($table)
{
$table->dropForeign('invoices_invoice_design_id_foreign');
$table->dropColumn('invoice_design_id');
});
Schema::table('accounts', function($table)
{
$table->dropForeign('accounts_invoice_design_id_foreign');
$table->dropColumn('invoice_design_id');
});
Schema::dropIfExists('invoice_designs');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddPhoneToAccount extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->string('work_phone')->nullable();
$table->string('work_email')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropColumn('work_phone');
$table->dropColumn('work_email');
});
}
}

View File

@ -0,0 +1,60 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddLanguageSupport extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('languages', function($table)
{
$table->increments('id');
$table->string('name');
$table->string('locale');
});
DB::table('languages')->insert(['name' => 'English', 'locale' => 'en']);
DB::table('languages')->insert(['name' => 'Italian', 'locale' => 'it']);
DB::table('languages')->insert(['name' => 'German', 'locale' => 'de']);
DB::table('languages')->insert(['name' => 'French', 'locale' => 'fr']);
DB::table('languages')->insert(['name' => 'Brazilian Portuguese', 'locale' => 'pt_BR']);
DB::table('languages')->insert(['name' => 'Dutch', 'locale' => 'nl']);
DB::table('languages')->insert(['name' => 'Spanish', 'locale' => 'es']);
DB::table('languages')->insert(['name' => 'Norwegian', 'locale' => 'nb_NO']);
Schema::table('accounts', function($table)
{
$table->unsignedInteger('language_id')->default(1);
});
DB::table('accounts')->update(['language_id' => 1]);
Schema::table('accounts', function($table)
{
$table->foreign('language_id')->references('id')->on('languages');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropForeign('accounts_language_id_foreign');
$table->dropColumn('language_id');
});
Schema::drop('languages');
}
}

Some files were not shown because too many files have changed in this diff Show More