"The endpoint '{$_SERVER['REQUEST_URI']}' was not found. Double-check your request and submit again.", 'uri' => $_SERVER['REQUEST_URI'] ), 404); } function globalBefore() { if (defined('HESK_DEMO') && $_SERVER['REQUEST_METHOD'] !== 'GET') { print_error('Demo Mode', 'Only read-only commands are available in demo mode!', null, 401); die(); } } function internalHandler() { buildUserContextFromSession(); } function authTokenHandler() { assertApiIsEnabled(); $token = \BusinessLogic\Helpers::getHeader('X-AUTH-TOKEN'); buildUserContext($token); } function internalOrAuthHandler() { $internalUse = \BusinessLogic\Helpers::getHeader('X-INTERNAL-CALL'); if ($internalUse === 'true') { internalHandler(); } else { authTokenHandler(); } } function publicHandler() { global $userContext; // Check if we passed in a X-Auth-Token or X-Internal-Call header. Those take priority if (\BusinessLogic\Helpers::getHeader('X-INTERNAL-CALL') === 'true') { internalHandler(); } elseif (\BusinessLogic\Helpers::getHeader('X-AUTH-TOKEN') !== null) { authTokenHandler(); } else { //-- Create an "anonymous" UserContext $userContext = \BusinessLogic\Security\UserContext::buildAnonymousUser(); } } function assertApiIsEnabled() { global $applicationContext, $hesk_settings; /* @var $apiChecker \BusinessLogic\Settings\ApiChecker */ $apiChecker = $applicationContext->get(\BusinessLogic\Settings\ApiChecker::clazz()); if (!$apiChecker->isApiEnabled($hesk_settings)) { print output(array('message' => 'API Disabled'), 404); die(); } return; } function buildUserContextFromSession() { global $userContext; hesk_session_start(); if (empty($_SESSION['id'])) { throw new \BusinessLogic\Exceptions\SessionNotActiveException(); } /* @var $userContext \BusinessLogic\Security\UserContext */ $userContext = \BusinessLogic\Security\UserContext::fromDataRow($_SESSION); } function buildUserContext($xAuthToken) { global $applicationContext, $userContext, $hesk_settings; /* @var $userContextBuilder \BusinessLogic\Security\UserContextBuilder */ $userContextBuilder = $applicationContext->get(\BusinessLogic\Security\UserContextBuilder::clazz()); $userContext = $userContextBuilder->buildUserContext($xAuthToken, $hesk_settings); } function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { exceptionHandler(new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage))); } /** * @param $exception Exception */ function exceptionHandler($exception) { global $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; } // We don't cast API Friendly Exceptions as they're user-generated errors if (exceptionIsOfType($exception, \BusinessLogic\Exceptions\ApiFriendlyException::clazz())) { /* @var $castedException \BusinessLogic\Exceptions\ApiFriendlyException */ $castedException = $exception; print_error($castedException->title, $castedException->getMessage(), null, $castedException->httpResponseCode); } elseif (exceptionIsOfType($exception, \Core\Exceptions\SQLException::clazz())) { /* @var $castedException \Core\Exceptions\SQLException */ $castedException = $exception; $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}", $logId); } else { $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}", $logId); } 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::clazz()); 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]; $location = basename($lastCall['file'], '.php'); if ($location === null || trim($location) === '') { $location = 'N/A'; } return "REST API: {$location}"; } /** * @param $exception Exception thrown exception * @param $class string The name of the expected exception type * @return bool */ function exceptionIsOfType($exception, $class) { return is_a($exception, $class); } function fatalErrorShutdownHandler() { $last_error = error_get_last(); if ($last_error['type'] === E_ERROR) { // fatal error errorHandler(E_ERROR, $last_error['message'], $last_error['file'], $last_error['line']); } } Link::before('globalBefore'); Link::all(array( // Categories '/v1/categories/all' => action(\Controllers\Categories\CategoryController::clazz() . '::printAllCategories', array(RequestMethod::GET), SecurityHandler::INTERNAL_OR_AUTH_TOKEN), '/v1/categories' => action(\Controllers\Categories\CategoryController::clazz(), array(RequestMethod::POST), SecurityHandler::INTERNAL_OR_AUTH_TOKEN), '/v1/categories/{i}' => action(\Controllers\Categories\CategoryController::clazz(), array(RequestMethod::GET, RequestMethod::PUT, RequestMethod::DELETE), SecurityHandler::INTERNAL_OR_AUTH_TOKEN), '/v1-internal/categories/{i}/sort/{s}' => action(\Controllers\Categories\CategoryController::clazz() . '::sort', array(RequestMethod::POST), SecurityHandler::INTERNAL), // Tickets '/v1/tickets' => action(\Controllers\Tickets\CustomerTicketController::clazz(), RequestMethod::all(), SecurityHandler::OPEN), '/v1/tickets/{i}/replies' => action(\Controllers\Tickets\CustomerReplyController::clazz(), array(RequestMethod::POST), SecurityHandler::OPEN), // Tickets - Staff '/v1/staff/tickets/{i}' => action(\Controllers\Tickets\StaffTicketController::clazz(), RequestMethod::all()), '/v1/staff/tickets/{i}/due-date' => action(\Controllers\Tickets\StaffTicketController::clazz() . '::updateDueDate', array(RequestMethod::PATCH), SecurityHandler::INTERNAL_OR_AUTH_TOKEN), // Attachments '/v1/tickets/{a}/attachments/{i}' => action(\Controllers\Attachments\PublicAttachmentController::clazz() . '::getRaw', RequestMethod::all()), '/v1/staff/tickets/{i}/attachments' => action(\Controllers\Attachments\StaffTicketAttachmentsController::clazz(), RequestMethod::all()), '/v1/staff/tickets/{i}/attachments/{i}' => action(\Controllers\Attachments\StaffTicketAttachmentsController::clazz(), RequestMethod::all()), // Statuses '/v1/statuses' => action(\Controllers\Statuses\StatusController::clazz(), RequestMethod::all()), // Settings '/v1/settings' => action(\Controllers\Settings\SettingsController::clazz(), RequestMethod::all()), // Calendar '/v1/calendar/business-hours' => action(\Controllers\Calendar\CalendarController::clazz() . '::getBusinessHours', array(RequestMethod::GET), SecurityHandler::OPEN), '/v1/calendar/events' => action(\Controllers\Calendar\CalendarController::clazz(), array(RequestMethod::GET), SecurityHandler::OPEN), '/v1/calendar/events/staff' => action(\Controllers\Calendar\CalendarController::clazz(), array(RequestMethod::GET, RequestMethod::POST), SecurityHandler::INTERNAL_OR_AUTH_TOKEN), '/v1/calendar/events/staff/{i}' => action(\Controllers\Calendar\CalendarController::clazz(), array(RequestMethod::PUT, RequestMethod::DELETE), SecurityHandler::INTERNAL_OR_AUTH_TOKEN), // Service Messages '/v1/service-messages' => action(\Controllers\ServiceMessages\ServiceMessagesController::clazz(), array(RequestMethod::GET, RequestMethod::POST), SecurityHandler::OPEN), '/v1/service-messages/{i}' => action(\Controllers\ServiceMessages\ServiceMessagesController::clazz(), array(RequestMethod::PUT, RequestMethod::DELETE), SecurityHandler::INTERNAL_OR_AUTH_TOKEN), '/v1-internal/service-messages/{i}/sort/{s}' => action(\Controllers\ServiceMessages\ServiceMessagesController::clazz() . '::sort', array(RequestMethod::POST), SecurityHandler::INTERNAL), /* Internal use only routes */ // Resend email response '/v1-internal/staff/tickets/{i}/resend-email' => action(\Controllers\Tickets\ResendTicketEmailToCustomerController::clazz(), RequestMethod::all(), SecurityHandler::INTERNAL), // Custom Navigation '/v1-internal/custom-navigation/all' => action(\Controllers\Navigation\CustomNavElementController::clazz() . '::getAll', RequestMethod::all(), SecurityHandler::INTERNAL), '/v1-internal/custom-navigation' => action(\Controllers\Navigation\CustomNavElementController::clazz(), RequestMethod::all(), SecurityHandler::INTERNAL), '/v1-internal/custom-navigation/{i}' => action(\Controllers\Navigation\CustomNavElementController::clazz(), RequestMethod::all(), SecurityHandler::INTERNAL), '/v1-internal/custom-navigation/{i}/sort/{s}' => action(\Controllers\Navigation\CustomNavElementController::clazz() . '::sort', RequestMethod::all(), SecurityHandler::INTERNAL), '/v1-public/hesk-version' => action(\Controllers\System\HeskVersionController::clazz() . '::getHeskVersion', RequestMethod::all(), SecurityHandler::OPEN), '/v1-public/mods-for-hesk-version' => action(\Controllers\System\HeskVersionController::clazz() . '::getModsForHeskVersion', RequestMethod::all(), SecurityHandler::OPEN), // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' )); /** * @param $class object|string The class name (and optional static method) * @param $requestMethods array The accepted request methods for this endpoint * @param $securityHandler string The proper security handler * @return array The configured path */ function action($class, $requestMethods, $securityHandler = SecurityHandler::AUTH_TOKEN) { return array($class, $class, $securityHandler, $requestMethods); } class SecurityHandler { const OPEN = 'publicHandler'; const INTERNAL = 'internalHandler'; const AUTH_TOKEN = 'authTokenHandler'; const INTERNAL_OR_AUTH_TOKEN = 'internalOrAuthHandler'; }