Making WordPress.org

Ticket #1569: 1569.diff

File 1569.diff, 20.7 KB (added by obenland, 9 years ago)
  • wp-content/plugins/plugin-directory/class-wporg-plugin-directory-review-admin.php

     
     1<?php
     2/**
     3 *
     4 */
     5
     6/**
     7 * Class WPorg_Review_Admin
     8 */
     9class WPorg_Review_Admin {
     10
     11        /**
     12         * Constructor.
     13         */
     14        public function __construct() {
     15                add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) );
     16        }
     17
     18        /**
     19         * @param WP_Post $post
     20         */
     21        public function edit_form_after_title( $post ) {
     22                $zip_files = get_attached_media( 'application/zip' );
     23                $zip_file  = current( $zip_files );
     24
     25                if ( $zip_file ) :
     26                        ?>
     27
     28                        <p style="padding: 0 10px;">
     29                                <?php printf( __( '<strong>Zip file:</strong> %s' ), sprintf( '<a href="%s">%s</a>', esc_url( $zip_file->guid ), $zip_file->guid ) ); ?>
     30                        </p>
     31
     32                        <?php
     33                endif;
     34        }
     35}
  • wp-content/plugins/plugin-directory/class-wporg-plugin-directory-upload-handler.php

    Property changes on: wp-content/plugins/plugin-directory/class-wporg-plugin-directory-review-admin.php
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1<?php
     2/**
     3 * @package WPorg_Plugin_Directory
     4 */
     5
     6/**
     7 * Class WPORG_Plugins_Upload
     8 *
     9 * Processes a plugin upload.
     10 */
     11class WPorg_Plugins_Upload {
     12
     13        /**
     14         * Path to `rm` script.
     15         *
     16         * @var string
     17         */
     18        const RM = '/bin/rm';
     19
     20        /**
     21         * Path to `unzip` script.
     22         *
     23         * @var string
     24         */
     25        const UNZIP = '/usr/bin/unzip';
     26
     27        /**
     28         * Path to temporary directory.
     29         *
     30         * @var string
     31         */
     32        protected $tmp_dir;
     33
     34        /**
     35         * Path to temporary plugin folder.
     36         *
     37         * @var string
     38         */
     39        protected $plugin_dir;
     40
     41        /**
     42         * The uploaded plugin.
     43         *
     44         * @var array
     45         */
     46        protected $plugin;
     47
     48        /**
     49         * The plugin slug.
     50         *
     51         * @var string
     52         */
     53        protected $plugin_slug;
     54
     55        /**
     56         * The plugin post if it already exists in the repository.
     57         *
     58         * @var WP_Post
     59         */
     60        protected $plugin_post;
     61
     62        /**
     63         * The plugin author (current user).
     64         *
     65         * @var WP_User
     66         */
     67        protected $author;
     68
     69        /**
     70         * Get set up to run tests on the uploaded plugin.
     71         */
     72        public function __construct() {
     73                require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
     74                require_once( ABSPATH . 'wp-admin/includes/image.php'  );
     75                require_once( ABSPATH . 'wp-admin/includes/file.php'   );
     76                require_once( ABSPATH . 'wp-admin/includes/media.php'  );
     77
     78                $this->create_tmp_dirs();
     79                $this->unwrap_package();
     80        }
     81
     82        /**
     83         * Processes the plugin upload.
     84         *
     85         * Runs various tests and creates plugin post.
     86         *
     87         * @return string Failure or success message.
     88         */
     89        public function process_upload() {
     90                $plugin_files = $this->get_all_files( $this->plugin_dir );
     91
     92                // First things first. Do we have something to work with?
     93                if ( empty( $plugin_files ) ) {
     94                        return __( 'The zip file was empty.', 'wporg-plugins' );
     95                }
     96
     97                foreach ( $plugin_files as $plugin_file ) {
     98                        if ( ! is_readable( $plugin_file ) ) {
     99                                continue;
     100                        }
     101
     102                        $plugin_data = get_plugin_data( $plugin_file, false, false ); // No markup/translation needed.
     103                        if ( ! empty( $plugin_data['Name'] ) ) {
     104                                $this->plugin = $plugin_data;
     105                                break;
     106                        }
     107                }
     108
     109                // Let's check some plugin headers, shall we?
     110
     111                if ( ! $this->plugin['Name'] ) {
     112                        $error = __( 'The plugin has no name.', 'wporg-plugins' ) . ' ';
     113
     114                        /* translators: 1: comment header line, 2: Codex URL */
     115                        $error .= sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%2$s">Plugin Headers</a>', 'wporg-plugins' ),
     116                                '<code>Plugin Name:</code>',
     117                                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
     118                        );
     119
     120                        return $error;
     121                }
     122
     123                // Determine the plugin slug based on the name of the plugin in the main plugin file.
     124                $this->plugin_slug = sanitize_title_with_dashes( $this->plugin['Name'] );
     125                $this->author      = wp_get_current_user();
     126
     127                // Make sure it doesn't use a slug deemed not to be used by the public.
     128                if ( $this->has_reserved_slug() ) {
     129                        /* translators: 1: plugin slug, 2: style.css */
     130                        return sprintf( __( 'Sorry, the plugin name %1$s is reserved for use by WordPress Core. Please change the name of your plugin and upload it again.', 'wporg-plugins' ),
     131                                '<code>' . $this->plugin_slug . '</code>'
     132                        );
     133                }
     134
     135                // Populate the plugin post and author.
     136                $this->plugin_post = $this->get_plugin_post();
     137
     138                // Is there already a plugin with the name name?
     139                if ( ! empty( $this->plugin_post ) ) {
     140                        /* translators: 1: plugin slug, 2: style.css */
     141                        return sprintf( __( 'There is already a plugin called %1$s by a different author. Please change the name of your plugin and upload it again.', 'wporg-plugins' ),
     142                                '<code>' . $this->plugin_slug . '</code>'
     143                        );
     144                }
     145
     146                $plugin_description = $this->strip_non_utf8( (string) $this->plugin['Description'] );
     147                if ( empty( $plugin_description ) ) {
     148                        $error = __( 'The plugin has no description.', 'wporg-plugins' ) . ' ';
     149
     150                        /* translators: 1: comment header line, 2: style.css, 3: Codex URL */
     151                        $error .= sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%3$s">Plugin Headers</a>', 'wporg-plugins' ),
     152                                '<code>Description:</code>',
     153                                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
     154                        );
     155
     156                        return $error;
     157                }
     158
     159                if ( ! $this->plugin['Version'] ) {
     160                        $error = __( 'The plugin has no version.', 'wporg-plugins' ) . ' ';
     161
     162                        /* translators: 1: comment header line, 2: style.css, 3: Codex URL */
     163                        $error .= sprintf( __( 'Add a %1$s line to your main plugin file and upload the plugin again. <a href="%3$s">Plugin Headers</a>', 'wporg-plugins' ),
     164                                '<code>Version:</code>',
     165                                __( 'https://codex.wordpress.org/File_Header', 'wporg-plugins' )
     166                        );
     167
     168                        return $error;
     169                }
     170
     171                if ( preg_match( '|[^\d\.]|', $this->plugin['Version'] ) ) {
     172                        /* translators: %s: style.css */
     173                        return sprintf( __( 'Version strings can only contain numeric and period characters (like 1.2). Please fix your %s line in your main plugin file and upload the plugin again.', 'wporg-plugins' ),
     174                                '<code>Version:</code>'
     175                        );
     176                }
     177
     178                // Prevent duplicate URLs.
     179                $plugin_uri = $this->plugin['PluginURI'];
     180                $author_uri = $this->plugin['AuthorURI'];
     181                if ( ! empty( $plugin_uri ) && ! empty( $author_uri ) && $plugin_uri == $author_uri ) {
     182                        return __( 'Duplicate plugin and author URLs. A plugin URL is a page/site that provides details about this specific plugin. An author URL is a page/site that provides information about the author of the plugin. You aren&rsquo;t required to provide both, so pick the one that best applies to your URL.', 'wporg-plugins' );
     183                }
     184
     185                // Don't send special plugins through Plugin Check.
     186                if ( ! has_category( 'special-case-plugin', $this->plugin_post ) ) {
     187                        // Pass it through Plugin Check and see how great this plugin really is.
     188                        $result = $this->check_plugin( $plugin_files );
     189
     190                        if ( ! $result ) {
     191                                /* translators: 1: Plugin Check Plugin URL, 2: make.wordpress.org/plugins */
     192                                return sprintf( __( 'Your plugin has failed the plugin check. Please correct the problems with it and upload it again. You can also use the <a href="%1$s">Plugin Check Plugin</a> to test your plugin before uploading. If you have any questions about this please post them to %2$s.', 'wporg-plugins' ),
     193                                        '//wordpress.org/plugins/plugin-check/',
     194                                        '<a href="https://make.wordpress.org/plugins">https://make.wordpress.org/plugins</a>'
     195                                );
     196                        }
     197                }
     198
     199                // Passed all tests!
     200                // Let's save everything and get things wrapped up.
     201
     202                // Add a Plugin Directory entry for this plugin.
     203                $post_id = $this->create_plugin_post();
     204
     205
     206                $attachment = $this->save_zip_file();
     207                if ( is_wp_error( $attachment ) ) {
     208                        return $attachment->get_error_message();
     209                }
     210
     211                // Send plugin author an email for peace of mind.
     212                $this->send_email_notification();
     213
     214                do_action( 'plugin_upload', $this->plugin, $this->plugin_post );
     215
     216                // Success!
     217                /* translators: 1: plugin name */
     218                return sprintf( __( 'Thank you for uploading %1$s to the WordPress Plugin Directory. We&rsquo;ve sent you an email verifying that we&rsquo;ve received it.', 'wporg-plugins' ),
     219                        $this->plugin['Name']
     220                );
     221        }
     222
     223        /**
     224         * Creates a temporary directory, and the plugin dir within it.
     225         */
     226        public function create_tmp_dirs() {
     227                // Create a temporary directory if it doesn't exist yet.
     228                $tmp = '/tmp/wporg-plugin-upload';
     229                if ( ! is_dir( $tmp ) ) {
     230                        mkdir( $tmp, 0777 );
     231                }
     232
     233                // Create file with unique file name.
     234                $this->tmp_dir = tempnam( $tmp, 'WPORG_PLUGIN_' );
     235
     236                // Remove that file.
     237                unlink( $this->tmp_dir );
     238
     239                // Create a directory with that unique name.
     240                mkdir( $this->tmp_dir, 0777 );
     241
     242                // Get a sanitized name for that plugin and create a directory for it.
     243                $base_name        = $this->get_sanitized_zip_name();
     244                $this->plugin_dir = "{$this->tmp_dir}/{$base_name}";
     245                mkdir( $this->plugin_dir, 0777 );
     246
     247                // Make sure we clean up after ourselves.
     248                add_action( 'shutdown', array( $this, 'remove_files' ) );
     249        }
     250
     251        /**
     252         * Unzips the uploaded plugin and saves it in the temporary plugin dir.
     253         */
     254        public function unwrap_package() {
     255                $unzip      = escapeshellarg( self::UNZIP );
     256                $zip_file   = escapeshellarg( $_FILES['zip_file']['tmp_name'] );
     257                $plugin_dir = escapeshellarg( $this->plugin_dir );
     258
     259                // Unzip it into the plugin directory.
     260                exec( escapeshellcmd( "{$unzip} -DD {$zip_file} -d {$plugin_dir}" ) );
     261
     262                // Fix any permissions issues with the files. Sets 755 on directories, 644 on files.
     263                exec( escapeshellcmd( "chmod -R 755 {$plugin_dir}" ) );
     264                exec( escapeshellcmd( "find {$plugin_dir} -type f -print0" ) . ' | xargs -I% -0 chmod 644 %' );
     265        }
     266
     267        /**
     268         * Returns the the plugin post if it already exists in the repository.
     269         *
     270         * @return WP_Post|null
     271         */
     272        public function get_plugin_post() {
     273                $plugins = get_posts( array(
     274                        'name'             => $this->plugin_slug,
     275                        'posts_per_page'   => 1,
     276                        'post_type'        => 'plugin',
     277                        'orderby'          => 'ID',
     278
     279                        /*
     280                         * Specify post stati so this query returns a result for draft plugins, even
     281                         * if the uploading user doesn't have have the permission to view drafts.
     282                         */
     283                        'post_status'      => array( 'publish', 'pending', 'draft', 'future', 'trash', 'suspend' ),
     284                        'suppress_filters' => false,
     285                ) );
     286
     287                return current( $plugins );
     288        }
     289
     290        /**
     291         * Whether the uploaded plugin uses a reserved slug.
     292         *
     293         * Passes if the author happens to be `wordpressdotorg`.
     294         *
     295         * @return bool
     296         */
     297        public function has_reserved_slug() {
     298                $reserved_slugs = array(
     299                        // Plugin Directory URL parameters.
     300                        'browse', 'tag', 'search', 'filter', 'upload',
     301                        'featured', 'popular', 'new', 'updated',
     302                );
     303
     304                return in_array( $this->plugin_slug, $reserved_slugs ) && 'wordpressdotorg' !== $this->author->user_login;
     305        }
     306
     307        /**
     308         * Sends a plugin through Plugin Check.
     309         *
     310         * @param array $files All plugin files to check.
     311         * @return bool Whether the plugin passed the checks.
     312         */
     313        public function check_plugin( $files ) {
     314
     315                // Run the checks.
     316                // @todo Include plugin checker.
     317                $result = true;
     318
     319                // Display the errors.
     320                $verdict = $result ? array( 'tc-pass', __( 'Pass', 'wporg-plugins' ) ) : array( 'tc-fail', __( 'Fail', 'wporg-plugins' ) );
     321                echo '<h4>' . sprintf( __( 'Results of Automated Plugin Scanning: %s', 'wporg-plugins' ), vsprintf( '<span class="%1$s">%2$s</span>', $verdict ) ) . '</h4>';
     322                echo '<ul class="tc-result">' . 'Result' . '</ul>';
     323                echo '<div class="notice notice-info"><p>' . __( 'Note: While the automated plugin scan is based on the Plugin Review Guidelines, it is not a complete review. A successful result from the scan does not guarantee that the plugin will pass review. All submitted plugins are reviewed manually before approval.', 'wporg-plugins' ) . '</p></div>';
     324
     325                return $result;
     326        }
     327
     328        /**
     329         * Creates a plugin post.
     330         *
     331         * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
     332         */
     333        public function create_plugin_post() {
     334                $upload_date = current_time( 'mysql' );
     335
     336                return wp_insert_post( array(
     337                        'post_author'    => $this->author->ID,
     338                        'post_title'     => $this->plugin['Name'],
     339                        'post_name'      => $this->plugin_slug,
     340                        'post_content'   => $this->plugin['Description'],
     341                        'post_date'      => $upload_date,
     342                        'post_date_gmt'  => $upload_date,
     343                        'comment_status' => 'closed',
     344                        'ping_status'    => 'closed',
     345                        'post_type'      => 'plugin',
     346                        'tags_input'     => $this->plugin['Tags'],
     347                ) );
     348        }
     349
     350        /**
     351         * Saves zip file and attaches it to the plugin post.
     352         *
     353         * @return int|WP_Error Attachment ID or upload error.
     354         */
     355        public function save_zip_file( $post_id ) {
     356                add_filter( 'site_option_upload_filetypes',         array( $this, 'whitelist_zip_files' ) );
     357                add_filter( 'default_site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
     358
     359                $attachment_id = media_handle_upload( 'zip_file', $post_id );
     360
     361                remove_filter( 'site_option_upload_filetypes',         array( $this, 'whitelist_zip_files' ) );
     362                remove_filter( 'default_site_option_upload_filetypes', array( $this, 'whitelist_zip_files' ) );
     363
     364                return $attachment_id;
     365        }
     366
     367        /**
     368         * Sends out an email confirmation to the plugin's author.
     369         */
     370        public function send_email_notification() {
     371
     372                /* translators: %s: plugin name */
     373                $email_subject = sprintf( __( '[WordPress Plugins] New Plugin - %s', 'wporg-plugins' ),
     374                        $this->plugin['Name']
     375                );
     376
     377                /* translators: 1: plugin name, 2: Trac ticket URL */
     378                $email_content = sprintf( __( 'Thank you for uploading %1$s to the WordPress Plugin Directory. If your plugin is selected to be part of the directory we\'ll send a follow up email.
     379
     380--
     381The WordPress.org Plugins Team
     382https://make.wordpress.org/plugins', 'wporg-plugins' ),
     383                        $this->plugin['Name']
     384                );
     385
     386                wp_mail( $this->author->user_email, $email_subject, $email_content, 'From: plugins@wordpress.org' );
     387        }
     388
     389        // Helper
     390
     391        /**
     392         * Returns a sanitized version of the uploaded zip file name.
     393         *
     394         * @return string
     395         */
     396        public function get_sanitized_zip_name() {
     397                return preg_replace( '|\W|', '', strtolower( basename( $_FILES['zip_file']['name'], '.zip' ) ) );
     398        }
     399
     400        /**
     401         * Returns all (usable) files of a given directory.
     402         *
     403         * @param string $dir Path to directory to search.
     404         * @return array All files within the passed directory.
     405         */
     406        public function get_all_files( $dir ) {
     407                $files        = array();
     408                $dir_iterator = new RecursiveDirectoryIterator( $dir );
     409                $iterator     = new RecursiveIteratorIterator( $dir_iterator, RecursiveIteratorIterator::SELF_FIRST );
     410
     411                foreach ( $iterator as $file ) {
     412                        // Only return files that are no directory references or Mac resource forks.
     413                        if ( $file->isFile() && ! in_array( $file->getBasename(), array( '..', '.' ) ) && ! stristr( $file->getPathname(), '__MACOSX' ) ) {
     414                                array_push( $files, $file->getPathname() );
     415                        }
     416                }
     417
     418                return $files;
     419        }
     420
     421        /**
     422         * Whitelist zip files to be allowed to be uploaded to the media library.
     423         *
     424         * @param string Whitelisted file extentions.
     425         * @return string Whitelisted file extentions.
     426         */
     427        public function whitelist_zip_files( $site_exts ) {
     428                $file_extenstions   = explode( ' ', $site_exts );
     429                $file_extenstions[] = 'zip';
     430
     431                return implode( ' ', array_unique( $file_extenstions ) );
     432        }
     433
     434        /**
     435         * Deletes the temporary directory.
     436         */
     437        public function remove_files() {
     438                $rm    = escapeshellarg( self::RM );
     439                $files = escapeshellarg( $this->tmp_dir );
     440
     441                exec( escapeshellcmd( "{$rm} -rf {$files}" ) );
     442        }
     443
     444        /**
     445         * Strips invalid UTF-8 characters.
     446         *
     447         * Non-UTF-8 characters in plugin descriptions will causes blank descriptions in plugins.trac.
     448         *
     449         * @param string $string The string to be converted.
     450         * @return string The converted string.
     451         */
     452        protected function strip_non_utf8( $string ) {
     453                ini_set( 'mbstring.substitute_character', 'none' );
     454
     455                return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
     456        }
     457}
  • wp-content/plugins/plugin-directory/shortcodes/plugin-upload.php

    Property changes on: wp-content/plugins/plugin-directory/class-wporg-plugin-directory-upload-handler.php
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1<?php
     2/**
     3 * @package WPorg_Plugin_Directory
     4 */
     5
     6/**
     7 * Registers the upload shortcode.
     8 */
     9function wporg_plugins_upload_shortcode() {
     10        add_shortcode( 'wporg-plugins-upload', 'wporg_plugins_render_upload_shortcode' );
     11}
     12add_action( 'init', 'wporg_plugins_upload_shortcode' );
     13
     14/**
     15 * Renders the upload shortcode.
     16 */
     17function wporg_plugins_render_upload_shortcode() {
     18        if ( is_user_logged_in() ) :
     19
     20                if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'wporg-plugins-upload' ) && 'upload' === $_POST['action'] ) {
     21                        $message = wporg_plugins_process_upload();
     22
     23                        if ( ! empty( $message ) ) {
     24                                echo "<div class='notice notice-warning'><p>{$message}</p></div>\n";
     25                        }
     26                }
     27        ?>
     28                <form enctype="multipart/form-data" id="upload_form" method="POST" action="">
     29                        <?php wp_nonce_field( 'wporg-plugins-upload' ); ?>
     30                        <input type="hidden" name="action" value="upload"/>
     31                        <input type="file" id="zip_file" name="zip_file" size="25"/>
     32                        <input id="upload_button" class="button" type="submit" value="<?php esc_attr_e( 'Upload', 'wporg-plugins' ); ?>"/>
     33
     34                        <p>
     35                                <small><?php printf( __( 'Maximum allowed file size: %s', 'wporg-plugins' ), esc_html( wporg_plugins_get_max_allowed_file_size() ) ); ?></small>
     36                        </p>
     37                </form>
     38        <?php else : ?>
     39                <p><?php printf( __( 'Before you can upload a new plugin, <a href="%s">please log in</a>.', 'wporg-plugins' ), esc_url( 'https://login.wordpress.org/' ) ); ?><p>
     40        <?php endif;
     41}
     42
     43/**
     44 * Returns a human readable version of the max allowed upload size.
     45 *
     46 * @return string The allowed file size.
     47 */
     48function wporg_plugins_get_max_allowed_file_size() {
     49        $upload_size_unit = wp_max_upload_size();
     50        $byte_sizes       = array( 'KB', 'MB', 'GB' );
     51
     52        for ( $unit = -1; $upload_size_unit > 1024 && $unit < count( $byte_sizes ) - 1; $unit++ ) {
     53                $upload_size_unit /= 1024;
     54        }
     55
     56        if ( $unit < 0 ) {
     57                $upload_size_unit = $unit = 0;
     58        } else {
     59                $upload_size_unit = (int) $upload_size_unit;
     60        }
     61
     62        return $upload_size_unit . $byte_sizes[ $unit ];
     63}
     64
     65/**
     66 * Runs basic checks and hands off to the upload processor.
     67 *
     68 * @return string Failure or success message.
     69 */
     70function wporg_plugins_process_upload( ) {
     71        if ( ! is_user_logged_in() ) {
     72                return __( 'You must be logged in to upload a new plugin.', 'wporg-plugins' );
     73        }
     74
     75        if ( 0 !== $_FILES['zip_file']['error'] ) {
     76                return __( 'Error in file upload.', 'wporg-plugins' );
     77        }
     78
     79        if ( ! class_exists( 'WPorg_Plugins_Upload' ) ) {
     80                include_once plugin_dir_path( __FILE__ ) . 'class-wporg-plugins-upload.php';
     81        }
     82
     83        switch_to_blog( WPORG_PLUGIN_DIRECTORY_BLOGID );
     84
     85        $upload  = new WPorg_Plugins_Upload;
     86        $message = $upload->process_upload();
     87
     88        restore_current_blog();
     89
     90        return $message;
     91}
  • wp-content/plugins/plugin-directory/wporg-plugin-directory.php

    Property changes on: wp-content/plugins/plugin-directory/shortcodes/plugin-upload.php
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    2020include __DIR__ . '/shortcodes/screenshots.php';
    2121
    2222$wporg_plugin_directory = new WPorg_Plugin_Directory();
     23
     24if ( is_admin() ) {
     25        include __DIR__ . '/class-wporg-plugin-directory-review-admin.php';
     26        $wporg_review_admin = new WPorg_Review_Admin();
     27} else {
     28        include __DIR__ . '/class-wporg-plugin-directory-upload-handler.php';
     29        include __DIR__ . '/shortcodes/plugin-upload.php';
     30}
     31
    2332register_activation_hook( __FILE__, array( $wporg_plugin_directory, 'activate' ) );
    2433register_deactivation_hook( __FILE__, array( $wporg_plugin_directory, 'deactivate' ) );