Skip to content

Commit c7d8203

Browse files
authored
Merge pull request #17866 from grokability/_reworked_tcpdf
Fixed #14744 and #17808 - Added CJK and Arabic font support for asset acceptance
2 parents 96b5c1d + e977771 commit c7d8203

20 files changed

+604
-877
lines changed

app/Helpers/Helper.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,34 @@ public static function defaultChartColors(int $index = 0)
435435
return $colors[$index];
436436
}
437437

438+
/**
439+
* Check if a string has any RTL characters
440+
* @param $value
441+
* @return bool
442+
*/
443+
public static function hasRtl($string) {
444+
$rtlChar = '/[\x{0590}-\x{083F}]|[\x{08A0}-\x{08FF}]|[\x{FB1D}-\x{FDFF}]|[\x{FE70}-\x{FEFF}]/u';
445+
return preg_match($rtlChar, $string) != 0;
446+
}
447+
448+
// is chinese, japanese or korean language
449+
public static function isCjk($string) {
450+
return Helper::isChinese($string) || Helper::isJapanese($string) || Helper::isKorean($string);
451+
}
452+
453+
public static function isChinese($string) {
454+
return preg_match("/\p{Han}+/u", $string);
455+
}
456+
457+
public static function isJapanese($string) {
458+
return preg_match('/[\x{4E00}-\x{9FBF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $string);
459+
}
460+
461+
public static function isKorean($string) {
462+
return preg_match('/[\x{3130}-\x{318F}\x{AC00}-\x{D7AF}]/u', $string);
463+
}
464+
465+
438466
/**
439467
* Increases or decreases the brightness of a color by a percentage of the current brightness.
440468
*

app/Http/Controllers/Account/AcceptanceController.php

Lines changed: 84 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
use App\Events\ItemDeclined;
99
use App\Http\Controllers\Controller;
1010
use App\Mail\CheckoutAcceptanceResponseMail;
11-
use App\Models\Actionlog;
12-
use App\Models\Asset;
1311
use App\Models\CheckoutAcceptance;
1412
use App\Models\Company;
1513
use App\Models\Contracts\Acceptable;
@@ -25,16 +23,16 @@
2523
use App\Notifications\AcceptanceAssetDeclinedNotification;
2624
use Exception;
2725
use Illuminate\Http\Request;
28-
use Illuminate\Support\Facades\Auth;
2926
use Illuminate\Support\Facades\Mail;
3027
use Illuminate\Support\Facades\Storage;
3128
use Illuminate\Support\Str;
3229
use App\Http\Controllers\SettingsController;
33-
use Barryvdh\DomPDF\Facade\Pdf;
3430
use Carbon\Carbon;
3531
use \Illuminate\Contracts\View\View;
3632
use \Illuminate\Http\RedirectResponse;
3733
use Illuminate\Support\Facades\Log;
34+
use TCPDF;
35+
use App\Helpers\Helper;
3836

3937
class AcceptanceController extends Controller
4038
{
@@ -107,12 +105,18 @@ public function store(Request $request, $id) : RedirectResponse
107105
}
108106

109107
/**
110-
* Get the signature and save it
108+
* Check for the signature directory
111109
*/
112110
if (! Storage::exists('private_uploads/signatures')) {
113111
Storage::makeDirectory('private_uploads/signatures', 775);
114112
}
115113

114+
/**
115+
* Check for the eula-pdfs directory
116+
*/
117+
if (! Storage::exists('private_uploads/eula-pdfs')) {
118+
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
119+
}
116120

117121

118122
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
@@ -123,19 +127,7 @@ public function store(Request $request, $id) : RedirectResponse
123127

124128
if ($request->input('asset_acceptance') == 'accepted') {
125129

126-
/**
127-
* Check for the eula-pdfs directory
128-
*/
129-
if (! Storage::exists('private_uploads/eula-pdfs')) {
130-
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
131-
}
132-
133130
if (Setting::getSettings()->require_accept_signature == '1') {
134-
135-
// Check if the signature directory exists, if not create it
136-
if (!Storage::exists('private_uploads/signatures')) {
137-
Storage::makeDirectory('private_uploads/signatures', 775);
138-
}
139131

140132
// The item was accepted, check for a signature
141133
if ($request->filled('signature_output')) {
@@ -152,56 +144,8 @@ public function store(Request $request, $id) : RedirectResponse
152144
}
153145
}
154146

155-
156147
$assigned_user = User::find($acceptance->assigned_to_id);
157-
// this is horrible
158-
switch($acceptance->checkoutable_type){
159-
case 'App\Models\Asset':
160-
$pdf_view_route ='account.accept.accept-asset-eula';
161-
$asset_model = AssetModel::find($item->model_id);
162-
if (!$asset_model) {
163-
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
164-
}
165-
$display_model = $asset_model->name;
166-
break;
167-
168-
case 'App\Models\Accessory':
169-
$pdf_view_route ='account.accept.accept-accessory-eula';
170-
$accessory = Accessory::find($item->id);
171-
$display_model = $accessory->name;
172-
break;
173-
174-
case 'App\Models\LicenseSeat':
175-
$pdf_view_route ='account.accept.accept-license-eula';
176-
$license = License::find($item->license_id);
177-
$display_model = $license->name;
178-
break;
179-
180-
case 'App\Models\Component':
181-
$pdf_view_route ='account.accept.accept-component-eula';
182-
$component = Component::find($item->id);
183-
$display_model = $component->name;
184-
break;
185-
186-
case 'App\Models\Consumable':
187-
$pdf_view_route ='account.accept.accept-consumable-eula';
188-
$consumable = Consumable::find($item->id);
189-
$display_model = $consumable->name;
190-
break;
191-
}
192-
// if ($acceptance->checkoutable_type == 'App\Models\Asset') {
193-
// $pdf_view_route ='account.accept.accept-asset-eula';
194-
// $asset_model = AssetModel::find($item->model_id);
195-
// $display_model = $asset_model->name;
196-
// $assigned_to = User::find($item->assigned_to)->present()->fullName;
197-
//
198-
// } elseif ($acceptance->checkoutable_type== 'App\Models\Accessory') {
199-
// $pdf_view_route ='account.accept.accept-accessory-eula';
200-
// $accessory = Accessory::find($item->id);
201-
// $display_model = $accessory->name;
202-
// $assigned_to = User::find($item->assignedTo);
203-
//
204-
// }
148+
205149

206150
/**
207151
* Gather the data for the PDF. We fire this whether there is a signature required or not,
@@ -225,9 +169,9 @@ public function store(Request $request, $id) : RedirectResponse
225169
'item_status' => $item->assetstatus?->name,
226170
'eula' => $item->getEula(),
227171
'note' => $request->input('note'),
228-
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
229-
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
230-
'assigned_to' => $assigned_user->present()->fullName,
172+
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d H:i:s'),
173+
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d H:i:s'),
174+
'assigned_to' => $assigned_user->display_name,
231175
'company_name' => $branding_settings->site_name,
232176
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
233177
'logo' => $path_logo,
@@ -236,11 +180,73 @@ public function store(Request $request, $id) : RedirectResponse
236180
'qty' => $acceptance->qty ?? 1,
237181
];
238182

239-
if ($pdf_view_route!='') {
240-
Log::debug($pdf_filename.' is the filename, and the route was specified.');
241-
$pdf = Pdf::loadView($pdf_view_route, $data);
242-
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
183+
// set some language dependent data:
184+
$lg = Array();
185+
$lg['a_meta_charset'] = 'UTF-8';
186+
$lg['w_page'] = 'page';
187+
188+
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
189+
$pdf->setRTL(false);
190+
$pdf->setLanguageArray($lg);
191+
$pdf->SetFontSubsetting(true);
192+
$pdf->SetCreator('Snipe-IT');
193+
$pdf->SetAuthor($data['assigned_to']);
194+
$pdf->SetTitle('Asset Acceptance: '.$data['item_tag']);
195+
$pdf->SetSubject('Asset Acceptance: '.$data['item_tag']);
196+
$pdf->SetKeywords('Snipe-IT, assets, acceptance, eula', 'tos');
197+
$pdf->SetFont('dejavusans', '', 8, '', true);
198+
$pdf->SetPrintHeader(false);
199+
$pdf->SetPrintFooter(false);
200+
$pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
201+
$pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
202+
203+
$pdf->AddPage();
204+
$pdf->writeHTML('<img src="'.$path_logo.'" height="30">', true, 0, true, 0, '');
205+
206+
if ($data['item_serial']) {
207+
$pdf->writeHTML("<strong>" . trans('general.asset_tag') . '</strong>: ' . $data['item_tag'], true, 0, true, 0, '');
208+
}
209+
$pdf->writeHTML("<strong>".trans('general.asset_model').'</strong>: '.$data['item_model'], true, 0, true, 0, '');
210+
if ($data['item_serial']) {
211+
$pdf->writeHTML("<strong>".trans('admin/hardware/form.serial').'</strong>: '.$data['item_serial'], true, 0, true, 0, '');
243212
}
213+
$pdf->writeHTML("<strong>".trans('general.assigned_date').'</strong>: '.$data['check_out_date'], true, 0, true, 0, '');
214+
$pdf->writeHTML("<strong>".trans('general.assignee').'</strong>: '.$data['assigned_to'], true, 0, true, 0, '');
215+
$pdf->Ln();
216+
217+
// Break the EULA into lines based on newlines, and check each line for RTL or CJK characters
218+
$eula_lines = preg_split("/\r\n|\n|\r/", $item->getEula());
219+
220+
foreach ($eula_lines as $eula_line) {
221+
Helper::hasRtl($eula_line) ? $pdf->setRTL(true) : $pdf->setRTL(false);
222+
Helper::isCjk($eula_line) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
223+
224+
$pdf->writeHTML(Helper::parseEscapedMarkedown($eula_line), true, 0, true, 0, '');
225+
}
226+
$pdf->Ln();
227+
$pdf->Ln();
228+
$pdf->setRTL(false);
229+
$pdf->writeHTML('<br><br>', true, 0, true, 0, '');
230+
231+
if ($data['note'] != null) {
232+
Helper::isCjk($data['note']) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
233+
$pdf->writeHTML("<strong>".trans('general.notes') . '</strong>: ' . $data['note'], true, 0, true, 0, '');
234+
$pdf->Ln();
235+
}
236+
237+
if ($data['signature'] != null) {
238+
239+
$pdf->writeHTML('<img src="'.$data['signature'].'" style="max-width: 600px;">', true, 0, true, 0, '');
240+
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
241+
}
242+
243+
$pdf->writeHTML("<strong>".trans('general.accepted_date').'</strong>: '.$data['accepted_date'], true, 0, true, 0, '');
244+
245+
246+
$pdf_content = $pdf->Output($pdf_filename, 'S');
247+
248+
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf_content);
249+
244250

245251
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
246252

@@ -249,9 +255,8 @@ public function store(Request $request, $id) : RedirectResponse
249255

250256
// Add the attachment for the signing user into the $data array
251257
$data['file'] = $pdf_filename;
252-
$locale = $assigned_user->locale;
253258
try {
254-
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($locale));
259+
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($assigned_user->locale));
255260
} catch (\Exception $e) {
256261
Log::warning($e);
257262
}
@@ -265,23 +270,12 @@ public function store(Request $request, $id) : RedirectResponse
265270

266271
$return_msg = trans('admin/users/message.accepted');
267272

273+
// Item was not accepted
268274
} else {
269275

270-
/**
271-
* Check for the eula-pdfs directory
272-
*/
273-
if (! Storage::exists('private_uploads/eula-pdfs')) {
274-
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
275-
}
276-
277276
if (Setting::getSettings()->require_accept_signature == '1') {
278-
279-
// Check if the signature directory exists, if not create it
280-
if (!Storage::exists('private_uploads/signatures')) {
281-
Storage::makeDirectory('private_uploads/signatures', 775);
282-
}
283277

284-
// The item was accepted, check for a signature
278+
// The item was declined, check for a signature
285279
if ($request->filled('signature_output')) {
286280
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
287281
$data_uri = $request->input('signature_output');
@@ -298,39 +292,11 @@ public function store(Request $request, $id) : RedirectResponse
298292

299293
// Format the data to send the declined notification
300294
$branding_settings = SettingsController::getPDFBranding();
301-
302-
// This is the most horriblest
303-
switch($acceptance->checkoutable_type){
304-
case 'App\Models\Asset':
305-
$asset_model = AssetModel::find($item->model_id);
306-
$display_model = $asset_model->name;
307-
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
308-
break;
309-
310-
case 'App\Models\Accessory':
311-
$accessory = Accessory::find($item->id);
312-
$display_model = $accessory->name;
313-
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
314-
break;
315-
316-
case 'App\Models\LicenseSeat':
317-
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
318-
break;
319-
320-
case 'App\Models\Component':
321-
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
322-
break;
323-
324-
case 'App\Models\Consumable':
325-
$consumable = Consumable::find($item->id);
326-
$display_model = $consumable->name;
327-
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
328-
break;
329-
}
295+
$assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName;
330296

331297
$data = [
332298
'item_tag' => $item->asset_tag,
333-
'item_model' => $display_model,
299+
'item_model' => $item->model ? $item->model->name : $item->display_name,
334300
'item_serial' => $item->serial,
335301
'item_status' => $item->assetstatus?->name,
336302
'note' => $request->input('note'),
@@ -342,11 +308,6 @@ public function store(Request $request, $id) : RedirectResponse
342308
'qty' => $acceptance->qty ?? 1,
343309
];
344310

345-
if ($pdf_view_route!='') {
346-
Log::debug($pdf_filename.' is the filename, and the route was specified.');
347-
$pdf = Pdf::loadView($pdf_view_route, $data);
348-
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
349-
}
350311

351312
for ($i = 0; $i < ($acceptance->qty ?? 1); $i++) {
352313
$acceptance->decline($sig_filename, $request->input('note'));

app/Http/Controllers/Api/CategoriesController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public function index(Request $request) : array
3838
'consumables_count',
3939
'components_count',
4040
'licenses_count',
41+
'created_at',
42+
'updated_at',
4143
'image',
4244
'notes',
4345
];

app/Models/Accessory.php

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -309,27 +309,6 @@ public function requireAcceptance()
309309
return $this->category->require_acceptance ?? false;
310310
}
311311

312-
/**
313-
* Checks for a category-specific EULA, and if that doesn't exist,
314-
* checks for a settings level EULA
315-
*
316-
* @author [A. Gianotto] [<[email protected]>]
317-
* @since [v3.0]
318-
* @return string
319-
*/
320-
public function getEula()
321-
{
322-
323-
if ($this->category->eula_text) {
324-
return Helper::parseEscapedMarkedown($this->category->eula_text);
325-
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
326-
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
327-
}
328-
329-
return null;
330-
}
331-
332-
333312
/**
334313
* Check how many items within an accessory are checked out
335314
*

0 commit comments

Comments
 (0)