<?php

/**
 * Manage plugins.
 *
 * @package wp-cli
 */
class Plugin_Command extends \WP_CLI\CommandWithUpgrade {

	protected $item_type = 'plugin';
	protected $upgrade_refresh = 'wp_update_plugins';
	protected $upgrade_transient = 'update_plugins';

	protected $obj_fields = array(
		'name',
		'status',
		'update',
		'version'
	);

	function __construct() {
		require_once ABSPATH.'wp-admin/includes/plugin.php';
		require_once ABSPATH.'wp-admin/includes/plugin-install.php';

		parent::__construct();

		$this->fetcher = new \WP_CLI\Fetchers\Plugin;
	}

	protected function get_upgrader_class( $force ) {
		return $force ? '\\WP_CLI\\DestructivePluginUpgrader' : 'Plugin_Upgrader';
	}

	/**
	 * See the status of one or all plugins.
	 *
	 * ## OPTIONS
	 *
	 * [<plugin>]
	 * : A particular plugin to show the status for.
	 */
	function status( $args ) {
		parent::status( $args );
	}

	/**
	 * Search the wordpress.org plugin repository.
	 *
	 * ## OPTIONS
	 *
	 * <search>
	 * : The string to search for.
	 *
	 * [--per-page=<per-page>]
	 * : Optional number of results to display. Defaults to 10.
	 *
	 * [--field=<field>]
	 * : Prints the value of a single field for each plugin.
	 *
	 * [--fields=<fields>]
	 * : Ask for specific fields from the API. Defaults to name,slug,author_profile,rating. Acceptable values:
	 *
	 *     **name**: Plugin Name
	 *     **slug**: Plugin Slug
	 *     **version**: Current Version Number
	 *     **author**: Plugin Author
	 *     **author_profile**: Plugin Author Profile
	 *     **contributors**: Plugin Contributors
	 *     **requires**: Plugin Minimum Requirements
	 *     **tested**: Plugin Tested Up To
	 *     **compatibility**: Plugin Compatible With
	 *     **rating**: Plugin Rating
	 *     **num_ratings**: Number of Plugin Ratings
	 *     **homepage**: Plugin Author's Homepage
	 *     **description**: Plugin's Description
	 *     **short_description**: Plugin's Short Description
	 *
	 * [--format=<format>]
	 * : Output list as table, CSV or JSON. Defaults to table.
	 *
	 * ## EXAMPLES
	 *
	 *     wp plugin search dsgnwrks --per-page=20 --format=json
	 *
	 *     wp plugin search dsgnwrks --fields=name,version,slug,rating,num_ratings
	 */
	public function search( $args, $assoc_args ) {
		parent::_search( $args, $assoc_args );
	}

	protected function status_single( $args ) {
		$plugin = $this->fetcher->get_check( $args[0] );
		$file = $plugin->file;

		$details = $this->get_details( $file );

		$status = $this->format_status( $this->get_status( $file ), 'long' );

		$version = $details['Version'];

		if ( $this->has_update( $file ) )
			$version .= ' (%gUpdate available%n)';

		echo WP_CLI::colorize( \WP_CLI\Utils\mustache_render( 'plugin-status.mustache', array(
			'slug' => $this->get_name( $file ),
			'status' => $status,
			'version' => $version,
			'name' => $details['Name'],
			'author' => $details['Author'],
			'description' => $details['Description']
		) ) );
	}

	protected function get_all_items() {
		$items = $this->get_item_list();

		foreach ( get_mu_plugins() as $file => $mu_plugin ) {
			$items[ $file ] = array(
				'name' => $this->get_name( $file ),
				'status' => 'must-use',
				'update' => false
			);
		}

		return $items;
	}

	/**
	 * Activate a plugin.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>...
	 * : One or more plugins to activate.
	 *
	 * [--network]
	 * : If set, the plugin will be activated for the entire multisite network.
	 */
	function activate( $args, $assoc_args = array() ) {
		$network_wide = isset( $assoc_args['network'] );

		foreach ( $this->fetcher->get_many( $args ) as $plugin ) {
			activate_plugin( $plugin->file, '', $network_wide );

			if ( $this->check_active( $plugin->file, $network_wide ) ) {
				if ( $network_wide )
					WP_CLI::success( "Plugin '{$plugin->name}' network activated." );
				else
					WP_CLI::success( "Plugin '{$plugin->name}' activated." );
			} else {
				WP_CLI::error( "Could not activate the '{$plugin->name}' plugin." );
			}
		}
	}

	/**
	 * Deactivate a plugin.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>...
	 * : One or more plugins to deactivate.
	 *
	 * [--network]
	 * : If set, the plugin will be deactivated for the entire multisite network.
	 */
	function deactivate( $args, $assoc_args = array() ) {
		$network_wide = isset( $assoc_args['network'] );

		foreach ( $this->fetcher->get_many( $args ) as $plugin ) {
			deactivate_plugins( $plugin->file, false, $network_wide );

			if ( ! $this->check_active( $plugin->file, $network_wide ) ) {
				if ( $network_wide )
					WP_CLI::success( "Plugin '{$plugin->name}' network deactivated." );
				else
					WP_CLI::success( "Plugin '{$plugin->name}' deactivated." );
			} else {
				WP_CLI::error( "Could not deactivate the '{$plugin->name}' plugin." );
			}
		}
	}

	/**
	 * Toggle a plugin's activation state.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>...
	 * : One or more plugins to toggle.
	 *
	 * [--network]
	 * : If set, the plugin will be toggled for the entire multisite network.
	 */
	function toggle( $args, $assoc_args = array() ) {
		$network_wide = isset( $assoc_args['network'] );

		foreach ( $this->fetcher->get_many( $args ) as $plugin ) {
			if ( $this->check_active( $plugin->file, $network_wide ) ) {
				$this->deactivate( array( $plugin->name ), $assoc_args );
			} else {
				$this->activate( array( $plugin->name ), $assoc_args );
			}
		}
	}

	/**
	 * Get the path to a plugin or to the plugin directory.
	 *
	 * ## OPTIONS
	 *
	 * [<plugin>]
	 * : The plugin to get the path to. If not set, will return the path to the
	 * plugins directory.
	 *
	 * [--dir]
	 * : If set, get the path to the closest parent directory, instead of the
	 * plugin file.
	 *
	 * ## EXAMPLES
	 *
	 *     cd $(wp plugin path)
	 */
	function path( $args, $assoc_args ) {
		$path = untrailingslashit( WP_PLUGIN_DIR );

		if ( !empty( $args ) ) {
			$plugins = $this->fetcher->get_many( $args );
			if ( empty( $plugins ) )
				return;

			list( $plugin ) = $plugins;

			$path .= '/' . $plugin->file;

			if ( isset( $assoc_args['dir'] ) )
				$path = dirname( $path );
		}

		WP_CLI::line( $path );
	}

	protected function install_from_repo( $slug, $assoc_args ) {
		$api = plugins_api( 'plugin_information', array( 'slug' => $slug ) );

		if ( is_wp_error( $api ) ) {
			return $api;
		}

		if ( isset( $assoc_args['version'] ) ) {
			self::alter_api_response( $api, $assoc_args['version'] );
		}

		$status = install_plugin_install_status( $api );

		if ( !isset( $assoc_args['force'] ) && 'install' != $status['status'] ) {
			// We know this will fail, so avoid a needless download of the package.
			return new WP_Error( 'already_installed', 'Plugin already installed.' );
		}

		WP_CLI::log( sprintf( 'Installing %s (%s)', $api->name, $api->version ) );
		if ( !isset( $assoc_args['version'] ) || 'dev' !== $assoc_args['version'] ) {
			WP_CLI::get_http_cache_manager()->whitelist_package( $api->download_link, $this->item_type, $api->slug, $api->version );
		}
		$result = $this->get_upgrader( $assoc_args )->install( $api->download_link );

		return $result;
	}

	/**
	 * Update one or more plugins.
	 *
	 * ## OPTIONS
	 *
	 * [<plugin>...]
	 * : One or more plugins to update.
	 *
	 * [--all]
	 * : If set, all plugins that have updates will be updated.
	 *
	 * [--version=<version>]
	 * : If set, the plugin will be updated to the latest development version,
	 * regardless of what version is currently installed.
	 *
	 * [--dry-run]
	 * : Preview which plugins would be updated.
	 *
	 * ## EXAMPLES
	 *
	 *     wp plugin update bbpress --version=dev
	 *
	 *     wp plugin update --all
	 */
	function update( $args, $assoc_args ) {
		if ( isset( $assoc_args['version'] ) && 'dev' == $assoc_args['version'] ) {
			foreach ( $this->fetcher->get_many( $args ) as $plugin ) {
				$this->_delete( $plugin );
				$this->install( array( $plugin->name ), $assoc_args );
			}
		} else {
			parent::update_many( $args, $assoc_args );
		}
	}

	protected function get_item_list() {
		$items = array();

		foreach ( get_plugins() as $file => $details ) {
			$update_info = $this->get_update_info( $file );

			$items[ $file ] = array(
				'name' => $this->get_name( $file ),
				'status' => $this->get_status( $file ),
				'update' => (bool) $update_info,
				'update_version' => $update_info['new_version'],
				'update_package' => $update_info['package'],
				'version' => $details['Version'],
				'update_id' => $file,
				'title' => $details['Name'],
				'description' => wordwrap( $details['Description'] ),
			);
		}

		return $items;
	}

	protected function filter_item_list( $items, $args ) {
		$basenames = wp_list_pluck( $this->fetcher->get_many( $args ), 'file' );
		return \WP_CLI\Utils\pick_fields( $items, $basenames );
	}

	/**
	 * Install a plugin.
	 *
	 * ## OPTIONS
	 *
	 * <plugin|zip|url>...
	 * : A plugin slug, the path to a local zip file, or URL to a remote zip file.
	 *
	 * [--version=<version>]
	 * : If set, get that particular version from wordpress.org, instead of the
	 * stable version.
	 *
	 * [--force]
	 * : If set, the command will overwrite any installed version of the plugin, without prompting
	 * for confirmation.
	 *
	 * [--activate]
	 * : If set, the plugin will be activated immediately after install.
	 *
	 * ## EXAMPLES
	 *
	 *     # Install the latest version from wordpress.org and activate
	 *     wp plugin install bbpress --activate
	 *
	 *     # Install the development version from wordpress.org
	 *     wp plugin install bbpress --version=dev
	 *
	 *     # Install from a local zip file
	 *     wp plugin install ../my-plugin.zip
	 *
	 *     # Install from a remote zip file
	 *     wp plugin install http://s3.amazonaws.com/bucketname/my-plugin.zip?AWSAccessKeyId=123&Expires=456&Signature=abcdef
	 */
	function install( $args, $assoc_args ) {
		parent::install( $args, $assoc_args );
	}

	/**
	 * Get plugin download link.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>
	 * : The plugin to get the download link.
	 *
	 * ## EXAMPLES
	 *
	 *     wget $(wp plugin get-download-link bbpress)
	 *
	 * @subcommand get-download-link
	 */
	public function get_download_link( $args, $assoc_args ) {
		$api = plugins_api( 'plugin_information', array( 'slug' => $args[0] ) );
		if ( is_wp_error( $api ) ) {
			WP_CLI::line( '' );
		}
		WP_CLI::line( $api->download_link );
	}

	/**
	 * Get a plugin.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>
	 * : The plugin to get.
	 *
	 * [--field=<field>]
	 * : Instead of returning the whole plugin, returns the value of a single field.
	 *
	 * [--format=<format>]
	 * : Output list as table or JSON. Defaults to table.
	 *
	 * ## EXAMPLES
	 *
	 *     wp plugin get bbpress --format=json
	 */
	public function get( $args, $assoc_args ) {
		$plugin = $this->fetcher->get_check( $args[0] );
		$file = $plugin->file;

		$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $file, false, false );

		$plugin_obj = (object)array(
			'name'        => $this->get_name( $file ),
			'title'       => $plugin_data['Name'],
			'author'      => $plugin_data['Author'],
			'version'     => $plugin_data['Version'],
			'description' => wordwrap( $plugin_data['Description'] ),
			'status'      => $this->get_status( $file ),
			'update'      => $this->has_update( $file ),
		);

		$formatter = $this->get_formatter( $assoc_args );
		$formatter->display_item( $plugin_obj );
	}

	/**
	 * Uninstall a plugin.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>...
	 * : One or more plugins to uninstall.
	 *
	 * [--no-delete]
	 * : If set, the plugin files will not be deleted. Only the uninstall procedure
	 * will be run.
	 *
	 * ## EXAMPLES
	 *
	 *     wp plugin uninstall hello
	 */
	function uninstall( $args, $assoc_args = array() ) {
		foreach ( $this->fetcher->get_many( $args ) as $plugin ) {
			if ( is_plugin_active( $plugin->file ) ) {
				WP_CLI::error( "The '{$plugin->name}' plugin is active." );
				continue;
			}

			uninstall_plugin( $plugin->file );

			if ( !isset( $assoc_args['no-delete'] ) && $this->_delete( $plugin ) ) {
				WP_CLI::success( "Uninstalled '$plugin->name' plugin." );
			}
		}
	}

	/**
	 * Check if the plugin is installed.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>
	 * : The plugin to check.
	 *
	 * ## EXAMPLES
	 *
	 *     wp plugin is-installed hello
	 *
	 * @subcommand is-installed
	 */
	function is_installed( $args, $assoc_args = array() ) {
		if ( $this->fetcher->get( $args[0] ) ) {
			exit( 0 );
		} else {
			exit( 1 );
		}
	}

	/**
	 * Delete plugin files.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>...
	 * : One or more plugins to delete.
	 *
	 * ## EXAMPLES
	 *
	 *     wp plugin delete hello
	 */
	function delete( $args, $assoc_args = array() ) {
		foreach ( $this->fetcher->get_many( $args ) as $plugin ) {
			if ( $this->_delete( $plugin ) ) {
				WP_CLI::success( "Deleted '{$plugin->name}' plugin." );
			}
		}
	}

	/**
	 * Get a list of plugins.
	 *
	 * ## OPTIONS
	 *
	 * [--<field>=<value>]
	 * : Filter results based on the value of a field.
	 *
	 * [--field=<field>]
	 * : Prints the value of a single field for each plugin.
	 *
	 * [--fields=<fields>]
	 * : Limit the output to specific object fields. Defaults to name,status,update,version.
	 *
	 * [--format=<format>]
	 * : Output list as table, CSV or JSON. Defaults to table.
     *
     * [--check-updates=<check-updates>]
     * : Check updates for instance:
     *
     *    **true**: check updates for instance.
     *    **false**: do not check updates for instance.
	 *
	 * ## EXAMPLES
	 *
	 *     wp plugin list --status=active --format=json
	 *
	 * @subcommand list
	 */
	function _list( $_, $assoc_args ) {
		parent::_list( $_, $assoc_args );
	}

	/* PRIVATES */

	private function check_active( $file, $network_wide ) {
		$required = $network_wide ? 'active-network' : 'active';

		return $required == $this->get_status( $file );
	}

	protected function get_status( $file ) {
		if ( is_plugin_active_for_network( $file ) )
			return 'active-network';

		if ( is_plugin_active( $file ) )
			return 'active';

		return 'inactive';
	}

	/**
	 * Get the details of a plugin.
	 *
	 * @param object
	 * @return array
	 */
	private function get_details( $file ) {
		$plugin_folder = get_plugins(  '/' . plugin_basename( dirname( $file ) ) );
		$plugin_file = basename( $file );

		return $plugin_folder[$plugin_file];
	}

	/**
	 * Converts a plugin basename back into a friendly slug.
	 */
	private function get_name( $file ) {
		if ( false === strpos( $file, '/' ) )
			$name = basename( $file, '.php' );
		else
			$name = dirname( $file );

		return $name;
	}

	private function _delete( $plugin ) {
		$plugin_dir = dirname( $plugin->file );
		if ( '.' == $plugin_dir )
			$plugin_dir = $plugin->file;

		$path = path_join( WP_PLUGIN_DIR, $plugin_dir );

		if ( \WP_CLI\Utils\is_windows() ) {
			$command = 'rd /s /q ';
			$path = str_replace( "/", DIRECTORY_SEPARATOR, $path );
		} else {
			$command = 'rm -rf ';
		}
		
		$this->_check_content_direcory_permissions();
		
		// check, that plugin files is writeable
		if ( !is_writable( $path ) ) {
			\WP_CLI::error( "Unable to remove plugin files: $path", 40007 );
		}

		return ! WP_CLI::launch( $command . $path );
	}
}

WP_CLI::add_command( 'plugin', 'Plugin_Command' );

