@@ -331,6 +331,35 @@ public function insertAttachmentFile(int $documentId, string $path, string $user
331331 return $ this ->copyFile ($ originalFile , $ saveDir , $ textFile );
332332 }
333333
334+ /**
335+ * create a new file in the attachment folder
336+ *
337+ * @param int $documentId
338+ * @param string $userId
339+ *
340+ * @return array
341+ * @throws NotFoundException
342+ * @throws NotPermittedException
343+ * @throws InvalidPathException
344+ * @throws NoUserException
345+ */
346+ public function createAttachmentFile (int $ documentId , string $ newFileName , string $ userId ): array {
347+ $ textFile = $ this ->getTextFile ($ documentId , $ userId );
348+ if (!$ textFile ->isUpdateable ()) {
349+ throw new NotPermittedException ('No write permissions ' );
350+ }
351+ $ saveDir = $ this ->getAttachmentDirectoryForFile ($ textFile , true );
352+ $ fileName = self ::getUniqueFileName ($ saveDir , $ newFileName );
353+ $ newFile = $ saveDir ->newFile ($ fileName );
354+ return [
355+ 'name ' => $ newFile ->getName (),
356+ 'dirname ' => $ saveDir ->getName (),
357+ 'id ' => $ newFile ->getId (),
358+ 'documentId ' => $ textFile ->getId (),
359+ 'mimetype ' => $ newFile ->getMimetype (),
360+ ];
361+ }
362+
334363 /**
335364 * @param File $originalFile
336365 * @param Folder $saveDir
@@ -552,23 +581,44 @@ public function cleanupAttachments(int $fileId): int {
552581 // this only happens if the attachment dir was deleted by the user while editing the document
553582 return 0 ;
554583 }
555- $ attachmentsByName = [];
556- foreach ($ attachmentDir ->getDirectoryListing () as $ attNode ) {
557- $ attachmentsByName [$ attNode ->getName ()] = $ attNode ;
558- }
559-
584+ $ contentAttachmentFileIds = self ::getAttachmentIdsFromContent ($ textFile ->getContent ());
560585 $ contentAttachmentNames = self ::getAttachmentNamesFromContent ($ textFile ->getContent (), $ fileId );
561586
562- $ toDelete = array_diff (array_keys ($ attachmentsByName ), $ contentAttachmentNames );
563- foreach ($ toDelete as $ name ) {
564- $ attachmentsByName [$ name ]->delete ();
587+ $ toDelete = array_filter ($ attachmentDir ->getDirectoryListing (),
588+ function ($ node ) use ($ contentAttachmentFileIds , $ contentAttachmentNames ) {
589+ return !in_array ($ node ->getName (), $ contentAttachmentNames ) &&
590+ !in_array ($ node ->getId (), $ contentAttachmentFileIds );
591+ }
592+ );
593+ foreach ($ toDelete as $ node ) {
594+ $ node ->delete ();
565595 }
566596 return count ($ toDelete );
567597 }
568598 }
569599 return 0 ;
570600 }
571601
602+ /**
603+ * Get attachment file ids listed in the markdown file content
604+ *
605+ * @param string $content
606+ *
607+ * @return array
608+ */
609+ public static function getAttachmentIdsFromContent (string $ content ): array {
610+ $ matches = [];
611+ // matches [ANY_CONSIDERED_CORRECT_BY_PHP-MARKDOWN](ANY_URL/f/FILE_ID and captures FILE_ID
612+ preg_match_all (
613+ '/\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[\])*\])*\])*\])*\])*\])*\]\(\S+\/f\/(\d+)/ ' ,
614+ $ content ,
615+ $ matches ,
616+ PREG_SET_ORDER
617+ );
618+ return array_map (static function (array $ match ) {
619+ return intval ($ match [1 ]);
620+ }, $ matches );
621+ }
572622
573623 /**
574624 * Get attachment file names listed in the markdown file content
0 commit comments