errors = new Errors( array_merge( $args, array( 'type' => $this->type ) ) ); //** Determine if Composer autoloader is included and modules classes are up to date */ $this->composer_dependencies(); //** Determine if plugin/theme requires or recommends another plugin(s) */ $this->plugins_dependencies(); // Maybe run install or upgrade processes. $this->maybe_run_upgrade_process(); //** Set install/upgrade pages if needed */ $this->define_splash_pages(); //** Maybe need to show UD splash page. Used static functions intentionaly. */ if ( !has_action( 'admin_init', array( Dashboard::get_instance(), 'maybe_ud_splash_page' ) ) ) { add_action( 'admin_init', array( Dashboard::get_instance(), 'maybe_ud_splash_page' ) ); } if ( !has_action( 'admin_menu', array( Dashboard::get_instance(), 'add_ud_splash_page') ) ) { add_action( 'admin_menu', array( Dashboard::get_instance(), 'add_ud_splash_page') ); } add_action( 'wp_ajax_ud_bootstrap_dismiss_notice', array( $this, 'ud_bootstrap_dismiss_notice' ) ); } /** * Initialize application. * Redeclare the method in final class! * * @author peshkov@UD */ public function init() {} /** * Determine if errors exist * Just wrapper. */ public function has_errors() { return $this->errors->has_errors(); } /** * @param string $key * @param mixed $value * * @author peshkov@UD * @return \UsabilityDynamics\Settings */ public function set( $key = null, $value = null ) { if( !is_object( $this->settings ) || !is_callable( array( $this->settings, 'set' ) ) ) { return false; } return $this->settings->set( $key, $value ); } /** * @param string $key * @param mixed $default * * @author peshkov@UD * @return \UsabilityDynamics\type */ public function get( $key = null, $default = null ) { if( !is_object( $this->settings ) || !is_callable( array( $this->settings, 'get' ) ) ) { return $default; } return $this->settings->get( $key, $default ); } /** * Returns specific schema from composer.json file. * * @param string $file Path to file * @author peshkov@UD * @return mixed array or false */ public function get_schema( $key = '' ) { if( $this->schema === null ) { if( !empty( $this->schema_path ) && file_exists( $this->schema_path ) ) { $this->schema = (array)\UsabilityDynamics\Utility::l10n_localize( json_decode( file_get_contents( $this->schema_path ), true ), (array)$this->get_localization() ); } } //** Break if composer.json does not exist */ if( !is_array( $this->schema ) ) { return false; } //** Resolve dot-notated key. */ if( strpos( $key, '.' ) ) { $current = $this->schema; $p = strtok( $key, '.' ); while( $p !== false ) { if( !isset( $current[ $p ] ) ) { return false; } $current = $current[ $p ]; $p = strtok( '.' ); } return $current; } //** Get default key */ else { return isset( $this->schema[ $key ] ) ? $this->schema[ $key ] : false; } } /** * Return localization's list. * * Example: * If schema contains l10n.{key} values: * * { 'config': 'l10n.hello_world' } * * the current function should return something below: * * return array( * 'hello_world' => __( 'Hello World', $this->domain ), * ); * * @author peshkov@UD * @return array */ public function get_localization() { return array(); } /** * Determine if product is just installed or upgraded * and run install/upgrade processes * * @author peshkov@UD */ protected function maybe_run_upgrade_process() { //** Determine what to show depending on version installed */ $version = get_option($this->slug . '-current-version', 0); $this->old_version = $version; //** Just installed */ if (!$version) { /* Run Install handlers */ add_action( 'plugins_loaded', array( $this, '_run_install_process' ), 0 ); } //** Upgraded */ elseif (version_compare($version, $this->args['version']) == -1) { /* Run Upgrade handlers */ add_action( 'plugins_loaded', array( $this, '_run_upgrade_process' ), 0 ); } // Need to save current version on plugins_loaded action, // unless _run_install_process and _run_upgrade_process not get called. add_action( 'plugins_loaded', array( $this, 'save_version_no' ), 100 ); } /** * Saving version no to database. * */ public function save_version_no($value=''){ update_option( $this->slug . '-current-version', $this->args['version'] ); } /** * Installation Handler * Internal method. Use run_install_process() instead */ public function _run_install_process() { /* Delete 'Install/Upgrade' notice 'dismissed' information */ delete_option( sanitize_key( 'dismiss_' . $this->slug . '_' . str_replace( '.', '_', $this->args['version'] ) . '_notice' ) ); /* Delete 'Bootstrap' notice 'dismissed' information */ delete_option( 'dismissed_notice_' . sanitize_key( $this->name ) ); $this->run_install_process(); } /** * Upgrade Handler * Internal method. Use run_upgrade_process() instead */ public function _run_upgrade_process() { /* Delete 'Install/Upgrade' notice 'dismissed' information */ delete_option( sanitize_key( 'dismiss_' . $this->slug . '_' . str_replace( '.', '_', $this->args['version'] ) . '_notice' ) ); /* Delete 'Bootstrap' notice 'dismissed' information */ delete_option( 'dismissed_notice_' . sanitize_key( $this->name ) ); $this->run_upgrade_process(); } /** * Run Install Process. * * Re-define the function in child. */ public function run_install_process() {} /** * Run Upgrade Process. * * Re-define the function in child. */ public function run_upgrade_process() {} /** * Define splash pages for plugins if needed * And Renders Admin Notice about installed product * * @return boolean * @author korotkov@UD * @author peshkov@UD */ public function define_splash_pages() { //** If not defined in schemas or not determined - skip */ if ( !$splashes = $this->get_schema( 'extra.splashes' ) ) { return false; } foreach( (array)$splashes as $splash => $shortpath ) { $path = $this->type == 'theme' ? get_template_directory() . '/' . ltrim( $shortpath, '/\\' ) : $this->path( $shortpath, 'dir' ); if( !file_exists( $path ) ) { unset( $splashes[ $splash ] ); } } //** If no splash templates files or missed 'install' splash - skip */ if( empty( $splashes ) || !isset( $splashes[ 'install' ] ) ) { return false; } $page = false; //** Determine what to show depending on version installed */ $version = get_option( $this->slug . '-splash-version', 0 ); //** Just installed */ if( !$version ) { $page = 'install'; } //** Upgraded */ elseif ( version_compare( $version, $this->args['version'] ) == -1 ) { if( isset( $splashes[ 'upgrade' ] ) ) { $page = 'upgrade'; } else { $page = 'install'; } } //** In other case do not do this */ else { /** * Maybe Render Install Notice * about current instance */ $option = sanitize_key( 'dismiss_' . $this->slug . '_' . str_replace( '.', '_', $this->args['version'] ) . '_notice' ); if( isset( $_REQUEST[ 'page' ] ) && $_REQUEST[ 'page' ] == Dashboard::get_instance()->page_slug && isset( $_REQUEST[ 'slug' ] ) && $_REQUEST[ 'slug' ] == $this->slug ) { /** Dismiss Admin Notice */ if( isset( $_REQUEST[ 'dismiss' ] ) ) { update_option( $option, array( 'slug' => $this->slug, 'type' => $this->type, 'version' => $this->args['version'] ) ); if( !function_exists( 'wp_redirect' ) ) { require_once( ABSPATH . 'wp-includes/pluggable.php' ); } if( !empty( $_SERVER[ 'HTTP_REFERER' ] ) ) { wp_redirect( $_SERVER[ 'HTTP_REFERER' ] ); } else { wp_redirect( admin_url( 'plugins.php' ) ); } exit; } /** Add information about product to Dashboard page */ else { if( isset( $splashes[ 'upgrade' ] ) ) { $page = 'upgrade'; } else { $page = 'install'; } } } if( !get_option( $option ) ) { add_action( 'admin_notices', array( $this, 'render_upgrade_notice' ), 1 ); } if( empty( $page ) ) { return false; } } $content = $this->root_path . ltrim( $splashes[$page], '/\\' ); //** Abort if no files exist */ if ( !file_exists( $content ) ) { return false; } //** Push data to temp transient */ $_current_pages_to_show = get_transient( Dashboard::get_instance()->transient_key ); //** If empty - create */ if ( !$_current_pages_to_show ) { set_transient( Dashboard::get_instance()->transient_key, array( $this->slug => array( 'name' => $this->name, 'content' => $content, 'version' => $this->args['version'] ) ), 30 ); } //** If not empty - update */ else { $_current_pages_to_show[$this->slug] = array( 'name' => $this->name, 'content' => $content, 'version' => $this->args['version'] ); set_transient( Dashboard::get_instance()->transient_key, $_current_pages_to_show, 30 ); } set_transient( Dashboard::get_instance()->need_splash_key, Dashboard::get_instance()->transient_key, 30 ); } /** * Renders Upgrade Notice. * */ public function render_upgrade_notice() { if( $this->type == 'theme' ) { if( !current_user_can( 'switch_themes' ) ) { return; } $icon = file_exists( get_template_directory() . '/static/images/icon.png' ) ? get_template_directory_uri() . '/static/images/icon.png' : false; } else { if( !current_user_can( 'activate_plugins' ) ) { return; } $icon = file_exists( $this->path( 'static/images/icon.png', 'dir' ) ) ? $this->path( 'static/images/icon.png', 'url' ) : false; } ob_start(); $vars = apply_filters( 'ud::bootstrap::upgrade_notice::vars', array( 'content' => false, 'icon' => $icon, 'name' => $this->name, 'type' => $this->type, 'slug' => $this->slug, 'version' => $this->args['version'], 'dashboard_link' => admin_url( 'index.php?page='. Dashboard::get_instance()->page_slug . '&slug=' . $this->slug ), 'dismiss_link' => admin_url( 'index.php?page='. Dashboard::get_instance()->page_slug . '&slug=' . $this->slug . '&dismiss=1' ), 'home_link' => !empty( $this->schema[ 'homepage' ] ) ? $this->schema[ 'homepage' ] : false, ) ); extract( $vars ); $content = ob_get_clean(); echo apply_filters( 'ud::bootstrap::upgrade_notice::template', $content, $this->slug, $vars ); } /** * Check plugins requirements * * @author peshkov@UD */ public function check_plugins_requirements() { //** Determine if we have TGMA Plugin Activation initialized. */ $is_tgma = $this->is_tgma; if( $is_tgma ) { $tgma = TGM_Plugin_Activation::get_instance(); //** Maybe get TGMPA notices. */ $notices = $tgma->notices( get_class( $this ) ); if( !empty( $notices[ 'messages' ] ) && is_array( $notices[ 'messages' ] ) ) { $error_links = false; $message_links = false; foreach( $notices[ 'messages' ] as $m ) { if( $m[ 'type' ] == 'error' ) $error_links = true; elseif( $m[ 'type' ] == 'message' ) $message_links = true; $this->errors->add( $m[ 'value' ], $m[ 'type' ] ); } //** Maybe add footer action links to errors and|or notices block. */ if( !empty( $notices[ 'links' ] ) && is_array( $notices[ 'links' ] ) ) { foreach( $notices[ 'links' ] as $type => $links ) { foreach( $links as $link ) { $this->errors->add_action_link( $link, $type ); } } } } } } /** * Maybe determines if Composer autoloader is included and modules classes are up to date * * @author peshkov@UD */ private function composer_dependencies() { $dependencies = $this->get_schema( 'extra.schemas.dependencies.modules' ); if( !empty( $dependencies ) && is_array( $dependencies ) ) { foreach( $dependencies as $module => $classes ) { if( !empty( $classes ) && is_array( $classes ) ) { foreach( $classes as $class => $v ) { if( !class_exists( $class ) ) { $this->errors->add( sprintf( __( 'Module %s is not installed or the version is old, class %s does not exist.', $this->domain ), $module, $class ) ); continue; } if ( '*' != trim( $v ) && ( !property_exists( $class, 'version' ) || $class::$version < $v ) ) { $this->errors->add( sprintf( __( 'Module %s should be updated to the latest version, class %s must have version %s or higher.', $this->domain ), $module, $class, $v ) ); } } } } } } /** * Determine if plugin/theme requires or recommends another plugin(s) * * @author peshkov@UD */ private function plugins_dependencies() { /** * Dependencies must be checked before plugins_loaded hook to prevent issues! * * The current condition fixes incorrect behaviour on custom 'Install Plugins' page * after activation plugin which has own dependencies. * * The condition belongs to WordPress 4.3 and higher. */ if( did_action( 'plugins_loaded' ) && $this->type == 'plugin' ) { return; } $plugins = $this->get_schema( 'extra.schemas.dependencies.plugins' ); if( !empty( $plugins ) && is_array( $plugins ) ) { $tgma = TGM_Plugin_Activation::get_instance(); foreach( $plugins as $plugin ) { $plugin[ '_referrer' ] = get_class( $this ); $plugin[ '_referrer_name' ] = $this->name; $tgma->register( $plugin ); } $this->is_tgma = true; } } /** * Defines License Client if 'licenses' schema is set * * @author peshkov@UD */ protected function define_license_client() { //** Break if we already have errors to prevent fatal ones. */ if( $this->has_errors() ) { return false; } //** Be sure we have licenses scheme to continue */ $schema = $this->get_schema( 'extra.schemas.licenses.client' ); if( !$schema ) { return false; } //** Licenses Manager */ if( !class_exists( '\UsabilityDynamics\UD_API\Bootstrap' ) ) { $this->errors->add( __( 'Class \UsabilityDynamics\UD_API\Bootstrap does not exist. Be sure all required plugins and (or) composer modules installed and activated.', $this->domain ) ); return false; } $args = $this->args; $args = array_merge( $args, array( 'type' => $this->type, 'name' => $this->name, 'slug' => $this->slug, 'referrer_slug' => $this->slug, 'domain' => $this->domain, 'errors_callback' => array( $this->errors, 'add' ), ), $schema ); if( empty( $args[ 'screen' ] ) ) { $this->errors->add( __( 'Licenses client can not be activated due to invalid \'licenses\' schema.', $this->domain ) ); } $this->client = new \UsabilityDynamics\UD_API\Bootstrap( $args ); } /** * Defines License Manager if 'license' schema is set * * @author peshkov@UD */ public function define_license_manager() { //** Break if we already have errors to prevent fatal ones. */ if( $this->has_errors() ) { return false; } //** Be sure we have license scheme to continue */ $schema = $this->get_schema( 'extra.schemas.licenses.product' ); if( !$schema ) { return false; } if( empty( $schema[ 'product_id' ] ) || ( empty( $schema[ 'referrer' ] ) && $this->type !== 'theme' ) ) { $this->errors->add( __( 'Product requires license, but product ID and (or) referrer is undefined. Please, be sure, that license schema has all required data.', $this->domain ), 'message' ); } $schema = array_merge( (array)$schema, array( 'type' => $this->type, 'name' => $this->name, 'boot_file' => $this->boot_file, 'errors_callback' => array( $this->errors, 'add' ) ) ); //** Licenses Manager */ if( !class_exists( '\UsabilityDynamics\UD_API\Manager' ) ) { //$this->errors->add( __( 'Class \UsabilityDynamics\UD_API\Manager does not exist. Be sure all required plugins installed and activated.', $this->domain ), 'message' ); return false; } $this->license_manager = new \UsabilityDynamics\UD_API\Manager( $schema ); return true; } public function ud_bootstrap_dismiss_notice() { $response = array( 'success' => '0', 'error' => __( 'There was an error in request.', $this->domain ), ); $error = false; if( empty( $_POST['key'] ) || empty( $_POST['slug'] ) || empty( $_POST['type'] ) || empty( $_POST['version'] ) ) { $response['error'] = __( 'Invalid values', $this->domain ); $error = true; } if ( ! $error && update_option( ( $_POST['key'] ), array( 'slug' => $_POST['slug'], 'type' => $_POST['type'], 'version' => $_POST['version'], ) ) ) { $response['success'] = '1'; } wp_send_json( $response ); } } } }