diff --git a/app/Http/Controllers/Staff/AttachmentController.php b/app/Http/Controllers/Staff/AttachmentController.php new file mode 100644 index 0000000..6352b36 --- /dev/null +++ b/app/Http/Controllers/Staff/AttachmentController.php @@ -0,0 +1,43 @@ +user()->may(Permission::AttachmentUpload), 403); + $this->decodeKeys(); + $study = Study::findOrFail($this->key); + + $request->validate([ + 'file.*' => 'required|mimes:pdf,jpg,png|max:2048', + ]); + + foreach ($request->file('file') as $file) { + $study->addMedia($file)->toMediaCollection('uploads'); + } + + return response()->json(['success' => 'Files uploaded successfully']); + } + + public function delete($mediaId) + { + abort_unless(auth()->user()->may(Permission::AttachmentUpload), 403); + $this->decodeKeys(); + $study = Study::findOrFail($this->key); + $media = $study->getMedia('uploads')->where('id', (int) $mediaId)->first(); + if ($media) { + $media->delete(); + + return redirect()->back()->with('success', 'File deleted successfully'); + } + + return redirect()->back()->with('error', 'File not found'); + } +} diff --git a/app/Http/Controllers/Staff/StudyHistoryController.php b/app/Http/Controllers/Staff/StudyHistoryController.php index 989958c..a072a84 100644 --- a/app/Http/Controllers/Staff/StudyHistoryController.php +++ b/app/Http/Controllers/Staff/StudyHistoryController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\HashidControllerBase; use App\Http\Requests\StudyHistoryRequest; use App\Models\Enums\Permission; +use App\Models\Study; use App\Models\StudyDetails; class StudyHistoryController extends HashidControllerBase @@ -14,8 +15,9 @@ public function view() abort_unless(auth()->user()->may(Permission::StudyHistoryView), 403); $this->decodeKeys(); $details = StudyDetails::historyOnly($this->key); + $study = Study::findOrFail($this->key); - return view('staff.history.view', compact('details')); + return view('staff.history.view', compact('details', 'study')); } public function edit() @@ -23,8 +25,9 @@ public function edit() abort_unless(auth()->user()->may(Permission::StudyHistoryEdit), 403); $this->decodeKeys(); $details = StudyDetails::historyOnly($this->key); + $study = Study::findOrFail($this->key); - return view('staff.history.edit', compact('details')); + return view('staff.history.edit', compact('details', 'study')); } public function save(StudyHistoryRequest $request) diff --git a/app/Models/Study.php b/app/Models/Study.php index af42566..9fa3f76 100644 --- a/app/Models/Study.php +++ b/app/Models/Study.php @@ -13,10 +13,13 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Support\Str; +use Spatie\MediaLibrary\HasMedia; +use Spatie\MediaLibrary\InteractsWithMedia; -class Study extends BaseModel +class Study extends BaseModel implements HasMedia { use HashableId; + use InteractsWithMedia; public function details(): HasOne { diff --git a/composer.json b/composer.json index e5f43e3..1e42b0f 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "rap2hpoutre/fast-excel": "^5.5", "rawilk/laravel-settings": "^3.4", "sentry/sentry-laravel": "^4.10", + "spatie/laravel-medialibrary": "^11.11", "spatie/laravel-permission": "^6.10", "vinkla/hashids": "^12.0", "yajra/laravel-datatables": "^11.0" diff --git a/config/media-library.php b/config/media-library.php new file mode 100644 index 0000000..f7d6caa --- /dev/null +++ b/config/media-library.php @@ -0,0 +1,280 @@ + env('MEDIA_DISK', '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, // 10MB + + /* + * This queue connection will be used to generate derived and responsive images. + * Leave empty to use the default queue connection. + */ + 'queue_connection_name' => env('QUEUE_CONNECTION', 'sync'), + + /* + * This queue will be used to generate derived and responsive images. + * Leave empty to use the default queue. + */ + 'queue_name' => env('MEDIA_QUEUE', ''), + + /* + * By default all conversions will be performed on a queue. + */ + 'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true), + + /* + * Should database transactions be run after database commits? + */ + 'queue_conversions_after_database_commit' => env('QUEUE_CONVERSIONS_AFTER_DB_COMMIT', true), + + /* + * The fully qualified class name of the media model. + */ + 'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class, + + /* + * When enabled, media collections will be serialised using the default + * laravel model serialization behaviour. + * + * Keep this option disabled if using Media Library Pro components (https://medialibrary.pro) + */ + 'use_default_collection_serialization' => false, + + /* + * The fully qualified class name of the model used for temporary uploads. + * + * This model is only used in Media Library Pro (https://medialibrary.pro) + */ + 'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class, + + /* + * When enabled, Media Library Pro will only process temporary uploads that were uploaded + * in the same session. You can opt to disable this for stateless usage of + * the pro components. + */ + 'enable_temporary_uploads_session_affinity' => true, + + /* + * When enabled, Media Library pro will generate thumbnails for uploaded file. + */ + 'generate_thumbnails_for_temporary_uploads' => true, + + /* + * This is the class that is responsible for naming generated files. + */ + 'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class, + + /* + * The class that contains the strategy for determining a media file's path. + */ + 'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class, + + /* + * The class that contains the strategy for determining how to remove files. + */ + 'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class, + + /* + * Here you can specify which path generator should be used for the given class. + */ + 'custom_path_generators' => [ + // Model::class => PathGenerator::class + // or + // 'model_morph_alias' => PathGenerator::class + ], + + /* + * When urls to files get generated, this class will be called. Use the default + * if your files are stored locally above the site root or on s3. + */ + 'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class, + + /* + * Moves media on updating to keep path consistent. Enable it only with a custom + * PathGenerator that uses, for example, the media UUID. + */ + 'moves_media_on_update' => false, + + /* + * Whether to activate versioning when urls to files get generated. + * When activated, this attaches a ?v=xx query string to the URL. + */ + 'version_urls' => false, + + /* + * The media library 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 => [ + '-m85', // set maximum quality to 85% + '--force', // ensure that progressive generation is always done also if a little bigger + '--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 + ], + Spatie\ImageOptimizer\Optimizers\Cwebp::class => [ + '-m 6', // for the slowest compression method in order to get the best compression. + '-pass 10', // for maximizing the amount of analysis pass. + '-mt', // multithreading for some speed improvements. + '-q 90', // quality factor that brings the least noticeable changes. + ], + Spatie\ImageOptimizer\Optimizers\Avifenc::class => [ + '-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63). + '-j all', // number of jobs (worker threads, "all" uses all available cores). + '--min 0', // min quantizer for color (0-63). + '--max 63', // max quantizer for color (0-63). + '--minalpha 0', // min quantizer for alpha (0-63). + '--maxalpha 63', // max quantizer for alpha (0-63). + '-a end-usage=q', // rate control mode set to Constant Quality mode. + '-a tune=ssim', // SSIM as tune the encoder for distortion metric. + ], + ], + + /* + * These generators will be used to create an image of media files. + */ + 'image_generators' => [ + Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Avif::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class, + ], + + /* + * The path where to store temporary files while performing image conversions. + * If set to null, storage_path('media-library/temp') will be used. + */ + 'temporary_directory_path' => null, + + /* + * The engine that should perform the image conversions. + * Should be either `gd` or `imagick`. + */ + 'image_driver' => env('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'), + + /* + * 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\Conversions\Jobs\PerformConversionsJob::class, + 'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class, + ], + + /* + * When using the addMediaFromUrl method you may want to replace the default downloader. + * This is particularly useful when the url of the image is behind a firewall and + * need to add additional flags, possibly using curl. + */ + 'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class, + + /* + * When using the addMediaFromUrl method the SSL is verified by default. + * This is option disables SSL verification when downloading remote media. + * Please note that this is a security risk and should only be false in a local environment. + */ + 'media_downloader_ssl' => env('MEDIA_DOWNLOADER_SSL', true), + + '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 30% + * smaller than the previous one. More info in the documentation. + * + * https://docs.spatie.be/laravel-medialibrary/v9/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. + * When disabled, no tiny placeholder is generated. + */ + 'use_tiny_placeholders' => true, + + /* + * This class will generate the tiny placeholder used for progressive image loading. By default + * the media library will use a tiny blurred jpg image. + */ + 'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class, + ], + + /* + * When enabling this option, a route will be registered that will enable + * the Media Library Pro Vue and React components to move uploaded files + * in a S3 bucket to their right place. + */ + 'enable_vapor_uploads' => env('ENABLE_MEDIA_LIBRARY_VAPOR_UPLOADS', false), + + /* + * When converting Media instances to response the media library will add + * a `loading` attribute to the `img` tag. Here you can set the default + * value of that attribute. + * + * Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction. + * + * More info: https://css-tricks.com/native-lazy-loading/ + */ + 'default_loading_attribute_value' => null, + + /* + * You can specify a prefix for that is used for storing all media. + * If you set this to `/my-subdir`, all your media will be stored in a `/my-subdir` directory. + */ + 'prefix' => env('MEDIA_PREFIX', '/attachments'), + + /* + * When forcing lazy loading, media will be loaded even if you don't eager load media and you have + * disabled lazy loading globally in the service provider. + */ + 'force_lazy_loading' => env('FORCE_MEDIA_LIBRARY_LAZY_LOADING', true), +]; diff --git a/resources/views/staff/history/edit.blade.php b/resources/views/staff/history/edit.blade.php index 6bb4fef..0594b40 100644 --- a/resources/views/staff/history/edit.blade.php +++ b/resources/views/staff/history/edit.blade.php @@ -33,6 +33,8 @@ + + @endsection @section('page-script') @@ -49,6 +51,20 @@ autogrow: true, resetCss: true }); + + Dropzone.options.fileDropzone = { + paramName: 'file', + maxFilesize: 2, // MB + acceptedFiles: '.pdf,.jpg,.png', + uploadMultiple: true, + parallelUploads: 3, + headers: { + 'X-CSRF-TOKEN': "{{ csrf_token() }}" + }, + success: function (file, response) { + console.log(response.success); + } + }; @endsection @@ -93,6 +109,35 @@ +