├── _ide_helper.php ├── composer.json └── src ├── Config └── laravel-media-uploader.php ├── Console └── TemporaryClearCommand.php ├── Database └── Migrations │ └── 2020_06_03_131044_create_temporary_files_table.php ├── Entities ├── Concerns │ └── HasUploader.php └── TemporaryFile.php ├── Http ├── Controllers │ └── MediaController.php └── Requests │ └── MediaRequest.php ├── Listeners └── ProcessUploadedMedia.php ├── Providers ├── EventServiceProvider.php ├── RouteServiceProvider.php └── UploaderServiceProvider.php ├── Resources └── lang │ ├── ar │ └── validation.php │ └── en │ └── validation.php ├── Routes └── api.php ├── Rules └── MediaRule.php ├── Support ├── FFmpegDriver.php └── Uploader.php └── Transformers └── MediaResource.php /_ide_helper.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /** 11 | * @method static \AhmedAliraqi\LaravelMediaUploader\Forms\Components\ImageComponent image($name = null) 12 | * @method static \AhmedAliraqi\LaravelMediaUploader\Forms\Components\AudioComponent audio($name = null) 13 | * @method static \AhmedAliraqi\LaravelMediaUploader\Forms\Components\VideoComponent video($name = null) 14 | * @method static \AhmedAliraqi\LaravelMediaUploader\Forms\Components\MediaComponent media($name = null) 15 | */ 16 | class BsForm extends \Laraeast\LaravelBootstrapForms\Facades\BsForm {} -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ahmed-aliraqi/laravel-media-uploader", 3 | "description": "This package used to upload files using laravel-media-library", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Ahmed Fathy", 9 | "email": "aliraqi.dev@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.4|^8.0", 14 | "laravel/framework": "~5.7|~5.8|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", 15 | "spatie/laravel-medialibrary": "^9.0|^10.0|^11.0", 16 | "php-ffmpeg/php-ffmpeg": "^1.0" 17 | }, 18 | "require-dev": { 19 | "orchestra/testbench": "~3.0|~5.3|~6.0|~7.0", 20 | "mockery/mockery": "^1.4" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "AhmedAliraqi\\LaravelMediaUploader\\": "src/" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "AhmedAliraqi\\LaravelMediaUploader\\Tests\\": "tests/" 30 | } 31 | }, 32 | "extra": { 33 | "laravel": { 34 | "providers": [ 35 | "AhmedAliraqi\\LaravelMediaUploader\\Providers\\UploaderServiceProvider" 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Config/laravel-media-uploader.php: -------------------------------------------------------------------------------- 1 | true, 8 | 9 | 'documents_mime_types' => [ 10 | 'application/msword', 11 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .doc & .docx 12 | 'application/vnd.ms-powerpoint', 13 | 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .ppt & .pptx 14 | 'application/vnd.ms-excel', 15 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xls & .xlsx 16 | 'text/plain', 17 | 'application/pdf', 18 | 'application/zip', 19 | 'application/x-rar', 20 | 'application/x-rar-compressed', 21 | 'application/octet-stream', 22 | ], 23 | ]; 24 | -------------------------------------------------------------------------------- /src/Console/TemporaryClearCommand.php: -------------------------------------------------------------------------------- 1 | subHours(6)) 42 | ->each(function (TemporaryFile $file) { 43 | $file->delete(); 44 | }); 45 | 46 | $this->info( 47 | "\nThe temporary files has been cleaned successfully. " 48 | .now()->toDateTimeString() 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Database/Migrations/2020_06_03_131044_create_temporary_files_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('token'); 19 | $table->string('collection')->default('default'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('temporary_files'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Entities/Concerns/HasUploader.php: -------------------------------------------------------------------------------- 1 | where('collection', $collection); 32 | } 33 | 34 | $mediaIds = []; 35 | 36 | $query->whereIn('token', $tokens) 37 | ->each(function (TemporaryFile $file) use (&$mediaIds) { 38 | foreach ($file->getMedia($file->collection) as $media) { 39 | $media->forceFill([ 40 | 'model_type' => $this->getMorphClass(), 41 | 'model_id' => $this->getKey(), 42 | ])->save(); 43 | $mediaIds[] = $media->id; 44 | } 45 | 46 | $file->delete(); 47 | }); 48 | 49 | if (count($mediaIds) > 0 && Config::get('laravel-media-uploader.regenerate-after-assigning')) { 50 | Artisan::call('media-library:regenerate', [ 51 | '--ids' => implode(',', $mediaIds), 52 | '--force' => true, 53 | ]); 54 | } 55 | 56 | $collection = $collection ?: 'default'; 57 | 58 | if ($collectionSizeLimit = optional($this->getMediaCollection($collection))->collectionSizeLimit) { 59 | $collectionMedia = $this->refresh()->getMedia($collection); 60 | 61 | if ($collectionMedia->count() > $collectionSizeLimit) { 62 | $this->clearMediaCollectionExcept( 63 | $collection, 64 | $collectionMedia 65 | ->reverse() 66 | ->take($collectionSizeLimit) 67 | ); 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Get all the model media of the given collection using "MediaResource". 74 | * 75 | * @param string $collection 76 | * @return \Illuminate\Support\Collection 77 | */ 78 | public function getMediaResource($collection = 'default') 79 | { 80 | return collect( 81 | MediaResource::collection( 82 | $this->getMedia($collection) 83 | )->jsonSerialize() 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Entities/TemporaryFile.php: -------------------------------------------------------------------------------- 1 | addMediaConversion('thumb') 34 | ->width(70) 35 | ->format('png'); 36 | 37 | $this->addMediaConversion('small') 38 | ->width(120) 39 | ->format('png'); 40 | 41 | $this->addMediaConversion('medium') 42 | ->width(160) 43 | ->format('png'); 44 | 45 | $this->addMediaConversion('large') 46 | ->width(320) 47 | ->format('png'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Http/Controllers/MediaController.php: -------------------------------------------------------------------------------- 1 | whereIn('token', $tokens); 35 | $builder->when(request('collection'), function (Builder $builder) { 36 | $builder->where(request()->only('collection')); 37 | }); 38 | } 39 | )->get(); 40 | 41 | return MediaResource::collection($media); 42 | } 43 | 44 | /** 45 | * Store a newly created resource in storage. 46 | * 47 | * @param \AhmedAliraqi\LaravelMediaUploader\Http\Requests\MediaRequest $request 48 | * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection 49 | * 50 | * @throws \Spatie\MediaLibrary\MediaCollections\Exceptions\FileDoesNotExist 51 | * @throws \Spatie\MediaLibrary\MediaCollections\Exceptions\FileIsTooBig 52 | */ 53 | public function store(MediaRequest $request) 54 | { 55 | /** @var \AhmedAliraqi\LaravelMediaUploader\Entities\TemporaryFile $temporaryFile */ 56 | $temporaryFile = TemporaryFile::create([ 57 | 'token' => Str::random(60), 58 | 'collection' => $request->input('collection', 'default'), 59 | ]); 60 | 61 | if (is_string($request->file) && base64_decode(base64_encode($request->file)) === $request->file) { 62 | $temporaryFile->addMediaFromBase64($request->file) 63 | ->usingFileName(time().'.png') 64 | ->toMediaCollection($temporaryFile->collection); 65 | } 66 | 67 | if ($request->hasFile('file')) { 68 | $temporaryFile->addMedia($request->file) 69 | ->usingFileName(Uploader::formatName($request->file)) 70 | ->toMediaCollection($temporaryFile->collection); 71 | } 72 | 73 | foreach ($request->file('files', []) as $file) { 74 | $temporaryFile->addMedia($file) 75 | ->usingFileName(Uploader::formatName($file)) 76 | ->toMediaCollection($temporaryFile->collection); 77 | } 78 | 79 | return MediaResource::collection( 80 | $temporaryFile->getMedia( 81 | $temporaryFile->collection ?: 'default' 82 | ) 83 | )->additional([ 84 | 'token' => $temporaryFile->token, 85 | ]); 86 | } 87 | 88 | /** 89 | * @param $media 90 | * @return \Illuminate\Http\JsonResponse 91 | */ 92 | public function destroy($media) 93 | { 94 | $modelClass = Config::get( 95 | 'media-library.media_model', 96 | \Spatie\MediaLibrary\MediaCollections\Models\Media::class 97 | ); 98 | 99 | $media = $modelClass::findOrFail($media); 100 | 101 | $media->delete(); 102 | 103 | return response()->json([ 104 | 'message' => 'deleted', 105 | ]); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Http/Requests/MediaRequest.php: -------------------------------------------------------------------------------- 1 | ['sometimes', 'required', new MediaRule('image', 'video', 'audio', 'document')], 19 | 'files' => ['sometimes', 'required', 'array'], 20 | 'files.*' => ['sometimes', 'required', new MediaRule('image', 'video', 'audio', 'document')], 21 | 'collection' => ['nullable', 'string'], 22 | ]; 23 | } 24 | 25 | /** 26 | * Determine if the user is authorized to make this request. 27 | * 28 | * @return bool 29 | */ 30 | public function authorize() 31 | { 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Listeners/ProcessUploadedMedia.php: -------------------------------------------------------------------------------- 1 | runningUnitTests()) { 30 | return; 31 | } 32 | 33 | if ($event->media->getCustomProperty('status') == 'processed') { 34 | // Skipped Processing Media File 35 | return; 36 | } 37 | 38 | try { 39 | if ($this->isImage($event->media)) { 40 | $path = $this->processImage($event->media); 41 | } elseif ($this->isDocument($event->media)) { 42 | $path = $this->processDocument($event->media); 43 | } elseif ($this->isVideo($event->media)) { 44 | $path = $this->processVideo($event->media); 45 | } elseif ($this->isAudio($event->media)) { 46 | $path = $this->processAudio($event->media); 47 | } else { 48 | $path = null; 49 | } 50 | $this->processingDone($event->media, $path); 51 | } catch (RuntimeException $e) { 52 | $this->processingFailed($event->media); 53 | } 54 | 55 | $event->media->setCustomProperty('status', 'processing')->save(); 56 | } 57 | 58 | /** 59 | * Determine if the media file is an image. 60 | * 61 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 62 | * @return bool 63 | */ 64 | protected function isImage(Media $media) 65 | { 66 | return (new ImageGenerator())->canHandleMime($media->mime_type); 67 | } 68 | 69 | /** 70 | * Determine if the media file is a document. 71 | * 72 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 73 | * @return bool 74 | */ 75 | protected function isDocument(Media $media) 76 | { 77 | return in_array( 78 | $media->mime_type, 79 | Config::get('laravel-media-uploader.documents_mime_types') 80 | ); 81 | } 82 | 83 | /** 84 | * Determine if the media file is a video and initiate the required driver. 85 | * 86 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 87 | * @return bool 88 | */ 89 | protected function isVideo(Media $media) 90 | { 91 | return app('ffmpeg-driver')->open($media->getPath()) instanceof Video; 92 | } 93 | 94 | /** 95 | * Determine if the media file is an audio and the initiate required driver. 96 | * 97 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 98 | * @return bool 99 | */ 100 | protected function isAudio(Media $media) 101 | { 102 | return app('ffmpeg-driver')->open($media->getPath()) instanceof Audio; 103 | } 104 | 105 | /** 106 | * Process Image File. 107 | * 108 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 109 | * @return null 110 | */ 111 | protected function processImage(Media $media) 112 | { 113 | $image = Image::make($media->getPath())->orientate(); 114 | 115 | $media 116 | ->setCustomProperty('type', 'image') 117 | ->setCustomProperty('width', $image->width()) 118 | ->setCustomProperty('height', $image->height()) 119 | ->setCustomProperty('ratio', (string) round($image->width() / $image->height(), 3)) 120 | ->save(); 121 | } 122 | 123 | /** 124 | * Process Document File. 125 | * 126 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 127 | * @return null 128 | */ 129 | protected function processDocument(Media $media) 130 | { 131 | $media->setCustomProperty('type', 'document')->save(); 132 | } 133 | 134 | /** 135 | * Process Video File. 136 | * 137 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 138 | * @return string 139 | */ 140 | protected function processVideo(Media $media) 141 | { 142 | $media->setCustomProperty('type', 'video')->save(); 143 | 144 | $video = app('ffmpeg-driver')->open($media->getPath()); 145 | 146 | $format = new X264(); 147 | 148 | $format->on('progress', $this->increaseProcessProgress($media)); 149 | 150 | $format->setAudioCodec('aac'); 151 | 152 | $format->setAdditionalParameters(['-vf', 'pad=ceil(iw/2)*2:ceil(ih/2)*2']); 153 | 154 | $video->save($format, $processedFile = $this->generatePathForProcessedFile($media, 'mp4')); 155 | 156 | return $processedFile; 157 | } 158 | 159 | /** 160 | * Process Audio File. 161 | * 162 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 163 | * @return string 164 | */ 165 | protected function processAudio(Media $media) 166 | { 167 | $media->setCustomProperty('type', 'audio')->save(); 168 | 169 | $audio = app('ffmpeg-driver')->open($media->getPath()); 170 | 171 | $format = new Mp3(); 172 | 173 | $format->on('progress', $this->increaseProcessProgress($media)); 174 | 175 | $audio->save($format, $processedFile = $this->generatePathForProcessedFile($media, 'mp3')); 176 | 177 | return $processedFile; 178 | } 179 | 180 | /** 181 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 182 | * @return \Closure 183 | */ 184 | protected function increaseProcessProgress(Media $media): \Closure 185 | { 186 | return function ( 187 | $file, 188 | $format, 189 | $percentage 190 | ) use ($media) { 191 | // Progress Percentage is $percentage 192 | $media->setCustomProperty('progress', $percentage); 193 | $media->save(); 194 | }; 195 | } 196 | 197 | /** 198 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 199 | * @param null $processedFilePath 200 | * @return void 201 | * 202 | * @throws \Exception 203 | */ 204 | protected function processingDone(Media $media, $processedFilePath = null) 205 | { 206 | // If the processing does not ended with generating a new file. 207 | if (is_null($processedFilePath)) { 208 | $media->setCustomProperty('status', 'processed') 209 | ->setCustomProperty('progress', 100) 210 | ->save(); 211 | } else { 212 | // New Converted Media Will Be Added 213 | $duration = app('ffmpeg-driver') 214 | ->getFFProbe() 215 | ->format($processedFilePath) 216 | ->get('duration'); 217 | 218 | $media->model 219 | ->addMedia($processedFilePath) 220 | ->withCustomProperties([ 221 | 'type' => $media->getCustomProperty('type'), 222 | 'status' => 'processed', 223 | 'progress' => 100, 224 | 'duration' => $duration, 225 | ]) 226 | ->preservingOriginal() 227 | ->toMediaCollection($media->collection_name); 228 | 229 | (clone $media)->delete(); 230 | } 231 | } 232 | 233 | /** 234 | * Mark media status as failed. 235 | * 236 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 237 | */ 238 | protected function processingFailed(Media $media) 239 | { 240 | $media->setCustomProperty('status', 'failed')->save(); 241 | } 242 | 243 | /** 244 | * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media 245 | * @param null $extension 246 | * @return string 247 | */ 248 | protected function generatePathForProcessedFile(Media $media, $extension = null) 249 | { 250 | $path = $media->getPath(); 251 | 252 | return pathinfo($path, PATHINFO_DIRNAME) 253 | .DIRECTORY_SEPARATOR.pathinfo($path, PATHINFO_FILENAME) 254 | .'.processed.'.$extension; 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 18 | ProcessUploadedMedia::class, 19 | ], 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /src/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 37 | } 38 | 39 | /** 40 | * Define the "api" routes for the application. 41 | * 42 | * These routes are typically stateless. 43 | * 44 | * @return void 45 | */ 46 | protected function mapApiRoutes() 47 | { 48 | Route::prefix('api') 49 | ->middleware('api') 50 | ->namespace($this->namespace) 51 | ->group(__DIR__.'/../Routes/api.php'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Providers/UploaderServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerConfig(); 24 | 25 | $this->registerTranslations(); 26 | 27 | $this->publishes([ 28 | __DIR__.'/../Database/Migrations' => database_path('/migrations'), 29 | ], 'migrations'); 30 | 31 | $this->commands([ 32 | TemporaryClearCommand::class, 33 | ]); 34 | 35 | if (! $this->app->runningUnitTests()) { 36 | $this->app->booted(function () { 37 | $schedule = $this->app->make(Schedule::class); 38 | $schedule->command('temporary:clean')->everySixHours(); 39 | }); 40 | } 41 | } 42 | 43 | /** 44 | * Register the service provider. 45 | * 46 | * @return void 47 | */ 48 | public function register() 49 | { 50 | $this->app->register(RouteServiceProvider::class); 51 | $this->app->register(EventServiceProvider::class); 52 | 53 | $this->app->singleton('ffmpeg-driver', function () { 54 | return (new FFmpegDriver())->driver(); 55 | }); 56 | } 57 | 58 | /** 59 | * Register config. 60 | * 61 | * @return void 62 | */ 63 | protected function registerConfig() 64 | { 65 | $this->publishes([ 66 | __DIR__.'/../Config/laravel-media-uploader.php' => config_path('laravel-media-uploader.php'), 67 | ], 'config'); 68 | $this->mergeConfigFrom( 69 | __DIR__.'/../Config/laravel-media-uploader.php', 70 | 'laravel-media-uploader' 71 | ); 72 | } 73 | 74 | /** 75 | * Register translations. 76 | * 77 | * @return void 78 | */ 79 | public function registerTranslations() 80 | { 81 | $this->publishes([ 82 | __DIR__.'/../Resources/lang' => resource_path('lang/vendor/uploader'), 83 | ], 'uploader:translations'); 84 | 85 | $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'uploader'); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Resources/lang/ar/validation.php: -------------------------------------------------------------------------------- 1 | 'قيمة حقل :attribute غير مدعومة', 5 | ]; 6 | -------------------------------------------------------------------------------- /src/Resources/lang/en/validation.php: -------------------------------------------------------------------------------- 1 | 'The :attribute is not supported.', 5 | ]; 6 | -------------------------------------------------------------------------------- /src/Routes/api.php: -------------------------------------------------------------------------------- 1 | name('uploader.media.index'); 4 | Route::post('uploader/media/upload', 'MediaController@store')->name('uploader.media.store'); 5 | Route::delete('uploader/media/{media}', 'MediaController@destroy')->name('uploader.media.destroy'); 6 | -------------------------------------------------------------------------------- /src/Rules/MediaRule.php: -------------------------------------------------------------------------------- 1 | types = $types; 31 | } 32 | 33 | /** 34 | * Determine if the validation rule passes. 35 | * 36 | * @param string $attribute 37 | * @param UploadedFile|mixed $value 38 | * @return bool 39 | */ 40 | public function passes($attribute, $value) 41 | { 42 | if (! $value instanceof UploadedFile && ! $this->isBase64($value)) { 43 | return false; 44 | } 45 | 46 | try { 47 | $type = $this->getTypeString($value); 48 | } catch (RuntimeException $e) { 49 | return false; 50 | } 51 | 52 | return in_array($type, $this->types); 53 | } 54 | 55 | /** 56 | * Get the validation error message. 57 | * 58 | * @return string 59 | */ 60 | public function message() 61 | { 62 | return trans('uploader::validation.invalid'); 63 | } 64 | 65 | /** 66 | * @param UploadedFile|mixed $value 67 | * @return string 68 | */ 69 | protected function getTypeString($value): string 70 | { 71 | if ($this->isBase64($value)) { 72 | return 'image'; 73 | } 74 | 75 | $fileFullPath = $value->getRealPath(); 76 | 77 | if ((new Image())->canHandleMime($value->getMimeType())) { 78 | $type = 'image'; 79 | } elseif (in_array($value->getMimeType(), $this->documentsMimeTypes())) { 80 | $type = 'document'; 81 | } else { 82 | $type = strtolower(class_basename(get_class( 83 | app('ffmpeg-driver')->open($fileFullPath) 84 | ))); 85 | } 86 | 87 | return $type; // either: image, video or audio. 88 | } 89 | 90 | /** 91 | * The supported mime types for document files. 92 | * 93 | * @return string[] 94 | */ 95 | protected function documentsMimeTypes() 96 | { 97 | return Config::get('laravel-media-uploader.documents_mime_types'); 98 | } 99 | 100 | /** 101 | * Determine whither the value is base64 image. 102 | * 103 | * @param $value 104 | * @return bool 105 | */ 106 | protected function isBase64($value) 107 | { 108 | return is_string($value) && base64_decode(base64_encode($value)) === $value; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Support/FFmpegDriver.php: -------------------------------------------------------------------------------- 1 | driver = FFMpeg::create([ 21 | 'ffmpeg.binaries' => Config::get('media-library.ffmpeg_path'), 22 | 'ffprobe.binaries' => Config::get('media-library.ffprobe_path'), 23 | 'timeout' => 3600, 24 | 'ffmpeg.threads' => 12, 25 | ]); 26 | } 27 | 28 | /** 29 | * @return \FFMpeg\FFMpeg 30 | */ 31 | public function driver() 32 | { 33 | return $this->driver; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Support/Uploader.php: -------------------------------------------------------------------------------- 1 | getClientOriginalExtension(); 19 | 20 | $name = trim($file->getClientOriginalName(), $extension); 21 | 22 | $name = self::replaceNumbers($name); 23 | 24 | return Str::slug($name).$extension; 25 | } 26 | 27 | /** 28 | * Convert arabic & persian decimal to valid decimal. 29 | * 30 | * @param string $string 31 | * @return string 32 | */ 33 | public static function replaceNumbers(string $string): string 34 | { 35 | $newNumbers = range(0, 9); 36 | 37 | // 1. Persian HTML decimal 38 | $persianDecimal = [ 39 | '۰', 40 | '۱', 41 | '۲', 42 | '۳', 43 | '۴', 44 | '۵', 45 | '۶', 46 | '۷', 47 | '۸', 48 | '۹', 49 | ]; 50 | // 2. Arabic HTML decimal 51 | $arabicDecimal = [ 52 | '٠', 53 | '١', 54 | '٢', 55 | '٣', 56 | '٤', 57 | '٥', 58 | '٦', 59 | '٧', 60 | '٨', 61 | '٩', 62 | ]; 63 | // 3. Arabic Numeric 64 | $arabic = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; 65 | // 4. Persian Numeric 66 | $persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; 67 | 68 | $string = str_replace($persianDecimal, $newNumbers, $string); 69 | $string = str_replace($arabicDecimal, $newNumbers, $string); 70 | $string = str_replace($arabic, $newNumbers, $string); 71 | 72 | return str_replace($persian, $newNumbers, $string); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Transformers/MediaResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 21 | 'url' => $this->getFullUrl(), 22 | 'preview' => $this->getPreviewUrl(), 23 | 'name' => $this->name, 24 | 'file_name' => $this->file_name, 25 | 'type' => $this->getType(), 26 | 'mime_type' => $this->mime_type, 27 | 'size' => $this->size, 28 | 'human_readable_size' => $this->human_readable_size, 29 | 'details' => $this->mediaDetails(), 30 | 'status' => $this->mediaStatus(), 31 | 'progress' => $this->when($this->mediaStatus() == 'processing', $this->getCustomProperty('progress')), 32 | 'conversions' => $this->when( 33 | ($this->isImage() || $this->isVideo()) && ! empty($this->getConversions()), 34 | $this->getConversions() 35 | ), 36 | 'links' => [ 37 | 'delete' => [ 38 | 'href' => url('api/uploader/media/'.$this->getRouteKey()), 39 | 'method' => 'DELETE', 40 | ], 41 | ], 42 | ]; 43 | } 44 | 45 | /** 46 | * Get the generated conversions links. 47 | * 48 | * @return array 49 | */ 50 | public function getConversions() 51 | { 52 | $results = []; 53 | 54 | foreach (array_keys($this->getGeneratedConversions()->toArray()) as $conversionName) { 55 | $conversion = ConversionCollection::createForMedia($this->resource) 56 | ->first(fn (Conversion $conversion) => $conversion->getName() === $conversionName); 57 | 58 | if ($conversion) { 59 | $results[$conversionName] = $this->getFullUrl($conversionName); 60 | } 61 | } 62 | 63 | return $results; 64 | } 65 | 66 | /** 67 | * Determine if the media type is video. 68 | * 69 | * @return bool 70 | */ 71 | public function isVideo() 72 | { 73 | return $this->getType() == 'video'; 74 | } 75 | 76 | /** 77 | * Determine if the media type is image. 78 | * 79 | * @return bool 80 | */ 81 | public function isImage() 82 | { 83 | return $this->getType() == 'image'; 84 | } 85 | 86 | /** 87 | * Determine if the media type is audio. 88 | * 89 | * @return bool 90 | */ 91 | public function isAudio() 92 | { 93 | return $this->getType() == 'audio'; 94 | } 95 | 96 | /** 97 | * Get the media type. 98 | * 99 | * @return mixed|string 100 | */ 101 | public function getType() 102 | { 103 | return $this->getCustomProperty('type') ?: $this->type; 104 | } 105 | 106 | /** 107 | * Get the preview url. 108 | * 109 | * @return string|void 110 | */ 111 | public function getPreviewUrl() 112 | { 113 | if ($this->getType() == 'image') { 114 | return $this->getFullUrl(); 115 | } 116 | 117 | return 'https://cdn.jsdelivr.net/npm/laravel-file-uploader/dist/img/attach.png'; 118 | } 119 | 120 | /** 121 | * @return array 122 | */ 123 | protected function mediaDetails(): array 124 | { 125 | $duration = (float) $this->getCustomProperty('duration'); 126 | 127 | return [ 128 | $this->mergeWhen($this->isImage(), [ 129 | 'width' => $this->getCustomProperty('width'), 130 | 'height' => $this->getCustomProperty('height'), 131 | 'ratio' => (float) $this->getCustomProperty('ratio'), 132 | ]), 133 | 'duration' => $this->when($this->isVideo() || $this->isAudio(), $duration), 134 | ]; 135 | } 136 | 137 | /** 138 | * @return mixed 139 | */ 140 | protected function mediaStatus() 141 | { 142 | return $this->getCustomProperty('status'); 143 | } 144 | } 145 | --------------------------------------------------------------------------------