Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mu-plugins/*
!websub-compat.php
!mu-plugins/osi-events-manager-tweaks
!mu-plugins/osi-editor-tweaks
!mu-plugins/osi-supporter-cleanup

themes/*
!themes/osi
Expand Down
195 changes: 195 additions & 0 deletions mu-plugins/osi-supporter-cleanup/osi-supporter-cleanup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php
/**
* WP-CLI command to remove the supporter posts other forms created.
*
* @see https://linear.app/a8c/issue/T51ENG-2307
* @package osi
*/

if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
return;
}

/**
* Class OSI_Supporter_Cleanup_Command
*/
class OSI_Supporter_Cleanup_Command extends WP_CLI_Command {

/**
* Trashes, or deletes, anonymous, never-edited supporter posts with no endorsement type, link or logo, in batches.
*
* ## OPTIONS
*
* [--dry-run=<bool>]
* : Report what would be removed without removing it. Pass --dry-run=false to remove.
* ---
* default: 'true'
* ---
*
* [--force]
* : Delete permanently instead of moving to the trash.
*
* [--trashed]
* : Target matching supporter posts already in the trash. Use with --force to purge them.
*
* [--newest-first]
* : Work from the highest ID down, e.g. to remove a test submission with --limit=1.
*
* [--batch-size=<number>]
* : Posts per batch.
* ---
* default: 100
* ---
*
* [--limit=<number>]
* : Stop after this many posts (0 = no limit).
* ---
* default: 0
* ---
*
* [--sleep=<seconds>]
* : Pause between batches.
* ---
* default: 2
* ---
*
* [--yes]
* : Skip the confirmation prompt on a real run.
*
* ## EXAMPLES
*
* wp osi-supporter-cleanup
* wp osi-supporter-cleanup --limit=10 --dry-run=false
* wp osi-supporter-cleanup --dry-run=false --yes | tee supporter-cleanup.log
* wp osi-supporter-cleanup --trashed --force --dry-run=false
* wp osi-supporter-cleanup --newest-first --limit=1 --dry-run=false
*
* @param array $args Positional arguments.
* @param array $assoc_args Associative arguments.
*
* @return void
*/
public function __invoke( array $args, array $assoc_args ): void {
$dry_run = ! in_array( strtolower( (string) $assoc_args['dry-run'] ), array( 'false', '0', 'no', '' ), true );
$force = ! empty( $assoc_args['force'] );
$status = empty( $assoc_args['trashed'] ) ? 'pending' : 'trash';
$desc = ! empty( $assoc_args['newest-first'] );
$batch_size = max( 1, absint( $assoc_args['batch-size'] ) );
$limit = absint( $assoc_args['limit'] );
$sleep = absint( $assoc_args['sleep'] );
$verb = ( $dry_run ? 'would be ' : '' ) . ( $force ? 'deleted' : 'trashed' );

$this->log( sprintf( '%s %s posts.', $dry_run ? 'DRY RUN on' : ( $force ? 'DELETING' : 'TRASHING' ), $status ) );

if ( ! $dry_run ) {
WP_CLI::confirm( sprintf( '%s supporter posts from other forms%s?', $force ? 'Permanently delete' : 'Trash', $limit ? " (limit {$limit})" : '' ), $assoc_args );
wp_defer_term_counting( true );
}

$limit = $limit ? $limit : PHP_INT_MAX;
$processed = 0;
$failed = 0;
$last_id = $desc ? PHP_INT_MAX : 0;

while ( $processed + $failed < $limit ) {
$ids = $this->get_candidates( $status, $last_id, min( $batch_size, $limit - $processed - $failed ), $desc );
if ( empty( $ids ) ) {
break;
}
$last_id = end( $ids );

foreach ( $ids as $id ) {
if ( ! $dry_run && ! ( $force ? wp_delete_post( $id, true ) : wp_trash_post( $id ) ) ) {
++$failed;
WP_CLI::warning( sprintf( 'Failed on %d.', $id ) );
continue;
}
++$processed;
}

$this->log( sprintf( '%d %s so far, %s MB in use. IDs: %s', $processed, $verb, number_format( memory_get_usage() / MB_IN_BYTES, 1 ), implode( ',', $ids ) ) );

if ( wp_cache_supports( 'flush_runtime' ) ) {
wp_cache_flush_runtime();
}
if ( $sleep ) {
sleep( $sleep );
}
}

if ( ! $dry_run ) {
wp_defer_term_counting( false );
}

if ( $failed ) {
$this->log( sprintf( 'Failed: %d', $failed ) );
}
WP_CLI::success( sprintf( '%d supporter posts from other forms %s.', $processed, $verb ) );
}

/**
* Fetch the next batch of anonymous, never-edited supporter IDs with no endorsement type, link or logo.
* The endorsement form always sends a type, so a submission without one came from another form.
*
* @param string $status Post status to search.
* @param integer $last_id Fetch IDs past this one.
* @param integer $size Batch size.
* @param boolean $desc Highest IDs first.
*
* @return int[]
*/
private function get_candidates( string $status, int $last_id, int $size, bool $desc ): array {
global $wpdb;

if ( $desc ) {
$ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT p.ID FROM {$wpdb->posts} p
WHERE p.post_type = 'supporter' AND p.post_status = %s AND p.post_author = 0 AND p.ID < %d
AND NOT EXISTS (
SELECT 1 FROM {$wpdb->postmeta} m WHERE m.post_id = p.ID
AND m.meta_key IN ( 'endorsement_type', 'link', '_thumbnail_id', '_edit_last' ) AND TRIM( m.meta_value ) NOT IN ( '', '0' )
)
ORDER BY p.ID DESC LIMIT %d",
$status,
$last_id,
$size
)
);
} else {
$ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT p.ID FROM {$wpdb->posts} p
WHERE p.post_type = 'supporter' AND p.post_status = %s AND p.post_author = 0 AND p.ID > %d
AND NOT EXISTS (
SELECT 1 FROM {$wpdb->postmeta} m WHERE m.post_id = p.ID
AND m.meta_key IN ( 'endorsement_type', 'link', '_thumbnail_id', '_edit_last' ) AND TRIM( m.meta_value ) NOT IN ( '', '0' )
)
ORDER BY p.ID ASC LIMIT %d",
$status,
$last_id,
$size
)
);
}

if ( $wpdb->last_error ) {
WP_CLI::error( $wpdb->last_error );
}

return array_map( 'intval', $ids );
}

/**
* Log a line with a UTC timestamp, so a teed run doubles as a record.
*
* @param string $message Message.
*
* @return void
*/
private function log( string $message ): void {
WP_CLI::log( sprintf( '[%s UTC] %s', gmdate( 'Y-m-d H:i:s' ), $message ) );
}
}

WP_CLI::add_command( 'osi-supporter-cleanup', 'OSI_Supporter_Cleanup_Command' );
30 changes: 29 additions & 1 deletion plugins/osi-features/acf-fields/group_672accc71e0c4.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,34 @@
"prepend": "",
"append": ""
},
{
"key": "field_672accd44e671",
"label": "Endorsement type",
"name": "endorsement_type",
"aria-label": "",
"type": "radio",
"instructions": "From the endorsement form. A supporter without one did not come from that form.",
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "",
"class": "",
"id": ""
},
"relevanssi_exclude": 0,
"choices": {
"Individual": "Individual",
"Institutional": "Institutional",
"Both": "Both"
},
"default_value": "",
"return_format": "value",
"allow_null": 1,
"other_choice": 0,
"allow_in_bindings": 0,
"layout": "horizontal",
"save_other_choice": 0
},
{
"key": "field_672ad07b77def",
"label": "Link",
Expand Down Expand Up @@ -130,5 +158,5 @@
"active": true,
"description": "",
"show_in_rest": 0,
"modified": 1733133647
"modified": 1793810000
}
40 changes: 32 additions & 8 deletions themes/osi/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -441,14 +441,24 @@ function osi_wpdc_comment_body( string $comment_body ) {
}
add_filter( 'wpdc_comment_body', 'osi_wpdc_comment_body', 10, 1 );

/**
* Title of the form that collects endorsements, and of the Flamingo channel it feeds.
* Keyed on the title because form IDs differ between environments.
*/
const OSI_ENDORSEMENT_FORM_TITLE = 'OSAID Endorsement';

/**
* Process the supporter form submission.
*
* @param WPCF7_ContactForm $contact_form The Contact Form 7 instance.
*
* @return void
*/
function osi_handle_support_form_submission( WPCF7_ContactForm $contact_form ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
function osi_handle_support_form_submission( WPCF7_ContactForm $contact_form ): void {
if ( OSI_ENDORSEMENT_FORM_TITLE !== $contact_form->title() ) {
return;
}

$submission = WPCF7_Submission::get_instance();

if ( $submission ) {
Expand All @@ -467,6 +477,10 @@ function osi_handle_support_form_submission( WPCF7_ContactForm $contact_form ):
* @return void
*/
function osi_save_supporter_form_data_to_cpt( array $form_data ): void {
if ( '' === trim( (string) ( $form_data['your-name'] ?? '' ) ) ) {
return;
}

$post_id = wp_insert_post(
array(
'post_title' => $form_data['your-name'],
Expand All @@ -482,12 +496,24 @@ function osi_save_supporter_form_data_to_cpt( array $form_data ): void {

if ( $post_id ) {
update_field( 'name', $form_data['your-name'], $post_id );
update_field( 'organization', $form_data['your-org'], $post_id );
update_field( 'endorsement_type', $form_data['your-endorsement'], $post_id );
update_field( 'quote', $form_data['your-message'], $post_id );
update_field( 'organization', $form_data['your-org'] ?? '', $post_id );
update_field( 'endorsement_type', osi_single_endorsement_type( $form_data['your-endorsement'] ?? '' ), $post_id );
update_field( 'quote', $form_data['your-message'] ?? '', $post_id );
}
}

/**
* CF7 posts a radio as a one-item array; the Endorsement type field is a radio, so keep the single value.
*
* @param mixed $value Stored or submitted endorsement type.
*
* @return mixed
*/
function osi_single_endorsement_type( mixed $value ): mixed {
return is_array( $value ) ? (string) reset( $value ) : $value;
}
add_filter( 'acf/load_value/name=endorsement_type', 'osi_single_endorsement_type' );

/**
* Handle the supporter form spam status change.
*
Expand All @@ -505,7 +531,7 @@ function osi_handle_supporter_form_flamingo_spam_status_change( string $new_stat
if ( 'flamingo-spam' === $old_status && 'publish' === $new_status ) {
$term_obj_list = get_the_terms( $post->ID, 'flamingo_inbound_channel' );

if ( empty( $term_obj_list ) || is_wp_error( $term_obj_list ) || 'OSAID Endorsement' !== $term_obj_list[0]->name ) {
if ( empty( $term_obj_list ) || is_wp_error( $term_obj_list ) || OSI_ENDORSEMENT_FORM_TITLE !== $term_obj_list[0]->name ) {
return;
}

Expand All @@ -516,9 +542,7 @@ function osi_handle_supporter_form_flamingo_spam_status_change( string $new_stat
'your-message' => get_post_meta( $post->ID, '_field_your-message', true ),
);

if ( ! empty( $form_data['your-name'] ) ) {
osi_save_supporter_form_data_to_cpt( $form_data );
}
osi_save_supporter_form_data_to_cpt( $form_data );
}
}
add_action( 'transition_post_status', 'osi_handle_supporter_form_flamingo_spam_status_change', 10, 3 );
Expand Down
Loading