Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/js/components/EditorSidebar/actions/DeleteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const DeleteButton: React.FC = () => {

<ConfirmDialog
open={isDialogOpen}
title={__('Permanently delete?', 'code-snippets')}
title={__('Delete?', 'code-snippets')}
confirmLabel={__('Delete', 'code-snippets')}
confirmButtonClassName="is-destructive"
onCancel={() => setIsDialogOpen(false)}
Expand All @@ -43,10 +43,9 @@ export const DeleteButton: React.FC = () => {
}}
>
<p style={{ marginBlockStart: 0 }}>
{__('You are about to permanently delete this snippet.', 'code-snippets')}{' '}
{__('You are about to delete this snippet.', 'code-snippets')}{' '}
{__('Are you sure?', 'code-snippets')}
</p>
<p><strong>{__('This action cannot be undone.', 'code-snippets')}</strong></p>
</ConfirmDialog>
</>
)
Expand Down
171 changes: 142 additions & 29 deletions src/php/class-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class List_Table extends WP_List_Table {
*
* @var array<string>
*/
public array $statuses = [ 'all', 'active', 'inactive', 'recently_activated' ];
public array $statuses = [ 'all', 'active', 'inactive', 'recently_activated', 'trashed' ];

/**
* Column name to use when ordering the snippets list.
Expand Down Expand Up @@ -246,7 +246,26 @@ public function get_action_link( string $action, Snippet $snippet ): string {
private function get_snippet_action_links( Snippet $snippet ): array {
$actions = array();

if ( ! $this->is_network && $snippet->network && ! $snippet->shared_network ) {
if ( $snippet->is_trashed() ) {
$actions['restore'] = sprintf(
'<a href="%s">%s</a>',
esc_url( $this->get_action_link( 'restore', $snippet ) ),
esc_html__( 'Restore', 'code-snippets' )
);

$actions['delete_permanently'] = sprintf(
'<a href="%2$s" class="delete" onclick="%3$s">%1$s</a>',
esc_html__( 'Delete Permanently', 'code-snippets' ),
esc_url( $this->get_action_link( 'delete_permanently', $snippet ) ),
esc_js(
sprintf(
'return confirm("%s");',
esc_html__( 'You are about to permanently delete the selected item.', 'code-snippets' ) . "\n" .
esc_html__( "'Cancel' to stop, 'OK' to delete.", 'code-snippets' )
)
)
);
} elseif ( ! $this->is_network && $snippet->network && ! $snippet->shared_network ) {
// Display special links if on a subsite and dealing with a network-active snippet.
if ( $snippet->active ) {
$actions['network_active'] = esc_html__( 'Network Active', 'code-snippets' );
Expand All @@ -267,16 +286,9 @@ private function get_snippet_action_links( Snippet $snippet ): array {
}

$actions['delete'] = sprintf(
'<a href="%2$s" class="delete" onclick="%3$s">%1$s</a>',
esc_html__( 'Delete', 'code-snippets' ),
esc_url( $this->get_action_link( 'delete', $snippet ) ),
esc_js(
sprintf(
'return confirm("%s");',
esc_html__( 'You are about to permanently delete the selected item.', 'code-snippets' ) . "\n" .
esc_html__( "'Cancel' to stop, 'OK' to delete.", 'code-snippets' )
)
)
'<a href="%2$s" class="delete">%1$s</a>',
esc_html__( 'Trash', 'code-snippets' ),
esc_url( $this->get_action_link( 'delete', $snippet ) )
);
}

Expand All @@ -291,6 +303,10 @@ private function get_snippet_action_links( Snippet $snippet ): array {
* @return string Output for activation switch.
*/
protected function column_activate( Snippet $snippet ): string {
if ( $snippet->is_trashed() ) {
return '';
}

if ( $this->is_network && ( $snippet->shared_network || ( ! $this->is_network && $snippet->network && ! $snippet->shared_network ) ) ) {
return '';
}
Expand Down Expand Up @@ -352,8 +368,8 @@ protected function column_name( Snippet $snippet ): string {

$out = esc_html( $snippet->display_name );

// Add a link to the snippet if it isn't an unreadable network-only snippet.
if ( $this->is_network || ! $snippet->network || current_user_can( code_snippets()->get_network_cap_name() ) ) {
// Add a link to the snippet if it isn't an unreadable network-only snippet and isn't trashed.
if ( ! $snippet->is_trashed() && ( $this->is_network || ! $snippet->network || current_user_can( code_snippets()->get_network_cap_name() ) ) ) {
$out = sprintf(
'<a href="%s" class="snippet-name">%s</a>',
esc_attr( code_snippets()->get_snippet_edit_url( $snippet->id, $snippet->network ? 'network' : 'admin' ) ),
Expand Down Expand Up @@ -482,14 +498,23 @@ public function get_sortable_columns(): array {
* @return array<string, string> An array of menu items with the ID paired to the label
*/
public function get_bulk_actions(): array {
$actions = [
'activate-selected' => $this->is_network ? __( 'Network Activate', 'code-snippets' ) : __( 'Activate', 'code-snippets' ),
'deactivate-selected' => $this->is_network ? __( 'Network Deactivate', 'code-snippets' ) : __( 'Deactivate', 'code-snippets' ),
'clone-selected' => __( 'Clone', 'code-snippets' ),
'download-selected' => __( 'Export Code', 'code-snippets' ),
'export-selected' => __( 'Export', 'code-snippets' ),
'delete-selected' => __( 'Delete', 'code-snippets' ),
];
global $status;

if ( 'trashed' === $status ) {
$actions = [
'restore-selected' => __( 'Restore', 'code-snippets' ),
'delete-permanently-selected' => __( 'Delete Permanently', 'code-snippets' ),
];
} else {
$actions = [
'activate-selected' => $this->is_network ? __( 'Network Activate', 'code-snippets' ) : __( 'Activate', 'code-snippets' ),
'deactivate-selected' => $this->is_network ? __( 'Network Deactivate', 'code-snippets' ) : __( 'Deactivate', 'code-snippets' ),
'clone-selected' => __( 'Clone', 'code-snippets' ),
'download-selected' => __( 'Export Code', 'code-snippets' ),
'export-selected' => __( 'Export', 'code-snippets' ),
'delete-selected' => __( 'Move to Trash', 'code-snippets' ),
];
}

return apply_filters( 'code_snippets/list_table/bulk_actions', $actions );
}
Expand Down Expand Up @@ -558,6 +583,14 @@ public function get_views(): array {
'code-snippets'
);

// translators: %s: total number of trashed snippets.
$labels['trashed'] = _n(
'Trashed <span class="count">(%s)</span>',
'Trashed <span class="count">(%s)</span>',
$count,
'code-snippets'
);

// The page URL with the status parameter.
$url = esc_url( add_query_arg( 'status', $type ) );

Expand Down Expand Up @@ -737,9 +770,17 @@ private function perform_action( int $id, string $action ) {
return 'cloned';

case 'delete':
delete_snippet( $id, $this->is_network );
trash_snippet( $id, $this->is_network );
return 'deleted';

case 'restore':
restore_snippet( $id, $this->is_network );
return 'restored';

case 'delete_permanently':
delete_snippet( $id, $this->is_network );
return 'deleted_permanently';

case 'export':
$export = new Export_Attachment( [ $id ], $this->is_network );
$export->download_snippets_json();
Expand Down Expand Up @@ -789,7 +830,28 @@ public function process_requested_actions() {
$result = $this->perform_action( $id, sanitize_key( $_GET['action'] ) );

if ( $result ) {
wp_safe_redirect( esc_url_raw( add_query_arg( 'result', $result ) ) );
$redirect_args = array( 'result' => $result );

if ( 'deleted' === $result ) {
$redirect_args['ids'] = $id;
}

wp_safe_redirect( esc_url_raw( add_query_arg( $redirect_args ) ) );
exit;
}
}

if ( isset( $_GET['action'] ) && 'restore' === $_GET['action'] && isset( $_GET['ids'] ) ) {
$ids = array_map( 'intval', explode( ',', sanitize_text_field( $_GET['ids'] ) ) );

if ( ! empty( $ids ) ) {
check_admin_referer( 'bulk-' . $this->_args['plural'] );

foreach ( $ids as $id ) {
restore_snippet( $id, $this->is_network );
}

wp_safe_redirect( esc_url_raw( add_query_arg( 'result', 'restored' ) ) );
exit;
}
}
Expand Down Expand Up @@ -860,14 +922,35 @@ public function process_requested_actions() {

case 'delete-selected':
foreach ( $ids as $id ) {
delete_snippet( $id, $this->is_network );
trash_snippet( $id, $this->is_network );
}
$result = 'deleted-multi';
break;

case 'restore-selected':
foreach ( $ids as $id ) {
restore_snippet( $id, $this->is_network );
}
$result = 'restored-multi';
break;

case 'delete-permanently-selected':
foreach ( $ids as $id ) {
delete_snippet( $id, $this->is_network );
}
$result = 'deleted-permanently-multi';
break;
}

if ( isset( $result ) ) {
wp_safe_redirect( esc_url_raw( add_query_arg( 'result', $result ) ) );
$redirect_args = array( 'result' => $result );

// Add snippet IDs for undo functionality on bulk delete
if ( 'deleted-multi' === $result && ! empty( $ids ) ) {
$redirect_args['ids'] = implode( ',', $ids );
}

wp_safe_redirect( esc_url_raw( add_query_arg( $redirect_args ) ) );
exit;
}
}
Expand Down Expand Up @@ -978,9 +1061,19 @@ public function prepare_items() {
$this->process_requested_actions();
$snippets = array_fill_keys( $this->statuses, array() );

$snippets['all'] = apply_filters( 'code_snippets/list_table/get_snippets', get_snippets() );
$all_snippets = apply_filters( 'code_snippets/list_table/get_snippets', get_snippets() );
$this->fetch_shared_network_snippets();

// Separate trashed snippets from the main collection
$snippets['trashed'] = array_filter( $all_snippets, function( $snippet ) {
return $snippet->is_trashed();
});

// Filter out trashed snippets from the 'all' collection
$snippets['all'] = array_filter( $all_snippets, function( $snippet ) {
return ! $snippet->is_trashed();
});

foreach ( $snippets['all'] as $snippet ) {
if ( $snippet->active ) {
$this->active_by_condition[ $snippet->condition_id ][] = $snippet;
Expand All @@ -997,23 +1090,39 @@ function ( Snippet $snippet ) use ( $type ) {
return $type === $snippet->type;
}
);

// Filter trashed snippets by type
$snippets['trashed'] = array_filter(
$snippets['trashed'],
function ( Snippet $snippet ) use ( $type ) {
return $type === $snippet->type;
}
);
}

// Add scope tags.
// Add scope tags to all snippets (including trashed).
foreach ( $snippets['all'] as $snippet ) {
if ( 'global' !== $snippet->scope ) {
$snippet->add_tag( $snippet->scope );
}
}

foreach ( $snippets['trashed'] as $snippet ) {
if ( 'global' !== $snippet->scope ) {
$snippet->add_tag( $snippet->scope );
}
}

// Filter snippets by tag.
if ( ! empty( $_GET['tag'] ) ) {
$snippets['all'] = array_filter( $snippets['all'], array( $this, 'tags_filter_callback' ) );
$snippets['trashed'] = array_filter( $snippets['trashed'], array( $this, 'tags_filter_callback' ) );
}

// Filter snippets based on search query.
if ( $s ) {
$snippets['all'] = array_filter( $snippets['all'], array( $this, 'search_by_line_callback' ) );
$snippets['trashed'] = array_filter( $snippets['trashed'], array( $this, 'search_by_line_callback' ) );
}

// Clear recently activated snippets older than a week.
Expand All @@ -1037,6 +1146,11 @@ function ( Snippet $snippet ) use ( $type ) {
* @var Snippet $snippet
*/
foreach ( $snippets['all'] as $snippet ) {
// Skip trashed snippets (they're already in their own section)
if ( $snippet->is_trashed() ) {
continue;
}

if ( $snippet->active || $this->is_condition_active( $snippet ) ) {
$snippets['active'][] = $snippet;
} else {
Expand Down Expand Up @@ -1310,7 +1424,6 @@ public function search_notice() {
*/
public function single_row( $item ) {
$status = $item->active || $this->is_condition_active( $item ) ? 'active' : 'inactive';

$row_class = "snippet $status-snippet $item->type-snippet $item->scope-scope";

if ( $item->shared_network ) {
Expand Down
24 changes: 23 additions & 1 deletion src/php/class-snippet.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,25 @@ class Snippet extends Data_Item {
*/
public const DEFAULT_DATE = '0000-00-00 00:00:00';

/**
* Raw active value from database before processing.
*
* @var mixed
*/
private $raw_active_value;

/**
* Constructor function.
*
* @param array<string, mixed>|object $initial_data Initial snippet data.
*/
public function __construct( $initial_data = null ) {
if ( is_array( $initial_data ) && isset( $initial_data['active'] ) ) {
$this->raw_active_value = $initial_data['active'];
} elseif ( is_object( $initial_data ) && isset( $initial_data->active ) ) {
$this->raw_active_value = $initial_data->active;
}

$default_values = array(
'id' => 0,
'name' => '',
Expand Down Expand Up @@ -101,6 +114,15 @@ public function is_condition(): bool {
return 'condition' === $this->scope;
}

/**
* Determine if the snippet is trashed (soft deleted).
*
* @return bool
*/
public function is_trashed(): bool {
return -1 === (int) $this->raw_active_value;
}

/**
* Prepare a value before it is stored.
*
Expand All @@ -120,7 +142,7 @@ protected function prepare_field( $value, string $field ) {
return code_snippets_build_tags_array( $value );

case 'active':
return ( is_bool( $value ) ? $value : (bool) $value ) && ! $this->is_condition();
return ( is_bool( $value ) ? $value : (bool) $value ) && ! $this->is_condition() && (int) $value != -1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth changing the type of active entirely from string to int given we're using the full breadth of values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sheabunge Can we maybe do this in another PR. It started looking like a big scary change 🫣


default:
return $value;
Expand Down
1 change: 1 addition & 0 deletions src/php/flat-files/classes/class-snippet-files.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public function register_hooks(): void {
add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 );
add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 );
add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 );
add_action( 'code_snippets/trash_snippet', [ $this, 'delete_snippet' ], 10, 2 );
add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 1 );
add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 );
add_action( 'code_snippets/activate_snippets', [ $this, 'activate_snippets' ], 10, 2 );
Expand Down
Loading
Loading