Created
May 11, 2017 17:09
-
-
Save hovsep/82c0ef61afc7308f3b1fb74112cc89fb to your computer and use it in GitHub Desktop.
Laravel 5.4 code example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Controllers; | |
use App\Entities\CampaignState; | |
use App\Entities\EventAction; | |
use App\Entities\GroupScope; | |
use App\Entities\SubjectMode; | |
use App\Facades\BladeStringRenderer; | |
use App\Facades\Mail; | |
use App\Http\Controllers\Traits\RBAC; | |
use App\Jobs\EnqueueStatEvent; | |
use App\Models\Campaign; | |
use App\Models\CampaignDynamicSubject; | |
use App\Models\CampaignHeader; | |
use App\Models\CampaignVariable; | |
use App\Models\EmailTransport; | |
use App\Models\Group; | |
use App\Models\RBAC\Permission; | |
use App\Models\RecipientList; | |
use App\Models\Settings; | |
use App\Models\Template; | |
use App\Models\TrackingCategory; | |
use App\Utils\Campaign\Manager as CampaignManager; | |
use Illuminate\Http\Request; | |
use Illuminate\Pagination\Paginator; | |
use Illuminate\Support\Facades\Config; | |
use Illuminate\Support\Facades\Redirect; | |
use Illuminate\Support\Facades\Session; | |
use Illuminate\Support\Facades\Validator; | |
use Illuminate\Support\Facades\View; | |
use Illuminate\Support\MessageBag; | |
use Triton\Entities\Recipient\RecipientField; | |
use Triton\Entities\Variable\Reserved; | |
use Triton\Entities\Variable\VariableField; | |
class CampaignsController extends Controller { | |
use RBAC; | |
public function __construct() | |
{ | |
$this->setupAccessControll([ | |
Permission::CAMPAIGN_CREATE => ['create', 'store', 'showImportForm', 'import'], | |
Permission::CAMPAIGN_EDIT => ['edit', 'update', 'run', 'stop'], | |
Permission::CAMPAIGN_DELETE => ['destroy'], | |
Permission::CAMPAIGN_TEST => ['test'] | |
]); | |
} | |
/** | |
* Display a listing of the resource. | |
* | |
* @return \Illuminate\Http\Response | |
*/ | |
public function index(Request $request) | |
{ | |
$groupId = $request->get('group_id', Group::ANY); | |
if ($groupId === Group::ANY) { | |
$campaigns = Campaign::orderBy('id')->paginate(Settings::get('campaigns_per_page', 50)); | |
} else { | |
$groupId = (int) $groupId; | |
$campaigns = Campaign::where('group_id', $groupId)->orderBy('id')->paginate(Settings::get('campaigns_per_page', 50)); | |
} | |
//Store state | |
Session::put('campaigns.getParams', [ | |
'page' => Paginator::resolveCurrentPage(), | |
'group_id' => $groupId | |
]); | |
return response(view('campaigns.list', [ | |
'campaigns' => $campaigns, | |
'groupsOptions' => Group::getOptions(GroupScope::CAMPAIGNS, [0 => 'Not assigned', Group::ANY => 'Any']), | |
'groupId' => $groupId //Selected group filter | |
])->render(), 200, [ | |
'Cache-Control' => 'private, must-revalidate,max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0', | |
'Pragma' => 'no-cache' | |
]); | |
} | |
/** | |
* Show the form for creating a new resource. | |
* | |
* @return \Illuminate\Http\Response | |
*/ | |
public function create() | |
{ | |
$listOptions = RecipientList::getOptions(); | |
$templateOptions = Template::getOptions(); | |
if (empty($listOptions)) { | |
return $this->noLists(); | |
} | |
if (empty($templateOptions)) { | |
return $this->noTemplates(); | |
} | |
$groupId = Session::get('campaigns.getParams.group_id', 0); | |
return view('campaigns.create', [ | |
'campaign' => new Campaign(), | |
'lists' => $listOptions, | |
'templates' => $templateOptions, | |
'trackingOptions' => TrackingCategory::getOptions([0 => 'No tracking']), | |
'transportOptions' => EmailTransport::getOptions(), | |
'groupsOptions' => Group::getOptions(GroupScope::CAMPAIGNS, [0 => 'Not assigned']), | |
'groupId' => ($groupId != Group::ANY) ? $groupId : 0//Preset current group filter | |
] | |
); | |
} | |
/** | |
* Store a newly created resource in storage. | |
* | |
* @param \Illuminate\Http\Request $request | |
* @return \Illuminate\Http\Response | |
*/ | |
public function store(Request $request) | |
{ | |
$fields = [ | |
'name' => 'required|string|max:100|unique:campaigns,name', | |
'template_id' => 'required', | |
'list_id' => 'required', | |
'subjectMode' => 'required|in:' . implode(',', SubjectMode::all()), | |
'subject' => 'required_if:subjectMode,' . SubjectMode::SINGLE, | |
'description' => 'nullable|string|max:2000', | |
'tracking_category_id' => 'integer|in:' . implode(',', array_keys(TrackingCategory::getOptions([0 => 'No tracking']))), | |
'group_id' => 'integer|in:' . implode(',', array_keys(Group::getOptions(GroupScope::CAMPAIGNS, [0 => 'Not assigned']))), | |
'transport_id' => 'string|in:' . implode(',', array_keys(EmailTransport::getOptions())), | |
'scheduled_at' => 'nullable|date|after:now' | |
]; | |
$validationMessages = []; | |
###CUSTOM HEADERS VALIDATION RULES START | |
$customHeadersCount = (int) $request->get('customHeadersCount', 0); | |
$usedHeaderNames = [];//Need for validation | |
if ((bool) $request->get('useCustomHeaders') && ($customHeadersCount > 0)) { | |
for($i=0; $i < $customHeadersCount; $i++) { | |
$fields["customHeaderName_$i"] = 'required|string'; | |
$validationMessages["customHeaderName_$i.required"] = 'Header name is required'; | |
if (!empty($usedHeaderNames)) { | |
$fields["customHeaderName_$i"] .= '|not_in:' . implode(',', $usedHeaderNames); | |
$validationMessages["customHeaderName_$i.not_in"] = 'Header name already used'; | |
} | |
$usedHeaderNames[] = strtolower($request->get("customHeaderName_$i")); | |
$fields["customHeaderValue_$i"] = 'required|string'; | |
$validationMessages["customHeaderValue_$i.required"] = 'Header value is required'; | |
} | |
} | |
###CUSTOM HEADERS VALIDATION RULES END | |
###CUSTOM VARIABLES VALIDATION RULES START | |
$reservedVarNames = Reserved::all(); | |
$list = RecipientList::find((int) $request->get('list_id')); | |
if (empty($list)) { | |
throw new \Exception('List not found'); | |
} | |
$listColumnNames = $list->getDataTableColumns(); | |
$customVariablesCount = (int) $request->get('customVariablesCount', 0); | |
$usedVariablesNames = [];//Need for validation | |
if ((bool) $request->get('useCustomVariables') && ($customVariablesCount > 0)) { | |
for($i = 0; $i < $customVariablesCount; $i++) { | |
$fields["customVariableName_$i"] = 'required|string|regex:~^[a-zA-Z_][a-zA-Z0-9_]*$~|not_in:' . implode(',', array_merge($usedVariablesNames, $reservedVarNames, $listColumnNames)); | |
$validationMessages["customVariableName_$i.required"] = 'Variable name is required'; | |
$validationMessages["customVariableName_$i.regex"] = 'Variable name is incorrect'; | |
$validationMessages["customVariableName_$i.not_in"] = 'Variable name reserved or used in list. Please try another name'; | |
$usedVariablesNames[] = strtolower($request->get("customVariableName_$i")); | |
$fields["customVariableValue_$i"] = 'required|string'; | |
$validationMessages["customVariableValue_$i.required"] = 'Variable value is required'; | |
} | |
} | |
###CUSTOM VARIABLES VALIDATION RULES END | |
###DYNAMIC SUBJECTS VALIDATION RULES START | |
$dynamicSubjectsCount = (int) $request->get('dynamicSubjectsCount', 0); | |
$dynamicSubjects = []; | |
if (SubjectMode::isDynamic($request->get('subjectMode')) && $dynamicSubjectsCount > 0) { | |
for($i = 0; $i < $dynamicSubjectsCount; $i++) { | |
$fields["dynamicSubject_$i"] = 'required|string'; | |
$validationMessages["dynamicSubject_$i.required"] = 'Subject can not be empty'; | |
$dynamicSubjects["dynamicSubject_$i"] = $request->get("dynamicSubject_$i"); | |
} | |
} | |
###DYNAMIC SUBJECTS VALIDATION RULES END | |
$values = []; | |
foreach ($fields as $field => $rules) { | |
$values[$field] = $request->get($field); | |
if ((false !== strpos($field, 'customHeaderName_')) || (false !== strpos($field, 'customVariableName_'))) { | |
$values[$field] = strtolower($values[$field]); | |
} | |
} | |
unset($field); | |
unset($rules); | |
/* @var $validator Validator */ | |
$validator = Validator::make($values, $fields, $validationMessages); | |
try { | |
if ($validator->fails()) { | |
throw new \Exception('Validation failed'); | |
} | |
$campaign = new Campaign($values); | |
$campaign->setState(CampaignState::IDLE); | |
###SUBJECTS_VALIDATION_START | |
$context = $campaign->getTestContext(); | |
$subjectScopeVariables = array_map(function($item) {return $item[VariableField::TEST_VALUE];}, $context); | |
View::share($subjectScopeVariables); | |
if ($campaign->subjectMode == SubjectMode::SINGLE) { | |
try { | |
$rendered = BladeStringRenderer::render($campaign->subject); | |
} catch (\Exception $e) { | |
$validator->errors()->add('subject', 'Failed to render subject. Reason: ' . $e->getMessage()); | |
throw $e; | |
} | |
} elseif (SubjectMode::isDynamic($campaign->subjectMode)) { | |
if (count($dynamicSubjects) <= 1) { | |
$validator->errors()->add('tooFewSubjects', 'Please define at least 2 subjects'); | |
throw new \Exception('Dynamic mode does not make sense if you have single subject'); | |
} | |
$dynamicSubjectsRenderingFail = false; | |
foreach($dynamicSubjects as $field => $ds) { | |
try { | |
$rendered = BladeStringRenderer::render($ds); | |
} catch (\Exception $e) { | |
$validator->errors()->add($field, 'Failed to render subject. Reason: ' . $e->getMessage()); | |
$dynamicSubjectsRenderingFail = true;// Do not throw exception here to collect each subject rendering errors | |
} | |
} | |
//Throw validation exception after all subjects are validated | |
if ($dynamicSubjectsRenderingFail) { | |
throw new \Exception('Some dynamic subjects may not be rendered. Please check'); | |
} | |
unset($field); | |
} | |
###SUBJECTS_VALIDATION_END | |
$campaign->save(); | |
//Save subject(s) | |
if ($campaign->subjectMode == SubjectMode::SINGLE) { | |
//Subject already saved within campaign record | |
} elseif (SubjectMode::isDynamic($campaign->subjectMode)) { | |
$subjectOrder = 1; | |
foreach($dynamicSubjects as $field => $ds) { | |
$dSubject = new CampaignDynamicSubject(['campaign_id' => $campaign->id, 'value' => $ds]); | |
if (SubjectMode::SEQUENTIAL == $campaign->subjectMode) { | |
$nextSubjectOrder = ($subjectOrder === count($dynamicSubjects)) ? 1 : ($subjectOrder + 1); | |
$dSubject->order = $subjectOrder; | |
$dSubject->next = $nextSubjectOrder; | |
$subjectOrder++; | |
} | |
$dSubject->save(); | |
} | |
} | |
//Save headers | |
for ($i = 0; $i < $customHeadersCount; $i++) { | |
$header = new CampaignHeader(['campaign_id' => $campaign->id, 'name' => $values["customHeaderName_$i"], 'value' => $values["customHeaderValue_$i"]]); | |
$header->save(); | |
} | |
//Save variables | |
for ($i = 0; $i < $customVariablesCount; $i++) { | |
$variable = new CampaignVariable(['campaign_id' => $campaign->id, 'name' => $values["customVariableName_$i"], 'value' => $values["customVariableValue_$i"]]); | |
$variable->save(); | |
} | |
return Redirect::route('campaigns.index', Session::get('campaign.getParams'))->withMessage('Campaign created !'); | |
} catch (\Exception $e) { | |
return Redirect::to('campaigns/create')->withErrors($validator)->withError('Failed to store campaign. Reason: ' . $e->getMessage())->withInput(); | |
} | |
} | |
/** | |
* Show the form for editing the specified resource. | |
* | |
* @param $id | |
* @return \Illuminate\Contracts\View\Factory|View|\Illuminate\View\View | |
*/ | |
public function edit($id) | |
{ | |
try { | |
if (empty($id)) { | |
throw new \Exception('Empty id received'); | |
} | |
$campaign = Campaign::find($id); | |
if ($campaign->getState() == CampaignState::RUNNING) { | |
throw new \Exception('You can not modify running campaign'); | |
} | |
$listsOptions = RecipientList::getOptions(); | |
$templateOptions = Template::getOptions(); | |
if (empty($listsOptions)) { | |
return $this->noLists(); | |
} | |
if (empty($templateOptions)) { | |
return $this->noTemplates(); | |
} | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
$campaign->useCustomHeaders = (bool) (count($campaign->headers()) > 0); | |
$campaign->useCustomVariables = (bool) (count($campaign->variables()) > 0); | |
return view('campaigns.edit', [ | |
'campaign' => $campaign, | |
'lists' => $listsOptions, | |
'templates' => $templateOptions, | |
'trackingOptions' => TrackingCategory::getOptions([0 => 'No tracking']), | |
'transportOptions' => EmailTransport::getOptions(), | |
'groupsOptions' => Group::getOptions(GroupScope::CAMPAIGNS, [0 => 'Not assigned']) | |
]); | |
} catch (\Exception $e){ | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withError('Failed to edit campaign. Reason: ' . $e->getMessage()); | |
} | |
} | |
/** | |
* Update the specified resource in storage. | |
* | |
* @param \Illuminate\Http\Request $request | |
* @param int $id | |
* @return \Illuminate\Http\Response | |
*/ | |
public function update(Request $request, $id) | |
{ | |
try { | |
if (empty($id)) { | |
throw new \Exception('Empty id received'); | |
} | |
$campaign = Campaign::find($id); | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
$fields = [ | |
'name' => "required|string|max:100|unique:campaigns,name,$id", | |
'template_id' => 'required', | |
'list_id' => 'required', | |
'subjectMode' => 'required|in:' . implode(',', SubjectMode::all()), | |
'subject' => 'required_if:subjectMode,' . SubjectMode::SINGLE, | |
'description' => 'nullable|string|max:2000', | |
'tracking_category_id' => 'integer|in:' . implode(',', array_keys(TrackingCategory::getOptions([0 => 'No tracking']))), | |
'group_id' => 'integer|in:' . implode(',', array_keys(Group::getOptions(GroupScope::CAMPAIGNS, [0 => 'Not assigned']))), | |
'transport_id' => 'string|in:' . implode(',', array_keys(EmailTransport::getOptions())), | |
'scheduled_at' => 'nullable|date|after:now' | |
]; | |
$validationMessages = []; | |
###CUSTOM HEADERS VALIDATION RULES START | |
$customHeadersCount = (int) $request->get('customHeadersCount', 0); | |
$usedHeaderNames = [];//Need for validation | |
if ((bool) $request->get('useCustomHeaders', false) && ($customHeadersCount > 0)) { | |
for($i = 0; $i < $customHeadersCount; $i++) { | |
$fields["customHeaderName_$i"] = 'required|string'; | |
$validationMessages["customHeaderName_$i.required"] = 'Header name is required'; | |
if (!empty($usedHeaderNames)) { | |
$fields["customHeaderName_$i"] .= '|not_in:' . implode(',', $usedHeaderNames); | |
$validationMessages["customHeaderName_$i.not_in"] = 'Header name already used'; | |
} | |
$usedHeaderNames[] = strtolower($request->get("customHeaderName_$i")); | |
$fields["customHeaderValue_$i"] = 'required|string'; | |
$validationMessages["customHeaderValue_$i.required"] = 'Header value is required'; | |
} | |
} | |
###CUSTOM HEADERS VALIDATION RULES END | |
###CUSTOM VARIABLES VALIDATION RULES START | |
$reservedVarNames = Reserved::all(); | |
$list = RecipientList::find((int) $request->get('list_id')); | |
if (empty($list)) { | |
throw new \Exception('List not found'); | |
} | |
$listColumnNames = $list->getDataTableColumns(); | |
$customVariablesCount = (int) $request->get('customVariablesCount', 0); | |
$usedVariablesNames = [];//Need for validation | |
if ((bool) $request->get('useCustomVariables', false) && ($customVariablesCount > 0)) { | |
for($i=0; $i < $customVariablesCount; $i++) { | |
$fields["customVariableName_$i"] = 'required|string|regex:~^[a-zA-Z_][a-zA-Z0-9_]*$~|not_in:' . implode(',', array_merge($usedVariablesNames, $reservedVarNames, $listColumnNames)); | |
$validationMessages["customVariableName_$i.required"] = 'Variable name is required'; | |
$validationMessages["customVariableName_$i.regex"] = 'Variable name is incorrect'; | |
$validationMessages["customVariableName_$i.not_in"] = 'Variable name already reserved. Please try another name'; | |
$usedVariablesNames[] = strtolower($request->get("customVariableName_$i")); | |
$fields["customVariableValue_$i"] = 'required|string'; | |
$validationMessages["customVariableValue_$i.required"] = 'Variable value is required'; | |
} | |
} | |
###CUSTOM VARIABLES VALIDATION RULES END | |
###DYNAMIC SUBJECTS VALIDATION RULES START | |
$dynamicSubjectsCount = (int) $request->get('dynamicSubjectsCount', 0); | |
$dynamicSubjects = []; | |
if (SubjectMode::isDynamic($request->get('subjectMode')) && $dynamicSubjectsCount > 0) { | |
for($i = 0; $i < $dynamicSubjectsCount; $i++) { | |
$fields["dynamicSubject_$i"] = 'required|string'; | |
$validationMessages["dynamicSubject_$i.required"] = 'Subject can not be empty'; | |
$dynamicSubjects["dynamicSubject_$i"] = $request->get("dynamicSubject_$i"); | |
} | |
} | |
###DYNAMIC SUBJECTS VALIDATION RULES END | |
$values = []; | |
foreach ($fields as $field => $rules) { | |
$values[$field] = $request->get($field); | |
if ((false !== strpos($field, 'customHeaderName_')) || (false !== strpos($field, 'customVariableName_'))) { | |
$values[$field] = strtolower($values[$field]); | |
} | |
} | |
unset($field); | |
unset($rules); | |
$validator = Validator::make($values, $fields, $validationMessages); | |
try { | |
if ($validator->fails()) { | |
throw new \Exception('Validation failed'); | |
} | |
###SUBJECTS_VALIDATION_START | |
$context = $campaign->getTestContext(); | |
$subjectScopeVariables = array_map(function($item) {return $item[VariableField::TEST_VALUE];}, $context); | |
View::share($subjectScopeVariables); | |
if ($values['subjectMode'] == SubjectMode::SINGLE) { | |
try { | |
$rendered = BladeStringRenderer::render($values['subject']); | |
} catch (\Exception $e) { | |
$validator->errors()->add('subject', 'Failed to render subject. Reason: ' . $e->getMessage()); | |
throw $e; | |
} | |
} elseif (SubjectMode::isDynamic($values['subjectMode'])) { | |
if (count($dynamicSubjects) <= 1) { | |
$validator->errors()->add('tooFewSubjects', 'Please define at least 2 subjects'); | |
throw new \Exception('Dynamic mode does not make sense if you have single subject'); | |
} | |
$dynamicSubjectsRenderingFail = false; | |
foreach($dynamicSubjects as $field => $ds) { | |
try { | |
$rendered = BladeStringRenderer::render($ds); | |
} catch (\Exception $e) { | |
$validator->errors()->add($field, 'Failed to render subject. Reason: ' . $e->getMessage()); | |
$dynamicSubjectsRenderingFail = true;// Do not throw exception here to collect each subject rendering errors | |
} | |
} | |
//Throw validation exception after all subjects are validated | |
if ($dynamicSubjectsRenderingFail) { | |
throw new \Exception('Some dynamic subjects has errors'); | |
} | |
unset($field); | |
} | |
###SUBJECTS_VALIDATION_END | |
$campaign->update($values); | |
//Save subject(s) | |
$campaign->deleteAllDynamicSubjects(); | |
if ($campaign->subjectMode == SubjectMode::SINGLE) { | |
//Subject already saved within campaign record | |
} elseif (SubjectMode::isDynamic($campaign->subjectMode)) { | |
$subjectOrder = 1; | |
foreach($dynamicSubjects as $field => $ds) { | |
$dSubject = new CampaignDynamicSubject(['campaign_id' => $campaign->id, 'value' => $ds]); | |
if (SubjectMode::SEQUENTIAL == $campaign->subjectMode) { | |
$nextSubjectOrder = ($subjectOrder === count($dynamicSubjects)) ? 1 : ($subjectOrder + 1); | |
$dSubject->order = $subjectOrder; | |
$dSubject->next = $nextSubjectOrder; | |
$subjectOrder++; | |
} | |
$dSubject->save(); | |
} | |
} | |
//Update headers | |
$campaign->deleteAllHeaders(); | |
for ($i = 0; $i < $customHeadersCount; $i++) { | |
$header = new CampaignHeader(['campaign_id' => $campaign->id, 'name' => $values["customHeaderName_$i"], 'value' => $values["customHeaderValue_$i"]]); | |
$header->save(); | |
} | |
//Update variables | |
$campaign->deleteAllVariables(); | |
for ($i = 0; $i < $customVariablesCount; $i++) { | |
$variable = new CampaignVariable(['campaign_id' => $campaign->id, 'name' => $values["customVariableName_$i"], 'value' => $values["customVariableValue_$i"]]); | |
$variable->save(); | |
} | |
if ($request->get('saveMode', 'save') == 'save_and_exit') { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withMessage("Campaign #$id successfully updated !"); | |
} else { | |
return Redirect::action('CampaignsController@edit', ['id' => $id])->withMessage("Campaign #$id successfully updated !"); | |
} | |
} catch (\Exception $e) { | |
return Redirect::action('CampaignsController@edit', ['id' => $id])->withErrors($validator)->withError('Failed to update campaign. Reason: ' . $e->getMessage())->withInput(); | |
} | |
} catch (\Exception $e) { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withError('Failed to update campaign. Reason: ' . $e->getMessage()); | |
} | |
} | |
/** | |
* Remove the specified resource from storage. | |
* | |
* @param int $id | |
* @return \Illuminate\Http\Response | |
*/ | |
public function destroy($id) | |
{ | |
try { | |
if (empty($id)) { | |
throw new \Exception('Empty id received'); | |
} | |
$campaign = Campaign::find($id); | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
if ($campaign->getState() == CampaignState::RUNNING) { | |
throw new \Exception('You can not delete running campaign'); | |
} | |
$deletedCount = $campaign->delete(); | |
if (empty($deletedCount)) { | |
throw new \Exception('DB error'); | |
} | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withMessage("Campaign #$id successfully deleted !"); | |
} catch (\Exception $e) { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withError('Failed to delete campaign. Reason: ' . $e->getMessage()); | |
} | |
} | |
/** | |
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | |
*/ | |
public function noLists() | |
{ | |
return view('campaigns.no_lists'); | |
} | |
/** | |
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | |
*/ | |
public function noTemplates() | |
{ | |
return view('campaigns.no_templates'); | |
} | |
/** | |
* Send or render test email | |
*/ | |
public function test($id, Request $request) | |
{ | |
$formFields = [ | |
"testSubject_$id" => 'required', | |
"campaign_{$id}_testMethod" => 'required|in:send,render' | |
]; | |
//Just for pretty validation messages | |
$formFieldNames = [ | |
"testSubject_$id" => 'subject', | |
"campaign_{$id}_testMethod" => 'test method' | |
]; | |
$formValues = []; | |
foreach ($formFields as $field => $rules) { | |
$formValues[$field] = $request->get($field); | |
} | |
unset($field); | |
unset($rules); | |
try { | |
$dialogValidator = Validator::make($formValues, $formFields); | |
$dialogValidator->setAttributeNames($formFieldNames); | |
/* @var $dialogValidator Validator */ | |
if ($dialogValidator->passes()) { | |
$campaign = Campaign::find($id); | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
$testMethod = $formValues["campaign_{$id}_testMethod"]; | |
$context = $campaign->getTestContext(); | |
$variables = array_map(function($item) {return $item[VariableField::TEST_VALUE];}, $context); | |
//Override redefined variables | |
foreach ($variables as $varName => &$varValue) { | |
$redefined = $request->{"campaign_{$campaign->id}_test_" . $varName}; | |
$varValue = empty($redefined) ? null : $redefined; | |
} | |
//Share them to make available at helper functions scope | |
View::share($variables); | |
//Validate & render subject | |
try { | |
$subject = BladeStringRenderer::render($formValues['testSubject_' . $id]); | |
} catch (\Exception $e) { | |
$dialogValidator->errors()->add('testSubject_' . $id, 'Incorrect syntax in subject'); | |
throw new \Exception('Failed to render subject. Reason: ' . $e->getMessage()); | |
} | |
try { | |
view()->addLocation(Config::get('triton.data_dir') . 'templates' . DIRECTORY_SEPARATOR); | |
$body = view($campaign->template->getViewName())->render(); | |
} catch (\Exception $e) { | |
throw new \Exception('Failed to render template. Reason: ' . $e->getMessage()); | |
} | |
if ('send' === $testMethod) { | |
Mail::send($subject, $body, $variables[RecipientField::EMAIL], $campaign->headersToArray(), $campaign->transport_id); | |
if (count(Mail::failures()) == 0) { | |
if ($campaign->trackingEnabled()) { | |
dispatch((new EnqueueStatEvent( | |
$campaign->trackingCategory->id, | |
$campaign->trackingCategory->name, | |
EventAction::SEND, | |
$variables[RecipientField::USER_ID], | |
$variables[RecipientField::CID]) | |
)->onQueue(Config::get('queue.stats_queue_name'))); | |
} | |
Session::flash('message', 'Mail successfully sent'); | |
} | |
} elseif ('render' === $testMethod) { | |
return view('campaigns.test_render', ['subject' => $subject, 'body' => $body]); | |
} | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams')); | |
} else { | |
throw new \Exception('Form validation failed'); | |
} | |
} catch (\Exception $e) { | |
$dialogValidator->errors()->add('testDialogErrorCampaignId', $id); | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withErrors($dialogValidator)->with('testDialogError_' . $id, 'Oops! Something goes wrong. Details:' . $e->getMessage())->withInput(); | |
} | |
} | |
/** | |
* Export campaign | |
* | |
* @param $id | |
* @return \Symfony\Component\HttpFoundation\StreamedResponse | |
*/ | |
public function export($id) { | |
try { | |
if (empty($id)) { | |
throw new \Exception('Empty id received'); | |
} | |
$campaign = Campaign::find($id); | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
return response()->tritonFile($campaign->getExportableData(), 'campaign', "campaign_{$campaign->id}_{$campaign->name}"); | |
} catch (\Exception $e) { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withError('Failed to export campaign. Reason: ' . $e->getMessage()); | |
} | |
} | |
/** | |
* Import campaign from file | |
* | |
* @param Request $request | |
* @return mixed | |
*/ | |
public function import(Request $request) | |
{ | |
try { | |
$fileInfo = $request->file('campaignFile'); | |
if (empty($fileInfo)) { | |
throw new \Exception('File is not uploaded'); | |
} | |
if (!$fileInfo->isValid()) { | |
throw new \Exception($fileInfo->getErrorMessage()); | |
} | |
if (!is_file($fileInfo->getPathname()) || !is_readable($fileInfo->getPathname())) { | |
throw new \Exception('Failed to read file'); | |
} | |
$errors = new MessageBag(); | |
$fileData = file_get_contents($fileInfo->getPathname(), FILE_TEXT); | |
//Check file | |
$securityToken = md5('campaign'); | |
$fileData = base64_decode($fileData); | |
if (false === strpos($fileData, $securityToken)) { | |
throw new \Exception('Invalid or corrupted file'); | |
} | |
$fileData = str_replace($securityToken, '', $fileData); | |
if (empty($fileData)) { | |
throw new \Exception('Empty file'); | |
} | |
try { | |
$importedData = unserialize($fileData); | |
} catch (\Exception $e) { | |
throw new \Exception('Failed to deserialize data'); | |
} | |
$campaignFields = [ | |
'name' => 'required|string|max:100|unique:campaigns,name', | |
'template_id' => 'required', | |
'list_id' => 'required', | |
'subjectMode' => 'required|in:' . implode(',', SubjectMode::all()), | |
'subject' => 'required_if:subjectMode,' . SubjectMode::SINGLE, | |
'description' => 'nullable|string|max:2000', | |
'scheduled_at' => 'nullable|date|after:now' | |
]; | |
$campaignValues = []; | |
$customValidationMessages = []; | |
foreach ($campaignFields as $field => $rules) { | |
$campaignValues[$field] = isset($importedData[$field]) ? trim($importedData[$field]) : null; | |
} | |
unset($field); | |
unset($rules); | |
//Handle template reference | |
if (!empty($importedData['template_name'])) { | |
$template = Template::where('name', $importedData['template_name'])->first(); | |
$customValidationMessages['template_id.required'] = 'Template ' . $importedData['template_name'] . ' not found'; | |
if (!empty($template)) { | |
$campaignValues['template_id'] = $template->id; | |
} | |
} | |
//Handle list reference | |
if (!empty($importedData['list_name'])) { | |
$list = RecipientList::where('name', $importedData['list_name'])->first(); | |
$customValidationMessages['list_id.required'] = 'List ' . $importedData['list_name'] . ' not found'; | |
if (!empty($list)) { | |
$campaignValues['list_id'] = $list->id; | |
} | |
} | |
//Handle group reference | |
if (!empty($importedData['group_name'])) { | |
$group = Group::where([['scope', '=', GroupScope::CAMPAIGNS], ['name', '=', $importedData['group_name']]])->first(); | |
if (!empty($group)) { | |
$campaignValues['group_id'] = $group->id; | |
} | |
} | |
//Handle category reference | |
if (!empty($importedData['tracking_category_name'])) { | |
$category = TrackingCategory::where('name', $importedData['tracking_category_name'])->first(); | |
if (!empty($category)) { | |
$campaignValues['tracking_category_id'] = $category->id; | |
} | |
} | |
//Check transport | |
if (!empty($importedData['transport_id'])) { | |
$campaignValues['transport_id'] = Config::has('mail.custom_smtp_transports.' . $importedData['transport_id']) ? $importedData['transport_id'] : EmailTransport::DEFAULT_TRANSPORT_ID; | |
} | |
/* @var $campaignValidator Validator */ | |
$campaignValidator = Validator::make($campaignValues, $campaignFields, $customValidationMessages); | |
if ($campaignValidator->fails()) { | |
/* @var $errors MessageBag */ | |
$errors = $campaignValidator->errors(); | |
throw new \Exception('Campaign validation failed'); | |
} | |
$campaign = new Campaign($campaignValues); | |
if ($campaign->save()) { | |
//Save subject(s) | |
if ($campaign->subjectMode == SubjectMode::SINGLE) { | |
//Subject already saved within campaign record | |
} elseif (SubjectMode::isDynamic($campaign->subjectMode)) { | |
$subjectOrder = 1; | |
foreach($importedData['dynamic_subjects'] as $item) { | |
$dSubject = new CampaignDynamicSubject(['campaign_id' => $campaign->id, 'value' => $item['value']]); | |
if (SubjectMode::SEQUENTIAL == $campaign->subjectMode) { | |
$nextSubjectOrder = ($subjectOrder === count($importedData['dynamic_subjects'])) ? 1 : ($subjectOrder + 1); | |
$dSubject->order = $subjectOrder; | |
$dSubject->next = $nextSubjectOrder; | |
$subjectOrder++; | |
} | |
$dSubject->save(); | |
} | |
} | |
//Save headers | |
foreach($importedData['headers'] as $headerName => $headerValue) { | |
$header = new CampaignHeader(['campaign_id' => $campaign->id, 'name' => $headerName, 'value' => $headerValue]); | |
$header->save(); | |
} | |
//Save variables | |
foreach($importedData['variables'] as $varName => $varValue) { | |
$variable = new CampaignVariable(['campaign_id' => $campaign->id, 'name' => $varName, 'value' => $varValue]); | |
$variable->save(); | |
} | |
} | |
if ($request->get('importMode', 'import') == 'import_and_exit') { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withMessage("Campaign #{$campaign->id} successfully imported !"); | |
} elseif ($request->get('importMode', 'import') == 'import_and_edit') { | |
return Redirect::action('CampaignsController@edit', ['id' => $campaign->id])->withMessage("Campaign #{$campaign->id} just imported !"); | |
} else { | |
return Redirect::action('CampaignsController@showImportForm')->withMessage("Campaign #{$campaign->id} successfully imported !"); | |
} | |
} catch (\Exception $e) { | |
return Redirect::action('CampaignsController@showImportForm')->withError('Failed to import campaign. Reason: ' . $e->getMessage())->withErrors($errors)->withInput(); | |
} | |
} | |
/** | |
* Show import gui | |
* | |
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | |
*/ | |
public function showImportForm() | |
{ | |
return view('campaigns.import'); | |
} | |
/** | |
* Start sending campaign | |
* | |
* @param $id | |
* @return mixed | |
*/ | |
public function run($id) { | |
try { | |
if (empty($id)) { | |
throw new \Exception('Empty id received'); | |
} | |
$campaign = Campaign::find($id); | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
CampaignManager::run($campaign); | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withMessage("Campaign #$id will be launched in minute"); | |
} catch (\Exception $e) { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withError('Failed to run campaign. Reason: ' . $e->getMessage()); | |
} | |
} | |
/** | |
* Stop sending campaign | |
* | |
* @param $id | |
* @return mixed | |
*/ | |
public function pause($id) { | |
try { | |
if (empty($id)) { | |
throw new \Exception('Empty id received'); | |
} | |
$campaign = Campaign::find($id); | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
CampaignManager::pause($campaign); | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withMessage("Campaign #$id paused"); | |
} catch (\Exception $e) { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withError('Failed to run campaign. Reason: ' . $e->getMessage()); | |
} | |
} | |
/** | |
* Stop campaign & reset recipients states | |
* | |
* @param $id | |
* @return mixed | |
*/ | |
public function stop($id) { | |
try { | |
if (empty($id)) { | |
throw new \Exception('Empty id received'); | |
} | |
$campaign = Campaign::find($id); | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
CampaignManager::stop($campaign); | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withMessage("Campaign #$id stopped"); | |
} catch (\Exception $e) { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withError('Failed to run campaign. Reason: ' . $e->getMessage()); | |
} | |
} | |
/** | |
* @param $id | |
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse | |
*/ | |
public function downloadSkippedReport($id) | |
{ | |
try { | |
if (empty($id)) { | |
throw new \Exception('Empty id received'); | |
} | |
$campaign = Campaign::find($id); | |
if (empty($campaign)) { | |
throw new \Exception('Campaign not found'); | |
} | |
return response()->download($campaign->getSkippedReportFilePath()); | |
} catch (\Exception $e) { | |
return Redirect::route('campaigns.index', Session::get('campaigns.getParams'))->withError('Failed to download file. Reason: ' . $e->getMessage()); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Listeners; | |
use Illuminate\Auth\Events\Registered; | |
use Illuminate\Support\Facades\Config; | |
use PHPZen\LaravelRbac\Model\Role; | |
use Illuminate\Support\Facades\Log; | |
class AssignDefaultRole | |
{ | |
/** | |
* Handle the event. | |
* | |
* @param Registered $event | |
* @return void | |
*/ | |
public function handle(Registered $event) | |
{ | |
$user = $event->user; | |
$default_roles = Config::get('rbac.default_roles'); | |
if (!empty($default_roles) && is_array($default_roles)) { | |
foreach($default_roles as $slug) { | |
/* @var $r Role */ | |
$r = Role::where('slug', $slug)->first(); | |
$user->roles()->attach($r->id); | |
Log::info('Role assigned to user', ['uid' => $user->id, 'role' => $r->slug]); | |
} | |
} else { | |
Log::notice('Default user roles not defined'); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Jobs; | |
use App\Entities\EventAction; | |
use App\Exceptions\EventExpiredException; | |
use App\Exceptions\EventTriggerDisabledException; | |
use App\Exceptions\EventTriggerNotFoundException; | |
use App\Exceptions\MailArchiveException; | |
use App\Facades\BladeStringRenderer; | |
use App\Facades\DataContractsRepository; | |
use App\Facades\Mail; | |
use App\Models\Trigger; | |
use Illuminate\Bus\Queueable; | |
use Illuminate\Contracts\Queue\ShouldQueue; | |
use Illuminate\Queue\InteractsWithQueue; | |
use Illuminate\Queue\SerializesModels; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\Config; | |
use Illuminate\Support\Facades\Log; | |
use Illuminate\Support\Facades\Queue; | |
use Illuminate\Support\Facades\View; | |
use Swift_RfcComplianceException; | |
use Triton\DataContractProtocol\BaseEventContract; | |
use Triton\Entities\Recipient\RecipientField; | |
class ProcessEvent extends Job implements ShouldQueue { | |
use InteractsWithQueue, Queueable, SerializesModels; | |
/** | |
* Max attempts to process failed jobs | |
*/ | |
const DEFAULT_JOB_PROCESS_ATTEMPTS = 3; | |
private $maxJobProcessAttempts = 0; | |
/** | |
* Create a new job instance. | |
* | |
*/ | |
public function __construct() | |
{ | |
view()->addLocation(Config::get('triton.data_dir') . 'templates' . DIRECTORY_SEPARATOR); | |
$this->maxJobProcessAttempts = Config::get('triton.event_process_attempts', self::DEFAULT_JOB_PROCESS_ATTEMPTS); | |
} | |
/** | |
* Execute the job. | |
* | |
* @return void | |
*/ | |
public function fire($job, $data) | |
{ | |
//Track job processing attempts | |
if (empty($data['attempts'])) { | |
$data['attempts'] = 1; | |
} else { | |
$data['attempts']++; | |
} | |
try { | |
$contract = DataContractsRepository::factory($data); | |
/* @var $contract BaseEventContract */ | |
if (!$contract->isValid()) { | |
throw new \Exception('Data Contract validation failed'); | |
} | |
$trigger = Trigger::findByEventId($contract->getEventId()); | |
if (empty($trigger)) { | |
throw new EventTriggerNotFoundException('Trigger not found for event ' . $contract->getEventId()); | |
} | |
if (!empty($contract->getTimestamp()) && !empty($trigger->event_ttl)) { | |
if (time() > ($contract->getTimestamp() + $trigger->event_ttl * 3600)) { | |
throw new EventExpiredException('Event expired'); | |
} | |
} | |
if (!$trigger->is_active) { | |
throw new EventTriggerDisabledException('Trigger is disabled'); | |
} | |
$this->pull($trigger, $contract); | |
} catch (MailArchiveException $mae) { | |
Log::warning('Event processed, but mail is not archived', ['reason' => $mae->getMessage()]); | |
} catch (\Exception $e) { | |
//Event should be skipped | |
if (($e instanceof EventTriggerNotFoundException) || | |
($e instanceof EventTriggerDisabledException) || | |
($e instanceof EventExpiredException) || | |
($e instanceof Swift_RfcComplianceException)) { | |
Cache::tags(['today_counters'])->increment('event.skipped'); | |
Log::info('Event skipped', ['reason' => $e->getMessage()]); | |
} else { | |
//Event should be precessed again | |
if ($data['attempts'] < $this->maxJobProcessAttempts) { | |
Queue::pushOn(Config::get('queue.failed_events_queue_name'), self::class, $data); | |
Cache::tags(['today_counters'])->increment('event.moved_to_failed_queue'); | |
Log::error('Failed to process event. Moved to failed jobs queue', ['reason' => $e->getMessage(), 'attempts' => $data['attempts']]); | |
} else { | |
Log::error('Failed to process event. Max process attempts exceed. Event skipped', ['reason' => $e->getMessage(), 'attempts' => $data['attempts']]); | |
} | |
} | |
} finally { | |
$job->delete(); | |
} | |
} | |
/** | |
* Execute trigger scenario | |
* | |
* @param Trigger $trigger | |
* @param BaseEventContract $contract | |
* @throws \Exception | |
*/ | |
private function pull(Trigger $trigger, BaseEventContract $contract) | |
{ | |
Cache::tags(['today_counters'])->increment("trigger.{$trigger->id}.pulled"); | |
$variables = $trigger->getActualContext($contract); | |
$userId = empty($variables[RecipientField::USER_ID]) ? null : $variables[RecipientField::USER_ID]; | |
$userCid = empty($variables[RecipientField::CID]) ? null : $variables[RecipientField::CID]; | |
$userEmail = empty($variables[RecipientField::EMAIL]) ? null : $variables[RecipientField::EMAIL]; | |
View::share($variables); | |
try { | |
$subject = BladeStringRenderer::render($trigger->resolveSubject($userId)); | |
} catch (\Exception $e) { | |
throw new \Exception("Failed to render subject (trigger id #{$trigger->id}). Reason: " . $e->getMessage()); | |
} | |
try { | |
$renderAttempts = Config::get('triton.template_render_attempts', 3); | |
do { | |
$body = view($trigger->template->getViewName())->render(); | |
$renderAttempts --; | |
if (empty($body)) { | |
usleep(500000); | |
} | |
} while (($renderAttempts > 0) && empty($body)); | |
if (empty($body)) { | |
throw new \Exception('Empty template rendered'); | |
} | |
} catch (\Exception $e) { | |
throw new \Exception('Failed to render template. Reason: ' . strtolower($e->getMessage())); | |
} | |
Mail::send($subject, $body, $userEmail, $trigger->headersToArray(), $trigger->transport_id, Config::get('triton.archive_trigger_mails')); | |
if ((count(Mail::failures()) == 0) && $trigger->trackingEnabled()) { | |
dispatch( | |
(new EnqueueStatEvent( | |
$trigger->trackingCategory->id, | |
$trigger->trackingCategory->name, | |
EventAction::SEND, | |
$userId, | |
$userCid) | |
)->onQueue(Config::get('queue.stats_queue_name'))); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Models; | |
use App\Entities\CampaignCounter; | |
use App\Entities\CampaignState; | |
use App\Entities\RecipientState; | |
use App\Entities\SubjectMode; | |
use App\Models\Traits\RecipientTestContext; | |
use App\Models\Traits\SubjectResolver; | |
use App\Utils\Campaign\Manager; | |
use Carbon\Carbon; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\DB; | |
use Illuminate\Support\Facades\Log; | |
use Illuminate\Support\Facades\Config; | |
use Illuminate\Support\Facades\Storage; | |
use Triton\Entities\Recipient\RecipientField; | |
use Triton\Entities\Variable\Context; | |
use Triton\Entities\Variable\VariableField; | |
class Campaign extends Model { | |
use SubjectResolver, RecipientTestContext; | |
protected $table = 'campaigns'; | |
public $timestamps = false; | |
public $fillable = [ | |
'name', | |
'template_id', | |
'list_id', | |
'subjectMode', | |
'subject', | |
'description', | |
'tracking_category_id', | |
'group_id', | |
'transport_id', | |
'scheduled_at' | |
]; | |
/** | |
* Relationship with template | |
* | |
* @return \Illuminate\Database\Eloquent\Relations\HasOne | |
*/ | |
public function template() | |
{ | |
return $this->hasOne(Template::class, 'id', 'template_id'); | |
} | |
/** | |
* Relationship with list | |
* | |
* @return \Illuminate\Database\Eloquent\Relations\HasOne | |
*/ | |
public function recipientsList() | |
{ | |
return $this->hasOne(RecipientList::class, 'id', 'list_id'); | |
} | |
/** | |
* Relationship with tracking category | |
* | |
* @return \Illuminate\Database\Eloquent\Relations\HasOne | |
*/ | |
public function trackingCategory() | |
{ | |
return $this->hasOne(TrackingCategory::class, 'id', 'tracking_category_id'); | |
} | |
/** | |
* Relationship with group | |
* | |
* @return \Illuminate\Database\Eloquent\Relations\HasOne | |
*/ | |
public function group() | |
{ | |
return $this->hasOne(Group::class, 'id', 'group_id'); | |
} | |
/** | |
* Returns related variables | |
* | |
* @return \Illuminate\Database\Eloquent\Collection | |
*/ | |
public function variables() | |
{ | |
return $this->hasMany(CampaignVariable::class)->get(); | |
} | |
/** | |
* Returns user defined variables map | |
* | |
* @return array | |
*/ | |
public function varsToArray() | |
{ | |
return $this->variables()->pluck('value', 'name')->toArray(); | |
} | |
/** | |
* Returns related headers | |
* | |
* @return \Illuminate\Database\Eloquent\Collection | |
*/ | |
public function headers() | |
{ | |
return $this->hasMany(CampaignHeader::class)->get(); | |
} | |
/** | |
* Returns header map | |
* | |
* @return array | |
*/ | |
public function headersToArray() | |
{ | |
return $this->headers()->pluck('value', 'name')->toArray(); | |
} | |
/** | |
* Delete all headers | |
*/ | |
public function deleteAllHeaders() | |
{ | |
return CampaignHeader::where('campaign_id', $this->id)->delete(); | |
} | |
/** | |
* Delete all variables | |
*/ | |
public function deleteAllVariables() | |
{ | |
return CampaignVariable::where('campaign_id', $this->id)->delete(); | |
} | |
/** | |
* Returns related dynamic subjects | |
* | |
* @return \Illuminate\Database\Eloquent\Relations\HasMany | |
*/ | |
public function dynamicSubjects() | |
{ | |
return $this->hasMany(CampaignDynamicSubject::class); | |
} | |
/** | |
* Delete all dynamic subjects | |
*/ | |
public function deleteAllDynamicSubjects() | |
{ | |
return CampaignDynamicSubject::where('campaign_id', $this->id)->delete(); | |
} | |
/** | |
* @param array $options | |
* @return bool | |
*/ | |
public function save(array $options = []) | |
{ | |
if (SubjectMode::isDynamic($this->subjectMode)) { | |
$this->subject = null; | |
} | |
return parent::save($options); | |
} | |
/** | |
* Returns exportable presentation of campaign | |
* | |
* @return array | |
*/ | |
public function getExportableData() | |
{ | |
return [ | |
'name' => $this->name, | |
'subjectMode' => $this->subjectMode, | |
'subject' => $this->subject, | |
'description' => $this->description, | |
'template_name' => empty($this->template_id) ? null : $this->template->name, | |
'list_name' => empty($this->list_id) ? null : $this->recipientsList->name, | |
'transport_id' => empty($this->transport_id) ? EmailTransport::DEFAULT_TRANSPORT_ID : $this->transport_id, | |
'tracking_category_name' => empty($this->tracking_category_id) ? null : $this->trackingCategory->name, | |
'group_name' => empty($this->group_id) ? null : $this->group->name, | |
'dynamic_subjects' => ($this->subjectMode !== SubjectMode::SINGLE) ? $this->dynamicSubjects()->get()->toArray() : [], | |
'headers' => $this->headersToArray(), | |
'variables' => $this->varsToArray(), | |
'scheduled_at' => $this->scheduled_at | |
]; | |
} | |
/** | |
* Returns array of campaign contexts | |
* | |
* @return array | |
*/ | |
public static function getContextOptions() | |
{ | |
return self::orderBy('name')->pluck('name', 'id')->mapWithKeys(function($item, $key) {return ['campaign_' . $key => $item];})->toArray(); | |
} | |
/** | |
* Returns test-mode context (variables with meta) | |
* | |
* @return array | |
*/ | |
public function getTestContext() | |
{ | |
$campaignVars = []; | |
$campaignVars = $this->variables()->pluck('name', 'value')->mapWithKeys( | |
function($varName, $varValue) { | |
return [$varName => [ | |
VariableField::CONTEXT => Context::CAMPAIGN, | |
VariableField::DESCRIPTION => 'Custom campaign variable', | |
VariableField::TEST_VALUE => $varValue | |
] | |
]; | |
})->toArray(); | |
$listVars = []; | |
$listColumns = $this->recipientsList->getDataTableColumns(); | |
//Some columns are not available in campaign context | |
$listColumns = array_diff($listColumns, RecipientField::all()); | |
foreach ($listColumns as $column) { | |
$listVars[$column] = [ | |
VariableField::CONTEXT => Context::RECIPIENT_LIST, | |
VariableField::DESCRIPTION => "List '{$this->recipientsList->name}' column value", | |
VariableField::TEST_VALUE => DB::table($this->recipientsList->getDataTableName()) | |
->select($column) | |
->whereNotNull($column) | |
->limit(1) | |
->value($column) | |
]; | |
} | |
$runtimeVars = []; | |
$runtimeVars['tracking_category_id'] = [ | |
VariableField::CONTEXT => Context::CAMPAIGN, | |
VariableField::TEST_VALUE => $this->trackingEnabled() ? $this->tracking_category_id : 0, | |
VariableField::DESCRIPTION => 'Campaign tracking category ID' | |
]; | |
$runtimeVars['tracking_category_name'] = [ | |
VariableField::CONTEXT => Context::CAMPAIGN, | |
VariableField::TEST_VALUE => $this->trackingEnabled() ? $this->trackingCategory->name : '', | |
VariableField::DESCRIPTION => 'Campaign tracking category name' | |
]; | |
return array_merge($this->getRecipientTestContext(), $campaignVars, $listVars, $runtimeVars); | |
} | |
/** | |
* Returns actual context | |
* | |
* @param array $recipientData | |
* @return array | |
*/ | |
public function getActualContext(array $recipientData) | |
{ | |
$campaignVars = []; | |
$campaignVars = $this->varsToArray(); | |
$runtimeVars = []; | |
if ($this->trackingEnabled()) { | |
$runtimeVars['tracking_category_id'] = $this->tracking_category_id; | |
$runtimeVars['tracking_category_name'] = $this->trackingCategory->name; | |
} | |
return array_merge($recipientData, $campaignVars, $runtimeVars); | |
} | |
/** | |
* Set campaign set | |
* | |
* @param $state | |
* @param string $stateVerbose | |
* @return $this | |
*/ | |
public function setState($state, $stateVerbose = '') | |
{ | |
$oldState = $this->getState(); | |
$this->state = $state; | |
$this->state_verbose = $stateVerbose; | |
if ($this->update()) { | |
Cache::forever("campaign:{$this->id}_state", $state); | |
Log::info('Campaign state changed', ['campaign' => $this->id, 'state' => "$oldState ==> $state", 'verbose' => $stateVerbose]); | |
} | |
return $this; | |
} | |
/** | |
* Returns campaign state | |
* | |
* @return mixed | |
*/ | |
public function getState() | |
{ | |
return Cache::rememberForever("campaign:{$this->id}_state", function () { | |
return $this->state; | |
}); | |
} | |
/** | |
* Returns true if campaign in one of idle states | |
* | |
* @return bool | |
*/ | |
public function isIdle() | |
{ | |
return in_array($this->getState(), CampaignState::getIdleStates()); | |
} | |
/** | |
* Returns recipients count | |
* | |
* @return mixed | |
*/ | |
public function getListSize() | |
{ | |
return $this->recipientsList->getRecipientsCount(); | |
} | |
/** | |
* Returns true if campaign running | |
* | |
* @return bool | |
*/ | |
public function isRunning() | |
{ | |
return $this->getState() == CampaignState::RUNNING; | |
} | |
/** | |
* Delete campaign | |
* | |
* @return bool|null | |
* @throws \Exception | |
*/ | |
public function delete() | |
{ | |
Manager::deleteCampaignAllCounters($this); | |
Manager::forgetRecipientStates($this); | |
$this->deleteSkippedReport(); | |
return parent::delete(); | |
} | |
/** | |
* Returns true if campaign has related tracking category | |
* | |
* @return bool | |
*/ | |
public function trackingEnabled() | |
{ | |
return !empty($this->trackingCategory); | |
} | |
/** | |
* Returns default subject | |
* used as fallback | |
* | |
* @return mixed | |
*/ | |
protected function getDefaultSubject() | |
{ | |
return Config::get('triton.default_campaign_subject'); | |
} | |
/** | |
* Should campaign start now? | |
* | |
*/ | |
public function scheduledNow() | |
{ | |
return Carbon::parse($this->scheduled_at)->isSameAs('Y-m-d H:i', Carbon::now()); | |
} | |
/** | |
* Do we processed whole campaign? | |
*/ | |
public function isSentOut() | |
{ | |
$processedCount = Manager::getCampaignCounter($this, CampaignCounter::PROCESSED); | |
$enqueuedCount = Manager::getCampaignCounter($this, RecipientState::ENQUEUED); | |
return (!empty($processedCount) && !empty($enqueuedCount) && ($processedCount == $enqueuedCount)); | |
} | |
/** | |
* Returns short file name | |
* | |
* @return string | |
*/ | |
public function getSkippedReportFileName() | |
{ | |
return "campaign_{$this->id}_report.txt"; | |
} | |
/** | |
* Returns absolute file path | |
* | |
* @return string | |
*/ | |
public function getSkippedReportFilePath() | |
{ | |
return Storage::disk('reports')->getDriver()->getAdapter()->getPathPrefix() . $this->getSkippedReportFileName(); | |
} | |
/** | |
* Returns true if report file exists | |
* | |
* @return mixed | |
*/ | |
public function hasSkippedReport() | |
{ | |
return Storage::disk('reports')->exists($this->getSkippedReportFileName()); | |
} | |
/** | |
* Delete skipped recipients report | |
*/ | |
public function deleteSkippedReport() | |
{ | |
if ($this->hasSkippedReport()) { | |
Storage::disk('reports')->delete($this->getSkippedReportFileName()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment