v1.0.0
This commit is contained in:
parent
8caa140991
commit
5cdf7f48e5
29 changed files with 6526 additions and 0 deletions
290
includes/class-robotstxt-mlp2-mlp5-logger.php
Normal file
290
includes/class-robotstxt-mlp2-mlp5-logger.php
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
<?php
|
||||
/**
|
||||
* Migration log manager.
|
||||
*
|
||||
* Handles creation, insertion, retrieval, truncation, and CSV export
|
||||
* of the migration log table.
|
||||
*
|
||||
* @package Robotstxt_Mlp2_Mlp5
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Robotstxt_Mlp2_Mlp5_Logger
|
||||
*
|
||||
* All queries in this class are intentionally direct — the log table is
|
||||
* write-once per operation and must never be read from an object cache.
|
||||
* Per-line DirectDatabaseQuery suppressions are applied throughout.
|
||||
*/
|
||||
class Robotstxt_Mlp2_Mlp5_Logger {
|
||||
|
||||
/**
|
||||
* Sitemeta key used to track the installed DB schema version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DB_VERSION_KEY = 'mlp_migrator_db_version';
|
||||
|
||||
/**
|
||||
* Return the fully qualified log table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function table_name(): string {
|
||||
global $wpdb;
|
||||
return $wpdb->base_prefix . 'mlp_migrator_log';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create (or update) the log table via dbDelta().
|
||||
*
|
||||
* Safe to call multiple times — dbDelta() is idempotent and only alters
|
||||
* the table when the schema has changed. The DB upgrader is responsible
|
||||
* for calling this method and for updating the stored schema version.
|
||||
*/
|
||||
public static function create_table(): void {
|
||||
global $wpdb;
|
||||
|
||||
$table = self::table_name();
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
// Note: dbDelta() requires two spaces before PRIMARY KEY.
|
||||
$sql = "CREATE TABLE {$table} (
|
||||
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
created_at datetime NOT NULL,
|
||||
type varchar(20) NOT NULL,
|
||||
context varchar(50) NOT NULL,
|
||||
source_id bigint(20) unsigned DEFAULT NULL,
|
||||
target_id bigint(20) unsigned DEFAULT NULL,
|
||||
message text NOT NULL,
|
||||
metadata longtext DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY type (type),
|
||||
KEY context (context)
|
||||
) {$charset_collate};";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
dbDelta( $sql );
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the log table and remove the schema version option.
|
||||
*
|
||||
* Only called from uninstall.php when the user has explicitly opted in
|
||||
* to data deletion via the plugin settings.
|
||||
*/
|
||||
public static function drop_table(): void {
|
||||
global $wpdb;
|
||||
|
||||
$table = self::table_name();
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query( "DROP TABLE IF EXISTS {$table}" );
|
||||
|
||||
delete_site_option( self::DB_VERSION_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a log entry.
|
||||
*
|
||||
* @param string $type Entry type: 'success', 'skipped', or 'error'.
|
||||
* @param string $context Operation context: 'site_relation', 'post_relation', 'term_relation', or 'cleanup'.
|
||||
* @param int|null $source_id Source content or site ID, or null if not applicable.
|
||||
* @param int|null $target_id Target content or site ID, or null if not applicable.
|
||||
* @param string $message Human-readable description of the operation result.
|
||||
* @param array $metadata Optional additional structured data (stored as JSON).
|
||||
*
|
||||
* @return int Inserted row ID, or 0 on failure.
|
||||
*/
|
||||
public static function log(
|
||||
string $type,
|
||||
string $context,
|
||||
?int $source_id,
|
||||
?int $target_id,
|
||||
string $message,
|
||||
array $metadata = array()
|
||||
): int {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
|
||||
$inserted = $wpdb->insert(
|
||||
self::table_name(),
|
||||
array(
|
||||
'created_at' => current_time( 'mysql', true ),
|
||||
'type' => $type,
|
||||
'context' => $context,
|
||||
'source_id' => $source_id,
|
||||
'target_id' => $target_id,
|
||||
'message' => $message,
|
||||
'metadata' => empty( $metadata ) ? null : wp_json_encode( $metadata ),
|
||||
),
|
||||
array( '%s', '%s', '%s', '%d', '%d', '%s', '%s' )
|
||||
);
|
||||
|
||||
return $inserted ? (int) $wpdb->insert_id : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve log entries.
|
||||
*
|
||||
* @param array $args {
|
||||
* Optional. Query arguments.
|
||||
*
|
||||
* @type string $type Filter by type: 'success', 'skipped', or 'error'.
|
||||
* @type string $context Filter by context: 'site_relation', 'post_relation', 'term_relation', or 'cleanup'.
|
||||
* @type int $limit Maximum number of rows to return. Default 50.
|
||||
* @type int $offset Row offset for pagination. Default 0.
|
||||
* @type string $order Sort direction: 'ASC' or 'DESC'. Default 'DESC'.
|
||||
* }
|
||||
*
|
||||
* @return array Array of row objects, or an empty array.
|
||||
*/
|
||||
public static function get_entries( array $args = array() ): array {
|
||||
global $wpdb;
|
||||
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'type' => '',
|
||||
'context' => '',
|
||||
'limit' => 50,
|
||||
'offset' => 0,
|
||||
'order' => 'DESC',
|
||||
)
|
||||
);
|
||||
|
||||
$table = self::table_name();
|
||||
$order = 'ASC' === strtoupper( (string) $args['order'] ) ? 'ASC' : 'DESC';
|
||||
$where = '';
|
||||
$values = array();
|
||||
|
||||
if ( ! empty( $args['type'] ) ) {
|
||||
$where .= ' AND type = %s';
|
||||
$values[] = $args['type'];
|
||||
}
|
||||
|
||||
if ( ! empty( $args['context'] ) ) {
|
||||
$where .= ' AND context = %s';
|
||||
$values[] = $args['context'];
|
||||
}
|
||||
|
||||
$values[] = (int) $args['limit'];
|
||||
$values[] = (int) $args['offset'];
|
||||
|
||||
// Table name and ORDER BY direction are hardcoded — not from user input.
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
|
||||
return (array) $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$table} WHERE 1=1{$where} ORDER BY id {$order} LIMIT %d OFFSET %d",
|
||||
$values
|
||||
)
|
||||
);
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Count log entries.
|
||||
*
|
||||
* Accepts the same filter arguments as get_entries() except limit, offset, and order.
|
||||
*
|
||||
* @param array $args Optional. 'type' and/or 'context' filter keys.
|
||||
*
|
||||
* @return int Total count of matching entries.
|
||||
*/
|
||||
public static function count( array $args = array() ): int {
|
||||
global $wpdb;
|
||||
|
||||
$table = self::table_name();
|
||||
$where = '';
|
||||
$values = array();
|
||||
|
||||
if ( ! empty( $args['type'] ) ) {
|
||||
$where .= ' AND type = %s';
|
||||
$values[] = $args['type'];
|
||||
}
|
||||
|
||||
if ( ! empty( $args['context'] ) ) {
|
||||
$where .= ' AND context = %s';
|
||||
$values[] = $args['context'];
|
||||
}
|
||||
|
||||
if ( empty( $values ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE 1=1{$where}", $values )
|
||||
);
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the log table.
|
||||
*
|
||||
* Removes all rows without dropping the table structure. Used when
|
||||
* restarting a migration from scratch.
|
||||
*/
|
||||
public static function clear(): void {
|
||||
global $wpdb;
|
||||
|
||||
$table = self::table_name();
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query( "TRUNCATE TABLE {$table}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the full log as a CSV string.
|
||||
*
|
||||
* Columns: id, created_at, type, context, source_id, target_id, message, metadata.
|
||||
* Rows are ordered by id ASC.
|
||||
*
|
||||
* @return string CSV content with a header row, or an empty string on failure.
|
||||
*/
|
||||
public static function export_csv(): string {
|
||||
global $wpdb;
|
||||
|
||||
$table = self::table_name();
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$rows = $wpdb->get_results( "SELECT * FROM {$table} ORDER BY id ASC", ARRAY_A );
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
|
||||
$buffer = fopen( 'php://temp', 'r+' );
|
||||
|
||||
if ( false === $buffer ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
fputcsv( $buffer, array( 'id', 'created_at', 'type', 'context', 'source_id', 'target_id', 'message', 'metadata' ) );
|
||||
|
||||
if ( ! empty( $rows ) ) {
|
||||
foreach ( $rows as $row ) {
|
||||
fputcsv(
|
||||
$buffer,
|
||||
array(
|
||||
$row['id'],
|
||||
$row['created_at'],
|
||||
$row['type'],
|
||||
$row['context'],
|
||||
$row['source_id'] ?? '',
|
||||
$row['target_id'] ?? '',
|
||||
$row['message'],
|
||||
$row['metadata'] ?? '',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
rewind( $buffer );
|
||||
$csv = stream_get_contents( $buffer );
|
||||
fclose( $buffer ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
|
||||
|
||||
return is_string( $csv ) ? $csv : '';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue