[
MAINHACK
]
Mail Test
BC
Config Scan
HOME
Create...
New File
New Folder
Viewing / Editing File: Tasks.php
<?php /** -------------------------------------------------------------------------------- * This controller manages all the business logic for tasks * * @package Grow CRM * @author NextLoop *----------------------------------------------------------------------------------*/ namespace App\Http\Controllers; use App\Http\Controllers\Controller; use App\Http\Requests\Tasks\TaskRecurrringSettings; use App\Http\Requests\Tasks\TaskStoreUpdate; use App\Http\Responses\Tasks\ActivateResponse; use App\Http\Responses\Tasks\ArchiveResponse; use App\Http\Responses\Tasks\AttachFilesResponse; use App\Http\Responses\Tasks\ChecklistCommentsResponse; use App\Http\Responses\Tasks\ChecklistResponse; use App\Http\Responses\Tasks\CloneResponse; use App\Http\Responses\Tasks\CloneStoreResponse; use App\Http\Responses\Tasks\contentResponse; use App\Http\Responses\Tasks\CreateResponse; use App\Http\Responses\Tasks\DeleteTaskDependencyResponse; use App\Http\Responses\Tasks\DestroyResponse; use App\Http\Responses\Tasks\ImportChecklistResponse; use App\Http\Responses\Tasks\IndexKanbanResponse; use App\Http\Responses\Tasks\IndexListResponse; use App\Http\Responses\Tasks\PinningResponse; use App\Http\Responses\Tasks\RecurringSettingsResponse; use App\Http\Responses\Tasks\ShowResponse; use App\Http\Responses\Tasks\StoreChecklistResponse; use App\Http\Responses\Tasks\StoreCommentResponse; use App\Http\Responses\Tasks\StoreResponse; use App\Http\Responses\Tasks\StoreTaskDependencyResponse; use App\Http\Responses\Tasks\TimerStartResponse; use App\Http\Responses\Tasks\TimerStopResponse; use App\Http\Responses\Tasks\UpdateChecklistResponse; use App\Http\Responses\Tasks\UpdateErrorResponse; use App\Http\Responses\Tasks\UpdateLockedResponse; use App\Http\Responses\Tasks\UpdateResponse; use App\Http\Responses\Tasks\UpdateStatusLockedResponse; use App\Http\Responses\Tasks\UpdateStatusResponse; use App\Http\Responses\Tasks\UpdateTagsResponse; use App\Imports\TasksChecklistImport; use App\Models\Checklist; use App\Models\Comment; use App\Models\Task; use App\Models\Timer; use App\Permissions\AttachmentPermissions; use App\Permissions\ChecklistPermissions; use App\Permissions\CommentPermissions; use App\Permissions\ProjectPermissions; use App\Permissions\TaskPermissions; use App\Repositories\AttachmentRepository; use App\Repositories\CategoryRepository; use App\Repositories\ChecklistRepository; use App\Repositories\CommentRepository; use App\Repositories\CustomFieldsRepository; use App\Repositories\DestroyRepository; use App\Repositories\EmailerRepository; use App\Repositories\EventRepository; use App\Repositories\EventTrackingRepository; use App\Repositories\PinnedRepository; use App\Repositories\ProjectAssignedRepository; use App\Repositories\ProjectRepository; use App\Repositories\TagRepository; use App\Repositories\TaskAssignedRepository; use App\Repositories\TaskDependencyRepository; use App\Repositories\TaskRepository; use App\Repositories\TaskStatusRepository; use App\Repositories\TimerRepository; use App\Repositories\UserRepository; use App\Rules\CheckBox; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Image; use Intervention\Image\Exception\NotReadableException; use Validator; class Tasks extends Controller { protected $taskrepo; protected $tagrepo; protected $userrepo; protected $timerrepo; protected $taskmodel; protected $commentpermissions; protected $attachmentpermissions; protected $checklistpermissions; protected $taskpermissions; protected $eventrepo; protected $trackingrepo; protected $emailerrepo; protected $customrepo; protected $projectpermission; protected $dependencyrepo; public function __construct( TaskRepository $taskrepo, TagRepository $tagrepo, UserRepository $userrepo, TimerRepository $timerrepo, TaskPermissions $taskpermissions, CommentPermissions $commentpermissions, AttachmentPermissions $attachmentpermissions, ChecklistPermissions $checklistpermissions, EventRepository $eventrepo, EventTrackingRepository $trackingrepo, EmailerRepository $emailerrepo, Task $taskmodel, CustomFieldsRepository $customrepo, TaskStatusRepository $statusrepo, ProjectPermissions $projectpermission, TaskDependencyRepository $dependencyrepo ) { //core controller instantation parent::__construct(); $this->taskrepo = $taskrepo; $this->tagrepo = $tagrepo; $this->userrepo = $userrepo; $this->taskpermissions = $taskpermissions; $this->taskmodel = $taskmodel; $this->commentpermissions = $commentpermissions; $this->attachmentpermissions = $attachmentpermissions; $this->checklistpermissions = $checklistpermissions; $this->timerrepo = $timerrepo; $this->eventrepo = $eventrepo; $this->trackingrepo = $trackingrepo; $this->emailerrepo = $emailerrepo; $this->customrepo = $customrepo; $this->projectpermission = $projectpermission; $this->dependencyrepo = $dependencyrepo; //authenticated $this->middleware('auth'); //route middleware $this->middleware('tasksMiddlewareTimer')->only([ 'timerStart', 'timerStop', 'timerStopAll', ]); //Permissions on methods $this->middleware('tasksMiddlewareIndex')->only([ 'index', 'update', 'toggleStatus', 'store', 'updateStartDate', 'updateDueDate', 'updateStatus', 'updatePriority', 'updateVisibility', 'updateMilestone', 'updateAssigned', 'timerStart', 'timerStop', 'timerStopAll', 'archive', 'activate', 'cloneStore', 'recurringSettingsUpdate', 'stopRecurring', 'storeDependency', 'deleteDependency', 'togglePinning', ]); $this->middleware('tasksMiddlewareCreate')->only([ 'create', 'store', ]); $this->middleware('tasksMiddlewareShow')->only([ 'show', 'showCustomFields', 'updateCustomFields', 'showMyNotes', 'createMyNotes', 'editMyNotes', 'deleteMyNotes', ]); $this->middleware('tasksMiddlewareEdit')->only([ 'updateDescription', 'updateTitle', 'updateStartDate', 'updateDueDate', 'updateVisibility', 'updateMilestone', 'updateAssigned', 'storeChecklist', 'archive', 'activate', 'editCustomFields', 'updateCustomFields', 'updateTags', 'recurringSettings', 'recurringSettingsUpdate', 'updateCoverImage', 'removeCoverImage', 'showImportChecklists', 'importChecklists', 'storeChecklistComment', ]); $this->middleware('tasksMiddlewareParticipate')->only([ 'storeComment', 'attachFiles', 'updateStatus', 'updatePriority', ]); $this->middleware('tasksMiddlewareDeleteAttachment')->only([ 'deleteAttachment', ]); $this->middleware('tasksMiddlewareDownloadAttachment')->only([ 'downloadAttachment', ]); $this->middleware('tasksMiddlewareDeleteComment')->only([ 'deleteComment', ]); $this->middleware('tasksMiddlewareEditDeleteChecklist')->only([ 'updateChecklist', 'deleteChecklist', 'toggleChecklistStatus', ]); $this->middleware('tasksMiddlewareDestroy')->only([ 'destroy', ]); $this->middleware('tasksMiddlewareAssign')->only([ 'updateAssigned', ]); $this->middleware('tasksMiddlewareCloning')->only([ 'cloneTask', 'cloneStore', ]); $this->middleware('tasksMiddlewareManageDependencies')->only([ 'storeDependency', 'deleteDependency', ]); } /** * Display a listing of tasks * @return \Illuminate\Http\Response */ public function index() { if (auth()->user()->pref_view_tasks_layout == 'list') { $payload = $this->indexList(); return new IndexListResponse($payload); } else { $payload = $this->indexKanban(); return new IndexKanbanResponse($payload); } } /** * Display a listing of tasks * @return \Illuminate\Http\Response */ public function indexList() { //defaults $milestones = []; //get stats before other filters has been applied $stats = $this->statsWidget(); //get tasks $tasks = $this->taskrepo->search('', ['extended_mode' => false]); //count rows $count = $tasks->total(); //process for timers $this->processTasks($tasks); //apply some permissions if ($tasks) { foreach ($tasks as $task) { $this->applyPermissions($task); } } //basic page settings $page = $this->pageSettings('tasks', ['count' => $count]); //page setting for embedded view if (request('source') == 'ext') { $page = $this->pageSettings('ext', ['count' => $count]); } //get all tags (type: lead) - for filter panel $tags = $this->tagrepo->getByType('task'); //all available lead statuses $statuses = \App\Models\TaskStatus::all(); //get all milestones if viewing from project page (for use in filter panel) if (request()->filled('taskresource_id') && request('taskresource_type') == 'project') { $milestones = \App\Models\Milestone::Where('milestone_projectid', request('taskresource_id'))->get(); } $priorities = \App\Models\TaskPriority::orderBy('taskpriority_position', 'asc')->get(); //reponse payload $payload = [ 'page' => $page, 'milestones' => $milestones, 'tasks' => $tasks, 'stats' => $stats, 'tags' => $tags, 'statuses' => $statuses, 'priorities' => $priorities, 'fields' => $this->getCustomFields(), ]; //show the view return $payload; } /** * Display a listing of tasks * @return \Illuminate\Http\Response */ public function indexKanban() { //defaults $milestones = []; //get stats before other filters has been applied $stats = $this->statsWidget(); $boards = $this->taskBoards(); //basic page settings $page = $this->pageSettings('tasks', []); //page setting for embedded view if (request('source') == 'ext') { $page = $this->pageSettings('ext', []); } //get all tags (type: lead) - for filter panel $tags = $this->tagrepo->getByType('task'); //all available lead statuses $statuses = \App\Models\TaskStatus::all(); //get all milestones if viewing from project page (for use in filter panel) if (request()->filled('taskresource_id') && request('taskresource_type') == 'project') { $milestones = \App\Models\Milestone::Where('milestone_projectid', request('taskresource_id'))->get(); } //check if the user has participation rights on the task if (auth()->user()->is_client) { if (request()->filled('taskresource_id') && request('taskresource_type') == 'project') { if ($this->projectpermission->check('tasks-participate', request('taskresource_id'))) { config(['visibility.tasks_participate' => true]); } } } $priorities = \App\Models\TaskPriority::orderBy('taskpriority_position', 'asc')->get(); //reponse payload $payload = [ 'page' => $page, 'boards' => $boards, 'milestones' => $milestones, 'stats' => $stats, 'tags' => $tags, 'statuses' => $statuses, 'priorities' => $priorities, 'fields' => $this->getCustomFields(), ]; //show the view return $payload; } /** * process/group tasks into boards * @return object */ private function taskBoards() { $statuses = \App\Models\TaskStatus::orderBy('taskstatus_position', 'asc')->get(); foreach ($statuses as $status) { request()->merge([ 'filter_single_task_status' => $status->taskstatus_id, 'query_type' => 'kanban', ]); //get tasks $tasks = $this->taskrepo->search('', ['extended_mode' => false]); //count rows $count = $tasks->total(); //process for timers $this->processTasks($tasks); //apply some permissions if ($tasks) { foreach ($tasks as $task) { $this->applyPermissions($task); } } //apply custom fields if ($tasks) { foreach ($tasks as $task) { $task->fields = $this->getCustomFields($task); } } //initial loadmore button if ($tasks->currentPage() < $tasks->lastPage()) { $boards[$status->taskstatus_id]['load_more'] = ''; $boards[$status->taskstatus_id]['load_more_url'] = loadMoreButtonUrl($tasks->currentPage() + 1, $status->taskstatus_id); } else { $boards[$status->taskstatus_id]['load_more'] = 'hidden'; $boards[$status->taskstatus_id]['load_more_url'] = ''; } $boards[$status->taskstatus_id]['name'] = $status->taskstatus_title; $boards[$status->taskstatus_id]['id'] = $status->taskstatus_id; $boards[$status->taskstatus_id]['tasks'] = $tasks; $boards[$status->taskstatus_id]['color'] = $status->taskstatus_color; } return $boards; } /** * Show the form for creating a new task * @param object CategoryRepository instance of the repository * @return \Illuminate\Http\Response */ public function create(CategoryRepository $categoryrepo) { //default $milestones = []; //page settings $page = $this->pageSettings('create'); //get tags $tags = $this->tagrepo->getByType('task'); $statuses = \App\Models\TaskStatus::orderBy('taskstatus_position', 'asc')->get(); $priorities = \App\Models\TaskPriority::orderBy('taskpriority_position', 'asc')->get(); //milestones if (request()->filled('taskresource_id') && request('taskresource_type') == 'project') { $milestones = \App\Models\Milestone::Where('milestone_projectid', request('taskresource_id'))->get(); } //get customfields request()->merge([ 'filter_show_standard_form_status' => 'enabled', 'filter_field_status' => 'enabled', 'sort_by' => 'customfields_position', ]); $fields = $this->getCustomFields(); //reponse payload $payload = [ 'page' => $page, 'tags' => $tags, 'milestones' => $milestones, 'stats' => $this->statsWidget(), 'fields' => $fields, 'statuses' => $statuses, 'priorities' => $priorities, ]; //show the form return new CreateResponse($payload); } /** * get all custom fields for clients * - if they are being used in the 'edit' modal form, also get the current data * from the cliet record. Store this temporarily in '$field->customfields_name' * this will then be used to prefill data in the custom fields * @param model client model - only when showing the edit modal form * @return collection */ public function getCustomFields($obj = '') { //set typs request()->merge([ 'customfields_type' => 'tasks', ]); //show all fields config(['settings.custom_fields_display_limit' => 1000]); //get fields $fields = $this->customrepo->search(); //when in editing view - get current value that is stored for this custom field if ($obj instanceof \App\Models\Task) { foreach ($fields as $field) { $field->current_value = $obj[$field->customfields_name]; } } return $fields; } /** * Store a newly created task in storage. * @param object TaskStoreUpdate instance of the request validation object * @param object TaskAssignedRepository instance of the repository * @return \Illuminate\Http\Response */ public function store(TaskStoreUpdate $request, TaskAssignedRepository $assignedrepo) { //defaults $assigned_users = []; //get client id of attached project (if this is a project task) $project = \App\Models\Project::find(request('task_projectid')); $client_id = $project->project_clientid; //custom field validation if ($messages = $this->customFieldValidationFailed()) { abort(409, $messages); } request()->merge([ 'task_clientid' => $project->project_clientid, ]); //validate milestone id if (request()->filled('task_milestoneid')) { if (!\App\Models\Milestone::where('milestone_id', request('task_milestoneid')) ->where('milestone_projectid', request('task_projectid'))->first()) { abort(409, __('lang.item_not_found')); } } //no milestone provided - get default milestone if (!request()->filled('task_milestoneid')) { if ($milestone = \App\Models\Milestone::where('milestone_type', 'uncategorised') ->where('milestone_projectid', request('task_projectid'))->first()) { request()->merge([ 'task_milestoneid' => $milestone->milestone_id, ]); } else { abort(409, __('lang.milestone_not_found')); Log::critical("add task - default milestone could not be found", ['process' => '[tasks]', config('app.debug_ref'), 'function' => __function__, 'file' => basename(__FILE__), 'line' => __line__, 'path' => __file__, 'project_id' => request('task_projectid')]); } } //get the last row (order by position - desc) if ($last = $this->taskmodel::orderBy('task_position', 'desc')->first()) { $position = $last->task_position + config('settings.db_position_increment'); } else { //default position increment $position = config('settings.db_position_increment'); } //create new record if (!$task_id = $this->taskrepo->create($position)) { abort(409); } //add tags $this->tagrepo->add('task', $task_id); /** * [client added task] * - task will remain un-assigned * [team added task - with no assigning permission] * - assigned to the user adding the task * [team added task - with assigning permission] * - assign as per posted list (or none) * */ if (auth()->user()->is_team) { if (auth()->user()->role->role_assign_tasks == 'no') { $assigned_users = $assignedrepo->add($task_id, auth()->id()); } else { $assigned_users = $assignedrepo->add($task_id, ''); } } //get the task object (friendly for rendering in blade template) $tasks = $this->taskrepo->search($task_id, ['apply_filters' => false]); $task = $tasks->first(); //process task (timers) $this->processTask($task); //apply permissions $this->applyPermissions($task); //custom fields $task->fields = $this->getCustomFields($task); /** ---------------------------------------------- * record assignment events and send emails * ----------------------------------------------*/ foreach ($assigned_users as $assigned_user_id) { if ($assigned_user = \App\Models\User::Where('id', $assigned_user_id)->first()) { $data = [ 'event_creatorid' => auth()->id(), 'event_item' => 'assigned', 'event_item_id' => '', 'event_item_lang' => 'event_assigned_user_to_a_task', 'event_item_lang_alt' => 'event_assigned_user_to_a_task_alt', 'event_item_content' => __('lang.assigned'), 'event_item_content2' => $assigned_user_id, 'event_item_content3' => $assigned_user->first_name, 'event_parent_type' => 'task', 'event_parent_id' => $task->task_id, 'event_parent_title' => $task->task_title, 'event_show_item' => 'yes', 'event_show_in_timeline' => 'yes', 'event_clientid' => $task->task_clientid, 'eventresource_type' => 'project', 'eventresource_id' => $task->task_projectid, 'event_notification_category' => 'notifications_new_assignement', ]; //record event if ($event_id = $this->eventrepo->create($data)) { //record notification (skip the user creating this event) if ($assigned_user_id != auth()->id()) { $emailusers = $this->trackingrepo->recordEvent($data, [$assigned_user_id], $event_id); } } /** ---------------------------------------------- * send email [assignment] * ----------------------------------------------*/ if ($assigned_user_id != auth()->id()) { if ($assigned_user->notifications_new_assignement == 'yes_email') { $mail = new \App\Mail\TaskAssignment($assigned_user, $data, $task); $mail->build(); } } } } //counting rows $rows = $this->taskrepo->search(); $count = $rows->total(); //reponse payload $payload = [ 'tasks' => $tasks, 'task' => $task, 'count' => $count, 'stats' => $this->statsWidget(), ]; //card view response if (auth()->user()->pref_view_tasks_layout == 'kanban') { request()->merge([ 'filter_task_status' => request('task_status'), ]); if (request()->filled('taskresource_id')) { request()->merge([ 'filter_task_projectid' => request('task_projectid'), ]); } //counting rows $rows = $this->taskrepo->search(); //payload $board['tasks'] = $tasks; $payload['board'] = $board; $payload['count'] = $rows->total(); } //process reponse return new StoreResponse($payload); } /** * Returns false when all is ok * @return \Illuminate\Http\Response */ public function customFieldValidationFailed() { //custom field validation $fields = \App\Models\CustomField::Where('customfields_type', 'tasks')->get(); $errors = ''; foreach ($fields as $field) { if ($field->customfields_status == 'enabled' && $field->customfields_standard_form_status == 'enabled' && $field->customfields_required == 'yes') { if (request($field->customfields_name) == '') { $errors .= '<li>' . $field->customfields_title . ' - ' . __('lang.is_required') . '</li>'; } } } //return if ($errors != '') { return $errors; } else { return false; } } /** * Display the specified task * @param object TaskAssignedRepository instance of the repository * @param object ProjectAssignedRepository instance of the repository * @param object CommentRepository instance of the repository * @param object AttachmentRepository instance of the repository * @param object ChecklistRepository instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function show( TaskAssignedRepository $assignedrepo, ProjectAssignedRepository $projectassignedrepo, CommentRepository $commentrepo, AttachmentRepository $attachmentrepo, ChecklistRepository $checklistrepo, $id) { //get the task $tasks = $this->taskrepo->search($id); //task $task = $tasks->first(); //[fix] -if task has an invalid task status if ($task->task_status == 0 || $task->task_status == null) { $task->task_status = 1; $task->save(); } //apply permissions $this->applyPermissions($task); //process task $this->processTask($task); //get tags $tags_resource = $this->tagrepo->getByResource('task', $id); $tags_system = $this->tagrepo->getByType('task'); $tags = $tags_resource->merge($tags_system); $tags = $tags->unique('tag_title'); //get tags (attachements) $attachment_tags = $this->tagrepo->getByType('attachment'); $attachment_tags = $attachment_tags->unique('tag_title'); //get assigned users $assigned = $assignedrepo->getAssigned($id); //get team members who are assigned to this tasks project $project_assigned = $projectassignedrepo->getAssigned($task->task_projectid); //get clients users $client_users = \App\Models\User::Where('clientid', $task->task_clientid)->orderBy('first_name', 'asc')->get(); //get the global users for this task (array of user id's) $user_ids = $this->taskpermissions->check('users', $id); //comments request()->merge([ 'commentresource_type' => 'task', 'commentresource_id' => $id, ]); $comments = $commentrepo->search(); foreach ($comments as $comment) { $this->applyCommentPermissions($comment); } //attachments request()->merge([ 'attachmentresource_type' => 'task', 'attachmentresource_id' => $id, ]); $attachments = $attachmentrepo->search(); foreach ($attachments as $attachment) { $this->applyAttachmentPermissions($attachment, $task); } //checklists request()->merge([ 'checklistresource_type' => 'task', 'checklistresource_id' => $id, ]); $checklists = $checklistrepo->search(); foreach ($checklists as $checklist) { $this->applyChecklistPermissions($checklist); } //milestone $milestones = \App\Models\Milestone::Where('milestone_projectid', $task->task_projectid)->get(); //page settings $page = $this->pageSettings('task', $task); //mark events as read \App\Models\EventTracking::where('parent_id', $id) ->where('parent_type', 'task') ->where('eventtracking_userid', auth()->id()) ->update(['eventtracking_status' => 'read']); //get users reminders if ($reminder = \App\Models\Reminder::Where('reminderresource_type', 'task') ->Where('reminderresource_id', $id) ->Where('reminder_userid', auth()->id())->first()) { $has_reminder = true; } else { $reminder = []; $has_reminder = false; } //get all tasks is same project (for task dependencies) $project_tasks = \App\Models\Task::Where('task_projectid', $task->task_projectid)->orderBy('task_title', 'ASC')->get(); //all dependecies $dependecies_all = $this->dependencyrepo->search($task->task_id); //all dependecies request()->merge([ 'filter_currently_blocking' => true, ]); $dependecies_blocking = $this->dependencyrepo->search($task->task_id); //reponse payload $payload = [ 'page' => $page, 'task' => $task, 'id' => $id, 'tags' => $tags, 'current_tags' => $task->tags, 'assigned' => $assigned, 'project_assigned' => $project_assigned, 'comments' => $comments, 'attachments' => $attachments, 'checklists' => $checklists, 'milestones' => $milestones, 'reminder' => $reminder, 'resource_type' => 'task', 'resource_id' => $id, 'has_reminder' => $has_reminder, 'progress' => $this->checklistProgress($checklists), 'client_users' => $client_users, 'project_tasks' => $project_tasks, 'dependecies_all' => $dependecies_all, 'dependecies_blocking' => $dependecies_blocking, 'attachment_tags' => $attachment_tags, ]; //showing just the tab if (request('show') == 'tab') { $payload['type'] = 'show-main'; return new contentResponse($payload); } //response return new ShowResponse($payload); } /** * Update the specified task in storage. * @param int $id task id * @return \Illuminate\Http\Response */ public function update($id) { //reponse payload $payload = [ 'stats' => $this->statsWidget(), ]; //process reponse return new UpdateResponse($payload); } /** * Remove the specified task from storage. * @param object DestroyRepository instance of the repository * @return \Illuminate\Http\Response */ public function destroy(DestroyRepository $destroyrepo) { //delete each record in the array $allrows = array(); foreach (request('ids') as $id => $value) { //only checked items if ($value == 'on') { //delete the task and associated items $destroyrepo->destroyTask($id); //add to array $allrows[] = $id; } } //reponse payload $payload = [ 'allrows' => $allrows, 'stats' => $this->statsWidget(), ]; //generate a response return new DestroyResponse($payload); } /** * Start a users timer for a given task * @param int $id task id * @return \Illuminate\Http\Response */ public function timerStart($id) { $action = 'start'; //get the task and apply permissions $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $this->applyPermissions($task); //stop running timer for this user $this->timerrepo->stopRunningTimers([ 'timer_creatorid' => auth()->id(), ]); //create a new timer for this user if (!$this->timerrepo->createTimer($task)) { $action = 'failed'; } //needed by the topnav timer dropdown request()->merge([ 'users_running_timer_task_id' => $task->task_id, 'users_running_timer_title' => $task->task_title, 'users_running_timer_task_title' => str_slug($task->task_title), ]); $payload = [ 'task' => $task, ]; //process reponse return new TimerStartResponse($payload); } /** * Start a users timer for a given task * @param int $id task id * @return \Illuminate\Http\Response */ public function timerStartTopnav() { //get the task and apply permissions $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $this->applyPermissions($task); //stop running timer for this user $this->timerrepo->stopRunningTimers([ 'timer_creatorid' => auth()->id(), ]); //create a new timer for this user if (!$this->timerrepo->createTimer($task)) { } $payload = []; //process reponse return new TimerStartResponse($payload); } /** * Stop a users timer for a given task * @param int $id task id * @return \Illuminate\Http\Response */ public function timerStop($id) { //get the task and apply permissions $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $this->applyPermissions($task); //stop running timer for this user $this->timerrepo->stopRunningTimers([ 'timer_creatorid' => auth()->id(), ]); $payload = [ 'task_id' => $id, ]; //process reponse return new TimerStopResponse($payload); } /** * Stop a users timer for a given task * @param int $id task id * @return \Illuminate\Http\Response */ public function timerStopUser() { //stop running timer for this user $this->timerrepo->stopRunningTimers([ 'timer_creatorid' => auth()->id(), ]); //process reponse return new TimerStopResponse([]); } /** * Stop a users timer for a given task * @param int $id task id * @return \Illuminate\Http\Response */ public function timerStopAll($id) { //get the task and apply permissions $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $this->applyPermissions($task); //stop all running timers for this task $this->timerrepo->stopRunningTimers([ 'task_id' => $id, ]); $payload = [ 'task_id' => $id, ]; //process reponse return new TimerStopResponse($payload); } /** * send each task for processing * @return null */ private function processTasks($tasks = '') { //sanity - make sure this is a valid tasks object if ($tasks instanceof \Illuminate\Pagination\LengthAwarePaginator) { foreach ($tasks as $task) { $this->processTask($task); } } } /** * check the task for the following: * 1. Check if task is assigned to me - add 'assigned_to_me' (yes/no) attribute * 2. check if there are any running timers on the tasks - add 'running_timer' (yes/no) * @param object task instance of the task model object * @return object */ private function processTask($task = '') { //sanity - make sure this is a valid task object if ($task instanceof \App\Models\Task) { //default values $task->assigned_to_me = false; $task->running_timers = false; $task->timer_current_status = false; $task->has_attachments = false; $task->has_comments = false; $task->has_checklist = false; //check if the task is assigned to me foreach ($task->assigned as $user) { if ($user->id == auth()->id()) { //its assigned to me $task->assigned_to_me = true; } } $task->has_attachments = ($task->attachments_count > 0) ? true : false; $task->has_comments = ($task->comments_count > 0) ? true : false; $task->has_checklist = ($task->checklists_count > 0) ? true : false; //check if there are any running timers foreach ($task->timers as $timer) { if ($timer->timer_status == 'running') { //its has a running timer $task->running_timers = true; if ($timer->timer_creatorid == auth()->id()) { $task->timer_current_status = true; } } } //get users current/refreshed time for the task (if applcable) $task->my_time = $this->timerrepo->sumTimers($task->task_id, auth()->id()); //custom fields $task->fields = $this->getCustomFields($task); } } /** * update task description * @param int $id task id * @return object */ public function updateDescription($id) { $task = $this->taskmodel::find($id); $task->task_description = request('task_description'); $task->save(); //update card description $jsondata['dom_html'][] = [ 'selector' => '#card-description-container', 'action' => 'replace', 'value' => clean($task->task_description), ]; $jsondata['dom_visibility'][] = [ 'selector' => '#card-description-container', 'action' => 'show', ]; return response()->json($jsondata); } /** * update resource * @param int $id task id * @return null */ public function updateStartDate($id) { //get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //save task_date_due to request so can access it n validation request()->merge(['task_date_due' => $task->task_date_due]); //validate $validator = Validator::make(request()->all(), [ 'task_date_start' => [ 'bail', 'required', 'date', function ($attribute, $value, $fail) { if (request('task_date_due') != '') { if (strtotime($value) > strtotime(request('task_date_due'))) { return $fail(__('lang.start_date_must_be_before_due_date')); } } }, ], ]); //validation errors if ($validator->fails()) { $errors = $validator->errors(); $messages = ''; foreach ($errors->all() as $message) { $messages .= "<li>$message</li>"; } return new UpdateErrorResponse([ 'reset_target' => '#task-start-date-container', 'reset_value' => runtimeDate($task->task_date_start), 'error_message' => $messages, ]); } //update $task->task_date_start = request('task_date_start'); $task->save(); //update and apply permissions $this->processTask($task); $this->applyPermissions($task); //reponse payload $payload = [ 'tasks' => $tasks, 'stats' => $this->statsWidget(), ]; //process reponse return new UpdateResponse($payload); } /** * update resource * @param int $id task id * @return null */ public function updateDueDate($id) { //get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //save task_date_start to request so can access it in validation request()->merge(['task_date_start' => $task->task_date_start]); //validate $validator = Validator::make(request()->all(), [ 'task_date_due' => [ 'bail', 'required', 'date', function ($attribute, $value, $fail) { if (request('task_date_due') != '') { if (strtotime($value) < strtotime(request('task_date_start'))) { return $fail(__('lang.due_date_must_be_after_start_date')); } } }, ], ]); //validation errors if ($validator->fails()) { $errors = $validator->errors(); $messages = ''; foreach ($errors->all() as $message) { $messages .= "<li>$message</li>"; } return new UpdateErrorResponse([ 'reset_target' => '#task-due-date-container', 'reset_value' => runtimeDate($task->task_date_due), 'error_message' => $messages, ]); } //update $task->task_date_due = request('task_date_due'); $task->save(); //process and apply permissions $this->processTask($task); $this->applyPermissions($task); //reponse payload $payload = [ 'tasks' => $tasks, 'stats' => $this->statsWidget(), ]; //process reponse return new UpdateResponse($payload); } /** * update task status * @param object ProjectPermissions instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function updateStatus(ProjectPermissions $projectpermissions, $id) { //get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //old status $old_status = $task->task_status; //check users permission to change task status if (!$this->taskpermissions->check('edit', $task)) { $this->processTasks($tasks); $payload = [ 'task' => $tasks->first(), 'tasks' => $tasks, ]; return new UpdateLockedResponse($payload); } //check dependency locks - for attempt to mark task as completed if ($task->count_dependency_cannot_complete > 0) { if (request('task_status') == 2 || request('status') == 2) { $this->processTasks($tasks); $payload = [ 'task' => $tasks->first(), 'tasks' => $tasks, ]; return new UpdateStatusLockedResponse($payload); } } //validate $validator = Validator::make(request()->all(), [ 'task_status' => [ 'required', function ($attribute, $value, $fail) { if (\App\Models\TaskStatus::Where('taskstatus_id', $value)->doesntExist()) { return $fail(__('lang.invalid_status')); } }, ], ]); //validation errors if ($validator->fails()) { $errors = $validator->errors(); $messages = ''; foreach ($errors->all() as $message) { $messages .= "<li>$message</li>"; } return new UpdateErrorResponse([ 'reset_target' => '#card-task-status-text', 'reset_value' => safestr(request('current_task_status_text')), 'error_message' => $messages, ]); } //we are moving task to a new board - update its position to top of the new list if ($old_status != request('task_status')) { if ($first_task = \App\Models\Task::Where('task_status', request('task_status'))->orderBy('task_position', 'ASC')->first()) { $task->task_position = $first_task->task_position / 2; } } //update $task->task_status = request('task_status'); $task->save(); //get refreshed $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //process and apply permissions $this->processTask($task); $this->applyPermissions($task); /** ---------------------------------------------- * record event [status] * ----------------------------------------------*/ $data = [ 'event_creatorid' => auth()->id(), 'event_item' => 'status', 'event_item_id' => '', 'event_item_lang' => 'event_changed_task_status', 'event_item_content' => $task->task_status, 'event_item_content2' => '', 'event_parent_type' => 'task', 'event_parent_id' => $task->task_id, 'event_parent_title' => $task->task_title, 'event_show_item' => 'yes', 'event_show_in_timeline' => config('system.settings_projects_events_show_task_status_change'), 'event_clientid' => $task->task_clientid, 'eventresource_type' => 'project', 'eventresource_id' => $task->task_projectid, 'event_notification_category' => 'notifications_tasks_activity', ]; //record event if ($old_status != request('task_status')) { if ($event_id = $this->eventrepo->create($data)) { //get users $users = $projectpermissions->check('users', $task); //record notification $emailusers = $this->trackingrepo->recordEvent($data, $users, $event_id); } } /** ---------------------------------------------- * send email [status] * ----------------------------------------------*/ if (isset($emailusers) && is_array($emailusers)) { $data = []; //send to users if ($users = \App\Models\User::WhereIn('id', $emailusers)->get()) { foreach ($users as $user) { $mail = new \App\Mail\TaskStatusChanged($user, $data, $task); $mail->build(); } } } //refresh dependecies $this->refreshDependencies($task); //reponse payload $payload = [ 'tasks' => $tasks, 'stats' => $this->statsWidget(), 'old_status' => $old_status, 'new_status' => request('task_status'), 'display_status' => runtimeLang(request('task_status')), ]; //process reponse return new UpdateStatusResponse($payload); } /** * update task priority * @param int $id task id * @return \Illuminate\Http\Response */ public function updatePriority($id) { //get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //validate $validator = Validator::make(request()->all(), [ 'task_priority' => [ 'required', ], ]); //validation errors if ($validator->fails()) { $errors = $validator->errors(); $messages = ''; foreach ($errors->all() as $message) { $messages .= "<li>$message</li>"; } return new UpdateErrorResponse([ 'reset_target' => '#card-task-priority-text', 'reset_value' => safestr(request('current_task_priority_text')), 'error_message' => $messages, ]); } //get the priority $priority = \App\Models\TaskPriority::Where('taskpriority_id', request('task_priority'))->first(); //save $task->task_priority = request('task_priority'); $task->save(); //process and permissions $this->processTask($task); $this->applyPermissions($task); //reponse payload $payload = [ 'type' => 'update-priority', 'tasks' => $tasks, 'stats' => $this->statsWidget(), 'display_priority' => $priority->taskpriority_title, ]; //process reponse return new UpdateResponse($payload); } /** * update task visibility * @param int $id task id * @return \Illuminate\Http\Response */ public function updateVisibility($id) { //get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //validate $validator = Validator::make(request()->all(), [ 'task_client_visibility' => [ 'required', function ($attribute, $value, $fail) { if (!in_array($value, ['yes', 'no'])) { return $fail(__('lang.client_visibility_invalid')); } }, ], ]); //validation errors if ($validator->fails()) { $errors = $validator->errors(); $messages = ''; foreach ($errors->all() as $message) { $messages .= "<li>$message</li>"; } return new UpdateErrorResponse([ 'error_message' => $messages, ]); } //validate $task->task_client_visibility = request('task_client_visibility'); $task->save(); //process and apply permissions $this->processTask($task); $this->applyPermissions($task); //reponse payload $payload = [ 'type' => 'update-vivibility', 'tasks' => $tasks, 'stats' => $this->statsWidget(), 'display_text' => ($task->task_client_visibility == 'yes') ? __('lang.visible') : __('lang.hidden'), ]; //process reponse return new UpdateResponse($payload); } /** * update task milestone * @param int $id task id * @return \Illuminate\Http\Response */ public function updateMilestone($id) { //get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //validate if (!\App\Models\Milestone::Where('milestone_id', request('task_milestoneid'))->where('milestone_projectid', $task->task_projectid)->exists()) { //show error and reset values $payload = [ 'reset_target' => '', 'reset_value' => '', 'error_message' => __('lang.invalid_or_missing_data'), ]; //process reponse return new UpdateErrorResponse($payload); } //validate $task->task_milestoneid = request('task_milestoneid'); $task->save(); //get refreshed $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //process $this->processTask($task); $this->applyPermissions($task); //reponse payload $payload = [ 'tasks' => $tasks, 'stats' => $this->statsWidget(), ]; //process reponse return new UpdateResponse($payload); } /** * update task title * @param int $id task id * @return \Illuminate\Http\Response */ public function updateTitle($id) { //get the task $task = $this->taskmodel::find($id); //validation if (hasHTML(request('task_title'))) { //[type options] error|success $jsondata['notification'] = [ 'type' => 'error', 'value' => __('lang.title') . ' ' . __('lang.must_not_contain_any_html'), ]; //update back the title $jsondata['dom_html'][] = [ 'selector' => '#card-title-editable', 'action' => 'replace', 'value' => safestr($task->task_title), ]; return response()->json($jsondata); } //validation if (!request()->filled('task_title')) { //[type options] error|success $jsondata['notification'] = [ 'type' => 'error', 'value' => __('lang.title_is_required'), ]; //update back the title $jsondata['dom_html'][] = [ 'selector' => '#card-title-editable', 'action' => 'replace', 'value' => safestr($task->task_title), ]; return response()->json($jsondata); } else { $task->task_title = request('task_title'); $task->save(); //get refreshed & reprocess $tasks = $this->taskrepo->search($id); $this->processTask($tasks->first()); //update table row $jsondata['dom_html'][] = [ 'selector' => "#table_task_title_$id", 'action' => 'replace', 'value' => str_limit(safestr($task->task_title), 25), ]; //update kanban card title $jsondata['dom_html'][] = [ 'selector' => "#kanban_task_title_$id", 'action' => 'replace', 'value' => str_limit(safestr($task->task_title), 45), ]; //update card $jsondata['dom_html'][] = [ 'selector' => '#card-title-editable', 'action' => 'replace', 'value' => safestr($task->task_title), ]; return response()->json($jsondata); } } /** * update task assigned users * @param object TaskAssignedRepository instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function updateAssigned(TaskAssignedRepository $assignedrepo, $id) { //fix - remove own tasks filter- so that a user with "assign tasks" role can use this method $data = [ 'apply_filters' => false, ]; //get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //currently assigned $currently_assigned = $task->assigned->pluck('id')->toArray(); //milestone $milestones = \App\Models\Milestone::Where('milestone_projectid', $task->task_projectid)->get(); //validation - data type if (request()->filled('assigned') && !is_array(request('assigned'))) { return new UpdateResponse([ 'type' => 'update-assigned', 'tasks' => $tasks, 'task' => $task, 'assigned' => $assignedrepo->getAssigned($id), 'milestones' => $milestones, 'error' => true, 'message' => __('lang.request_is_invalid'), ]); } //validate users exist if (request()->filled('assigned')) { foreach (request('assigned') as $user_id => $value) { if ($value == 'on') { //validate user exists if (\App\Models\User::Where('id', $user_id)->doesntExist()) { return new UpdateResponse([ 'type' => 'update-assigned', 'tasks' => $tasks, 'task' => $task, 'assigned' => $assignedrepo->getAssigned($id), 'milestones' => $milestones, 'error' => true, 'message' => __('lang.assiged_user_not_found'), ]); } } } } //delete all assigned $assignedrepo->delete($id); //add each user $newly_signed_users = []; if (request()->filled('assigned')) { foreach (request('assigned') as $user_id => $value) { if ($value == 'on') { $assigned_users = $assignedrepo->add($id, $user_id); if (!in_array($user_id, $currently_assigned)) { $newly_signed_users[] = $user_id; } } } } //stop timers of recently un-assigned users foreach ($currently_assigned as $current_user) { if (!in_array($current_user, $newly_signed_users)) { //reset existing account owner \App\Models\Timer::where('timer_taskid', $id)->where('timer_creatorid', $current_user) ->update(['timer_status' => 'stopped']); } } /** ---------------------------------------------- * record assignment events and send emails * ----------------------------------------------*/ foreach ($newly_signed_users as $assigned_user_id) { if ($assigned_user = \App\Models\User::Where('id', $assigned_user_id)->first()) { $data = [ 'event_creatorid' => auth()->id(), 'event_item' => 'assigned', 'event_item_id' => '', 'event_item_lang' => 'event_assigned_user_to_a_task', 'event_item_lang_alt' => 'event_assigned_user_to_a_task_alt', 'event_item_content' => __('lang.assigned'), 'event_item_content2' => $assigned_user_id, 'event_item_content3' => $assigned_user->first_name, 'event_parent_type' => 'task', 'event_parent_id' => $task->task_id, 'event_parent_title' => $task->task_title, 'event_show_item' => 'yes', 'event_show_in_timeline' => 'yes', 'event_clientid' => $task->task_clientid, 'eventresource_type' => 'project', 'eventresource_id' => $task->task_projectid, 'event_notification_category' => 'notifications_new_assignement', ]; //record event if ($event_id = $this->eventrepo->create($data)) { //record notification (skip the user creating this event) if ($assigned_user_id != auth()->id()) { $emailusers = $this->trackingrepo->recordEvent($data, [$assigned_user_id], $event_id); } } /** ---------------------------------------------- * send email [assignment] * ----------------------------------------------*/ if ($assigned_user_id != auth()->id()) { if ($assigned_user->notifications_new_assignement == 'yes_email') { $mail = new \App\Mail\TaskAssignment($assigned_user, $data, $task); $mail->build(); } } } } //get refereshed $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //process and apply permissions $this->processTask($task); $this->applyPermissions($task); //get assigned $assigned = $assignedrepo->getAssigned($id); //reponse payload $payload = [ 'type' => 'update-assigned', 'tasks' => $tasks, 'task' => $task, 'assigned' => $assigned, 'milestones' => $milestones, ]; //process reponse return new UpdateResponse($payload); } /** * update task priority * @param int $id task id * @return \Illuminate\Http\Response */ public function updateTags($id) { //delete & update tags $this->tagrepo->delete('task', $id); $this->tagrepo->add('task', $id); //get tags $tags_resource = $this->tagrepo->getByResource('task', $id); $tags_system = $this->tagrepo->getByType('task'); $tags = $tags_resource->merge($tags_system); $tags = $tags->unique('tag_title'); //get refreshed task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //apply permissions $this->applyPermissions($task); //process $this->processTask($task); //custom fields $task->fields = $this->getCustomFields($task); //reponse payload $payload = [ 'task' => $task, 'tasks' => $tasks, 'tags' => $tags, 'current_tags' => $task->tags, ]; //process reponse return new UpdateTagsResponse($payload); } /** * save task comment * @param object CommentRepository instance of the repository * @return \Illuminate\Http\Response */ public function storeComment(CommentRepository $commentrepo, $id) { //validate $validator = Validator::make(request()->all(), [ 'comment_text' => [ 'required', ], ]); //validation errors if ($validator->fails()) { $errors = $validator->errors(); $messages = ''; foreach ($errors->all() as $message) { $messages .= "<li>$message</li>"; } abort(409, $messages); } request()->merge([ 'commentresource_type' => 'task', 'commentresource_id' => $id, 'comment_text' => request('comment_text'), ]); $comment_id = $commentrepo->create(); //get complete comment $comments = $commentrepo->search($comment_id); $comment = $comments->first(); $this->applyCommentPermissions($comment); //get task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $this->processTask($task); /** ---------------------------------------------- * record event [coment] * ----------------------------------------------*/ $data = [ 'event_creatorid' => auth()->id(), 'event_item' => 'comment', 'event_item_id' => $comment->comment_id, 'event_item_lang' => 'event_posted_a_comment', 'event_item_content' => $comment->comment_text, 'event_item_content2' => '', 'event_parent_type' => 'task', 'event_parent_id' => $task->task_id, 'event_parent_title' => $task->task_title, 'event_show_item' => 'yes', 'event_show_in_timeline' => 'no', 'event_clientid' => $task->task_clientid, 'eventresource_type' => 'project', 'eventresource_id' => $task->task_projectid, 'event_notification_category' => 'notifications_tasks_activity', ]; //record event if ($event_id = $this->eventrepo->create($data)) { //get users $users = $this->taskpermissions->check('users', $task); //record notification $emailusers = $this->trackingrepo->recordEvent($data, $users, $event_id); } /** ---------------------------------------------- * send email [comment] * ----------------------------------------------*/ if (isset($emailusers) && is_array($emailusers)) { //the comment $data = $comment->toArray(); //send to users if ($users = \App\Models\User::WhereIn('id', $emailusers)->get()) { foreach ($users as $user) { $mail = new \App\Mail\TaskComment($user, $data, $task); $mail->build(); } } } //reponse payload $payload = [ 'comments' => $comments, 'tasks' => $tasks, ]; //show the form return new StoreCommentResponse($payload); } /** * store checklist * @param object ChecklistRepository instance of the repository * @return object */ public function StoreChecklist(ChecklistRepository $checklistrepo, $id) { //validate $validator = Validator::make(request()->all(), [ 'checklist_text' => [ 'required', ], ]); //validation errors if ($validator->fails()) { $errors = $validator->errors(); $messages = ''; foreach ($errors->all() as $message) { $messages .= "<li>$message</li>"; } return new UpdateErrorResponse([ 'type' => 'store-checklist', 'error_message' => $messages, ]); } //we are creating a new list request()->merge([ 'checklistresource_type' => 'task', 'checklistresource_id' => $id, 'checklist_text' => request('checklist_text'), ]); //get next position if ($last = \App\Models\Checklist::Where('checklistresource_type', 'task') ->Where('checklistresource_id', $id) ->orderBy('checklist_position', 'desc') ->first()) { $position = $last->checklist_position + 1; } else { //default position $position = 1; } //save checklist $checklist_id = $checklistrepo->create($position); //get complete checklist $checklists = $checklistrepo->search($checklist_id); $this->applyChecklistPermissions($checklists->first()); //get task $tasks = $this->taskrepo->search($id); $this->processTask($tasks->first()); //reponse payload $payload = [ 'checklists' => $checklists, 'progress' => $this->checklistProgress($checklistrepo->search()), 'tasks' => $tasks, ]; //show the form return new StoreChecklistResponse($payload); } /** * update a task checklist * @param object ChecklistRepository instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function UpdateChecklist(ChecklistRepository $checklistrepo, $id) { //validate $validator = Validator::make(request()->all(), [ 'checklist_text' => [ 'required', ], ]); //validation errors if ($validator->fails()) { $errors = $validator->errors(); $messages = ''; foreach ($errors->all() as $message) { $messages .= "<li>$message</li>"; } return new UpdateErrorResponse([ 'type' => 'store-checklist', 'error_message' => $messages, ]); } //update checklist $checklist = \App\Models\Checklist::Where('checklist_id', $id)->first(); $checklist->checklist_text = request('checklist_text'); $checklist->save(); //get refreshed $checklists = $checklistrepo->search($id); $this->applyChecklistPermissions($checklists->first()); //reponse payload $payload = [ 'checklist' => $checklist, 'checklists' => $checklists, ]; //show the form return new UpdateChecklistResponse($payload); } /** * update task checklist item positions * @return \Illuminate\Http\Response */ public function updateChecklistPositions() { //update position $position = 0; if (is_array(request('card_checklist'))) { foreach (request('card_checklist') as $key => $value) { if (is_numeric($key)) { \App\Models\Checklist::where('checklist_id', $key) ->update(['checklist_position' => $position]); } $position++; } } } /** * change task status using the checkbox * @return \Illuminate\Http\Response */ public function toggleStatus() { //get the task $tasks = $this->taskrepo->search(request()->route('task')); $task = $tasks->first(); //check dependency locks - for attempt to mark task as completed if ($task->count_dependency_cannot_complete > 0) { if (request('task_status') == 2 || request('status') == 2) { $this->processTasks($tasks); $payload = [ 'task' => $tasks->first(), 'tasks' => $tasks, ]; return new UpdateStatusLockedResponse($payload); } } //update the task if (request('toggle_task_status') == 'on') { $task->task_previous_status = $task->task_status; $task->task_status = 2; $task->save(); } else { $task->task_status = $task->task_previous_status; $task->save(); } //stop all running timers if ($task->task_status == 2) { $this->timerrepo->stopRunningTimers([ 'task_id' => request()->route('task'), ]); } //get refreshed task $tasks = $this->taskrepo->search(request()->route('task')); $task = $tasks->first(); //apply permissions $this->applyPermissions($task); //process $this->processTask($task); //record event (task completed) if ($task->task_status == 2) { /** ---------------------------------------------- * record event [comment] * see database table to details of each key * ----------------------------------------------*/ $data = [ 'event_creatorid' => auth()->id(), 'event_item' => 'task', 'event_item_id' => $task->task_id, 'event_item_lang' => 'event_changed_task_status_completed', 'event_item_content' => $task->task_title, 'event_item_content2' => '', 'event_clientid' => $task->task_clientid, 'event_parent_type' => 'project', 'event_parent_id' => $task->task_projectid, 'event_parent_title' => $task->project_title, 'event_show_item' => 'yes', 'event_show_in_timeline' => config('system.settings_projects_events_show_task_status_change'), 'eventresource_type' => 'project', 'eventresource_id' => $task->task_projectid, 'event_notification_category' => 'notifications_tasks_activity', ]; //record event if ($event_id = $this->eventrepo->create($data)) { //get users $users = $this->taskpermissions->check('users', $task); //record notification $emailusers = $this->trackingrepo->recordEvent($data, $users, $event_id); } /** ---------------------------------------------- * send email [comment * ----------------------------------------------*/ if (isset($emailusers) && is_array($emailusers)) { //additional data $data = []; //send to users if ($users = \App\Models\User::WhereIn('id', $emailusers)->get()) { foreach ($users as $user) { $mail = new \App\Mail\TaskStatusChanged($user, $data, $task); $mail->build(); } } } } //refresh dependecies $this->refreshDependencies($task); //reponse payload $payload = [ 'tasks' => $tasks, 'task_id' => request()->route('task'), 'stats' => $this->statsWidget(), ]; //show the form return new UpdateResponse($payload); } /** * save an uploaded file * @param object Request instance of the request object * @param object AttachmentRepository instance of the repository * @param int $id task id */ public function attachFiles(Request $request, AttachmentRepository $attachmentrepo, $id) { //validate the task exists $task = $this->taskmodel::find($id); //save the file in its own folder in the temp folder if ($file = $request->file('file')) { //defaults $file_type = 'file'; //unique file id & directory name $uniqueid = Str::random(40); $directory = $uniqueid; //original file name $filename = $file->getClientOriginalName(); //filepath $file_path = BASE_DIR . "/storage/files/$directory/$filename"; //extension $extension = pathinfo($file_path, PATHINFO_EXTENSION); //thumb path $thumb_name = generateThumbnailName($filename); $thumb_path = BASE_DIR . "/storage/files/$directory/$thumb_name"; //create directory Storage::makeDirectory("files/$directory"); //save file to directory Storage::putFileAs("files/$directory", $file, $filename); //if the file type is an image, create a thumb by default if (is_array(@getimagesize($file_path))) { $file_type = 'image'; try { $img = Image::make($file_path)->resize(null, 90, function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); }); $img->save($thumb_path); } catch (NotReadableException $e) { $message = $e->getMessage(); Log::error("[Image Library] failed to create uplaoded image thumbnail. Image type is not supported on this server", ['process' => '[permissions]', config('app.debug_ref'), 'function' => __function__, 'file' => basename(__FILE__), 'line' => __line__, 'path' => __file__, 'error_message' => $message]); abort(409, __('lang.image_file_type_not_supported')); } } //save files $data = [ 'attachment_clientid' => $task->task_clientid, 'attachment_uniqiueid' => $uniqueid, 'attachment_directory' => $directory, 'attachment_filename' => $filename, 'attachment_extension' => $extension, 'attachment_type' => $file_type, 'attachment_size' => humanFileSize(filesize($file_path)), 'attachment_thumbname' => $thumb_name, 'attachmentresource_type' => 'task', 'attachmentresource_id' => $id, ]; $attachment_id = $attachmentrepo->create($data); //tags $this->tagrepo->add('attachment', $attachment_id); //get refreshed attachment $attachments = $attachmentrepo->search($attachment_id); $attachment = $attachments->first(); //get task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $this->applyPermissions($task); $this->processTask($task); //apply permissions $this->applyAttachmentPermissions($attachment, $task); /** ---------------------------------------------- * record event [attachment] * ----------------------------------------------*/ $data = [ 'event_creatorid' => auth()->id(), 'event_item' => 'attachment', 'event_item_id' => $attachment_id, 'event_item_lang' => 'event_attached_a_file', 'event_item_content' => $filename, 'event_item_content2' => "tasks/download-attachment/$uniqueid", 'event_parent_type' => 'task', 'event_parent_id' => $task->task_id, 'event_parent_title' => $task->task_title, 'event_show_item' => 'yes', 'event_show_in_timeline' => 'no', 'event_clientid' => $task->task_clientid, 'eventresource_type' => 'project', 'eventresource_id' => $task->task_projectid, 'event_notification_category' => 'notifications_tasks_activity', ]; //record event if ($event_id = $this->eventrepo->create($data)) { //get users $users = $this->taskpermissions->check('users', $task); //record notification $emailusers = $this->trackingrepo->recordEvent($data, $users, $event_id); } /** ---------------------------------------------- * send email [attachment] * ----------------------------------------------*/ if (isset($emailusers) && is_array($emailusers)) { $data = $attachment->toArray(); //send to users if ($users = \App\Models\User::WhereIn('id', $emailusers)->get()) { foreach ($users as $user) { $mail = new \App\Mail\TaskFileUploaded($user, $data, $task); $mail->build(); } } } //reponse payload $payload = [ 'attachments' => $attachments, 'tasks' => $tasks, ]; //show the form return new AttachFilesResponse($payload); } } /** * delete task attachment * @param int $id task id * @return \Illuminate\Http\Response */ public function deleteAttachment() { $cover = false; //check if file exists in the database $attachment = \App\Models\Attachment::Where('attachment_uniqiueid', request()->route('uniqueid'))->first(); //confirm thumb exists if ($attachment->attachment_directory != '') { if (Storage::exists("files/$attachment->attachment_directory")) { Storage::deleteDirectory("files/$attachment->attachment_directory"); } } //check if image is being used as cover image $task_id = $attachment->attachmentresource_id; if ($task = \App\Models\Task::Where('task_id', $task_id)->first()) { if ($task->task_cover_image_uniqueid == request()->route('uniqueid')) { $cover = true; $task->task_cover_image = 'no'; $task->task_cover_image_uniqueid = ''; $task->task_cover_image_filename = ''; $task->save(); } } //delete tags $this->tagrepo->delete('attachment', $attachment->attachment_id); //delete $attachment->delete(); //hide and remove row $jsondata['dom_visibility'][] = array( 'selector' => '#card_attachment_' . $attachment->attachment_uniqiueid, 'action' => 'slideup-slow-remove', ); //if cover if ($cover) { $jsondata['postrun_functions'][] = [ 'value' => 'NXCardRemoveCover', ]; $jsondata['dom_visibility'][] = [ 'selector' => '#kanban-card-cover-image-' . $task->task_id, 'action' => 'hide', ]; } //response return response()->json($jsondata); } /** * download task attachment * @param int $id task id * @return \Illuminate\Http\Response */ public function downloadAttachment() { //check if file exists in the database $attachment = \App\Models\Attachment::Where('attachment_uniqiueid', request()->route('uniqueid'))->first(); //confirm thumb exists if ($attachment->attachment_filename != '') { $file_path = "files/$attachment->attachment_directory/$attachment->attachment_filename"; if (Storage::exists($file_path)) { return Storage::download($file_path); } } abort(404, __('lang.file_not_found')); } /** * delete a task comment * @param object DestroyRepository instance of the repository * @param object Comment instance of the comment model object * @param int $id task id * @return \Illuminate\Http\Response */ public function deleteComment(DestroyRepository $destroyrepo, Comment $comment, $id) { //delete comment $destroyrepo->destroyComment($id); //hide and remove row $jsondata['dom_visibility'][] = array( 'selector' => '#card_comment_' . $comment->comment_id, 'action' => 'slideup-slow-remove', ); //response return response()->json($jsondata); } /** * delete checklist * @param object Checklist instance of the request object * @param object ChecklistRepository instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function deleteChecklist(Checklist $checklist, ChecklistRepository $checklistrepo) { //check if file exists in the database $checklist = $checklist::find(request()->route('checklistid')); //some data $resource_id = $checklist->checklistresource_id; $checklist_id = $checklist->checklist_id; //delete $checklist->delete(); //delete checklist comment \App\Models\Comment::Where('commentresource_type', 'checklist')->Where('commentresource_id', $checklist_id)->delete(); //checklists request()->merge([ 'checklistresource_type' => 'task', 'checklistresource_id' => $resource_id, ]); $checklists = $checklistrepo->search(); //reponse payload $payload = [ 'progress' => $this->checklistProgress($checklists), 'action' => 'delete', 'checklistid' => $checklist_id, ]; //show the form return new ChecklistResponse($payload); } /** * delete checklist * @param object Checklist instance of the request validation object * @param object ChecklistRepository instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function toggleChecklistStatus(Checklist $checklist, ChecklistRepository $checklistrepo) { //get checklist id $id = request()->route('checklistid'); //check if file exists in the database $checklist = $checklist::find(request()->route('checklistid')); if (request("card_checklist.$id") == 'on') { $checklist->checklist_status = 'completed'; } else { $checklist->checklist_status = 'pending'; } //save $checklist->save(); //checklists request()->merge([ 'checklistresource_type' => 'task', 'checklistresource_id' => $checklist->checklistresource_id, ]); $checklists = $checklistrepo->search(); //reponse payload $payload = [ 'progress' => $this->checklistProgress($checklists), ]; //show the form return new ChecklistResponse($payload); } /** * create the checklists progress bar data * @param object checklists instance of the checklists model object * @return object */ private function checklistProgress($checklists) { $progress['bar'] = 'w-0'; //css width % $progress['completed'] = '---'; //sanity - make sure this is a valid tasks object if ($checklists instanceof \Illuminate\Pagination\LengthAwarePaginator) { $count = 0; $completed = 0; foreach ($checklists as $checklist) { if ($checklist->checklist_status == 'completed') { $completed++; } $count++; } //finial $progress['completed'] = "$completed/$count"; if ($count > 0) { $percentage = round(($completed / $count) * 100); $progress['bar'] = "w-$percentage"; } } return $progress; } /** * apply permissions. * @param object $task instance of the task model object * @return object */ private function applyPermissions($task = '') { //sanity - make sure this is a valid task object if ($task instanceof \App\Models\Task) { //[sql optimisation] $project = $task->project()->first(); $assigned_users = $task->assigned->pluck('id'); $assigned_project_users = $project->assigned->pluck('id'); $project_managers = $task->projectmanagers->pluck('id'); //project tasks if ($task->task_projectid > 0) { //edit permissions $task->permission_edit_task = $this->taskpermissions->check('edit', $task, $project, $assigned_users, $assigned_project_users, $project_managers); //delete permissions $task->permission_delete_task = $this->taskpermissions->check('delete', $task, $project, $assigned_users, $assigned_project_users, $project_managers); //delete participate $task->permission_participate = $this->taskpermissions->check('participate', $task, $project, $assigned_users, $assigned_project_users, $project_managers); //super user $task->permission_assign_users = $this->taskpermissions->check('assign-users', $task, $project, $assigned_users, $assigned_project_users, $project_managers); //super user $task->permission_super_user = $this->taskpermissions->check('super-user', $task, $project, $assigned_users, $assigned_project_users, $project_managers); //manage dependencies $task->permission_manage_dependencies = $this->taskpermissions->check('manage-dependencies', $task, $project, $assigned_users, $assigned_project_users, $project_managers); } //template tasks if ($task->task_projectid < 0) { //edit permissions $task->permission_edit_task = (auth()->user()->role->role_templates_projects >= 2) ? true : false; //delete permissions $task->permission_delete_task = (auth()->user()->role->role_templates_projects >= 2) ? true : false; //delete participate $task->permission_participate = (auth()->user()->role->role_templates_projects >= 2) ? true : false; //super user $task->permission_assign_users = false; //super user $task->permission_super_user = false; } } } /** * apply permissions to each comment * @param object $comment instance of the comment model object * @return object */ private function applyCommentPermissions($comment = '') { //sanity - make sure this is a valid object if ($comment instanceof \App\Models\Comment) { //delete permissions $comment->permission_delete_comment = $this->commentpermissions->check('delete', $comment); } } /** * apply permissions to each attachment * @param object $attachment instance of the attachment model object * @return object */ private function applyAttachmentPermissions($attachment = '', $task = []) { //sanity - make sure this is a valid object if ($attachment instanceof \App\Models\Attachment) { //delete permissions $attachment->permission_delete_attachment = $this->attachmentpermissions->check('delete', $attachment); //cover image $attachment->permission_set_cover = $task->permission_edit_task; $attachment->task_cover_image_uniqueid = $task->task_cover_image_uniqueid; } } /** * apply permissions to each checklist * @param object $checklist instance of the checklist model object * @return object */ private function applyChecklistPermissions($checklist = '') { //sanity - make sure this is a valid object if ($checklist instanceof \App\Models\Checklist) { //delete permissions $checklist->permission_edit_delete_checklist = $this->checklistpermissions->check('edit-delete', $checklist); } } /** * update a cards position (kanban drag & drop) * @return null */ public function updatePosition() { //validation if (!request()->filled('status')) { abort(409, __('lang.error_request_could_not_be_completed')); } if (\App\Models\TaskStatus::Where('taskstatus_id', request('status'))->doesntExist()) { abort(409, __('lang.error_request_could_not_be_completed')); } if (!$task = $this->taskmodel::find(request('task_id'))) { abort(409, __('lang.error_request_could_not_be_completed')); } //get the task $tasks = $this->taskrepo->search(request('task_id')); $task = $tasks->first(); //check users permission to change task status if (!$this->taskpermissions->check('edit', $task)) { $this->processTasks($tasks); $payload = [ 'task' => $tasks->first(), 'tasks' => $tasks, ]; return new UpdateLockedResponse($payload); } //check dependency locks - for attempt to mark task as completed if ($task->count_dependency_cannot_complete > 0) { if (request('task_status') == 2 || request('status') == 2) { $this->processTasks($tasks); $payload = [ 'task' => $tasks->first(), 'tasks' => $tasks, ]; return new UpdateStatusLockedResponse($payload); } } //old status $old_status = $task->task_status; //(scenario - 1) card is placed in between 2 other cards if (is_numeric(request('previous_task_id')) && is_numeric(request('next_task_id'))) { //get previous task if (!$previous_task = $this->taskmodel::find(request('previous_task_id'))) { abort(409, __('lang.error_request_could_not_be_completed')); } //get next task if (!$next_task = $this->taskmodel::find(request('next_task_id'))) { abort(409, __('lang.error_request_could_not_be_completed')); } //calculate this tasks new position & update it $new_position = ($previous_task->task_position + $next_task->task_position) / 2; $task->task_position = $new_position; $task->task_status = request('status'); $task->save(); } //(scenario - 2) card is placed at the end of a list if (is_numeric(request('previous_task_id')) && !request()->filled('next_task_id')) { //get previous task if (!$previous_task = $this->taskmodel::find(request('previous_task_id'))) { abort(409, __('lang.error_request_could_not_be_completed')); } //calculate this tasks new position & update it $new_position = $previous_task->task_position + config('settings.db_position_increment'); $task->task_position = $new_position; $task->task_status = request('status'); $task->save(); } //(scenario - 3) card is placed at the start of a list if (is_numeric(request('next_task_id')) && !request()->filled('previous_task_id')) { //get next task if (!$next_task = $this->taskmodel::find(request('next_task_id'))) { abort(409, __('lang.error_request_could_not_be_completed')); } //calculate this tasks new position & update it $new_position = $next_task->task_position / 2; $task->task_position = $new_position; $task->task_status = request('status'); $task->save(); } //(scenario - 4) card is placed on an empty board if (!request()->filled('previous_task_id') && !request()->filled('next_task_id')) { //update only status $task->task_status = request('status'); $task->save(); } //status was changed - record event if ($old_status != $task->task_status) { //get refreshed task $tasks = $this->taskrepo->search(request('task_id')); $task = $tasks->first(); /** ---------------------------------------------- * record event [status] * ----------------------------------------------*/ $data = [ 'event_creatorid' => auth()->id(), 'event_item' => 'status', 'event_item_id' => '', 'event_item_lang' => 'event_changed_task_status', 'event_item_content' => $task->task_status, 'event_item_content2' => '', 'event_parent_type' => 'task', 'event_parent_id' => $task->task_id, 'event_parent_title' => $task->task_title, 'event_show_item' => 'yes', 'event_show_in_timeline' => config('system.settings_projects_events_show_task_status_change'), 'event_clientid' => $task->task_clientid, 'eventresource_type' => 'project', 'eventresource_id' => $task->task_projectid, 'event_notification_category' => 'notifications_tasks_activity', ]; //record event if ($event_id = $this->eventrepo->create($data)) { //get users $users = $this->taskpermissions->check('users', $task); //record notification $emailusers = $this->trackingrepo->recordEvent($data, $users, $event_id); } /** ---------------------------------------------- * send email [status] * ----------------------------------------------*/ if (isset($emailusers) && is_array($emailusers)) { $data = []; //send to users if ($users = \App\Models\User::WhereIn('id', $emailusers)->get()) { foreach ($users as $user) { $mail = new \App\Mail\TaskStatusChanged($user, $data, $task); $mail->build(); } } } } //refresh dependecies $this->refreshDependencies($task); //reponse payload $payload = [ 'stats' => $this->statsWidget(), ]; } /** * Archive a task * @param object TimerRepository instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function archive($id) { //get task and update status $task = \App\Models\Task::Where('task_id', $id)->first(); $task->task_active_state = 'archived'; $task->save(); //get refreshed task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //apply permissions $this->applyPermissions($task); //reponse payload $payload = [ 'tasks' => $tasks, 'action' => 'archive', ]; //show the form return new ArchiveResponse($payload); } /** * Activate a task * @param object TimerRepository instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function activate($id) { //get task and update status $task = \App\Models\Task::Where('task_id', $id)->first(); $task->task_active_state = 'active'; $task->save(); //get refreshed task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //apply permissions $this->applyPermissions($task); //reponse payload $payload = [ 'tasks' => $tasks, 'action' => 'archive', ]; //show the form return new ActivateResponse($payload); } /** * show custom fields data * * @param int $id * @return \Illuminate\Http\Response */ public function showCustomFields($id) { //get tasks $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //get customfields request()->merge([ 'sort_by' => 'customfields_position', 'filter_field_status' => 'enabled', ]); $fields = $this->getCustomFields($task); //package to send to response $payload = [ 'type' => 'show-custom-fields', 'task' => $task, 'fields' => $fields, ]; //show the form return new contentResponse($payload); } /** * show custom fields data * * @param int $id * @return \Illuminate\Http\Response */ public function editCustomFields($id) { //get tasks $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //get customfields request()->merge([ 'sort_by' => 'customfields_position', 'filter_field_status' => 'enabled', ]); $fields = $this->getCustomFields($task); //package to send to response $payload = [ 'type' => 'edit-custom-fields', 'task' => $task, 'fields' => $fields, ]; //show the form return new contentResponse($payload); } /** * show custom fields data * * @param int $id * @return \Illuminate\Http\Response */ public function updateCustomFields($id) { //get tasks $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //get customfields request()->merge([ 'sort_by' => 'customfields_position', 'filter_field_status' => 'enabled', ]); $fields = $this->getCustomFields($task); //update foreach ($fields as $field) { \App\Models\Task::where('task_id', $id) ->update([ $field->customfields_name => $_POST[$field->customfields_name], ]); } //refeshed data $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $fields = $this->getCustomFields($task); //package to send to response $payload = [ 'type' => 'show-custom-fields', 'task' => $task, 'fields' => $fields, ]; //show the form return new contentResponse($payload); } /** * show my notes data * * @param int $id * @return \Illuminate\Http\Response */ public function showMyNotes($id) { //get tasks if ($note = \App\Models\Note::Where('noteresource_type', 'task') ->Where('noteresource_id', $id) ->Where('note_creatorid', auth()->id())->first()) { $has_note = true; } else { $note = []; $has_note = false; } //refeshed data $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //package to send to response $payload = [ 'type' => 'show-notes', 'note' => $note, 'task' => $task, 'has_note' => $has_note, ]; //show the form return new contentResponse($payload); } /** * show my notes data * * @param int $id * @return \Illuminate\Http\Response */ public function editMyNotes($id) { //get tasks $note = \App\Models\Note::Where('noteresource_type', 'task') ->Where('noteresource_id', $id) ->Where('note_creatorid', auth()->id())->first(); //refeshed data $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //package to send to response $payload = [ 'type' => 'edit-notes', 'note' => $note, 'task' => $task, ]; //show the form return new contentResponse($payload); } /** * delete note * * @param int $id * @return \Illuminate\Http\Response */ public function deleteMyNotes($id) { //delete all notes by this user \App\Models\Note::Where('noteresource_type', 'task') ->where('noteresource_id', $id) ->where('note_creatorid', auth()->id())->delete(); //refeshed data $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $payload = [ 'type' => 'show-notes', 'note' => [], 'task' => $task, 'has_note' => false, ]; //show the form return new contentResponse($payload); } /** * show text editor * * @param int $id * @return \Illuminate\Http\Response */ public function createMyNotes($id) { //delete all notes by this user \App\Models\Note::Where('noteresource_type', 'task') ->where('noteresource_id', $id) ->where('note_creatorid', auth()->id())->delete(); //refeshed data $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $payload = [ 'type' => 'create-notes', 'note' => [], 'task' => $task, ]; //show the form return new contentResponse($payload); } /** * update notes * * @param int $id * @return \Illuminate\Http\Response */ public function updateMyNotes($id) { //validation if (!request()->filled('task_mynotes')) { abort(409, __('lang.fill_in_all_required_fields')); } //delete all notes by this user \App\Models\Note::Where('noteresource_type', 'task') ->where('noteresource_id', $id) ->where('note_creatorid', auth()->id())->delete(); //create note $note = new \App\Models\Note(); $note->noteresource_type = 'task'; $note->noteresource_id = $id; $note->note_creatorid = auth()->id(); $note->note_description = request('task_mynotes'); $note->note_visibility = 'private'; $note->save(); //refeshed data $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //package to send to response $payload = [ 'type' => 'show-notes', 'note' => $note, 'task' => $task, 'has_note' => true, ]; //show the form return new contentResponse($payload); } /** * show form for cloning tasks * * @param int $id * @return \Illuminate\Http\Response */ public function cloneTask($id) { //get task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //payload $payload = [ 'task' => $task, ]; //show the view return new CloneResponse($payload); } /** * show form for cloning tasks * * @param int $id * @return \Illuminate\Http\Response */ public function cloneStore(ProjectRepository $projectrepo, ProjectPermissions $projectpermissions, TaskAssignedRepository $assignedrepo, $id) { //validate task and milestones if (!request()->filled('task_milestoneid') || !request()->filled('project_id')) { abort(409, __('lang.fill_in_all_required_fields')); } //project exists if (\App\Models\Project::Where('project_id', request('project_id'))->doesntExist()) { abort(409, __('lang.project') . ' - ' . __('lang.is_invalid')); } //project exists if (\App\Models\Milestone::Where('milestone_id', request('task_milestoneid'))->where('milestone_projectid', request('project_id'))->doesntExist()) { abort(409, __('lang.milestone') . ' - ' . __('lang.is_invalid')); } //get users projects if (auth()->user()->is_team) { if (auth()->user()->is_admin) { $projects = \App\Models\Project::Where('project_type', 'project')->get(); $project_list = []; foreach ($projects as $project) { $project_list[] = $project->project_id; } } else { $project_list = $projectrepo->usersAssignedAndManageProjects(auth()->id(), 'list'); } } else { $project_list = $rojectrepo->clientsProjects(auth()->user()->clientid, 'list'); } //validate the project is valid for this user if (!in_array(request('project_id'), $project_list)) { abort(409, __('lang.invalid_permissions_for_project')); } //task $task = \App\Models\Task::Where('task_id', $id)->first(); //project $project = \App\Models\Project::Where('project_id', request('project_id'))->first(); //clone the task $data = [ 'task_title' => request('task_title'), 'task_status' => request('task_status'), 'task_milestoneid' => request('task_milestoneid'), 'copy_checklist' => (request('copy_checklist') == 'on') ? true : false, 'copy_files' => (request('copy_files') == 'on') ? true : false, ]; if (!$new_task = $this->taskrepo->cloneTask($task, $project, $data)) { abort(409, __('lang.error_request_could_not_be_completed')); } //assign the task to self, for none admin users if (auth()->user()->is_team) { if (!$projectpermissions->check('super-user', $project)) { $assigned_users = $assignedrepo->add($new_task->task_id, auth()->id()); } } //get table friendly collection $tasks = $this->taskrepo->search($new_task->task_id, ['apply_filters' => false]); //process for timers $this->processTasks($tasks); //apply some permissions if ($tasks) { foreach ($tasks as $task) { $this->applyPermissions($task); } } //apply custom fields if ($tasks) { foreach ($tasks as $task) { $task->fields = $this->getCustomFields($task); } } //payload $payload = [ 'task' => $tasks->first(), 'tasks' => $tasks, ]; //show the view return new CloneStoreResponse($payload); } /** * Show the form for editing the specified task * @param int $task task id * @return \Illuminate\Http\Response */ public function recurringSettings($id) { //get the project $task = \App\Models\Task::Where('task_id', $id)->first(); //reponse payload $payload = [ 'page' => $this->pageSettings('edit'), 'task' => $task, ]; //modal request if (request('source') == 'modal') { $html = view('pages/task/components/recurring', compact('task'))->render(); $jsondata['dom_html'][] = [ 'selector' => '#card-left-panel', 'action' => 'replace', 'value' => $html, ]; //ajax response return response()->json($jsondata); } //response return new RecurringSettingsResponse($payload); } /** * Update recurring settings * @param object TaskRecurrringSettings instance of the request validation object * @param int $task task id * @return \Illuminate\Http\Response */ public function recurringSettingsUpdate(TaskRecurrringSettings $request, $id) { //get project $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //update $task->task_recurring = 'yes'; $task->task_recurring_duration = request('task_recurring_duration'); $task->task_recurring_period = request('task_recurring_period'); $task->task_recurring_cycles = request('task_recurring_cycles'); $task->task_recurring_next = request('task_recurring_next'); $task->task_recurring_copy_checklists = (request('task_recurring_copy_checklists') == 'on') ? 'yes' : 'no'; $task->task_recurring_copy_files = (request('task_recurring_copy_files') == 'on') ? 'yes' : 'no'; $task->task_recurring_automatically_assign = (request('task_recurring_automatically_assign') == 'on') ? 'yes' : 'no'; $task->save(); //reset for infinite tasks (incase it had previously been set to finished) if ($task->task_recurring_cycles == 0) { $task->task_recurring_finished = 'no'; $task->save(); } //get refreshed $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //apply permissions $this->applyPermissions($task); //process $this->processTask($task); //reponse payload $payload = [ 'type' => 'update-recurring', 'action' => 'update', 'page' => $this->pageSettings('edit'), 'tasks' => $tasks, 'task' => $task, ]; //response return new UpdateResponse($payload); } /** * stop an task from recurring * @return \Illuminate\Http\Response */ public function stopRecurring() { //get the task $task = \App\Models\Task::Where('task_id', request()->route('task'))->first(); //update the task $task->task_recurring = 'no'; $task->task_recurring_duration = null; $task->task_recurring_period = null; $task->task_recurring_cycles = null; $task->task_recurring_next = null; $task->save(); //get refreshed $tasks = $this->taskrepo->search(request()->route('task')); $task = $tasks->first(); //apply permissions $this->applyPermissions($task); //process $this->processTask($task); //reponse payload $payload = [ 'type' => 'update-recurring', 'action' => 'stop', 'page' => $this->pageSettings('edit'), 'tasks' => $tasks, 'task' => $task, ]; //show the form return new UpdateResponse($payload); } /** * store a task dependency * * @param int $id * @return \Illuminate\Http\Response */ public function storeDependency(TaskDependencyRepository $dependencyrepo, $id) { //get the task $task = \App\Models\Task::Where('task_id', $id)->first(); //get the blocking task if (!$blocking_task = \App\Models\Task::Where('task_id', request('tasksdependency_blockerid'))->first()) { abort(409, __('lang.blocking_task') . ' - ' . __('lang.could_not_be_found')); } //validation if (!request()->filled('tasksdependency_blockerid')) { abort(409, __('lang.blocking_task') . ' - ' . __('lang.is_required')); } //validation if ($blocking_task->task_status == 2) { abort(409, __('lang.blocking_task_is_already_completed')); } //check we do not already have this dependency if (\App\Models\TaskDependency::Where('tasksdependency_taskid', $id) ->Where('tasksdependency_blockerid', request('tasksdependency_blockerid')) ->Where('tasksdependency_type', request('tasksdependency_type')) ->exists()) { abort(409, __('lang.task_dependency_already_exists')); } $dependency = new \App\Models\TaskDependency(); $dependency->tasksdependency_creatorid = auth()->id(); $dependency->tasksdependency_projectid = $task->task_projectid; $dependency->tasksdependency_clientid = $task->task_clientid; $dependency->tasksdependency_taskid = $id; $dependency->tasksdependency_blockerid = request('tasksdependency_blockerid'); $dependency->tasksdependency_type = request('tasksdependency_type'); $dependency->save(); //get all dependencies $dependecies_all = $dependencyrepo->search($id); //get refreshed $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //process and apply permissions $this->processTask($task); $this->applyPermissions($task); //reponse payload $payload = [ 'dependecies_all' => $dependecies_all, 'task' => $task, 'tasks' => $tasks, ]; //show the form return new StoreTaskDependencyResponse($payload); } /** * A task's status has changed. * If its completed - marke dependecies as 'fullfiled' * If its not 'completed' mark dependecies as 'active' * * @param int $id * @return \Illuminate\Http\Response */ public function refreshDependencies($task) { //if task is completed if ($task->task_status == 2) { \App\Models\TaskDependency::where('tasksdependency_blockerid', $task->task_id) ->update(['tasksdependency_status' => 'fulfilled']); } else { \App\Models\TaskDependency::where('tasksdependency_blockerid', $task->task_id) ->update(['tasksdependency_status' => 'active']); } } /** * delete task dependency * @param int $id task id * @return \Illuminate\Http\Response */ public function deleteDependency($id) { //delete record \App\Models\TaskDependency::Where('tasksdependency_taskid', $id)->where('tasksdependency_id', request('dependency_id'))->delete(); //get refreshed $tasks = $this->taskrepo->search($id); $task = $tasks->first(); //process and apply permissions $this->processTask($task); $this->applyPermissions($task); //reponse payload $payload = [ 'tasks' => $tasks, ]; //show the form return new DeleteTaskDependencyResponse($payload); } /** * Add a task cover image * @param int $id task id * @return \Illuminate\Http\Response */ public function addCoverImage($id) { //validate if (!request()->filled('imageid')) { abort(409, __('lang.error_request_could_not_be_completed')); } //get the task and apply permissions $task = \App\Models\Task::Where('task_id', $id)->first(); //the image $attachment_uniqiueid = request('imageid'); //validate image if (!$image = \App\Models\Attachment::Where('attachment_uniqiueid', $attachment_uniqiueid)->Where('attachment_type', 'image')->Where('attachmentresource_id', $id)->first()) { abort(409, __('lang.error_request_could_not_be_completed')); } //update task record $task->task_cover_image = 'yes'; $task->task_cover_image_uniqueid = $attachment_uniqiueid; $task->task_cover_image_filename = $image->attachment_filename; $task->save(); //all changes are done in frontend with js return response()->json(array( 'status' => true, )); } /** * Remove a task cover image * @param int $id task id * @return \Illuminate\Http\Response */ public function removeCoverImage($id) { //get the task and apply permissions $task = \App\Models\Task::Where('task_id', $id)->first(); //update task record $task->task_cover_image = 'no'; $task->task_cover_image_uniqueid = ''; $task->task_cover_image_filename = ''; $task->save(); //all changes are done in frontend with js return response()->json(array( 'status' => true, )); } /** * toggle pinned state of tasks * * @return \Illuminate\Http\Response */ public function togglePinning(PinnedRepository $pinrepo, $id) { //toggle pin $status = $pinrepo->togglePinned($id, 'task'); //get the task $task = \App\Models\Task::Where('task_id', $id)->first(); //reponse payload $payload = [ 'task_id' => $id, 'task' => $task, 'status' => $status, ]; //generate a response return new PinningResponse($payload); } /** * Import checklist items from uploaded file (Excel, CSV, or Text) * @param object ChecklistRepository instance of the repository * @param int $id task id * @return \Illuminate\Http\Response */ public function importChecklists(ChecklistRepository $checklistrepo, $id) { //limit checklists items to import $import_limit = 500; // Get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); // Check if task exists if (!$task) { abort(404, __('lang.task_not_found')); } //START EDITS - Check for attachments array instead of direct file upload // CHANGED: Validate attachments array exists if (!request('attachments') || !is_array(request('attachments'))) { abort(409, __('lang.no_file_uploaded')); } // Get the first (and only) uploaded file from attachments array $attachments = request('attachments'); $directory = key($attachments); $filename = reset($attachments); // CHANGED: Build file path from temp directory where file was uploaded via attachFiles() $file_path = BASE_DIR . "/storage/temp/$directory/$filename"; // Check if file exists if (!file_exists($file_path)) { abort(409, $file_path); } // Get file extension $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); // Validate file type $allowed_extensions = ['xlsx', 'xls', 'csv', 'txt']; if (!in_array($extension, $allowed_extensions)) { abort(409, __('lang.invalid_file_type')); } //END EDITS - the rest of this method will work as before, with $file_path // Initialize results $import_results = [ 'success' => false, 'imported' => 0, 'skipped' => 0, 'message' => '', ]; try { // Handle different file types if (in_array($extension, ['xlsx', 'xls', 'csv'])) { // Handle Excel/CSV files using existing TasksChecklistImport class $import = new TasksChecklistImport($id, $import_limit); try { $import->import($file_path); $import_results = [ 'success' => true, 'imported' => $import->getRowCount(), 'skipped' => $import->getSkippedCount(), 'message' => "Successfully imported {$import->getRowCount()} checklist items", ]; if ($import->maxLimitReached()) { $import_results['message'] .= __('lang.maximum_importing_limit_reached') . ": " . $import->getMaxItems(); } } catch (Exception $e) { $import_results = [ 'success' => false, 'imported' => 0, 'skipped' => 0, 'message' => 'Import failed: ' . $e->getMessage(), ]; Log::error("Excel/CSV checklist import failed: " . $e->getMessage(), ['checklist.import', config('app.debug_ref'), basename(__FILE__), __line__]); } } elseif ($extension === 'txt') { // Handle text files using repository method $import_results = $checklistrepo->importTextChecklistTask($file_path, $id, $import_limit); } } catch (Exception $e) { $import_results = [ 'success' => false, 'imported' => 0, 'skipped' => 0, 'message' => 'Import failed due to an error', ]; Log::error("Checklist import failed: " . $e->getMessage(), ['checklist.import', config('app.debug_ref'), basename(__FILE__), __line__]); } // Clean up - delete the temporary file if (Storage::exists("temp/$directory")) { Storage::deleteDirectory("temp/$directory"); } // Get updated checklists and progress after import request()->merge([ 'checklistresource_type' => 'task', 'checklistresource_id' => $id, ]); $checklists = $checklistrepo->search(); foreach ($checklists as $checklist) { $this->applyChecklistPermissions($checklist); } //get new progress $progress = $this->checklistProgress($checklists); // Get updated task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); $this->applyPermissions($task); //reponse payload $payload = [ 'import_results' => $import_results, 'checklists' => $checklists, 'progress' => $progress, 'task' => $task, ]; //generate a response return new ImportChecklistResponse($payload); } /** * Store a newly created checklist comment * @param int $id task id * @return \Illuminate\Http\Response */ public function storeChecklistComment(CommentRepository $commentrepo, $id) { //validate input if (!request()->filled('checklist-comment')) { abort(409, __('lang.comment_is_required')); } //get checklist id from form $checklist_id = request('checklist-comments-checklist-id'); //get the checklist $checklist = \App\Models\Checklist::Where('checklist_id', $checklist_id) ->Where('checklistresource_type', 'task') ->Where('checklistresource_id', $id) ->first(); //checklist must exist and belong to this task if (!$checklist) { abort(404); } //get the task $tasks = $this->taskrepo->search($id); $task = $tasks->first(); // Check if task exists if (!$task) { abort(404, __('lang.task_not_found')); } //get the global users for this task (array of user id's) $user_ids = $this->taskpermissions->check('users', $id); //create the comment $comment = new \App\Models\Comment(); $comment->comment_creatorid = auth()->id(); $comment->comment_text = convertTextareaToHtml(request('checklist-comment')); $comment->commentresource_type = 'checklist'; $comment->commentresource_id = $checklist_id; $comment->save(); //get complete comment $comments = $commentrepo->search($comment->comment_id); $comment = $comments->first(); $this->applyCommentPermissions($comment); /** ---------------------------------------------- * record event [coment] * ----------------------------------------------*/ $data = [ 'event_creatorid' => auth()->id(), 'event_item' => 'comment', 'event_item_id' => $comment->comment_id, 'event_item_lang' => 'event_posted_a_comment', 'event_item_content' => $comment->comment_text, 'event_item_content2' => '', 'event_parent_type' => 'task', 'event_parent_id' => $task->task_id, 'event_parent_title' => $task->task_title, 'event_show_item' => 'yes', 'event_show_in_timeline' => 'no', 'event_clientid' => $task->task_clientid, 'eventresource_type' => 'project', 'eventresource_id' => $task->task_projectid, 'event_notification_category' => 'notifications_tasks_activity', ]; //record event if ($event_id = $this->eventrepo->create($data)) { //get users $users = $this->taskpermissions->check('users', $task); //record notification $emailusers = $this->trackingrepo->recordEvent($data, $users, $event_id); } /** ---------------------------------------------- * send email [comment] * ----------------------------------------------*/ if (isset($emailusers) && is_array($emailusers)) { //the comment $data = $comment->toArray(); //add the checklist and also styling to the comment $data['comment_text'] = formatChecklistComment($comment, $checklist); //send to users if ($users = \App\Models\User::WhereIn('id', $emailusers)->get()) { foreach ($users as $user) { $mail = new \App\Mail\TaskComment($user, $data, $task); $mail->build(); } } } //response payload $payload = [ 'response' => 'store', 'comment' => $comment, 'checklist_id' => $checklist_id, ]; //generate response return new ChecklistCommentsResponse($payload); } /** * Remove the specified checklist comment from storage * @param int $id task id * @return \Illuminate\Http\Response */ public function destroyChecklistComment($id) { //get the comment $comment = \App\Models\Comment::Where('comment_id', $id) ->Where('commentresource_type', 'checklist') ->first(); //comment must exist if (!$comment) { abort(404); } //permission if ($comment->comment_creatorid != auth()->id() && auth()->user()->role_id == 1) { abort(403); } //delete the comment $comment->delete(); //response payload $payload = [ 'response' => 'delete', 'comment_id' => $comment_id, ]; //generate response return new ChecklistCommentsResponse($payload); } /** * basic page setting for this section of the app * @param string $section page section (optional) * @param array $data any other data (optional) * @return array */ private function pageSettings($section = '', $data = []) { //common settings $page = [ 'crumbs' => [ __('lang.tasks'), ], 'crumbs_special_class' => 'list-pages-crumbs', 'page' => 'tasks', 'no_results_message' => __('lang.no_results_found'), 'mainmenu_tasks' => 'active', 'sidepanel_id' => 'sidepanel-filter-tasks', 'dynamic_search_url' => url('tasks/search?action=search&taskresource_id=' . request('taskresource_id') . '&taskresource_type=' . request('taskresource_type')), 'add_button_classes' => '', 'load_more_button_route' => 'tasks', 'source' => 'list', ]; //default modal settings (modify for sepecif sections) $page += [ 'add_modal_title' => __('lang.add_task'), 'add_modal_create_url' => url('tasks/create?taskresource_id=' . request('taskresource_id') . '&taskresource_type=' . request('taskresource_type')), 'add_modal_action_url' => url('tasks?taskresource_id=' . request('taskresource_id') . '&taskresource_type=' . request('taskresource_type') . '&count=' . ($data['count'] ?? '')), 'add_modal_action_ajax_class' => '', 'add_modal_action_ajax_loading_target' => 'commonModalBody', 'add_modal_action_method' => 'POST', ]; //tasks list page if ($section == 'tasks') { $page += [ 'meta_title' => __('lang.tasks'), 'heading' => __('lang.tasks'), 'mainmenu_tasks' => 'active', ]; return $page; } //task page if ($section == 'task') { //adjust $page['page'] = 'task'; //add $page += [ ]; return $page; } //ext page settings if ($section == 'ext') { $page += [ 'list_page_actions_size' => 'col-lg-12', ]; return $page; } //create new resource if ($section == 'create') { $page += [ 'section' => 'create', ]; return $page; } //edit new resource if ($section == 'edit') { $page += [ 'section' => 'edit', ]; return $page; } //return return $page; } /** * data for the stats widget * @return array */ private function statsWidget($data = array()) { //default values $stats = []; foreach (config('task_statuses') as $status) { $stat = [ 'value' => \App\Models\Task::where('task_status', $status->taskstatus_id)->count(), 'title' => runtimeLang($status->taskstatus_title), 'percentage' => '100%', 'color' => 'bg-' . $status->taskstatus_color, ]; array_push($stats, $stat); } //return return $stats; } }
Save Changes
Cancel / Back
Close ×
Server Info
Hostname: server1.winmanyltd.com
Server IP: 203.161.60.52
PHP Version: 8.3.27
Server Software: Apache
System: Linux server1.winmanyltd.com 4.18.0-553.22.1.el8_10.x86_64 #1 SMP Tue Sep 24 05:16:59 EDT 2024 x86_64
HDD Total: 117.98 GB
HDD Free: 59.69 GB
Domains on IP: N/A (Requires external lookup)
System Features
Safe Mode:
Off
disable_functions:
None
allow_url_fopen:
On
allow_url_include:
Off
magic_quotes_gpc:
Off
register_globals:
Off
open_basedir:
None
cURL:
Enabled
ZipArchive:
Enabled
MySQLi:
Enabled
PDO:
Enabled
wget:
Yes
curl (cmd):
Yes
perl:
Yes
python:
Yes (py3)
gcc:
Yes
pkexec:
Yes
git:
Yes
User Info
Username: eliosofonline
User ID (UID): 1002
Group ID (GID): 1003
Script Owner UID: 1002
Current Dir Owner: 1002