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 term meta (category/tag OG fields on term.php). $this->term_meta = new Robotstxt_OG_Term_Meta(); $this->term_meta->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(); $this->manager_notice = new Robotstxt_OG_Manager_Notice(); $this->manager_notice->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'; require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-term-meta.php'; // Load admin class if in admin context. if ( is_admin() ) { require_once ROBOTSTXT_OG_PATH . 'admin/class-robotstxt-og-admin-settings.php'; require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-manager-notice.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> $exporters List of exporters. * @return array> 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> $erasers List of erasers. * @return array> 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>, 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 ); $creator = get_post_meta( $post_id, '_twitter_creator', true ); $og_alt = get_post_meta( $post_id, '_og_image_alt', true ); if ( empty( $og_title ) && empty( $og_desc ) && empty( $creator ) && empty( $og_alt ) ) { 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( $creator ) && is_string( $creator ) ) { $item_data[] = array( 'name' => __( 'Twitter Author Handle', 'robotstxt-og' ), 'value' => $creator, ); } if ( ! empty( $og_alt ) && is_string( $og_alt ) ) { $item_data[] = array( 'name' => __( 'Custom OG Image Alt', 'robotstxt-og' ), 'value' => $og_alt, ); } 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 ) { $removed = false; foreach ( array( '_og_title', '_og_description', '_twitter_creator', '_og_image_alt' ) as $meta_key ) { if ( delete_post_meta( $post_id, $meta_key ) ) { $removed = true; } } if ( $removed ) { ++$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; } }