Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
David Bomba 2016-03-01 20:18:30 +11:00
commit f7da00ff27
17 changed files with 1175 additions and 199 deletions

View File

@ -3,7 +3,7 @@ language: php
sudo: true
php:
- 5.5
- 5.5.9
# - 5.6
# - 7.0
# - hhvm

View File

@ -117,4 +117,77 @@ class AccountApiController extends BaseAPIController
return $this->response($account);
}
public function addDeviceToken(Request $request)
{
$account = Auth::user()->account;
//scan if this user has a token already registered (tokens can change, so we need to use the users email as key)
$devices = json_decode($account->devices,TRUE);
for($x=0; $x<count($devices); $x++)
{
if ($devices[$x]['email'] == Auth::user()->username) {
$devices[$x]['token'] = $request->token; //update
$account->devices = json_encode($devices);
$account->save();
return $this->response($account);
}
}
//User does not have a device, create new record
$newDevice = [
'token' => $request->token,
'email' => $request->email,
'device' => $request->device,
'notify_sent' => TRUE,
'notify_viewed' => TRUE,
'notify_approved' => TRUE,
'notify_paid' => TRUE,
];
$devices[] = $newDevice;
$account->devices = json_encode($devices);
$account->save();
return $this->response($account);
}
public function updatePushNotifications(Request $request)
{
$account = Auth::user()->account;
$devices = json_decode($account->devices, TRUE);
if(count($devices)<1)
return $this->errorResponse(['message'=>'no devices exist'], 400);
for($x=0; $x<count($devices); $x++)
{
if($devices[$x]['email'] == Auth::user()->username)
{
unset($devices[$x]);
$newDevice = [
'token' => $request->token,
'email' => $request->email,
'device' => $request->device,
'notify_sent' => $request->notify_sent,
'notify_viewed' => $request->notify_viewed,
'notify_approved' => $request->notify_approved,
'notify_paid' => $request->notify_paid,
];
$devices[] = $newDevice;
$account->devices = json_encode($devices);
$account->save();
return $this->response($account);
}
}
}
}

View File

@ -432,6 +432,7 @@ class AccountController extends BaseController
'client_view_css' => $css,
'title' => trans("texts.client_portal"),
'section' => ACCOUNT_CLIENT_PORTAL,
'account' => $account,
];
return View::make("accounts.client_portal", $data);
@ -544,6 +545,7 @@ class AccountController extends BaseController
$account = Auth::user()->account;
$account->client_view_css = $sanitized_css;
$account->enable_client_portal = Input::get('enable_client_portal') ? true : false;
$account->save();
Session::flash('message', trans('texts.updated_settings'));

View File

@ -34,10 +34,7 @@ class PublicClientController extends BaseController
public function view($invitationKey)
{
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return response()->view('error', [
'error' => trans('texts.invoice_not_found'),
'hideHeader' => true,
]);
return $this->returnError();
}
$invoice = $invitation->invoice;
@ -118,6 +115,7 @@ class PublicClientController extends BaseController
'showBreadcrumbs' => false,
'hideLogo' => $account->isWhiteLabel(),
'hideHeader' => $account->isNinjaAccount(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'invoice' => $invoice->hidePrivateFields(),
@ -188,11 +186,16 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
$invoice = $invitation->invoice;
$client = $invoice->client;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
if (!$account->enable_client_portal) {
return $this->returnError();
}
$data = [
'color' => $color,
'account' => $account,
@ -244,6 +247,7 @@ class PublicClientController extends BaseController
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.invoices'),
@ -275,6 +279,7 @@ class PublicClientController extends BaseController
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'entityType' => ENTITY_PAYMENT,
@ -312,6 +317,7 @@ class PublicClientController extends BaseController
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.quotes'),
@ -332,13 +338,11 @@ class PublicClientController extends BaseController
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch'));
}
private function returnError()
private function returnError($error = false)
{
return response()->view('error', [
'error' => trans('texts.invoice_not_found'),
'error' => $error ?: trans('texts.invoice_not_found'),
'hideHeader' => true,
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
]);
}

View File

@ -235,6 +235,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::resource('tax_rates', 'TaxRateApiController');
Route::resource('users', 'UserApiController');
Route::resource('expenses','ExpenseApiController');
Route::post('add_token', 'AccountApiController@addDeviceToken');
Route::post('update_notifications', 'AccountApiController@updatePushNotifications');
// Vendor
Route::resource('vendors', 'VendorApiController');
@ -552,6 +554,9 @@ if (!defined('CONTACT_EMAIL')) {
define('TEST_PASSWORD', 'password');
define('API_SECRET', 'API_SECRET');
define('IOS_PRODUCTION_PUSH','ninjaIOS');
define('IOS_DEV_PUSH','devNinjaIOS');
define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2);
define('TOKEN_BILLING_OPT_OUT', 3);

View File

@ -9,16 +9,19 @@ use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed;
use App\Events\QuoteInvitationWasApproved;
use App\Events\PaymentWasCreated;
use App\Ninja\Notifications;
class NotificationListener
{
protected $userMailer;
protected $contactMailer;
protected $pushService;
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer)
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer, Notifications\PushService $pushService)
{
$this->userMailer = $userMailer;
$this->contactMailer = $contactMailer;
$this->pushService = $pushService;
}
private function sendEmails($invoice, $type, $payment = null)
@ -35,26 +38,31 @@ class NotificationListener
public function emailedInvoice(InvoiceWasEmailed $event)
{
$this->sendEmails($event->invoice, 'sent');
$this->pushService->sendNotification($event->invoice, 'sent');
}
public function emailedQuote(QuoteWasEmailed $event)
{
$this->sendEmails($event->quote, 'sent');
$this->pushService->sendNotification($event->quote, 'sent');
}
public function viewedInvoice(InvoiceInvitationWasViewed $event)
{
$this->sendEmails($event->invoice, 'viewed');
$this->pushService->sendNotification($event->invoice, 'viewed');
}
public function viewedQuote(QuoteInvitationWasViewed $event)
{
$this->sendEmails($event->quote, 'viewed');
$this->pushService->sendNotification($event->quote, 'viewed');
}
public function approvedQuote(QuoteInvitationWasApproved $event)
{
$this->sendEmails($event->quote, 'approved');
$this->pushService->sendNotification($event->quote, 'approved');
}
public function createdPayment(PaymentWasCreated $event)
@ -66,6 +74,8 @@ class NotificationListener
$this->contactMailer->sendPaymentConfirmation($event->payment);
$this->sendEmails($event->payment->invoice, 'paid', $event->payment);
$this->pushService->sendNotification($event->payment->invoice, 'paid');
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Ninja\Notifications;
use Davibennun\LaravelPushNotification\Facades\PushNotification;
use Illuminate\Http\Request;
/**
* Class PushFactory
* @package App\Ninja\Notifications
*/
class PushFactory
{
/**
* PushFactory constructor.
*
* @param $this->certificate - Development or production.
*
* Static variables defined in routes.php
*
* IOS_PRODUCTION_PUSH
* IOS_DEV_PUSH
*/
public function __construct()
{
$this->certificate = IOS_DEV_PUSH;
}
/**
* customMessage function
*
* Send a message with a nested custom payload to perform additional trickery within application
*
* @access public
*
* @param $token
* @param $message
* @param $messageArray
*
* @return void
*/
public function customMessage($token, $message, $messageArray)
{
$customMessage = PushNotification::Message($message, $messageArray);
$this->message($token, $customMessage);
}
/**
* message function
*
* Send a plain text only message to a single device.
*
* @access public
*
* @param $token - device token
* @param $message - user specific message
*
* @return void
*
*/
public function message($token, $message)
{
PushNotification::app($this->certificate)
->to($token)
->send($message);
}
/**
* getFeedback function
*
* Returns an array of expired/invalid tokens to be removed from iOS PUSH notifications.
*
* We need to run this once ~ 24hrs
*
* @access public
*
* @param string $token - A valid token (can be any valid token)
* @param string $message - Nil value for message
*
* @return array
*/
public function getFeedback($token, $message = '')
{
$feedback = PushNotification::app($this->certificate)
->to($token)
->send($message);
return $feedback->getFeedback();
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace App\Services;
use Illuminate\Http\Request;
use App\Ninja\Notifications\PushFactory;
/**
* Class PushService
* @package App\Ninja\Notifications
*/
/**
* $account->devices Definition
*
* @param string token (push notification device token)
* @param string email (user email address - required for use as key)
* @param string device (ios, gcm etc etc)
* @param bool notify_sent
* @param bool notify_paid
* @param bool notify_approved
* @param bool notify_viewed
*/
class PushService
{
protected $pushFactory;
/**
* @param PushFactory $pushFactory
*/
public function __construct(PushFactory $pushFactory)
{
$this->pushFactory = $pushFactory;
}
/**
* @param $invoice - Invoice object
* @param $type - Type of notification, ie. Quote APPROVED, Invoice PAID, Invoice/Quote SENT, Invoice/Quote VIEWED
*/
public function sendNotification($invoice, $type)
{
//check user has registered for push notifications
if(!$this->checkDeviceExists($invoice->account))
return;
//Harvest an array of devices that are registered for this notification type
$devices = json_decode($invoice->account->devices, TRUE);
foreach($devices as $device)
{
if(($device["notify_{$type}"] == TRUE) && ($device['device'] == 'ios'))
$this->pushMessage($invoice, $device['token'], $device["notify_{$type}"]);
}
}
/**
* pushMessage function
*
* method to dispatch iOS notifications
*
* @param $invoice
* @param $token
* @param $type
*/
private function pushMessage($invoice, $token, $type)
{
$this->pushFactory->message($token, $this->messageType($invoice, $type));
}
/**
* checkDeviceExists function
*
* Returns a boolean if this account has devices registered for PUSH notifications
*
* @param $account
* @return bool
*/
private function checkDeviceExists($account)
{
$devices = json_decode($account->devices, TRUE);
if(count($devices) >= 1)
return TRUE;
else
return FALSE;
}
/**
* messageType function
*
* method which formats an appropriate message depending on message type
*
* @param $invoice
* @param $type
* @return string
*/
private function messageType($invoice, $type)
{
switch($type)
{
case 'notify_sent':
return $this->entitySentMessage($invoice);
break;
case 'notify_paid':
return $this->invoicePaidMessage($invoice);
break;
case 'notify_approved':
return $this->quoteApprovedMessage($invoice);
break;
case 'notify_viewed':
return $this->entityViewedMessage($invoice);
break;
}
}
/**
* @param $invoice
* @return string
*/
private function entitySentMessage($invoice)
{
if($invoice->is_quote)
return 'Quote #{$invoice->invoice_number} sent!';
else
return 'Invoice #{$invoice->invoice_number} sent!';
}
/**
* @param $invoice
* @return string
*/
private function invoicePaidMessage($invoice)
{
return 'Invoice #{$invoice->invoice_number} paid!';
}
/**
* @param $invoice
* @return string
*/
private function quoteApprovedMessage($invoice)
{
return 'Quote #{$invoice->invoice_number} approved!';
}
/**
* @param $invoice
* @return string
*/
private function entityViewedMessage($invoice)
{
if($invoice->is_quote)
return 'Quote #{$invoice->invoice_number} viewed!';
else
return 'Invoice #{$invoice->invoice_number} viewed!';
}
}

View File

@ -10,6 +10,7 @@
}
],
"require": {
"turbo124/laravel-push-notification": "dev-laravel5",
"omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525",
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
"omnipay/gocardless": "dev-master",
@ -27,7 +28,6 @@
"jsanc623/phpbenchtime": "2.x",
"lokielse/omnipay-alipay": "dev-master",
"coatesap/omnipay-datacash": "~2.0",
"alfaproject/omnipay-neteller": "1.0.*@dev",
"mfauveau/omnipay-pacnet": "~2.0",
"coatesap/omnipay-paymentsense": "2.0.0",
"coatesap/omnipay-realex": "~2.0",

927
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -164,6 +164,7 @@ return [
'App\Providers\RouteServiceProvider',
'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
'Davibennun\LaravelPushNotification\LaravelPushNotificationServiceProvider',
],
/*
@ -249,6 +250,7 @@ return [
'Rocketeer' => 'Rocketeer\Facades\Rocketeer',
'Socialite' => 'Laravel\Socialite\Facades\Socialite',
'Excel' => 'Maatwebsite\Excel\Facades\Excel',
'PushNotification' => 'Davibennun\LaravelPushNotification\Facades\PushNotification',
],

View File

@ -0,0 +1,23 @@
<?php
return [
'devNinjaIOS' => [
'environment' =>'development',
'certificate'=>app_path().'/certs/ninjaIOS.pem',
'passPhrase' =>'',
'service' =>'apns'
],
'ninjaIOS' => [
'environment' =>'production',
'certificate'=>app_path().'/certs/productionNinjaIOS.pem',
'passPhrase' =>'',
'service' =>'apns'
],
'ninjaAndroid' => [
'environment' =>'production',
'apiKey' =>'yourAPIKey',
'service' =>'gcm'
]
];

View File

@ -22,7 +22,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'Buckaroo', 'provider' => 'Buckaroo_CreditCard', 'payment_library_id' => 1],
['name' => 'Coinbase', 'provider' => 'Coinbase', 'payment_library_id' => 1],
['name' => 'DataCash', 'provider' => 'DataCash', 'payment_library_id' => 1],
['name' => 'Neteller', 'provider' => 'Neteller', 'payment_library_id' => 1],
['name' => 'Neteller', 'provider' => 'Neteller', 'payment_library_id' => 2],
['name' => 'Pacnet', 'provider' => 'Pacnet', 'payment_library_id' => 1],
['name' => 'PaymentSense', 'provider' => 'PaymentSense', 'payment_library_id' => 1],
['name' => 'Realex', 'provider' => 'Realex_Remote', 'payment_library_id' => 1],

View File

@ -1048,7 +1048,10 @@ $LANG = array(
'invoice_item_fields' => 'Invoice Item Fields',
'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.',
'recurring_invoice_number' => 'Recurring Invoice Number',
'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.'
'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.',
'enable_client_portal' => 'Dashboard',
'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.',
);
return $LANG;

View File

@ -12,6 +12,7 @@
{!! Former::open_for_files()
->addClass('warn-on-exit') !!}
{!! Former::populateField('enable_client_portal', intval($account->enable_client_portal)) !!}
{!! Former::populateField('client_view_css', $client_view_css) !!}
@if (!Utils::isNinja() && !Auth::user()->account->isWhiteLabel())
@ -25,7 +26,20 @@
@include('accounts.nav', ['selected' => ACCOUNT_CLIENT_PORTAL])
<div class="row">
<div class="col-md-12">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.client_portal') !!}</h3>
</div>
<div class="panel-body">
<div class="col-md-10 col-md-offset-1">
{!! Former::checkbox('enable_client_portal')
->text(trans('texts.enable'))
->help(trans('texts.enable_client_portal_help')) !!}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
@ -43,7 +57,7 @@
</div>
</div>
</div>
</div>
</div>
</div>
<center>

View File

@ -22,10 +22,8 @@
</div>
<div class="list-group">
@foreach ($settings as $section)
@if ($section != ACCOUNT_CLIENT_PORTAL || !Utils::isNinjaProd())
<a href="{{ URL::to("settings/{$section}") }}" class="list-group-item {{ $selected === $section ? 'selected' : '' }}"
style="width:100%;text-align:left">{{ trans("texts.{$section}") }}</a>
@endif
<a href="{{ URL::to("settings/{$section}") }}" class="list-group-item {{ $selected === $section ? 'selected' : '' }}"
style="width:100%;text-align:left">{{ trans("texts.{$section}") }}</a>
@endforeach
@if ($type === ADVANCED_SETTINGS && !Utils::isNinjaProd())
<a href="{{ URL::to("settings/system_settings") }}" class="list-group-item {{ $selected === 'system_settings' ? 'selected' : '' }}"

View File

@ -76,9 +76,11 @@
<div id="navbar" class="collapse navbar-collapse">
@if (!isset($hideHeader) || !$hideHeader)
<ul class="nav navbar-nav navbar-right">
<li {{ Request::is('*client/dashboard') ? 'class="active"' : '' }}>
{!! link_to('/client/dashboard', trans('texts.dashboard') ) !!}
</li>
@if (!isset($hideDashboard) || !$hideDashboard)
<li {{ Request::is('*client/dashboard') ? 'class="active"' : '' }}>
{!! link_to('/client/dashboard', trans('texts.dashboard') ) !!}
</li>
@endif
<li {{ Request::is('*client/quotes') ? 'class="active"' : '' }}>
{!! link_to('/client/quotes', trans('texts.quotes') ) !!}
</li>