From d1ae2a80095582eb0c66ffff6c6f86b344dadabf Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 11 Jan 2017 21:55:22 -0500 Subject: [PATCH 001/192] Getting started on new ticket validator --- api/businesslogic/ValidationModel.php | 15 +++ api/businesslogic/email_validators.php | 119 ++++++++++++++++++ .../ticket/CreateTicketForCustomerModel.php | 42 +++++++ api/businesslogic/ticket/ticket_creator.php | 93 ++++++++++++++ api/loader.php | 3 + api/ticket/index.php | 1 - 6 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 api/businesslogic/ValidationModel.php create mode 100644 api/businesslogic/email_validators.php create mode 100644 api/businesslogic/ticket/CreateTicketForCustomerModel.php create mode 100644 api/businesslogic/ticket/ticket_creator.php create mode 100644 api/loader.php diff --git a/api/businesslogic/ValidationModel.php b/api/businesslogic/ValidationModel.php new file mode 100644 index 00000000..aa2258d2 --- /dev/null +++ b/api/businesslogic/ValidationModel.php @@ -0,0 +1,15 @@ + $v) { + if (!hesk_isValidEmail($v)) { + unset($all[$k]); + } + } + + /* If at least one is found return the value */ + if (count($all)) { + if ($return_emails) { + return implode(',', $all); + } + + return true; + } elseif (!$return_emails) { + return false; + } + } else { + /* Make sure people don't try to enter multiple addresses */ + $address = str_replace(strstr($address, ','), '', $address); + $address = str_replace(strstr($address, ';'), '', $address); + $address = trim($address); + + /* Valid address? */ + if (hesk_isValidEmail($address)) { + if ($return_emails) { + return $address; + } + + return true; + } + } + + if ($return_emails) { + return null; + } + + return true; +} // END hesk_validateEmail() + +/** + * @param $email + * @return bool + */ +function hesk_isValidEmail($email) { + /* Check for header injection attempts */ + if (preg_match("/\r|\n|%0a|%0d/i", $email)) { + return false; + } + + /* Does it contain an @? */ + $atIndex = strrpos($email, "@"); + if ($atIndex === false) { + return false; + } + + /* Get local and domain parts */ + $domain = substr($email, $atIndex + 1); + $local = substr($email, 0, $atIndex); + $localLen = strlen($local); + $domainLen = strlen($domain); + + /* Check local part length */ + if ($localLen < 1 || $localLen > 64) { + return false; + } + + /* Check domain part length */ + if ($domainLen < 1 || $domainLen > 254) { + return false; + } + + /* Local part mustn't start or end with a dot */ + if ($local[0] == '.' || $local[$localLen - 1] == '.') { + return false; + } + + /* Local part mustn't have two consecutive dots*/ + if (strpos($local, '..') !== false) { + return false; + } + + /* Check domain part characters */ + if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) { + return false; + } + + /* Domain part mustn't have two consecutive dots */ + if (strpos($domain, '..') !== false) { + return false; + } + + /* Character not valid in local part unless local part is quoted */ + if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local))) /* " */ { + if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local))) /* " */ { + return false; + } + } + + /* All tests passed, email seems to be OK */ + return true; +} // END hesk_isValidEmail() \ No newline at end of file diff --git a/api/businesslogic/ticket/CreateTicketForCustomerModel.php b/api/businesslogic/ticket/CreateTicketForCustomerModel.php new file mode 100644 index 00000000..da4c233c --- /dev/null +++ b/api/businesslogic/ticket/CreateTicketForCustomerModel.php @@ -0,0 +1,42 @@ +name === NULL || $ticket_request->name == '') { + $validationModel->errorKeys[] = 'NO_NAME'; + } + + if (hesk_validateEmail($ticket_request->email, $hesk_settings['multi_eml'], false)) { + $validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL'; + } + + if (intval($ticket_request->category) === 0) { + // TODO add support for invalid category ID + $validationModel->errorKeys[] = 'NO_CATEGORY'; + } + + // Don't allow critical priority tickets + if ($hesk_settings['cust_urgency'] && intval($ticket_request->priority) === $TICKET_PRIORITY_CRITICAL) { + $validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN'; + } + + if ($hesk_settings['require_subject'] === 1 && + ($ticket_request->subject === NULL || $ticket_request->subject === '')) { + $validationModel->errorKeys[] = 'SUBJECT_REQUIRED'; + } + + if ($hesk_settings['require_message'] === 1 && + ($ticket_request->message === NULL || $ticket_request->message === '')) { + $validationModel->errorKeys[] = 'MESSAGE_REQUIRED'; + } + + foreach ($hesk_settings['custom_fields'] as $key => $value) { + // TODO Only check categories that apply to this custom field + if ($value['use'] == 1 && hesk_is_custom_field_in_category($key, intval($ticket_request->category))) { + $custom_field_value = $ticket_request->customFields[$key]; + if (empty($custom_field_value)) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::NO_VALUE'; + continue; + } + switch($v['type']) { + case 'date': + if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_DATE'; + } else { + // Actually validate based on range + $date = strtotime($custom_field_value . ' t00:00:00'); + $dmin = strlen($value['value']['dmin']) ? strtotime($value['value']['dmin'] . ' t00:00:00') : false; + $dmax = strlen($value['value']['dmax']) ? strtotime($value['value']['dmax'] . ' t00:00:00') : false; + + if ($dmin && $dmin > $date) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::DATE_BEFORE_MIN::MIN-' . $dmin . '::ENTERED-' . $date; + } elseif ($dmax && $dmax < $date) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::DATE_AFTER_MAX::MAX-' . $dmax . '::ENTERED-' . $date; + } + } + break; + case 'email': + if (!hesk_validateEmail($custom_field_value, $value['value']['multiple'], false)) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_OR_MISSING_EMAIL'; + } + break; + } + } + } + + // TODO Check bans (email only; don't check IP on REST requests as they'll most likely be sent via servers) + // TODO submit_ticket.php:320-322 + + // TODO Check if we're at the max number of tickets + // TODO submit_ticket.php:325-334 + + return $validationModel; +} \ No newline at end of file diff --git a/api/loader.php b/api/loader.php new file mode 100644 index 00000000..34a86085 --- /dev/null +++ b/api/loader.php @@ -0,0 +1,3 @@ + Date: Sat, 14 Jan 2017 22:00:27 -0500 Subject: [PATCH 002/192] Use __DIR__ to make importing easier and only when required --- api/businesslogic/ValidationException.php | 18 +++++++++++++++ ...el.php => CreateTicketByCustomerModel.php} | 2 +- api/businesslogic/ticket/ticket_creator.php | 22 ++++++++++++++----- 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 api/businesslogic/ValidationException.php rename api/businesslogic/ticket/{CreateTicketForCustomerModel.php => CreateTicketByCustomerModel.php} (92%) diff --git a/api/businesslogic/ValidationException.php b/api/businesslogic/ValidationException.php new file mode 100644 index 00000000..3f174212 --- /dev/null +++ b/api/businesslogic/ValidationException.php @@ -0,0 +1,18 @@ +errorKeys) === 0) { + throw new Exception('Tried to throw a ValidationException, but the validation model was valid!'); + } + + $this->validationModel = $validationModel; + } +} \ No newline at end of file diff --git a/api/businesslogic/ticket/CreateTicketForCustomerModel.php b/api/businesslogic/ticket/CreateTicketByCustomerModel.php similarity index 92% rename from api/businesslogic/ticket/CreateTicketForCustomerModel.php rename to api/businesslogic/ticket/CreateTicketByCustomerModel.php index da4c233c..46840751 100644 --- a/api/businesslogic/ticket/CreateTicketForCustomerModel.php +++ b/api/businesslogic/ticket/CreateTicketByCustomerModel.php @@ -1,6 +1,6 @@ errorKeys) > 0) { + require_once(__DIR__ . '/../ValidationException.php'); + + // Validation failed + throw new ValidationException($validationModel); + } + + // Create the ticket } /** - * @param $ticket_request CreateTicketForCustomerModel + * @param $ticket_request CreateTicketByCustomerModel * @param $staff bool * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket */ function validate($ticket_request, $staff, $hesk_settings, $modsForHesk_settings) { - require_once('../email_validators.php'); + require_once(__DIR__ . '/../email_validators.php'); //require_once('../category/retriever.php'); //require_once('../bans/retriever.php'); From 403b87bbda057af8cf9c004261a0bcce3fc7b92c Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 16 Jan 2017 22:21:11 -0500 Subject: [PATCH 003/192] Bunch of stuff --- api/businesslogic/category/Category.php | 45 ++++++++++++++ .../category/CategoryRetriever.php | 19 ++++++ api/businesslogic/category/retriever.php | 5 ++ api/businesslogic/ticket/ticket_creator.php | 3 +- api/businesslogic/ticket_retriever.php | 1 + api/category/index.php | 58 ------------------- api/controllers/CategoryController.php | 19 ++++++ api/dao/CategoryGateway.php | 38 ++++++++++++ api/dao/Connection.php | 26 +++++++++ api/index.php | 8 +++ api/loader.php | 3 - 11 files changed, 163 insertions(+), 62 deletions(-) create mode 100644 api/businesslogic/category/Category.php create mode 100644 api/businesslogic/category/CategoryRetriever.php create mode 100644 api/businesslogic/category/retriever.php delete mode 100644 api/category/index.php create mode 100644 api/controllers/CategoryController.php create mode 100644 api/dao/CategoryGateway.php create mode 100644 api/dao/Connection.php create mode 100644 api/index.php delete mode 100644 api/loader.php diff --git a/api/businesslogic/category/Category.php b/api/businesslogic/category/Category.php new file mode 100644 index 00000000..77a6be9c --- /dev/null +++ b/api/businesslogic/category/Category.php @@ -0,0 +1,45 @@ +category) === 0) { - // TODO add support for invalid category ID + $allCategories = null; $validationModel->errorKeys[] = 'NO_CATEGORY'; } diff --git a/api/businesslogic/ticket_retriever.php b/api/businesslogic/ticket_retriever.php index eaa06d5c..282af64e 100644 --- a/api/businesslogic/ticket_retriever.php +++ b/api/businesslogic/ticket_retriever.php @@ -2,6 +2,7 @@ require_once(API_PATH . 'dao/ticket_dao.php'); function get_ticket_for_staff($hesk_settings, $user, $id = NULL) { + $tickets = get_ticket_for_id($hesk_settings, $user, $id); if ($tickets == NULL) { diff --git a/api/category/index.php b/api/category/index.php deleted file mode 100644 index 7e3b27b0..00000000 --- a/api/category/index.php +++ /dev/null @@ -1,58 +0,0 @@ -`false` otherwise - * @apiSuccess {Integer} type `0` - Public
`1` - Private - * @apiSuccess {Integer} priority Default priority of tickets created in this category - * @apiSuccess {Integer} manager User ID of the category manager, or `null` if there is no manager. - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "id": 1, - * "name": "General", - * "displayOrder": 10, - * "autoassign": true, - * "type": 0, - * "priority": 2, - * "manager": null - * } - */ -if ($request_method == 'GET') { - if (isset($_GET['id'])) { - $results = get_category($hesk_settings, $_GET['id']); - } else { - $results = get_category($hesk_settings); - } - - if ($results == NULL) { - return http_response_code(404); - } - return output($results); -} - -return http_response_code(405); diff --git a/api/controllers/CategoryController.php b/api/controllers/CategoryController.php new file mode 100644 index 00000000..46558d4e --- /dev/null +++ b/api/controllers/CategoryController.php @@ -0,0 +1,19 @@ +id = intval($row['id']); + $category->catOrder = intval($row['cat_order']); + $category->autoAssign = $row['autoassign'] == 1; + $category->type = intval($row['type']); + $category->usage = intval($row['usage']); + $category->color = $row['color']; + $category->priority = intval($row['priority']); + $category->manager = intval($row['manager']) == 0 ? NULL : intval($row['manager']); + $results[$category->id] = $category; + } + + return $results; + } +} \ No newline at end of file diff --git a/api/dao/Connection.php b/api/dao/Connection.php new file mode 100644 index 00000000..d7da3228 --- /dev/null +++ b/api/dao/Connection.php @@ -0,0 +1,26 @@ +close(); + } +} \ No newline at end of file diff --git a/api/index.php b/api/index.php new file mode 100644 index 00000000..27528b5e --- /dev/null +++ b/api/index.php @@ -0,0 +1,8 @@ + Date: Tue, 17 Jan 2017 21:58:57 -0500 Subject: [PATCH 004/192] More API changes --- api/controllers/CategoryController.php | 2 +- api/core/common.php | 6 ++++++ api/dao/CategoryGateway.php | 8 +++++++- api/dao/Connection.php | 26 -------------------------- api/index.php | 9 ++++++--- inc/common.inc.php | 6 +++--- 6 files changed, 23 insertions(+), 34 deletions(-) create mode 100644 api/core/common.php delete mode 100644 api/dao/Connection.php diff --git a/api/controllers/CategoryController.php b/api/controllers/CategoryController.php index 46558d4e..07558d9d 100644 --- a/api/controllers/CategoryController.php +++ b/api/controllers/CategoryController.php @@ -14,6 +14,6 @@ class CategoryController { static function getAllCategories($hesk_settings) { require_once(__DIR__ . '/../businesslogic/category/CategoryRetriever.php'); - return json_encode(CategoryRetriever::get_all_categories($hesk_settings)); + return CategoryRetriever::get_all_categories($hesk_settings); } } \ No newline at end of file diff --git a/api/core/common.php b/api/core/common.php new file mode 100644 index 00000000..77c14cd6 --- /dev/null +++ b/api/core/common.php @@ -0,0 +1,6 @@ +close(); - } -} \ No newline at end of file diff --git a/api/index.php b/api/index.php index 27528b5e..cd9592ab 100644 --- a/api/index.php +++ b/api/index.php @@ -1,8 +1,11 @@ Date: Tue, 17 Jan 2017 22:08:48 -0500 Subject: [PATCH 005/192] Getting started on routing --- api/index.php | 11 ++++------- api/testcategory.php | 11 +++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 api/testcategory.php diff --git a/api/index.php b/api/index.php index cd9592ab..215544c5 100644 --- a/api/index.php +++ b/api/index.php @@ -1,11 +1,8 @@ Date: Tue, 17 Jan 2017 22:12:45 -0500 Subject: [PATCH 006/192] Add Link for simple API routing --- Link | 1 + api/index.php | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) create mode 160000 Link diff --git a/Link b/Link new file mode 160000 index 00000000..6176303a --- /dev/null +++ b/Link @@ -0,0 +1 @@ +Subproject commit 6176303ac4e34fadaa393a4c06d169aef3fbb32e diff --git a/api/index.php b/api/index.php index 215544c5..dfcb9e63 100644 --- a/api/index.php +++ b/api/index.php @@ -1,8 +1,14 @@ 'routeMe', +)); \ No newline at end of file From d9000c12c74fece118fe8e49056f2dcb799f9ffb Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 18 Jan 2017 17:22:24 -0500 Subject: [PATCH 007/192] More routing changes --- api/index.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/api/index.php b/api/index.php index dfcb9e63..ca5719ef 100644 --- a/api/index.php +++ b/api/index.php @@ -5,10 +5,26 @@ //require_once(__DIR__ . '/core/common.php'); require(__DIR__ . '/../Link/src/Link.php'); -function routeMe(){ - echo 'I am routed'; +class HomeController +{ + + function get($i){ + echo 'You have got to home :) Val:' . intval($i); + } + + function post(){ + echo 'You have posted to home'; + } + + function put(){ + echo 'You have put to home'; + } + + function delete(){ + echo 'You have deleted the home :('; + } } -Link::all( array( - '/test' => 'routeMe', +Link::all(array( + '/test/{i}' => 'HomeController', )); \ No newline at end of file From c184469219408a03f6fb96bee9cb97406a01e962 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 18 Jan 2017 21:56:12 -0500 Subject: [PATCH 008/192] Able to hit a controller through link --- api/controllers/CategoryController.php | 4 ++++ api/index.php | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/api/controllers/CategoryController.php b/api/controllers/CategoryController.php index 07558d9d..cf27e2b8 100644 --- a/api/controllers/CategoryController.php +++ b/api/controllers/CategoryController.php @@ -11,6 +11,10 @@ namespace Controllers\Category; use BusinessLogic\Category\CategoryRetriever; class CategoryController { + function get($i) { + print json_encode(intval($i)); + } + static function getAllCategories($hesk_settings) { require_once(__DIR__ . '/../businesslogic/category/CategoryRetriever.php'); diff --git a/api/index.php b/api/index.php index ca5719ef..0529e779 100644 --- a/api/index.php +++ b/api/index.php @@ -1,9 +1,14 @@ 'HomeController', + '/test/{i}' => '\Controllers\Category\CategoryController', + '404' => 'handle404' )); \ No newline at end of file From 96120bc74482e8233f3e02ba0de2ecb4c4201e70 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 20 Jan 2017 07:19:39 -0500 Subject: [PATCH 009/192] Working on user context --- api/businesslogic/security/UserContext.php | 32 ++++++++++++++++ .../security/UserContextBuilder.php | 38 +++++++++++++++++++ .../security/UserContextNotifications.php | 21 ++++++++++ .../security/UserContextPreferences.php | 21 ++++++++++ api/dao/CategoryGateway.php | 2 + api/index.php | 7 ++++ 6 files changed, 121 insertions(+) create mode 100644 api/businesslogic/security/UserContext.php create mode 100644 api/businesslogic/security/UserContextBuilder.php create mode 100644 api/businesslogic/security/UserContextNotifications.php create mode 100644 api/businesslogic/security/UserContextPreferences.php diff --git a/api/businesslogic/security/UserContext.php b/api/businesslogic/security/UserContext.php new file mode 100644 index 00000000..ec99ccf9 --- /dev/null +++ b/api/businesslogic/security/UserContext.php @@ -0,0 +1,32 @@ +id = $_SESSION['id']; + $userContext->username = $_SESSION['user']; + $userContext->admin = $_SESSION['isadmin']; + $userContext->name = $_SESSION['name']; + $userContext->email = $_SESSION['email']; + $userContext->signature = $_SESSION['signature']; + $userContext->language = $_SESSION['language']; + $userContext->categories = explode(',', $_SESSION['categories']); + + $preferences = new UserContextPreferences(); + $preferences->afterReply = $_SESSION['afterreply']; + $preferences->autoStartTimeWorked = $_SESSION['autostart']; + $preferences- + + + return $userContext; + } +} \ No newline at end of file diff --git a/api/businesslogic/security/UserContextNotifications.php b/api/businesslogic/security/UserContextNotifications.php new file mode 100644 index 00000000..caada957 --- /dev/null +++ b/api/businesslogic/security/UserContextNotifications.php @@ -0,0 +1,21 @@ +id] = $category; } + hesk_dbClose(); + return $results; } } \ No newline at end of file diff --git a/api/index.php b/api/index.php index 0529e779..c7e90a44 100644 --- a/api/index.php +++ b/api/index.php @@ -35,8 +35,15 @@ function handle404() { print json_encode('404 found'); } +function assertApiIsEnabled() { + //-- TODO +} + // Must use fully-qualified namespace to controllers +Link::before('assertApiIsEnabled'); + Link::all(array( + '/' => 'assertApiIsEnabled', '/test/{i}' => '\Controllers\Category\CategoryController', '404' => 'handle404' )); \ No newline at end of file From 17f6bb770d74ce0a7f3738cb9878c81b6a37b633 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 20 Jan 2017 21:50:58 -0500 Subject: [PATCH 010/192] UserContextBuilder mostly done --- .../security/UserContextBuilder.php | 50 ++++++++++++++----- .../security/UserContextNotifications.php | 1 + 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/api/businesslogic/security/UserContextBuilder.php b/api/businesslogic/security/UserContextBuilder.php index 5e63741b..2ec6c954 100644 --- a/api/businesslogic/security/UserContextBuilder.php +++ b/api/businesslogic/security/UserContextBuilder.php @@ -11,27 +11,53 @@ class UserContextBuilder { /** * Builds a user context based on the current session. **The session must be active!** + * @param $dataRow array the $_SESSION superglobal or the hesk_users result set * @return UserContext the built user context */ - static function fromSession() { + static function fromSession($dataRow) { require_once(__DIR__ . '/UserContext.php'); require_once(__DIR__ . '/UserContextPreferences.php'); + require_once(__DIR__ . '/UserContextNotifications.php'); $userContext = new UserContext(); - $userContext->id = $_SESSION['id']; - $userContext->username = $_SESSION['user']; - $userContext->admin = $_SESSION['isadmin']; - $userContext->name = $_SESSION['name']; - $userContext->email = $_SESSION['email']; - $userContext->signature = $_SESSION['signature']; - $userContext->language = $_SESSION['language']; - $userContext->categories = explode(',', $_SESSION['categories']); + $userContext->id = $dataRow['id']; + $userContext->username = $dataRow['user']; + $userContext->admin = $dataRow['isadmin']; + $userContext->name = $dataRow['name']; + $userContext->email = $dataRow['email']; + $userContext->signature = $dataRow['signature']; + $userContext->language = $dataRow['language']; + $userContext->categories = explode(',', $dataRow['categories']); + $userContext->permissions = explode(',', $dataRow['heskprivileges']); + $userContext->autoAssign = $dataRow['autoassign']; + $userContext->ratingNegative = $dataRow['ratingneg']; + $userContext->ratingPositive = $dataRow['ratingpos']; + $userContext->rating = $dataRow['rating']; + $userContext->totalNumberOfReplies = $dataRow['replies']; + $userContext->active = $dataRow['active']; $preferences = new UserContextPreferences(); - $preferences->afterReply = $_SESSION['afterreply']; - $preferences->autoStartTimeWorked = $_SESSION['autostart']; - $preferences- + $preferences->afterReply = $dataRow['afterreply']; + $preferences->autoStartTimeWorked = $dataRow['autostart']; + $preferences->autoreload = $dataRow['autoreload']; + $preferences->defaultNotifyCustomerNewTicket = $dataRow['notify_customer_new']; + $preferences->defaultNotifyCustomerReply = $dataRow['notify_customer_reply']; + $preferences->showSuggestedKnowledgebaseArticles = $dataRow['show_suggested']; + $preferences->defaultCalendarView = $dataRow['default_calendar_view']; + $preferences->defaultTicketView = $dataRow['default_list']; + $userContext->preferences = $preferences; + $notifications = new UserContextNotifications(); + $notifications->newUnassigned = $dataRow['notify_new_unassigned']; + $notifications->newAssignedToMe = $dataRow['notify_new_my']; + $notifications->replyUnassigned = $dataRow['notify_reply_unassigned']; + $notifications->replyToMe = $dataRow['notify_reply_my']; + $notifications->ticketAssignedToMe = $dataRow['notify_assigned']; + $notifications->privateMessage = $dataRow['notify_pm']; + $notifications->noteOnTicketAssignedToMe = $dataRow['notify_note']; + $notifications->noteOnTicketNotAssignedToMe = $dataRow['notify_note_unassigned']; + $notifications->overdueTicketUnassigned = $dataRow['notify_overdue_unassigned']; + $userContext->notificationSettings = $notifications; return $userContext; } diff --git a/api/businesslogic/security/UserContextNotifications.php b/api/businesslogic/security/UserContextNotifications.php index caada957..aab70faa 100644 --- a/api/businesslogic/security/UserContextNotifications.php +++ b/api/businesslogic/security/UserContextNotifications.php @@ -14,6 +14,7 @@ class UserContextNotifications { public $newAssignedToMe; public $replyUnassigned; public $replyToMe; + public $ticketAssignedToMe; public $privateMessage; public $noteOnTicketAssignedToMe; public $noteOnTicketNotAssignedToMe; From 93431e9a981faff99c5b2365868981ec43c75e57 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 21 Jan 2017 16:34:03 -0500 Subject: [PATCH 011/192] Build the user context based on hashed token --- .../security/UserContextBuilder.php | 8 +++-- api/dao/UserDao.php | 34 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 api/dao/UserDao.php diff --git a/api/businesslogic/security/UserContextBuilder.php b/api/businesslogic/security/UserContextBuilder.php index 2ec6c954..a6d22645 100644 --- a/api/businesslogic/security/UserContextBuilder.php +++ b/api/businesslogic/security/UserContextBuilder.php @@ -3,10 +3,12 @@ namespace BusinessLogic\Security; +use DataAccess\Security\UserDao; + class UserContextBuilder { static function buildUserContext($authToken, $hesk_settings) { - //$userForToken = gateway.getUserForToken($authToken); - + $hashedToken = hash('sha512', $authToken); + return UserDao::getUserForAuthToken($hashedToken, $hesk_settings); } /** @@ -14,7 +16,7 @@ class UserContextBuilder { * @param $dataRow array the $_SESSION superglobal or the hesk_users result set * @return UserContext the built user context */ - static function fromSession($dataRow) { + static function fromDataRow($dataRow) { require_once(__DIR__ . '/UserContext.php'); require_once(__DIR__ . '/UserContextPreferences.php'); require_once(__DIR__ . '/UserContextNotifications.php'); diff --git a/api/dao/UserDao.php b/api/dao/UserDao.php new file mode 100644 index 00000000..a2e14c77 --- /dev/null +++ b/api/dao/UserDao.php @@ -0,0 +1,34 @@ + Date: Sat, 21 Jan 2017 22:09:29 -0500 Subject: [PATCH 012/192] Category Controller is working --- api/controllers/CategoryController.php | 12 +++++++++--- api/index.php | 27 ++++---------------------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/api/controllers/CategoryController.php b/api/controllers/CategoryController.php index cf27e2b8..14cc66ff 100644 --- a/api/controllers/CategoryController.php +++ b/api/controllers/CategoryController.php @@ -11,11 +11,17 @@ namespace Controllers\Category; use BusinessLogic\Category\CategoryRetriever; class CategoryController { - function get($i) { - print json_encode(intval($i)); + function get($id) { + $categories = self::getAllCategories(); + output($categories[$id]); } - static function getAllCategories($hesk_settings) { + static function printAllCategories() { + output(self::getAllCategories()); + } + + private static function getAllCategories() { + global $hesk_settings; require_once(__DIR__ . '/../businesslogic/category/CategoryRetriever.php'); return CategoryRetriever::get_all_categories($hesk_settings); diff --git a/api/index.php b/api/index.php index c7e90a44..5858499e 100644 --- a/api/index.php +++ b/api/index.php @@ -8,27 +8,7 @@ require(__DIR__ . '/../hesk_settings.inc.php'); // Controllers require(__DIR__ . '/controllers/CategoryController.php'); - - -class HomeController -{ - - function get($i){ - echo 'You have got to home :) Val:' . intval($i); - } - - function post(){ - echo 'You have posted to home'; - } - - function put(){ - echo 'You have put to home'; - } - - function delete(){ - echo 'You have deleted the home :('; - } -} +hesk_load_api_database_functions(); function handle404() { http_response_code(404); @@ -43,7 +23,8 @@ function assertApiIsEnabled() { Link::before('assertApiIsEnabled'); Link::all(array( - '/' => 'assertApiIsEnabled', - '/test/{i}' => '\Controllers\Category\CategoryController', + // Categories + '/v1/categories' => '\Controllers\Category\CategoryController::printAllCategories', + '/v1/categories/{i}' => '\Controllers\Category\CategoryController', '404' => 'handle404' )); \ No newline at end of file From dc07f5e757dace3b9ffbacabd23c42c9aa824582 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 25 Jan 2017 21:27:39 -0500 Subject: [PATCH 013/192] Move link to the api folder --- Link | 1 - api/Link.php | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++ api/index.php | 2 +- 3 files changed, 219 insertions(+), 2 deletions(-) delete mode 160000 Link create mode 100644 api/Link.php diff --git a/Link b/Link deleted file mode 160000 index 6176303a..00000000 --- a/Link +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6176303ac4e34fadaa393a4c06d169aef3fbb32e diff --git a/api/Link.php b/api/Link.php new file mode 100644 index 00000000..090e666d --- /dev/null +++ b/api/Link.php @@ -0,0 +1,218 @@ + $routeDesc ){ + $routePath = preg_replace( $regex, $replacements, $routePath ); + if( preg_match( '#^/?' . $routePath . '/?$#', $path, $matches ) ){ + if( is_array( $routeDesc ) ) { + $handler = $routeDesc[0]; + if( isset( $routeDesc[2] )) { + $middleware = $routeDesc[2]; + } + } + else + $handler = $routeDesc; + $matched = $matches; + break; + } + } + } + unset( $matched[0] ); + + if( isset($middleware) ){ + $newMatched = self::callFunction( $middleware, $matched, $method ); + /* If new wildcard param are there pass them to main handler */ + if( $newMatched ) { + self::callFunction( $handler, $newMatched, $method ); + } else { + self::callFunction( $handler, $matched, $method ); + } + } else { + self::callFunction( $handler, $matched, $method ); + } + + /* Call all the function that are to be executed after routing */ + foreach( self::$afterFuncs as $afterFunc ) + if( $afterFunc[1] ) { + call_user_func_array( $afterFunc[0] , $afterFunc[1] ); + } else { + call_user_func( $afterFunc[0] ); + } + } + + /** + * Static function that helps you generate links effortlessly and pass parameters to them, thus enabling to generate dynamic links + * + * @param string $name name of the route for which the link has to be generated + * @param array $params An array of parameters that are replaced in the route if it contains wildcards + * For e.g. if route is /name/{i}/{a} and parameters passed are 1, aps then link generated will be /name/1/aps + */ + public static function route( $name, $params = array() ) + { + $href = null; + foreach ( self::$routes as $routePath => $routeDesc ) { + if( is_array( $routeDesc ) ){ + if( $name == $routeDesc[1] ){ + $href = $routePath; + for( $i = 0; $i < count($params); $i++){ + $href = preg_replace('#{(.*?)}#', $params[$i], $href, 1); + } + } + } + } + return $href; + } + + /** + * Static function to handle cases when route is not found, call handler of 404 if defined else + * sends a 404 header + */ + public static function handle404() + { + /* Call '404' route if it exists */ + if( isset ( self::$routes['404'] ) ) { + call_user_func( self::$routes['404'] ); + } else { + header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); + } + } + + /** + * Static function to handle both middlewares' call and main handler's call. + * + * @param array|string $handler Handler that will handle the routes call or middleware + * @param array $matched The parameters that we get from the route wildcard + * @return array $newParams The parameters return in the case of middleware if you intend to + * the wildcards that were originally passed, this newParams will + * be next passed to main handler + */ + public static function callFunction( $handler , $matched, $method ) + { + if ( $handler ) { + if ( is_callable( $handler ) ) { + $newParams = call_user_func_array( $handler, $matched ) ; + } else { + + /* Check if class exists in the case user is using RESTful pattern */ + + if( class_exists( $handler ) ) { + $instanceOfHandler = new $handler(); // Won't work in case of middleware since we aren't using RESTful in that + } else { + print_r('Class or function ' . $handler . ' not found'); + header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); + die(); + } + } + } else { + self::handle404(); + } + + if( isset( $instanceOfHandler ) ) { + if( method_exists( $instanceOfHandler, $method ) ) { + try { + $newParams = call_user_func_array( array( $instanceOfHandler, $method ), $matched ); + } catch ( Exception $exception ){ + $string = str_replace("\n", ' ', var_export($exception, TRUE)); + error_log($string); //Log to error file only if display errors has been declared + header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); + die(); + } + } + } + if( isset( $newParams ) && $newParams ) { + return $newParams; + } + } + + /** + * Static function to add functions that are to be excuted before each routing, must be called before Link::all + * + * @param string $funcName Name of the funtion to be called upon before + * @param array $params Array of parameters that are to be passed to before function, can be null but if not + * it must be an array + */ + public static function before( $funcName, $params = null ) + { + array_push( self::$beforeFuncs, [ $funcName, $params ]); + } + + /** + * Static function to add functions that are to be excuted after each routing, must be called before Link::all + * + * @param string $funcName Name of the funtion to be called upon after + * @param array $params Array of parameters that are to be passed to after function, can be null but if not + * it must be an array + */ + public static function after( $funcName, $params = null ) + { + array_push( self::$afterFuncs, [ $funcName, $params ]); + } +} \ No newline at end of file diff --git a/api/index.php b/api/index.php index 5858499e..b5f8c6e6 100644 --- a/api/index.php +++ b/api/index.php @@ -3,7 +3,7 @@ define('IN_SCRIPT', 1); define('HESK_PATH', '../'); require_once(__DIR__ . '/core/common.php'); -require(__DIR__ . '/../Link/src/Link.php'); +require(__DIR__ . '/Link.php'); require(__DIR__ . '/../hesk_settings.inc.php'); // Controllers From fc38673b7dd7def573521b0085bf3807ea5a1a51 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 25 Jan 2017 21:55:13 -0500 Subject: [PATCH 014/192] Some temporary error handling --- api/core/json_error.php | 4 +++- api/index.php | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/api/core/json_error.php b/api/core/json_error.php index 61b491e6..2280d511 100644 --- a/api/core/json_error.php +++ b/api/core/json_error.php @@ -1,11 +1,13 @@ Date: Thu, 26 Jan 2017 22:00:45 -0500 Subject: [PATCH 015/192] Getting started on ticket endpoint... again --- api/businesslogic/ValidationException.php | 4 + api/businesslogic/ValidationModel.php | 2 + .../ticket/CreateTicketByCustomerModel.php | 2 + api/businesslogic/ticket/TicketCreator.php | 117 ++++++++++++++++++ api/businesslogic/ticket/ticket_creator.php | 104 ---------------- api/index.php | 11 +- 6 files changed, 131 insertions(+), 109 deletions(-) create mode 100644 api/businesslogic/ticket/TicketCreator.php diff --git a/api/businesslogic/ValidationException.php b/api/businesslogic/ValidationException.php index 3f174212..b2e3f398 100644 --- a/api/businesslogic/ValidationException.php +++ b/api/businesslogic/ValidationException.php @@ -1,5 +1,9 @@ errorKeys) > 0) { + require_once(__DIR__ . '/../ValidationException.php'); + + // Validation failed + throw new ValidationException($validationModel); + } + + // Create the ticket + } + + /** + * @param $ticketRequest CreateTicketByCustomerModel + * @param $staff bool + * @param $heskSettings array HESK settings + * @param $modsForHeskSettings array Mods for HESK settings + * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket + */ + function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings) { + require_once(__DIR__ . '/../email_validators.php'); + require_once(__DIR__ . '/../../dao/category_dao.php'); + //require_once('../category/retriever.php'); + //require_once('../bans/retriever.php'); + + $TICKET_PRIORITY_CRITICAL = 0; + + $validationModel = new ValidationModel(); + + if ($ticketRequest->name === NULL || $ticketRequest->name == '') { + $validationModel->errorKeys[] = 'NO_NAME'; + } + + if (hesk_validateEmail($ticketRequest->email, $heskSettings['multi_eml'], false)) { + $validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL'; + } + + if (intval($ticketRequest->category) === 0) { + $allCategories = null; + $validationModel->errorKeys[] = 'NO_CATEGORY'; + } + + // Don't allow critical priority tickets + if ($heskSettings['cust_urgency'] && intval($ticketRequest->priority) === $TICKET_PRIORITY_CRITICAL) { + $validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN'; + } + + if ($heskSettings['require_subject'] === 1 && + ($ticketRequest->subject === NULL || $ticketRequest->subject === '')) { + $validationModel->errorKeys[] = 'SUBJECT_REQUIRED'; + } + + if ($heskSettings['require_message'] === 1 && + ($ticketRequest->message === NULL || $ticketRequest->message === '')) { + $validationModel->errorKeys[] = 'MESSAGE_REQUIRED'; + } + + foreach ($heskSettings['custom_fields'] as $key => $value) { + // TODO Only check categories that apply to this custom field + if ($value['use'] == 1 && hesk_is_custom_field_in_category($key, intval($ticketRequest->category))) { + $custom_field_value = $ticketRequest->customFields[$key]; + if (empty($custom_field_value)) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::NO_VALUE'; + continue; + } + switch($value['type']) { + case 'date': + if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_DATE'; + } else { + // Actually validate based on range + $date = strtotime($custom_field_value . ' t00:00:00'); + $dmin = strlen($value['value']['dmin']) ? strtotime($value['value']['dmin'] . ' t00:00:00') : false; + $dmax = strlen($value['value']['dmax']) ? strtotime($value['value']['dmax'] . ' t00:00:00') : false; + + if ($dmin && $dmin > $date) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::DATE_BEFORE_MIN::MIN-' . $dmin . '::ENTERED-' . $date; + } elseif ($dmax && $dmax < $date) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::DATE_AFTER_MAX::MAX-' . $dmax . '::ENTERED-' . $date; + } + } + break; + case 'email': + if (!hesk_validateEmail($custom_field_value, $value['value']['multiple'], false)) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_OR_MISSING_EMAIL'; + } + break; + } + } + } + + // TODO Check bans (email only; don't check IP on REST requests as they'll most likely be sent via servers) + // TODO submit_ticket.php:320-322 + + // TODO Check if we're at the max number of tickets + // TODO submit_ticket.php:325-334 + + return $validationModel; + } +} \ No newline at end of file diff --git a/api/businesslogic/ticket/ticket_creator.php b/api/businesslogic/ticket/ticket_creator.php index 5edfee18..a4abe2da 100644 --- a/api/businesslogic/ticket/ticket_creator.php +++ b/api/businesslogic/ticket/ticket_creator.php @@ -1,106 +1,2 @@ errorKeys) > 0) { - require_once(__DIR__ . '/../ValidationException.php'); - - // Validation failed - throw new ValidationException($validationModel); - } - - // Create the ticket -} - -/** - * @param $ticket_request CreateTicketByCustomerModel - * @param $staff bool - * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket - */ -function validate($ticket_request, $staff, $hesk_settings, $modsForHesk_settings) { - require_once(__DIR__ . '/../email_validators.php'); - require_once(__DIR__ . '/../../dao/category_dao.php'); - //require_once('../category/retriever.php'); - //require_once('../bans/retriever.php'); - - $TICKET_PRIORITY_CRITICAL = 0; - - $validationModel = new ValidationModel(); - - if ($ticket_request->name === NULL || $ticket_request->name == '') { - $validationModel->errorKeys[] = 'NO_NAME'; - } - - if (hesk_validateEmail($ticket_request->email, $hesk_settings['multi_eml'], false)) { - $validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL'; - } - - if (intval($ticket_request->category) === 0) { - $allCategories = null; - $validationModel->errorKeys[] = 'NO_CATEGORY'; - } - - // Don't allow critical priority tickets - if ($hesk_settings['cust_urgency'] && intval($ticket_request->priority) === $TICKET_PRIORITY_CRITICAL) { - $validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN'; - } - - if ($hesk_settings['require_subject'] === 1 && - ($ticket_request->subject === NULL || $ticket_request->subject === '')) { - $validationModel->errorKeys[] = 'SUBJECT_REQUIRED'; - } - - if ($hesk_settings['require_message'] === 1 && - ($ticket_request->message === NULL || $ticket_request->message === '')) { - $validationModel->errorKeys[] = 'MESSAGE_REQUIRED'; - } - - foreach ($hesk_settings['custom_fields'] as $key => $value) { - // TODO Only check categories that apply to this custom field - if ($value['use'] == 1 && hesk_is_custom_field_in_category($key, intval($ticket_request->category))) { - $custom_field_value = $ticket_request->customFields[$key]; - if (empty($custom_field_value)) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::NO_VALUE'; - continue; - } - switch($v['type']) { - case 'date': - if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_DATE'; - } else { - // Actually validate based on range - $date = strtotime($custom_field_value . ' t00:00:00'); - $dmin = strlen($value['value']['dmin']) ? strtotime($value['value']['dmin'] . ' t00:00:00') : false; - $dmax = strlen($value['value']['dmax']) ? strtotime($value['value']['dmax'] . ' t00:00:00') : false; - - if ($dmin && $dmin > $date) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::DATE_BEFORE_MIN::MIN-' . $dmin . '::ENTERED-' . $date; - } elseif ($dmax && $dmax < $date) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::DATE_AFTER_MAX::MAX-' . $dmax . '::ENTERED-' . $date; - } - } - break; - case 'email': - if (!hesk_validateEmail($custom_field_value, $value['value']['multiple'], false)) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_OR_MISSING_EMAIL'; - } - break; - } - } - } - - // TODO Check bans (email only; don't check IP on REST requests as they'll most likely be sent via servers) - // TODO submit_ticket.php:320-322 - - // TODO Check if we're at the max number of tickets - // TODO submit_ticket.php:325-334 - - return $validationModel; -} \ No newline at end of file diff --git a/api/index.php b/api/index.php index 20ae1aa1..6ecf566c 100644 --- a/api/index.php +++ b/api/index.php @@ -3,15 +3,16 @@ define('IN_SCRIPT', 1); define('HESK_PATH', '../'); require_once(__DIR__ . '/core/common.php'); -require(__DIR__ . '/Link.php'); -require(__DIR__ . '/../hesk_settings.inc.php'); +require_once(__DIR__ . '/Link.php'); +require_once(__DIR__ . '/../hesk_settings.inc.php'); // Controllers -require(__DIR__ . '/controllers/CategoryController.php'); +require_once(__DIR__ . '/controllers/CategoryController.php'); hesk_load_api_database_functions(); +require_once(__DIR__ . '/../inc/custom_fields.inc.php'); // Properly handle error logging, as well as a fatal error workaround -error_reporting(0); // Override hesk_settings. We're smarter than it +error_reporting(0); set_error_handler('errorHandler'); register_shutdown_function('fatalErrorShutdownHandler'); @@ -21,7 +22,7 @@ function handle404() { } function assertApiIsEnabled() { - throw new Exception("Some exception here!", 33); + } function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { From ecbd2fd94ad44cef7f60a7751cb8ea03e58a61d4 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 27 Jan 2017 22:26:28 -0500 Subject: [PATCH 016/192] Fixed some folder structures and worked on ban retrieval --- .../category/CategoryRetriever.php | 2 +- api/businesslogic/security/BanRetriever.php | 46 +++++++++++ api/businesslogic/security/BannedEmail.php | 32 ++++++++ api/businesslogic/security/BannedIp.php | 42 ++++++++++ .../security/UserContextBuilder.php | 6 +- api/businesslogic/ticket/TicketCreator.php | 3 +- api/dao/{ => category}/CategoryGateway.php | 2 +- api/dao/security/BanGateway.php | 77 +++++++++++++++++++ .../{UserDao.php => security/UserGateway.php} | 4 +- 9 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 api/businesslogic/security/BanRetriever.php create mode 100644 api/businesslogic/security/BannedEmail.php create mode 100644 api/businesslogic/security/BannedIp.php rename api/dao/{ => category}/CategoryGateway.php (93%) create mode 100644 api/dao/security/BanGateway.php rename api/dao/{UserDao.php => security/UserGateway.php} (88%) diff --git a/api/businesslogic/category/CategoryRetriever.php b/api/businesslogic/category/CategoryRetriever.php index 0a40bfda..fecdf8ec 100644 --- a/api/businesslogic/category/CategoryRetriever.php +++ b/api/businesslogic/category/CategoryRetriever.php @@ -12,7 +12,7 @@ use DataAccess\CategoryGateway; class CategoryRetriever { static function get_all_categories($hesk_settings) { - require_once(__DIR__ . '/../../dao/CategoryGateway.php'); + require_once(__DIR__ . '/../../dao/category/CategoryGateway.php'); return CategoryGateway::getAllCategories($hesk_settings); } diff --git a/api/businesslogic/security/BanRetriever.php b/api/businesslogic/security/BanRetriever.php new file mode 100644 index 00000000..a79aa7ff --- /dev/null +++ b/api/businesslogic/security/BanRetriever.php @@ -0,0 +1,46 @@ +email === $email) { + return true; + } + } + + return false; + } + + /** + * @param $ip int the IP address, converted beforehand using ip2long() + * @param $heskSettings + * @return bool + */ + static function isIpAddressBanned($ip, $heskSettings) { + require_once(__DIR__ . '/../../dao/security/BanGateway.php'); + + $bannedIps = BanGateway::getIpBans($heskSettings); + + foreach ($bannedIps as $bannedIp) { + if ($bannedIp->ipFrom <= $ip && $bannedIp->ipTo >= $ip) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/api/businesslogic/security/BannedEmail.php b/api/businesslogic/security/BannedEmail.php new file mode 100644 index 00000000..8b612ed9 --- /dev/null +++ b/api/businesslogic/security/BannedEmail.php @@ -0,0 +1,32 @@ + $value) { - // TODO Only check categories that apply to this custom field if ($value['use'] == 1 && hesk_is_custom_field_in_category($key, intval($ticketRequest->category))) { $custom_field_value = $ticketRequest->customFields[$key]; if (empty($custom_field_value)) { diff --git a/api/dao/CategoryGateway.php b/api/dao/category/CategoryGateway.php similarity index 93% rename from api/dao/CategoryGateway.php rename to api/dao/category/CategoryGateway.php index 2ae16bee..b9e222d0 100644 --- a/api/dao/CategoryGateway.php +++ b/api/dao/category/CategoryGateway.php @@ -13,7 +13,7 @@ use Exception; class CategoryGateway { static function getAllCategories($hesk_settings) { - require_once(__DIR__ . '/../businesslogic/category/Category.php'); + require_once(__DIR__ . '/../../businesslogic/category/Category.php'); if (!function_exists('hesk_dbConnect')) { throw new Exception('Database not loaded!'); diff --git a/api/dao/security/BanGateway.php b/api/dao/security/BanGateway.php new file mode 100644 index 00000000..d1d9a829 --- /dev/null +++ b/api/dao/security/BanGateway.php @@ -0,0 +1,77 @@ +id = intval($row['id']); + $bannedEmail->email = $row['email']; + $bannedEmail->bannedById = $row['banned_by'] === null ? null : intval($row['banned_by']); + $bannedEmail->dateBanned = $row['dt']; + + $bannedEmails[$bannedEmail->id] = $bannedEmail; + } + + return $bannedEmails; + } + + /** + * @param $heskSettings + * @return BannedIp[] + */ + static function getIpBans($heskSettings) { + require_once(__DIR__ . '/../../businesslogic/security/BannedIp.php'); + + $rs = hesk_dbQuery("SELECT `bans`.`id` AS `id`, `bans`.`ip_from` AS `ip_from`, + `bans`.`ip_to` AS `ip_to`, `bans`.`ip_display` AS `ip_display`, + `users`.`id` AS `banned_by`, `bans`.`dt` AS `dt` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "_banned_ips` AS `bans` + LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "_users` AS `users` + ON `bans`.`banned_by` = `users`.`id` + AND `users`.`active` = '1'"); + + $bannedIps = array(); + + while ($row = hesk_dbFetchAssoc($rs)) { + $bannedIp = new BannedIp(); + $bannedIp->id = intval($row['id']); + $bannedIp->ipFrom = intval($row['ip_from']); + $bannedIp->ipTo = intval($row['ip_to']); + $bannedIp->ipDisplay = $row['ip_display']; + $bannedIp->bannedById = $row['banned_by'] === null ? null : intval($row['banned_by']); + $bannedIp->dateBanned = $row['dt']; + + $bannedIps[$bannedIp->id] = $bannedIp; + } + + return $bannedIps; + } +} \ No newline at end of file diff --git a/api/dao/UserDao.php b/api/dao/security/UserGateway.php similarity index 88% rename from api/dao/UserDao.php rename to api/dao/security/UserGateway.php index a2e14c77..c533095a 100644 --- a/api/dao/UserDao.php +++ b/api/dao/security/UserGateway.php @@ -12,9 +12,9 @@ namespace DataAccess\Security; use BusinessLogic\Security\UserContextBuilder; use Exception; -class UserDao { +class UserGateway { static function getUserForAuthToken($hashedToken, $hesk_settings) { - require_once(__DIR__ . '/../businesslogic/security/UserContextBuilder.php'); + require_once(__DIR__ . '/../../businesslogic/security/UserContextBuilder.php'); if (!function_exists('hesk_dbConnect')) { throw new Exception('Database not loaded!'); From 2ef67de7188d01b77aa379d18d2229ed25415e3f Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 28 Jan 2017 01:28:53 -0500 Subject: [PATCH 017/192] Added proper DI and autoloading to make things simpler for the actual logic' --- api/DependencyManager.php | 18 +++++++++++++ api/autoload.php | 25 +++++++++++++++++++ api/businesslogic/ValidationException.php | 2 +- .../category/CategoryRetriever.php | 13 +++++++--- api/controllers/CategoryController.php | 14 ++++------- api/core/json_error.php | 4 +-- api/core/output.php | 2 +- api/dao/category/CategoryGateway.php | 4 +-- api/http_response_code.php | 15 +++++++++++ api/index.php | 15 +++-------- 10 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 api/DependencyManager.php create mode 100644 api/autoload.php create mode 100644 api/http_response_code.php diff --git a/api/DependencyManager.php b/api/DependencyManager.php new file mode 100644 index 00000000..005e731f --- /dev/null +++ b/api/DependencyManager.php @@ -0,0 +1,18 @@ +get = array(); + + $this->get['CategoryGateway'] = new CategoryGateway(); + $this->get['CategoryRetriever'] = new CategoryRetriever($this->get['CategoryGateway']); + } +} \ No newline at end of file diff --git a/api/autoload.php b/api/autoload.php new file mode 100644 index 00000000..351d664f --- /dev/null +++ b/api/autoload.php @@ -0,0 +1,25 @@ +categoryGateway = $categoryGateway; + } + + function getAllCategories($hesk_settings) { + return $this->categoryGateway->getAllCategories($hesk_settings); } } \ No newline at end of file diff --git a/api/controllers/CategoryController.php b/api/controllers/CategoryController.php index 14cc66ff..be4ab2a5 100644 --- a/api/controllers/CategoryController.php +++ b/api/controllers/CategoryController.php @@ -1,10 +1,4 @@ get['CategoryRetriever']; + + return $categoryRetriever->getAllCategories($hesk_settings); } } \ No newline at end of file diff --git a/api/core/json_error.php b/api/core/json_error.php index 2280d511..24154ae5 100644 --- a/api/core/json_error.php +++ b/api/core/json_error.php @@ -1,6 +1,6 @@ '\Controllers\Category\CategoryController::printAllCategories', '/v1/categories/{i}' => '\Controllers\Category\CategoryController', + + // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' )); \ No newline at end of file From e68ecf50c6964d8d3760ec06f56363fb2c570674 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 28 Jan 2017 01:41:29 -0500 Subject: [PATCH 018/192] Moved some more stuff to new structure --- ...encyManager.php => ApplicationContext.php} | 7 ++++- api/autoload.php | 22 +++++++++++---- .../category/CategoryRetriever.php | 6 ---- api/businesslogic/security/BanRetriever.php | 20 ++++++++----- api/core/common.php | 6 ---- api/dao/CommonDao.php | 28 +++++++++++++++++++ api/dao/category/CategoryGateway.php | 15 ++-------- api/dao/security/BanGateway.php | 22 +++++++-------- 8 files changed, 77 insertions(+), 49 deletions(-) rename api/{DependencyManager.php => ApplicationContext.php} (64%) delete mode 100644 api/core/common.php create mode 100644 api/dao/CommonDao.php diff --git a/api/DependencyManager.php b/api/ApplicationContext.php similarity index 64% rename from api/DependencyManager.php rename to api/ApplicationContext.php index 005e731f..64cf99a1 100644 --- a/api/DependencyManager.php +++ b/api/ApplicationContext.php @@ -4,9 +4,11 @@ namespace Core; // Responsible for loading in all necessary classes. AKA a poor man's DI solution. use BusinessLogic\Category\CategoryRetriever; +use BusinessLogic\Security\BanRetriever; use DataAccess\CategoryGateway; +use DataAccess\Security\BanGateway; -class DependencyManager { +class ApplicationContext { public $get; function __construct() { @@ -14,5 +16,8 @@ class DependencyManager { $this->get['CategoryGateway'] = new CategoryGateway(); $this->get['CategoryRetriever'] = new CategoryRetriever($this->get['CategoryGateway']); + + $this->get['BanGateway'] = new BanGateway(); + $this->get['BanRetriever'] = new BanRetriever($this->get['BanGateway']); } } \ No newline at end of file diff --git a/api/autoload.php b/api/autoload.php index 351d664f..5be0ed5a 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -1,25 +1,35 @@ banGateway = $banGateway; + } + /** * @param $email * @param $heskSettings * @return bool */ - static function isEmailBanned($email, $heskSettings) { - require_once(__DIR__ . '/../../dao/security/BanGateway.php'); + function isEmailBanned($email, $heskSettings) { - $bannedEmails = BanGateway::getEmailBans($heskSettings); + $bannedEmails = $this->banGateway->getEmailBans($heskSettings); foreach ($bannedEmails as $bannedEmail) { if ($bannedEmail->email === $email) { @@ -30,10 +38,8 @@ class BanRetriever { * @param $heskSettings * @return bool */ - static function isIpAddressBanned($ip, $heskSettings) { - require_once(__DIR__ . '/../../dao/security/BanGateway.php'); - - $bannedIps = BanGateway::getIpBans($heskSettings); + function isIpAddressBanned($ip, $heskSettings) { + $bannedIps = $this->banGateway->getIpBans($heskSettings); foreach ($bannedIps as $bannedIp) { if ($bannedIp->ipFrom <= $ip && $bannedIp->ipTo >= $ip) { diff --git a/api/core/common.php b/api/core/common.php deleted file mode 100644 index 77c14cd6..00000000 --- a/api/core/common.php +++ /dev/null @@ -1,6 +0,0 @@ -init(); $sql = 'SELECT * FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'categories`'; @@ -37,7 +28,7 @@ class CategoryGateway { $results[$category->id] = $category; } - hesk_dbClose(); + $this->close(); return $results; } diff --git a/api/dao/security/BanGateway.php b/api/dao/security/BanGateway.php index d1d9a829..78139819 100644 --- a/api/dao/security/BanGateway.php +++ b/api/dao/security/BanGateway.php @@ -1,25 +1,21 @@ init(); $rs = hesk_dbQuery("SELECT `bans`.`id` AS `id`, `bans`.`email` AS `email`, `users`.`id` AS `banned_by`, `bans`.`dt` AS `dt` @@ -40,6 +36,8 @@ class BanGateway { $bannedEmails[$bannedEmail->id] = $bannedEmail; } + $this->close(); + return $bannedEmails; } @@ -47,8 +45,8 @@ class BanGateway { * @param $heskSettings * @return BannedIp[] */ - static function getIpBans($heskSettings) { - require_once(__DIR__ . '/../../businesslogic/security/BannedIp.php'); + function getIpBans($heskSettings) { + $this->init(); $rs = hesk_dbQuery("SELECT `bans`.`id` AS `id`, `bans`.`ip_from` AS `ip_from`, `bans`.`ip_to` AS `ip_to`, `bans`.`ip_display` AS `ip_display`, @@ -72,6 +70,8 @@ class BanGateway { $bannedIps[$bannedIp->id] = $bannedIp; } + $this->close(); + return $bannedIps; } } \ No newline at end of file From 8968be1ffdd1440ed2f2411aa78a7bff50b67cf5 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 28 Jan 2017 22:35:42 -0500 Subject: [PATCH 019/192] More work on exception handling and user context building --- api/ApplicationContext.php | 8 ++ api/autoload.php | 14 +++ api/businesslogic/Helpers.php | 29 +++++ api/businesslogic/category/Category.php | 7 +- .../category/CategoryRetriever.php | 17 ++- api/businesslogic/category/retriever.php | 5 - .../InvalidAuthenticationTokenException.php | 11 ++ .../MissingAuthenticationTokenException.php | 17 +++ .../{ => exception}/ValidationException.php | 3 +- .../security/UserContextBuilder.php | 37 ++++-- api/businesslogic/security_retriever.php | 9 -- api/businesslogic/ticket/TicketCreator.php | 9 +- api/businesslogic/ticket/ticket_creator.php | 2 - api/businesslogic/ticket_retriever.php | 107 ------------------ api/businesslogic/user_retriever.php | 77 ------------- api/controllers/CategoryController.php | 4 +- api/core/database.inc.php | 17 ++- api/core/database_mysqli.inc.php | 13 ++- api/core/headers.php | 9 -- api/dao/category/CategoryGateway.php | 4 + api/dao/category_dao.php | 28 ----- api/dao/security/UserGateway.php | 33 +++--- api/exception/AccessException.php | 11 -- api/index.php | 47 +++++++- 24 files changed, 215 insertions(+), 303 deletions(-) create mode 100644 api/businesslogic/Helpers.php delete mode 100644 api/businesslogic/category/retriever.php create mode 100644 api/businesslogic/exception/InvalidAuthenticationTokenException.php create mode 100644 api/businesslogic/exception/MissingAuthenticationTokenException.php rename api/businesslogic/{ => exception}/ValidationException.php (87%) delete mode 100644 api/businesslogic/security_retriever.php delete mode 100644 api/businesslogic/ticket/ticket_creator.php delete mode 100644 api/businesslogic/ticket_retriever.php delete mode 100644 api/businesslogic/user_retriever.php delete mode 100644 api/core/headers.php delete mode 100644 api/dao/category_dao.php delete mode 100644 api/exception/AccessException.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 64cf99a1..1c2c9307 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -5,8 +5,10 @@ namespace Core; // Responsible for loading in all necessary classes. AKA a poor man's DI solution. use BusinessLogic\Category\CategoryRetriever; use BusinessLogic\Security\BanRetriever; +use BusinessLogic\Security\UserContextBuilder; use DataAccess\CategoryGateway; use DataAccess\Security\BanGateway; +use DataAccess\Security\UserGateway; class ApplicationContext { public $get; @@ -14,10 +16,16 @@ class ApplicationContext { function __construct() { $this->get = array(); + // Categories $this->get['CategoryGateway'] = new CategoryGateway(); $this->get['CategoryRetriever'] = new CategoryRetriever($this->get['CategoryGateway']); + // Bans $this->get['BanGateway'] = new BanGateway(); $this->get['BanRetriever'] = new BanRetriever($this->get['BanGateway']); + + // User Context + $this->get['UserGateway'] = new UserGateway(); + $this->get['UserContextBuilder'] = new UserContextBuilder($this->get['UserGateway']); } } \ No newline at end of file diff --git a/api/autoload.php b/api/autoload.php index 5be0ed5a..b4b7d7da 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -12,6 +12,15 @@ require_once(__DIR__ . '/../hesk_settings.inc.php'); // Mods for HESK API Files require_once(__DIR__ . '/http_response_code.php'); +require_once(__DIR__ . '/dao/CommonDao.php'); +require_once(__DIR__ . '/businesslogic/Helpers.php'); + +// User Context +require_once(__DIR__ . '/dao/security/UserGateway.php'); +require_once(__DIR__ . '/businesslogic/security/UserContextBuilder.php'); +require_once(__DIR__ . '/businesslogic/security/UserContextNotifications.php'); +require_once(__DIR__ . '/businesslogic/security/UserContextPreferences.php'); +require_once(__DIR__ . '/businesslogic/security/UserContext.php'); // Categories require_once(__DIR__ . '/dao/category/CategoryGateway.php'); @@ -25,6 +34,11 @@ require_once(__DIR__ . '/businesslogic/security/BanRetriever.php'); require_once(__DIR__ . '/businesslogic/security/BannedEmail.php'); require_once(__DIR__ . '/businesslogic/security/BannedIp.php'); +// Exceptions +require_once(__DIR__ . '/businesslogic/exception/InvalidAuthenticationTokenException.php'); +require_once(__DIR__ . '/businesslogic/exception/MissingAuthenticationTokenException.php'); +require_once(__DIR__ . '/businesslogic/exception/ValidationException.php'); + hesk_load_api_database_functions(); // HESK files that require database access diff --git a/api/businesslogic/Helpers.php b/api/businesslogic/Helpers.php new file mode 100644 index 00000000..0df1d131 --- /dev/null +++ b/api/businesslogic/Helpers.php @@ -0,0 +1,29 @@ + $value) { + $uppercaseHeaders[strtoupper($header)] = $value; + } + + return isset($uppercaseHeaders[$key]) + ? $uppercaseHeaders[$key] + : NULL; + } + + static function hashToken($token) { + return hash('sha512', $token); + } +} \ No newline at end of file diff --git a/api/businesslogic/category/Category.php b/api/businesslogic/category/Category.php index 77a6be9c..e73a7888 100644 --- a/api/businesslogic/category/Category.php +++ b/api/businesslogic/category/Category.php @@ -39,7 +39,12 @@ class Category { public $priority; /** - * @var int? The manager for the category, if applicable + * @var int|null The manager for the category, if applicable */ public $manager; + + /** + * @var bool Indication if the user has access to the category + */ + public $accessible; } \ No newline at end of file diff --git a/api/businesslogic/category/CategoryRetriever.php b/api/businesslogic/category/CategoryRetriever.php index c45ee146..3c52f79a 100644 --- a/api/businesslogic/category/CategoryRetriever.php +++ b/api/businesslogic/category/CategoryRetriever.php @@ -2,6 +2,7 @@ namespace BusinessLogic\Category; +use BusinessLogic\Security\UserContext; use DataAccess\CategoryGateway; class CategoryRetriever { @@ -14,7 +15,19 @@ class CategoryRetriever { $this->categoryGateway = $categoryGateway; } - function getAllCategories($hesk_settings) { - return $this->categoryGateway->getAllCategories($hesk_settings); + /** + * @param $heskSettings array + * @param $userContext UserContext + * @return array + */ + function getAllCategories($heskSettings, $userContext) { + $categories = $this->categoryGateway->getAllCategories($heskSettings); + + foreach ($categories as $category) { + $category->accessible = $userContext->admin || + in_array($category->id, $userContext->categories); + } + + return $categories; } } \ No newline at end of file diff --git a/api/businesslogic/category/retriever.php b/api/businesslogic/category/retriever.php deleted file mode 100644 index d8e45267..00000000 --- a/api/businesslogic/category/retriever.php +++ /dev/null @@ -1,5 +0,0 @@ -userGateway = $userGateway; + } + + function buildUserContext($authToken, $heskSettings) { + $NULL_OR_EMPTY_STRING = 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'; - $hashedToken = hash('sha512', $authToken); - return UserGateway::getUserForAuthToken($hashedToken, $hesk_settings); + $hashedToken = Helpers::hashToken($authToken); + + if ($hashedToken === $NULL_OR_EMPTY_STRING) { + throw new MissingAuthenticationTokenException(); + } + + $userRow = $this->userGateway->getUserForAuthToken($hashedToken, $heskSettings); + + if ($userRow === null) { + throw new InvalidAuthenticationTokenException(); + } + + return $this->fromDataRow($userRow); } /** @@ -18,11 +41,7 @@ class UserContextBuilder { * @param $dataRow array the $_SESSION superglobal or the hesk_users result set * @return UserContext the built user context */ - static function fromDataRow($dataRow) { - require_once(__DIR__ . '/UserContext.php'); - require_once(__DIR__ . '/UserContextPreferences.php'); - require_once(__DIR__ . '/UserContextNotifications.php'); - + function fromDataRow($dataRow) { $userContext = new UserContext(); $userContext->id = $dataRow['id']; $userContext->username = $dataRow['user']; diff --git a/api/businesslogic/security_retriever.php b/api/businesslogic/security_retriever.php deleted file mode 100644 index 742d97c7..00000000 --- a/api/businesslogic/security_retriever.php +++ /dev/null @@ -1,9 +0,0 @@ -errorKeys) > 0) { - require_once(__DIR__ . '/../ValidationException.php'); - // Validation failed throw new ValidationException($validationModel); } @@ -35,11 +33,6 @@ class TicketCreator { * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket */ function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings) { - require_once(__DIR__ . '/../email_validators.php'); - require_once(__DIR__ . '/../category/CategoryRetriever.php'); - //require_once('../category/retriever.php'); - //require_once('../bans/retriever.php'); - $TICKET_PRIORITY_CRITICAL = 0; $validationModel = new ValidationModel(); diff --git a/api/businesslogic/ticket/ticket_creator.php b/api/businesslogic/ticket/ticket_creator.php deleted file mode 100644 index a4abe2da..00000000 --- a/api/businesslogic/ticket/ticket_creator.php +++ /dev/null @@ -1,2 +0,0 @@ -get['CategoryRetriever']; - return $categoryRetriever->getAllCategories($hesk_settings); + return $categoryRetriever->getAllCategories($hesk_settings, $userContext); } } \ No newline at end of file diff --git a/api/core/database.inc.php b/api/core/database.inc.php index c56381e6..08a20ae2 100755 --- a/api/core/database.inc.php +++ b/api/core/database.inc.php @@ -120,9 +120,8 @@ function hesk_dbConnect() { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_connect_db'], $message); - return http_response_code(500); + //TODO Throw exception + //print_error($hesklang['cant_connect_db'], $message); } if ( ! @mysql_select_db($hesk_settings['db_name'], $hesk_db_link)) @@ -135,9 +134,8 @@ function hesk_dbConnect() { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_connect_db'], $message); - die(); + //TODO Throw exception + //print_error($hesklang['cant_connect_db'], $message); } // Check MySQL/PHP version and set encoding to utf8 @@ -182,10 +180,9 @@ function hesk_dbQuery($query) { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_sql'], $message); - die(); - + //TODO Throw exception + //print_error($hesklang['cant_sql'], $message); + return null; } // END hesk_dbQuery() diff --git a/api/core/database_mysqli.inc.php b/api/core/database_mysqli.inc.php index b52122aa..f25f3004 100755 --- a/api/core/database_mysqli.inc.php +++ b/api/core/database_mysqli.inc.php @@ -128,9 +128,9 @@ function hesk_dbConnect() { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_connect_db'], $message); - http_response_code(500); + + //TODO Throw exception instead + //print_error($hesklang['cant_connect_db'], $message); } // Check MySQL/PHP version and set encoding to utf8 @@ -177,9 +177,10 @@ function hesk_dbQuery($query) { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_sql'], $message); - die(http_response_code(500)); + + //TODO Throw exception instead + //print_error($hesklang['cant_sql'], $message); + return null; } // END hesk_dbQuery() diff --git a/api/core/headers.php b/api/core/headers.php deleted file mode 100644 index 892fafb0..00000000 --- a/api/core/headers.php +++ /dev/null @@ -1,9 +0,0 @@ -init(); diff --git a/api/dao/category_dao.php b/api/dao/category_dao.php deleted file mode 100644 index d92ea51c..00000000 --- a/api/dao/category_dao.php +++ /dev/null @@ -1,28 +0,0 @@ -init(); - if (!function_exists('hesk_dbConnect')) { - throw new Exception('Database not loaded!'); - } - hesk_dbConnect(); - - $rs = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'users` WHERE `id` = ( + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = ( SELECT `` - FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'user_api_tokens` - WHERE `token` = ' . hesk_dbEscape($hashedToken) . ' - )'); + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "user_api_tokens` + WHERE `tokens`.`token` = " . hesk_dbEscape($hashedToken) . " + ) AND `active` = '1'"); + + if (hesk_dbNumRows($rs) === 0) { + return null; + } $row = hesk_dbFetchAssoc($rs); - return UserContextBuilder::fromDataRow($row); + $this->close(); + + return $row; } } \ No newline at end of file diff --git a/api/exception/AccessException.php b/api/exception/AccessException.php deleted file mode 100644 index 7931affe..00000000 --- a/api/exception/AccessException.php +++ /dev/null @@ -1,11 +0,0 @@ -get['UserContextBuilder']; + + $userContext = $userContextBuilder->buildUserContext($xAuthToken, $hesk_settings); } function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { - print_error(sprintf("Uncaught error in %s", $errorFile), $errorMessage); + throw new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage)); +} + +/** + * @param $exception Exception + */ +function exceptionHandler($exception) { + if (exceptionIsOfType($exception, 'MissingAuthenticationTokenException')) { + print_error("Security Exception", $exception->getMessage(), 400); + } elseif (exceptionIsOfType($exception, 'InvalidAuthenticationTokenException')) { + print_error("Security Exception", $exception->getMessage(), 401); + } else { + print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + } + // Log more stuff to logging table if possible; we'll catch any exceptions from this die(); } +/** + * @param $exception Exception thrown exception + * @param $class string The name of the expected exception type + * @return bool + */ +function exceptionIsOfType($exception, $class) { + return strpos(get_class($exception), $class) !== false; +} + function fatalErrorShutdownHandler() { $last_error = error_get_last(); if ($last_error['type'] === E_ERROR) { @@ -27,8 +69,7 @@ function fatalErrorShutdownHandler() { } } -// Must use fully-qualified namespace to controllers -Link::before('assertApiIsEnabled'); +Link::before('before'); Link::all(array( // Categories From a4af2e668f6ab0fbb9703d6be5082ffe8c007f36 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 30 Jan 2017 22:10:14 -0500 Subject: [PATCH 020/192] More work on ticket stuff --- api/autoload.php | 2 + .../exception/ApiFriendlyException.php | 25 +++ .../InvalidAuthenticationTokenException.php | 7 +- .../MissingAuthenticationTokenException.php | 14 +- api/businesslogic/ticket/Ticket.php | 191 ++++++++++++++++++ api/businesslogic/ticket/TicketValidators.php | 12 ++ api/core/SQLException.php | 18 ++ api/core/database.inc.php | 47 ++--- api/core/database_mysqli.inc.php | 25 +-- api/dao/security/UserGateway.php | 11 +- api/dao/ticket/TicketGateway.php | 13 ++ api/index.php | 18 +- api/testcategory.php | 11 - 13 files changed, 300 insertions(+), 94 deletions(-) create mode 100644 api/businesslogic/exception/ApiFriendlyException.php create mode 100644 api/businesslogic/ticket/Ticket.php create mode 100644 api/businesslogic/ticket/TicketValidators.php create mode 100644 api/core/SQLException.php create mode 100644 api/dao/ticket/TicketGateway.php delete mode 100644 api/testcategory.php diff --git a/api/autoload.php b/api/autoload.php index b4b7d7da..dbc64116 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -35,9 +35,11 @@ require_once(__DIR__ . '/businesslogic/security/BannedEmail.php'); require_once(__DIR__ . '/businesslogic/security/BannedIp.php'); // Exceptions +require_once(__DIR__ . '/businesslogic/exception/ApiFriendlyException.php'); require_once(__DIR__ . '/businesslogic/exception/InvalidAuthenticationTokenException.php'); require_once(__DIR__ . '/businesslogic/exception/MissingAuthenticationTokenException.php'); require_once(__DIR__ . '/businesslogic/exception/ValidationException.php'); +require_once(__DIR__ . '/core/SQLException.php'); hesk_load_api_database_functions(); diff --git a/api/businesslogic/exception/ApiFriendlyException.php b/api/businesslogic/exception/ApiFriendlyException.php new file mode 100644 index 00000000..f9fac53b --- /dev/null +++ b/api/businesslogic/exception/ApiFriendlyException.php @@ -0,0 +1,25 @@ +title = $title; + $this->httpResponseCode = $httpResponseCode; + + parent::__construct($message); + } + +} \ No newline at end of file diff --git a/api/businesslogic/exception/InvalidAuthenticationTokenException.php b/api/businesslogic/exception/InvalidAuthenticationTokenException.php index d4686332..c28ff8a0 100644 --- a/api/businesslogic/exception/InvalidAuthenticationTokenException.php +++ b/api/businesslogic/exception/InvalidAuthenticationTokenException.php @@ -2,10 +2,11 @@ namespace BusinessLogic\Exceptions; -use Exception; -class InvalidAuthenticationTokenException extends Exception { +class InvalidAuthenticationTokenException extends ApiFriendlyException { public function __construct() { - parent::__construct('The X-Auth-Token is invalid. The token must be for an active helpdesk user.'); + parent::__construct('The X-Auth-Token is invalid. The token must be for an active helpdesk user.', + 'Security Exception', + 401); } } \ No newline at end of file diff --git a/api/businesslogic/exception/MissingAuthenticationTokenException.php b/api/businesslogic/exception/MissingAuthenticationTokenException.php index 1e0ee1e9..596839ff 100644 --- a/api/businesslogic/exception/MissingAuthenticationTokenException.php +++ b/api/businesslogic/exception/MissingAuthenticationTokenException.php @@ -1,17 +1,11 @@ failingQuery = $failingQuery; + + parent::__construct('A SQL exception occurred. Check the logs for more information.'); + } +} \ No newline at end of file diff --git a/api/core/database.inc.php b/api/core/database.inc.php index 08a20ae2..d93a1389 100755 --- a/api/core/database.inc.php +++ b/api/core/database.inc.php @@ -112,30 +112,15 @@ function hesk_dbConnect() // Errors? if ( ! $hesk_db_link) { - if ($hesk_settings['debug_mode']) - { - $message = $hesklang['mysql_said'] . ': ' . mysql_error(); - } - else - { - $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; - } - //TODO Throw exception - //print_error($hesklang['cant_connect_db'], $message); + $message = $hesklang['mysql_said'] . ': ' . mysql_error(); + + throw new \Core\Exceptions\SQLException($message); } - if ( ! @mysql_select_db($hesk_settings['db_name'], $hesk_db_link)) - { - if ($hesk_settings['debug_mode']) - { - $message = $hesklang['mysql_said'] . ': ' . mysql_error(); - } - else - { - $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; - } - //TODO Throw exception - //print_error($hesklang['cant_connect_db'], $message); + if ( ! @mysql_select_db($hesk_settings['db_name'], $hesk_db_link)) { + $message = $hesklang['mysql_said'] . ': ' . mysql_error(); + + throw new \Core\Exceptions\SQLException($message); } // Check MySQL/PHP version and set encoding to utf8 @@ -168,21 +153,12 @@ function hesk_dbQuery($query) $hesk_last_query = $query; - if ($res = @mysql_query($query, $hesk_db_link)) - { + if ($res = @mysql_query($query, $hesk_db_link)) { return $res; } - elseif ($hesk_settings['debug_mode']) - { - $message = $hesklang['mysql_said'] . mysql_error(); - } - else - { - $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; - } - //TODO Throw exception - //print_error($hesklang['cant_sql'], $message); - return null; + + $message = $hesklang['mysql_said'] . mysql_error(); + throw new \Core\Exceptions\SQLException($message); } // END hesk_dbQuery() @@ -219,6 +195,7 @@ function hesk_dbInsertID() return $lastid; } + return null; } // END hesk_dbInsertID() diff --git a/api/core/database_mysqli.inc.php b/api/core/database_mysqli.inc.php index f25f3004..0734a2b5 100755 --- a/api/core/database_mysqli.inc.php +++ b/api/core/database_mysqli.inc.php @@ -120,17 +120,9 @@ function hesk_dbConnect() // Errors? if ( ! $hesk_db_link) { - if ($hesk_settings['debug_mode']) - { - $message = $hesklang['mysql_said'] . ': (' . mysqli_connect_errno() . ') ' . mysqli_connect_error(); - } - else - { - $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; - } + $message = $hesklang['mysql_said'] . ': (' . mysqli_connect_errno() . ') ' . mysqli_connect_error(); - //TODO Throw exception instead - //print_error($hesklang['cant_connect_db'], $message); + throw new \Core\Exceptions\SQLException($message); } // Check MySQL/PHP version and set encoding to utf8 @@ -169,18 +161,9 @@ function hesk_dbQuery($query) { return $res; } - elseif ($hesk_settings['debug_mode']) - { - $message = $hesklang['mysql_said'] . ': ' . mysqli_error($hesk_db_link); - } - else - { - $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; - } - //TODO Throw exception instead - //print_error($hesklang['cant_sql'], $message); - return null; + $message = $hesklang['mysql_said'] . ': ' . mysqli_error($hesk_db_link); + throw new \Core\Exceptions\SQLException($message); } // END hesk_dbQuery() diff --git a/api/dao/security/UserGateway.php b/api/dao/security/UserGateway.php index 95eb9dcf..1962f3ff 100644 --- a/api/dao/security/UserGateway.php +++ b/api/dao/security/UserGateway.php @@ -1,11 +1,4 @@ init(); $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = ( - SELECT `` + SELECT `user_id` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "user_api_tokens` - WHERE `tokens`.`token` = " . hesk_dbEscape($hashedToken) . " + WHERE `token` = '" . hesk_dbEscape($hashedToken) . "' ) AND `active` = '1'"); if (hesk_dbNumRows($rs) === 0) { diff --git a/api/dao/ticket/TicketGateway.php b/api/dao/ticket/TicketGateway.php new file mode 100644 index 00000000..cb0f246e --- /dev/null +++ b/api/dao/ticket/TicketGateway.php @@ -0,0 +1,13 @@ +getMessage(), 400); - } elseif (exceptionIsOfType($exception, 'InvalidAuthenticationTokenException')) { - print_error("Security Exception", $exception->getMessage(), 401); + if (exceptionIsOfType($exception, 'ApiFriendlyException')) { + /* @var $castedException \BusinessLogic\Exceptions\ApiFriendlyException */ + $castedException = $exception; + + print_error($castedException->title, $castedException->getMessage(), $castedException->httpResponseCode); } else { - print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + if (exceptionIsOfType($exception, 'SQLException')) { + /* @var $castedException \Core\Exceptions\SQLException */ + $castedException = $exception; + print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); + } else { + print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + } + } // Log more stuff to logging table if possible; we'll catch any exceptions from this die(); diff --git a/api/testcategory.php b/api/testcategory.php deleted file mode 100644 index cd9592ab..00000000 --- a/api/testcategory.php +++ /dev/null @@ -1,11 +0,0 @@ - Date: Tue, 31 Jan 2017 22:26:46 -0500 Subject: [PATCH 021/192] Ticket stuff slowly taking shape --- api/ApplicationContext.php | 6 ++ api/autoload.php | 10 ++ api/businesslogic/ticket/Attachment.php | 27 +++++ api/businesslogic/ticket/Ticket.php | 105 +++++++++++++++++-- api/businesslogic/ticket/TicketRetriever.php | 27 +++++ api/controllers/TicketController.php | 17 +++ api/dao/ticket/TicketGateway.php | 24 ++++- api/index.php | 2 + 8 files changed, 207 insertions(+), 11 deletions(-) create mode 100644 api/businesslogic/ticket/Attachment.php create mode 100644 api/businesslogic/ticket/TicketRetriever.php create mode 100644 api/controllers/TicketController.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 1c2c9307..09e4cdd4 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -6,9 +6,11 @@ namespace Core; use BusinessLogic\Category\CategoryRetriever; use BusinessLogic\Security\BanRetriever; use BusinessLogic\Security\UserContextBuilder; +use BusinessLogic\Tickets\TicketRetriever; use DataAccess\CategoryGateway; use DataAccess\Security\BanGateway; use DataAccess\Security\UserGateway; +use DataAccess\Tickets\TicketGateway; class ApplicationContext { public $get; @@ -20,6 +22,10 @@ class ApplicationContext { $this->get['CategoryGateway'] = new CategoryGateway(); $this->get['CategoryRetriever'] = new CategoryRetriever($this->get['CategoryGateway']); + // Tickets + $this->get['TicketGateway'] = new TicketGateway(); + $this->get['TicketRetriever'] = new TicketRetriever($this->get['TicketGateway']); + // Bans $this->get['BanGateway'] = new BanGateway(); $this->get['BanRetriever'] = new BanRetriever($this->get['BanGateway']); diff --git a/api/autoload.php b/api/autoload.php index dbc64116..d468c912 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -34,6 +34,16 @@ require_once(__DIR__ . '/businesslogic/security/BanRetriever.php'); require_once(__DIR__ . '/businesslogic/security/BannedEmail.php'); require_once(__DIR__ . '/businesslogic/security/BannedIp.php'); +// Tickets +require_once(__DIR__ . '/dao/ticket/TicketGateway.php'); +require_once(__DIR__ . '/businesslogic/ticket/Attachment.php'); +require_once(__DIR__ . '/businesslogic/ticket/Ticket.php'); +require_once(__DIR__ . '/businesslogic/ticket/CreateTicketByCustomerModel.php'); +require_once(__DIR__ . '/businesslogic/ticket/TicketValidators.php'); +require_once(__DIR__ . '/businesslogic/ticket/TicketCreator.php'); +require_once(__DIR__ . '/businesslogic/ticket/TicketRetriever.php'); +require_once(__DIR__ . '/controllers/TicketController.php'); + // Exceptions require_once(__DIR__ . '/businesslogic/exception/ApiFriendlyException.php'); require_once(__DIR__ . '/businesslogic/exception/InvalidAuthenticationTokenException.php'); diff --git a/api/businesslogic/ticket/Attachment.php b/api/businesslogic/ticket/Attachment.php new file mode 100644 index 00000000..125a8ced --- /dev/null +++ b/api/businesslogic/ticket/Attachment.php @@ -0,0 +1,27 @@ +id = $row['id']; + $ticket->trackingId = $row['trackid']; + $ticket->name = $row['name']; + $ticket->email = $row['email']; + $ticket->categoryId = $row['category']; + $ticket->priorityId = $row['priority']; + $ticket->subject = $row['subject']; + $ticket->message = $row['message']; + $ticket->dateCreated = $row['dt']; + $ticket->lastChanged = $row['lastchange']; + $ticket->firstReplyDate = $row['firstreply']; + $ticket->closedDate = $row['closedat']; + $ticket->suggestedArticles = explode(',', $row['articles']); + $ticket->ipAddress = $row['ip']; + $ticket->language = $row['language']; + $ticket->statusId = $row['status']; + $ticket->openedBy = $row['openedby']; + $ticket->firstReplyByUserId = $row['firstreplyby']; + $ticket->closedByUserId = $row['closedby']; + $ticket->numberOfReplies = $row['replies']; + $ticket->numberOfStaffReplies = $row['staffreplies']; + $ticket->ownerId = $row['owner']; + $ticket->timeWorked = $row['time_worked']; + $ticket->lastReplyBy = $row['lastreplier']; + $ticket->lastReplier = $row['replierid']; + $ticket->archived = intval($row['archive']) === 1; + $ticket->locked = intval($row['locked']) === 1; + + if (trim($row['attachments']) !== '') { + $attachments = explode(',', $row['attachments']); + $attachmentArray = array(); + foreach ($attachments as $attachment) { + $attachmentRow = explode('#', $attachment); + $attachmentModel = new Attachment(); + + $attachmentModel->id = $attachmentRow[0]; + $attachmentModel->fileName = $attachmentRow[1]; + $attachmentModel->savedName = $attachmentRow[2]; + + $attachmentArray[] = $attachmentModel; + } + $ticket->attachments = $attachmentArray; + } + + if (trim($row['merged']) !== '') { + $ticket->mergedTicketIds = explode(',', $row['merged']); + } + + $ticket->auditTrailHtml = $row['history']; + + $ticket->customFields = array(); + foreach ($heskSettings['custom_fields'] as $key => $value) { + if ($value['use'] == 1 && hesk_is_custom_field_in_category($key, intval($ticket->categoryId))) { + $ticket->customFields[str_replace('custom', '', $key)] = $row[$key]; + } + } + + $ticket->linkedTicketIds = array(); + while ($linkedTicketsRow = hesk_dbFetchAssoc($linkedTicketsRs)) { + $ticket->linkedTicketIds[] = $linkedTicketsRow['id']; + } + + $ticket->location = array(); + $ticket->location[0] = $row['latitude']; + $ticket->location[1] = $row['longitude']; + + $ticket->usesHtml = intval($row['html']) === 1; + $ticket->userAgent = $row['user_agent']; + $ticket->screenResolution = array(); + $ticket->screenResolution[0] = $row['screen_resolution_width']; + $ticket->screenResolution[1] = $row['screen_resolution_height']; + + $ticket->dueDate = $row['due_date']; + $ticket->dueDateOverdueEmailSent = $row['overdue_email_sent'] !== null && intval($row['overdue_email_sent']) === 1; + + return $ticket; + } + /** * @var int */ @@ -27,12 +107,12 @@ class Ticket { /** * @var int */ - public $category; + public $categoryId; /** * @var int */ - public $priority; + public $priorityId; /** * @var string @@ -65,7 +145,7 @@ class Ticket { public $closedDate; /** - * @var string|null + * @var string[]|null */ public $suggestedArticles; @@ -85,9 +165,9 @@ class Ticket { public $statusId; /** - * @var int (convert to enum) + * @var int */ - public $openedByUserId; + public $openedBy; /** * @var int|null @@ -120,7 +200,12 @@ class Ticket { public $timeWorked; /** - * @var int (convert to enum) + * @var int + */ + public $lastReplyBy; + + /** + * @var int|null */ public $lastReplier; @@ -135,7 +220,7 @@ class Ticket { public $locked; /** - * @var array|null (TODO clarify this later) + * @var Attachment[]|null */ public $attachments; @@ -150,7 +235,7 @@ class Ticket { public $auditTrailHtml; /** - * @var array (TODO clarify this later) + * @var string[] */ public $customFields; @@ -160,7 +245,7 @@ class Ticket { public $linkedTicketIds; /** - * @var float[2]|null + * @var float[]|null */ public $location; @@ -175,7 +260,7 @@ class Ticket { public $userAgent; /** - * @var int[2]|null + * @var int[]|null */ public $screenResolution; diff --git a/api/businesslogic/ticket/TicketRetriever.php b/api/businesslogic/ticket/TicketRetriever.php new file mode 100644 index 00000000..0ff84fae --- /dev/null +++ b/api/businesslogic/ticket/TicketRetriever.php @@ -0,0 +1,27 @@ +ticketGateway = $ticketGateway; + } + + function getTicketById($id, $heskSettings, $userContext) { + return $this->ticketGateway->getTicketById($id, $heskSettings); + } +} \ No newline at end of file diff --git a/api/controllers/TicketController.php b/api/controllers/TicketController.php new file mode 100644 index 00000000..57125ccb --- /dev/null +++ b/api/controllers/TicketController.php @@ -0,0 +1,17 @@ +get['TicketRetriever']; + + output($ticketRetriever->getTicketById($id, $hesk_settings, $userContext)); + } +} \ No newline at end of file diff --git a/api/dao/ticket/TicketGateway.php b/api/dao/ticket/TicketGateway.php index cb0f246e..9b05e594 100644 --- a/api/dao/ticket/TicketGateway.php +++ b/api/dao/ticket/TicketGateway.php @@ -3,11 +3,33 @@ namespace DataAccess\Tickets; -class TicketGateway { +use BusinessLogic\Tickets\Ticket; +use DataAccess\CommonDao; + +class TicketGateway extends CommonDao { + function getTicketById($id, $heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `id` = " . intval($id)); + $row = hesk_dbFetchAssoc($rs); + $linkedTicketsRs = hesk_dbQuery("SELECT * FROM `hesk_tickets` WHERE `parent` = " . intval($id)); + + $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $heskSettings); + + $this->close(); + + return $ticket; + } + function getTicketsByEmail($emailAddress, $heskSettings) { $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `email` = '" . hesk_dbEscape($emailAddress) . "'"); + $tickets = array(); + + while ($row = hesk_dbFetchAssoc($rs)) { + $ticket = new Ticket(); + } } } \ No newline at end of file diff --git a/api/index.php b/api/index.php index 5b853c41..b58ce89a 100644 --- a/api/index.php +++ b/api/index.php @@ -83,6 +83,8 @@ Link::all(array( // Categories '/v1/categories' => '\Controllers\Category\CategoryController::printAllCategories', '/v1/categories/{i}' => '\Controllers\Category\CategoryController', + // Tickets + '/v1/tickets/{i}' => '\Controllers\Tickets\TicketController', // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' From 2b0d2bb9ccfc50eb1aa19bdf6a4fedde718c1726 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 1 Feb 2017 21:03:05 -0500 Subject: [PATCH 022/192] Send all enabled custom fields --- api/businesslogic/ticket/Ticket.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/businesslogic/ticket/Ticket.php b/api/businesslogic/ticket/Ticket.php index bf1326c0..f5bf749d 100644 --- a/api/businesslogic/ticket/Ticket.php +++ b/api/businesslogic/ticket/Ticket.php @@ -58,7 +58,7 @@ class Ticket { $ticket->customFields = array(); foreach ($heskSettings['custom_fields'] as $key => $value) { - if ($value['use'] == 1 && hesk_is_custom_field_in_category($key, intval($ticket->categoryId))) { + if ($value['use'] && hesk_is_custom_field_in_category($key, intval($ticket->categoryId))) { $ticket->customFields[str_replace('custom', '', $key)] = $row[$key]; } } From ad43c420bb51be7eb97948ee435145d08e4290f9 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 1 Feb 2017 22:04:52 -0500 Subject: [PATCH 023/192] Use PSR-4 standard for classloading --- api/ApplicationContext.php | 7 ++- api/BusinessLogic/Categories/Category.php | 50 ++++++++++++++++++ .../Categories}/CategoryRetriever.php | 4 +- .../Exceptions}/ApiFriendlyException.php | 0 .../InvalidAuthenticationTokenException.php | 0 .../MissingAuthenticationTokenException.php | 0 .../Exceptions}/ValidationException.php | 2 +- .../Tickets}/Attachment.php | 0 .../Tickets}/CreateTicketByCustomerModel.php | 0 .../Tickets}/Ticket.php | 52 ++++++++++++------- .../Tickets}/TicketCreator.php | 4 +- .../Tickets}/TicketRetriever.php | 0 .../Tickets}/TicketValidators.php | 0 .../Categories}/CategoryController.php | 0 .../Tickets}/TicketController.php | 0 .../Exceptions}/SQLException.php | 2 +- .../Categories}/CategoryGateway.php | 3 +- api/{dao => DataAccess}/CommonDao.php | 6 --- .../Security}/BanGateway.php | 0 .../Security}/UserGateway.php | 0 .../Tickets}/TicketGateway.php | 0 api/{dao => DataAccess}/canned_dao.php | 0 api/{dao => DataAccess}/status_dao.php | 0 api/{dao => DataAccess}/ticket_dao.php | 4 +- .../ticket_template_dao.php | 0 api/{dao => DataAccess}/user_dao.php | 0 api/autoload.php | 49 ++--------------- api/bootstrap.php | 8 +++ api/businesslogic/Helpers.php | 8 +-- api/businesslogic/ValidationModel.php | 2 +- api/businesslogic/category/Category.php | 50 ------------------ .../security/UserContextBuilder.php | 2 +- api/dao/security_dao.php | 23 -------- api/index.php | 12 +++-- 34 files changed, 117 insertions(+), 171 deletions(-) create mode 100644 api/BusinessLogic/Categories/Category.php rename api/{businesslogic/category => BusinessLogic/Categories}/CategoryRetriever.php (90%) rename api/{businesslogic/exception => BusinessLogic/Exceptions}/ApiFriendlyException.php (100%) rename api/{businesslogic/exception => BusinessLogic/Exceptions}/InvalidAuthenticationTokenException.php (100%) rename api/{businesslogic/exception => BusinessLogic/Exceptions}/MissingAuthenticationTokenException.php (100%) rename api/{businesslogic/exception => BusinessLogic/Exceptions}/ValidationException.php (92%) rename api/{businesslogic/ticket => BusinessLogic/Tickets}/Attachment.php (100%) rename api/{businesslogic/ticket => BusinessLogic/Tickets}/CreateTicketByCustomerModel.php (100%) rename api/{businesslogic/ticket => BusinessLogic/Tickets}/Ticket.php (74%) rename api/{businesslogic/ticket => BusinessLogic/Tickets}/TicketCreator.php (98%) rename api/{businesslogic/ticket => BusinessLogic/Tickets}/TicketRetriever.php (100%) rename api/{businesslogic/ticket => BusinessLogic/Tickets}/TicketValidators.php (100%) rename api/{controllers => Controllers/Categories}/CategoryController.php (100%) rename api/{controllers => Controllers/Tickets}/TicketController.php (100%) rename api/{core => Core/Exceptions}/SQLException.php (72%) rename api/{dao/category => DataAccess/Categories}/CategoryGateway.php (94%) rename api/{dao => DataAccess}/CommonDao.php (81%) rename api/{dao/security => DataAccess/Security}/BanGateway.php (100%) rename api/{dao/security => DataAccess/Security}/UserGateway.php (100%) rename api/{dao/ticket => DataAccess/Tickets}/TicketGateway.php (100%) rename api/{dao => DataAccess}/canned_dao.php (100%) rename api/{dao => DataAccess}/status_dao.php (100%) rename api/{dao => DataAccess}/ticket_dao.php (93%) rename api/{dao => DataAccess}/ticket_template_dao.php (100%) rename api/{dao => DataAccess}/user_dao.php (100%) create mode 100644 api/bootstrap.php delete mode 100644 api/businesslogic/category/Category.php delete mode 100644 api/dao/security_dao.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 09e4cdd4..aa09011c 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -1,17 +1,16 @@ id = $row['id']; + $ticket->id = intval($row['id']); $ticket->trackingId = $row['trackid']; $ticket->name = $row['name']; $ticket->email = $row['email']; - $ticket->categoryId = $row['category']; - $ticket->priorityId = $row['priority']; + $ticket->categoryId = intval($row['category']); + $ticket->priorityId = intval($row['priority']); $ticket->subject = $row['subject']; $ticket->message = $row['message']; $ticket->dateCreated = $row['dt']; $ticket->lastChanged = $row['lastchange']; $ticket->firstReplyDate = $row['firstreply']; $ticket->closedDate = $row['closedat']; - $ticket->suggestedArticles = explode(',', $row['articles']); + + if (trim($row['articles']) !== '') { + $ticket->suggestedArticles = explode(',', $row['articles']); + } + $ticket->ipAddress = $row['ip']; $ticket->language = $row['language']; - $ticket->statusId = $row['status']; - $ticket->openedBy = $row['openedby']; - $ticket->firstReplyByUserId = $row['firstreplyby']; - $ticket->closedByUserId = $row['closedby']; - $ticket->numberOfReplies = $row['replies']; - $ticket->numberOfStaffReplies = $row['staffreplies']; - $ticket->ownerId = $row['owner']; + $ticket->statusId = intval($row['status']); + $ticket->openedBy = intval($row['openedby']); + $ticket->firstReplyByUserId = $row['firstreplyby'] === null ? null : intval($row['firstreplyby']); + $ticket->closedByUserId = $row['closedby'] === null ? null : intval($row['closedby']); + $ticket->numberOfReplies = intval($row['replies']); + $ticket->numberOfStaffReplies = intval($row['staffreplies']); + $ticket->ownerId = intval($row['owner']); $ticket->timeWorked = $row['time_worked']; - $ticket->lastReplyBy = $row['lastreplier']; - $ticket->lastReplier = $row['replierid']; + $ticket->lastReplyBy = intval($row['lastreplier']); + $ticket->lastReplier = $row['replierid'] === null ? null : intval($row['replierid']); $ticket->archived = intval($row['archive']) === 1; $ticket->locked = intval($row['locked']) === 1; @@ -68,15 +72,23 @@ class Ticket { $ticket->linkedTicketIds[] = $linkedTicketsRow['id']; } - $ticket->location = array(); - $ticket->location[0] = $row['latitude']; - $ticket->location[1] = $row['longitude']; + if ($row['latitude'] !== '' && $row['longitude'] !== '') { + $ticket->location = array(); + $ticket->location[0] = $row['latitude']; + $ticket->location[1] = $row['longitude']; + } $ticket->usesHtml = intval($row['html']) === 1; - $ticket->userAgent = $row['user_agent']; - $ticket->screenResolution = array(); - $ticket->screenResolution[0] = $row['screen_resolution_width']; - $ticket->screenResolution[1] = $row['screen_resolution_height']; + + if ($row['user_agent'] !== null && trim($row['user_agent']) !== '') { + $ticket->userAgent = $row['user_agent']; + } + + if ($row['screen_resolution_height'] !== null && $row['screen_resolution_width'] !== null){ + $ticket->screenResolution = array(); + $ticket->screenResolution[0] = $row['screen_resolution_width']; + $ticket->screenResolution[1] = $row['screen_resolution_height']; + } $ticket->dueDate = $row['due_date']; $ticket->dueDateOverdueEmailSent = $row['overdue_email_sent'] !== null && intval($row['overdue_email_sent']) === 1; diff --git a/api/businesslogic/ticket/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php similarity index 98% rename from api/businesslogic/ticket/TicketCreator.php rename to api/BusinessLogic/Tickets/TicketCreator.php index f34ae95f..74d7786d 100644 --- a/api/businesslogic/ticket/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -22,7 +22,7 @@ class TicketCreator { throw new ValidationException($validationModel); } - // Create the ticket + // Create the Tickets } /** @@ -30,7 +30,7 @@ class TicketCreator { * @param $staff bool * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings - * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket + * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid Tickets */ function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings) { $TICKET_PRIORITY_CRITICAL = 0; diff --git a/api/businesslogic/ticket/TicketRetriever.php b/api/BusinessLogic/Tickets/TicketRetriever.php similarity index 100% rename from api/businesslogic/ticket/TicketRetriever.php rename to api/BusinessLogic/Tickets/TicketRetriever.php diff --git a/api/businesslogic/ticket/TicketValidators.php b/api/BusinessLogic/Tickets/TicketValidators.php similarity index 100% rename from api/businesslogic/ticket/TicketValidators.php rename to api/BusinessLogic/Tickets/TicketValidators.php diff --git a/api/controllers/CategoryController.php b/api/Controllers/Categories/CategoryController.php similarity index 100% rename from api/controllers/CategoryController.php rename to api/Controllers/Categories/CategoryController.php diff --git a/api/controllers/TicketController.php b/api/Controllers/Tickets/TicketController.php similarity index 100% rename from api/controllers/TicketController.php rename to api/Controllers/Tickets/TicketController.php diff --git a/api/core/SQLException.php b/api/Core/Exceptions/SQLException.php similarity index 72% rename from api/core/SQLException.php rename to api/Core/Exceptions/SQLException.php index e9530a5b..db3f412b 100644 --- a/api/core/SQLException.php +++ b/api/Core/Exceptions/SQLException.php @@ -13,6 +13,6 @@ class SQLException extends Exception { function __construct($failingQuery) { $this->failingQuery = $failingQuery; - parent::__construct('A SQL exception occurred. Check the logs for more information.'); + parent::__construct('A SQL Exceptions occurred. Check the logs for more information.'); } } \ No newline at end of file diff --git a/api/dao/category/CategoryGateway.php b/api/DataAccess/Categories/CategoryGateway.php similarity index 94% rename from api/dao/category/CategoryGateway.php rename to api/DataAccess/Categories/CategoryGateway.php index a2e02174..d12a7e92 100644 --- a/api/dao/category/CategoryGateway.php +++ b/api/DataAccess/Categories/CategoryGateway.php @@ -1,8 +1,9 @@ " . intval($user['id']) . "))"; @@ -34,7 +34,7 @@ function build_results($response) { $results = array(); while ($row = hesk_dbFetchAssoc($response)) { $row['id'] = intval($row['id']); - $row['category'] = intval($row['category']); + $row['Categories'] = intval($row['Categories']); $row['priority'] = intval($row['priority']); $row['status'] = intval($row['status']); $row['replierid'] = intval($row['replierid']); diff --git a/api/dao/ticket_template_dao.php b/api/DataAccess/ticket_template_dao.php similarity index 100% rename from api/dao/ticket_template_dao.php rename to api/DataAccess/ticket_template_dao.php diff --git a/api/dao/user_dao.php b/api/DataAccess/user_dao.php similarity index 100% rename from api/dao/user_dao.php rename to api/DataAccess/user_dao.php diff --git a/api/autoload.php b/api/autoload.php index d468c912..c00cea7b 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -1,55 +1,15 @@ failingQuery, $exception->getTraceAsString())); + print_error("Fought an uncaught Exceptions", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); } else { - print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + print_error("Fought an uncaught Exceptions", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); } } @@ -61,8 +63,8 @@ function exceptionHandler($exception) { } /** - * @param $exception Exception thrown exception - * @param $class string The name of the expected exception type + * @param $exception Exception thrown Exceptions + * @param $class string The name of the expected Exceptions type * @return bool */ function exceptionIsOfType($exception, $class) { From a702d157cd145ee07850ea080ad9bf74e7beea7e Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 2 Feb 2017 20:21:25 -0500 Subject: [PATCH 024/192] Delete unused DAOs, fix some replace errors --- api/BusinessLogic/Tickets/TicketCreator.php | 4 +- api/Core/Exceptions/SQLException.php | 2 +- api/DataAccess/canned_dao.php | 28 ---------- api/DataAccess/status_dao.php | 52 ------------------ api/DataAccess/ticket_dao.php | 59 --------------------- api/DataAccess/ticket_template_dao.php | 26 --------- api/DataAccess/user_dao.php | 56 ------------------- api/index.php | 10 ++-- 8 files changed, 7 insertions(+), 230 deletions(-) delete mode 100644 api/DataAccess/canned_dao.php delete mode 100644 api/DataAccess/status_dao.php delete mode 100644 api/DataAccess/ticket_dao.php delete mode 100644 api/DataAccess/ticket_template_dao.php delete mode 100644 api/DataAccess/user_dao.php diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 74d7786d..f34ae95f 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -22,7 +22,7 @@ class TicketCreator { throw new ValidationException($validationModel); } - // Create the Tickets + // Create the ticket } /** @@ -30,7 +30,7 @@ class TicketCreator { * @param $staff bool * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings - * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid Tickets + * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket */ function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings) { $TICKET_PRIORITY_CRITICAL = 0; diff --git a/api/Core/Exceptions/SQLException.php b/api/Core/Exceptions/SQLException.php index db3f412b..e9530a5b 100644 --- a/api/Core/Exceptions/SQLException.php +++ b/api/Core/Exceptions/SQLException.php @@ -13,6 +13,6 @@ class SQLException extends Exception { function __construct($failingQuery) { $this->failingQuery = $failingQuery; - parent::__construct('A SQL Exceptions occurred. Check the logs for more information.'); + parent::__construct('A SQL exception occurred. Check the logs for more information.'); } } \ No newline at end of file diff --git a/api/DataAccess/canned_dao.php b/api/DataAccess/canned_dao.php deleted file mode 100644 index e4700491..00000000 --- a/api/DataAccess/canned_dao.php +++ /dev/null @@ -1,28 +0,0 @@ - $value) { - if ($key != 'id') { - $lowercase_key = lcfirst($key); - $row[$lowercase_key] = $row[$key]; - unset($row[$key]); - } - if ($key == 'id' || $lowercase_key == 'closable' - || $lowercase_key == 'key' || $lowercase_key == 'sort' - || $lowercase_key == 'textColor') { - continue; - } - $row[$lowercase_key] = $row[$lowercase_key] == true; - } - - $language_sql = "SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "text_to_status_xref` " - . "WHERE `status_id` = ".intval($row['id']); - - $language_rs = hesk_dbQuery($language_sql); - if (hesk_dbNumRows($language_rs) > 0) { - $row['key'] = NULL; - $row['keys'] = array(); - } - while ($language_row = hesk_dbFetchAssoc($language_rs)) { - unset($language_row['id']); - unset($language_row['status_id']); - $row['keys'][] = $language_row; - } - - $results[] = $row; - } - - return $id == NULL ? $results : $results[0]; -} \ No newline at end of file diff --git a/api/DataAccess/ticket_dao.php b/api/DataAccess/ticket_dao.php deleted file mode 100644 index f7805323..00000000 --- a/api/DataAccess/ticket_dao.php +++ /dev/null @@ -1,59 +0,0 @@ - " . intval($user['id']) . "))"; - } - - $response = hesk_dbQuery($sql); - - if (hesk_dbNumRows($response) == 0) { - return NULL; - } - - $results = build_results($response); - - return $id == NULL ? $results : $results[0]; -} - -function build_results($response) { - $results = array(); - while ($row = hesk_dbFetchAssoc($response)) { - $row['id'] = intval($row['id']); - $row['Categories'] = intval($row['Categories']); - $row['priority'] = intval($row['priority']); - $row['status'] = intval($row['status']); - $row['replierid'] = intval($row['replierid']); - $row['archive'] = $row['archive'] == true; - $row['locked'] = $row['locked'] == true; - $row['html'] = $row['html'] == true; - $row['screen_resolution_height'] = convert_to_int($row['screen_resolution_height']); - $row['screen_resolution_width'] = convert_to_int($row['screen_resolution_width']); - $row['owner'] = convert_to_int($row['owner']); - $row['parent'] = convert_to_int($row['parent']); - $row['overdue_email_sent'] = $row['overdue_email_sent'] == true; - - - $results[] = $row; - } - - return $results; -} - -function convert_to_int($item) { - return $item != NULL ? intval($item) : NULL; -} \ No newline at end of file diff --git a/api/DataAccess/ticket_template_dao.php b/api/DataAccess/ticket_template_dao.php deleted file mode 100644 index 118ba218..00000000 --- a/api/DataAccess/ticket_template_dao.php +++ /dev/null @@ -1,26 +0,0 @@ -failingQuery, $exception->getTraceAsString())); + print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); } else { - print_error("Fought an uncaught Exceptions", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); } } @@ -63,8 +61,8 @@ function exceptionHandler($exception) { } /** - * @param $exception Exception thrown Exceptions - * @param $class string The name of the expected Exceptions type + * @param $exception Exception thrown exception + * @param $class string The name of the expected exception type * @return bool */ function exceptionIsOfType($exception, $class) { From 2b792cff723b18e8e0a3eff4c2528ff6c00e7c72 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 2 Feb 2017 21:22:04 -0500 Subject: [PATCH 025/192] Fix exception handler for E_NOTICE --- api/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/index.php b/api/index.php index 44c036ce..27c7eeb3 100644 --- a/api/index.php +++ b/api/index.php @@ -34,7 +34,7 @@ function buildUserContext($xAuthToken) { } function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { - throw new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage)); + exceptionHandler(new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage))); } /** From bbdb03d1035b79b06d6dac5679f2a6ada16865ce Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 2 Feb 2017 22:30:41 -0500 Subject: [PATCH 026/192] Testing is working! --- .gitignore | 4 + api/Tests/AutoLoader.php | 38 + .../BusinessLogic/Tests/BanRetrieverTest.php | 34 + api/Tests/bootstrap.php | 2 + api/Tests/phpunit.xml | 2 + api/composer.json | 15 + api/composer.lock | 1326 +++++++++++++++++ 7 files changed, 1421 insertions(+) create mode 100644 api/Tests/AutoLoader.php create mode 100644 api/Tests/BusinessLogic/Tests/BanRetrieverTest.php create mode 100644 api/Tests/bootstrap.php create mode 100644 api/Tests/phpunit.xml create mode 100644 api/composer.json create mode 100644 api/composer.lock diff --git a/.gitignore b/.gitignore index 1aafcd90..30dbedef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Mods for HESK-specific files +api/vendor + +# HESK Files admin/admin_suggest_articles.php admin/archive.php admin/custom_statuses.php diff --git a/api/Tests/AutoLoader.php b/api/Tests/AutoLoader.php new file mode 100644 index 00000000..6a72f7ec --- /dev/null +++ b/api/Tests/AutoLoader.php @@ -0,0 +1,38 @@ +isDir() && !$file->isLink() && !$file->isDot()) { + // recurse into directories other than a few special ones + self::registerDirectory($file->getPathname()); + } elseif (substr($file->getFilename(), -4) === '.php') { + // save the class name / path of a .php file found + $className = substr($file->getFilename(), 0, -4); + AutoLoader::registerClass($className, $file->getPathname()); + } + } + } + + public static function registerClass($className, $fileName) { + AutoLoader::$classNames[$className] = $fileName; + } + + public static function loadClass($className) { + if (isset(AutoLoader::$classNames[$className])) { + require_once(AutoLoader::$classNames[$className]); + } + } + +} + +spl_autoload_register(array('AutoLoader', 'loadClass')); \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tests/BanRetrieverTest.php b/api/Tests/BusinessLogic/Tests/BanRetrieverTest.php new file mode 100644 index 00000000..aea5a51b --- /dev/null +++ b/api/Tests/BusinessLogic/Tests/BanRetrieverTest.php @@ -0,0 +1,34 @@ +createMock(BanGateway::class); + $banRetriever = new BanRetriever($banGateway); + $bannedEmail = new BannedEmail(); + $bannedEmail->email = 'my@email.address'; + $banGateway->method('getEmailBans') + ->willReturn([$bannedEmail]); + + //-- Act + $result = $banRetriever->isEmailBanned('my@email.address', null); + + //-- Assert + $this->assertThat($result, $this->isTrue()); + } +} diff --git a/api/Tests/bootstrap.php b/api/Tests/bootstrap.php new file mode 100644 index 00000000..2e555474 --- /dev/null +++ b/api/Tests/bootstrap.php @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/api/composer.json b/api/composer.json new file mode 100644 index 00000000..280cb3a4 --- /dev/null +++ b/api/composer.json @@ -0,0 +1,15 @@ +{ + "name": "mike-koch/Mods-for-HESK", + "description": "New UI and features for HESK, a free helpdesk solution", + "minimum-stability": "dev", + "license": "MIT", + "authors": [ + { + "name": "Mike Koch", + "email": "mkoch227@gmail.com" + } + ], + "require": { + "phpunit/phpunit": "5.7.9" + } +} diff --git a/api/composer.lock b/api/composer.lock new file mode 100644 index 00000000..4bf817a6 --- /dev/null +++ b/api/composer.lock @@ -0,0 +1,1326 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "63ae3f15414ec2f99c94a87db755a1af", + "packages": [ + { + "name": "doctrine/instantiator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "68099b02b60bbf3b088ff5cb67bf506770ef9cac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/68099b02b60bbf3b088ff5cb67bf506770ef9cac", + "reference": "68099b02b60bbf3b088ff5cb67bf506770ef9cac", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2017-01-23 09:23:06" + }, + { + "name": "myclabs/deep-copy", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-01-26T22:05:40+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-09-30T07:12:33+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-11-25T06:54:22+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0|^2.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-11-21 14:58:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7a2bfe73aa381a76cb6d13599ae37bf74a12a02f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7a2bfe73aa381a76cb6d13599ae37bf74a12a02f", + "reference": "7a2bfe73aa381a76cb6d13599ae37bf74a12a02f", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "~1.0|~2.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.4.0", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-01-24 16:35:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2016-10-03 07:40:28" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12T18:03:57+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2016-11-15 14:06:22" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "69f832b87c731d5cacad7f91948778fe98335fdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69f832b87c731d5cacad7f91948778fe98335fdd", + "reference": "69f832b87c731d5cacad7f91948778fe98335fdd", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.0 || ^2.0", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-01-28T06:14:33+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2016-12-08 20:27:08" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "11606652af09e847cdbbbc3ca17df26b1173a454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/11606652af09e847cdbbbc3ca17df26b1173a454", + "reference": "11606652af09e847cdbbbc3ca17df26b1173a454", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2016-12-06 20:05:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29 09:50:25" + }, + { + "name": "sebastian/diff", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "d0814318784b7756fb932116acd19ee3b0cbe67a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/d0814318784b7756fb932116acd19ee3b0cbe67a", + "reference": "d0814318784b7756fb932116acd19ee3b0cbe67a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2016-10-03 07:45:03" + }, + { + "name": "sebastian/environment", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26 07:53:53" + }, + { + "name": "sebastian/exporter", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19 08:54:04" + }, + { + "name": "sebastian/global-state", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "ab3e5ce501d9d45288b53f885a54c87e0cdc7164" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ab3e5ce501d9d45288b53f885a54c87e0cdc7164", + "reference": "ab3e5ce501d9d45288b53f885a54c87e0cdc7164", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2016-12-12 08:07:45" + }, + { + "name": "sebastian/object-enumerator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2016-11-19 07:35:10" + }, + { + "name": "sebastian/recursion-context", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19 07:33:16" + }, + { + "name": "sebastian/resource-operations", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "fadc83f7c41fb2924e542635fea47ae546816ece" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/fadc83f7c41fb2924e542635fea47ae546816ece", + "reference": "fadc83f7c41fb2924e542635fea47ae546816ece", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2016-10-03 07:43:09" + }, + { + "name": "sebastian/version", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03 07:35:21" + }, + { + "name": "symfony/yaml", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "7928849b226f065dae93ec0e8be3b829f73ba67b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/7928849b226f065dae93ec0e8be3b829f73ba67b", + "reference": "7928849b226f065dae93ec0e8be3b829f73ba67b", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-01-21 17:10:26" + }, + { + "name": "webmozart/assert", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "4a8bf11547e139e77b651365113fc12850c43d9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/4a8bf11547e139e77b651365113fc12850c43d9a", + "reference": "4a8bf11547e139e77b651365113fc12850c43d9a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23 20:04:41" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} From 176b7862795be697cfe06c106a5dc41fcec87ff6 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 3 Feb 2017 22:08:44 -0500 Subject: [PATCH 027/192] Move ban test, work on TicketValidators test --- api/BusinessLogic/Tickets/TicketCreator.php | 16 ++-- .../Tickets/TicketValidators.php | 22 ++++- .../Security/BanRetrieverTest.php | 92 +++++++++++++++++++ .../BusinessLogic/Tests/BanRetrieverTest.php | 34 ------- .../Tickets/TicketValidatorsTest.php | 75 +++++++++++++++ 5 files changed, 197 insertions(+), 42 deletions(-) create mode 100644 api/Tests/BusinessLogic/Security/BanRetrieverTest.php delete mode 100644 api/Tests/BusinessLogic/Tests/BanRetrieverTest.php create mode 100644 api/Tests/BusinessLogic/Tickets/TicketValidatorsTest.php diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index f34ae95f..a534e466 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -4,18 +4,20 @@ namespace BusinessLogic\Tickets; use BusinessLogic\Exceptions\ValidationException; -use BusinessLogic\Validation\ValidationModel; +use BusinessLogic\Security\BanRetriever; +use BusinessLogic\ValidationModel; use BusinessObjects\CreateTicketByCustomerModel; class TicketCreator { /** * @param $ticketRequest CreateTicketByCustomerModel + * @param $banRetriever BanRetriever * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings * @throws ValidationException When a required field in $ticket_request is missing */ - static function createTicketByCustomer($ticketRequest, $heskSettings, $modsForHeskSettings) { - $validationModel = validate($ticketRequest, false, $heskSettings, $modsForHeskSettings); + static function createTicketByCustomer($ticketRequest, $banRetriever, $heskSettings, $modsForHeskSettings) { + $validationModel = validate($ticketRequest, false, $banRetriever, $heskSettings, $modsForHeskSettings); if (count($validationModel->errorKeys) > 0) { // Validation failed @@ -28,11 +30,12 @@ class TicketCreator { /** * @param $ticketRequest CreateTicketByCustomerModel * @param $staff bool + * @param $banRetriever BanRetriever * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket */ - function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings) { + function validate($ticketRequest, $staff, $banRetriever, $heskSettings, $modsForHeskSettings) { $TICKET_PRIORITY_CRITICAL = 0; $validationModel = new ValidationModel(); @@ -98,8 +101,9 @@ class TicketCreator { } } - // TODO Check bans (email only; don't check IP on REST requests as they'll most likely be sent via servers) - // TODO submit_ticket.php:320-322 + if ($banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) { + $validationModel->errorKeys[] = 'EMAIL_BANNED'; + } // TODO Check if we're at the max number of tickets // TODO submit_ticket.php:325-334 diff --git a/api/BusinessLogic/Tickets/TicketValidators.php b/api/BusinessLogic/Tickets/TicketValidators.php index b6539ef9..15a53bb3 100644 --- a/api/BusinessLogic/Tickets/TicketValidators.php +++ b/api/BusinessLogic/Tickets/TicketValidators.php @@ -2,11 +2,29 @@ namespace BusinessLogic\Tickets; +use DataAccess\Tickets\TicketGateway; + class TicketValidators { /** - * @param $customerEmail string + * @var $ticketGateway TicketGateway + */ + private $ticketGateway; + + function __construct($ticketGateway) { + $this->ticketGateway = $ticketGateway; + } + + + /** + * @param $customerEmail string The email address + * @param $heskSettings array HESK Settings + * @return bool true if the user is maxed out on open tickets, false otherwise */ - function isCustomerAtMaxTickets($customerEmail) { + function isCustomerAtMaxTickets($customerEmail, $heskSettings) { + if ($heskSettings['max_open'] === 0) { + return false; + } + return count($this->ticketGateway->getTicketsByEmail($customerEmail, $heskSettings)) >= $heskSettings['max_open']; } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Security/BanRetrieverTest.php b/api/Tests/BusinessLogic/Security/BanRetrieverTest.php new file mode 100644 index 00000000..fd1bf0a7 --- /dev/null +++ b/api/Tests/BusinessLogic/Security/BanRetrieverTest.php @@ -0,0 +1,92 @@ +banGateway = $this->createMock(BanGateway::class); + $this->banRetriever = new BanRetriever($this->banGateway); + } + + function testItReturnsTrueWhenTheEmailIsBanned() { + //-- Arrange + $bannedEmail = new BannedEmail(); + $bannedEmail->email = 'my@email.address'; + $this->banGateway->method('getEmailBans') + ->willReturn([$bannedEmail]); + + //-- Act + $result = $this->banRetriever->isEmailBanned('my@email.address', null); + + //-- Assert + $this->assertThat($result, $this->isTrue()); + } + + function testItReturnsFalseWhenTheEmailIsNotBanned() { + //-- Arrange + $bannedEmail = new BannedEmail(); + $bannedEmail->email = 'my@other.address'; + $this->banGateway->method('getEmailBans') + ->willReturn([$bannedEmail]); + + //-- Act + $result = $this->banRetriever->isEmailBanned('my@email.address', null); + + //-- Assert + $this->assertThat($result, $this->isFalse()); + } + + function testItReturnsTrueWhenTheIpIsBanned() { + //-- Arrange + $bannedIp = new BannedIp(); + $bannedIp->ipFrom = ip2long('1.0.0.0'); + $bannedIp->ipTo = ip2long('1.0.0.5'); + $this->banGateway->method('getIpBans') + ->willReturn([$bannedIp]); + + //-- Act + $result = $this->banRetriever->isIpAddressBanned(ip2long('1.0.0.3'), null); + + //-- Assert + $this->assertThat($result, $this->isTrue()); + } + + function testItReturnsFalseWhenTheIpIsNotBanned() { + //-- Arrange + $bannedIp = new BannedIp(); + $bannedIp->ipFrom = ip2long('1.0.0.0'); + $bannedIp->ipTo = ip2long('1.0.0.5'); + $this->banGateway->method('getIpBans') + ->willReturn([$bannedIp]); + + //-- Act + $result = $this->banRetriever->isIpAddressBanned(ip2long('2.0.0.3'), null); + + //-- Assert + $this->assertThat($result, $this->isFalse()); + } +} diff --git a/api/Tests/BusinessLogic/Tests/BanRetrieverTest.php b/api/Tests/BusinessLogic/Tests/BanRetrieverTest.php deleted file mode 100644 index aea5a51b..00000000 --- a/api/Tests/BusinessLogic/Tests/BanRetrieverTest.php +++ /dev/null @@ -1,34 +0,0 @@ -createMock(BanGateway::class); - $banRetriever = new BanRetriever($banGateway); - $bannedEmail = new BannedEmail(); - $bannedEmail->email = 'my@email.address'; - $banGateway->method('getEmailBans') - ->willReturn([$bannedEmail]); - - //-- Act - $result = $banRetriever->isEmailBanned('my@email.address', null); - - //-- Assert - $this->assertThat($result, $this->isTrue()); - } -} diff --git a/api/Tests/BusinessLogic/Tickets/TicketValidatorsTest.php b/api/Tests/BusinessLogic/Tickets/TicketValidatorsTest.php new file mode 100644 index 00000000..6fceb76e --- /dev/null +++ b/api/Tests/BusinessLogic/Tickets/TicketValidatorsTest.php @@ -0,0 +1,75 @@ +ticketGateway = $this->createMock(TicketGateway::class); + $this->ticketValidator = new TicketValidators($this->ticketGateway); + } + + function testItReturnsTrueWhenTheUserIsMaxedOutOnOpenTickets() { + //-- Arrange + $tickets = [new Ticket(), new Ticket(), new Ticket()]; + $this->ticketGateway->method('getTicketsByEmail') + ->with('my@email.com') + ->willReturn($tickets); + $heskSettings = array( + 'max_open' => 3 + ); + + //-- Act + $result = $this->ticketValidator->isCustomerAtMaxTickets('my@email.com', $heskSettings); + + //-- Assert + $this->assertThat($result, $this->isTrue(), str_replace('test','',__FUNCTION__)); + } + + function testItReturnsFalseWhenTheUserIsNotMaxedOutOnOpenTickets() { + //-- Arrange + $tickets = [new Ticket(), new Ticket(), new Ticket()]; + $this->ticketGateway->method('getTicketsByEmail') + ->with('my@email.com') + ->willReturn($tickets); + $heskSettings = array( + 'max_open' => 10 + ); + + //-- Act + $result = $this->ticketValidator->isCustomerAtMaxTickets('my@email.com', $heskSettings); + + //-- Assert + $this->assertThat($result, $this->isFalse(), str_replace('test','',__FUNCTION__)); + } + + function testItReturnsFalseWhenMaxOpenIsZero() { + //-- Arrange + $tickets = [new Ticket(), new Ticket(), new Ticket()]; + $this->ticketGateway->method('getTicketsByEmail') + ->with('my@email.com') + ->willReturn($tickets); + $heskSettings = array( + 'max_open' => 0 + ); + + //-- Act + $result = $this->ticketValidator->isCustomerAtMaxTickets('my@email.com', $heskSettings); + + //-- Assert + $this->assertThat($result, $this->isFalse(), str_replace('test','',__FUNCTION__)); + } +} From 98cbd6e4dd68b280adc4c9c779fdfd46dd6af03b Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 5 Feb 2017 22:12:18 -0500 Subject: [PATCH 028/192] Working on TicketCreator tests --- .../Tickets/CreateTicketByCustomerModel.php | 2 +- api/BusinessLogic/Tickets/TicketCreator.php | 20 +++-- .../Tickets/TicketCreatorTest.php | 78 +++++++++++++++++++ api/bootstrap.php | 3 + 4 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php diff --git a/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php b/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php index 19fc540e..aa6ab0ff 100644 --- a/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php +++ b/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php @@ -1,6 +1,6 @@ banRetriever = $banRetriever; + } + /** * @param $ticketRequest CreateTicketByCustomerModel - * @param $banRetriever BanRetriever * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings * @throws ValidationException When a required field in $ticket_request is missing */ - static function createTicketByCustomer($ticketRequest, $banRetriever, $heskSettings, $modsForHeskSettings) { - $validationModel = validate($ticketRequest, false, $banRetriever, $heskSettings, $modsForHeskSettings); + function createTicketByCustomer($ticketRequest, $heskSettings, $modsForHeskSettings) { + $validationModel = $this->validate($ticketRequest, false, $heskSettings, $modsForHeskSettings); if (count($validationModel->errorKeys) > 0) { // Validation failed + $validationModel->valid = false; throw new ValidationException($validationModel); } @@ -30,12 +35,11 @@ class TicketCreator { /** * @param $ticketRequest CreateTicketByCustomerModel * @param $staff bool - * @param $banRetriever BanRetriever * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket */ - function validate($ticketRequest, $staff, $banRetriever, $heskSettings, $modsForHeskSettings) { + function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings) { $TICKET_PRIORITY_CRITICAL = 0; $validationModel = new ValidationModel(); @@ -44,7 +48,7 @@ class TicketCreator { $validationModel->errorKeys[] = 'NO_NAME'; } - if (hesk_validateEmail($ticketRequest->email, $heskSettings['multi_eml'], false)) { + /*if (hesk_validateEmail($ticketRequest->email, $heskSettings['multi_eml'], false)) { $validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL'; } @@ -103,7 +107,7 @@ class TicketCreator { if ($banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) { $validationModel->errorKeys[] = 'EMAIL_BANNED'; - } + }*/ // TODO Check if we're at the max number of tickets // TODO submit_ticket.php:325-334 diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php new file mode 100644 index 00000000..91cbb847 --- /dev/null +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php @@ -0,0 +1,78 @@ +banRetriever = $this->createMock(BanRetriever::class); + $this->ticketCreator = new TicketCreator($this->banRetriever); + + $this->ticketRequest = new CreateTicketByCustomerModel(); + $this->ticketRequest->name = 'Name'; + } + + function testItAddsTheProperValidationErrorWhenNameIsNull() { + //-- Arrange + $this->ticketRequest->name = null; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['NO_NAME'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenNameIsBlank() { + //-- Arrange + $this->ticketRequest->name = ''; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['NO_NAME'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } +} diff --git a/api/bootstrap.php b/api/bootstrap.php index 53251c8f..755dc9c5 100644 --- a/api/bootstrap.php +++ b/api/bootstrap.php @@ -1,5 +1,8 @@ Date: Mon, 6 Feb 2017 22:13:16 -0500 Subject: [PATCH 029/192] More work on ticket creator tests --- api/BusinessLogic/Categories/Category.php | 2 +- api/BusinessLogic/Tickets/TicketCreator.php | 31 ++- api/BusinessLogic/Validators.php | 129 ++++++++++++ api/DataAccess/Categories/CategoryGateway.php | 2 +- .../Tickets/TicketCreatorTest.php | 189 +++++++++++++++++- api/businesslogic/email_validators.php | 119 ----------- 6 files changed, 339 insertions(+), 133 deletions(-) create mode 100644 api/BusinessLogic/Validators.php delete mode 100644 api/businesslogic/email_validators.php diff --git a/api/BusinessLogic/Categories/Category.php b/api/BusinessLogic/Categories/Category.php index 5b975b61..5711f33d 100644 --- a/api/BusinessLogic/Categories/Category.php +++ b/api/BusinessLogic/Categories/Category.php @@ -1,6 +1,6 @@ categoryRetriever = $categoryRetriever; $this->banRetriever = $banRetriever; } @@ -20,8 +30,8 @@ class TicketCreator { * @param $modsForHeskSettings array Mods for HESK settings * @throws ValidationException When a required field in $ticket_request is missing */ - function createTicketByCustomer($ticketRequest, $heskSettings, $modsForHeskSettings) { - $validationModel = $this->validate($ticketRequest, false, $heskSettings, $modsForHeskSettings); + function createTicketByCustomer($ticketRequest, $heskSettings, $modsForHeskSettings, $userContext) { + $validationModel = $this->validate($ticketRequest, false, $heskSettings, $modsForHeskSettings, $userContext); if (count($validationModel->errorKeys) > 0) { // Validation failed @@ -39,7 +49,7 @@ class TicketCreator { * @param $modsForHeskSettings array Mods for HESK settings * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket */ - function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings) { + function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings, $userContext) { $TICKET_PRIORITY_CRITICAL = 0; $validationModel = new ValidationModel(); @@ -48,17 +58,22 @@ class TicketCreator { $validationModel->errorKeys[] = 'NO_NAME'; } - /*if (hesk_validateEmail($ticketRequest->email, $heskSettings['multi_eml'], false)) { + if (!Validators::validateEmail($ticketRequest->email, $heskSettings['multi_eml'], false)) { $validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL'; } - if (intval($ticketRequest->category) === 0) { - $allCategories = null; + $categoryId = intval($ticketRequest->category); + if ($categoryId < 1) { $validationModel->errorKeys[] = 'NO_CATEGORY'; + } else { + $categoryExists = array_key_exists($categoryId, $this->categoryRetriever->getAllCategories($heskSettings, $userContext)); + if (!$categoryExists) { + $validationModel->errorKeys[] = 'CATEGORY_DOES_NOT_EXIST'; + } } // Don't allow critical priority tickets - if ($heskSettings['cust_urgency'] && intval($ticketRequest->priority) === $TICKET_PRIORITY_CRITICAL) { + /*if ($heskSettings['cust_urgency'] && intval($ticketRequest->priority) === $TICKET_PRIORITY_CRITICAL) { $validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN'; } diff --git a/api/BusinessLogic/Validators.php b/api/BusinessLogic/Validators.php new file mode 100644 index 00000000..7bfea07f --- /dev/null +++ b/api/BusinessLogic/Validators.php @@ -0,0 +1,129 @@ + $v) { + if (!self::isValidEmail($v)) { + unset($all[$k]); + } + } + + /* If at least one is found return the value */ + if (count($all)) { + if ($return_emails) { + return implode(',', $all); + } + + return true; + } elseif (!$return_emails) { + return false; + } + } else { + /* Make sure people don't try to enter multiple addresses */ + $address = str_replace(strstr($address, ','), '', $address); + $address = str_replace(strstr($address, ';'), '', $address); + $address = trim($address); + + /* Valid address? */ + if (self::isValidEmail($address)) { + if ($return_emails) { + return $address; + } + + return true; + } else { + return false; + } + } + + //-- We shouldn't get here + return false; + } // END hesk_validateEmail() + + /** + * @param $email + * @return bool + */ + static function isValidEmail($email) { + /* Check for header injection attempts */ + if (preg_match("/\r|\n|%0a|%0d/i", $email)) { + return false; + } + + /* Does it contain an @? */ + $atIndex = strrpos($email, "@"); + if ($atIndex === false) { + return false; + } + + /* Get local and domain parts */ + $domain = substr($email, $atIndex + 1); + $local = substr($email, 0, $atIndex); + $localLen = strlen($local); + $domainLen = strlen($domain); + + /* Check local part length */ + if ($localLen < 1 || $localLen > 64) { + return false; + } + + /* Check domain part length */ + if ($domainLen < 1 || $domainLen > 254) { + return false; + } + + /* Local part mustn't start or end with a dot */ + if ($local[0] == '.' || $local[$localLen - 1] == '.') { + return false; + } + + /* Local part mustn't have two consecutive dots*/ + if (strpos($local, '..') !== false) { + return false; + } + + /* Check domain part characters */ + if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) { + return false; + } + + /* Domain part mustn't have two consecutive dots */ + if (strpos($domain, '..') !== false) { + return false; + } + + /* Character not valid in local part unless local part is quoted */ + if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local))) /* " */ { + if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local))) /* " */ { + return false; + } + } + + /* All tests passed, email seems to be OK */ + return true; + } // END hesk_isValidEmail() +} \ No newline at end of file diff --git a/api/DataAccess/Categories/CategoryGateway.php b/api/DataAccess/Categories/CategoryGateway.php index d12a7e92..68f605e4 100644 --- a/api/DataAccess/Categories/CategoryGateway.php +++ b/api/DataAccess/Categories/CategoryGateway.php @@ -2,7 +2,7 @@ namespace DataAccess\Categories; -use BusinessObjects\Category; +use BusinessLogic\Categories\Category; use DataAccess\CommonDao; use Exception; diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php index 91cbb847..d337a23e 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php @@ -9,8 +9,11 @@ namespace BusinessLogic\Tickets; +use BusinessLogic\Categories\Category; +use BusinessLogic\Categories\CategoryRetriever; use BusinessLogic\Exceptions\ValidationException; use BusinessLogic\Security\BanRetriever; +use BusinessLogic\Security\UserContext; use PHPUnit\Framework\TestCase; class TicketCreatorTest extends TestCase { @@ -20,24 +23,49 @@ class TicketCreatorTest extends TestCase { private $ticketCreator; /** - * @var $banRetriever BanRetriever + * @var $banRetriever \PHPUnit_Framework_MockObject_MockObject */ private $banRetriever; + /** + * @var $categoryRetriever \PHPUnit_Framework_MockObject_MockObject + */ + private $categoryRetriever; + /** * @var $ticketRequest CreateTicketByCustomerModel */ private $ticketRequest; + /** + * @var $userContext UserContext + */ + private $userContext; + private $heskSettings = array(); private $modsForHeskSettings = array(); function setUp() { $this->banRetriever = $this->createMock(BanRetriever::class); - $this->ticketCreator = new TicketCreator($this->banRetriever); + $this->categoryRetriever = $this->createMock(CategoryRetriever::class); + $this->ticketCreator = new TicketCreator($this->categoryRetriever, $this->banRetriever); + $this->userContext = new UserContext(); $this->ticketRequest = new CreateTicketByCustomerModel(); $this->ticketRequest->name = 'Name'; + $this->ticketRequest->email = 'some@e.mail'; + $this->ticketRequest->category = 1; + $this->heskSettings = array( + 'multi_eml' => false + ); + + $category = new Category(); + $category->accessible = true; + $category->id = 1; + $categories = array(); + $categories[1] = $category; + $this->categoryRetriever->method('getAllCategories') + ->willReturn($categories); } function testItAddsTheProperValidationErrorWhenNameIsNull() { @@ -47,7 +75,10 @@ class TicketCreatorTest extends TestCase { //-- Act $exceptionThrown = false; try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings); + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); } catch (ValidationException $e) { //-- Assert (1/2) $exceptionThrown = true; @@ -65,7 +96,10 @@ class TicketCreatorTest extends TestCase { //-- Act $exceptionThrown = false; try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings); + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); } catch (ValidationException $e) { //-- Assert (1/2) $exceptionThrown = true; @@ -75,4 +109,151 @@ class TicketCreatorTest extends TestCase { //-- Assert (2/2) $this->assertThat($exceptionThrown, $this->equalTo(true)); } + + function testItAddsTheProperValidationErrorWhenEmailIsNull() { + //-- Arrange + $this->ticketRequest->email = null; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenEmailIsBlank() { + //-- Arrange + $this->ticketRequest->email = ''; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenEmailIsInvalid() { + //-- Arrange + $this->ticketRequest->email = 'something@'; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItSupportsMultipleEmails() { + //-- Arrange + $this->ticketRequest->email = 'something@email.com;another@valid.email'; + $this->heskSettings['multi_eml'] = true; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + var_dump($e->validationModel->errorKeys); + $this->fail('Should not have thrown a ValidationException! Validation error keys are above.'); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(false)); + } + + function testItAddsTheProperValidationErrorWhenCategoryIsNotANumber() { + //-- Arrange + $this->ticketRequest->category = 'something'; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['NO_CATEGORY'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenCategoryIsNegative() { + //-- Arrange + $this->ticketRequest->category = -5; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['NO_CATEGORY'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCategoryDoesNotExist() { + //-- Arrange + $this->ticketRequest->category = 10; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['CATEGORY_DOES_NOT_EXIST'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } } diff --git a/api/businesslogic/email_validators.php b/api/businesslogic/email_validators.php deleted file mode 100644 index 1b69d289..00000000 --- a/api/businesslogic/email_validators.php +++ /dev/null @@ -1,119 +0,0 @@ - $v) { - if (!hesk_isValidEmail($v)) { - unset($all[$k]); - } - } - - /* If at least one is found return the value */ - if (count($all)) { - if ($return_emails) { - return implode(',', $all); - } - - return true; - } elseif (!$return_emails) { - return false; - } - } else { - /* Make sure people don't try to enter multiple addresses */ - $address = str_replace(strstr($address, ','), '', $address); - $address = str_replace(strstr($address, ';'), '', $address); - $address = trim($address); - - /* Valid address? */ - if (hesk_isValidEmail($address)) { - if ($return_emails) { - return $address; - } - - return true; - } - } - - if ($return_emails) { - return null; - } - - return true; -} // END hesk_validateEmail() - -/** - * @param $email - * @return bool - */ -function hesk_isValidEmail($email) { - /* Check for header injection attempts */ - if (preg_match("/\r|\n|%0a|%0d/i", $email)) { - return false; - } - - /* Does it contain an @? */ - $atIndex = strrpos($email, "@"); - if ($atIndex === false) { - return false; - } - - /* Get local and domain parts */ - $domain = substr($email, $atIndex + 1); - $local = substr($email, 0, $atIndex); - $localLen = strlen($local); - $domainLen = strlen($domain); - - /* Check local part length */ - if ($localLen < 1 || $localLen > 64) { - return false; - } - - /* Check domain part length */ - if ($domainLen < 1 || $domainLen > 254) { - return false; - } - - /* Local part mustn't start or end with a dot */ - if ($local[0] == '.' || $local[$localLen - 1] == '.') { - return false; - } - - /* Local part mustn't have two consecutive dots*/ - if (strpos($local, '..') !== false) { - return false; - } - - /* Check domain part characters */ - if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) { - return false; - } - - /* Domain part mustn't have two consecutive dots */ - if (strpos($domain, '..') !== false) { - return false; - } - - /* Character not valid in local part unless local part is quoted */ - if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local))) /* " */ { - if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local))) /* " */ { - return false; - } - } - - /* All tests passed, email seems to be OK */ - return true; -} // END hesk_isValidEmail() \ No newline at end of file From e22d318b9244329d9f1497d1e83741e6bb94308d Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 7 Feb 2017 22:16:15 -0500 Subject: [PATCH 030/192] More tests for TicketCreator. Need to refactor some custom field logic --- api/BusinessLogic/Tickets/TicketCreator.php | 15 +- api/Core/Constants/Priority.php | 11 ++ .../Tickets/TicketCreatorTest.php | 152 +++++++++++++++++- 3 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 api/Core/Constants/Priority.php diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index de42e351..005db7e2 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -73,7 +73,7 @@ class TicketCreator { } // Don't allow critical priority tickets - /*if ($heskSettings['cust_urgency'] && intval($ticketRequest->priority) === $TICKET_PRIORITY_CRITICAL) { + if ($heskSettings['cust_urgency'] && intval($ticketRequest->priority) === $TICKET_PRIORITY_CRITICAL) { $validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN'; } @@ -88,13 +88,14 @@ class TicketCreator { } foreach ($heskSettings['custom_fields'] as $key => $value) { - if ($value['use'] == 1 && hesk_is_custom_field_in_category($key, intval($ticketRequest->category))) { - $custom_field_value = $ticketRequest->customFields[$key]; + $customFieldNumber = intval(str_replace('custom', '', $key)); + if ($value['use'] == 1 && hesk_is_custom_field_in_category($customFieldNumber, intval($ticketRequest->category))) { + $custom_field_value = $ticketRequest->customFields[$customFieldNumber]; if (empty($custom_field_value)) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::NO_VALUE'; + $validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::NO_VALUE"; continue; } - switch($value['type']) { + /*switch($value['type']) { case 'date': if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) { $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_DATE'; @@ -116,11 +117,11 @@ class TicketCreator { $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_OR_MISSING_EMAIL'; } break; - } + }*/ } } - if ($banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) { + /*if ($banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) { $validationModel->errorKeys[] = 'EMAIL_BANNED'; }*/ diff --git a/api/Core/Constants/Priority.php b/api/Core/Constants/Priority.php new file mode 100644 index 00000000..88f98cc3 --- /dev/null +++ b/api/Core/Constants/Priority.php @@ -0,0 +1,11 @@ +ticketRequest->name = 'Name'; $this->ticketRequest->email = 'some@e.mail'; $this->ticketRequest->category = 1; + $this->ticketRequest->priority = Priority::HIGH; + $this->ticketRequest->subject = 'Subject'; + $this->ticketRequest->message = 'Message'; + $this->ticketRequest->customFields = array(); $this->heskSettings = array( - 'multi_eml' => false + 'multi_eml' => false, + 'cust_urgency' => false, + 'require_subject' => 1, + 'require_message' => 1, + 'custom_fields' => array(), ); $category = new Category(); @@ -256,4 +265,145 @@ class TicketCreatorTest extends TestCase { //-- Assert (2/2) $this->assertThat($exceptionThrown, $this->equalTo(true)); } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithPriorityCritical() { + //-- Arrange + $this->ticketRequest->priority = Priority::CRITICAL; + $this->heskSettings['cust_urgency'] = true; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['CRITICAL_PRIORITY_FORBIDDEN'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullSubjectAndItIsRequired() { + //-- Arrange + $this->ticketRequest->subject = null; + $this->heskSettings['require_subject'] = 1; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['SUBJECT_REQUIRED'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankSubjectAndItIsRequired() { + //-- Arrange + $this->ticketRequest->subject = ''; + $this->heskSettings['require_subject'] = 1; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['SUBJECT_REQUIRED'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullMessageAndItIsRequired() { + //-- Arrange + $this->ticketRequest->message = null; + $this->heskSettings['require_message'] = 1; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['MESSAGE_REQUIRED'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankMessageAndItIsRequired() { + //-- Arrange + $this->ticketRequest->message = ''; + $this->heskSettings['require_message'] = 1; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['MESSAGE_REQUIRED'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullRequiredCustomField() { + $this->markTestIncomplete( + 'Not complete; need to refactor custom field in category' + ); + + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = 'text'; + $customField['use'] = 1; + $customField['category'] = array(); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = null; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::NO_VALUE'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } } From e44baa99c27a0eb33941480a02509f459794ae18 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 9 Feb 2017 12:57:28 -0500 Subject: [PATCH 031/192] Refactored custom field validator, re-enabled test --- .../Helpers.php | 0 .../CustomFields/CustomFieldValidator.php | 24 ++++++ api/BusinessLogic/Tickets/TicketCreator.php | 4 +- .../ValidationModel.php | 0 .../security/BanRetriever.php | 0 .../security/BannedEmail.php | 0 .../security/BannedIp.php | 0 .../security/UserContext.php | 0 .../security/UserContextBuilder.php | 0 .../security/UserContextNotifications.php | 0 .../security/UserContextPreferences.php | 0 api/{core => Core}/database.inc.php | 0 api/{core => Core}/database_mysqli.inc.php | 0 api/{core => Core}/json_error.php | 0 api/{core => Core}/output.php | 0 .../CustomFields/CustomFieldValidatorTest.php | 86 +++++++++++++++++++ .../Tickets/TicketCreatorTest.php | 4 - 17 files changed, 112 insertions(+), 6 deletions(-) rename api/{businesslogic => BusinessLogic}/Helpers.php (100%) create mode 100644 api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php rename api/{businesslogic => BusinessLogic}/ValidationModel.php (100%) rename api/{businesslogic => BusinessLogic}/security/BanRetriever.php (100%) rename api/{businesslogic => BusinessLogic}/security/BannedEmail.php (100%) rename api/{businesslogic => BusinessLogic}/security/BannedIp.php (100%) rename api/{businesslogic => BusinessLogic}/security/UserContext.php (100%) rename api/{businesslogic => BusinessLogic}/security/UserContextBuilder.php (100%) rename api/{businesslogic => BusinessLogic}/security/UserContextNotifications.php (100%) rename api/{businesslogic => BusinessLogic}/security/UserContextPreferences.php (100%) rename api/{core => Core}/database.inc.php (100%) rename api/{core => Core}/database_mysqli.inc.php (100%) rename api/{core => Core}/json_error.php (100%) rename api/{core => Core}/output.php (100%) create mode 100644 api/Tests/BusinessLogic/Tickets/CustomFields/CustomFieldValidatorTest.php diff --git a/api/businesslogic/Helpers.php b/api/BusinessLogic/Helpers.php similarity index 100% rename from api/businesslogic/Helpers.php rename to api/BusinessLogic/Helpers.php diff --git a/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php b/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php new file mode 100644 index 00000000..037d828e --- /dev/null +++ b/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php @@ -0,0 +1,24 @@ +priority) === $TICKET_PRIORITY_CRITICAL) { $validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN'; } @@ -89,7 +89,7 @@ class TicketCreator { foreach ($heskSettings['custom_fields'] as $key => $value) { $customFieldNumber = intval(str_replace('custom', '', $key)); - if ($value['use'] == 1 && hesk_is_custom_field_in_category($customFieldNumber, intval($ticketRequest->category))) { + if ($value['use'] == 1 && CustomFieldValidator::isCustomFieldInCategory($customFieldNumber, intval($ticketRequest->category), false, $heskSettings)) { $custom_field_value = $ticketRequest->customFields[$customFieldNumber]; if (empty($custom_field_value)) { $validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::NO_VALUE"; diff --git a/api/businesslogic/ValidationModel.php b/api/BusinessLogic/ValidationModel.php similarity index 100% rename from api/businesslogic/ValidationModel.php rename to api/BusinessLogic/ValidationModel.php diff --git a/api/businesslogic/security/BanRetriever.php b/api/BusinessLogic/security/BanRetriever.php similarity index 100% rename from api/businesslogic/security/BanRetriever.php rename to api/BusinessLogic/security/BanRetriever.php diff --git a/api/businesslogic/security/BannedEmail.php b/api/BusinessLogic/security/BannedEmail.php similarity index 100% rename from api/businesslogic/security/BannedEmail.php rename to api/BusinessLogic/security/BannedEmail.php diff --git a/api/businesslogic/security/BannedIp.php b/api/BusinessLogic/security/BannedIp.php similarity index 100% rename from api/businesslogic/security/BannedIp.php rename to api/BusinessLogic/security/BannedIp.php diff --git a/api/businesslogic/security/UserContext.php b/api/BusinessLogic/security/UserContext.php similarity index 100% rename from api/businesslogic/security/UserContext.php rename to api/BusinessLogic/security/UserContext.php diff --git a/api/businesslogic/security/UserContextBuilder.php b/api/BusinessLogic/security/UserContextBuilder.php similarity index 100% rename from api/businesslogic/security/UserContextBuilder.php rename to api/BusinessLogic/security/UserContextBuilder.php diff --git a/api/businesslogic/security/UserContextNotifications.php b/api/BusinessLogic/security/UserContextNotifications.php similarity index 100% rename from api/businesslogic/security/UserContextNotifications.php rename to api/BusinessLogic/security/UserContextNotifications.php diff --git a/api/businesslogic/security/UserContextPreferences.php b/api/BusinessLogic/security/UserContextPreferences.php similarity index 100% rename from api/businesslogic/security/UserContextPreferences.php rename to api/BusinessLogic/security/UserContextPreferences.php diff --git a/api/core/database.inc.php b/api/Core/database.inc.php similarity index 100% rename from api/core/database.inc.php rename to api/Core/database.inc.php diff --git a/api/core/database_mysqli.inc.php b/api/Core/database_mysqli.inc.php similarity index 100% rename from api/core/database_mysqli.inc.php rename to api/Core/database_mysqli.inc.php diff --git a/api/core/json_error.php b/api/Core/json_error.php similarity index 100% rename from api/core/json_error.php rename to api/Core/json_error.php diff --git a/api/core/output.php b/api/Core/output.php similarity index 100% rename from api/core/output.php rename to api/Core/output.php diff --git a/api/Tests/BusinessLogic/Tickets/CustomFields/CustomFieldValidatorTest.php b/api/Tests/BusinessLogic/Tickets/CustomFields/CustomFieldValidatorTest.php new file mode 100644 index 00000000..b9958be1 --- /dev/null +++ b/api/Tests/BusinessLogic/Tickets/CustomFields/CustomFieldValidatorTest.php @@ -0,0 +1,86 @@ + array( + 'custom1' => array( + 'use' => 1, + 'category' => array(1, 2) + ) + ) + ); + + //-- Act + $result = CustomFieldValidator::isCustomFieldInCategory(1, 1, false, $heskSettings); + + //-- Assert + $this->assertThat($result, $this->isTrue()); + } + + function testItReturnsTrueWhenTheCustomFieldIsForAllCategories() { + //-- Arrange + $heskSettings = array( + 'custom_fields' => array( + 'custom1' => array( + 'use' => 1, + 'category' => [] + ) + ) + ); + + //-- Act + $result = CustomFieldValidator::isCustomFieldInCategory(1, 1, false, $heskSettings); + + //-- Assert + $this->assertThat($result, $this->isTrue()); + } + + function testItReturnsFalseWhenTheCustomFieldIsNotInTheCategory() { + //-- Arrange + $heskSettings = array( + 'custom_fields' => array( + 'custom1' => array( + 'use' => 1, + 'category' => array(1, 2) + ) + ) + ); + + //-- Act + $result = CustomFieldValidator::isCustomFieldInCategory(1, 50, false, $heskSettings); + + //-- Assert + $this->assertThat($result, $this->isFalse()); + } + + function testItReturnsFalseWhenTheCustomFieldIsForStaffOnly() { + //-- Arrange + $heskSettings = array( + 'custom_fields' => array( + 'custom1' => array( + 'use' => 2, + 'category' => array(1, 2) + ) + ) + ); + + //-- Act + $result = CustomFieldValidator::isCustomFieldInCategory(1, 1, false, $heskSettings); + + //-- Assert + $this->assertThat($result, $this->isFalse()); + } +} diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php index c4b9e2b8..6ce6d5c1 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php @@ -377,10 +377,6 @@ class TicketCreatorTest extends TestCase { } function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullRequiredCustomField() { - $this->markTestIncomplete( - 'Not complete; need to refactor custom field in category' - ); - //-- Arrange $customField = array(); $customField['req'] = 1; From bc9bb698c56cb347f9e3ec6e4b03ea2bbb463652 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 10 Feb 2017 13:05:40 -0500 Subject: [PATCH 032/192] Add some more validation tests for dates --- api/BusinessLogic/Tickets/TicketCreator.php | 17 +-- api/Core/Constants/CustomField.php | 22 ++++ .../Tickets/TicketCreatorTest.php | 119 +++++++++++++++++- 3 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 api/Core/Constants/CustomField.php diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index ee510b69..df177387 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -9,6 +9,7 @@ use BusinessLogic\Security\BanRetriever; use BusinessLogic\Tickets\CustomFields\CustomFieldValidator; use BusinessLogic\ValidationModel; use BusinessLogic\Validators; +use Core\Constants\CustomField; class TicketCreator { /** @@ -95,10 +96,10 @@ class TicketCreator { $validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::NO_VALUE"; continue; } - /*switch($value['type']) { - case 'date': + switch($value['type']) { + case CustomField::DATE: if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_DATE'; + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::INVALID_DATE'; } else { // Actually validate based on range $date = strtotime($custom_field_value . ' t00:00:00'); @@ -106,18 +107,18 @@ class TicketCreator { $dmax = strlen($value['value']['dmax']) ? strtotime($value['value']['dmax'] . ' t00:00:00') : false; if ($dmin && $dmin > $date) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::DATE_BEFORE_MIN::MIN-' . $dmin . '::ENTERED-' . $date; + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_BEFORE_MIN::MIN:' . date('Y-m-d', $dmin) . '::ENTERED:' . date('Y-m-d', $date); } elseif ($dmax && $dmax < $date) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::DATE_AFTER_MAX::MAX-' . $dmax . '::ENTERED-' . $date; + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_AFTER_MAX::MAX:' . date('Y-m-d', $dmax) . '::ENTERED:' . date('Y-m-d', $date); } } break; - case 'email': + /*case 'email': if (!hesk_validateEmail($custom_field_value, $value['value']['multiple'], false)) { $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_OR_MISSING_EMAIL'; } - break; - }*/ + break;*/ + } } } diff --git a/api/Core/Constants/CustomField.php b/api/Core/Constants/CustomField.php new file mode 100644 index 00000000..566e1c76 --- /dev/null +++ b/api/Core/Constants/CustomField.php @@ -0,0 +1,22 @@ +heskSettings['custom_fields']['custom1'] = $customField; @@ -402,4 +403,120 @@ class TicketCreatorTest extends TestCase { //-- Assert (2/2) $this->assertThat($exceptionThrown, $this->equalTo(true)); } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankRequiredCustomField() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::TEXT; + $customField['use'] = 1; + $customField['category'] = array(); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = ''; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::NO_VALUE'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateCustomFieldThatIsInvalid() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::DATE; + $customField['use'] = 1; + $customField['category'] = array(); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = '2017-30-00'; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::INVALID_DATE'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateThatIsBeforeMinDate() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::DATE; + $customField['use'] = 1; + $customField['category'] = array(); + $customField['value'] = array( + 'dmin' => '2017-01-01', + 'dmax' => '' + ); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = '2016-12-31'; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::DATE_BEFORE_MIN::MIN:2017-01-01::ENTERED:2016-12-31'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateThatIsAfterMaxDate() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::DATE; + $customField['use'] = 1; + $customField['category'] = array(); + $customField['value'] = array( + 'dmin' => '', + 'dmax' => '2017-01-01' + ); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = '2017-01-02'; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::DATE_AFTER_MAX::MAX:2017-01-01::ENTERED:2017-01-02'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } } From d476f86c8c041fe08a80a002848c2ba25e4fdb6a Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 10 Feb 2017 22:09:46 -0500 Subject: [PATCH 033/192] Finished validation tests for create ticket --- api/BusinessLogic/Tickets/TicketCreator.php | 23 +++-- .../Tickets/TicketCreatorTest.php | 86 ++++++++++++++++++- 2 files changed, 101 insertions(+), 8 deletions(-) diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index df177387..0ace14ea 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -20,10 +20,15 @@ class TicketCreator { * @var $banRetriever BanRetriever */ private $banRetriever; + /** + * @var $ticketValidators TicketValidators + */ + private $ticketValidators; - function __construct($categoryRetriever, $banRetriever) { + function __construct($categoryRetriever, $banRetriever, $ticketValidators) { $this->categoryRetriever = $categoryRetriever; $this->banRetriever = $banRetriever; + $this->ticketValidators = $ticketValidators; } /** @@ -113,18 +118,22 @@ class TicketCreator { } } break; - /*case 'email': - if (!hesk_validateEmail($custom_field_value, $value['value']['multiple'], false)) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $key . '_INVALID::INVALID_OR_MISSING_EMAIL'; + case CustomField::EMAIL: + if (!Validators::validateEmail($custom_field_value, $value['value']['multiple'], false)) { + $validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::INVALID_EMAIL"; } - break;*/ + break; } } } - /*if ($banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) { + if ($this->banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) { $validationModel->errorKeys[] = 'EMAIL_BANNED'; - }*/ + } + + if ($this->ticketValidators->isCustomerAtMaxTickets($ticketRequest->email, $heskSettings)) { + $validationModel->errorKeys[] = 'EMAIL_AT_MAX_OPEN_TICKETS'; + } // TODO Check if we're at the max number of tickets // TODO submit_ticket.php:325-334 diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php index cd54c8a8..2098bee6 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php @@ -34,6 +34,11 @@ class TicketCreatorTest extends TestCase { */ private $categoryRetriever; + /** + * @var $ticketValidators \PHPUnit_Framework_MockObject_MockObject + */ + private $ticketValidators; + /** * @var $ticketRequest CreateTicketByCustomerModel */ @@ -50,7 +55,8 @@ class TicketCreatorTest extends TestCase { function setUp() { $this->banRetriever = $this->createMock(BanRetriever::class); $this->categoryRetriever = $this->createMock(CategoryRetriever::class); - $this->ticketCreator = new TicketCreator($this->categoryRetriever, $this->banRetriever); + $this->ticketValidators = $this->createMock(TicketValidators::class); + $this->ticketCreator = new TicketCreator($this->categoryRetriever, $this->banRetriever, $this->ticketValidators); $this->userContext = new UserContext(); $this->ticketRequest = new CreateTicketByCustomerModel(); @@ -519,4 +525,82 @@ class TicketCreatorTest extends TestCase { //-- Assert (2/2) $this->assertThat($exceptionThrown, $this->equalTo(true)); } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithEmailThatIsInvalid() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::EMAIL; + $customField['use'] = 1; + $customField['category'] = array(); + $customField['value'] = array( + 'multiple' => 0 + ); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = 'invalid@'; + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::INVALID_EMAIL'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithABannedEmail() { + //-- Arrange + $this->ticketRequest->email = 'some@banned.email'; + $this->banRetriever->method('isEmailBanned') + ->with($this->ticketRequest->email, $this->heskSettings) + ->willReturn(true); + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['EMAIL_BANNED'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWhenTheyAreMaxedOut() { + //-- Arrange + $this->ticketRequest->email = 'some@maxedout.email'; + $this->ticketValidators->method('isCustomerAtMaxTickets') + ->with($this->ticketRequest->email, $this->heskSettings) + ->willReturn(true); + + //-- Act + $exceptionThrown = false; + try { + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, + $this->heskSettings, + $this->modsForHeskSettings, + $this->userContext); + } catch (ValidationException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + $this->assertArraySubset(['EMAIL_AT_MAX_OPEN_TICKETS'], $e->validationModel->errorKeys); + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->equalTo(true)); + } } From 6f87dfc149990585a62ecdb49666e03d06ae5ab8 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 10 Feb 2017 22:10:39 -0500 Subject: [PATCH 034/192] Small fix on validation model --- api/BusinessLogic/ValidationModel.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/BusinessLogic/ValidationModel.php b/api/BusinessLogic/ValidationModel.php index 130e1f9d..740af78e 100644 --- a/api/BusinessLogic/ValidationModel.php +++ b/api/BusinessLogic/ValidationModel.php @@ -8,10 +8,13 @@ class ValidationModel { */ public $errorKeys; + /** + * @var bool + */ public $valid; function __construct() { - $errorKeys = []; - $valid = true; + $this->errorKeys = []; + $this->valid = true; } } \ No newline at end of file From 44239ed459185053924ffd962d63bab087350c3c Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 11 Feb 2017 16:24:08 -0500 Subject: [PATCH 035/192] Start working on TrackingIdGenerator --- api/BusinessLogic/Tickets/TrackingIdGenerator.php | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 api/BusinessLogic/Tickets/TrackingIdGenerator.php diff --git a/api/BusinessLogic/Tickets/TrackingIdGenerator.php b/api/BusinessLogic/Tickets/TrackingIdGenerator.php new file mode 100644 index 00000000..3a85f350 --- /dev/null +++ b/api/BusinessLogic/Tickets/TrackingIdGenerator.php @@ -0,0 +1,10 @@ + Date: Sat, 11 Feb 2017 16:27:31 -0500 Subject: [PATCH 036/192] Remove unnecessary comment --- api/BusinessLogic/Tickets/TicketCreator.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 0ace14ea..5cd964d7 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -135,9 +135,6 @@ class TicketCreator { $validationModel->errorKeys[] = 'EMAIL_AT_MAX_OPEN_TICKETS'; } - // TODO Check if we're at the max number of tickets - // TODO submit_ticket.php:325-334 - return $validationModel; } } \ No newline at end of file From 7e966b93a5192937b1c135892868e51e1ad06833 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 11 Feb 2017 16:42:40 -0500 Subject: [PATCH 037/192] Intermediate commit --- .../Tickets/TrackingIdGenerator.php | 6 +++ api/DataAccess/Tickets/TicketGateway.php | 1 + .../Tickets/TrackingIdGeneratorTest.php | 37 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 api/Tests/BusinessLogic/Tickets/TrackingIdGeneratorTest.php diff --git a/api/BusinessLogic/Tickets/TrackingIdGenerator.php b/api/BusinessLogic/Tickets/TrackingIdGenerator.php index 3a85f350..33793401 100644 --- a/api/BusinessLogic/Tickets/TrackingIdGenerator.php +++ b/api/BusinessLogic/Tickets/TrackingIdGenerator.php @@ -4,6 +4,12 @@ namespace BusinessLogic\Tickets; class TrackingIdGenerator { + private $ticketGateway; + + function __construct($ticketGateway) { + $this->ticketGateway = $ticketGateway; + } + function generateTrackingId() { } diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index 9b05e594..9317aaae 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -30,6 +30,7 @@ class TicketGateway extends CommonDao { while ($row = hesk_dbFetchAssoc($rs)) { $ticket = new Ticket(); + //-- TODO Finish this! } } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/TrackingIdGeneratorTest.php b/api/Tests/BusinessLogic/Tickets/TrackingIdGeneratorTest.php new file mode 100644 index 00000000..c851b158 --- /dev/null +++ b/api/Tests/BusinessLogic/Tickets/TrackingIdGeneratorTest.php @@ -0,0 +1,37 @@ +ticketGateway = $this->createMock(TicketGateway::class); + + $this->trackingIdGenerator = new TrackingIdGenerator($this->ticketGateway); + } + + function testItReturnsTrackingIdInTheProperFormat() { + //-- Arrange + $format = ''; + + //-- Act + + //-- Assert + } +} From 65b10bae3cb2e19c96ca8eefb137bbc5879686a7 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 11 Feb 2017 22:07:10 -0500 Subject: [PATCH 038/192] TrackingIdGenerator working. Stubbed out todo list for rest of creating ticket --- .../Tickets/CreateTicketByCustomerModel.php | 18 +++ .../UnableToGenerateTrackingIdException.php | 12 ++ api/BusinessLogic/Tickets/TicketCreator.php | 22 ++++ .../Tickets/TrackingIdGenerator.php | 122 +++++++++++++++++- api/DataAccess/Tickets/TicketGateway.php | 55 +++++++- .../Tickets/TrackingIdGeneratorTest.php | 31 ++++- 6 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 api/BusinessLogic/Tickets/Exceptions/UnableToGenerateTrackingIdException.php diff --git a/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php b/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php index aa6ab0ff..736ce29e 100644 --- a/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php +++ b/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php @@ -40,5 +40,23 @@ class CreateTicketByCustomerModel { */ public $customFields; + /** + * @var double[]|null + */ public $location; + + /** + * @var int[]|null + */ + public $suggestedKnowledgebaseArticleIds; + + /** + * @var string|null + */ + public $userAgent; + + /** + * @var int[]|null + */ + public $screenResolution; } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/Exceptions/UnableToGenerateTrackingIdException.php b/api/BusinessLogic/Tickets/Exceptions/UnableToGenerateTrackingIdException.php new file mode 100644 index 00000000..0d2718a8 --- /dev/null +++ b/api/BusinessLogic/Tickets/Exceptions/UnableToGenerateTrackingIdException.php @@ -0,0 +1,12 @@ +NOT handled here! + * * @param $ticketRequest CreateTicketByCustomerModel * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings * @throws ValidationException When a required field in $ticket_request is missing + * */ function createTicketByCustomer($ticketRequest, $heskSettings, $modsForHeskSettings, $userContext) { $validationModel = $this->validate($ticketRequest, false, $heskSettings, $modsForHeskSettings, $userContext); @@ -47,6 +50,25 @@ class TicketCreator { } // Create the ticket + //-- TODO Get tracking ID + + //-- TODO handle message + + //-- TODO suggested kb articles + + //-- TODO autoassign logic + + //-- TODO latitude/longitude + + //-- TODO HTML flag + + //-- TODO Screen res / user agent + + //-- TODO Should ticket validation exist? + + //-- TODO Create the ticket + + //-- TODO return the freshly created ticket. Any extra stuff the web side does will be handled in submit_ticket.php } /** diff --git a/api/BusinessLogic/Tickets/TrackingIdGenerator.php b/api/BusinessLogic/Tickets/TrackingIdGenerator.php index 33793401..a97811ca 100644 --- a/api/BusinessLogic/Tickets/TrackingIdGenerator.php +++ b/api/BusinessLogic/Tickets/TrackingIdGenerator.php @@ -3,14 +3,134 @@ namespace BusinessLogic\Tickets; +use BusinessLogic\Tickets\Exceptions\UnableToGenerateTrackingIdException; +use DataAccess\Tickets\TicketGateway; + class TrackingIdGenerator { + /** + * @var $ticketGateway TicketGateway + */ private $ticketGateway; function __construct($ticketGateway) { $this->ticketGateway = $ticketGateway; } - function generateTrackingId() { + /** + * @param $heskSettings array + * @return string + */ + function generateTrackingId($heskSettings) { + $acceptableCharacters = 'AEUYBDGHJLMNPQRSTVWXZ123456789'; + + /* Generate raw ID */ + $trackingId = ''; + + /* Let's avoid duplicate ticket ID's, try up to 3 times */ + for ($i = 1; $i <= 3; $i++) { + for ($i = 0; $i < 10; $i++) { + $trackingId .= $acceptableCharacters[mt_rand(0, 29)]; + } + + $trackingId = $this->formatTrackingId($trackingId); + + /* Check for duplicate IDs */ + $ticket = $this->ticketGateway->getTicketByTrackingId($trackingId, $heskSettings); + + if ($ticket === null) { + return $trackingId; + } + + /* A duplicate ID has been found! Let's try again (up to 2 more) */ + $trackingId = ''; + } + + /* No valid tracking ID, try one more time with microtime() */ + $trackingId = $acceptableCharacters[mt_rand(0, 29)]; + $trackingId .= $acceptableCharacters[mt_rand(0, 29)]; + $trackingId .= $acceptableCharacters[mt_rand(0, 29)]; + $trackingId .= $acceptableCharacters[mt_rand(0, 29)]; + $trackingId .= $acceptableCharacters[mt_rand(0, 29)]; + $trackingId .= substr(microtime(), -5); + + /* Format the ID to the correct shape and check wording */ + $trackingId = $this->formatTrackingId($trackingId); + + $ticket = $this->ticketGateway->getTicketByTrackingId($trackingId, $heskSettings); + + if ($ticket === null) { + return $trackingId; + } + + throw new UnableToGenerateTrackingIdException(); + } + + /** + * @param $id string + * @return string + */ + private function formatTrackingId($id) { + $acceptableCharacters = 'AEUYBDGHJLMNPQRSTVWXZ123456789'; + + $replace = $acceptableCharacters[mt_rand(0, 29)]; + $replace .= mt_rand(1, 9); + $replace .= $acceptableCharacters[mt_rand(0, 29)]; + + /* + Remove 3 letter bad words from ID + Possiblitiy: 1:27,000 + */ + $remove = array( + 'ASS', + 'CUM', + 'FAG', + 'FUK', + 'GAY', + 'SEX', + 'TIT', + 'XXX', + ); + + $id = str_replace($remove, $replace, $id); + + /* + Remove 4 letter bad words from ID + Possiblitiy: 1:810,000 + */ + $remove = array( + 'ANAL', + 'ANUS', + 'BUTT', + 'CAWK', + 'CLIT', + 'COCK', + 'CRAP', + 'CUNT', + 'DICK', + 'DYKE', + 'FART', + 'FUCK', + 'JAPS', + 'JERK', + 'JIZZ', + 'KNOB', + 'PISS', + 'POOP', + 'SHIT', + 'SLUT', + 'SUCK', + 'TURD', + + // Also, remove words that are known to trigger mod_security + 'WGET', + ); + + $replace .= mt_rand(1, 9); + $id = str_replace($remove, $replace, $id); + + /* Format the ID string into XXX-XXX-XXXX format for easier readability */ + $id = $id[0] . $id[1] . $id[2] . '-' . $id[3] . $id[4] . $id[5] . '-' . $id[6] . $id[7] . $id[8] . $id[9]; + return $id; } } \ No newline at end of file diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index 9317aaae..c4a40a8c 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -7,12 +7,22 @@ use BusinessLogic\Tickets\Ticket; use DataAccess\CommonDao; class TicketGateway extends CommonDao { + /** + * @param $id int + * @param $heskSettings array + * @return Ticket|null + */ function getTicketById($id, $heskSettings) { $this->init(); $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `id` = " . intval($id)); + + if (hesk_dbNumRows($rs) === 0) { + return null; + } + $row = hesk_dbFetchAssoc($rs); - $linkedTicketsRs = hesk_dbQuery("SELECT * FROM `hesk_tickets` WHERE `parent` = " . intval($id)); + $linkedTicketsRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `parent` = " . intval($id)); $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $heskSettings); @@ -21,16 +31,55 @@ class TicketGateway extends CommonDao { return $ticket; } + /** + * @param $emailAddress string + * @param $heskSettings array + * @return array|null + */ function getTicketsByEmail($emailAddress, $heskSettings) { + $this->init(); + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `email` = '" . hesk_dbEscape($emailAddress) . "'"); + if (hesk_dbNumRows($rs) === 0) { + return null; + } + $tickets = array(); while ($row = hesk_dbFetchAssoc($rs)) { - $ticket = new Ticket(); + $linkedTicketsRs = + hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `parent` = " . intval($row['id'])); - //-- TODO Finish this! + $tickets[] = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $heskSettings); } + + $this->close(); + + return $tickets; + } + + /** + * @param $trackingId string + * @param $heskSettings array + * @return Ticket|null + */ + function getTicketByTrackingId($trackingId, $heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `id` = " . intval($trackingId)); + if (hesk_dbNumRows($rs) === 0) { + return null; + } + + $row = hesk_dbFetchAssoc($rs); + $linkedTicketsRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `parent` = " . intval($trackingId)); + + $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $heskSettings); + + $this->close(); + + return $ticket; } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/TrackingIdGeneratorTest.php b/api/Tests/BusinessLogic/Tickets/TrackingIdGeneratorTest.php index c851b158..15cfb484 100644 --- a/api/Tests/BusinessLogic/Tickets/TrackingIdGeneratorTest.php +++ b/api/Tests/BusinessLogic/Tickets/TrackingIdGeneratorTest.php @@ -9,6 +9,7 @@ namespace BusinessLogic\Tickets; +use BusinessLogic\Tickets\Exceptions\UnableToGenerateTrackingIdException; use DataAccess\Tickets\TicketGateway; use PHPUnit\Framework\TestCase; @@ -18,6 +19,9 @@ class TrackingIdGeneratorTest extends TestCase { */ private $ticketGateway; + /** + * @var $trackingIdGenerator TrackingIdGenerator + */ private $trackingIdGenerator; function setUp() { @@ -28,10 +32,35 @@ class TrackingIdGeneratorTest extends TestCase { function testItReturnsTrackingIdInTheProperFormat() { //-- Arrange - $format = ''; + $this->ticketGateway->method('getTicketByTrackingId') + ->willReturn(null); + $acceptableCharacters = '[AEUYBDGHJLMNPQRSTVWXZ123456789]'; + $format = "/^{$acceptableCharacters}{3}-{$acceptableCharacters}{3}-{$acceptableCharacters}{4}$/"; //-- Act + $trackingId = $this->trackingIdGenerator->generateTrackingId(array()); //-- Assert + $this->assertThat($trackingId, $this->matchesRegularExpression($format)); } + + function testItThrowsAnExceptionWhenItWasUnableToGenerateAValidTrackingId() { + //-- Arrange + $exceptionThrown = false; + $this->ticketGateway->method('getTicketByTrackingId') + ->willReturn(new Ticket()); + + //-- Act + try { + $this->trackingIdGenerator->generateTrackingId(array()); + } catch (UnableToGenerateTrackingIdException $e) { + //-- Assert (1/2) + $exceptionThrown = true; + } + + //-- Assert (2/2) + $this->assertThat($exceptionThrown, $this->isTrue()); + } + + //-- Trying to test the database logic is tricky, so no tests here. } From 044faa77f68cfa76eedec614c2c31585a6a56cde Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Feb 2017 00:50:30 -0500 Subject: [PATCH 039/192] TicketGateway can now insert tickets --- api/BusinessLogic/Tickets/Ticket.php | 12 +++ api/DataAccess/Tickets/TicketGateway.php | 105 +++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/api/BusinessLogic/Tickets/Ticket.php b/api/BusinessLogic/Tickets/Ticket.php index 13b5a43a..6594562c 100644 --- a/api/BusinessLogic/Tickets/Ticket.php +++ b/api/BusinessLogic/Tickets/Ticket.php @@ -236,6 +236,15 @@ class Ticket { */ public $attachments; + function getAttachmentsForDatabase() { + $attachmentArray = array(); + foreach ($this->attachments as $attachment) { + $attachmentArray[] = $attachment->id . '#' . $attachment->fileName . '#' . $attachment->savedName; + } + + return implode(',', $attachmentArray); + } + /** * @var int[]|null */ @@ -272,6 +281,9 @@ class Ticket { public $userAgent; /** + * 0 => width + * 1 => height + * * @var int[]|null */ public $screenResolution; diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index c4a40a8c..f2101c7a 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -82,4 +82,109 @@ class TicketGateway extends CommonDao { return $ticket; } + + /** + * @param $ticket Ticket + * @param $heskSettings + */ + function createTicket($ticket, $heskSettings) { + global $hesklang; + + // If language is not set or default, set it to NULL. + if ($ticket->language === null || empty($ticket->language)) { + $language = (!$heskSettings['can_sel_lang']) ? HESK_DEFAULT_LANGUAGE : hesk_dbEscape($hesklang['LANGUAGE']); + } else { + $language = $ticket->language; + } + + $dueDate = $ticket->dueDate ? "'{$ticket->dueDate}'" : "NULL"; + // Prepare SQL for custom fields + $customWhere = ''; + $customWhat = ''; + + for ($i=1; $i<=50; $i++) + { + $customWhere .= ", `custom{$i}`"; + $customWhat .= ", '" . (isset($ticket->customFields[$i]) ? hesk_dbEscape($ticket->customFields[$i]) : '') . "'"; + } + + $suggestedArticles = ''; + if ($ticket->suggestedArticles !== null && !empty($ticket->suggestedArticles)) { + $suggestedArticles = implode(',', $ticket->suggestedArticles); + } + + $latitude = $ticket->location !== null + && isset($ticket->location[0]) + && $ticket->location[0] !== null ? $ticket->location[0] : ''; + $longitude = $ticket->location !== null + && isset($ticket->location[1]) + && $ticket->location[1] !== null ? $ticket->location[1] : ''; + $userAgent = $ticket->userAgent !== null ? $ticket->userAgent : ''; + $screenResolutionWidth = $ticket->screenResolution !== null + && isset($ticket->screenResolution[0]) + && $ticket->screenResolution[0] !== null ? intval($ticket->screenResolution[0]) : ''; + $screenResolutionHeight = $ticket->screenResolution !== null + && isset($ticket->screenResolution[1]) + && $ticket->screenResolution[1] !== null ? intval($ticket->screenResolution[1]) : ''; + + $sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` + ( + `trackid`, + `name`, + `email`, + `category`, + `priority`, + `subject`, + `message`, + `dt`, + `lastchange`, + `articles`, + `ip`, + `language`, + `openedby`, + `owner`, + `attachments`, + `merged`, + `status`, + `latitude`, + `longitude`, + `html`, + `user_agent`, + `screen_resolution_height`, + `screen_resolution_width`, + `due_date`, + `history` + {$customWhere} + ) + VALUES + ( + '" . hesk_dbEscape($ticket->trackingId) . "', + '" . hesk_dbEscape($ticket->name) . "', + '" . hesk_dbEscape($ticket->email) . "', + '" . intval($ticket->categoryId) . "', + '" . intval($ticket->priorityId) . "', + '" . hesk_dbEscape($ticket->subject) . "', + '" . hesk_dbEscape($ticket->message) . "', + NOW(), + NOW(), + " . $suggestedArticles . ", + '" . hesk_dbEscape($ticket->ipAddress) . "', + '" . hesk_dbEscape($language) . "', + '" . intval($ticket->openedBy) . "', + '" . intval($ticket->ownerId) . "', + '" . hesk_dbEscape($ticket->getAttachmentsForDatabase()) . "', + '', + '" . intval($ticket->statusId) . "', + '" . hesk_dbEscape($latitude) . "', + '" . hesk_dbEscape($longitude) . "', + '" . hesk_dbEscape($ticket->usesHtml) . "', + '" . hesk_dbEscape($userAgent) . "', + " . hesk_dbEscape($screenResolutionHeight) . ", + " . hesk_dbEscape($screenResolutionWidth) . ", + {$dueDate}, + '" . hesk_dbEscape($ticket->auditTrailHtml) . "' + {$customWhat} + ) + "; + } } \ No newline at end of file From 1a8a989e87a1d4de20cf40a626e673e8b01f2273 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Feb 2017 01:38:12 -0500 Subject: [PATCH 040/192] Moved validation logic to its own class, working on createTicket tests --- .../Tickets/NewTicketValidator.php | 127 ++++ api/BusinessLogic/Tickets/TicketCreator.php | 137 +--- .../Tickets/TrackingIdGenerator.php | 1 + .../Tickets/NewTicketValidatorTest.php | 432 +++++++++++++ .../Tickets/TicketCreatorTest.php | 606 ------------------ .../CreateTicketForCustomerTest.php | 108 ++++ 6 files changed, 688 insertions(+), 723 deletions(-) create mode 100644 api/BusinessLogic/Tickets/NewTicketValidator.php create mode 100644 api/Tests/BusinessLogic/Tickets/NewTicketValidatorTest.php delete mode 100644 api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php create mode 100644 api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php diff --git a/api/BusinessLogic/Tickets/NewTicketValidator.php b/api/BusinessLogic/Tickets/NewTicketValidator.php new file mode 100644 index 00000000..5f6d6a23 --- /dev/null +++ b/api/BusinessLogic/Tickets/NewTicketValidator.php @@ -0,0 +1,127 @@ +categoryRetriever = $categoryRetriever; + $this->banRetriever = $banRetriever; + $this->ticketValidators = $ticketValidators; + } + + /** + * @param $ticketRequest CreateTicketByCustomerModel + * @param $heskSettings array HESK settings + * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket + */ + function validateNewTicketForCustomer($ticketRequest, $heskSettings, $userContext) { + $TICKET_PRIORITY_CRITICAL = 0; + + $validationModel = new ValidationModel(); + + if ($ticketRequest->name === NULL || $ticketRequest->name == '') { + $validationModel->errorKeys[] = 'NO_NAME'; + } + + if (!Validators::validateEmail($ticketRequest->email, $heskSettings['multi_eml'], false)) { + $validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL'; + } + + $categoryId = intval($ticketRequest->category); + if ($categoryId < 1) { + $validationModel->errorKeys[] = 'NO_CATEGORY'; + } else { + $categoryExists = array_key_exists($categoryId, $this->categoryRetriever->getAllCategories($heskSettings, $userContext)); + if (!$categoryExists) { + $validationModel->errorKeys[] = 'CATEGORY_DOES_NOT_EXIST'; + } + } + + //-- TODO assert priority exists + + if ($heskSettings['cust_urgency'] && intval($ticketRequest->priority) === $TICKET_PRIORITY_CRITICAL) { + $validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN'; + } + + if ($heskSettings['require_subject'] === 1 && + ($ticketRequest->subject === NULL || $ticketRequest->subject === '')) { + $validationModel->errorKeys[] = 'SUBJECT_REQUIRED'; + } + + if ($heskSettings['require_message'] === 1 && + ($ticketRequest->message === NULL || $ticketRequest->message === '')) { + $validationModel->errorKeys[] = 'MESSAGE_REQUIRED'; + } + + foreach ($heskSettings['custom_fields'] as $key => $value) { + $customFieldNumber = intval(str_replace('custom', '', $key)); + if ($value['use'] == 1 && CustomFieldValidator::isCustomFieldInCategory($customFieldNumber, intval($ticketRequest->category), false, $heskSettings)) { + $custom_field_value = $ticketRequest->customFields[$customFieldNumber]; + if (empty($custom_field_value)) { + $validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::NO_VALUE"; + continue; + } + switch($value['type']) { + case CustomField::DATE: + if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::INVALID_DATE'; + } else { + // Actually validate based on range + $date = strtotime($custom_field_value . ' t00:00:00'); + $dmin = strlen($value['value']['dmin']) ? strtotime($value['value']['dmin'] . ' t00:00:00') : false; + $dmax = strlen($value['value']['dmax']) ? strtotime($value['value']['dmax'] . ' t00:00:00') : false; + + if ($dmin && $dmin > $date) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_BEFORE_MIN::MIN:' . date('Y-m-d', $dmin) . '::ENTERED:' . date('Y-m-d', $date); + } elseif ($dmax && $dmax < $date) { + $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_AFTER_MAX::MAX:' . date('Y-m-d', $dmax) . '::ENTERED:' . date('Y-m-d', $date); + } + } + break; + case CustomField::EMAIL: + if (!Validators::validateEmail($custom_field_value, $value['value']['multiple'], false)) { + $validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::INVALID_EMAIL"; + } + break; + } + } + } + + if ($this->banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) { + $validationModel->errorKeys[] = 'EMAIL_BANNED'; + } + + if ($this->ticketValidators->isCustomerAtMaxTickets($ticketRequest->email, $heskSettings)) { + $validationModel->errorKeys[] = 'EMAIL_AT_MAX_OPEN_TICKETS'; + } + + return $validationModel; + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index b7be8613..0c6b506e 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -2,33 +2,29 @@ namespace BusinessLogic\Tickets; - -use BusinessLogic\Categories\CategoryRetriever; use BusinessLogic\Exceptions\ValidationException; -use BusinessLogic\Security\BanRetriever; -use BusinessLogic\Tickets\CustomFields\CustomFieldValidator; -use BusinessLogic\ValidationModel; -use BusinessLogic\Validators; -use Core\Constants\CustomField; +use DataAccess\Tickets\TicketGateway; class TicketCreator { /** - * @var $categoryRetriever CategoryRetriever + * @var $newTicketValidator NewTicketValidator */ - private $categoryRetriever; + private $newTicketValidator; + /** - * @var $banRetriever BanRetriever + * @var $trackingIdGenerator TrackingIdGenerator */ - private $banRetriever; + private $trackingIdGenerator; + /** - * @var $ticketValidators TicketValidators + * @var $ticketGateway TicketGateway */ - private $ticketValidators; + private $ticketGateway; - function __construct($categoryRetriever, $banRetriever, $ticketValidators) { - $this->categoryRetriever = $categoryRetriever; - $this->banRetriever = $banRetriever; - $this->ticketValidators = $ticketValidators; + function __construct($newTicketValidator, $trackingIdGenerator, $ticketGateway) { + $this->newTicketValidator = $newTicketValidator; + $this->trackingIdGenerator = $trackingIdGenerator; + $this->ticketGateway = $ticketGateway; } /** @@ -37,11 +33,12 @@ class TicketCreator { * @param $ticketRequest CreateTicketByCustomerModel * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings + * @return Ticket The newly created ticket * @throws ValidationException When a required field in $ticket_request is missing * */ function createTicketByCustomer($ticketRequest, $heskSettings, $modsForHeskSettings, $userContext) { - $validationModel = $this->validate($ticketRequest, false, $heskSettings, $modsForHeskSettings, $userContext); + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($ticketRequest, $heskSettings, $userContext); if (count($validationModel->errorKeys) > 0) { // Validation failed @@ -50,113 +47,19 @@ class TicketCreator { } // Create the ticket - //-- TODO Get tracking ID - - //-- TODO handle message + $ticket = new Ticket(); + $ticket->trackingId = $this->trackingIdGenerator->generateTrackingId($heskSettings); //-- TODO suggested kb articles - //-- TODO autoassign logic + //-- TODO owner/autoassign logic //-- TODO latitude/longitude //-- TODO HTML flag - //-- TODO Screen res / user agent - - //-- TODO Should ticket validation exist? - - //-- TODO Create the ticket - - //-- TODO return the freshly created ticket. Any extra stuff the web side does will be handled in submit_ticket.php - } - - /** - * @param $ticketRequest CreateTicketByCustomerModel - * @param $staff bool - * @param $heskSettings array HESK settings - * @param $modsForHeskSettings array Mods for HESK settings - * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket - */ - function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings, $userContext) { - $TICKET_PRIORITY_CRITICAL = 0; - - $validationModel = new ValidationModel(); - - if ($ticketRequest->name === NULL || $ticketRequest->name == '') { - $validationModel->errorKeys[] = 'NO_NAME'; - } - - if (!Validators::validateEmail($ticketRequest->email, $heskSettings['multi_eml'], false)) { - $validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL'; - } - - $categoryId = intval($ticketRequest->category); - if ($categoryId < 1) { - $validationModel->errorKeys[] = 'NO_CATEGORY'; - } else { - $categoryExists = array_key_exists($categoryId, $this->categoryRetriever->getAllCategories($heskSettings, $userContext)); - if (!$categoryExists) { - $validationModel->errorKeys[] = 'CATEGORY_DOES_NOT_EXIST'; - } - } - - if ($heskSettings['cust_urgency'] && intval($ticketRequest->priority) === $TICKET_PRIORITY_CRITICAL) { - $validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN'; - } - - if ($heskSettings['require_subject'] === 1 && - ($ticketRequest->subject === NULL || $ticketRequest->subject === '')) { - $validationModel->errorKeys[] = 'SUBJECT_REQUIRED'; - } - - if ($heskSettings['require_message'] === 1 && - ($ticketRequest->message === NULL || $ticketRequest->message === '')) { - $validationModel->errorKeys[] = 'MESSAGE_REQUIRED'; - } - - foreach ($heskSettings['custom_fields'] as $key => $value) { - $customFieldNumber = intval(str_replace('custom', '', $key)); - if ($value['use'] == 1 && CustomFieldValidator::isCustomFieldInCategory($customFieldNumber, intval($ticketRequest->category), false, $heskSettings)) { - $custom_field_value = $ticketRequest->customFields[$customFieldNumber]; - if (empty($custom_field_value)) { - $validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::NO_VALUE"; - continue; - } - switch($value['type']) { - case CustomField::DATE: - if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::INVALID_DATE'; - } else { - // Actually validate based on range - $date = strtotime($custom_field_value . ' t00:00:00'); - $dmin = strlen($value['value']['dmin']) ? strtotime($value['value']['dmin'] . ' t00:00:00') : false; - $dmax = strlen($value['value']['dmax']) ? strtotime($value['value']['dmax'] . ' t00:00:00') : false; - - if ($dmin && $dmin > $date) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_BEFORE_MIN::MIN:' . date('Y-m-d', $dmin) . '::ENTERED:' . date('Y-m-d', $date); - } elseif ($dmax && $dmax < $date) { - $validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_AFTER_MAX::MAX:' . date('Y-m-d', $dmax) . '::ENTERED:' . date('Y-m-d', $date); - } - } - break; - case CustomField::EMAIL: - if (!Validators::validateEmail($custom_field_value, $value['value']['multiple'], false)) { - $validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::INVALID_EMAIL"; - } - break; - } - } - } - - if ($this->banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) { - $validationModel->errorKeys[] = 'EMAIL_BANNED'; - } - - if ($this->ticketValidators->isCustomerAtMaxTickets($ticketRequest->email, $heskSettings)) { - $validationModel->errorKeys[] = 'EMAIL_AT_MAX_OPEN_TICKETS'; - } + $this->ticketGateway->createTicket($ticket, $heskSettings); - return $validationModel; + return $ticket; } } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/TrackingIdGenerator.php b/api/BusinessLogic/Tickets/TrackingIdGenerator.php index a97811ca..25cbe010 100644 --- a/api/BusinessLogic/Tickets/TrackingIdGenerator.php +++ b/api/BusinessLogic/Tickets/TrackingIdGenerator.php @@ -19,6 +19,7 @@ class TrackingIdGenerator { /** * @param $heskSettings array * @return string + * @throws UnableToGenerateTrackingIdException */ function generateTrackingId($heskSettings) { $acceptableCharacters = 'AEUYBDGHJLMNPQRSTVWXZ123456789'; diff --git a/api/Tests/BusinessLogic/Tickets/NewTicketValidatorTest.php b/api/Tests/BusinessLogic/Tickets/NewTicketValidatorTest.php new file mode 100644 index 00000000..de978568 --- /dev/null +++ b/api/Tests/BusinessLogic/Tickets/NewTicketValidatorTest.php @@ -0,0 +1,432 @@ +banRetriever = $this->createMock(BanRetriever::class); + $this->categoryRetriever = $this->createMock(CategoryRetriever::class); + $this->ticketValidators = $this->createMock(TicketValidators::class); + $this->newTicketValidator = new NewTicketValidator($this->categoryRetriever, $this->banRetriever, $this->ticketValidators); + $this->userContext = new UserContext(); + + $this->ticketRequest = new CreateTicketByCustomerModel(); + $this->ticketRequest->name = 'Name'; + $this->ticketRequest->email = 'some@e.mail'; + $this->ticketRequest->category = 1; + $this->ticketRequest->priority = Priority::HIGH; + $this->ticketRequest->subject = 'Subject'; + $this->ticketRequest->message = 'Message'; + $this->ticketRequest->customFields = array(); + $this->heskSettings = array( + 'multi_eml' => false, + 'cust_urgency' => false, + 'require_subject' => 1, + 'require_message' => 1, + 'custom_fields' => array(), + ); + + $category = new Category(); + $category->accessible = true; + $category->id = 1; + $categories = array(); + $categories[1] = $category; + $this->categoryRetriever->method('getAllCategories') + ->willReturn($categories); + } + + function testItAddsTheProperValidationErrorWhenNameIsNull() { + //-- Arrange + $this->ticketRequest->name = null; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['NO_NAME'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenNameIsBlank() { + //-- Arrange + $this->ticketRequest->name = ''; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['NO_NAME'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenEmailIsNull() { + //-- Arrange + $this->ticketRequest->email = null; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenEmailIsBlank() { + //-- Arrange + $this->ticketRequest->email = ''; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenEmailIsInvalid() { + //-- Arrange + $this->ticketRequest->email = 'something@'; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $validationModel->errorKeys); + } + + function testItSupportsMultipleEmails() { + //-- Arrange + $this->ticketRequest->email = 'something@email.com;another@valid.email'; + $this->heskSettings['multi_eml'] = true; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertThat($validationModel->valid, $this->isTrue()); + } + + function testItAddsTheProperValidationErrorWhenCategoryIsNotANumber() { + //-- Arrange + $this->ticketRequest->category = 'something'; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['NO_CATEGORY'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenCategoryIsNegative() { + //-- Arrange + $this->ticketRequest->category = -5; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['NO_CATEGORY'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCategoryDoesNotExist() { + //-- Arrange + $this->ticketRequest->category = 10; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['CATEGORY_DOES_NOT_EXIST'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithPriorityCritical() { + //-- Arrange + $this->ticketRequest->priority = Priority::CRITICAL; + $this->heskSettings['cust_urgency'] = true; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['CRITICAL_PRIORITY_FORBIDDEN'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullSubjectAndItIsRequired() { + //-- Arrange + $this->ticketRequest->subject = null; + $this->heskSettings['require_subject'] = 1; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['SUBJECT_REQUIRED'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankSubjectAndItIsRequired() { + //-- Arrange + $this->ticketRequest->subject = ''; + $this->heskSettings['require_subject'] = 1; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['SUBJECT_REQUIRED'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullMessageAndItIsRequired() { + //-- Arrange + $this->ticketRequest->message = null; + $this->heskSettings['require_message'] = 1; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['MESSAGE_REQUIRED'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankMessageAndItIsRequired() { + //-- Arrange + $this->ticketRequest->message = ''; + $this->heskSettings['require_message'] = 1; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['MESSAGE_REQUIRED'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullRequiredCustomField() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::TEXT; + $customField['use'] = 1; + $customField['category'] = array(); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = null; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::NO_VALUE'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankRequiredCustomField() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::TEXT; + $customField['use'] = 1; + $customField['category'] = array(); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = ''; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::NO_VALUE'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateCustomFieldThatIsInvalid() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::DATE; + $customField['use'] = 1; + $customField['category'] = array(); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = '2017-30-00'; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::INVALID_DATE'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateThatIsBeforeMinDate() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::DATE; + $customField['use'] = 1; + $customField['category'] = array(); + $customField['value'] = array( + 'dmin' => '2017-01-01', + 'dmax' => '' + ); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = '2016-12-31'; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::DATE_BEFORE_MIN::MIN:2017-01-01::ENTERED:2016-12-31'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateThatIsAfterMaxDate() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::DATE; + $customField['use'] = 1; + $customField['category'] = array(); + $customField['value'] = array( + 'dmin' => '', + 'dmax' => '2017-01-01' + ); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = '2017-01-02'; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::DATE_AFTER_MAX::MAX:2017-01-01::ENTERED:2017-01-02'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithEmailThatIsInvalid() { + //-- Arrange + $customField = array(); + $customField['req'] = 1; + $customField['type'] = CustomField::EMAIL; + $customField['use'] = 1; + $customField['category'] = array(); + $customField['value'] = array( + 'multiple' => 0 + ); + $this->heskSettings['custom_fields']['custom1'] = $customField; + $this->ticketRequest->customFields[1] = 'invalid@'; + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::INVALID_EMAIL'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithABannedEmail() { + //-- Arrange + $this->ticketRequest->email = 'some@banned.email'; + $this->banRetriever->method('isEmailBanned') + ->with($this->ticketRequest->email, $this->heskSettings) + ->willReturn(true); + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['EMAIL_BANNED'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWhenTheyAreMaxedOut() { + //-- Arrange + $this->ticketRequest->email = 'some@maxedout.email'; + $this->ticketValidators->method('isCustomerAtMaxTickets') + ->with($this->ticketRequest->email, $this->heskSettings) + ->willReturn(true); + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['EMAIL_AT_MAX_OPEN_TICKETS'], $validationModel->errorKeys); + } +} diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php deleted file mode 100644 index 2098bee6..00000000 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTest.php +++ /dev/null @@ -1,606 +0,0 @@ -banRetriever = $this->createMock(BanRetriever::class); - $this->categoryRetriever = $this->createMock(CategoryRetriever::class); - $this->ticketValidators = $this->createMock(TicketValidators::class); - $this->ticketCreator = new TicketCreator($this->categoryRetriever, $this->banRetriever, $this->ticketValidators); - $this->userContext = new UserContext(); - - $this->ticketRequest = new CreateTicketByCustomerModel(); - $this->ticketRequest->name = 'Name'; - $this->ticketRequest->email = 'some@e.mail'; - $this->ticketRequest->category = 1; - $this->ticketRequest->priority = Priority::HIGH; - $this->ticketRequest->subject = 'Subject'; - $this->ticketRequest->message = 'Message'; - $this->ticketRequest->customFields = array(); - $this->heskSettings = array( - 'multi_eml' => false, - 'cust_urgency' => false, - 'require_subject' => 1, - 'require_message' => 1, - 'custom_fields' => array(), - ); - - $category = new Category(); - $category->accessible = true; - $category->id = 1; - $categories = array(); - $categories[1] = $category; - $this->categoryRetriever->method('getAllCategories') - ->willReturn($categories); - } - - function testItAddsTheProperValidationErrorWhenNameIsNull() { - //-- Arrange - $this->ticketRequest->name = null; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['NO_NAME'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenNameIsBlank() { - //-- Arrange - $this->ticketRequest->name = ''; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['NO_NAME'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenEmailIsNull() { - //-- Arrange - $this->ticketRequest->email = null; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenEmailIsBlank() { - //-- Arrange - $this->ticketRequest->email = ''; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenEmailIsInvalid() { - //-- Arrange - $this->ticketRequest->email = 'something@'; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['INVALID_OR_MISSING_EMAIL'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItSupportsMultipleEmails() { - //-- Arrange - $this->ticketRequest->email = 'something@email.com;another@valid.email'; - $this->heskSettings['multi_eml'] = true; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - var_dump($e->validationModel->errorKeys); - $this->fail('Should not have thrown a ValidationException! Validation error keys are above.'); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(false)); - } - - function testItAddsTheProperValidationErrorWhenCategoryIsNotANumber() { - //-- Arrange - $this->ticketRequest->category = 'something'; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['NO_CATEGORY'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenCategoryIsNegative() { - //-- Arrange - $this->ticketRequest->category = -5; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['NO_CATEGORY'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCategoryDoesNotExist() { - //-- Arrange - $this->ticketRequest->category = 10; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['CATEGORY_DOES_NOT_EXIST'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithPriorityCritical() { - //-- Arrange - $this->ticketRequest->priority = Priority::CRITICAL; - $this->heskSettings['cust_urgency'] = true; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['CRITICAL_PRIORITY_FORBIDDEN'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullSubjectAndItIsRequired() { - //-- Arrange - $this->ticketRequest->subject = null; - $this->heskSettings['require_subject'] = 1; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['SUBJECT_REQUIRED'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankSubjectAndItIsRequired() { - //-- Arrange - $this->ticketRequest->subject = ''; - $this->heskSettings['require_subject'] = 1; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['SUBJECT_REQUIRED'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullMessageAndItIsRequired() { - //-- Arrange - $this->ticketRequest->message = null; - $this->heskSettings['require_message'] = 1; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['MESSAGE_REQUIRED'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankMessageAndItIsRequired() { - //-- Arrange - $this->ticketRequest->message = ''; - $this->heskSettings['require_message'] = 1; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['MESSAGE_REQUIRED'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithNullRequiredCustomField() { - //-- Arrange - $customField = array(); - $customField['req'] = 1; - $customField['type'] = CustomField::TEXT; - $customField['use'] = 1; - $customField['category'] = array(); - $this->heskSettings['custom_fields']['custom1'] = $customField; - $this->ticketRequest->customFields[1] = null; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::NO_VALUE'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithBlankRequiredCustomField() { - //-- Arrange - $customField = array(); - $customField['req'] = 1; - $customField['type'] = CustomField::TEXT; - $customField['use'] = 1; - $customField['category'] = array(); - $this->heskSettings['custom_fields']['custom1'] = $customField; - $this->ticketRequest->customFields[1] = ''; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::NO_VALUE'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateCustomFieldThatIsInvalid() { - //-- Arrange - $customField = array(); - $customField['req'] = 1; - $customField['type'] = CustomField::DATE; - $customField['use'] = 1; - $customField['category'] = array(); - $this->heskSettings['custom_fields']['custom1'] = $customField; - $this->ticketRequest->customFields[1] = '2017-30-00'; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::INVALID_DATE'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateThatIsBeforeMinDate() { - //-- Arrange - $customField = array(); - $customField['req'] = 1; - $customField['type'] = CustomField::DATE; - $customField['use'] = 1; - $customField['category'] = array(); - $customField['value'] = array( - 'dmin' => '2017-01-01', - 'dmax' => '' - ); - $this->heskSettings['custom_fields']['custom1'] = $customField; - $this->ticketRequest->customFields[1] = '2016-12-31'; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::DATE_BEFORE_MIN::MIN:2017-01-01::ENTERED:2016-12-31'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithDateThatIsAfterMaxDate() { - //-- Arrange - $customField = array(); - $customField['req'] = 1; - $customField['type'] = CustomField::DATE; - $customField['use'] = 1; - $customField['category'] = array(); - $customField['value'] = array( - 'dmin' => '', - 'dmax' => '2017-01-01' - ); - $this->heskSettings['custom_fields']['custom1'] = $customField; - $this->ticketRequest->customFields[1] = '2017-01-02'; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::DATE_AFTER_MAX::MAX:2017-01-01::ENTERED:2017-01-02'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithEmailThatIsInvalid() { - //-- Arrange - $customField = array(); - $customField['req'] = 1; - $customField['type'] = CustomField::EMAIL; - $customField['use'] = 1; - $customField['category'] = array(); - $customField['value'] = array( - 'multiple' => 0 - ); - $this->heskSettings['custom_fields']['custom1'] = $customField; - $this->ticketRequest->customFields[1] = 'invalid@'; - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['CUSTOM_FIELD_1_INVALID::INVALID_EMAIL'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithABannedEmail() { - //-- Arrange - $this->ticketRequest->email = 'some@banned.email'; - $this->banRetriever->method('isEmailBanned') - ->with($this->ticketRequest->email, $this->heskSettings) - ->willReturn(true); - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['EMAIL_BANNED'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } - - function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWhenTheyAreMaxedOut() { - //-- Arrange - $this->ticketRequest->email = 'some@maxedout.email'; - $this->ticketValidators->method('isCustomerAtMaxTickets') - ->with($this->ticketRequest->email, $this->heskSettings) - ->willReturn(true); - - //-- Act - $exceptionThrown = false; - try { - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, - $this->heskSettings, - $this->modsForHeskSettings, - $this->userContext); - } catch (ValidationException $e) { - //-- Assert (1/2) - $exceptionThrown = true; - $this->assertArraySubset(['EMAIL_AT_MAX_OPEN_TICKETS'], $e->validationModel->errorKeys); - } - - //-- Assert (2/2) - $this->assertThat($exceptionThrown, $this->equalTo(true)); - } -} diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php new file mode 100644 index 00000000..f46f8faf --- /dev/null +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -0,0 +1,108 @@ +ticketGateway = $this->createMock(TicketGateway::class); + $this->newTicketValidator = $this->createMock(NewTicketValidator::class); + $this->trackingIdGenerator = $this->createMock(TrackingIdGenerator::class); + + $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, $this->ticketGateway); + + $this->ticketRequest = new CreateTicketByCustomerModel(); + $this->ticketRequest->name = 'Name'; + $this->ticketRequest->email = 'some@e.mail'; + $this->ticketRequest->category = 1; + $this->ticketRequest->priority = Priority::HIGH; + $this->ticketRequest->subject = 'Subject'; + $this->ticketRequest->message = 'Message'; + $this->ticketRequest->customFields = array(); + $this->heskSettings = array( + 'multi_eml' => false, + 'cust_urgency' => false, + 'require_subject' => 1, + 'require_message' => 1, + 'custom_fields' => array(), + ); + $this->modsForHeskSettings = array(); + $this->userContext = new UserContext(); + + $this->newTicketValidator->method('validateNewTicketForCustomer')->willReturn(new ValidationModel()); + $this->trackingIdGenerator->method('generateTrackingId')->willReturn('123-456-7890'); + } + + function testItSavesTheTicketToTheDatabase() { + //-- Assert + $this->ticketGateway->expects($this->once())->method('createTicket'); + + //-- Act + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + } + + function testItSetsTheTrackingIdOnTheTicket() { + //-- Act + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + + //-- Assert + self::assertThat($ticket->trackingId, self::equalTo('123-456-7890')); + } +} From 489f191a13deba8ebe5ffb3daef6bbe498f51c50 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Feb 2017 22:10:42 -0500 Subject: [PATCH 041/192] Majority of create ticket logic done. Just need to add attachments(?) and retriever SQL-specific fields --- api/BusinessLogic/Tickets/Autoassigner.php | 21 ++++++ .../Tickets/CreateTicketByCustomerModel.php | 9 ++- api/BusinessLogic/Tickets/Ticket.php | 8 +-- api/BusinessLogic/Tickets/TicketCreator.php | 32 +++++++-- api/DataAccess/Tickets/TicketGateway.php | 3 + .../CreateTicketForCustomerTest.php | 66 ++++++++++++++++++- 6 files changed, 124 insertions(+), 15 deletions(-) create mode 100644 api/BusinessLogic/Tickets/Autoassigner.php diff --git a/api/BusinessLogic/Tickets/Autoassigner.php b/api/BusinessLogic/Tickets/Autoassigner.php new file mode 100644 index 00000000..ae9faa80 --- /dev/null +++ b/api/BusinessLogic/Tickets/Autoassigner.php @@ -0,0 +1,21 @@ +newTicketValidator = $newTicketValidator; $this->trackingIdGenerator = $trackingIdGenerator; + $this->autoassigner = $autoassigner; $this->ticketGateway = $ticketGateway; } @@ -50,15 +56,27 @@ class TicketCreator { $ticket = new Ticket(); $ticket->trackingId = $this->trackingIdGenerator->generateTrackingId($heskSettings); - //-- TODO suggested kb articles - - //-- TODO owner/autoassign logic + if ($heskSettings['autoassign']) { + $ticket->ownerId = $this->autoassigner->getNextUserForTicket($ticketRequest->category, $heskSettings); + } - //-- TODO latitude/longitude + // Transform one-to-one properties + $ticket->name = $ticketRequest->name; + $ticket->email = $ticketRequest->email; + $ticket->priorityId = $ticketRequest->priority; + $ticket->categoryId = $ticketRequest->category; + $ticket->subject = $ticketRequest->subject; + $ticket->message = $ticketRequest->message; + $ticket->usesHtml = $ticketRequest->html; + $ticket->customFields = $ticketRequest->customFields; + $ticket->location = $ticketRequest->location; + $ticket->suggestedArticles = $ticketRequest->suggestedKnowledgebaseArticleIds; + $ticket->userAgent = $ticketRequest->userAgent; + $ticket->screenResolution = $ticketRequest->screenResolution; - //-- TODO HTML flag + $ticket = $this->ticketGateway->createTicket($ticket, $heskSettings); - $this->ticketGateway->createTicket($ticket, $heskSettings); + //-- TODO get SQL-generated fields return $ticket; } diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index f2101c7a..50c6e7ff 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -86,6 +86,7 @@ class TicketGateway extends CommonDao { /** * @param $ticket Ticket * @param $heskSettings + * @return Ticket */ function createTicket($ticket, $heskSettings) { global $hesklang; @@ -186,5 +187,7 @@ class TicketGateway extends CommonDao { {$customWhat} ) "; + + return $ticket; } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index f46f8faf..8b54b62e 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -10,6 +10,7 @@ namespace BusinessLogic\Tickets\TicketCreatorTests; use BusinessLogic\Security\UserContext; +use BusinessLogic\Tickets\Autoassigner; use BusinessLogic\Tickets\CreateTicketByCustomerModel; use BusinessLogic\Tickets\NewTicketValidator; use BusinessLogic\Tickets\TicketCreator; @@ -36,6 +37,11 @@ class CreateTicketTest extends TestCase { */ private $trackingIdGenerator; + /** + * @var $autoassigner \PHPUnit_Framework_MockObject_MockObject + */ + private $autoassigner; + /** * @var $ticketRequest CreateTicketByCustomerModel */ @@ -65,8 +71,9 @@ class CreateTicketTest extends TestCase { $this->ticketGateway = $this->createMock(TicketGateway::class); $this->newTicketValidator = $this->createMock(NewTicketValidator::class); $this->trackingIdGenerator = $this->createMock(TrackingIdGenerator::class); + $this->autoassigner = $this->createMock(Autoassigner::class); - $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, $this->ticketGateway); + $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, $this->autoassigner, $this->ticketGateway); $this->ticketRequest = new CreateTicketByCustomerModel(); $this->ticketRequest->name = 'Name'; @@ -82,12 +89,15 @@ class CreateTicketTest extends TestCase { 'require_subject' => 1, 'require_message' => 1, 'custom_fields' => array(), + 'autoassign' => 0, ); $this->modsForHeskSettings = array(); $this->userContext = new UserContext(); $this->newTicketValidator->method('validateNewTicketForCustomer')->willReturn(new ValidationModel()); $this->trackingIdGenerator->method('generateTrackingId')->willReturn('123-456-7890'); + $this->autoassigner->method('getNextUserForTicket')->willReturn(1); + $this->ticketGateway->method('createTicket')->will($this->returnArgument(0)); } function testItSavesTheTicketToTheDatabase() { @@ -105,4 +115,58 @@ class CreateTicketTest extends TestCase { //-- Assert self::assertThat($ticket->trackingId, self::equalTo('123-456-7890')); } + + function testItSetsTheNextUserForAutoassign() { + //-- Arrange + $this->heskSettings['autoassign'] = 1; + + //-- Act + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + + //-- Assert + self::assertThat($ticket->ownerId, self::equalTo(1)); + } + + function testItDoesntCallTheAutoassignerWhenDisabledInHesk() { + //-- Act + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + + //-- Assert + self::assertThat($ticket->ownerId, self::equalTo(null)); + } + + function testItTransformsTheBasicProperties() { + //-- Arrange + $this->ticketRequest->name = 'Name'; + $this->ticketRequest->email = 'some@email.test'; + $this->ticketRequest->priority = Priority::MEDIUM; + $this->ticketRequest->category = 1; + $this->ticketRequest->subject = 'Subject'; + $this->ticketRequest->message = 'Message'; + $this->ticketRequest->html = false; + $this->ticketRequest->customFields = array( + 1 => 'something' + ); + $this->ticketRequest->location = ['10.157', '-10.177']; + $this->ticketRequest->suggestedKnowledgebaseArticleIds = [1, 2, 3]; + $this->ticketRequest->userAgent = 'UserAgent'; + $this->ticketRequest->screenResolution = [1400, 900]; + + //-- Act + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + + //-- Assert + self::assertThat($ticket->name, self::equalTo($this->ticketRequest->name)); + self::assertThat($ticket->email, self::equalTo($this->ticketRequest->email)); + self::assertThat($ticket->priorityId, self::equalTo($this->ticketRequest->priority)); + self::assertThat($ticket->categoryId, self::equalTo($this->ticketRequest->category)); + self::assertThat($ticket->subject, self::equalTo($this->ticketRequest->subject)); + self::assertThat($ticket->message, self::equalTo($this->ticketRequest->message)); + self::assertThat($ticket->usesHtml, self::equalTo($this->ticketRequest->html)); + self::assertThat($ticket->customFields[1], self::equalTo($this->ticketRequest->customFields[1])); + self::assertThat($ticket->location, self::equalTo($this->ticketRequest->location)); + self::assertThat($ticket->suggestedArticles, self::equalTo($this->ticketRequest->suggestedKnowledgebaseArticleIds)); + self::assertThat($ticket->userAgent, self::equalTo($this->ticketRequest->userAgent)); + self::assertThat($ticket->screenResolution, self::equalTo($this->ticketRequest->screenResolution)); + } } From dc0c8a351b3747d659b2fb38194976cf82dd5788 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 13 Feb 2017 12:52:43 -0500 Subject: [PATCH 042/192] I think I finished the ticket creator --- api/BusinessLogic/Tickets/TicketCreator.php | 5 ++-- .../Tickets/TicketGatewayGeneratedFields.php | 15 ++++++++++++ api/DataAccess/Tickets/TicketGateway.php | 14 +++++++++-- .../CreateTicketForCustomerTest.php | 23 ++++++++++++++++++- 4 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 api/BusinessLogic/Tickets/TicketGatewayGeneratedFields.php diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 3aac73fc..4897a8f4 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -74,9 +74,10 @@ class TicketCreator { $ticket->userAgent = $ticketRequest->userAgent; $ticket->screenResolution = $ticketRequest->screenResolution; - $ticket = $this->ticketGateway->createTicket($ticket, $heskSettings); + $ticketGatewayGeneratedFields = $this->ticketGateway->createTicket($ticket, $heskSettings); - //-- TODO get SQL-generated fields + $ticket->dateCreated = $ticketGatewayGeneratedFields->dateCreated; + $ticket->lastChanged = $ticketGatewayGeneratedFields->dateModified; return $ticket; } diff --git a/api/BusinessLogic/Tickets/TicketGatewayGeneratedFields.php b/api/BusinessLogic/Tickets/TicketGatewayGeneratedFields.php new file mode 100644 index 00000000..00d9521b --- /dev/null +++ b/api/BusinessLogic/Tickets/TicketGatewayGeneratedFields.php @@ -0,0 +1,15 @@ +dateCreated = $row['dt']; + $generatedFields->dateModified = $row['lastchange']; + + return $generatedFields; } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index 8b54b62e..46f16dd2 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -14,6 +14,7 @@ use BusinessLogic\Tickets\Autoassigner; use BusinessLogic\Tickets\CreateTicketByCustomerModel; use BusinessLogic\Tickets\NewTicketValidator; use BusinessLogic\Tickets\TicketCreator; +use BusinessLogic\Tickets\TicketGatewayGeneratedFields; use BusinessLogic\Tickets\TrackingIdGenerator; use BusinessLogic\ValidationModel; use Core\Constants\Priority; @@ -67,6 +68,11 @@ class CreateTicketTest extends TestCase { */ private $userContext; + /** + * @var $ticketGatewayGeneratedFields TicketGatewayGeneratedFields + */ + private $ticketGatewayGeneratedFields; + protected function setUp() { $this->ticketGateway = $this->createMock(TicketGateway::class); $this->newTicketValidator = $this->createMock(NewTicketValidator::class); @@ -97,7 +103,8 @@ class CreateTicketTest extends TestCase { $this->newTicketValidator->method('validateNewTicketForCustomer')->willReturn(new ValidationModel()); $this->trackingIdGenerator->method('generateTrackingId')->willReturn('123-456-7890'); $this->autoassigner->method('getNextUserForTicket')->willReturn(1); - $this->ticketGateway->method('createTicket')->will($this->returnArgument(0)); + $this->ticketGatewayGeneratedFields = new TicketGatewayGeneratedFields(); + $this->ticketGateway->method('createTicket')->willReturn($this->ticketGatewayGeneratedFields); } function testItSavesTheTicketToTheDatabase() { @@ -169,4 +176,18 @@ class CreateTicketTest extends TestCase { self::assertThat($ticket->userAgent, self::equalTo($this->ticketRequest->userAgent)); self::assertThat($ticket->screenResolution, self::equalTo($this->ticketRequest->screenResolution)); } + + function testItReturnsTheGeneratedPropertiesOnTheTicket() { + //-- Arrange + $this->ticketGatewayGeneratedFields->dateCreated = 'date created'; + $this->ticketGatewayGeneratedFields->dateModified = 'date modified'; + + + //-- Act + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + + //-- Assert + self::assertThat($ticket->dateCreated, self::equalTo($this->ticketGatewayGeneratedFields->dateCreated)); + self::assertThat($ticket->lastChanged, self::equalTo($this->ticketGatewayGeneratedFields->dateModified)); + } } From 843528252b013c77893d5def4e9643b2d3c861d3 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 13 Feb 2017 13:02:40 -0500 Subject: [PATCH 043/192] Getting started on POST for ticket controller --- api/Controllers/Tickets/TicketController.php | 12 ++++++++++++ api/autoload.php | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/api/Controllers/Tickets/TicketController.php b/api/Controllers/Tickets/TicketController.php index 57125ccb..daf12f72 100644 --- a/api/Controllers/Tickets/TicketController.php +++ b/api/Controllers/Tickets/TicketController.php @@ -2,6 +2,7 @@ namespace Controllers\Tickets; +use BusinessLogic\Tickets\TicketCreator; use BusinessLogic\Tickets\TicketRetriever; @@ -14,4 +15,15 @@ class TicketController { output($ticketRetriever->getTicketById($id, $hesk_settings, $userContext)); } + + function post() { + global $applicationContext, $hesk_settings, $modsForHeskSettings, $userContext; + + /* @var $ticketCreator TicketCreator */ + $ticketCreator = $applicationContext->get[TicketCreator::class]; + + //-- TODO Parse POST data + + $ticketCreator->createTicketByCustomer(null, $hesk_settings, $modsForHeskSettings, $userContext); + } } \ No newline at end of file diff --git a/api/autoload.php b/api/autoload.php index c00cea7b..bb2c67a4 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -17,4 +17,5 @@ hesk_load_api_database_functions(); require_once(__DIR__ . '/../inc/custom_fields.inc.php'); // Load the ApplicationContext -$applicationContext = new \ApplicationContext(); \ No newline at end of file +$applicationContext = new \ApplicationContext(); +$modsForHeskSettings = mfh_getSettings(); \ No newline at end of file From 1cb4209be244233f4ac0a26f1ee1c9476d29c78b Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 13 Feb 2017 22:34:15 -0500 Subject: [PATCH 044/192] Working more on create ticket endpoint --- api/ApplicationContext.php | 37 ++++++++++++------ api/BusinessLogic/Helpers.php | 6 +++ .../CustomFields/CustomFieldValidator.php | 4 +- .../Tickets/NewTicketValidator.php | 12 +++--- .../Categories/CategoryController.php | 4 +- api/Controllers/JsonRetriever.php | 22 +++++++++++ api/Controllers/Tickets/TicketController.php | 39 +++++++++++++++++-- api/DataAccess/Security/BanGateway.php | 8 ++-- api/Link.php | 9 +---- api/autoload.php | 2 +- api/index.php | 5 ++- 11 files changed, 109 insertions(+), 39 deletions(-) create mode 100644 api/Controllers/JsonRetriever.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index aa09011c..08c26c40 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -4,7 +4,12 @@ use BusinessLogic\Categories\CategoryRetriever; use BusinessLogic\Security\BanRetriever; use BusinessLogic\Security\UserContextBuilder; +use BusinessLogic\Tickets\Autoassigner; use BusinessLogic\Tickets\TicketRetriever; +use BusinessLogic\Tickets\TicketCreator; +use BusinessLogic\Tickets\NewTicketValidator; +use BusinessLogic\Tickets\TicketValidators; +use BusinessLogic\Tickets\TrackingIdGenerator; use DataAccess\Categories\CategoryGateway; use DataAccess\Security\BanGateway; use DataAccess\Security\UserGateway; @@ -17,20 +22,30 @@ class ApplicationContext { function __construct() { $this->get = array(); - // Categories - $this->get['CategoryGateway'] = new CategoryGateway(); - $this->get['CategoryRetriever'] = new CategoryRetriever($this->get['CategoryGateway']); + // User Context + $this->get[UserGateway::class] = new UserGateway(); + $this->get[UserContextBuilder::class] = new UserContextBuilder($this->get[UserGateway::class]); - // Tickets - $this->get['TicketGateway'] = new TicketGateway(); - $this->get['TicketRetriever'] = new TicketRetriever($this->get['TicketGateway']); + // Categories + $this->get[CategoryGateway::class] = new CategoryGateway(); + $this->get[CategoryRetriever::class] = new CategoryRetriever($this->get[CategoryGateway::class]); // Bans - $this->get['BanGateway'] = new BanGateway(); - $this->get['BanRetriever'] = new BanRetriever($this->get['BanGateway']); + $this->get[BanGateway::class] = new BanGateway(); + $this->get[BanRetriever::class] = new BanRetriever($this->get[BanGateway::class]); - // User Context - $this->get['UserGateway'] = new UserGateway(); - $this->get['UserContextBuilder'] = new UserContextBuilder($this->get['UserGateway']); + // Tickets + $this->get[TicketGateway::class] = new TicketGateway(); + $this->get[TicketRetriever::class] = new TicketRetriever($this->get[TicketGateway::class]); + $this->get[TicketValidators::class] = new TicketValidators($this->get[TicketGateway::class]); + $this->get[TrackingIdGenerator::class] = new TrackingIdGenerator($this->get[TicketGateway::class]); + $this->get[Autoassigner::class] = new Autoassigner(); + $this->get[NewTicketValidator::class] = new NewTicketValidator($this->get[CategoryRetriever::class], + $this->get[BanRetriever::class], + $this->get[TicketValidators::class]); + $this->get[TicketCreator::class] = new TicketCreator($this->get[NewTicketValidator::class], + $this->get[TrackingIdGenerator::class], + $this->get[Autoassigner::class], + $this->get[TicketGateway::class]); } } \ No newline at end of file diff --git a/api/BusinessLogic/Helpers.php b/api/BusinessLogic/Helpers.php index cb5bf624..61f6af2d 100644 --- a/api/BusinessLogic/Helpers.php +++ b/api/BusinessLogic/Helpers.php @@ -20,4 +20,10 @@ class Helpers { static function hashToken($token) { return hash('sha512', $token); } + + static function safeArrayGet($array, $key) { + return $array !== null && array_key_exists($key, $array) + ? $array[$key] + : null; + } } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php b/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php index 037d828e..77bbd336 100644 --- a/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php +++ b/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php @@ -18,7 +18,7 @@ class CustomFieldValidator { return false; } - return count($customField['category']) === 0 || - in_array($categoryId, $customField['category']); + return count($customField['Categories']) === 0 || + in_array($categoryId, $customField['Categories']); } } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/NewTicketValidator.php b/api/BusinessLogic/Tickets/NewTicketValidator.php index 5f6d6a23..61af85e8 100644 --- a/api/BusinessLogic/Tickets/NewTicketValidator.php +++ b/api/BusinessLogic/Tickets/NewTicketValidator.php @@ -1,10 +1,4 @@ $value) { $customFieldNumber = intval(str_replace('custom', '', $key)); + + //TODO test this + if (!array_key_exists($customFieldNumber, $ticketRequest->customFields)) { + continue; + } + if ($value['use'] == 1 && CustomFieldValidator::isCustomFieldInCategory($customFieldNumber, intval($ticketRequest->category), false, $heskSettings)) { $custom_field_value = $ticketRequest->customFields[$customFieldNumber]; if (empty($custom_field_value)) { diff --git a/api/Controllers/Categories/CategoryController.php b/api/Controllers/Categories/CategoryController.php index acbfe7bb..6265d2fd 100644 --- a/api/Controllers/Categories/CategoryController.php +++ b/api/Controllers/Categories/CategoryController.php @@ -2,7 +2,7 @@ namespace Controllers\Category; -use BusinessLogic\Category\CategoryRetriever; +use BusinessLogic\Categories\CategoryRetriever; class CategoryController { function get($id) { @@ -18,7 +18,7 @@ class CategoryController { global $hesk_settings, $applicationContext, $userContext; /* @var $categoryRetriever CategoryRetriever */ - $categoryRetriever = $applicationContext->get['CategoryRetriever']; + $categoryRetriever = $applicationContext->get[CategoryRetriever::class]; return $categoryRetriever->getAllCategories($hesk_settings, $userContext); } diff --git a/api/Controllers/JsonRetriever.php b/api/Controllers/JsonRetriever.php new file mode 100644 index 00000000..ee590448 --- /dev/null +++ b/api/Controllers/JsonRetriever.php @@ -0,0 +1,22 @@ +get['TicketRetriever']; + $ticketRetriever = $applicationContext->get[TicketRetriever::class]; output($ticketRetriever->getTicketById($id, $hesk_settings, $userContext)); } @@ -22,8 +25,38 @@ class TicketController { /* @var $ticketCreator TicketCreator */ $ticketCreator = $applicationContext->get[TicketCreator::class]; - //-- TODO Parse POST data + $jsonRequest = JsonRetriever::getJsonData(); - $ticketCreator->createTicketByCustomer(null, $hesk_settings, $modsForHeskSettings, $userContext); + $ticketCreator->createTicketByCustomer($this->buildTicketRequestFromJson($jsonRequest), $hesk_settings, $modsForHeskSettings, $userContext); + } + + /** + * @param $json array + * @return CreateTicketByCustomerModel + */ + private function buildTicketRequestFromJson($json) { + $ticketRequest = new CreateTicketByCustomerModel(); + $ticketRequest->name = Helpers::safeArrayGet($json, 'name'); + $ticketRequest->email = Helpers::safeArrayGet($json, 'email'); + $ticketRequest->category = Helpers::safeArrayGet($json, 'category'); + $ticketRequest->priority = Helpers::safeArrayGet($json, 'priority'); + $ticketRequest->subject = Helpers::safeArrayGet($json, 'subject'); + $ticketRequest->message = Helpers::safeArrayGet($json, 'message'); + $ticketRequest->html = Helpers::safeArrayGet($json, 'html'); + $ticketRequest->location = Helpers::safeArrayGet($json, 'location'); + $ticketRequest->suggestedKnowledgebaseArticleIds = Helpers::safeArrayGet($json, 'suggestedArticles'); + $ticketRequest->userAgent = Helpers::safeArrayGet($json, 'userAgent'); + $ticketRequest->screenResolution = Helpers::safeArrayGet($json, 'screenResolution'); + $ticketRequest->customFields = array(); + + $jsonCustomFields = Helpers::safeArrayGet($json, 'customFields'); + + if ($jsonCustomFields !== null && !empty($jsonCustomFields)) { + foreach ($jsonCustomFields as $key => $value) { + $ticketRequest->customFields[intval($key)] = $value; + } + } + + return $ticketRequest; } } \ No newline at end of file diff --git a/api/DataAccess/Security/BanGateway.php b/api/DataAccess/Security/BanGateway.php index 78139819..74cacdb7 100644 --- a/api/DataAccess/Security/BanGateway.php +++ b/api/DataAccess/Security/BanGateway.php @@ -19,8 +19,8 @@ class BanGateway extends CommonDao { $rs = hesk_dbQuery("SELECT `bans`.`id` AS `id`, `bans`.`email` AS `email`, `users`.`id` AS `banned_by`, `bans`.`dt` AS `dt` - FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "_banned_emails` AS `bans` - LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "_users` AS `users` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "banned_emails` AS `bans` + LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` AS `users` ON `bans`.`banned_by` = `users`.`id` AND `users`.`active` = '1'"); @@ -51,8 +51,8 @@ class BanGateway extends CommonDao { $rs = hesk_dbQuery("SELECT `bans`.`id` AS `id`, `bans`.`ip_from` AS `ip_from`, `bans`.`ip_to` AS `ip_to`, `bans`.`ip_display` AS `ip_display`, `users`.`id` AS `banned_by`, `bans`.`dt` AS `dt` - FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "_banned_ips` AS `bans` - LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "_users` AS `users` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "banned_ips` AS `bans` + LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` AS `users` ON `bans`.`banned_by` = `users`.`id` AND `users`.`active` = '1'"); diff --git a/api/Link.php b/api/Link.php index 090e666d..6ae9a884 100644 --- a/api/Link.php +++ b/api/Link.php @@ -177,14 +177,7 @@ class Link if( isset( $instanceOfHandler ) ) { if( method_exists( $instanceOfHandler, $method ) ) { - try { - $newParams = call_user_func_array( array( $instanceOfHandler, $method ), $matched ); - } catch ( Exception $exception ){ - $string = str_replace("\n", ' ', var_export($exception, TRUE)); - error_log($string); //Log to error file only if display errors has been declared - header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); - die(); - } + $newParams = call_user_func_array( array( $instanceOfHandler, $method ), $matched ); } } if( isset( $newParams ) && $newParams ) { diff --git a/api/autoload.php b/api/autoload.php index bb2c67a4..1a2db8c2 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -18,4 +18,4 @@ require_once(__DIR__ . '/../inc/custom_fields.inc.php'); // Load the ApplicationContext $applicationContext = new \ApplicationContext(); -$modsForHeskSettings = mfh_getSettings(); \ No newline at end of file +//$modsForHeskSettings = mfh_getSettings(); \ No newline at end of file diff --git a/api/index.php b/api/index.php index 27c7eeb3..07e97f30 100644 --- a/api/index.php +++ b/api/index.php @@ -28,7 +28,7 @@ function buildUserContext($xAuthToken) { global $applicationContext, $userContext, $hesk_settings; /* @var $userContextBuilder \BusinessLogic\Security\UserContextBuilder */ - $userContextBuilder = $applicationContext->get['UserContextBuilder']; + $userContextBuilder = $applicationContext->get[\BusinessLogic\Security\UserContextBuilder::class]; $userContext = $userContextBuilder->buildUserContext($xAuthToken, $hesk_settings); } @@ -52,7 +52,7 @@ function exceptionHandler($exception) { $castedException = $exception; print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); } else { - print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + print_error("Fought an uncaught exception of type " . get_class($exception), sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); } } @@ -85,6 +85,7 @@ Link::all(array( '/v1/categories/{i}' => '\Controllers\Category\CategoryController', // Tickets '/v1/tickets/{i}' => '\Controllers\Tickets\TicketController', + '/v1/tickets' => '\Controllers\Tickets\TicketController', // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' From 1b6cca8ddfee05b4a5f6d837304966030e3ca4dd Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 14 Feb 2017 12:45:02 -0500 Subject: [PATCH 045/192] Create ticket endpoint now working, but needs some changes --- api/BusinessLogic/Exceptions/ValidationException.php | 10 ++++------ api/BusinessLogic/Tickets/Ticket.php | 7 +++++-- api/Controllers/JsonRetriever.php | 2 +- api/Controllers/Tickets/TicketController.php | 4 +++- api/DataAccess/Tickets/TicketGateway.php | 10 +++++----- api/index.php | 6 +++--- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/api/BusinessLogic/Exceptions/ValidationException.php b/api/BusinessLogic/Exceptions/ValidationException.php index aa382328..6ed8b552 100644 --- a/api/BusinessLogic/Exceptions/ValidationException.php +++ b/api/BusinessLogic/Exceptions/ValidationException.php @@ -5,19 +5,17 @@ namespace BusinessLogic\Exceptions; use BusinessLogic\ValidationModel; use Exception; -class ValidationException extends Exception { - public $validationModel; - +class ValidationException extends ApiFriendlyException { /** * ValidationException constructor. * @param ValidationModel $validationModel The validation model * @throws Exception If the validationModel's errorKeys is empty */ function __construct($validationModel) { - if (count($validationModel->errorKeys) === 0) { - throw new Exception('Tried to throw a ValidationException, but the validation model was valid!'); + if (count($validationModel->errorKeys) === 0 || $validationModel->valid) { + throw new Exception('Tried to throw a ValidationException, but the validation model was valid or had 0 error keys!'); } - $this->validationModel = $validationModel; + parent::__construct(implode(",", $validationModel->errorKeys), "Validation Failed. Error keys are available in the message section.", 400); } } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/Ticket.php b/api/BusinessLogic/Tickets/Ticket.php index 7a8f7b1f..dd480fd6 100644 --- a/api/BusinessLogic/Tickets/Ticket.php +++ b/api/BusinessLogic/Tickets/Ticket.php @@ -238,8 +238,11 @@ class Ticket { function getAttachmentsForDatabase() { $attachmentArray = array(); - foreach ($this->attachments as $attachment) { - $attachmentArray[] = $attachment->id . '#' . $attachment->fileName . '#' . $attachment->savedName; + + if ($this->attachments !== null) { + foreach ($this->attachments as $attachment) { + $attachmentArray[] = $attachment->id . '#' . $attachment->fileName . '#' . $attachment->savedName; + } } return implode(',', $attachmentArray); diff --git a/api/Controllers/JsonRetriever.php b/api/Controllers/JsonRetriever.php index ee590448..74711032 100644 --- a/api/Controllers/JsonRetriever.php +++ b/api/Controllers/JsonRetriever.php @@ -17,6 +17,6 @@ class JsonRetriever { */ static function getJsonData() { $json = file_get_contents('php://input'); - return json_decode($json); + return json_decode($json, true); } } \ No newline at end of file diff --git a/api/Controllers/Tickets/TicketController.php b/api/Controllers/Tickets/TicketController.php index 2d0bdf95..294882c5 100644 --- a/api/Controllers/Tickets/TicketController.php +++ b/api/Controllers/Tickets/TicketController.php @@ -27,7 +27,9 @@ class TicketController { $jsonRequest = JsonRetriever::getJsonData(); - $ticketCreator->createTicketByCustomer($this->buildTicketRequestFromJson($jsonRequest), $hesk_settings, $modsForHeskSettings, $userContext); + $ticket = $ticketCreator->createTicketByCustomer($this->buildTicketRequestFromJson($jsonRequest), $hesk_settings, $modsForHeskSettings, $userContext); + + return output($ticket); } /** diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index 86efcb23..5c6d7cb0 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -124,10 +124,10 @@ class TicketGateway extends CommonDao { $userAgent = $ticket->userAgent !== null ? $ticket->userAgent : ''; $screenResolutionWidth = $ticket->screenResolution !== null && isset($ticket->screenResolution[0]) - && $ticket->screenResolution[0] !== null ? intval($ticket->screenResolution[0]) : ''; + && $ticket->screenResolution[0] !== null ? intval($ticket->screenResolution[0]) : 'NULL'; $screenResolutionHeight = $ticket->screenResolution !== null && isset($ticket->screenResolution[1]) - && $ticket->screenResolution[1] !== null ? intval($ticket->screenResolution[1]) : ''; + && $ticket->screenResolution[1] !== null ? intval($ticket->screenResolution[1]) : 'NULL'; $sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` ( @@ -169,14 +169,14 @@ class TicketGateway extends CommonDao { '" . hesk_dbEscape($ticket->message) . "', NOW(), NOW(), - " . $suggestedArticles . ", + '" . $suggestedArticles . "', '" . hesk_dbEscape($ticket->ipAddress) . "', '" . hesk_dbEscape($language) . "', '" . intval($ticket->openedBy) . "', '" . intval($ticket->ownerId) . "', '" . hesk_dbEscape($ticket->getAttachmentsForDatabase()) . "', '', - '" . intval($ticket->statusId) . "', + " . intval($ticket->statusId) . ", '" . hesk_dbEscape($latitude) . "', '" . hesk_dbEscape($longitude) . "', '" . hesk_dbEscape($ticket->usesHtml) . "', @@ -192,7 +192,7 @@ class TicketGateway extends CommonDao { hesk_dbQuery($sql); $rs = hesk_dbQuery('SELECT `dt`, `lastchange` FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . 'tickets` WHERE `id` = ' . intval(hesk_dbInsertID())); - $row = hesk_dbFetchRow($rs); + $row = hesk_dbFetchAssoc($rs); $generatedFields = new TicketGatewayGeneratedFields(); $generatedFields->dateCreated = $row['dt']; diff --git a/api/index.php b/api/index.php index 07e97f30..d6932a50 100644 --- a/api/index.php +++ b/api/index.php @@ -41,13 +41,13 @@ function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { * @param $exception Exception */ function exceptionHandler($exception) { - if (exceptionIsOfType($exception, 'ApiFriendlyException')) { + if (exceptionIsOfType($exception, \BusinessLogic\Exceptions\ApiFriendlyException::class)) { /* @var $castedException \BusinessLogic\Exceptions\ApiFriendlyException */ $castedException = $exception; print_error($castedException->title, $castedException->getMessage(), $castedException->httpResponseCode); } else { - if (exceptionIsOfType($exception, 'SQLException')) { + if (exceptionIsOfType($exception, \Core\Exceptions\SQLException::class)) { /* @var $castedException \Core\Exceptions\SQLException */ $castedException = $exception; print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); @@ -66,7 +66,7 @@ function exceptionHandler($exception) { * @return bool */ function exceptionIsOfType($exception, $class) { - return strpos(get_class($exception), $class) !== false; + return is_a($exception, $class); } function fatalErrorShutdownHandler() { From 31ced3f57268f4a7a4d00f24065852c9bc1f2534 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 14 Feb 2017 22:03:46 -0500 Subject: [PATCH 046/192] Add somre more fields --- .../Tickets/CreateTicketByCustomerModel.php | 10 ++++++ .../CustomFields/CustomFieldValidator.php | 4 +-- .../Tickets/NewTicketValidator.php | 5 +++ api/BusinessLogic/Tickets/TicketCreator.php | 2 ++ api/Controllers/Tickets/TicketController.php | 2 ++ api/DataAccess/Tickets/TicketGateway.php | 12 +++---- .../Tickets/NewTicketValidatorTest.php | 32 +++++++++++++++++++ .../CreateTicketForCustomerTest.php | 4 +++ 8 files changed, 61 insertions(+), 10 deletions(-) diff --git a/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php b/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php index 30980251..25a577f0 100644 --- a/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php +++ b/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php @@ -62,4 +62,14 @@ class CreateTicketByCustomerModel { * @var int[]|null */ public $screenResolution; + + /** + * @var int|null + */ + public $ipAddress; + + /** + * @var string + */ + public $language; } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php b/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php index 77bbd336..037d828e 100644 --- a/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php +++ b/api/BusinessLogic/Tickets/CustomFields/CustomFieldValidator.php @@ -18,7 +18,7 @@ class CustomFieldValidator { return false; } - return count($customField['Categories']) === 0 || - in_array($categoryId, $customField['Categories']); + return count($customField['category']) === 0 || + in_array($categoryId, $customField['category']); } } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/NewTicketValidator.php b/api/BusinessLogic/Tickets/NewTicketValidator.php index 61af85e8..bb80a1f8 100644 --- a/api/BusinessLogic/Tickets/NewTicketValidator.php +++ b/api/BusinessLogic/Tickets/NewTicketValidator.php @@ -122,6 +122,11 @@ class NewTicketValidator { $validationModel->errorKeys[] = 'EMAIL_AT_MAX_OPEN_TICKETS'; } + if ($ticketRequest->language === null || + $ticketRequest->language === '') { + $validationModel->errorKeys[] = 'MISSING_LANGUAGE'; + } + return $validationModel; } } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 4897a8f4..c723fafd 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -73,6 +73,8 @@ class TicketCreator { $ticket->suggestedArticles = $ticketRequest->suggestedKnowledgebaseArticleIds; $ticket->userAgent = $ticketRequest->userAgent; $ticket->screenResolution = $ticketRequest->screenResolution; + $ticket->ipAddress = $ticketRequest->ipAddress; + $ticket->language = $ticketRequest->language; $ticketGatewayGeneratedFields = $this->ticketGateway->createTicket($ticket, $heskSettings); diff --git a/api/Controllers/Tickets/TicketController.php b/api/Controllers/Tickets/TicketController.php index 294882c5..5b67fd2c 100644 --- a/api/Controllers/Tickets/TicketController.php +++ b/api/Controllers/Tickets/TicketController.php @@ -49,6 +49,8 @@ class TicketController { $ticketRequest->suggestedKnowledgebaseArticleIds = Helpers::safeArrayGet($json, 'suggestedArticles'); $ticketRequest->userAgent = Helpers::safeArrayGet($json, 'userAgent'); $ticketRequest->screenResolution = Helpers::safeArrayGet($json, 'screenResolution'); + $ticketRequest->ipAddress = Helpers::safeArrayGet($json, 'ip'); + $ticketRequest->language = Helpers::safeArrayGet($json, 'language'); $ticketRequest->customFields = array(); $jsonCustomFields = Helpers::safeArrayGet($json, 'customFields'); diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index 5c6d7cb0..ae5c2b4d 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -92,13 +92,6 @@ class TicketGateway extends CommonDao { function createTicket($ticket, $heskSettings) { global $hesklang; - // If language is not set or default, set it to NULL. - if ($ticket->language === null || empty($ticket->language)) { - $language = (!$heskSettings['can_sel_lang']) ? HESK_DEFAULT_LANGUAGE : hesk_dbEscape($hesklang['LANGUAGE']); - } else { - $language = $ticket->language; - } - $dueDate = $ticket->dueDate ? "'{$ticket->dueDate}'" : "NULL"; // Prepare SQL for custom fields $customWhere = ''; @@ -129,6 +122,9 @@ class TicketGateway extends CommonDao { && isset($ticket->screenResolution[1]) && $ticket->screenResolution[1] !== null ? intval($ticket->screenResolution[1]) : 'NULL'; + $ipAddress = $ticket->ipAddress !== null + && $ticket->ipAddress !== '' ? $ticket->ipAddress : ''; + $sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` ( `trackid`, @@ -171,7 +167,7 @@ class TicketGateway extends CommonDao { NOW(), '" . $suggestedArticles . "', '" . hesk_dbEscape($ticket->ipAddress) . "', - '" . hesk_dbEscape($language) . "', + '" . hesk_dbEscape($ticket->language) . "', '" . intval($ticket->openedBy) . "', '" . intval($ticket->ownerId) . "', '" . hesk_dbEscape($ticket->getAttachmentsForDatabase()) . "', diff --git a/api/Tests/BusinessLogic/Tickets/NewTicketValidatorTest.php b/api/Tests/BusinessLogic/Tickets/NewTicketValidatorTest.php index de978568..05f25c9d 100644 --- a/api/Tests/BusinessLogic/Tickets/NewTicketValidatorTest.php +++ b/api/Tests/BusinessLogic/Tickets/NewTicketValidatorTest.php @@ -429,4 +429,36 @@ class NewTicketValidatorTest extends TestCase { //-- Assert $this->assertArraySubset(['EMAIL_AT_MAX_OPEN_TICKETS'], $validationModel->errorKeys); } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithLanguageNull() { + //-- Arrange + $this->ticketRequest->language = null; + $this->ticketValidators->method('isCustomerAtMaxTickets') + ->with($this->ticketRequest->email, $this->heskSettings) + ->willReturn(false); + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['MISSING_LANGUAGE'], $validationModel->errorKeys); + } + + function testItAddsTheProperValidationErrorWhenTheCustomerSubmitsTicketWithLanguageBlank() { + //-- Arrange + $this->ticketRequest->language = ''; + $this->ticketValidators->method('isCustomerAtMaxTickets') + ->with($this->ticketRequest->email, $this->heskSettings) + ->willReturn(false); + + //-- Act + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($this->ticketRequest, + $this->heskSettings, + $this->userContext); + + //-- Assert + $this->assertArraySubset(['MISSING_LANGUAGE'], $validationModel->errorKeys); + } } diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index 46f16dd2..93f1c8c1 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -158,6 +158,8 @@ class CreateTicketTest extends TestCase { $this->ticketRequest->suggestedKnowledgebaseArticleIds = [1, 2, 3]; $this->ticketRequest->userAgent = 'UserAgent'; $this->ticketRequest->screenResolution = [1400, 900]; + $this->ticketRequest->ipAddress = ip2long('127.0.0.1'); + $this->ticketRequest->language = 'English'; //-- Act $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); @@ -175,6 +177,8 @@ class CreateTicketTest extends TestCase { self::assertThat($ticket->suggestedArticles, self::equalTo($this->ticketRequest->suggestedKnowledgebaseArticleIds)); self::assertThat($ticket->userAgent, self::equalTo($this->ticketRequest->userAgent)); self::assertThat($ticket->screenResolution, self::equalTo($this->ticketRequest->screenResolution)); + self::assertThat($ticket->ipAddress, self::equalTo($this->ticketRequest->ipAddress)); + self::assertThat($ticket->language, self::equalTo($this->ticketRequest->language)); } function testItReturnsTheGeneratedPropertiesOnTheTicket() { From c8485c0fa345f90e47a587ac9f1ade195dd42f0b Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 15 Feb 2017 22:01:15 -0500 Subject: [PATCH 047/192] Working on a status gateway to get the ticket's default status --- .../{security => Security}/BanRetriever.php | 0 .../{security => Security}/BannedEmail.php | 0 .../{security => Security}/BannedIp.php | 0 .../{security => Security}/UserContext.php | 0 .../UserContextBuilder.php | 0 .../UserContextNotifications.php | 0 .../UserContextPreferences.php | 0 api/BusinessLogic/Statuses/Closable.php | 17 +++++++++ .../Statuses/DefaultStatusForAction.php | 22 ++++++++++++ api/BusinessLogic/Statuses/Status.php | 36 +++++++++++++++++++ api/DataAccess/Statuses/StatusGateway.php | 19 ++++++++++ .../CreateTicketForCustomerTest.php | 2 +- 12 files changed, 95 insertions(+), 1 deletion(-) rename api/BusinessLogic/{security => Security}/BanRetriever.php (100%) rename api/BusinessLogic/{security => Security}/BannedEmail.php (100%) rename api/BusinessLogic/{security => Security}/BannedIp.php (100%) rename api/BusinessLogic/{security => Security}/UserContext.php (100%) rename api/BusinessLogic/{security => Security}/UserContextBuilder.php (100%) rename api/BusinessLogic/{security => Security}/UserContextNotifications.php (100%) rename api/BusinessLogic/{security => Security}/UserContextPreferences.php (100%) create mode 100644 api/BusinessLogic/Statuses/Closable.php create mode 100644 api/BusinessLogic/Statuses/DefaultStatusForAction.php create mode 100644 api/BusinessLogic/Statuses/Status.php create mode 100644 api/DataAccess/Statuses/StatusGateway.php diff --git a/api/BusinessLogic/security/BanRetriever.php b/api/BusinessLogic/Security/BanRetriever.php similarity index 100% rename from api/BusinessLogic/security/BanRetriever.php rename to api/BusinessLogic/Security/BanRetriever.php diff --git a/api/BusinessLogic/security/BannedEmail.php b/api/BusinessLogic/Security/BannedEmail.php similarity index 100% rename from api/BusinessLogic/security/BannedEmail.php rename to api/BusinessLogic/Security/BannedEmail.php diff --git a/api/BusinessLogic/security/BannedIp.php b/api/BusinessLogic/Security/BannedIp.php similarity index 100% rename from api/BusinessLogic/security/BannedIp.php rename to api/BusinessLogic/Security/BannedIp.php diff --git a/api/BusinessLogic/security/UserContext.php b/api/BusinessLogic/Security/UserContext.php similarity index 100% rename from api/BusinessLogic/security/UserContext.php rename to api/BusinessLogic/Security/UserContext.php diff --git a/api/BusinessLogic/security/UserContextBuilder.php b/api/BusinessLogic/Security/UserContextBuilder.php similarity index 100% rename from api/BusinessLogic/security/UserContextBuilder.php rename to api/BusinessLogic/Security/UserContextBuilder.php diff --git a/api/BusinessLogic/security/UserContextNotifications.php b/api/BusinessLogic/Security/UserContextNotifications.php similarity index 100% rename from api/BusinessLogic/security/UserContextNotifications.php rename to api/BusinessLogic/Security/UserContextNotifications.php diff --git a/api/BusinessLogic/security/UserContextPreferences.php b/api/BusinessLogic/Security/UserContextPreferences.php similarity index 100% rename from api/BusinessLogic/security/UserContextPreferences.php rename to api/BusinessLogic/Security/UserContextPreferences.php diff --git a/api/BusinessLogic/Statuses/Closable.php b/api/BusinessLogic/Statuses/Closable.php new file mode 100644 index 00000000..58c409ae --- /dev/null +++ b/api/BusinessLogic/Statuses/Closable.php @@ -0,0 +1,17 @@ +ticketRequest->suggestedKnowledgebaseArticleIds = [1, 2, 3]; $this->ticketRequest->userAgent = 'UserAgent'; $this->ticketRequest->screenResolution = [1400, 900]; - $this->ticketRequest->ipAddress = ip2long('127.0.0.1'); + $this->ticketRequest->ipAddress = '127.0.0.1'; $this->ticketRequest->language = 'English'; //-- Act From ca499d5eac8a52d7f4c53023df7efeefd8cdfe40 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 16 Feb 2017 21:46:47 -0500 Subject: [PATCH 048/192] Basic ticket creator appears to be working. Still need to handle email --- api/ApplicationContext.php | 3 ++ .../Statuses/DefaultStatusForAction.php | 16 ++++++- api/BusinessLogic/Statuses/Status.php | 38 ++++++++++++++++- api/BusinessLogic/Statuses/StatusLanguage.php | 20 +++++++++ api/BusinessLogic/Tickets/TicketCreator.php | 25 ++++++++++- .../Tickets/TicketGatewayGeneratedFields.php | 1 + api/DataAccess/Statuses/StatusGateway.php | 26 +++++++++--- api/DataAccess/Tickets/TicketGateway.php | 20 +++++---- .../CreateTicketForCustomerTest.php | 42 ++++++++++++++++++- 9 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 api/BusinessLogic/Statuses/StatusLanguage.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 08c26c40..76de8849 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -13,6 +13,7 @@ use BusinessLogic\Tickets\TrackingIdGenerator; use DataAccess\Categories\CategoryGateway; use DataAccess\Security\BanGateway; use DataAccess\Security\UserGateway; +use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; @@ -35,6 +36,7 @@ class ApplicationContext { $this->get[BanRetriever::class] = new BanRetriever($this->get[BanGateway::class]); // Tickets + $this->get[StatusGateway::class] = new StatusGateway(); $this->get[TicketGateway::class] = new TicketGateway(); $this->get[TicketRetriever::class] = new TicketRetriever($this->get[TicketGateway::class]); $this->get[TicketValidators::class] = new TicketValidators($this->get[TicketGateway::class]); @@ -46,6 +48,7 @@ class ApplicationContext { $this->get[TicketCreator::class] = new TicketCreator($this->get[NewTicketValidator::class], $this->get[TrackingIdGenerator::class], $this->get[Autoassigner::class], + $this->get[StatusGateway::class], $this->get[TicketGateway::class]); } } \ No newline at end of file diff --git a/api/BusinessLogic/Statuses/DefaultStatusForAction.php b/api/BusinessLogic/Statuses/DefaultStatusForAction.php index 569fe224..ac9a7805 100644 --- a/api/BusinessLogic/Statuses/DefaultStatusForAction.php +++ b/api/BusinessLogic/Statuses/DefaultStatusForAction.php @@ -18,5 +18,19 @@ class DefaultStatusForAction { const REOPENED_BY_STAFF = "IsStaffReopenedStatus"; const DEFAULT_STAFF_REPLY = "IsDefaultStaffReplyStatus"; const LOCKED_TICKET = "LockedTicketStatus"; - const AUTOCLOSE_STATUS = "IsAutoCloseOption"; + const AUTOCLOSE_STATUS = "IsAutocloseOption"; + + static function getAll() { + return array( + self::NEW_TICKET, + self::CLOSED_STATUS, + self::CLOSED_BY_CLIENT, + self::CUSTOMER_REPLY, + self::CLOSED_BY_STAFF, + self::REOPENED_BY_STAFF, + self::DEFAULT_STAFF_REPLY, + self::LOCKED_TICKET, + self::AUTOCLOSE_STATUS + ); + } } \ No newline at end of file diff --git a/api/BusinessLogic/Statuses/Status.php b/api/BusinessLogic/Statuses/Status.php index 89c8db3e..615abf75 100644 --- a/api/BusinessLogic/Statuses/Status.php +++ b/api/BusinessLogic/Statuses/Status.php @@ -4,6 +4,42 @@ namespace BusinessLogic\Statuses; class Status { + static function fromDatabase($row, $languageRs) { + $status = new Status(); + $status->id = $row['ID']; + $status->textColor = $row['TextColor']; + $status->defaultActions = array(); + + foreach (DefaultStatusForAction::getAll() as $defaultStatus) { + $status = self::addDefaultStatusIfSet($status, $row, $defaultStatus); + } + + $status->closable = $row['Closable']; + + $localizedLanguages = array(); + while ($languageRow = hesk_dbFetchAssoc($languageRs)) { + $localizedLanguages[] = new StatusLanguage($languageRow['language'], $languageRow['text']); + } + $status->localizedNames = $localizedLanguages; + $status->sort = $row['sort']; + + return $status; + } + + /** + * @param $status Status + * @param $row array + * @param $key string + * @return Status + */ + private static function addDefaultStatusIfSet($status, $row, $key) { + if ($row[$key]) { + $status->defaultActions[] = $key; + } + + return $status; + } + /** * @var $id int */ @@ -30,7 +66,7 @@ class Status { public $sort; /** - * @var $name string[] + * @var $name StatusLanguage[] */ public $localizedNames; } \ No newline at end of file diff --git a/api/BusinessLogic/Statuses/StatusLanguage.php b/api/BusinessLogic/Statuses/StatusLanguage.php new file mode 100644 index 00000000..181a04a0 --- /dev/null +++ b/api/BusinessLogic/Statuses/StatusLanguage.php @@ -0,0 +1,20 @@ +language = $language; + $this->text = $text; + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index c723fafd..7d6c5687 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -3,6 +3,8 @@ namespace BusinessLogic\Tickets; use BusinessLogic\Exceptions\ValidationException; +use BusinessLogic\Statuses\DefaultStatusForAction; +use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; class TicketCreator { @@ -21,15 +23,21 @@ class TicketCreator { */ private $autoassigner; + /** + * @var $statusGateway StatusGateway + */ + private $statusGateway; + /** * @var $ticketGateway TicketGateway */ private $ticketGateway; - function __construct($newTicketValidator, $trackingIdGenerator, $autoassigner, $ticketGateway) { + function __construct($newTicketValidator, $trackingIdGenerator, $autoassigner, $statusGateway, $ticketGateway) { $this->newTicketValidator = $newTicketValidator; $this->trackingIdGenerator = $trackingIdGenerator; $this->autoassigner = $autoassigner; + $this->statusGateway = $statusGateway; $this->ticketGateway = $ticketGateway; } @@ -76,10 +84,25 @@ class TicketCreator { $ticket->ipAddress = $ticketRequest->ipAddress; $ticket->language = $ticketRequest->language; + $status = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings); + + if ($status === null) { + throw new \Exception("Could not find the default status for a new ticket!"); + } + $ticket->statusId = $status->id; + $ticketGatewayGeneratedFields = $this->ticketGateway->createTicket($ticket, $heskSettings); $ticket->dateCreated = $ticketGatewayGeneratedFields->dateCreated; $ticket->lastChanged = $ticketGatewayGeneratedFields->dateModified; + $ticket->archived = false; + $ticket->locked = false; + $ticket->id = $ticketGatewayGeneratedFields->id; + $ticket->openedBy = 0; + $ticket->numberOfReplies = 0; + $ticket->numberOfStaffReplies = 0; + $ticket->timeWorked = '00:00:00'; + $ticket->lastReplier = 0; return $ticket; } diff --git a/api/BusinessLogic/Tickets/TicketGatewayGeneratedFields.php b/api/BusinessLogic/Tickets/TicketGatewayGeneratedFields.php index 00d9521b..49f2f2ac 100644 --- a/api/BusinessLogic/Tickets/TicketGatewayGeneratedFields.php +++ b/api/BusinessLogic/Tickets/TicketGatewayGeneratedFields.php @@ -10,6 +10,7 @@ namespace BusinessLogic\Tickets; class TicketGatewayGeneratedFields { + public $id; public $dateCreated; public $dateModified; } \ No newline at end of file diff --git a/api/DataAccess/Statuses/StatusGateway.php b/api/DataAccess/Statuses/StatusGateway.php index c244f19e..de812784 100644 --- a/api/DataAccess/Statuses/StatusGateway.php +++ b/api/DataAccess/Statuses/StatusGateway.php @@ -5,15 +5,31 @@ namespace DataAccess\Statuses; use BusinessLogic\Statuses\DefaultStatusForAction; use BusinessLogic\Statuses\Status; +use DataAccess\CommonDao; -class StatusGateway { +class StatusGateway extends CommonDao { /** - * @param $defaultAction DefaultStatusForAction + * @param $defaultAction string * @return Status */ - function getStatusForDefaultAction($defaultAction) { - //-- TODO - return new Status(); + function getStatusForDefaultAction($defaultAction, $heskSettings) { + $this->init(); + + $metaRs = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . 'statuses` + WHERE `' . $defaultAction . '` = 1'); + if (hesk_dbNumRows($metaRs) === 0) { + return null; + } + $row = hesk_dbFetchAssoc($metaRs); + + $languageRs = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . 'text_to_status_xref` + WHERE `status_id` = ' . intval($row['ID'])); + + $status = Status::fromDatabase($row, $languageRs); + + $this->close(); + + return $status; } } \ No newline at end of file diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index ae5c2b4d..625c58f9 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -92,6 +92,8 @@ class TicketGateway extends CommonDao { function createTicket($ticket, $heskSettings) { global $hesklang; + $this->init(); + $dueDate = $ticket->dueDate ? "'{$ticket->dueDate}'" : "NULL"; // Prepare SQL for custom fields $customWhere = ''; @@ -103,17 +105,17 @@ class TicketGateway extends CommonDao { $customWhat .= ", '" . (isset($ticket->customFields[$i]) ? hesk_dbEscape($ticket->customFields[$i]) : '') . "'"; } - $suggestedArticles = ''; + $suggestedArticles = 'NULL'; if ($ticket->suggestedArticles !== null && !empty($ticket->suggestedArticles)) { - $suggestedArticles = implode(',', $ticket->suggestedArticles); + $suggestedArticles = "'" .implode(',', $ticket->suggestedArticles) . "'"; } $latitude = $ticket->location !== null && isset($ticket->location[0]) - && $ticket->location[0] !== null ? $ticket->location[0] : ''; + && $ticket->location[0] !== null ? $ticket->location[0] : 'E-0'; $longitude = $ticket->location !== null && isset($ticket->location[1]) - && $ticket->location[1] !== null ? $ticket->location[1] : ''; + && $ticket->location[1] !== null ? $ticket->location[1] : 'E-0'; $userAgent = $ticket->userAgent !== null ? $ticket->userAgent : ''; $screenResolutionWidth = $ticket->screenResolution !== null && isset($ticket->screenResolution[0]) @@ -165,8 +167,8 @@ class TicketGateway extends CommonDao { '" . hesk_dbEscape($ticket->message) . "', NOW(), NOW(), - '" . $suggestedArticles . "', - '" . hesk_dbEscape($ticket->ipAddress) . "', + " . $suggestedArticles . ", + '" . hesk_dbEscape($ipAddress) . "', '" . hesk_dbEscape($ticket->language) . "', '" . intval($ticket->openedBy) . "', '" . intval($ticket->ownerId) . "', @@ -186,14 +188,18 @@ class TicketGateway extends CommonDao { "; hesk_dbQuery($sql); + $id = hesk_dbInsertID(); - $rs = hesk_dbQuery('SELECT `dt`, `lastchange` FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . 'tickets` WHERE `id` = ' . intval(hesk_dbInsertID())); + $rs = hesk_dbQuery('SELECT `dt`, `lastchange` FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . 'tickets` WHERE `id` = ' . intval($id)); $row = hesk_dbFetchAssoc($rs); $generatedFields = new TicketGatewayGeneratedFields(); + $generatedFields->id = $id; $generatedFields->dateCreated = $row['dt']; $generatedFields->dateModified = $row['lastchange']; + $this->close(); + return $generatedFields; } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index 967466ca..3a7f7086 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -10,6 +10,8 @@ namespace BusinessLogic\Tickets\TicketCreatorTests; use BusinessLogic\Security\UserContext; +use BusinessLogic\Statuses\DefaultStatusForAction; +use BusinessLogic\Statuses\Status; use BusinessLogic\Tickets\Autoassigner; use BusinessLogic\Tickets\CreateTicketByCustomerModel; use BusinessLogic\Tickets\NewTicketValidator; @@ -18,6 +20,7 @@ use BusinessLogic\Tickets\TicketGatewayGeneratedFields; use BusinessLogic\Tickets\TrackingIdGenerator; use BusinessLogic\ValidationModel; use Core\Constants\Priority; +use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; use PHPUnit\Framework\TestCase; @@ -43,6 +46,11 @@ class CreateTicketTest extends TestCase { */ private $autoassigner; + /** + * @var $statusGateway \PHPUnit_Framework_MockObject_MockObject + */ + private $statusGateway; + /** * @var $ticketRequest CreateTicketByCustomerModel */ @@ -78,8 +86,10 @@ class CreateTicketTest extends TestCase { $this->newTicketValidator = $this->createMock(NewTicketValidator::class); $this->trackingIdGenerator = $this->createMock(TrackingIdGenerator::class); $this->autoassigner = $this->createMock(Autoassigner::class); + $this->statusGateway = $this->createMock(StatusGateway::class); - $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, $this->autoassigner, $this->ticketGateway); + $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, + $this->autoassigner, $this->statusGateway, $this->ticketGateway); $this->ticketRequest = new CreateTicketByCustomerModel(); $this->ticketRequest->name = 'Name'; @@ -105,6 +115,11 @@ class CreateTicketTest extends TestCase { $this->autoassigner->method('getNextUserForTicket')->willReturn(1); $this->ticketGatewayGeneratedFields = new TicketGatewayGeneratedFields(); $this->ticketGateway->method('createTicket')->willReturn($this->ticketGatewayGeneratedFields); + + $status = new Status(); + $status->id = 1; + $this->statusGateway->method('getStatusForDefaultAction') + ->willReturn($status); } function testItSavesTheTicketToTheDatabase() { @@ -185,7 +200,7 @@ class CreateTicketTest extends TestCase { //-- Arrange $this->ticketGatewayGeneratedFields->dateCreated = 'date created'; $this->ticketGatewayGeneratedFields->dateModified = 'date modified'; - + $this->ticketGatewayGeneratedFields->id = 50; //-- Act $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); @@ -193,5 +208,28 @@ class CreateTicketTest extends TestCase { //-- Assert self::assertThat($ticket->dateCreated, self::equalTo($this->ticketGatewayGeneratedFields->dateCreated)); self::assertThat($ticket->lastChanged, self::equalTo($this->ticketGatewayGeneratedFields->dateModified)); + self::assertThat($ticket->id, self::equalTo($this->ticketGatewayGeneratedFields->id)); + } + + function testItSetsTheDefaultStatus() { + //-- Act + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + + //-- Assert + self::assertThat($ticket->statusId, self::equalTo(1)); + } + + function testItSetsTheDefaultProperties() { + //-- Act + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + + //-- Assert + self::assertThat($ticket->archived, self::isFalse()); + self::assertThat($ticket->locked, self::isFalse()); + self::assertThat($ticket->openedBy, self::equalTo(0)); + self::assertThat($ticket->numberOfReplies, self::equalTo(0)); + self::assertThat($ticket->numberOfStaffReplies, self::equalTo(0)); + self::assertThat($ticket->timeWorked, self::equalTo('00:00:00')); + self::assertThat($ticket->lastReplier, self::equalTo(0)); } } From b4dcbc98461b46e6afa04bfda1f4d55208252a07 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 16 Feb 2017 22:04:51 -0500 Subject: [PATCH 049/192] Getting started on email sender for customer ticket creation --- api/BusinessLogic/Emails/EmailBuilder.php | 14 +++++ api/BusinessLogic/Emails/EmailSender.php | 25 ++++++++ .../Emails/MailgunEmailSender.php | 29 +++++++++ .../Emails/PhpMailEmailSender.php | 29 +++++++++ api/BusinessLogic/Emails/StmpEmailSender.php | 29 +++++++++ api/BusinessLogic/Security/BannedEmail.php | 6 -- api/BusinessLogic/Security/BannedIp.php | 6 -- .../Security/UserContextNotifications.php | 6 -- .../Security/UserContextPreferences.php | 6 -- api/BusinessLogic/Statuses/Closable.php | 6 -- .../Statuses/DefaultStatusForAction.php | 6 -- api/BusinessLogic/Statuses/StatusLanguage.php | 6 -- api/BusinessLogic/Tickets/Attachment.php | 6 -- api/BusinessLogic/Tickets/Autoassigner.php | 6 -- .../CustomFields/CustomFieldValidator.php | 6 -- .../Tickets/TicketGatewayGeneratedFields.php | 6 -- api/BusinessLogic/Tickets/TicketRetriever.php | 6 -- api/BusinessLogic/Validators.php | 6 -- api/Controllers/JsonRetriever.php | 6 -- api/Core/Constants/CustomField.php | 6 -- api/DataAccess/Statuses/StatusGateway.php | 1 - .../Security/BanRetrieverTest.php | 6 -- .../CustomFields/CustomFieldValidatorTest.php | 6 -- .../Tickets/NewTicketValidatorTest.php | 6 -- .../CreateTicketForCustomerTest.php | 6 -- .../Tickets/TrackingIdGeneratorTest.php | 6 -- api/composer.json | 3 +- api/composer.lock | 62 ++++++++++++++++++- 28 files changed, 189 insertions(+), 123 deletions(-) create mode 100644 api/BusinessLogic/Emails/EmailBuilder.php create mode 100644 api/BusinessLogic/Emails/EmailSender.php create mode 100644 api/BusinessLogic/Emails/MailgunEmailSender.php create mode 100644 api/BusinessLogic/Emails/PhpMailEmailSender.php create mode 100644 api/BusinessLogic/Emails/StmpEmailSender.php diff --git a/api/BusinessLogic/Emails/EmailBuilder.php b/api/BusinessLogic/Emails/EmailBuilder.php new file mode 100644 index 00000000..62739d00 --- /dev/null +++ b/api/BusinessLogic/Emails/EmailBuilder.php @@ -0,0 +1,14 @@ +=5.0.0" + }, + "require-dev": { + "phpdocumentor/phpdocumentor": "*", + "phpunit/phpunit": "4.7.*" + }, + "suggest": { + "league/oauth2-google": "Needed for Google XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "classmap": [ + "class.phpmailer.php", + "class.phpmaileroauth.php", + "class.phpmaileroauthgoogle.php", + "class.smtp.php", + "class.pop3.php", + "extras/EasyPeasyICS.php", + "extras/ntlm_sasl_client.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "time": "2017-01-09T09:33:47+00:00" + }, { "name": "phpspec/prophecy", "version": "dev-master", From 2a145bfa3ebbf355151e098d52048be784e2361c Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 18 Feb 2017 22:11:45 -0500 Subject: [PATCH 050/192] SMTP emails working with EmailSender...haven't tested attaching things though --- .gitignore | 1 + api/BusinessLogic/Emails/BasicEmailSender.php | 67 ++++++++++++++++ api/BusinessLogic/Emails/EmailBuilder.php | 43 +++++++++-- api/BusinessLogic/Emails/EmailSender.php | 16 ++-- .../Emails/PhpMailEmailSender.php | 29 ------- api/BusinessLogic/Emails/StmpEmailSender.php | 29 ------- .../BasicEmailSenderIntegrationTest.php | 77 +++++++++++++++++++ .../integration_test_mfh_settings.sample.php | 3 + 8 files changed, 190 insertions(+), 75 deletions(-) create mode 100644 api/BusinessLogic/Emails/BasicEmailSender.php delete mode 100644 api/BusinessLogic/Emails/PhpMailEmailSender.php delete mode 100644 api/BusinessLogic/Emails/StmpEmailSender.php create mode 100644 api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php create mode 100644 api/Tests/integration_test_mfh_settings.sample.php diff --git a/.gitignore b/.gitignore index 30dbedef..2e3f55a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Mods for HESK-specific files api/vendor +api/Tests/integration_test_mfh_settings.php # HESK Files admin/admin_suggest_articles.php diff --git a/api/BusinessLogic/Emails/BasicEmailSender.php b/api/BusinessLogic/Emails/BasicEmailSender.php new file mode 100644 index 00000000..a93aad9f --- /dev/null +++ b/api/BusinessLogic/Emails/BasicEmailSender.php @@ -0,0 +1,67 @@ +isSMTP(); + $mailer->SMTPAuth = true; + if ($heskSettings['smtp_ssl']) { + $mailer->SMTPSecure = "ssl"; + } elseif ($heskSettings['smtp_tls']) { + $mailer->SMTPSecure = "tls"; + } + $mailer->Host = $heskSettings['smtp_host_name']; + $mailer->Port = $heskSettings['smtp_host_port']; + $mailer->Username = $heskSettings['smtp_user']; + $mailer->Password = $heskSettings['smtp_password']; + } + + $mailer->FromName = $heskSettings['noreply_name'] ? $heskSettings['noreply_name'] : ''; + $mailer->From = $heskSettings['noreply_mail']; + + if ($emailBuilder->to !== null) { + foreach ($emailBuilder->to as $to) { + $mailer->addAddress($to); + } + } + + if ($emailBuilder->cc !== null) { + foreach ($emailBuilder->cc as $cc) { + $mailer->addCC($cc); + } + } + + if ($emailBuilder->bcc !== null) { + foreach ($emailBuilder->bcc as $bcc) { + $mailer->addBCC($bcc); + } + } + + $mailer->Subject = $emailBuilder->subject; + + if ($sendAsHtml) { + $mailer->Body = $emailBuilder->htmlMessage; + $mailer->AltBody = $emailBuilder->message; + } else { + $mailer->Body = $emailBuilder->message; + $mailer->isHTML(false); + } + $mailer->Timeout = $heskSettings['smtp_timeout']; + + if ($mailer->send()) { + return true; + } + + return $mailer->ErrorInfo; + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Emails/EmailBuilder.php b/api/BusinessLogic/Emails/EmailBuilder.php index 62739d00..c5691df3 100644 --- a/api/BusinessLogic/Emails/EmailBuilder.php +++ b/api/BusinessLogic/Emails/EmailBuilder.php @@ -3,12 +3,41 @@ namespace BusinessLogic\Emails; +use BusinessLogic\Tickets\Attachment; + class EmailBuilder { - private $to; - private $cc; - private $bcc; - private $subject; - private $message; - private $htmlMessage; - private $attachments; + /** + * @var $to string[] + */ + public $to; + + /** + * @var $cc string[] + */ + public $cc; + + /** + * @var $bcc string[] + */ + public $bcc; + + /** + * @var $subject string + */ + public $subject; + + /** + * @var $message string + */ + public $message; + + /** + * @var $htmlMessage string + */ + public $htmlMessage; + + /** + * @var $attachments Attachment[] + */ + public $attachments; } \ No newline at end of file diff --git a/api/BusinessLogic/Emails/EmailSender.php b/api/BusinessLogic/Emails/EmailSender.php index 75f25e68..2cd191bc 100644 --- a/api/BusinessLogic/Emails/EmailSender.php +++ b/api/BusinessLogic/Emails/EmailSender.php @@ -5,21 +5,17 @@ namespace BusinessLogic\Emails; use BusinessLogic\Tickets\Attachment; use BusinessLogic\Tickets\Ticket; +use PHPMailer; interface EmailSender { /** + * Use to send emails that do NOT include ticket information + * * @param $emailBuilder EmailBuilder * @param $heskSettings array * @param $modsForHeskSettings array + * @param $sendAsHtml bool + * @return bool|string true if message sent successfully, error string otherwise */ - function sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings); - - /** - * @param $emailBuilder EmailBuilder - * @param $ticket Ticket - * @param $attachments Attachment[] - * @param $heskSettings array - * @param $modsForHeskSettings array - */ - function sendEmailWithTicket($emailBuilder, $ticket, $attachments, $heskSettings, $modsForHeskSettings); + function sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $sendAsHtml); } \ No newline at end of file diff --git a/api/BusinessLogic/Emails/PhpMailEmailSender.php b/api/BusinessLogic/Emails/PhpMailEmailSender.php deleted file mode 100644 index 56086090..00000000 --- a/api/BusinessLogic/Emails/PhpMailEmailSender.php +++ /dev/null @@ -1,29 +0,0 @@ -emailSender = new BasicEmailSender(); + $this->heskSettings = $hesk_settings; + $this->modsForHeskSettings = $modsForHesk_settings; + } + + function testItCanSendHtmlMail() { + //-- Arrange + //$hesk_settings['smtp'] = 0 //Uncomment this to use PHPMail + $emailBuilder = new EmailBuilder(); + $emailBuilder->to = array('mfh1@mailinator.com'); + $emailBuilder->cc = array('mfh2@mailinator.com'); + $emailBuilder->bcc = array('mfh3@mailinator.com'); + $emailBuilder->message = "Test PLAIN TEXT message"; + $emailBuilder->htmlMessage = "Test HTML message"; + $emailBuilder->subject = "BasicEmailSenderIntegrationTest"; + + + //-- Act + $result = $this->emailSender->sendEmail($emailBuilder, $this->heskSettings, $this->modsForHeskSettings, true); + + //-- Assert + if ($result !== true) { + $this->fail($result); + } + } + + function testItCanSendPlaintextMail() { + //-- Arrange + //$hesk_settings['smtp'] = 0 //Uncomment this to use PHPMail + $emailBuilder = new EmailBuilder(); + $emailBuilder->to = array('mfh1@mailinator.com'); + $emailBuilder->cc = array('mfh2@mailinator.com'); + $emailBuilder->bcc = array('mfh3@mailinator.com'); + $emailBuilder->message = "Test PLAIN TEXT message"; + $emailBuilder->subject = "BasicEmailSenderIntegrationTest"; + + + //-- Act + $result = $this->emailSender->sendEmail($emailBuilder, $this->heskSettings, $this->modsForHeskSettings, false); + + //-- Assert + if ($result !== true) { + $this->fail($result); + } + } +} diff --git a/api/Tests/integration_test_mfh_settings.sample.php b/api/Tests/integration_test_mfh_settings.sample.php new file mode 100644 index 00000000..592cbc13 --- /dev/null +++ b/api/Tests/integration_test_mfh_settings.sample.php @@ -0,0 +1,3 @@ + Date: Sun, 19 Feb 2017 22:02:10 -0500 Subject: [PATCH 051/192] Created email senders and integration tests for these senders --- api/BusinessLogic/Emails/BasicEmailSender.php | 10 +- api/BusinessLogic/Emails/EmailSender.php | 2 +- .../Emails/MailgunEmailSender.php | 76 +- .../BasicEmailSenderIntegrationTest.php | 37 +- .../MailgunEmailSenderIntegrationTest.php | 109 ++ .../BusinessLogic/IntegrationTestCaseBase.php | 10 + api/Tests/bootstrap.php | 1 + api/composer.json | 7 +- api/composer.lock | 1018 ++++++++++++++++- 9 files changed, 1210 insertions(+), 60 deletions(-) create mode 100644 api/Tests/BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php create mode 100644 api/Tests/BusinessLogic/IntegrationTestCaseBase.php diff --git a/api/BusinessLogic/Emails/BasicEmailSender.php b/api/BusinessLogic/Emails/BasicEmailSender.php index a93aad9f..058ef937 100644 --- a/api/BusinessLogic/Emails/BasicEmailSender.php +++ b/api/BusinessLogic/Emails/BasicEmailSender.php @@ -26,7 +26,8 @@ class BasicEmailSender implements EmailSender { $mailer->Password = $heskSettings['smtp_password']; } - $mailer->FromName = $heskSettings['noreply_name'] ? $heskSettings['noreply_name'] : ''; + $mailer->FromName = $heskSettings['noreply_name'] !== null && + $heskSettings['noreply_name'] !== '' ? $heskSettings['noreply_name'] : ''; $mailer->From = $heskSettings['noreply_mail']; if ($emailBuilder->to !== null) { @@ -58,6 +59,13 @@ class BasicEmailSender implements EmailSender { } $mailer->Timeout = $heskSettings['smtp_timeout']; + if ($emailBuilder->attachments !== null) { + foreach ($emailBuilder->attachments as $attachment) { + $mailer->addAttachment(__DIR__ . '/../../../' . $heskSettings['attach_dir'] . '/' . $attachment->savedName, + $attachment->fileName); + } + } + if ($mailer->send()) { return true; } diff --git a/api/BusinessLogic/Emails/EmailSender.php b/api/BusinessLogic/Emails/EmailSender.php index 2cd191bc..4f300529 100644 --- a/api/BusinessLogic/Emails/EmailSender.php +++ b/api/BusinessLogic/Emails/EmailSender.php @@ -15,7 +15,7 @@ interface EmailSender { * @param $heskSettings array * @param $modsForHeskSettings array * @param $sendAsHtml bool - * @return bool|string true if message sent successfully, error string otherwise + * @return bool|string|\stdClass true if message sent successfully, string for PHPMail/Smtp error, stdClass for Mailgun error */ function sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $sendAsHtml); } \ No newline at end of file diff --git a/api/BusinessLogic/Emails/MailgunEmailSender.php b/api/BusinessLogic/Emails/MailgunEmailSender.php index c29df57e..58c53e4e 100644 --- a/api/BusinessLogic/Emails/MailgunEmailSender.php +++ b/api/BusinessLogic/Emails/MailgunEmailSender.php @@ -5,25 +5,69 @@ namespace BusinessLogic\Emails; use BusinessLogic\Tickets\Attachment; use BusinessLogic\Tickets\Ticket; +use Mailgun\Mailgun; class MailgunEmailSender implements EmailSender { - /** - * @param $emailBuilder EmailBuilder - * @param $heskSettings array - * @param $modsForHeskSettings array - */ - function sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings) { - // TODO: Implement sendEmail() method. + function sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $sendAsHtml) { + $mailgunArray = array(); + + $mailgunArray['from'] = $heskSettings['noreply_mail']; // Email Address + if ($heskSettings['noreply_name'] !== null && $heskSettings['noreply_name'] !== '') { + $mailgunArray['from'] = "{$heskSettings['noreply_name']} <{$heskSettings['noreply_mail']}>"; // Name and address + } + + $mailgunArray['to'] = implode(',', $emailBuilder->to); + + if ($emailBuilder->cc !== null) { + $mailgunArray['cc'] = implode(',', $emailBuilder->cc); + } + + if ($emailBuilder->bcc !== null) { + $mailgunArray['bcc'] = implode(',', $emailBuilder->bcc); + } + + $mailgunArray['subject'] = $emailBuilder->subject; + $mailgunArray['text'] = $emailBuilder->message; + + if ($sendAsHtml) { + $mailgunArray['html'] = $emailBuilder->htmlMessage; + } + + $mailgunAttachments = array(); + if ($emailBuilder->attachments !== null) { + foreach ($emailBuilder->attachments as $attachment) { + $mailgunAttachments[] = array( + 'remoteName' => $attachment->fileName, + 'filePath' => __DIR__ . '/../../../' . $heskSettings['attach_dir'] . '/' . $attachment->savedName + ); + } + } + + var_dump($mailgunArray); + + $result = $this->sendMessage($mailgunArray, $mailgunAttachments, $modsForHeskSettings); + + + if (isset($result->http_response_code) + && $result->http_response_code === 200) { + return true; + } + + return $result; } - /** - * @param $emailBuilder EmailBuilder - * @param $ticket Ticket - * @param $attachments Attachment[] - * @param $heskSettings array - * @param $modsForHeskSettings array - */ - function sendEmailWithTicket($emailBuilder, $ticket, $attachments, $heskSettings, $modsForHeskSettings) { - // TODO: Implement sendEmailWithTicket() method. + private function sendMessage($mailgunArray, $attachments, $modsForHeskSettings) { + $messageClient = new Mailgun($modsForHeskSettings['mailgun_api_key']); + + $mailgunAttachments = array(); + if (count($attachments) > 0) { + $mailgunAttachments = array( + 'attachment' => $attachments + ); + } + + $result = $messageClient->sendMessage($modsForHeskSettings['mailgun_domain'], $mailgunArray, $mailgunAttachments); + + return $result; } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php b/api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php index d71fe09f..4c946738 100644 --- a/api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php +++ b/api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php @@ -2,9 +2,10 @@ namespace BusinessLogic\Emails; -use PHPUnit\Framework\TestCase; +use BusinessLogic\IntegrationTestCaseBase; +use BusinessLogic\Tickets\Attachment; -class BasicEmailSenderIntegrationTest extends TestCase { +class BasicEmailSenderIntegrationTest extends IntegrationTestCaseBase { /** * @var $emailSender BasicEmailSender; */ @@ -20,9 +21,16 @@ class BasicEmailSenderIntegrationTest extends TestCase { */ private $modsForHeskSettings; + /** + * @var $attachmentsToPurge string[] + */ + private $attachmentsToPurge; + protected function setUp() { global $hesk_settings, $modsForHesk_settings; + $this->skip(); + if (!defined('IN_SCRIPT')) { define('IN_SCRIPT', 1); } @@ -32,6 +40,13 @@ class BasicEmailSenderIntegrationTest extends TestCase { $this->emailSender = new BasicEmailSender(); $this->heskSettings = $hesk_settings; $this->modsForHeskSettings = $modsForHesk_settings; + $this->attachmentsToPurge = array(); + } + + protected function tearDown() { + foreach ($this->attachmentsToPurge as $file) { + unlink($file); + } } function testItCanSendHtmlMail() { @@ -45,6 +60,24 @@ class BasicEmailSenderIntegrationTest extends TestCase { $emailBuilder->htmlMessage = "Test HTML message"; $emailBuilder->subject = "BasicEmailSenderIntegrationTest"; + // Uncomment to test attachments. + $attachment = new Attachment(); + $attachment->id = 1; + $attachment->fileName = "file.txt"; + $attachment->savedName = "test1.txt"; + $filename1 = __DIR__ . '/../../../../' . $this->heskSettings['attach_dir'] . '/' . $attachment->savedName; + file_put_contents($filename1, 'TEST DATA'); + + $otherAttachment = new Attachment(); + $otherAttachment->id = 2; + $otherAttachment->fileName = "file2.txt"; + $otherAttachment->savedName = "test2.txt"; + $filename2 = __DIR__ . '/../../../../' . $this->heskSettings['attach_dir'] . '/' . $otherAttachment->savedName; + file_put_contents($filename2, 'TEST DATA 2'); + + $emailBuilder->attachments = array($attachment, $otherAttachment); + $this->attachmentsToPurge = array($filename1, $filename2); + //-- Act $result = $this->emailSender->sendEmail($emailBuilder, $this->heskSettings, $this->modsForHeskSettings, true); diff --git a/api/Tests/BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php b/api/Tests/BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php new file mode 100644 index 00000000..1a5d5ec3 --- /dev/null +++ b/api/Tests/BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php @@ -0,0 +1,109 @@ +skip(); + + if (!defined('IN_SCRIPT')) { + define('IN_SCRIPT', 1); + } + require(__DIR__ . '/../../../../hesk_settings.inc.php'); + require(__DIR__ . '/../../integration_test_mfh_settings.php'); + + $this->emailSender = new MailgunEmailSender(); + $this->heskSettings = $hesk_settings; + $this->modsForHeskSettings = $modsForHesk_settings; + $this->attachmentsToPurge = array(); + } + + protected function tearDown() { + foreach ($this->attachmentsToPurge as $file) { + unlink($file); + } + } + + function testItCanSendMail() { + //-- Arrange + $emailBuilder = new EmailBuilder(); + $emailBuilder->to = array('mfh1@mailinator.com'); + $emailBuilder->cc = array('mfh2@mailinator.com'); + $emailBuilder->bcc = array('mfh3@mailinator.com'); + $emailBuilder->message = "Test PLAIN TEXT message"; + $emailBuilder->htmlMessage = "Test HTML message"; + $emailBuilder->subject = "MailgunEmailSenderIntegrationTest"; + + // Uncomment to test attachments. + $attachment = new Attachment(); + $attachment->id = 1; + $attachment->fileName = "file.txt"; + $attachment->savedName = "test1.txt"; + $filename1 = __DIR__ . '/../../../../' . $this->heskSettings['attach_dir'] . '/' . $attachment->savedName; + file_put_contents($filename1, 'TEST DATA'); + + $otherAttachment = new Attachment(); + $otherAttachment->id = 2; + $otherAttachment->fileName = "file2.txt"; + $otherAttachment->savedName = "test2.txt"; + $filename2 = __DIR__ . '/../../../../' . $this->heskSettings['attach_dir'] . '/' . $otherAttachment->savedName; + file_put_contents($filename2, 'TEST DATA 2'); + + $emailBuilder->attachments = array($attachment, $otherAttachment); + $this->attachmentsToPurge = array($filename1, $filename2); + + + //-- Act + $result = $this->emailSender->sendEmail($emailBuilder, $this->heskSettings, $this->modsForHeskSettings, true); + + //-- Assert + if ($result !== true) { + $this->fail($result); + } + } + + function testItCanSendPlaintextMail() { + //-- Arrange + //$hesk_settings['smtp'] = 0 //Uncomment this to use PHPMail + $emailBuilder = new EmailBuilder(); + $emailBuilder->to = array('mfh1@mailinator.com'); + $emailBuilder->cc = array('mfh2@mailinator.com'); + $emailBuilder->bcc = array('mfh3@mailinator.com'); + $emailBuilder->message = "Test PLAIN TEXT message"; + $emailBuilder->subject = "MailgunEmailSenderIntegrationTest"; + + + //-- Act + $result = $this->emailSender->sendEmail($emailBuilder, $this->heskSettings, $this->modsForHeskSettings, false); + + //-- Assert + if ($result !== true) { + $this->fail($result); + } + } +} diff --git a/api/Tests/BusinessLogic/IntegrationTestCaseBase.php b/api/Tests/BusinessLogic/IntegrationTestCaseBase.php new file mode 100644 index 00000000..963f223c --- /dev/null +++ b/api/Tests/BusinessLogic/IntegrationTestCaseBase.php @@ -0,0 +1,10 @@ +markTestSkipped(sprintf("Skipping Integration Test %s", get_class($this))); + } +} diff --git a/api/Tests/bootstrap.php b/api/Tests/bootstrap.php index 2e555474..a728a8a5 100644 --- a/api/Tests/bootstrap.php +++ b/api/Tests/bootstrap.php @@ -1,2 +1,3 @@ =5.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\StreamFilter\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A simple and modern approach to stream filtering in PHP", + "homepage": "https://github.com/clue/php-stream-filter", + "keywords": [ + "bucket brigade", + "callback", + "filter", + "php_user_filter", + "stream", + "stream_filter_append", + "stream_filter_register" + ], + "time": "2015-11-08T23:41:30+00:00" + }, { "name": "doctrine/instantiator", "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "68099b02b60bbf3b088ff5cb67bf506770ef9cac" + "url": "https://github.com/doctrine/instantiator.git", + "reference": "68099b02b60bbf3b088ff5cb67bf506770ef9cac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/68099b02b60bbf3b088ff5cb67bf506770ef9cac", + "reference": "68099b02b60bbf3b088ff5cb67bf506770ef9cac", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2017-01-23 09:23:06" + }, + { + "name": "guzzle/guzzle", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "f7778ed85e3db90009d79725afd6c3a82dab32fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/f7778ed85e3db90009d79725afd6c3a82dab32fe", + "reference": "f7778ed85e3db90009d79725afd6c3a82dab32fe", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2016-10-26 18:22:07" + }, + { + "name": "guzzlehttp/guzzle", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "6a99df94a22f01b4b9c32ed8789cf30d05bdba92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/6a99df94a22f01b4b9c32ed8789cf30d05bdba92", + "reference": "6a99df94a22f01b4b9c32ed8789cf30d05bdba92", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.3.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-02-19 15:59:27" + }, + { + "name": "guzzlehttp/promises", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20 10:07:11" + }, + { + "name": "guzzlehttp/psr7", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "41972f428b31bc3ebff0707f63dd2165d3ac4cf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/41972f428b31bc3ebff0707f63dd2165d3ac4cf6", + "reference": "41972f428b31bc3ebff0707f63dd2165d3ac4cf6", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-02-18 11:43:27" + }, + { + "name": "mailgun/mailgun-php", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/mailgun/mailgun-php.git", + "reference": "54b7f851b8e0241d593897dc2d50906bf4a43995" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mailgun/mailgun-php/zipball/54b7f851b8e0241d593897dc2d50906bf4a43995", + "reference": "54b7f851b8e0241d593897dc2d50906bf4a43995", + "shasum": "" + }, + "require": { + "php": "^5.5|^7.0", + "php-http/discovery": "^1.0", + "php-http/httplug": "^1.0", + "php-http/message": "^1.0", + "php-http/multipart-stream-builder": "^0.1" + }, + "require-dev": { + "php-http/guzzle6-adapter": "^1.0", + "phpunit/phpunit": "~4.6" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mailgun": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Travis Swientek", + "email": "travis@mailgunhq.com" + } + ], + "description": "The Mailgun SDK provides methods for all API functions.", + "time": "2016-08-10T16:58:18+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-01-26T22:05:40+00:00" + }, + { + "name": "php-http/curl-client", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/curl-client.git", + "reference": "0972ad0d7d37032a52077a5cbe27cf370f2007d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/curl-client/zipball/0972ad0d7d37032a52077a5cbe27cf370f2007d8", + "reference": "0972ad0d7d37032a52077a5cbe27cf370f2007d8", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^5.5 || ^7.0", + "php-http/discovery": "^1.0", + "php-http/httplug": "^1.0", + "php-http/message": "^1.2", + "php-http/message-factory": "^1.0.2" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^1.0", + "php-http/client-integration-tests": "^0.5.1", + "phpunit/phpunit": "^4.8.27", + "zendframework/zend-diactoros": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Client\\Curl\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Михаил Красильников", + "email": "m.krasilnikov@yandex.ru" + } + ], + "description": "cURL client for PHP-HTTP", + "homepage": "http://php-http.org", + "keywords": [ + "curl", + "http" + ], + "time": "2017-02-09T15:18:33+00:00" + }, + { + "name": "php-http/discovery", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "cc5669d9cb51170ad0278a3b984cd3c7894d6ff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/cc5669d9cb51170ad0278a3b984cd3c7894d6ff9", + "reference": "cc5669d9cb51170ad0278a3b984cd3c7894d6ff9", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^2.0.2", + "php-http/httplug": "^1.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^2.4", + "puli/composer-plugin": "1.0.0-beta10" + }, + "suggest": { + "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories", + "puli/composer-plugin": "Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds installed HTTPlug implementations and PSR-7 message factories", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr7" + ], + "time": "2017-02-12 08:49:24" + }, + { + "name": "php-http/guzzle6-adapter", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-http/guzzle6-adapter.git", + "reference": "c0168c6e5fa286c3837310d591114d2683b9b9a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/c0168c6e5fa286c3837310d591114d2683b9b9a5", + "reference": "c0168c6e5fa286c3837310d591114d2683b9b9a5", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": "^5.5 || ^7.0", + "php-http/httplug": "^1.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/client-integration-tests": "^0.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + } + ], + "description": "Guzzle 6 HTTP Adapter", + "homepage": "http://httplug.io", + "keywords": [ + "Guzzle", + "http" + ], + "time": "2016-08-02 09:03:17" + }, + { + "name": "php-http/httplug", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "f32fefee51cb96e99edb0c4bb1d11b5026ad5069" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/68099b02b60bbf3b088ff5cb67bf506770ef9cac", - "reference": "68099b02b60bbf3b088ff5cb67bf506770ef9cac", + "url": "https://api.github.com/repos/php-http/httplug/zipball/f32fefee51cb96e99edb0c4bb1d11b5026ad5069", + "reference": "f32fefee51cb96e99edb0c4bb1d11b5026ad5069", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -47,60 +690,247 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", "keywords": [ - "constructor", - "instantiate" + "client", + "http" ], - "time": "2017-01-23 09:23:06" + "time": "2017-01-02 06:37:42" }, { - "name": "myclabs/deep-copy", - "version": "1.6.0", + "name": "php-http/message", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" + "url": "https://github.com/php-http/message.git", + "reference": "13df8c48f40ca7925303aa336f19be4b80984f01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", - "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "url": "https://api.github.com/repos/php-http/message/zipball/13df8c48f40ca7925303aa336f19be4b80984f01", + "reference": "13df8c48f40ca7925303aa336f19be4b80984f01", "shasum": "" }, "require": { - "php": ">=5.4.0" + "clue/stream-filter": "^1.3", + "php": ">=5.4", + "php-http/message-factory": "^1.0.2", + "psr/http-message": "^1.0" }, "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" + "akeneo/phpspec-skip-example-extension": "^1.0", + "coduo/phpspec-data-provider-extension": "^1.0", + "ext-zlib": "*", + "guzzlehttp/psr7": "^1.0", + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4", + "slim/slim": "^3.0", + "zendframework/zend-diactoros": "^1.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation", + "zendframework/zend-diactoros": "Used with Diactoros Factories" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, "autoload": { "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "Http\\Message\\": "src/" + }, + "files": [ + "src/filters.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTP Message related tools", + "homepage": "http://php-http.org", + "keywords": [ + "http", + "message", + "psr-7" + ], + "time": "2017-02-14 08:58:37" + }, + { + "name": "php-http/message-factory", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a2809d4fe294ebe8879aec8d4d5bf21faa029344" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a2809d4fe294ebe8879aec8d4d5bf21faa029344", + "reference": "a2809d4fe294ebe8879aec8d4d5bf21faa029344", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "factory", + "http", + "message", + "stream", + "uri" ], - "time": "2017-01-26T22:05:40+00:00" + "time": "2016-02-03 08:16:31" + }, + { + "name": "php-http/multipart-stream-builder", + "version": "0.1.6", + "source": { + "type": "git", + "url": "https://github.com/php-http/multipart-stream-builder.git", + "reference": "74d5ac517778ae87a065c6f4076316c35b58a777" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/74d5ac517778ae87a065c6f4076316c35b58a777", + "reference": "74d5ac517778ae87a065c6f4076316c35b58a777", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "php-http/discovery": "^1.0", + "php-http/message-factory": "^1.0.2", + "psr/http-message": "^1.0" + }, + "require-dev": { + "php-http/message": "^1.5", + "phpunit/phpunit": "^4.8 || ^5.4", + "zendframework/zend-diactoros": "^1.3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.2-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\MultipartStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + } + ], + "description": "A builder class that help you create a multipart stream", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "multipart stream", + "stream" + ], + "time": "2017-02-16T08:52:59+00:00" + }, + { + "name": "php-http/promise", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "810b30da8bcf69e4b82c4b9bc6b31518234293ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/810b30da8bcf69e4b82c4b9bc6b31518234293ab", + "reference": "810b30da8bcf69e4b82c4b9bc6b31518234293ab", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "time": "2016-01-28 07:54:12" }, { "name": "phpdocumentor/reflection-common", @@ -756,6 +1586,56 @@ ], "time": "2016-12-08 20:27:08" }, + { + "name": "psr/http-message", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06 14:39:51" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "dev-master", @@ -1269,6 +2149,66 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03 07:35:21" }, + { + "name": "symfony/event-dispatcher", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "3178c0e247b81da8a0265b460ac23bec6d2e6627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3178c0e247b81da8a0265b460ac23bec6d2e6627", + "reference": "3178c0e247b81da8a0265b460ac23bec6d2e6627", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-02-18 19:13:35" + }, { "name": "symfony/yaml", "version": "dev-master", From 08d7347f00477fb961040e364b4259697d2e24e6 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 19 Feb 2017 22:04:47 -0500 Subject: [PATCH 052/192] Small tweaks --- api/BusinessLogic/Emails/MailgunEmailSender.php | 2 -- .../BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php | 1 - .../BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php | 1 - 3 files changed, 4 deletions(-) diff --git a/api/BusinessLogic/Emails/MailgunEmailSender.php b/api/BusinessLogic/Emails/MailgunEmailSender.php index 58c53e4e..2d290094 100644 --- a/api/BusinessLogic/Emails/MailgunEmailSender.php +++ b/api/BusinessLogic/Emails/MailgunEmailSender.php @@ -43,8 +43,6 @@ class MailgunEmailSender implements EmailSender { } } - var_dump($mailgunArray); - $result = $this->sendMessage($mailgunArray, $mailgunAttachments, $modsForHeskSettings); diff --git a/api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php b/api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php index 4c946738..a3feb1a8 100644 --- a/api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php +++ b/api/Tests/BusinessLogic/Emails/BasicEmailSenderIntegrationTest.php @@ -60,7 +60,6 @@ class BasicEmailSenderIntegrationTest extends IntegrationTestCaseBase { $emailBuilder->htmlMessage = "Test HTML message"; $emailBuilder->subject = "BasicEmailSenderIntegrationTest"; - // Uncomment to test attachments. $attachment = new Attachment(); $attachment->id = 1; $attachment->fileName = "file.txt"; diff --git a/api/Tests/BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php b/api/Tests/BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php index 1a5d5ec3..86948697 100644 --- a/api/Tests/BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php +++ b/api/Tests/BusinessLogic/Emails/MailgunEmailSenderIntegrationTest.php @@ -59,7 +59,6 @@ class MailgunEmailSenderIntegrationTest extends IntegrationTestCaseBase { $emailBuilder->htmlMessage = "Test HTML message"; $emailBuilder->subject = "MailgunEmailSenderIntegrationTest"; - // Uncomment to test attachments. $attachment = new Attachment(); $attachment->id = 1; $attachment->fileName = "file.txt"; From c1638aeb981f04b8e07efcc47ecd3a81512030ae Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 20 Feb 2017 13:05:41 -0500 Subject: [PATCH 053/192] First steps in supporting email validation --- api/BusinessLogic/Tickets/TicketCreator.php | 3 +- .../Tickets/VerifiedEmailChecker.php | 27 +++++++++ .../Tickets/VerifiedEmailGateway.php | 16 ++++++ .../Tickets/VerifiedEmailCheckerTest.php | 56 +++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 api/BusinessLogic/Tickets/VerifiedEmailChecker.php create mode 100644 api/DataAccess/Tickets/VerifiedEmailGateway.php create mode 100644 api/Tests/BusinessLogic/Tickets/VerifiedEmailCheckerTest.php diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 7d6c5687..ed0797d2 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -47,9 +47,10 @@ class TicketCreator { * @param $ticketRequest CreateTicketByCustomerModel * @param $heskSettings array HESK settings * @param $modsForHeskSettings array Mods for HESK settings + * @param $userContext * @return Ticket The newly created ticket * @throws ValidationException When a required field in $ticket_request is missing - * + * @throws \Exception When the default status for new tickets is not found */ function createTicketByCustomer($ticketRequest, $heskSettings, $modsForHeskSettings, $userContext) { $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($ticketRequest, $heskSettings, $userContext); diff --git a/api/BusinessLogic/Tickets/VerifiedEmailChecker.php b/api/BusinessLogic/Tickets/VerifiedEmailChecker.php new file mode 100644 index 00000000..fccb32fc --- /dev/null +++ b/api/BusinessLogic/Tickets/VerifiedEmailChecker.php @@ -0,0 +1,27 @@ +verifiedEmailGateway = $verifiedEmailGateway; + } + + function isEmailVerified($emailAddress, $heskSettings) { + return $this->verifiedEmailGateway->isEmailVerified($emailAddress, $heskSettings); + } +} \ No newline at end of file diff --git a/api/DataAccess/Tickets/VerifiedEmailGateway.php b/api/DataAccess/Tickets/VerifiedEmailGateway.php new file mode 100644 index 00000000..4fa0667b --- /dev/null +++ b/api/DataAccess/Tickets/VerifiedEmailGateway.php @@ -0,0 +1,16 @@ +init(); + + $rs = hesk_dbQuery("SELECT 1 FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "verified_emails` WHERE `Email` = '" . hesk_dbEscape($emailAddress) . "'"); + + return hesk_dbNumRows($rs) > 0; + } +} \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/VerifiedEmailCheckerTest.php b/api/Tests/BusinessLogic/Tickets/VerifiedEmailCheckerTest.php new file mode 100644 index 00000000..2cf838c5 --- /dev/null +++ b/api/Tests/BusinessLogic/Tickets/VerifiedEmailCheckerTest.php @@ -0,0 +1,56 @@ +verifiedEmailGateway = $this->createMock(VerifiedEmailGateway::class); + $this->heskSettings = array(); + $this->verifiedEmailChecker = new VerifiedEmailChecker($this->verifiedEmailGateway); + } + + function testItGetsTheValidationStateFromTheGatewayWhenItItTrue() { + //-- Arrange + $this->verifiedEmailGateway->method('isEmailVerified') + ->with('some email', $this->heskSettings) + ->willReturn(true); + + //-- Act + $actual = $this->verifiedEmailChecker->isEmailVerified('some email', $this->heskSettings); + + //-- Assert + self::assertThat($actual, self::isTrue()); + } + + function testItGetsTheValidationStateFromTheGatewayWhenItItFalse() { + //-- Arrange + $this->verifiedEmailGateway->method('isEmailVerified') + ->with('some email', $this->heskSettings) + ->willReturn(false); + + //-- Act + $actual = $this->verifiedEmailChecker->isEmailVerified('some email', $this->heskSettings); + + //-- Assert + self::assertThat($actual, self::isFalse()); + } +} From 4c7449ea3e56b62ed90018c05764c1c1269368c6 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 20 Feb 2017 22:07:39 -0500 Subject: [PATCH 054/192] Properly handle tickets that need validation. Added comments for next steps --- api/BusinessLogic/Tickets/StageTicket.php | 14 +++++++++++ api/BusinessLogic/Tickets/TicketCreator.php | 19 ++++++++++++--- api/Controllers/Tickets/TicketController.php | 4 ++++ api/DataAccess/Tickets/TicketGateway.php | 11 +++++---- .../CreateTicketForCustomerTest.php | 24 +++++++++++++++++-- 5 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 api/BusinessLogic/Tickets/StageTicket.php diff --git a/api/BusinessLogic/Tickets/StageTicket.php b/api/BusinessLogic/Tickets/StageTicket.php new file mode 100644 index 00000000..7fec23a5 --- /dev/null +++ b/api/BusinessLogic/Tickets/StageTicket.php @@ -0,0 +1,14 @@ +newTicketValidator = $newTicketValidator; $this->trackingIdGenerator = $trackingIdGenerator; $this->autoassigner = $autoassigner; $this->statusGateway = $statusGateway; $this->ticketGateway = $ticketGateway; + $this->verifiedEmailChecker = $verifiedEmailChecker; } /** @@ -61,8 +67,15 @@ class TicketCreator { throw new ValidationException($validationModel); } + $emailVerified = true; + if ($modsForHeskSettings['customer_email_verification_required']) { + $emailVerified = $this->verifiedEmailChecker->isEmailVerified($ticketRequest->email, $heskSettings); + } + // Create the ticket - $ticket = new Ticket(); + $ticket = $emailVerified + ? new Ticket() + : new StageTicket(); $ticket->trackingId = $this->trackingIdGenerator->generateTrackingId($heskSettings); if ($heskSettings['autoassign']) { @@ -92,7 +105,7 @@ class TicketCreator { } $ticket->statusId = $status->id; - $ticketGatewayGeneratedFields = $this->ticketGateway->createTicket($ticket, $heskSettings); + $ticketGatewayGeneratedFields = $this->ticketGateway->createTicket($ticket, $emailVerified, $heskSettings); $ticket->dateCreated = $ticketGatewayGeneratedFields->dateCreated; $ticket->lastChanged = $ticketGatewayGeneratedFields->dateModified; diff --git a/api/Controllers/Tickets/TicketController.php b/api/Controllers/Tickets/TicketController.php index 5b67fd2c..01a58e13 100644 --- a/api/Controllers/Tickets/TicketController.php +++ b/api/Controllers/Tickets/TicketController.php @@ -29,6 +29,10 @@ class TicketController { $ticket = $ticketCreator->createTicketByCustomer($this->buildTicketRequestFromJson($jsonRequest), $hesk_settings, $modsForHeskSettings, $userContext); + //if ticket is a stageTicket, email user + //else if assigned to owner, email new owner + //else email all staff + return output($ticket); } diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index 625c58f9..4d03b6e2 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -86,12 +86,11 @@ class TicketGateway extends CommonDao { /** * @param $ticket Ticket + * @param $isEmailVerified * @param $heskSettings * @return TicketGatewayGeneratedFields */ - function createTicket($ticket, $heskSettings) { - global $hesklang; - + function createTicket($ticket, $isEmailVerified, $heskSettings) { $this->init(); $dueDate = $ticket->dueDate ? "'{$ticket->dueDate}'" : "NULL"; @@ -127,7 +126,9 @@ class TicketGateway extends CommonDao { $ipAddress = $ticket->ipAddress !== null && $ticket->ipAddress !== '' ? $ticket->ipAddress : ''; - $sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` + $tableName = $isEmailVerified ? 'tickets' : 'stage_tickets'; + + $sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . $tableName ."` ( `trackid`, `name`, @@ -190,7 +191,7 @@ class TicketGateway extends CommonDao { hesk_dbQuery($sql); $id = hesk_dbInsertID(); - $rs = hesk_dbQuery('SELECT `dt`, `lastchange` FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . 'tickets` WHERE `id` = ' . intval($id)); + $rs = hesk_dbQuery('SELECT `dt`, `lastchange` FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . $tableName .'` WHERE `id` = ' . intval($id)); $row = hesk_dbFetchAssoc($rs); $generatedFields = new TicketGatewayGeneratedFields(); diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index fb8d81cc..54c22684 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -12,6 +12,7 @@ use BusinessLogic\Tickets\NewTicketValidator; use BusinessLogic\Tickets\TicketCreator; use BusinessLogic\Tickets\TicketGatewayGeneratedFields; use BusinessLogic\Tickets\TrackingIdGenerator; +use BusinessLogic\Tickets\VerifiedEmailChecker; use BusinessLogic\ValidationModel; use Core\Constants\Priority; use DataAccess\Statuses\StatusGateway; @@ -75,15 +76,21 @@ class CreateTicketTest extends TestCase { */ private $ticketGatewayGeneratedFields; + /** + * @var $verifiedEmailChecker \PHPUnit_Framework_MockObject_MockObject + */ + private $verifiedEmailChecker; + protected function setUp() { $this->ticketGateway = $this->createMock(TicketGateway::class); $this->newTicketValidator = $this->createMock(NewTicketValidator::class); $this->trackingIdGenerator = $this->createMock(TrackingIdGenerator::class); $this->autoassigner = $this->createMock(Autoassigner::class); $this->statusGateway = $this->createMock(StatusGateway::class); + $this->verifiedEmailChecker = $this->createMock(VerifiedEmailChecker::class); $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, - $this->autoassigner, $this->statusGateway, $this->ticketGateway); + $this->autoassigner, $this->statusGateway, $this->ticketGateway, $this->verifiedEmailChecker); $this->ticketRequest = new CreateTicketByCustomerModel(); $this->ticketRequest->name = 'Name'; @@ -101,7 +108,9 @@ class CreateTicketTest extends TestCase { 'custom_fields' => array(), 'autoassign' => 0, ); - $this->modsForHeskSettings = array(); + $this->modsForHeskSettings = array( + 'customer_email_verification_required' => false + ); $this->userContext = new UserContext(); $this->newTicketValidator->method('validateNewTicketForCustomer')->willReturn(new ValidationModel()); @@ -226,4 +235,15 @@ class CreateTicketTest extends TestCase { self::assertThat($ticket->timeWorked, self::equalTo('00:00:00')); self::assertThat($ticket->lastReplier, self::equalTo(0)); } + + function testItChecksIfTheEmailIsVerified() { + //-- Arrange + $this->modsForHeskSettings['customer_email_verification_required'] = true; + + //-- Assert + $this->verifiedEmailChecker->expects($this->once())->method('isEmailVerified'); + + //-- Act + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + } } From b53e9aafd7b750d95befe95ea9142916af5abb06 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 22 Feb 2017 22:02:24 -0500 Subject: [PATCH 055/192] Getting started on email retrieval/parsing --- .../Emails/EmailTemplateRetriever.php | 48 +++++++++++++++++++ .../Emails/ValidEmailTemplates.php | 31 ++++++++++++ .../EmailTemplateNotFoundException.php | 17 +++++++ 3 files changed, 96 insertions(+) create mode 100644 api/BusinessLogic/Emails/EmailTemplateRetriever.php create mode 100644 api/BusinessLogic/Emails/ValidEmailTemplates.php create mode 100644 api/BusinessLogic/Exceptions/EmailTemplateNotFoundException.php diff --git a/api/BusinessLogic/Emails/EmailTemplateRetriever.php b/api/BusinessLogic/Emails/EmailTemplateRetriever.php new file mode 100644 index 00000000..cab2e26b --- /dev/null +++ b/api/BusinessLogic/Emails/EmailTemplateRetriever.php @@ -0,0 +1,48 @@ + 'forgot_ticket_id', + 'new_reply_by_staff' => 'new_reply_by_staff', + 'new_ticket' => 'ticket_received', + 'verify_email' => 'verify_email', + 'ticket_closed' => 'ticket_closed', + 'category_moved' => 'category_moved', + 'new_reply_by_customer' => 'new_reply_by_customer', + 'new_ticket_staff' => 'new_ticket_staff', + 'ticket_assigned_to_you' => 'ticket_assigned_to_you', + 'new_pm' => 'new_pm', + 'new_note' => 'new_note', + 'reset_password' => 'reset_password', + 'calendar_reminder' => 'calendar_reminder', + 'overdue_ticket' => 'overdue_ticket', + ); + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Exceptions/EmailTemplateNotFoundException.php b/api/BusinessLogic/Exceptions/EmailTemplateNotFoundException.php new file mode 100644 index 00000000..9f1e63a3 --- /dev/null +++ b/api/BusinessLogic/Exceptions/EmailTemplateNotFoundException.php @@ -0,0 +1,17 @@ + Date: Thu, 23 Feb 2017 22:04:56 -0500 Subject: [PATCH 056/192] Little progress, but still something --- .../Emails/EmailTemplateParser.php | 66 +++++++++++++++++++ .../Emails/EmailTemplateRetriever.php | 48 -------------- .../InvalidEmailTemplateException.php | 16 +++++ 3 files changed, 82 insertions(+), 48 deletions(-) create mode 100644 api/BusinessLogic/Emails/EmailTemplateParser.php delete mode 100644 api/BusinessLogic/Emails/EmailTemplateRetriever.php create mode 100644 api/BusinessLogic/Exceptions/InvalidEmailTemplateException.php diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php new file mode 100644 index 00000000..6f2fa836 --- /dev/null +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -0,0 +1,66 @@ +statusGateway = $statusGateway; + } + + /** + * @param $templateName string + * @param $language string + * @param $ticket Ticket + */ + function getFormattedEmailForLanguage($templateName, $language, $ticket) { + global $hesklang; + + $template = self::getFromFileSystem($templateName, $language); + $subject = ValidEmailTemplates::getValidEmailTemplates()[$templateName]; + + $subject = self::parseSubject($subject, $ticket); + } + + private function getFromFileSystem($template, $language) + { + if (!isset(ValidEmailTemplates::getValidEmailTemplates()[$template])) { + throw new InvalidEmailTemplateException($template); + } + + /* Get email template */ + $file = 'language/' . $language . '/emails/' . $template . '.txt'; + $absoluteFilePath = __DIR__ . '/../../../' . $file; + + if (file_exists($absoluteFilePath)) { + return file_get_contents($absoluteFilePath); + } else { + throw new EmailTemplateNotFoundException($template, $language); + } + } + + private static function parseSubject($subjectTemplate, $ticket) { + if ($ticket === null) { + return $subjectTemplate; + } + + //-- + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Emails/EmailTemplateRetriever.php b/api/BusinessLogic/Emails/EmailTemplateRetriever.php deleted file mode 100644 index cab2e26b..00000000 --- a/api/BusinessLogic/Emails/EmailTemplateRetriever.php +++ /dev/null @@ -1,48 +0,0 @@ - Date: Fri, 24 Feb 2017 22:01:54 -0500 Subject: [PATCH 057/192] Changes --- .../Emails/EmailTemplateParser.php | 57 +++++++++++++++++-- api/BusinessLogic/Statuses/Status.php | 2 +- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index 6f2fa836..ac77fe0d 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -11,7 +11,10 @@ namespace BusinessLogic\Emails; use BusinessLogic\Exceptions\EmailTemplateNotFoundException; use BusinessLogic\Exceptions\InvalidEmailTemplateException; +use BusinessLogic\Statuses\DefaultStatusForAction; use BusinessLogic\Tickets\Ticket; +use Core\Constants\Priority; +use DataAccess\Categories\CategoryGateway; use DataAccess\Statuses\StatusGateway; class EmailTemplateParser { @@ -21,8 +24,14 @@ class EmailTemplateParser { */ private $statusGateway; - function __construct($statusGateway) { + /** + * @var $categoryGateway CategoryGateway + */ + private $categoryGateway; + + function __construct($statusGateway, $categoryGateway) { $this->statusGateway = $statusGateway; + $this->categoryGateway = $categoryGateway; } /** @@ -30,13 +39,13 @@ class EmailTemplateParser { * @param $language string * @param $ticket Ticket */ - function getFormattedEmailForLanguage($templateName, $language, $ticket) { + function getFormattedEmailForLanguage($templateName, $language, $ticket, $heskSettings) { global $hesklang; $template = self::getFromFileSystem($templateName, $language); $subject = ValidEmailTemplates::getValidEmailTemplates()[$templateName]; - $subject = self::parseSubject($subject, $ticket); + $subject = $this->parseSubject($subject, $ticket, $language, $heskSettings); } private function getFromFileSystem($template, $language) @@ -56,11 +65,49 @@ class EmailTemplateParser { } } - private static function parseSubject($subjectTemplate, $ticket) { + /** + * @param $subjectTemplate string + * @param $ticket Ticket + * @param $language string + * @param $heskSettings array + * @return string + */ + private function parseSubject($subjectTemplate, $ticket, $language, $heskSettings) { + global $hesklang; + if ($ticket === null) { return $subjectTemplate; } - //-- + // Status name and category name + $defaultStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings); + $statusName = $defaultStatus->localizedNames[$language]->text; + $category = $this->categoryGateway->getAllCategories($heskSettings)[$ticket->categoryId]; + + $priority = ''; + switch ($ticket->priorityId) { + case Priority::CRITICAL: + $priority = $hesklang['critical']; + break; + case Priority::HIGH: + $priority = $hesklang['high']; + break; + case Priority::MEDIUM: + $priority = $hesklang['medium']; + break; + case Priority::LOW: + $priority = $hesklang['low']; + break; + default: + $priority = 'PRIORITY NOT FOUND'; + break; + } + + // Special tags + $msg = str_replace('%%SUBJECT%%', $ticket->subject, $subjectTemplate); + $msg = str_replace('%%TRACK_ID%%', $ticket->trackingId, $msg); + $msg = str_replace('%%CATEGORY%%', $category->id, $msg); + $msg = str_replace('%%PRIORITY%%', $priority, $msg); + $msg = str_replace('%%STATUS%%', $statusName, $msg); } } \ No newline at end of file diff --git a/api/BusinessLogic/Statuses/Status.php b/api/BusinessLogic/Statuses/Status.php index 615abf75..37561bd7 100644 --- a/api/BusinessLogic/Statuses/Status.php +++ b/api/BusinessLogic/Statuses/Status.php @@ -18,7 +18,7 @@ class Status { $localizedLanguages = array(); while ($languageRow = hesk_dbFetchAssoc($languageRs)) { - $localizedLanguages[] = new StatusLanguage($languageRow['language'], $languageRow['text']); + $localizedLanguages[$languageRow['language']] = new StatusLanguage($languageRow['language'], $languageRow['text']); } $status->localizedNames = $localizedLanguages; $status->sort = $row['sort']; From 41123e987b0614c907343daef5a23712ff6cb7be Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 26 Feb 2017 21:52:12 -0500 Subject: [PATCH 058/192] Some more API changes --- .../Emails/EmailTemplateParser.php | 116 ++++++++++++++++-- api/DataAccess/Security/UserGateway.php | 15 +++ api/autoload.php | 1 + api/composer.json | 3 +- inc/common.inc.php | 1 - 5 files changed, 124 insertions(+), 12 deletions(-) diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index ac77fe0d..8f0e9f4d 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -15,6 +15,7 @@ use BusinessLogic\Statuses\DefaultStatusForAction; use BusinessLogic\Tickets\Ticket; use Core\Constants\Priority; use DataAccess\Categories\CategoryGateway; +use DataAccess\Security\UserGateway; use DataAccess\Statuses\StatusGateway; class EmailTemplateParser { @@ -29,9 +30,15 @@ class EmailTemplateParser { */ private $categoryGateway; - function __construct($statusGateway, $categoryGateway) { + /** + * @var $userGateway UserGateway + */ + private $userGateway; + + function __construct($statusGateway, $categoryGateway, $userGateway) { $this->statusGateway = $statusGateway; $this->categoryGateway = $categoryGateway; + $this->userGateway = $userGateway; } /** @@ -42,20 +49,31 @@ class EmailTemplateParser { function getFormattedEmailForLanguage($templateName, $language, $ticket, $heskSettings) { global $hesklang; - $template = self::getFromFileSystem($templateName, $language); + $template = self::getFromFileSystem($templateName, $language, false); + $htmlTemplate = self::getFromFileSystem($templateName, $language, true); $subject = ValidEmailTemplates::getValidEmailTemplates()[$templateName]; $subject = $this->parseSubject($subject, $ticket, $language, $heskSettings); + $message = $this->parseMessage($subject, $ticket, $language, $heskSettings); } - private function getFromFileSystem($template, $language) + /** + * @param $template string + * @param $language string + * @param $html bool + * @return string The template + * @throws EmailTemplateNotFoundException If the template was not found in the filesystem for the provided language + * @throws InvalidEmailTemplateException If the $template is not a valid template name + */ + private function getFromFileSystem($template, $language, $html) { if (!isset(ValidEmailTemplates::getValidEmailTemplates()[$template])) { throw new InvalidEmailTemplateException($template); } + $htmlFolder = $html ? 'html/' : ''; /* Get email template */ - $file = 'language/' . $language . '/emails/' . $template . '.txt'; + $file = "language/{$language}/emails/{$htmlFolder}{$template}.txt"; $absoluteFilePath = __DIR__ . '/../../../' . $file; if (file_exists($absoluteFilePath)) { @@ -71,10 +89,15 @@ class EmailTemplateParser { * @param $language string * @param $heskSettings array * @return string + * @throws \Exception if common.inc.php isn't loaded */ private function parseSubject($subjectTemplate, $ticket, $language, $heskSettings) { global $hesklang; + if (!function_exists('hesk_msgToPlain')) { + throw new \Exception("common.inc.php not loaded!"); + } + if ($ticket === null) { return $subjectTemplate; } @@ -84,7 +107,6 @@ class EmailTemplateParser { $statusName = $defaultStatus->localizedNames[$language]->text; $category = $this->categoryGateway->getAllCategories($heskSettings)[$ticket->categoryId]; - $priority = ''; switch ($ticket->priorityId) { case Priority::CRITICAL: $priority = $hesklang['critical']; @@ -104,10 +126,84 @@ class EmailTemplateParser { } // Special tags - $msg = str_replace('%%SUBJECT%%', $ticket->subject, $subjectTemplate); - $msg = str_replace('%%TRACK_ID%%', $ticket->trackingId, $msg); - $msg = str_replace('%%CATEGORY%%', $category->id, $msg); - $msg = str_replace('%%PRIORITY%%', $priority, $msg); - $msg = str_replace('%%STATUS%%', $statusName, $msg); + $subject = str_replace('%%SUBJECT%%', $ticket->subject, $subjectTemplate); + $subject = str_replace('%%TRACK_ID%%', $ticket->trackingId, $subject); + $subject = str_replace('%%CATEGORY%%', $category->id, $subject); + $subject = str_replace('%%PRIORITY%%', $priority, $subject); + $subject = str_replace('%%STATUS%%', $statusName, $subject); + + return $subject; + } + + /** + * @param $messageTemplate string + * @param $ticket Ticket + * @param $language string + * @param $heskSettings array + * @return string + * @throws \Exception if common.inc.php isn't loaded + */ + private function parseMessage($messageTemplate, $ticket, $language, $admin, $heskSettings) { + global $hesklang; + + if (!function_exists('hesk_msgToPlain')) { + throw new \Exception("common.inc.php not loaded!"); + } + + if ($ticket === null) { + return $messageTemplate; + } + + $heskSettings['site_title'] = hesk_msgToPlain($heskSettings['site_title'], 1); + + // Is email required to view ticket (for customers only)? + $heskSettings['e_param'] = $heskSettings['email_view_ticket'] ? '&e=' . rawurlencode($ticket->email) : ''; + + /* Generate the ticket URLs */ + $trackingURL = $heskSettings['hesk_url']; + $trackingURL .= $admin ? '/' . $heskSettings['admin_dir'] . '/admin_ticket.php' : '/ticket.php'; + $trackingURL .= '?track=' . $ticket['trackid'] . ($admin ? '' : $heskSettings['e_param']) . '&Refresh=' . rand(10000, 99999); + + // Status name and category name + $defaultStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings); + $statusName = hesk_msgToPlain($defaultStatus->localizedNames[$language]->text); + $category = hesk_msgToPlain($this->categoryGateway->getAllCategories($heskSettings)[$ticket->categoryId]); + $owner = hesk_msgToPlain($this->userGateway->getNameForId($ticket->ownerId, $heskSettings)); + + switch ($ticket->priorityId) { + case Priority::CRITICAL: + $priority = $hesklang['critical']; + break; + case Priority::HIGH: + $priority = $hesklang['high']; + break; + case Priority::MEDIUM: + $priority = $hesklang['medium']; + break; + case Priority::LOW: + $priority = $hesklang['low']; + break; + default: + $priority = 'PRIORITY NOT FOUND'; + break; + } + + // Special tags + $msg = str_replace('%%NAME%%', $ticket->name, $messageTemplate); + $msg = str_replace('%%SUBJECT%%', $ticket['subject'], $msg); + $msg = str_replace('%%TRACK_ID%%', $ticket['trackid'], $msg); + $msg = str_replace('%%TRACK_URL%%', $trackingURL, $msg); + $msg = str_replace('%%SITE_TITLE%%', $hesk_settings['site_title'], $msg); + $msg = str_replace('%%SITE_URL%%', $hesk_settings['site_url'], $msg); + $msg = str_replace('%%CATEGORY%%', $ticket['category'], $msg); + $msg = str_replace('%%PRIORITY%%', $ticket['priority'], $msg); + $msg = str_replace('%%OWNER%%', $ticket['owner'], $msg); + $msg = str_replace('%%STATUS%%', $ticket['status'], $msg); + $msg = str_replace('%%EMAIL%%', $ticket['email'], $msg); + $msg = str_replace('%%CREATED%%', $ticket['dt'], $msg); + $msg = str_replace('%%UPDATED%%', $ticket['lastchange'], $msg); + $msg = str_replace('%%ID%%', $ticket['id'], $msg); + + return $subject; } } \ No newline at end of file diff --git a/api/DataAccess/Security/UserGateway.php b/api/DataAccess/Security/UserGateway.php index 1962f3ff..b44be8b5 100644 --- a/api/DataAccess/Security/UserGateway.php +++ b/api/DataAccess/Security/UserGateway.php @@ -31,4 +31,19 @@ class UserGateway extends CommonDao { return $row; } + + // TODO Replace this with a basic User retrieval + function getNameForId($id, $heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT `name` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = " . intval($id)); + + if (hesk_dbNumRows($rs) === 0) { + return null; + } + + $row = hesk_dbFetchAssoc($rs); + + return $row['name']; + } } \ No newline at end of file diff --git a/api/autoload.php b/api/autoload.php index 1a2db8c2..5ffee38e 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -4,6 +4,7 @@ // Core requirements define('IN_SCRIPT', 1); define('HESK_PATH', '../'); +require_once(__DIR__ . 'vendor/autoload.php'); require_once(__DIR__ . '/bootstrap.php'); require_once(__DIR__ . '/../hesk_settings.inc.php'); require_once(__DIR__ . '/../inc/common.inc.php'); diff --git a/api/composer.json b/api/composer.json index 8b3f0002..f79dd05d 100644 --- a/api/composer.json +++ b/api/composer.json @@ -16,6 +16,7 @@ "php-http/guzzle6-adapter": "^1.1", "php-http/message": "^1.5", "php-http/curl-client": "^1.7", - "guzzlehttp/psr7": "^1.3" + "guzzlehttp/psr7": "^1.3", + "doctrine/orm": "*" } } diff --git a/inc/common.inc.php b/inc/common.inc.php index cf54fa9e..72772a16 100644 --- a/inc/common.inc.php +++ b/inc/common.inc.php @@ -989,7 +989,6 @@ function hesk_ticketToPlain($ticket, $specialchars = 0, $strip = 1) } } // END hesk_ticketToPlain() - function hesk_msgToPlain($msg, $specialchars = 0, $strip = 1) { $msg = preg_replace('/\/i', "$2", $msg); From c3e70309bcfe7a98ce7b44f7c87b6bb698119b89 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 27 Feb 2017 21:24:24 -0500 Subject: [PATCH 059/192] Some more changes --- .../Emails/EmailTemplateParser.php | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index 8f0e9f4d..8d32bfef 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -46,7 +46,7 @@ class EmailTemplateParser { * @param $language string * @param $ticket Ticket */ - function getFormattedEmailForLanguage($templateName, $language, $ticket, $heskSettings) { + function getFormattedEmailForLanguage($templateName, $language, $ticket, $forStaff, $heskSettings) { global $hesklang; $template = self::getFromFileSystem($templateName, $language, false); @@ -54,7 +54,8 @@ class EmailTemplateParser { $subject = ValidEmailTemplates::getValidEmailTemplates()[$templateName]; $subject = $this->parseSubject($subject, $ticket, $language, $heskSettings); - $message = $this->parseMessage($subject, $ticket, $language, $heskSettings); + $message = $this->parseMessage($template, $ticket, $language, $forStaff, $heskSettings); + $htmlMessage = $this->parseMessage($htmlTemplate, $ticket, $language, $forStaff, $heskSettings); } /** @@ -162,7 +163,7 @@ class EmailTemplateParser { /* Generate the ticket URLs */ $trackingURL = $heskSettings['hesk_url']; $trackingURL .= $admin ? '/' . $heskSettings['admin_dir'] . '/admin_ticket.php' : '/ticket.php'; - $trackingURL .= '?track=' . $ticket['trackid'] . ($admin ? '' : $heskSettings['e_param']) . '&Refresh=' . rand(10000, 99999); + $trackingURL .= '?track=' . $ticket->trackingId . ($admin ? '' : $heskSettings['e_param']) . '&Refresh=' . rand(10000, 99999); // Status name and category name $defaultStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings); @@ -190,20 +191,20 @@ class EmailTemplateParser { // Special tags $msg = str_replace('%%NAME%%', $ticket->name, $messageTemplate); - $msg = str_replace('%%SUBJECT%%', $ticket['subject'], $msg); - $msg = str_replace('%%TRACK_ID%%', $ticket['trackid'], $msg); + $msg = str_replace('%%SUBJECT%%', $ticket->subject, $msg); + $msg = str_replace('%%TRACK_ID%%', $ticket->trackingId, $msg); $msg = str_replace('%%TRACK_URL%%', $trackingURL, $msg); - $msg = str_replace('%%SITE_TITLE%%', $hesk_settings['site_title'], $msg); - $msg = str_replace('%%SITE_URL%%', $hesk_settings['site_url'], $msg); - $msg = str_replace('%%CATEGORY%%', $ticket['category'], $msg); - $msg = str_replace('%%PRIORITY%%', $ticket['priority'], $msg); - $msg = str_replace('%%OWNER%%', $ticket['owner'], $msg); - $msg = str_replace('%%STATUS%%', $ticket['status'], $msg); - $msg = str_replace('%%EMAIL%%', $ticket['email'], $msg); - $msg = str_replace('%%CREATED%%', $ticket['dt'], $msg); - $msg = str_replace('%%UPDATED%%', $ticket['lastchange'], $msg); - $msg = str_replace('%%ID%%', $ticket['id'], $msg); - - return $subject; + $msg = str_replace('%%SITE_TITLE%%', $heskSettings['site_title'], $msg); + $msg = str_replace('%%SITE_URL%%', $heskSettings['site_url'], $msg); + $msg = str_replace('%%CATEGORY%%', $category, $msg); + $msg = str_replace('%%PRIORITY%%', $priority, $msg); + $msg = str_replace('%%OWNER%%', $owner, $msg); + $msg = str_replace('%%STATUS%%', $statusName, $msg); + $msg = str_replace('%%EMAIL%%', $ticket->email, $msg); + $msg = str_replace('%%CREATED%%', $ticket->dateCreated, $msg); + $msg = str_replace('%%UPDATED%%', $ticket->lastChanged, $msg); + $msg = str_replace('%%ID%%', $ticket->id, $msg); + + return $msg; } } \ No newline at end of file From 36f8de957a18608402a43d5d6dcf1206ebbe45cd Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 28 Feb 2017 22:03:08 -0500 Subject: [PATCH 060/192] Able to parse email templates.... I hope --- .../Emails/EmailTemplateParser.php | 109 +++++++++++++++++- .../Emails/ParsedEmailProperties.php | 33 ++++++ api/BusinessLogic/Tickets/Reply.php | 62 ++++++++++ api/BusinessLogic/Tickets/Ticket.php | 41 ++++++- api/DataAccess/Tickets/TicketGateway.php | 4 +- 5 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 api/BusinessLogic/Emails/ParsedEmailProperties.php create mode 100644 api/BusinessLogic/Tickets/Reply.php diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index 8d32bfef..850393fa 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -46,7 +46,7 @@ class EmailTemplateParser { * @param $language string * @param $ticket Ticket */ - function getFormattedEmailForLanguage($templateName, $language, $ticket, $forStaff, $heskSettings) { + function getFormattedEmailForLanguage($templateName, $language, $ticket, $forStaff, $heskSettings, $modsForHeskSettings) { global $hesklang; $template = self::getFromFileSystem($templateName, $language, false); @@ -54,8 +54,10 @@ class EmailTemplateParser { $subject = ValidEmailTemplates::getValidEmailTemplates()[$templateName]; $subject = $this->parseSubject($subject, $ticket, $language, $heskSettings); - $message = $this->parseMessage($template, $ticket, $language, $forStaff, $heskSettings); - $htmlMessage = $this->parseMessage($htmlTemplate, $ticket, $language, $forStaff, $heskSettings); + $message = $this->parseMessage($template, $ticket, $language, $forStaff, $heskSettings, $modsForHeskSettings, false); + $htmlMessage = $this->parseMessage($htmlTemplate, $ticket, $language, $forStaff, $heskSettings, $modsForHeskSettings, true); + + return new ParsedEmailProperties($subject, $message, $htmlMessage); } /** @@ -144,7 +146,7 @@ class EmailTemplateParser { * @return string * @throws \Exception if common.inc.php isn't loaded */ - private function parseMessage($messageTemplate, $ticket, $language, $admin, $heskSettings) { + private function parseMessage($messageTemplate, $ticket, $language, $admin, $heskSettings, $modsForHeskSettings, $html) { global $hesklang; if (!function_exists('hesk_msgToPlain')) { @@ -205,6 +207,105 @@ class EmailTemplateParser { $msg = str_replace('%%UPDATED%%', $ticket->lastChanged, $msg); $msg = str_replace('%%ID%%', $ticket->id, $msg); + /* All custom fields */ + for ($i=1; $i<=50; $i++) { + $k = 'custom'.$i; + + if (isset($heskSettings['custom_fields'][$k])) { + $v = $heskSettings['custom_fields'][$k]; + + switch ($v['type']) { + case 'checkbox': + $ticket->customFields[$i] = str_replace("
","\n",$ticket->customFields[$i]); + break; + case 'date': + $ticket->customFields[$i] = hesk_custom_date_display_format($ticket->customFields[$i], $v['value']['date_format']); + break; + } + + $msg = str_replace('%%'.strtoupper($k).'%%',stripslashes($ticket->customFields[$i]),$msg); + } else { + $msg = str_replace('%%'.strtoupper($k).'%%','',$msg); + } + } + + // Is message tag in email template? + if (strpos($msg, '%%MESSAGE%%') !== false) { + // Replace message + if ($html) { + $htmlMessage = nl2br($ticket->message); + $msg = str_replace('%%MESSAGE%%', $htmlMessage, $msg); + } else { + $plainTextMessage = $ticket->message; + + $messageHtml = $ticket->usesHtml; + + if (count($ticket->replies) > 0) { + $lastReply = end($ticket->replies); + $messageHtml = $lastReply->usesHtml; + } + + if ($messageHtml) { + if (!function_exists('convert_html_to_text')) { + require(__DIR__ . '/../../../inc/html2text/html2text.php'); + } + $plainTextMessage = convert_html_to_text($plainTextMessage); + $plainTextMessage = fix_newlines($plainTextMessage); + } + $msg = str_replace('%%MESSAGE%%', $plainTextMessage, $msg); + } + + // Add direct links to any attachments at the bottom of the email message + if ($heskSettings['attachments']['use'] && isset($ticket->attachments) && count($ticket->attachments) > 0) { + if (!$modsForHeskSettings['attachments']) { + if ($html) { + $msg .= "


" . $hesklang['fatt']; + } else { + $msg .= "\n\n\n" . $hesklang['fatt']; + } + + foreach ($ticket->attachments as $attachment) { + if ($html) { + $msg .= "

{$attachment->fileName}
"; + } else { + $msg .= "\n\n{$attachment->fileName}\n"; + } + + $msg .= "{$heskSettings['hesk_url']}/download_attachment.php?att_id={$attachment->id}&track={$ticket->trackingId}{$heskSettings['e_param']}"; + } + } + } + + // For customer notifications: if we allow email piping/pop 3 fetching and + // stripping quoted replies add an "reply above this line" tag + if (!$admin && ($heskSettings['email_piping'] || $heskSettings['pop3']) && $heskSettings['strip_quoted']) { + $msg = $hesklang['EMAIL_HR'] . "\n\n" . $msg; + } + } elseif (strpos($msg, '%%MESSAGE_NO_ATTACHMENTS%%') !== false) { + if ($html) { + $htmlMessage = nl2br($ticket->message); + $msg = str_replace('%%MESSAGE_NO_ATTACHMENTS%%', $htmlMessage, $msg); + } else { + $plainTextMessage = $ticket->message; + + $messageHtml = $ticket->usesHtml; + + if (count($ticket->replies) > 0) { + $lastReply = end($ticket->replies); + $messageHtml = $lastReply->usesHtml; + } + + if ($messageHtml) { + if (!function_exists('convert_html_to_text')) { + require(__DIR__ . '/../../../inc/html2text/html2text.php'); + } + $plainTextMessage = convert_html_to_text($plainTextMessage); + $plainTextMessage = fix_newlines($plainTextMessage); + } + $msg = str_replace('%%MESSAGE_NO_ATTACHMENTS%%', $plainTextMessage, $msg); + } + } + return $msg; } } \ No newline at end of file diff --git a/api/BusinessLogic/Emails/ParsedEmailProperties.php b/api/BusinessLogic/Emails/ParsedEmailProperties.php new file mode 100644 index 00000000..5cbe594f --- /dev/null +++ b/api/BusinessLogic/Emails/ParsedEmailProperties.php @@ -0,0 +1,33 @@ +subject = $subject; + $this->message = $message; + $this->htmlMessage = $htmlMessage; + } + + /** + * @var $subject string + */ + public $subject; + + /** + * @var $message string + */ + public $message; + + /** + * @var $htmlMessage string + */ + public $htmlMessage; +} \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/Reply.php b/api/BusinessLogic/Tickets/Reply.php new file mode 100644 index 00000000..77c1d4b9 --- /dev/null +++ b/api/BusinessLogic/Tickets/Reply.php @@ -0,0 +1,62 @@ +id = intval($row['id']); $ticket->trackingId = $row['trackid']; @@ -93,6 +93,40 @@ class Ticket { $ticket->dueDate = $row['due_date']; $ticket->dueDateOverdueEmailSent = $row['overdue_email_sent'] !== null && intval($row['overdue_email_sent']) === 1; + $replies = array(); + while ($replyRow = hesk_dbFetchAssoc($repliesRs)) { + $reply = new Reply(); + $reply->id = $replyRow['id']; + $reply->ticketId = $replyRow['replyto']; + $reply->replierName = $replyRow['name']; + $reply->message = $replyRow['message']; + $reply->dateCreated = $replyRow['dt']; + + if (trim($replyRow['attachments']) !== '') { + $attachments = explode(',', $replyRow['attachments']); + $attachmentArray = array(); + foreach ($attachments as $attachment) { + $attachmentRow = explode('#', $attachment); + $attachmentModel = new Attachment(); + + $attachmentModel->id = $attachmentRow[0]; + $attachmentModel->fileName = $attachmentRow[1]; + $attachmentModel->savedName = $attachmentRow[2]; + + $attachmentArray[] = $attachmentModel; + } + $reply->attachments = $attachmentArray; + } + + $reply->staffId = $replyRow['staffid'] > 0 ? $replyRow['staffid'] : null; + $reply->rating = $replyRow['rating']; + $reply->isRead = $replyRow['read']; + $reply->usesHtml = $replyRow['html']; + + $replies[] = $reply; + } + $ticket->replies = $replies; + return $ticket; } @@ -300,4 +334,9 @@ class Ticket { * @var bool|null */ public $dueDateOverdueEmailSent; + + /** + * @var Reply[] + */ + public $replies; } \ No newline at end of file diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index 4d03b6e2..8b1f29d4 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -25,7 +25,9 @@ class TicketGateway extends CommonDao { $row = hesk_dbFetchAssoc($rs); $linkedTicketsRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `parent` = " . intval($id)); - $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $heskSettings); + $repliesRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `replyto` = " . intval($id) . " ORDER BY `id` ASC"); + + $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $heskSettings); $this->close(); From 9832ca58ba93401b8161c948bb555735793c2ff1 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 2 Mar 2017 22:05:28 -0500 Subject: [PATCH 061/192] Some refactoring to the email stuff --- api/BusinessLogic/Emails/EmailSender.php | 2 +- .../Emails/EmailSenderHelper.php | 35 ++++++++++ api/BusinessLogic/Emails/EmailTemplate.php | 27 ++++++++ .../Emails/EmailTemplateParser.php | 42 ++++++------ .../Emails/EmailTemplateRetriever.php | 65 +++++++++++++++++++ .../Emails/ValidEmailTemplates.php | 31 --------- .../Emails/EmailSenderHelperTest.php | 41 ++++++++++++ 7 files changed, 192 insertions(+), 51 deletions(-) create mode 100644 api/BusinessLogic/Emails/EmailSenderHelper.php create mode 100644 api/BusinessLogic/Emails/EmailTemplate.php create mode 100644 api/BusinessLogic/Emails/EmailTemplateRetriever.php delete mode 100644 api/BusinessLogic/Emails/ValidEmailTemplates.php create mode 100644 api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php diff --git a/api/BusinessLogic/Emails/EmailSender.php b/api/BusinessLogic/Emails/EmailSender.php index 4f300529..d1658451 100644 --- a/api/BusinessLogic/Emails/EmailSender.php +++ b/api/BusinessLogic/Emails/EmailSender.php @@ -9,7 +9,7 @@ use PHPMailer; interface EmailSender { /** - * Use to send emails that do NOT include ticket information + * Use to send emails * * @param $emailBuilder EmailBuilder * @param $heskSettings array diff --git a/api/BusinessLogic/Emails/EmailSenderHelper.php b/api/BusinessLogic/Emails/EmailSenderHelper.php new file mode 100644 index 00000000..5558024e --- /dev/null +++ b/api/BusinessLogic/Emails/EmailSenderHelper.php @@ -0,0 +1,35 @@ +emailTemplateParser = $emailTemplateParser; + $this->basicEmailSender = $basicEmailSender; + $this->mailgunEmailSender = $mailgunEmailSender; + } + + function sendEmailForTicket($emailTemplateName, $ticket, $heskSettings, $modsForHeskSettings) { + //-- parse template + + //-- if no mailgun, use basic sender + + //-- otherwise use mailgun sender + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Emails/EmailTemplate.php b/api/BusinessLogic/Emails/EmailTemplate.php new file mode 100644 index 00000000..02884679 --- /dev/null +++ b/api/BusinessLogic/Emails/EmailTemplate.php @@ -0,0 +1,27 @@ +languageKey = $languageKey === null ? $fileName : $languageKey; + $this->fileName = $fileName; + $this->forStaff = $forStaff; + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index 850393fa..c6a3df11 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -1,10 +1,4 @@ statusGateway = $statusGateway; $this->categoryGateway = $categoryGateway; $this->userGateway = $userGateway; + $this->emailTemplateRetriever = $emailTemplateRetriever; } /** - * @param $templateName string + * @param $templateId int * @param $language string * @param $ticket Ticket + * @param $heskSettings array + * @param $modsForHeskSettings array + * @return ParsedEmailProperties + * @throws InvalidEmailTemplateException */ - function getFormattedEmailForLanguage($templateName, $language, $ticket, $forStaff, $heskSettings, $modsForHeskSettings) { - global $hesklang; + function getFormattedEmailForLanguage($templateId, $language, $ticket, $heskSettings, $modsForHeskSettings) { + $emailTemplate = $this->emailTemplateRetriever->getTemplate($templateId); + + if ($emailTemplate === null) { + throw new InvalidEmailTemplateException($templateId); + } - $template = self::getFromFileSystem($templateName, $language, false); - $htmlTemplate = self::getFromFileSystem($templateName, $language, true); - $subject = ValidEmailTemplates::getValidEmailTemplates()[$templateName]; + $template = self::getFromFileSystem($emailTemplate->fileName, $language, false); + $htmlTemplate = self::getFromFileSystem($emailTemplate->fileName, $language, true); + $subject = $emailTemplate->languageKey; $subject = $this->parseSubject($subject, $ticket, $language, $heskSettings); - $message = $this->parseMessage($template, $ticket, $language, $forStaff, $heskSettings, $modsForHeskSettings, false); - $htmlMessage = $this->parseMessage($htmlTemplate, $ticket, $language, $forStaff, $heskSettings, $modsForHeskSettings, true); + $message = $this->parseMessage($template, $ticket, $language, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, false); + $htmlMessage = $this->parseMessage($htmlTemplate, $ticket, $language, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, true); return new ParsedEmailProperties($subject, $message, $htmlMessage); } @@ -66,13 +74,9 @@ class EmailTemplateParser { * @param $html bool * @return string The template * @throws EmailTemplateNotFoundException If the template was not found in the filesystem for the provided language - * @throws InvalidEmailTemplateException If the $template is not a valid template name */ private function getFromFileSystem($template, $language, $html) { - if (!isset(ValidEmailTemplates::getValidEmailTemplates()[$template])) { - throw new InvalidEmailTemplateException($template); - } $htmlFolder = $html ? 'html/' : ''; /* Get email template */ diff --git a/api/BusinessLogic/Emails/EmailTemplateRetriever.php b/api/BusinessLogic/Emails/EmailTemplateRetriever.php new file mode 100644 index 00000000..6263d8a7 --- /dev/null +++ b/api/BusinessLogic/Emails/EmailTemplateRetriever.php @@ -0,0 +1,65 @@ +validTemplates = array(); + $this->initializeArray(); + } + + const FORGOT_TICKET_ID = 0; + const NEW_REPLY_BY_STAFF = 1; + const NEW_TICKET = 2; + const VERIFY_EMAIL = 3; + const TICKET_CLOSED = 4; + const CATEGORY_MOVED = 5; + const NEW_REPLY_BY_CUSTOMER = 6; + const NEW_TICKET_STAFF = 7; + const TICKET_ASSIGNED_TO_YOU = 8; + const NEW_PM = 9; + const NEW_NOTE = 10; + const RESET_PASSWORD = 11; + const CALENDAR_REMINDER = 12; + const OVERDUE_TICKET = 13; + + function initializeArray() { + if (count($this->validTemplates) > 0) { + //-- Map already built + return; + } + + $this->validTemplates[self::FORGOT_TICKET_ID] = new EmailTemplate(false, 'forgot_ticket_id'); + $this->validTemplates[self::NEW_REPLY_BY_STAFF] = new EmailTemplate(false, 'new_reply_by_staff'); + $this->validTemplates[self::NEW_TICKET] = new EmailTemplate(false, 'new_ticket', 'ticket_received'); + $this->validTemplates[self::VERIFY_EMAIL] = new EmailTemplate(false, 'verify_email'); + $this->validTemplates[self::TICKET_CLOSED] = new EmailTemplate(false, 'ticket_closed'); + $this->validTemplates[self::CATEGORY_MOVED] = new EmailTemplate(true, 'category_moved'); + $this->validTemplates[self::NEW_REPLY_BY_CUSTOMER] = new EmailTemplate(true, 'new_reply_by_customer'); + $this->validTemplates[self::NEW_TICKET_STAFF] = new EmailTemplate(true, 'new_ticket_staff'); + $this->validTemplates[self::TICKET_ASSIGNED_TO_YOU] = new EmailTemplate(true, 'ticket_assigned_to_you'); + $this->validTemplates[self::NEW_PM] = new EmailTemplate(true, 'new_pm'); + $this->validTemplates[self::NEW_NOTE] = new EmailTemplate(true, 'new_note'); + $this->validTemplates[self::RESET_PASSWORD] = new EmailTemplate(true, 'reset_password'); + $this->validTemplates[self::CALENDAR_REMINDER] = new EmailTemplate(true, 'reset_password'); + $this->validTemplates[self::OVERDUE_TICKET] = new EmailTemplate(true, 'overdue_ticket'); + } + + /** + * @param $templateId + * @return EmailTemplate|null + */ + function getTemplate($templateId) { + if (isset($this->validTemplates[$templateId])) { + return $this->validTemplates[$templateId]; + } + + return null; + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Emails/ValidEmailTemplates.php b/api/BusinessLogic/Emails/ValidEmailTemplates.php deleted file mode 100644 index b57d3894..00000000 --- a/api/BusinessLogic/Emails/ValidEmailTemplates.php +++ /dev/null @@ -1,31 +0,0 @@ - 'forgot_ticket_id', - 'new_reply_by_staff' => 'new_reply_by_staff', - 'new_ticket' => 'ticket_received', - 'verify_email' => 'verify_email', - 'ticket_closed' => 'ticket_closed', - 'category_moved' => 'category_moved', - 'new_reply_by_customer' => 'new_reply_by_customer', - 'new_ticket_staff' => 'new_ticket_staff', - 'ticket_assigned_to_you' => 'ticket_assigned_to_you', - 'new_pm' => 'new_pm', - 'new_note' => 'new_note', - 'reset_password' => 'reset_password', - 'calendar_reminder' => 'calendar_reminder', - 'overdue_ticket' => 'overdue_ticket', - ); - } -} \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php new file mode 100644 index 00000000..4efcb99e --- /dev/null +++ b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php @@ -0,0 +1,41 @@ +emailTemplateParser = $this->createMock(EmailTemplateParser::class); + $this->basicEmailSender = $this->createMock(BasicEmailSender::class); + $this->mailgunEmailSender = $this->createMock(MailgunEmailSender::class); + + $this->emailSenderHelper = new EmailSenderHelper($this->emailTemplateParser, $this->basicEmailSender, + $this->mailgunEmailSender); + } + + function testItParsesTheTemplateForTheTicket() { + + } +} From e7eeac9f344d26249804ab07b7f895f7e8fdbe16 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 3 Mar 2017 22:05:41 -0500 Subject: [PATCH 062/192] Working on more email sender stuff --- .../Emails/EmailSenderHelper.php | 5 ++-- .../Emails/EmailTemplateParser.php | 14 +++++------ .../Emails/EmailSenderHelperTest.php | 23 +++++++++++++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/api/BusinessLogic/Emails/EmailSenderHelper.php b/api/BusinessLogic/Emails/EmailSenderHelper.php index 5558024e..10dd99ca 100644 --- a/api/BusinessLogic/Emails/EmailSenderHelper.php +++ b/api/BusinessLogic/Emails/EmailSenderHelper.php @@ -25,8 +25,9 @@ class EmailSenderHelper { $this->mailgunEmailSender = $mailgunEmailSender; } - function sendEmailForTicket($emailTemplateName, $ticket, $heskSettings, $modsForHeskSettings) { - //-- parse template + function sendEmailForTicket($templateId, $languageCode, $ticket, $heskSettings, $modsForHeskSettings) { + $parsedTemplate = $this->emailTemplateParser->getFormattedEmailForLanguage($templateId, $languageCode, + $ticket, $heskSettings, $modsForHeskSettings); //-- if no mailgun, use basic sender diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index c6a3df11..0d3974ca 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -43,27 +43,27 @@ class EmailTemplateParser { /** * @param $templateId int - * @param $language string + * @param $languageCode string * @param $ticket Ticket * @param $heskSettings array * @param $modsForHeskSettings array * @return ParsedEmailProperties * @throws InvalidEmailTemplateException */ - function getFormattedEmailForLanguage($templateId, $language, $ticket, $heskSettings, $modsForHeskSettings) { + function getFormattedEmailForLanguage($templateId, $languageCode, $ticket, $heskSettings, $modsForHeskSettings) { $emailTemplate = $this->emailTemplateRetriever->getTemplate($templateId); if ($emailTemplate === null) { throw new InvalidEmailTemplateException($templateId); } - $template = self::getFromFileSystem($emailTemplate->fileName, $language, false); - $htmlTemplate = self::getFromFileSystem($emailTemplate->fileName, $language, true); + $template = self::getFromFileSystem($emailTemplate->fileName, $languageCode, false); + $htmlTemplate = self::getFromFileSystem($emailTemplate->fileName, $languageCode, true); $subject = $emailTemplate->languageKey; - $subject = $this->parseSubject($subject, $ticket, $language, $heskSettings); - $message = $this->parseMessage($template, $ticket, $language, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, false); - $htmlMessage = $this->parseMessage($htmlTemplate, $ticket, $language, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, true); + $subject = $this->parseSubject($subject, $ticket, $languageCode, $heskSettings); + $message = $this->parseMessage($template, $ticket, $languageCode, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, false); + $htmlMessage = $this->parseMessage($htmlTemplate, $ticket, $languageCode, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, true); return new ParsedEmailProperties($subject, $message, $htmlMessage); } diff --git a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php index 4efcb99e..304868b4 100644 --- a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php +++ b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php @@ -3,6 +3,7 @@ namespace BusinessLogic\Emails; +use BusinessLogic\Tickets\Ticket; use PHPUnit\Framework\TestCase; class EmailSenderHelperTest extends TestCase { @@ -26,16 +27,38 @@ class EmailSenderHelperTest extends TestCase { */ private $emailSenderHelper; + /** + * @var $heskSettings array + */ + private $heskSettings; + + /** + * @var $modsForHeskSettings array + */ + private $modsForHeskSettings; + protected function setUp() { $this->emailTemplateParser = $this->createMock(EmailTemplateParser::class); $this->basicEmailSender = $this->createMock(BasicEmailSender::class); $this->mailgunEmailSender = $this->createMock(MailgunEmailSender::class); + $this->heskSettings = array(); $this->emailSenderHelper = new EmailSenderHelper($this->emailTemplateParser, $this->basicEmailSender, $this->mailgunEmailSender); } function testItParsesTheTemplateForTheTicket() { + //-- Arrange + $templateId = EmailTemplateRetriever::NEW_NOTE; + $languageCode = 'en'; + $ticket = new Ticket(); + + //-- Assert + $this->emailTemplateParser->expects($this->once()) + ->method('getFormattedEmailForLanguage') + ->with($templateId, $languageCode, $ticket, $this->heskSettings, $this->modsForHeskSettings); + //-- Act + $this->emailSenderHelper->sendEmailForTicket($templateId, $languageCode, $ticket, $this->heskSettings, $this->modsForHeskSettings); } } From 2a6766c10e64061997d75720d03b36e9da24aa6b Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 4 Mar 2017 00:43:08 -0500 Subject: [PATCH 063/192] Pretty much done with the EmailSenderHelper --- api/BusinessLogic/Emails/Addressees.php | 21 ++++++ .../Emails/EmailSenderHelper.php | 31 ++++++++- .../Emails/EmailSenderHelperTest.php | 64 ++++++++++++++++++- 3 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 api/BusinessLogic/Emails/Addressees.php diff --git a/api/BusinessLogic/Emails/Addressees.php b/api/BusinessLogic/Emails/Addressees.php new file mode 100644 index 00000000..4e71c8bf --- /dev/null +++ b/api/BusinessLogic/Emails/Addressees.php @@ -0,0 +1,21 @@ +mailgunEmailSender = $mailgunEmailSender; } - function sendEmailForTicket($templateId, $languageCode, $ticket, $heskSettings, $modsForHeskSettings) { + /** + * @param $templateId int the EmailTemplateRetriever::TEMPLATE_NAME + * @param $languageCode string the language code that matches the language folder + * @param $addressees Addressees the addressees. **cc and bcc addresses from custom fields will be added here!** + * @param $ticket Ticket + * @param $heskSettings array + * @param $modsForHeskSettings array + */ + function sendEmailForTicket($templateId, $languageCode, $addressees, $ticket, $heskSettings, $modsForHeskSettings) { $parsedTemplate = $this->emailTemplateParser->getFormattedEmailForLanguage($templateId, $languageCode, $ticket, $heskSettings, $modsForHeskSettings); - //-- if no mailgun, use basic sender + $emailBuilder = new EmailBuilder(); + $emailBuilder->subject = $parsedTemplate->subject; + $emailBuilder->message = $parsedTemplate->message; + $emailBuilder->htmlMessage = $parsedTemplate->htmlMessage; + $emailBuilder->to = $addressees->to; + $emailBuilder->cc = $addressees->cc; + $emailBuilder->bcc = $addressees->bcc; + + if ($modsForHeskSettings['attachments']) { + $emailBuilder->attachments = $ticket->attachments; + } + + if ($modsForHeskSettings['use_mailgun']) { + $this->mailgunEmailSender->sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $modsForHeskSettings['html_emails']); + } else { + $this->basicEmailSender->sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $modsForHeskSettings['html_emails']); + } - //-- otherwise use mailgun sender } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php index 304868b4..2f3a4d98 100644 --- a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php +++ b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php @@ -42,6 +42,11 @@ class EmailSenderHelperTest extends TestCase { $this->basicEmailSender = $this->createMock(BasicEmailSender::class); $this->mailgunEmailSender = $this->createMock(MailgunEmailSender::class); $this->heskSettings = array(); + $this->modsForHeskSettings = array( + 'attachments' => 0, + 'use_mailgun' => 0, + 'html_emails' => 0 + ); $this->emailSenderHelper = new EmailSenderHelper($this->emailTemplateParser, $this->basicEmailSender, $this->mailgunEmailSender); @@ -52,6 +57,7 @@ class EmailSenderHelperTest extends TestCase { $templateId = EmailTemplateRetriever::NEW_NOTE; $languageCode = 'en'; $ticket = new Ticket(); + $this->emailTemplateParser->method('getFormattedEmailForLanguage')->willReturn(new ParsedEmailProperties('Subject', 'Message', 'HTML Message')); //-- Assert $this->emailTemplateParser->expects($this->once()) @@ -59,6 +65,62 @@ class EmailSenderHelperTest extends TestCase { ->with($templateId, $languageCode, $ticket, $this->heskSettings, $this->modsForHeskSettings); //-- Act - $this->emailSenderHelper->sendEmailForTicket($templateId, $languageCode, $ticket, $this->heskSettings, $this->modsForHeskSettings); + $this->emailSenderHelper->sendEmailForTicket($templateId, $languageCode, new Addressees(), $ticket, $this->heskSettings, $this->modsForHeskSettings); + } + + function testItSendsTheEmailThroughTheMailgunEmailSender() { + //-- Arrange + $addressees = new Addressees(); + $addressees->to = ['to@email']; + $addressees->cc = ['cc1', 'cc2']; + $addressees->bcc = ['bcc1', 'bcc2']; + $this->modsForHeskSettings['use_mailgun'] = 1; + $this->modsForHeskSettings['html_emails'] = true; + + $expectedEmailBuilder = new EmailBuilder(); + $expectedEmailBuilder->to = $addressees->to; + $expectedEmailBuilder->cc = $addressees->cc; + $expectedEmailBuilder->bcc = $addressees->bcc; + $expectedEmailBuilder->subject = 'Subject'; + $expectedEmailBuilder->message = 'Message'; + $expectedEmailBuilder->htmlMessage = 'HTML Message'; + + $this->emailTemplateParser->method('getFormattedEmailForLanguage')->willReturn(new ParsedEmailProperties('Subject', 'Message', 'HTML Message')); + + //-- Assert + $this->mailgunEmailSender->expects($this->once()) + ->method('sendEmail') + ->with($expectedEmailBuilder, $this->heskSettings, $this->modsForHeskSettings, true); + + //-- Act + $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_NOTE, 'en', $addressees, new Ticket(), $this->heskSettings, $this->modsForHeskSettings); + } + + function testItSendsTheEmailThroughTheBasicEmailSender() { + //-- Arrange + $addressees = new Addressees(); + $addressees->to = ['to@email']; + $addressees->cc = ['cc1', 'cc2']; + $addressees->bcc = ['bcc1', 'bcc2']; + $this->modsForHeskSettings['use_mailgun'] = 0; + $this->modsForHeskSettings['html_emails'] = true; + + $expectedEmailBuilder = new EmailBuilder(); + $expectedEmailBuilder->to = $addressees->to; + $expectedEmailBuilder->cc = $addressees->cc; + $expectedEmailBuilder->bcc = $addressees->bcc; + $expectedEmailBuilder->subject = 'Subject'; + $expectedEmailBuilder->message = 'Message'; + $expectedEmailBuilder->htmlMessage = 'HTML Message'; + + $this->emailTemplateParser->method('getFormattedEmailForLanguage')->willReturn(new ParsedEmailProperties('Subject', 'Message', 'HTML Message')); + + //-- Assert + $this->basicEmailSender->expects($this->once()) + ->method('sendEmail') + ->with($expectedEmailBuilder, $this->heskSettings, $this->modsForHeskSettings, true); + + //-- Act + $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_NOTE, 'en', $addressees, new Ticket(), $this->heskSettings, $this->modsForHeskSettings); } } From 672d089b201e0a3f09f2e90a811272507b80c619 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 4 Mar 2017 21:59:55 -0500 Subject: [PATCH 064/192] Working on more email stuff --- api/BusinessLogic/Emails/EmailSenderHelper.php | 11 +++++++++++ api/BusinessLogic/Tickets/TicketCreator.php | 10 +++++++++- .../BusinessLogic/Emails/EmailSenderHelperTest.php | 4 +++- .../CreateTicketForCustomerTest.php | 4 ++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/api/BusinessLogic/Emails/EmailSenderHelper.php b/api/BusinessLogic/Emails/EmailSenderHelper.php index 4dbd9436..1724dc25 100644 --- a/api/BusinessLogic/Emails/EmailSenderHelper.php +++ b/api/BusinessLogic/Emails/EmailSenderHelper.php @@ -47,6 +47,17 @@ class EmailSenderHelper { $emailBuilder->cc = $addressees->cc; $emailBuilder->bcc = $addressees->bcc; + foreach ($heskSettings['custom_fields'] as $k => $v) { + $number = intval(str_replace('custom', '', $k)); + if ($v['use'] && $v['type'] == 'email' && !empty($ticket->customFields[$number])) { + if ($v['value']['email_type'] == 'cc') { + $emailBuilder->cc[] = $ticket->customFields[$number]; + } elseif ($v['value']['email_type'] == 'bcc') { + $emailBuilder->bcc[] = $ticket->customFields[$number]; + } + } + } + if ($modsForHeskSettings['attachments']) { $emailBuilder->attachments = $ticket->attachments; } diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 8409623c..25e4ba22 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -2,6 +2,7 @@ namespace BusinessLogic\Tickets; +use BusinessLogic\Emails\EmailSenderHelper; use BusinessLogic\Exceptions\ValidationException; use BusinessLogic\Statuses\DefaultStatusForAction; use DataAccess\Statuses\StatusGateway; @@ -38,13 +39,20 @@ class TicketCreator { */ private $verifiedEmailChecker; - function __construct($newTicketValidator, $trackingIdGenerator, $autoassigner, $statusGateway, $ticketGateway, $verifiedEmailChecker) { + /** + * @var $emailSenderHelper EmailSenderHelper + */ + private $emailSenderHelper; + + function __construct($newTicketValidator, $trackingIdGenerator, $autoassigner, + $statusGateway, $ticketGateway, $verifiedEmailChecker, $emailSenderHelper) { $this->newTicketValidator = $newTicketValidator; $this->trackingIdGenerator = $trackingIdGenerator; $this->autoassigner = $autoassigner; $this->statusGateway = $statusGateway; $this->ticketGateway = $ticketGateway; $this->verifiedEmailChecker = $verifiedEmailChecker; + $this->emailSenderHelper = $emailSenderHelper; } /** diff --git a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php index 2f3a4d98..050abcb3 100644 --- a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php +++ b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php @@ -41,7 +41,9 @@ class EmailSenderHelperTest extends TestCase { $this->emailTemplateParser = $this->createMock(EmailTemplateParser::class); $this->basicEmailSender = $this->createMock(BasicEmailSender::class); $this->mailgunEmailSender = $this->createMock(MailgunEmailSender::class); - $this->heskSettings = array(); + $this->heskSettings = array( + 'custom_fields' => array() + ); $this->modsForHeskSettings = array( 'attachments' => 0, 'use_mailgun' => 0, diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index 54c22684..bf80c962 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -246,4 +246,8 @@ class CreateTicketTest extends TestCase { //-- Act $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); } + + function testItSendsAnEmailToTheCustomerWhenTheTicketIsCreated() { + //-- + } } From 2201f188bd230539035e8067c1b9d20a2c54e2d3 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 5 Mar 2017 22:03:11 -0500 Subject: [PATCH 065/192] Some changes --- .../Tickets/CreateTicketByCustomerModel.php | 5 ++++ .../CreateTicketForCustomerTest.php | 24 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php b/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php index 25a577f0..7163e6b8 100644 --- a/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php +++ b/api/BusinessLogic/Tickets/CreateTicketByCustomerModel.php @@ -72,4 +72,9 @@ class CreateTicketByCustomerModel { * @var string */ public $language; + + /** + * @var $sendEmailToCustomer bool + */ + public $sendEmailToCustomer; } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index bf80c962..65e8110b 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -3,6 +3,10 @@ namespace BusinessLogic\Tickets\TicketCreatorTests; +use BusinessLogic\Emails\Addressees; +use BusinessLogic\Emails\EmailSenderHelper; +use BusinessLogic\Emails\EmailTemplate; +use BusinessLogic\Emails\EmailTemplateRetriever; use BusinessLogic\Security\UserContext; use BusinessLogic\Statuses\DefaultStatusForAction; use BusinessLogic\Statuses\Status; @@ -81,6 +85,11 @@ class CreateTicketTest extends TestCase { */ private $verifiedEmailChecker; + /** + * @var $emailSenderHelper \PHPUnit_Framework_MockObject_MockObject + */ + private $emailSenderHelper; + protected function setUp() { $this->ticketGateway = $this->createMock(TicketGateway::class); $this->newTicketValidator = $this->createMock(NewTicketValidator::class); @@ -88,9 +97,10 @@ class CreateTicketTest extends TestCase { $this->autoassigner = $this->createMock(Autoassigner::class); $this->statusGateway = $this->createMock(StatusGateway::class); $this->verifiedEmailChecker = $this->createMock(VerifiedEmailChecker::class); + $this->emailSenderHelper = $this->createMock(EmailSenderHelper::class); $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, - $this->autoassigner, $this->statusGateway, $this->ticketGateway, $this->verifiedEmailChecker); + $this->autoassigner, $this->statusGateway, $this->ticketGateway, $this->verifiedEmailChecker, $this->emailSenderHelper); $this->ticketRequest = new CreateTicketByCustomerModel(); $this->ticketRequest->name = 'Name'; @@ -248,6 +258,16 @@ class CreateTicketTest extends TestCase { } function testItSendsAnEmailToTheCustomerWhenTheTicketIsCreated() { - //-- + //-- Arrange + $this->ticketRequest->sendEmailToCustomer = true; + $expectedAddressees = new Addressees(); + $expectedAddressees->to = $this->ticketRequest->email; + + //-- Assert + $this->emailSenderHelper->expects($this->once())->method('sendEmailForTicket') + ->with(EmailTemplateRetriever::NEW_TICKET, 'en', $this->anything(), $this->heskSettings, $this->modsForHeskSettings); + + //-- Act + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); } } From 01c56da52ca4c3b11d9f76de48f43ab2659a2dda Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 6 Mar 2017 21:44:38 -0500 Subject: [PATCH 066/192] Working on adding Spot ORM --- .../Emails/EmailSenderHelper.php | 6 +- api/BusinessLogic/Tickets/TicketCreator.php | 36 +- api/DataAccess/Security/UserGateway.php | 15 + .../Emails/EmailSenderHelperTest.php | 9 +- .../CreateTicketForCustomerTest.php | 36 +- api/autoload.php | 7 + api/composer.json | 2 +- api/composer.lock | 621 +++++++++++++++++- 8 files changed, 721 insertions(+), 11 deletions(-) diff --git a/api/BusinessLogic/Emails/EmailSenderHelper.php b/api/BusinessLogic/Emails/EmailSenderHelper.php index 1724dc25..b02453ab 100644 --- a/api/BusinessLogic/Emails/EmailSenderHelper.php +++ b/api/BusinessLogic/Emails/EmailSenderHelper.php @@ -29,13 +29,15 @@ class EmailSenderHelper { /** * @param $templateId int the EmailTemplateRetriever::TEMPLATE_NAME - * @param $languageCode string the language code that matches the language folder + * @param $language string the language name * @param $addressees Addressees the addressees. **cc and bcc addresses from custom fields will be added here!** * @param $ticket Ticket * @param $heskSettings array * @param $modsForHeskSettings array */ - function sendEmailForTicket($templateId, $languageCode, $addressees, $ticket, $heskSettings, $modsForHeskSettings) { + function sendEmailForTicket($templateId, $language, $addressees, $ticket, $heskSettings, $modsForHeskSettings) { + $languageCode = $heskSettings['languages'][$language]['folder']; + $parsedTemplate = $this->emailTemplateParser->getFormattedEmailForLanguage($templateId, $languageCode, $ticket, $heskSettings, $modsForHeskSettings); diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 25e4ba22..46a17589 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -2,9 +2,12 @@ namespace BusinessLogic\Tickets; +use BusinessLogic\Emails\Addressees; use BusinessLogic\Emails\EmailSenderHelper; +use BusinessLogic\Emails\EmailTemplateRetriever; use BusinessLogic\Exceptions\ValidationException; use BusinessLogic\Statuses\DefaultStatusForAction; +use DataAccess\Security\UserGateway; use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; @@ -44,8 +47,13 @@ class TicketCreator { */ private $emailSenderHelper; + /** + * @var $userGateway UserGateway + */ + private $userGateway; + function __construct($newTicketValidator, $trackingIdGenerator, $autoassigner, - $statusGateway, $ticketGateway, $verifiedEmailChecker, $emailSenderHelper) { + $statusGateway, $ticketGateway, $verifiedEmailChecker, $emailSenderHelper, $userGateway) { $this->newTicketValidator = $newTicketValidator; $this->trackingIdGenerator = $trackingIdGenerator; $this->autoassigner = $autoassigner; @@ -53,6 +61,7 @@ class TicketCreator { $this->ticketGateway = $ticketGateway; $this->verifiedEmailChecker = $verifiedEmailChecker; $this->emailSenderHelper = $emailSenderHelper; + $this->userGateway = $userGateway; } /** @@ -126,6 +135,31 @@ class TicketCreator { $ticket->timeWorked = '00:00:00'; $ticket->lastReplier = 0; + $addressees = new Addressees(); + $addressees->to = $this->getAddressees($ticket->email); + + if ($ticketRequest->sendEmailToCustomer) { + $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_TICKET, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings); + } + + if ($ticket->ownerId !== null) { + $ownerEmail = $this->userGateway->getEmailForId($ticket->ownerId, $heskSettings); + + $addressees = new Addressees(); + $addressees->to = array($ownerEmail); + $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::TICKET_ASSIGNED_TO_YOU, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings); + } + return $ticket; } + + private function getAddressees($emailAddress) { + if ($emailAddress === null) { + return null; + } + + $emails = str_replace(';', ',', $emailAddress); + + return explode(',', $emails); + } } \ No newline at end of file diff --git a/api/DataAccess/Security/UserGateway.php b/api/DataAccess/Security/UserGateway.php index b44be8b5..be6e43ff 100644 --- a/api/DataAccess/Security/UserGateway.php +++ b/api/DataAccess/Security/UserGateway.php @@ -46,4 +46,19 @@ class UserGateway extends CommonDao { return $row['name']; } + + // TODO Replace this with a basic User retriever + function getEmailForId($id, $heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT `email` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = " . intval($id)); + + if (hesk_dbNumRows($rs) === 0) { + return null; + } + + $row = hesk_dbFetchAssoc($rs); + + return $row['email']; + } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php index 050abcb3..c9d5c0cb 100644 --- a/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php +++ b/api/Tests/BusinessLogic/Emails/EmailSenderHelperTest.php @@ -42,6 +42,9 @@ class EmailSenderHelperTest extends TestCase { $this->basicEmailSender = $this->createMock(BasicEmailSender::class); $this->mailgunEmailSender = $this->createMock(MailgunEmailSender::class); $this->heskSettings = array( + 'languages' => array( + 'English' => array('folder' => 'en') + ), 'custom_fields' => array() ); $this->modsForHeskSettings = array( @@ -67,7 +70,7 @@ class EmailSenderHelperTest extends TestCase { ->with($templateId, $languageCode, $ticket, $this->heskSettings, $this->modsForHeskSettings); //-- Act - $this->emailSenderHelper->sendEmailForTicket($templateId, $languageCode, new Addressees(), $ticket, $this->heskSettings, $this->modsForHeskSettings); + $this->emailSenderHelper->sendEmailForTicket($templateId, 'English', new Addressees(), $ticket, $this->heskSettings, $this->modsForHeskSettings); } function testItSendsTheEmailThroughTheMailgunEmailSender() { @@ -95,7 +98,7 @@ class EmailSenderHelperTest extends TestCase { ->with($expectedEmailBuilder, $this->heskSettings, $this->modsForHeskSettings, true); //-- Act - $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_NOTE, 'en', $addressees, new Ticket(), $this->heskSettings, $this->modsForHeskSettings); + $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_NOTE, 'English', $addressees, new Ticket(), $this->heskSettings, $this->modsForHeskSettings); } function testItSendsTheEmailThroughTheBasicEmailSender() { @@ -123,6 +126,6 @@ class EmailSenderHelperTest extends TestCase { ->with($expectedEmailBuilder, $this->heskSettings, $this->modsForHeskSettings, true); //-- Act - $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_NOTE, 'en', $addressees, new Ticket(), $this->heskSettings, $this->modsForHeskSettings); + $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_NOTE, 'English', $addressees, new Ticket(), $this->heskSettings, $this->modsForHeskSettings); } } diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index 65e8110b..23ab848c 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -116,7 +116,7 @@ class CreateTicketTest extends TestCase { 'require_subject' => 1, 'require_message' => 1, 'custom_fields' => array(), - 'autoassign' => 0, + 'autoassign' => 0 ); $this->modsForHeskSettings = array( 'customer_email_verification_required' => false @@ -260,12 +260,42 @@ class CreateTicketTest extends TestCase { function testItSendsAnEmailToTheCustomerWhenTheTicketIsCreated() { //-- Arrange $this->ticketRequest->sendEmailToCustomer = true; + $this->ticketRequest->language = 'English'; + $expectedAddressees = new Addressees(); + $expectedAddressees->to = array($this->ticketRequest->email); + + //-- Assert + $this->emailSenderHelper->expects($this->once())->method('sendEmailForTicket') + ->with(EmailTemplateRetriever::NEW_TICKET, 'English', $expectedAddressees, $this->anything(), $this->heskSettings, $this->modsForHeskSettings); + + //-- Act + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + } + + function testItDoesNotSendsAnEmailToTheCustomerWhenTheTicketIsCreatedAndSendToCustomerIsFalse() { + //-- Arrange + $this->ticketRequest->sendEmailToCustomer = false; + $this->ticketRequest->language = 'English'; + $expectedAddressees = new Addressees(); + $expectedAddressees->to = array($this->ticketRequest->email); + + //-- Assert + $this->emailSenderHelper->expects($this->never())->method('sendEmailForTicket'); + + //-- Act + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + } + + function testItSendsAnEmailToTheAssignedToOwnerWhenTheTicketIsCreated() { + //-- Arrange + $this->ticketRequest->sendEmailToCustomer = true; + $this->ticketRequest->language = 'English'; $expectedAddressees = new Addressees(); - $expectedAddressees->to = $this->ticketRequest->email; + $expectedAddressees->to = array($this->ticketRequest->email); //-- Assert $this->emailSenderHelper->expects($this->once())->method('sendEmailForTicket') - ->with(EmailTemplateRetriever::NEW_TICKET, 'en', $this->anything(), $this->heskSettings, $this->modsForHeskSettings); + ->with(EmailTemplateRetriever::NEW_TICKET, 'English', $expectedAddressees, $this->anything(), $this->heskSettings, $this->modsForHeskSettings); //-- Act $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); diff --git a/api/autoload.php b/api/autoload.php index 5ffee38e..acdb59e9 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -14,9 +14,16 @@ require_once(__DIR__ . '/http_response_code.php'); hesk_load_api_database_functions(); +global $hesk_settings; + // HESK files that require database access require_once(__DIR__ . '/../inc/custom_fields.inc.php'); +// Load Spot ORM +$config = new \Spot\Config(); +$config->addConnection('mysql', "mysql://{$hesk_settings['db_user']}:{$hesk_settings['db_pass']}@{$hesk_settings['db_host']}/{$hesk_settings['db_name']}"); +$spot = new \Spot\Locator($config); + // Load the ApplicationContext $applicationContext = new \ApplicationContext(); //$modsForHeskSettings = mfh_getSettings(); \ No newline at end of file diff --git a/api/composer.json b/api/composer.json index f79dd05d..3c793199 100644 --- a/api/composer.json +++ b/api/composer.json @@ -17,6 +17,6 @@ "php-http/message": "^1.5", "php-http/curl-client": "^1.7", "guzzlehttp/psr7": "^1.3", - "doctrine/orm": "*" + "vlucas/spot2": "~2.0" } } diff --git a/api/composer.lock b/api/composer.lock index 1048b547..7ac81d56 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "d7fe662ac2836f7d979fd4117ec8bca6", + "content-hash": "266b9167ab52abc3c4a2514bf29491ed", "packages": [ { "name": "clue/stream-filter", @@ -55,6 +55,422 @@ ], "time": "2015-11-08T23:41:30+00:00" }, + { + "name": "doctrine/annotations", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-02-24 16:22:25" + }, + { + "name": "doctrine/cache", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "0da649fce4838f7a6121c501c9a86d4b8921b648" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/0da649fce4838f7a6121c501c9a86d4b8921b648", + "reference": "0da649fce4838f7a6121c501c9a86d4b8921b648", + "shasum": "" + }, + "require": { + "php": "~5.6|~7.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2017-03-06 14:38:51" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03T10:49:41+00:00" + }, + { + "name": "doctrine/common", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "4b434dbf8d204198dac708f2e938f7c805864dd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/4b434dbf8d204198dac708f2e938f7c805864dd6", + "reference": "4b434dbf8d204198dac708f2e938f7c805864dd6", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~5.6|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ], + "time": "2017-03-06 07:30:42" + }, + { + "name": "doctrine/dbal", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "50bf623418be0feb3282bb50d07a4aea977fb33a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/50bf623418be0feb3282bb50d07a4aea977fb33a", + "reference": "50bf623418be0feb3282bb50d07a4aea977fb33a", + "shasum": "" + }, + "require": { + "doctrine/common": "^2.7.1", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4.6", + "phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5", + "symfony/console": "2.*||^3.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ], + "time": "2017-02-25 22:09:19" + }, + { + "name": "doctrine/inflector", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "803a2ed9fea02f9ca47cd45395089fe78769a392" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/803a2ed9fea02f9ca47cd45395089fe78769a392", + "reference": "803a2ed9fea02f9ca47cd45395089fe78769a392", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2016-05-12 17:23:41" + }, { "name": "doctrine/instantiator", "version": "dev-master", @@ -109,6 +525,60 @@ ], "time": "2017-01-23 09:23:06" }, + { + "name": "doctrine/lexer", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09 13:34:57" + }, { "name": "guzzle/guzzle", "version": "dev-master", @@ -1636,6 +2106,57 @@ ], "time": "2016-08-06 14:39:51" }, + { + "name": "sabre/event", + "version": "2.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-event.git", + "reference": "337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-event/zipball/337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff", + "reference": "337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff", + "shasum": "" + }, + "require": { + "php": ">=5.4.1" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~0.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "sabre/event is a library for lightweight event-based programming", + "homepage": "http://sabre.io/event/", + "keywords": [ + "EventEmitter", + "events", + "hooks", + "plugin", + "promise", + "signal" + ], + "time": "2015-05-19 10:24:22" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "dev-master", @@ -2264,6 +2785,104 @@ "homepage": "https://symfony.com", "time": "2017-01-21 17:10:26" }, + { + "name": "vlucas/spot2", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/vlucas/spot2.git", + "reference": "f30e5439c1c8d969490d773bc3f87937e675083a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/spot2/zipball/f30e5439c1c8d969490d773bc3f87937e675083a", + "reference": "f30e5439c1c8d969490d773bc3f87937e675083a", + "shasum": "" + }, + "require": { + "doctrine/dbal": "~2.4", + "php": ">=5.4", + "sabre/event": "~2.0", + "vlucas/valitron": "1.x" + }, + "type": "library", + "autoload": { + "psr-0": { + "Spot": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com", + "role": "Developer" + } + ], + "description": "Simple DataMapper built on top of Doctrine DBAL", + "homepage": "https://github.com/vlucas/spot2", + "keywords": [ + "database", + "datamapper", + "dbal", + "doctrine", + "entity", + "mapper", + "model", + "orm" + ], + "time": "2014-07-03T14:29:08+00:00" + }, + { + "name": "vlucas/valitron", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/valitron.git", + "reference": "b33c79116260637337187b7125f955ae26d306cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/valitron/zipball/b33c79116260637337187b7125f955ae26d306cc", + "reference": "b33c79116260637337187b7125f955ae26d306cc", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Valitron": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Simple, elegant, stand-alone validation library with NO dependencies", + "homepage": "http://github.com/vlucas/valitron", + "keywords": [ + "valid", + "validation", + "validator" + ], + "time": "2017-02-23T08:31:59+00:00" + }, { "name": "webmozart/assert", "version": "dev-master", From 64428872df0bee1e4ed11de1e11922d9aafe113d Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 8 Mar 2017 21:49:30 -0500 Subject: [PATCH 067/192] Created an entity for user --- api/DataAccess/Entities/BaseEntity.php | 14 ++++++++ api/DataAccess/Entities/User.php | 49 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 api/DataAccess/Entities/BaseEntity.php create mode 100644 api/DataAccess/Entities/User.php diff --git a/api/DataAccess/Entities/BaseEntity.php b/api/DataAccess/Entities/BaseEntity.php new file mode 100644 index 00000000..ccc77d62 --- /dev/null +++ b/api/DataAccess/Entities/BaseEntity.php @@ -0,0 +1,14 @@ + ['type' => 'integer', 'primary' => true, 'autoincrement' => true], + 'user' => ['type' => 'string', 'required' => true, 'default' => ''], + 'pass' => ['type' => 'string', 'required' => true], + 'isadmin' => ['type' => 'string', 'required' => true, 'default' => '0'], + 'name' => ['type' => 'string', 'required' => true, 'default' => ''], + 'email' => ['type' => 'string', 'required' => true, 'default' => ''], + 'signature' => ['type' => 'string', 'required' => true, 'default' => ''], + 'language' => ['type' => 'string', 'required' => false], + 'categories' => ['type' => 'string', 'required' => true, 'default' => ''], + 'afterreply' => ['type' => 'string', 'required' => true, 'default' => '0'], + 'autostart' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'autoreload' => ['type' => 'smallint', 'required' => true, 'default' => 0], + 'notify_customer_new' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_customer_reply' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'show_suggested' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_new_unassigned' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_new_my' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_reply_unassigned' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_reply_my' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_assigned' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_pm' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_note' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'notify_note_unassigned' => ['type' => 'string', 'required' => false, 'default' => '0'], + 'default_calendar_view' => ['type' => 'integer', 'required' => true, 'default' => '0'], + 'notify_overdue_unassigned' => ['type' => 'string', 'required' => true, 'default' => '0'], + 'default_list' => ['type' => 'string', 'required' => true, 'default' => ''], + 'autoassign' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'heskprivileges' => ['type' => 'string', 'required' => false], + 'ratingneg' => ['type' => 'integer', 'required' => true, 'default' => 0], + 'ratingpos' => ['type' => 'integer', 'required' => true, 'default' => 0], + 'rating' => ['type' => 'float', 'required' => true, 'default' => 0], + 'replies' => ['type' => 'integer', 'required' => true, 'default' => 0], + 'active' => ['type' => 'string', 'required' => true, 'default' => '1'], + 'permission_template' => ['type' => 'integer', 'required' => false] + ]; + //@formatter:on + } +} \ No newline at end of file From ad2c96c667705e90c6aadfbd747d90eb4542960e Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 9 Mar 2017 21:55:32 -0500 Subject: [PATCH 068/192] Update composer.json and ApplicationContext --- api/ApplicationContext.php | 34 ++++++++++++-- api/BusinessLogic/Tickets/TicketCreator.php | 2 + api/DataAccess/Entities/BaseEntity.php | 14 ------ api/DataAccess/Entities/User.php | 49 --------------------- api/autoload.php | 7 +-- api/composer.json | 3 +- api/index.php | 9 +++- 7 files changed, 43 insertions(+), 75 deletions(-) delete mode 100644 api/DataAccess/Entities/BaseEntity.php delete mode 100644 api/DataAccess/Entities/User.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 76de8849..3f13facf 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -2,6 +2,11 @@ // Responsible for loading in all necessary classes. AKA a poor man's DI solution. use BusinessLogic\Categories\CategoryRetriever; +use BusinessLogic\Emails\BasicEmailSender; +use BusinessLogic\Emails\EmailSenderHelper; +use BusinessLogic\Emails\EmailTemplateParser; +use BusinessLogic\Emails\EmailTemplateRetriever; +use BusinessLogic\Emails\MailgunEmailSender; use BusinessLogic\Security\BanRetriever; use BusinessLogic\Security\UserContextBuilder; use BusinessLogic\Tickets\Autoassigner; @@ -10,11 +15,13 @@ use BusinessLogic\Tickets\TicketCreator; use BusinessLogic\Tickets\NewTicketValidator; use BusinessLogic\Tickets\TicketValidators; use BusinessLogic\Tickets\TrackingIdGenerator; +use BusinessLogic\Tickets\VerifiedEmailChecker; use DataAccess\Categories\CategoryGateway; use DataAccess\Security\BanGateway; use DataAccess\Security\UserGateway; use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; +use DataAccess\Tickets\VerifiedEmailGateway; class ApplicationContext { @@ -23,7 +30,11 @@ class ApplicationContext { function __construct() { $this->get = array(); - // User Context + // Verified Email Checker + $this->get[VerifiedEmailGateway::class] = new VerifiedEmailGateway(); + $this->get[VerifiedEmailChecker::class] = new VerifiedEmailChecker($this->get[VerifiedEmailGateway::class]); + + // Users $this->get[UserGateway::class] = new UserGateway(); $this->get[UserContextBuilder::class] = new UserContextBuilder($this->get[UserGateway::class]); @@ -35,8 +46,22 @@ class ApplicationContext { $this->get[BanGateway::class] = new BanGateway(); $this->get[BanRetriever::class] = new BanRetriever($this->get[BanGateway::class]); - // Tickets + // Statuses $this->get[StatusGateway::class] = new StatusGateway(); + + // Email Sender + $this->get[EmailTemplateRetriever::class] = new EmailTemplateRetriever(); + $this->get[EmailTemplateParser::class] = new EmailTemplateParser($this->get[StatusGateway::class], + $this->get[CategoryGateway::class], + $this->get[UserGateway::class], + $this->get[EmailTemplateRetriever::class]); + $this->get[BasicEmailSender::class] = new BasicEmailSender(); + $this->get[MailgunEmailSender::class] = new MailgunEmailSender(); + $this->get[EmailSenderHelper::class] = new EmailSenderHelper($this->get[EmailTemplateParser::class], + $this->get[BasicEmailSender::class], + $this->get[MailgunEmailSender::class]); + + // Tickets $this->get[TicketGateway::class] = new TicketGateway(); $this->get[TicketRetriever::class] = new TicketRetriever($this->get[TicketGateway::class]); $this->get[TicketValidators::class] = new TicketValidators($this->get[TicketGateway::class]); @@ -49,6 +74,9 @@ class ApplicationContext { $this->get[TrackingIdGenerator::class], $this->get[Autoassigner::class], $this->get[StatusGateway::class], - $this->get[TicketGateway::class]); + $this->get[TicketGateway::class], + $this->get[VerifiedEmailChecker::class], + $this->get[EmailSenderHelper::class], + $this->get[UserGateway::class]); } } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 46a17589..a6d1225f 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -148,6 +148,8 @@ class TicketCreator { $addressees = new Addressees(); $addressees->to = array($ownerEmail); $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::TICKET_ASSIGNED_TO_YOU, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings); + } else { + // TODO email all users who should be notified } return $ticket; diff --git a/api/DataAccess/Entities/BaseEntity.php b/api/DataAccess/Entities/BaseEntity.php deleted file mode 100644 index ccc77d62..00000000 --- a/api/DataAccess/Entities/BaseEntity.php +++ /dev/null @@ -1,14 +0,0 @@ - ['type' => 'integer', 'primary' => true, 'autoincrement' => true], - 'user' => ['type' => 'string', 'required' => true, 'default' => ''], - 'pass' => ['type' => 'string', 'required' => true], - 'isadmin' => ['type' => 'string', 'required' => true, 'default' => '0'], - 'name' => ['type' => 'string', 'required' => true, 'default' => ''], - 'email' => ['type' => 'string', 'required' => true, 'default' => ''], - 'signature' => ['type' => 'string', 'required' => true, 'default' => ''], - 'language' => ['type' => 'string', 'required' => false], - 'categories' => ['type' => 'string', 'required' => true, 'default' => ''], - 'afterreply' => ['type' => 'string', 'required' => true, 'default' => '0'], - 'autostart' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'autoreload' => ['type' => 'smallint', 'required' => true, 'default' => 0], - 'notify_customer_new' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_customer_reply' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'show_suggested' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_new_unassigned' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_new_my' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_reply_unassigned' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_reply_my' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_assigned' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_pm' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_note' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'notify_note_unassigned' => ['type' => 'string', 'required' => false, 'default' => '0'], - 'default_calendar_view' => ['type' => 'integer', 'required' => true, 'default' => '0'], - 'notify_overdue_unassigned' => ['type' => 'string', 'required' => true, 'default' => '0'], - 'default_list' => ['type' => 'string', 'required' => true, 'default' => ''], - 'autoassign' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'heskprivileges' => ['type' => 'string', 'required' => false], - 'ratingneg' => ['type' => 'integer', 'required' => true, 'default' => 0], - 'ratingpos' => ['type' => 'integer', 'required' => true, 'default' => 0], - 'rating' => ['type' => 'float', 'required' => true, 'default' => 0], - 'replies' => ['type' => 'integer', 'required' => true, 'default' => 0], - 'active' => ['type' => 'string', 'required' => true, 'default' => '1'], - 'permission_template' => ['type' => 'integer', 'required' => false] - ]; - //@formatter:on - } -} \ No newline at end of file diff --git a/api/autoload.php b/api/autoload.php index acdb59e9..e06961f9 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -4,7 +4,7 @@ // Core requirements define('IN_SCRIPT', 1); define('HESK_PATH', '../'); -require_once(__DIR__ . 'vendor/autoload.php'); +require_once(__DIR__ . '/vendor/autoload.php'); require_once(__DIR__ . '/bootstrap.php'); require_once(__DIR__ . '/../hesk_settings.inc.php'); require_once(__DIR__ . '/../inc/common.inc.php'); @@ -19,11 +19,6 @@ global $hesk_settings; // HESK files that require database access require_once(__DIR__ . '/../inc/custom_fields.inc.php'); -// Load Spot ORM -$config = new \Spot\Config(); -$config->addConnection('mysql', "mysql://{$hesk_settings['db_user']}:{$hesk_settings['db_pass']}@{$hesk_settings['db_host']}/{$hesk_settings['db_name']}"); -$spot = new \Spot\Locator($config); - // Load the ApplicationContext $applicationContext = new \ApplicationContext(); //$modsForHeskSettings = mfh_getSettings(); \ No newline at end of file diff --git a/api/composer.json b/api/composer.json index 3c793199..8b3f0002 100644 --- a/api/composer.json +++ b/api/composer.json @@ -16,7 +16,6 @@ "php-http/guzzle6-adapter": "^1.1", "php-http/message": "^1.5", "php-http/curl-client": "^1.7", - "guzzlehttp/psr7": "^1.3", - "vlucas/spot2": "~2.0" + "guzzlehttp/psr7": "^1.3" } } diff --git a/api/index.php b/api/index.php index d6932a50..3010cddf 100644 --- a/api/index.php +++ b/api/index.php @@ -34,13 +34,20 @@ function buildUserContext($xAuthToken) { } function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { - exceptionHandler(new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage))); + if ($errorNumber === E_WARNING) { + //-- TODO log a warning + } elseif ($errorNumber === E_NOTICE || $errorNumber === E_USER_NOTICE) { + //-- TODO log an info + } else { + exceptionHandler(new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage))); + } } /** * @param $exception Exception */ function exceptionHandler($exception) { + //-- TODO Log an error if (exceptionIsOfType($exception, \BusinessLogic\Exceptions\ApiFriendlyException::class)) { /* @var $castedException \BusinessLogic\Exceptions\ApiFriendlyException */ $castedException = $exception; From af789a70604a12cd0ca732c24ab8828a2eebc630 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 10 Mar 2017 22:06:32 -0500 Subject: [PATCH 069/192] Working on autoassign rewrite logic --- api/BusinessLogic/Security/UserContext.php | 49 +++++++++++++ api/BusinessLogic/Tickets/Autoassigner.php | 26 ++++++- api/DataAccess/Security/UserGateway.php | 26 +++++++ .../Tickets/AutoassignerTest.php | 73 +++++++++++++++++++ .../CreateTicketForCustomerTest.php | 10 ++- 5 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 api/Tests/BusinessLogic/Tickets/AutoassignerTest.php diff --git a/api/BusinessLogic/Security/UserContext.php b/api/BusinessLogic/Security/UserContext.php index ec99ccf9..1f781023 100644 --- a/api/BusinessLogic/Security/UserContext.php +++ b/api/BusinessLogic/Security/UserContext.php @@ -29,4 +29,53 @@ class UserContext { public $rating; public $totalNumberOfReplies; public $active; + + /** + * Builds a user context based on the current session. **The session must be active!** + * @param $dataRow array the $_SESSION superglobal or the hesk_users result set + * @return UserContext the built user context + */ + static function fromDataRow($dataRow) { + $userContext = new UserContext(); + $userContext->id = $dataRow['id']; + $userContext->username = $dataRow['user']; + $userContext->admin = $dataRow['isadmin']; + $userContext->name = $dataRow['name']; + $userContext->email = $dataRow['email']; + $userContext->signature = $dataRow['signature']; + $userContext->language = $dataRow['language']; + $userContext->categories = explode(',', $dataRow['categories']); + $userContext->permissions = explode(',', $dataRow['heskprivileges']); + $userContext->autoAssign = $dataRow['autoassign']; + $userContext->ratingNegative = $dataRow['ratingneg']; + $userContext->ratingPositive = $dataRow['ratingpos']; + $userContext->rating = $dataRow['rating']; + $userContext->totalNumberOfReplies = $dataRow['replies']; + $userContext->active = $dataRow['active']; + + $preferences = new UserContextPreferences(); + $preferences->afterReply = $dataRow['afterreply']; + $preferences->autoStartTimeWorked = $dataRow['autostart']; + $preferences->autoreload = $dataRow['autoreload']; + $preferences->defaultNotifyCustomerNewTicket = $dataRow['notify_customer_new']; + $preferences->defaultNotifyCustomerReply = $dataRow['notify_customer_reply']; + $preferences->showSuggestedKnowledgebaseArticles = $dataRow['show_suggested']; + $preferences->defaultCalendarView = $dataRow['default_calendar_view']; + $preferences->defaultTicketView = $dataRow['default_list']; + $userContext->preferences = $preferences; + + $notifications = new UserContextNotifications(); + $notifications->newUnassigned = $dataRow['notify_new_unassigned']; + $notifications->newAssignedToMe = $dataRow['notify_new_my']; + $notifications->replyUnassigned = $dataRow['notify_reply_unassigned']; + $notifications->replyToMe = $dataRow['notify_reply_my']; + $notifications->ticketAssignedToMe = $dataRow['notify_assigned']; + $notifications->privateMessage = $dataRow['notify_pm']; + $notifications->noteOnTicketAssignedToMe = $dataRow['notify_note']; + $notifications->noteOnTicketNotAssignedToMe = $dataRow['notify_note_unassigned']; + $notifications->overdueTicketUnassigned = $dataRow['notify_overdue_unassigned']; + $userContext->notificationSettings = $notifications; + + return $userContext; + } } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/Autoassigner.php b/api/BusinessLogic/Tickets/Autoassigner.php index 514a9dda..4b8f654c 100644 --- a/api/BusinessLogic/Tickets/Autoassigner.php +++ b/api/BusinessLogic/Tickets/Autoassigner.php @@ -3,13 +3,35 @@ namespace BusinessLogic\Tickets; +use BusinessLogic\Security\UserContext; +use DataAccess\Categories\CategoryGateway; +use DataAccess\Security\UserGateway; + class Autoassigner { + /* @var $categoryGateway CategoryGateway */ + private $categoryGateway; + + /* @var $userGateway UserGateway */ + private $userGateway; + + function __construct($categoryGateway, $userGateway) { + $this->categoryGateway = $categoryGateway; + $this->userGateway = $userGateway; + } + /** * @param $categoryId int * @param $heskSettings array - * @return int|null The user ID, or null if no user found + * @return UserContext the user who is assigned, or null if no user should be assigned */ function getNextUserForTicket($categoryId, $heskSettings) { - return 0; + if (!$heskSettings['autoassign']) { + return null; + } + + $potentialUsers = $this->userGateway->getUsersByNumberOfOpenTickets($heskSettings); + + + return $potentialUsers[0]; } } \ No newline at end of file diff --git a/api/DataAccess/Security/UserGateway.php b/api/DataAccess/Security/UserGateway.php index be6e43ff..b32053b0 100644 --- a/api/DataAccess/Security/UserGateway.php +++ b/api/DataAccess/Security/UserGateway.php @@ -2,6 +2,7 @@ namespace DataAccess\Security; +use BusinessLogic\Security\UserContext; use BusinessLogic\Security\UserContextBuilder; use DataAccess\CommonDao; use Exception; @@ -61,4 +62,29 @@ class UserGateway extends CommonDao { return $row['email']; } + + function getUsersByNumberOfOpenTickets($heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT `t1`.`id`,`t1`.`user`,`t1`.`name`, `t1`.`email`, `t1`.`language`, `t1`.`isadmin`, + `t1`.`categories`, `t1`.`notify_assigned`, `t1`.`heskprivileges`, + (SELECT COUNT(*) FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` + WHERE `owner`=`t1`.`id` + AND `status` IN ( + SELECT `ID` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "statuses` + WHERE `IsClosed` = 0 + ) + ) AS `open_tickets` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` AS `t1` + WHERE `t1`.`autoassign` = '1' ORDER BY `open_tickets` ASC, RAND()"); + + $users = array(); + + while ($row = hesk_dbFetchAssoc($rs)) { + $user = UserContext::fromDataRow($row); + $users[] = $user; + } + + return $users; + } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php b/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php new file mode 100644 index 00000000..215adfee --- /dev/null +++ b/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php @@ -0,0 +1,73 @@ +categoryGateway = $this->createMock(CategoryGateway::class); + $this->userGateway = $this->createMock(UserGateway::class); + $this->autoassigner = new Autoassigner($this->categoryGateway, $this->userGateway); + $this->heskSettings = array( + 'autoassign' => 1 + ); + } + + function testItReturnsNullWhenAutoassignIsDisabled() { + //-- Arrange + $this->heskSettings['autoassign'] = 0; + + //-- Act + $owner = $this->autoassigner->getNextUserForTicket(0, $this->heskSettings); + + //-- Assert + self::assertThat($owner, self::isNull()); + } + + function testItReturnsTheUsersWithLeastOpenTickets() { + //-- Arrange + $userWithNoOpenTickets = new UserContext(); + $userWithNoOpenTickets->id = 1; + $userWithNoOpenTickets->categories = array(1); + $userWithOneOpenTicket = new UserContext(); + $userWithOneOpenTicket->id = 2; + $userWithOneOpenTicket->categories = array(1); + $usersToReturn = array( + $userWithNoOpenTickets, + $userWithOneOpenTicket + ); + + $this->userGateway->method('getUsersByNumberOfOpenTickets') + ->with($this->heskSettings) + ->willReturn($usersToReturn); + + //-- Act + $actual = $this->autoassigner->getNextUserForTicket(1, $this->heskSettings); + + //-- Assert + self::assertThat($actual, self::equalTo($userWithNoOpenTickets)); + } +} diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index 23ab848c..f3072816 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -19,6 +19,7 @@ use BusinessLogic\Tickets\TrackingIdGenerator; use BusinessLogic\Tickets\VerifiedEmailChecker; use BusinessLogic\ValidationModel; use Core\Constants\Priority; +use DataAccess\Security\UserGateway; use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; use PHPUnit\Framework\TestCase; @@ -90,6 +91,11 @@ class CreateTicketTest extends TestCase { */ private $emailSenderHelper; + /** + * @var $userGateway \PHPUnit_Framework_MockObject_MockObject + */ + private $userGateway; + protected function setUp() { $this->ticketGateway = $this->createMock(TicketGateway::class); $this->newTicketValidator = $this->createMock(NewTicketValidator::class); @@ -98,9 +104,11 @@ class CreateTicketTest extends TestCase { $this->statusGateway = $this->createMock(StatusGateway::class); $this->verifiedEmailChecker = $this->createMock(VerifiedEmailChecker::class); $this->emailSenderHelper = $this->createMock(EmailSenderHelper::class); + $this->userGateway = $this->createMock(UserGateway::class); $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, - $this->autoassigner, $this->statusGateway, $this->ticketGateway, $this->verifiedEmailChecker, $this->emailSenderHelper); + $this->autoassigner, $this->statusGateway, $this->ticketGateway, $this->verifiedEmailChecker, + $this->emailSenderHelper, $this->userGateway); $this->ticketRequest = new CreateTicketByCustomerModel(); $this->ticketRequest->name = 'Name'; From 06a38e9383718470221118388da24ce9c527bb71 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 11 Mar 2017 23:43:46 -0500 Subject: [PATCH 070/192] Autoassign logic is finished --- api/BusinessLogic/Security/UserContext.php | 37 +++++- api/BusinessLogic/Tickets/Autoassigner.php | 11 +- api/DataAccess/Security/UserGateway.php | 4 + .../Tickets/AutoassignerTest.php | 111 ++++++++++++++++++ 4 files changed, 156 insertions(+), 7 deletions(-) diff --git a/api/BusinessLogic/Security/UserContext.php b/api/BusinessLogic/Security/UserContext.php index 1f781023..30baf47b 100644 --- a/api/BusinessLogic/Security/UserContext.php +++ b/api/BusinessLogic/Security/UserContext.php @@ -4,30 +4,55 @@ namespace BusinessLogic\Security; class UserContext { + /* @var $id int */ public $id; + + /* @var $username string */ public $username; + + /* @var $admin bool */ public $admin; + + /* @var $name string */ public $name; + + /* @var $email string */ public $email; + + /* @var $signature string */ public $signature; + + /* @var $language string|null */ public $language; + + /* @var $categories int[] */ public $categories; + + /* @var $permissions string[] */ public $permissions; - /** - * @var UserContextPreferences - */ + /* @var UserContextPreferences */ public $preferences; - /** - * @var UserContextNotifications - */ + /* @var UserContextNotifications */ public $notificationSettings; + + /* @var $autoAssign bool */ public $autoAssign; + + /* @var $ratingNegative int */ public $ratingNegative; + + /* @var $ratingPositive int */ public $ratingPositive; + + /* @var $rating float */ public $rating; + + /* @var $totalNumberOfReplies int */ public $totalNumberOfReplies; + + /* @var $active bool */ public $active; /** diff --git a/api/BusinessLogic/Tickets/Autoassigner.php b/api/BusinessLogic/Tickets/Autoassigner.php index 4b8f654c..4ff6fabd 100644 --- a/api/BusinessLogic/Tickets/Autoassigner.php +++ b/api/BusinessLogic/Tickets/Autoassigner.php @@ -31,7 +31,16 @@ class Autoassigner { $potentialUsers = $this->userGateway->getUsersByNumberOfOpenTickets($heskSettings); + foreach ($potentialUsers as $potentialUser) { + if ($potentialUser->admin || + (in_array($categoryId, $potentialUser->categories) && + in_array('can_view_tickets', $potentialUser->permissions) && + in_array('can_reply_tickets', $potentialUser->permissions))) { + return $potentialUser; + } + } + - return $potentialUsers[0]; + return null; } } \ No newline at end of file diff --git a/api/DataAccess/Security/UserGateway.php b/api/DataAccess/Security/UserGateway.php index b32053b0..341f3e31 100644 --- a/api/DataAccess/Security/UserGateway.php +++ b/api/DataAccess/Security/UserGateway.php @@ -63,6 +63,10 @@ class UserGateway extends CommonDao { return $row['email']; } + /** + * @param $heskSettings array + * @return UserContext[] + */ function getUsersByNumberOfOpenTickets($heskSettings) { $this->init(); diff --git a/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php b/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php index 215adfee..1962395b 100644 --- a/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php +++ b/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php @@ -47,14 +47,20 @@ class AutoassignerTest extends TestCase { self::assertThat($owner, self::isNull()); } + function getPermissionsForUser() { + return array('can_view_tickets', 'can_reply_tickets'); + } + function testItReturnsTheUsersWithLeastOpenTickets() { //-- Arrange $userWithNoOpenTickets = new UserContext(); $userWithNoOpenTickets->id = 1; $userWithNoOpenTickets->categories = array(1); + $userWithNoOpenTickets->permissions = $this->getPermissionsForUser(); $userWithOneOpenTicket = new UserContext(); $userWithOneOpenTicket->id = 2; $userWithOneOpenTicket->categories = array(1); + $userWithOneOpenTicket->permissions = $this->getPermissionsForUser(); $usersToReturn = array( $userWithNoOpenTickets, $userWithOneOpenTicket @@ -70,4 +76,109 @@ class AutoassignerTest extends TestCase { //-- Assert self::assertThat($actual, self::equalTo($userWithNoOpenTickets)); } + + function testItOnlyReturnsUsersWhoCanAccessTheCategory() { + //-- Arrange + $userWithNoOpenTickets = new UserContext(); + $userWithNoOpenTickets->id = 1; + $userWithNoOpenTickets->categories = array(1); + $userWithNoOpenTickets->permissions = $this->getPermissionsForUser(); + $userWithOneOpenTicket = new UserContext(); + $userWithOneOpenTicket->id = 2; + $userWithOneOpenTicket->categories = array(2); + $userWithOneOpenTicket->permissions = $this->getPermissionsForUser(); + $usersToReturn = array( + $userWithNoOpenTickets, + $userWithOneOpenTicket + ); + + $this->userGateway->method('getUsersByNumberOfOpenTickets') + ->with($this->heskSettings) + ->willReturn($usersToReturn); + + //-- Act + $actual = $this->autoassigner->getNextUserForTicket(2, $this->heskSettings); + + //-- Assert + self::assertThat($actual, self::equalTo($userWithOneOpenTicket)); + } + + function testItReturnsAdminUsers() { + //-- Arrange + $userWithNoOpenTickets = new UserContext(); + $userWithNoOpenTickets->id = 1; + $userWithNoOpenTickets->categories = array(1); + $userWithNoOpenTickets->permissions = $this->getPermissionsForUser(); + $userWithNoOpenTickets->admin = true; + $userWithOneOpenTicket = new UserContext(); + $userWithOneOpenTicket->id = 2; + $userWithOneOpenTicket->categories = array(2); + $userWithOneOpenTicket->permissions = $this->getPermissionsForUser(); + $usersToReturn = array( + $userWithNoOpenTickets, + $userWithOneOpenTicket + ); + + $this->userGateway->method('getUsersByNumberOfOpenTickets') + ->with($this->heskSettings) + ->willReturn($usersToReturn); + + //-- Act + $actual = $this->autoassigner->getNextUserForTicket(2, $this->heskSettings); + + //-- Assert + self::assertThat($actual, self::equalTo($userWithNoOpenTickets)); + } + + function testItOnlyReturnsUsersWhoCanViewAndRespondToTickets() { + //-- Arrange + $userWithNoOpenTickets = new UserContext(); + $userWithNoOpenTickets->id = 1; + $userWithNoOpenTickets->categories = array(1); + $userWithNoOpenTickets->permissions = array(); + $userWithOneOpenTicket = new UserContext(); + $userWithOneOpenTicket->id = 2; + $userWithOneOpenTicket->categories = array(1); + $userWithOneOpenTicket->permissions = $this->getPermissionsForUser(); + $usersToReturn = array( + $userWithNoOpenTickets, + $userWithOneOpenTicket + ); + + $this->userGateway->method('getUsersByNumberOfOpenTickets') + ->with($this->heskSettings) + ->willReturn($usersToReturn); + + //-- Act + $actual = $this->autoassigner->getNextUserForTicket(1, $this->heskSettings); + + //-- Assert + self::assertThat($actual, self::equalTo($userWithOneOpenTicket)); + } + + function testItReturnsNullWhenNoOneCanHandleTheTicket() { + //-- Arrange + $userWithNoOpenTickets = new UserContext(); + $userWithNoOpenTickets->id = 1; + $userWithNoOpenTickets->categories = array(1); + $userWithNoOpenTickets->permissions = $this->getPermissionsForUser(); + $userWithOneOpenTicket = new UserContext(); + $userWithOneOpenTicket->id = 2; + $userWithOneOpenTicket->categories = array(1); + $userWithOneOpenTicket->permissions = $this->getPermissionsForUser(); + $usersToReturn = array( + $userWithNoOpenTickets, + $userWithOneOpenTicket + ); + + $this->userGateway->method('getUsersByNumberOfOpenTickets') + ->with($this->heskSettings) + ->willReturn($usersToReturn); + + //-- Act + $actual = $this->autoassigner->getNextUserForTicket(2, $this->heskSettings); + + //-- Assert + self::assertThat($actual, self::isNull()); + } } From ccbe2cd5801d39da746e51c149818bea1810a326 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Mar 2017 00:44:50 -0500 Subject: [PATCH 071/192] MFH Settings being loaded, emails being sent out --- api/ApplicationContext.php | 9 ++- api/BusinessLogic/Categories/Category.php | 3 + .../Emails/EmailTemplateParser.php | 2 +- api/BusinessLogic/Tickets/TicketCreator.php | 16 +++-- api/Controllers/Tickets/TicketController.php | 5 +- api/DataAccess/Categories/CategoryGateway.php | 1 + api/DataAccess/Security/UserGateway.php | 2 + .../Settings/ModsForHeskSettingsGateway.php | 23 ++++++ .../CreateTicketForCustomerTest.php | 71 +++++++++++++------ api/autoload.php | 3 +- 10 files changed, 100 insertions(+), 35 deletions(-) create mode 100644 api/DataAccess/Settings/ModsForHeskSettingsGateway.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 3f13facf..0fc14d58 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -19,6 +19,7 @@ use BusinessLogic\Tickets\VerifiedEmailChecker; use DataAccess\Categories\CategoryGateway; use DataAccess\Security\BanGateway; use DataAccess\Security\UserGateway; +use DataAccess\Settings\ModsForHeskSettingsGateway; use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; use DataAccess\Tickets\VerifiedEmailGateway; @@ -30,6 +31,9 @@ class ApplicationContext { function __construct() { $this->get = array(); + // Settings + $this->get[ModsForHeskSettingsGateway::class] = new ModsForHeskSettingsGateway(); + // Verified Email Checker $this->get[VerifiedEmailGateway::class] = new VerifiedEmailGateway(); $this->get[VerifiedEmailChecker::class] = new VerifiedEmailChecker($this->get[VerifiedEmailGateway::class]); @@ -66,7 +70,7 @@ class ApplicationContext { $this->get[TicketRetriever::class] = new TicketRetriever($this->get[TicketGateway::class]); $this->get[TicketValidators::class] = new TicketValidators($this->get[TicketGateway::class]); $this->get[TrackingIdGenerator::class] = new TrackingIdGenerator($this->get[TicketGateway::class]); - $this->get[Autoassigner::class] = new Autoassigner(); + $this->get[Autoassigner::class] = new Autoassigner($this->get[CategoryGateway::class], $this->get[UserGateway::class]); $this->get[NewTicketValidator::class] = new NewTicketValidator($this->get[CategoryRetriever::class], $this->get[BanRetriever::class], $this->get[TicketValidators::class]); @@ -77,6 +81,7 @@ class ApplicationContext { $this->get[TicketGateway::class], $this->get[VerifiedEmailChecker::class], $this->get[EmailSenderHelper::class], - $this->get[UserGateway::class]); + $this->get[UserGateway::class], + $this->get[ModsForHeskSettingsGateway::class]); } } \ No newline at end of file diff --git a/api/BusinessLogic/Categories/Category.php b/api/BusinessLogic/Categories/Category.php index 5711f33d..ab9bc1af 100644 --- a/api/BusinessLogic/Categories/Category.php +++ b/api/BusinessLogic/Categories/Category.php @@ -8,6 +8,9 @@ class Category { */ public $id; + /* @var $name string */ + public $name; + /** * @var int Categories order number */ diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index 0d3974ca..8a1601ab 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -174,7 +174,7 @@ class EmailTemplateParser { // Status name and category name $defaultStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings); $statusName = hesk_msgToPlain($defaultStatus->localizedNames[$language]->text); - $category = hesk_msgToPlain($this->categoryGateway->getAllCategories($heskSettings)[$ticket->categoryId]); + $category = hesk_msgToPlain($this->categoryGateway->getAllCategories($heskSettings)[$ticket->categoryId]->name); $owner = hesk_msgToPlain($this->userGateway->getNameForId($ticket->ownerId, $heskSettings)); switch ($ticket->priorityId) { diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index a6d1225f..822ee513 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -8,6 +8,7 @@ use BusinessLogic\Emails\EmailTemplateRetriever; use BusinessLogic\Exceptions\ValidationException; use BusinessLogic\Statuses\DefaultStatusForAction; use DataAccess\Security\UserGateway; +use DataAccess\Settings\ModsForHeskSettingsGateway; use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; @@ -52,8 +53,11 @@ class TicketCreator { */ private $userGateway; - function __construct($newTicketValidator, $trackingIdGenerator, $autoassigner, - $statusGateway, $ticketGateway, $verifiedEmailChecker, $emailSenderHelper, $userGateway) { + /* @var $modsForHeskSettingsGateway ModsForHeskSettingsGateway */ + private $modsForHeskSettingsGateway; + + function __construct($newTicketValidator, $trackingIdGenerator, $autoassigner, $statusGateway, $ticketGateway, + $verifiedEmailChecker, $emailSenderHelper, $userGateway, $modsForHeskSettingsGateway) { $this->newTicketValidator = $newTicketValidator; $this->trackingIdGenerator = $trackingIdGenerator; $this->autoassigner = $autoassigner; @@ -62,6 +66,7 @@ class TicketCreator { $this->verifiedEmailChecker = $verifiedEmailChecker; $this->emailSenderHelper = $emailSenderHelper; $this->userGateway = $userGateway; + $this->modsForHeskSettingsGateway = $modsForHeskSettingsGateway; } /** @@ -69,13 +74,14 @@ class TicketCreator { * * @param $ticketRequest CreateTicketByCustomerModel * @param $heskSettings array HESK settings - * @param $modsForHeskSettings array Mods for HESK settings * @param $userContext * @return Ticket The newly created ticket * @throws ValidationException When a required field in $ticket_request is missing * @throws \Exception When the default status for new tickets is not found */ - function createTicketByCustomer($ticketRequest, $heskSettings, $modsForHeskSettings, $userContext) { + function createTicketByCustomer($ticketRequest, $heskSettings, $userContext) { + $modsForHeskSettings = $this->modsForHeskSettingsGateway->getAllSettings($heskSettings); + $validationModel = $this->newTicketValidator->validateNewTicketForCustomer($ticketRequest, $heskSettings, $userContext); if (count($validationModel->errorKeys) > 0) { @@ -96,7 +102,7 @@ class TicketCreator { $ticket->trackingId = $this->trackingIdGenerator->generateTrackingId($heskSettings); if ($heskSettings['autoassign']) { - $ticket->ownerId = $this->autoassigner->getNextUserForTicket($ticketRequest->category, $heskSettings); + $ticket->ownerId = $this->autoassigner->getNextUserForTicket($ticketRequest->category, $heskSettings)->id; } // Transform one-to-one properties diff --git a/api/Controllers/Tickets/TicketController.php b/api/Controllers/Tickets/TicketController.php index 01a58e13..017e6819 100644 --- a/api/Controllers/Tickets/TicketController.php +++ b/api/Controllers/Tickets/TicketController.php @@ -20,14 +20,14 @@ class TicketController { } function post() { - global $applicationContext, $hesk_settings, $modsForHeskSettings, $userContext; + global $applicationContext, $hesk_settings, $userContext; /* @var $ticketCreator TicketCreator */ $ticketCreator = $applicationContext->get[TicketCreator::class]; $jsonRequest = JsonRetriever::getJsonData(); - $ticket = $ticketCreator->createTicketByCustomer($this->buildTicketRequestFromJson($jsonRequest), $hesk_settings, $modsForHeskSettings, $userContext); + $ticket = $ticketCreator->createTicketByCustomer($this->buildTicketRequestFromJson($jsonRequest), $hesk_settings, $userContext); //if ticket is a stageTicket, email user //else if assigned to owner, email new owner @@ -55,6 +55,7 @@ class TicketController { $ticketRequest->screenResolution = Helpers::safeArrayGet($json, 'screenResolution'); $ticketRequest->ipAddress = Helpers::safeArrayGet($json, 'ip'); $ticketRequest->language = Helpers::safeArrayGet($json, 'language'); + $ticketRequest->sendEmailToCustomer = Helpers::safeArrayGet($json, 'sendEmailToCustomer'); $ticketRequest->customFields = array(); $jsonCustomFields = Helpers::safeArrayGet($json, 'customFields'); diff --git a/api/DataAccess/Categories/CategoryGateway.php b/api/DataAccess/Categories/CategoryGateway.php index 68f605e4..abda07b9 100644 --- a/api/DataAccess/Categories/CategoryGateway.php +++ b/api/DataAccess/Categories/CategoryGateway.php @@ -23,6 +23,7 @@ class CategoryGateway extends CommonDao { $category = new Category(); $category->id = intval($row['id']); + $category->name = $row['name']; $category->catOrder = intval($row['cat_order']); $category->autoAssign = $row['autoassign'] == 1; $category->type = intval($row['type']); diff --git a/api/DataAccess/Security/UserGateway.php b/api/DataAccess/Security/UserGateway.php index 341f3e31..d9a24f54 100644 --- a/api/DataAccess/Security/UserGateway.php +++ b/api/DataAccess/Security/UserGateway.php @@ -89,6 +89,8 @@ class UserGateway extends CommonDao { $users[] = $user; } + $this->close(); + return $users; } } \ No newline at end of file diff --git a/api/DataAccess/Settings/ModsForHeskSettingsGateway.php b/api/DataAccess/Settings/ModsForHeskSettingsGateway.php new file mode 100644 index 00000000..aa56b229 --- /dev/null +++ b/api/DataAccess/Settings/ModsForHeskSettingsGateway.php @@ -0,0 +1,23 @@ +init(); + + $rs = hesk_dbQuery("SELECT `Key`, `Value` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "settings` WHERE `Key` <> 'modsForHeskVersion'"); + + $settings = array(); + while ($row = hesk_dbFetchAssoc($rs)) { + $settings[$row['Key']] = $row['Value']; + } + + $this->close(); + + return $settings; + } +} \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index f3072816..5f8383e0 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -5,10 +5,8 @@ namespace BusinessLogic\Tickets\TicketCreatorTests; use BusinessLogic\Emails\Addressees; use BusinessLogic\Emails\EmailSenderHelper; -use BusinessLogic\Emails\EmailTemplate; use BusinessLogic\Emails\EmailTemplateRetriever; use BusinessLogic\Security\UserContext; -use BusinessLogic\Statuses\DefaultStatusForAction; use BusinessLogic\Statuses\Status; use BusinessLogic\Tickets\Autoassigner; use BusinessLogic\Tickets\CreateTicketByCustomerModel; @@ -20,6 +18,7 @@ use BusinessLogic\Tickets\VerifiedEmailChecker; use BusinessLogic\ValidationModel; use Core\Constants\Priority; use DataAccess\Security\UserGateway; +use DataAccess\Settings\ModsForHeskSettingsGateway; use DataAccess\Statuses\StatusGateway; use DataAccess\Tickets\TicketGateway; use PHPUnit\Framework\TestCase; @@ -66,11 +65,6 @@ class CreateTicketTest extends TestCase { */ private $heskSettings; - /** - * @var $modsForHeskSettings array - */ - private $modsForHeskSettings; - /** * @var $userContext UserContext */ @@ -96,6 +90,12 @@ class CreateTicketTest extends TestCase { */ private $userGateway; + /* @var $modsForHeskSettingsGateway \PHPUnit_Framework_MockObject_MockObject */ + private $modsForHeskSettingsGateway; + + /* @var $modsForHeskSettings array */ + private $modsForHeskSettings; + protected function setUp() { $this->ticketGateway = $this->createMock(TicketGateway::class); $this->newTicketValidator = $this->createMock(NewTicketValidator::class); @@ -105,10 +105,11 @@ class CreateTicketTest extends TestCase { $this->verifiedEmailChecker = $this->createMock(VerifiedEmailChecker::class); $this->emailSenderHelper = $this->createMock(EmailSenderHelper::class); $this->userGateway = $this->createMock(UserGateway::class); + $this->modsForHeskSettingsGateway = $this->createMock(ModsForHeskSettingsGateway::class); $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, $this->autoassigner, $this->statusGateway, $this->ticketGateway, $this->verifiedEmailChecker, - $this->emailSenderHelper, $this->userGateway); + $this->emailSenderHelper, $this->userGateway, $this->modsForHeskSettingsGateway); $this->ticketRequest = new CreateTicketByCustomerModel(); $this->ticketRequest->name = 'Name'; @@ -133,7 +134,6 @@ class CreateTicketTest extends TestCase { $this->newTicketValidator->method('validateNewTicketForCustomer')->willReturn(new ValidationModel()); $this->trackingIdGenerator->method('generateTrackingId')->willReturn('123-456-7890'); - $this->autoassigner->method('getNextUserForTicket')->willReturn(1); $this->ticketGatewayGeneratedFields = new TicketGatewayGeneratedFields(); $this->ticketGateway->method('createTicket')->willReturn($this->ticketGatewayGeneratedFields); @@ -144,16 +144,22 @@ class CreateTicketTest extends TestCase { } function testItSavesTheTicketToTheDatabase() { + //-- Arrange + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); + //-- Assert $this->ticketGateway->expects($this->once())->method('createTicket'); //-- Act - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); } function testItSetsTheTrackingIdOnTheTicket() { + //-- Arrange + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); + //-- Act - $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); //-- Assert self::assertThat($ticket->trackingId, self::equalTo('123-456-7890')); @@ -162,17 +168,24 @@ class CreateTicketTest extends TestCase { function testItSetsTheNextUserForAutoassign() { //-- Arrange $this->heskSettings['autoassign'] = 1; + $autoassignUser = new UserContext(); + $autoassignUser->id = 1; + $this->autoassigner->method('getNextUserForTicket')->willReturn($autoassignUser); + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); //-- Act - $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); //-- Assert self::assertThat($ticket->ownerId, self::equalTo(1)); } function testItDoesntCallTheAutoassignerWhenDisabledInHesk() { + //-- Arrange + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); + //-- Act - $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); //-- Assert self::assertThat($ticket->ownerId, self::equalTo(null)); @@ -196,9 +209,10 @@ class CreateTicketTest extends TestCase { $this->ticketRequest->screenResolution = [1400, 900]; $this->ticketRequest->ipAddress = '127.0.0.1'; $this->ticketRequest->language = 'English'; + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); //-- Act - $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); //-- Assert self::assertThat($ticket->name, self::equalTo($this->ticketRequest->name)); @@ -222,9 +236,10 @@ class CreateTicketTest extends TestCase { $this->ticketGatewayGeneratedFields->dateCreated = 'date created'; $this->ticketGatewayGeneratedFields->dateModified = 'date modified'; $this->ticketGatewayGeneratedFields->id = 50; + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); //-- Act - $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); //-- Assert self::assertThat($ticket->dateCreated, self::equalTo($this->ticketGatewayGeneratedFields->dateCreated)); @@ -233,16 +248,22 @@ class CreateTicketTest extends TestCase { } function testItSetsTheDefaultStatus() { + //-- Arrange + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); + //-- Act - $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); //-- Assert self::assertThat($ticket->statusId, self::equalTo(1)); } function testItSetsTheDefaultProperties() { + //-- Arrange + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); + //-- Act - $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $ticket = $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); //-- Assert self::assertThat($ticket->archived, self::isFalse()); @@ -257,12 +278,13 @@ class CreateTicketTest extends TestCase { function testItChecksIfTheEmailIsVerified() { //-- Arrange $this->modsForHeskSettings['customer_email_verification_required'] = true; + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); //-- Assert $this->verifiedEmailChecker->expects($this->once())->method('isEmailVerified'); //-- Act - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); } function testItSendsAnEmailToTheCustomerWhenTheTicketIsCreated() { @@ -271,13 +293,14 @@ class CreateTicketTest extends TestCase { $this->ticketRequest->language = 'English'; $expectedAddressees = new Addressees(); $expectedAddressees->to = array($this->ticketRequest->email); + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); //-- Assert $this->emailSenderHelper->expects($this->once())->method('sendEmailForTicket') - ->with(EmailTemplateRetriever::NEW_TICKET, 'English', $expectedAddressees, $this->anything(), $this->heskSettings, $this->modsForHeskSettings); + ->with(EmailTemplateRetriever::NEW_TICKET, 'English', $expectedAddressees, $this->anything(), $this->heskSettings, $this->anything()); //-- Act - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); } function testItDoesNotSendsAnEmailToTheCustomerWhenTheTicketIsCreatedAndSendToCustomerIsFalse() { @@ -286,12 +309,13 @@ class CreateTicketTest extends TestCase { $this->ticketRequest->language = 'English'; $expectedAddressees = new Addressees(); $expectedAddressees->to = array($this->ticketRequest->email); + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); //-- Assert $this->emailSenderHelper->expects($this->never())->method('sendEmailForTicket'); //-- Act - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); } function testItSendsAnEmailToTheAssignedToOwnerWhenTheTicketIsCreated() { @@ -300,12 +324,13 @@ class CreateTicketTest extends TestCase { $this->ticketRequest->language = 'English'; $expectedAddressees = new Addressees(); $expectedAddressees->to = array($this->ticketRequest->email); + $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); //-- Assert $this->emailSenderHelper->expects($this->once())->method('sendEmailForTicket') - ->with(EmailTemplateRetriever::NEW_TICKET, 'English', $expectedAddressees, $this->anything(), $this->heskSettings, $this->modsForHeskSettings); + ->with(EmailTemplateRetriever::NEW_TICKET, 'English', $expectedAddressees, $this->anything(), $this->heskSettings, $this->anything()); //-- Act - $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->modsForHeskSettings, $this->userContext); + $this->ticketCreator->createTicketByCustomer($this->ticketRequest, $this->heskSettings, $this->userContext); } } diff --git a/api/autoload.php b/api/autoload.php index e06961f9..c12b9647 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -20,5 +20,4 @@ global $hesk_settings; require_once(__DIR__ . '/../inc/custom_fields.inc.php'); // Load the ApplicationContext -$applicationContext = new \ApplicationContext(); -//$modsForHeskSettings = mfh_getSettings(); \ No newline at end of file +$applicationContext = new \ApplicationContext(); \ No newline at end of file From 25929d82f429154df7fd8089203d8c68b874feb0 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Mar 2017 01:06:50 -0500 Subject: [PATCH 072/192] Fixed some email issues --- .../Emails/EmailTemplateParser.php | 6 ++- api/BusinessLogic/Tickets/Autoassigner.php | 2 +- api/BusinessLogic/Tickets/TicketCreator.php | 30 ++++++++++-- api/DataAccess/Security/UserGateway.php | 46 +++++++++---------- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index 8a1601ab..1bcd84a6 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -51,6 +51,8 @@ class EmailTemplateParser { * @throws InvalidEmailTemplateException */ function getFormattedEmailForLanguage($templateId, $languageCode, $ticket, $heskSettings, $modsForHeskSettings) { + global $hesklang; + $emailTemplate = $this->emailTemplateRetriever->getTemplate($templateId); if ($emailTemplate === null) { @@ -59,7 +61,7 @@ class EmailTemplateParser { $template = self::getFromFileSystem($emailTemplate->fileName, $languageCode, false); $htmlTemplate = self::getFromFileSystem($emailTemplate->fileName, $languageCode, true); - $subject = $emailTemplate->languageKey; + $subject = $hesklang[$emailTemplate->languageKey]; $subject = $this->parseSubject($subject, $ticket, $languageCode, $heskSettings); $message = $this->parseMessage($template, $ticket, $languageCode, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, false); @@ -175,7 +177,7 @@ class EmailTemplateParser { $defaultStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings); $statusName = hesk_msgToPlain($defaultStatus->localizedNames[$language]->text); $category = hesk_msgToPlain($this->categoryGateway->getAllCategories($heskSettings)[$ticket->categoryId]->name); - $owner = hesk_msgToPlain($this->userGateway->getNameForId($ticket->ownerId, $heskSettings)); + $owner = hesk_msgToPlain($this->userGateway->getUserById($ticket->ownerId, $heskSettings)->name); switch ($ticket->priorityId) { case Priority::CRITICAL: diff --git a/api/BusinessLogic/Tickets/Autoassigner.php b/api/BusinessLogic/Tickets/Autoassigner.php index 4ff6fabd..e7d24207 100644 --- a/api/BusinessLogic/Tickets/Autoassigner.php +++ b/api/BusinessLogic/Tickets/Autoassigner.php @@ -29,7 +29,7 @@ class Autoassigner { return null; } - $potentialUsers = $this->userGateway->getUsersByNumberOfOpenTickets($heskSettings); + $potentialUsers = $this->userGateway->getUsersByNumberOfOpenTicketsForAutoassign($heskSettings); foreach ($potentialUsers as $potentialUser) { if ($potentialUser->admin || diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php index 822ee513..7e160936 100644 --- a/api/BusinessLogic/Tickets/TicketCreator.php +++ b/api/BusinessLogic/Tickets/TicketCreator.php @@ -149,13 +149,22 @@ class TicketCreator { } if ($ticket->ownerId !== null) { - $ownerEmail = $this->userGateway->getEmailForId($ticket->ownerId, $heskSettings); + $owner = $this->userGateway->getUserById($ticket->ownerId, $heskSettings); - $addressees = new Addressees(); - $addressees->to = array($ownerEmail); - $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::TICKET_ASSIGNED_TO_YOU, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings); + if ($owner->notificationSettings->newAssignedToMe) { + $addressees = new Addressees(); + $addressees->to = array($owner->email); + $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::TICKET_ASSIGNED_TO_YOU, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings); + } } else { - // TODO email all users who should be notified + // TODO Test + $usersToBeNotified = $this->userGateway->getUsersForNewTicketNotification($heskSettings); + + foreach ($usersToBeNotified as $user) { + if ($user->admin || in_array($ticket->categoryId, $user->categories)) { + $this->sendEmailToStaff($user, $ticket, $heskSettings, $modsForHeskSettings); + } + } } return $ticket; @@ -170,4 +179,15 @@ class TicketCreator { return explode(',', $emails); } + + private function sendEmailToStaff($user, $ticket, $heskSettings, $modsForHeskSettings) { + $addressees = new Addressees(); + $addressees->to = array($user->email); + $language = $user->language !== null && trim($user->language) !== '' + ? $user->language + : $heskSettings['language']; + + $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_TICKET_STAFF, $language, + $addressees, $ticket, $heskSettings, $modsForHeskSettings); + } } \ No newline at end of file diff --git a/api/DataAccess/Security/UserGateway.php b/api/DataAccess/Security/UserGateway.php index d9a24f54..7f15a870 100644 --- a/api/DataAccess/Security/UserGateway.php +++ b/api/DataAccess/Security/UserGateway.php @@ -32,46 +32,27 @@ class UserGateway extends CommonDao { return $row; } - - // TODO Replace this with a basic User retrieval - function getNameForId($id, $heskSettings) { - $this->init(); - - $rs = hesk_dbQuery("SELECT `name` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = " . intval($id)); - - if (hesk_dbNumRows($rs) === 0) { - return null; - } - - $row = hesk_dbFetchAssoc($rs); - - return $row['name']; - } - // TODO Replace this with a basic User retriever - function getEmailForId($id, $heskSettings) { + function getUserById($id, $heskSettings) { $this->init(); - $rs = hesk_dbQuery("SELECT `email` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = " . intval($id)); + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = " . intval($id)); if (hesk_dbNumRows($rs) === 0) { return null; } - $row = hesk_dbFetchAssoc($rs); - - return $row['email']; + return UserContext::fromDataRow(hesk_dbFetchAssoc($rs)); } /** * @param $heskSettings array * @return UserContext[] */ - function getUsersByNumberOfOpenTickets($heskSettings) { + function getUsersByNumberOfOpenTicketsForAutoassign($heskSettings) { $this->init(); - $rs = hesk_dbQuery("SELECT `t1`.`id`,`t1`.`user`,`t1`.`name`, `t1`.`email`, `t1`.`language`, `t1`.`isadmin`, - `t1`.`categories`, `t1`.`notify_assigned`, `t1`.`heskprivileges`, + $rs = hesk_dbQuery("SELECT `t1`.*, (SELECT COUNT(*) FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `owner`=`t1`.`id` AND `status` IN ( @@ -93,4 +74,21 @@ class UserGateway extends CommonDao { return $users; } + + /** + * @param $heskSettings array + * @return UserContext[] + */ + function getUsersForNewTicketNotification($heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `notify_new_unassigned` = '1' AND `active` = '1'"); + + $users = array(); + while ($row = hesk_dbFetchAssoc($rs)) { + $users[] = UserContext::fromDataRow($row); + } + + return $users; + } } \ No newline at end of file From d0475b22c1f59ffd60e06d079534704a2de88fa8 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Mar 2017 15:37:23 -0400 Subject: [PATCH 073/192] Add UserPrivilege enum, provide better 404 information --- api/BusinessLogic/Security/UserPrivilege.php | 15 ++++++++++ api/BusinessLogic/Tickets/Autoassigner.php | 5 ++-- api/index.php | 29 ++++++++------------ 3 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 api/BusinessLogic/Security/UserPrivilege.php diff --git a/api/BusinessLogic/Security/UserPrivilege.php b/api/BusinessLogic/Security/UserPrivilege.php new file mode 100644 index 00000000..8344d31d --- /dev/null +++ b/api/BusinessLogic/Security/UserPrivilege.php @@ -0,0 +1,15 @@ +admin || (in_array($categoryId, $potentialUser->categories) && - in_array('can_view_tickets', $potentialUser->permissions) && - in_array('can_reply_tickets', $potentialUser->permissions))) { + in_array(UserPrivilege::CAN_VIEW_TICKETS, $potentialUser->permissions) && + in_array(UserPrivilege::CAN_REPLY_TO_TICKETS, $potentialUser->permissions))) { return $potentialUser; } } diff --git a/api/index.php b/api/index.php index 3010cddf..7b1120a0 100644 --- a/api/index.php +++ b/api/index.php @@ -9,8 +9,10 @@ register_shutdown_function('fatalErrorShutdownHandler'); $userContext = null; function handle404() { - http_response_code(404); - print json_encode('404 found'); + print output(array( + 'message' => "The endpoint '{$_SERVER['REQUEST_URI']}' was not found. Double-check your request and submit again.", + 'uri' => $_SERVER['REQUEST_URI'] + ), 404); } function before() { @@ -21,6 +23,8 @@ function before() { } function assertApiIsEnabled() { + global $applicationContext; + return true; } @@ -34,13 +38,7 @@ function buildUserContext($xAuthToken) { } function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { - if ($errorNumber === E_WARNING) { - //-- TODO log a warning - } elseif ($errorNumber === E_NOTICE || $errorNumber === E_USER_NOTICE) { - //-- TODO log an info - } else { - exceptionHandler(new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage))); - } + exceptionHandler(new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage))); } /** @@ -53,15 +51,12 @@ function exceptionHandler($exception) { $castedException = $exception; print_error($castedException->title, $castedException->getMessage(), $castedException->httpResponseCode); + } elseif (exceptionIsOfType($exception, \Core\Exceptions\SQLException::class)) { + /* @var $castedException \Core\Exceptions\SQLException */ + $castedException = $exception; + print_error("Fought an uncaught SQL exception", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); } else { - if (exceptionIsOfType($exception, \Core\Exceptions\SQLException::class)) { - /* @var $castedException \Core\Exceptions\SQLException */ - $castedException = $exception; - print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); - } else { - print_error("Fought an uncaught exception of type " . get_class($exception), sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); - } - + print_error("Fought an uncaught exception of type " . get_class($exception), sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); } // Log more stuff to logging table if possible; we'll catch any exceptions from this die(); From 6af93506f1702bcf2359ef0d668e3702a2487c55 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Mar 2017 15:58:17 -0400 Subject: [PATCH 074/192] Properly handle if the API is disabled --- api/ApplicationContext.php | 4 ++++ api/BusinessLogic/Settings/ApiChecker.php | 21 +++++++++++++++++++ .../Categories/CategoryController.php | 2 +- api/index.php | 16 ++++++++++---- 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 api/BusinessLogic/Settings/ApiChecker.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 0fc14d58..4672efc0 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -9,6 +9,7 @@ use BusinessLogic\Emails\EmailTemplateRetriever; use BusinessLogic\Emails\MailgunEmailSender; use BusinessLogic\Security\BanRetriever; use BusinessLogic\Security\UserContextBuilder; +use BusinessLogic\Settings\ApiChecker; use BusinessLogic\Tickets\Autoassigner; use BusinessLogic\Tickets\TicketRetriever; use BusinessLogic\Tickets\TicketCreator; @@ -34,6 +35,9 @@ class ApplicationContext { // Settings $this->get[ModsForHeskSettingsGateway::class] = new ModsForHeskSettingsGateway(); + // API Checker + $this->get[ApiChecker::class] = new ApiChecker($this->get[ModsForHeskSettingsGateway::class]); + // Verified Email Checker $this->get[VerifiedEmailGateway::class] = new VerifiedEmailGateway(); $this->get[VerifiedEmailChecker::class] = new VerifiedEmailChecker($this->get[VerifiedEmailGateway::class]); diff --git a/api/BusinessLogic/Settings/ApiChecker.php b/api/BusinessLogic/Settings/ApiChecker.php new file mode 100644 index 00000000..863a6e06 --- /dev/null +++ b/api/BusinessLogic/Settings/ApiChecker.php @@ -0,0 +1,21 @@ +modsForHeskSettingsGateway = $modsForHeskSettingsGateway; + } + + function isApiEnabled($heskSettings) { + $modsForHeskSettings = $this->modsForHeskSettingsGateway->getAllSettings($heskSettings); + + return intval($modsForHeskSettings['public_api']) === 1; + } +} \ No newline at end of file diff --git a/api/Controllers/Categories/CategoryController.php b/api/Controllers/Categories/CategoryController.php index 6265d2fd..bf640a3e 100644 --- a/api/Controllers/Categories/CategoryController.php +++ b/api/Controllers/Categories/CategoryController.php @@ -1,6 +1,6 @@ get[\BusinessLogic\Settings\ApiChecker::class]; + + if (!$apiChecker->isApiEnabled($hesk_settings)) { + http_response_code(404); + die(); + } + + return; } function buildUserContext($xAuthToken) { @@ -83,8 +91,8 @@ Link::before('before'); Link::all(array( // Categories - '/v1/categories' => '\Controllers\Category\CategoryController::printAllCategories', - '/v1/categories/{i}' => '\Controllers\Category\CategoryController', + '/v1/categories' => '\Controllers\Categories\CategoryController::printAllCategories', + '/v1/categories/{i}' => '\Controllers\Categories\CategoryController', // Tickets '/v1/tickets/{i}' => '\Controllers\Tickets\TicketController', '/v1/tickets' => '\Controllers\Tickets\TicketController', From f15cb63d323b47d76091b16c7b2852ec0cc60747 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Mar 2017 20:50:54 -0400 Subject: [PATCH 075/192] Logging has been implemented for exceptions --- api/ApplicationContext.php | 4 ++ api/DataAccess/Logging/LoggingGateway.php | 47 ++++++++++++++++ api/DataAccess/Logging/Severity.php | 11 ++++ api/index.php | 65 +++++++++++++++++++++-- install/mods-for-hesk/sql/installSql.php | 7 +++ 5 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 api/DataAccess/Logging/LoggingGateway.php create mode 100644 api/DataAccess/Logging/Severity.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 4672efc0..5e873c38 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -18,6 +18,7 @@ use BusinessLogic\Tickets\TicketValidators; use BusinessLogic\Tickets\TrackingIdGenerator; use BusinessLogic\Tickets\VerifiedEmailChecker; use DataAccess\Categories\CategoryGateway; +use DataAccess\Logging\LoggingGateway; use DataAccess\Security\BanGateway; use DataAccess\Security\UserGateway; use DataAccess\Settings\ModsForHeskSettingsGateway; @@ -38,6 +39,9 @@ class ApplicationContext { // API Checker $this->get[ApiChecker::class] = new ApiChecker($this->get[ModsForHeskSettingsGateway::class]); + // Logging + $this->get[LoggingGateway::class] = new LoggingGateway(); + // Verified Email Checker $this->get[VerifiedEmailGateway::class] = new VerifiedEmailGateway(); $this->get[VerifiedEmailChecker::class] = new VerifiedEmailChecker($this->get[VerifiedEmailGateway::class]); diff --git a/api/DataAccess/Logging/LoggingGateway.php b/api/DataAccess/Logging/LoggingGateway.php new file mode 100644 index 00000000..0df48939 --- /dev/null +++ b/api/DataAccess/Logging/LoggingGateway.php @@ -0,0 +1,47 @@ +log(Severity::DEBUG, $location, $message, $stackTrace, $userContext, $heskSettings); + } + + function logInfo($location, $message, $stackTrace, $userContext, $heskSettings) { + return $this->log(Severity::INFO, $location, $message, $stackTrace, $userContext, $heskSettings); + } + + function logWarning($location, $message, $stackTrace, $userContext, $heskSettings) { + return $this->log(Severity::WARNING, $location, $message, $stackTrace, $userContext, $heskSettings); + } + + function logError($location, $message, $stackTrace, $userContext, $heskSettings) { + return $this->log(Severity::ERROR, $location, $message, $stackTrace, $userContext, $heskSettings); + } + + /** + * @param $severity int (from Severity) + * @param $location string + * @param $message string + * @param $userContext UserContext + * @param $heskSettings array + * @return int|null|string The inserted ID, or null on failure. + */ + private function log($severity, $location, $message, $stackTrace, $userContext, $heskSettings) { + $this->init(); + + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "logging` (`username`, `message`, `severity`, `location`, `timestamp`, `stack_trace`) + VALUES ('" . hesk_dbEscape($userContext->username) . "', + '" . hesk_dbEscape($message) . "', " . intval($severity) . ", '" . hesk_dbEscape($location) . "', NOW(), '" . hesk_dbEscape($stackTrace) . "')"); + + $insertedId = hesk_dbInsertID(); + + $this->close(); + + return $insertedId; + } +} \ No newline at end of file diff --git a/api/DataAccess/Logging/Severity.php b/api/DataAccess/Logging/Severity.php new file mode 100644 index 00000000..46124c35 --- /dev/null +++ b/api/DataAccess/Logging/Severity.php @@ -0,0 +1,11 @@ +get[\BusinessLogic\Settings\ApiChecker::class]; if (!$apiChecker->isApiEnabled($hesk_settings)) { - http_response_code(404); + print output(array('message' => 'API Disabled'), 404); die(); } @@ -53,7 +53,18 @@ function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { * @param $exception Exception */ function exceptionHandler($exception) { - //-- TODO Log an error + global $applicationContext, $userContext, $hesk_settings; + + if (strpos($exception->getTraceAsString(), 'LoggingGateway') !== false) { + //-- Suppress these for now, as it would cause issues to output two JSONs at one time. + return; + } + + + /* @var $loggingGateway \DataAccess\Logging\LoggingGateway */ + $loggingGateway = $applicationContext->get[\DataAccess\Logging\LoggingGateway::class]; + + // We don't cast API Friendly Exceptions as they're user-generated errors if (exceptionIsOfType($exception, \BusinessLogic\Exceptions\ApiFriendlyException::class)) { /* @var $castedException \BusinessLogic\Exceptions\ApiFriendlyException */ $castedException = $exception; @@ -62,14 +73,58 @@ function exceptionHandler($exception) { } elseif (exceptionIsOfType($exception, \Core\Exceptions\SQLException::class)) { /* @var $castedException \Core\Exceptions\SQLException */ $castedException = $exception; - print_error("Fought an uncaught SQL exception", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); + + $logId = tryToLog(getLoggingLocation($exception), + "Fought an uncaught SQL exception: " . $castedException->failingQuery, $castedException->getTraceAsString(), + $userContext, $hesk_settings); + + $logIdText = $logId === null ? "Additionally, the error could not be logged! :'(" : "Log ID: {$logId}"; + print_error("SQL Exception", "Fought an uncaught SQL exception. Check the logs for more information. {$logIdText}"); } else { - print_error("Fought an uncaught exception of type " . get_class($exception), sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + $logId = tryToLog(getLoggingLocation($exception), + $exception->getMessage(), $exception->getTraceAsString(), + $userContext, $hesk_settings); + + $logIdText = $logId === null ? "Additionally, the error could not be logged! :'(" : "Log ID: {$logId}"; + print_error("Exception Occurred", "Fought an uncaught exception. Check the logs for more information. {$logIdText}"); } - // Log more stuff to logging table if possible; we'll catch any exceptions from this + die(); } +/** + * @param $location string + * @param $message string + * @param $stackTrace string + * @param $userContext \BusinessLogic\Security\UserContext + * @param $heskSettings array + * @return int|null The inserted ID, or null if failed to log + * @internal param Exception $exception + */ +function tryToLog($location, $message, $stackTrace, $userContext, $heskSettings) { + global $applicationContext; + + /* @var $loggingGateway \DataAccess\Logging\LoggingGateway */ + $loggingGateway = $applicationContext->get[\DataAccess\Logging\LoggingGateway::class]; + + try { + return $loggingGateway->logError($location, $message, $stackTrace, $userContext, $heskSettings); + } catch (Exception $squished) { + return null; + } +} + +/** + * @param $exception Exception + * @return string The location of the exception + */ +function getLoggingLocation($exception) { + // http://stackoverflow.com/a/9133897/1509431 + $trace = $exception->getTrace(); + $lastCall = $trace[0]; + return basename($lastCall['file'], '.php'); +} + /** * @param $exception Exception thrown exception * @param $class string The name of the expected exception type diff --git a/install/mods-for-hesk/sql/installSql.php b/install/mods-for-hesk/sql/installSql.php index 6e2f9b11..35467509 100644 --- a/install/mods-for-hesk/sql/installSql.php +++ b/install/mods-for-hesk/sql/installSql.php @@ -958,4 +958,11 @@ function execute303Scripts() { hesk_dbConnect(); updateVersion('3.0.3'); +} + +function execute310Scripts() { + global $hesk_settings; + hesk_dbConnect(); + + executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "logging` ADD COLUMN `stack_trace` TEXT"); } \ No newline at end of file From cc880a15fcb5909640ec90fae026dc792ed1b524 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Mar 2017 20:57:36 -0400 Subject: [PATCH 076/192] Show stack traces on the message log page --- admin/view_message_log.php | 1 + api/index.php | 3 ++- internal-api/dao/message_log_dao.php | 3 +++ internal-api/js/view-message-log.js | 3 ++- language/en/text.php | 3 +++ 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/admin/view_message_log.php b/admin/view_message_log.php index 351d0753..e92f1bc2 100644 --- a/admin/view_message_log.php +++ b/admin/view_message_log.php @@ -97,6 +97,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); + diff --git a/api/index.php b/api/index.php index 1d2b0b91..4382f43c 100644 --- a/api/index.php +++ b/api/index.php @@ -122,7 +122,8 @@ function getLoggingLocation($exception) { // http://stackoverflow.com/a/9133897/1509431 $trace = $exception->getTrace(); $lastCall = $trace[0]; - return basename($lastCall['file'], '.php'); + $location = basename($lastCall['file'], '.php'); + return "REST API: {$location}"; } /** diff --git a/internal-api/dao/message_log_dao.php b/internal-api/dao/message_log_dao.php index cb8ddece..498acd68 100644 --- a/internal-api/dao/message_log_dao.php +++ b/internal-api/dao/message_log_dao.php @@ -23,12 +23,15 @@ function search_log($hesk_settings, $location, $from_date, $to_date, $severity_i if ($severity_id != NULL) { $sql .= "AND `severity` = " . intval($severity_id); } + $sql .= " ORDER BY `id` DESC"; $rs = hesk_dbQuery($sql); $results = array(); while ($row = hesk_dbFetchAssoc($rs)) { $row['timestamp'] = hesk_date($row['timestamp'], true); + $row['stackTrace'] = nl2br($row['stack_trace']); + unset($row['stack_trace']); $results[] = $row; } diff --git a/internal-api/js/view-message-log.js b/internal-api/js/view-message-log.js index c95f484a..6acbc741 100644 --- a/internal-api/js/view-message-log.js +++ b/internal-api/js/view-message-log.js @@ -53,7 +53,8 @@ function displayResults(data) { '' + result.timestamp + '' + '' + result.username + '' + '' + result.location + '' + - '' + result.message + ''); + '' + result.message + '' + + '' + result.stackTrace + ''); } } } diff --git a/language/en/text.php b/language/en/text.php index a30dafe3..4d171c27 100644 --- a/language/en/text.php +++ b/language/en/text.php @@ -45,6 +45,9 @@ $hesklang['_COLLATE']='utf8_unicode_ci'; // This is the email break line that will be used in email piping $hesklang['EMAIL_HR']='------ Reply above this line ------'; +// ADDED OR MODIFIED IN Mods for HESK 3.1.0 +$hesklang['stack_trace_header'] = 'Stack Trace'; + // ADDED OR MODIFIED IN Mods for HESK 3.0.0 $hesklang['you_have_x_messages'] = 'You have %s new %s'; // %s: Number of new messages, "message" or "messages", depending on # $hesklang['message_lower_case'] = 'message'; From f40eaf1a231ee92ecc4ead14dc8996cc6597b3f4 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Mar 2017 21:24:49 -0400 Subject: [PATCH 077/192] Fix tests --- api/Tests/BusinessLogic/Tickets/AutoassignerTest.php | 10 +++++----- .../TicketCreatorTests/CreateTicketForCustomerTest.php | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php b/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php index 1962395b..8e9836ac 100644 --- a/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php +++ b/api/Tests/BusinessLogic/Tickets/AutoassignerTest.php @@ -66,7 +66,7 @@ class AutoassignerTest extends TestCase { $userWithOneOpenTicket ); - $this->userGateway->method('getUsersByNumberOfOpenTickets') + $this->userGateway->method('getUsersByNumberOfOpenTicketsForAutoassign') ->with($this->heskSettings) ->willReturn($usersToReturn); @@ -92,7 +92,7 @@ class AutoassignerTest extends TestCase { $userWithOneOpenTicket ); - $this->userGateway->method('getUsersByNumberOfOpenTickets') + $this->userGateway->method('getUsersByNumberOfOpenTicketsForAutoassign') ->with($this->heskSettings) ->willReturn($usersToReturn); @@ -119,7 +119,7 @@ class AutoassignerTest extends TestCase { $userWithOneOpenTicket ); - $this->userGateway->method('getUsersByNumberOfOpenTickets') + $this->userGateway->method('getUsersByNumberOfOpenTicketsForAutoassign') ->with($this->heskSettings) ->willReturn($usersToReturn); @@ -145,7 +145,7 @@ class AutoassignerTest extends TestCase { $userWithOneOpenTicket ); - $this->userGateway->method('getUsersByNumberOfOpenTickets') + $this->userGateway->method('getUsersByNumberOfOpenTicketsForAutoassign') ->with($this->heskSettings) ->willReturn($usersToReturn); @@ -171,7 +171,7 @@ class AutoassignerTest extends TestCase { $userWithOneOpenTicket ); - $this->userGateway->method('getUsersByNumberOfOpenTickets') + $this->userGateway->method('getUsersByNumberOfOpenTicketsForAutoassign') ->with($this->heskSettings) ->willReturn($usersToReturn); diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index 5f8383e0..29b88a2b 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -7,6 +7,7 @@ use BusinessLogic\Emails\Addressees; use BusinessLogic\Emails\EmailSenderHelper; use BusinessLogic\Emails\EmailTemplateRetriever; use BusinessLogic\Security\UserContext; +use BusinessLogic\Security\UserContextNotifications; use BusinessLogic\Statuses\Status; use BusinessLogic\Tickets\Autoassigner; use BusinessLogic\Tickets\CreateTicketByCustomerModel; @@ -136,6 +137,7 @@ class CreateTicketTest extends TestCase { $this->trackingIdGenerator->method('generateTrackingId')->willReturn('123-456-7890'); $this->ticketGatewayGeneratedFields = new TicketGatewayGeneratedFields(); $this->ticketGateway->method('createTicket')->willReturn($this->ticketGatewayGeneratedFields); + $this->userGateway->method('getUsersForNewTicketNotification')->willReturn(array()); $status = new Status(); $status->id = 1; @@ -169,8 +171,12 @@ class CreateTicketTest extends TestCase { //-- Arrange $this->heskSettings['autoassign'] = 1; $autoassignUser = new UserContext(); + $notificationSettings = new UserContextNotifications(); + $notificationSettings->newAssignedToMe = true; + $autoassignUser->notificationSettings = $notificationSettings; $autoassignUser->id = 1; $this->autoassigner->method('getNextUserForTicket')->willReturn($autoassignUser); + $this->userGateway->method('getUserById')->willReturn($autoassignUser); $this->modsForHeskSettingsGateway->method('getAllSettings')->willReturn($this->modsForHeskSettings); //-- Act From 8378d35149f097cd25237e7d7bf9288ecd3626ea Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 15 Mar 2017 22:07:17 -0400 Subject: [PATCH 078/192] Start working on attachments API. Fix .gitignore --- .gitignore | 2 +- api/BusinessLogic/Attachments/Attachment.php | 21 +++++++++++++ .../Attachments/AttachmentHandler.php | 16 ++++++++++ .../Attachments/AttachmentType.php | 9 ++++++ .../CreateAttachmentForTicketModel.php | 12 ++++++++ .../Attachments/CreateAttachmentModel.php | 18 +++++++++++ .../Attachments/TicketAttachment.php | 12 ++++++++ .../StaffAttachmentsController.php | 10 +++++++ .../Attachments/AttachmentGateway.php | 30 +++++++++++++++++++ .../Attachments/AttachmentHandlerTest.php | 17 +++++++++++ 10 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 api/BusinessLogic/Attachments/Attachment.php create mode 100644 api/BusinessLogic/Attachments/AttachmentHandler.php create mode 100644 api/BusinessLogic/Attachments/AttachmentType.php create mode 100644 api/BusinessLogic/Attachments/CreateAttachmentForTicketModel.php create mode 100644 api/BusinessLogic/Attachments/CreateAttachmentModel.php create mode 100644 api/BusinessLogic/Attachments/TicketAttachment.php create mode 100644 api/Controllers/Attachments/StaffAttachmentsController.php create mode 100644 api/DataAccess/Attachments/AttachmentGateway.php create mode 100644 api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php diff --git a/.gitignore b/.gitignore index 2e3f55a2..70d3d8c3 100644 --- a/.gitignore +++ b/.gitignore @@ -270,7 +270,7 @@ readme.html robots.txt .idea/ attachments/__latest.txt -attachments +/attachments img/ban.png img/banned.png img/ico_tools.png diff --git a/api/BusinessLogic/Attachments/Attachment.php b/api/BusinessLogic/Attachments/Attachment.php new file mode 100644 index 00000000..380d00b3 --- /dev/null +++ b/api/BusinessLogic/Attachments/Attachment.php @@ -0,0 +1,21 @@ +AttachmentTypeAttachmentType] */ + public $type; +} \ No newline at end of file diff --git a/api/Controllers/Attachments/StaffAttachmentsController.php b/api/Controllers/Attachments/StaffAttachmentsController.php new file mode 100644 index 00000000..6bfbbd81 --- /dev/null +++ b/api/Controllers/Attachments/StaffAttachmentsController.php @@ -0,0 +1,10 @@ +init(); + + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "attachments` + (`ticket_id`, `note_id`, `saved_name`, `real_name`, `size`, `type`, `download_count`) + VALUES ('" . hesk_dbEscape($attachment->ticketTrackingId) . "', NULL, '" . hesk_dbEscape($attachment->savedName) . "', + '" . hesk_dbEscape($attachment->displayName) . "', " . intval($attachment->fileSize) . ", '" . intval($attachment->type) . "', 0)"); + + $attachmentId = hesk_dbInsertID(); + + $this->close(); + + return $attachmentId; + } +} \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php new file mode 100644 index 00000000..335b3b88 --- /dev/null +++ b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php @@ -0,0 +1,17 @@ +attachmentHandler = new AttachmentHandler(); + } +} From ffd3ac2edfb13edba28a6b5b2135058b3e54c9f8 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Fri, 17 Mar 2017 22:05:51 -0400 Subject: [PATCH 079/192] Working on some validation tests --- .../Attachments/AttachmentHandler.php | 40 +++++++- .../Attachments/CreateAttachmentModel.php | 2 +- .../Attachments/AttachmentHandlerTest.php | 92 +++++++++++++++++++ 3 files changed, 130 insertions(+), 4 deletions(-) diff --git a/api/BusinessLogic/Attachments/AttachmentHandler.php b/api/BusinessLogic/Attachments/AttachmentHandler.php index c61a109d..04a73e42 100644 --- a/api/BusinessLogic/Attachments/AttachmentHandler.php +++ b/api/BusinessLogic/Attachments/AttachmentHandler.php @@ -3,14 +3,48 @@ namespace BusinessLogic\Attachments; -class AttachmentHandler { +use BusinessLogic\Exceptions\ValidationException; +use BusinessLogic\ValidationModel; +class AttachmentHandler { + /** + * @param $createAttachmentModel CreateAttachmentForTicketModel + */ function createAttachmentForTicket($createAttachmentModel) { - + $this->validate($createAttachmentModel); } + /** + * @param $createAttachmentModel CreateAttachmentForTicketModel + * @throws ValidationException + */ private function validate($createAttachmentModel) { - + $errorKeys = array(); + if ($createAttachmentModel->attachmentContents === null || + trim($createAttachmentModel->attachmentContents) === '') { + $errorKeys[] = 'CONTENTS_EMPTY'; + } + + if (base64_decode($createAttachmentModel->attachmentContents, true) === false) { + $errorKeys[] = 'CONTENTS_NOT_BASE_64'; + } + + if ($createAttachmentModel->displayName === null || + trim($createAttachmentModel->displayName === '')) { + $errorKeys[] = 'DISPLAY_NAME_EMPTY'; + } + + if ($createAttachmentModel->ticketId === null || + $createAttachmentModel->ticketId < 1) { + $errorKeys[] = 'TICKET_ID_MISSING'; + } + + if (count($errorKeys) > 0) { + $validationModel = new ValidationModel(); + $validationModel->errorKeys = $errorKeys; + $validationModel->valid = false; + throw new ValidationException($validationModel); + } } } \ No newline at end of file diff --git a/api/BusinessLogic/Attachments/CreateAttachmentModel.php b/api/BusinessLogic/Attachments/CreateAttachmentModel.php index 209450d2..9a5f5428 100644 --- a/api/BusinessLogic/Attachments/CreateAttachmentModel.php +++ b/api/BusinessLogic/Attachments/CreateAttachmentModel.php @@ -13,6 +13,6 @@ class CreateAttachmentModel { /* @var $id int */ public $fileSize; - /* @var $attachmentContents string [base64-encoded] */ + /* @var $attachmentContents string */ public $attachmentContents; } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php index 335b3b88..e956e321 100644 --- a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php +++ b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php @@ -4,6 +4,7 @@ namespace BusinessLogic\Attachments; +use BusinessLogic\Exceptions\ValidationException; use PHPUnit\Framework\TestCase; class AttachmentHandlerTest extends TestCase { @@ -11,7 +12,98 @@ class AttachmentHandlerTest extends TestCase { /* @var $attachmentHandler AttachmentHandler */ private $attachmentHandler; + /* @var $createAttachmentModel CreateAttachmentForTicketModel */ + private $createAttachmentForTicketModel; + protected function setUp() { $this->attachmentHandler = new AttachmentHandler(); + $this->createAttachmentForTicketModel = new CreateAttachmentForTicketModel(); + $this->createAttachmentForTicketModel->attachmentContents = base64_encode('string'); + $this->createAttachmentForTicketModel->displayName = 'Display Name'; + $this->createAttachmentForTicketModel->ticketId = 1; + } + + function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsNull() { + //-- Arrange + $this->createAttachmentForTicketModel->attachmentContents = null; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/CONTENTS_EMPTY/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + } + + function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsEmpty() { + //-- Arrange + $this->createAttachmentForTicketModel->attachmentContents = ''; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/CONTENTS_EMPTY/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + } + + function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsInvalidBase64() { + //-- Arrange + $this->createAttachmentForTicketModel->attachmentContents = 'invalid base 64'; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/CONTENTS_NOT_BASE_64/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + } + + function testThatValidateThrowsAnExceptionWhenTheDisplayNameIsNull() { + //-- Arrange + $this->createAttachmentForTicketModel->displayName = null; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/DISPLAY_NAME_EMPTY/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + } + + function testThatValidateThrowsAnExceptionWhenTheDisplayNameIsEmpty() { + //-- Arrange + $this->createAttachmentForTicketModel->displayName = ''; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/DISPLAY_NAME_EMPTY/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + } + + function testThatValidateThrowsAnExceptionWhenTheTicketIdIsNull() { + //-- Arrange + $this->createAttachmentForTicketModel->ticketId = null; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/TICKET_ID_MISSING/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + } + + function testThatValidateThrowsAnExceptionWhenTheTicketIdIsANonPositiveInteger() { + //-- Arrange + $this->createAttachmentForTicketModel->ticketId = 0; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/TICKET_ID_MISSING/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); } } From 4fd6595ded1c60ee9555033c2d52bd379bf58b37 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 18 Mar 2017 22:13:08 -0400 Subject: [PATCH 080/192] Handle building the attachment and saving it to the FS --- .../Attachments/AttachmentHandler.php | 304 +++++++++++++++++- .../Attachments/CreateAttachmentModel.php | 3 + api/DataAccess/Files/FileWriter.php | 25 ++ .../Attachments/AttachmentHandlerTest.php | 107 +++++- 4 files changed, 429 insertions(+), 10 deletions(-) create mode 100644 api/DataAccess/Files/FileWriter.php diff --git a/api/BusinessLogic/Attachments/AttachmentHandler.php b/api/BusinessLogic/Attachments/AttachmentHandler.php index 04a73e42..4ef3d72b 100644 --- a/api/BusinessLogic/Attachments/AttachmentHandler.php +++ b/api/BusinessLogic/Attachments/AttachmentHandler.php @@ -5,14 +5,55 @@ namespace BusinessLogic\Attachments; use BusinessLogic\Exceptions\ValidationException; use BusinessLogic\ValidationModel; +use DataAccess\Attachments\AttachmentGateway; +use DataAccess\Files\FileWriter; +use DataAccess\Tickets\TicketGateway; class AttachmentHandler { + /* @var $ticketGateway TicketGateway */ + private $ticketGateway; + + /* @var $attachmentGateway AttachmentGateway */ + private $attachmentGateway; + + /* @var $fileWriter FileWriter */ + private $fileWriter; + + function __construct($ticketGateway, $attachmentGateway, $fileWriter) { + $this->ticketGateway = $ticketGateway; + $this->attachmentGateway = $attachmentGateway; + $this->fileWriter = $fileWriter; + } + /** * @param $createAttachmentModel CreateAttachmentForTicketModel + * @param $heskSettings array + * @return TicketAttachment the newly created attachment */ - function createAttachmentForTicket($createAttachmentModel) { + function createAttachmentForTicket($createAttachmentModel, $heskSettings) { $this->validate($createAttachmentModel); + + $decodedAttachment = base64_decode($createAttachmentModel->attachmentContents); + + $ticket = $this->ticketGateway->getTicketById($createAttachmentModel->ticketId, $heskSettings); + $cleanedFileName = $this->cleanFileName($createAttachmentModel->displayName); + + $ticketAttachment = new TicketAttachment(); + $ticketAttachment->savedName = $this->generateSavedName($ticket->trackingId, + $cleanedFileName, $createAttachmentModel->fileExtension); + $ticketAttachment->displayName = $cleanedFileName; + $ticketAttachment->ticketTrackingId = $ticket->trackingId; + $ticketAttachment->type = $createAttachmentModel->type; + $ticketAttachment->downloadCount = 0; + + $ticketAttachment->fileSize = + $this->fileWriter->writeToFile($ticketAttachment->savedName, $heskSettings['attach_dir'], $decodedAttachment); + + $attachmentId = $this->attachmentGateway->createAttachmentForTicket($ticketAttachment, $heskSettings); + $ticketAttachment->id = $attachmentId; + + return $ticketAttachment; } /** @@ -40,6 +81,12 @@ class AttachmentHandler { $errorKeys[] = 'TICKET_ID_MISSING'; } + if (!in_array($createAttachmentModel->type, array(AttachmentType::MESSAGE, AttachmentType::REPLY))) { + $errorKeys[] = 'INVALID_ATTACHMENT_TYPE'; + } + + //-- TODO Extension, size + if (count($errorKeys) > 0) { $validationModel = new ValidationModel(); $validationModel->errorKeys = $errorKeys; @@ -47,4 +94,259 @@ class AttachmentHandler { throw new ValidationException($validationModel); } } + + private function generateSavedName($trackingId, $displayName, $fileExtension) { + $useChars = 'AEUYBDGHJLMNPQRSTVWXZ123456789'; + $tmp = uniqid(); + for ($j = 1; $j < 10; $j++) { + $tmp .= $useChars{mt_rand(0, 29)}; + } + + + return substr($trackingId . '_' . md5($tmp . $displayName), 0, 200) . $fileExtension; + } + + /** + * @param $displayName string original file name + * @return string The cleaned file name + */ + private function cleanFileName($displayName) { + $filename = str_replace(array('%20', '+'), '-', $displayName); + $filename = preg_replace('/[\s-]+/', '-', $filename); + $filename = $this->removeAccents($filename); + $filename = preg_replace('/[^A-Za-z0-9\.\-_]/', '', $filename); + $filename = trim($filename, '-_'); + + return $filename; + } + + // The following code has been borrowed from Wordpress, and also from posting_functions.inc.php :P + // Credits: http://wordpress.org + private function removeAccents($string) + { + if (!preg_match('/[\x80-\xff]/', $string)) { + return $string; + } + + if ($this->seemsUtf8($string)) { + $chars = array( + // Decompositions for Latin-1 Supplement + chr(194) . chr(170) => 'a', chr(194) . chr(186) => 'o', + chr(195) . chr(128) => 'A', chr(195) . chr(129) => 'A', + chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A', + chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A', + chr(195) . chr(134) => 'AE', chr(195) . chr(135) => 'C', + chr(195) . chr(136) => 'E', chr(195) . chr(137) => 'E', + chr(195) . chr(138) => 'E', chr(195) . chr(139) => 'E', + chr(195) . chr(140) => 'I', chr(195) . chr(141) => 'I', + chr(195) . chr(142) => 'I', chr(195) . chr(143) => 'I', + chr(195) . chr(144) => 'D', chr(195) . chr(145) => 'N', + chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O', + chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O', + chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U', + chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U', + chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y', + chr(195) . chr(158) => 'TH', chr(195) . chr(159) => 's', + chr(195) . chr(160) => 'a', chr(195) . chr(161) => 'a', + chr(195) . chr(162) => 'a', chr(195) . chr(163) => 'a', + chr(195) . chr(164) => 'a', chr(195) . chr(165) => 'a', + chr(195) . chr(166) => 'ae', chr(195) . chr(167) => 'c', + chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e', + chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e', + chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i', + chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i', + chr(195) . chr(176) => 'd', chr(195) . chr(177) => 'n', + chr(195) . chr(178) => 'o', chr(195) . chr(179) => 'o', + chr(195) . chr(180) => 'o', chr(195) . chr(181) => 'o', + chr(195) . chr(182) => 'o', chr(195) . chr(184) => 'o', + chr(195) . chr(185) => 'u', chr(195) . chr(186) => 'u', + chr(195) . chr(187) => 'u', chr(195) . chr(188) => 'u', + chr(195) . chr(189) => 'y', chr(195) . chr(190) => 'th', + chr(195) . chr(191) => 'y', chr(195) . chr(152) => 'O', + // Decompositions for Latin Extended-A + chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a', + chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a', + chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a', + chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c', + chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c', + chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c', + chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c', + chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd', + chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd', + chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e', + chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e', + chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e', + chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e', + chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e', + chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g', + chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g', + chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g', + chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g', + chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h', + chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h', + chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i', + chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i', + chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i', + chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i', + chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i', + chr(196) . chr(178) => 'IJ', chr(196) . chr(179) => 'ij', + chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j', + chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k', + chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L', + chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L', + chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L', + chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L', + chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L', + chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N', + chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N', + chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N', + chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N', + chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N', + chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o', + chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o', + chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o', + chr(197) . chr(146) => 'OE', chr(197) . chr(147) => 'oe', + chr(197) . chr(148) => 'R', chr(197) . chr(149) => 'r', + chr(197) . chr(150) => 'R', chr(197) . chr(151) => 'r', + chr(197) . chr(152) => 'R', chr(197) . chr(153) => 'r', + chr(197) . chr(154) => 'S', chr(197) . chr(155) => 's', + chr(197) . chr(156) => 'S', chr(197) . chr(157) => 's', + chr(197) . chr(158) => 'S', chr(197) . chr(159) => 's', + chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's', + chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't', + chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't', + chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't', + chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u', + chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u', + chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u', + chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u', + chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u', + chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u', + chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w', + chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y', + chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z', + chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z', + chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z', + chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's', + // Decompositions for Latin Extended-B + chr(200) . chr(152) => 'S', chr(200) . chr(153) => 's', + chr(200) . chr(154) => 'T', chr(200) . chr(155) => 't', + // Euro Sign + chr(226) . chr(130) . chr(172) => 'E', + // GBP (Pound) Sign + chr(194) . chr(163) => '', + // Vowels with diacritic (Vietnamese) + // unmarked + chr(198) . chr(160) => 'O', chr(198) . chr(161) => 'o', + chr(198) . chr(175) => 'U', chr(198) . chr(176) => 'u', + // grave accent + chr(225) . chr(186) . chr(166) => 'A', chr(225) . chr(186) . chr(167) => 'a', + chr(225) . chr(186) . chr(176) => 'A', chr(225) . chr(186) . chr(177) => 'a', + chr(225) . chr(187) . chr(128) => 'E', chr(225) . chr(187) . chr(129) => 'e', + chr(225) . chr(187) . chr(146) => 'O', chr(225) . chr(187) . chr(147) => 'o', + chr(225) . chr(187) . chr(156) => 'O', chr(225) . chr(187) . chr(157) => 'o', + chr(225) . chr(187) . chr(170) => 'U', chr(225) . chr(187) . chr(171) => 'u', + chr(225) . chr(187) . chr(178) => 'Y', chr(225) . chr(187) . chr(179) => 'y', + // hook + chr(225) . chr(186) . chr(162) => 'A', chr(225) . chr(186) . chr(163) => 'a', + chr(225) . chr(186) . chr(168) => 'A', chr(225) . chr(186) . chr(169) => 'a', + chr(225) . chr(186) . chr(178) => 'A', chr(225) . chr(186) . chr(179) => 'a', + chr(225) . chr(186) . chr(186) => 'E', chr(225) . chr(186) . chr(187) => 'e', + chr(225) . chr(187) . chr(130) => 'E', chr(225) . chr(187) . chr(131) => 'e', + chr(225) . chr(187) . chr(136) => 'I', chr(225) . chr(187) . chr(137) => 'i', + chr(225) . chr(187) . chr(142) => 'O', chr(225) . chr(187) . chr(143) => 'o', + chr(225) . chr(187) . chr(148) => 'O', chr(225) . chr(187) . chr(149) => 'o', + chr(225) . chr(187) . chr(158) => 'O', chr(225) . chr(187) . chr(159) => 'o', + chr(225) . chr(187) . chr(166) => 'U', chr(225) . chr(187) . chr(167) => 'u', + chr(225) . chr(187) . chr(172) => 'U', chr(225) . chr(187) . chr(173) => 'u', + chr(225) . chr(187) . chr(182) => 'Y', chr(225) . chr(187) . chr(183) => 'y', + // tilde + chr(225) . chr(186) . chr(170) => 'A', chr(225) . chr(186) . chr(171) => 'a', + chr(225) . chr(186) . chr(180) => 'A', chr(225) . chr(186) . chr(181) => 'a', + chr(225) . chr(186) . chr(188) => 'E', chr(225) . chr(186) . chr(189) => 'e', + chr(225) . chr(187) . chr(132) => 'E', chr(225) . chr(187) . chr(133) => 'e', + chr(225) . chr(187) . chr(150) => 'O', chr(225) . chr(187) . chr(151) => 'o', + chr(225) . chr(187) . chr(160) => 'O', chr(225) . chr(187) . chr(161) => 'o', + chr(225) . chr(187) . chr(174) => 'U', chr(225) . chr(187) . chr(175) => 'u', + chr(225) . chr(187) . chr(184) => 'Y', chr(225) . chr(187) . chr(185) => 'y', + // acute accent + chr(225) . chr(186) . chr(164) => 'A', chr(225) . chr(186) . chr(165) => 'a', + chr(225) . chr(186) . chr(174) => 'A', chr(225) . chr(186) . chr(175) => 'a', + chr(225) . chr(186) . chr(190) => 'E', chr(225) . chr(186) . chr(191) => 'e', + chr(225) . chr(187) . chr(144) => 'O', chr(225) . chr(187) . chr(145) => 'o', + chr(225) . chr(187) . chr(154) => 'O', chr(225) . chr(187) . chr(155) => 'o', + chr(225) . chr(187) . chr(168) => 'U', chr(225) . chr(187) . chr(169) => 'u', + // dot below + chr(225) . chr(186) . chr(160) => 'A', chr(225) . chr(186) . chr(161) => 'a', + chr(225) . chr(186) . chr(172) => 'A', chr(225) . chr(186) . chr(173) => 'a', + chr(225) . chr(186) . chr(182) => 'A', chr(225) . chr(186) . chr(183) => 'a', + chr(225) . chr(186) . chr(184) => 'E', chr(225) . chr(186) . chr(185) => 'e', + chr(225) . chr(187) . chr(134) => 'E', chr(225) . chr(187) . chr(135) => 'e', + chr(225) . chr(187) . chr(138) => 'I', chr(225) . chr(187) . chr(139) => 'i', + chr(225) . chr(187) . chr(140) => 'O', chr(225) . chr(187) . chr(141) => 'o', + chr(225) . chr(187) . chr(152) => 'O', chr(225) . chr(187) . chr(153) => 'o', + chr(225) . chr(187) . chr(162) => 'O', chr(225) . chr(187) . chr(163) => 'o', + chr(225) . chr(187) . chr(164) => 'U', chr(225) . chr(187) . chr(165) => 'u', + chr(225) . chr(187) . chr(176) => 'U', chr(225) . chr(187) . chr(177) => 'u', + chr(225) . chr(187) . chr(180) => 'Y', chr(225) . chr(187) . chr(181) => 'y', + // Vowels with diacritic (Chinese, Hanyu Pinyin) + chr(201) . chr(145) => 'a', + // macron + chr(199) . chr(149) => 'U', chr(199) . chr(150) => 'u', + // acute accent + chr(199) . chr(151) => 'U', chr(199) . chr(152) => 'u', + // caron + chr(199) . chr(141) => 'A', chr(199) . chr(142) => 'a', + chr(199) . chr(143) => 'I', chr(199) . chr(144) => 'i', + chr(199) . chr(145) => 'O', chr(199) . chr(146) => 'o', + chr(199) . chr(147) => 'U', chr(199) . chr(148) => 'u', + chr(199) . chr(153) => 'U', chr(199) . chr(154) => 'u', + // grave accent + chr(199) . chr(155) => 'U', chr(199) . chr(156) => 'u', + ); + + $string = strtr($string, $chars); + } else { + // Assume ISO-8859-1 if not UTF-8 + $chars['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158) + . chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194) + . chr(195) . chr(196) . chr(197) . chr(199) . chr(200) . chr(201) . chr(202) + . chr(203) . chr(204) . chr(205) . chr(206) . chr(207) . chr(209) . chr(210) + . chr(211) . chr(212) . chr(213) . chr(214) . chr(216) . chr(217) . chr(218) + . chr(219) . chr(220) . chr(221) . chr(224) . chr(225) . chr(226) . chr(227) + . chr(228) . chr(229) . chr(231) . chr(232) . chr(233) . chr(234) . chr(235) + . chr(236) . chr(237) . chr(238) . chr(239) . chr(241) . chr(242) . chr(243) + . chr(244) . chr(245) . chr(246) . chr(248) . chr(249) . chr(250) . chr(251) + . chr(252) . chr(253) . chr(255); + + $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy"; + + $string = strtr($string, $chars['in'], $chars['out']); + $double_chars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254)); + $double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'); + $string = str_replace($double_chars['in'], $double_chars['out'], $string); + } + + return $string; + } + + private function seemsUtf8($str) + { + $length = strlen($str); + for ($i = 0; $i < $length; $i++) { + $c = ord($str[$i]); + if ($c < 0x80) $n = 0; # 0bbbbbbb + elseif (($c & 0xE0) == 0xC0) $n = 1; # 110bbbbb + elseif (($c & 0xF0) == 0xE0) $n = 2; # 1110bbbb + elseif (($c & 0xF8) == 0xF0) $n = 3; # 11110bbb + elseif (($c & 0xFC) == 0xF8) $n = 4; # 111110bb + elseif (($c & 0xFE) == 0xFC) $n = 5; # 1111110b + else return false; # Does not match any model + for ($j = 0; $j < $n; $j++) { # n bytes matching 10bbbbbb follow ? + if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80)) + return false; + } + } + return true; + } } \ No newline at end of file diff --git a/api/BusinessLogic/Attachments/CreateAttachmentModel.php b/api/BusinessLogic/Attachments/CreateAttachmentModel.php index 9a5f5428..cf137828 100644 --- a/api/BusinessLogic/Attachments/CreateAttachmentModel.php +++ b/api/BusinessLogic/Attachments/CreateAttachmentModel.php @@ -10,6 +10,9 @@ class CreateAttachmentModel { /* @var $displayName string */ public $displayName; + /* @var $fileExtension string */ + public $fileExtension; + /* @var $id int */ public $fileSize; diff --git a/api/DataAccess/Files/FileWriter.php b/api/DataAccess/Files/FileWriter.php new file mode 100644 index 00000000..8ca47891 --- /dev/null +++ b/api/DataAccess/Files/FileWriter.php @@ -0,0 +1,25 @@ +attachmentHandler = new AttachmentHandler(); + $this->ticketGateway = $this->createMock(TicketGateway::class); + $this->attachmentGateway = $this->createMock(AttachmentGateway::class); + $this->fileWriter = $this->createMock(FileWriter::class); + $this->heskSettings = array( + 'attach_dir' => 'attachments' + ); + + $this->attachmentHandler = new AttachmentHandler($this->ticketGateway, $this->attachmentGateway, $this->fileWriter); $this->createAttachmentForTicketModel = new CreateAttachmentForTicketModel(); $this->createAttachmentForTicketModel->attachmentContents = base64_encode('string'); - $this->createAttachmentForTicketModel->displayName = 'Display Name'; + $this->createAttachmentForTicketModel->displayName = 'DisplayName'; $this->createAttachmentForTicketModel->ticketId = 1; + $this->createAttachmentForTicketModel->type = AttachmentType::MESSAGE; } function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsNull() { @@ -32,7 +56,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/CONTENTS_EMPTY/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsEmpty() { @@ -44,7 +68,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/CONTENTS_EMPTY/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsInvalidBase64() { @@ -56,7 +80,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/CONTENTS_NOT_BASE_64/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheDisplayNameIsNull() { @@ -68,7 +92,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/DISPLAY_NAME_EMPTY/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheDisplayNameIsEmpty() { @@ -80,7 +104,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/DISPLAY_NAME_EMPTY/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheTicketIdIsNull() { @@ -92,7 +116,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/TICKET_ID_MISSING/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheTicketIdIsANonPositiveInteger() { @@ -104,6 +128,71 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/TICKET_ID_MISSING/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + } + + function testThatValidateThrowsAnExceptionWhenTheAttachmentTypeIsNeitherMessageNorReply() { + //-- Arrange + $this->createAttachmentForTicketModel->type = 5; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/INVALID_ATTACHMENT_TYPE/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + } + + function testItSavesATicketWithTheProperProperties() { + //-- Arrange + $this->createAttachmentForTicketModel->ticketId = 1; + $ticket = new Ticket(); + $ticket->trackingId = 'ABC-DEF-1234'; + $this->ticketGateway->method('getTicketById')->with(1, $this->anything())->willReturn($ticket); + + $ticketAttachment = new TicketAttachment(); + $ticketAttachment->displayName = $this->createAttachmentForTicketModel->displayName; + $ticketAttachment->ticketTrackingId = $ticket->trackingId; + $ticketAttachment->type = $this->createAttachmentForTicketModel->type; + $ticketAttachment->downloadCount = 0; + $ticketAttachment->id = 50; + + $this->attachmentGateway->method('createAttachmentForTicket')->willReturn(50); + + + //-- Act + $actual = $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + + //-- Assert + self::assertThat($actual->id, self::equalTo(50)); + self::assertThat($actual->downloadCount, self::equalTo(0)); + self::assertThat($actual->type, self::equalTo($this->createAttachmentForTicketModel->type)); + self::assertThat($actual->ticketTrackingId, self::equalTo($ticket->trackingId)); + self::assertThat($actual->displayName, self::equalTo($this->createAttachmentForTicketModel->displayName)); + } + + function testItSavesTheFileToTheFileSystem() { + //-- Arrange + $this->createAttachmentForTicketModel->ticketId = 1; + $ticket = new Ticket(); + $ticket->trackingId = 'ABC-DEF-1234'; + $this->ticketGateway->method('getTicketById')->with(1, $this->anything())->willReturn($ticket); + + $ticketAttachment = new TicketAttachment(); + $ticketAttachment->displayName = $this->createAttachmentForTicketModel->displayName; + $ticketAttachment->ticketTrackingId = $ticket->trackingId; + $ticketAttachment->type = $this->createAttachmentForTicketModel->type; + $ticketAttachment->downloadCount = 0; + $ticketAttachment->id = 50; + + $this->fileWriter->method('writeToFile')->willReturn(1024); + $this->attachmentGateway->method('createAttachmentForTicket')->willReturn(50); + + + //-- Act + $actual = $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + + //-- Assert + self::assertThat($actual->fileSize, self::equalTo(1024)); } } From 8d484f62eafed10af6beedc3504222b386e72a2b Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 20 Mar 2017 22:16:35 -0400 Subject: [PATCH 081/192] Mostly done with attachment uploading --- api/ApplicationContext.php | 10 +++++ .../Attachments/AttachmentHandler.php | 25 ++++++++++-- .../Attachments/CreateAttachmentModel.php | 3 -- .../StaffAttachmentsController.php | 10 ----- .../StaffTicketAttachmentsController.php | 39 +++++++++++++++++++ api/Controllers/Tickets/TicketController.php | 2 +- .../Attachments/AttachmentHandlerTest.php | 34 +++++++++++++++- 7 files changed, 103 insertions(+), 20 deletions(-) delete mode 100644 api/Controllers/Attachments/StaffAttachmentsController.php create mode 100644 api/Controllers/Attachments/StaffTicketAttachmentsController.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 5e873c38..5ec0939f 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -1,6 +1,7 @@ get[EmailSenderHelper::class], $this->get[UserGateway::class], $this->get[ModsForHeskSettingsGateway::class]); + + // Attachments + $this->get[FileWriter::class] = new FileWriter(); + $this->get[AttachmentGateway::class] = new AttachmentGateway(); + $this->get[AttachmentHandler::class] = new AttachmentHandler($this->get[TicketGateway::class], + $this->get[AttachmentGateway::class], + $this->get[FileWriter::class]); } } \ No newline at end of file diff --git a/api/BusinessLogic/Attachments/AttachmentHandler.php b/api/BusinessLogic/Attachments/AttachmentHandler.php index 4ef3d72b..d56ce05e 100644 --- a/api/BusinessLogic/Attachments/AttachmentHandler.php +++ b/api/BusinessLogic/Attachments/AttachmentHandler.php @@ -32,16 +32,17 @@ class AttachmentHandler { * @return TicketAttachment the newly created attachment */ function createAttachmentForTicket($createAttachmentModel, $heskSettings) { - $this->validate($createAttachmentModel); + $this->validate($createAttachmentModel, $heskSettings); $decodedAttachment = base64_decode($createAttachmentModel->attachmentContents); $ticket = $this->ticketGateway->getTicketById($createAttachmentModel->ticketId, $heskSettings); $cleanedFileName = $this->cleanFileName($createAttachmentModel->displayName); + $fileParts = pathinfo($cleanedFileName); $ticketAttachment = new TicketAttachment(); $ticketAttachment->savedName = $this->generateSavedName($ticket->trackingId, - $cleanedFileName, $createAttachmentModel->fileExtension); + $cleanedFileName, $fileParts['extension']); $ticketAttachment->displayName = $cleanedFileName; $ticketAttachment->ticketTrackingId = $ticket->trackingId; $ticketAttachment->type = $createAttachmentModel->type; @@ -58,9 +59,10 @@ class AttachmentHandler { /** * @param $createAttachmentModel CreateAttachmentForTicketModel + * @param $heskSettings array * @throws ValidationException */ - private function validate($createAttachmentModel) { + private function validate($createAttachmentModel, $heskSettings) { $errorKeys = array(); if ($createAttachmentModel->attachmentContents === null || trim($createAttachmentModel->attachmentContents) === '') { @@ -85,7 +87,21 @@ class AttachmentHandler { $errorKeys[] = 'INVALID_ATTACHMENT_TYPE'; } - //-- TODO Extension, size + $fileParts = pathinfo($createAttachmentModel->displayName); + if (!isset($fileParts['extension']) || !in_array(".{$fileParts['extension']}", $heskSettings['attachments']['allowed_types'])) { + $errorKeys[] = 'EXTENSION_NOT_PERMITTED'; + } + + $fileContents = base64_decode($createAttachmentModel->attachmentContents); + if (function_exists('mb_strlen')) { + $fileSize = mb_strlen($fileContents, '8bit'); + } else { + $fileSize = strlen($fileContents); + } + + if ($fileSize > $heskSettings['attachments']['max_size']) { + $errorKeys[] = 'FILE_SIZE_TOO_LARGE'; + } if (count($errorKeys) > 0) { $validationModel = new ValidationModel(); @@ -96,6 +112,7 @@ class AttachmentHandler { } private function generateSavedName($trackingId, $displayName, $fileExtension) { + $fileExtension = ".{$fileExtension}"; $useChars = 'AEUYBDGHJLMNPQRSTVWXZ123456789'; $tmp = uniqid(); for ($j = 1; $j < 10; $j++) { diff --git a/api/BusinessLogic/Attachments/CreateAttachmentModel.php b/api/BusinessLogic/Attachments/CreateAttachmentModel.php index cf137828..9a5f5428 100644 --- a/api/BusinessLogic/Attachments/CreateAttachmentModel.php +++ b/api/BusinessLogic/Attachments/CreateAttachmentModel.php @@ -10,9 +10,6 @@ class CreateAttachmentModel { /* @var $displayName string */ public $displayName; - /* @var $fileExtension string */ - public $fileExtension; - /* @var $id int */ public $fileSize; diff --git a/api/Controllers/Attachments/StaffAttachmentsController.php b/api/Controllers/Attachments/StaffAttachmentsController.php deleted file mode 100644 index 6bfbbd81..00000000 --- a/api/Controllers/Attachments/StaffAttachmentsController.php +++ /dev/null @@ -1,10 +0,0 @@ -get[AttachmentHandler::class]; + + $createAttachmentForTicketModel = $this->createModel(JsonRetriever::getJsonData()); + + $createdAttachment = $attachmentHandler->createAttachmentForTicket($createAttachmentForTicketModel, $hesk_settings); + + return output($createdAttachment, 201); + } + + private function createModel($json) { + $model = new CreateAttachmentForTicketModel(); + $model->attachmentContents = Helpers::safeArrayGet($json, 'data'); + $model->displayName = Helpers::safeArrayGet($json, 'displayName'); + $model->ticketId = Helpers::safeArrayGet($json, 'ticketId'); + $model->type = Helpers::safeArrayGet($json, 'type'); + + return $model; + } +} \ No newline at end of file diff --git a/api/Controllers/Tickets/TicketController.php b/api/Controllers/Tickets/TicketController.php index 017e6819..a26b18e2 100644 --- a/api/Controllers/Tickets/TicketController.php +++ b/api/Controllers/Tickets/TicketController.php @@ -33,7 +33,7 @@ class TicketController { //else if assigned to owner, email new owner //else email all staff - return output($ticket); + return output($ticket, 201); } /** diff --git a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php index e58a77f9..0a6feddc 100644 --- a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php +++ b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php @@ -36,13 +36,17 @@ class AttachmentHandlerTest extends TestCase { $this->attachmentGateway = $this->createMock(AttachmentGateway::class); $this->fileWriter = $this->createMock(FileWriter::class); $this->heskSettings = array( - 'attach_dir' => 'attachments' + 'attach_dir' => 'attachments', + 'attachments' => array( + 'allowed_types' => array('.txt'), + 'max_size' => 999 + ) ); $this->attachmentHandler = new AttachmentHandler($this->ticketGateway, $this->attachmentGateway, $this->fileWriter); $this->createAttachmentForTicketModel = new CreateAttachmentForTicketModel(); $this->createAttachmentForTicketModel->attachmentContents = base64_encode('string'); - $this->createAttachmentForTicketModel->displayName = 'DisplayName'; + $this->createAttachmentForTicketModel->displayName = 'DisplayName.txt'; $this->createAttachmentForTicketModel->ticketId = 1; $this->createAttachmentForTicketModel->type = AttachmentType::MESSAGE; } @@ -143,6 +147,32 @@ class AttachmentHandlerTest extends TestCase { $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); } + function testThatValidateThrowsAnExceptionWhenTheFileExtensionIsNotPermitted() { + //-- Arrange + $this->heskSettings['attachments']['allowed_types'] = array('.gif'); + $this->createAttachmentForTicketModel->ticketId = 0; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/EXTENSION_NOT_PERMITTED/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + } + + function testThatValidateThrowsAnExceptionWhenTheFileSizeIsLargerThanMaxPermitted() { + //-- Arrange + $this->createAttachmentForTicketModel->attachmentContents = base64_encode("msg"); + $this->heskSettings['attachments']['max_size'] = 1; + + //-- Assert + $this->expectException(ValidationException::class); + $this->expectExceptionMessageRegExp('/FILE_SIZE_TOO_LARGE/'); + + //-- Act + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + } + function testItSavesATicketWithTheProperProperties() { //-- Arrange $this->createAttachmentForTicketModel->ticketId = 1; From 0556d07a5635e53538cc69d784442f57cf9bf0e7 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 21 Mar 2017 22:11:19 -0400 Subject: [PATCH 082/192] Attachments can now be uploaded for a ticket. Still need to handle updating the reply's property --- .../Attachments/AttachmentHandler.php | 21 ++++++++++++++++++ api/DataAccess/Tickets/TicketGateway.php | 22 +++++++++++++++++++ api/index.php | 11 ++++++---- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/api/BusinessLogic/Attachments/AttachmentHandler.php b/api/BusinessLogic/Attachments/AttachmentHandler.php index d56ce05e..60c32cf0 100644 --- a/api/BusinessLogic/Attachments/AttachmentHandler.php +++ b/api/BusinessLogic/Attachments/AttachmentHandler.php @@ -4,6 +4,8 @@ namespace BusinessLogic\Attachments; use BusinessLogic\Exceptions\ValidationException; +use BusinessLogic\Tickets\Attachment; +use BusinessLogic\Tickets\Ticket; use BusinessLogic\ValidationModel; use DataAccess\Attachments\AttachmentGateway; use DataAccess\Files\FileWriter; @@ -52,6 +54,9 @@ class AttachmentHandler { $this->fileWriter->writeToFile($ticketAttachment->savedName, $heskSettings['attach_dir'], $decodedAttachment); $attachmentId = $this->attachmentGateway->createAttachmentForTicket($ticketAttachment, $heskSettings); + + $this->updateAttachmentsOnTicket($ticket, $ticketAttachment, $attachmentId, $heskSettings); + $ticketAttachment->id = $attachmentId; return $ticketAttachment; @@ -366,4 +371,20 @@ class AttachmentHandler { } return true; } + + /** + * @param $ticket Ticket + * @param $ticketAttachment TicketAttachment + * @param $attachmentId int + * @param $heskSettings array + */ + private function updateAttachmentsOnTicket($ticket, $ticketAttachment, $attachmentId, $heskSettings) { + $attachments = $ticket->attachments === null ? array() : $ticket->attachments; + $newAttachment = new Attachment(); + $newAttachment->savedName = $ticketAttachment->savedName; + $newAttachment->fileName = $ticketAttachment->displayName; + $newAttachment->id = $attachmentId; + $attachments[] = $newAttachment; + $this->ticketGateway->updateAttachmentsForTicket($ticket->id, $attachments, $heskSettings); + } } \ No newline at end of file diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index 8b1f29d4..faf0428c 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -3,6 +3,7 @@ namespace DataAccess\Tickets; +use BusinessLogic\Tickets\Attachment; use BusinessLogic\Tickets\Ticket; use BusinessLogic\Tickets\TicketGatewayGeneratedFields; use DataAccess\CommonDao; @@ -205,4 +206,25 @@ class TicketGateway extends CommonDao { return $generatedFields; } + + /** + * @param $ticketId int + * @param $attachments Attachment[] + * @param $heskSettings array + * + * Crappy logic that should just be pulled from the attachments table, but using for backwards compatibility + */ + function updateAttachmentsForTicket($ticketId, $attachments, $heskSettings) { + $this->init(); + + $attachmentStrings = array(); + foreach ($attachments as $attachment) { + $attachmentStrings[] = "{$attachment->id}#{$attachment->fileName}#{$attachment->savedName}"; + } + $attachmentStringToSave = implode(',', $attachmentStrings); + + hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` + SET `attachments` = '" . hesk_dbEscape($attachmentStringToSave) . "' + WHERE `id` = " . intval($ticketId)); + } } \ No newline at end of file diff --git a/api/index.php b/api/index.php index 4382f43c..3a894660 100644 --- a/api/index.php +++ b/api/index.php @@ -147,11 +147,14 @@ Link::before('before'); Link::all(array( // Categories - '/v1/categories' => '\Controllers\Categories\CategoryController::printAllCategories', - '/v1/categories/{i}' => '\Controllers\Categories\CategoryController', + '/v1/categories' => \Controllers\Categories\CategoryController::class . '::printAllCategories', + '/v1/categories/{i}' => \Controllers\Categories\CategoryController::class, // Tickets - '/v1/tickets/{i}' => '\Controllers\Tickets\TicketController', - '/v1/tickets' => '\Controllers\Tickets\TicketController', + '/v1/tickets/{i}' => \Controllers\Tickets\TicketController::class, + '/v1/tickets' => \Controllers\Tickets\TicketController::class, + + // Attachments + '/v1/staff/attachments' => \Controllers\Attachments\StaffTicketAttachmentsController::class, // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' From 5112a6a13bdae0f84dcc14c7e4038d5b3df31490 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 22 Mar 2017 12:54:43 -0400 Subject: [PATCH 083/192] Make the endpoint only for ticket message attachments, not replies --- .../Attachments/AttachmentHandler.php | 6 +----- .../CreateAttachmentForTicketModel.php | 3 --- .../StaffTicketAttachmentsController.php | 9 ++++----- .../Attachments/AttachmentHandlerTest.php | 18 +++--------------- api/index.php | 2 +- 5 files changed, 9 insertions(+), 29 deletions(-) diff --git a/api/BusinessLogic/Attachments/AttachmentHandler.php b/api/BusinessLogic/Attachments/AttachmentHandler.php index 60c32cf0..a05291d4 100644 --- a/api/BusinessLogic/Attachments/AttachmentHandler.php +++ b/api/BusinessLogic/Attachments/AttachmentHandler.php @@ -47,7 +47,7 @@ class AttachmentHandler { $cleanedFileName, $fileParts['extension']); $ticketAttachment->displayName = $cleanedFileName; $ticketAttachment->ticketTrackingId = $ticket->trackingId; - $ticketAttachment->type = $createAttachmentModel->type; + $ticketAttachment->type = 0; $ticketAttachment->downloadCount = 0; $ticketAttachment->fileSize = @@ -88,10 +88,6 @@ class AttachmentHandler { $errorKeys[] = 'TICKET_ID_MISSING'; } - if (!in_array($createAttachmentModel->type, array(AttachmentType::MESSAGE, AttachmentType::REPLY))) { - $errorKeys[] = 'INVALID_ATTACHMENT_TYPE'; - } - $fileParts = pathinfo($createAttachmentModel->displayName); if (!isset($fileParts['extension']) || !in_array(".{$fileParts['extension']}", $heskSettings['attachments']['allowed_types'])) { $errorKeys[] = 'EXTENSION_NOT_PERMITTED'; diff --git a/api/BusinessLogic/Attachments/CreateAttachmentForTicketModel.php b/api/BusinessLogic/Attachments/CreateAttachmentForTicketModel.php index 04336133..7fb6f7ff 100644 --- a/api/BusinessLogic/Attachments/CreateAttachmentForTicketModel.php +++ b/api/BusinessLogic/Attachments/CreateAttachmentForTicketModel.php @@ -6,7 +6,4 @@ namespace BusinessLogic\Attachments; class CreateAttachmentForTicketModel extends CreateAttachmentModel { /* @var $ticketId int */ public $ticketId; - - /* @var $type int [use AttachmentTypeget[AttachmentHandler::class]; - $createAttachmentForTicketModel = $this->createModel(JsonRetriever::getJsonData()); + $createAttachmentForTicketModel = $this->createModel(JsonRetriever::getJsonData(), $ticketId); $createdAttachment = $attachmentHandler->createAttachmentForTicket($createAttachmentForTicketModel, $hesk_settings); return output($createdAttachment, 201); } - private function createModel($json) { + private function createModel($json, $ticketId) { $model = new CreateAttachmentForTicketModel(); $model->attachmentContents = Helpers::safeArrayGet($json, 'data'); $model->displayName = Helpers::safeArrayGet($json, 'displayName'); - $model->ticketId = Helpers::safeArrayGet($json, 'ticketId'); - $model->type = Helpers::safeArrayGet($json, 'type'); + $model->ticketId = $ticketId; return $model; } diff --git a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php index 0a6feddc..a67bd7e6 100644 --- a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php +++ b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php @@ -135,18 +135,6 @@ class AttachmentHandlerTest extends TestCase { $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); } - function testThatValidateThrowsAnExceptionWhenTheAttachmentTypeIsNeitherMessageNorReply() { - //-- Arrange - $this->createAttachmentForTicketModel->type = 5; - - //-- Assert - $this->expectException(ValidationException::class); - $this->expectExceptionMessageRegExp('/INVALID_ATTACHMENT_TYPE/'); - - //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); - } - function testThatValidateThrowsAnExceptionWhenTheFileExtensionIsNotPermitted() { //-- Arrange $this->heskSettings['attachments']['allowed_types'] = array('.gif'); @@ -183,7 +171,7 @@ class AttachmentHandlerTest extends TestCase { $ticketAttachment = new TicketAttachment(); $ticketAttachment->displayName = $this->createAttachmentForTicketModel->displayName; $ticketAttachment->ticketTrackingId = $ticket->trackingId; - $ticketAttachment->type = $this->createAttachmentForTicketModel->type; + $ticketAttachment->type = 0; $ticketAttachment->downloadCount = 0; $ticketAttachment->id = 50; @@ -196,7 +184,7 @@ class AttachmentHandlerTest extends TestCase { //-- Assert self::assertThat($actual->id, self::equalTo(50)); self::assertThat($actual->downloadCount, self::equalTo(0)); - self::assertThat($actual->type, self::equalTo($this->createAttachmentForTicketModel->type)); + self::assertThat($actual->type, self::equalTo(AttachmentType::MESSAGE)); self::assertThat($actual->ticketTrackingId, self::equalTo($ticket->trackingId)); self::assertThat($actual->displayName, self::equalTo($this->createAttachmentForTicketModel->displayName)); } @@ -211,7 +199,7 @@ class AttachmentHandlerTest extends TestCase { $ticketAttachment = new TicketAttachment(); $ticketAttachment->displayName = $this->createAttachmentForTicketModel->displayName; $ticketAttachment->ticketTrackingId = $ticket->trackingId; - $ticketAttachment->type = $this->createAttachmentForTicketModel->type; + $ticketAttachment->type = AttachmentType::MESSAGE; $ticketAttachment->downloadCount = 0; $ticketAttachment->id = 50; diff --git a/api/index.php b/api/index.php index 3a894660..1dea517d 100644 --- a/api/index.php +++ b/api/index.php @@ -154,7 +154,7 @@ Link::all(array( '/v1/tickets' => \Controllers\Tickets\TicketController::class, // Attachments - '/v1/staff/attachments' => \Controllers\Attachments\StaffTicketAttachmentsController::class, + '/v1/staff/tickets/{i}/attachments' => \Controllers\Attachments\StaffTicketAttachmentsController::class, // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' From 37149ec831d15f1630dc3dc48ca44c0747a89fba Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 22 Mar 2017 22:07:14 -0400 Subject: [PATCH 084/192] Working on user ticket security checker --- api/BusinessLogic/Security/UserPrivilege.php | 1 + .../Security/UserToTicketChecker.php | 26 ++++++ .../Security/UserToTicketCheckerTest.php | 88 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 api/BusinessLogic/Security/UserToTicketChecker.php create mode 100644 api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php diff --git a/api/BusinessLogic/Security/UserPrivilege.php b/api/BusinessLogic/Security/UserPrivilege.php index 8344d31d..4f2fb281 100644 --- a/api/BusinessLogic/Security/UserPrivilege.php +++ b/api/BusinessLogic/Security/UserPrivilege.php @@ -12,4 +12,5 @@ namespace BusinessLogic\Security; class UserPrivilege { const CAN_VIEW_TICKETS = 'can_view_tickets'; const CAN_REPLY_TO_TICKETS = 'can_reply_tickets'; + const CAN_EDIT_TICKETS = 'can_edit_tickets'; } \ No newline at end of file diff --git a/api/BusinessLogic/Security/UserToTicketChecker.php b/api/BusinessLogic/Security/UserToTicketChecker.php new file mode 100644 index 00000000..3b0c54dc --- /dev/null +++ b/api/BusinessLogic/Security/UserToTicketChecker.php @@ -0,0 +1,26 @@ +admin === true || + (in_array($ticket->categoryId, $user->categories) && + in_array(UserPrivilege::CAN_VIEW_TICKETS, $user->permissions)); + + return $isEditing + ? $hasAccess && in_array(UserPrivilege::CAN_EDIT_TICKETS, $user->permissions) + : $hasAccess; + } +} \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php b/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php new file mode 100644 index 00000000..0a1eae4d --- /dev/null +++ b/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php @@ -0,0 +1,88 @@ +userToTicketChecker = new UserToTicketChecker(); + } + + function testItReturnsTrueWhenTheUserIsAnAdmin() { + //-- Arrange + $user = new UserContext(); + $user->admin = true; + + $ticket = new Ticket(); + + //-- Act + $result = $this->userToTicketChecker->isTicketWritableToUser($user, $ticket, false, $this->heskSettings); + + //-- Assert + self::assertThat($result, self::isTrue()); + } + + function testItReturnsTrueWhenTheUserHasAccessToTheCategory() { + //-- Arrange + $user = new UserContext(); + $user->admin = false; + $user->categories = array(1); + $user->permissions = array(UserPrivilege::CAN_VIEW_TICKETS); + + $ticket = new Ticket(); + $ticket->categoryId = 1; + + //-- Act + $result = $this->userToTicketChecker->isTicketWritableToUser($user, $ticket, false, $this->heskSettings); + + //-- Assert + self::assertThat($result, self::isTrue()); + } + + function testItReturnsFalseWhenTheUserCannotViewTickets() { + //-- Arrange + $user = new UserContext(); + $user->admin = false; + $user->categories = array(1); + $user->permissions = array(); + + $ticket = new Ticket(); + $ticket->categoryId = 1; + + //-- Act + $result = $this->userToTicketChecker->isTicketWritableToUser($user, $ticket, false, $this->heskSettings); + + //-- Assert + self::assertThat($result, self::isFalse()); + } + + function testItReturnsFalseWhenTheUserCannotViewAndEditTicketsWhenEditFlagIsTrue() { + //-- Arrange + $user = new UserContext(); + $user->admin = false; + $user->categories = array(1); + $user->permissions = array(UserPrivilege::CAN_VIEW_TICKETS, 'something else'); + + $ticket = new Ticket(); + $ticket->categoryId = 1; + + //-- Act + $result = $this->userToTicketChecker->isTicketWritableToUser($user, $ticket, true, $this->heskSettings); + + //-- Assert + self::assertThat($result, self::isFalse()); + } + + //-- TODO Category Manager +} From 97a96b5947845dddc1d3b352e0f99913565da91f Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 25 Mar 2017 16:36:47 -0400 Subject: [PATCH 085/192] UserToTicketChecker now checks for category managers --- .../Security/UserToTicketChecker.php | 18 +++++++++-- api/DataAccess/Security/UserGateway.php | 31 ++++++++++++++++++- .../Security/UserToTicketCheckerTest.php | 30 ++++++++++++++++-- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/api/BusinessLogic/Security/UserToTicketChecker.php b/api/BusinessLogic/Security/UserToTicketChecker.php index 3b0c54dc..0c3b27bf 100644 --- a/api/BusinessLogic/Security/UserToTicketChecker.php +++ b/api/BusinessLogic/Security/UserToTicketChecker.php @@ -4,8 +4,15 @@ namespace BusinessLogic\Security; use BusinessLogic\Tickets\Ticket; +use DataAccess\Security\UserGateway; class UserToTicketChecker { + /* @var $userGateway UserGateway */ + private $userGateway; + + function __construct($userGateway) { + $this->userGateway = $userGateway; + } /** * @param $user UserContext @@ -19,8 +26,13 @@ class UserToTicketChecker { (in_array($ticket->categoryId, $user->categories) && in_array(UserPrivilege::CAN_VIEW_TICKETS, $user->permissions)); - return $isEditing - ? $hasAccess && in_array(UserPrivilege::CAN_EDIT_TICKETS, $user->permissions) - : $hasAccess; + if ($isEditing) { + $categoryManagerId = $this->userGateway->getManagerForCategory($ticket->categoryId, $heskSettings); + + $hasAccess = $hasAccess && + (in_array(UserPrivilege::CAN_EDIT_TICKETS, $user->permissions) || $categoryManagerId == $user->id); + } + + return $hasAccess; } } \ No newline at end of file diff --git a/api/DataAccess/Security/UserGateway.php b/api/DataAccess/Security/UserGateway.php index 7f15a870..e836f18a 100644 --- a/api/DataAccess/Security/UserGateway.php +++ b/api/DataAccess/Security/UserGateway.php @@ -23,6 +23,7 @@ class UserGateway extends CommonDao { ) AND `active` = '1'"); if (hesk_dbNumRows($rs) === 0) { + $this->close(); return null; } @@ -39,10 +40,15 @@ class UserGateway extends CommonDao { $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = " . intval($id)); if (hesk_dbNumRows($rs) === 0) { + $this->close(); return null; } - return UserContext::fromDataRow(hesk_dbFetchAssoc($rs)); + $user = UserContext::fromDataRow(hesk_dbFetchAssoc($rs)); + + $this->close(); + + return $user; } /** @@ -89,6 +95,29 @@ class UserGateway extends CommonDao { $users[] = UserContext::fromDataRow($row); } + $this->close(); + return $users; } + + function getManagerForCategory($categoryId, $heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` + WHERE `id` = ( + SELECT `manager` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "categories` + WHERE `id` = " . intval($categoryId) . ")"); + + if (hesk_dbNumRows($rs) === 0) { + $this->close(); + return null; + } + + $user = UserContext::fromDataRow(hesk_dbFetchAssoc($rs)); + + $this->close(); + + return $user; + } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php b/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php index 0a1eae4d..9304fa7a 100644 --- a/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php +++ b/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php @@ -5,6 +5,7 @@ namespace BusinessLogic\Security; use BusinessLogic\Tickets\Ticket; +use DataAccess\Security\UserGateway; use PHPUnit\Framework\TestCase; class UserToTicketCheckerTest extends TestCase { @@ -12,17 +13,22 @@ class UserToTicketCheckerTest extends TestCase { /* @var $userToTicketChecker UserToTicketChecker */ private $userToTicketChecker; + /* @var $userGateway \PHPUnit_Framework_MockObject_MockObject */ + private $userGateway; + /* @var $heskSettings array */ private $heskSettings; protected function setUp() { - $this->userToTicketChecker = new UserToTicketChecker(); + $this->userGateway = $this->createMock(UserGateway::class); + $this->userToTicketChecker = new UserToTicketChecker($this->userGateway); } function testItReturnsTrueWhenTheUserIsAnAdmin() { //-- Arrange $user = new UserContext(); $user->admin = true; + $user->id = 99; $ticket = new Ticket(); @@ -39,6 +45,7 @@ class UserToTicketCheckerTest extends TestCase { $user->admin = false; $user->categories = array(1); $user->permissions = array(UserPrivilege::CAN_VIEW_TICKETS); + $user->id = 99; $ticket = new Ticket(); $ticket->categoryId = 1; @@ -56,6 +63,7 @@ class UserToTicketCheckerTest extends TestCase { $user->admin = false; $user->categories = array(1); $user->permissions = array(); + $user->id = 99; $ticket = new Ticket(); $ticket->categoryId = 1; @@ -73,6 +81,7 @@ class UserToTicketCheckerTest extends TestCase { $user->admin = false; $user->categories = array(1); $user->permissions = array(UserPrivilege::CAN_VIEW_TICKETS, 'something else'); + $user->id = 99; $ticket = new Ticket(); $ticket->categoryId = 1; @@ -84,5 +93,22 @@ class UserToTicketCheckerTest extends TestCase { self::assertThat($result, self::isFalse()); } - //-- TODO Category Manager + function testItReturnsTrueWhenTheUserDoesNotHaveEditPermissionsButIsTheCategoryManager() { + //-- Arrange + $user = new UserContext(); + $user->admin = false; + $user->categories = array(1); + $user->permissions = array(UserPrivilege::CAN_VIEW_TICKETS, 'something else'); + $user->id = 1; + $this->userGateway->method('getManagerForCategory')->willReturn(1); + + $ticket = new Ticket(); + $ticket->categoryId = 1; + + //-- Act + $result = $this->userToTicketChecker->isTicketWritableToUser($user, $ticket, true, $this->heskSettings); + + //-- Assert + self::assertThat($result, self::isTrue()); + } } From 3cec244e1546a73ff3e381b13b1631e060b81590 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 4 Apr 2017 22:57:21 -0400 Subject: [PATCH 086/192] Getting started on attachment retrieval --- .../Attachments/AttachmentRetriever.php | 24 ++++++++++++++ .../StaffTicketAttachmentsController.php | 14 +++++++-- .../Attachments/AttachmentGateway.php | 26 ++++++++++++++++ api/DataAccess/Files/FileReader.php | 25 +++++++++++++++ .../Attachments/AttachmentRetrieverTest.php | 31 +++++++++++++++++++ api/index.php | 1 + 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 api/BusinessLogic/Attachments/AttachmentRetriever.php create mode 100644 api/DataAccess/Files/FileReader.php create mode 100644 api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php diff --git a/api/BusinessLogic/Attachments/AttachmentRetriever.php b/api/BusinessLogic/Attachments/AttachmentRetriever.php new file mode 100644 index 00000000..bf905018 --- /dev/null +++ b/api/BusinessLogic/Attachments/AttachmentRetriever.php @@ -0,0 +1,24 @@ +attachmentGateway = $attachmentGateway; + $this->fileReader = $fileReader; + } + + function getAttachmentContentsForTicket($id, $heskSettings) { + + } +} \ No newline at end of file diff --git a/api/Controllers/Attachments/StaffTicketAttachmentsController.php b/api/Controllers/Attachments/StaffTicketAttachmentsController.php index 9c054e79..47f265ed 100644 --- a/api/Controllers/Attachments/StaffTicketAttachmentsController.php +++ b/api/Controllers/Attachments/StaffTicketAttachmentsController.php @@ -10,12 +10,22 @@ use BusinessLogic\Helpers; use Controllers\JsonRetriever; class StaffTicketAttachmentsController { - function post($ticketId) { + function get($attachmentId) { global $hesk_settings, $applicationContext; - if (!$hesk_settings['attachments']['use']) { + $this->verifyAttachmentsAreEnabled($hesk_settings); + } + + private function verifyAttachmentsAreEnabled($heskSettings) { + if (!$heskSettings['attachments']['use']) { throw new ApiFriendlyException('Attachments are disabled on this server', 'Attachments Disabled', 404); } + } + + function post($ticketId) { + global $hesk_settings, $applicationContext; + + $this->verifyAttachmentsAreEnabled($hesk_settings); /* @var $attachmentHandler AttachmentHandler */ $attachmentHandler = $applicationContext->get[AttachmentHandler::class]; diff --git a/api/DataAccess/Attachments/AttachmentGateway.php b/api/DataAccess/Attachments/AttachmentGateway.php index 70931e3e..ac7403fe 100644 --- a/api/DataAccess/Attachments/AttachmentGateway.php +++ b/api/DataAccess/Attachments/AttachmentGateway.php @@ -3,6 +3,7 @@ namespace DataAccess\Attachments; +use BusinessLogic\Attachments\Attachment; use BusinessLogic\Attachments\TicketAttachment; use DataAccess\CommonDao; @@ -27,4 +28,29 @@ class AttachmentGateway extends CommonDao { return $attachmentId; } + + function getAttachmentById($id, $heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT * + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "attachments` + WHERE `att_id` = " . intval($id)); + + if (hesk_dbNumRows($rs) === 0) { + return null; + } + + $row = hesk_dbFetchAssoc($rs); + + $attachment = new Attachment(); + $attachment->id = $row['att_id']; + $attachment->savedName = $row['saved_name']; + $attachment->displayName = $row['real_name']; + $attachment->downloadCount = $row['download_count']; + $attachment->fileSize = $row['size']; + + $this->close(); + + return $attachment; + } } \ No newline at end of file diff --git a/api/DataAccess/Files/FileReader.php b/api/DataAccess/Files/FileReader.php new file mode 100644 index 00000000..e30f810c --- /dev/null +++ b/api/DataAccess/Files/FileReader.php @@ -0,0 +1,25 @@ +attachmentGateway = $this->createMock(AttachmentGateway::class); + $this->fileReader = $this->createMock(FileReader::class); + + $this->attachmentRetriever = new AttachmentRetriever($this->attachmentGateway, $this->fileReader); + } + + function testItGetsTheAttachmentFromTheFilesystem() { + + } +} diff --git a/api/index.php b/api/index.php index 1dea517d..b4c1ef29 100644 --- a/api/index.php +++ b/api/index.php @@ -155,6 +155,7 @@ Link::all(array( // Attachments '/v1/staff/tickets/{i}/attachments' => \Controllers\Attachments\StaffTicketAttachmentsController::class, + '/v1/staff/attachments/{i}' => \Controllers\Attachments\StaffTicketAttachmentsController::class, // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' From c58867577f937c89badd35a4ebe6e7ec781ea8c5 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 5 Apr 2017 21:56:13 -0400 Subject: [PATCH 087/192] Attachments should be retrievable --- .../Attachments/AttachmentRetriever.php | 4 ++++ api/DataAccess/Files/FileReader.php | 5 ++-- .../Attachments/AttachmentRetrieverTest.php | 24 +++++++++++++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/api/BusinessLogic/Attachments/AttachmentRetriever.php b/api/BusinessLogic/Attachments/AttachmentRetriever.php index bf905018..00961d7f 100644 --- a/api/BusinessLogic/Attachments/AttachmentRetriever.php +++ b/api/BusinessLogic/Attachments/AttachmentRetriever.php @@ -19,6 +19,10 @@ class AttachmentRetriever { } function getAttachmentContentsForTicket($id, $heskSettings) { + $attachment = $this->attachmentGateway->getAttachmentById($id, $heskSettings); + $contents = base64_encode($this->fileReader->readFromFile( + $attachment->savedName, $heskSettings['attach_dir'])); + return $contents; } } \ No newline at end of file diff --git a/api/DataAccess/Files/FileReader.php b/api/DataAccess/Files/FileReader.php index e30f810c..296c966d 100644 --- a/api/DataAccess/Files/FileReader.php +++ b/api/DataAccess/Files/FileReader.php @@ -7,11 +7,10 @@ class FileReader { /** * @param $name string - The file name (including extension) * @param $folder - The folder name (relative to the ROOT of the helpdesk) - * @param $contents string - The contents of the file to write - * @return int The file size, in bytes + * @returns string - The contents of the file to write * @throws \Exception When the file fails to save */ - function readFromFile($name, $folder, $contents) { + function readFromFile($name, $folder) { // __DIR__ === '/{ROOT}/api/DataAccess/Files $location = __DIR__ . "/../../../{$folder}/{$name}"; $fileContents = file_get_contents($location); diff --git a/api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php b/api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php index 3c74198a..4790262c 100644 --- a/api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php +++ b/api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php @@ -18,14 +18,34 @@ class AttachmentRetrieverTest extends TestCase { /* @var $attachmentRetriever AttachmentRetriever */ private $attachmentRetriever; + /* @var $heskSettings array */ + private $heskSettings; + protected function setUp() { $this->attachmentGateway = $this->createMock(AttachmentGateway::class); $this->fileReader = $this->createMock(FileReader::class); + $this->heskSettings = array('attach_dir' => 'attachments'); $this->attachmentRetriever = new AttachmentRetriever($this->attachmentGateway, $this->fileReader); } - function testItGetsTheAttachmentFromTheFilesystem() { - + function testItGetsTheMetadataFromTheGateway() { + //-- Arrange + $attachmentMeta = new Attachment(); + $attachmentMeta->savedName = '5'; + $attachmentContents = 'string'; + $expectedContents = base64_encode($attachmentContents); + $this->attachmentGateway->method('getAttachmentById') + ->with(4, $this->heskSettings) + ->willReturn($attachmentMeta); + $this->fileReader->method('readFromFile') + ->with('5', $this->heskSettings['attach_dir']) + ->willReturn($attachmentContents); + + //-- Act + $actualContents = $this->attachmentRetriever->getAttachmentContentsForTicket(4, $this->heskSettings); + + //-- Assert + self::assertThat($actualContents, self::equalTo($expectedContents)); } } From e54d5278ac2e0ae25533f8f0d98e93ffaaad6318 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 5 Apr 2017 22:13:37 -0400 Subject: [PATCH 088/192] More work, forgot to include security when creating attachment --- api/ApplicationContext.php | 2 + .../Attachments/AttachmentHandler.php | 17 +++++++- .../Attachments/CreateAttachmentModel.php | 3 ++ .../StaffTicketAttachmentsController.php | 3 +- .../Attachments/AttachmentHandlerTest.php | 40 +++++++++++++------ 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 5ec0939f..a9107a74 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -10,6 +10,7 @@ use BusinessLogic\Emails\EmailTemplateRetriever; use BusinessLogic\Emails\MailgunEmailSender; use BusinessLogic\Security\BanRetriever; use BusinessLogic\Security\UserContextBuilder; +use BusinessLogic\Security\UserToTicketChecker; use BusinessLogic\Settings\ApiChecker; use BusinessLogic\Tickets\Autoassigner; use BusinessLogic\Tickets\TicketRetriever; @@ -96,6 +97,7 @@ class ApplicationContext { $this->get[ModsForHeskSettingsGateway::class]); // Attachments + $this->get[UserToTicketChecker::class] = new UserToTicketChecker($this->get[UserGateway::class]); $this->get[FileWriter::class] = new FileWriter(); $this->get[AttachmentGateway::class] = new AttachmentGateway(); $this->get[AttachmentHandler::class] = new AttachmentHandler($this->get[TicketGateway::class], diff --git a/api/BusinessLogic/Attachments/AttachmentHandler.php b/api/BusinessLogic/Attachments/AttachmentHandler.php index a05291d4..f11d98f6 100644 --- a/api/BusinessLogic/Attachments/AttachmentHandler.php +++ b/api/BusinessLogic/Attachments/AttachmentHandler.php @@ -4,6 +4,8 @@ namespace BusinessLogic\Attachments; use BusinessLogic\Exceptions\ValidationException; +use BusinessLogic\Security\UserContext; +use BusinessLogic\Security\UserToTicketChecker; use BusinessLogic\Tickets\Attachment; use BusinessLogic\Tickets\Ticket; use BusinessLogic\ValidationModel; @@ -21,24 +23,35 @@ class AttachmentHandler { /* @var $fileWriter FileWriter */ private $fileWriter; - function __construct($ticketGateway, $attachmentGateway, $fileWriter) { + /* @var $userToTicketChecker UserToTicketChecker */ + private $userToTicketChecker; + + function __construct($ticketGateway, $attachmentGateway, $fileWriter, $userToTicketChecker) { $this->ticketGateway = $ticketGateway; $this->attachmentGateway = $attachmentGateway; $this->fileWriter = $fileWriter; + $this->userToTicketChecker = $userToTicketChecker; } /** * @param $createAttachmentModel CreateAttachmentForTicketModel + * @param $userContext UserContext * @param $heskSettings array * @return TicketAttachment the newly created attachment + * @throws \Exception */ - function createAttachmentForTicket($createAttachmentModel, $heskSettings) { + function createAttachmentForTicket($createAttachmentModel, $userContext, $heskSettings) { $this->validate($createAttachmentModel, $heskSettings); $decodedAttachment = base64_decode($createAttachmentModel->attachmentContents); $ticket = $this->ticketGateway->getTicketById($createAttachmentModel->ticketId, $heskSettings); + + if (!$this->userToTicketChecker->isTicketWritableToUser($userContext, $ticket, $createAttachmentModel->isEditing, $heskSettings)) { + throw new \Exception("User does not have access to ticket {$ticket->id} being created / edited!"); + } + $cleanedFileName = $this->cleanFileName($createAttachmentModel->displayName); $fileParts = pathinfo($cleanedFileName); diff --git a/api/BusinessLogic/Attachments/CreateAttachmentModel.php b/api/BusinessLogic/Attachments/CreateAttachmentModel.php index 9a5f5428..14179a44 100644 --- a/api/BusinessLogic/Attachments/CreateAttachmentModel.php +++ b/api/BusinessLogic/Attachments/CreateAttachmentModel.php @@ -15,4 +15,7 @@ class CreateAttachmentModel { /* @var $attachmentContents string */ public $attachmentContents; + + /* @var $isEditing bool */ + public $isEditing; } \ No newline at end of file diff --git a/api/Controllers/Attachments/StaffTicketAttachmentsController.php b/api/Controllers/Attachments/StaffTicketAttachmentsController.php index 47f265ed..caa8b8ed 100644 --- a/api/Controllers/Attachments/StaffTicketAttachmentsController.php +++ b/api/Controllers/Attachments/StaffTicketAttachmentsController.php @@ -7,6 +7,7 @@ use BusinessLogic\Attachments\AttachmentHandler; use BusinessLogic\Attachments\CreateAttachmentForTicketModel; use BusinessLogic\Exceptions\ApiFriendlyException; use BusinessLogic\Helpers; +use BusinessLogic\Security\UserToTicketChecker; use Controllers\JsonRetriever; class StaffTicketAttachmentsController { @@ -23,7 +24,7 @@ class StaffTicketAttachmentsController { } function post($ticketId) { - global $hesk_settings, $applicationContext; + global $hesk_settings, $applicationContext, $userContext; $this->verifyAttachmentsAreEnabled($hesk_settings); diff --git a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php index a67bd7e6..f29940b6 100644 --- a/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php +++ b/api/Tests/BusinessLogic/Attachments/AttachmentHandlerTest.php @@ -5,6 +5,8 @@ namespace BusinessLogic\Attachments; use BusinessLogic\Exceptions\ValidationException; +use BusinessLogic\Security\UserContext; +use BusinessLogic\Security\UserToTicketChecker; use BusinessLogic\Tickets\Ticket; use DataAccess\Attachments\AttachmentGateway; use DataAccess\Files\FileWriter; @@ -25,9 +27,15 @@ class AttachmentHandlerTest extends TestCase { /* @var $attachmentGateway \PHPUnit_Framework_MockObject_MockObject */ private $attachmentGateway; + /* @var $userToTicketChecker \PHPUnit_Framework_MockObject_MockObject */ + private $userToTicketChecker; + /* @var $fileWriter \PHPUnit_Framework_MockObject_MockObject */ private $fileWriter; + /* @var $userContext UserContext */ + private $userContext; + /* @var $heskSettings array */ private $heskSettings; @@ -35,6 +43,8 @@ class AttachmentHandlerTest extends TestCase { $this->ticketGateway = $this->createMock(TicketGateway::class); $this->attachmentGateway = $this->createMock(AttachmentGateway::class); $this->fileWriter = $this->createMock(FileWriter::class); + $this->userToTicketChecker = $this->createMock(UserToTicketChecker::class); + $this->userToTicketChecker->method('isTicketWritableToUser')->willReturn(true); $this->heskSettings = array( 'attach_dir' => 'attachments', 'attachments' => array( @@ -43,12 +53,16 @@ class AttachmentHandlerTest extends TestCase { ) ); - $this->attachmentHandler = new AttachmentHandler($this->ticketGateway, $this->attachmentGateway, $this->fileWriter); + $this->attachmentHandler = new AttachmentHandler($this->ticketGateway, + $this->attachmentGateway, + $this->fileWriter, + $this->userToTicketChecker); $this->createAttachmentForTicketModel = new CreateAttachmentForTicketModel(); $this->createAttachmentForTicketModel->attachmentContents = base64_encode('string'); $this->createAttachmentForTicketModel->displayName = 'DisplayName.txt'; $this->createAttachmentForTicketModel->ticketId = 1; $this->createAttachmentForTicketModel->type = AttachmentType::MESSAGE; + $this->userContext = new UserContext(); } function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsNull() { @@ -60,7 +74,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/CONTENTS_EMPTY/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsEmpty() { @@ -72,7 +86,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/CONTENTS_EMPTY/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsInvalidBase64() { @@ -84,7 +98,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/CONTENTS_NOT_BASE_64/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheDisplayNameIsNull() { @@ -96,7 +110,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/DISPLAY_NAME_EMPTY/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheDisplayNameIsEmpty() { @@ -108,7 +122,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/DISPLAY_NAME_EMPTY/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheTicketIdIsNull() { @@ -120,7 +134,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/TICKET_ID_MISSING/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheTicketIdIsANonPositiveInteger() { @@ -132,7 +146,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/TICKET_ID_MISSING/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheFileExtensionIsNotPermitted() { @@ -145,7 +159,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/EXTENSION_NOT_PERMITTED/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testThatValidateThrowsAnExceptionWhenTheFileSizeIsLargerThanMaxPermitted() { @@ -158,7 +172,7 @@ class AttachmentHandlerTest extends TestCase { $this->expectExceptionMessageRegExp('/FILE_SIZE_TOO_LARGE/'); //-- Act - $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); } function testItSavesATicketWithTheProperProperties() { @@ -179,7 +193,7 @@ class AttachmentHandlerTest extends TestCase { //-- Act - $actual = $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $actual = $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); //-- Assert self::assertThat($actual->id, self::equalTo(50)); @@ -208,9 +222,11 @@ class AttachmentHandlerTest extends TestCase { //-- Act - $actual = $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->heskSettings); + $actual = $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings); //-- Assert self::assertThat($actual->fileSize, self::equalTo(1024)); } + + //-- TODO Test UserToTicketChecker } From d461059cf06da8bdb2064be7d5f296512f18e580 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 9 Apr 2017 22:14:13 -0400 Subject: [PATCH 089/192] Wrapped up retrieving ticket attachments --- api/ApplicationContext.php | 10 ++++++++- .../Attachments/AttachmentRetriever.php | 22 ++++++++++++++++--- .../StaffTicketAttachmentsController.php | 13 ++++++++--- .../Attachments/AttachmentRetrieverTest.php | 18 +++++++++++++-- api/index.php | 2 +- 5 files changed, 55 insertions(+), 10 deletions(-) diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index a9107a74..a170a5ec 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -2,6 +2,7 @@ // Responsible for loading in all necessary classes. AKA a poor man's DI solution. use BusinessLogic\Attachments\AttachmentHandler; +use BusinessLogic\Attachments\AttachmentRetriever; use BusinessLogic\Categories\CategoryRetriever; use BusinessLogic\Emails\BasicEmailSender; use BusinessLogic\Emails\EmailSenderHelper; @@ -21,6 +22,7 @@ use BusinessLogic\Tickets\TrackingIdGenerator; use BusinessLogic\Tickets\VerifiedEmailChecker; use DataAccess\Attachments\AttachmentGateway; use DataAccess\Categories\CategoryGateway; +use DataAccess\Files\FileReader; use DataAccess\Files\FileWriter; use DataAccess\Logging\LoggingGateway; use DataAccess\Security\BanGateway; @@ -99,9 +101,15 @@ class ApplicationContext { // Attachments $this->get[UserToTicketChecker::class] = new UserToTicketChecker($this->get[UserGateway::class]); $this->get[FileWriter::class] = new FileWriter(); + $this->get[FileReader::class] = new FileReader(); $this->get[AttachmentGateway::class] = new AttachmentGateway(); $this->get[AttachmentHandler::class] = new AttachmentHandler($this->get[TicketGateway::class], $this->get[AttachmentGateway::class], - $this->get[FileWriter::class]); + $this->get[FileWriter::class], + $this->get[UserToTicketChecker::class]); + $this->get[AttachmentRetriever::class] = new AttachmentRetriever($this->get[AttachmentGateway::class], + $this->get[FileReader::class], + $this->get[TicketGateway::class], + $this->get[UserToTicketChecker::class]); } } \ No newline at end of file diff --git a/api/BusinessLogic/Attachments/AttachmentRetriever.php b/api/BusinessLogic/Attachments/AttachmentRetriever.php index 00961d7f..c4889206 100644 --- a/api/BusinessLogic/Attachments/AttachmentRetriever.php +++ b/api/BusinessLogic/Attachments/AttachmentRetriever.php @@ -3,8 +3,10 @@ namespace BusinessLogic\Attachments; +use BusinessLogic\Security\UserToTicketChecker; use DataAccess\Attachments\AttachmentGateway; use DataAccess\Files\FileReader; +use DataAccess\Tickets\TicketGateway; class AttachmentRetriever { /* @var $attachmentGateway AttachmentGateway */ @@ -13,13 +15,27 @@ class AttachmentRetriever { /* @var $fileReader FileReader */ private $fileReader; - function __construct($attachmentGateway, $fileReader) { + /* @var $ticketGateway TicketGateway */ + private $ticketGateway; + + /* @var $userToTicketChecker UserToTicketChecker */ + private $userToTicketChecker; + + function __construct($attachmentGateway, $fileReader, $ticketGateway, $userToTicketChecker) { $this->attachmentGateway = $attachmentGateway; $this->fileReader = $fileReader; + $this->ticketGateway = $ticketGateway; + $this->userToTicketChecker = $userToTicketChecker; } - function getAttachmentContentsForTicket($id, $heskSettings) { - $attachment = $this->attachmentGateway->getAttachmentById($id, $heskSettings); + function getAttachmentContentsForTicket($ticketId, $attachmentId, $userContext, $heskSettings) { + $ticket = $this->ticketGateway->getTicketById($ticketId, $heskSettings); + + if (!$this->userToTicketChecker->isTicketWritableToUser($userContext, $ticket, false, $heskSettings)) { + throw new \Exception("User does not have access to attachment {$attachmentId}!"); + } + + $attachment = $this->attachmentGateway->getAttachmentById($attachmentId, $heskSettings); $contents = base64_encode($this->fileReader->readFromFile( $attachment->savedName, $heskSettings['attach_dir'])); diff --git a/api/Controllers/Attachments/StaffTicketAttachmentsController.php b/api/Controllers/Attachments/StaffTicketAttachmentsController.php index caa8b8ed..d06ddb75 100644 --- a/api/Controllers/Attachments/StaffTicketAttachmentsController.php +++ b/api/Controllers/Attachments/StaffTicketAttachmentsController.php @@ -4,6 +4,7 @@ namespace Controllers\Attachments; use BusinessLogic\Attachments\AttachmentHandler; +use BusinessLogic\Attachments\AttachmentRetriever; use BusinessLogic\Attachments\CreateAttachmentForTicketModel; use BusinessLogic\Exceptions\ApiFriendlyException; use BusinessLogic\Helpers; @@ -11,10 +12,15 @@ use BusinessLogic\Security\UserToTicketChecker; use Controllers\JsonRetriever; class StaffTicketAttachmentsController { - function get($attachmentId) { - global $hesk_settings, $applicationContext; + function get($ticketId, $attachmentId) { + global $hesk_settings, $applicationContext, $userContext; $this->verifyAttachmentsAreEnabled($hesk_settings); + + /* @var $attachmentRetriever AttachmentRetriever */ + $attachmentRetriever = $applicationContext->get[AttachmentRetriever::class]; + + $attachmentRetriever->getAttachmentContentsForTicket($ticketId, $attachmentId, $userContext, $hesk_settings); } private function verifyAttachmentsAreEnabled($heskSettings) { @@ -33,7 +39,8 @@ class StaffTicketAttachmentsController { $createAttachmentForTicketModel = $this->createModel(JsonRetriever::getJsonData(), $ticketId); - $createdAttachment = $attachmentHandler->createAttachmentForTicket($createAttachmentForTicketModel, $hesk_settings); + $createdAttachment = $attachmentHandler->createAttachmentForTicket( + $createAttachmentForTicketModel, $userContext, $hesk_settings); return output($createdAttachment, 201); } diff --git a/api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php b/api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php index 4790262c..d1b62e8a 100644 --- a/api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php +++ b/api/Tests/BusinessLogic/Attachments/AttachmentRetrieverTest.php @@ -4,8 +4,11 @@ namespace BusinessLogic\Attachments; +use BusinessLogic\Security\UserContext; +use BusinessLogic\Security\UserToTicketChecker; use DataAccess\Attachments\AttachmentGateway; use DataAccess\Files\FileReader; +use DataAccess\Tickets\TicketGateway; use PHPUnit\Framework\TestCase; class AttachmentRetrieverTest extends TestCase { @@ -15,6 +18,12 @@ class AttachmentRetrieverTest extends TestCase { /* @var $fileReader \PHPUnit_Framework_MockObject_MockObject */ private $fileReader; + /* @var $ticketGateway \PHPUnit_Framework_MockObject_MockObject */ + private $ticketGateway; + + /* @var $userToTicketChecker \PHPUnit_Framework_MockObject_MockObject */ + private $userToTicketChecker; + /* @var $attachmentRetriever AttachmentRetriever */ private $attachmentRetriever; @@ -24,9 +33,14 @@ class AttachmentRetrieverTest extends TestCase { protected function setUp() { $this->attachmentGateway = $this->createMock(AttachmentGateway::class); $this->fileReader = $this->createMock(FileReader::class); + $this->ticketGateway = $this->createMock(TicketGateway::class); + $this->userToTicketChecker = $this->createMock(UserToTicketChecker::class); $this->heskSettings = array('attach_dir' => 'attachments'); - $this->attachmentRetriever = new AttachmentRetriever($this->attachmentGateway, $this->fileReader); + $this->attachmentRetriever = new AttachmentRetriever($this->attachmentGateway, $this->fileReader, + $this->ticketGateway, $this->userToTicketChecker); + + $this->userToTicketChecker->method('isTicketWritableToUser')->willReturn(true); } function testItGetsTheMetadataFromTheGateway() { @@ -43,7 +57,7 @@ class AttachmentRetrieverTest extends TestCase { ->willReturn($attachmentContents); //-- Act - $actualContents = $this->attachmentRetriever->getAttachmentContentsForTicket(4, $this->heskSettings); + $actualContents = $this->attachmentRetriever->getAttachmentContentsForTicket(0, 4, new UserContext(), $this->heskSettings); //-- Assert self::assertThat($actualContents, self::equalTo($expectedContents)); diff --git a/api/index.php b/api/index.php index b4c1ef29..1968e797 100644 --- a/api/index.php +++ b/api/index.php @@ -155,7 +155,7 @@ Link::all(array( // Attachments '/v1/staff/tickets/{i}/attachments' => \Controllers\Attachments\StaffTicketAttachmentsController::class, - '/v1/staff/attachments/{i}' => \Controllers\Attachments\StaffTicketAttachmentsController::class, + '/v1/staff/tickets/{i}/attachments/{i}' => \Controllers\Attachments\StaffTicketAttachmentsController::class, // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' From c606be50ef03217270200519b288bf81942eb7e5 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 11 Apr 2017 12:39:09 -0400 Subject: [PATCH 090/192] Output attachment contents --- .../Attachments/StaffTicketAttachmentsController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/Controllers/Attachments/StaffTicketAttachmentsController.php b/api/Controllers/Attachments/StaffTicketAttachmentsController.php index d06ddb75..ad63b7c0 100644 --- a/api/Controllers/Attachments/StaffTicketAttachmentsController.php +++ b/api/Controllers/Attachments/StaffTicketAttachmentsController.php @@ -20,7 +20,9 @@ class StaffTicketAttachmentsController { /* @var $attachmentRetriever AttachmentRetriever */ $attachmentRetriever = $applicationContext->get[AttachmentRetriever::class]; - $attachmentRetriever->getAttachmentContentsForTicket($ticketId, $attachmentId, $userContext, $hesk_settings); + $contents = $attachmentRetriever->getAttachmentContentsForTicket($ticketId, $attachmentId, $userContext, $hesk_settings); + + output(array('contents' => base64_encode($contents))); } private function verifyAttachmentsAreEnabled($heskSettings) { From f4dbffa7e4b40c3d44f301320432bb66e4fcc018 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 10 Apr 2017 13:04:11 -0400 Subject: [PATCH 091/192] #544 Start updating to HESK 2.7.3 --- admin/admin_main.php | 2 +- admin/admin_settings_save.php | 6 ++ admin/admin_submit_ticket.php | 6 +- admin/admin_ticket.php | 2 +- admin/banned_emails.php | 2 +- admin/custom_fields.php | 20 +++--- admin/edit_post.php | 93 +++++++++++++++++++++++++-- admin/find_tickets.php | 12 ++-- admin/index.php | 4 +- admin/knowledgebase_private.php | 2 - admin/manage_knowledgebase.php | 9 +++ admin/password.php | 6 +- inc/admin_functions.inc.php | 8 +++ inc/attachments.inc.php | 2 +- inc/view_attachment_functions.inc.php | 8 +-- 15 files changed, 142 insertions(+), 40 deletions(-) diff --git a/admin/admin_main.php b/admin/admin_main.php index 21b752e8..0d6dcefc 100644 --- a/admin/admin_main.php +++ b/admin/admin_main.php @@ -116,7 +116,7 @@ else { F9ub3RpY2UnXS4nXCcpIj4nLiRoZXNrbGFuZ1snc2gnXS4nPC9hPjwvdGQ+PC90cj48L3RhYmxlPjxwP icuJGhlc2tsYW5nWydzdXBwb3J0X3JlbW92ZSddLicuIDxhIGhyZWY9Imh0dHBzOi8vd3d3Lmhlc2suY 29tL2J1eS5waHAiIHRhcmdldD0iX2JsYW5rIj4nLiRoZXNrbGFuZ1snY2xpY2tfaW5mbyddLic8L2E+P - C9wPic7DQp9DQo=', "\112"); + C9wPjxociAvPic7DQp9DQo=', "\112"); /* Clean unneeded session variables */ hesk_cleanSessionVars('hide'); diff --git a/admin/admin_settings_save.php b/admin/admin_settings_save.php index 7454a899..0c3df742 100644 --- a/admin/admin_settings_save.php +++ b/admin/admin_settings_save.php @@ -758,6 +758,12 @@ if (!$pop3_OK) { $tmp[] = '' . $hesklang['pop3e'] . ': ' . $pop3_error . '

' . $hesklang['pop3log'] . ''; } +// Clear the cache folder +hesk_purge_cache('kb'); +hesk_purge_cache('cf'); +hesk_purge_cache('export', 14400); +hesk_purge_cache('status'); + // Show the settings page and display any notices or success if (count($tmp)) { $errors = implode('

', $tmp); diff --git a/admin/admin_submit_ticket.php b/admin/admin_submit_ticket.php index 09592bb4..9eb40c3a 100644 --- a/admin/admin_submit_ticket.php +++ b/admin/admin_submit_ticket.php @@ -123,9 +123,9 @@ foreach ($hesk_settings['custom_fields'] as $k=>$v) { $tmpvar[$k] = hesk_POST($k); $_SESSION["as_$k"] = ''; if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $tmpvar[$k])) { - $date = strtotime($tmpvar[$k] . ' t00:00:00'); - $dmin = strlen($v['value']['dmin']) ? strtotime($v['value']['dmin'] . ' t00:00:00') : false; - $dmax = strlen($v['value']['dmax']) ? strtotime($v['value']['dmax'] . ' t00:00:00') : false; + $date = strtotime($tmpvar[$k] . ' t00:00:00 UTC'); + $dmin = strlen($v['value']['dmin']) ? strtotime($v['value']['dmin'] . ' t00:00:00 UTC') : false; + $dmax = strlen($v['value']['dmax']) ? strtotime($v['value']['dmax'] . ' t00:00:00 UTC') : false; $_SESSION["as_$k"] = $tmpvar[$k]; diff --git a/admin/admin_ticket.php b/admin/admin_ticket.php index 71100f3e..c782c81f 100644 --- a/admin/admin_ticket.php +++ b/admin/admin_ticket.php @@ -1604,7 +1604,7 @@ function mfh_print_message() {
' . $ticket['ip'] . ''; diff --git a/admin/banned_emails.php b/admin/banned_emails.php index 643b4da8..add9b597 100644 --- a/admin/banned_emails.php +++ b/admin/banned_emails.php @@ -239,7 +239,7 @@ function ban_email() hesk_token_check(); // Get the email - $email = strtolower(hesk_input(hesk_REQUEST('email'))); + $email = hesk_emailCleanup(strtolower(hesk_input(hesk_REQUEST('email')))); // Nothing entered? if (!strlen($email)) { diff --git a/admin/custom_fields.php b/admin/custom_fields.php index f23c7f35..a71b6a5a 100755 --- a/admin/custom_fields.php +++ b/admin/custom_fields.php @@ -732,7 +732,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); $num_before = 0; $num_after = 0; - foreach ($hesk_settings['custom_fields'] as $id => $cf) { + foreach ($hesk_settings['custom_fields'] as $tmp_id => $cf) { if ($cf['place']) { $num_after++; } else { @@ -741,8 +741,8 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); } $k = 1; - foreach ($hesk_settings['custom_fields'] as $id => $cf) { - $id = intval(str_replace('custom', '', $id)); + foreach ($hesk_settings['custom_fields'] as $tmp_id => $cf) { + $tmp_id = intval(str_replace('custom', '', $tmp_id)); if ($hide_up) { $hide_up = false; @@ -771,7 +771,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); $cf['category'] = count($cf['category']) ? $hesklang['cf_cat'] : $hesklang['cf_all']; ?> - + @@ -791,33 +791,33 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); } elseif ($k == 1 || $hide_up) { ?>   - + - +   - + - + - + - diff --git a/admin/edit_post.php b/admin/edit_post.php index 9052fb8b..289faf83 100644 --- a/admin/edit_post.php +++ b/admin/edit_post.php @@ -24,6 +24,8 @@ require(HESK_PATH . 'inc/admin_functions.inc.php'); require(HESK_PATH . 'inc/mail_functions.inc.php'); require(HESK_PATH . 'inc/custom_fields.inc.php'); hesk_load_database_functions(); +require(HESK_PATH . 'inc/posting_functions.inc.php'); +require(HESK_PATH . 'inc/view_attachment_functions.inc.php'); hesk_session_start(); hesk_dbConnect(); @@ -76,16 +78,51 @@ if (hesk_isREQUEST('reply')) { $is_reply = 1; } +// Count number of existing attachments for this post +$number_of_attachments = $is_reply ? hesk_countAttachments($reply['attachments']) : hesk_countAttachments($ticket['attachments']); + if (isset($_POST['save'])) { /* A security check */ hesk_token_check('POST'); $hesk_error_buffer = array(); + // Add attachments? + if ($hesk_settings['attachments']['use'] && $number_of_attachments < $hesk_settings['attachments']['max_number']) { + require_once(HESK_PATH . 'inc/attachments.inc.php'); + + $attachments = array(); + + $use_legacy_attachments = hesk_POST('use-legacy-attachments', 0); + + if ($use_legacy_attachments) { + for ($i = $number_of_attachments + 1; $i <= $hesk_settings['attachments']['max_number']; $i++) { + $att = hesk_uploadFile($i); + if ($att !== false && !empty($att)) { + $attachments[$i] = $att; + } + } + } else { + // The user used the new drag-and-drop system. + $temp_attachment_ids = hesk_POST_array('attachment-ids'); + foreach ($temp_attachment_ids as $temp_attachment_id) { + // Simply get the temp info and move it to the attachments table + $temp_attachment = mfh_getTemporaryAttachment($temp_attachment_id); + $attachments[] = $temp_attachment; + mfh_deleteTemporaryAttachment($temp_attachment_id); + } + } + } + if ($is_reply) { $tmpvar['message'] = hesk_input(hesk_POST('message')) or $hesk_error_buffer[] = $hesklang['enter_message']; if (count($hesk_error_buffer)) { + // Remove any successfully uploaded attachments + if ($hesk_settings['attachments']['use'] && isset($attachments)) { + hesk_removeAttachments($attachments); + } + $myerror = '
    '; foreach ($hesk_error_buffer as $error) { $myerror .= "
  • $error
  • \n"; @@ -101,7 +138,14 @@ if (isset($_POST['save'])) { $tmpvar['html'] = hesk_POST('html'); - hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` SET `html`='" . $tmpvar['html'] . "', `message`='" . hesk_dbEscape($tmpvar['message']) . "' WHERE `id`='" . intval($tmpvar['id']) . "' AND `replyto`='" . intval($ticket['id']) . "'"); + if ($hesk_settings['attachments']['use'] && !empty($attachments)) { + foreach ($attachments as $myatt) { + hesk_dbQuery("INSERT INTO `".hesk_dbEscape($hesk_settings['db_pfix'])."attachments` (`ticket_id`,`saved_name`,`real_name`,`size`) VALUES ('".hesk_dbEscape($trackingID)."','".hesk_dbEscape($myatt['saved_name'])."','".hesk_dbEscape($myatt['real_name'])."','".intval($myatt['size'])."')"); + $myattachments .= hesk_dbInsertID() . '#' . $myatt['real_name'] . '#' . $myatt['saved_name'] . ','; + } + } + + hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` SET `html`='" . $tmpvar['html'] . "', `message`='" . hesk_dbEscape($tmpvar['message']) . "', `attachments`=CONCAT(`attachments`, '".hesk_dbEscape($myattachments)."') WHERE `id`='" . intval($tmpvar['id']) . "' AND `replyto`='" . intval($ticket['id']) . "'"); } else { $tmpvar['language'] = hesk_POST('customerLanguage'); $tmpvar['name'] = hesk_input(hesk_POST('name')) or $hesk_error_buffer[] = $hesklang['enter_your_name']; @@ -161,9 +205,9 @@ if (isset($_POST['save'])) { $_SESSION["as_$k"] = ''; if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $tmpvar[$k])) { - $date = strtotime($tmpvar[$k] . ' t00:00:00'); - $dmin = strlen($v['value']['dmin']) ? strtotime($v['value']['dmin'] . ' t00:00:00') : false; - $dmax = strlen($v['value']['dmax']) ? strtotime($v['value']['dmax'] . ' t00:00:00') : false; + $date = strtotime($tmpvar[$k] . ' t00:00:00 UTC'); + $dmin = strlen($v['value']['dmin']) ? strtotime($v['value']['dmin'] . ' t00:00:00 UTC') : false; + $dmax = strlen($v['value']['dmax']) ? strtotime($v['value']['dmax'] . ' t00:00:00 UTC') : false; $_SESSION["as_$k"] = $tmpvar[$k]; @@ -208,6 +252,11 @@ if (isset($_POST['save'])) { } if (count($hesk_error_buffer)) { + // Remove any successfully uploaded attachments + if ($hesk_settings['attachments']['use'] && isset($attachments)) { + hesk_removeAttachments($attachments); + } + $myerror = '
      '; foreach ($hesk_error_buffer as $error) { $myerror .= "
    • $error
    • \n"; @@ -221,6 +270,13 @@ if (isset($_POST['save'])) { $tmpvar['message'] = nl2br($tmpvar['message']); } + if ($hesk_settings['attachments']['use'] && !empty($attachments)) { + foreach ($attachments as $myatt) { + hesk_dbQuery("INSERT INTO `".hesk_dbEscape($hesk_settings['db_pfix'])."attachments` (`ticket_id`,`saved_name`,`real_name`,`size`) VALUES ('".hesk_dbEscape($trackingID)."','".hesk_dbEscape($myatt['saved_name'])."','".hesk_dbEscape($myatt['real_name'])."','".intval($myatt['size'])."')"); + $myattachments .= hesk_dbInsertID() . '#' . $myatt['real_name'] . '#' . $myatt['saved_name'] . ','; + } + } + $custom_SQL = ''; for ($i = 1; $i <= 50; $i++) { $custom_SQL .= '`custom'.$i.'`=' . (isset($tmpvar['custom'.$i]) ? "'".hesk_dbEscape($tmpvar['custom'.$i])."'" : "''") . ','; @@ -232,6 +288,7 @@ if (isset($_POST['save'])) { `email`='" . hesk_dbEscape($tmpvar['email']) . "', `subject`='" . hesk_dbEscape($tmpvar['subject']) . "', `message`='" . hesk_dbEscape($tmpvar['message']) . "', + `attachments`=CONCAT(`attachments`, '".hesk_dbEscape($myattachments)."'), `language`='" . hesk_dbEscape($tmpvar['language']) . "', `html`='" . hesk_dbEscape($tmpvar['html']) . "', $custom_SQL @@ -278,7 +335,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); $onsubmit = 'onsubmit="return validateRichText(\'message-help-block\', \'message-group\', \'message\', \''.htmlspecialchars($hesklang['this_field_is_required']).'\')"'; } ?> -
      > + > + +
      + + +
      + +
      +
      +
      @@ -598,4 +668,15 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
      ' . $hesklang['invalid_search']; } diff --git a/admin/index.php b/admin/index.php index 627e803c..25f9f651 100644 --- a/admin/index.php +++ b/admin/index.php @@ -69,7 +69,7 @@ function do_login() require_once(HESK_PATH . 'inc/recaptcha/recaptchalib.php'); $resp = recaptcha_check_answer($hesk_settings['recaptcha_private_key'], - $_SERVER['REMOTE_ADDR'], + hesk_getClientIP(), hesk_POST('recaptcha_challenge_field', ''), hesk_POST('recaptcha_response_field', '') ); @@ -88,7 +88,7 @@ function do_login() // Was there a reCAPTCHA response? if (isset($_POST["g-recaptcha-response"])) { - $resp = $reCaptcha->verifyResponse($_SERVER["REMOTE_ADDR"], hesk_POST("g-recaptcha-response")); + $resp = $reCaptcha->verifyResponse(hesk_getClientIP(), hesk_POST("g-recaptcha-response")); } if ($resp != null && $resp->success) { diff --git a/admin/knowledgebase_private.php b/admin/knowledgebase_private.php index 565ad3fe..944aa878 100644 --- a/admin/knowledgebase_private.php +++ b/admin/knowledgebase_private.php @@ -146,8 +146,6 @@ function hesk_kb_search($query) { global $hesk_settings, $hesklang; - define('HESK_NO_ROBOTS',1); - $res = hesk_dbQuery('SELECT t1.`id`, t1.`subject`, LEFT(`t1`.`content`, '.max(200, $hesk_settings['kb_substrart'] * 2).') AS `content`, t1.`rating` FROM `'.hesk_dbEscape($hesk_settings['db_pfix']).'kb_articles` AS t1 LEFT JOIN `'.hesk_dbEscape($hesk_settings['db_pfix']).'kb_categories` AS t2 ON t1.`catid` = t2.`id` '." WHERE t1.`type` IN ('0','1') AND MATCH(`subject`,`content`,`keywords`) AGAINST ('".hesk_dbEscape($query)."') LIMIT ".intval($hesk_settings['kb_search_limit'])); $num = hesk_dbNumRows($res); $show_default_category = false; diff --git a/admin/manage_knowledgebase.php b/admin/manage_knowledgebase.php index 33066f06..47a1db5a 100644 --- a/admin/manage_knowledgebase.php +++ b/admin/manage_knowledgebase.php @@ -1080,6 +1080,9 @@ function edit_category() // Now delete the category hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."kb_categories` WHERE `id`='".intval($catid)."'"); + // Clear KB cache + hesk_purge_cache('kb'); + $_SESSION['hide'] = array( //'treemenu' => 1, 'new_article' => 1, @@ -1252,6 +1255,9 @@ function save_article() // Update article order update_article_order($catid); + // Clear KB cache + hesk_purge_cache('kb'); + // Redirect to the correct page switch ($from) { case 'draft': @@ -2174,6 +2180,9 @@ function remove_article() hesk_dbQuery("UPDATE `".hesk_dbEscape($hesk_settings['db_pfix'])."kb_categories` SET `articles_draft`=`articles_draft`-1 WHERE `id`='{$catid}'"); } + // Clear KB cache + hesk_purge_cache('kb'); + // Redirect to the correct page switch ($from) { case 'draft': diff --git a/admin/password.php b/admin/password.php index b6c35830..ddec4903 100644 --- a/admin/password.php +++ b/admin/password.php @@ -51,7 +51,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { require_once(HESK_PATH . 'inc/recaptcha/recaptchalib.php'); $resp = recaptcha_check_answer($hesk_settings['recaptcha_private_key'], - $_SERVER['REMOTE_ADDR'], + hesk_getClientIP(), hesk_POST('recaptcha_challenge_field', ''), hesk_POST('recaptcha_response_field', '') ); @@ -70,7 +70,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { // Was there a reCAPTCHA response? if (isset($_POST["g-recaptcha-response"])) { - $resp = $reCaptcha->verifyResponse($_SERVER["REMOTE_ADDR"], hesk_POST("g-recaptcha-response")); + $resp = $reCaptcha->verifyResponse(hesk_getClientIP(), hesk_POST("g-recaptcha-response")); } if ($resp != null && $resp->success) { @@ -121,7 +121,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { hesk_process_messages($hesklang['noace'], 'NOREDIRECT'); } else { $row = hesk_dbFetchAssoc($res); - $hash = sha1(microtime() . $_SERVER['REMOTE_ADDR'] . mt_rand() . $row['id'] . $row['name'] . $row['pass']); + $hash = sha1(microtime() . hesk_getClientIP() . mt_rand() . $row['id'] . $row['name'] . $row['pass']); // Insert the verification hash into the database hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "reset_password` (`user`, `hash`, `ip`) VALUES (" . intval($row['id']) . ", '{$hash}', '" . hesk_dbEscape($_SERVER['REMOTE_ADDR']) . "') "); diff --git a/inc/admin_functions.inc.php b/inc/admin_functions.inc.php index f2eb5e04..832c5cd4 100644 --- a/inc/admin_functions.inc.php +++ b/inc/admin_functions.inc.php @@ -34,6 +34,8 @@ $hesk_settings['possible_ticket_list'] = array( 'time_worked' => $hesklang['ts'], ); +define('HESK_NO_ROBOTS', true); + /*** FUNCTIONS ***/ @@ -564,6 +566,8 @@ function hesk_verifyGoto() 'banned_emails.php' => '', 'banned_ips.php' => '', 'change_status.php' => '', + 'custom_fields.php' => '', + 'custom_statuses.php' => '', 'edit_post.php' => '', 'email_templates.php' => '', 'export.php' => '', @@ -572,6 +576,7 @@ function hesk_verifyGoto() 'knowledgebase_private.php' => '', 'lock.php' => '', 'mail.php' => '', + 'mail.php?a=read&id=1' => '', 'manage_canned.php' => '', 'manage_categories.php' => '', 'manage_knowledgebase.php' => '', @@ -715,6 +720,9 @@ function hesk_purge_cache($type = '', $expire_after_seconds = 0) case 'cf': $files = glob($cache_dir.'cf_*', GLOB_NOSORT); break; + case 'kb': + $files = array($cache_dir.'kb.cache.php'); + break; default: hesk_rrmdir(trim($cache_dir, '/'), true); return true; diff --git a/inc/attachments.inc.php b/inc/attachments.inc.php index 37535af7..b2c9886e 100644 --- a/inc/attachments.inc.php +++ b/inc/attachments.inc.php @@ -103,7 +103,7 @@ function hesk_fileError($error) } // End hesk_fileError() -function hesk_removeAttachments($attachments, $isTicket) +function hesk_removeAttachments($attachments, $isTicket = true) { global $hesk_settings, $hesklang, $modsForHesk_settings; diff --git a/inc/view_attachment_functions.inc.php b/inc/view_attachment_functions.inc.php index a65c00e6..6ff6cc69 100644 --- a/inc/view_attachment_functions.inc.php +++ b/inc/view_attachment_functions.inc.php @@ -207,14 +207,14 @@ function output_attachment_id_holder_container($id) { echo '
      '; } -function build_dropzone_markup($admin = false, $id = 'filedrop') { +function build_dropzone_markup($admin = false, $id = 'filedrop', $startingId = 1) { global $hesklang, $hesk_settings; $directory_separator = $admin ? '../' : ''; echo '
      '; - for ($i = 1; $i <= $hesk_settings['attachments']['max_number']; $i++) { + for ($i = $startingId; $i <= $hesk_settings['attachments']['max_number']; $i++) { $cls = ($i == 1 && isset($_SESSION['iserror']) && in_array('attachments', $_SESSION['iserror'])) ? ' class="isError" ' : ''; echo '
      '; } @@ -225,7 +225,7 @@ function build_dropzone_markup($admin = false, $id = 'filedrop') { onclick="Javascript:hesk_window(\'' . $directory_separator . 'file_limits.php\',250,500);return false;">'. $hesklang['ful'] . ''; } -function display_dropzone_field($url, $id = 'filedrop') { +function display_dropzone_field($url, $id = 'filedrop', $max_files_override = -1) { global $hesk_settings, $hesklang; output_dropzone_window(); @@ -233,7 +233,7 @@ function display_dropzone_field($url, $id = 'filedrop') { $acceptedFiles = implode(',', $hesk_settings['attachments']['allowed_types']); $size = mfh_bytesToUnits($hesk_settings['attachments']['max_size']); - $max_files = $hesk_settings['attachments']['max_number']; + $max_files = $max_files_override > -1 ? $max_files_override : $hesk_settings['attachments']['max_number']; echo " @@ -111,6 +112,7 @@ if (is_dir(HESK_PATH . 'install')) { + diff --git a/install/mods-for-hesk/sql/installSql.php b/install/mods-for-hesk/sql/installSql.php index b2faa81a..b8772650 100644 --- a/install/mods-for-hesk/sql/installSql.php +++ b/install/mods-for-hesk/sql/installSql.php @@ -1009,6 +1009,7 @@ function execute310Scripts() { executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` ADD COLUMN `foreground_color` VARCHAR(7) NOT NULL DEFAULT 'AUTO'"); executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` ADD COLUMN `display_border_outline` ENUM('0','1') NOT NULL DEFAULT '0'"); executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` CHANGE `color` `background_color` VARCHAR(7) NOT NULL DEFAULT '#FFFFFF'"); + executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) VALUES ('login_background', '#d2d6de')"); updateVersion('3.1.0'); } \ No newline at end of file From fb2861ea218f1d256bfebb30272d99d858923ba3 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Mon, 5 Jun 2017 22:03:08 -0400 Subject: [PATCH 158/192] Add options for image url or background color --- admin/admin_settings.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/admin/admin_settings.php b/admin/admin_settings.php index cafbd5da..e6859513 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -3797,9 +3797,22 @@ $modsForHesk_settings = mfh_getSettings(); -
      - +
      +
      + +
          + +
      +
      + +
          +
      From 22789540ec1e36c30c902610e6e1218f673cc3d7 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 6 Jun 2017 13:02:53 -0400 Subject: [PATCH 159/192] Form is pretty much done for login background --- admin/admin_settings.php | 30 ++++++++++++++++++++---- install/mods-for-hesk/sql/installSql.php | 1 + 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/admin/admin_settings.php b/admin/admin_settings.php index e6859513..37cc07a7 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -3800,19 +3800,39 @@ $modsForHesk_settings = mfh_getSettings();
          - + >
      -
          - +
      + > + +
      + Login Background + +
      diff --git a/install/mods-for-hesk/sql/installSql.php b/install/mods-for-hesk/sql/installSql.php index b8772650..49fb23cd 100644 --- a/install/mods-for-hesk/sql/installSql.php +++ b/install/mods-for-hesk/sql/installSql.php @@ -1009,6 +1009,7 @@ function execute310Scripts() { executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` ADD COLUMN `foreground_color` VARCHAR(7) NOT NULL DEFAULT 'AUTO'"); executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` ADD COLUMN `display_border_outline` ENUM('0','1') NOT NULL DEFAULT '0'"); executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` CHANGE `color` `background_color` VARCHAR(7) NOT NULL DEFAULT '#FFFFFF'"); + executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) VALUES ('login_background_type', 'color')"); executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) VALUES ('login_background', '#d2d6de')"); updateVersion('3.1.0'); From 31265e76966af139cf933a7fbdf043269842569e Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Tue, 6 Jun 2017 22:02:46 -0400 Subject: [PATCH 160/192] Working on saving the login backdrop --- admin/admin_settings_save.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/admin/admin_settings_save.php b/admin/admin_settings_save.php index e62568b5..900b70ef 100644 --- a/admin/admin_settings_save.php +++ b/admin/admin_settings_save.php @@ -495,6 +495,25 @@ $set['dropdownItemTextHoverColor'] = hesk_input(hesk_POST('dropdownItemTextHover $set['questionMarkColor'] = hesk_input(hesk_POST('questionMarkColor')); $set['dropdownItemTextHoverBackgroundColor'] = hesk_input(hesk_POST('dropdownItemTextHoverBackgroundColor')); $set['admin_color_scheme'] = hesk_input(hesk_POST('admin-color-scheme')); + +$set['login_background_type'] = hesk_input(hesk_POST('login-background')); + +if ($set['login_background_type'] == 'color') { + unlink($hesk_settings['cache_dir'] . '/' . $set['login_background']); + $set['login_background'] = hesk_input(hesk_POST('login-background-color')); +} else { + $file_name = $_FILES['login-attachment-image']['name']; + + if (!empty($file_name)) { + unlink($hesk_settings['cache_dir'] . '/' . $set['login_background']); + + $file_size = $_FILES['login-attachment-image']['size']; + if ($file_size > $hesk_settings['attachments']['max_size']) { + return hesk_fileError(sprintf($hesklang['file_too_large'], $file_name)); + } + + } +} $set['login_background'] = hesk_input(hesk_POST('login-background')); mfh_updateSetting('rtl', $set['rtl']); mfh_updateSetting('show_icons', $set['show-icons']); From 65c7c15c74d8063bd84d312d6c9a0f373959dfe6 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 7 Jun 2017 21:58:47 -0400 Subject: [PATCH 161/192] more work on login background --- admin/admin_settings_save.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/admin/admin_settings_save.php b/admin/admin_settings_save.php index 900b70ef..dbec28e6 100644 --- a/admin/admin_settings_save.php +++ b/admin/admin_settings_save.php @@ -502,19 +502,30 @@ if ($set['login_background_type'] == 'color') { unlink($hesk_settings['cache_dir'] . '/' . $set['login_background']); $set['login_background'] = hesk_input(hesk_POST('login-background-color')); } else { - $file_name = $_FILES['login-attachment-image']['name']; + $file_name = hesk_cleanFileName($_FILES['login-attachment-image']['name']); if (!empty($file_name)) { - unlink($hesk_settings['cache_dir'] . '/' . $set['login_background']); - $file_size = $_FILES['login-attachment-image']['size']; if ($file_size > $hesk_settings['attachments']['max_size']) { return hesk_fileError(sprintf($hesklang['file_too_large'], $file_name)); } + unlink($hesk_settings['cache_dir'] . '/login-background/' . $set['login_background']); + + $useChars = 'AEUYBDGHJLMNPQRSTVWXZ123456789'; + $tmp = uniqid(); + for ($j = 1; $j < 10; $j++) { + $tmp .= $useChars{mt_rand(0, 29)}; + } + + $file_to_move = $_FILES['login-attachment-image']['tmp_name']; + if (!move_uploaded_file($file_to_move, __DIR__ . '/../' . $hesk_settings['cache_dir'] . '/login-background/' . $file_name)) { + hesk_error($hesklang['cannot_move_tmp']); + } + + $set['login_background'] = $file_name; } } -$set['login_background'] = hesk_input(hesk_POST('login-background')); mfh_updateSetting('rtl', $set['rtl']); mfh_updateSetting('show_icons', $set['show-icons']); mfh_updateSetting('custom_field_setting', $set['custom-field-setting']); From e48022b53317b412c3ef32ef7e789ea024ad94e8 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 8 Jun 2017 13:07:08 -0400 Subject: [PATCH 162/192] Login background can be uploaded --- admin/admin_settings.php | 4 ++-- admin/admin_settings_save.php | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/admin/admin_settings.php b/admin/admin_settings.php index 37cc07a7..f7fb677f 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -531,7 +531,7 @@ $modsForHesk_settings = mfh_getSettings(); onclick="javascript:alert('')"> - @@ -3815,7 +3815,7 @@ $modsForHesk_settings = mfh_getSettings(); >
      - Login Background + Login Background
      '; + echo recaptcha_get_html($hesk_settings['recaptcha_public_key'], null, true); + echo '
      '; } - - $is_1 = ''; - $is_2 = ''; - $is_3 = ''; - - $remember_user = hesk_POST('remember_user'); - - if ($hesk_settings['autologin'] && (isset($_COOKIE['hesk_p']) || $remember_user == 'AUTOLOGIN')) { - $is_1 = 'checked'; - } elseif (isset($_COOKIE['hesk_username']) || $remember_user == 'JUSTUSER') { - $is_2 = 'checked'; - } else { - $is_3 = 'checked'; + // Use reCaptcha API v2? + elseif ($hesk_settings['recaptcha_use'] == 2) + { + ?> +
      +
      +
      +
      +
      +
      '; + $cls = in_array('mysecnum',$_SESSION['a_iserror']) ? ' class="isError" ' : ''; - if ($hesk_settings['list_users']) : - $res = hesk_dbQuery("SELECT `user` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` WHERE `active` = '1' ORDER BY `user` ASC"); - ?> - - - - -
      - - -
      - -
      - -
      -
      -
      '.$hesklang['sec_img'].' '. + ''.$hesklang['reload'].''. + '

      '; + echo ''; + } + } // End if $hesk_settings['secimg_use'] == 2 - // SPAM prevention verified for this session - if (isset($_SESSION['img_a_verified'])) - { - echo ' '.$hesklang['vrfy']; - } - // Not verified yet, should we use Recaptcha? - elseif ($hesk_settings['recaptcha_use'] == 1) + if ($hesk_settings['autologin']) { ?> - -
      '; - echo recaptcha_get_html($hesk_settings['recaptcha_public_key'], null, true); - echo '
      '; - } - // Use reCaptcha API v2? - elseif ($hesk_settings['recaptcha_use'] == 2) - { - ?>
      -
      -
      +
      +
      + +
      +
      + +
      +
      + +
      '; - $cls = in_array('mysecnum',$_SESSION['a_iserror']) ? ' class="isError" ' : ''; - - echo $hesklang['sec_enter'].'

      '.$hesklang['sec_img'].' '. - ''.$hesklang['reload'].''. - '

      '; - echo '
      '; - } - } // End if $hesk_settings['secimg_use'] == 2 - - if ($hesk_settings['autologin']) - { - ?> -
      -
      -
      - -
      -
      - -
      -
      - + ?> +
      +
      +
      + +
      -
      -
      -
      - -
      + + + '; + } + + // Do we allow staff password reset? + if ($hesk_settings['reset_pass']) + { + echo '

      '.$hesklang['fpass'].''; + } + ?>
      - -
      -
      - - - '; - } - - // Do we allow staff password reset? - if ($hesk_settings['reset_pass']) - { - echo '

      '.$hesklang['fpass'].''; - } - ?> -
      -
      - + +
      ; } - + body { background: ; } - + + body { + background: url('') + no-repeat center center fixed; + background-size: cover; + } + + .login-box-background { + background: url('') + no-repeat center center fixed; + background-size: cover; + } + Date: Fri, 9 Jun 2017 22:39:00 -0400 Subject: [PATCH 165/192] Working on adding a login header image --- admin/admin_settings.php | 51 +++++++++++++----- admin/admin_settings_save.php | 69 +++++++++++++++++++++--- install/mods-for-hesk/sql/installSql.php | 2 + js/modsForHesk-javascript.js | 10 ++++ 4 files changed, 112 insertions(+), 20 deletions(-) diff --git a/admin/admin_settings.php b/admin/admin_settings.php index fb4ca274..b34007a4 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -3791,6 +3791,7 @@ $modsForHesk_settings = mfh_getSettings(); +

      LOGIN PAGE

      @@ -3800,7 +3801,9 @@ $modsForHesk_settings = mfh_getSettings();
          @@ -3808,35 +3811,55 @@ $modsForHesk_settings = mfh_getSettings();
      >
      - Login Background + Login Background
      +
      +
      +
      + +
      +
      + +

      +
      + + > + +
      + <?php echo $modsForHesk_settings['login_box_header_image']; ?> + +
      +
      +
      +
      +
      diff --git a/admin/admin_settings_save.php b/admin/admin_settings_save.php index 26c6e7e4..469094b0 100644 --- a/admin/admin_settings_save.php +++ b/admin/admin_settings_save.php @@ -39,6 +39,8 @@ hesk_checkPermission('can_manage_settings'); // A security check hesk_token_check('POST'); +$modsForHesk_settings = mfh_getSettings(); + // Demo mode if (defined('HESK_DEMO')) { hesk_process_messages($hesklang['sdemo'], 'admin_settings.php'); @@ -497,10 +499,14 @@ $set['dropdownItemTextHoverBackgroundColor'] = hesk_input(hesk_POST('dropdownIte $set['admin_color_scheme'] = hesk_input(hesk_POST('admin-color-scheme')); $set['login_background_type'] = hesk_input(hesk_POST('login-background')); +$set['login_box_header'] = hesk_input(hesk_POST('login-box-header')); $changedBackground = false; +$loadedAttachmentFuncs = false; if ($set['login_background_type'] == 'color') { - unlink($hesk_settings['cache_dir'] . '/lb_' . $set['login_background']); + if (file_exists($hesk_settings['cache_dir'] . '/lb_' . $set['login_background'])) { + unlink($hesk_settings['cache_dir'] . '/lb_' . $set['login_background']); + } $set['login_background'] = hesk_input(hesk_POST('login-background-color')); if ($set['login_background'] == '') { $set['login_background'] = '#d2d6de'; @@ -508,8 +514,11 @@ if ($set['login_background_type'] == 'color') { $changedBackground = true; } else { - include(HESK_PATH . 'inc/attachments.inc.php'); - include(HESK_PATH . 'inc/posting_functions.inc.php'); + if (!$loadedAttachmentFuncs) { + include(HESK_PATH . 'inc/attachments.inc.php'); + include(HESK_PATH . 'inc/posting_functions.inc.php'); + $loadedAttachmentFuncs = true; + } $file_name = hesk_cleanFileName($_FILES['login-background-image']['name']); @@ -522,8 +531,8 @@ if ($set['login_background_type'] == 'color') { } $ext = strtolower(strrchr($file_name, ".")); - if (file_exists($hesk_settings['cache_dir'] . '/lb_' . $set['login_background'])) { - unlink($hesk_settings['cache_dir'] . '/lb_' . $set['login_background']); + if (file_exists($hesk_settings['cache_dir'] . '/lb_' . $modsForHesk_settings['login_background'])) { + unlink($hesk_settings['cache_dir'] . '/lb_' . $modsForHesk_settings['login_background']); } $saved_name = 'login-background' . $ext; @@ -539,6 +548,49 @@ if ($set['login_background_type'] == 'color') { $changedBackground = true; } } +$changedLoginImage = false; +if ($set['login_box_header'] == 'image') { + if (!$loadedAttachmentFuncs) { + include(HESK_PATH . 'inc/attachments.inc.php'); + include(HESK_PATH . 'inc/posting_functions.inc.php'); + $loadedAttachmentFuncs = true; + } + + + $file_name = hesk_cleanFileName($_FILES['login-box-header-image']['name']); + + if (!empty($_FILES['login-box-header-image']['name'])) { + $file_size = $_FILES['login-box-header-image']['size']; + if ($file_size > $hesk_settings['attachments']['max_size']) { + return hesk_fileError(sprintf($hesklang['file_too_large'], $file_name)); + } + $ext = strtolower(strrchr($file_name, ".")); + + if (file_exists($hesk_settings['cache_dir'] . '/lbh_' . $modsForHesk_settings['login_box_header_image'])) { + unlink($hesk_settings['cache_dir'] . '/lbh_' . $modsForHesk_settings['login_box_header_image']); + } + + $saved_name = 'login-box-header-image' . $ext; + + $file_to_move = $_FILES['login-box-header-image']['tmp_name']; + + + if (!move_uploaded_file($file_to_move, dirname(dirname(__FILE__)) . '/' . $hesk_settings['cache_dir'] . '/lbh_' . $saved_name)) { + hesk_error($hesklang['cannot_move_tmp']); + } + + $set['login_box_header_image'] = $saved_name; + $changedLoginImage = true; + } +} else { + if (file_exists($hesk_settings['cache_dir'] . '/lbh_' . $set['login_box_header_image'])) { + unlink($hesk_settings['cache_dir'] . '/lbh_' . $set['login_box_header_image']); + } + + $set['login_box_header_image'] = ''; + $changedLoginImage = true; +} + mfh_updateSetting('rtl', $set['rtl']); mfh_updateSetting('show_icons', $set['show-icons']); mfh_updateSetting('custom_field_setting', $set['custom-field-setting']); @@ -577,9 +629,14 @@ mfh_updateSetting('first_day_of_week', $set['first_day_of_week'], false); mfh_updateSetting('default_calendar_view', $set['default_view'], true); mfh_updateSetting('admin_color_scheme', $set['admin_color_scheme'], true); +mfh_updateSetting('login_background_type', $set['login_background_type'], true); if ($changedBackground) { mfh_updateSetting('login_background', $set['login_background'], true); - mfh_updateSetting('login_background_type', $set['login_background_type'], true); +} + +mfh_updateSetting('login_box_header', $set['login_box_header'], true); +if ($changedLoginImage) { + mfh_updateSetting('login_box_header_image', $set['login_box_header_image'], true); } // Prepare settings file and save it diff --git a/install/mods-for-hesk/sql/installSql.php b/install/mods-for-hesk/sql/installSql.php index 49fb23cd..d50ebc42 100644 --- a/install/mods-for-hesk/sql/installSql.php +++ b/install/mods-for-hesk/sql/installSql.php @@ -1011,6 +1011,8 @@ function execute310Scripts() { executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` CHANGE `color` `background_color` VARCHAR(7) NOT NULL DEFAULT '#FFFFFF'"); executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) VALUES ('login_background_type', 'color')"); executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) VALUES ('login_background', '#d2d6de')"); + executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) VALUES ('login_box_header', 'helpdesk-title')"); + executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) VALUES ('login_box_header_image', '')"); updateVersion('3.1.0'); } \ No newline at end of file diff --git a/js/modsForHesk-javascript.js b/js/modsForHesk-javascript.js index cfac6bac..2dac5abe 100644 --- a/js/modsForHesk-javascript.js +++ b/js/modsForHesk-javascript.js @@ -94,6 +94,16 @@ var loadJquery = function() closeOnContentClick: true }); + $('[data-activate]').click(function() { + var activate = $(this).data('activate'); + $(activate).removeAttr('disabled'); + }); + + $('[data-deactivate]').click(function() { + var deactivate = $(this).data('deactivate'); + $(deactivate).attr('disabled', 'disabled'); + }); + //-- Initialize toastr properties toastr.options.progressBar = true; toastr.options.closeButton = true; From 01570e856c9cc42a434108a13f81ff7c80b0fc4e Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 10 Jun 2017 12:06:14 -0400 Subject: [PATCH 166/192] Login header image is working --- admin/admin_settings.php | 26 ++++++++++++++------------ admin/index.php | 9 +++++++-- language/en/text.php | 12 ++++++++++-- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/admin/admin_settings.php b/admin/admin_settings.php index b34007a4..92ba9505 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -3791,12 +3791,12 @@ $modsForHesk_settings = mfh_getSettings();
      -

      LOGIN PAGE

      +

      @@ -3804,23 +3804,25 @@ $modsForHesk_settings = mfh_getSettings(); > - SOLID COLOR +
          - > + >
      - > + >
      - Login Background + <?php echo $hesklang['login_background']; ?>
      @@ -3924,7 +3969,7 @@ $modsForHesk_settings = mfh_getSettings();
      -
      @@ -3944,7 +3989,7 @@ $modsForHesk_settings = mfh_getSettings();
      -
      @@ -3963,7 +4008,7 @@ $modsForHesk_settings = mfh_getSettings();
      -
      @@ -3971,7 +4016,7 @@ $modsForHesk_settings = mfh_getSettings();
      -
      -
      @@ -4018,7 +4063,7 @@ $modsForHesk_settings = mfh_getSettings();
      -
      From cebcefe7e2678da76e04e45b8e5e2c362a87f71a Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 15 Jun 2017 22:06:13 -0400 Subject: [PATCH 175/192] Actually save the settings --- admin/admin_settings.php | 24 +++++++++--------- admin/admin_settings_save.php | 31 +++++++++++++++++++++++- install/mods-for-hesk/sql/installSql.php | 8 +----- language/en/text.php | 13 +++++++++- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/admin/admin_settings.php b/admin/admin_settings.php index bd561326..f0d3486b 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -3725,18 +3725,18 @@ $modsForHesk_settings = mfh_getSettings();
      + '; + } + function hesk_checkVersion() { From 34358be6372e00cae3cbc93112e7c019ca86cf41 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 17 Jun 2017 22:02:03 -0400 Subject: [PATCH 179/192] Add dirty forms check on add reply form --- admin/admin_ticket.php | 5 +++-- js/jquery.dirtyforms.min.js | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 js/jquery.dirtyforms.min.js diff --git a/admin/admin_ticket.php b/admin/admin_ticket.php index f67d5c8d..54bd820b 100644 --- a/admin/admin_ticket.php +++ b/admin/admin_ticket.php @@ -17,7 +17,7 @@ define('WYSIWYG', 1); define('VALIDATOR', 1); define('MFH_PAGE_LAYOUT', 'TOP_AND_SIDE'); -define('EXTRA_JS', ''); +define('EXTRA_JS', ''); /* Get all the required files and functions */ require(HESK_PATH . 'hesk_settings.inc.php'); @@ -1884,7 +1884,7 @@ function hesk_printReplyForm() $onsubmit = 'onsubmit="force_stop();return validateRichText(\'message-help-block\', \'message-group\', \'message\', \''.htmlspecialchars($hesklang['this_field_is_required']).'\')"'; } ?> -
      >
      +
      diff --git a/js/jquery.dirtyforms.min.js b/js/jquery.dirtyforms.min.js new file mode 100644 index 00000000..3fa4d184 --- /dev/null +++ b/js/jquery.dirtyforms.min.js @@ -0,0 +1,7 @@ +/*! +Dirty Forms jQuery Plugin | v2.0.0 | github.com/snikch/jquery.dirtyforms +(c) 2010-2015 Mal Curtis +License MIT +*/ +!function(e,t,i,r){e.fn.on||(e.fn.on=function(e,t,i,r){return this.delegate(t,e,i,r)}),e.fn.dirtyForms=function(t){return n[t]?n[t].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof t&&t?void e.error("Method "+t+" does not exist on jQuery.dirtyForms"):n.init.apply(this,arguments)};var n={init:function(r){var n={};return a.initialized||(e.extend(!0,e.DirtyForms,r),e(i).trigger("bind.dirtyforms",[u]),u.bind(t,i,n),a.initialized=!0),this.filter("form").not(":dirtylistening").each(function(){var t=e(this);t.find(e.DirtyForms.fieldSelector).each(function(){m(e(this))}),t.trigger("scan.dirtyforms"),u.bindForm(t,n)}),this},isDirty:function(t){var i=p(),r=e.DirtyForms.dirtyClass,n=!1;return this.each(function(o){var a=e(this),s=b(a,i);return a.hasClass(r)&&!s?(n=!0,!1):(a.find("."+r).each(function(){return b(e(this),i)?void 0:(n=!0,!1)}),n?!1:s||t||(e.each(e.DirtyForms.helpers,function(e,t){return t.isDirty&&t.isDirty(a,o)?(n=!0,!1):void 0}),!n)?void 0:!1)}),n},setClean:function(t,i){var r=function(){var t=e(this);m(t),C(t,!1)};return g(this,e.DirtyForms.fieldSelector,t).each(r).parents("form").trigger("setclean.dirtyforms",[t]),i?this:y(this,"setClean",t,p())},rescan:function(t,i){var r=function(){var t=e(this);v(t)||m(t),C(t,F(t))};return g(this,e.DirtyForms.fieldSelector,t).each(r).parents("form").trigger("rescan.dirtyforms",[t]),i?this:y(this,"rescan",t,p())}};e.extend(e.expr[":"],{dirty:function(t){var i=e(t);return i.hasClass(e.DirtyForms.dirtyClass)&&!i.is(":dirtyignored")},dirtylistening:function(t){return e(t).hasClass(e.DirtyForms.listeningClass)},dirtyignored:function(t){return b(e(t),!1)}}),e.DirtyForms={message:"You've made changes on this page which aren't saved. If you leave you will lose these changes.",dirtyClass:"dirty",listeningClass:"dirtylisten",ignoreClass:"dirtyignore",ignoreSelector:"",fieldSelector:"input:not([type='button'],[type='image'],[type='submit'],[type='reset'],[type='file'],[type='search']),select,textarea",helpers:[],dialog:!1};var o,a={initialized:!1,formStash:!1,dialogStash:!1,deciding:!1,decidingEvent:!1},s=function(e){return e.data.bindEscKey&&27==e.which||e.data.bindEnterKey&&13==e.which?f(e,!1):void 0},d=function(t){var r=t.staySelector,n=t.proceedSelector;""!==r&&e(r).unbind("click",f).click(f),""!==n&&e(n).unbind("click",l).click(l),(t.bindEscKey||t.bindEnterKey)&&e(i).unbind("keydown",s).keydown(t,s)},c=function(t,i){e.isFunction(e.DirtyForms.dialog.close)&&e.DirtyForms.dialog.close(t,i)},l=function(e){return f(e,!0)},f=function(t,r){if(a.deciding){if(t.preventDefault(),r===!0){var n=a.decidingEvent;e(i).trigger("proceed.dirtyforms",[n]),u.clearUnload(),c(r,!1),E(n)}else{e(i).trigger("stay.dirtyforms");var o=e.DirtyForms.dialog!==!1&&a.dialogStash!==!1&&e.isFunction(e.DirtyForms.dialog.unstash);c(r,o),o&&e.DirtyForms.dialog.unstash(a.dialogStash,t),e(i).trigger("afterstay.dirtyforms")}return a.deciding=a.decidingEvent=a.dialogStash=a.formStash=!1,!1}},u={bind:function(t,i,r){e(t).bind("beforeunload",r,u.onBeforeUnload),e(i).on("click",'a:not([target="_blank"])',r,u.onAnchorClick).on("submit","form",r,u.onSubmit)},bindForm:function(t,r){var n=e.DirtyForms,o="onpropertychange"in i.createElement("input"),a="change input"+(o?" keyup selectionchange cut paste":"");t.addClass(n.listeningClass).on("focus keydown",n.fieldSelector,r,u.onFocus).on(a,n.fieldSelector,r,u.onFieldChange).bind("reset",r,u.onReset)},onFocus:function(t){var i=e(t.target);v(i)||m(i)},onFieldChange:function(t){var i=e(t.target);"change"!==t.type?S(function(){D(i)},100):D(i)},onReset:function(t){var i=e(t.target).closest("form");setTimeout(function(){i.dirtyForms("setClean")},100)},onAnchorClick:function(e){k(e)},onSubmit:function(e){k(e)},onBeforeUnload:function(e){var t=k(e);return t&&a.doubleunloadfix!==!0&&(a.deciding=!1),a.doubleunloadfix=!0,setTimeout(function(){a.doubleunloadfix=!1},200),"string"==typeof t?(e.returnValue=t,t):void 0},onRefireClick:function(t){var i=new e.Event("click");e(t.target).trigger(i),i.isDefaultPrevented()||u.onRefireAnchorClick(t)},onRefireAnchorClick:function(i){var n=e(i.target).closest("a[href]").attr("href");n!==r&&(t.location.href=n)},clearUnload:function(){e(t).unbind("beforeunload",u.onBeforeUnload),t.onbeforeunload=null,e(i).trigger("beforeunload.dirtyforms")}},g=function(e,t,i){var r=e.filter(t).add(e.find(t));return i&&(r=r.not(":dirtyignored")),r},y=function(t,i,r,n){return t.each(function(t){var o=e(this);r&&b(o,n)||e.each(e.DirtyForms.helpers,function(e,n){n[i]&&n[i](o,t,r)})})},h=function(t){var i;return t.is("select")?(i="",t.find("option").each(function(){var t=e(this);t.is(":selected")&&(i.length>0&&(i+=","),i+=t.val())})):i=t.is(":checkbox,:radio")?t.is(":checked"):t.val(),i},m=function(e){e.data("df-orig",h(e));var t=e.data("df-orig")===r;e.data("df-empty",t)},v=function(e){return e.data("df-orig")!==r||e.data("df-empty")===!0},p=function(){var t=e.DirtyForms,i=t.ignoreSelector;return e.each(t.helpers,function(e,t){"ignoreSelector"in t&&(i.length>0&&(i+=","),i+=t.ignoreSelector)}),i},b=function(t,i){return i||(i=p()),t.is(i)||t.closest("."+e.DirtyForms.ignoreClass).length>0},F=function(e,t){return!v(e)||b(e,t)?!1:h(e)!=e.data("df-orig")},D=function(t,i){if(!b(t,i))if(t.is(":radio[name]")){var r=t.attr("name"),n=t.parents("form");n.find(":radio[name='"+r+"']").each(function(){var t=e(this);C(t,F(t,i))})}else C(t,F(t,i))},C=function(t,i){var r=e.DirtyForms.dirtyClass,n=t.parents("form");t.toggleClass(r,i);var o=i!==(n.hasClass(r)&&0===n.find(":dirty").length);o&&(n.toggleClass(r,i),i&&n.trigger("dirty.dirtyforms"),i||n.trigger("clean.dirtyforms"))},S=function(){var e=0;return function(t,i){clearTimeout(e),e=setTimeout(t,i)}}(),k=function(t){var r=e(t.target),n=t.type,s=e.DirtyForms;if(t.isDefaultPrevented())return!1;if("beforeunload"==n&&a.doubleunloadfix)return a.doubleunloadfix=!1,!1;if(r.is(":dirtyignored"))return u.clearUnload(),!1;if(a.deciding)return!1;if(!e("form:dirtylistening").dirtyForms("isDirty"))return u.clearUnload(),!1;if("submit"==n&&r.dirtyForms("isDirty"))return u.clearUnload(),!0;if(e(i).trigger("defer.dirtyforms"),"beforeunload"==n)return s.message;if(s.dialog){t.preventDefault(),t.stopImmediatePropagation(),a.deciding=!0,a.decidingEvent=t,e.isFunction(s.dialog.stash)&&(a.dialogStash=s.dialog.stash());var c=s.dialog.stashSelector;"string"==typeof c&&r.is("form")&&r.parents(c).length>0?a.formStash=r.clone(!0).hide():a.formStash=!1,o={proceed:!1,commit:function(e){return f(e,o.proceed)},bindEscKey:!0,bindEnterKey:!1,proceedSelector:"",staySelector:""},s.dialog.open(o,s.message,s.ignoreClass),d(o)}},E=function(t){if("click"===t.type)u.onRefireClick(t);else{var i;a.formStash?(i=a.formStash,e("body").append(i)):i=e(t.target).closest("form"),i.trigger(t.type)}}}(jQuery,window,document); +//# sourceMappingURL=jquery.dirtyforms.min.js.map From f6283a4c139a551c972928f3e7ec28c89169234f Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 18 Jun 2017 21:21:26 -0400 Subject: [PATCH 180/192] Fix the API settings page, update validator, fix sidebar text hover --- admin/api_settings.php | 4 +-- install/mods-for-hesk/database-validation.php | 25 +++++++++++++++++++ install/mods-for-hesk/sql/installSql.php | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/admin/api_settings.php b/admin/api_settings.php index 85c654e0..4a1e8f4d 100644 --- a/admin/api_settings.php +++ b/admin/api_settings.php @@ -3,7 +3,7 @@ define('IN_SCRIPT', 1); define('HESK_PATH', '../'); define('PAGE_TITLE', 'ADMIN_SETTINGS'); -define('MFH_PAGE_LAYOUT', 'TOP_AND_SIDE'); +define('MFH_PAGE_LAYOUT', 'TOP_ONLY'); // Make sure the install folder is deleted if (is_dir(HESK_PATH . 'install')) { @@ -92,7 +92,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
      diff --git a/install/mods-for-hesk/database-validation.php b/install/mods-for-hesk/database-validation.php index 1794874b..05f96b69 100644 --- a/install/mods-for-hesk/database-validation.php +++ b/install/mods-for-hesk/database-validation.php @@ -184,6 +184,21 @@ hesk_dbConnect(); $all_good = $all_good & run_column_check('custom_nav_element_to_text', 'language'); $all_good = $all_good & run_column_check('custom_nav_element_to_text', 'text'); $all_good = $all_good & run_column_check('custom_nav_element_to_text', 'subtext'); + $all_good = $all_good & run_setting_check('admin_navbar_background'); + $all_good = $all_good & run_setting_check('admin_navbar_background_hover'); + $all_good = $all_good & run_setting_check('admin_navbar_text'); + $all_good = $all_good & run_setting_check('admin_navbar_text_hover'); + $all_good = $all_good & run_setting_check('admin_navbar_brand_background'); + $all_good = $all_good & run_setting_check('admin_navbar_brand_background_hover'); + $all_good = $all_good & run_setting_check('admin_navbar_brand_text'); + $all_good = $all_good & run_setting_check('admin_navbar_brand_text_hover'); + $all_good = $all_good & run_setting_check('admin_sidebar_background'); + $all_good = $all_good & run_setting_check('admin_sidebar_background_hover'); + $all_good = $all_good & run_setting_check('admin_sidebar_text'); + $all_good = $all_good & run_setting_check('admin_sidebar_text_hover'); + $all_good = $all_good & run_setting_check('admin_sidebar_font_weight'); + $all_good = $all_good & run_setting_check('admin_sidebar_header_background'); + $all_good = $all_good & run_setting_check('admin_sidebar_header_text'); if ($all_good) { echo ""; @@ -198,6 +213,16 @@ hesk_dbConnect(); Setting Exists: ' . $setting_name, $all_good); + + return $all_good !== false; +} + function run_table_check($table_name) { return run_column_check($table_name, '1'); } diff --git a/install/mods-for-hesk/sql/installSql.php b/install/mods-for-hesk/sql/installSql.php index 53080c41..e0e8bb90 100644 --- a/install/mods-for-hesk/sql/installSql.php +++ b/install/mods-for-hesk/sql/installSql.php @@ -1040,7 +1040,7 @@ function execute310Scripts() { $sidebar = array( 'background' => $light_theme ? '#f9fafc' : '#222d32', 'text' => $light_theme ? '#444' : '#b8c7ce', - 'text_hover' => $light_theme ? '#444' : '#b8c7ce', + 'text_hover' => $light_theme ? '#444' : '#fff', 'background_hover' => $light_theme ? '#f4f4f5' : '#1e282c', 'font_weight' => $light_theme ? 'bold' : 'normal' ); From c39e4462e8e162ca28c715f0342feaca4e5781d8 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 18 Jun 2017 21:31:48 -0400 Subject: [PATCH 181/192] Add nav element for API settings --- admin/api_settings.php | 2 +- inc/show_admin_nav.inc.php | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/admin/api_settings.php b/admin/api_settings.php index 4a1e8f4d..0af4b1b6 100644 --- a/admin/api_settings.php +++ b/admin/api_settings.php @@ -2,7 +2,7 @@ define('IN_SCRIPT', 1); define('HESK_PATH', '../'); -define('PAGE_TITLE', 'ADMIN_SETTINGS'); +define('PAGE_TITLE', 'ADMIN_API_SETTINGS'); define('MFH_PAGE_LAYOUT', 'TOP_ONLY'); // Make sure the install folder is deleted diff --git a/inc/show_admin_nav.inc.php b/inc/show_admin_nav.inc.php index e4e140cc..dce2c557 100644 --- a/inc/show_admin_nav.inc.php +++ b/inc/show_admin_nav.inc.php @@ -473,7 +473,7 @@ $mails = mfh_get_mail_headers_for_dropdown($_SESSION['id'], $hesk_settings, $hes '; } if (hesk_checkPermission('can_man_settings', 0)) { - $number_of_settings++; + $number_of_settings += 2; $active = ''; if (defined('PAGE_TITLE') && PAGE_TITLE == 'ADMIN_SETTINGS') { $active = 'active'; @@ -486,6 +486,19 @@ $mails = mfh_get_mail_headers_for_dropdown($_SESSION['id'], $hesk_settings, $hes ' . $hesklang['helpdesk_settings'] . ' '; + + $active = ''; + if (defined('PAGE_TITLE') && PAGE_TITLE == 'ADMIN_API_SETTINGS') { + $active = 'active'; + } + + $markup .= ' +
    • + + + ' . $hesklang['api_settings'] . ' + +
    • '; } if ($number_of_settings == 1) : From f5933e8bfa1c24d492e6fca83c4eeffcd32ea7f9 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 18 Jun 2017 22:03:35 -0400 Subject: [PATCH 182/192] update installer, fix colorpicker presets --- admin/admin_settings.php | 68 +- css/skins/_all-skins.min.css | 1 - hesk_style_RTL.css | 758 ------------------ inc/headerAdmin.inc.php | 1 - install/install_functions.inc.php | 2 +- .../ajax/install-database-ajax.php | 2 + install/mods-for-hesk/installModsForHesk.php | 1 + install/mods-for-hesk/js/version-scripts.js | 3 + install/mods-for-hesk/modsForHesk.php | 1 + 9 files changed, 42 insertions(+), 795 deletions(-) delete mode 100755 css/skins/_all-skins.min.css delete mode 100644 hesk_style_RTL.css diff --git a/admin/admin_settings.php b/admin/admin_settings.php index 581b0a37..6f686d97 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -3629,55 +3629,55 @@ $modsForHesk_settings = mfh_getSettings(); var lightTheme = val.match(/.+-light/i); - $('input[name="admin-sidebar-background-color"]').val(lightTheme ? '#f9fafc' : '#222d32'); - $('input[name="admin-sidebar-header-background-color"]').val(lightTheme ? '#f9fafc' : '#1a2226'); - $('input[name="admin-sidebar-text-color"]').val(lightTheme ? '#444' : '#b8c7ce'); - $('input[name="admin-sidebar-header-text-color"]').val(lightTheme ? '#848484' : '#4b646f'); - $('input[name="admin-sidebar-text-hover-color"]').val(lightTheme ? '#444' : '#fff'); - $('input[name="admin-sidebar-background-hover-color"]').val(lightTheme ? '#f4f4f5' : '#1e282c'); + $('#cpadmin-sidebar-background-color').colorpicker('setValue', lightTheme ? '#f9fafc' : '#222d32'); + $('#cpadmin-sidebar-header-background-color').colorpicker('setValue', lightTheme ? '#f9fafc' : '#1a2226'); + $('#cpadmin-sidebar-text-color').colorpicker('setValue', lightTheme ? '#444' : '#b8c7ce'); + $('#cpadmin-sidebar-header-text-color').colorpicker('setValue', lightTheme ? '#848484' : '#4b646f'); + $('#cpadmin-sidebar-text-hover-color').colorpicker('setValue', lightTheme ? '#444' : '#fff'); + $('#cpadmin-sidebar-background-hover-color').colorpicker('setValue', lightTheme ? '#f4f4f5' : '#1e282c'); $('input[name="admin-sidebar-font-weight"]').val(lightTheme ? ['bold'] : ['normal']); - $('input[name="admin-navbar-text-color"]').val('#fff'); - $('input[name="admin-navbar-text-hover-color"]').val('#fff'); - $('input[name="admin-navbar-brand-text-color"]').val('#fff'); - $('input[name="admin-navbar-brand-text-hover-color"]').val('#fff'); + $('#cpadmin-navbar-text-color').colorpicker('setValue', '#fff'); + $('#cpadmin-navbar-text-hover-color').colorpicker('setValue', '#fff'); + $('#cpadmin-navbar-brand-text-color').colorpicker('setValue', '#fff'); + $('#cpadmin-navbar-brand-text-hover-color').colorpicker('setValue', '#fff'); if (val.match(/blue.*/i)) { - $('input[name="admin-navbar-background-color"]').val('#3c8dbc'); - $('input[name="admin-navbar-background-hover-color"]').val('#367fa9'); + $('#cpadmin-navbar-background-color').colorpicker('setValue', '#3c8dbc'); + $('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#367fa9'); - $('input[name="admin-navbar-brand-background-color"]').val(lightTheme ? '#3c8dbc' : '#367fa9'); - $('input[name="admin-navbar-brand-background-hover-color"]').val(lightTheme ? '#3b8ab8' : '#357ca5'); + $('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#3c8dbc' : '#367fa9'); + $('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#3b8ab8' : '#357ca5'); } else if (val.match(/yellow.*/i)) { - $('input[name="admin-navbar-background-color"]').val('#f39c12'); - $('input[name="admin-navbar-background-hover-color"]').val('#da8c10'); + $('#cpadmin-navbar-background-color').colorpicker('setValue', '#f39c12'); + $('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#da8c10'); - $('input[name="admin-navbar-brand-background-color"]').val(lightTheme ? '#f39c12' : '#e08e0b'); - $('input[name="admin-navbar-brand-background-hover-color"]').val(lightTheme ? '#f39a0d' : '#db8b0b'); + $('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#f39c12' : '#e08e0b'); + $('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#f39a0d' : '#db8b0b'); } else if (val.match(/green.*/i)) { - $('input[name="admin-navbar-background-color"]').val('#00a65a'); - $('input[name="admin-navbar-background-hover-color"]').val('#009551'); + $('#cpadmin-navbar-background-color').colorpicker('setValue', '#00a65a'); + $('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#009551'); - $('input[name="admin-navbar-brand-background-color"]').val(lightTheme ? '#00a65a' : '#008d4c'); - $('input[name="admin-navbar-brand-background-hover-color"]').val(lightTheme ? '#00a157' : '#008749'); + $('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#00a65a' : '#008d4c'); + $('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#00a157' : '#008749'); } else if (val.match(/purple.*/i)) { - $('input[name="admin-navbar-background-color"]').val('#605ca8'); - $('input[name="admin-navbar-background-hover-color"]').val('#565397'); + $('#cpadmin-navbar-background-color').colorpicker('setValue', '#605ca8'); + $('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#565397'); - $('input[name="admin-navbar-brand-background-color"]').val(lightTheme ? '#605ca8' : '#555299'); - $('input[name="admin-navbar-brand-background-hover-color"]').val(lightTheme ? '#5d59a6' : '#545096'); + $('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#605ca8' : '#555299'); + $('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#5d59a6' : '#545096'); } else if (val.match(/red.*/i)) { - $('input[name="admin-navbar-background-color"]').val('#dd4b39'); - $('input[name="admin-navbar-background-hover-color"]').val('#c64333'); + $('#cpadmin-navbar-background-color').colorpicker('setValue', '#dd4b39'); + $('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#c64333'); - $('input[name="admin-navbar-brand-background-color"]').val(lightTheme ? '#dd4b39' : '#d73925'); - $('input[name="admin-navbar-brand-background-hover-color"]').val(lightTheme ? '#dc4735' : '#d33724'); + $('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#dd4b39' : '#d73925'); + $('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#dc4735' : '#d33724'); } else { //-- Black - $('input[name="admin-navbar-background-color"]').val('#fff'); - $('input[name="admin-navbar-background-hover-color"]').val('#eee'); + $('#cpadmin-navbar-background-color').colorpicker('setValue', '#fff'); + $('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#eee'); - $('input[name="admin-navbar-brand-background-color"]').val('#fff'); - $('input[name="admin-navbar-brand-background-hover-color"]').val('#fcfcfc'); + $('#cpadmin-navbar-brand-background-color').colorpicker('setValue', '#fff'); + $('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', '#fcfcfc'); } }); diff --git a/css/skins/_all-skins.min.css b/css/skins/_all-skins.min.css deleted file mode 100755 index e1d2398f..00000000 --- a/css/skins/_all-skins.min.css +++ /dev/null @@ -1 +0,0 @@ -.skin-blue .main-header .navbar{background-color:#3c8dbc}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue .main-header .logo{background-color:#367fa9;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#357ca5}.skin-blue .main-header li.user-header{background-color:#3c8dbc}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#222d32}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#3c8dbc}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-blue .sidebar a{color:#b8c7ce}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .treeview-menu>li>a{color:#8aa4af}.skin-blue .treeview-menu>li.active>a,.skin-blue .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header .navbar{background-color:#3c8dbc}.skin-blue-light .main-header .navbar .nav>li>a{color:#fff}.skin-blue-light .main-header .navbar .nav>li>a:hover,.skin-blue-light .main-header .navbar .nav>li>a:active,.skin-blue-light .main-header .navbar .nav>li>a:focus,.skin-blue-light .main-header .navbar .nav .open>a,.skin-blue-light .main-header .navbar .nav .open>a:hover,.skin-blue-light .main-header .navbar .nav .open>a:focus,.skin-blue-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue-light .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue-light .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue-light .main-header .logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header li.user-header{background-color:#3c8dbc}.skin-blue-light .content-header{background:transparent}.skin-blue-light .wrapper,.skin-blue-light .main-sidebar,.skin-blue-light .left-side{background-color:#f9fafc}.skin-blue-light .content-wrapper,.skin-blue-light .main-footer{border-left:1px solid #d2d6de}.skin-blue-light .user-panel>.info,.skin-blue-light .user-panel>.info>a{color:#444}.skin-blue-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-blue-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-blue-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-blue-light .sidebar-menu>li:hover>a,.skin-blue-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-blue-light .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-blue-light .sidebar-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-blue-light .sidebar a{color:#444}.skin-blue-light .sidebar a:hover{text-decoration:none}.skin-blue-light .treeview-menu>li>a{color:#777}.skin-blue-light .treeview-menu>li.active>a,.skin-blue-light .treeview-menu>li>a:hover{color:#000}.skin-blue-light .treeview-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-blue-light .sidebar-form input[type="text"],.skin-blue-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-blue-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue-light .sidebar-form input[type="text"]:focus,.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-blue-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-blue-light .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}.skin-black .main-header{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.skin-black .main-header .navbar-toggle{color:#333}.skin-black .main-header .navbar-brand{color:#333;border-right:1px solid #eee}.skin-black .main-header .navbar{background-color:#fff}.skin-black .main-header .navbar .nav>li>a{color:#333}.skin-black .main-header .navbar .nav>li>a:hover,.skin-black .main-header .navbar .nav>li>a:active,.skin-black .main-header .navbar .nav>li>a:focus,.skin-black .main-header .navbar .nav .open>a,.skin-black .main-header .navbar .nav .open>a:hover,.skin-black .main-header .navbar .nav .open>a:focus,.skin-black .main-header .navbar .nav>.active>a{background:#fff;color:#999}.skin-black .main-header .navbar .sidebar-toggle{color:#333}.skin-black .main-header .navbar .sidebar-toggle:hover{color:#999;background:#fff}.skin-black .main-header .navbar>.sidebar-toggle{color:#333;border-right:1px solid #eee}.skin-black .main-header .navbar .navbar-nav>li>a{border-right:1px solid #eee}.skin-black .main-header .navbar .navbar-custom-menu .navbar-nav>li>a,.skin-black .main-header .navbar .navbar-right>li>a{border-left:1px solid #eee;border-right-width:0}.skin-black .main-header>.logo{background-color:#fff;color:#333;border-bottom:0 solid transparent;border-right:1px solid #eee}.skin-black .main-header>.logo:hover{background-color:#fcfcfc}@media (max-width:767px){.skin-black .main-header>.logo{background-color:#222;color:#fff;border-bottom:0 solid transparent;border-right:none}.skin-black .main-header>.logo:hover{background-color:#1f1f1f}}.skin-black .main-header li.user-header{background-color:#222}.skin-black .content-header{background:transparent;box-shadow:none}.skin-black .wrapper,.skin-black .main-sidebar,.skin-black .left-side{background-color:#222d32}.skin-black .user-panel>.info,.skin-black .user-panel>.info>a{color:#fff}.skin-black .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-black .sidebar-menu>li>a{border-left:3px solid transparent}.skin-black .sidebar-menu>li:hover>a,.skin-black .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#fff}.skin-black .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-black .sidebar a{color:#b8c7ce}.skin-black .sidebar a:hover{text-decoration:none}.skin-black .treeview-menu>li>a{color:#8aa4af}.skin-black .treeview-menu>li.active>a,.skin-black .treeview-menu>li>a:hover{color:#fff}.skin-black .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-black .sidebar-form input[type="text"],.skin-black .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-black .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black .sidebar-form input[type="text"]:focus,.skin-black .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-black .pace .pace-progress{background:#222}.skin-black .pace .pace-activity{border-top-color:#222;border-left-color:#222}.skin-black-light .main-header{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.skin-black-light .main-header .navbar-toggle{color:#333}.skin-black-light .main-header .navbar-brand{color:#333;border-right:1px solid #eee}.skin-black-light .main-header .navbar{background-color:#fff}.skin-black-light .main-header .navbar .nav>li>a{color:#333}.skin-black-light .main-header .navbar .nav>li>a:hover,.skin-black-light .main-header .navbar .nav>li>a:active,.skin-black-light .main-header .navbar .nav>li>a:focus,.skin-black-light .main-header .navbar .nav .open>a,.skin-black-light .main-header .navbar .nav .open>a:hover,.skin-black-light .main-header .navbar .nav .open>a:focus,.skin-black-light .main-header .navbar .nav>.active>a{background:#fff;color:#999}.skin-black-light .main-header .navbar .sidebar-toggle{color:#333}.skin-black-light .main-header .navbar .sidebar-toggle:hover{color:#999;background:#fff}.skin-black-light .main-header .navbar>.sidebar-toggle{color:#333;border-right:1px solid #eee}.skin-black-light .main-header .navbar .navbar-nav>li>a{border-right:1px solid #eee}.skin-black-light .main-header .navbar .navbar-custom-menu .navbar-nav>li>a,.skin-black-light .main-header .navbar .navbar-right>li>a{border-left:1px solid #eee;border-right-width:0}.skin-black-light .main-header>.logo{background-color:#fff;color:#333;border-bottom:0 solid transparent;border-right:1px solid #eee}.skin-black-light .main-header>.logo:hover{background-color:#fcfcfc}@media (max-width:767px){.skin-black-light .main-header>.logo{background-color:#222;color:#fff;border-bottom:0 solid transparent;border-right:none}.skin-black-light .main-header>.logo:hover{background-color:#1f1f1f}}.skin-black-light .main-header li.user-header{background-color:#222}.skin-black-light .content-header{background:transparent;box-shadow:none}.skin-black-light .wrapper,.skin-black-light .main-sidebar,.skin-black-light .left-side{background-color:#f9fafc}.skin-black-light .content-wrapper,.skin-black-light .main-footer{border-left:1px solid #d2d6de}.skin-black-light .user-panel>.info,.skin-black-light .user-panel>.info>a{color:#444}.skin-black-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-black-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-black-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-black-light .sidebar-menu>li:hover>a,.skin-black-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-black-light .sidebar-menu>li.active{border-left-color:#fff}.skin-black-light .sidebar-menu>li.active>a{font-weight:600}.skin-black-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-black-light .sidebar a{color:#444}.skin-black-light .sidebar a:hover{text-decoration:none}.skin-black-light .treeview-menu>li>a{color:#777}.skin-black-light .treeview-menu>li.active>a,.skin-black-light .treeview-menu>li>a:hover{color:#000}.skin-black-light .treeview-menu>li.active>a{font-weight:600}.skin-black-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-black-light .sidebar-form input[type="text"],.skin-black-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-black-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black-light .sidebar-form input[type="text"]:focus,.skin-black-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-black-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-green .main-header .navbar{background-color:#00a65a}.skin-green .main-header .navbar .nav>li>a{color:#fff}.skin-green .main-header .navbar .nav>li>a:hover,.skin-green .main-header .navbar .nav>li>a:active,.skin-green .main-header .navbar .nav>li>a:focus,.skin-green .main-header .navbar .nav .open>a,.skin-green .main-header .navbar .nav .open>a:hover,.skin-green .main-header .navbar .nav .open>a:focus,.skin-green .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-green .main-header .navbar .sidebar-toggle{color:#fff}.skin-green .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-green .main-header .navbar .sidebar-toggle{color:#fff}.skin-green .main-header .navbar .sidebar-toggle:hover{background-color:#008d4c}@media (max-width:767px){.skin-green .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-green .main-header .navbar .dropdown-menu li a{color:#fff}.skin-green .main-header .navbar .dropdown-menu li a:hover{background:#008d4c}}.skin-green .main-header .logo{background-color:#008d4c;color:#fff;border-bottom:0 solid transparent}.skin-green .main-header .logo:hover{background-color:#008749}.skin-green .main-header li.user-header{background-color:#00a65a}.skin-green .content-header{background:transparent}.skin-green .wrapper,.skin-green .main-sidebar,.skin-green .left-side{background-color:#222d32}.skin-green .user-panel>.info,.skin-green .user-panel>.info>a{color:#fff}.skin-green .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-green .sidebar-menu>li>a{border-left:3px solid transparent}.skin-green .sidebar-menu>li:hover>a,.skin-green .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#00a65a}.skin-green .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-green .sidebar a{color:#b8c7ce}.skin-green .sidebar a:hover{text-decoration:none}.skin-green .treeview-menu>li>a{color:#8aa4af}.skin-green .treeview-menu>li.active>a,.skin-green .treeview-menu>li>a:hover{color:#fff}.skin-green .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-green .sidebar-form input[type="text"],.skin-green .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-green .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-green .sidebar-form input[type="text"]:focus,.skin-green .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-green .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-green .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-green-light .main-header .navbar{background-color:#00a65a}.skin-green-light .main-header .navbar .nav>li>a{color:#fff}.skin-green-light .main-header .navbar .nav>li>a:hover,.skin-green-light .main-header .navbar .nav>li>a:active,.skin-green-light .main-header .navbar .nav>li>a:focus,.skin-green-light .main-header .navbar .nav .open>a,.skin-green-light .main-header .navbar .nav .open>a:hover,.skin-green-light .main-header .navbar .nav .open>a:focus,.skin-green-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-green-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-green-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-green-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-green-light .main-header .navbar .sidebar-toggle:hover{background-color:#008d4c}@media (max-width:767px){.skin-green-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-green-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-green-light .main-header .navbar .dropdown-menu li a:hover{background:#008d4c}}.skin-green-light .main-header .logo{background-color:#00a65a;color:#fff;border-bottom:0 solid transparent}.skin-green-light .main-header .logo:hover{background-color:#00a157}.skin-green-light .main-header li.user-header{background-color:#00a65a}.skin-green-light .content-header{background:transparent}.skin-green-light .wrapper,.skin-green-light .main-sidebar,.skin-green-light .left-side{background-color:#f9fafc}.skin-green-light .content-wrapper,.skin-green-light .main-footer{border-left:1px solid #d2d6de}.skin-green-light .user-panel>.info,.skin-green-light .user-panel>.info>a{color:#444}.skin-green-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-green-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-green-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-green-light .sidebar-menu>li:hover>a,.skin-green-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-green-light .sidebar-menu>li.active{border-left-color:#00a65a}.skin-green-light .sidebar-menu>li.active>a{font-weight:600}.skin-green-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-green-light .sidebar a{color:#444}.skin-green-light .sidebar a:hover{text-decoration:none}.skin-green-light .treeview-menu>li>a{color:#777}.skin-green-light .treeview-menu>li.active>a,.skin-green-light .treeview-menu>li>a:hover{color:#000}.skin-green-light .treeview-menu>li.active>a{font-weight:600}.skin-green-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-green-light .sidebar-form input[type="text"],.skin-green-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-green-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-green-light .sidebar-form input[type="text"]:focus,.skin-green-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-green-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-green-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-green-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-red .main-header .navbar{background-color:#dd4b39}.skin-red .main-header .navbar .nav>li>a{color:#fff}.skin-red .main-header .navbar .nav>li>a:hover,.skin-red .main-header .navbar .nav>li>a:active,.skin-red .main-header .navbar .nav>li>a:focus,.skin-red .main-header .navbar .nav .open>a,.skin-red .main-header .navbar .nav .open>a:hover,.skin-red .main-header .navbar .nav .open>a:focus,.skin-red .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-red .main-header .navbar .sidebar-toggle{color:#fff}.skin-red .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-red .main-header .navbar .sidebar-toggle{color:#fff}.skin-red .main-header .navbar .sidebar-toggle:hover{background-color:#d73925}@media (max-width:767px){.skin-red .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-red .main-header .navbar .dropdown-menu li a{color:#fff}.skin-red .main-header .navbar .dropdown-menu li a:hover{background:#d73925}}.skin-red .main-header .logo{background-color:#d73925;color:#fff;border-bottom:0 solid transparent}.skin-red .main-header .logo:hover{background-color:#d33724}.skin-red .main-header li.user-header{background-color:#dd4b39}.skin-red .content-header{background:transparent}.skin-red .wrapper,.skin-red .main-sidebar,.skin-red .left-side{background-color:#222d32}.skin-red .user-panel>.info,.skin-red .user-panel>.info>a{color:#fff}.skin-red .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-red .sidebar-menu>li>a{border-left:3px solid transparent}.skin-red .sidebar-menu>li:hover>a,.skin-red .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#dd4b39}.skin-red .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-red .sidebar a{color:#b8c7ce}.skin-red .sidebar a:hover{text-decoration:none}.skin-red .treeview-menu>li>a{color:#8aa4af}.skin-red .treeview-menu>li.active>a,.skin-red .treeview-menu>li>a:hover{color:#fff}.skin-red .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-red .sidebar-form input[type="text"],.skin-red .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-red .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-red .sidebar-form input[type="text"]:focus,.skin-red .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-red .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-red .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-red-light .main-header .navbar{background-color:#dd4b39}.skin-red-light .main-header .navbar .nav>li>a{color:#fff}.skin-red-light .main-header .navbar .nav>li>a:hover,.skin-red-light .main-header .navbar .nav>li>a:active,.skin-red-light .main-header .navbar .nav>li>a:focus,.skin-red-light .main-header .navbar .nav .open>a,.skin-red-light .main-header .navbar .nav .open>a:hover,.skin-red-light .main-header .navbar .nav .open>a:focus,.skin-red-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-red-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-red-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-red-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-red-light .main-header .navbar .sidebar-toggle:hover{background-color:#d73925}@media (max-width:767px){.skin-red-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-red-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-red-light .main-header .navbar .dropdown-menu li a:hover{background:#d73925}}.skin-red-light .main-header .logo{background-color:#dd4b39;color:#fff;border-bottom:0 solid transparent}.skin-red-light .main-header .logo:hover{background-color:#dc4735}.skin-red-light .main-header li.user-header{background-color:#dd4b39}.skin-red-light .content-header{background:transparent}.skin-red-light .wrapper,.skin-red-light .main-sidebar,.skin-red-light .left-side{background-color:#f9fafc}.skin-red-light .content-wrapper,.skin-red-light .main-footer{border-left:1px solid #d2d6de}.skin-red-light .user-panel>.info,.skin-red-light .user-panel>.info>a{color:#444}.skin-red-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-red-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-red-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-red-light .sidebar-menu>li:hover>a,.skin-red-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-red-light .sidebar-menu>li.active{border-left-color:#dd4b39}.skin-red-light .sidebar-menu>li.active>a{font-weight:600}.skin-red-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-red-light .sidebar a{color:#444}.skin-red-light .sidebar a:hover{text-decoration:none}.skin-red-light .treeview-menu>li>a{color:#777}.skin-red-light .treeview-menu>li.active>a,.skin-red-light .treeview-menu>li>a:hover{color:#000}.skin-red-light .treeview-menu>li.active>a{font-weight:600}.skin-red-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-red-light .sidebar-form input[type="text"],.skin-red-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-red-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-red-light .sidebar-form input[type="text"]:focus,.skin-red-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-red-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-red-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-red-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-yellow .main-header .navbar{background-color:#f39c12}.skin-yellow .main-header .navbar .nav>li>a{color:#fff}.skin-yellow .main-header .navbar .nav>li>a:hover,.skin-yellow .main-header .navbar .nav>li>a:active,.skin-yellow .main-header .navbar .nav>li>a:focus,.skin-yellow .main-header .navbar .nav .open>a,.skin-yellow .main-header .navbar .nav .open>a:hover,.skin-yellow .main-header .navbar .nav .open>a:focus,.skin-yellow .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-yellow .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-yellow .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow .main-header .navbar .sidebar-toggle:hover{background-color:#e08e0b}@media (max-width:767px){.skin-yellow .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-yellow .main-header .navbar .dropdown-menu li a{color:#fff}.skin-yellow .main-header .navbar .dropdown-menu li a:hover{background:#e08e0b}}.skin-yellow .main-header .logo{background-color:#e08e0b;color:#fff;border-bottom:0 solid transparent}.skin-yellow .main-header .logo:hover{background-color:#db8b0b}.skin-yellow .main-header li.user-header{background-color:#f39c12}.skin-yellow .content-header{background:transparent}.skin-yellow .wrapper,.skin-yellow .main-sidebar,.skin-yellow .left-side{background-color:#222d32}.skin-yellow .user-panel>.info,.skin-yellow .user-panel>.info>a{color:#fff}.skin-yellow .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-yellow .sidebar-menu>li>a{border-left:3px solid transparent}.skin-yellow .sidebar-menu>li:hover>a,.skin-yellow .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#f39c12}.skin-yellow .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-yellow .sidebar a{color:#b8c7ce}.skin-yellow .sidebar a:hover{text-decoration:none}.skin-yellow .treeview-menu>li>a{color:#8aa4af}.skin-yellow .treeview-menu>li.active>a,.skin-yellow .treeview-menu>li>a:hover{color:#fff}.skin-yellow .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-yellow .sidebar-form input[type="text"],.skin-yellow .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-yellow .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-yellow .sidebar-form input[type="text"]:focus,.skin-yellow .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-yellow .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-yellow .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-yellow-light .main-header .navbar{background-color:#f39c12}.skin-yellow-light .main-header .navbar .nav>li>a{color:#fff}.skin-yellow-light .main-header .navbar .nav>li>a:hover,.skin-yellow-light .main-header .navbar .nav>li>a:active,.skin-yellow-light .main-header .navbar .nav>li>a:focus,.skin-yellow-light .main-header .navbar .nav .open>a,.skin-yellow-light .main-header .navbar .nav .open>a:hover,.skin-yellow-light .main-header .navbar .nav .open>a:focus,.skin-yellow-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-yellow-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-yellow-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow-light .main-header .navbar .sidebar-toggle:hover{background-color:#e08e0b}@media (max-width:767px){.skin-yellow-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-yellow-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-yellow-light .main-header .navbar .dropdown-menu li a:hover{background:#e08e0b}}.skin-yellow-light .main-header .logo{background-color:#f39c12;color:#fff;border-bottom:0 solid transparent}.skin-yellow-light .main-header .logo:hover{background-color:#f39a0d}.skin-yellow-light .main-header li.user-header{background-color:#f39c12}.skin-yellow-light .content-header{background:transparent}.skin-yellow-light .wrapper,.skin-yellow-light .main-sidebar,.skin-yellow-light .left-side{background-color:#f9fafc}.skin-yellow-light .content-wrapper,.skin-yellow-light .main-footer{border-left:1px solid #d2d6de}.skin-yellow-light .user-panel>.info,.skin-yellow-light .user-panel>.info>a{color:#444}.skin-yellow-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-yellow-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-yellow-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-yellow-light .sidebar-menu>li:hover>a,.skin-yellow-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-yellow-light .sidebar-menu>li.active{border-left-color:#f39c12}.skin-yellow-light .sidebar-menu>li.active>a{font-weight:600}.skin-yellow-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-yellow-light .sidebar a{color:#444}.skin-yellow-light .sidebar a:hover{text-decoration:none}.skin-yellow-light .treeview-menu>li>a{color:#777}.skin-yellow-light .treeview-menu>li.active>a,.skin-yellow-light .treeview-menu>li>a:hover{color:#000}.skin-yellow-light .treeview-menu>li.active>a{font-weight:600}.skin-yellow-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-yellow-light .sidebar-form input[type="text"],.skin-yellow-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-yellow-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-yellow-light .sidebar-form input[type="text"]:focus,.skin-yellow-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-yellow-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-yellow-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-yellow-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-purple .main-header .navbar{background-color:#605ca8}.skin-purple .main-header .navbar .nav>li>a{color:#fff}.skin-purple .main-header .navbar .nav>li>a:hover,.skin-purple .main-header .navbar .nav>li>a:active,.skin-purple .main-header .navbar .nav>li>a:focus,.skin-purple .main-header .navbar .nav .open>a,.skin-purple .main-header .navbar .nav .open>a:hover,.skin-purple .main-header .navbar .nav .open>a:focus,.skin-purple .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-purple .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-purple .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple .main-header .navbar .sidebar-toggle:hover{background-color:#555299}@media (max-width:767px){.skin-purple .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-purple .main-header .navbar .dropdown-menu li a{color:#fff}.skin-purple .main-header .navbar .dropdown-menu li a:hover{background:#555299}}.skin-purple .main-header .logo{background-color:#555299;color:#fff;border-bottom:0 solid transparent}.skin-purple .main-header .logo:hover{background-color:#545096}.skin-purple .main-header li.user-header{background-color:#605ca8}.skin-purple .content-header{background:transparent}.skin-purple .wrapper,.skin-purple .main-sidebar,.skin-purple .left-side{background-color:#222d32}.skin-purple .user-panel>.info,.skin-purple .user-panel>.info>a{color:#fff}.skin-purple .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-purple .sidebar-menu>li>a{border-left:3px solid transparent}.skin-purple .sidebar-menu>li:hover>a,.skin-purple .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#605ca8}.skin-purple .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-purple .sidebar a{color:#b8c7ce}.skin-purple .sidebar a:hover{text-decoration:none}.skin-purple .treeview-menu>li>a{color:#8aa4af}.skin-purple .treeview-menu>li.active>a,.skin-purple .treeview-menu>li>a:hover{color:#fff}.skin-purple .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-purple .sidebar-form input[type="text"],.skin-purple .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-purple .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-purple .sidebar-form input[type="text"]:focus,.skin-purple .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-purple .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-purple .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-purple-light .main-header .navbar{background-color:#605ca8}.skin-purple-light .main-header .navbar .nav>li>a{color:#fff}.skin-purple-light .main-header .navbar .nav>li>a:hover,.skin-purple-light .main-header .navbar .nav>li>a:active,.skin-purple-light .main-header .navbar .nav>li>a:focus,.skin-purple-light .main-header .navbar .nav .open>a,.skin-purple-light .main-header .navbar .nav .open>a:hover,.skin-purple-light .main-header .navbar .nav .open>a:focus,.skin-purple-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-purple-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-purple-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple-light .main-header .navbar .sidebar-toggle:hover{background-color:#555299}@media (max-width:767px){.skin-purple-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-purple-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-purple-light .main-header .navbar .dropdown-menu li a:hover{background:#555299}}.skin-purple-light .main-header .logo{background-color:#605ca8;color:#fff;border-bottom:0 solid transparent}.skin-purple-light .main-header .logo:hover{background-color:#5d59a6}.skin-purple-light .main-header li.user-header{background-color:#605ca8}.skin-purple-light .content-header{background:transparent}.skin-purple-light .wrapper,.skin-purple-light .main-sidebar,.skin-purple-light .left-side{background-color:#f9fafc}.skin-purple-light .content-wrapper,.skin-purple-light .main-footer{border-left:1px solid #d2d6de}.skin-purple-light .user-panel>.info,.skin-purple-light .user-panel>.info>a{color:#444}.skin-purple-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-purple-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-purple-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-purple-light .sidebar-menu>li:hover>a,.skin-purple-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-purple-light .sidebar-menu>li.active{border-left-color:#605ca8}.skin-purple-light .sidebar-menu>li.active>a{font-weight:600}.skin-purple-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-purple-light .sidebar a{color:#444}.skin-purple-light .sidebar a:hover{text-decoration:none}.skin-purple-light .treeview-menu>li>a{color:#777}.skin-purple-light .treeview-menu>li.active>a,.skin-purple-light .treeview-menu>li>a:hover{color:#000}.skin-purple-light .treeview-menu>li.active>a{font-weight:600}.skin-purple-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-purple-light .sidebar-form input[type="text"],.skin-purple-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-purple-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-purple-light .sidebar-form input[type="text"]:focus,.skin-purple-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-purple-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-purple-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-purple-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}} \ No newline at end of file diff --git a/hesk_style_RTL.css b/hesk_style_RTL.css deleted file mode 100644 index 8a390bdd..00000000 --- a/hesk_style_RTL.css +++ /dev/null @@ -1,758 +0,0 @@ -body { - background-color: #f3fef4; - color: black; - font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - margin-right:0; - margin-left:0; - margin-top:0; - margin-bottom:0; -} - -table.enclosing { - background-color:#FFFFFF; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - width: 960px; -} - - -td { - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - text-align: right; -} - -a img { - vertical-align: bottom; - border : none; -} - -td.white { - background-color: #FFFFFF; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; -} - -td.admin_white { - background-color: #FFFFFF; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; - border: #d1dceb 1px solid; - padding: 1px; -} - -td.admin_gray { - background-color: #f5fffa; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; - border: #d1dceb 1px solid; - padding: 1px; -} - -td.notes { - background-color: #fffbf2; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - border: #ffe6b8 1px solid; -} - -th.tDetailsHead { - background-color: #F7F7F7; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - font-weight: normal; - text-align: center; -} - -td.tDetailsBody { - background-color: #FFFFFF; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - border: #F7F7F7 1px solid; - text-align: center; -} - -.small { - font-size: 11px; -} - -.smaller { - font-size: 10px; -} - -a { - color : Blue; - text-decoration : underline; -} - -a:hover { - color : Red; - text-decoration : none; -} - -table.white { - background-color: #ffffff ; - color : #23559C; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - border-collapse: collapse; -} - -table.white th { - background-color: #ffffff; - color : #23559C; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; - border: #b2babd 1px solid; - border-collapse: collapse; - background-image: url(img/tableheader.jpg); - background-repeat: no-repeat; - background-position: right top; -} - -table.white th a { - color : #23559C; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; -} - -table.white td { - height: 25px; - padding-right: 5px; -} - -.noborder { -border: none; -} - -h3 { - color : #74804e; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 13px; - font-weight: bold; - margin: 0px; -} - -.important { - color: Red; -} - -.medium { - color: #FF9900; -} - -input { - font-size: 12px; - font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; - color:#000000; -} - -input.button { - font-size: 12px; - font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; - color:#000000; - background-color:#FFFFFF; -} - -select { - font-size: 12px; - height: 20px; -} - -header a img {border: none; text-decoration: none !important;} - -.open {color : #FF0000;} -.waitingreply {color : #FF9933;} -.replied {color : #0000FF;} -.resolved {color : #008000;} -.allbutresolved {color : #000000;} - -.success {color : #008000;} -.error {color : #FF0000;} -.notice {color : #FF9900} - -#ok { - border: 1px solid #679800; -} - -#error { - border: 1px solid #ba3225; -} - -.rate { - color: #666666; - text-align: left; - font-style: italic; -} - -a.article { - color : Black; - text-decoration: none; - font-size: 11px; -} - -a.article:hover { - color : Red; - text-decoration : none; -} - -.article_list { - font-size: 11px; -} - -fieldset { - margin-bottom: 6px; - border: 1px SOLID #267DDC; - padding: 4px; - background-color:white; - position:relative; - display:block; - padding: 15px 10px 10px 10px; - margin:20px 0px 20px 0px; -} - -legend { - background-image: url(img/bluebtn.png); - background-repeat: no-repeat; - color: #172901; - border: 1px solid #267DDC; - height: 10px; - font-size: 10px; - font-weight:bold; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - background-position: right top; - display:block; - width: auto; - padding:5px 5px; - position:relative; - width:130px; -} - -/*newly introduced styles (version 2.0)*/ - -.header { - width: 100%; - background-color: #74a62b; - color : #ffffff; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; - text-align: center; - background-image: url(img/header.png); - background-repeat: repeat-x; - height: 57px; - padding: 0px; - margin: 0px; -} - -.header td { -text-align: center; -vertical-align: middle; -color:#FFFFFF; -} -.header a, .header a:link, .header a:active, .header a:visited { - color : #ffffff; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; - text-align: center; -} -.header a:hover {} - -.headersm { - width: 100%; - background-color: #669933; - color : #ffffff; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - text-align: right; - background-image: url(img/headerbgsm.jpg); - background-repeat: repeat-x; - height: 25px; - padding: 0px; - margin: 0px; - font-weight:bold; - padding-right: 20px; -} - -hr { - border: none; - border-bottom: 1px dotted #adbac3; - width: 100%; - padding-top: 10px; - margin-bottom: 10px; - height: 1px; -} - -.greenbutton { - background-image: url(img/greenbtn.jpg); - background-repeat: no-repeat; - text-align: center; - color: #FFFFFF; - border: 1px solid #527234; - font-size: 10px; - font-weight:bold; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - background-position: right top; - height: 19px; - padding-right: 6px; - padding-left: 6px; - margin-right: 2px; - margin-left: 2px -} - -.greenbuttonover { - background-image: url(img/greenbtnover.gif); - background-repeat: no-repeat; - text-align: center; - color: #FFFFFF; - border: 1px solid #527234; - font-size: 10px; - font-weight:bold; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - background-position: right top; - height: 19px; - padding-right: 6px; - padding-left: 6px; - margin-right: 2px; - margin-left: 2px -} - -.orangebutton { - background-image: url(img/orangebtn.jpg); - background-repeat: no-repeat; - text-align: center; - color: #660000; - border: 1px solid #bf6628; - height: 20px; - font-size: 10px; - font-weight:bold; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - background-position: right top; - padding-right: 6px; - padding-left: 6px; - margin-right: 2px; - margin-left: 2px; - text-decoration: none; - margin-top: 0px; -} - -.orangebuttonover { - background-image: url(img/orangebtnover.gif); - background-repeat: no-repeat; - text-align: center; - color: #660000; - border: 1px solid #bf6628; - height: 20px; - font-size: 10px; - font-weight:bold; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - background-position: right top; - padding-right: 6px; - padding-left: 6px; - margin-right: 2px; - margin-left: 2px; - text-decoration: none; - margin-top: 0px; -} - -.bluebutton { - background-image: url(img/bluebtn.png); - background-repeat: no-repeat; - text-align: center; - color: #660000; - border: 1px solid #5b79a3; - height: 20px; - font-size: 10px; - font-weight:bold; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - background-position: right top; - padding-right: 6px; - padding-left: 6px; - margin-right: 2px; - text-decoration: none; - margin-left: 2px; - padding-top: 2px; -} - - -/*styles for roundcorners tables*/ - -.roundcornersleft { - width: 7px; - background-image: url(img/roundcornerslm.jpg); - background-repeat: repeat-y; - background-position: right; -} -.roundcornersright { - width: 7px; - background-image: url(img/roundcornersrm.jpg); - background-repeat: repeat-y; - background-position: left; -} -.roundcornerstop { - height: 7px; - background-image: url(img/roundcornerst.jpg); - background-repeat: repeat-x; - background-position: top; -} -.roundcornersbottom { - height: 7px; - background-image: url(img/roundcornersb.jpg); - background-repeat: repeat-x; - background-position: bottom; -} -.ticketrow { - background-color: #f5fffa; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - border: #748aaf 1px dotted; - padding: 6px; -} -.ticketalt { - background-color: #ffffff; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; - padding: 6px; -} - -.tickettd { - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 12px; -} - -.subheaderrow { - background-color: #ffffff; - color : #23559C; - border: #23559C solid 1px; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; - border-collapse: collapse; - background-image: url(img/tableheader.jpg); - background-repeat: no-repeat; - background-position: right top; - height: 25px; - padding-right: 20px; - padding-left: 20px; - padding-top: 5px; - } - -.homepageh3, .section { - color: #74804e; - font-weight: bold; -} - -/*newly introduced styles (version 2.2)*/ - -.large { - font-size:14px; -} - -.largebold { - font-size:14px; - font-weight:bold; -} - -.assignedyou { - color: red; -} - -.assignedother { - color: green; -} - -div.error { - border: 1px solid #cd0a0a; - background: #fef1ec; - color: #cd0a0a; - padding: 10px; -} - -div.success { - border: 1px solid #18760f; - background: #e9ffdb; - color: #363636; - padding: 10px; -} - -div.notice { - border: 1px solid #fcefa1; - background: #fff9de; - color: #363636; - padding: 10px; - vertical-align: middle; -} - -.admin_green { - background-color: #e3ffd0; - font-weight: bold; -} - -.admin_red { - background-color: #fef1ec; - font-weight: bold; -} - -.borderBottom { - border-bottom: silver 1px dashed; -} - -.borderTop { - border-top: silver 1px dashed; -} - -.alignTop { - vertical-align: top; -} - -.alignMiddle { - vertical-align: middle; -} - -.alignBottom { - vertical-align: bottom; -} - -hr.dashed { - border: none 0; - border-top: 1px dashed silver; - height: 1px; -} - -/* newly introduced styles (version 2.3) */ - -h1 { - color : #74804e; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 13px; - font-weight: bold; - margin: 0px; -} - -.critical { - color: #9400d3; - font-weight: bold; -} - -td.admin_critical { - background-color: #fff0ff; - color : #000000; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; - border: 1px solid #ff66ff; - padding: 1px; -} - -.isError { - color: black; - background-color: #fff9f7; - border: 1px solid red; -} - -.isNotice { - color: black; - border: 1px solid orange; - background: #fbf9ee; -} - -.optionWhiteON{ - padding:2px; - border:1px dotted silver; - background-color: #b0e0e6; -} - -.optionWhiteOFF { - padding:2px; - border:1px dotted silver; - background-color: white; -} - -.optionBlueON { - padding:2px; - border:1px dotted gray; - background-color: #b0e0e6; -} - -.optionBlueOFF { - padding:2px; - border:1px dotted gray; - background-color: #f5fffa; -} - -.optionWhiteNbON{ - border: none; - background-color: #b0e0e6; - vertical-align: text-bottom; -} - -.optionWhiteNbOFF { - border: none; - background-color: white; - vertical-align: text-bottom; -} - -.kbCatListON { - background-color: #fcefa1; -} - -.kbCatListOFF { - background-color: white; -} - -div.progress-container { - border: 1px solid #ccc; - width: 100px; - margin: 2px 0 2px 5px; - padding: 1px; - float: right; - background: white; -} - -div.progress-container > div { - background-color: #ACE97C; - height: 12px -} - -.black {color: black;} -.inprogress {color : #006400;} -.onhold {color : #000000;} - -div.online { - border: 1px solid #e5e8ff; - background: #ffffff; - color: #000000; - padding: 0px; - vertical-align: middle; -} - -span.online { - font-size: 10px; - white-space:nowrap; -} - -/* newly introduced styles (version 2.4) */ - -.orangebuttonsec { - background-image: url(img/orangebtnsec.jpg); - background-repeat: no-repeat; - text-align: center; - color: #660000; - border: 1px solid #bf6628; - height: 20px; - font-size: 10px; - font-weight:normal; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - background-position: right top; - padding-right: 6px; - padding-left: 6px; - margin-right: 2px; - margin-left: 2px; - text-decoration: none; - margin-top: 0px; -} - -.orangebuttonsecover { - background-image: url(img/orangebtnover.gif); - background-repeat: no-repeat; - text-align: center; - color: #660000; - border: 1px solid #bf6628; - height: 20px; - font-size: 10px; - font-weight:normal; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - background-position: right top; - padding-right: 6px; - padding-left: 6px; - margin-right: 2px; - margin-left: 2px; - text-decoration: none; - margin-top: 0px; -} - -td.admin_yellow { - background-color: #ffffe0; - color : #4a5571; - font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; - font-size: 11px; - border: #d1dceb 1px solid; - padding: 1px; -} - -/* New styles in HESK version 2.6 */ - -div.info { - border: 1px solid #9acfea; - background: #d9edf7; - color: #363636; - padding: 10px; - vertical-align: middle; -} - -div.none { - color: #363636; - padding: 10px; - vertical-align: middle; -} - -/* New styles in HESK version 2.5 */ - -.kb_published { - color: #009900; -} - -.kb_private { - color: #4a5571; -} - -.kb_draft { - color: #cc9933; -} - -.searchbutton { - cursor:pointer; - width:70px; - height:27px; - text-indent:-999px; - text-transform: capitalize; - color: transparent; - background: url(img/ico-search.png) no-repeat #4d90fe center; - border: 1px solid #3079ED; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; -} - -.searchbutton:hover { - background: url(img/ico-search.png) no-repeat center #357AE8; - border: 1px solid #2F5BB7; -} - -.searchfield { - width:400px; - height:21px; - font-size:14px; - text-indent:2px; - vertical-align: bottom; -} - -.sbsmall { - width:50px; - height:22px; - margin-top:2px; -} - -.sfsmall { - width:200px; - height:16px; - font-size:12px; - margin-top:2px; -} \ No newline at end of file diff --git a/inc/headerAdmin.inc.php b/inc/headerAdmin.inc.php index 5dba6b02..55941c3d 100644 --- a/inc/headerAdmin.inc.php +++ b/inc/headerAdmin.inc.php @@ -41,7 +41,6 @@ $modsForHesk_settings = mfh_getSettings(); - diff --git a/install/install_functions.inc.php b/install/install_functions.inc.php index e927d649..864e6dc8 100644 --- a/install/install_functions.inc.php +++ b/install/install_functions.inc.php @@ -16,7 +16,7 @@ if (!defined('IN_SCRIPT')) {die('Invalid attempt');} // We will be installing this HESK version: define('HESK_NEW_VERSION','2.7.3'); -define('MODS_FOR_HESK_NEW_VERSION','3.0.7'); +define('MODS_FOR_HESK_NEW_VERSION','3.1.0'); define('REQUIRE_PHP_VERSION','5.3.0'); define('REQUIRE_MYSQL_VERSION','5.0.7'); diff --git a/install/mods-for-hesk/ajax/install-database-ajax.php b/install/mods-for-hesk/ajax/install-database-ajax.php index e0d13e82..e28169ec 100644 --- a/install/mods-for-hesk/ajax/install-database-ajax.php +++ b/install/mods-for-hesk/ajax/install-database-ajax.php @@ -88,6 +88,8 @@ if ($version == 2) { execute306Scripts(); } elseif ($version == 41) { execute307Scripts(); +} elseif ($version == 42) { + execute310Scripts(); } else { $response = 'The version "' . $version . '" was not recognized. Check the value submitted and try again.'; print $response; diff --git a/install/mods-for-hesk/installModsForHesk.php b/install/mods-for-hesk/installModsForHesk.php index d5c3a827..cc89aaf4 100644 --- a/install/mods-for-hesk/installModsForHesk.php +++ b/install/mods-for-hesk/installModsForHesk.php @@ -50,6 +50,7 @@ $buildToVersionMap = array( 39 => '3.0.5', 40 => '3.0.6', 41 => '3.0.7', + 42 => '3.1.0', ); function echoInitialVersionRows($version, $build_to_version_map) diff --git a/install/mods-for-hesk/js/version-scripts.js b/install/mods-for-hesk/js/version-scripts.js index 5efbc3f0..68a83e12 100644 --- a/install/mods-for-hesk/js/version-scripts.js +++ b/install/mods-for-hesk/js/version-scripts.js @@ -119,6 +119,9 @@ function processUpdates(startingVersion) { } else if (startingVersion < 41) { startVersionUpgrade('307'); executeUpdate(41, '307', '3.0.7'); + } else if (startingVersion < 42) { + startVersionUpgrade('310'); + executeUpdate(42, '310', '3.1.0'); } else { installationFinished(); } diff --git a/install/mods-for-hesk/modsForHesk.php b/install/mods-for-hesk/modsForHesk.php index 0b6756f5..386a53ff 100644 --- a/install/mods-for-hesk/modsForHesk.php +++ b/install/mods-for-hesk/modsForHesk.php @@ -118,6 +118,7 @@ hesk_dbConnect(); From a2198831179799d4872936c5a7421abad3d713fc Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 21 Jun 2017 21:56:02 -0400 Subject: [PATCH 187/192] Fix colors on new category panel --- admin/manage_categories.php | 63 +++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/admin/manage_categories.php b/admin/manage_categories.php index e0525904..c90aebc8 100644 --- a/admin/manage_categories.php +++ b/admin/manage_categories.php @@ -107,8 +107,7 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
      -

      - ()

      +
      data-error="" required> +
      - @@ -149,15 +148,51 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
      + placeholder="" type="text" + name="background-color" maxlength="7" required> +
      +
      +
      +
      + +
      + +
      +
      +
      + +
      +
      + +
          +
      + +
      @@ -194,10 +229,12 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
      -
      - - - +
      +
      + + + +
      From 0caf4e740ed0fd5ae54bf708c067b7137583a5dc Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Wed, 21 Jun 2017 22:07:21 -0400 Subject: [PATCH 188/192] Fix color issues on calendar page --- admin/calendar.php | 8 ++++++-- js/calendar/mods-for-hesk-calendar.js | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/admin/calendar.php b/admin/calendar.php index e02dde94..a3256299 100644 --- a/admin/calendar.php +++ b/admin/calendar.php @@ -207,7 +207,9 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); echo ''; } foreach ($categories as $category): ?> - @@ -377,7 +379,9 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); echo ''; } foreach ($categories as $category): ?> - diff --git a/js/calendar/mods-for-hesk-calendar.js b/js/calendar/mods-for-hesk-calendar.js index 0e13b44e..e4bbb699 100644 --- a/js/calendar/mods-for-hesk-calendar.js +++ b/js/calendar/mods-for-hesk-calendar.js @@ -204,7 +204,9 @@ $(document).ready(function() { categoryId: $createForm.find('select[name="category"]').val(), action: 'create', type: 'CALENDAR', - categoryColor: $createForm.find('select[name="category"] :selected').attr('data-color'), + backgroundColor: $createForm.find('select[name="category"] :selected').attr('data-background-color'), + foregroundColor: $createForm.find('select[name="category"] :selected').attr('data-foreground-color'), + displayBorder: $createForm.find('select[name="category"] :selected').attr('data-display-border'), categoryName: $createForm.find('select[name="category"] :selected').text().trim(), reminderValue: $createForm.find('input[name="reminder-value"]').val(), reminderUnits: $createForm.find('select[name="reminder-unit"]').val() @@ -249,7 +251,9 @@ $(document).ready(function() { allDay: allDay, comments: $form.find('textarea[name="comments"]').val(), categoryId: $form.find('select[name="category"]').val(), - categoryColor: $form.find('select[name="category"] :selected').attr('data-color'), + backgroundColor: $form.find('select[name="category"] :selected').attr('data-background-color'), + foregroundColor: $form.find('select[name="category"] :selected').attr('data-foreground-color'), + displayBorder: $form.find('select[name="category"] :selected').attr('data-display-border'), categoryName: $form.find('select[name="category"] :selected').text().trim(), action: 'update', reminderValue: $form.find('input[name="reminder-value"]').val(), From 1234f858e5986521380b963643e37341fc657f6e Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 22 Jun 2017 12:04:12 +0000 Subject: [PATCH 189/192] Update build.php --- build.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.php b/build.php index 6f978f4b..799c27fb 100644 --- a/build.php +++ b/build.php @@ -1,4 +1,4 @@ Date: Thu, 22 Jun 2017 12:04:29 +0000 Subject: [PATCH 190/192] Update database-validation.php --- install/mods-for-hesk/database-validation.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install/mods-for-hesk/database-validation.php b/install/mods-for-hesk/database-validation.php index 05f96b69..22c34c98 100644 --- a/install/mods-for-hesk/database-validation.php +++ b/install/mods-for-hesk/database-validation.php @@ -216,7 +216,8 @@ hesk_dbConnect(); function run_setting_check($setting_name) { global $hesk_settings; - $all_good = run_check("SELECT 1 FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` WHERE `Key` = '{$setting_name}'"); + $res = run_check("SELECT 1 FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` WHERE `Key` = '{$setting_name}'"); + $all_good = hesk_dbNumRows($res) > 0; output_result('Setting Exists: ' . $setting_name, $all_good); From 77c5c3d8727fd15ad9d9cb990ca5fe5c9c3c3ed9 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 22 Jun 2017 12:52:04 -0400 Subject: [PATCH 191/192] Fix nav element API path --- internal-api/js/manage-custom-nav-elements.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal-api/js/manage-custom-nav-elements.js b/internal-api/js/manage-custom-nav-elements.js index 7c32619b..9495b1c6 100644 --- a/internal-api/js/manage-custom-nav-elements.js +++ b/internal-api/js/manage-custom-nav-elements.js @@ -121,7 +121,7 @@ $(document).ready(function() { function loadTable() { $('#overlay').show(); - var heskUrl = $('#heskUrl').text(); + var heskUrl = $('p#hesk-path').text(); var notFoundText = mfhLang.text('no_custom_nav_elements_found'); var places = []; var $tableBody = $('#table-body'); @@ -131,7 +131,7 @@ function loadTable() { $.ajax({ method: 'GET', - url: heskUrl + '/api/v1-internal/custom-navigation/all', + url: heskUrl + 'api/v1-internal/custom-navigation/all', headers: { 'X-Internal-Call': true }, success: function(data) { $tableBody.html(''); From ef481b3192f7125403c54863c68a9791562f0bf8 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Thu, 22 Jun 2017 21:21:38 -0400 Subject: [PATCH 192/192] Fixed some JS files --- internal-api/js/manage-custom-nav-elements.js | 8 ++++---- internal-api/js/view-message-log.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal-api/js/manage-custom-nav-elements.js b/internal-api/js/manage-custom-nav-elements.js index 9495b1c6..528b6340 100644 --- a/internal-api/js/manage-custom-nav-elements.js +++ b/internal-api/js/manage-custom-nav-elements.js @@ -82,7 +82,7 @@ $(document).ready(function() { url: navUrl }; - var url = heskUrl + '/api/v1-internal/custom-navigation/'; + var url = heskUrl + 'api/index.php/v1-internal/custom-navigation/'; var method = 'POST'; if (id !== -1) { @@ -131,7 +131,7 @@ function loadTable() { $.ajax({ method: 'GET', - url: heskUrl + 'api/v1-internal/custom-navigation/all', + url: heskUrl + 'api/index.php/v1-internal/custom-navigation/all', headers: { 'X-Internal-Call': true }, success: function(data) { $tableBody.html(''); @@ -311,7 +311,7 @@ function bindDeleteButton() { $.ajax({ method: 'DELETE', - url: heskUrl + '/api/v1-internal/custom-navigation/' + element.id, + url: heskUrl + 'api/index.php/v1-internal/custom-navigation/' + element.id, headers: { 'X-Internal-Call': true }, success: function() { mfhAlert.success(mfhLang.text('custom_nav_element_deleted')); @@ -335,7 +335,7 @@ function bindSortButtons() { $.ajax({ method: 'POST', - url: heskUrl + '/api/v1-internal/custom-navigation/' + element.id + '/sort/' + direction, + url: heskUrl + 'api/index.php/v1-internal/custom-navigation/' + element.id + '/sort/' + direction, headers: { 'X-Internal-Call': true }, success: function() { loadTable(); diff --git a/internal-api/js/view-message-log.js b/internal-api/js/view-message-log.js index 712b92ae..b0d9265e 100644 --- a/internal-api/js/view-message-log.js +++ b/internal-api/js/view-message-log.js @@ -45,7 +45,7 @@ function displayResults(data) { table.empty(); if (data.length === 0) { - table.append('No results found'); + table.append('No results found'); } else { for (var index in data) { var result = data[index];