v1.1.0
This commit is contained in:
parent
e7a4a45a74
commit
e80820f416
10 changed files with 389 additions and 9 deletions
|
|
@ -54,19 +54,297 @@ class Robotstxt_Manager_Installer {
|
|||
$name = $this->entry_name( $entry, $slug );
|
||||
$this->ensure_plugin_functions();
|
||||
|
||||
// Install missing dependencies first (ecosystem catalog plugins via
|
||||
// the store, WordPress.org plugins via their repository ZIPs).
|
||||
$deps_installed = $this->install_dependencies( $slug );
|
||||
|
||||
if ( is_wp_error( $deps_installed ) ) {
|
||||
$this->redirect_error( $deps_installed->get_error_message() );
|
||||
}
|
||||
|
||||
$result = $this->download_and_install( $slug );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$this->redirect_error( $result->get_error_message() );
|
||||
}
|
||||
|
||||
$this->redirect_success(
|
||||
sprintf(
|
||||
/* translators: %s: plugin name. */
|
||||
__( '%s installed. Activate it from the list below.', 'robotstxt-manager' ),
|
||||
$name
|
||||
$message = sprintf(
|
||||
/* translators: %s: plugin name. */
|
||||
__( '%s installed. Activate it from the list below.', 'robotstxt-manager' ),
|
||||
$name
|
||||
);
|
||||
|
||||
if ( is_string( $deps_installed ) && '' !== $deps_installed ) {
|
||||
$message .= ' ' . $deps_installed;
|
||||
}
|
||||
|
||||
$this->redirect_success( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the plugin's missing dependencies, dependencies first.
|
||||
*
|
||||
* Each dependency slug is resolved either against the store catalog
|
||||
* (installed through the store's download endpoint, like any catalog
|
||||
* plugin) or, when not in the catalog, against WordPress.org (installed
|
||||
* from the repository's plugin ZIP).
|
||||
*
|
||||
* @param string $slug Plugin slug being installed.
|
||||
*
|
||||
* @return string|WP_Error Installed-dependency names for the notice, '' when none were needed.
|
||||
*/
|
||||
private function install_dependencies( string $slug ) {
|
||||
$deps = $this->dependencies_for( $slug );
|
||||
|
||||
if ( array() === $deps ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$installed_names = array();
|
||||
|
||||
foreach ( $deps as $dep_slug ) {
|
||||
if ( '' !== $this->find_plugin_file( $dep_slug ) ) {
|
||||
continue; // Already installed.
|
||||
}
|
||||
|
||||
$result = $this->install_one_dependency( $dep_slug );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
/* translators: %s: dependency slug. */
|
||||
$detail = sprintf( __( 'Could not install the required plugin %s.', 'robotstxt-manager' ), $dep_slug );
|
||||
|
||||
return new WP_Error(
|
||||
'robotstxt_manager_dependency',
|
||||
$detail . ' ' . $result->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$installed_names[] = $result;
|
||||
}
|
||||
|
||||
if ( array() === $installed_names ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/* translators: %s: list of installed dependency names. */
|
||||
return sprintf( __( 'Installed required plugins: %s.', 'robotstxt-manager' ), implode( ', ', $installed_names ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a single dependency: catalog plugin via the store, else wp.org.
|
||||
*
|
||||
* @param string $dep_slug Dependency slug.
|
||||
*
|
||||
* @return string|WP_Error The dependency's display name on success.
|
||||
*/
|
||||
private function install_one_dependency( string $dep_slug ) {
|
||||
if ( null !== $this->find_catalog_entry( $dep_slug ) ) {
|
||||
return $this->install_via_store( $dep_slug );
|
||||
}
|
||||
|
||||
// Not in the catalog — treat as a WordPress.org plugin.
|
||||
return $this->install_via_wordpress_org( $dep_slug );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a dependency that exists in the store catalog.
|
||||
*
|
||||
* @param string $dep_slug Dependency slug.
|
||||
*
|
||||
* @return string|WP_Error Dependency name on success.
|
||||
*/
|
||||
protected function install_via_store( string $dep_slug ) {
|
||||
$result = $this->download_and_install( $dep_slug );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$entry = $this->find_catalog_entry( $dep_slug );
|
||||
|
||||
return $this->entry_name( is_array( $entry ) ? $entry : null, $dep_slug );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a dependency that is not in the catalog from WordPress.org.
|
||||
*
|
||||
* @param string $dep_slug wp.org plugin slug.
|
||||
*
|
||||
* @return string|WP_Error Plugin name on success.
|
||||
*/
|
||||
protected function install_via_wordpress_org( string $dep_slug ) {
|
||||
return $this->install_wporg_zip( $dep_slug );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a plugin's dependency slugs (parsed, deduplicated, deps-first
|
||||
* order, self-references dropped).
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*
|
||||
* @return list<string> Dependency slugs.
|
||||
*/
|
||||
private function dependencies_for( string $slug ): array {
|
||||
$all = array( $slug => true );
|
||||
$queue = array( $slug );
|
||||
$ordered = array();
|
||||
|
||||
while ( ! empty( $queue ) ) {
|
||||
$current = array_shift( $queue );
|
||||
|
||||
foreach ( $this->raw_dependencies_of( $current ) as $dep ) {
|
||||
if ( isset( $all[ $dep ] ) ) {
|
||||
continue; // Already seen: self, duplicate, or cycle.
|
||||
}
|
||||
|
||||
$all[ $dep ] = true;
|
||||
$ordered[] = $dep;
|
||||
$queue[] = $dep;
|
||||
}
|
||||
}
|
||||
|
||||
// Dependencies discovered later are deeper — reverse so dependencies
|
||||
// of dependencies install first.
|
||||
return array_reverse( $ordered );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the raw requires-plugins list of a catalog entry.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*
|
||||
* @return list<string> Dependency slugs (unresolved).
|
||||
*/
|
||||
private function raw_dependencies_of( string $slug ): array {
|
||||
$entry = $this->find_catalog_entry( $slug );
|
||||
|
||||
if ( null === $entry ) {
|
||||
return array(); // wp.org plugin: dependencies come from its own headers on install.
|
||||
}
|
||||
|
||||
$raw = $entry['requires_plugins'] ?? '';
|
||||
$raw = is_string( $raw ) ? $raw : '';
|
||||
|
||||
if ( '' === $raw ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$slugs = array();
|
||||
foreach ( explode( ',', $raw ) as $part ) {
|
||||
$dep = sanitize_key( trim( $part ) );
|
||||
if ( '' !== $dep ) {
|
||||
$slugs[ $dep ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys( $slugs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a WordPress.org plugin by slug via plugins_api + Plugin_Upgrader.
|
||||
*
|
||||
* @param string $slug wp.org plugin slug.
|
||||
*
|
||||
* @return string|WP_Error Plugin name on success.
|
||||
*/
|
||||
private function install_wporg_zip( string $slug ) {
|
||||
$this->ensure_plugin_functions();
|
||||
|
||||
if ( ! function_exists( 'plugins_api' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
}
|
||||
|
||||
$api = plugins_api(
|
||||
'plugin_information',
|
||||
array(
|
||||
'slug' => $slug,
|
||||
'fields' => array(
|
||||
'sections' => false,
|
||||
'versions' => false,
|
||||
'downloaded' => false,
|
||||
'rating' => false,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $api ) ) {
|
||||
/* translators: %s: error message from WordPress.org. */
|
||||
return new WP_Error( 'robotstxt_manager_wporg', sprintf( __( 'WordPress.org lookup failed: %s', 'robotstxt-manager' ), $api->get_error_message() ) );
|
||||
}
|
||||
|
||||
$download_link = is_object( $api ) && isset( $api->download_link ) && is_string( $api->download_link )
|
||||
? $api->download_link
|
||||
: '';
|
||||
|
||||
if ( '' === $download_link ) {
|
||||
/* translators: %s: plugin slug. */
|
||||
return new WP_Error( 'robotstxt_manager_wporg', sprintf( __( 'No download found on WordPress.org for %s.', 'robotstxt-manager' ), $slug ) );
|
||||
}
|
||||
|
||||
$tmp_file = wp_tempnam( $slug . '.zip' );
|
||||
|
||||
if ( ! $tmp_file ) {
|
||||
return new WP_Error( 'robotstxt_manager_temp', __( 'Could not create a temporary file for download.', 'robotstxt-manager' ) );
|
||||
}
|
||||
|
||||
$response = wp_remote_get(
|
||||
$download_link,
|
||||
array(
|
||||
'timeout' => 300,
|
||||
'stream' => true,
|
||||
'filename' => $tmp_file,
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
wp_delete_file( $tmp_file );
|
||||
|
||||
/* translators: %s: HTTP transport error message. */
|
||||
return new WP_Error( 'robotstxt_manager_download', sprintf( __( 'Download failed: %s', 'robotstxt-manager' ), $response->get_error_message() ) );
|
||||
}
|
||||
|
||||
if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
|
||||
wp_delete_file( $tmp_file );
|
||||
|
||||
/* translators: %d: HTTP status code. */
|
||||
return new WP_Error( 'robotstxt_manager_http', sprintf( __( 'Download failed (HTTP %d).', 'robotstxt-manager' ), (int) wp_remote_retrieve_response_code( $response ) ) );
|
||||
}
|
||||
|
||||
if ( ! $this->is_valid_zip( $tmp_file ) ) {
|
||||
wp_delete_file( $tmp_file );
|
||||
|
||||
return new WP_Error( 'robotstxt_manager_zip', __( 'The store returned an invalid file.', 'robotstxt-manager' ) );
|
||||
}
|
||||
|
||||
$upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() );
|
||||
$result = $upgrader->install(
|
||||
$tmp_file,
|
||||
array(
|
||||
'overwrite' => false,
|
||||
'overwrite_package' => false,
|
||||
)
|
||||
);
|
||||
|
||||
wp_delete_file( $tmp_file );
|
||||
|
||||
if ( true !== $result ) {
|
||||
$detail = ( $result instanceof WP_Error ) ? $result->get_error_message() : '';
|
||||
|
||||
/* translators: %s: upgrader error message. */
|
||||
return new WP_Error( 'robotstxt_manager_install', '' !== $detail ? sprintf( __( 'Installation failed: %s', 'robotstxt-manager' ), $detail ) : __( 'Installation failed.', 'robotstxt-manager' ) );
|
||||
}
|
||||
|
||||
$file = $this->find_plugin_file( $slug );
|
||||
|
||||
if ( '' === $file ) {
|
||||
/* translators: %s: plugin slug. */
|
||||
return new WP_Error( 'robotstxt_manager_wporg', sprintf( __( 'The downloaded plugin for %s does not have the expected folder structure.', 'robotstxt-manager' ), $slug ) );
|
||||
}
|
||||
|
||||
$data = get_plugins();
|
||||
$raw_name = isset( $data[ $file ]['Name'] ) && is_string( $data[ $file ]['Name'] ) ? $data[ $file ]['Name'] : '';
|
||||
|
||||
return '' !== $raw_name ? $raw_name : $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -135,6 +135,18 @@ $compat_warnings = 0;
|
|||
$website_url = '' !== $page_url ? $page_url : $homepage;
|
||||
$has_detail = ( '' !== $website_url || '' !== $desc );
|
||||
|
||||
$raw_reqs = $entry['requires_plugins'] ?? '';
|
||||
$req_slugs = array();
|
||||
if ( is_string( $raw_reqs ) && '' !== trim( $raw_reqs ) ) {
|
||||
foreach ( explode( ',', $raw_reqs ) as $part ) {
|
||||
$dep = sanitize_key( trim( $part ) );
|
||||
if ( '' !== $dep ) {
|
||||
$req_slugs[] = $dep;
|
||||
}
|
||||
}
|
||||
}
|
||||
$has_detail = $has_detail || array() !== $req_slugs;
|
||||
|
||||
// Compatibility checks.
|
||||
$wp_ok = '' === $req_wp || version_compare( $local_wp_version, $req_wp, '>=' );
|
||||
$php_ok = '' === $req_php || version_compare( $local_php_version, $req_php, '>=' );
|
||||
|
|
@ -251,6 +263,19 @@ $compat_warnings = 0;
|
|||
<?php if ( '' !== $desc ) : ?>
|
||||
<span class="robotstxt-manager-detail-description"><?php echo esc_html( $desc ); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ( array() !== $req_slugs ) : ?>
|
||||
<span class="robotstxt-manager-detail-requires">
|
||||
<?php
|
||||
echo esc_html(
|
||||
sprintf(
|
||||
/* translators: %s: list of plugin slugs. */
|
||||
__( 'Requires: %s', 'robotstxt-manager' ),
|
||||
implode( ', ', $req_slugs )
|
||||
)
|
||||
);
|
||||
?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
|
|
|||
Loading…
Reference in a new issue