416 lines
10 KiB
PHP
416 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* Main Plugin Class
|
|
*
|
|
* Orchestrates plugin initialization and component integration.
|
|
*
|
|
* @package ROBOTSTXT_OG
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly.
|
|
}
|
|
|
|
/**
|
|
* Class Robotstxt_OG_Image_Fallback
|
|
*
|
|
* Main plugin class using singleton pattern.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
class Robotstxt_OG_Image_Fallback {
|
|
|
|
/**
|
|
* Single instance of the class.
|
|
*
|
|
* @since 1.0.0
|
|
* @var Robotstxt_OG_Image_Fallback|null
|
|
*/
|
|
private static ?Robotstxt_OG_Image_Fallback $instance = null;
|
|
|
|
/**
|
|
* Image resolver instance.
|
|
*
|
|
* @since 1.0.0
|
|
* @var Robotstxt_OG_Image_Resolver
|
|
*/
|
|
private Robotstxt_OG_Image_Resolver $resolver;
|
|
|
|
/**
|
|
* OG tags generator instance.
|
|
*
|
|
* @since 1.0.0
|
|
* @var Robotstxt_OG_Tags
|
|
*/
|
|
private Robotstxt_OG_Tags $tags;
|
|
|
|
/**
|
|
* Admin settings instance.
|
|
*
|
|
* @since 1.0.0
|
|
* @var Robotstxt_OG_Admin_Settings|null
|
|
*/
|
|
private ?Robotstxt_OG_Admin_Settings $admin_settings = null;
|
|
|
|
/**
|
|
* REST API instance.
|
|
*
|
|
* @since 1.0.0
|
|
* @var Robotstxt_OG_REST_API
|
|
*/
|
|
private Robotstxt_OG_REST_API $rest_api;
|
|
|
|
/**
|
|
* Meta box instance.
|
|
*
|
|
* @since 1.2.0
|
|
* @var Robotstxt_OG_Meta_Box
|
|
*/
|
|
private Robotstxt_OG_Meta_Box $meta_box;
|
|
|
|
/**
|
|
* Get singleton instance.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return Robotstxt_OG_Image_Fallback
|
|
*/
|
|
public static function get_instance(): Robotstxt_OG_Image_Fallback {
|
|
if ( null === self::$instance ) {
|
|
self::$instance = new self();
|
|
}
|
|
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* Private to enforce singleton pattern.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
private function __construct() {
|
|
// Constructor is private.
|
|
}
|
|
|
|
/**
|
|
* Initialize the plugin.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function init(): void {
|
|
// Load dependencies.
|
|
$this->load_dependencies();
|
|
|
|
// Initialize components.
|
|
$this->resolver = new Robotstxt_OG_Image_Resolver();
|
|
$this->tags = new Robotstxt_OG_Tags( $this->resolver );
|
|
|
|
// Initialize OG tags generator.
|
|
$this->tags->init();
|
|
|
|
// Initialize REST API.
|
|
$this->rest_api = new Robotstxt_OG_REST_API( $this->resolver );
|
|
$this->rest_api->init();
|
|
|
|
// Initialize meta box (registers post meta for REST + editor UI).
|
|
$this->meta_box = new Robotstxt_OG_Meta_Box();
|
|
$this->meta_box->init();
|
|
|
|
// Initialize admin settings if in admin context.
|
|
if ( is_admin() ) {
|
|
$this->admin_settings = new Robotstxt_OG_Admin_Settings( $this->resolver );
|
|
$this->admin_settings->init();
|
|
}
|
|
|
|
// Register WP-CLI commands.
|
|
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
|
WP_CLI::add_command( 'og-fallback', new Robotstxt_OG_CLI( $this->resolver ) );
|
|
}
|
|
|
|
// Auto-regenerate cache when featured image changes.
|
|
add_action( 'updated_post_meta', array( $this, 'handle_thumbnail_change' ), 10, 4 );
|
|
add_action( 'deleted_post_meta', array( $this, 'handle_thumbnail_change' ), 10, 4 );
|
|
|
|
// Register GDPR privacy data handlers.
|
|
add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_privacy_exporter' ) );
|
|
add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_privacy_eraser' ) );
|
|
|
|
// Register activation and deactivation hooks.
|
|
register_activation_hook( ROBOTSTXT_OG_PATH . 'robotstxt-og.php', array( $this, 'activate' ) );
|
|
register_deactivation_hook( ROBOTSTXT_OG_PATH . 'robotstxt-og.php', array( $this, 'deactivate' ) );
|
|
}
|
|
|
|
/**
|
|
* Load plugin dependencies.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
private function load_dependencies(): void {
|
|
require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-image-resolver.php';
|
|
require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-tags.php';
|
|
require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-rest-api.php';
|
|
require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-meta-box.php';
|
|
|
|
// Load admin class if in admin context.
|
|
if ( is_admin() ) {
|
|
require_once ROBOTSTXT_OG_PATH . 'admin/class-robotstxt-og-admin-settings.php';
|
|
}
|
|
|
|
// Load WP-CLI class if running in CLI context.
|
|
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
|
require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-cli.php';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Activation hook callback.
|
|
*
|
|
* Runs when the plugin is activated.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function activate(): void {
|
|
// Set default options if they don't exist.
|
|
if ( false === get_option( 'robotstxt_og_fallback_image' ) ) {
|
|
add_option( 'robotstxt_og_fallback_image', '' );
|
|
}
|
|
|
|
if ( false === get_option( 'robotstxt_og_delete_data_on_uninstall' ) ) {
|
|
add_option( 'robotstxt_og_delete_data_on_uninstall', false );
|
|
}
|
|
|
|
// Flush rewrite rules (if needed in future).
|
|
flush_rewrite_rules();
|
|
}
|
|
|
|
/**
|
|
* Deactivation hook callback.
|
|
*
|
|
* Runs when the plugin is deactivated.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function deactivate(): void {
|
|
// Flush rewrite rules (if needed in future).
|
|
flush_rewrite_rules();
|
|
}
|
|
|
|
/**
|
|
* Clear fallback cache when a post's featured image is changed or removed.
|
|
*
|
|
* Hooked to `updated_post_meta` and `deleted_post_meta` for `_thumbnail_id`.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int|int[] $meta_id ID or array of IDs of the meta data entry.
|
|
* @param int $post_id Post ID.
|
|
* @param string $meta_key Meta key being updated.
|
|
* @param mixed $meta_value New meta value (unused).
|
|
* @return void
|
|
*/
|
|
public function handle_thumbnail_change( int|array $meta_id, int $post_id, string $meta_key, $meta_value = null ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
if ( '_thumbnail_id' !== $meta_key ) {
|
|
return;
|
|
}
|
|
|
|
$this->resolver->clear_cache( $post_id );
|
|
}
|
|
|
|
/**
|
|
* Register the personal data exporter.
|
|
*
|
|
* @since 1.0.3
|
|
*
|
|
* @param array<int, array<string, mixed>> $exporters List of exporters.
|
|
* @return array<int, array<string, mixed>> Updated exporters list.
|
|
*/
|
|
public function register_privacy_exporter( array $exporters ): array {
|
|
$exporters[] = array(
|
|
'exporter_friendly_name' => __( 'OpenGraph Custom Post Data', 'robotstxt-og' ),
|
|
'callback' => array( $this, 'export_privacy_data' ),
|
|
);
|
|
return $exporters;
|
|
}
|
|
|
|
/**
|
|
* Register the personal data eraser.
|
|
*
|
|
* @since 1.0.3
|
|
*
|
|
* @param array<int, array<string, mixed>> $erasers List of erasers.
|
|
* @return array<int, array<string, mixed>> Updated erasers list.
|
|
*/
|
|
public function register_privacy_eraser( array $erasers ): array {
|
|
$erasers[] = array(
|
|
'eraser_friendly_name' => __( 'OpenGraph Custom Post Data', 'robotstxt-og' ),
|
|
'callback' => array( $this, 'erase_privacy_data' ),
|
|
);
|
|
return $erasers;
|
|
}
|
|
|
|
/**
|
|
* Export personal data stored by this plugin for a given email address.
|
|
*
|
|
* Exports the custom og:title and og:description stored in post meta for
|
|
* all posts authored by the user with the given email address.
|
|
*
|
|
* @since 1.0.3
|
|
*
|
|
* @param string $email_address User email address.
|
|
* @param int $page Pagination page (1-based).
|
|
* @return array{data: array<int, array<string, mixed>>, done: bool} Export result.
|
|
*/
|
|
public function export_privacy_data( string $email_address, int $page = 1 ): array {
|
|
$user = get_user_by( 'email', $email_address );
|
|
|
|
if ( ! $user ) {
|
|
return array(
|
|
'data' => array(),
|
|
'done' => true,
|
|
);
|
|
}
|
|
|
|
$posts = get_posts(
|
|
array(
|
|
'author' => $user->ID,
|
|
'post_type' => 'any',
|
|
'post_status' => 'any',
|
|
'posts_per_page' => 100,
|
|
'paged' => $page,
|
|
'fields' => 'ids',
|
|
)
|
|
);
|
|
|
|
$data = array();
|
|
|
|
foreach ( $posts as $post_id ) {
|
|
$og_title = get_post_meta( $post_id, '_og_title', true );
|
|
$og_desc = get_post_meta( $post_id, '_og_description', true );
|
|
|
|
if ( empty( $og_title ) && empty( $og_desc ) ) {
|
|
continue;
|
|
}
|
|
|
|
$item_data = array();
|
|
|
|
if ( ! empty( $og_title ) && is_string( $og_title ) ) {
|
|
$item_data[] = array(
|
|
'name' => __( 'Custom OG Title', 'robotstxt-og' ),
|
|
'value' => $og_title,
|
|
);
|
|
}
|
|
|
|
if ( ! empty( $og_desc ) && is_string( $og_desc ) ) {
|
|
$item_data[] = array(
|
|
'name' => __( 'Custom OG Description', 'robotstxt-og' ),
|
|
'value' => $og_desc,
|
|
);
|
|
}
|
|
|
|
if ( ! empty( $item_data ) ) {
|
|
$data[] = array(
|
|
'group_id' => 'robotstxt-og-post-meta',
|
|
'group_label' => __( 'OpenGraph Post Meta', 'robotstxt-og' ),
|
|
'item_id' => 'post-' . $post_id,
|
|
'data' => $item_data,
|
|
);
|
|
}
|
|
}
|
|
|
|
$done = count( $posts ) < 100;
|
|
|
|
return array(
|
|
'data' => $data,
|
|
'done' => $done,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Erase personal data stored by this plugin for a given email address.
|
|
*
|
|
* Removes custom og:title and og:description from all posts authored by
|
|
* the user with the given email address.
|
|
*
|
|
* @since 1.0.3
|
|
*
|
|
* @param string $email_address User email address.
|
|
* @param int $page Pagination page (1-based).
|
|
* @return array{items_removed: int, items_retained: int, messages: string[], done: bool} Erase result.
|
|
*/
|
|
public function erase_privacy_data( string $email_address, int $page = 1 ): array {
|
|
$user = get_user_by( 'email', $email_address );
|
|
|
|
if ( ! $user ) {
|
|
return array(
|
|
'items_removed' => 0,
|
|
'items_retained' => 0,
|
|
'messages' => array(),
|
|
'done' => true,
|
|
);
|
|
}
|
|
|
|
$posts = get_posts(
|
|
array(
|
|
'author' => $user->ID,
|
|
'post_type' => 'any',
|
|
'post_status' => 'any',
|
|
'posts_per_page' => 100,
|
|
'paged' => $page,
|
|
'fields' => 'ids',
|
|
)
|
|
);
|
|
|
|
$items_removed = 0;
|
|
|
|
foreach ( $posts as $post_id ) {
|
|
$deleted_title = delete_post_meta( $post_id, '_og_title' );
|
|
$deleted_desc = delete_post_meta( $post_id, '_og_description' );
|
|
|
|
if ( $deleted_title || $deleted_desc ) {
|
|
++$items_removed;
|
|
}
|
|
}
|
|
|
|
$done = count( $posts ) < 100;
|
|
|
|
return array(
|
|
'items_removed' => $items_removed,
|
|
'items_retained' => 0,
|
|
'messages' => array(),
|
|
'done' => $done,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get image resolver instance.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return Robotstxt_OG_Image_Resolver
|
|
*/
|
|
public function get_resolver(): Robotstxt_OG_Image_Resolver {
|
|
return $this->resolver;
|
|
}
|
|
|
|
/**
|
|
* Get OG tags generator instance.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return Robotstxt_OG_Tags
|
|
*/
|
|
public function get_tags(): Robotstxt_OG_Tags {
|
|
return $this->tags;
|
|
}
|
|
}
|