1515use Illuminate \Database \Connection ;
1616use Illuminate \Http \Client \PendingRequest ;
1717use Illuminate \Support \Arr ;
18+ use Illuminate \Support \Collection ;
1819use Illuminate \Support \Facades \Cache ;
20+ use Symfony \Component \HttpFoundation \File \File ;
1921use Illuminate \Support \Facades \Http ;
2022use Psr \Http \Message \RequestInterface ;
2123use Psr \Http \Message \ResponseInterface ;
@@ -104,7 +106,7 @@ protected function getRecordUrl()
104106 protected function getLayoutUrl ($ layout = null )
105107 {
106108 // Set the connection layout as the layout parameter, otherwise get the layout from the connection
107- if ($ layout ){
109+ if ($ layout ) {
108110 $ this ->setLayout ($ layout );
109111 }
110112 return $ this ->getDatabaseUrl () . '/layouts/ ' . $ this ->getLayout ();
@@ -155,17 +157,17 @@ public function uploadToContainerField(FMBaseBuilder $query)
155157 * The array should be in the format:
156158 * [ $file, 'myFile.pdf' ]
157159 */
158- if (is_array ($ query ->containerFile )){
160+ if (is_array ($ query ->containerFile )) {
159161 // we have a file and file name
160162 $ file = $ query ->containerFile [0 ];
161163 $ filename = $ query ->containerFile [1 ];
162- } else {
164+ } else {
163165 $ file = $ query ->containerFile ;
164166 $ filename = $ file ->getFilename ();
165167 }
166168
167169 // create a stream resource
168- $ stream = fopen ($ file ->getPath (). '/ ' . $ file ->getFilename (), 'r ' );
170+ $ stream = fopen ($ file ->getPath () . '/ ' . $ file ->getFilename (), 'r ' );
169171
170172 $ request = Http::attach ('upload ' , $ stream , $ filename );
171173 $ response = $ this ->makeRequest ('post ' , $ url , [], $ request );
@@ -313,6 +315,7 @@ public function editRecord(FMBaseBuilder $query)
313315 return $ response ;
314316 }
315317
318+
316319 /**
317320 * Attempt to emulate a sql update in FileMaker
318321 *
@@ -331,7 +334,26 @@ public function update($query, $bindings = [])
331334 // This is just a single record to edit
332335 try {
333336 // Do the update
334- $ this ->editRecord ($ query );
337+
338+ // Insert container data before updating text fields since scripts can be attached to the regular record edit
339+
340+ // Only attempt to write modified container fields
341+ // Figure out which fields are containers vs non-containers
342+ $ textDataFields = $ this ->getNonContainerFieldsForRecordWrite ($ query ->fieldData ) ?? [];
343+ $ modifiedContainerFields = collect ($ query ->fieldData )->diffKeys ($ textDataFields );
344+ foreach ($ modifiedContainerFields as $ containerField => $ fileData ) {
345+ $ eachResponse = $ query ->recordId ($ query ->getRecordId ())->setContainer ($ containerField , $ fileData );
346+ $ query ->modId ($ this ->getModIdFromFmResponse ($ eachResponse ));
347+ }
348+
349+ // only do an edit record if there are non-container fields to edit
350+ // It's technically valid in the FileMaker Data API to call edit with no fields to modify to create a
351+ // record, but that doesn't really match Laravel's behavior. Users who want to do this should
352+ // call edit() directly.
353+ if ($ textDataFields ->count () > 0 ) {
354+ $ this ->editRecord ($ query );
355+ }
356+
335357 } catch (FileMakerDataApiException $ e ) {
336358 // Record is missing is ok for laravel functions
337359 // Throw if it isn't error code 101, which is missing record
@@ -346,6 +368,11 @@ public function update($query, $bindings = [])
346368 return 1 ;
347369 }
348370
371+ protected function getModIdFromFmResponse ($ response )
372+ {
373+ return $ response ['response ' ]['modId ' ];
374+ }
375+
349376 /**
350377 * @param FMBaseBuilder $query
351378 * @return int
@@ -377,7 +404,7 @@ protected function performFindAndUpdateResults(FMBaseBuilder $query)
377404 $ builder ->layout ($ query ->from );
378405 try {
379406 // Do the update
380- $ this ->editRecord ($ builder );
407+ $ this ->update ($ builder );
381408 // Update if we don't get a record missing exception
382409 $ updatedCount ++;
383410 } catch (FileMakerDataApiException $ e ) {
@@ -422,7 +449,7 @@ protected function buildPostDataFromQuery(FMBaseBuilder $query)
422449 // only set field data if it exists
423450 if ($ query ->fieldData ) {
424451 // fieldData needs to have a value if it's there, but an empty object is ok instead of null
425- $ postData ['fieldData ' ] = $ query ->fieldData ?? json_decode ("{} " );
452+ $ postData ['fieldData ' ] = $ this -> getNonContainerFieldsForRecordWrite ( $ query ->fieldData ) ?? json_decode ("{} " );
426453 }
427454
428455 // attribute => parameter
@@ -475,6 +502,47 @@ protected function buildPostDataFromQuery(FMBaseBuilder $query)
475502 return $ postData ;
476503 }
477504
505+ /**
506+ * Strip out containers and read-only fields to prepare for a write query
507+ * OR - do the opposite and get ONLY containers
508+ *
509+ * @return Collection
510+ */
511+ protected function getNonContainerFieldsForRecordWrite ($ fieldArray )
512+ {
513+
514+ $ fieldData = collect ($ fieldArray );
515+
516+ // Remove any fields which have been set to write a file, as they should be handled as containers
517+ foreach ($ fieldData as $ key => $ field ) {
518+ // remove any containers to be written.
519+ // users can set the field to be a File, UploadFile, or array [$file, 'MyFile.pdf']
520+ if ($ this ->isContainer ($ field )) {
521+ $ fieldData ->forget ($ key );
522+ }
523+ }
524+ return $ fieldData ;
525+ }
526+
527+ protected function isContainer ($ field )
528+ {
529+
530+ // if this is a file then we know it's a container
531+ if (is_a ($ field , File::class)) {
532+ return true ;
533+ }
534+
535+ // if it's an array, it could be a file => filename key-value pair.
536+ // it's a conainer if the first object in the array is a file
537+ if (is_array ($ field ) && sizeof ($ field ) === 2 && $ this ->isFile ($ field [0 ])) {
538+ return true ;
539+ }
540+
541+ return false ;
542+ }
543+
544+
545+
478546 public function executeScript (FMBaseBuilder $ query )
479547 {
480548 $ this ->setLayout ($ query ->from );
@@ -643,7 +711,8 @@ protected function getDefaultQueryGrammar()
643711 return new FMGrammar ();
644712 }
645713
646- public function getLayoutMetadata ($ layout = null ){
714+ public function getLayoutMetadata ($ layout = null )
715+ {
647716 $ response = $ this ->makeRequest ('get ' , $ this ->getLayoutUrl ($ layout ));
648717 return $ response ['response ' ];
649718 }
0 commit comments