Projekt

Allgemein

Profil

GX-Bug #43158 » EmailsController.inc.php

/GXEngine/Controllers/ - Till Tepelmann, 24.09.2015 19:09

 
1
<?php
2
/* --------------------------------------------------------------
3
   EmailsController.inc.php 2015-07-22 gm
4
   Gambio GmbH
5
   http://www.gambio.de
6
   Copyright (c) 2015 Gambio GmbH
7
   Released under the GNU General Public License (Version 2)
8
   [http://www.gnu.org/licenses/gpl-2.0.html]
9
   --------------------------------------------------------------
10
*/
11

    
12
MainFactory::load_class('HttpViewController');
13

    
14
/**
15
 * Class EmailsController
16
 *
17
 * PHP controller that handles the admin/emails page operations. You can also use it to
18
 * perform email operations from JavaScript by providing encoded JSON arrays with email
19
 * information from other pages.
20
 *
21
 * @category System
22
 * @package  Controllers
23
 */
24
class EmailsController extends HttpViewController
25
{
26
	/**
27
	 * Used core emails operations.
28
	 *
29
	 * @var EmailServiceInterface
30
	 */
31
	protected $emailService;
32

    
33
	/**
34
	 * Used for parsing and encoding operations.
35
	 *
36
	 * @var EmailParser
37
	 */
38
	protected $emailParser;
39

    
40
	/**
41
	 * Used for attachment files manipulation.
42
	 *
43
	 * @var AttachmentsHandler
44
	 */
45
	protected $attachmentsHandler;
46

    
47

    
48
	/**
49
	 * Initialize the controller.
50
	 *
51
	 * Perform the common operations before the parent class proceeds with the controller
52
	 * method execution. In this case every method needs the EmailService so it is loaded
53
	 * once before every method.
54
	 *
55
	 * @param HttpContextInterface $httpContext
56
	 */
57
	public function proceed(HttpContextInterface $httpContext)
58
	{
59
		$this->emailService       = StaticGXCoreLoader::getService('Email');
60
		$this->emailParser        = MainFactory::create('EmailParser', $this->emailService);
61
		$this->attachmentsHandler = MainFactory::create('AttachmentsHandler', DIR_FS_CATALOG . 'uploads');
62
		$this->contentView->set_template_dir(DIR_FS_ADMIN . 'templates/');
63
		parent::proceed($httpContext); // proceed http context from parent class
64
	}
65

    
66

    
67
	/**
68
	 * Displays the administration emails page.
69
	 *
70
	 * The administration page contains various JavaScript controllers which will make AJAX
71
	 * requests to this class in order to get/store email information. Check the JavaScript
72
	 * code of the page in the "admin/javascript/engine/controllers/emails" directory.
73
	 *
74
	 * @return AdminPageHttpControllerResponse
75
	 */
76
	public function actionDefault()
77
	{
78
		$html = $this->_render('emails.html', array(
79
				'pageToken' => $_SESSION['coo_page_token']->generate_token()
80
		));
81

    
82
		$JavaScriptEngineLanguages = array(
83
				'admin_labels',
84
				'buttons',
85
				'db_backup',
86
				'emails',
87
				'lightbox_buttons',
88
				'messages'
89
		);
90

    
91
		return new AdminPageHttpControllerResponse('Emails', $html, null, $JavaScriptEngineLanguages);
92
	}
93

    
94

    
95
	/**
96
	 * [AJAX - GET] Server-side processing of the main emails table.
97
	 *
98
	 * The data returned by this method will be used by the main table of the page which
99
	 * will display the emails records. DataTables will automatically make an AJAX request
100
	 * and display the returned data.
101
	 *
102
	 * @link https://datatables.net/examples/ajax/objects.html
103
	 * @link http://www.datatables.net/examples/server_side/simple.html
104
	 *
105
	 * @return JsonHttpControllerResponse Array that contains the table data.
106
	 */
107
	public function actionDataTable()
108
	{
109
		try
110
		{
111
			// Filter Keyword
112
			$keyword = $_REQUEST['search']['value'];
113

    
114
			// Limit Records 
115
			$limit = array(
116
					'limit'  => (int)$_REQUEST['length'],
117
					'offset' => (int)$_REQUEST['start']
118
			);
119

    
120
			// Order Rules			
121
			$order = $this->_getTableOrder($_REQUEST['order'][0]);
122

    
123
			$emails = $this->emailService->filter($keyword, $limit, $order);
124

    
125
			if($emails === null)
126
			{
127
				$emails = array(); // No records found
128
			}
129

    
130
			// Prepare Table Data 
131
			$tableData = array();
132
			$rowCount  = (int)$_REQUEST['start'];
133
			foreach($emails->getArray() as $email)
134
			{
135
				$tableData[] = array(
136
						'DT_RowData'    => $this->emailParser->encodeEmail($email),
137
						'row_count'     => ++$rowCount,
138
						'creation_date' => $email->getCreationDate()->format('d.m.Y H:i'),
139
						'sent_date'     => ($email->getSentDate() !== null) ? $email->getSentDate()
140
						                                                            ->format('d.m.Y H:i') : '-',
141
						// XSS Protection
142
						'sender'        => htmlspecialchars((string)$email->getSender()->getEmailAddress(), ENT_QUOTES),
143
						'recipient'     => htmlspecialchars((string)$email->getRecipient()->getEmailAddress(),
144
						                                    ENT_QUOTES),
145
						'subject'       => htmlspecialchars((string)$email->getSubject(), ENT_QUOTES),
146
						'is_pending'    => $email->isPending()
147
				);
148
			}
149

    
150
			$response = array(
151
					'draw'            => (int)$_REQUEST['draw'],
152
					'recordsTotal'    => $this->emailService->getRecordCount(),
153
					'recordsFiltered' => $this->emailService->getRecordCount($keyword),
154
					'data'            => $tableData
155
			);
156
		}
157
		catch(Exception $ex)
158
		{
159
			$response = AjaxException::response($ex);
160
		}
161

    
162
		return new JsonHttpControllerResponse($response);
163
	}
164

    
165

    
166
	/**
167
	 * [AJAX - POST] Sends and saves the provided email collection.
168
	 *
169
	 * This method expects the $_POST['collection'] array to be present, containing email
170
	 * records to  be send. Check the "EmailParser" class to see the expected JSON
171
	 * format.
172
	 *
173
	 * @return JsonHttpControllerResponse Returns a success response or exception information.
174
	 */
175
	public function actionSend()
176
	{
177
		try
178
		{
179
			$this->_validatePageToken(); // CSRF Protection
180

    
181
			$postCollection = $this->_getPostData('collection');
182

    
183
			if(!$postCollection)
184
			{
185
				throw new AjaxException('Post collection was not set as an argument for "send" method.');
186
			}
187
			
188
			$collection = $this->emailParser->parseCollection($postCollection);
189
			$this->emailService->sendCollection($collection);
190

    
191
			$response = array(
192
					'success' => true,
193
					'action'  => 'Send',
194
					'emails'  => $postCollection
195
			);
196
		}
197
		catch(AttachmentNotFoundException $ex)
198
		{
199
			// Translate the error for the frontend dialog message.
200
			$lang                = MainFactory::create_object('LanguageTextManager',
201
			                                                  array('emails', $_SESSION['languages_id']));
202
			$translatedException = new Exception($lang->get_text('message_attachment_could_not_be_found') . ' '
203
			                                     . $ex->getAttachmentPath());
204
			$response            = AjaxException::response($translatedException);
205
		}
206
		catch(Exception $ex)
207
		{
208
			$response = AjaxException::response($ex);
209
		}
210

    
211
		return new JsonHttpControllerResponse($response);
212
	}
213

    
214

    
215
	/**
216
	 * [AJAX - POST] Queue email records into the database.
217
	 *
218
	 * The queue operation will save the email with a pending status. Queue operation will be executed
219
	 * for all the email records inside the $_POST['collection'] variable.
220
	 *
221
	 * @return JsonHttpControllerResponse Returns a success response or exception information.
222
	 */
223
	public function actionQueue()
224
	{
225
		try
226
		{
227
			$this->_validatePageToken(); // CSRF Protection
228

    
229
			$postCollection = $this->_getPostData('collection');
230

    
231
			if(!$postCollection)
232
			{
233
				throw new AjaxException('Post collection was not set as an argument for "queue" method.');
234
			}
235

    
236
			$collection = $this->emailParser->parseCollection($postCollection);
237
			$this->emailService->queueCollection($collection);
238

    
239
			$response = array(
240
					'success' => true,
241
					'action'  => 'Queue',
242
					'emails'  => $postCollection
243
			);
244
		}
245
		catch(Exception $ex)
246
		{
247
			$response = AjaxException::response($ex);
248
		}
249

    
250
		return new JsonHttpControllerResponse($response);
251
	}
252

    
253

    
254
	/**
255
	 * [AJAX - POST] Remove email records from the database.
256
	 *
257
	 * Will remove all the email records inside the $_POST['collection'] variable.
258
	 *
259
	 * @return JsonHttpControllerResponse Returns a success response or exception information.
260
	 */
261
	public function actionDelete()
262
	{
263
		try
264
		{
265
			$this->_validatePageToken(); // CSRF Protection
266

    
267
			$postCollection = $this->_getPostData('collection');
268

    
269
			if(!$postCollection)
270
			{
271
				throw new AjaxException('Post collection was not set as an argument for "delete" method.');
272
			}
273

    
274
			$collection = $this->emailParser->parseCollection($postCollection);
275
			$this->emailService->deleteCollection($collection);
276

    
277
			$response = array(
278
					'success' => true,
279
					'action'  => 'Delete',
280
					'emails'  => $postCollection
281
			);
282
		}
283
		catch(Exception $ex)
284
		{
285
			$response = AjaxException::response($ex);
286
		}
287

    
288
		return new JsonHttpControllerResponse($response);
289
	}
290

    
291

    
292
	/**
293
	 * [AJAX - POST] Get email record by ID.
294
	 *
295
	 * This method uses the provided $_POST['email_id'] value to fetch the data of the email and
296
	 * return it to the client. It is not used by the admin/emails page but might be useful in other
297
	 * pages.
298
	 *
299
	 * @return JsonHttpControllerResponse Returns a success response or exception information.
300
	 */
301
	public function actionGet()
302
	{
303
		try
304
		{
305
			$this->_validatePageToken(); // CSRF Protection
306

    
307
			$postEmailId = $this->_getPostData('email_id');
308

    
309
			if(!isset($postEmailId))
310
			{
311
				throw new AjaxException('Email ID was not set as an argument for "get" method.');
312
			}
313

    
314
			$email    = $this->emailService->getById(MainFactory::create('Id', $postEmailId));
315
			$response = $this->emailParser->encodeEmail($email);
316
		}
317
		catch(Exception $ex)
318
		{
319
			$response = AjaxException::response($ex);
320
		}
321

    
322
		return new JsonHttpControllerResponse($response);
323
	}
324

    
325

    
326
	/**
327
	 * [AJAX - POST] Upload new attachment file to server.
328
	 *
329
	 * The $_FILES array contains information about the file that was uploaded. When an email
330
	 * file is uploaded it is stored in the "uploads/tmp" directory until the email is created
331
	 * and then is is moved to its own directory. The reason for this algorithm is that we do
332
	 * not want email attachments to be in one place altogether.
333
	 *
334
	 * @return JsonHttpControllerResponse Returns a success response or exception information.
335
	 */
336
	public function actionUploadAttachment()
337
	{
338
		try
339
		{
340
			$this->_validatePageToken(); // CSRF Protection
341

    
342
			if(!isset($_FILES) || empty($_FILES))
343
			{
344
				throw new AjaxException('No files where provided for upload.');
345
			}
346

    
347
			// Get the first item of $_FILES array.
348
			$file = array_shift($_FILES);
349
			$tmpAttachmentPath = MainFactory::create('AttachmentPath', $file['tmp_name']);
350
			$tmpAttachmentName = MainFactory::create('AttachmentName', $file['name']); 
351
			$tmpEmailAttachment = MainFactory::create('EmailAttachment', $tmpAttachmentPath, $tmpAttachmentName);
352
			$newEmailAttachment = $this->attachmentsHandler->uploadAttachment($tmpEmailAttachment);
353

    
354
			// Return success response to client. 
355
			$response = array(
356
					'success' => true,
357
					'action'  => 'UploadAttachment',
358
					'path'    => (string)$newEmailAttachment->getPath()
359
			);
360
		}
361
		catch(Exception $ex)
362
		{
363
			$response = AjaxException::response($ex);
364
		}
365

    
366
		return new JsonHttpControllerResponse($response);
367
	}
368

    
369

    
370
	public function actionDownloadAttachment()
371
	{
372
		// Check provided argument. 
373
		if(!isset($_GET['path']) || empty($_GET['path']))
374
		{
375
			throw new InvalidArgumentException('$_GET["path"] argument was not provided.');
376
		}
377

    
378
		// Validate argument, the user is only able to download files from the uploads
379
		// directory. Otherwise there would be a security issue. 
380
		$path                   = realpath($_GET['path']);
381
		$validateAttachmentsDir = basename(dirname(dirname(dirname($path))));
382
		$validateTmpDir         = basename(dirname(dirname($path)));
383

    
384
		if(file_exists($path) && is_file($path)
385
		   && ($validateAttachmentsDir == 'uploads' || $validateTmpDir == 'uploads')
386
		)
387
		{
388
			$finfo = new finfo(FILEINFO_MIME_TYPE);
389

    
390
			$basename = basename($path);
391
			$filename = (strpos($basename, 'email_id') !== false) ? substr($basename,
392
			                                                               strpos($basename, '-') + 1) : $basename;
393

    
394
			header('Cache-Control: public');
395
			header('Content-Description: File Transfer');
396
			header('Content-Disposition: attachment; filename="' . $filename . '"');
397
			header('Content-Type: ' . $finfo->file($path));
398
			header('Content-Transfer-Encoding: binary');
399
			header('Connection: Keep-Alive');
400
			header('Expires: 0');
401
			header('Cache-Control: must-revalidate');
402
			header('Pragma: public');
403
			header('Content-Length: ' . filesize($path));
404
			readfile($path);
405

    
406
			return new HttpControllerResponse('');
407
		}
408
		else
409
		{
410
			$langEmails   = MainFactory::create_object('LanguageTextManager',
411
			                                           array('emails', $_SESSION['languages_id']));
412
			$langMessages = MainFactory::create_object('LanguageTextManager',
413
			                                           array('messages', $_SESSION['languages_id']));
414
			$html         = '
415
				<div class="gx-container">
416
					<h3 class="page-header">' . $langEmails->get_text('message_download_attachment_error') . '</h3>
417
					<pre>' . $_GET['path'] . '</pre>
418
				 </div>';
419

    
420
			return new AdminPageHttpControllerResponse($langMessages->get_text('error'), $html);
421
		}
422
	}
423

    
424

    
425
	/**
426
	 * [AJAX - POST] Remove existing "tmp" attachment from the server.
427
	 *
428
	 * This method requires an array present in the $postDataArray that contain the paths
429
	 * to the server "tmp" attachments to be removed. It will not remove attachments that reside
430
	 * in other directories because they might be used by other email records (e.g. forward existing
431
	 * email with attachments -> user might remove attachment that is used by the original email
432
	 * record causing problems to the system).
433
	 *
434
	 * @return JsonHttpControllerResponse Returns a success response or exception information.
435
	 */
436
	public function actionDeleteAttachment()
437
	{
438
		try
439
		{
440
			$this->_validatePageToken(); // CSRF Protection
441

    
442
			$postAttachments = $this->_getPostData('attachments');
443

    
444
			if(!isset($postAttachments))
445
			{
446
				throw new AjaxException('No attachments where provided for removal.');
447
			}
448

    
449
			foreach($postAttachments as $path)
450
			{
451
				if(basename(dirname($path)) === 'tmp')
452
				{
453
					$attachmentPath = MainFactory::create('AttachmentPath', $path); 
454
					$emailAttachment = MainFactory::create('EmailAttachment', $attachmentPath); 
455
					$this->attachmentsHandler->deleteAttachment($emailAttachment);
456
				}
457
			}
458

    
459
			$response = array(
460
					'success'     => true,
461
					'action'      => 'DeleteAttachment',
462
					'attachments' => $postAttachments
463
			);
464
		}
465
		catch(Exception $ex)
466
		{
467
			$response = AjaxException::response($ex);
468
		}
469

    
470
		return new JsonHttpControllerResponse($response);
471
	}
472

    
473

    
474
	/**
475
	 * [AJAX - POST] Delete old attachment files from emails.
476
	 *
477
	 * This method will filter the emails and remove their attachments on the provided date
478
	 * and before. This is necessary because the admin user needs a way to clean the old unnecessary
479
	 * files from the server and free some extra space. As an extra action this method will empty the 
480
	 * "uploads/tmp" directory.
481
	 *
482
	 * @return JsonHttpControllerResponse Returns a success response or exception information.
483
	 */
484
	public function actionDeleteOldAttachments()
485
	{
486
		try
487
		{
488
			$this->_validatePageToken(); // CSRF Protection
489

    
490
			if($this->_getPostData('removalDate') === null)
491
			{
492
				throw new Exception('Removal date was not provided with the request.');
493
			}
494

    
495
			// Add one day to the selected removal date so that it will include all the attachments 
496
			// which have a creation time newer than 00:00:00 AM.
497
			$removalDate = new DateTime();
498
			$removalDate->setTimestamp(strtotime('+1 day', strtotime($this->_getPostData('removalDate'))));
499

    
500
			// Remove old attachments.
501
			$removalInfo = $this->attachmentsHandler->deleteOldAttachments($removalDate);
502
			
503
			// Remove tmp files (if any). 
504
			$this->attachmentsHandler->emptyTempDirectory(); 
505

    
506
			$response = array(
507
					'success' => true,
508
					'action'  => 'DeleteOldAttachments',
509
					'count'   => $removalInfo['count'],
510
					'size'    => array(
511
							'bytes'     => $removalInfo['size'],
512
							'megabytes' => round($removalInfo['size'] / 1024 / 1024, 2) // convert in megabytes
513
					)
514
			);
515
		}
516
		catch(Exception $ex)
517
		{
518
			$response = AjaxException::response($ex);
519
		}
520

    
521
		return new JsonHttpControllerResponse($response);
522
	}
523

    
524

    
525
	/**
526
	 * [AJAX] Get attachments size in MB
527
	 *
528
	 * @return JsonHttpControllerResponse
529
	 */
530
	public function actionGetAttachmentsSize()
531
	{
532
		try
533
		{
534
			$attachmentsSize = $this->attachmentsHandler->getAttachmentsSize(); // in bytes
535

    
536
			$response = array(
537
					'success' => true,
538
					'action'  => 'GetAttachmentsSize',
539
					'size'    => array(
540
							'bytes'     => $attachmentsSize,
541
							'megabytes' => round($attachmentsSize / 1024 / 1024, 2) // convert to megabytes
542
					)
543
			);
544
		}
545
		catch(Exception $ex)
546
		{
547
			$response = AjaxException::response($ex);
548
		}
549

    
550
		return new JsonHttpControllerResponse($response);
551
	}
552

    
553

    
554
	/**
555
	 * [AJAX - GET] Get shops email settings and configure the client accordingly.
556
	 *
557
	 * @return JsonHttpControllerResponse
558
	 */
559
	public function actionGetEmailSettings()
560
	{
561
		try
562
		{
563
			$response = array(
564
					'signature'    => (EMAIL_SIGNATURE !== '') ? EMAIL_SIGNATURE : null,
565
					'useHtml'      => (EMAIL_USE_HTML == 'true'),
566
					'replyAddress' => (CONTACT_US_REPLY_ADDRESS !== '') ? CONTACT_US_REPLY_ADDRESS : null,
567
					'replyName'    => (CONTACT_US_REPLY_ADDRESS !== '') ? CONTACT_US_REPLY_ADDRESS_NAME : null
568
			);
569
		}
570
		catch(Exception $ex)
571
		{
572
			$response = AjaxException::response($ex);
573
		}
574

    
575
		return new JsonHttpControllerResponse($response);
576
	}
577

    
578

    
579
	/**
580
	 * Get the table order clause in string.
581
	 *
582
	 * Since the EmailsController handles the page main table it needs to take care of many operations such
583
	 * as filtering, limiting and ordering. This method will return the correct order string for each table,
584
	 * but needs to be updated if there is a change in the column order.
585
	 *
586
	 * @param array $rule Contains the DataTables order data.
587
	 *
588
	 * @return string Returns the order by value to be used by the CI query builder.
589
	 *
590
	 * @link http://www.datatables.net/manual/server-side
591
	 */
592
	protected function _getTableOrder($rule)
593
	{
594
		switch($rule['column'])
595
		{
596
			case 0:
597
			case 1:
598
			case 8: // Empty
599
				$order = array();
600
				break;
601

    
602
			case 2: // Creation Date
603
				$order = array(
604
						'emails.creation_date' => $rule['dir']
605
				);
606
				break;
607

    
608
			case 3: // Sent Date
609
				$order = array(
610
						'emails.sent_date' => $rule['dir']
611
				);
612
				break;
613

    
614
			case 4: // Sender
615
				$order = array(
616
						'email_contacts.email_address' => $rule['dir'],
617
						'email_contacts.contact_type'  => 'desc'
618
				);
619
				break;
620

    
621
			case 5: // Recipient
622
				$order = array(
623
						'email_contacts.email_address' => $rule['dir'],
624
						'email_contacts.contact_type'  => 'asc'
625
				);
626
				break;
627

    
628
			case 6: // Subject
629
				$order = array(
630
						'emails.subject' => $rule['dir'],
631
				);
632
				break;
633

    
634
			case 7: // Status
635
				$order = array(
636
						'emails.is_pending' => $rule['dir']
637
				);
638
				break;
639

    
640
			default:
641
				throw new UnexpectedValueException('Provided column index is not present in the table.');
642
		}
643

    
644
		return $order;
645
	}
646
}
(1-1/2)