post_status, [ 'draft', 'auto-draft' ], true ) ) { return false; } $status = (string) get_post_meta( $post_id, '_easy_invoice_status', true ); return 'draft' !== $status; } /** * May this invoice be permanently deleted? * * @param int $post_id Invoice ID. * @return true|\WP_Error True, or an error explaining what to do instead. */ public static function mayDelete( int $post_id ) { $post = get_post( $post_id ); if ( ! $post instanceof \WP_Post || ! in_array( $post->post_type, self::PROTECTED_TYPES, true ) ) { return true; } if ( ! self::isIssued( $post_id ) ) { return true; } /** * Filter whether an issued invoice may be permanently deleted. * * The escape hatch for a site that has a genuine reason — a botched * import, a staging copy, a legal instruction. It is deliberately not a * setting: this should be a considered act by someone who can edit code, * not a checkbox someone ticks to make a warning go away. * * @param bool $allowed Whether deletion is allowed. * @param int $post_id Invoice ID. */ if ( apply_filters( 'easy_invoice_allow_issued_invoice_deletion', false, $post_id ) ) { return true; } $number = (string) get_post_meta( $post_id, '_easy_invoice_number', true ); return new \WP_Error( 'easy_invoice_issued_invoice_protected', $number ? sprintf( /* translators: %s: invoice number. */ __( 'Invoice %s has been issued, so it cannot be deleted permanently — it is a tax record, and removing it breaks your numbering sequence. Move it to trash to take it out of your lists, or issue a credit note to cancel it out.', 'easy-invoice' ), $number ) : __( 'This invoice has been issued, so it cannot be deleted permanently — it is a tax record, and removing it breaks your numbering sequence. Move it to trash to take it out of your lists, or issue a credit note to cancel it out.', 'easy-invoice' ), [ 'status' => 409 ] ); } /** * Short-circuit `wp_delete_post()` for a protected invoice. * * @param \WP_Post|false|null $check Short-circuit value. * @param \WP_Post $post Post being deleted. * @param bool $force Whether this bypasses the trash. * @return \WP_Post|false|null */ public static function blockDeletion( $check, $post, $force ) { // Without $force this is a move to trash, which is reversible and stays // allowed. WordPress also passes $force = true for post types that have // no trash support, so the type check below still matters. if ( ! $force ) { return $check; } if ( ! $post instanceof \WP_Post || ! in_array( $post->post_type, self::PROTECTED_TYPES, true ) ) { return $check; } $may = self::mayDelete( (int) $post->ID ); if ( is_wp_error( $may ) ) { // false tells WordPress the deletion failed, which is exactly what // happened. Callers that used mayDelete() first will already have // told the user why; the rest at least do not silently succeed. return false; } return $check; } }