not_activated_addons[] = $installed_addon['name']; } } if ( ! empty( $this->not_activated_addons ) ) { add_action( 'admin_notices', array( $this, 'show_addons_activation_notice' ) ); } } } } /** * Add the Add-ons menu * * @since 1.3.6 * * @param array $menus * * @return array */ public function add_menu( $menus ) { $menus['addons'] = array( 'title' => __( 'Add-ons', ATUM_TEXT_DOMAIN ), 'callback' => array( $this, 'load_addons_page' ), 'slug' => ATUM_SHORT_NAME . '-addons', 'menu_order' => self::MENU_ORDER, ); return $menus; } /** * Initialize the installed ATUM's addons * * @since 1.2.0 */ public function init_addons() { if ( ! empty( self::$addons ) ) { foreach ( self::$addons as $addon ) { // Load the addon. Each addon should have a callback method for bootstraping. if ( ! empty( $addon['bootstrap'] ) && is_callable( $addon['bootstrap'] ) ) { call_user_func( $addon['bootstrap'] ); } } } } /** * Load the Addons page * * @since 1.2.0 */ public function load_addons_page() { wp_register_style( 'sweetalert2', ATUM_URL . 'assets/css/vendor/sweetalert2.min.css', array(), ATUM_VERSION ); wp_register_style( 'atum-addons', ATUM_URL . 'assets/css/atum-addons.css', array( 'sweetalert2' ), ATUM_VERSION ); wp_register_script( 'sweetalert2', ATUM_URL . 'assets/js/vendor/sweetalert2.min.js', array(), ATUM_VERSION, TRUE ); Helpers::maybe_es6_promise(); // ATUM marketing popup. AtumMarketingPopup::maybe_enqueue_scripts(); wp_register_script( 'atum-addons', ATUM_URL . 'assets/js/build/atum-addons.js', array( 'jquery', 'sweetalert2' ), ATUM_VERSION, TRUE ); wp_localize_script( 'atum-addons', 'atumAddons', array( 'error' => __( 'Error!', ATUM_TEXT_DOMAIN ), 'success' => __( 'Success!', ATUM_TEXT_DOMAIN ), 'activated' => __( 'Activated!', ATUM_TEXT_DOMAIN ), 'activation' => __( 'License Activation', ATUM_TEXT_DOMAIN ), 'activate' => __( 'Activate', ATUM_TEXT_DOMAIN ), 'addonActivated' => __( 'Your add-on license has been activated.', ATUM_TEXT_DOMAIN ), 'limitedDeactivations' => __( 'Limited Deactivations!', ATUM_TEXT_DOMAIN ), 'allowedDeactivations' => __( 'You are allowed to deactivate a license a max of 2 times.', ATUM_TEXT_DOMAIN ), 'ok' => __( 'OK', ATUM_TEXT_DOMAIN ), 'cancel' => __( 'Cancel', ATUM_TEXT_DOMAIN ), 'continue' => __( 'Continue', ATUM_TEXT_DOMAIN ), 'invalidKey' => __( 'Please enter a valid add-on license key.', ATUM_TEXT_DOMAIN ), ) ); wp_enqueue_style( 'sweetalert2' ); wp_enqueue_style( 'atum-addons' ); if ( wp_script_is( 'es6-promise', 'registered' ) ) { wp_enqueue_script( 'es6-promise' ); } wp_enqueue_script( 'sweetalert2' ); wp_enqueue_script( 'atum-addons' ); $args = array( 'addons' => $this->get_addons_list(), 'addons_keys' => self::get_keys(), ); Helpers::load_view( 'addons', $args ); } /** * Check for updates for the installed ATUM addons * * @since 1.2.0 */ public function check_addons_updates() { $license_keys = self::get_keys(); if ( ! empty( $license_keys ) ) { foreach ( $license_keys as $addon_name => $license_key ) { if ( $license_key && 'valid' === $license_key['status'] ) { // All the ATUM addons' names should start with 'ATUM'. $addon_info = Helpers::is_plugin_installed( 'ATUM ' . $addon_name, 'name', FALSE ); if ( $addon_info ) { // Setup the updater. $addon_file = key( $addon_info ); new Updater( $addon_file, array( 'version' => $addon_info[ $addon_file ]['Version'], 'license' => $license_key['key'], 'item_name' => $addon_name, 'beta' => FALSE, ) ); } } } } } /** * Get the list of available ATUM addons and their data * * @since 1.2.0 * * @return array|bool */ private function get_addons_list() { $transient_name = AtumCache::get_transient_key( 'addons_list' ); $addons = AtumCache::get_transient( $transient_name ); if ( ! $addons ) { $args = array( 'method' => 'POST', 'timeout' => 15, 'redirection' => 1, 'httpversion' => '1.0', 'user-agent' => 'ATUM/' . ATUM_VERSION . ';' . home_url(), 'blocking' => TRUE, 'headers' => array(), 'body' => array(), 'cookies' => array(), ); $response = wp_remote_post( self::ADDONS_STORE_URL . self::ADDONS_API_ENDPOINT, $args ); // Admin notification about the error. if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); if ( TRUE === ATUM_DEBUG ) { error_log( __METHOD__ . ": $error_message" ); } /* translators: error message displayed */ Helpers::display_notice( 'error', sprintf( __( "Something failed getting the ATUM's add-ons list: %s", ATUM_TEXT_DOMAIN ), $error_message ) ); return FALSE; } $response_body = wp_remote_retrieve_body( $response ); $addons = $response_body ? json_decode( $response_body, TRUE ) : array(); if ( empty( $addons ) ) { Helpers::display_notice( 'error', __( "Something failed getting the ATUM's add-ons list", ATUM_TEXT_DOMAIN ) ); return FALSE; } AtumCache::set_transient( $transient_name, $addons, DAY_IN_SECONDS ); } return $addons; } /** * Display an admin notice if there are ATUM add-ons installed but not activated for updates * * @since 1.4.4 */ public function show_addons_activation_notice() { $message = sprintf( /* translators: opening and closing HTML link to the add-ons page */ __( 'Please, activate %1$syour purchased ATUM premium add-ons%2$s to receive automatic updates.', ATUM_TEXT_DOMAIN ), '', '' ); Helpers::display_notice( 'info', $message, TRUE, 'activate-addons' ); } /** * Disable SSL verification in order to prevent download update failures * * @since 1.2.0 * * @param array $args * @param string $url * * @return array */ public function http_request_args( $args, $url ) { // If it is an https request and we are performing a package download, disable ssl verification. if ( strpos( $url, 'https://' ) !== FALSE && strpos( $url, 'package_download' ) !== FALSE && strpos( $url, self::ADDONS_STORE_URL ) !== FALSE ) { $args['sslverify'] = FALSE; } return $args; } /** * Get an addon key from database (if a valid name is passed as parameter) or all the registered addon keys (with no params) * * @since 1.2.0 * * @param string $addon_name Optional. The addon name from which get the key. * * @return string|array|bool */ public static function get_keys( $addon_name = '' ) { $keys = get_option( self::ADDONS_KEY_OPTION ); if ( $addon_name ) { if ( ! empty( $keys ) && is_array( $keys ) && isset( $keys[ $addon_name ] ) ) { return $keys[ $addon_name ]; } return ''; } return $keys; } /** * Generate a license API request * * @param string $addon_name The addon name (must match to the ATUM store's addon name). * @param string $key The license key. * @param string $endpoint The API endpoint. * @param array $extra_params Optional. Any other param that will be sent to the API. * * @return array|\WP_Error */ private static function api_request( $addon_name, $key, $endpoint, $extra_params = array() ) { $params = array_merge( $extra_params, array( 'edd_action' => $endpoint, 'license' => $key, 'item_name' => rawurlencode( $addon_name ), 'url' => home_url(), ) ); $request_params = array( 'timeout' => 15, 'sslverify' => FALSE, 'body' => $params, ); // Call the license manager API. return wp_remote_post( self::ADDONS_STORE_URL, $request_params ); } /** * Update the license key and its current status for the specified addon * * @since 1.2.0 * * @param string $addon_name The addon name. * @param array $key The license key. */ public static function update_key( $addon_name, $key ) { $keys = get_option( self::ADDONS_KEY_OPTION ); $keys[ $addon_name ] = $key; update_option( self::ADDONS_KEY_OPTION, $keys ); } /** * Get the status data of an ATUM addon knowing its name * * @since 1.2.0 * * @param string $addon_name The addon name (must match with the ATUM store's name). * @param string $addon_slug The addon slug (must match with the plugin directory name). * * @return array The addon status info. */ public static function get_addon_status( $addon_name, $addon_slug ) { // TODO: CLEAR TRANSIENTS WHEN AN ADD-ON IS DISABLED. $transient_name = AtumCache::get_transient_key( 'addon_status', $addon_name ); $addon_status = AtumCache::get_transient( $transient_name, TRUE ); if ( empty( $addon_status ) ) { // Status defaults. $addon_status = array( 'installed' => Helpers::is_plugin_installed( $addon_slug ), 'status' => 'invalid', 'key' => '', ); $saved_license = self::get_keys( $addon_name ); if ( ! empty( $saved_license ) ) { $addon_status['key'] = $saved_license['key']; // Check the license. $status = self::check_license( $addon_name, $addon_status['key'] ); if ( ! is_wp_error( $status ) ) { $license_data = json_decode( wp_remote_retrieve_body( $status ) ); if ( $license_data ) { $addon_status['status'] = $license_data->license; if ( $license_data->license !== $saved_license['status'] ) { $saved_license['status'] = $license_data->license; self::update_key( $addon_name, $saved_license ); } } } } else { self::update_key( $addon_name, [ 'key' => '', 'status' => 'invalid', ] ); } switch ( $addon_status['status'] ) { case 'invalid': case 'disabled': case 'expired': case 'item_name_mismatch': $addon_status['status'] = 'invalid'; $addon_status['button_text'] = __( 'Validate', ATUM_TEXT_DOMAIN ); $addon_status['button_class'] = 'validate-key'; $addon_status['button_action'] = ATUM_PREFIX . 'validate_license'; break; case 'inactive': case 'site_inactive': $addon_status['status'] = 'inactive'; $addon_status['button_text'] = __( 'Activate', ATUM_TEXT_DOMAIN ); $addon_status['button_class'] = 'activate-key'; $addon_status['button_action'] = ATUM_PREFIX . 'activate_license'; break; case 'valid': $addon_status['button_text'] = __( 'Deactivate', ATUM_TEXT_DOMAIN ); $addon_status['button_class'] = 'deactivate-key'; $addon_status['button_action'] = ATUM_PREFIX . 'deactivate_license'; break; } AtumCache::set_transient( $transient_name, $addon_status, DAY_IN_SECONDS, TRUE ); } return $addon_status; } /** * Delete an addon status transient * * @since 1.4.1.2 * * @param string $addon_name */ public static function delete_status_transient( $addon_name ) { $transient_name = AtumCache::get_transient_key( 'addon_status', $addon_name ); AtumCache::delete_transients( $transient_name ); } /* @noinspection PhpDocRedundantThrowsInspection */ /** * Download an ATUM addon and install it * * @since 1.2.0 * * @param string $addon_name The addon name. * @param string $addon_slug The addon slug. * @param string $download_link The link to download the addon zip file. * * @return array An array with the result and the message. * * @throws AtumException If the download fails could throw an exception. */ public static function install_addon( $addon_name, $addon_slug, $download_link ) { // Ensure that the download link URL is pointing to the right place. if ( ! filter_var( $download_link, FILTER_VALIDATE_URL ) || trailingslashit( wp_parse_url( $download_link, PHP_URL_SCHEME ) . '://' . wp_parse_url( $download_link, PHP_URL_HOST ) ) !== self::ADDONS_STORE_URL ) { return array( 'success' => FALSE, 'data' => __( 'The download link is not valid', ATUM_TEXT_DOMAIN ), ); } // Start the addon download and installation. require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; WP_Filesystem(); $skin = new \Automatic_Upgrader_Skin(); $upgrader = new \WP_Upgrader( $skin ); $plugin = "$addon_slug/$addon_slug.php"; $installed = Helpers::is_plugin_installed( $addon_slug ); $activate = $installed ? ! is_plugin_active( $plugin ) : FALSE; // Install this new addon. if ( ! $installed ) { // Suppress feedback. ob_start(); try { $download = $upgrader->download_package( $download_link ); if ( is_wp_error( $download ) ) { throw new AtumException( 'addon_download_error', $download->get_error_message() ); } $working_dir = $upgrader->unpack_package( $download, TRUE ); if ( is_wp_error( $working_dir ) ) { throw new AtumException( 'addon_unpack_error', $working_dir->get_error_message() ); } $result = $upgrader->install_package( array( 'source' => $working_dir, 'destination' => WP_PLUGIN_DIR, 'clear_destination' => FALSE, 'abort_if_destination_exists' => FALSE, 'clear_working' => TRUE, 'hook_extra' => array( 'type' => 'plugin', 'action' => 'install', ), ) ); if ( is_wp_error( $result ) ) { throw new AtumException( 'addon_not_installed', $result->get_error_message() ); } $activate = TRUE; } catch ( AtumException $e ) { return array( 'success' => FALSE, 'data' => sprintf( /* translators: first one is the add-on nam and the second the error message */ __( 'ATUM %1$s could not be installed (%2$s). Please download it from your account and install it manually.', ATUM_TEXT_DOMAIN ), $addon_name, $e->getMessage() ), ); } // Discard feedback. ob_end_clean(); } wp_clean_plugins_cache(); // Activate this thing. if ( $activate ) { try { $result = activate_plugin( $plugin ); if ( is_wp_error( $result ) ) { throw new AtumException( 'addon_activation_error', $result->get_error_message() ); } } catch ( AtumException $e ) { return array( 'success' => FALSE, 'data' => sprintf( /* translators: first one is the add-on nam, and the others are the opening and closing HTML link tags to the plugins page */ __( 'ATUM %1$s was installed but could not be activated. %2$sPlease activate it manually by clicking here.%3$s', ATUM_TEXT_DOMAIN ), $addon_name, '', '' ), ); } // Installed and activated. return array( 'success' => TRUE, /* translators: the add-on name */ 'data' => sprintf( __( 'The ATUM %s addon was installed and activated successfully.', ATUM_TEXT_DOMAIN ), $addon_name ), ); } // Installed. return array( 'success' => TRUE, /* translators: the add-on name */ 'data' => sprintf( __( 'The ATUM %s addon was installed successfully.', ATUM_TEXT_DOMAIN ), $addon_name ), ); } /** * Call to the license manager API to validate a license * * @since 1.2.0 * * @param string $addon_name The addon name (must match to the ATUM store's addon name). * @param string $key The license key. * * @return array|\WP_Error */ public static function check_license( $addon_name, $key ) { return self::api_request( $addon_name, $key, 'check_license' ); } /** * Call to the license manager API to activate a license * * @since 1.2.0 * * @param string $addon_name The addon name (must match to the ATUM store's addon name). * @param string $key The license key. * * @return array|\WP_Error */ public static function activate_license( $addon_name, $key ) { return self::api_request( $addon_name, $key, 'activate_license' ); } /** * Call to the license manager API to deactivate a license * * @since 1.2.0 * * @param string $addon_name The addon name (must match to the ATUM store's addon name). * @param string $key The license key. * * @return array|\WP_Error */ public static function deactivate_license( $addon_name, $key ) { return self::api_request( $addon_name, $key, 'deactivate_license' ); } /** * Call to the license manager API to get an addon version info * * @since 1.2.0 * * @param string $addon_name The addon name (must match to the ATUM store's addon name). * @param string $key The license key. * @param string $version The current addon version. * @param bool $beta Whether to look for beta versions. * * @return array|\WP_Error */ public static function get_version( $addon_name, $key, $version, $beta = FALSE ) { return self::api_request( $addon_name, $key, 'get_version', array( 'version' => $version, 'beta' => $beta, ) ); } /** * Getter for the installed addons arrray * * @since 1.2.0 * * @return array */ public static function get_installed_addons() { return self::$addons; } /** * Checks if there is a valid key installed in the current site * * @since 1.4.1.2 * * @return bool */ public static function has_valid_key() { $keys = self::get_keys(); if ( ! empty( $key ) ) { foreach ( $keys as $key ) { if ( ! empty( $key['key'] ) && ! empty( $key['status'] ) && 'valid' === $key['status'] ) { return TRUE; } } } return FALSE; } /**************************** * Instance methods ****************************/ /** * Cannot be cloned */ public function __clone() { _doing_it_wrong( __FUNCTION__, esc_attr__( 'Cheatin’ huh?', ATUM_TEXT_DOMAIN ), '1.0.0' ); } /** * Cannot be serialized */ public function __sleep() { _doing_it_wrong( __FUNCTION__, esc_attr__( 'Cheatin’ huh?', ATUM_TEXT_DOMAIN ), '1.0.0' ); } /** * Get Singleton instance * * @return Addons */ public static function get_instance() { if ( ! ( self::$instance && is_a( self::$instance, __CLASS__ ) ) ) { self::$instance = new self(); } return self::$instance; } }