WordPress.org

Make WordPress Core


Ignore:
Timestamp:
09/24/2018 03:08:32 PM (23 months ago)
Author:
flixos90
Message:

Multisite: Introduce a site initialization and uninitialization API.

This changeset makes the new CRUD API for sites introduced in [43548] usable for real-world sites. A new function wp_initialize_site(), which takes care of creating a site's database tables and populating them with initial values, is hooked into the site insertion process that is initiated when calling wp_insert_site(). Similarly, a new function wp_uninitialize_site(), which takes care of dropping a site's database tables, is hooked into the site deletion process that is initiated when calling wp_delete_site().

A new function wp_is_site_initialized() completes the API, allowing to check whether a site is initialized. Since this function always makes a database request in its default behavior, it should be called with caution. Plugins that would like to use site initialization in special ways can leverage a pre_wp_is_site_initialized filter to alter that default behavior.

The separate handling of the site's row in the wp_blogs database table and the actual site setup allows for more flexibility in controlling whether or how a site's data is set up. For example, a unit test that only checks data from the site's database table row can unhook the site initialization process to improve performance. At the same time, developers consuming the new sites API only need to know about the CRUD functions, since the initialization and uninitialization processes happen internally.

With this changeset, the foundation for a sites REST API endpoint is fully available. The previously recommended functions wpmu_create_blog() and wpmu_delete_blog() now call the new respective function internally. Further follow-up work to this includes replacing calls to wpmu_create_blog() with wp_insert_site(), update_blog_details() with wp_update_site() and wpmu_delete_blog() with wp_delete_blog() throughout the codebase.

As a side-effect of this work, the wpmu_new_blog, delete_blog, and deleted_blog actions and the install_blog() function have been deprecated.

Fixes #41333. See #40364.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/ms-blogs.php

    r43571 r43654  
    443443    );
    444444
     445    // Extract the passed arguments that may be relevant for site initialization.
     446    $args = array_diff_key( $data, $defaults );
     447    if ( isset( $args['site_id'] ) ) {
     448        unset( $args['site_id'] );
     449    }
     450
    445451    $data = wp_prepare_site_data( $data, $defaults );
    446452    if ( is_wp_error( $data ) ) {
     
    464470     */
    465471    do_action( 'wp_insert_site', $new_site );
     472
     473    /**
     474     * Fires when a site's initialization routine should be executed.
     475     *
     476     * @since 5.0.0
     477     *
     478     * @param WP_Site $new_site New site object.
     479     * @param array   $args     Arguments for the initialization.
     480     */
     481    do_action( 'wp_initialize_site', $new_site, $args );
     482
     483    // Only compute extra hook parameters if the deprecated hook is actually in use.
     484    if ( has_action( 'wpmu_new_blog' ) ) {
     485        $user_id = ! empty( $args['user_id'] ) ? $args['user_id'] : 0;
     486        $meta    = ! empty( $args['options'] ) ? $args['options'] : array();
     487
     488        /**
     489         * Fires immediately after a new site is created.
     490         *
     491         * @since MU (3.0.0)
     492         * @deprecated 5.0.0 Use wp_insert_site
     493         *
     494         * @param int    $site_id    Site ID.
     495         * @param int    $user_id    User ID.
     496         * @param string $domain     Site domain.
     497         * @param string $path       Site path.
     498         * @param int    $network_id Network ID. Only relevant on multi-network installations.
     499         * @param array  $meta       Meta data. Used to set initial site options.
     500         */
     501        do_action_deprecated( 'wpmu_new_blog', array( $new_site->id, $user_id, $new_site->domain, $new_site->path, $new_site->network_id, $meta ), '5.0.0', 'wp_insert_site' );
     502    }
    466503
    467504    return (int) $new_site->id;
     
    544581    }
    545582
     583    $errors = new WP_Error();
     584
     585    /**
     586     * Fires before a site should be deleted from the database.
     587     *
     588     * Plugins should amend the `$errors` object via its `WP_Error::add()` method. If any errors
     589     * are present, the site will not be deleted.
     590     *
     591     * @since 5.0.0
     592     *
     593     * @param WP_Error $errors   Error object to add validation errors to.
     594     * @param WP_Site  $old_site The site object to be deleted.
     595     */
     596    do_action( 'wp_validate_site_deletion', $errors, $old_site );
     597
     598    if ( ! empty( $errors->errors ) ) {
     599        return $errors;
     600    }
     601
     602    /**
     603     * Fires before a site is deleted.
     604     *
     605     * @since MU (3.0.0)
     606     * @deprecated 5.0.0
     607     *
     608     * @param int  $site_id The site ID.
     609     * @param bool $drop    True if site's table should be dropped. Default is false.
     610     */
     611    do_action_deprecated( 'delete_blog', array( $old_site->id, true ), '5.0.0' );
     612
     613    /**
     614     * Fires when a site's uninitialization routine should be executed.
     615     *
     616     * @since 5.0.0
     617     *
     618     * @param WP_Site $old_site Deleted site object.
     619     */
     620    do_action( 'wp_uninitialize_site', $old_site );
     621
     622    if ( is_site_meta_supported() ) {
     623        $blog_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->blogmeta WHERE blog_id = %d ", $old_site->id ) );
     624        foreach ( $blog_meta_ids as $mid ) {
     625            delete_metadata_by_mid( 'blog', $mid );
     626        }
     627    }
     628
    546629    if ( false === $wpdb->delete( $wpdb->blogs, array( 'blog_id' => $old_site->id ) ) ) {
    547630        return new WP_Error( 'db_delete_error', __( 'Could not delete site from the database.' ), $wpdb->last_error );
     
    558641     */
    559642    do_action( 'wp_delete_site', $old_site );
     643
     644    /**
     645     * Fires after the site is deleted from the network.
     646     *
     647     * @since 4.8.0
     648     * @deprecated 5.0.0
     649     *
     650     * @param int  $site_id The site ID.
     651     * @param bool $drop    True if site's tables should be dropped. Default is false.
     652     */
     653    do_action_deprecated( 'deleted_blog', array( $old_site->id, true ), '5.0.0' );
    560654
    561655    return $old_site;
     
    620714    $non_cached_ids = _get_non_cached_ids( $ids, 'sites' );
    621715    if ( ! empty( $non_cached_ids ) ) {
    622         $fresh_sites = $wpdb->get_results( sprintf( "SELECT * FROM $wpdb->blogs WHERE blog_id IN (%s)", join( ',', array_map( 'intval', $non_cached_ids ) ) ) );
     716        $fresh_sites = $wpdb->get_results( sprintf( "SELECT * FROM $wpdb->blogs WHERE blog_id IN (%s)", join( ',', array_map( 'intval', $non_cached_ids ) ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    623717
    624718        update_site_cache( $fresh_sites, $update_meta_cache );
     
    9141008
    9151009/**
     1010 * Runs the initialization routine for a given site.
     1011 *
     1012 * This process includes creating the site's database tables and
     1013 * populating them with defaults.
     1014 *
     1015 * @since 5.0.0
     1016 *
     1017 * @global wpdb     $wpdb     WordPress database abstraction object.
     1018 * @global WP_Roles $wp_roles WordPress role management object.
     1019 *
     1020 * @param int|WP_Site $site_id Site ID or object.
     1021 * @param array       $args    {
     1022 *     Optional. Arguments to modify the initialization behavior.
     1023 *
     1024 *     @type int    $user_id Required. User ID for the site administrator.
     1025 *     @type string $title   Site title. Default is 'Site %d' where %d is the
     1026 *                           site ID.
     1027 *     @type array  $options Custom option $key => $value pairs to use. Default
     1028 *                           empty array.
     1029 *     @type array  $meta    Custom site metadata $key => $value pairs to use.
     1030 *                           Default empty array.
     1031 * }
     1032 * @return bool|WP_Error True on success, or error object on failure.
     1033 */
     1034function wp_initialize_site( $site_id, array $args = array() ) {
     1035    global $wpdb, $wp_roles;
     1036
     1037    if ( empty( $site_id ) ) {
     1038        return new WP_Error( 'site_empty_id', __( 'Site ID must not be empty.' ) );
     1039    }
     1040
     1041    $site = get_site( $site_id );
     1042    if ( ! $site ) {
     1043        return new WP_Error( 'site_invalid_id', __( 'Site with the ID does not exist.' ) );
     1044    }
     1045
     1046    if ( wp_is_site_initialized( $site ) ) {
     1047        return new WP_Error( 'site_already_initialized', __( 'The site appears to be already initialized.' ) );
     1048    }
     1049
     1050    $network = get_network( $site->network_id );
     1051    if ( ! $network ) {
     1052        $network = get_network();
     1053    }
     1054
     1055    $args = wp_parse_args(
     1056        $args,
     1057        array(
     1058            'user_id' => 0,
     1059            /* translators: %d: site ID */
     1060            'title'   => sprintf( __( 'Site %d' ), $site->id ),
     1061            'options' => array(),
     1062            'meta'    => array(),
     1063        )
     1064    );
     1065
     1066    /**
     1067     * Filters the arguments for initializing a site.
     1068     *
     1069     * @since 5.0.0
     1070     *
     1071     * @param array      $args    Arguments to modify the initialization behavior.
     1072     * @param WP_Site    $site    Site that is being initialized.
     1073     * @param WP_Network $network Network that the site belongs to.
     1074     */
     1075    $args = apply_filters( 'wp_initialize_site_args', $args, $site, $network );
     1076
     1077    $orig_installing = wp_installing();
     1078    if ( ! $orig_installing ) {
     1079        wp_installing( true );
     1080    }
     1081
     1082    $switch = false;
     1083    if ( get_current_blog_id() !== $site->id ) {
     1084        $switch = true;
     1085        switch_to_blog( $site->id );
     1086    }
     1087
     1088    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
     1089
     1090    // Set up the database tables.
     1091    make_db_current_silent( 'blog' );
     1092
     1093    $home_scheme    = 'http';
     1094    $siteurl_scheme = 'http';
     1095    if ( ! is_subdomain_install() ) {
     1096        if ( 'https' === parse_url( get_home_url( $network->site_id ), PHP_URL_SCHEME ) ) {
     1097            $home_scheme = 'https';
     1098        }
     1099        if ( 'https' === parse_url( get_network_option( $network->id, 'siteurl' ), PHP_URL_SCHEME ) ) {
     1100            $siteurl_scheme = 'https';
     1101        }
     1102    }
     1103
     1104    // Populate the site's options.
     1105    populate_options(
     1106        array_merge(
     1107            array(
     1108                'home'        => untrailingslashit( $home_scheme . '://' . $site->domain . $site->path ),
     1109                'siteurl'     => untrailingslashit( $siteurl_scheme . '://' . $site->domain . $site->path ),
     1110                'blogname'    => wp_unslash( $args['title'] ),
     1111                'admin_email' => '',
     1112                'upload_path' => get_network_option( $network->id, 'ms_files_rewriting' ) ? UPLOADBLOGSDIR . "/{$site->id}/files" : get_blog_option( $network->site_id, 'upload_path' ),
     1113                'blog_public' => (int) $site->public,
     1114                'WPLANG'      => get_network_option( $network->id, 'WPLANG' ),
     1115            ),
     1116            $args['options']
     1117        )
     1118    );
     1119
     1120    // Populate the site's roles.
     1121    populate_roles();
     1122    $wp_roles = new WP_Roles();
     1123
     1124    // Populate metadata for the site.
     1125    populate_site_meta( $site->id, $args['meta'] );
     1126
     1127    // Remove all permissions that may exist for the site.
     1128    $table_prefix = $wpdb->get_blog_prefix();
     1129    delete_metadata( 'user', 0, $table_prefix . 'user_level', null, true ); // delete all
     1130    delete_metadata( 'user', 0, $table_prefix . 'capabilities', null, true ); // delete all
     1131
     1132    // Install default site content.
     1133    wp_install_defaults( $args['user_id'] );
     1134
     1135    // Set the site administrator.
     1136    add_user_to_blog( $site->id, $args['user_id'], 'administrator' );
     1137    if ( ! user_can( $args['user_id'], 'manage_network' ) && ! get_user_meta( $args['user_id'], 'primary_blog', true ) ) {
     1138        update_user_meta( $args['user_id'], 'primary_blog', $site->id );
     1139    }
     1140
     1141    if ( $switch ) {
     1142        restore_current_blog();
     1143    }
     1144
     1145    wp_installing( $orig_installing );
     1146
     1147    return true;
     1148}
     1149
     1150/**
     1151 * Runs the uninitialization routine for a given site.
     1152 *
     1153 * This process includes dropping the site's database tables and deleting its uploads directory.
     1154 *
     1155 * @since 5.0.0
     1156 *
     1157 * @global wpdb $wpdb WordPress database abstraction object.
     1158 *
     1159 * @param int|WP_Site $site_id Site ID or object.
     1160 * @return bool|WP_Error True on success, or error object on failure.
     1161 */
     1162function wp_uninitialize_site( $site_id ) {
     1163    global $wpdb;
     1164
     1165    if ( empty( $site_id ) ) {
     1166        return new WP_Error( 'site_empty_id', __( 'Site ID must not be empty.' ) );
     1167    }
     1168
     1169    $site = get_site( $site_id );
     1170    if ( ! $site ) {
     1171        return new WP_Error( 'site_invalid_id', __( 'Site with the ID does not exist.' ) );
     1172    }
     1173
     1174    if ( ! wp_is_site_initialized( $site ) ) {
     1175        return new WP_Error( 'site_already_uninitialized', __( 'The site appears to be already uninitialized.' ) );
     1176    }
     1177
     1178    $users = get_users( array(
     1179        'blog_id' => $site->id,
     1180        'fields'  => 'ids',
     1181    ) );
     1182
     1183    // Remove users from the site.
     1184    if ( ! empty( $users ) ) {
     1185        foreach ( $users as $user_id ) {
     1186            remove_user_from_blog( $user_id, $site->id );
     1187        }
     1188    }
     1189
     1190    $switch = false;
     1191    if ( get_current_blog_id() !== $site->id ) {
     1192        $switch = true;
     1193        switch_to_blog( $site->id );
     1194    }
     1195
     1196    $uploads = wp_get_upload_dir();
     1197
     1198    $tables = $wpdb->tables( 'blog' );
     1199
     1200    /**
     1201     * Filters the tables to drop when the site is deleted.
     1202     *
     1203     * @since MU (3.0.0)
     1204     *
     1205     * @param string[] $tables  Array of names of the site tables to be dropped.
     1206     * @param int      $site_id The ID of the site to drop tables for.
     1207     */
     1208    $drop_tables = apply_filters( 'wpmu_drop_tables', $tables, $site->id );
     1209
     1210    foreach ( (array) $drop_tables as $table ) {
     1211        $wpdb->query( "DROP TABLE IF EXISTS `$table`" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     1212    }
     1213
     1214    /**
     1215     * Filters the upload base directory to delete when the site is deleted.
     1216     *
     1217     * @since MU (3.0.0)
     1218     *
     1219     * @param string $uploads['basedir'] Uploads path without subdirectory. @see wp_upload_dir()
     1220     * @param int    $site_id            The site ID.
     1221     */
     1222    $dir     = apply_filters( 'wpmu_delete_blog_upload_dir', $uploads['basedir'], $site->id );
     1223    $dir     = rtrim( $dir, DIRECTORY_SEPARATOR );
     1224    $top_dir = $dir;
     1225    $stack   = array( $dir );
     1226    $index   = 0;
     1227
     1228    while ( $index < count( $stack ) ) {
     1229        // Get indexed directory from stack
     1230        $dir = $stack[ $index ];
     1231
     1232        // phpcs:disable Generic.PHP.NoSilencedErrors.Discouraged
     1233        $dh = @opendir( $dir );
     1234        if ( $dh ) {
     1235            $file = @readdir( $dh );
     1236            while ( false !== $file ) {
     1237                if ( '.' === $file || '..' === $file ) {
     1238                    $file = @readdir( $dh );
     1239                    continue;
     1240                }
     1241
     1242                if ( @is_dir( $dir . DIRECTORY_SEPARATOR . $file ) ) {
     1243                    $stack[] = $dir . DIRECTORY_SEPARATOR . $file;
     1244                } elseif ( @is_file( $dir . DIRECTORY_SEPARATOR . $file ) ) {
     1245                    @unlink( $dir . DIRECTORY_SEPARATOR . $file );
     1246                }
     1247
     1248                $file = @readdir( $dh );
     1249            }
     1250            @closedir( $dh );
     1251        }
     1252        $index++;
     1253    }
     1254
     1255    $stack = array_reverse( $stack ); // Last added dirs are deepest
     1256    foreach ( (array) $stack as $dir ) {
     1257        if ( $dir != $top_dir ) {
     1258            @rmdir( $dir );
     1259        }
     1260    }
     1261
     1262    // phpcs:enable Generic.PHP.NoSilencedErrors.Discouraged
     1263    if ( $switch ) {
     1264        restore_current_blog();
     1265    }
     1266
     1267    return true;
     1268}
     1269
     1270/**
     1271 * Checks whether a site is initialized.
     1272 *
     1273 * A site is considered initialized when its database tables are present.
     1274 *
     1275 * @since 5.0.0
     1276 *
     1277 * @global wpdb $wpdb WordPress database abstraction object.
     1278 *
     1279 * @param int|WP_Site $site_id Site ID or object.
     1280 * @return bool True if the site is initialized, false otherwise.
     1281 */
     1282function wp_is_site_initialized( $site_id ) {
     1283    global $wpdb;
     1284
     1285    if ( is_object( $site_id ) ) {
     1286        $site_id = $site_id->blog_id;
     1287    }
     1288    $site_id = (int) $site_id;
     1289
     1290    /**
     1291     * Filters the check for whether a site is initialized before the database is accessed.
     1292     *
     1293     * Returning a non-null value will effectively short-circuit the function, returning
     1294     * that value instead.
     1295     *
     1296     * @since 5.0.0
     1297     *
     1298     * @param bool|null $pre     The value to return, if not null.
     1299     * @param int       $site_id The site ID that is being checked.
     1300     */
     1301    $pre = apply_filters( 'pre_wp_is_site_initialized', null, $site_id );
     1302    if ( null !== $pre ) {
     1303        return (bool) $pre;
     1304    }
     1305
     1306    $switch = false;
     1307    if ( get_current_blog_id() !== $site_id ) {
     1308        $switch = true;
     1309        remove_action( 'switch_blog', 'wp_switch_roles_and_user', 1 );
     1310        switch_to_blog( $site_id );
     1311    }
     1312
     1313    $suppress = $wpdb->suppress_errors();
     1314    $result   = (bool) $wpdb->get_results( "DESCRIBE {$wpdb->posts}" );
     1315    $wpdb->suppress_errors( $suppress );
     1316
     1317    if ( $switch ) {
     1318        restore_current_blog();
     1319        add_action( 'switch_blog', 'wp_switch_roles_and_user', 1, 2 );
     1320    }
     1321
     1322    return $result;
     1323}
     1324
     1325/**
    9161326 * Retrieve option value for a given blog id based on name of option.
    9171327 *
     
    16222032    $non_cached_ids = _get_non_cached_ids( $network_ids, 'networks' );
    16232033    if ( ! empty( $non_cached_ids ) ) {
    1624         $fresh_networks = $wpdb->get_results( sprintf( "SELECT $wpdb->site.* FROM $wpdb->site WHERE id IN (%s)", join( ',', array_map( 'intval', $non_cached_ids ) ) ) );
     2034        $fresh_networks = $wpdb->get_results( sprintf( "SELECT $wpdb->site.* FROM $wpdb->site WHERE id IN (%s)", join( ',', array_map( 'intval', $non_cached_ids ) ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    16252035
    16262036        update_network_cache( $fresh_networks );
     
    17582168
    17592169    if ( $new_site->spam != $old_site->spam ) {
    1760         if ( $new_site->spam == 1 ) {
     2170        if ( 1 == $new_site->spam ) {
    17612171
    17622172            /**
     
    17822192
    17832193    if ( $new_site->mature != $old_site->mature ) {
    1784         if ( $new_site->mature == 1 ) {
     2194        if ( 1 == $new_site->mature ) {
    17852195
    17862196            /**
     
    18062216
    18072217    if ( $new_site->archived != $old_site->archived ) {
    1808         if ( $new_site->archived == 1 ) {
     2218        if ( 1 == $new_site->archived ) {
    18092219
    18102220            /**
     
    18302240
    18312241    if ( $new_site->deleted != $old_site->deleted ) {
    1832         if ( $new_site->deleted == 1 ) {
     2242        if ( 1 == $new_site->deleted ) {
    18332243
    18342244            /**
     
    18902300 */
    18912301function wp_update_blog_public_option_on_site_update( $site_id, $public ) {
     2302
     2303    // Bail if the site's database tables do not exist (yet).
     2304    if ( ! wp_is_site_initialized( $site_id ) ) {
     2305        return;
     2306    }
     2307
    18922308    update_blog_option( $site_id, 'blog_public', $public );
    18932309}
Note: See TracChangeset for help on using the changeset viewer.