3030import com .dotmarketing .portlets .folders .exception .InvalidFolderNameException ;
3131import com .dotmarketing .portlets .folders .model .Folder ;
3232import com .dotmarketing .portlets .folders .struts .FolderForm ;
33+ import com .dotmarketing .portlets .htmlpageasset .business .HTMLPageAssetAPI ;
3334import com .dotmarketing .portlets .links .model .Link ;
3435import com .dotmarketing .portlets .structure .model .Structure ;
3536import com .dotmarketing .util .Config ;
4243import com .liferay .portal .util .Constants ;
4344import com .liferay .portlet .ActionRequestImpl ;
4445import com .liferay .util .servlet .SessionMessages ;
46+ import io .vavr .Lazy ;
4547import java .util .List ;
4648import java .util .Set ;
4749import javax .servlet .http .HttpServletRequest ;
@@ -62,9 +64,14 @@ public class EditFolderAction extends DotPortletAction {
6264
6365 private FolderAPI folderAPI = APILocator .getFolderAPI ();
6466 private HostAPI hostAPI = APILocator .getHostAPI ();
67+ private HTMLPageAssetAPI htmlPageAssetAPI = APILocator .getHTMLPageAssetAPI ();
6568
6669 private static final int MAX_FOLDER_PATH_LENGTH = 255 ;
6770 private static final int MAX_FOLDER_NAME_LENGTH = 255 ;
71+ private static final String DELETE_NOT_EMPTY_FOLDER_PROPERTY = "no.delete.notempty.folder" ;
72+ private static final String DELETE_NOT_EMPTY_FOLDER_MESSAGE_KEY = "message.folder.delete.not.empty" ;
73+ private static final Lazy <Boolean > DELETE_NOT_EMPTY_FOLDER_PROTECTION =
74+ Lazy .of (() -> Config .getBooleanProperty (DELETE_NOT_EMPTY_FOLDER_PROPERTY , false ));
6875
6976 /**
7077 *
@@ -438,26 +445,103 @@ public void _deleteFolder(ActionRequest req, ActionResponse res,
438445 // gets the session object for the messages
439446 HttpSession session = httpReq .getSession ();
440447
441- String selectedFolder = ((String ) session
442- .getAttribute (com .dotmarketing .util .WebKeys .FOLDER_SELECTED ) != null ) ? (String ) session
443- .getAttribute (com .dotmarketing .util .WebKeys .FOLDER_SELECTED )
444- : "" ;
445-
446-
447-
448-
448+ session .removeAttribute (com .dotmarketing .util .WebKeys .FOLDER_SELECTED );
449449
450+ if (isDeleteNotEmptyFolderProtectionEnabled () && isNotEmpty (f )) {
451+ SessionMessages .add (req , "message" , DELETE_NOT_EMPTY_FOLDER_MESSAGE_KEY );
452+ return ;
453+ }
450454
451- session .removeAttribute (com .dotmarketing .util .WebKeys .FOLDER_SELECTED );
452455 User user = _getUser (req );
453456 folderAPI .delete (f , user ,false );
454457
455-
456458 // For messages to be displayed on messages page
457459 SessionMessages .add (req , "message" , "message.folder.delete" );
458460
459461 }
460462
463+ /**
464+ * Determines whether the protection that blocks the deletion of non-empty
465+ * folders is enabled through configuration.
466+ *
467+ * @return {@code true} if the protection is enabled, otherwise {@code false}.
468+ */
469+ private boolean isDeleteNotEmptyFolderProtectionEnabled () {
470+ return DELETE_NOT_EMPTY_FOLDER_PROTECTION .get ();
471+ }
472+
473+ /**
474+ * Recursively checks whether the specified folder, or one of its descendants,
475+ * contains content that makes the branch non-empty.
476+ *
477+ * @param folder
478+ * the folder to inspect.
479+ * @return {@code true} if at least one relevant asset is found.
480+ * @throws DotDataException
481+ * if a data access error occurs.
482+ * @throws DotStateException
483+ * if a folder is in an invalid state.
484+ * @throws DotSecurityException
485+ * if access to the APIs is not authorized.
486+ */
487+ private boolean isNotEmpty (final Folder folder )
488+ throws DotDataException , DotStateException , DotSecurityException {
489+ return isNotEmpty (folder , APILocator .getUserAPI ().getSystemUser ());
490+ }
491+
492+ /**
493+ * Performs the recursive check while reusing the same system user for all
494+ * queries executed against the folder tree.
495+ *
496+ * @param folder
497+ * the current folder to inspect.
498+ * @param user
499+ * the user used to execute the internal checks.
500+ * @return {@code true} as soon as content is found in the current branch.
501+ * @throws DotDataException
502+ * if a data access error occurs.
503+ * @throws DotStateException
504+ * if a folder is in an invalid state.
505+ * @throws DotSecurityException
506+ * if access to the APIs is not authorized.
507+ */
508+ private boolean isNotEmpty (final Folder folder , final User user )
509+ throws DotDataException , DotStateException , DotSecurityException {
510+ if (containsRelevantAssets (folder , user )) {
511+ return true ;
512+ }
513+
514+ for (final Folder childFolder : folderAPI .findSubFolders (folder , user , false )) {
515+ if (isNotEmpty (childFolder , user )) {
516+ return true ;
517+ }
518+ }
519+
520+ return false ;
521+ }
522+
523+ /**
524+ * Determines whether the current folder directly contains at least one
525+ * contentlet, link, or HTML page.
526+ *
527+ * @param folder
528+ * the folder to inspect.
529+ * @param user
530+ * the user used to query the internal APIs.
531+ * @return {@code true} if the folder contains direct relevant assets.
532+ * @throws DotDataException
533+ * if a data access error occurs.
534+ * @throws DotSecurityException
535+ * if access to the APIs is not authorized.
536+ */
537+ private boolean containsRelevantAssets (final Folder folder , final User user )
538+ throws DotDataException , DotSecurityException {
539+ return !folderAPI .getContent (folder , user , false ).isEmpty ()
540+ || !folderAPI .getLinks (folder , user , false ).isEmpty ()
541+ || !htmlPageAssetAPI .getHTMLPages (folder , false , false , user , false ).isEmpty ()
542+ || !htmlPageAssetAPI .getHTMLPages (folder , true , false , user , false ).isEmpty ();
543+ }
544+
461545 /**
462546 *
463547 * @param folder
0 commit comments