44
55use App \Imports \ResourcesImport ;
66use App \Imports \ResourcesPreviewImport ;
7+ use App \Services \LearnTeachWorkbookParser ;
78use App \Services \ResourcesImportResult ;
89use App \Services \ResourcesUploadValidator ;
910use Illuminate \Http \RedirectResponse ;
1415use Illuminate \Support \Str ;
1516use Illuminate \View \View ;
1617use Maatwebsite \Excel \Facades \Excel ;
18+ use ZipArchive ;
1719
1820class ResourcesImportController extends Controller
1921{
@@ -38,7 +40,7 @@ public function verify(Request $request): RedirectResponse
3840 'file ' => [
3941 'required ' ,
4042 'file ' ,
41- 'max:10240 ' ,
43+ 'max:51200 ' ,
4244 function ($ attribute , $ value , $ fail ) {
4345 if ($ value ) {
4446 $ ext = strtolower ($ value ->getClientOriginalExtension ());
@@ -52,10 +54,12 @@ function ($attribute, $value, $fail) {
5254 }
5355 },
5456 ],
57+ 'assets_zip ' => ['nullable ' , 'file ' , 'mimes:zip ' , 'max:512000 ' ],
5558 'focus ' => ['nullable ' , 'boolean ' ],
5659 ], [
5760 'file.required ' => 'Please select a file to upload. ' ,
58- 'file.max ' => 'The file may not be greater than 10 MB. ' ,
61+ 'file.max ' => 'The file may not be greater than 50 MB. ' ,
62+ 'assets_zip.max ' => 'The assets ZIP may not be greater than 500 MB. ' ,
5963 ]);
6064
6165 $ file = $ request ->file ('file ' );
@@ -84,13 +88,28 @@ function ($attribute, $value, $fail) {
8488 $ request ->session ()->forget ([self ::SESSION_FILE_PATH , self ::SESSION_ROWS , self ::SESSION_FOCUS ]);
8589
8690 $ path = $ file ->storeAs ('temp ' , 'resources_import_ ' .time ().'. ' .$ extension , $ tempDisk );
91+ $ absolutePath = Storage::disk ($ tempDisk )->path ($ path );
92+ $ assetsDir = null ;
93+
94+ if ($ request ->hasFile ('assets_zip ' )) {
95+ try {
96+ $ assetsDir = $ this ->extractAssetsZip ($ request ->file ('assets_zip ' ), $ tempDisk );
97+ } catch (\Throwable $ e ) {
98+ Storage::disk ($ tempDisk )->delete ($ path );
99+
100+ return redirect ()->route ('admin.resources-import.index ' )
101+ ->withErrors (['assets_zip ' => 'Could not extract assets ZIP: ' .$ e ->getMessage ()])
102+ ->withInput ();
103+ }
104+ }
87105
88106 try {
89- $ import = new ResourcesPreviewImport ;
90- Excel::import ($ import , $ path , $ tempDisk );
91- $ rows = $ import ->data ;
107+ $ rows = $ this ->parseUploadedMetadata ($ absolutePath , $ path , $ extension , $ tempDisk );
92108 } catch (\Throwable $ e ) {
93109 Storage::disk ($ tempDisk )->delete ($ path );
110+ if ($ assetsDir ) {
111+ Storage::disk ($ tempDisk )->deleteDirectory ($ assetsDir );
112+ }
94113
95114 return redirect ()->route ('admin.resources-import.index ' )
96115 ->withErrors (['file ' => 'Could not parse file: ' .$ e ->getMessage ()])
@@ -100,13 +119,19 @@ function ($attribute, $value, $fail) {
100119 $ headerCheck = ResourcesUploadValidator::validateRequiredColumnsFromRows ($ rows );
101120 if (! $ headerCheck ['valid ' ]) {
102121 Storage::disk ($ tempDisk )->delete ($ path );
122+ if ($ assetsDir ) {
123+ Storage::disk ($ tempDisk )->deleteDirectory ($ assetsDir );
124+ }
103125
104126 return redirect ()->route ('admin.resources-import.index ' )
105127 ->withErrors (['file ' => 'Missing required column(s): ' .implode (', ' , $ headerCheck ['missing ' ]).'. Please add a header row with at least "name_of_the_resource". ' ])
106128 ->withInput ();
107129 }
108130 if (empty ($ rows )) {
109131 Storage::disk ($ tempDisk )->delete ($ path );
132+ if ($ assetsDir ) {
133+ Storage::disk ($ tempDisk )->deleteDirectory ($ assetsDir );
134+ }
110135
111136 return redirect ()->route ('admin.resources-import.index ' )
112137 ->withErrors (['file ' => 'The file has no data rows. ' ])
@@ -123,6 +148,7 @@ function ($attribute, $value, $fail) {
123148 'path ' => $ path ,
124149 'disk ' => $ tempDisk ,
125150 'focus ' => $ focus ,
151+ 'assets_dir ' => $ assetsDir ,
126152 ], now ()->addHours (1 ));
127153 $ request ->session ()->put ('resources_import_token ' , $ importToken );
128154
@@ -172,6 +198,7 @@ public function import(Request $request): RedirectResponse
172198 {
173199 $ path = null ;
174200 $ focus = false ;
201+ $ assetsDir = null ;
175202 $ tempDisk = config ('filesystems.resources_import_temp_disk ' , 'local ' );
176203
177204 $ token = $ request ->input ('import_token ' );
@@ -181,6 +208,7 @@ public function import(Request $request): RedirectResponse
181208 $ path = $ cached ['path ' ];
182209 $ tempDisk = $ cached ['disk ' ] ?? $ tempDisk ;
183210 $ focus = (bool ) ($ cached ['focus ' ] ?? false );
211+ $ assetsDir = $ cached ['assets_dir ' ] ?? null ;
184212 }
185213 }
186214
@@ -266,10 +294,15 @@ public function import(Request $request): RedirectResponse
266294
267295 try {
268296 $ result = new ResourcesImportResult ;
269- $ import = new ResourcesImport (null , null , $ focus , $ overrides , $ result , $ filenameMode , $ batchTimestamp , $ customSuffix );
297+ $ imagesDir = $ assetsDir ? Storage::disk ($ tempDisk )->path ($ assetsDir .'/images ' ) : null ;
298+ $ pdfsDir = $ assetsDir ? Storage::disk ($ tempDisk )->path ($ assetsDir .'/links ' ) : null ;
299+ $ import = new ResourcesImport ($ imagesDir , $ pdfsDir , $ focus , $ overrides , $ result , $ filenameMode , $ batchTimestamp , $ customSuffix );
270300 Excel::import ($ import , $ path , $ tempDisk );
271301
272302 Storage::disk ($ tempDisk )->delete ($ path );
303+ if ($ assetsDir ) {
304+ Storage::disk ($ tempDisk )->deleteDirectory ($ assetsDir );
305+ }
273306 $ request ->session ()->forget ([self ::SESSION_FILE_PATH , self ::SESSION_ROWS , self ::SESSION_FOCUS , 'resources_import_token ' ]);
274307 if (is_string ($ token = $ request ->input ('import_token ' )) && $ token !== '' ) {
275308 Cache::forget ('resources_import_ ' . $ token );
@@ -284,6 +317,9 @@ public function import(Request $request): RedirectResponse
284317 if (Storage::disk ($ tempDisk )->exists ($ path )) {
285318 Storage::disk ($ tempDisk )->delete ($ path );
286319 }
320+ if ($ assetsDir ) {
321+ Storage::disk ($ tempDisk )->deleteDirectory ($ assetsDir );
322+ }
287323 $ request ->session ()->forget ([self ::SESSION_FILE_PATH , self ::SESSION_ROWS , self ::SESSION_FOCUS , 'resources_import_token ' ]);
288324 if (is_string ($ token = $ request ->input ('import_token ' )) && $ token !== '' ) {
289325 Cache::forget ('resources_import_ ' . $ token );
@@ -314,4 +350,48 @@ public function report(Request $request): View|RedirectResponse
314350 'failures ' => $ failures ?? [],
315351 ]);
316352 }
353+
354+ /**
355+ * @return array<int, array<string, mixed>>
356+ */
357+ private function parseUploadedMetadata (string $ absolutePath , string &$ storedPath , string $ extension , string $ disk ): array
358+ {
359+ if (in_array ($ extension , ['xlsx ' , 'xls ' ], true ) && LearnTeachWorkbookParser::looksLikeLearnTeachWorkbook ($ absolutePath )) {
360+ $ parser = new LearnTeachWorkbookParser ;
361+ $ rows = $ parser ->parse ($ absolutePath );
362+ $ csvPath = 'temp/resources_import_ ' .time ().'.csv ' ;
363+ $ parser ->writeCsv ($ rows , Storage::disk ($ disk )->path ($ csvPath ));
364+ Storage::disk ($ disk )->delete ($ storedPath );
365+ $ storedPath = $ csvPath ;
366+
367+ return $ rows ;
368+ }
369+
370+ $ import = new ResourcesPreviewImport ;
371+ Excel::import ($ import , $ storedPath , $ disk );
372+
373+ return $ import ->data ;
374+ }
375+
376+ private function extractAssetsZip (\Illuminate \Http \UploadedFile $ zipFile , string $ disk ): string
377+ {
378+ $ assetsDir = 'temp/learn_teach_assets_ ' .time ();
379+ Storage::disk ($ disk )->makeDirectory ($ assetsDir );
380+ $ targetPath = Storage::disk ($ disk )->path ($ assetsDir );
381+
382+ $ zip = new ZipArchive ;
383+ $ opened = $ zip ->open ($ zipFile ->getRealPath ());
384+ if ($ opened !== true ) {
385+ throw new \RuntimeException ('Invalid ZIP archive. ' );
386+ }
387+
388+ if (! $ zip ->extractTo ($ targetPath )) {
389+ $ zip ->close ();
390+ throw new \RuntimeException ('ZIP extraction failed. ' );
391+ }
392+
393+ $ zip ->close ();
394+
395+ return $ assetsDir ;
396+ }
317397}
0 commit comments