From 005380f75d8ac8596e6b27fe58818a00e6362568 Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Tue, 6 May 2025 13:32:48 +0530 Subject: [PATCH 01/11] refactor(recyclebin): move interface from Products.CMFPlone --- src/plone/base/interfaces/recyclebin.py | 116 ++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/plone/base/interfaces/recyclebin.py diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py new file mode 100644 index 0000000..92bbcb4 --- /dev/null +++ b/src/plone/base/interfaces/recyclebin.py @@ -0,0 +1,116 @@ +"""Interfaces for the Plone Recycle Bin functionality.""" + +from zope import schema +from zope.interface import Interface + + +class IRecycleBinControlPanelSettings(Interface): + """Interface for recycle bin settings""" + + recycling_enabled = schema.Bool( + title="Enable the recycle bin", + description="Enable or disable the recycle bin feature.", + default=False, + ) + + retention_period = schema.Int( + title="Retention period", + description="Number of days to keep deleted items in the recycle bin. Set to '0' to disable automatic purging.", + default=30, + min=0, + ) + + maximum_size = schema.Int( + title="Maximum size", + description="Maximum size of the recycle bin in MB. When the total size of items in the recycle bin exceeds its maximum size, the oldest items in the recycle bin will be permanently purged.", + default=100, + min=10, + ) + + +class IRecycleBin(Interface): + """Interface for the recycle bin functionality""" + + def add_item(obj, original_container, original_path): + """Add deleted item to recycle bin + + Args: + obj: The object being deleted + original_container: The parent container before deletion + original_path: The full path to the object before deletion + + Returns: + The ID of the item in the recycle bin + """ + + def get_items(): + """Return all items in recycle bin + + Returns: + A list of dictionaries with information about deleted items + """ + + def get_item(item_id): + """Get a specific deleted item by ID + + Args: + item_id: The ID of the deleted item in the recycle bin + + Returns: + Dictionary with item information or None if not found + """ + + def restore_item(item_id, target_container=None): + """Restore item to original location or specified container + + Args: + item_id: The ID of the item in the recycle bin + target_container: Optional target container to restore to + (defaults to original container) + + Returns: + The restored object or None if restore failed + """ + + def purge_item(item_id): + """Permanently delete an item + + Args: + item_id: The ID of the item in the recycle bin + + Returns: + Boolean indicating success + """ + + def purge_expired_items(): + """Purge items that exceed the retention period + + Returns: + Number of items purged + """ + + def is_enabled(): + """Check if recycle bin is enabled in the registry settings + + Returns: + Boolean indicating whether recycle bin is enabled + """ + +class IRecycleBinForm(Interface): + """Schema for the recycle bin form""" + + selected_items = schema.List( + title="Selected Items", + description="Selected items for operations", + value_type=schema.TextLine(), + required=False, + ) + +class IRecycleBinItemForm(Interface): + """Schema for the recycle bin item form""" + + target_container = schema.TextLine( + title="Target container", + description="Path to container where the item should be restored (optional)", + required=False, + ) \ No newline at end of file From 6f8d4738f3d71cc9812812e846da3d4319425bfc Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Sun, 11 May 2025 12:08:19 +0530 Subject: [PATCH 02/11] lint --- src/plone/base/interfaces/recyclebin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index 92bbcb4..bc21701 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -96,6 +96,7 @@ def is_enabled(): Boolean indicating whether recycle bin is enabled """ + class IRecycleBinForm(Interface): """Schema for the recycle bin form""" @@ -106,6 +107,7 @@ class IRecycleBinForm(Interface): required=False, ) + class IRecycleBinItemForm(Interface): """Schema for the recycle bin item form""" @@ -113,4 +115,4 @@ class IRecycleBinItemForm(Interface): title="Target container", description="Path to container where the item should be restored (optional)", required=False, - ) \ No newline at end of file + ) From 835d025c02f1a726d84d6e82fcf5bc761d71668a Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Mon, 28 Jul 2025 18:37:23 +0530 Subject: [PATCH 03/11] changelog --- news/89.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/89.feature diff --git a/news/89.feature b/news/89.feature new file mode 100644 index 0000000..6fa4e5f --- /dev/null +++ b/news/89.feature @@ -0,0 +1 @@ +Adds interface for recyclebin functionality @rohnsha0 \ No newline at end of file From 73cf713dd30194c9a486c63718330e79de5c030a Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Sun, 21 Sep 2025 00:09:02 +0530 Subject: [PATCH 04/11] feat(recyclebin): add restore_to_initial_state option to IRecycleBinControlPanelSettings --- src/plone/base/interfaces/recyclebin.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index bc21701..4780817 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -11,6 +11,8 @@ class IRecycleBinControlPanelSettings(Interface): title="Enable the recycle bin", description="Enable or disable the recycle bin feature.", default=False, + required=False, + ) retention_period = schema.Int( @@ -27,6 +29,13 @@ class IRecycleBinControlPanelSettings(Interface): min=10, ) + restore_to_initial_state = schema.Bool( + title="Restore to initial workflow state", + description="When enabled, restored content will be set to its initial workflow state (usually 'draft') instead of the workflow state it was in when deleted.", + default=False, + required=False, + ) + class IRecycleBin(Interface): """Interface for the recycle bin functionality""" From abaefca5d40c04e44da670caae57e896c733ee46 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:44:23 +0000 Subject: [PATCH 05/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/plone/base/interfaces/recyclebin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index 4780817..6fa0099 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -12,7 +12,6 @@ class IRecycleBinControlPanelSettings(Interface): description="Enable or disable the recycle bin feature.", default=False, required=False, - ) retention_period = schema.Int( From 4ff27e89210f99dfb471cdc4513eeeb95c01545a Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Sun, 21 Sep 2025 00:39:26 +0530 Subject: [PATCH 06/11] fix(recyclebin): change target_container schema type to Choice for better container selection --- src/plone/base/interfaces/recyclebin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index 6fa0099..9e9f335 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -119,8 +119,9 @@ class IRecycleBinForm(Interface): class IRecycleBinItemForm(Interface): """Schema for the recycle bin item form""" - target_container = schema.TextLine( + target_container = schema.Choice( title="Target container", - description="Path to container where the item should be restored (optional)", + description="Choose the container where the item should be restored when original location no longer exists", + vocabulary="plone.recyclebin.RestoreContainers", required=False, ) From be666cbff101251a10e1f0da9afac82944e24b15 Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Mon, 22 Sep 2025 09:22:21 +0530 Subject: [PATCH 07/11] fix(recyclebin): change target_container schema type to TextLine for improved path input --- src/plone/base/interfaces/recyclebin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index 9e9f335..f1abbb4 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -119,9 +119,8 @@ class IRecycleBinForm(Interface): class IRecycleBinItemForm(Interface): """Schema for the recycle bin item form""" - target_container = schema.Choice( + target_container = schema.TextLine( title="Target container", - description="Choose the container where the item should be restored when original location no longer exists", - vocabulary="plone.recyclebin.RestoreContainers", + description="Enter the path to the container where the item should be restored (e.g., /folder1/subfolder)", required=False, ) From 6550420e157c5e57f27d2409071b19fc54cc8de3 Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Mon, 22 Sep 2025 09:25:31 +0530 Subject: [PATCH 08/11] fix(recyclebin): update string literals to use PloneMessageFactory for localization --- src/plone/base/interfaces/recyclebin.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index f1abbb4..7afad7c 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -1,5 +1,6 @@ """Interfaces for the Plone Recycle Bin functionality.""" +from plone.base import PloneMessageFactory as _ from zope import schema from zope.interface import Interface @@ -8,29 +9,29 @@ class IRecycleBinControlPanelSettings(Interface): """Interface for recycle bin settings""" recycling_enabled = schema.Bool( - title="Enable the recycle bin", - description="Enable or disable the recycle bin feature.", + title=_("Enable the recycle bin"), + description=_("Enable or disable the recycle bin feature."), default=False, required=False, ) retention_period = schema.Int( - title="Retention period", - description="Number of days to keep deleted items in the recycle bin. Set to '0' to disable automatic purging.", + title=_("Retention period"), + description=_("Number of days to keep deleted items in the recycle bin. Set to '0' to disable automatic purging."), default=30, min=0, ) maximum_size = schema.Int( - title="Maximum size", - description="Maximum size of the recycle bin in MB. When the total size of items in the recycle bin exceeds its maximum size, the oldest items in the recycle bin will be permanently purged.", + title=_("Maximum size"), + description=_("Maximum size of the recycle bin in MB. When the total size of items in the recycle bin exceeds its maximum size, the oldest items in the recycle bin will be permanently purged."), default=100, min=10, ) restore_to_initial_state = schema.Bool( - title="Restore to initial workflow state", - description="When enabled, restored content will be set to its initial workflow state (usually 'draft') instead of the workflow state it was in when deleted.", + title=_("Restore to initial workflow state"), + description=_("When enabled, restored content will be set to its initial workflow state (usually 'draft') instead of the workflow state it was in when deleted."), default=False, required=False, ) @@ -109,8 +110,8 @@ class IRecycleBinForm(Interface): """Schema for the recycle bin form""" selected_items = schema.List( - title="Selected Items", - description="Selected items for operations", + title=_("Selected Items"), + description=_("Selected items for operations"), value_type=schema.TextLine(), required=False, ) @@ -120,7 +121,7 @@ class IRecycleBinItemForm(Interface): """Schema for the recycle bin item form""" target_container = schema.TextLine( - title="Target container", - description="Enter the path to the container where the item should be restored (e.g., /folder1/subfolder)", + title=_("Target container"), + description=_("Enter the path to the container where the item should be restored (e.g., /folder1/subfolder)"), required=False, ) From 1ac980739f58f008006849c648c122b0f3460495 Mon Sep 17 00:00:00 2001 From: Rohan Shaw Date: Mon, 22 Sep 2025 09:30:10 +0530 Subject: [PATCH 09/11] refactor(recyclebin): remove IRecycleBinForm and IRecycleBinItemForm back to CMFPlone --- src/plone/base/interfaces/recyclebin.py | 33 +++++++------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index 7afad7c..ec54cab 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -17,21 +17,27 @@ class IRecycleBinControlPanelSettings(Interface): retention_period = schema.Int( title=_("Retention period"), - description=_("Number of days to keep deleted items in the recycle bin. Set to '0' to disable automatic purging."), + description=_( + "Number of days to keep deleted items in the recycle bin. Set to '0' to disable automatic purging." + ), default=30, min=0, ) maximum_size = schema.Int( title=_("Maximum size"), - description=_("Maximum size of the recycle bin in MB. When the total size of items in the recycle bin exceeds its maximum size, the oldest items in the recycle bin will be permanently purged."), + description=_( + "Maximum size of the recycle bin in MB. When the total size of items in the recycle bin exceeds its maximum size, the oldest items in the recycle bin will be permanently purged." + ), default=100, min=10, ) restore_to_initial_state = schema.Bool( title=_("Restore to initial workflow state"), - description=_("When enabled, restored content will be set to its initial workflow state (usually 'draft') instead of the workflow state it was in when deleted."), + description=_( + "When enabled, restored content will be set to its initial workflow state (usually 'draft') instead of the workflow state it was in when deleted." + ), default=False, required=False, ) @@ -104,24 +110,3 @@ def is_enabled(): Returns: Boolean indicating whether recycle bin is enabled """ - - -class IRecycleBinForm(Interface): - """Schema for the recycle bin form""" - - selected_items = schema.List( - title=_("Selected Items"), - description=_("Selected items for operations"), - value_type=schema.TextLine(), - required=False, - ) - - -class IRecycleBinItemForm(Interface): - """Schema for the recycle bin item form""" - - target_container = schema.TextLine( - title=_("Target container"), - description=_("Enter the path to the container where the item should be restored (e.g., /folder1/subfolder)"), - required=False, - ) From 2797d758a8bdd9a668fd941710a251a37336809f Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Thu, 19 Mar 2026 10:59:28 +0100 Subject: [PATCH 10/11] Remove maximum_size from recyclebin interface --- src/plone/base/interfaces/recyclebin.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index ec54cab..d8ce75d 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -24,15 +24,6 @@ class IRecycleBinControlPanelSettings(Interface): min=0, ) - maximum_size = schema.Int( - title=_("Maximum size"), - description=_( - "Maximum size of the recycle bin in MB. When the total size of items in the recycle bin exceeds its maximum size, the oldest items in the recycle bin will be permanently purged." - ), - default=100, - min=10, - ) - restore_to_initial_state = schema.Bool( title=_("Restore to initial workflow state"), description=_( From bc154f047566a113ff57a278eb849b11a1c52166 Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Fri, 20 Mar 2026 12:49:53 +0100 Subject: [PATCH 11/11] add search method --- src/plone/base/interfaces/recyclebin.py | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/plone/base/interfaces/recyclebin.py b/src/plone/base/interfaces/recyclebin.py index d8ce75d..cc42bbd 100644 --- a/src/plone/base/interfaces/recyclebin.py +++ b/src/plone/base/interfaces/recyclebin.py @@ -95,6 +95,41 @@ def purge_expired_items(): Number of items purged """ + def search( + title=None, + path=None, + portal_type=None, + date_from=None, + date_to=None, + deleted_by=None, + has_subitems=None, + language=None, + review_state=None, + sort_on="deletion_date", + sort_order="descending", + ): + """Return filtered and sorted items from the recycle bin. + + All parameters are optional; omitting them returns all items. + + Args: + title: Case-insensitive substring to match against item title (str) + path: Case-insensitive substring to match against item path (str) + portal_type: Exact portal_type to filter by (str) + date_from: Earliest deletion date, inclusive (datetime.date) + date_to: Latest deletion date, inclusive (datetime.date) + deleted_by: User ID who deleted the item (str) + has_subitems: True → only items with children; False → only without (bool or None) + language: Language code to filter by (str) + review_state: Workflow state to filter by (str) + sort_on: Field to sort by — one of title, portal_type, path, size, + deletion_date (default), review_state (str) + sort_order: "ascending" or "descending" (default) (str) + + Returns: + Filtered and sorted list of item dicts (same shape as get_items()) + """ + def is_enabled(): """Check if recycle bin is enabled in the registry settings