feat: add dist

This commit is contained in:
paolosantarsiero9 2023-02-13 20:55:10 +01:00
parent 320227815f
commit b5e3316040
4094 changed files with 330786 additions and 0 deletions

2
.gitignore vendored
View File

@ -8,4 +8,6 @@ server/files/
server/.dbdata
server/.fakemail
server/apidoc
database/
config/
.env

0
client/src/assets/images/icon.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

0
client/src/assets/images/logo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

0
client/src/assets/images/profile.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

5
dist/.htaccess vendored Executable file
View File

@ -0,0 +1,5 @@
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/?(api)/
RewriteRule .* index.php [PT,L]

4
dist/api/.htaccess vendored Executable file
View File

@ -0,0 +1,4 @@
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [PT,L]

20
dist/api/composer.json vendored Executable file
View File

@ -0,0 +1,20 @@
{
"require": {
"slim/slim": "~2.0",
"respect/validation": "^1.1",
"phpmailer/phpmailer": "^5.2",
"google/recaptcha": "~1.1",
"gabordemooij/redbean": "^5.4",
"ifsnop/mysqldump-php": "2.*",
"ezyang/htmlpurifier": "^4.8",
"codeguy/upload": "^1.3",
"php-imap/php-imap": "^3.1",
"willdurand/email-reply-parser": "^2.8"
},
"require-dev": {
"phpunit/phpunit": "^5.7"
},
"autoload":{
"classmap": ["libs/", "models/", "controllers/", "data/"]
}
}

2062
dist/api/composer.lock generated vendored Executable file

File diff suppressed because it is too large Load Diff

0
dist/api/config.php vendored Executable file
View File

13
dist/api/controllers/article.php vendored Executable file
View File

@ -0,0 +1,13 @@
<?php
$articleControllers = new ControllerGroup();
$articleControllers->setGroupPath('/article');
$articleControllers->addController(new AddTopicController);
$articleControllers->addController(new EditTopicController);
$articleControllers->addController(new DeleteTopicController);
$articleControllers->addController(new AddArticleController);
$articleControllers->addController(new EditArticleController);
$articleControllers->addController(new DeleteArticleController);
$articleControllers->addController(new GetAllArticlesController);
$articleControllers->finalize();

61
dist/api/controllers/article/add-topic.php vendored Executable file
View File

@ -0,0 +1,61 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/add-topic Add topic
* @apiVersion 4.11.0
*
* @apiName Add topic
*
* @apiGroup Article
*
* @apiDescription This path adds a new topic.
*
* @apiPermission staff2
*
* @apiParam {String} name Name of the new topic.
* @apiParam {String} icon Icon of the new topic.
* @apiParam {String} iconColor Icon's color of the new topic.
* @apiParam {Boolean} private Indicates if the topic is not shown to users.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
*
* @apiSuccess {Object} data Topic info
* @apiSuccess {Number} data.topicId Topic id
*
*/
class AddTopicController extends Controller {
const PATH = '/add-topic';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
],
]
];
}
public function handler() {
$topic = new Topic();
$topic->setProperties([
'name' => Controller::request('name', true),
'icon' => Controller::request('icon'),
'iconColor' => Controller::request('iconColor'),
'private' => Controller::request('private') ? 1 : 0
]);
Log::createLog('ADD_TOPIC', $topic->name);
Response::respondSuccess([
'topicId' => $topic->store()
]);
}
}

82
dist/api/controllers/article/add.php vendored Executable file
View File

@ -0,0 +1,82 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/add Add article
* @apiVersion 4.11.0
*
* @apiName Add article
*
* @apiGroup Article
*
* @apiDescription This path adds a new article.
*
* @apiPermission staff2
*
* @apiParam {String} title Title of the new article.
* @apiParam {String} content Content of the new article.
* @apiParam {Number} position Position of the new article.
* @apiParam {Number} topicId Id of the articles's topic.
* @apiParam {Number} images The number of images in the content
* @apiParam image_i The image file of index `i` (mutiple params accepted)
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TITLE
* @apiUse INVALID_CONTENT
* @apiUse INVALID_TOPIC
*
* @apiSuccess {Object} data Article info
* @apiSuccess {Number} data.articleId Article id
*/
class AddArticleController extends Controller {
const PATH = '/add';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'title' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_TITLE, LengthConfig::MAX_LENGTH_TITLE),
'error' => ERRORS::INVALID_TITLE
],
'content' => [
'validation' => DataValidator::content(),
'error' => ERRORS::INVALID_CONTENT
],
'topicId' => [
'validation' => DataValidator::dataStoreId('topic'),
'error' => ERRORS::INVALID_TOPIC
]
]
];
}
public function handler() {
$content = Controller::request('content', true);
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_ARTICLE);
$imagePaths = $this->uploadImages(true);
$article = new Article();
$article->setProperties([
'title' => Controller::request('title', true),
'content' => $this->replaceWithImagePaths($imagePaths, $content),
'lastEdited' => Date::getCurrentDate(),
'position' => Controller::request('position') || 1
]);
$topic = Topic::getDataStore(Controller::request('topicId'));
$topic->ownArticleList->add($article);
$topic->store();
Log::createLog('ADD_ARTICLE', $article->title);
Response::respondSuccess([
'articleId' => $article->store()
]);
}
}

50
dist/api/controllers/article/delete-topic.php vendored Executable file
View File

@ -0,0 +1,50 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/delete-topic Delete topic
* @apiVersion 4.11.0
*
* @apiName Delete topic
*
* @apiGroup Article
*
* @apiDescription This path deletes a topic.
*
* @apiPermission staff2
*
* @apiParam {Number} topicId Id of the topic.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TOPIC
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteTopicController extends Controller {
const PATH = '/delete-topic';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'topicId' => [
'validation' => DataValidator::dataStoreId('topic'),
'error' => ERRORS::INVALID_TOPIC
]
]
];
}
public function handler() {
$topic = Topic::getDataStore(Controller::request('topicId'));
Log::createLog('DELETE_TOPIC', $topic->name);
$topic->delete();
Response::respondSuccess();
}
}

50
dist/api/controllers/article/delete.php vendored Executable file
View File

@ -0,0 +1,50 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/delete Delete article
* @apiVersion 4.11.0
*
* @apiName Delete article
*
* @apiGroup Article
*
* @apiDescription This path deletes an article.
*
* @apiPermission staff2
*
* @apiParam {Number} articleId Id of the article.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TOPIC
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteArticleController extends Controller {
const PATH = '/delete';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'articleId' => [
'validation' => DataValidator::dataStoreId('article'),
'error' => ERRORS::INVALID_TOPIC
]
]
];
}
public function handler() {
$article = Article::getDataStore(Controller::request('articleId'));
Log::createLog('DELETE_ARTICLE', $article->title);
$article->delete();
Response::respondSuccess();
}
}

72
dist/api/controllers/article/edit-topic.php vendored Executable file
View File

@ -0,0 +1,72 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/edit-topic Edit topic
* @apiVersion 4.11.0
*
* @apiName Edit topic
*
* @apiGroup Article
*
* @apiDescription This path edits a topic.
*
* @apiPermission staff2
*
* @apiParam {Number} topicId Id of the topic.
* @apiParam {String} name The new name of the topic. Optional.
* @apiParam {String} icon The new icon of the topic. Optional.
* @apiParam {String} iconColor The new Icon's color of the topic. Optional.
* @apiParam {Boolean} private Indicates if the topic is not shown to users.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TOPIC
* @apiUse INVALID_NAME
*
* @apiSuccess {Object} data Empty object
*
*/
class EditTopicController extends Controller {
const PATH = '/edit-topic';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'topicId' => [
'validation' => DataValidator::dataStoreId('topic'),
'error' => ERRORS::INVALID_TOPIC
],
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
]
]
];
}
public function handler() {
$topic = Topic::getDataStore(Controller::request('topicId'));
if(Controller::request('name')) {
$topic->name = Controller::request('name', true);
}
if(Controller::request('iconColor')) {
$topic->iconColor = Controller::request('iconColor');
}
if(Controller::request('icon')) {
$topic->icon = Controller::request('icon');
}
if (Controller::request('private') !== null) {
$topic->private = Controller::request('private');
}
$topic->store();
Response::respondSuccess();
}
}

101
dist/api/controllers/article/edit.php vendored Executable file
View File

@ -0,0 +1,101 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/edit Edit article
* @apiVersion 4.11.0
*
* @apiName Edit a article
*
* @apiGroup Article
*
* @apiDescription This path edits an article.
*
* @apiPermission staff2
*
* @apiParam {Number} articleId Id of the article.
* @apiParam {Number} topicId Id of the topic of the article. Optional.
* @apiParam {String} content The new content of the article. Optional.
* @apiParam {String} title The new title of the article. Optional.
* @apiParam {Number} position The new position of the article. Optional.
* @apiParam {Number} images The number of images in the content
* @apiParam image_i The image file of index `i` (mutiple params accepted)
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TOPIC
* @apiUse INVALID_FILE
* @apiUse INVALID_TITLE
*
* @apiSuccess {Object} data Empty object
*
*/
class EditArticleController extends Controller {
const PATH = '/edit';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'articleId' => [
'validation' => DataValidator::dataStoreId('article'),
'error' => ERRORS::INVALID_TOPIC
],
'title' => [
'validation' => DataValidator::oneOf(
DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_TITLE, LengthConfig::MAX_LENGTH_TITLE),
DataValidator::nullType()
),
'error' => ERRORS::INVALID_TITLE
],
'content' => [
'validation' => DataValidator::oneOf(DataValidator::content(),DataValidator::nullType()),
'error' => ERRORS::INVALID_CONTENT
]
]
];
}
public function handler() {
$article = Article::getDataStore(Controller::request('articleId'));
if (Controller::request('topicId')) {
$newArticleTopic = Topic::getDataStore(Controller::request('topicId'));
if (!$newArticleTopic->isNull()) {
$article->topic = $newArticleTopic;
} else {
throw new RequestException(ERRORS::INVALID_TOPIC);
return;
}
}
if(Controller::request('content')) {
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_ARTICLE);
$content = Controller::request('content', true);
$imagePaths = $this->uploadImages(true);
$article->content = $this->replaceWithImagePaths($imagePaths, $content);
}
if(Controller::request('title')) {
$article->title = Controller::request('title');
}
if(Controller::request('position')) {
$article->position = Controller::request('position');
}
$article->lastEdited = Date::getCurrentDate();
$article->store();
Log::createLog('EDIT_ARTICLE', $article->title);
Response::respondSuccess();
}
}

47
dist/api/controllers/article/get-all.php vendored Executable file
View File

@ -0,0 +1,47 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /article/get-all Get all articles
* @apiVersion 4.11.0
*
* @apiName Get all articles
*
* @apiGroup Article
*
* @apiDescription This path retrieves all the articles.
*
* @apiPermission any or user
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[Topic](#api-Data_Structures-ObjectTopic)[]} data Array of topics.
*/
class GetAllArticlesController extends Controller {
const PATH = '/get-all';
const METHOD = 'POST';
public function validations() {
return [
'permission' => (Controller::isLoginMandatory()) ? 'user' : 'any',
'requestData' => []
];
}
public function handler() {
$topics = Topic::getAll();
$topicsArray = [];
foreach($topics as $topic) {
if (Controller::isStaffLogged()) {
$topicsArray[] = $topic->toArray();
} else if (!$topic->private) {
$topicsArray[] = $topic->toArray();
}
}
Response::respondSuccess($topicsArray);
}
}

19
dist/api/controllers/staff.php vendored Executable file
View File

@ -0,0 +1,19 @@
<?php
$systemControllerGroup = new ControllerGroup();
$systemControllerGroup->setGroupPath('/staff');
$systemControllerGroup->addController(new GetStaffController);
$systemControllerGroup->addController(new AssignStaffController);
$systemControllerGroup->addController(new UnAssignStaffController);
$systemControllerGroup->addController(new GetTicketStaffController);
$systemControllerGroup->addController(new GetNewTicketsStaffController);
$systemControllerGroup->addController(new GetAllTicketsStaffController);
$systemControllerGroup->addController(new SearchTicketStaffController);
$systemControllerGroup->addController(new InviteStaffController);
$systemControllerGroup->addController(new GetAllStaffController);
$systemControllerGroup->addController(new DeleteStaffController);
$systemControllerGroup->addController(new EditStaffController);
$systemControllerGroup->addController(new LastEventsStaffController);
$systemControllerGroup->addController(new ResendInviteStaffController);
$systemControllerGroup->finalize();

95
dist/api/controllers/staff/assign-ticket.php vendored Executable file
View File

@ -0,0 +1,95 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/assign-ticket Assign ticket
* @apiVersion 4.11.0
*
* @apiName Assign ticket
*
* @apiGroup Staff
*
* @apiDescription This path assigns a ticket to a staff member.
*
* @apiPermission staff1
*
* @apiParam {Number} ticketNumber The number of the ticket to assign.
* @apiParam {Number} staffId The id of the staff.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
* @apiUse TICKET_ALREADY_ASSIGNED
* @apiUse INVALID_DEPARTMENT
*
* @apiSuccess {Object} data Empty object
*
*/
class AssignStaffController extends Controller {
const PATH = '/assign-ticket';
const METHOD = 'POST';
private $ticket;
private $staffToAssign;
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
}
public function handler() {
$ticketNumber = Controller::request('ticketNumber');
$staffId = Controller::request('staffId');
$this->ticket = Ticket::getByTicketNumber($ticketNumber);
$user = Controller::getLoggedUser();
if($staffId) {
$this->staffToAssign = Staff::getDataStore($staffId, 'id');
if($this->staffToAssign->isNull()) {
throw new RequestException(ERRORS::INVALID_STAFF);
}
if(!$this->staffToAssign->sharedDepartmentList->includesId($this->ticket->department->id)) {
throw new RequestException(ERRORS::INVALID_DEPARTMENT);
}
} else {
$this->staffToAssign = Controller::getLoggedUser();
}
if($this->ticket->owner) {
throw new RequestException(ERRORS::TICKET_ALREADY_ASSIGNED);
}
if(!$user->canManageTicket($this->ticket)) {
throw new RequestException(ERRORS::NO_PERMISSION);
} else {
$this->staffToAssign->sharedTicketList->add($this->ticket);
$this->ticket->owner = $this->staffToAssign;
$this->ticket->totalOwners++;
$this->ticket->unread = !$this->ticket->isAuthor($this->staffToAssign);
$event = Ticketevent::getEvent(Ticketevent::ASSIGN);
$event->setProperties(array(
'authorStaff' => Controller::getLoggedUser(),
'date' => Date::getCurrentDate(),
'content' => $this->staffToAssign->name,
));
$this->ticket->addEvent($event);
$this->ticket->store();
$this->staffToAssign->store();
Response::respondSuccess();
}
}
}

68
dist/api/controllers/staff/delete.php vendored Executable file
View File

@ -0,0 +1,68 @@
<?php
use Respect\Validation\Validator as DataValidator;
use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /staff/delete Delete staff
* @apiVersion 4.11.0
*
* @apiName Delete staff
*
* @apiGroup Staff
*
* @apiDescription This path deletes a staff member.
*
* @apiPermission staff3
*
* @apiParam {Number} staffId The id of the staff member.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_STAFF
*
* @apiSuccess {Object} data Empty object
*
*/
DataValidator::with('CustomValidations', true);
class DeleteStaffController extends Controller {
const PATH = '/delete';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'staffId' =>[
'validation' => DataValidator::dataStoreId('staff'),
'error' => ERRORS::INVALID_STAFF
]
]
];
}
public function handler() {
$staffId = Controller::request('staffId');
$staff = Staff::getDataStore($staffId);
if($staffId === Controller::getLoggedUser()->id) {
throw new RequestException(ERRORS::YOU_CAN_NOT_DELETE_YOURSELF);
}
foreach($staff->sharedTicketList as $ticket) {
$ticket->owner = null;
$ticket->unreadStaff = true;
$ticket->store();
}
foreach($staff->sharedDepartmentList as $department) {
$department->owners--;
$department->store();
}
RedBean::exec('DELETE FROM log WHERE author_staff_id = ?', [$staffId]);
$staff->delete();
Response::respondSuccess();
}
}

202
dist/api/controllers/staff/edit.php vendored Executable file
View File

@ -0,0 +1,202 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/edit Edit staff
* @apiVersion 4.11.0
*
* @apiName Edit staff
*
* @apiGroup Staff
*
* @apiDescription This path edits a staff member.
*
* @apiPermission staff1
*
* @apiParam {Number} staffId Id of the staff.
* @apiParam {String} departments The name of the departments to change. Optional.
* @apiParam {String} email The new email of the staff member. Optional.
* @apiParam {String} password The new password of the staff member. Optional.
* @apiParam {Number} level The new level of the staff member. Optional.
* @apiParam {Boolean} sendEmailOnNewTicket Indicates if it receives an email when a new ticket is created.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_EMAIL
* @apiUse INVALID_PASSWORD
* @apiUse INVALID_LEVEL
* @apiUse INVALID_STAFF
*
* @apiSuccess {Object} data Empty object
*
*/
class EditStaffController extends Controller {
const PATH = '/edit';
const METHOD = 'POST';
private $staffInstance;
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'email' => [
'validation' => DataValidator::oneOf(DataValidator::email(), DataValidator::falseVal()),
'error' => ERRORS::INVALID_EMAIL
],
'password' => [
'validation' => DataValidator::oneOf(
DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_PASSWORD, LengthConfig::MAX_LENGTH_PASSWORD),
DataValidator::falseVal()
),
'error' => ERRORS::INVALID_PASSWORD
],
'level' => [
'validation' => DataValidator::oneOf(DataValidator::between(1, 3, true), DataValidator::falseVal()),
'error' => ERRORS::INVALID_LEVEL
]
]
];
}
public function handler() {
$staffId = Controller::request('staffId');
if(!$staffId) {
$this->staffInstance = Controller::getLoggedUser();
} else if(Controller::isStaffLogged(3) || ((Controller::isStaffLogged() && Controller::getLoggedUser()->id == $staffId)) ) {
$this->staffInstance = Staff::getDataStore($staffId, 'id');
if($this->staffInstance->isNull()) {
throw new RequestException(ERRORS::INVALID_STAFF);
}
} else {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(Controller::request('departments')) {
$this->updateDepartmentsOwners();
}
$this->editInformation();
Response::respondSuccess();
}
private function editInformation() {
if(Controller::request('email')) {
$newEmail = Controller::request('email');
$this->verifyEmail($newEmail);
$this->staffInstance->email = $newEmail;
}
if(Controller::request('password')) {
$this->staffInstance->password = Hashing::hashPassword(Controller::request('password'));
}
if(Controller::request('level') && Controller::isStaffLogged(3) && !$this->isModifyingCurrentStaff()) {
$this->staffInstance->level = Controller::request('level');
}
if(Controller::request('departments') && Controller::isStaffLogged(3)) {
$departmentList = $this->getDepartmentList();
$ticketList = $this->staffInstance->sharedTicketList;
$this->staffInstance->sharedDepartmentList = $departmentList;
foreach($ticketList as $ticket) {
if(!$departmentList->includesId($ticket->department->id)) {
if($ticket->isOwner($this->staffInstance) ) {
$ticket->owner = null;
}
if(!$ticket->isAuthor($this->staffInstance)) {
$this->staffInstance->sharedTicketList->remove($ticket);
}
$ticket->store();
}
}
}
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_PROFILE);
if($fileUploader = $this->uploadFile(true)) {
$this->staffInstance->profilePic = ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null;
}
if(Controller::request('sendEmailOnNewTicket') !== null && Controller::request('sendEmailOnNewTicket') !== '' && $this->isModifyingCurrentStaff()) {
$this->staffInstance->sendEmailOnNewTicket = intval(Controller::request('sendEmailOnNewTicket'));
}
$this->staffInstance->store();
}
private function verifyEmail($email){
$staff = Staff::getDataStore($email,'email');
$user = User::getDataStore($email,'email');
if($user->email == $email){
throw new RequestException(ERRORS::INVALID_EMAIL);
}
if($staff->email == $email && $this->staffInstance->email != $email){
throw new RequestException(ERRORS::INVALID_EMAIL);
}
}
private function getDepartmentList() {
$listDepartments = new DataStoreList();
$departmentIds = json_decode(Controller::request('departments'));
foreach($departmentIds as $id) {
$department = Department::getDataStore($id);
$listDepartments->add($department);
}
return $listDepartments;
}
private function updateDepartmentsOwners() {
$list1 = $this->staffInstance->sharedDepartmentList;
$list2 = $this->getDepartmentList();
foreach ($list1 as $department1) {
$match = false;
foreach ($list2 as $department2) {
if($department1->id == $department2->id) {
$match = true;
}
}
if(!$match) {
$department1->owners--;
$department1->store();
}
}
foreach ($list2 as $department2) {
$match = false;
foreach ($list1 as $department1) {
if($department2->id == $department1->id) {
$match = true;
}
}
if(!$match) {
$department2->owners++;
$department2->store();
}
}
}
private function isModifyingCurrentStaff() {
return !Controller::request('staffId') || Controller::request('staffId') === Controller::getLoggedUser()->id;
}
}

114
dist/api/controllers/staff/get-all-tickets.php vendored Executable file
View File

@ -0,0 +1,114 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-all-tickets Get all tickets according to search
* @apiVersion 4.11.0
*
* @apiName Get all tickets
*
* @apiGroup Staff
*
* @apiDescription This path retrieves all tickets according to search and opened/closed filters.
*
* @apiPermission staff1
*
* @apiParam {Number} page The page number.
* @apiParam {String} query Query string to search.
* @apiParam {Boolean} closed Include closed tickets.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PAGE
*
* @apiSuccess {Object} data Information about a tickets and quantity of pages.
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data.tickets Array of tickets of the current page.
* @apiSuccess {Number} data.pages Quantity of pages.
*
*/
class GetAllTicketsStaffController extends Controller {
const PATH = '/get-all-tickets';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'page' => [
'validation' => DataValidator::numeric(),
'error' => ERRORS::INVALID_PAGE
]
]
];
}
public function handler() {
if (Ticket::isTableEmpty()) {
Response::respondSuccess([
'tickets' => [],
'pages' => 0
]);
return;
}
Response::respondSuccess([
'tickets' => $this->getTicketList()->toArray(true),
'pages' => $this->getTotalPages()
]);
}
private function getTicketList() {
$page = Controller::request('page');
$query = $this->getSearchQuery();
$query .= $this->getStaffDepartmentsQueryFilter();
$query .= $this->getClosedFilter();
$query .= "ORDER BY CASE WHEN (title LIKE ?) THEN 1 ELSE 2 END ASC, id DESC LIMIT 10 OFFSET " . (($page-1)*10);
return Ticket::find($query, [
Controller::request('query') . '%',
'%' . Controller::request('query') . '%',
Controller::request('query') . '%'
]);
}
private function getSearchQuery() {
$page = Controller::request('page');
$query = " (title LIKE ? OR title LIKE ?) AND ";
return $query;
}
private function getTotalPages() {
$query = $this->getSearchQuery();
$query .= $this->getStaffDepartmentsQueryFilter();
$query .= $this->getClosedFilter();
return ceil(Ticket::count($query, [
Controller::request('query') . '%',
'%' . Controller::request('query') . '%'
]) / 10);
}
private function getStaffDepartmentsQueryFilter() {
$user = Controller::getLoggedUser();
$query = ' (';
foreach ($user->sharedDepartmentList as $department) {
$query .= 'department_id=' . $department->id . ' OR ';
}
$query .= 'FALSE) ';
return $query;
}
private function getClosedFilter() {
$closed = Controller::request('closed')*1;
if ($closed) {
return '';
} else {
return " AND (closed = '0')";
}
}
}

63
dist/api/controllers/staff/get-all.php vendored Executable file
View File

@ -0,0 +1,63 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-all Get all staffs
* @apiVersion 4.11.0
*
* @apiName Get all staffs
*
* @apiGroup Staff
*
* @apiDescription This path retrieves information about all the staff member.
*
* @apiPermission staff1
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[Staff](#api-Data_Structures-ObjectStaff)[]} data Array of staff members.
*
*/
class GetAllStaffController extends Controller {
const PATH ='/get-all';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => []
];
}
public function handler() {
$staffs = Staff::getAll();
$staffArray = [];
foreach($staffs as $staff) {
$assignedTickets = 0;
$closedTickets = 0;
foreach ($staff->sharedTicketList as $ticket) {
if(($ticket->closed) && ($ticket->owner_id == $staff->id)) $closedTickets++;
if($ticket->owner_id == $staff->id) $assignedTickets++;
}
$staffArray[] = [
'id' => $staff->id,
'name' => $staff->name,
'email' => $staff->email,
'profilePic' => $staff->profilePic,
'level' => $staff->level,
'departments' => $staff->sharedDepartmentList->toArray(),
'assignedTickets' => $assignedTickets,
'closedTickets' => $closedTickets,
'lastLogin' => $staff->lastLogin
];
}
Response::respondSuccess($staffArray);
}
}

View File

@ -0,0 +1,96 @@
<?php
use RedBeanPHP\Facade as RedBean;
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-new-tickets Get new tickets
* @apiVersion 4.11.0
*
* @apiName Get new tickets
*
* @apiGroup Staff
*
* @apiDescription This path retrieves the new tickets of the departments the staff has assigned.
*
* @apiPermission staff1
*
* @apiParam {Number} page The page number.
* @apiParam {Number} departmentId The id of the department searched
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PAGE
* @apiUse INVALID_PAGE_SIZE
*
* @apiSuccess {Object} data Information about a tickets and quantity of pages.
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data.tickets Array of new tickets of the current page.
* @apiSuccess {Number} data.page Number of current page.
* @apiSuccess {Number} data.pages Quantity of pages.
*
*/
class GetNewTicketsStaffController extends Controller {
const PATH = '/get-new-tickets';
const METHOD = 'POST';
public function validations() {
return[
'permission' => 'staff_1',
'requestData' => [
'page' => [
'validation' => DataValidator::numeric(),
'error' => ERRORS::INVALID_PAGE
],
'pageSize' => [
'validation' => DataValidator::oneOf(DataValidator::intVal()->between(5, 50),DataValidator::nullType()),
'error' => ERRORS::INVALID_PAGE_SIZE
]
]
];
}
public function handler() {
$page = Controller::request('page');
$departmentId = Controller::request('departmentId');
$pageSize = Controller::request('pageSize') ? Controller::request('pageSize') : 10;
if (Ticket::isTableEmpty()) {
Response::respondSuccess([
'tickets' => [],
'page' => $page,
'pages' => 0
]);
return;
}
$user = Controller::getLoggedUser();
$query = ' (';
foreach ($user->sharedDepartmentList as $department) {
$query .= 'department_id=' . $department->id . ' OR ';
}
$ownerExists = RedBean::exec('SHOW COLUMNS FROM ticket LIKE \'owner_id\'');
if($ownerExists != 0) {
$query .= 'FALSE) AND closed = 0 AND owner_id IS NULL';
} else {
$query .= 'FALSE) AND closed = 0';
}
if($departmentId) {
$query .= ' AND department_id=' . $departmentId;
}
$countTotal = Ticket::count($query);
$query .= ' ORDER BY unread_staff DESC';
$query .= ' LIMIT ' . $pageSize . ' OFFSET ' . ($page-1)*10;
$ticketList = Ticket::find($query);
Response::respondSuccess([
'tickets' => $ticketList->toArray(true),
'page' => $page,
'pages' => ceil($countTotal / $pageSize)
]);
}
}

85
dist/api/controllers/staff/get-tickets.php vendored Executable file
View File

@ -0,0 +1,85 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/get-tickets Get tickets
* @apiVersion 4.11.0
*
* @apiName Get tickets
*
* @apiGroup Staff
*
* @apiDescription This path retrieves the tickets assigned to the current staff member.
*
* @apiPermission staff1
*
* @apiParam {Number} page The page number.
* @apiParam {bool} closed Include closed tickets in the response.
* @apiParam {Number} departmentId The id of the department searched
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PAGE
* @apiUse INVALID_PAGE_SIZE
*
* @apiSuccess {Object} data Information about a tickets and quantity of pages.
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data.tickets Array of tickets assigned to the staff of the current page.
* @apiSuccess {Number} data.page Number of current page.
* @apiSuccess {Number} data.pages Quantity of pages.
*
*/
class GetTicketStaffController extends Controller {
const PATH = '/get-tickets';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'page' => [
'validation' => DataValidator::numeric(),
'error' => ERRORS::INVALID_PAGE
],
'pageSize' => [
'validation' => DataValidator::oneOf(DataValidator::intVal()->between(5, 50),DataValidator::nullType()),
'error' => ERRORS::INVALID_PAGE_SIZE
]
]
];
}
public function handler() {
$user = Controller::getLoggedUser();
$closed = Controller::request('closed');
$page = Controller::request('page');
$departmentId = Controller::request('departmentId');
$pageSize = Controller::request('pageSize') ? Controller::request('pageSize') : 10;
$offset = ($page-1)*$pageSize;
$condition = 'TRUE';
$bindings = [];
if($departmentId) {
$condition .= ' AND department_id = ?';
$bindings[] = $departmentId;
}
if(!$closed) {
$condition .= ' AND closed = ?';
$bindings[] = '0';
}
$countTotal = $user->withCondition($condition, $bindings)->countShared('ticket');
$condition .= ' LIMIT ' . $pageSize . ' OFFSET ?';
$bindings[] = $offset;
$tickets = $user->withCondition($condition, $bindings)->sharedTicketList->toArray(true);
Response::respondSuccess([
'tickets' => $tickets,
'page' => $page,
'pages' => ceil($countTotal / $pageSize)
]);
}
}

74
dist/api/controllers/staff/get.php vendored Executable file
View File

@ -0,0 +1,74 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/get Get staff
* @apiVersion 4.11.0
*
* @apiName Get staff
*
* @apiGroup Staff
*
* @apiDescription This path retrieves information about a staff member.
*
* @apiPermission staff1
*
* @apiParam {Number} staffId The id of the staff member to be searched.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {Object} data Information about a staff member
* @apiSuccess {String} data.name Name of the staff member
* @apiSuccess {String} data.email Elmail of the staff member
* @apiSuccess {String} data.profilePic Profile pic filename of staff member
* @apiSuccess {Number} data.level Level of staff member
* @apiSuccess {Boolean} data.staff Indicates that it is a staff (always true)
* @apiSuccess {[Department](#api-Data_Structures-ObjectDepartment)[]} data.departments Array of departments that has assigned.
* @apiSuccess {Boolean} data.sendEmailOnNewTicket Indicates if this member receives a mail when a ticket is created.
*
*/
class GetStaffController extends Controller {
const PATH = '/get';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => []
];
}
public function handler() {
$user = Controller::getLoggedUser();
$userId = Controller::request('staffId');
$userRow = Staff::getDataStore($userId);
if($user->level == 3 && !$userRow->isNull()) {
$user = $userRow;
}
$parsedDepartmentList = [];
$departmentList = $user->sharedDepartmentList;
foreach($departmentList as $department) {
$parsedDepartmentList[] = [
'id' => $department->id,
'name' => $department->name,
'private' => $department->private
];
}
Response::respondSuccess([
'name' => $user->name,
'email' => $user->email,
'profilePic' => $user->profilePic,
'level' => $user->level,
'staff' => true,
'departments' => $parsedDepartmentList,
'sendEmailOnNewTicket' => $user->sendEmailOnNewTicket
]);
}
}

149
dist/api/controllers/staff/invite.php vendored Executable file
View File

@ -0,0 +1,149 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/invite Invite staff
* @apiVersion 4.11.0
*
* @apiName Invite staff
*
* @apiGroup Staff
*
* @apiDescription This path invites a new staff member.
*
* @apiPermission staff3
*
* @apiParam {String} name The name of the new staff member.
* @apiParam {String} email The email of the new staff member.
* @apiParam {Number} level The level of the new staff member.
* @apiParam {String} profilePic The profile pic of the new staff member.
* @apiParam {Number[]} departments The departments that will have assigned the new staff member.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
* @apiUse INVALID_EMAIL
* @apiUse INVALID_LEVEL
* @apiUse ALREADY_A_STAFF
* @apiUse INVALID_DEPARTMENT
*
* @apiSuccess {Object} data Staff info object
* @apiSuccess {Number} data.id Staff id
*
*/
class InviteStaffController extends Controller {
const PATH = '/invite';
const METHOD = 'POST';
private $name;
private $email;
private $profilePic;
private $level;
private $departments;
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
],
'email' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
],
'level' => [
'validation' => DataValidator::between(1, 3, true),
'error' => ERRORS::INVALID_LEVEL
],
'departments' => [
'validation' => DataValidator::oneOf(DataValidator::validDepartmentsId(),DataValidator::nullType()),
'error' => ERRORS::INVALID_DEPARTMENT
]
]
];
}
public function handler() {
$this->storeRequestData();
$staffRow = Staff::getDataStore($this->email, 'email');
if(!$staffRow->isNull()) throw new RequestException(ERRORS::ALREADY_A_STAFF);
$staff = new Staff();
$staff->setProperties([
'name'=> $this->name,
'email' => $this->email,
'password'=> Hashing::hashPassword(Hashing::generateRandomToken()),
'profilePic' => $this->profilePic,
'level' => $this->level,
'sharedDepartmentList' => $this->getDepartmentList()
]);
$this->addOwner();
$this->token = Hashing::generateRandomToken();
$recoverPassword = new RecoverPassword();
$recoverPassword->setProperties(array(
'email' => $this->email,
'token' => $this->token,
'staff' => true
));
$recoverPassword->store();
$this->sendInvitationMail();
Response::respondSuccess([
'id' => $staff->store()
]);
Log::createLog('INVITE', $this->name);
}
public function storeRequestData() {
$this->name = Controller::request('name');
$this->email = Controller::request('email');
$this->profilePic = Controller::request('profilePic');
$this->level = Controller::request('level');
$this->departments = Controller::request('departments');
}
public function getDepartmentList() {
$listDepartments = new DataStoreList();
$departmentIds = json_decode($this->departments);
foreach($departmentIds as $id) {
$department = Department::getDataStore($id);
$listDepartments->add($department);
}
return $listDepartments;
}
public function addOwner() {
$departmentIds = json_decode($this->departments);
foreach($departmentIds as $id) {
$departmentRow = Department::getDataStore($id);
$departmentRow->owners++;
$departmentRow->store();
}
}
public function sendInvitationMail() {
$mailSender = MailSender::getInstance();
$mailSender->setTemplate(MailTemplate::USER_INVITE, [
'to' => $this->email,
'name' => $this->name,
'url' => Setting::getSetting('url')->getValue(),
'token' => $this->token
]);
$mailSender->send();
}
}

59
dist/api/controllers/staff/last-events.php vendored Executable file
View File

@ -0,0 +1,59 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/last-events Get last events
* @apiVersion 4.11.0
*
* @apiName Get last events
*
* @apiGroup Staff
*
* @apiDescription This path retrieves the last events.
*
* @apiPermission staff1
*
* @apiParam {Number} page The page number.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PAGE
*
* @apiSuccess {[TicketEvent](#api-Data_Structures-ObjectTicketevent)[]} data Array of last events
*
*/
class LastEventsStaffController extends Controller {
const PATH = '/last-events';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'page' => [
'validation' => DataValidator::numeric(),
'error' => ERRORS::INVALID_PAGE
]
]
];
}
public function handler() {
$page = Controller::request('page');
$user = Controller::getLoggedUser();
$query = ' (';
foreach ($user->sharedTicketList as $ticket) {
$query .= 'ticket_id =' . $ticket->id . ' OR ';
}
$query = substr($query,0,-3);
$query .= ') ORDER BY id desc LIMIT ? OFFSET ?' ;
if(Ticketevent::count() && !$user->sharedTicketList->isEmpty()) {
$eventList = Ticketevent::find($query, [10, 10*($page-1)]);
Response::respondSuccess($eventList->toArray());
} else {
Response::respondSuccess([]);
}
}
}

View File

@ -0,0 +1,71 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/resend-invite-staff resend invite staff
* @apiVersion 4.11.0
*
* @apiName Resend resend invite staff
*
* @apiGroup Staff
*
* @apiDescription This path resend invitation to a staff
*
* @apiPermission staff3
*
* @apiParam {String} email The email of the new staff member.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_EMAIL
*
* @apiSuccess {Object} data Empty object
*
*/
class ResendInviteStaffController extends Controller {
const PATH = '/resend-invite-staff';
const METHOD = 'POST';
private $email;
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'email' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
]
]
];
}
public function handler() {
$this->email = Controller::request('email');
$staffRow = Staff::getDataStore($this->email, 'email');
$recoverPassword = RecoverPassword::getDataStore($this->email, 'email');
if($staffRow->isNull() || $recoverPassword->isNull() || $recoverPassword->staff != 1) throw new RequestException(ERRORS::INVALID_EMAIL);
$this->sendInvitationMail($staffRow, $recoverPassword->token);
Response::respondSuccess();
Log::createLog('INVITE', $staffRow->name);
}
public function sendInvitationMail($staffRow, $token) {
$mailSender = MailSender::getInstance();
$mailSender->setTemplate(MailTemplate::USER_INVITE, [
'to' => $staffRow->email,
'name' => $staffRow->name,
'url' => Setting::getSetting('url')->getValue(),
'token' => $token
]);
$mailSender->send();
}
}

100
dist/api/controllers/staff/search-tickets.php vendored Executable file
View File

@ -0,0 +1,100 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /staff/search-tickets Search tickets
* @apiVersion 4.11.0
*
* @apiName Search tickets
*
* @apiGroup Staff
*
* @apiDescription This path search some tickets.
*
* @apiPermission staff1
*
* @apiParam {String} query Query string to search.
* @apiParam {Number} page The page number.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_QUERY
* @apiUse INVALID_PAGE
*
* @apiSuccess {Object} data Information about tickets
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data.tickets Array of tickets found
* @apiSuccess {Number} data.pages Number of pages
*
*/
class SearchTicketStaffController extends Controller {
const PATH = '/search-tickets';
const METHOD = 'POST';
public function validations() {
return[
'permission' => 'staff_1',
'requestData' => [
'query' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_QUERY),
'error' => ERRORS::INVALID_QUERY
],
'page' => [
'validation' => DataValidator::numeric(),
'error' => ERRORS::INVALID_PAGE
]
]
];
}
public function handler() {
Response::respondSuccess([
'tickets' => $this->getTicketList()->toArray(),
'pages' => $this->getTotalPages()
]);
}
private function getTicketList() {
$query = $this->getSearchQuery();
return Ticket::find($query, [
Controller::request('query') . '%',
'%' . Controller::request('query') . '%',
Controller::request('query') . '%'
]);
}
private function getSearchQuery() {
$page = Controller::request('page');
$query = " (title LIKE ? OR title LIKE ?) AND ";
$query .= $this->getStaffDepartmentsQueryFilter();
$query .= "ORDER BY CASE WHEN (title LIKE ?) THEN 1 ELSE 2 END ASC LIMIT 10 OFFSET " . (($page-1)*10);
return $query;
}
private function getTotalPages() {
$query = " (title LIKE ? OR title LIKE ?) AND ";
$query .= $this->getStaffDepartmentsQueryFilter();
$ticketQuantity = Ticket::count($query, [
Controller::request('query') . '%',
'%' . Controller::request('query') . '%'
]);
return ceil($ticketQuantity / 10);
}
private function getStaffDepartmentsQueryFilter() {
$user = Controller::getLoggedUser();
$query = ' (';
foreach ($user->sharedDepartmentList as $department) {
$query .= 'department_id=' . $department->id . ' OR ';
}
$query = substr($query, 0, -3);
$query .= ') ';
return $query;
}
}

View File

@ -0,0 +1,81 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /staff/un-assign-ticket Un-assign ticket
* @apiVersion 4.11.0
*
* @apiName Un-assign ticket
*
* @apiGroup Staff
*
* @apiDescription This path un-assigns a ticket to the current staff member.
*
* @apiPermission staff1
*
* @apiParam {Number} ticketNumber Ticket number to un-assign.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
*
* @apiSuccess {Object} data Empty object
*
*/
class UnAssignStaffController extends Controller {
const PATH = '/un-assign-ticket';
const METHOD = 'POST';
private $user;
public function __construct($user=null) {
$this->user = $user;
}
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
}
public function handler() {
$ticketNumber = Controller::request('ticketNumber');
$user = ($this->user? $this->user : Controller::getLoggedUser());
$ticket = Ticket::getByTicketNumber($ticketNumber);
$owner = $ticket->owner;
if(!$user->canManageTicket($ticket)) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if($owner) {
if(!$ticket->isAuthor($owner)) {
$owner->sharedTicketList->remove($ticket);
$owner->store();
}
$ticket->owner = null;
$ticket->unread = !$ticket->isAuthor($user);
$event = Ticketevent::getEvent(Ticketevent::UN_ASSIGN);
$event->setProperties(array(
'authorStaff' => $user,
'date' => Date::getCurrentDate(),
'content' => $owner->name
));
$ticket->addEvent($event);
$ticket->store();
Response::respondSuccess();
} else {
throw new RequestException(ERRORS::NO_PERMISSION);
}
}
}

39
dist/api/controllers/system.php vendored Executable file
View File

@ -0,0 +1,39 @@
<?php
$systemControllerGroup = new ControllerGroup();
$systemControllerGroup->setGroupPath('/system');
$systemControllerGroup->addController(new CheckRequirementsController);
$systemControllerGroup->addController(new InitDatabaseController);
$systemControllerGroup->addController(new InitSettingsController);
$systemControllerGroup->addController(new InitAdminController);
$systemControllerGroup->addController(new InstallationDoneController);
$systemControllerGroup->addController(new GetSettingsController);
$systemControllerGroup->addController(new EditSettingsController);
$systemControllerGroup->addController(new AddDepartmentController);
$systemControllerGroup->addController(new EditDepartmentController);
$systemControllerGroup->addController(new DeleteDepartmentController);
$systemControllerGroup->addController(new GetLogsController);
$systemControllerGroup->addController(new GetMailTemplateListController);
$systemControllerGroup->addController(new GetMailTemplateController);
$systemControllerGroup->addController(new EditMailTemplateController);
$systemControllerGroup->addController(new RecoverMailTemplateController);
$systemControllerGroup->addController(new DisableRegistrationController);
$systemControllerGroup->addController(new EnableRegistrationController);
$systemControllerGroup->addController(new AddAPIKeyController);
$systemControllerGroup->addController(new DeleteAPIKeyController);
$systemControllerGroup->addController(new GetAPIKeysController);
$systemControllerGroup->addController(new DeleteAllUsersController);
$systemControllerGroup->addController(new BackupDatabaseController);
$systemControllerGroup->addController(new DownloadController);
$systemControllerGroup->addController(new CSVImportController);
$systemControllerGroup->addController(new EnableMandatoryLoginController);
$systemControllerGroup->addController(new DisableMandatoryLoginController);
$systemControllerGroup->addController(new TestSMTPController);
$systemControllerGroup->addController(new TestIMAPController);
$systemControllerGroup->addController(new EmailPollingController);
$systemControllerGroup->addController(new AddCustomFieldController);
$systemControllerGroup->addController(new DeleteCustomFieldController);
$systemControllerGroup->addController(new GetCustomFieldsController);
$systemControllerGroup->addController(new GetStatsController);
$systemControllerGroup->finalize();

74
dist/api/controllers/system/add-api-key.php vendored Executable file
View File

@ -0,0 +1,74 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/add-api-key Add APIKey
* @apiVersion 4.11.0
*
* @apiName Add APIKey
*
* @apiGroup System
*
* @apiDescription This path creates a new APIKey.
*
* @apiPermission staff3
*
* @apiParam {String} name Name of the new APIKey.
* @apiParam {Boolean} canCreateUsers canCreateUsers determinates if the apikey has the permission to create users
* @apiParam {Boolean} canCreateTickets canCreateTickets determinates if the apikey has the permission to create tickets
* @apiParam {Boolean} canCheckTickets canCheckTickets determinates if the apikey has the permission to check tickets
* @apiParam {Boolean} shouldReturnTicketNumber shouldReturnTicketNumber determinates if the apikey has the permission of returning ticket number after ticket creation
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
* @apiUse NAME_ALREADY_USED
*
* @apiSuccess {String} data Token of the APIKey.
*
*/
class AddAPIKeyController extends Controller {
const PATH = '/add-api-key';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME)->alnum(),
'error' => ERRORS::INVALID_NAME
]
]
];
}
public function handler() {
$apiInstance = new APIKey();
$name = Controller::request('name');
$canCreateUsers = (bool)Controller::request('canCreateUsers');
$canCreateTickets = (bool)Controller::request('canCreateTickets');
$canCheckTickets = (bool)Controller::request('canCheckTickets');
$shouldReturnTicketNumber = (bool)Controller::request('shouldReturnTicketNumber');
$keyInstance = APIKey::getDataStore($name, 'name');
if($keyInstance->isNull()){
$token = Hashing::generateRandomToken();
$apiInstance->setProperties([
'name' => $name,
'token' => $token,
'canCreateUsers' => $canCreateUsers,
'canCreateTickets' => $canCreateTickets,
'canCheckTickets' => $canCheckTickets,
'shouldReturnTicketNumber' => $shouldReturnTicketNumber
]);
$apiInstance->store();
Response::respondSuccess($token);
} else {
throw new RequestException(ERRORS::NAME_ALREADY_USED);
}
}
}

View File

@ -0,0 +1,105 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/add-custom-field Add a custom field
* @apiVersion 4.11.0
*
* @apiName Add Custom field
*
* @apiGroup System
*
* @apiDescription This path creates a Custom field.
*
* @apiPermission staff2
*
* @apiParam {Number} name Name of the custom field.
* @apiParam {String} type One of 'text' and 'select'.
* @apiParam {String} options JSON array of strings with the option names.
* @apiParam {String} description Description of the custom field.
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
* @apiUse INVALID_CUSTOM_FIELD_TYPE
* @apiUse INVALID_CUSTOM_FIELD_OPTIONS
* @apiUse CUSTOM_FIELD_ALREADY_EXISTS
*
* @apiSuccess {Object} data Empty object
*
*/
class AddCustomFieldController extends Controller {
const PATH = '/add-custom-field';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
],
'description' => [
'validation' => DataValidator::length(LengthConfig::MIN_LENGTH_DESCRIPTION, LengthConfig::MAX_LENGTH_DESCRIPTION),
'error' => ERRORS::INVALID_DESCRIPTION
],
'type' => [
'validation' => DataValidator::oneOf(
DataValidator::equals('text'),
DataValidator::equals('select')
),
'error' => ERRORS::INVALID_CUSTOM_FIELD_TYPE
],
'options' => [
'validation' => DataValidator::oneOf(
DataValidator::ValidOptions(),
DataValidator::nullType()
),
'error' => ERRORS::INVALID_CUSTOM_FIELD_OPTIONS
]
]
];
}
public function handler() {
$name = Controller::request('name', true);
$type = Controller::request('type');
$description = Controller::request('description', true);
$options = Controller::request('options');
if(!Customfield::getDataStore($name, 'name')->isNull()) {
throw new Exception(ERRORS::CUSTOM_FIELD_ALREADY_EXISTS);
}
$customField = new Customfield();
$customField->setProperties([
'name' => $name,
'type' => $type,
'description' => $description,
'ownCustomfieldoptionList' => $this->getOptionList($options)
]);
$customField->store();
Response::respondSuccess();
}
public function getOptionList($optionNames) {
$options = new DataStoreList();
if(!$optionNames) return $options;
$optionNames = json_decode($optionNames);
foreach($optionNames as $optionName) {
$option = new Customfieldoption();
$option->setProperties([
'name' => $optionName,
]);
$options->add($option);
}
return $options;
}
}

View File

@ -0,0 +1,62 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/add-department Add department
* @apiVersion 4.11.0
*
* @apiName Add department
*
* @apiGroup System
*
* @apiDescription This path create a new department.
*
* @apiPermission staff3
*
* @apiParam {String} name Name of the new department.
* @apiParam {Boolean} private Indicates if the deparment is not shown to users.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {Object} data Empty object
*
*/
class AddDepartmentController extends Controller {
const PATH = '/add-department';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::AllOf(
DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
DataValidator::ValidDepartmentName()
),
'error' => ERRORS::INVALID_NAME
]
]
];
}
public function handler() {
$name = Controller::request('name', true);
$private = Controller::request('private');
$departmentInstance = new Department();
$departmentInstance->setProperties([
'name' => $name ,
'private' => $private ? 1 : 0
]);
$departmentInstance->store();
Log::createLog('ADD_DEPARTMENT', $name);
Response::respondSuccess();
}
}

View File

@ -0,0 +1,44 @@
<?php
use Ifsnop\Mysqldump as IMysqldump;
/**
* @api {post} /system/backup-database Backup database
* @apiVersion 4.11.0
*
* @apiName Backup database
*
* @apiGroup System
*
* @apiDescription This path does a backup of the database.
*
* @apiPermission staff3
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {File} file File of the backup
*
*/
class BackupDatabaseController extends Controller {
const PATH = '/backup-database';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$fileDownloader = FileDownloader::getInstance();
$fileDownloader->setFileName('backup.sql');
$mysqlDump = new IMysqldump\Mysqldump('mysql:host='. MYSQL_HOST . ';port=' . MYSQL_PORT . ';dbname=' . MYSQL_DATABASE , MYSQL_USER, MYSQL_PASSWORD);
$mysqlDump->start($fileDownloader->getFullFilePath());
if($fileDownloader->download()) {
$fileDownloader->eraseFile();
}
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* @api {post} /system/check-requirements Checks requirements
* @apiVersion 4.11.0
*
* @apiName Check requirements
*
* @apiGroup System
*
* @apiDescription This path checks and retrieves the current requirements for the installation.
*
* @apiPermission any
*
* @apiSuccess {Object} data
*
*/
class CheckRequirementsController extends Controller {
const PATH = '/check-requirements';
const METHOD = 'POST';
const requiredPHPVersion = '5.6';
public function validations() {
return [
'permission' => 'any',
'requestData' => []
];
}
public function handler() {
if(InstallationDoneController::isInstallationDone()) {
throw new RequestException(ERRORS::INIT_SETTINGS_DONE);
}
Response::respondSuccess([
'phpVersion' => [
'name' => 'PHP Version',
'value' => phpversion(),
'ok' => $this->checkVersion()
],
'PDO' => [
'name' => 'PDO Module',
'value' => class_exists('PDO') ? 'Available' : 'Not available',
'ok' => class_exists('PDO')
],
'configFile' => [
'name' => 'File: /api/config.php',
'value' => is_writable('config.php') ? 'Writable' : 'Not writable',
'ok' => is_writable('config.php')
],
'files' => [
'name' => 'Folder: /api/files',
'value' => is_writable('files') ? 'Writable' : 'Not writable',
'ok' => is_writable('files')
]
]);
}
private function checkVersion() {
$requiredVersion = explode('.', CheckRequirementsController::requiredPHPVersion);
$currentVersion = explode('.', phpversion());
if($currentVersion[0] > $requiredVersion[0]) return true;
else if($currentVersion[0] < $requiredVersion[0]) return false;
else return $currentVersion[1] >= $requiredVersion[1];
}
}

90
dist/api/controllers/system/csv-import.php vendored Executable file
View File

@ -0,0 +1,90 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/csv-import CSV import
* @apiVersion 4.11.0
*
* @apiName CSV import
*
* @apiGroup System
*
* @apiDescription This path receives a csv file with a list of users to signup.
*
* @apiPermission staff3
*
* @apiParam {String} file A csv file with this content format: email, password, name.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PASSWORD
* @apiUse INVALID_FILE
*
* @apiSuccess {String[]} data Array of errors found
*
*/
class CSVImportController extends Controller {
const PATH = '/csv-import';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [],
'password' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_PASSWORD, LengthConfig::MAX_LENGTH_PASSWORD),
'error' => ERRORS::INVALID_PASSWORD
]
];
}
public function handler() {
$password = Controller::request('password');
if(!Hashing::verifyPassword($password, Controller::getLoggedUser()->password)) {
throw new RequestException(ERRORS::INVALID_PASSWORD);
}
$fileUploader = $this->uploadFile(true);
if(!$fileUploader instanceof FileUploader) {
throw new RequestException(ERRORS::INVALID_FILE);
}
$fileDownloader = FileDownloader::getInstance();
$fileDownloader->setFileName($fileUploader->getFileName());
$file = $fileDownloader->fopen();
$errors = [];
while($file && ($user = fgetcsv($file)) != false) {
Controller::setDataRequester(function ($key) use ($user) {
switch ($key) {
case 'email':
return $user[0];
case 'password':
return $user[1];
case 'name':
return $user[2];
}
return null;
});
$signupController = new SignUpController(true);
try {
$signupController->validate();
$signupController->handler();
} catch (\Exception $exception) {
$errors[] = $exception->getMessage() . ' in email ' . $user[0];
}
}
fclose($file);
unlink($fileUploader->getFullFilePath());
Response::respondSuccess($errors);
}
}

View File

@ -0,0 +1,54 @@
<?php
use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/delete-all-users Delete all users
* @apiVersion 4.11.0
*
* @apiName Delete all users
*
* @apiGroup System
*
* @apiDescription This path deletes all users in database.
*
* @apiPermission staff3
*
* @apiParam {String} password The password of the current staff.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PASSWORD
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteAllUsersController extends Controller {
const PATH = '/delete-all-users';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$password = Controller::request('password');
if(!Hashing::verifyPassword($password, Controller::getLoggedUser()->password)) {
throw new RequestException(ERRORS::INVALID_PASSWORD);
return;
}
Redbean::exec('SET FOREIGN_KEY_CHECKS = 0;');
RedBean::wipe(SessionCookie::TABLE);
RedBean::wipe(User::TABLE);
RedBean::wipe(Ticket::TABLE);
RedBean::wipe(Ticketevent::TABLE);
RedBean::wipe('ticket_user');
Redbean::exec('SET FOREIGN_KEY_CHECKS = 1;');
Response::respondSuccess();
}
}

View File

@ -0,0 +1,54 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/delete-api-key Delete APIKey
* @apiVersion 4.11.0
*
* @apiName Delete APIKey
*
* @apiGroup System
*
* @apiDescription This path deletes an APIKey.
*
* @apiPermission staff3
*
* @apiParam {String} name Name of the APIKey to delete.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteAPIKeyController extends Controller {
const PATH = '/delete-api-key';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
]
]
];
}
public function handler() {
$name = Controller::request('name');
$keyInstance = APIKey::getDataStore($name, 'name');
if($keyInstance->isNull()) {
throw new RequestException(ERRORS::INVALID_NAME);
return;
}
$keyInstance->delete();
Response::respondSuccess();
}
}

View File

@ -0,0 +1,60 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/delete-custom-field Delete custom field
* @apiVersion 4.11.0
*
* @apiName Delete a custom field
*
* @apiGroup System
*
* @apiDescription This path deletes a custom field and all its uses.
*
* @apiPermission staff2
*
* @apiParam {Number} id Id of the custom field to delete.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_CUSTOM_FIELD
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteCustomFieldController extends Controller {
const PATH = '/delete-custom-field';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'id' => [
'validation' => DataValidator::dataStoreId('customfield'),
'error' => ERRORS::INVALID_CUSTOM_FIELD,
],
]
];
}
public function handler() {
$customField = Customfield::getDataStore(Controller::request('id'));
foreach(User::getAll() as $user) {
$customFieldValueList = $user->xownCustomfieldvalueList ? $user->xownCustomfieldvalueList : [];
foreach($customFieldValueList as $customFieldValue) {
if($customFieldValue->customfield->id == $customField->id) {
$user->xownCustomfieldvalueList->remove($customFieldValue);
}
}
$user->store();
}
$customField->delete();
Response::respondSuccess();
}
}

View File

@ -0,0 +1,119 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/delete-department Delete department
* @apiVersion 4.11.0
*
* @apiName Delete department
*
* @apiGroup System
*
* @apiDescription This path deletes a department.
*
* @apiPermission staff3
*
* @apiParam {Number} departmentId Id of the department to be deleted.
* @apiParam {Number} transferDepartmentId Id of the department where the tickets will be transfer to.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_DEPARTMENT
* @apiUse SAME_DEPARTMENT
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteDepartmentController extends Controller {
const PATH = '/delete-department';
const METHOD = 'POST';
private $departmentId;
private $transferDepartmentId;
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'departmentId' => [
'validation' => DataValidator::dataStoreId('department'),
'error' => ERRORS::INVALID_DEPARTMENT
],
'transferDepartmentId' => [
'validation' => DataValidator::dataStoreId('department'),
'error' => ERRORS::INVALID_DEPARTMENT
]
]
];
}
public function handler() {
$this->departmentId = Controller::request('departmentId');
$this->transferDepartmentId = Controller::request('transferDepartmentId');
$this->checkDepartmentIdDefault();
if ($this->departmentId === $this->transferDepartmentId) {
throw new RequestException(ERRORS::SAME_DEPARTMENT);
}
$departmentToTransfer = Department::getDataStore($this->transferDepartmentId);
$departmentInstance = Department::getDataStore($this->departmentId);
if($departmentToTransfer->private && Ticket::count(' author_id IS NOT NULL AND department_id = ? ', [$departmentInstance->id])) {
throw new RequestException(ERRORS::DEPARTMENT_PRIVATE_TICKETS);
}
$this->transferDepartmentTickets();
$departmentInstance->delete();
Log::createLog('DELETE_DEPARTMENT', $departmentInstance->name);
Response::respondSuccess();
}
public function transferDepartmentTickets() {
$tickets = Ticket::find('department_id = ?', [$this->departmentId]);
$newDepartment = Department::getDataStore($this->transferDepartmentId);
foreach($tickets as $ticket) {
$staffOwner = $ticket->owner;
if($staffOwner) {
$hasDepartment = false;
foreach($staffOwner->sharedDepartmentList as $department) {
if($department->id === $newDepartment->id) {
$hasDepartment = true;
}
}
if(!$hasDepartment) {
$staffOwner->sharedTicketList->remove($ticket);
$staffOwner->store();
$ticket->owner = null;
$ticket->unread = true;
$event = Ticketevent::getEvent(Ticketevent::UN_ASSIGN);
$event->setProperties(array(
'authorStaff' => $staffOwner,
'date' => Date::getCurrentDate()
));
$ticket->addEvent($event);
}
}
$ticket->department = $newDepartment;
$ticket->store();
}
}
public function checkDepartmentIdDefault() {
$defaultDepartment = Setting::getSetting('default-department-id');
if ($defaultDepartment && $this->departmentId == $defaultDepartment->value) throw new Exception(ERRORS::CAN_NOT_DELETE_DEFAULT_DEPARTMENT);
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* @api {post} /system/disable-mandatory-login Disable mandatory Login
* @apiVersion 4.11.0
*
* @apiName Disable Mandatory Login
*
* @apiGroup System
*
* @apiDescription This path disables the mandatory Login.
*
* @apiPermission staff3
*
* @apiParam {String} password The password of the current staff.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PASSWORD
* @apiUse REGISTRATION_IS_DESACTIVATED
*
* @apiSuccess {Object} data Empty object
*
*/
class DisableMandatoryLoginController extends Controller {
const PATH = '/disable-mandatory-login';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$password = Controller::request('password');
if(!Setting::getSetting('registration')->getValue()) {
throw new Exception(ERRORS::REGISTRATION_IS_DESACTIVATED);
}
if(!Hashing::verifyPassword($password, Controller::getLoggedUser()->password)) {
throw new RequestException(ERRORS::INVALID_PASSWORD);
}
$mandatoryLoginRow = Setting::getSetting('mandatory-login');
$mandatoryLoginRow->value = 0;
$mandatoryLoginRow->store();
Response::respondSuccess();
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* @api {post} /system/disable-registration Disable registration
* @apiVersion 4.11.0
*
* @apiName Disable registration
*
* @apiGroup System
*
* @apiDescription This path disables the registration.
*
* @apiPermission staff3
*
* @apiParam {String} password The password of the current staff.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PASSWORD
* @apiUse MANDATORY_LOGIN_IS_DESACTIVATED
*
* @apiSuccess {Object} data Empty object
*
*/
class DisableRegistrationController extends Controller {
const PATH = '/disable-registration';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$password = Controller::request('password');
if(!Hashing::verifyPassword($password, Controller::getLoggedUser()->password)) {
throw new RequestException(ERRORS::INVALID_PASSWORD);
return;
}
if(!Setting::getSetting('mandatory-login')->getValue()) {
throw new Exception(ERRORS::MANDATORY_LOGIN_IS_DESACTIVATED);
}
$registrationRow = Setting::getSetting('registration');
$registrationRow->value = false;
$registrationRow->store();
Response::respondSuccess();
}
}

94
dist/api/controllers/system/download.php vendored Executable file
View File

@ -0,0 +1,94 @@
<?php
use Ifsnop\Mysqldump as IMysqldump;
use Respect\Validation\Validator as DataValidator;
/**
* @api {get} /system/download Download file
* @apiVersion 4.11.0
*
* @apiName Download file
*
* @apiGroup System
*
* @apiDescription This path downloads a file.
*
* @apiPermission any
*
* @apiParam {String} file The filename to be downloaded.
*
* @apiError 403 You have no permission to access the file.
*
* @apiSuccess {Object} file File content
*
*/
class DownloadController extends Controller {
const PATH = '/download';
const METHOD = 'GET';
public function validations() {
return [
'permission' => 'any',
'requestData' => [
'file' => [
'validation' => DataValidator::alnum('_.-')->noWhitespace(),
'error' => ERRORS::INVALID_FILE
]
]
];
}
public function handler() {
$fileName = Controller::request('file');
$isStaffProfilePic = !Staff::getDataStore($fileName, 'profilePic')->isNull();
$fileDownloader = FileDownloader::getInstance();
$fileDownloader->setFileName($fileName);
$session = Session::getInstance();
if(!$session->isStaffLogged()) {
switch($fileDownloader->getFilePermission()) {
case FileManager::PERMISSION_TICKET:
$ticketNumber = $fileDownloader->getTicketNumber();
$ticket = Ticket::getByTicketNumber($ticketNumber);
if($this->isNotAuthor($ticket, Controller::getLoggedUser())) {
return Response::respond403();
}
break;
case FileManager::PERMISSION_ARTICLE:
if(!$session->sessionExists()) {
return Response::respond403();
}
break;
case FileManager::PERMISSION_PROFILE:
break;
default:
return Response::respond403();
}
}
$fileDownloader->download();
exit();
}
private function isNotAuthor($ticket, $loggedUser) {
$session = Session::getInstance();
if($session->getTicketNumber()) {
return $session->getTicketNumber() !== $ticket->ticketNumber;
} else {
return $ticket->author->id !== $loggedUser->id || ($loggedUser instanceof Staff) !== $ticket->authorToArray()['staff'];
}
}
private function isNotDepartmentOwner($ticket, $loggedUser) {
$session = Session::getInstance();
if($session->getTicketNumber()) {
return $session->getTicketNumber() !== $ticket->ticketNumber;
} else {
return !($loggedUser->level >= 1) || !$loggedUser->sharedDepartmentList->includesId($ticket->department->id);
}
}
}

View File

@ -0,0 +1,79 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/edit-department Edit department
* @apiVersion 4.11.0
*
* @apiName Edit department
*
* @apiGroup System
*
* @apiDescription This path edits a department's name.
*
* @apiPermission staff3
*
* @apiParam {String} name The new name of the department.
* @apiParam {Number} departmentId The Id of the department.
* @apiParam {Boolean} private Indicates if the department is shown to users;
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
* @apiUse INVALID_DEPARTMENT
* @apiUse NAME_ALREADY_USED
*
* @apiSuccess {Object} data Empty object
*
*/
class EditDepartmentController extends Controller {
const PATH = '/edit-department';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'departmentId' => [
'validation' => DataValidator::dataStoreId('department'),
'error' => ERRORS::INVALID_DEPARTMENT
],
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
],
]
];
}
public function handler() {
$newName = Controller::request('name');
$departmentId = Controller::request('departmentId');
$private = Controller::request('private');
$departmentInstance = Department::getDataStore($departmentId);
$createdDepartment = Department::getDataStore($newName, 'name');
if(!$createdDepartment->isNull() && $createdDepartment->name !== $departmentInstance->name){
throw new RequestException(ERRORS::NAME_ALREADY_USED);
}
if($private && $departmentId == Setting::getSetting('default-department-id')->getValue()){
throw new RequestException(ERRORS::DEFAULT_DEPARTMENT_CAN_NOT_BE_PRIVATE);
}
if($private && Ticket::count(' author_id IS NOT NULL AND department_id = ? ', [$departmentId])) {
throw new RequestException(ERRORS::DEPARTMENT_PRIVATE_TICKETS);
}
if($newName) $departmentInstance->name = $newName;
$departmentInstance->private = $private ? 1 : 0;
$departmentInstance->store();
Log::createLog('EDIT_DEPARTMENT', $departmentInstance->name);
Response::respondSuccess();
}
}

View File

@ -0,0 +1,143 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/edit-mail-template Edit mail template
* @apiVersion 4.11.0
*
* @apiName Edit mail template
*
* @apiGroup System
*
* @apiDescription This path edits a mail template.
*
* @apiPermission staff3
*
* @apiParam {String} template The template to edit.
* @apiParam {String} language The language of the template to edit.
* @apiParam {String} subject The new subject of the template.
* @apiParam {String} text1 The first paragraph template.
* @apiParam {String} text2 The second paragraph template.
* @apiParam {String} text3 The third paragraph template.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TEMPLATE
* @apiUse INVALID_LANGUAGE
* @apiUse INVALID_SUBJECT
* @apiUse INVALID_TEXT_1
* @apiUse INVALID_TEXT_2
* @apiUse INVALID_TEXT_3
*
* @apiSuccess {Object} data Empty object
*
*/
class EditMailTemplateController extends Controller {
const PATH = '/edit-mail-template';
const METHOD = 'POST';
private $language;
private $templateType;
private $subject;
private $texts;
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'template' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_TEMPLATE),
'error' => ERRORS::INVALID_TEMPLATE
],
'language' => [
'validation' => DataValidator::oneOf(DataValidator::in(Language::getSupportedLanguages()), DataValidator::nullType()),
'error' => ERRORS::INVALID_LANGUAGE
],
'subject' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_SUBJECT),
'error' => ERRORS::INVALID_SUBJECT
],
]
];
}
public function handler() {
$this->language = Controller::request('language');
$this->templateType = Controller::request('template');
$this->subject = Controller::request('subject', true);
$this->texts = [
Controller::request('text1'),
Controller::request('text2'),
Controller::request('text3'),
];
$mailTemplate = MailTemplate::findOne(' language = ? AND template = ?', [$this->language, $this->templateType]);
if($mailTemplate->isNull()) {
throw new RequestException(ERRORS::INVALID_TEMPLATE);
}
$this->validateReplacements();
$mailTemplate->subject = $this->subject;
$mailTemplate->text1 = $this->texts[0];
$mailTemplate->text2 = $this->texts[1];
$mailTemplate->text3 = $this->texts[2];
$mailTemplate->store();
Response::respondSuccess();
}
public function validateReplacements() {
$originalText = MailTexts::getTexts()[$this->language][$this->templateType];
if(array_key_exists(1, $originalText) && !$this->includes(
$this->getReplacementStrings($originalText[1]),
$this->getReplacementStrings($this->texts[0])
)) {
throw new RequestException(ERRORS::INVALID_TEXT_1);
}
if(array_key_exists(2, $originalText) && !$this->includes(
$this->getReplacementStrings($originalText[2]),
$this->getReplacementStrings($this->texts[1])
)) {
throw new RequestException(ERRORS::INVALID_TEXT_2);
}
if(array_key_exists(3, $originalText) && !$this->includes(
$this->getReplacementStrings($originalText[3]),
$this->getReplacementStrings($this->texts[2])
)) {
throw new RequestException(ERRORS::INVALID_TEXT_3);
}
}
public function includes($array1, $array2) {
foreach($array1 as $item) {
if(!in_array($item, $array2)) return false;
}
return true;
}
public function getReplacementStrings($string) {
$replacements = [];
for($i=0; $i<strlen($string)-1; $i++) {
if($string[$i] == '{' && $string[$i+1] == '{') {
$replacement = "";
$i += 2;
for(; $i<strlen($string)-1;$i++) {
if($string[$i] == '}' && $string[$i+1] == '}') break;
$replacement .= $string[$i];
}
$replacements[] = $replacement;
}
}
return $replacements;
}
}

122
dist/api/controllers/system/edit-settings.php vendored Executable file
View File

@ -0,0 +1,122 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/edit-settings Edit settings
* @apiVersion 4.11.0
*
* @apiName Edit settings
*
* @apiGroup System
*
* @apiDescription This path edit the settings of the system.
*
* @apiPermission staff3
*
* @apiParam {String} allowedLanguages The list of languages allowed.
* @apiParam {String} supportedLanguages The list of languages supported.
* @apiParam {Setting} setting A setting can be any of the following: language, recaptcha-public, recaptcha-private, server-email, smtp-host, smtp-port, smtp-user, smtp-pass, maintenance-mode, layout, allow-attachments, max-size, title, url.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {Object} data Empty object
*
*/
class EditSettingsController extends Controller {
const PATH = '/edit-settings';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'default-department-id' => [
'validation' => DataValidator::oneOf(DataValidator::dataStoreId('department'),DataValidator::nullType()),
'error' => ERRORS::INVALID_DEFAULT_DEPARTMENT
],
]
];
}
public function handler() {
$settings = [
'language',
'recaptcha-public',
'recaptcha-private',
'server-email',
'imap-host',
'imap-user',
'imap-pass',
'imap-token',
'smtp-host',
'smtp-user',
'smtp-pass',
'maintenance-mode',
'layout',
'allow-attachments',
'max-size',
'title',
'url',
'mail-template-header-image',
'default-is-locked',
'default-department-id'
];
$this->checkDefaultDepartmentValid();
foreach($settings as $setting) {
if(Controller::request($setting)!==null) {
$settingInstance = Setting::getSetting($setting);
$settingInstance->value = Controller::request($setting);
$settingInstance->store();
}
}
if(Controller::request('allowedLanguages') || Controller::request('supportedLanguages')) {
$this->handleLanguages();
}
Log::createLog('EDIT_SETTINGS', null);
Response::respondSuccess();
}
public function handleLanguages() {
$allowed = json_decode(Controller::request('allowedLanguages'));
$supported = json_decode(Controller::request('supportedLanguages'));
if (array_diff($supported, $allowed)) {
throw new RequestException(ERRORS::INVALID_SUPPORTED_LANGUAGES);
}
foreach(Language::LANGUAGES as $languageCode) {
$language = Language::getDataStore($languageCode, 'code');
$language->allowed = in_array($languageCode, $allowed);
$language->supported = in_array($languageCode, $supported);
$language->store();
}
}
public function checkDefaultDepartmentValid() {
$departmentId = Controller::request('default-department-id');
if($departmentId){
$Publicdepartments = Department::getPublicDepartmentNames();
$isValid = false;
foreach($Publicdepartments as $department) {
if($department['id'] == $departmentId){
$isValid = true;
}
}
if(!$isValid) throw new Exception(ERRORS::INVALID_DEFAULT_DEPARTMENT);
}
}
}

155
dist/api/controllers/system/email-polling.php vendored Executable file
View File

@ -0,0 +1,155 @@
<?php
use Respect\Validation\Validator as DataValidator;
class EmailPollingController extends Controller {
const PATH = '/email-polling';
const METHOD = 'POST';
private $mailbox;
public function validations() {
return [
'permission' => 'any',
'requestData' => [
'token' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_TOKEN, LengthConfig::MAX_LENGTH_TOKEN),
'error' => ERRORS::INVALID_TOKEN
]
]
];
}
public function handler() {
$commentController = new CommentController();
$createController = new CreateController();
$defaultLanguage = Setting::getSetting('language')->getValue();
$defaultDepartmentId = Department::getAll()->first()->id;
if(Controller::request('token') !== Setting::getSetting('imap-token')->getValue())
throw new RequestException(ERRORS::INVALID_TOKEN);
$this->mailbox = new \PhpImap\Mailbox(
Setting::getSetting('imap-host')->getValue(),
Setting::getSetting('imap-user')->getValue(),
Setting::getSetting('imap-pass')->getValue(),
'files/'
);
$errors = [];
$emails = $this->getLastEmails();
$session = Session::getInstance();
$oldSession = [
'userId' => $session->getUserId(),
'staff' => $session->getToken(),
'token' => $session->isStaffLogged(),
];
foreach($emails as $email) {
Controller::setDataRequester(function ($key) use ($email, $defaultDepartmentId, $defaultLanguage) {
switch ($key) {
case 'ticketNumber':
return $email->getTicketNumber();
case 'title':
return $email->getSubject();
case 'content':
return $email->getContent();
case 'departmentId':
return $defaultDepartmentId;
case 'language':
return $defaultLanguage;
case 'email':
return $email->getSender();
case 'name':
return $email->getSenderName();
}
return null;
});
/*
if($email->getAttachment()) {
$attachment = $email->getAttachment();
$_FILES['file'] = [
'name' => $attachment->name,
'type' => mime_content_type($attachment->filePath),
'tmp_name' => $attachment->filePath,
'error' => UPLOAD_ERR_OK,
'size' => filesize($attachment->filePath),
];
}
*/
try {
if($email->isReply()) {
$ticketAuthor = $email->getTicket()->authorToArray();
if($ticketAuthor['email'] === $email->getSender()) {
$session->clearSessionData();
$session->createSession($ticketAuthor['id'],
$ticketAuthor['staff'],
$email->getTicket()->ticketNumber);
$commentController->handler();
}
} else {
$createController->handler();
}
} catch(\Exception $e) {
$errors[] = [
'author' => $email->getSender(),
'ticketNumber' => $email->getTicketNumber(),
'error' => $e->__toString(),
];
}
unset($_FILES['file']);
}
$session->clearSessionData();
$session->setSessionData($oldSession);
if(count($errors)) {
Response::respondError(ERRORS::EMAIL_POLLING, null, $errors);
} else {
$this->eraseAllEmails();
Response::respondSuccess();
}
}
public function getLastEmails() {
$mailsIds = $this->mailbox->searchMailbox('ALL');
$emails = [];
sort($mailsIds);
foreach($mailsIds as $mailId) {
$mail = $this->mailbox->getMail($mailId);
$mailHeader = $this->mailbox->getMailHeader($mailId);
// $mailAttachment = count($mail->getAttachments()) ? current($mail->getAttachments()) : null;
$emails[] = new Email([
'fromAddress' => $mailHeader->fromAddress,
'fromName' => $mailHeader->fromName,
'subject' => $mailHeader->subject,
'content' => $mail->textPlain,
'file' => null,
]);
foreach($mail->getAttachments() as $attachment) {
unlink($attachment->filePath);
}
}
return $emails;
}
public function eraseAllEmails() {
$mailsIds = $this->mailbox->searchMailbox('ALL');
foreach($mailsIds as $mailId) {
$this->mailbox->deleteMail($mailId);
}
$this->mailbox->expungeDeletedMails();
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* @api {post} /system/enable-mandatory-login Enable mandatory Login
* @apiVersion 4.11.0
*
* @apiName Enable Mandatory Login
*
* @apiGroup System
*
* @apiDescription This path enable the mandatory Login.
*
* @apiPermission staff3
*
* @apiParam {String} password The password of the current staff.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PASSWORD
*
* @apiSuccess {Object} data Empty object
*
*/
class EnableMandatoryLoginController extends Controller {
const PATH = '/enable-mandatory-login';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$password = Controller::request('password');
if(!Hashing::verifyPassword($password, Controller::getLoggedUser()->password)) {
throw new RequestException(ERRORS::INVALID_PASSWORD);
return;
}
$mandatoryLoginRow = Setting::getSetting('mandatory-login');
$mandatoryLoginRow->value = 1;
$mandatoryLoginRow->store();
Response::respondSuccess();
}
}

View File

@ -0,0 +1,51 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/enable-registration Enable registration
* @apiVersion 4.11.0
*
* @apiName Enable registration
*
* @apiGroup System
*
* @apiDescription This path enables the registration.
*
* @apiPermission staff3
*
* @apiParam {String} password The password of the current staff.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PASSWORD
*
* @apiSuccess {Object} data Empty object
*
*/
class EnableRegistrationController extends Controller {
const PATH = '/enable-registration';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$password = Controller::request('password');
if(!Hashing::verifyPassword($password,Controller::getLoggedUser()->password)) {
throw new RequestException(ERRORS::INVALID_PASSWORD);
return;
}
$registrationRow = Setting::getSetting('registration');
$registrationRow->value = true;
$registrationRow->store();
Response::respondSuccess();
}
}

37
dist/api/controllers/system/get-api-keys.php vendored Executable file
View File

@ -0,0 +1,37 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-api-keys Get APIKeys
* @apiVersion 4.11.0
*
* @apiName Get APIKeys
*
* @apiGroup System
*
* @apiDescription This path retrieves the all APIKeys.
*
* @apiPermission staff3
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[APIKey](#api-Data_Structures-ObjectApikey)[]} data Array of APIKeys
*
*/
class GetAPIKeysController extends Controller {
const PATH = '/get-api-keys';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$apiList = APIKey::getAll();
Response::respondSuccess($apiList->toArray());
}
}

View File

@ -0,0 +1,38 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-custom-fields Get custom fields
* @apiVersion 4.11.0
*
* @apiName Get all Custom field items
*
* @apiGroup System
*
* @apiDescription This path retrieves the all CustomField items.
*
* @apiPermission any
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[Customfield](#api-Data_Structures-ObjectCustomfield)[]} data Array of Customfield
*
*/
class GetCustomFieldsController extends Controller {
const PATH = '/get-custom-fields';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => []
];
}
public function handler() {
$customFieldList = Customfield::getAll();
Response::respondSuccess($customFieldList->toArray());
}
}

58
dist/api/controllers/system/get-logs.php vendored Executable file
View File

@ -0,0 +1,58 @@
<?php
use Respect\Validation\Validator as DataValidator;
use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/get-logs Get logs
* @apiVersion 4.11.0
*
* @apiName Get logs
*
* @apiGroup System
*
* @apiDescription This path retrieves the all the logs.
*
* @apiPermission staff1
*
* @apiParam {Number} page The page of logs.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PAGE
*
* @apiSuccess {[Log](#api-Data_Structures-ObjectLog)[]} data Array of last logs
*
*/
class GetLogsController extends Controller {
const PATH = '/get-logs';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'page' => [
'validation' => DataValidator::numeric(),
'error' => ERRORS::INVALID_PAGE
]
]
];
}
public function handler() {
$this->deleteLastLogs();
$page = Controller::request('page');
$logList = Log::find('ORDER BY id desc LIMIT ? OFFSET ?', [10, 10*($page-1)]);
Response::respondSuccess($logList->toArray());
}
public function deleteLastLogs() {
$removeOlderThanDays = 31;
$oldDate = floor(Date::getPreviousDate($removeOlderThanDays) / 10000);
try {
RedBean::exec("DELETE FROM log WHERE date < $oldDate");
} catch(Exception $e) {}
}
}

View File

@ -0,0 +1,36 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-mail-template-list Get mail template
* @apiVersion 4.11.0
*
* @apiName Get mail template list
*
* @apiGroup System
*
* @apiDescription This path retrieves the list of mail templates
*
* @apiPermission staff3
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[MailTemplate](#api-Data_Structures-ObjectMailtemplate)[]} data Array of mail templates
*
*/
class GetMailTemplateListController extends Controller {
const PATH = '/get-mail-template-list';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
Response::respondSuccess(array_keys(MailTemplate::getFilePaths()));
}
}

View File

@ -0,0 +1,57 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/get-mail-template Get mail template
* @apiVersion 4.11.0
*
* @apiName Get mail template
*
* @apiGroup System
*
* @apiDescription This path retrieves the data of one of the mail templates.
*
* @apiPermission staff3
*
* @apiParam {String} template The type of template to retrieve.
* @apiParam {String} language The language of the template to retrieve.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[MailTemplate](#api-Data_Structures-ObjectMailtemplate)} data Data of the mail template
*
*/
class GetMailTemplateController extends Controller {
const PATH = '/get-mail-template';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'template' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_TEMPLATE),
'error' => ERRORS::INVALID_TEMPLATE
],
'language' => [
'validation' => DataValidator::oneOf(DataValidator::in(Language::getSupportedLanguages()), DataValidator::nullType()),
'error' => ERRORS::INVALID_LANGUAGE
],
]
];
}
public function handler() {
$type = Controller::request('template');
$language = Controller::request('language');
$mailTemplate = MailTemplate::findOne(' language = ? AND template = ?', [$language, $type]);
if($mailTemplate->isNull()) {
throw new RequestException(ERRORS::INVALID_TEMPLATE);
}
Response::respondSuccess($mailTemplate->toArray());
}
}

88
dist/api/controllers/system/get-settings.php vendored Executable file
View File

@ -0,0 +1,88 @@
<?php
/**
* @api {post} /system/get-settings Get settings
* @apiVersion 4.11.0
*
* @apiName Get settings
*
* @apiGroup System
*
* @apiDescription This path retrieves all the settings.
*
* @apiPermission any
*
* @apiParam {Boolean} allSettings Indicates if you want the regular settings list or a complete settings list. Complete list only available for staff level3.
*
* @apiSuccess {Object} data Contains the information about the settings
*
*/
class GetSettingsController extends Controller {
const PATH = '/get-settings';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => []
];
}
public function handler() {
$settingsList = [];
if(InstallationDoneController::isInstallationDone()) {
if(Controller::request('allSettings') && Controller::isStaffLogged(3)) {
$settingsList = [
'language' => Setting::getSetting('language')->getValue(),
'reCaptchaKey' => Setting::getSetting('recaptcha-public')->getValue(),
'reCaptchaPrivate' => Setting::getSetting('recaptcha-private')->getValue(),
'maintenance-mode' => intval(Setting::getSetting('maintenance-mode')->getValue()),
'layout' => Setting::getSetting('layout')->getValue(),
'allow-attachments' => intval(Setting::getSetting('allow-attachments')->getValue()),
'max-size' => Setting::getSetting('max-size')->getValue(),
'url' => Setting::getSetting('url')->getValue(),
'title' => Setting::getSetting('title')->getValue(),
'server-email' => Setting::getSetting('server-email')->getValue(),
'smtp-host' => Setting::getSetting('smtp-host')->getValue(),
'smtp-user' => Setting::getSetting('smtp-user')->getValue(),
'imap-host' => Setting::getSetting('imap-host')->getValue(),
'imap-user' => Setting::getSetting('imap-user')->getValue(),
'imap-token' => Setting::getSetting('imap-token')->getValue(),
'registration' => Setting::getSetting('registration')->getValue(),
'departments' => Department::getAllDepartmentNames(),
'supportedLanguages' => Language::getSupportedLanguages(),
'allowedLanguages' => Language::getAllowedLanguages(),
'session-prefix' => Setting::getSetting('session-prefix')->getValue(),
'mail-template-header-image' => Setting::getSetting('mail-template-header-image')->getValue(),
'tags' => Tag::getAll()->toArray(),
'mandatory-login' => Setting::getSetting('mandatory-login')->getValue(),
'default-department-id' => Setting::getSetting('default-department-id')->getValue(),
'default-is-locked' => Setting::getSetting('default-is-locked')->getValue()
];
} else {
$settingsList = [
'language' => Setting::getSetting('language')->getValue(),
'reCaptchaKey' => Setting::getSetting('recaptcha-public')->getValue(),
'maintenance-mode' => intval(Setting::getSetting('maintenance-mode')->getValue()),
'layout' => Setting::getSetting('layout')->getValue(),
'allow-attachments' => intval(Setting::getSetting('allow-attachments')->getValue()),
'max-size' => Setting::getSetting('max-size')->getValue(),
'title' => Setting::getSetting('title')->getValue(),
'registration' => Setting::getSetting('registration')->getValue(),
'departments' => Controller::isStaffLogged() ? Department::getAllDepartmentNames() : Department::getPublicDepartmentNames(),
'supportedLanguages' => Language::getSupportedLanguages(),
'allowedLanguages' => Language::getAllowedLanguages(),
'session-prefix' => Setting::getSetting('session-prefix')->getValue(),
'tags' => Tag::getAll()->toArray(),
'mandatory-login' => Setting::getSetting('mandatory-login')->getValue(),
'default-department-id' => Setting::getSetting('default-department-id')->getValue(),
'default-is-locked' => Setting::getSetting('default-is-locked')->getValue()
];
}
}
Response::respondSuccess($settingsList);
}
}

80
dist/api/controllers/system/init-admin.php vendored Executable file
View File

@ -0,0 +1,80 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/init-admin Init admin
* @apiVersion 4.11.0
*
* @apiName Init admin
*
* @apiGroup System
*
* @apiDescription This path creates the main administrator account. It can only be used once during installation
*
* @apiPermission any
*
* @apiParam {String} name Name of the administrator.
* @apiParam {String} email Email of the administrator.
* @apiParam {String} password Password of the administrator.
*
* @apiUse INVALID_NAME
* @apiUse INVALID_EMAIL
* @apiUse INVALID_PASSWORD
* @apiUse INIT_SETTINGS_DONE
*
* @apiSuccess {Object} data Empty object
*
*/
class InitAdminController extends Controller {
const PATH = '/init-admin';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => [
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
],
'email' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
],
'password' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_PASSWORD, LengthConfig::MAX_LENGTH_PASSWORD),
'error' => ERRORS::INVALID_PASSWORD
],
]
];
}
public function handler() {
if(!Staff::isTableEmpty()) {
throw new RequestException(ERRORS::INIT_SETTINGS_DONE);
}
$staff = new Staff();
$staff->setProperties([
'name' => Controller::request('name'),
'email' => Controller::request('email'),
'password' => Hashing::hashPassword(Controller::request('password')),
'profilePic' => '',
'level' => 3,
'sharedDepartmentList' => Department::getAll(),
'sharedTicketList' => [],
'sendEmailOnNewTicket' => 1
]);
foreach(Department::getAll() as $department) {
$department->owners++;
$department->store();
}
$staff->store();
Response::respondSuccess();
}
}

84
dist/api/controllers/system/init-database.php vendored Executable file
View File

@ -0,0 +1,84 @@
<?php
use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/init-database Init database
* @apiVersion 4.11.0
*
* @apiName Init database
*
* @apiGroup System
*
* @apiDescription This path sets the database settings. It can only be used once during installation.
*
* @apiPermission any
*
* @apiParam {String} dbHost Location of the database server.
* @apiParam {String} dbPort Port of the database server.
* @apiParam {String} dbName Name of the database. If not given, the system will try to create one.
* @apiParam {String} dbUser User of the database server.
* @apiParam {String} dbPassword Password of the database server.
*
* @apiUse DATABASE_CONNECTION
* @apiUse DATABASE_CREATION
* @apiUse INVALID_FILE
*
* @apiSuccess {Object} data Empty object
*
*/
class InitDatabaseController extends Controller {
const PATH = '/init-database';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => []
];
}
public function handler() {
if(defined('MYSQL_HOST')) {
throw new RequestException(ERRORS::INIT_SETTINGS_DONE);
}
$dbHost = Controller::request('dbHost');
$dbPort = Controller::request('dbPort');
$dbName = Controller::request('dbName');
$dbUser = Controller::request('dbUser');
$dbPass = Controller::request('dbPassword');
RedBean::setup("mysql:host=$dbHost;port=$dbPort", $dbUser, $dbPass);
if($dbName) {
RedBean::addDatabase($dbName, "mysql:host=$dbHost;port=$dbPort;dbname=$dbName", $dbUser, $dbPass);
RedBean::selectDatabase($dbName);
if(!RedBean::testConnection()) {
throw new RequestException(ERRORS::DATABASE_CONNECTION);
}
} else {
$dbName = 'opensupports_' . Hashing::generateRandomNumber(100, 999);
RedBean::exec('CREATE DATABASE ' . $dbName);
RedBean::addDatabase($dbName, "mysql:host=$dbHost;port=$dbPort;dbname=$dbName", $dbUser, $dbPass);
RedBean::selectDatabase($dbName);
if(!RedBean::testConnection()) {
throw new RequestException(ERRORS::DATABASE_CREATION);
}
}
$configFile = fopen('config.php', 'w+') or die(ERRORS::INVALID_FILE);
$content = '<?php' . PHP_EOL;
$content .= 'define(\'MYSQL_HOST\', \'' . $dbHost . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_PORT\', \'' . $dbPort . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_USER\', \'' . $dbUser . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_PASSWORD\', \'' . $dbPass . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_DATABASE\', \'' . $dbName . '\');' . PHP_EOL;
fwrite($configFile, $content);
fclose($configFile);
Response::respondSuccess();
}
}

156
dist/api/controllers/system/init-settings.php vendored Executable file
View File

@ -0,0 +1,156 @@
<?php
use RedBeanPHP\Facade as RedBean;
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /system/init-settings Init settings
* @apiVersion 4.11.0
*
* @apiName Init settings
*
* @apiGroup System
*
* @apiDescription This path sets the initial settings. It can only be used once during installation.
*
* @apiPermission any
*
* @apiParam {String} language Indicates the default language of the system.
* @apiParam {String} registration Indicates if the registration should be enabled.
* @apiParam {String} server-email Email from where automated emails will be sent.
* @apiParam {String} smtp-host SMTP Server address.
* @apiParam {String} smtp-user SMTP Authentication User.
* @apiParam {String} smtp-pass SMTP Authentication Password.
* @apiParam {String} allow-attachments Indicates if files can be attached to tickets and comments.
* @apiParam {String} title Title of the support center
* @apiParam {String} url Url of the frontend client.
* @apiParam {Boolean} mandatory-login Indicates if the login is mandatory.
* @apiUse INVALID_LANGUAGE
* @apiUse INIT_SETTINGS_DONE
*
* @apiSuccess {Object} data Empty object
*
*/
class InitSettingsController extends Controller {
const PATH = '/init-settings';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => [
'language' => [
'validation' => DataValidator::validLanguage(),
'error' => ERRORS::INVALID_LANGUAGE
]
]
];
}
public function handler() {
if (Setting::isTableEmpty()) {
RedBean::exec(file_get_contents('data/db_schema.sql'));
$this->storeGlobalSettings();
$this->storeMailTemplates();
$this->storeLanguages();
$this->storeMockedDepartments();
Response::respondSuccess();
} else {
throw new RequestException(ERRORS::INIT_SETTINGS_DONE);
}
}
private function storeGlobalSettings() {
$this->storeSettings([
'language' => Controller::request('language'),
'recaptcha-public' => '',
'recaptcha-private' => '',
'server-email' => Controller::request('server-email'),
'imap-host' => Controller::request('imap-host'),
'imap-user' => Controller::request('imap-user'),
'imap-pass' => Controller::request('imap-pass'),
'smtp-host' => Controller::request('smtp-host'),
'smtp-user' => Controller::request('smtp-user'),
'smtp-pass' => Controller::request('smtp-pass'),
'maintenance-mode' => 0,
'layout' => 'boxed',
'allow-attachments' => !!Controller::request('allow-attachments'),
'max-size' => 1,
'title' => Controller::request('title') ? Controller::request('title') : 'Support Center',
'url' => Controller::request('url') ? Controller::request('url') : ('http://' . $_SERVER['HTTP_HOST']),
'registration' => !!Controller::request('registration'),
'last-stat-day' => date('YmdHi', strtotime(' -12 day ')),
'ticket-gap' => Hashing::generateRandomPrime(100000, 999999),
'ticket-first-number' => Hashing::generateRandomNumber(100000, 999999),
'session-prefix' => 'opensupports-'.Hashing::generateRandomToken().'_',
'mail-template-header-image' => 'https://s3.amazonaws.com/opensupports/logo.png',
'default-department-id' => 1,
'default-is-locked' => false,
'imap-token' => '',
'mandatory-login' => !!Controller::request('mandatory-login')
]);
}
private function storeMailTemplates() {
$mailLanguages = MailTexts::getTexts();
foreach ($mailLanguages as $language => $mailTemplate) {
foreach ($mailTemplate as $template => $texts) {
$mailTemplate = new MailTemplate();
$mailTemplate->setProperties([
'template' => $template,
'language' => $language,
'subject' => $texts[0],
'text1' => array_key_exists(1, $texts) ? $texts[1] : '',
'text2' => array_key_exists(2, $texts) ? $texts[2] : '',
'text3' => array_key_exists(3, $texts) ? $texts[3] : '',
]);
$mailTemplate->store();
}
}
}
private function storeSettings($settings) {
foreach ($settings as $settingName => $settingValue) {
$setting = new Setting();
$setting->setProperties([
'name' => $settingName,
'value' => $settingValue
]);
$setting->store();
}
}
private function storeLanguages() {
$defaultLanguage = Controller::request('language');
foreach(Language::LANGUAGES as $languageCode) {
$language = new Language();
$language->setProperties([
'code' => $languageCode,
'allowed' => 1,
'supported' => ($languageCode === $defaultLanguage)
]);
$language->store();
}
}
private function storeMockedDepartments() {
$departments = [
'Help and Support'
];
foreach ($departments as $departmentName) {
$department = new Department();
$department->name = $departmentName;
$department->private = 0;
$department->store();
}
}
}

View File

@ -0,0 +1,42 @@
<?php
use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/installation-done Installation done
* @apiVersion 4.11.0
*
* @apiName Installation done
*
* @apiGroup System
*
* @apiDescription This path checks if the installation is already done.
*
* @apiPermission any
*
* @apiSuccess {Boolean} data Indicates if the installation is already done.
*
*/
class InstallationDoneController extends Controller {
const PATH = '/installation-done';
const METHOD = 'POST';
public static function isInstallationDone() {
return RedBean::testConnection() && !Setting::isTableEmpty() && !Staff::isTableEmpty();
}
public function validations() {
return [
'permission' => 'any',
'requestData' => []
];
}
public function handler() {
if(InstallationDoneController::isInstallationDone()) {
Response::respondSuccess(1);
} else {
Response::respondSuccess(0);
}
}
}

View File

@ -0,0 +1,68 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/recover-mail-template Recover mail template
* @apiVersion 4.11.0
*
* @apiName Recover mail template
*
* @apiGroup System
*
* @apiDescription This path recovers an mail template to its original content.
*
* @apiPermission staff3
*
* @apiParam {String} template Type of the template.
* @apiParam {String} language Lenguage of the template.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TEMPLATE
* @apiUse INVALID_LANGUAGE
*
* @apiSuccess {Object} data Empty object
*
*/
class RecoverMailTemplateController extends Controller {
const PATH = '/recover-mail-template';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'template' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_TEMPLATE),
'error' => ERRORS::INVALID_TEMPLATE
],
'language' => [
'validation' => DataValidator::oneOf(DataValidator::in(Language::getSupportedLanguages()), DataValidator::nullType()),
'error' => ERRORS::INVALID_LANGUAGE
],
]
];
}
public function handler() {
$templateType = Controller::request('template');
$language = Controller::request('language');
$mailTemplate = MailTemplate::findOne(' language = ? AND template = ?', [$language, $templateType]);
if($mailTemplate->isNull()) {
throw new RequestException(ERRORS::INVALID_TEMPLATE);
}
$mailTexts = MailTexts::getTexts()[$language][$templateType];
$mailTemplate->subject = $mailTexts[0];
$mailTemplate->text1 = (array_key_exists(1, $mailTexts)) ? $mailTexts[1] : '';
$mailTemplate->text2 = (array_key_exists(2, $mailTexts)) ? $mailTexts[2] : '';
$mailTemplate->text3 = (array_key_exists(3, $mailTexts)) ? $mailTexts[3] : '';
$mailTemplate->store();
Response::respondSuccess();
}
}

375
dist/api/controllers/system/stats.php vendored Executable file
View File

@ -0,0 +1,375 @@
<?php
use Respect\Validation\Validator as DataValidator;
use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /system/get-stats Get overall stats
* @apiVersion 4.11.0
*
* @apiName Stats
*
* @apiGroup System
*
* @apiDescription This path retrieves the last stats.
*
* @apiPermission staff1
*
* @apiParam {Number[]} tags The ids of the tags for the custom stats.
* @apiParam {Number[]} dateRange The array with start and end date of the range for the custom stats.
* @apiParam {Number[]} departments The ids of the departments for the custom stats.
* @apiParam {Number[]} owners The ids of the owners for the custom stats.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_DEPARTMENT_FILTER
* @apiUse INVALID_DATE_RANGE_FILTER
* @apiUse INVALID_OWNER_FILTER
*
* @apiSuccess {[StatList](#api-Data_Structures-ObjectStatlist)[]} data Array of the stats
*
*/
class GetStatsController extends Controller {
const PATH = '/get-stats';
const METHOD = 'POST';
private $table;
private $groupBy;
private $dateRangeFilter;
private $departmentsFilter;
private $tagsFilter;
private $ownersFilter;
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'dateRange' => [
'validation' => DataValidator::oneOf(DataValidator::validDateRange(), DataValidator::nullType()),
'error' => ERRORS::INVALID_DATE_RANGE_FILTER
],
'departments' => [
'validation' => DataValidator::oneOf(DataValidator::validDepartmentsId(), DataValidator::nullType()),
'error' => ERRORS::INVALID_DEPARTMENT_FILTER
],
'tags' => [
'validation' => DataValidator::oneOf(DataValidator::validTagsId(), DataValidator::nullType()),
'error' => ERRORS::INVALID_TAG_FILTER
],
'owners' => [
'validation' => DataValidator::oneOf(DataValidator::validOwnersId(), DataValidator::nullType()),
'error' => ERRORS::INVALID_OWNER_FILTER
],
]
];
}
public function handler() {
$this->table = " ticket LEFT JOIN ticketevent ON ticket.id = ticketevent.ticket_id
LEFT JOIN tag_ticket ON ticket.id = tag_ticket.ticket_id ";
$this->groupBy = " GROUP BY ticket.id ";
$this->dateRangeFilter = $this->addDateRangeFilter();
$this->departmentsFilter = $this->addDepartmentsFilter();
$this->tagsFilter = $this->addTagsFilter();
$this->ownersFilter = $this->addOwnersFilter();
Response::respondSuccess([
'created' => $this->getNumberOfCreatedTickets(),
'open' => $this->getNumberOfOpenTickets(),
'closed' => $this->getNumberOfClosedTickets(),
'instant' => $this->getNumberOfInstantTickets(),
'reopened' => $this->getNumberOfReopenedTickets(),
'created_by_hour' => $this->getNumberOfCreatedTicketsByHour(),
'created_by_weekday' => $this->getNumberOfCreatedTicketsByWeekday(),
'average_first_reply' => $this->getAverageFirstReply(),
'average_first_closed' => $this->getAverageFirstClosed(),
'average_last_closed' => $this->getAverageLastClosed(),
'average_department_hops' => $this->getAverageDepartmentHops(),
'average_staff_hops' => $this->getAverageStaffHops()
]);
}
// This function assumes there is a previous condition
private function addDateRangeFilter() {
$dateRange = json_decode(Controller::request('dateRange'));
if ($dateRange === NULL) return " ";
$sql = " AND ticket.date >= {$dateRange[0]} AND ticket.date <= {$dateRange[1]} ";
return $sql;
}
// This function assumes there is a previous condition
private function addDepartmentsFilter() {
$departments = json_decode(Controller::request('departments'));
if ($departments === NULL || empty($departments)) return " ";
$sql = " AND ";
for ($i = 0; $i < count($departments); ++$i) {
$departmentId = $departments[$i];
$departments[$i] = " ticket.department_id={$departmentId} ";
}
$sql .= '(' . join(" OR ", $departments) . ')';
return $sql;
}
// This function assumes there is a previous condition
private function addTagsFilter() {
$tags = json_decode(Controller::request('tags'));
if ($tags === NULL || empty($tags)) return " ";
$sql = " AND ";
for ($i = 0; $i < count($tags); ++$i) {
$tagId = $tags[$i];
$tags[$i] = " tag_ticket.tag_id={$tagId} ";
}
$sql .= '(' . join(" OR ", $tags) . ')';
return $sql;
}
// This function assumes there is a previous condition
private function addOwnersFilter() {
$owners = json_decode(Controller::request('owners'));
if ($owners === NULL || empty($owners)) return " ";
$sql = " AND ";
for ($i = 0; $i < count($owners); ++$i) {
$ownerId = $owners[$i];
$owners[$i] = " ticket.owner_id={$ownerId} ";
}
$sql .= '(' . join(" OR ", $owners) . ')';
return $sql;
}
public function getNumberOfCreatedTickets() {
return (int) RedBean::getCell("
SELECT
COUNT(*)
FROM
(SELECT COUNT(*) FROM {$this->table} WHERE 1=1
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
public function getNumberOfOpenTickets() {
return (int) RedBean::getCell("
SELECT
COUNT(*)
FROM
(SELECT COUNT(*) FROM {$this->table} WHERE closed=0
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
public function getNumberOfClosedTickets() {
return (int) RedBean::getCell("
SELECT
COUNT(*)
FROM
(SELECT COUNT(*) FROM {$this->table} WHERE closed=1
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
public function getNumberOfInstantTickets() {
return (int) RedBean::getCell("
SELECT
COUNT(*)
FROM
(SELECT
COUNT(*)
FROM
{$this->table}
WHERE
ticketevent.type = 'COMMENT'
AND ticketevent.author_staff_id
AND private = 0
AND closed = 1
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}
HAVING COUNT(*) = 1) AS Z;
");
}
public function getNumberOfReopenedTickets() {
return (int) RedBean::getCell("
SELECT
COUNT(*)
FROM
(SELECT COUNT(*) FROM {$this->table} WHERE reopened=1
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
// Returns an array of size 24 with the number of tickets created for each hour
public function getNumberOfCreatedTicketsByHour() {
$result = RedBean::getAll("
SELECT
VALID_TICKETS.HOUR_DAY, COUNT(RAW_CNT) AS CNT
FROM
(SELECT
COUNT(*) AS RAW_CNT,
ticket.date % 10000 DIV 100 AS HOUR_DAY
FROM {$this->table} WHERE 1=1
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS VALID_TICKETS
GROUP BY VALID_TICKETS.HOUR_DAY;
");
$ans = array_fill(0, 24, 0);
for ($i = 0; $i < count($result); ++$i) {
$hour = (int)$result[$i]["HOUR_DAY"];
$cnt = (int)$result[$i]["CNT"];
$ans[$hour] = $cnt;
}
return $ans;
}
// Returns an array of size 7 with the number of tickets created by weekday (0 - monday, ..., 6 - sunday)
public function getNumberOfCreatedTicketsByWeekday() {
$result = RedBean::getAll("
SELECT
VALID_TICKETS.WEEK_DAY, COUNT(RAW_CNT) AS CNT
FROM
(SELECT
COUNT(*) AS RAW_CNT, WEEKDAY(STR_TO_DATE(CONVERT(ticket.date, CHAR), '%Y%m%d%H%i')) AS WEEK_DAY
FROM {$this->table} WHERE 1 = 1
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS VALID_TICKETS
GROUP BY VALID_TICKETS.WEEK_DAY;
");
$ans = array_fill(0, 7, 0);
for ($i = 0; $i < count($result); ++$i) {
$hour = (int)$result[$i]["WEEK_DAY"];
$cnt = (int)$result[$i]["CNT"];
$ans[$hour] = $cnt;
}
return $ans;
}
// Returns the number of seconds (as int), on average, that a ticket waits for the first reply of a staff
public function getAverageFirstReply() {
return (int) RedBean::getCell("
SELECT AVG(SECS)
FROM (
SELECT
ticket.id,
UNIX_TIMESTAMP(STR_TO_DATE(CONVERT(MIN(ticketevent.date), CHAR), '%Y%m%d%H%i')) -
UNIX_TIMESTAMP(STR_TO_DATE(CONVERT(ticket.date, CHAR), '%Y%m%d%H%i')) AS SECS
FROM
{$this->table}
WHERE
ticketevent.type = 'COMMENT'
AND ticketevent.author_staff_id
AND private = 0
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
// Returns the number of seconds (as int), on average, that a ticket waits until it's first closed
public function getAverageFirstClosed() {
return (int) RedBean::getCell("
SELECT
AVG(SECS)
FROM
(SELECT
ticket.id,
UNIX_TIMESTAMP(STR_TO_DATE(CONVERT( MIN(ticket.first_closed_at) , CHAR), '%Y%m%d%H%i')) - UNIX_TIMESTAMP(STR_TO_DATE(CONVERT( ticket.date , CHAR), '%Y%m%d%H%i')) AS SECS
FROM
{$this->table}
WHERE
first_closed_at IS NOT NULL
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
// Returns the number of seconds (as int), on average, that a ticket waits until it's closed for the last time
public function getAverageLastClosed() {
return (int) RedBean::getCell("
SELECT
AVG(SECS)
FROM
(SELECT
ticket.id,
UNIX_TIMESTAMP(STR_TO_DATE(CONVERT( MIN(ticket.last_closed_at) , CHAR), '%Y%m%d%H%i')) - UNIX_TIMESTAMP(STR_TO_DATE(CONVERT( ticket.date , CHAR), '%Y%m%d%H%i')) AS SECS
FROM
{$this->table}
WHERE
first_closed_at IS NOT NULL
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
// Returns the average number of departments (as double) a ticket has been in
public function getAverageDepartmentHops() {
return (double) RedBean::getCell("
SELECT
AVG(CNT)
FROM
(SELECT
ticket.id, ticket.total_departments AS CNT
FROM
{$this->table}
WHERE
1 = 1
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
// Returns the average number of staff members a ticket has been assigned to
public function getAverageStaffHops() {
return (double) RedBean::getCell("
SELECT
AVG(CNT)
FROM
(SELECT
ticket.id, ticket.total_owners AS CNT
FROM
{$this->table}
WHERE
1 = 1
{$this->dateRangeFilter}
{$this->departmentsFilter}
{$this->tagsFilter}
{$this->ownersFilter}
{$this->groupBy}) AS Z;
");
}
}

43
dist/api/controllers/system/test-imap.php vendored Executable file
View File

@ -0,0 +1,43 @@
<?php
/**
* @api {post} /system/test-imap Test IMAP Connection
* @apiVersion 4.11.0
*
* @apiName Test IMAP Connection
*
* @apiGroup System
*
* @apiDescription Test if the given values connect correctly to a IMAP server.
*
* @apiPermission any
*
* @apiParam {String} imap-host Host of the IMAP server.
* @apiParam {String} imap-user User for the IMAP server.
* @apiParam {String} imap-pass Password for the IMAP server.
*
* @apiUse IMAP_CONNECTION
*
* @apiSuccess {Object} data Empty object
*
*/
class TestIMAPController extends Controller {
const PATH = '/test-imap';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => []
];
}
public function handler() {
if(imap_open(Controller::request('imap-host'), Controller::request('imap-user'), Controller::request('imap-pass'))) {
Response::respondSuccess();
} else {
throw new RequestException(ERRORS::IMAP_CONNECTION);
}
}
}

52
dist/api/controllers/system/test-smtp.php vendored Executable file
View File

@ -0,0 +1,52 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /system/test-smtp Test SMTP Connection
* @apiVersion 4.11.0
*
* @apiName Test SMTP Connection
*
* @apiGroup System
*
* @apiDescription Test if the given values connect correctly to a SMTP server.
*
* @apiPermission any
*
* @apiParam {String} smtp-host Host of the SMTP server.
* @apiParam {String} smtp-user User for the SMTP server.
* @apiParam {String} smtp-pass Password for the SMTP server.
*
* @apiUse SMTP_CONNECTION
*
* @apiSuccess {Object} data Empty object
*
*/
class TestSMTPController extends Controller {
const PATH = '/test-smtp';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => []
];
}
public function handler() {
$mailSender = MailSender::getInstance();
$mailSender->setConnectionSettings(
Controller::request('smtp-host'),
Controller::request('smtp-user'),
Controller::request('smtp-pass'),
''
);
if($mailSender->isConnected()) {
Response::respondSuccess();
} else {
throw new RequestException(ERRORS::SMTP_CONNECTION);
}
}
}

30
dist/api/controllers/ticket.php vendored Executable file
View File

@ -0,0 +1,30 @@
<?php
$ticketControllers = new ControllerGroup();
$ticketControllers->setGroupPath('/ticket');
$ticketControllers->addController(new CreateController);
$ticketControllers->addController(new EditCommentController);
$ticketControllers->addController(new EditTitleController);
$ticketControllers->addController(new CommentController);
$ticketControllers->addController(new TicketGetController);
$ticketControllers->addController(new CheckTicketController);
$ticketControllers->addController(new AddCustomResponseController);
$ticketControllers->addController(new DeleteCustomResponseController);
$ticketControllers->addController(new EditCustomResponseController);
$ticketControllers->addController(new GetCustomResponsesController);
$ticketControllers->addController(new ChangeDepartmentController);
$ticketControllers->addController(new CloseController);
$ticketControllers->addController(new ReOpenController);
$ticketControllers->addController(new SeenController);
$ticketControllers->addController(new DeleteController);
$ticketControllers->addController(new CreateTagController);
$ticketControllers->addController(new EditTagController);
$ticketControllers->addController(new DeleteTagController);
$ticketControllers->addController(new GetTagsController);
$ticketControllers->addController(new AddTagController);
$ticketControllers->addController(new RemoveTagController);
$ticketControllers->addController(new SearchController);
$ticketControllers->addController(new SearchAuthorsController);
$ticketControllers->addController(new GetAuthorsController);
$ticketControllers->finalize();

View File

@ -0,0 +1,67 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/add-custom-response Add custom responses
* @apiVersion 4.11.0
*
* @apiName Add a custom response
*
* @apiGroup Ticket
*
* @apiDescription This path allows add new custom responses for tickets.
*
* @apiPermission staff2
*
* @apiParam {String} name Name of the response.
* @apiParam {String} content Content of the response.
* @apiParam {String} language Language of the response.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_CONTENT
* @apiUse INVALID_LANGUAGE
* @apiUse INVALID_TITLE
*
* @apiSuccess {Object} data Empty object
*
*/
class AddCustomResponseController extends Controller {
const PATH = '/add-custom-response';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_TITLE
],
'content' => [
'validation' => DataValidator::content(),
'error' => ERRORS::INVALID_CONTENT
],
'language' => [
'validation' => DataValidator::validLanguage(),
'error' => ERRORS::INVALID_LANGUAGE
]
]
];
}
public function handler() {
$customResponse = new CustomResponse();
$customResponse->setProperties([
'name' => Controller::request('name', true),
'content' => Controller::request('content', true),
'language' => Controller::request('language')
]);
$customResponse->store();
Log::createLog('ADD_CUSTOM_RESPONSE', null);
Response::respondSuccess();
}
}

64
dist/api/controllers/ticket/add-tag.php vendored Executable file
View File

@ -0,0 +1,64 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/add-tag Add tag
* @apiVersion 4.11.0
*
* @apiName Add tag
*
* @apiGroup Ticket
*
* @apiDescription This path attaches a new tag to a ticket.
*
* @apiPermission staff1
*
* @apiParam {Number} ticketNumber The number of the ticket which the tag is going to be attached.
* @apiParam {String} tagId The id of the tag to attach.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
* @apiUse INVALID_TAG
* @apiUse TAG_EXISTS
*
* @apiSuccess {Object} data Empty object
*
*/
class AddTagController extends Controller {
const PATH = '/add-tag';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
],
'tagId' => [
'validation' => DataValidator::dataStoreId('tag'),
'error' => ERRORS::INVALID_TAG
]
]
];
}
public function handler() {
$tagId = Controller::request('tagId');
$tag = Tag::getDataStore($tagId);
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
$user = Controller::getLoggedUser();
if(!$user->canManageTicket($ticket)) throw new RequestException(ERRORS::NO_PERMISSION);
if ($ticket->sharedTagList->includesId($tagId)) throw new RequestException(ERRORS::TAG_EXISTS);
$ticket->sharedTagList->add($tag);
$ticket->store();
Response::respondSuccess();
}
}

View File

@ -0,0 +1,91 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/change-department Change department
* @apiVersion 4.11.0
*
* @apiName Change department
*
* @apiGroup Ticket
*
* @apiDescription This path changes the department of a ticket.
*
* @apiPermission staff1
*
* @apiParam {Number} ticketNumber The number of the ticket.
* @apiParam {Number} departmentId The id of the new department of the ticket.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
* @apiUse INVALID_DEPARTMENT
*
* @apiSuccess {Object} data Empty object
*
*/
class ChangeDepartmentController extends Controller {
const PATH = '/change-department';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
],
'departmentId' => [
'validation' => DataValidator::dataStoreId('department'),
'error' => ERRORS::INVALID_DEPARTMENT
]
]
];
}
public function handler() {
$ticketNumber = Controller::request('ticketNumber');
$departmentId = Controller::request('departmentId');
$ticket = Ticket::getByTicketNumber($ticketNumber);
$department = Department::getDataStore($departmentId);
$user = Controller::getLoggedUser();
if(!$ticket->authorStaffId && $department->private){
throw new Exception(ERRORS::NO_PERMISSION);
}
if(!$user->canManageTicket($ticket)){
throw new RequestException(ERRORS::NO_PERMISSION);
}
if($ticket->department->id == $department->id){
throw new RequestException(ERRORS::SAME_DEPARTMENT);
}
if($ticket->owner && !$ticket->owner->sharedDepartmentList->includesId($department->id)) {
$unAssignTicketController = new UnAssignStaffController($user);
$unAssignTicketController->validate();
$unAssignTicketController->handler();
}
$ticket = Ticket::getByTicketNumber($ticketNumber);
$event = Ticketevent::getEvent(Ticketevent::DEPARTMENT_CHANGED);
$event->setProperties(array(
'authorStaff' => $user,
'content' => $department->name,
'date' => Date::getCurrentDate()
));
$ticket->addEvent($event);
$ticket->department = $department;
$ticket->totalDepartments++;
$ticket->unread = !$ticket->isAuthor($user);
$ticket->store();
Log::createLog('DEPARTMENT_CHANGED', $ticket->ticketNumber);
Response::respondSuccess();
}
}

79
dist/api/controllers/ticket/check.php vendored Executable file
View File

@ -0,0 +1,79 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/check Check ticket
* @apiVersion 4.11.0
*
* @apiName Check ticket
*
* @apiGroup Ticket
*
* @apiDescription This path logs you in to see a ticket, but only when there is no users.
*
* @apiPermission any
*
* @apiParam {Number} ticketNumber The number of a ticket.
* @apiParam {String} email Email of the person who created the ticket.
* @apiParam {String} captcha Encrypted value generated by google captcha client.
*
* @apiUse INVALID_TICKET
* @apiUse INVALID_EMAIL
* @apiUse INVALID_CAPTCHA
* @apiUse NO_PERMISSION
*
* @apiSuccess {Object} data Data for the ticket session
* @apiSuccess {String} data.token Token of the ticket session
* @apiSuccess {Number} data.ticketNumber Number of the ticket
*
*/
class CheckTicketController extends Controller {
const PATH = '/check';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
],
'email' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
],
'captcha' => [
'validation' => DataValidator::captcha(APIKey::TICKET_CHECK_PERMISSION),
'error' => ERRORS::INVALID_CAPTCHA
]
]
];
}
public function handler() {
if (Controller::isLoginMandatory()) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
$email = Controller::request('email');
$ticketNumber = Controller::request('ticketNumber');
$ticket = Ticket::getByTicketNumber($ticketNumber);
if($ticket->authorEmail === $email) {
$session = Session::getInstance();
$user = User::getUser($email, 'email');
$session->createSession($user->id, false, $ticketNumber);
Response::respondSuccess([
'token' => $session->getToken(),
'userId' => $session->getUserId(),
'ticketNumber' => $session->getTicketNumber()
]);
} else {
throw new RequestException(ERRORS::NO_PERMISSION);
}
}
}

103
dist/api/controllers/ticket/close.php vendored Executable file
View File

@ -0,0 +1,103 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/close Close ticket
* @apiVersion 4.11.0
*
* @apiName Close
*
* @apiGroup Ticket
*
* @apiDescription This path closes a ticket.
*
* @apiPermission user
*
* @apiParam {Number} ticketNumber The number of a ticket.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
*
* @apiSuccess {Object} data Empty object
*
*/
class CloseController extends Controller {
const PATH = '/close';
const METHOD = 'POST';
private $ticket;
public function validations() {
$session = Session::getInstance();
return [
'permission' => 'user',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
}
public function handler() {
$this->ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
$user = Controller::getLoggedUser();
if(!$user->canManageTicket($this->ticket)){
throw new RequestException(ERRORS::NO_PERMISSION);
}
$this->markAsUnread();
$this->addCloseEvent();
$this->ticket->closed = true;
if (!$this->ticket->first_closed_at) {
$this->ticket->first_closed_at = Date::getCurrentDate();
}
$this->ticket->last_closed_at = Date::getCurrentDate();
$this->ticket->store();
$this->sendMail();
Log::createLog('CLOSE', $this->ticket->ticketNumber);
Response::respondSuccess();
}
private function markAsUnread() {
$this->ticket->unread = !$this->ticket->isAuthor(Controller::getLoggedUser());
$this->ticket->unreadStaff = !$this->ticket->isOwner(Controller::getLoggedUser());
}
private function addCloseEvent() {
$event = Ticketevent::getEvent(Ticketevent::CLOSE);
$event->setProperties(array(
'date' => Date::getCurrentDate()
));
if(Controller::isStaffLogged()) {
$event->authorStaff = Controller::getLoggedUser();
} else {
$event->authorUser = Controller::getLoggedUser();
}
$this->ticket->addEvent($event);
}
private function sendMail() {
$mailSender = MailSender::getInstance();
$mailSender->setTemplate(MailTemplate::TICKET_CLOSED, [
'to' => ($this->ticket->author) ? $this->ticket->author->email : $this->ticket->authorEmail,
'name' => ($this->ticket->author) ? $this->ticket->author->name : $this->ticket->authorName,
'ticketNumber' => $this->ticket->ticketNumber,
'title' => $this->ticket->title,
'url' => Setting::getSetting('url')->getValue()
]);
$mailSender->send();
}
}

161
dist/api/controllers/ticket/comment.php vendored Executable file
View File

@ -0,0 +1,161 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/comment Comment ticket
* @apiVersion 4.11.0
*
* @apiName Comment ticket
*
* @apiGroup Ticket
*
* @apiDescription This path comments a ticket.
*
* @apiPermission user
*
* @apiParam {String} content Content of the comment.
* @apiParam {Number} ticketNumber The number of the ticket to comment.
* @apiParam {Boolean} private Indicates if the comment is not shown to users.
* @apiParam {Number} images The number of images in the content.
* @apiParam {String} apiKey apiKey to comment a ticket.
* @apiParam image_i The image file of index `i` (mutiple params accepted)
* @apiParam file The file you with to upload.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_CONTENT
* @apiUse INVALID_TICKET
* @apiUse INVALID_FILE
*
* @apiSuccess {Object} data Empty object
*
*/
class CommentController extends Controller {
const PATH = '/comment';
const METHOD = 'POST';
private $ticket;
private $content;
private $session;
private $imagePaths;
public function validations() {
$validations = [
'permission' => 'user',
'requestData' => [
'content' => [
'validation' => DataValidator::content(),
'error' => ERRORS::INVALID_CONTENT
],
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
if(Controller::request('captcha')){
$validations['permission'] = 'any';
$validations['requestData']['captcha'] = [
'validation' => DataValidator::captcha(APIKey::TICKET_CREATE_PERMISSION),
'error' => ERRORS::INVALID_CAPTCHA
];
}
return $validations;
}
public function handler() {
$ticketNumber = Controller::request('ticketNumber');
$this->ticket = Ticket::getByTicketNumber($ticketNumber);
$this->content = Controller::request('content', true);
$this->user = Controller::getLoggedUser();
$ticketAuthor = $this->ticket->authorToArray();
$isAuthor = $this->ticket->isAuthor($this->user);
$isOwner = $this->ticket->isOwner($this->user);
$private = Controller::request('private');
$apiKey = APIKey::getDataStore(Controller::request('apiKey'), 'token');
if(!$this->user->canManageTicket($this->ticket)) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
$this->storeComment();
if(!$isAuthor && !$private) {
$this->sendMail($ticketAuthor);
}
if($this->ticket->owner && !$isOwner) {
$this->sendMail([
'email' => $this->ticket->owner->email,
'name' => $this->ticket->owner->name,
'staff' => true
]);
}
Log::createLog('COMMENT', $this->ticket->ticketNumber);
Response::respondSuccess();
}
private function storeComment() {
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_TICKET, $this->ticket->ticketNumber);
$fileUploader = $this->uploadFile(Controller::isStaffLogged());
$comment = Ticketevent::getEvent(Ticketevent::COMMENT);
$comment->setProperties(array(
'content' => $this->replaceWithImagePaths($this->getImagePaths(), $this->content),
'file' => ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null,
'date' => Date::getCurrentDate(),
'private' => (Controller::isStaffLogged() && Controller::request('private')) ? 1 : 0
));
if(Controller::isStaffLogged()) {
$this->ticket->unread = !$this->ticket->isAuthor($this->user);
$this->ticket->unreadStaff = !$this->ticket->isOwner($this->user);
$comment->authorStaff = $this->user;
} else {
$this->ticket->unreadStaff = true;
$comment->authorUser = $this->user;
}
$this->ticket->addEvent($comment);
$this->ticket->store();
}
private function sendMail($recipient) {
$mailSender = MailSender::getInstance();
$email = $recipient['email'];
$name = $recipient['name'];
$isStaff = array_key_exists('staff', $recipient) && $recipient['staff'];
$url = Setting::getSetting('url')->getValue();
if(!Controller::isLoginMandatory() && !$isStaff){
$url .= '/check-ticket/' . $this->ticket->ticketNumber;
$url .= '/' . $email;
}
$mailSender->setTemplate(MailTemplate::TICKET_RESPONDED, [
'to' => $email,
'name' => $name,
'title' => $this->ticket->title,
'ticketNumber' => $this->ticket->ticketNumber,
'content' => $this->replaceWithImagePaths($this->getImagePaths(), $this->content),
'url' => $url
]);
$mailSender->send();
}
private function getImagePaths() {
if(!$this->imagePaths){
$this->imagePaths = $this->uploadImages(Controller::isStaffLogged());
}
return $this->imagePaths;
}
}

65
dist/api/controllers/ticket/create-tag.php vendored Executable file
View File

@ -0,0 +1,65 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/create-tag Create tag
* @apiVersion 4.11.0
*
* @apiName Create tag
*
* @apiGroup Ticket
*
* @apiDescription This path creates a new tag.
*
* @apiPermission staff3
*
* @apiParam {Number} name The new name of the tag.
* @apiParam {String} color The new color of the tag.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
* @apiUse TAG_EXISTS
*
* @apiSuccess {Object} data Empty object
*
*/
class CreateTagController extends Controller {
const PATH = '/create-tag';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
],
'color' => [
'validation' => DataValidator::hexRgbColor()->startsWith('#'),
'error' => ERRORS::INVALID_COLOR
]
]
];
}
public function handler() {
$name = Controller::request('name', true);
$color = Controller::request('color');
if (!Tag::getDataStore($name, 'name')->isNull()) {
throw new RequestException(ERRORS::TAG_EXISTS);
}
$tagInstance = new Tag();
$tagInstance->setProperties([
'name' => $name,
'color' => $color
]);
$tagInstance->store();
Response::respondSuccess();
}
}

274
dist/api/controllers/ticket/create.php vendored Executable file
View File

@ -0,0 +1,274 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/create Create ticket
* @apiVersion 4.11.0
*
* @apiName Create ticket
*
* @apiGroup Ticket
*
* @apiDescription This path creates a new ticket.
*
* @apiPermission user
*
* @apiParam {String} title Title of the ticket.
* @apiParam {String} content Content of the ticket.
* @apiParam {Number} departmentId The id of the department of the current ticket.
* @apiParam {String} language The language of the ticket.
* @apiParam {String} name The name of author of the ticket.
* @apiParam {String} email The email of the user who created the ticket.
* @apiParam {Number} images The number of images in the content.
* @apiParam {String} apiKey apiKey to create tickets and show ticket-number created.
* @apiParam image_i The image file of index `i` (mutiple params accepted)
* @apiParam file The file you with to upload.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TITLE
* @apiUse INVALID_CONTENT
* @apiUse INVALID_DEPARTMENT
* @apiUse INVALID_LANGUAGE
* @apiUse INVALID_CAPTCHA
* @apiUse INVALID_EMAIL
* @apiUse INVALID_FILE
*
* @apiSuccess {Object} data Information of the new ticket
* @apiSuccess {Number} data.ticketNumber Number of the new ticket
*
*/
class CreateController extends Controller {
const PATH = '/create';
const METHOD = 'POST';
private $title;
private $content;
private $departmentId;
private $language;
private $ticketNumber;
private $email;
private $name;
private $apiKey;
public function validations() {
$validations = [
'permission' => 'user',
'requestData' => [
'title' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_TITLE, LengthConfig::MAX_LENGTH_TITLE),
'error' => ERRORS::INVALID_TITLE
],
'content' => [
'validation' => DataValidator::content(),
'error' => ERRORS::INVALID_CONTENT
],
'departmentId' => [
'validation' => DataValidator::oneOf(DataValidator::dataStoreId('department'), DataValidator::nullType()),
'error' => ERRORS::INVALID_DEPARTMENT
],
'language' => [
'validation' => DataValidator::oneOf(DataValidator::in(Language::getSupportedLanguages()), DataValidator::nullType()),
'error' => ERRORS::INVALID_LANGUAGE
]
]
];
if (!Controller::isLoginMandatory() && !Controller::isStaffLogged() && !Controller::isUserLogged()) {
$validations['permission'] = 'any';
$validations['requestData']['captcha'] = [
'validation' => DataValidator::captcha(APIKey::TICKET_CREATE_PERMISSION),
'error' => ERRORS::INVALID_CAPTCHA
];
$validations['requestData']['email'] = [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
];
$validations['requestData']['name'] = [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
];
}
return $validations;
}
public function handler() {
$session = Session::getInstance();
if($session->isTicketSession()) {
$session->clearSessionData();
}
$this->title = Controller::request('title', true);
$this->content = Controller::request('content', true);
$this->departmentId = Controller::request('departmentId');
$this->language = Controller::request('language');
$this->email = Controller::request('email');
$this->name = Controller::request('name');
$this->apiKey = APIKey::getDataStore(Controller::request('apiKey'), 'token');
if(!Controller::isStaffLogged() && Department::getDataStore($this->departmentId)->private) {
throw new Exception(ERRORS::INVALID_DEPARTMENT);
}
if(!Staff::getUser($this->email,'email')->isNull() || $this->isEmailInvalid()) {
throw new Exception(ERRORS::INVALID_EMAIL);
}
if(!Controller::isLoginMandatory() && !Controller::isStaffLogged() && !Controller::isUserLogged() && User::getUser($this->email, 'email')->isNull()){
$this->createNewUser();
}
$this->storeTicket();
if(!Controller::isLoginMandatory() && !Controller::isUserLogged()) {
$this->sendMail();
}
$staffs = Staff::find('send_email_on_new_ticket = 1');
foreach ($staffs as $staff) {
if($staff->sharedDepartmentList->includesId(Controller::request('departmentId'))) {
$this->sendMailStaff($staff->email);
}
}
Log::createLog('CREATE_TICKET', $this->ticketNumber);
if(!$this->apiKey->isNull() && $this->apiKey->shouldReturnTicketNumber){
Response::respondSuccess([
'ticketNumber' => $this->ticketNumber
]);
}else{
Response::respondSuccess();
}
}
private function isEmailInvalid(){
$session = Session::getInstance();
$sessionUser = User::getUser($session->getUserId() ,'id');
return ($session->sessionExists() && $sessionUser && $this->email && !($sessionUser->email == $this->email));
}
private function createNewUser() {
$signupController = new SignUpController(true);
Controller::setDataRequester(function ($key) {
switch ($key) {
case 'email':
return $this->email;
case 'password':
return Hashing::generateRandomToken();
case 'name':
return $this->name;
case 'indirectSignUp' :
return true;
}
return null;
});
$signupController->validations();
$signupController->handler();
}
private function storeTicket() {
$department = Department::getDataStore($this->getCorrectDepartmentId());
$author = $this->getAuthor();
$this->language = $this->getCorrectLanguage();
$ticket = new Ticket();
$fileUploader = FileUploader::getInstance();
$fileUploader->setPermission(FileManager::PERMISSION_TICKET, $ticket->generateUniqueTicketNumber());
$imagePaths = $this->uploadImages(Controller::isStaffLogged());
$fileUploader = $this->uploadFile(Controller::isStaffLogged());
$ticket->setProperties(array(
'title' => $this->title,
'content' => $this->replaceWithImagePaths($imagePaths, $this->content),
'language' => $this->language,
'department' => $department,
'file' => ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null,
'date' => Date::getCurrentDate(),
'unread' => false,
'unreadStaff' => true,
'closed' => false,
'authorName' => $this->name,
'authorEmail' => $this->email,
'totalDepartments' => 0,
'totalOwners' => 0
));
$ticket->setAuthor($author);
$author->sharedTicketList->add($ticket);
if(!Controller::isStaffLogged()) {
$author->tickets++;
$this->email = $author->email;
$this->name = $author->name;
}
$author->store();
$ticket->store();
$this->ticketNumber = $ticket->ticketNumber;
}
private function getCorrectLanguage() {
if($this->language){
return $this->language;
}else{
return Setting::getSetting('language')->getValue();
}
}
private function getCorrectDepartmentId(){
$defaultDepartmentId = Setting::getSetting('default-department-id')->getValue();
$isLocked = Setting::getSetting('default-is-locked')->getValue();
$validDepartment = Department::getDataStore($defaultDepartmentId)->id;
if (Controller::isStaffLogged()) {
if ($this->departmentId) $validDepartment = $this->departmentId;
} else {
if (!$isLocked && $this->departmentId) $validDepartment = $this->departmentId;
}
return $validDepartment;
}
private function getAuthor() {
if (!Controller::getLoggedUser()->isNull()) {
return Controller::getLoggedUser();
} else {
return User::getUser($this->email, 'email');
}
}
private function sendMail() {
$mailSender = MailSender::getInstance();
$mailSender->setTemplate(MailTemplate::TICKET_CREATED, [
'to' => $this->email,
'name' => $this->name,
'ticketNumber' => $this->ticketNumber,
'title' => $this->title,
'url' => Setting::getSetting('url')->getValue()
]);
$mailSender->send();
}
private function sendMailStaff($email) {
$mailSender = MailSender::getInstance();
$mailSender->setTemplate(MailTemplate::TICKET_CREATED_STAFF, [
'to' => $email,
'name' => $this->name,
'ticketNumber' => $this->ticketNumber,
'title' => $this->title
]);
$mailSender->send();
}
}

View File

@ -0,0 +1,49 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/delete-custom-response Delete custom response
* @apiVersion 4.11.0
*
* @apiName Delete custom response
*
* @apiGroup Ticket
*
* @apiDescription This path deletes a custom response.
*
* @apiPermission user
*
* @apiParam {Number} id Id of the custom response to delete.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteCustomResponseController extends Controller {
const PATH = '/delete-custom-response';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'id' => [
'validation' => DataValidator::dataStoreId('customresponse'),
'error' => ERRORS::INVALID_NAME
]
]
];
}
public function handler() {
$customResponse = CustomResponse::getDataStore(Controller::request('id'));
$customResponse->delete();
Log::createLog('DELETE_CUSTOM_RESPONSE', null);
Response::respondSuccess();
}
}

50
dist/api/controllers/ticket/delete-tag.php vendored Executable file
View File

@ -0,0 +1,50 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/delete-tag Delete a tag
* @apiVersion 4.11.0
*
* @apiName Delete tag
*
* @apiGroup Ticket
*
* @apiDescription This path delete a tag.
*
* @apiPermission staff3
*
* @apiParam {Number} tagId The id of the tag.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TAG
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteTagController extends Controller {
const PATH = '/delete-tag';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'tagId' => [
'validation' => DataValidator::dataStoreId('tag'),
'error' => ERRORS::INVALID_TAG
]
]
];
}
public function handler() {
$tagInstance = Tag::getDataStore(Controller::request('tagId'));
$tagInstance->delete();
Response::respondSuccess();
}
}

69
dist/api/controllers/ticket/delete.php vendored Executable file
View File

@ -0,0 +1,69 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/delete Delete a ticket
* @apiVersion 4.11.0
*
* @apiName Delete ticket
*
* @apiGroup Ticket
*
* @apiDescription This path deletes a ticket.
*
* @apiPermission user
*
* @apiParam {Number} ticketNumber The number of the ticket to delete.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
*
* @apiSuccess {Object} data Empty object
*
*/
class DeleteController extends Controller {
const PATH = '/delete';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
}
public function handler() {
$user = Controller::getLoggedUser();
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
$ticketAuthor = $ticket->authorToArray();
if($ticket->owner) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(Controller::isStaffLogged() && $user->level < 3 && ($user->email !== $ticketAuthor['email'])) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(!Controller::isStaffLogged() && ($user->email !== $ticketAuthor['email'] || $ticketAuthor['staff'])) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if($ticket->author){
$ticketAuthor = User::getUser($ticket->authorToArray()['id']);
$ticketAuthor->tickets--;
$ticketAuthor->store();
}
$ticket->delete();
Response::respondSuccess();
}
}

100
dist/api/controllers/ticket/edit-comment.php vendored Executable file
View File

@ -0,0 +1,100 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/edit-comment Edit a comment
* @apiVersion 4.11.0
*
* @apiName Edit comment
*
* @apiGroup Ticket
*
* @apiDescription This path edits a comment.
*
* @apiPermission user
*
* @apiParam {String} content The new content of the comment.
* @apiParam {Number} ticketEventId The id of the ticket event.
* @apiParam {Number} ticketNumber The number of the ticket.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_CONTENT
* @apiUse INVALID_TICKET
* @apiUse INVALID_TICKET_EVENT
* @apiUse TICKET_CONTENT_CANNOT_BE_EDITED
*
* @apiSuccess {Object} data Empty object
*
*/
class EditCommentController extends Controller {
const PATH = '/edit-comment';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'content' => [
'validation' => DataValidator::content(),
'error' => ERRORS::INVALID_CONTENT
],
'ticketNumber' => [
'validation' => DataValidator::oneOf(DataValidator::validTicketNumber(),DataValidator::nullType()),
'error' => ERRORS::INVALID_TICKET
]
]
];
}
public function handler() {
$user = Controller::getLoggedUser();
$newcontent = Controller::request('content', true);
$ticketNumberLog = null;
$ticketEvent = Ticketevent::getTicketEvent(Controller::request('ticketEventId'));
$commentAuthor = null;
if(!$ticketEvent->isNull()) {
$ticket = Ticket::getDataStore($ticketEvent->ticketId);
$commentAuthor = $ticketEvent->toArray()["author"];
} else {
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
$commentAuthor = $ticket->toArray()["author"];
}
if((!!$user->toArray()["isStaff"] !== !!$commentAuthor["staff"]) || ($user->id !== $commentAuthor["id"])) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(Controller::isStaffLogged() && !$user->canManageTicket($ticket)) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if(!$ticketEvent->isNull()) {
if($ticketEvent->type !== "COMMENT" || $ticket->closed || $ticket->getLatestEventOfType("COMMENT")['id'] !== $ticketEvent->id) {
throw new RequestException(ERRORS::INVALID_TICKET_EVENT);
}
} else if(sizeof($ticket->getEventsOfType("COMMENT"))) {
throw new RequestException(ERRORS::TICKET_CONTENT_CANNOT_BE_EDITED);
}
if(!$ticketEvent->isNull()){
$ticketNumber = Ticket::getTicket($ticketEvent->ticketId)->ticketNumber;
$ticketEvent->content = $newcontent;
$ticketEvent->editedContent = true;
$ticketEvent->store();
} else {
$ticketNumber = $ticket->ticketNumber;
$ticket->content = $newcontent;
$ticket->editedContent = true;
$ticket->store();
}
Log::createLog('EDIT_COMMENT', $ticketNumber);
Response::respondSuccess();
}
}

View File

@ -0,0 +1,76 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/edit-custom-response Edit custom response
* @apiVersion 4.11.0
*
* @apiName Edit custom response
*
* @apiGroup Ticket
*
* @apiDescription This path edits a custom response.
*
* @apiPermission staff2
*
* @apiParam {Number} id Id of the custom response to edit.
* @apiParam {String} content The new content of the custom response. It won't be changed if not defined.
* @apiParam {String} language The new language of the custom response. It won't be changed if not defined,
* @apiParam {String} name The new name of the custom response. It won't be changed if not defined.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_NAME
*
* @apiSuccess {Object} data Empty object
*
*/
class EditCustomResponseController extends Controller {
const PATH = '/edit-custom-response';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_2',
'requestData' => [
'id' => [
'validation' => DataValidator::dataStoreId('customresponse'),
'error' => ERRORS::INVALID_CUSTOM_RESPONSE
],
'content' => [
'validation' => DataValidator::content(),
'error' => ERRORS::INVALID_CONTENT
],
'name' => [
'validation' => DataValidator::oneOf(
DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
DataValidator::nullType()
),
'error' => ERRORS::INVALID_NAME
],
]
];
}
public function handler() {
$customResponse = CustomResponse::getDataStore(Controller::request('id'));
if (Controller::request('content')) {
$customResponse->content = Controller::request('content', true);
}
if (Controller::request('language')) {
$customResponse->language = Controller::request('language');
}
if (Controller::request('name')) {
$customResponse->name = Controller::request('name');
}
$customResponse->store();
Log::createLog('EDIT_CUSTOM_RESPONSE', null);
Response::respondSuccess();
}
}

70
dist/api/controllers/ticket/edit-tag.php vendored Executable file
View File

@ -0,0 +1,70 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/edit-tag Edit tag
* @apiVersion 4.11.0
*
* @apiName Edit tag
*
* @apiGroup Ticket
*
* @apiDescription This path edit tags.
*
* @apiPermission staff3
*
* @apiParam {Number} tagId The id of the tag.
* @apiParam {Number} name The new name of the tag.
* @apiParam {String} color The new color of the tag.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TAG
* @apiUse TAG_EXISTS
*
* @apiSuccess {Object} data Empty object
*
*/
class EditTagController extends Controller {
const PATH = '/edit-tag';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => [
'tagId' => [
'validation' => DataValidator::dataStoreId('tag'),
'error' => ERRORS::INVALID_TAG
],
'color' => [
'validation' => DataValidator::hexRgbColor()->startsWith('#'),
'error' => ERRORS::INVALID_COLOR
],
'name' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_NAME, LengthConfig::MAX_LENGTH_NAME),
'error' => ERRORS::INVALID_NAME
]
]
];
}
public function handler() {
$name = Controller::request('name');
$color = Controller::request('color');
$tagInstance = Tag::getDataStore(Controller::request('tagId'));
if($name) $tagInstance->name = $name;
if($color) $tagInstance->color = $color;
$newNameTagInstance = Tag::getDataStore($name, 'name');
if (!$newNameTagInstance ->isNull() && $newNameTagInstance->id !== $tagInstance->id) {
throw new RequestException(ERRORS::TAG_EXISTS);
}
$tagInstance->store();
Response::respondSuccess();
}
}

66
dist/api/controllers/ticket/edit-title.php vendored Executable file
View File

@ -0,0 +1,66 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/edit-title Edit title of a ticket
* @apiVersion 4.11.0
*
* @apiName Edit title
*
* @apiGroup Ticket
*
* @apiDescription This path edits the title of a ticket.
*
* @apiPermission user
*
* @apiParam {String} title The new title of the ticket.
* @apiParam {Number} ticketNumber The number of the ticket.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TITLE
* @apiUse INVALID_TICKET
*
* @apiSuccess {Object} data Empty object
*
*/
class EditTitleController extends Controller {
const PATH = '/edit-title';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'title' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_TITLE, LengthConfig::MAX_LENGTH_TITLE),
'error' => ERRORS::INVALID_TITLE
],
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
}
public function handler() {
$user = Controller::getLoggedUser();
$newtitle = Controller::request('title');
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
if(!$user->canManageTicket($ticket)) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
$ticket->title = $newtitle;
$ticket->editedTitle = true;
$ticket->store();
$ticketNumber = $ticket->ticketNumber;
Log::createLog('EDIT_TITLE', $ticketNumber);
Response::respondSuccess();
}
}

60
dist/api/controllers/ticket/get-authors.php vendored Executable file
View File

@ -0,0 +1,60 @@
<?php
use Respect\Validation\Validator as DataValidator;
use RedBeanPHP\Facade as RedBean;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/get-authors Get authors of tickets
* @apiVersion 4.11.0
*
* @apiName Get authors
*
* @apiGroup Ticket
*
* @apiDescription This path returns all the authors that match with the ids of the list .
*
* @apiPermission staff1
*
*
* @apiParam {Object[]} authors A array of object {id, isStaff} with id and boolean to get users or staffs.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_LIST
*
* @apiSuccess {Object} data Empty object
*
*/
class GetAuthorsController extends Controller {
const PATH = '/get-authors';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'authors' => [
'validation' => DataValidator::ValidAuthorsList(),
'error' => ERRORS::INVALID_LIST
]
]
];
}
public function handler() {
$dataStores = [];
$authors = json_decode(Controller::request('authors'));
if($authors){
foreach ($authors as $author) {
$item = [];
if($author->isStaff){
$item = Staff::getUser($author->id)->toArray(true);
}else{
$item = User::getUser($author->id)->toArray(true);
}
array_push($dataStores,$item);
}
}
Response::respondSuccess($dataStores);
}
}

View File

@ -0,0 +1,39 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/get-custom-responses Get custom responses
* @apiVersion 4.11.0
*
* @apiName Get custom responses
*
* @apiGroup Ticket
*
* @apiDescription This path returns all the custom responses.
*
* @apiPermission staff1
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[CustomResponse](#api-Data_Structures-ObjectCustomresponse)[]} data List of custom responses.
*
*/
class GetCustomResponsesController extends Controller {
const PATH = '/get-custom-responses';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => []
];
}
public function handler() {
$customResponsesList = CustomResponse::getAll();
Response::respondSuccess($customResponsesList->toArray());
}
}

39
dist/api/controllers/ticket/get-tags.php vendored Executable file
View File

@ -0,0 +1,39 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/get-tags Get tags
* @apiVersion 4.11.0
*
* @apiName Get tags
*
* @apiGroup Ticket
*
* @apiDescription This path returns all the tags.
*
* @apiPermission staff3
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {Object} data Empty object
*
*/
class GetTagsController extends Controller {
const PATH = '/get-tags';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_3',
'requestData' => []
];
}
public function handler() {
$tags = Tag::getAll();
Response::respondSuccess($tags->toArray());
}
}

58
dist/api/controllers/ticket/get.php vendored Executable file
View File

@ -0,0 +1,58 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/get Get ticket
* @apiVersion 4.11.0
*
* @apiName Get ticket
*
* @apiGroup Ticket
*
* @apiDescription This path retrieves information about a ticket.
*
* @apiPermission user
*
* @apiParam {Number} ticketNumber The number of the ticket.
*
* @apiUse NO_PERMISSION
*
* @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)} data Information about the requested ticket.
*
*/
class TicketGetController extends Controller {
const PATH = '/get';
const METHOD = 'POST';
private $ticket;
public function validations() {
$session = Session::getInstance();
return [
'permission' => 'user',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::NO_PERMISSION
]
]
];
}
public function handler() {
$this->ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
if ($this->shouldDenyPermission()) {
throw new RequestException(ERRORS::NO_PERMISSION);
} else {
Response::respondSuccess($this->ticket->toArray());
}
}
private function shouldDenyPermission() {
$user = Controller::getLoggedUser();
return !$user->canManageTicket($this->ticket);
}
}

86
dist/api/controllers/ticket/re-open.php vendored Executable file
View File

@ -0,0 +1,86 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /ticket/re-open Reopen ticket
* @apiVersion 4.11.0
*
* @apiName Reopen ticket
*
* @apiGroup Ticket
*
* @apiDescription This path reopens a closed ticket.
*
* @apiPermission user
*
* @apiParam {String} ticketNumber Number of the ticket to be reopened.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
*
* @apiSuccess {Object} data Empty object
*
*/
class ReOpenController extends Controller {
const PATH = '/re-open';
const METHOD = 'POST';
private $ticket;
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
}
public function handler() {
$this->ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
$user = Controller::getLoggedUser();
if (!$user->canManageTicket($this->ticket)) throw new RequestException(ERRORS::NO_PERMISSION);
$this->markAsUnread();
$this->addReopenEvent();
if ($this->ticket->closed) {
$this->ticket->reopened = true;
}
$this->ticket->closed = false;
$this->ticket->store();
Log::createLog('RE_OPEN', $this->ticket->ticketNumber);
Response::respondSuccess();
}
private function markAsUnread() {
if(Controller::isStaffLogged()) {
$this->ticket->unread = true;
} else {
$this->ticket->unreadStaff = true;
}
}
private function addReopenEvent() {
$event = Ticketevent::getEvent(Ticketevent::RE_OPEN);
$event->setProperties(array(
'date' => Date::getCurrentDate()
));
if(Controller::isStaffLogged()) {
$event->authorStaff = Controller::getLoggedUser();
} else {
$event->authorUser = Controller::getLoggedUser();
}
$this->ticket->addEvent($event);
}
}

63
dist/api/controllers/ticket/remove-tag.php vendored Executable file
View File

@ -0,0 +1,63 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/remove-tag Remove tag
* @apiVersion 4.11.0
*
* @apiName Remove tag
*
* @apiGroup Ticket
*
* @apiDescription This path removes a tag from a ticket.
*
* @apiPermission staff1
*
* @apiParam {Number} ticketNumber The number of the ticket which the tag is going to be removed.
* @apiParam {String} tagId The id of the tag to be removed.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
* @apiUse INVALID_TAG
*
* @apiSuccess {Object} data Empty object
*
*/
class RemoveTagController extends Controller {
const PATH = '/remove-tag';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
],
'tagId' => [
'validation' => DataValidator::dataStoreId('tag'),
'error' => ERRORS::INVALID_TAG
]
]
];
}
public function handler() {
$tagId = Controller::request('tagId');
$tag = Tag::getDataStore($tagId);
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
$user = Controller::getLoggedUser();
if (!$user->canManageTicket($ticket)) throw new RequestException(ERRORS::NO_PERMISSION);
if (!$ticket->sharedTagList->includesId($tagId)) throw new RequestException(ERRORS::INVALID_TAG);
$ticket->sharedTagList->remove($tag);
$ticket->store();
Response::respondSuccess();
}
}

122
dist/api/controllers/ticket/search-authors.php vendored Executable file
View File

@ -0,0 +1,122 @@
<?php
use Respect\Validation\Validator as DataValidator;
use RedBeanPHP\Facade as RedBean;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/search-authors search authors of tickets
* @apiVersion 4.11.0
*
* @apiName Search authors
*
* @apiGroup Ticket
*
* @apiDescription This path returns all the authors that match with the query.
*
* @apiPermission staff1
*
*
* @apiParam {String} query A string to find into a ticket to make a custom search.
* @apiParam {Object[]} blackList A array of objects {id, isStaff} with id and boolean to eliminate the authors of the new list.
* @apiParam {Boolean} searchUsers A boolean that determinates if the search is for users or not
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_QUERY
*
* @apiSuccess {Object} data Empty object
*
*/
class SearchAuthorsController extends Controller {
const PATH = '/search-authors';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'query' => [
'validation' => DataValidator::oneOf(DataValidator::stringType(),DataValidator::nullType()),
'error' => ERRORS::INVALID_QUERY
],
'blackList' => [
'validation' => DataValidator::oneOf(DataValidator::validAuthorsBlackList(),DataValidator::nullType()),
'error' => ERRORS::INVALID_BLACK_LIST
],
'searchUsers' => [
'validation' => DataValidator::oneOf(DataValidator::in(['0','1']),DataValidator::nullType()),
'error' => ERRORS::INVALID_USER_SEARCH_OPTION
]
]
];
}
public function handler() {
$query = Controller::request('query');
$searchUser = Controller::request('searchUsers') ? Controller::request('searchUsers') : 0;
if(!$searchUser){
$sqlQuery = $this->generateAuthorsIdQuery($query);
}else{
$sqlQuery = $this->generateUsersIdQuery($query);
}
$dataStoresMatch = RedBean::getAll($sqlQuery, [':query' => "%" .$query . "%",':queryAtBeginning' => $query . "%"] );
$list = [];
foreach($dataStoresMatch as $dataStoreMatch) {
if(!$searchUser && $dataStoreMatch['level'] >=1 && $dataStoreMatch['level'] <= 3){
$dataStore = Staff::getDataStore($dataStoreMatch['id']*1);
} else {
$dataStore = User::getDataStore($dataStoreMatch['id']*1);
}
array_push($list, $dataStore->toArray(true));
}
Response::respondSuccess([
'authors' => $list
]);
}
public function generateAuthorsIdQuery($query) {
if ($query){
return "SELECT id,name, level FROM staff WHERE name LIKE :query " . $this->generateStaffBlackListQuery() . " UNION SELECT id,name,signup_date FROM user WHERE name LIKE :query " . $this->generateUserBlackListQuery() . " ORDER BY CASE WHEN (name LIKE :queryAtBeginning) THEN 1 ELSE 2 END ASC LIMIT 10";
} else {
return "SELECT id,name, level FROM staff WHERE 1=1 ". $this->generateStaffBlackListQuery() . " UNION SELECT id,name,signup_date FROM user WHERE 1=1". $this->generateUserBlackListQuery() ." ORDER BY id LIMIT 10";
}
}
public function generateUsersIdQuery($query) {
if ($query){
return "SELECT id FROM user WHERE name LIKE :query " . $this->generateUserBlackListQuery() . " ORDER BY CASE WHEN (name LIKE :queryAtBeginning) THEN 1 ELSE 2 END ASC LIMIT 10";
} else {
return "SELECT id FROM user WHERE 1=1 ". $this->generateUserBlackListQuery() ." ORDER BY id LIMIT 10";
}
}
public function generateStaffBlackListQuery(){
$StaffBlackList = $this->getBlackListFiltered();
return $this->generateBlackListQuery($StaffBlackList);
}
public function generateUserBlackListQuery(){
$UserBlackList = $this->getBlackListFiltered(0);
return $this->generateBlackListQuery($UserBlackList);
}
public function generateBlackListQuery($idList){
$text = "";
foreach ($idList as $id) {
$text .= " AND id != " . $id;
}
return $text;
}
public function getBlackListFiltered($staff = 1){
$blackList = json_decode(Controller::request('blackList'));
$idList = [];
if($blackList){
foreach ($blackList as $item) {
if($staff == $item->isStaff) array_push($idList, $item->id);
}
}
return $idList;
}
}

374
dist/api/controllers/ticket/search.php vendored Executable file
View File

@ -0,0 +1,374 @@
<?php
use Respect\Validation\Validator as DataValidator;
use RedBeanPHP\Facade as RedBean;
DataValidator::with('CustomValidations', true);
/**
* @api {post} /ticket/search Search tickets
* @apiVersion 4.11.0
*
* @apiName Search ticket
*
* @apiGroup Ticket
*
* @apiDescription This path search specific tickets.
*
* @apiPermission user
*
* @apiParam {Number[]} tags The ids of the tags to make a custom search.
* @apiParam {Number} closed The status of closed 1 or 0 to make a custom search.
* @apiParam {Number} unreadStaff The status of unread_staff 1 or 0 to make a custom search.
* @apiParam {Number[]} dateRange The numbers of the range of date to make a custom search.
* @apiParam {Number[]} departments The ids of the departments to make a custom search.
* @apiParam {Object[]} authors A object {id,staff} with id and boolean to make a custom search.
* @apiParam {Number} assigned The status of assigned 1 or 0 to make a custom search.
* @apiParam {String} query A string to find into a ticket to make a custom search.
* @apiParam {Number} page The number of the page of the tickets.
* @apiParam {Object} orderBy A object {value, asc}with string and boolean to make a especific order of the search.
* @apiParam {Number[]} owners The ids of the owners to make a custom search.
* @apiParam {Boolean} supervisor Boolean to deteminate if a super-user is making the call.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TAG_FILTER
* @apiUse INVALID_CLOSED_FILTER
* @apiUse INVALID_UNREAD_STAFF_FILTER
* @apiUse INVALID_DATE_RANGE_FILTER
* @apiUse INVALID_DEPARTMENT_FILTER
* @apiUse INVALID_AUTHOR_FILTER
* @apiUse INVALID_ASSIGNED_FILTER
* @apiUse INVALID_ORDER_BY
* @apiUse INVALID_PAGE
*
* @apiSuccess {Object} data Empty object
*
*
*/
class SearchController extends Controller {
const PATH = '/search';
const METHOD = 'POST';
private $ignoreDeparmentFilter;
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'page' => [
'validation' => DataValidator::oneOf(DataValidator::numeric()->positive(),DataValidator::nullType()),
'error' => ERRORS::INVALID_PAGE
],
'tags' => [
'validation' => DataValidator::oneOf(DataValidator::validTagsId(),DataValidator::nullType()),
'error' => ERRORS::INVALID_TAG_FILTER
],
'closed' => [
'validation' => DataValidator::oneOf(DataValidator::in(['0','1']),DataValidator::nullType()),
'error' => ERRORS::INVALID_CLOSED_FILTER
],
'unreadStaff' => [
'validation' => DataValidator::oneOf(DataValidator::in(['0','1']),DataValidator::nullType()),
'error' => ERRORS::INVALID_UNREAD_STAFF_FILTER
],
'dateRange' => [
'validation' => DataValidator::oneOf(DataValidator::validDateRange(),DataValidator::nullType()),
'error' => ERRORS::INVALID_DATE_RANGE_FILTER
],
'departments' => [
'validation' => DataValidator::oneOf(DataValidator::validDepartmentsId(),DataValidator::nullType()),
'error' => ERRORS::INVALID_DEPARTMENT_FILTER
],
'authors' => [
'validation' => DataValidator::oneOf(DataValidator::validAuthorsId(),DataValidator::nullType()),
'error' => ERRORS::INVALID_AUTHOR_FILTER
],
'owners' => [
'validation' => DataValidator::oneOf(DataValidator::validOwnersId(),DataValidator::nullType()),
'error' => ERRORS::INVALID_OWNER_FILTER
],
'assigned' => [
'validation' => DataValidator::oneOf(DataValidator::in(['0','1']),DataValidator::nullType()),
'error' => ERRORS::INVALID_ASSIGNED_FILTER
],
'query' => [
'validation' => DataValidator::oneOf(DataValidator::notBlank(),DataValidator::nullType()),
'error' => ERRORS::INVALID_QUERY_FILTER
],
'orderBy' => [
'validation' => DataValidator::oneOf(DataValidator::validOrderBy(),DataValidator::nullType()),
'error' => ERRORS::INVALID_ORDER_BY
],
'pageSize' => [
'validation' => DataValidator::oneOf(DataValidator::intVal()->between(5, 50),DataValidator::nullType()),
'error' => ERRORS::INVALID_PAGE_SIZE
]
]
];
}
public function handler() {
$this->ignoreDeparmentFilter = (bool)Controller::request('supervisor');
$allowedDepartmentsId = [];
foreach (Controller::getLoggedUser()->sharedDepartmentList->toArray() as $department) {
array_push($allowedDepartmentsId,$department['id']);
}
$inputs = [
'closed' => Controller::request('closed'),
'tags' => json_decode(Controller::request('tags')),
'unreadStaff' => Controller::request('unreadStaff'),
'dateRange' => json_decode(Controller::request('dateRange')),
'departments' => json_decode(Controller::request('departments')),
'authors' => json_decode(Controller::request('authors'),true),
'owners' => json_decode(Controller::request('owners')),
'assigned' => Controller::request('assigned'),
'query' => Controller::request('query'),
'orderBy' => json_decode(Controller::request('orderBy'),true),
'page' => Controller::request('page'),
'allowedDepartments' => $allowedDepartmentsId,
'staffId' => Controller::getLoggedUser()->id,
'pageSize' => Controller::request('pageSize') ? Controller::request('pageSize') : 10
];
$query = $this->getSQLQuery($inputs);
$queryWithOrder = $this->getSQLQueryWithOrder($inputs, $query);
$totalCount = RedBean::getAll("SELECT COUNT(*) FROM (SELECT COUNT(*) " . $query . " ) AS T2", [':query' => "%" . $inputs['query'] . "%", ':queryAtBeginning' => $inputs['query'] . "%" ])[0]['COUNT(*)'];
$ticketIdList = RedBean::getAll($queryWithOrder, [':query' => "%" . $inputs['query'] . "%", ':queryAtBeginning' => $inputs['query'] . "%"]);
$ticketList = [];
foreach ($ticketIdList as $item) {
$ticket = Ticket::getDataStore($item['id']);
array_push($ticketList, $ticket->toArray());
}
Response::respondSuccess([
'tickets' => $ticketList,
'pages' => ceil($totalCount / $inputs['pageSize']),
'page' => $inputs['page'] ? ($inputs['page']*1) : 1
]);
}
public function getSQLQuery($inputs) {
$taglistQuery = " LEFT JOIN tag_ticket ON tag_ticket.ticket_id = ticket.id";
$ticketeventlistQuery = " LEFT JOIN ticketevent ON ticketevent.ticket_id = ticket.id";
$query = "FROM (ticket" . $taglistQuery . $ticketeventlistQuery .")";
$filters = "";
$this->setQueryFilters($inputs, $filters);
$query .= $filters . " GROUP BY ticket.id";
return $query;
}
public function getSQLQueryWithOrder($inputs, $query) {
$order = "";
$query = "SELECT ticket.id " . $query;
$this->setQueryOrder($inputs, $order);
$inputs['page'] ? $page = $inputs['page'] : $page = 1 ;
$query .= $order . ' LIMIT ' . $inputs['pageSize'] . ' OFFSET ' . ($page-1)*10;
return $query;
}
//FILTER
private function setQueryFilters($inputs, &$filters){
if(array_key_exists('tags',$inputs)) $this->setTagFilter($inputs['tags'], $filters);
if(array_key_exists('closed',$inputs)) $this->setClosedFilter($inputs['closed'], $filters);
if(array_key_exists('assigned',$inputs)) $this->setAssignedFilter($inputs['assigned'], $filters);
if(array_key_exists('unreadStaff',$inputs)) {
$this->setSeenFilter($inputs['unreadStaff'], $filters);
}
if(array_key_exists('dateRange',$inputs)) $this->setDateFilter($inputs['dateRange'], $filters);
if(array_key_exists('departments',$inputs) && array_key_exists('allowedDepartments',$inputs) && array_key_exists('staffId',$inputs)){
if(!$this->ignoreDeparmentFilter) $this->setDepartmentFilter($inputs['departments'],$inputs['allowedDepartments'], $inputs['staffId'], $filters);
}
if(array_key_exists('authors',$inputs)) $this->setAuthorFilter($inputs['authors'], $filters);
if(array_key_exists('owners',$inputs)) $this->setOwnerFilter($inputs['owners'], $filters);
if(array_key_exists('query',$inputs)) $this->setStringFilter($inputs['query'], $filters);
if($filters != "") $filters = " WHERE " . $filters;
}
private function setTagFilter($tagList, &$filters){
if($tagList){
$filters != "" ? $filters .= " and " : null;
foreach($tagList as $key => $tag) {
$key == 0 ? $filters .= " ( " : null;
($key != 0 && $key != sizeof($tagList)) ? $filters .= " or " : null;
$filters .= "tag_ticket.tag_id = " . $tag ;
}
$filters .= ")";
}
}
public function setClosedFilter($closed, &$filters){
if ($closed !== null) {
if ($filters != "") $filters .= " and ";
$filters .= "ticket.closed = " . $closed ;
}
}
private function setSeenFilter($unreadStaff, &$filters){
if ($unreadStaff !== null) {
if ($filters != "") $filters .= " and ";
$filters .= "ticket.unread_staff = " . $unreadStaff;
}
}
private function setDateFilter($dateRange, &$filters){
if ($dateRange !== null) {
if ($filters != "") $filters .= " and ";
foreach($dateRange as $key => $date) {
$key == 0 ? ($filters .= "(ticket.date >= " . $date ): ($filters .= " and ticket.date <= " . $date . ")");
}
}
}
private function setDepartmentFilter($requestedDepartments,$myDepartments, $idStaff, &$filters){
if (!$requestedDepartments) $requestedDepartments = [];
$requestedOwnedDepartments = $this->getRequestedOwnedDepartments($requestedDepartments, $myDepartments);
$requestedNotOwnedDepartments = $this->getRequestedNotOwnedDepartments($requestedDepartments, $myDepartments);
$first = TRUE;
if(!$requestedOwnedDepartments && !$requestedNotOwnedDepartments && !!$myDepartments){
if ($filters != "") $filters .= " and ";
foreach($myDepartments as $department) {
if($first){
$filters .= "(ticket.author_staff_id = " . $idStaff . " or ";
$first = FALSE;
} else {
$filters .= " or ";
}
$filters .= "ticket.department_id = " . $department;
}
$filters .= ")";
}
if($requestedOwnedDepartments){
foreach($requestedOwnedDepartments as $department) {
if($first){
$filters .= " ( ";
$first = FALSE;
} else {
$filters .= " or ";
}
$filters .= "ticket.department_id = " . $department;
}
}
if($requestedNotOwnedDepartments){
if($requestedOwnedDepartments) $filters .= " or ";
$filters .= "(ticket.author_staff_id = " . $idStaff . " and ";
$first = TRUE;
foreach($requestedNotOwnedDepartments as $department) {
if($first){
$filters .= " ( ";
$first = FALSE;
} else {
$filters .= " or ";
}
$filters .= "ticket.department_id = " . $department;
}
$filters .= "))";
}
if($requestedOwnedDepartments) $filters .= " )";
}
private function setAuthorFilter($authors, &$filters){
if($authors){
$first = TRUE;
if ($filters != "") $filters .= " and ";
foreach($authors as $author){
if($first){
$filters .= " ( ";
$first = FALSE;
} else {
$filters .= " or ";
}
if($author['isStaff']){
$filters .= "ticket.author_staff_id = " . $author['id'];
} else {
$filters .= "ticket.author_id = " . $author['id'];
}
}
$filters .= ")";
}
}
private function setOwnerFilter($owners, &$filters){
if($owners){
$first = TRUE;
if ($filters != "") $filters .= " and ";
foreach($owners as $owner){
if($first){
$filters .= "(";
$first = FALSE;
} else {
$filters .= " or ";
}
$filters .= "ticket.owner_id = " . $owner;
}
$filters .= ")";
}
}
private function setAssignedFilter($assigned, &$filters){
if($assigned !== null){
if ($filters != "") $filters .= " and ";
$key = "";
$assigned == 0 ? $key = "IS NULL" : $key = "IS NOT NULL";
$filters .= "ticket.owner_id " . $key;
}
}
private function setStringFilter($search, &$filters){
if($search !== null){
if ($filters != "") $filters .= " and ";
$ticketevent = " or (ticketevent.type = 'COMMENT' and ticketevent.content LIKE :query)";
$filters .= " (ticket.title LIKE :query or ticket.content LIKE :query or ticket.ticket_number LIKE :query". $ticketevent ." )";
};
}
private function getRequestedOwnedDepartments($requestedDepartments, $myDepartments){
$requestedOwnedDepartments = [];
$requestedOwnedDepartments = array_values(array_unique(array_intersect($requestedDepartments, $myDepartments)));
return $requestedOwnedDepartments;
}
private function getRequestedNotOwnedDepartments($requestedDepartments, $myDepartments){
$requestedNotOwnedDepartments = [];
$requestedOwnedDepartments = [];
$requestedOwnedDepartments = array_values(array_unique(array_intersect($requestedDepartments, $myDepartments)));
$requestedNotOwnedDepartments = array_values(array_diff($requestedDepartments, $requestedOwnedDepartments));
return $requestedNotOwnedDepartments;
}
//ORDER
private function setQueryOrder($inputs, &$order){
$order = " ORDER BY ";
if(array_key_exists('query',$inputs)) $this->setStringOrder($inputs['query'], $order);
if(array_key_exists('orderBy',$inputs)) $this->setEspecificOrder($inputs['orderBy'], $order);
$order .= "ticket.closed asc, ticket.owner_id asc, ticket.unread_staff asc, ticket.date desc, ticket.id desc";
}
private function setEspecificOrder($orderBy, &$order){
if($orderBy !== null){
$orientation = ($orderBy['asc'] ? " asc" : " desc" );
$order .= "ticket." . $orderBy['value'] . $orientation . ",";
};
}
private function setStringOrder($querysearch, &$order){
if($querysearch !== null){
$ticketeventOrder = " WHEN (ticketevent.content LIKE :query) THEN 5 ";
$order .= "CASE WHEN (ticket.ticket_number LIKE :query) THEN 1 WHEN (ticket.title LIKE :queryAtBeginning) THEN 2 WHEN (ticket.title LIKE :query) THEN 3 WHEN ( ticket.content LIKE :query) THEN 4 " . $ticketeventOrder ."END asc, ";
}
}
}

62
dist/api/controllers/ticket/seen.php vendored Executable file
View File

@ -0,0 +1,62 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /ticket/seen See ticket
* @apiVersion 4.11.0
*
* @apiName See ticket
*
* @apiGroup Ticket
*
* @apiDescription This path sets "unread" value to false, so it does not appear highlighted.
*
* @apiPermission user
*
* @apiParam {Number} ticketNumber Number of the ticket.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_TICKET
*
* @apiSuccess {Object} data Empty object
*
*/
class SeenController extends Controller {
const PATH = '/seen';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
}
public function handler() {
$ticketnumber = Controller::request('ticketNumber');
$user = Controller::getLoggedUser();
$ticket = Ticket::getByTicketNumber($ticketnumber);
if(!$user->canManageTicket($ticket) && !$ticket->isAuthor($user)) {
throw new RequestException(ERRORS::NO_PERMISSION);
}
if ($ticket->isOwner($user)) {
$ticket->unreadStaff = false;
}
if ($ticket->isAuthor($user)) {
$ticket->unread = false;
}
$ticket->store();
Response::respondSuccess();
}
}

31
dist/api/controllers/user.php vendored Executable file
View File

@ -0,0 +1,31 @@
<?php
$userControllers = new ControllerGroup();
$userControllers->setGroupPath('/user');
$userControllers->addController(new LoginController);
$userControllers->addController(new SignUpController);
$userControllers->addController(new InviteUserController);
$userControllers->addController(new LogoutController);
$userControllers->addController(new CheckSessionController);
$userControllers->addController(new SendRecoverPasswordController);
$userControllers->addController(new RecoverPasswordController);
$userControllers->addController(new EditPassword);
$userControllers->addController(new EditEmail);
$userControllers->addController(new GetUserController);
$userControllers->addController(new GetUsersController);
$userControllers->addController(new GetUserByIdController);
$userControllers->addController(new DeleteUserController);
$userControllers->addController(new BanUserController);
$userControllers->addController(new UnBanUserController);
$userControllers->addController(new ListBanUserController);
$userControllers->addController(new VerifyController);
$userControllers->addController(new EnableUserController);
$userControllers->addController(new DisableUserController);
$userControllers->addController(new EditCustomFieldsController);
$userControllers->addController(new EditSupervisedListController);
$userControllers->addController(new GetSupervisedTicketController);
$userControllers->addController(new ResendEmailTokenController);
$userControllers->addController(new ResendInviteUserController);
$userControllers->finalize();

61
dist/api/controllers/user/ban.php vendored Executable file
View File

@ -0,0 +1,61 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /user/ban Ban email
* @apiVersion 4.11.0
*
* @apiName Ban email
*
* @apiGroup User
*
* @apiDescription This path takes an email and adds it to the ban list.
*
* @apiPermission staff1
*
* @apiParam {String} email Email of user to ban.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_EMAIL
* @apiUse ALREADY_BANNED
*
* @apiSuccess {Object} data Empty object
*
*/
class BanUserController extends Controller {
const PATH = '/ban';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'email' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
]
]
];
}
public function handler() {
$email = Controller::request('email');
$banRow = Ban::getDataStore($email,'email');
if($banRow->isNull()) {
$ban = new Ban();
$ban->setProperties(array(
'email' => $email
));
$ban->store();
Log::createLog('BAN_USER', $email);
Response::respondSuccess();
} else {
throw new RequestException(ERRORS::ALREADY_BANNED);
}
}
}

38
dist/api/controllers/user/check-session.php vendored Executable file
View File

@ -0,0 +1,38 @@
<?php
/**
* @api {post} /user/check-session Check session
* @apiVersion 4.11.0
*
* @apiName Check session
*
* @apiGroup User
*
* @apiDescription This path checks if the session exists.
*
* @apiPermission any
*
* @apiSuccess {Object} data Information about the session.
* @apiSuccess {Boolean} data.sessionActive Indicates if the session is active.
*
*/
class CheckSessionController extends Controller {
const PATH = '/check-session';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => []
];
}
public function handler() {
$session = Session::getInstance();
Response::respondSuccess([
'sessionActive' => $session->sessionExists()
]);
}
}

60
dist/api/controllers/user/delete.php vendored Executable file
View File

@ -0,0 +1,60 @@
<?php
use Respect\Validation\Validator as DataValidator;
use RedBeanPHP\Facade as RedBean;
/**
* @api {post} /user/delete Delete user
* @apiVersion 4.11.0
*
* @apiName Delete user
*
* @apiGroup User
*
* @apiDescription This path receives an user id and deletes the user.
*
* @apiPermission staff1
*
* @apiParam {Number} userId The id of the user to delete.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_USER
*
* @apiSuccess {Object} data Empty object
*
*/
DataValidator::with('CustomValidations', true);
class DeleteUserController extends Controller {
const PATH = '/delete';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'userId' => [
'validation' => DataValidator::dataStoreId('user'),
'error' => ERRORS::INVALID_USER
]
]
];
}
public function handler() {
$userId = Controller::request('userId');
$user = User::getDataStore($userId);
Log::createLog('DELETE_USER', $user->name);
RedBean::exec('DELETE FROM log WHERE author_user_id = ?', [$userId]);
foreach($user->sharedTicketList as $ticket) {
$ticket->delete();
}
$user->delete();
Response::respondSuccess();
}
}

52
dist/api/controllers/user/disable.php vendored Executable file
View File

@ -0,0 +1,52 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /user/disable Ban email
* @apiVersion 4.11.0
*
* @apiName Disable User
*
* @apiGroup User
*
* @apiDescription This path takes an user id and disabled access for it
*
* @apiPermission staff1
*
* @apiParam {String} userId Id of the user to disable
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_USER
* @apiUse ALREADY_DISABLED
*
* @apiSuccess {Object} data Empty object
*
*/
class DisableUserController extends Controller {
const PATH = '/disable';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'userId' => [
'validation' => DataValidator::dataStoreId('user'),
'error' => ERRORS::INVALID_USER
]
]
];
}
public function handler() {
$user = User::getDataStore(Controller::request('userId'));
if($user->disabled) {
throw new RequestException(ERRORS::ALREADY_DISABLED);
}
$user->disabled = 1;
$user->store();
Response::respondSuccess();
}
}

View File

@ -0,0 +1,55 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /user/edit-custom-fields Edit custom field values
* @apiVersion 4.11.0
*
* @apiName Edit custom field values
*
* @apiGroup User
*
* @apiDescription This path is for editing the custom fields of a user.
*
* @apiPermission user
*
* @apiParam {String} userId Id of the user if it is not the one logged. Optional.
* @apiParam {String} customfield_ Custom field values for this user.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_CUSTOM_FIELD_OPTION
*
* @apiSuccess {Object} data Empty object
*
*/
class EditCustomFieldsController extends Controller {
const PATH = '/edit-custom-fields';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => []
];
}
public function handler() {
$userId = Controller::request('userId') * 1;
$user = Controller::getLoggedUser();
if($userId && Controller::isStaffLogged(2)) {
$user = User::getDataStore($userId);
if($user->isNull())
throw new RequestException(ERRORS::INVALID_USER);
}
$user->setProperties([
'xownCustomfieldvalueList' => $this->getCustomFieldValues()
]);
$user->store();
Response::respondSuccess();
}
}

76
dist/api/controllers/user/edit-email.php vendored Executable file
View File

@ -0,0 +1,76 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /user/edit-email Edit email
* @apiVersion 4.11.0
*
* @apiName Edit email
*
* @apiGroup User
*
* @apiDescription This path changes the email of an user.
*
* @apiPermission user
*
* @apiParam {String} newEmail The new email that the user wants to change.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_EMAIL
*
* @apiSuccess {Object} data Empty object
*
*/
class EditEmail extends Controller{
const PATH = '/edit-email';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'newEmail' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
]
]
];
}
public function handler() {
$newEmail = Controller::request('newEmail');
$user = Controller::getLoggedUser();
$oldEmail = $user->email;
$this->verifyEmail($newEmail, $user);
$user->email = $newEmail;
$user->store();
$mailSender = MailSender::getInstance();
$mailSender->setTemplate('USER_EMAIL', [
'to'=>$oldEmail,
'newemail'=>$user->email,
'name'=>$user->name
]);
$mailSender->send();
Response::respondSuccess();
}
private function verifyEmail($email, $logedUser){
$staff = Staff::getDataStore($email,'email');
$user = User::getDataStore($email,'email');
if($user->email == $email && $logedUser->email != $email){
throw new RequestException(ERRORS::INVALID_EMAIL);
}
if($staff->email == $email){
throw new RequestException(ERRORS::INVALID_EMAIL);
}
}
}

64
dist/api/controllers/user/edit-password.php vendored Executable file
View File

@ -0,0 +1,64 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /user/edit-password Edit password
* @apiVersion 4.11.0
*
* @apiName Edit password
*
* @apiGroup User
*
* @apiDescription This path edits the password of an user.
*
* @apiPermission user
*
* @apiParam {String} newPassword The new password that the user wants to change.
* @apiParam {String} oldPassword The actual password of the user.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_PASSWORD
* @apiUse INVALID_OLD_PASSWORD
*
* @apiSuccess {Object} data Empty object
*
*/
class EditPassword extends Controller {
const PATH = '/edit-password';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'user',
'requestData' => [
'newPassword' => [
'validation' => DataValidator::notBlank()->length(LengthConfig::MIN_LENGTH_PASSWORD, LengthConfig::MAX_LENGTH_PASSWORD),
'error' => ERRORS::INVALID_PASSWORD
]
]
];
}
public function handler() {
$oldPassword = Controller::request('oldPassword');
$newPassword = Controller::request('newPassword');
$user = Controller::getLoggedUser() ;
if (Hashing::verifyPassword($oldPassword, $user->password)) {
$user->password = Hashing::hashPassword($newPassword);
$user->store();
$mailSender = MailSender::getInstance();
$mailSender->setTemplate('USER_PASSWORD', [
'to'=>$user->email,
'name'=>$user->name
]);
$mailSender->send();
Response::respondSuccess();
} else{
throw new RequestException(ERRORS::INVALID_OLD_PASSWORD);
}
}
}

View File

@ -0,0 +1,81 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /user/edit-supervised-list Edit user list
* @apiVersion 4.11.0
*
* @apiName Edit user list
*
* @apiGroup User
*
* @apiDescription This path edits the user list of a user.
*
* @apiPermission staff1
*
* @apiParam {Number[]} userIdList The ids of the users.
* @apiParam {Number} userId Id of the supervisor user.
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_LIST
* @apiUse INVALID_USER
* @apiUse SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF
*
* @apiSuccess {Object} data Empty object
*
*/
class EditSupervisedListController extends Controller {
const PATH = '/edit-supervised-list';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'userIdList' => [
'validation' => DataValidator::validUsersId(),
'error' => ERRORS::INVALID_LIST
],
'userId' => [
'validation' => DataValidator::dataStoreId('user'),
'error' => ERRORS::INVALID_USER
]
]
];
}
public function handler() {
$userIdList = $this->getUserIdListCleared();
$superUser = User::getDataStore(Controller::request('userId'));
if(!$superUser->supervisedrelation) {
$superUser->supervisedrelation = new Supervisedrelation();
}
$superUser->supervisedrelation->sharedUserList->clear();
foreach($userIdList as $userId) {
$user = User::getDataStore($userId);
$superUser->supervisedrelation->sharedUserList->add($user);
}
$superUser->supervisedrelation->store();
$superUser->store();
Response::respondSuccess();
}
public function getUserIdListCleared(){
$clearedList = array_unique(json_decode(Controller::request('userIdList')));
$superUser = User::getDataStore(Controller::request('userId'));
foreach ($clearedList as $item) {
if($item == $superUser->id) throw new Exception(ERRORS::SUPERVISOR_CAN_NOT_SUPERVISE_HIMSELF);
}
return $clearedList;
}
}

53
dist/api/controllers/user/enable.php vendored Executable file
View File

@ -0,0 +1,53 @@
<?php
use Respect\Validation\Validator as DataValidator;
/**
* @api {post} /user/enable Ban email
* @apiVersion 4.11.0
*
* @apiName Enable User
*
* @apiGroup User
*
* @apiDescription This path takes an user id and enables it
*
* @apiPermission staff1
*
* @apiParam {String} userId Id of the user to enable
*
* @apiUse NO_PERMISSION
* @apiUse INVALID_USER
* @apiUse ALREADY_ENABLED
*
* @apiSuccess {Object} data Empty object
*
*/
class EnableUserController extends Controller {
const PATH = '/enable';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => [
'userId' => [
'validation' => DataValidator::dataStoreId('user'),
'error' => ERRORS::INVALID_USER
]
]
];
}
public function handler() {
$user = User::getDataStore(Controller::request('userId'));
if(!$user->disabled) {
throw new RequestException(ERRORS::ALREADY_ENABLED);
}
$user->disabled = 0;
$user->store();
Response::respondSuccess();
}
}

Some files were not shown because too many files have changed in this diff Show More