diff --git a/app/Entities/Projects/Job.php b/app/Entities/Projects/Job.php index 14f1c27..7b1dd46 100755 --- a/app/Entities/Projects/Job.php +++ b/app/Entities/Projects/Job.php @@ -2,127 +2,130 @@ namespace App\Entities\Projects; -use DB; use App\Entities\Users\User; +use DB; use Illuminate\Database\Eloquent\Model; use Laracasts\Presenter\PresentableTrait; +use Spatie\MediaLibrary\HasMedia\HasMedia; +use Spatie\MediaLibrary\HasMedia\HasMediaTrait; /** * Job Model. * * @author Nafies Luthfi */ -class Job extends Model +class Job extends Model implements HasMedia { - use PresentableTrait; + use PresentableTrait; + use HasMediaTrait; - /** - * @var \App\Entities\Projects\JobPresenter - */ - protected $presenter = JobPresenter::class; + /** + * @var \App\Entities\Projects\JobPresenter + */ + protected $presenter = JobPresenter::class; - /** - * @var array - */ - protected $guarded = ['id', 'created_at', 'updated_at']; + /** + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Show job name with link to job detail. - * - * @return Illuminate\Support\HtmlString - */ - public function nameLink() - { - return link_to_route('jobs.show', $this->name, [$this->id], [ - 'title' => __( - 'app.show_detail_title', - ['name' => $this->name, 'type' => __('job.job')] - ), - ]); - } + /** + * Show job name with link to job detail. + * + * @return Illuminate\Support\HtmlString + */ + public function nameLink() + { + return link_to_route('jobs.show', $this->name, [$this->id], [ + 'title' => __( + 'app.show_detail_title', + ['name' => $this->name, 'type' => __('job.job')] + ), + ]); + } - /** - * Job belongs to a Project relation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function project() - { - return $this->belongsTo(Project::class, 'project_id'); - } + /** + * Job belongs to a Project relation. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function project() + { + return $this->belongsTo(Project::class, 'project_id'); + } - /** - * Job belongs to a Worker relation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function worker() - { - return $this->belongsTo(User::class, 'worker_id'); - } + /** + * Job belongs to a Worker relation. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function worker() + { + return $this->belongsTo(User::class, 'worker_id'); + } - /** - * Job has many Tasks relation ordered by position. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function tasks() - { - return $this->hasMany(Task::class)->orderBy('position'); - } + /** + * Job has many Tasks relation ordered by position. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function tasks() + { + return $this->hasMany(Task::class)->orderBy('position'); + } - /** - * Job has many comments relation. - * - * @return \Illuminate\Database\Eloquent\Relations\MorphMany - */ - public function comments() - { - return $this->morphMany(Comment::class, 'commentable'); - } + /** + * Job has many comments relation. + * + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function comments() + { + return $this->morphMany(Comment::class, 'commentable'); + } - /** - * Get the job type. - * - * @return string - */ - public function type() - { - return $this->type_id == 1 ? __('job.main') : __('job.additional'); - } + /** + * Get the job type. + * + * @return string + */ + public function type() + { + return $this->type_id == 1 ? __('job.main') : __('job.additional'); + } - /** - * Add progress attribute on Job model. - * - * @return int - */ - public function getProgressAttribute() - { - return $this->tasks->isEmpty() ? 0 : $this->tasks->avg('progress'); - } + /** + * Add progress attribute on Job model. + * + * @return int + */ + public function getProgressAttribute() + { + return $this->tasks->isEmpty() ? 0 : $this->tasks->avg('progress'); + } - /** - * Add receiveable_earning attribute on Job model. - * - * @return float - */ - public function getReceiveableEarningAttribute() - { - return $this->price * ($this->progress / 100); - } + /** + * Add receiveable_earning attribute on Job model. + * + * @return float + */ + public function getReceiveableEarningAttribute() + { + return $this->price * ($this->progress / 100); + } - /** - * Delete the model from the database. - * - * @return bool|null - */ - public function delete() - { - DB::beginTransaction(); - $this->tasks()->delete(); - $this->comments()->delete(); - DB::commit(); + /** + * Delete the model from the database. + * + * @return bool|null + */ + public function delete() + { + DB::beginTransaction(); + $this->tasks()->delete(); + $this->comments()->delete(); + DB::commit(); - return parent::delete(); - } + return parent::delete(); + } } diff --git a/app/Entities/Projects/JobsRepository.php b/app/Entities/Projects/JobsRepository.php index 5f206dd..4270c67 100755 --- a/app/Entities/Projects/JobsRepository.php +++ b/app/Entities/Projects/JobsRepository.php @@ -82,8 +82,19 @@ class JobsRepository extends BaseRepository } $jobData['price'] = str_replace('.', '', $jobData['price']); + $jobData = collect( $jobData); $job = $this->requireById($jobId); - $job->update($jobData); + + if($jobData->has('files')){ + foreach ($jobData->get('files') as $file) { + $path = $file->getPathName(); + if(is_file($path)){ + $job->addMedia($path)->toMediaCollection(); + } + } + } + + $job->update($jobData->except('files')->toArray()); return $job; } diff --git a/app/Http/Controllers/JobsController.php b/app/Http/Controllers/JobsController.php index 35fbc23..9badcfe 100755 --- a/app/Http/Controllers/JobsController.php +++ b/app/Http/Controllers/JobsController.php @@ -2,13 +2,13 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use App\Entities\Projects\Job; use App\Entities\Projects\Comment; -use App\Entities\Projects\Project; +use App\Entities\Projects\Job; use App\Entities\Projects\JobsRepository; +use App\Entities\Projects\Project; use App\Http\Requests\Jobs\DeleteRequest; use App\Http\Requests\Jobs\UpdateRequest; +use Illuminate\Http\Request; /** * Jobs Controller. @@ -17,149 +17,151 @@ use App\Http\Requests\Jobs\UpdateRequest; */ class JobsController extends Controller { - /** - * @var \App\Entities\Projects\JobsRepository - */ - private $repo; - - /** - * Create new Jobs Controller. - * - * @param \App\Entities\Projects\JobsRepository $repo - */ - public function __construct(JobsRepository $repo) - { - $this->repo = $repo; - } - - /** - * Show unfinished job list. - * - * @return \Illuminate\View\View - */ - public function index() - { - $user = auth()->user(); - - if ($user->hasRole('admin')) { - $projects = Project::whereIn('status_id', [2, 3])->pluck('name', 'id'); - } else { - $projects = $user->projects() - ->whereIn('status_id', [2, 3]) - ->pluck('projects.name', 'projects.id'); - } - - $jobs = $this->repo->getUnfinishedJobs($user, request('project_id')); - - return view('jobs.unfinished', compact('jobs', 'projects')); - } - - /** - * Show a job detail. - * - * @param \Illuminate\Http\Request $request - * @param \App\Entities\Projects\Job $job - * @return \Illuminate\View\View - */ - public function show(Request $request, Job $job) - { - $this->authorize('view', $job); - - $editableTask = null; - $editableComment = null; - $comments = $job->comments()->with('creator')->latest()->paginate(); - - if ($request->get('action') == 'task_edit' && $request->has('task_id')) { - $editableTask = $this->repo->requireTaskById($request->get('task_id')); - } - - if ($request->get('action') == 'task_delete' && $request->has('task_id')) { - $editableTask = $this->repo->requireTaskById($request->get('task_id')); - } - - if (request('action') == 'comment-edit' && request('comment_id') != null) { - $editableComment = Comment::find(request('comment_id')); - } - - return view('jobs.show', compact('job', 'editableTask', 'comments', 'editableComment')); - } - - /** - * Show a job edit form. - * - * @param \App\Entities\Projects\Job $job - * @return \Illuminate\View\View - */ - public function edit(Job $job) - { - $this->authorize('view', $job); - - $workers = $this->repo->getWorkersList(); - - return view('jobs.edit', compact('job', 'workers')); - } - - /** - * Update a job on database. - * - * @param \App\Http\Requests\Jobs\UpdateRequest $request - * @param \App\Entities\Projects\Job $job - * @return \Illuminate\Routing\Redirector - */ - public function update(UpdateRequest $request, Job $job) - { - $job = $this->repo->update($request->except(['_method', '_token']), $job->id); - flash(trans('job.updated'), 'success'); - - return redirect()->route('jobs.show', $job); - } - - /** - * Show job delete confirmation page. - * - * @param \App\Entities\Projects\Job $job - * @return \Illuminate\View\View - */ - public function delete(Job $job) - { - return view('jobs.delete', compact('job')); - } - - /** - * Show job delete confirmation page. - * - * @param \App\Http\Requests\Jobs\DeleteRequest $request - * @param \App\Entities\Projects\Job $job - * @return \Illuminate\View\View - */ - public function destroy(DeleteRequest $request, Job $job) - { - $projectId = $job->project_id; - - if ($job->id == $request->get('job_id')) { - $job->tasks()->delete(); - $job->delete(); - flash(trans('job.deleted'), 'success'); - } else { - flash(trans('job.undeleted'), 'danger'); - } - - return redirect()->route('projects.jobs.index', $projectId); - } - - /** - * Reorder job task position. - * - * @param \Illuminate\Http\Request $request - * @param \App\Entities\Projects\Job $job - * @return string|null - */ - public function tasksReorder(Request $request, Job $job) - { - if ($request->expectsJson()) { - $data = $this->repo->tasksReorder($request->get('postData')); - - return 'oke'; - } - } + /** + * @var \App\Entities\Projects\JobsRepository + */ + private $repo; + + /** + * Create new Jobs Controller. + * + * @param \App\Entities\Projects\JobsRepository $repo + */ + public function __construct(JobsRepository $repo) + { + $this->repo = $repo; + } + + /** + * Show unfinished job list. + * + * @return \Illuminate\View\View + */ + public function index() + { + $user = auth()->user(); + + if ($user->hasRole('admin')) { + $projects = Project::whereIn('status_id', [2, 3])->pluck('name', 'id'); + } else { + $projects = $user->projects() + ->whereIn('status_id', [2, 3]) + ->pluck('projects.name', 'projects.id'); + } + + $jobs = $this->repo->getUnfinishedJobs($user, request('project_id')); + + return view('jobs.unfinished', compact('jobs', 'projects')); + } + + /** + * Show a job detail. + * + * @param \Illuminate\Http\Request $request + * @param \App\Entities\Projects\Job $job + * @return \Illuminate\View\View + */ + public function show(Request $request, Job $job) + { + $this->authorize('view', $job); + + $editableTask = null; + $editableComment = null; + $comments = $job->comments()->with('creator')->latest()->paginate(); + + if ($request->get('action') == 'task_edit' && $request->has('task_id')) { + $editableTask = $this->repo->requireTaskById($request->get('task_id')); + } + + if ($request->get('action') == 'task_delete' && $request->has('task_id')) { + $editableTask = $this->repo->requireTaskById($request->get('task_id')); + } + + if (request('action') == 'comment-edit' && request('comment_id') != null) { + $editableComment = Comment::find(request('comment_id')); + } + + return view('jobs.show', compact('job', 'editableTask', 'comments', 'editableComment')); + } + + /** + * Show a job edit form. + * + * @param \App\Entities\Projects\Job $job + * @return \Illuminate\View\View + */ + public function edit(Job $job) + { + + $files = $job->getMedia(); + + $this->authorize('view', $job); + + $workers = $this->repo->getWorkersList(); + return view('jobs.edit', compact('job', 'workers', 'files')); + } + + /** + * Update a job on database. + * + * @param \App\Http\Requests\Jobs\UpdateRequest $request + * @param \App\Entities\Projects\Job $job + * @return \Illuminate\Routing\Redirector + */ + public function update(UpdateRequest $request, Job $job) + { + $job = $this->repo->update($request->except(['_method', '_token']), $job->id); + flash(trans('job.updated'), 'success'); + + return redirect()->route('jobs.show', $job); + } + + /** + * Show job delete confirmation page. + * + * @param \App\Entities\Projects\Job $job + * @return \Illuminate\View\View + */ + public function delete(Job $job) + { + return view('jobs.delete', compact('job')); + } + + /** + * Show job delete confirmation page. + * + * @param \App\Http\Requests\Jobs\DeleteRequest $request + * @param \App\Entities\Projects\Job $job + * @return \Illuminate\View\View + */ + public function destroy(DeleteRequest $request, Job $job) + { + $projectId = $job->project_id; + + if ($job->id == $request->get('job_id')) { + $job->tasks()->delete(); + $job->delete(); + flash(trans('job.deleted'), 'success'); + } else { + flash(trans('job.undeleted'), 'danger'); + } + + return redirect()->route('projects.jobs.index', $projectId); + } + + /** + * Reorder job task position. + * + * @param \Illuminate\Http\Request $request + * @param \App\Entities\Projects\Job $job + * @return string|null + */ + public function tasksReorder(Request $request, Job $job) + { + if ($request->expectsJson()) { + $data = $this->repo->tasksReorder($request->get('postData')); + + return 'oke'; + } + } } diff --git a/app/Http/Controllers/Projects/FilesController.php b/app/Http/Controllers/Projects/FilesController.php index 9779c51..9848b5a 100644 --- a/app/Http/Controllers/Projects/FilesController.php +++ b/app/Http/Controllers/Projects/FilesController.php @@ -2,10 +2,10 @@ namespace App\Http\Controllers\Projects; -use File as FileSystem; -use Illuminate\Http\Request; use App\Entities\Projects\File; use App\Http\Controllers\Controller; +use File as FileSystem; +use Illuminate\Http\Request; /** * Project Files Controller. @@ -14,115 +14,130 @@ use App\Http\Controllers\Controller; */ class FilesController extends Controller { - private $fileableTypes = [ - 'projects' => 'App\Entities\Projects\Project', - ]; - - public function index(Request $request, $fileableId) - { - $editableFile = null; - $fileableType = $request->segment(1); // projects, jobs - $modelName = $this->getModelName($fileableType); - $modelShortName = $this->getModelShortName($modelName); - $model = $modelName::findOrFail($fileableId); - $files = $model->files; - - if (in_array($request->get('action'), ['edit', 'delete']) && $request->has('id')) { - $editableFile = File::find($request->get('id')); - } - - return view($fileableType.'.files', [$modelShortName => $model, 'files' => $files, 'editableFile' => $editableFile]); - } - - public function create(Request $request, $fileableId) - { - $this->validate($request, [ - 'fileable_type' => 'required', - 'file' => 'required|file|max:10000', - 'title' => 'required|max:60', - 'description' => 'nullable|max:255', - ]); - - $fileableType = array_search($request->get('fileable_type'), $this->fileableTypes); - - if ($fileableType) { - $file = $this->proccessPhotoUpload($request->except('_token'), $fileableType, $fileableId); - - if ($file->exists) { - flash('Upload file berhasil.', 'success'); - } else { - flash('Upload file gagal, coba kembali.', 'danger'); - } - } else { - flash('Upload file gagal, coba kembali.', 'danger'); - } - - return back(); - } - - public function show($fileId) - { - $file = File::find($fileId); - - if ($file && file_exists(storage_path('app/public/files/'.$file->filename))) { - $extension = FileSystem::extension('public/files/'.$file->filename); - - return response()->download(storage_path('app/public/files/'.$file->filename), $file->title.'.'.$extension); - } - - flash(__('file.not_found'), 'danger'); - - if (\URL::previous() != \URL::current()) { - return back(); - } - - return redirect()->home(); - } - - public function update(Request $request, File $file) - { - $file->title = $request->get('title'); - $file->description = $request->get('description'); - $file->save(); - - flash(__('file.updated'), 'success'); - - return redirect()->route($file->fileable_type.'.files', $file->fileable_id); - } - - public function destroy(Request $request, File $file) - { - $file->delete(); - flash(__('file.deleted'), 'warning'); - - return redirect()->route($file->fileable_type.'.files', $file->fileable_id); - } - - private function proccessPhotoUpload($data, $fileableType, $fileableId) - { - $file = $data['file']; - $fileName = $file->hashName(); - - $fileData['fileable_id'] = $fileableId; - $fileData['fileable_type'] = $fileableType; - $fileData['filename'] = $fileName; - $fileData['title'] = $data['title']; - $fileData['description'] = $data['description']; - \DB::beginTransaction(); - $file->store('public/files'); - $file = File::create($fileData); - \DB::commit(); - - return $file; - } - - public function getModelName($fileableType) - { - return isset($this->fileableTypes[$fileableType]) ? $this->fileableTypes[$fileableType] : false; - } - - public function getModelShortName($modelName) - { - return strtolower((new \ReflectionClass($modelName))->getShortName()); - } + private $fileableTypes = [ + 'projects' => 'App\Entities\Projects\Project', + ]; + + public function index(Request $request, $fileableId) + { + $editableFile = null; + $fileableType = $request->segment(1); // projects, jobs + $modelName = $this->getModelName($fileableType); + $modelShortName = $this->getModelShortName($modelName); + $model = $modelName::findOrFail($fileableId); + $files = $model->files; + + if (in_array($request->get('action'), ['edit', 'delete']) && $request->has('id')) { + $editableFile = File::find($request->get('id')); + } + + return view($fileableType . '.files', [$modelShortName => $model, 'files' => $files, 'editableFile' => $editableFile]); + } + + public function create(Request $request, $fileableId) + { + $this->validate($request, [ + 'fileable_type' => 'required', + 'file' => 'required|file|max:10000', + 'title' => 'required|max:60', + 'description' => 'nullable|max:255', + ]); + + $fileableType = array_search($request->get('fileable_type'), $this->fileableTypes); + + if ($fileableType) { + $file = $this->proccessPhotoUpload($request->except('_token'), $fileableType, $fileableId); + + if ($file->exists) { + flash('Upload file berhasil.', 'success'); + } else { + flash('Upload file gagal, coba kembali.', 'danger'); + } + } else { + flash('Upload file gagal, coba kembali.', 'danger'); + } + + return back(); + } + + public function show($fileId) + { + $file = File::find($fileId); + + if ($file && file_exists(storage_path('app/public/files/' . $file->filename))) { + $extension = FileSystem::extension('public/files/' . $file->filename); + + return response()->download(storage_path('app/public/files/' . $file->filename), $file->title . '.' . $extension); + } + + flash(__('file.not_found'), 'danger'); + + if (\URL::previous() != \URL::current()) { + return back(); + } + + return redirect()->home(); + } + + public function update(Request $request, File $file) + { + $file->title = $request->get('title'); + $file->description = $request->get('description'); + $file->save(); + + flash(__('file.updated'), 'success'); + + return redirect()->route($file->fileable_type . '.files', $file->fileable_id); + } + + public function destroy(Request $request, File $file) + { + $file->delete(); + flash(__('file.deleted'), 'warning'); + + return redirect()->route($file->fileable_type . '.files', $file->fileable_id); + } + + private function proccessPhotoUpload($data, $fileableType, $fileableId) + { + $file = $data['file']; + $fileName = $file->hashName(); + + $fileData['fileable_id'] = $fileableId; + $fileData['fileable_type'] = $fileableType; + $fileData['filename'] = $fileName; + $fileData['title'] = $data['title']; + $fileData['description'] = $data['description']; + \DB::beginTransaction(); + $file->store('public/files'); + $file = File::create($fileData); + \DB::commit(); + + return $file; + } + + public function getModelName($fileableType) + { + return isset($this->fileableTypes[$fileableType]) ? $this->fileableTypes[$fileableType] : false; + } + + public function getModelShortName($modelName) + { + return strtolower((new \ReflectionClass($modelName))->getShortName()); + } + + public function showAttachment(Request $request, \App\Entities\Projects\Job $job, $media_id) + { + $files = $job->getMedia(); + + foreach ($files as $file) { + + if ($file->id == $media_id) { + return \Response::file($file->getPath()); + } + + } + + return back()->withErrors(['media not found']); + } } diff --git a/config/medialibrary.php b/config/medialibrary.php new file mode 100644 index 0000000..d1ec134 --- /dev/null +++ b/config/medialibrary.php @@ -0,0 +1,150 @@ + 'public', + + /* + * The maximum file size of an item in bytes. + * Adding a larger file will result in an exception. + */ + 'max_file_size' => 1024 * 1024 * 10, + + /* + * This queue will be used to generate derived and responsive images. + * Leave empty to use the default queue. + */ + 'queue_name' => '', + + /* + * The fully qualified class name of the media model. + */ + 'media_model' => Spatie\MediaLibrary\Models\Media::class, + + 's3' => [ + /* + * The domain that should be prepended when generating urls. + */ + 'domain' => 'https://'.env('AWS_BUCKET').'.s3.amazonaws.com', + ], + + 'remote' => [ + /* + * Any extra headers that should be included when uploading media to + * a remote disk. Even though supported headers may vary between + * different drivers, a sensible default has been provided. + * + * Supported by S3: CacheControl, Expires, StorageClass, + * ServerSideEncryption, Metadata, ACL, ContentEncoding + */ + 'extra_headers' => [ + 'CacheControl' => 'max-age=604800', + ], + ], + + 'responsive_images' => [ + + /* + * This class is responsible for calculating the target widths of the responsive + * images. By default we optimize for filesize and create variations that each are 20% + * smaller than the previous one. More info in the documentation. + * + * https://docs.spatie.be/laravel-medialibrary/v7/advanced-usage/generating-responsive-images + */ + 'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class, + + /* + * By default rendering media to a responsive image will add some javascript and a tiny placeholder. + * This ensures that the browser can already determine the correct layout. + */ + 'use_tiny_placeholders' => true, + + /* + * This class will generate the tiny placeholder used for progressive image loading. By default + * the medialibrary will use a tiny blurred jpg image. + */ + 'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class, + ], + + /* + * When urls to files get generated, this class will be called. Leave empty + * if your files are stored locally above the site root or on s3. + */ + 'url_generator' => null, + + /* + * The class that contains the strategy for determining a media file's path. + */ + 'path_generator' => null, + + /* + * Medialibrary will try to optimize all converted images by removing + * metadata and applying a little bit of compression. These are + * the optimizers that will be used by default. + */ + 'image_optimizers' => [ + Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [ + '--strip-all', // this strips out all text information such as comments and EXIF data + '--all-progressive', // this will make sure the resulting image is a progressive one + ], + Spatie\ImageOptimizer\Optimizers\Pngquant::class => [ + '--force', // required parameter for this package + ], + Spatie\ImageOptimizer\Optimizers\Optipng::class => [ + '-i0', // this will result in a non-interlaced, progressive scanned image + '-o2', // this set the optimization level to two (multiple IDAT compression trials) + '-quiet', // required parameter for this package + ], + Spatie\ImageOptimizer\Optimizers\Svgo::class => [ + '--disable=cleanupIDs', // disabling because it is known to cause troubles + ], + Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [ + '-b', // required parameter for this package + '-O3', // this produces the slowest but best results + ], + ], + + /* + * These generators will be used to create an image of media files. + */ + 'image_generators' => [ + Spatie\MediaLibrary\ImageGenerators\FileTypes\Image::class, + Spatie\MediaLibrary\ImageGenerators\FileTypes\Webp::class, + Spatie\MediaLibrary\ImageGenerators\FileTypes\Pdf::class, + Spatie\MediaLibrary\ImageGenerators\FileTypes\Svg::class, + Spatie\MediaLibrary\ImageGenerators\FileTypes\Video::class, + ], + + /* + * The engine that should perform the image conversions. + * Should be either `gd` or `imagick`. + */ + 'image_driver' => 'gd', + + /* + * FFMPEG & FFProbe binaries paths, only used if you try to generate video + * thumbnails and have installed the php-ffmpeg/php-ffmpeg composer + * dependency. + */ + 'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'), + 'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'), + + /* + * The path where to store temporary files while performing image conversions. + * If set to null, storage_path('medialibrary/temp') will be used. + */ + 'temporary_directory_path' => null, + + /* + * Here you can override the class names of the jobs used by this package. Make sure + * your custom jobs extend the ones provided by the package. + */ + 'jobs' => [ + 'perform_conversions' => Spatie\MediaLibrary\Jobs\PerformConversions::class, + 'generate_responsive_images' => Spatie\MediaLibrary\Jobs\GenerateResponsiveImages::class, + ], +]; diff --git a/database/migrations/2019_06_29_145856_create_media_table.php b/database/migrations/2019_06_29_145856_create_media_table.php new file mode 100644 index 0000000..378baef --- /dev/null +++ b/database/migrations/2019_06_29_145856_create_media_table.php @@ -0,0 +1,38 @@ +increments('id'); + $table->morphs('model'); + $table->string('collection_name'); + $table->string('name'); + $table->string('file_name'); + $table->string('mime_type')->nullable(); + $table->string('disk'); + $table->unsignedInteger('size'); + $table->json('manipulations'); + $table->json('custom_properties'); + $table->json('responsive_images'); + $table->unsignedInteger('order_column')->nullable(); + $table->nullableTimestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('media'); + } +} diff --git a/resources/lang/de/auth.php b/resources/lang/de/auth.php index 8a0c8da..4c041ed 100644 --- a/resources/lang/de/auth.php +++ b/resources/lang/de/auth.php @@ -1,43 +1,43 @@ 'Mein Profil', - 'profile_edit' => 'Mein Profil bearbeiten', - 'update_profile' => 'Profil aktualisieren', - 'profile_updated' => 'Profil wurde aktualisiert.', + // Profile + 'profile' => 'Mein Profil', + 'profile_edit' => 'Mein Profil bearbeiten', + 'update_profile' => 'Profil aktualisieren', + 'profile_updated' => 'Profil wurde aktualisiert.', - // Registration - 'register' => 'Neuen Account anlegen', - 'need_account' => 'Account benötigt?', - 'have_an_account' => 'Ich habe bereits einen Account', + // Registration + 'register' => 'Neuen Account anlegen', + 'need_account' => 'Account benötigt?', + 'have_an_account' => 'Ich habe bereits einen Account', - // Login & Logout - 'login' => 'Login', - 'welcome' => 'Willkommen :name.', - 'failed' => 'Diese Zugangsdaten passen nicht zu unseren Daten.', - 'throttle' => 'Zu viele Login Versuche. Bitte probiere es erneut in :seconds Sekunden.', - 'logout' => 'Logout', - 'logged_out' => 'Sie haben sich ausgeloggt.', - 'remember_me' => 'Erinnere dich an mich', + // Login & Logout + 'login' => 'Login', + 'welcome' => 'Willkommen :name.', + 'failed' => 'Diese Zugangsdaten passen nicht zu unseren Daten.', + 'throttle' => 'Zu viele Login Versuche. Bitte probiere es erneut in :seconds Sekunden.', + 'logout' => 'Logout', + 'logged_out' => 'Sie haben sich ausgeloggt.', + 'remember_me' => 'Erinnere dich an mich', - // Password - 'change_password' => 'Passwort ändern', - 'password_changed' => 'Ihr Passwort wurde geändert', - 'forgot_password' => 'Passwort vergessen?', - 'reset_password' => 'Password zurücksetzen', - 'send_reset_password_link' => 'Sende Passwort Reset Link', - 'old_password_failed' => 'Altes Passwort stimmt nicht überein!', - 'reset_password_hint' => 'Bitte setzen Sie Ihr Passwort zurück, indem Sie dieses Formular ausfüllen', + // Password + 'change_password' => 'Passwort ändern', + 'password_changed' => 'Ihr Passwort wurde geändert', + 'forgot_password' => 'Passwort vergessen?', + 'reset_password' => 'Password zurücksetzen', + 'send_reset_password_link' => 'Sende Passwort Reset Link', + 'old_password_failed' => 'Altes Passwort stimmt nicht überein!', + 'reset_password_hint' => 'Bitte setzen Sie Ihr Passwort zurück, indem Sie dieses Formular ausfüllen', - // Attributes - 'email' => 'E-Mail', - 'password' => 'Passwort', - 'password_confirmation' => 'Passwort bestätigen', - 'old_password' => 'Altes Passwort', - 'new_password' => 'Neues Passwort', - 'new_password_confirmation' => 'Neues Passwort bestätigen', + // Attributes + 'email' => 'E-Mail', + 'password' => 'Passwort', + 'password_confirmation' => 'Passwort bestätigen', + 'old_password' => 'Altes Passwort', + 'new_password' => 'Neues Passwort', + 'new_password_confirmation' => 'Neues Passwort bestätigen', - // Authorization - 'unauthorized_access' => 'Sie können nicht auf die Seite :url zugreifen.', + // Authorization + 'unauthorized_access' => 'Sie können nicht auf die Seite :url zugreifen.', ]; diff --git a/resources/lang/de/job.php b/resources/lang/de/job.php index f0c33c1..b3d4205 100644 --- a/resources/lang/de/job.php +++ b/resources/lang/de/job.php @@ -1,53 +1,54 @@ 'Beschäftigung', - 'list' => 'Beschäftigungsliste', - 'on_progress' => 'in Arbeit', - 'detail' => 'Beschäftigungsdetails', - 'search' => 'Beschäftigung suchen', - 'found' => 'Beschäftigung gefunden.', - 'not_found' => 'Beschäftigung nicht gefunden.', - 'tasks' => 'Aufgabenliste', - 'price_total' => 'Beschäftigungsgesamtpreis', - 'tasks_count' => 'Beschäftigungsanzahl', - 'empty' => 'Beschäftigungsliste ist leer.', - 'back_to_index' => 'zurück zur Beschäftigungsliste', - 'starts' => 'Starts', - 'ends' => 'Ends', - 'target' => 'Target', - 'actual' => 'Actual', - 'duration' => 'Duration', + // Labels + 'job' => 'Beschäftigung', + 'list' => 'Beschäftigungsliste', + 'on_progress' => 'in Arbeit', + 'detail' => 'Beschäftigungsdetails', + 'search' => 'Beschäftigung suchen', + 'found' => 'Beschäftigung gefunden.', + 'not_found' => 'Beschäftigung nicht gefunden.', + 'tasks' => 'Aufgabenliste', + 'price_total' => 'Beschäftigungsgesamtpreis', + 'tasks_count' => 'Beschäftigungsanzahl', + 'empty' => 'Beschäftigungsliste ist leer.', + 'back_to_index' => 'zurück zur Beschäftigungsliste', + 'starts' => 'Starts', + 'ends' => 'Ends', + 'target' => 'Target', + 'actual' => 'Actual', + 'duration' => 'Duration', - // Actions - 'create' => 'Neue Beschäftigung anlegen', - 'add' => 'Beschäftigung hinzufügen', - 'created' => 'Neue Beschäftigung wurde angelegt.', - 'show' => 'Beschäftigungsdetails anzeigen', - 'edit' => 'Beschäftigung bearbeiten', - 'update' => 'Beschäftigung aktualisieren', - 'updated' => 'Beschäftigungsdetails wurden aktualisiert.', - 'delete' => 'Beschäftigung löschen', - 'deleted' => 'Beschäftigung wurde gelöscht.', - 'undeleted' => 'Beschäftigung nicht gelöscht.', - 'add_from_other_project' => 'Beschäftigung von einem anderen Projekt hinzufügen', - 'select_project' => 'ein Projekt auswählen.', - 'sort_tasks' => 'sortiere Aufgabenpriorität', + // Actions + 'create' => 'Neue Beschäftigung anlegen', + 'add' => 'Beschäftigung hinzufügen', + 'created' => 'Neue Beschäftigung wurde angelegt.', + 'show' => 'Beschäftigungsdetails anzeigen', + 'edit' => 'Beschäftigung bearbeiten', + 'update' => 'Beschäftigung aktualisieren', + 'updated' => 'Beschäftigungsdetails wurden aktualisiert.', + 'delete' => 'Beschäftigung löschen', + 'deleted' => 'Beschäftigung wurde gelöscht.', + 'undeleted' => 'Beschäftigung nicht gelöscht.', + 'add_from_other_project' => 'Beschäftigung von einem anderen Projekt hinzufügen', + 'select_project' => 'ein Projekt auswählen.', + 'sort_tasks' => 'sortiere Aufgabenpriorität', - // Attributes - 'name' => 'Beschäftigungsname', - 'description' => 'Beschreibung', - 'progress' => 'Fortschritt', - 'worker' => 'Bearbeiter', - 'price' => 'Preis', - 'type' => 'Beschäftigungstyp', - 'target_start_date' => 'Target Start Date', - 'target_end_date' => 'Target End Date', - 'actual_start_date' => 'Actual Start Date', - 'actual_end_date' => 'Actual End Date', + // Attributes + 'name' => 'Beschäftigungsname', + 'description' => 'Beschreibung', + 'progress' => 'Fortschritt', + 'worker' => 'Bearbeiter', + 'price' => 'Preis', + 'type' => 'Beschäftigungstyp', + 'target_start_date' => 'Target Start Date', + 'target_end_date' => 'Target End Date', + 'actual_start_date' => 'Actual Start Date', + 'actual_end_date' => 'Actual End Date', + 'files' => 'An diesen Job angehängte Dateien', - // Types - 'main' => 'Haupt', - 'additional' => 'Zusätzlich', + // Types + 'main' => 'Haupt', + 'additional' => 'Zusätzlich', ]; diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index 624a176..016827e 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -1,43 +1,43 @@ 'My Profile', - 'profile_edit' => 'Edit My Profile', - 'update_profile' => 'Update Profile', - 'profile_updated' => 'Profile has been updated.', + // Profile + 'profile' => 'My Profile', + 'profile_edit' => 'Edit My Profile', + 'update_profile' => 'Update Profile', + 'profile_updated' => 'Profile has been updated.', - // Registration - 'register' => 'Create new Account', - 'need_account' => 'Need an Account?', - 'have_an_account' => 'I have an Account', + // Registration + 'register' => 'Create new Account', + 'need_account' => 'Need an Account?', + 'have_an_account' => 'I have an Account', - // Login & Logout - 'login' => 'Login', - 'welcome' => 'Welcome :name.', - 'failed' => 'These credentials do not match our records.', - 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', - 'logout' => 'Logout', - 'logged_out' => 'You have logged out.', - 'remember_me' => 'Remember me', + // Login & Logout + 'login' => 'Login', + 'welcome' => 'Welcome :name.', + 'failed' => 'These credentials do not match our records.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'logout' => 'Logout', + 'logged_out' => 'You have logged out.', + 'remember_me' => 'Remember me', - // Password - 'change_password' => 'Change Password', - 'password_changed' => 'Your password has been changed.', - 'forgot_password' => 'Forgot Password?', - 'reset_password' => 'Reset Password', - 'send_reset_password_link' => 'Send reset password link', - 'old_password_failed' => 'Old password does not match!', - 'reset_password_hint' => 'Please reset your password by filling out this form', + // Password + 'change_password' => 'Change Password', + 'password_changed' => 'Your password has been changed.', + 'forgot_password' => 'Forgot Password?', + 'reset_password' => 'Reset Password', + 'send_reset_password_link' => 'Send reset password link', + 'old_password_failed' => 'Old password does not match!', + 'reset_password_hint' => 'Please reset your password by filling out this form', - // Attributes - 'email' => 'Email', - 'password' => 'Password', - 'password_confirmation' => 'Confirm Password', - 'old_password' => 'Old Password', - 'new_password' => 'New Password', - 'new_password_confirmation' => 'Confirm new Password', + // Attributes + 'email' => 'Email', + 'password' => 'Password', + 'password_confirmation' => 'Confirm Password', + 'old_password' => 'Old Password', + 'new_password' => 'New Password', + 'new_password_confirmation' => 'Confirm new Password', - // Authorization - 'unauthorized_access' => 'You cannot access :url page.', + // Authorization + 'unauthorized_access' => 'You cannot access :url page.', ]; diff --git a/resources/lang/en/job.php b/resources/lang/en/job.php index 3b5c936..4b53781 100644 --- a/resources/lang/en/job.php +++ b/resources/lang/en/job.php @@ -1,55 +1,56 @@ 'Job', - 'list' => 'Job List', - 'on_progress' => 'Job on Progress', - 'detail' => 'Job Detail', - 'search' => 'Ssearch Job', - 'found' => 'Job found.', - 'not_found' => 'Job not found.', - 'tasks' => 'Task List', - 'price_total' => 'Job Price Total', - 'tasks_count' => 'Tasks Count', - 'empty' => 'Job list is empty.', - 'back_to_index' => 'Back to Job List', - 'starts' => 'Starts', - 'ends' => 'Ends', - 'target' => 'Target', - 'actual' => 'Actual', - 'duration' => 'Duration', + // Labels + 'job' => 'Job', + 'list' => 'Job List', + 'on_progress' => 'Job on Progress', + 'detail' => 'Job Detail', + 'search' => 'Ssearch Job', + 'found' => 'Job found.', + 'not_found' => 'Job not found.', + 'tasks' => 'Task List', + 'price_total' => 'Job Price Total', + 'tasks_count' => 'Tasks Count', + 'empty' => 'Job list is empty.', + 'back_to_index' => 'Back to Job List', + 'starts' => 'Starts', + 'ends' => 'Ends', + 'target' => 'Target', + 'actual' => 'Actual', + 'duration' => 'Duration', - // Actions - 'create' => 'Create new Job', - 'add' => 'Add Jobs', - 'created' => 'New Job has been created.', - 'show' => 'View Job Detail', - 'edit' => 'Edit Job', - 'update' => 'Update Job', - 'updated' => 'Job data has been updated.', - 'delete' => 'Delete Job', - 'deleted' => 'Job has been deleted.', - 'undeleted' => 'Job not deleted.', - 'add_from_other_project' => 'Add Job from another Project', - 'select_project' => 'Select a project.', - 'sort_tasks' => 'Sort Task Priority', + // Actions + 'create' => 'Create new Job', + 'add' => 'Add Jobs', + 'created' => 'New Job has been created.', + 'show' => 'View Job Detail', + 'edit' => 'Edit Job', + 'update' => 'Update Job', + 'updated' => 'Job data has been updated.', + 'delete' => 'Delete Job', + 'deleted' => 'Job has been deleted.', + 'undeleted' => 'Job not deleted.', + 'add_from_other_project' => 'Add Job from another Project', + 'select_project' => 'Select a project.', + 'sort_tasks' => 'Sort Task Priority', - 'created_from_other_project' => 'Job has been added from other Project.', + 'created_from_other_project' => 'Job has been added from other Project.', - // Attributes - 'name' => 'Job Name', - 'description' => 'Description', - 'progress' => 'Progress', - 'worker' => 'Worker', - 'price' => 'Price', - 'type' => 'Job Type', - 'target_start_date' => 'Target Start Date', - 'target_end_date' => 'Target End Date', - 'actual_start_date' => 'Actual Start Date', - 'actual_end_date' => 'Actual End Date', + // Attributes + 'name' => 'Job Name', + 'description' => 'Description', + 'progress' => 'Progress', + 'worker' => 'Worker', + 'price' => 'Price', + 'type' => 'Job Type', + 'target_start_date' => 'Target Start Date', + 'target_end_date' => 'Target End Date', + 'actual_start_date' => 'Actual Start Date', + 'actual_end_date' => 'Actual End Date', + 'files' => 'Files attached to this job', - // Types - 'main' => 'Main', - 'additional' => 'Additional', + // Types + 'main' => 'Main', + 'additional' => 'Additional', ]; diff --git a/resources/lang/id/auth.php b/resources/lang/id/auth.php index b875a18..1bfbc57 100644 --- a/resources/lang/id/auth.php +++ b/resources/lang/id/auth.php @@ -1,43 +1,43 @@ 'Profil Saya', - 'profile_edit' => 'Edit Profil Saya', - 'update_profile' => 'Update Profil', - 'profile_updated' => 'Profil sudah diupdate.', + // Profile + 'profile' => 'Profil Saya', + 'profile_edit' => 'Edit Profil Saya', + 'update_profile' => 'Update Profil', + 'profile_updated' => 'Profil sudah diupdate.', - // Registration - 'register' => 'Buat Akun Baru', - 'need_account' => 'Belum punya Akun?', - 'have_an_account' => 'Saya sudah punya Akun', + // Registration + 'register' => 'Buat Akun Baru', + 'need_account' => 'Belum punya Akun?', + 'have_an_account' => 'Saya sudah punya Akun', - // Login & Logout - 'login' => 'Login', - 'welcome' => 'Selamat datang kembali :name.', - 'failed' => 'Identitas tersebut tidak cocok dengan data kami.', - 'throttle' => 'Terlalu banyak usaha masuk. Silahkan coba lagi dalam :seconds detik.', - 'logout' => 'Keluar', - 'logged_out' => 'Anda telah logout.', - 'remember_me' => 'Ingat saya', + // Login & Logout + 'login' => 'Login', + 'welcome' => 'Selamat datang kembali :name.', + 'failed' => 'Identitas tersebut tidak cocok dengan data kami.', + 'throttle' => 'Terlalu banyak usaha masuk. Silahkan coba lagi dalam :seconds detik.', + 'logout' => 'Keluar', + 'logged_out' => 'Anda telah logout.', + 'remember_me' => 'Ingat saya', - // Password - 'change_password' => 'Ganti Password', - 'password_changed' => 'Password berhasil diubah.', - 'forgot_password' => 'Lupa Password?', - 'reset_password' => 'Reset Password', - 'send_reset_password_link' => 'Kirim Link Reset Password', - 'old_password_failed' => 'Password lama tidak cocok!', - 'reset_password_hint' => 'Silakan melakukan reset password dengan mengisi form berikut', + // Password + 'change_password' => 'Ganti Password', + 'password_changed' => 'Password berhasil diubah.', + 'forgot_password' => 'Lupa Password?', + 'reset_password' => 'Reset Password', + 'send_reset_password_link' => 'Kirim Link Reset Password', + 'old_password_failed' => 'Password lama tidak cocok!', + 'reset_password_hint' => 'Silakan melakukan reset password dengan mengisi form berikut', - // Attributes - 'email' => 'Email', - 'password' => 'Password', - 'password_confirmation' => 'Ulangi Password', - 'old_password' => 'Password Lama', - 'new_password' => 'Password Baru', - 'new_password_confirmation' => 'Ulangi Password Baru', + // Attributes + 'email' => 'Email', + 'password' => 'Password', + 'password_confirmation' => 'Ulangi Password', + 'old_password' => 'Password Lama', + 'new_password' => 'Password Baru', + 'new_password_confirmation' => 'Ulangi Password Baru', - // Authorization - 'unauthorized_access' => 'Anda tidak dapat mengakses halaman :url.', + // Authorization + 'unauthorized_access' => 'Anda tidak dapat mengakses halaman :url.', ]; diff --git a/resources/lang/id/job.php b/resources/lang/id/job.php index 33bd5f7..c7f6427 100644 --- a/resources/lang/id/job.php +++ b/resources/lang/id/job.php @@ -1,55 +1,56 @@ 'Job', - 'list' => 'Daftar Job', - 'on_progress' => 'Job on Progress', - 'detail' => 'Detail Job', - 'search' => 'Cari Job', - 'found' => 'Job ditemukan', - 'not_found' => 'Job tidak ditemukan', - 'tasks' => 'Daftar Task', - 'price_total' => 'Nilai Job Total', - 'tasks_count' => 'Jumlah Task', - 'empty' => 'Belum ada Job', - 'back_to_index' => 'Kembali ke daftar Job', - 'starts' => 'Mulai', - 'ends' => 'Selesai', - 'target' => 'Target', - 'actual' => 'Aktual', - 'duration' => 'Durasi', + // Labels + 'job' => 'Job', + 'list' => 'Daftar Job', + 'on_progress' => 'Job on Progress', + 'detail' => 'Detail Job', + 'search' => 'Cari Job', + 'found' => 'Job ditemukan', + 'not_found' => 'Job tidak ditemukan', + 'tasks' => 'Daftar Task', + 'price_total' => 'Nilai Job Total', + 'tasks_count' => 'Jumlah Task', + 'empty' => 'Belum ada Job', + 'back_to_index' => 'Kembali ke daftar Job', + 'starts' => 'Mulai', + 'ends' => 'Selesai', + 'target' => 'Target', + 'actual' => 'Aktual', + 'duration' => 'Durasi', - // Actions - 'create' => 'Input Job Baru', - 'add' => 'Tambahkan Job', - 'created' => 'Input Job baru telah berhasil.', - 'show' => 'Detail Job', - 'edit' => 'Edit Job', - 'update' => 'Update Job', - 'updated' => 'Update data Job telah berhasil.', - 'delete' => 'Hapus Job', - 'deleted' => 'Hapus data Job telah berhasil.', - 'undeleted' => 'Data Job gagal dihapus.', - 'add_from_other_project' => 'Tambah Job dari Project Lain', - 'select_project' => 'Pilih salah satu project.', - 'sort_tasks' => 'Urutkan Prioritas Task', + // Actions + 'create' => 'Input Job Baru', + 'add' => 'Tambahkan Job', + 'created' => 'Input Job baru telah berhasil.', + 'show' => 'Detail Job', + 'edit' => 'Edit Job', + 'update' => 'Update Job', + 'updated' => 'Update data Job telah berhasil.', + 'delete' => 'Hapus Job', + 'deleted' => 'Hapus data Job telah berhasil.', + 'undeleted' => 'Data Job gagal dihapus.', + 'add_from_other_project' => 'Tambah Job dari Project Lain', + 'select_project' => 'Pilih salah satu project.', + 'sort_tasks' => 'Urutkan Prioritas Task', - 'created_from_other_project' => 'Berhasil tambah job dari Project lain.', + 'created_from_other_project' => 'Berhasil tambah job dari Project lain.', - // Attributes - 'name' => 'Nama Job', - 'description' => 'Deskripsi', - 'progress' => 'Progress', - 'worker' => 'Pekerja', - 'price' => 'Biaya Pengerjaan', - 'type' => 'Jenis Job', - 'target_start_date' => 'Target Tgl Mulai', - 'target_end_date' => 'Target Tgl Selesai', - 'actual_start_date' => 'Tgl Mulai Aktual', - 'actual_end_date' => 'Tgl Selesai Aktual', + // Attributes + 'name' => 'Nama Job', + 'description' => 'Deskripsi', + 'progress' => 'Progress', + 'worker' => 'Pekerja', + 'price' => 'Biaya Pengerjaan', + 'type' => 'Jenis Job', + 'target_start_date' => 'Target Tgl Mulai', + 'target_end_date' => 'Target Tgl Selesai', + 'actual_start_date' => 'Tgl Mulai Aktual', + 'actual_end_date' => 'Tgl Selesai Aktual', + 'files' => 'File terlampir pada pekerjaan ini', - // Types - 'main' => 'Utama', - 'additional' => 'Tambahan', + // Types + 'main' => 'Utama', + 'additional' => 'Tambahan', ]; diff --git a/resources/views/jobs/edit.blade.php b/resources/views/jobs/edit.blade.php index e6f74ae..9934e6d 100755 --- a/resources/views/jobs/edit.blade.php +++ b/resources/views/jobs/edit.blade.php @@ -5,7 +5,7 @@ @section('content')

- {!! Form::model($job, ['route' => ['jobs.update', $job], 'method' => 'patch']) !!} + {!! Form::model($job, ['route' => ['jobs.update', $job], 'method' => 'patch' , 'files' => true] ) !!}

{{ $job->name }} {{ __('job.edit') }}

@@ -30,6 +30,16 @@
{!! FormField::text('actual_end_date', ['label' => __('job.actual_end_date'), 'class' => 'date-select']) !!}
{!! FormField::textarea('description', ['label' => __('job.description') , 'class' => 'countable']) !!} + + @if ($files->isNotEmpty()) + + {{$files->count()}} files + + @else + no files attached yet + @endif + {!! Form::file('files[]', ['label' => __('job.files') , 'multiple' => 'true']) !!} +
diff --git a/routes/web/projects.php b/routes/web/projects.php index e31823c..fcbf426 100644 --- a/routes/web/projects.php +++ b/routes/web/projects.php @@ -1,104 +1,109 @@ ['auth'], 'namespace' => 'Projects'], function () { - /* - * Projects Routes - */ - Route::get('projects/{project}/delete', ['as' => 'projects.delete', 'uses' => 'ProjectsController@delete']); - Route::get('projects/{project}/subscriptions', ['as' => 'projects.subscriptions', 'uses' => 'ProjectsController@subscriptions']); - Route::patch('projects/{project}/status-update', ['as' => 'projects.status-update', 'uses' => 'ProjectsController@statusUpdate']); - Route::resource('projects', 'ProjectsController'); - - /* - * Project Payments Routes - */ - Route::get('projects/{project}/payments', ['as' => 'projects.payments', 'uses' => 'ProjectsController@payments']); - - /* - * Project Fees Routes - */ - Route::get('projects/{project}/fees/create', ['as' => 'projects.fees.create', 'uses' => 'FeesController@create']); - Route::post('projects/{project}/fees/store', ['as' => 'projects.fees.store', 'uses' => 'FeesController@store']); - - /* - * Project Invoices Routes - */ - Route::get('projects/{project}/invoices', ['as' => 'projects.invoices', 'uses' => 'InvoicesController@index']); - - /* - * Project Jobs Routes - */ - Route::get('projects/{project}/jobs-export/{type?}', ['as' => 'projects.jobs-export', 'uses' => 'JobsController@jobsExport']); - Route::get('projects/{project}/job-progress-export/{type?}', ['as' => 'projects.job-progress-export', 'uses' => 'JobsController@jobProgressExport']); - Route::get('projects/{project}/jobs/create', ['as' => 'projects.jobs.create', 'uses' => 'JobsController@create']); - Route::post('projects/{project}/jobs', ['as' => 'projects.jobs.store', 'uses' => 'JobsController@store']); - Route::get('projects/{project}/jobs/add-from-other-project', ['as' => 'projects.jobs.add-from-other-project', 'uses' => 'JobsController@addFromOtherProject']); - Route::post('projects/{project}/jobs/store-from-other-project', ['as' => 'projects.jobs.store-from-other-project', 'uses' => 'JobsController@storeFromOtherProject']); - Route::get('projects/{project}/jobs', ['as' => 'projects.jobs.index', 'uses' => 'JobsController@index']); - Route::post('projects/{project}/jobs-reorder', ['as' => 'projects.jobs-reorder', 'uses' => 'ProjectsController@jobsReorder']); - - /* - * Project Comments Routes - */ - Route::get('projects/{project}/comments', 'CommentsController@index')->name('projects.comments.index'); - Route::post('projects/{project}/comments', 'CommentsController@store')->name('projects.comments.store'); - Route::patch('projects/{project}/comments/{comment}', 'CommentsController@update')->name('projects.comments.update'); - Route::delete('projects/{project}/comments/{comment}', 'CommentsController@destroy')->name('projects.comments.destroy'); - - /* - * Project Issues Routes - */ - Route::get('projects/{project}/issues', 'IssueController@index')->name('projects.issues.index'); - Route::get('projects/{project}/issues/create', 'IssueController@create')->name('projects.issues.create'); - Route::post('projects/{project}/issues', 'IssueController@store')->name('projects.issues.store'); - Route::get('projects/{project}/issues/{issue}', 'IssueController@show')->name('projects.issues.show'); - Route::get('projects/{project}/issues/{issue}/edit', 'IssueController@edit')->name('projects.issues.edit'); - Route::patch('projects/{project}/issues/{issue}', 'IssueController@update')->name('projects.issues.update'); - Route::delete('projects/{project}/issues/{issue}', 'IssueController@destroy')->name('projects.issues.destroy'); - - /* - * Tasks Routes - */ - Route::get('jobs/{job}/tasks/create', ['as' => 'tasks.create', 'uses' => 'TasksController@create']); - Route::post('jobs/{job}/tasks', ['as' => 'tasks.store', 'uses' => 'TasksController@store']); - Route::patch('tasks/{task}', ['as' => 'tasks.update', 'uses' => 'TasksController@update']); - Route::delete('tasks/{task}', ['as' => 'tasks.destroy', 'uses' => 'TasksController@destroy']); - Route::post('tasks/{task}/set-as-job', ['as' => 'tasks.set-as-job', 'uses' => 'TasksController@setAsJob']); - - /* - * Files Routes - */ - Route::get('projects/{project}/files', ['as' => 'projects.files', 'uses' => 'FilesController@index']); - Route::post('files/{fileable}', ['as' => 'files.upload', 'uses' => 'FilesController@create']); - Route::get('files/{file}', ['as' => 'files.download', 'uses' => 'FilesController@show']); - Route::patch('files/{file}', ['as' => 'files.update', 'uses' => 'FilesController@update']); - Route::delete('files/{file}', ['as' => 'files.destroy', 'uses' => 'FilesController@destroy']); + /* + * Projects Routes + */ + Route::get('projects/{project}/delete', ['as' => 'projects.delete', 'uses' => 'ProjectsController@delete']); + Route::get('projects/{project}/subscriptions', ['as' => 'projects.subscriptions', 'uses' => 'ProjectsController@subscriptions']); + Route::patch('projects/{project}/status-update', ['as' => 'projects.status-update', 'uses' => 'ProjectsController@statusUpdate']); + Route::resource('projects', 'ProjectsController'); + + /* + * Project Payments Routes + */ + Route::get('projects/{project}/payments', ['as' => 'projects.payments', 'uses' => 'ProjectsController@payments']); + + /* + * Project Fees Routes + */ + Route::get('projects/{project}/fees/create', ['as' => 'projects.fees.create', 'uses' => 'FeesController@create']); + Route::post('projects/{project}/fees/store', ['as' => 'projects.fees.store', 'uses' => 'FeesController@store']); + + /* + * Project Invoices Routes + */ + Route::get('projects/{project}/invoices', ['as' => 'projects.invoices', 'uses' => 'InvoicesController@index']); + + /* + * Project Jobs Routes + */ + Route::get('projects/{project}/jobs-export/{type?}', ['as' => 'projects.jobs-export', 'uses' => 'JobsController@jobsExport']); + Route::get('projects/{project}/job-progress-export/{type?}', ['as' => 'projects.job-progress-export', 'uses' => 'JobsController@jobProgressExport']); + Route::get('projects/{project}/jobs/create', ['as' => 'projects.jobs.create', 'uses' => 'JobsController@create']); + Route::post('projects/{project}/jobs', ['as' => 'projects.jobs.store', 'uses' => 'JobsController@store']); + Route::get('projects/{project}/jobs/add-from-other-project', ['as' => 'projects.jobs.add-from-other-project', 'uses' => 'JobsController@addFromOtherProject']); + Route::post('projects/{project}/jobs/store-from-other-project', ['as' => 'projects.jobs.store-from-other-project', 'uses' => 'JobsController@storeFromOtherProject']); + Route::get('projects/{project}/jobs', ['as' => 'projects.jobs.index', 'uses' => 'JobsController@index']); + Route::post('projects/{project}/jobs-reorder', ['as' => 'projects.jobs-reorder', 'uses' => 'ProjectsController@jobsReorder']); + + /* + * Project Comments Routes + */ + Route::get('projects/{project}/comments', 'CommentsController@index')->name('projects.comments.index'); + Route::post('projects/{project}/comments', 'CommentsController@store')->name('projects.comments.store'); + Route::patch('projects/{project}/comments/{comment}', 'CommentsController@update')->name('projects.comments.update'); + Route::delete('projects/{project}/comments/{comment}', 'CommentsController@destroy')->name('projects.comments.destroy'); + + /* + * Project Issues Routes + */ + Route::get('projects/{project}/issues', 'IssueController@index')->name('projects.issues.index'); + Route::get('projects/{project}/issues/create', 'IssueController@create')->name('projects.issues.create'); + Route::post('projects/{project}/issues', 'IssueController@store')->name('projects.issues.store'); + Route::get('projects/{project}/issues/{issue}', 'IssueController@show')->name('projects.issues.show'); + Route::get('projects/{project}/issues/{issue}/edit', 'IssueController@edit')->name('projects.issues.edit'); + Route::patch('projects/{project}/issues/{issue}', 'IssueController@update')->name('projects.issues.update'); + Route::delete('projects/{project}/issues/{issue}', 'IssueController@destroy')->name('projects.issues.destroy'); + + /* + * Tasks Routes + */ + Route::get('jobs/{job}/tasks/create', ['as' => 'tasks.create', 'uses' => 'TasksController@create']); + Route::post('jobs/{job}/tasks', ['as' => 'tasks.store', 'uses' => 'TasksController@store']); + Route::patch('tasks/{task}', ['as' => 'tasks.update', 'uses' => 'TasksController@update']); + Route::delete('tasks/{task}', ['as' => 'tasks.destroy', 'uses' => 'TasksController@destroy']); + Route::post('tasks/{task}/set-as-job', ['as' => 'tasks.set-as-job', 'uses' => 'TasksController@setAsJob']); + + /* + * Files Routes + */ + Route::get('projects/{project}/files', ['as' => 'projects.files', 'uses' => 'FilesController@index']); + Route::post('files/{fileable}', ['as' => 'files.upload', 'uses' => 'FilesController@create']); + Route::get('files/{file}', ['as' => 'files.download', 'uses' => 'FilesController@show']); + Route::patch('files/{file}', ['as' => 'files.update', 'uses' => 'FilesController@update']); + Route::delete('files/{file}', ['as' => 'files.destroy', 'uses' => 'FilesController@destroy']); + + /** + * Job file attachments + */ + Route::get('files/show/{job}/{media_id}', 'FilesController@showAttachment')->name('show-job-file'); }); Route::group(['middleware' => ['auth']], function () { - /* - * Jobs Routes - */ - Route::get('jobs', ['as' => 'jobs.index', 'uses' => 'JobsController@index']); - Route::get('jobs/{job}', ['as' => 'jobs.show', 'uses' => 'JobsController@show']); - - /* - * Job Actions Routes - */ - Route::get('jobs/{job}/edit', ['as' => 'jobs.edit', 'uses' => 'JobsController@edit']); - Route::patch('jobs/{job}', ['as' => 'jobs.update', 'uses' => 'JobsController@update']); - Route::get('jobs/{job}/delete', ['as' => 'jobs.delete', 'uses' => 'JobsController@delete']); - Route::delete('jobs/{job}', ['as' => 'jobs.destroy', 'uses' => 'JobsController@destroy']); - Route::post('jobs/{id}/tasks-reorder', ['as' => 'jobs.tasks-reorder', 'uses' => 'JobsController@tasksReorder']); - - /* - * Project Comments Routes - */ - Route::get('jobs/{job}/comments', 'Jobs\CommentsController@index')->name('jobs.comments.index'); - Route::post('jobs/{job}/comments', 'Jobs\CommentsController@store')->name('jobs.comments.store'); - Route::patch('jobs/{job}/comments/{comment}', 'Jobs\CommentsController@update')->name('jobs.comments.update'); - Route::delete('jobs/{job}/comments/{comment}', 'Jobs\CommentsController@destroy')->name('jobs.comments.destroy'); + /* + * Jobs Routes + */ + Route::get('jobs', ['as' => 'jobs.index', 'uses' => 'JobsController@index']); + Route::get('jobs/{job}', ['as' => 'jobs.show', 'uses' => 'JobsController@show']); + + /* + * Job Actions Routes + */ + Route::get('jobs/{job}/edit', ['as' => 'jobs.edit', 'uses' => 'JobsController@edit']); + Route::patch('jobs/{job}', ['as' => 'jobs.update', 'uses' => 'JobsController@update']); + Route::get('jobs/{job}/delete', ['as' => 'jobs.delete', 'uses' => 'JobsController@delete']); + Route::delete('jobs/{job}', ['as' => 'jobs.destroy', 'uses' => 'JobsController@destroy']); + Route::post('jobs/{id}/tasks-reorder', ['as' => 'jobs.tasks-reorder', 'uses' => 'JobsController@tasksReorder']); + + /* + * Project Comments Routes + */ + Route::get('jobs/{job}/comments', 'Jobs\CommentsController@index')->name('jobs.comments.index'); + Route::post('jobs/{job}/comments', 'Jobs\CommentsController@store')->name('jobs.comments.store'); + Route::patch('jobs/{job}/comments/{comment}', 'Jobs\CommentsController@update')->name('jobs.comments.update'); + Route::delete('jobs/{job}/comments/{comment}', 'Jobs\CommentsController@destroy')->name('jobs.comments.destroy'); }); /*