File: //wordpress/plugins/wp-cloud-client/1.1.5/src/Mail/MailTable.php
<?php
declare(strict_types=1);
namespace VPlugins\WPCloudClient\Mail;
/**
* Manages the wpcc_mails database table.
*
* Schema:
* eid bigint(20) unsigned – microtime-based unique ID
* created datetime – insertion timestamp (UTC)
* notified datetime – when Portal was last notified (nullable)
* content longtext – JSON-encoded email envelope
* notify_attempts tinyint(3) unsigned – consecutive notification failures (v2)
*/
final class MailTable {
private const TABLE = 'wpcc_mails';
private const DB_VERSION_OPTION = 'wpcc_mail_db_version';
private const DB_VERSION = 2;
private static ?self $instance = null;
/**
* Private constructor — use getInstance().
*/
private function __construct() {}
/**
* Return the singleton instance.
*/
public static function getInstance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Return the full prefixed table name.
*/
public function tableName(): string {
global $wpdb;
return $wpdb->prefix . self::TABLE;
}
/**
* Insert a blank row and return its eid.
*
* @param string $content JSON-encoded email envelope.
*/
public function register( string $content = '' ): int {
global $wpdb;
// Microtime-based ID (matches wsp-bootstrapper approach).
$eid = (int) ( microtime( true ) * 10000 );
$wpdb->insert(
$this->tableName(),
[
'eid' => $eid,
'created' => current_time( 'mysql', true ),
'content' => $content,
],
[ '%d', '%s', '%s' ]
);
return $eid;
}
/**
* Update the content of an existing row.
*
* @param int $id Row eid.
* @param string $content JSON-encoded email envelope.
*/
public function envelop( int $id, string $content ): void {
global $wpdb;
$wpdb->update(
$this->tableName(),
[ 'content' => $content ],
[ 'eid' => $id ],
[ '%s' ],
[ '%d' ]
);
}
/**
* Retrieve and JSON-decode an email envelope.
*
* @param int $id Row eid.
* @return array<string, mixed>|null
*/
public function get( int $id ): ?array {
global $wpdb;
$table = $this->tableName();
$row = $wpdb->get_row(
$wpdb->prepare( "SELECT * FROM `{$table}` WHERE eid = %d", $id ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
ARRAY_A
);
if ( null === $row ) {
return null;
}
$content = json_decode( (string) $row['content'], true );
return array_merge( $row, [ 'content' => is_array( $content ) ? $content : [] ] );
}
/**
* Delete a row by eid.
*
* @param int $id Row eid.
*/
public function remove( int $id ): void {
global $wpdb;
$wpdb->delete( $this->tableName(), [ 'eid' => $id ], [ '%d' ] );
}
/**
* Stamp the notified datetime for an email.
*
* @param int $id Row eid.
*/
public function markNotified( int $id ): void {
global $wpdb;
$wpdb->update(
$this->tableName(),
[ 'notified' => current_time( 'mysql', true ) ],
[ 'eid' => $id ],
[ '%s' ],
[ '%d' ]
);
}
/**
* Increment the notify_attempts counter for a set of rows.
*
* @param int[] $ids Row eids to increment.
*/
public function incrementAttempts( array $ids ): void {
if ( empty( $ids ) ) {
return;
}
global $wpdb;
$table = $this->tableName();
$placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
"UPDATE `{$table}` SET notify_attempts = notify_attempts + 1 WHERE eid IN ({$placeholders})",
...$ids
)
);
}
/**
* Retrieve emails that have not yet been notified to Portal,
* excluding rows that have hit or exceeded the attempt ceiling.
*
* @param int $limit Maximum rows to return; 0 means no limit.
* @param int $maxAttempts Rows with notify_attempts >= this value are skipped.
* @return list<array<string, mixed>>
*/
public function listUnnotified( int $limit = 0, int $maxAttempts = PHP_INT_MAX ): array {
global $wpdb;
$table = $this->tableName();
if ( $limit > 0 ) {
$sql = $wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT * FROM `{$table}` WHERE notified IS NULL AND notify_attempts < %d ORDER BY created ASC LIMIT %d",
$maxAttempts,
$limit
);
} else {
$sql = $wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT * FROM `{$table}` WHERE notified IS NULL AND notify_attempts < %d ORDER BY created ASC",
$maxAttempts
);
}
$rows = $wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
if ( ! is_array( $rows ) ) {
return [];
}
return array_map(
static function ( array $row ): array {
$content = json_decode( (string) $row['content'], true );
return array_merge( $row, [ 'content' => is_array( $content ) ? $content : [] ] );
},
$rows
);
}
/**
* Create or upgrade the table schema.
*/
public function initialize(): void {
if ( (int) get_option( self::DB_VERSION_OPTION, 0 ) >= self::DB_VERSION ) {
return;
}
global $wpdb;
$table = $this->tableName();
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE `{$table}` (
eid bigint(20) unsigned NOT NULL,
created datetime NOT NULL,
notified datetime DEFAULT NULL,
content longtext NOT NULL,
notify_attempts tinyint(3) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (eid),
KEY eid (eid)
) {$charset};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
update_option( self::DB_VERSION_OPTION, self::DB_VERSION, false );
}
/**
* Drop the table entirely (called on plugin uninstall).
*/
public function drop(): void {
global $wpdb;
$table = $this->tableName();
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
delete_option( self::DB_VERSION_OPTION );
}
}