load_options(); // Get details of installed git sourced plugins. $this->config = $this->get_plugin_meta(); if ( null === $this->config ) { return; } } /** * Returns an array of configurations for the known plugins. * * @return array */ public function get_plugin_configs() { return $this->config; } /** * Get details of Git-sourced plugins from those that are installed. * * @return array Indexed array of associative arrays of plugin details. */ protected function get_plugin_meta() { // Ensure get_plugins() function is available. include_once ABSPATH . '/wp-admin/includes/plugin.php'; $plugins = get_plugins(); $git_plugins = []; /** * Filter to add plugins not containing appropriate header line. * * @since 5.4.0 * @access public * * @param array $additions Listing of plugins to add. * Default null. * @param array $plugins Listing of all plugins. * @param string 'plugin' Type being passed. */ $additions = apply_filters( 'github_updater_additions', null, $plugins, 'plugin' ); $plugins = array_merge( $plugins, (array) $additions ); foreach ( (array) $plugins as $plugin => $headers ) { $git_plugin = []; foreach ( (array) static::$extra_headers as $value ) { $header = null; if ( empty( $headers[ $value ] ) || false === stripos( $value, 'Plugin' ) ) { continue; } $header_parts = explode( ' ', $value ); $repo_parts = $this->get_repo_parts( $header_parts[0], 'plugin' ); if ( $repo_parts['bool'] ) { $header = $this->parse_header_uri( $headers[ $value ] ); if ( empty( $header ) ) { continue; } } $header = $this->parse_extra_headers( $header, $headers, $header_parts, $repo_parts ); $current_branch = "current_branch_{$header['repo']}"; $branch = isset( static::$options[ $current_branch ] ) ? static::$options[ $current_branch ] : false; $git_plugin['type'] = 'plugin'; $git_plugin['git'] = $repo_parts['git_server']; $git_plugin['uri'] = "{$header['base_uri']}/{$header['owner_repo']}"; $git_plugin['enterprise'] = $header['enterprise_uri']; $git_plugin['enterprise_api'] = $header['enterprise_api']; $git_plugin['owner'] = $header['owner']; $git_plugin['slug'] = $header['repo']; $git_plugin['branch'] = $branch ?: 'master'; $git_plugin['file'] = $plugin; $git_plugin['local_path'] = WP_PLUGIN_DIR . "/{$header['repo']}/"; $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $git_plugin['file'] ); $git_plugin['author'] = $plugin_data['AuthorName']; $git_plugin['name'] = $plugin_data['Name']; $git_plugin['homepage'] = $plugin_data['PluginURI']; $git_plugin['local_version'] = strtolower( $plugin_data['Version'] ); $git_plugin['sections']['description'] = $plugin_data['Description']; $git_plugin['languages'] = $header['languages']; $git_plugin['ci_job'] = $header['ci_job']; $git_plugin['release_asset'] = $header['release_asset']; $git_plugin['broken'] = ( empty( $header['owner'] ) || empty( $header['repo'] ) ); $git_plugin['banners']['high'] = file_exists( WP_PLUGIN_DIR . "/{$header['repo']}/assets/banner-1544x500.png" ) ? WP_PLUGIN_URL . "/{$header['repo']}/assets/banner-1544x500.png" : null; $git_plugin['banners']['low'] = file_exists( WP_PLUGIN_DIR . "/{$header['repo']}/assets/banner-772x250.png" ) ? WP_PLUGIN_URL . "/{$header['repo']}/assets/banner-772x250.png" : null; $git_plugin['icons'] = []; $icons = [ 'svg' => 'icon.svg', '1x_png' => 'icon-128x128.png', '1x_jpg' => 'icon-128x128.jpg', '2x_png' => 'icon-256x256.png', '2x_jpg' => 'icon-256x256.jpg', ]; foreach ( $icons as $key => $filename ) { $key = preg_replace( '/_png|_jpg/', '', $key ); $git_plugin['icons'][ $key ] = file_exists( $git_plugin['local_path'] . 'assets/' . $filename ) ? WP_PLUGIN_URL . "/{$git_plugin['slug']}/assets/{$filename}" : null; } } // Exit if not git hosted plugin. if ( empty( $git_plugin ) ) { continue; } $git_plugins[ $git_plugin['slug'] ] = (object) $git_plugin; } return $git_plugins; } /** * Get remote plugin meta to populate $config plugin objects. * Calls to remote APIs to get data. */ public function get_remote_plugin_meta() { $plugins = []; foreach ( (array) $this->config as $plugin ) { /** * Filter to set if WP-Cron is disabled or if user wants to return to old way. * * @since 7.4.0 * @access public * * @param bool */ if ( ! $this->waiting_for_background_update( $plugin ) || static::is_wp_cli() || apply_filters( 'github_updater_disable_wpcron', false ) ) { $this->get_remote_repo_meta( $plugin ); } else { $plugins[ $plugin->slug ] = $plugin; } // current_filter() check due to calling hook for shiny updates, don't show row twice. if ( ! $plugin->release_asset && 'init' === current_filter() && ( ! is_multisite() || is_network_admin() ) ) { add_action( "after_plugin_row_{$plugin->file}", [ $this, 'plugin_branch_switcher' ], 15, 3 ); } } $schedule_event = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ? is_main_site() : true; if ( $schedule_event ) { if ( ! wp_next_scheduled( 'ghu_get_remote_plugin' ) && ! $this->is_duplicate_wp_cron_event( 'ghu_get_remote_plugin' ) && ! apply_filters( 'github_updater_disable_wpcron', false ) ) { wp_schedule_single_event( time(), 'ghu_get_remote_plugin', [ $plugins ] ); } } if ( ! static::is_wp_cli() ) { $this->load_pre_filters(); } } /** * Load pre-update filters. */ public function load_pre_filters() { add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 2 ); add_filter( 'plugins_api', [ $this, 'plugins_api' ], 99, 3 ); add_filter( 'site_transient_update_plugins', [ $this, 'update_site_transient' ], 15, 1 ); } /** * Add branch switch row to plugins page. * * @param string $plugin_file * @param \stdClass $plugin_data * * @return bool */ public function plugin_branch_switcher( $plugin_file, $plugin_data ) { if ( empty( static::$options['branch_switch'] ) ) { return false; } $enclosure = $this->update_row_enclosure( $plugin_file, 'plugin', true ); $plugin = $this->get_repo_slugs( dirname( $plugin_file ) ); $nonced_update_url = wp_nonce_url( $this->get_update_url( 'plugin', 'upgrade-plugin', $plugin_file ), 'upgrade-plugin_' . $plugin_file ); if ( ! empty( $plugin ) ) { $id = $plugin['slug'] . '-id'; $branches = isset( $this->config[ $plugin['slug'] ]->branches ) ? $this->config[ $plugin['slug'] ]->branches : null; } else { return false; } // Get current branch. $repo = $this->config[ $plugin['slug'] ]; $branch = Singleton::get_instance( 'Branch', $this )->get_current_branch( $repo ); $branch_switch_data = []; $branch_switch_data['slug'] = $plugin['slug']; $branch_switch_data['nonced_update_url'] = $nonced_update_url; $branch_switch_data['id'] = $id; $branch_switch_data['branch'] = $branch; $branch_switch_data['branches'] = $branches; /* * Create after_plugin_row_ */ echo $enclosure['open']; $this->make_branch_switch_row( $branch_switch_data ); echo $enclosure['close']; return true; } /** * Add 'View details' link to plugins page. * * @param array $links * @param string $file * * @return array $links */ public function plugin_row_meta( $links, $file ) { $regex_pattern = '/(.*)<\/a>/'; $repo = dirname( $file ); /* * Sanity check for some commercial plugins. */ if ( ! isset( $links[2] ) ) { return $links; } preg_match( $regex_pattern, $links[2], $matches ); /* * Remove 'Visit plugin site' link in favor or 'View details' link. */ if ( array_key_exists( $repo, $this->config ) ) { if ( null !== $repo ) { unset( $links[2] ); $links[] = sprintf( '%s', esc_url( add_query_arg( [ 'tab' => 'plugin-information', 'plugin' => $repo, 'TB_iframe' => 'true', 'width' => 600, 'height' => 550, ], network_admin_url( 'plugin-install.php' ) ) ), esc_html__( 'View details', 'github-updater' ) ); } } return $links; } /** * Put changelog in plugins_api, return WP.org data as appropriate * * @param bool $false * @param string $action * @param \stdClass $response * * @return mixed */ public function plugins_api( $false, $action, $response ) { if ( ! ( 'plugin_information' === $action ) ) { return $false; } $plugin = isset( $this->config[ $response->slug ] ) ? $this->config[ $response->slug ] : false; // Skip if waiting for background update. if ( $this->waiting_for_background_update( $plugin ) ) { return $false; } // wp.org plugin. if ( ! $plugin || ( $plugin->dot_org && 'master' === $plugin->branch ) ) { return $false; } $response->slug = $plugin->slug; $response->plugin_name = $plugin->name; $response->name = $plugin->name; $response->author = $plugin->author; $response->homepage = $plugin->homepage; $response->donate_link = $plugin->donate_link; $response->version = $plugin->remote_version; $response->sections = $plugin->sections; $response->requires = $plugin->requires; $response->requires_php = $plugin->requires_php; $response->tested = $plugin->tested; $response->downloaded = $plugin->downloaded; $response->last_updated = $plugin->last_updated; $response->download_link = $plugin->download_link; $response->banners = $plugin->banners; $response->icons = ! empty( $plugin->icons ) ? $plugin->icons : []; $response->contributors = $plugin->contributors; if ( ! $this->is_private( $plugin ) ) { $response->num_ratings = $plugin->num_ratings; $response->rating = $plugin->rating; } return $response; } /** * Hook into site_transient_update_plugins to update from GitHub. * * @param \stdClass $transient * * @return mixed */ public function update_site_transient( $transient ) { foreach ( (array) $this->config as $plugin ) { if ( $this->can_update_repo( $plugin ) ) { $response = [ 'slug' => $plugin->slug, 'plugin' => $plugin->file, 'new_version' => $plugin->remote_version, 'url' => $plugin->uri, 'package' => $plugin->download_link, 'icons' => $plugin->icons, 'tested' => $plugin->tested, 'requires_php' => $plugin->requires_php, 'branch' => $plugin->branch, 'branches' => array_keys( $plugin->branches ), 'type' => "{$plugin->git}-{$plugin->type}", ]; // Skip on RESTful updating. if ( isset( $_GET['action'], $_GET['plugin'] ) && 'github-updater-update' === $_GET['action'] && $response['slug'] === $_GET['plugin'] ) { continue; } // Pull update from dot org if not overriding. if ( ! $this->override_dot_org( 'plugin', $plugin ) ) { continue; } $transient->response[ $plugin->file ] = (object) $response; } else { /** * Filter to return array of overrides to dot org. * * @since 8.5.0 * @return array */ $overrides = apply_filters( 'github_updater_override_dot_org', [] ); if ( isset( $transient->response[ $plugin->file ] ) && in_array( $plugin->file, $overrides, true ) ) { unset( $transient->response[ $plugin->file ] ); } } // Set transient on rollback. if ( isset( $_GET['plugin'], $_GET['rollback'] ) && $plugin->file === $_GET['plugin'] ) { $transient->response[ $plugin->file ] = $this->set_rollback_transient( 'plugin', $plugin ); } } return $transient; } }