* @return void */ public function __construct() { if ( is_admin() ) { add_action( 'admin_init', array( $this, 'redirect_to_wizard' ) ); add_action( 'upgrader_process_complete', array( $this, 'upgrade' ), 10, 2 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Support-only forced upgrade endpoint is protected by a secret key and only runs in admin. if ( isset( $_GET['affilizz_force_upgrade'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Support-only forced upgrade endpoint is protected by a secret key and only runs in admin. $force_upgrade_argument = sanitize_key( wp_unslash( $_GET['affilizz_force_upgrade'] ) ); if ( $force_upgrade_argument == self::$force_affilizz_update_key ) { $this->upgrade(); die(); } } } } /** * Checks the transient to display the wizard page. * @author Affilizz * @return void */ public function redirect_to_wizard() { if ( get_transient( 'affilizz_should_display_install_wizard' ) ) { // Prevent Ajax request catching if ( wp_doing_ajax() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin page check used during install redirect. if ( sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) ) !== '/affilizz-wizard' ) { delete_transient( 'affilizz_should_display_install_wizard' ); wp_safe_redirect( admin_url( 'admin.php?page=affilizz-wizard' ) ); exit; } } } /** * Sets up the plugin. * @author Affilizz * @return void */ public static function install() { if ( empty( get_option( 'affilizz_api_key' ) ) ) { set_transient( 'affilizz_should_display_install_wizard', 1, 0 ); // New installations use the hybrid rendering mode (existing sites are offered it by a notice) if ( false === get_option( 'affilizz_rendering_mode' ) ) { update_option( 'affilizz_rendering_mode', 'hybrid' ); } } else { delete_transient( 'affilizz_should_display_install_wizard' ); } // If we install the plugin and it's already been installed previously, upgrade it self::upgrade(); } /** * Creates the table if it does not exist. * @author Affilizz * @since 1.12.0 * @version 1.13.1 * @return void */ public static function create_table() { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $table_name = esc_sql( $wpdb->prefix . ( \Affilizz\Core::get_publications_database_table() ) ); $creation_query = "CREATE TABLE {$table_name} ( `affilizz_publication_id` int(11) NOT NULL AUTO_INCREMENT, `id` varchar(13) NOT NULL, `publication_id` varchar(32) NOT NULL, `publication_name` varchar(255) DEFAULT NULL, `publication_contents` json NOT NULL, `publication_channel_id` varchar(32) NOT NULL, `user_id` bigint(20) NOT NULL, `post_id` bigint(20) NOT NULL, `date_created` timestamp NOT NULL DEFAULT current_timestamp(), `date_modified` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), `data` text DEFAULT NULL, `render` mediumblob DEFAULT NULL, PRIMARY KEY (`affilizz_publication_id`), UNIQUE KEY `id` (`id`), KEY `publication_id` (`publication_id`), KEY `user_id` (`user_id`), KEY `post_id` (`post_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; dbDelta( $creation_query ); } /** * Drops the table if it does not exist. * @author Affilizz * @since 1.12.0 * @return void */ public static function drop_table() { global $wpdb; $table_name = esc_sql( $wpdb->prefix . ( \Affilizz\Core::get_publications_database_table() ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Intentional custom table deletion after explicit admin confirmation. return $wpdb->query( "DROP TABLE IF EXISTS `{$table_name}`" ); } /** * Creates or updates the table of the SSR fragments (hybrid rendering mode) : one fragment per content and per post. * @author Affilizz * @since 1.18.0 * @return void */ public static function create_fragments_table() { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; // dbDelta syntax : two spaces after PRIMARY KEY, no backticks in the key definitions $table_name = esc_sql( \Affilizz\Util\Fragments::get_table_name() ); $charset_collate = $wpdb->get_charset_collate(); dbDelta( "CREATE TABLE {$table_name} ( content_id varchar(64) NOT NULL, post_id bigint(20) unsigned NOT NULL DEFAULT 0, component varchar(16) NOT NULL DEFAULT 'rendering', html mediumtext NOT NULL, etag varchar(255) NOT NULL DEFAULT '', fetched_at int(10) unsigned NOT NULL DEFAULT 0, locked_until int(10) unsigned NOT NULL DEFAULT 0, PRIMARY KEY (content_id,post_id), KEY post_id (post_id) ) {$charset_collate};" ); update_option( 'affilizz_fragments_table_version', \Affilizz\Util\Fragments::TABLE_VERSION ); } /** * Drops the table of the SSR fragments (hybrid rendering mode). * @author Affilizz * @since 1.18.0 * @return void */ public static function drop_fragments_table() { global $wpdb; $table_name = esc_sql( \Affilizz\Util\Fragments::get_table_name() ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Intentional custom table deletion on uninstall. $wpdb->query( "DROP TABLE IF EXISTS `{$table_name}`" ); delete_option( 'affilizz_fragments_table_version' ); } /** * Upgrades the plugin. * @author Affilizz * @version 1.13.1 * @return void */ public static function upgrade() { global $wpdb; self::create_table(); self::create_fragments_table(); // Get the current API version, default to the first released version if inexistant (since we added this feature in 1.10.0) $current_plugin_version = get_option( 'affilizz_plugin_version' ); if ( empty( $current_plugin_version ) ) { $current_plugin_version = '1.0.0'; } if ( version_compare( $current_plugin_version, '1.10.0', '<' ) ) { // Upgrades the publication name to accomodate longer names on old publications $table_name = esc_sql( $wpdb->prefix . ( \Affilizz\Core::get_publications_database_table() ) ); $db_charset = esc_sql( DB_CHARSET ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Intentional activation upgrade schema migration with escaped identifiers. $wpdb->query( "ALTER TABLE `{$table_name}` CHANGE `publication_name` `publication_name` varchar(255) CHARACTER SET {$db_charset}" ); } // Adds the custom UUID for the proxy URL $affilizz_proxy_uuid = get_option( 'affilizz_proxy_uuid' ); if ( version_compare( $current_plugin_version, '1.11.0', '<' ) || empty( $affilizz_proxy_uuid ) ) { update_option( 'affilizz_proxy_uuid', wp_generate_uuid4() ); } if ( version_compare( $current_plugin_version, '1.13.1', '<' ) ) { // Upgrades the publication name to accomodate longer names on old publications $table_name = esc_sql( $wpdb->prefix . ( \Affilizz\Core::get_publications_database_table() ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Intentional activation upgrade schema migration with escaped identifiers. $wpdb->query( "ALTER TABLE `{$table_name}` CHANGE `data` `data` mediumblob" ); } // Bump the version to the installed version Core::regenerate_uuid(); // Flush rules flush_rewrite_rules(); // Create the cache directories ( Core::get_instance() )->init_cache_directories(); ( Core::get_instance() )->refresh_cache_directory( $affilizz_proxy_uuid ); // Post WordPress 6.4, update the autolading of options if ( version_compare( $current_plugin_version, '1.14.0', '>=' ) && function_exists( 'wp_set_option_autoload_values' ) ) { \wp_set_option_autoload_values( array( 'affilizz_channel_label', 'affilizz_media_label', 'affilizz_organization_label' ), 'no' ); } } }