Wordpress 4.6
[autoinstalls/wordpress.git] / wp-admin / includes / ms.php
1 <?php
2 /**
3  * Multisite administration functions.
4  *
5  * @package WordPress
6  * @subpackage Multisite
7  * @since 3.0.0
8  */
9
10 /**
11  * Determine if uploaded file exceeds space quota.
12  *
13  * @since 3.0.0
14  *
15  * @param array $file $_FILES array for a given file.
16  * @return array $_FILES array with 'error' key set if file exceeds quota. 'error' is empty otherwise.
17  */
18 function check_upload_size( $file ) {
19         if ( get_site_option( 'upload_space_check_disabled' ) )
20                 return $file;
21
22         if ( $file['error'] != '0' ) // there's already an error
23                 return $file;
24
25         if ( defined( 'WP_IMPORTING' ) )
26                 return $file;
27
28         $space_left = get_upload_space_available();
29
30         $file_size = filesize( $file['tmp_name'] );
31         if ( $space_left < $file_size ) {
32                 $file['error'] = sprintf( __( 'Not enough space to upload. %1$s KB needed.' ), number_format( ( $file_size - $space_left ) / KB_IN_BYTES ) );
33         }
34
35         if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) {
36                 $file['error'] = sprintf( __( 'This file is too big. Files must be less than %1$s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) );
37         }
38
39         if ( upload_is_user_over_quota( false ) ) {
40                 $file['error'] = __( 'You have used your space quota. Please delete files before uploading.' );
41         }
42
43         if ( $file['error'] != '0' && ! isset( $_POST['html-upload'] ) && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) {
44                 wp_die( $file['error'] . ' <a href="javascript:history.go(-1)">' . __( 'Back' ) . '</a>' );
45         }
46
47         return $file;
48 }
49
50 /**
51  * Delete a site.
52  *
53  * @since 3.0.0
54  *
55  * @global wpdb $wpdb WordPress database abstraction object.
56  *
57  * @param int  $blog_id Site ID.
58  * @param bool $drop    True if site's database tables should be dropped. Default is false.
59  */
60 function wpmu_delete_blog( $blog_id, $drop = false ) {
61         global $wpdb;
62
63         $switch = false;
64         if ( get_current_blog_id() != $blog_id ) {
65                 $switch = true;
66                 switch_to_blog( $blog_id );
67         }
68
69         $blog = get_blog_details( $blog_id );
70         /**
71          * Fires before a site is deleted.
72          *
73          * @since MU
74          *
75          * @param int  $blog_id The site ID.
76          * @param bool $drop    True if site's table should be dropped. Default is false.
77          */
78         do_action( 'delete_blog', $blog_id, $drop );
79
80         $users = get_users( array( 'blog_id' => $blog_id, 'fields' => 'ids' ) );
81
82         // Remove users from this blog.
83         if ( ! empty( $users ) ) {
84                 foreach ( $users as $user_id ) {
85                         remove_user_from_blog( $user_id, $blog_id );
86                 }
87         }
88
89         update_blog_status( $blog_id, 'deleted', 1 );
90
91         $current_site = get_current_site();
92
93         // If a full blog object is not available, do not destroy anything.
94         if ( $drop && ! $blog ) {
95                 $drop = false;
96         }
97
98         // Don't destroy the initial, main, or root blog.
99         if ( $drop && ( 1 == $blog_id || is_main_site( $blog_id ) || ( $blog->path == $current_site->path && $blog->domain == $current_site->domain ) ) ) {
100                 $drop = false;
101         }
102
103         $upload_path = trim( get_option( 'upload_path' ) );
104
105         // If ms_files_rewriting is enabled and upload_path is empty, wp_upload_dir is not reliable.
106         if ( $drop && get_site_option( 'ms_files_rewriting' ) && empty( $upload_path ) ) {
107                 $drop = false;
108         }
109
110         if ( $drop ) {
111                 $uploads = wp_get_upload_dir();
112
113                 $tables = $wpdb->tables( 'blog' );
114                 /**
115                  * Filters the tables to drop when the site is deleted.
116                  *
117                  * @since MU
118                  *
119                  * @param array $tables  The site tables to be dropped.
120                  * @param int   $blog_id The ID of the site to drop tables for.
121                  */
122                 $drop_tables = apply_filters( 'wpmu_drop_tables', $tables, $blog_id );
123
124                 foreach ( (array) $drop_tables as $table ) {
125                         $wpdb->query( "DROP TABLE IF EXISTS `$table`" );
126                 }
127
128                 $wpdb->delete( $wpdb->blogs, array( 'blog_id' => $blog_id ) );
129
130                 /**
131                  * Filters the upload base directory to delete when the site is deleted.
132                  *
133                  * @since MU
134                  *
135                  * @param string $uploads['basedir'] Uploads path without subdirectory. @see wp_upload_dir()
136                  * @param int    $blog_id            The site ID.
137                  */
138                 $dir = apply_filters( 'wpmu_delete_blog_upload_dir', $uploads['basedir'], $blog_id );
139                 $dir = rtrim( $dir, DIRECTORY_SEPARATOR );
140                 $top_dir = $dir;
141                 $stack = array($dir);
142                 $index = 0;
143
144                 while ( $index < count( $stack ) ) {
145                         // Get indexed directory from stack
146                         $dir = $stack[$index];
147
148                         $dh = @opendir( $dir );
149                         if ( $dh ) {
150                                 while ( ( $file = @readdir( $dh ) ) !== false ) {
151                                         if ( $file == '.' || $file == '..' )
152                                                 continue;
153
154                                         if ( @is_dir( $dir . DIRECTORY_SEPARATOR . $file ) ) {
155                                                 $stack[] = $dir . DIRECTORY_SEPARATOR . $file;
156                                         } elseif ( @is_file( $dir . DIRECTORY_SEPARATOR . $file ) ) {
157                                                 @unlink( $dir . DIRECTORY_SEPARATOR . $file );
158                                         }
159                                 }
160                                 @closedir( $dh );
161                         }
162                         $index++;
163                 }
164
165                 $stack = array_reverse( $stack ); // Last added dirs are deepest
166                 foreach ( (array) $stack as $dir ) {
167                         if ( $dir != $top_dir)
168                         @rmdir( $dir );
169                 }
170
171                 clean_blog_cache( $blog );
172         }
173
174         if ( $switch )
175                 restore_current_blog();
176 }
177
178 /**
179  * Delete a user from the network and remove from all sites.
180  *
181  * @since 3.0.0
182  *
183  * @todo Merge with wp_delete_user() ?
184  *
185  * @global wpdb $wpdb WordPress database abstraction object.
186  *
187  * @param int $id The user ID.
188  * @return bool True if the user was deleted, otherwise false.
189  */
190 function wpmu_delete_user( $id ) {
191         global $wpdb;
192
193         if ( ! is_numeric( $id ) ) {
194                 return false;
195         }
196
197         $id = (int) $id;
198         $user = new WP_User( $id );
199
200         if ( !$user->exists() )
201                 return false;
202
203         // Global super-administrators are protected, and cannot be deleted.
204         $_super_admins = get_super_admins();
205         if ( in_array( $user->user_login, $_super_admins, true ) ) {
206                 return false;
207         }
208
209         /**
210          * Fires before a user is deleted from the network.
211          *
212          * @since MU
213          *
214          * @param int $id ID of the user about to be deleted from the network.
215          */
216         do_action( 'wpmu_delete_user', $id );
217
218         $blogs = get_blogs_of_user( $id );
219
220         if ( ! empty( $blogs ) ) {
221                 foreach ( $blogs as $blog ) {
222                         switch_to_blog( $blog->userblog_id );
223                         remove_user_from_blog( $id, $blog->userblog_id );
224
225                         $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author = %d", $id ) );
226                         foreach ( (array) $post_ids as $post_id ) {
227                                 wp_delete_post( $post_id );
228                         }
229
230                         // Clean links
231                         $link_ids = $wpdb->get_col( $wpdb->prepare( "SELECT link_id FROM $wpdb->links WHERE link_owner = %d", $id ) );
232
233                         if ( $link_ids ) {
234                                 foreach ( $link_ids as $link_id )
235                                         wp_delete_link( $link_id );
236                         }
237
238                         restore_current_blog();
239                 }
240         }
241
242         $meta = $wpdb->get_col( $wpdb->prepare( "SELECT umeta_id FROM $wpdb->usermeta WHERE user_id = %d", $id ) );
243         foreach ( $meta as $mid )
244                 delete_metadata_by_mid( 'user', $mid );
245
246         $wpdb->delete( $wpdb->users, array( 'ID' => $id ) );
247
248         clean_user_cache( $user );
249
250         /** This action is documented in wp-admin/includes/user.php */
251         do_action( 'deleted_user', $id );
252
253         return true;
254 }
255
256 /**
257  * Sends an email when a site administrator email address is changed.
258  *
259  * @since 3.0.0
260  *
261  * @param string $old_value The old email address. Not currently used.
262  * @param string $value     The new email address.
263  */
264 function update_option_new_admin_email( $old_value, $value ) {
265         if ( $value == get_option( 'admin_email' ) || !is_email( $value ) )
266                 return;
267
268         $hash = md5( $value. time() .mt_rand() );
269         $new_admin_email = array(
270                 'hash' => $hash,
271                 'newemail' => $value
272         );
273         update_option( 'adminhash', $new_admin_email );
274
275         /* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
276         $email_text = __( 'Howdy ###USERNAME###,
277
278 You recently requested to have the administration email address on
279 your site changed.
280
281 If this is correct, please click on the following link to change it:
282 ###ADMIN_URL###
283
284 You can safely ignore and delete this email if you do not want to
285 take this action.
286
287 This email has been sent to ###EMAIL###
288
289 Regards,
290 All at ###SITENAME###
291 ###SITEURL###' );
292
293         /**
294          * Filters the email text sent when the site admin email is changed.
295          *
296          * The following strings have a special meaning and will get replaced dynamically:
297          * ###USERNAME###  The current user's username.
298          * ###ADMIN_URL### The link to click on to confirm the email change.
299          * ###EMAIL###     The new email.
300          * ###SITENAME###  The name of the site.
301          * ###SITEURL###   The URL to the site.
302          *
303          * @since MU
304          *
305          * @param string $email_text      Text in the email.
306          * @param string $new_admin_email New admin email that the current administration email was changed to.
307          */
308         $content = apply_filters( 'new_admin_email_content', $email_text, $new_admin_email );
309
310         $current_user = wp_get_current_user();
311         $content = str_replace( '###USERNAME###', $current_user->user_login, $content );
312         $content = str_replace( '###ADMIN_URL###', esc_url( self_admin_url( 'options.php?adminhash='.$hash ) ), $content );
313         $content = str_replace( '###EMAIL###', $value, $content );
314         $content = str_replace( '###SITENAME###', get_site_option( 'site_name' ), $content );
315         $content = str_replace( '###SITEURL###', network_home_url(), $content );
316
317         wp_mail( $value, sprintf( __( '[%s] New Admin Email Address' ), wp_specialchars_decode( get_option( 'blogname' ) ) ), $content );
318 }
319
320 /**
321  * Sends an email when an email address change is requested.
322  *
323  * @since 3.0.0
324  *
325  * @global WP_Error $errors WP_Error object.
326  * @global wpdb     $wpdb   WordPress database object.
327  */
328 function send_confirmation_on_profile_email() {
329         global $errors, $wpdb;
330         $current_user = wp_get_current_user();
331         if ( ! is_object($errors) )
332                 $errors = new WP_Error();
333
334         if ( $current_user->ID != $_POST['user_id'] )
335                 return false;
336
337         if ( $current_user->user_email != $_POST['email'] ) {
338                 if ( !is_email( $_POST['email'] ) ) {
339                         $errors->add( 'user_email', __( "<strong>ERROR</strong>: The email address isn&#8217;t correct." ), array( 'form-field' => 'email' ) );
340                         return;
341                 }
342
343                 if ( $wpdb->get_var( $wpdb->prepare( "SELECT user_email FROM {$wpdb->users} WHERE user_email=%s", $_POST['email'] ) ) ) {
344                         $errors->add( 'user_email', __( "<strong>ERROR</strong>: The email address is already used." ), array( 'form-field' => 'email' ) );
345                         delete_user_meta( $current_user->ID, '_new_email' );
346                         return;
347                 }
348
349                 $hash = md5( $_POST['email'] . time() . mt_rand() );
350                 $new_user_email = array(
351                         'hash' => $hash,
352                         'newemail' => $_POST['email']
353                 );
354                 update_user_meta( $current_user->ID, '_new_email', $new_user_email );
355
356                 /* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
357                 $email_text = __( 'Howdy ###USERNAME###,
358
359 You recently requested to have the email address on your account changed.
360
361 If this is correct, please click on the following link to change it:
362 ###ADMIN_URL###
363
364 You can safely ignore and delete this email if you do not want to
365 take this action.
366
367 This email has been sent to ###EMAIL###
368
369 Regards,
370 All at ###SITENAME###
371 ###SITEURL###' );
372
373                 /**
374                  * Filters the email text sent when a user changes emails.
375                  *
376                  * The following strings have a special meaning and will get replaced dynamically:
377                  * ###USERNAME###  The current user's username.
378                  * ###ADMIN_URL### The link to click on to confirm the email change.
379                  * ###EMAIL###     The new email.
380                  * ###SITENAME###  The name of the site.
381                  * ###SITEURL###   The URL to the site.
382                  *
383                  * @since MU
384                  *
385                  * @param string $email_text     Text in the email.
386                  * @param string $new_user_email New user email that the current user has changed to.
387                  */
388                 $content = apply_filters( 'new_user_email_content', $email_text, $new_user_email );
389
390                 $content = str_replace( '###USERNAME###', $current_user->user_login, $content );
391                 $content = str_replace( '###ADMIN_URL###', esc_url( admin_url( 'profile.php?newuseremail='.$hash ) ), $content );
392                 $content = str_replace( '###EMAIL###', $_POST['email'], $content);
393                 $content = str_replace( '###SITENAME###', get_site_option( 'site_name' ), $content );
394                 $content = str_replace( '###SITEURL###', network_home_url(), $content );
395
396                 wp_mail( $_POST['email'], sprintf( __( '[%s] New Email Address' ), wp_specialchars_decode( get_option( 'blogname' ) ) ), $content );
397                 $_POST['email'] = $current_user->user_email;
398         }
399 }
400
401 /**
402  * Adds an admin notice alerting the user to check for confirmation email
403  * after email address change.
404  *
405  * @since 3.0.0
406  *
407  * @global string $pagenow
408  */
409 function new_user_email_admin_notice() {
410         global $pagenow;
411         if ( 'profile.php' === $pagenow && isset( $_GET['updated'] ) && $email = get_user_meta( get_current_user_id(), '_new_email', true ) ) {
412                 /* translators: %s: New email address */
413                 echo '<div class="notice notice-info"><p>' . sprintf( __( 'Your email address has not been updated yet. Please check your inbox at %s for a confirmation email.' ), '<code>' . esc_html( $email['newemail'] ) . '</code>' ) . '</p></div>';
414         }
415 }
416
417 /**
418  * Check whether a site has used its allotted upload space.
419  *
420  * @since MU
421  *
422  * @param bool $echo Optional. If $echo is set and the quota is exceeded, a warning message is echoed. Default is true.
423  * @return bool True if user is over upload space quota, otherwise false.
424  */
425 function upload_is_user_over_quota( $echo = true ) {
426         if ( get_site_option( 'upload_space_check_disabled' ) )
427                 return false;
428
429         $space_allowed = get_space_allowed();
430         if ( ! is_numeric( $space_allowed ) ) {
431                 $space_allowed = 10; // Default space allowed is 10 MB
432         }
433         $space_used = get_space_used();
434
435         if ( ( $space_allowed - $space_used ) < 0 ) {
436                 if ( $echo )
437                         _e( 'Sorry, you have used your space allocation. Please delete some files to upload more files.' );
438                 return true;
439         } else {
440                 return false;
441         }
442 }
443
444 /**
445  * Displays the amount of disk space used by the current site. Not used in core.
446  *
447  * @since MU
448  */
449 function display_space_usage() {
450         $space_allowed = get_space_allowed();
451         $space_used = get_space_used();
452
453         $percent_used = ( $space_used / $space_allowed ) * 100;
454
455         if ( $space_allowed > 1000 ) {
456                 $space = number_format( $space_allowed / KB_IN_BYTES );
457                 /* translators: Gigabytes */
458                 $space .= __( 'GB' );
459         } else {
460                 $space = number_format( $space_allowed );
461                 /* translators: Megabytes */
462                 $space .= __( 'MB' );
463         }
464         ?>
465         <strong><?php printf( __( 'Used: %1$s%% of %2$s' ), number_format( $percent_used ), $space ); ?></strong>
466         <?php
467 }
468
469 /**
470  * Get the remaining upload space for this site.
471  *
472  * @since MU
473  *
474  * @param int $size Current max size in bytes
475  * @return int Max size in bytes
476  */
477 function fix_import_form_size( $size ) {
478         if ( upload_is_user_over_quota( false ) ) {
479                 return 0;
480         }
481         $available = get_upload_space_available();
482         return min( $size, $available );
483 }
484
485 /**
486  * Displays the site upload space quota setting form on the Edit Site Settings screen.
487  *
488  * @since 3.0.0
489  *
490  * @param int $id The ID of the site to display the setting for.
491  */
492 function upload_space_setting( $id ) {
493         switch_to_blog( $id );
494         $quota = get_option( 'blog_upload_space' );
495         restore_current_blog();
496
497         if ( !$quota )
498                 $quota = '';
499
500         ?>
501         <tr>
502                 <th><label for="blog-upload-space-number"><?php _e( 'Site Upload Space Quota' ); ?></label></th>
503                 <td>
504                         <input type="number" step="1" min="0" style="width: 100px" name="option[blog_upload_space]" id="blog-upload-space-number" aria-describedby="blog-upload-space-desc" value="<?php echo $quota; ?>" />
505                         <span id="blog-upload-space-desc"><span class="screen-reader-text"><?php _e( 'Size in megabytes' ); ?></span> <?php _e( 'MB (Leave blank for network default)' ); ?></span>
506                 </td>
507         </tr>
508         <?php
509 }
510
511 /**
512  * Update the status of a user in the database.
513  *
514  * Used in core to mark a user as spam or "ham" (not spam) in Multisite.
515  *
516  * @since 3.0.0
517  *
518  * @global wpdb $wpdb WordPress database abstraction object.
519  *
520  * @param int    $id         The user ID.
521  * @param string $pref       The column in the wp_users table to update the user's status
522  *                           in (presumably user_status, spam, or deleted).
523  * @param int    $value      The new status for the user.
524  * @param null   $deprecated Deprecated as of 3.0.2 and should not be used.
525  * @return int   The initially passed $value.
526  */
527 function update_user_status( $id, $pref, $value, $deprecated = null ) {
528         global $wpdb;
529
530         if ( null !== $deprecated )
531                 _deprecated_argument( __FUNCTION__, '3.0.2' );
532
533         $wpdb->update( $wpdb->users, array( sanitize_key( $pref ) => $value ), array( 'ID' => $id ) );
534
535         $user = new WP_User( $id );
536         clean_user_cache( $user );
537
538         if ( $pref == 'spam' ) {
539                 if ( $value == 1 ) {
540                         /**
541                          * Fires after the user is marked as a SPAM user.
542                          *
543                          * @since 3.0.0
544                          *
545                          * @param int $id ID of the user marked as SPAM.
546                          */
547                         do_action( 'make_spam_user', $id );
548                 } else {
549                         /**
550                          * Fires after the user is marked as a HAM user. Opposite of SPAM.
551                          *
552                          * @since 3.0.0
553                          *
554                          * @param int $id ID of the user marked as HAM.
555                          */
556                         do_action( 'make_ham_user', $id );
557                 }
558         }
559
560         return $value;
561 }
562
563 /**
564  * Cleans the user cache for a specific user.
565  *
566  * @since 3.0.0
567  *
568  * @param int $id The user ID.
569  * @return bool|int The ID of the refreshed user or false if the user does not exist.
570  */
571 function refresh_user_details( $id ) {
572         $id = (int) $id;
573
574         if ( !$user = get_userdata( $id ) )
575                 return false;
576
577         clean_user_cache( $user );
578
579         return $id;
580 }
581
582 /**
583  * Returns the language for a language code.
584  *
585  * @since 3.0.0
586  *
587  * @param string $code Optional. The two-letter language code. Default empty.
588  * @return string The language corresponding to $code if it exists. If it does not exist,
589  *                then the first two letters of $code is returned.
590  */
591 function format_code_lang( $code = '' ) {
592         $code = strtolower( substr( $code, 0, 2 ) );
593         $lang_codes = array(
594                 'aa' => 'Afar', 'ab' => 'Abkhazian', 'af' => 'Afrikaans', 'ak' => 'Akan', 'sq' => 'Albanian', 'am' => 'Amharic', 'ar' => 'Arabic', 'an' => 'Aragonese', 'hy' => 'Armenian', 'as' => 'Assamese', 'av' => 'Avaric', 'ae' => 'Avestan', 'ay' => 'Aymara', 'az' => 'Azerbaijani', 'ba' => 'Bashkir', 'bm' => 'Bambara', 'eu' => 'Basque', 'be' => 'Belarusian', 'bn' => 'Bengali',
595                 'bh' => 'Bihari', 'bi' => 'Bislama', 'bs' => 'Bosnian', 'br' => 'Breton', 'bg' => 'Bulgarian', 'my' => 'Burmese', 'ca' => 'Catalan; Valencian', 'ch' => 'Chamorro', 'ce' => 'Chechen', 'zh' => 'Chinese', 'cu' => 'Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic', 'cv' => 'Chuvash', 'kw' => 'Cornish', 'co' => 'Corsican', 'cr' => 'Cree',
596                 'cs' => 'Czech', 'da' => 'Danish', 'dv' => 'Divehi; Dhivehi; Maldivian', 'nl' => 'Dutch; Flemish', 'dz' => 'Dzongkha', 'en' => 'English', 'eo' => 'Esperanto', 'et' => 'Estonian', 'ee' => 'Ewe', 'fo' => 'Faroese', 'fj' => 'Fijjian', 'fi' => 'Finnish', 'fr' => 'French', 'fy' => 'Western Frisian', 'ff' => 'Fulah', 'ka' => 'Georgian', 'de' => 'German', 'gd' => 'Gaelic; Scottish Gaelic',
597                 'ga' => 'Irish', 'gl' => 'Galician', 'gv' => 'Manx', 'el' => 'Greek, Modern', 'gn' => 'Guarani', 'gu' => 'Gujarati', 'ht' => 'Haitian; Haitian Creole', 'ha' => 'Hausa', 'he' => 'Hebrew', 'hz' => 'Herero', 'hi' => 'Hindi', 'ho' => 'Hiri Motu', 'hu' => 'Hungarian', 'ig' => 'Igbo', 'is' => 'Icelandic', 'io' => 'Ido', 'ii' => 'Sichuan Yi', 'iu' => 'Inuktitut', 'ie' => 'Interlingue',
598                 'ia' => 'Interlingua (International Auxiliary Language Association)', 'id' => 'Indonesian', 'ik' => 'Inupiaq', 'it' => 'Italian', 'jv' => 'Javanese', 'ja' => 'Japanese', 'kl' => 'Kalaallisut; Greenlandic', 'kn' => 'Kannada', 'ks' => 'Kashmiri', 'kr' => 'Kanuri', 'kk' => 'Kazakh', 'km' => 'Central Khmer', 'ki' => 'Kikuyu; Gikuyu', 'rw' => 'Kinyarwanda', 'ky' => 'Kirghiz; Kyrgyz',
599                 'kv' => 'Komi', 'kg' => 'Kongo', 'ko' => 'Korean', 'kj' => 'Kuanyama; Kwanyama', 'ku' => 'Kurdish', 'lo' => 'Lao', 'la' => 'Latin', 'lv' => 'Latvian', 'li' => 'Limburgan; Limburger; Limburgish', 'ln' => 'Lingala', 'lt' => 'Lithuanian', 'lb' => 'Luxembourgish; Letzeburgesch', 'lu' => 'Luba-Katanga', 'lg' => 'Ganda', 'mk' => 'Macedonian', 'mh' => 'Marshallese', 'ml' => 'Malayalam',
600                 'mi' => 'Maori', 'mr' => 'Marathi', 'ms' => 'Malay', 'mg' => 'Malagasy', 'mt' => 'Maltese', 'mo' => 'Moldavian', 'mn' => 'Mongolian', 'na' => 'Nauru', 'nv' => 'Navajo; Navaho', 'nr' => 'Ndebele, South; South Ndebele', 'nd' => 'Ndebele, North; North Ndebele', 'ng' => 'Ndonga', 'ne' => 'Nepali', 'nn' => 'Norwegian Nynorsk; Nynorsk, Norwegian', 'nb' => 'Bokmål, Norwegian, Norwegian Bokmål',
601                 'no' => 'Norwegian', 'ny' => 'Chichewa; Chewa; Nyanja', 'oc' => 'Occitan, Provençal', 'oj' => 'Ojibwa', 'or' => 'Oriya', 'om' => 'Oromo', 'os' => 'Ossetian; Ossetic', 'pa' => 'Panjabi; Punjabi', 'fa' => 'Persian', 'pi' => 'Pali', 'pl' => 'Polish', 'pt' => 'Portuguese', 'ps' => 'Pushto', 'qu' => 'Quechua', 'rm' => 'Romansh', 'ro' => 'Romanian', 'rn' => 'Rundi', 'ru' => 'Russian',
602                 'sg' => 'Sango', 'sa' => 'Sanskrit', 'sr' => 'Serbian', 'hr' => 'Croatian', 'si' => 'Sinhala; Sinhalese', 'sk' => 'Slovak', 'sl' => 'Slovenian', 'se' => 'Northern Sami', 'sm' => 'Samoan', 'sn' => 'Shona', 'sd' => 'Sindhi', 'so' => 'Somali', 'st' => 'Sotho, Southern', 'es' => 'Spanish; Castilian', 'sc' => 'Sardinian', 'ss' => 'Swati', 'su' => 'Sundanese', 'sw' => 'Swahili',
603                 'sv' => 'Swedish', 'ty' => 'Tahitian', 'ta' => 'Tamil', 'tt' => 'Tatar', 'te' => 'Telugu', 'tg' => 'Tajik', 'tl' => 'Tagalog', 'th' => 'Thai', 'bo' => 'Tibetan', 'ti' => 'Tigrinya', 'to' => 'Tonga (Tonga Islands)', 'tn' => 'Tswana', 'ts' => 'Tsonga', 'tk' => 'Turkmen', 'tr' => 'Turkish', 'tw' => 'Twi', 'ug' => 'Uighur; Uyghur', 'uk' => 'Ukrainian', 'ur' => 'Urdu', 'uz' => 'Uzbek',
604                 've' => 'Venda', 'vi' => 'Vietnamese', 'vo' => 'Volapük', 'cy' => 'Welsh','wa' => 'Walloon','wo' => 'Wolof', 'xh' => 'Xhosa', 'yi' => 'Yiddish', 'yo' => 'Yoruba', 'za' => 'Zhuang; Chuang', 'zu' => 'Zulu' );
605
606         /**
607          * Filters the language codes.
608          *
609          * @since MU
610          *
611          * @param array  $lang_codes Key/value pair of language codes where key is the short version.
612          * @param string $code       A two-letter designation of the language.
613          */
614         $lang_codes = apply_filters( 'lang_codes', $lang_codes, $code );
615         return strtr( $code, $lang_codes );
616 }
617
618 /**
619  * Synchronize category and post tag slugs when global terms are enabled.
620  *
621  * @since 3.0.0
622  *
623  * @param object $term     The term.
624  * @param string $taxonomy The taxonomy for `$term`. Should be 'category' or 'post_tag', as these are
625  *                         the only taxonomies which are processed by this function; anything else
626  *                         will be returned untouched.
627  * @return object|array Returns `$term`, after filtering the 'slug' field with sanitize_title()
628  *                      if $taxonomy is 'category' or 'post_tag'.
629  */
630 function sync_category_tag_slugs( $term, $taxonomy ) {
631         if ( global_terms_enabled() && ( $taxonomy == 'category' || $taxonomy == 'post_tag' ) ) {
632                 if ( is_object( $term ) ) {
633                         $term->slug = sanitize_title( $term->name );
634                 } else {
635                         $term['slug'] = sanitize_title( $term['name'] );
636                 }
637         }
638         return $term;
639 }
640
641 /**
642  * Displays an access denied message when a user tries to view a site's dashboard they
643  * do not have access to.
644  *
645  * @since 3.2.0
646  * @access private
647  */
648 function _access_denied_splash() {
649         if ( ! is_user_logged_in() || is_network_admin() )
650                 return;
651
652         $blogs = get_blogs_of_user( get_current_user_id() );
653
654         if ( wp_list_filter( $blogs, array( 'userblog_id' => get_current_blog_id() ) ) )
655                 return;
656
657         $blog_name = get_bloginfo( 'name' );
658
659         if ( empty( $blogs ) )
660                 wp_die( sprintf( __( 'You attempted to access the "%1$s" dashboard, but you do not currently have privileges on this site. If you believe you should be able to access the "%1$s" dashboard, please contact your network administrator.' ), $blog_name ), 403 );
661
662         $output = '<p>' . sprintf( __( 'You attempted to access the "%1$s" dashboard, but you do not currently have privileges on this site. If you believe you should be able to access the "%1$s" dashboard, please contact your network administrator.' ), $blog_name ) . '</p>';
663         $output .= '<p>' . __( 'If you reached this screen by accident and meant to visit one of your own sites, here are some shortcuts to help you find your way.' ) . '</p>';
664
665         $output .= '<h3>' . __('Your Sites') . '</h3>';
666         $output .= '<table>';
667
668         foreach ( $blogs as $blog ) {
669                 $output .= '<tr>';
670                 $output .= "<td>{$blog->blogname}</td>";
671                 $output .= '<td><a href="' . esc_url( get_admin_url( $blog->userblog_id ) ) . '">' . __( 'Visit Dashboard' ) . '</a> | ' .
672                         '<a href="' . esc_url( get_home_url( $blog->userblog_id ) ). '">' . __( 'View Site' ) . '</a></td>';
673                 $output .= '</tr>';
674         }
675
676         $output .= '</table>';
677
678         wp_die( $output, 403 );
679 }
680
681 /**
682  * Checks if the current user has permissions to import new users.
683  *
684  * @since 3.0.0
685  *
686  * @param string $permission A permission to be checked. Currently not used.
687  * @return bool True if the user has proper permissions, false if they do not.
688  */
689 function check_import_new_users( $permission ) {
690         if ( !is_super_admin() )
691                 return false;
692         return true;
693 }
694 // See "import_allow_fetch_attachments" and "import_attachment_size_limit" filters too.
695
696 /**
697  * Generates and displays a drop-down of available languages.
698  *
699  * @since 3.0.0
700  *
701  * @param array  $lang_files Optional. An array of the language files. Default empty array.
702  * @param string $current    Optional. The current language code. Default empty.
703  */
704 function mu_dropdown_languages( $lang_files = array(), $current = '' ) {
705         $flag = false;
706         $output = array();
707
708         foreach ( (array) $lang_files as $val ) {
709                 $code_lang = basename( $val, '.mo' );
710
711                 if ( $code_lang == 'en_US' ) { // American English
712                         $flag = true;
713                         $ae = __( 'American English' );
714                         $output[$ae] = '<option value="' . esc_attr( $code_lang ) . '"' . selected( $current, $code_lang, false ) . '> ' . $ae . '</option>';
715                 } elseif ( $code_lang == 'en_GB' ) { // British English
716                         $flag = true;
717                         $be = __( 'British English' );
718                         $output[$be] = '<option value="' . esc_attr( $code_lang ) . '"' . selected( $current, $code_lang, false ) . '> ' . $be . '</option>';
719                 } else {
720                         $translated = format_code_lang( $code_lang );
721                         $output[$translated] = '<option value="' . esc_attr( $code_lang ) . '"' . selected( $current, $code_lang, false ) . '> ' . esc_html ( $translated ) . '</option>';
722                 }
723
724         }
725
726         if ( $flag === false ) // WordPress english
727                 $output[] = '<option value=""' . selected( $current, '', false ) . '>' . __( 'English' ) . "</option>";
728
729         // Order by name
730         uksort( $output, 'strnatcasecmp' );
731
732         /**
733          * Filters the languages available in the dropdown.
734          *
735          * @since MU
736          *
737          * @param array $output     HTML output of the dropdown.
738          * @param array $lang_files Available language files.
739          * @param string $current   The current language code.
740          */
741         $output = apply_filters( 'mu_dropdown_languages', $output, $lang_files, $current );
742
743         echo implode( "\n\t", $output );
744 }
745
746 /**
747  * Displays an admin notice to upgrade all sites after a core upgrade.
748  *
749  * @since 3.0.0
750  *
751  * @global int    $wp_db_version The version number of the database.
752  * @global string $pagenow
753  *
754  * @return false False if the current user is not a super admin.
755  */
756 function site_admin_notice() {
757         global $wp_db_version, $pagenow;
758
759         if ( ! is_super_admin() ) {
760                 return false;
761         }
762
763         if ( 'upgrade.php' == $pagenow ) {
764                 return;
765         }
766
767         if ( get_site_option( 'wpmu_upgrade_site' ) != $wp_db_version ) {
768                 echo "<div class='update-nag'>" . sprintf( __( 'Thank you for Updating! Please visit the <a href="%s">Upgrade Network</a> page to update all your sites.' ), esc_url( network_admin_url( 'upgrade.php' ) ) ) . "</div>";
769         }
770 }
771
772 /**
773  * Avoids a collision between a site slug and a permalink slug.
774  *
775  * In a subdirectory install this will make sure that a site and a post do not use the
776  * same subdirectory by checking for a site with the same name as a new post.
777  *
778  * @since 3.0.0
779  *
780  * @param array $data    An array of post data.
781  * @param array $postarr An array of posts. Not currently used.
782  * @return array The new array of post data after checking for collisions.
783  */
784 function avoid_blog_page_permalink_collision( $data, $postarr ) {
785         if ( is_subdomain_install() )
786                 return $data;
787         if ( $data['post_type'] != 'page' )
788                 return $data;
789         if ( !isset( $data['post_name'] ) || $data['post_name'] == '' )
790                 return $data;
791         if ( !is_main_site() )
792                 return $data;
793
794         $post_name = $data['post_name'];
795         $c = 0;
796         while( $c < 10 && get_id_from_blogname( $post_name ) ) {
797                 $post_name .= mt_rand( 1, 10 );
798                 $c ++;
799         }
800         if ( $post_name != $data['post_name'] ) {
801                 $data['post_name'] = $post_name;
802         }
803         return $data;
804 }
805
806 /**
807  * Handles the display of choosing a user's primary site.
808  *
809  * This displays the user's primary site and allows the user to choose
810  * which site is primary.
811  *
812  * @since 3.0.0
813  */
814 function choose_primary_blog() {
815         ?>
816         <table class="form-table">
817         <tr>
818         <?php /* translators: My sites label */ ?>
819                 <th scope="row"><label for="primary_blog"><?php _e( 'Primary Site' ); ?></label></th>
820                 <td>
821                 <?php
822                 $all_blogs = get_blogs_of_user( get_current_user_id() );
823                 $primary_blog = get_user_meta( get_current_user_id(), 'primary_blog', true );
824                 if ( count( $all_blogs ) > 1 ) {
825                         $found = false;
826                         ?>
827                         <select name="primary_blog" id="primary_blog">
828                                 <?php foreach ( (array) $all_blogs as $blog ) {
829                                         if ( $primary_blog == $blog->userblog_id )
830                                                 $found = true;
831                                         ?><option value="<?php echo $blog->userblog_id ?>"<?php selected( $primary_blog, $blog->userblog_id ); ?>><?php echo esc_url( get_home_url( $blog->userblog_id ) ) ?></option><?php
832                                 } ?>
833                         </select>
834                         <?php
835                         if ( !$found ) {
836                                 $blog = reset( $all_blogs );
837                                 update_user_meta( get_current_user_id(), 'primary_blog', $blog->userblog_id );
838                         }
839                 } elseif ( count( $all_blogs ) == 1 ) {
840                         $blog = reset( $all_blogs );
841                         echo esc_url( get_home_url( $blog->userblog_id ) );
842                         if ( $primary_blog != $blog->userblog_id ) // Set the primary blog again if it's out of sync with blog list.
843                                 update_user_meta( get_current_user_id(), 'primary_blog', $blog->userblog_id );
844                 } else {
845                         echo "N/A";
846                 }
847                 ?>
848                 </td>
849         </tr>
850         </table>
851         <?php
852 }
853
854 /**
855  * Whether or not we can edit this network from this page.
856  *
857  * By default editing of network is restricted to the Network Admin for that `$site_id`
858  * this allows for this to be overridden.
859  *
860  * @since 3.1.0
861  *
862  * @global wpdb $wpdb WordPress database abstraction object.
863  *
864  * @param int $site_id The network/site ID to check.
865  * @return bool True if network can be edited, otherwise false.
866  */
867 function can_edit_network( $site_id ) {
868         global $wpdb;
869
870         if ( $site_id == $wpdb->siteid )
871                 $result = true;
872         else
873                 $result = false;
874
875         /**
876          * Filters whether this network can be edited from this page.
877          *
878          * @since 3.1.0
879          *
880          * @param bool $result  Whether the network can be edited from this page.
881          * @param int  $site_id The network/site ID to check.
882          */
883         return apply_filters( 'can_edit_network', $result, $site_id );
884 }
885
886 /**
887  * Thickbox image paths for Network Admin.
888  *
889  * @since 3.1.0
890  *
891  * @access private
892  */
893 function _thickbox_path_admin_subfolder() {
894 ?>
895 <script type="text/javascript">
896 var tb_pathToImage = "<?php echo includes_url( 'js/thickbox/loadingAnimation.gif', 'relative' ); ?>";
897 </script>
898 <?php
899 }
900
901 /**
902  *
903  * @param array $users
904  */
905 function confirm_delete_users( $users ) {
906         $current_user = wp_get_current_user();
907         if ( ! is_array( $users ) || empty( $users ) ) {
908                 return false;
909         }
910         ?>
911         <h1><?php esc_html_e( 'Users' ); ?></h1>
912
913         <?php if ( 1 == count( $users ) ) : ?>
914                 <p><?php _e( 'You have chosen to delete the user from all networks and sites.' ); ?></p>
915         <?php else : ?>
916                 <p><?php _e( 'You have chosen to delete the following users from all networks and sites.' ); ?></p>
917         <?php endif; ?>
918
919         <form action="users.php?action=dodelete" method="post">
920         <input type="hidden" name="dodelete" />
921         <?php
922         wp_nonce_field( 'ms-users-delete' );
923         $site_admins = get_super_admins();
924         $admin_out = '<option value="' . esc_attr( $current_user->ID ) . '">' . $current_user->user_login . '</option>'; ?>
925         <table class="form-table">
926         <?php foreach ( ( $allusers = (array) $_POST['allusers'] ) as $user_id ) {
927                 if ( $user_id != '' && $user_id != '0' ) {
928                         $delete_user = get_userdata( $user_id );
929
930                         if ( ! current_user_can( 'delete_user', $delete_user->ID ) ) {
931                                 wp_die( sprintf( __( 'Warning! User %s cannot be deleted.' ), $delete_user->user_login ) );
932                         }
933
934                         if ( in_array( $delete_user->user_login, $site_admins ) ) {
935                                 wp_die( sprintf( __( 'Warning! User cannot be deleted. The user %s is a network administrator.' ), '<em>' . $delete_user->user_login . '</em>' ) );
936                         }
937                         ?>
938                         <tr>
939                                 <th scope="row"><?php echo $delete_user->user_login; ?>
940                                         <?php echo '<input type="hidden" name="user[]" value="' . esc_attr( $user_id ) . '" />' . "\n"; ?>
941                                 </th>
942                         <?php $blogs = get_blogs_of_user( $user_id, true );
943
944                         if ( ! empty( $blogs ) ) {
945                                 ?>
946                                 <td><fieldset><p><legend><?php printf(
947                                         /* translators: user login */
948                                         __( 'What should be done with content owned by %s?' ),
949                                         '<em>' . $delete_user->user_login . '</em>'
950                                 ); ?></legend></p>
951                                 <?php
952                                 foreach ( (array) $blogs as $key => $details ) {
953                                         $blog_users = get_users( array( 'blog_id' => $details->userblog_id, 'fields' => array( 'ID', 'user_login' ) ) );
954                                         if ( is_array( $blog_users ) && !empty( $blog_users ) ) {
955                                                 $user_site = "<a href='" . esc_url( get_home_url( $details->userblog_id ) ) . "'>{$details->blogname}</a>";
956                                                 $user_dropdown = '<label for="reassign_user" class="screen-reader-text">' . __( 'Select a user' ) . '</label>';
957                                                 $user_dropdown .= "<select name='blog[$user_id][$key]' id='reassign_user'>";
958                                                 $user_list = '';
959                                                 foreach ( $blog_users as $user ) {
960                                                         if ( ! in_array( $user->ID, $allusers ) ) {
961                                                                 $user_list .= "<option value='{$user->ID}'>{$user->user_login}</option>";
962                                                         }
963                                                 }
964                                                 if ( '' == $user_list ) {
965                                                         $user_list = $admin_out;
966                                                 }
967                                                 $user_dropdown .= $user_list;
968                                                 $user_dropdown .= "</select>\n";
969                                                 ?>
970                                                 <ul style="list-style:none;">
971                                                         <li><?php printf( __( 'Site: %s' ), $user_site ); ?></li>
972                                                         <li><label><input type="radio" id="delete_option0" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID ?>]" value="delete" checked="checked" />
973                                                         <?php _e( 'Delete all content.' ); ?></label></li>
974                                                         <li><label><input type="radio" id="delete_option1" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID ?>]" value="reassign" />
975                                                         <?php _e( 'Attribute all content to:' ); ?></label>
976                                                         <?php echo $user_dropdown; ?></li>
977                                                 </ul>
978                                                 <?php
979                                         }
980                                 }
981                                 echo "</fieldset></td></tr>";
982                         } else {
983                                 ?>
984                                 <td><fieldset><p><legend><?php _e( 'User has no sites or content and will be deleted.' ); ?></legend></p>
985                         <?php } ?>
986                         </tr>
987                 <?php
988                 }
989         }
990
991         ?>
992         </table>
993         <?php
994         /** This action is documented in wp-admin/users.php */
995         do_action( 'delete_user_form', $current_user, $allusers );
996
997         if ( 1 == count( $users ) ) : ?>
998                 <p><?php _e( 'Once you hit &#8220;Confirm Deletion&#8221;, the user will be permanently removed.' ); ?></p>
999         <?php else : ?>
1000                 <p><?php _e( 'Once you hit &#8220;Confirm Deletion&#8221;, these users will be permanently removed.' ); ?></p>
1001         <?php endif;
1002
1003         submit_button( __('Confirm Deletion'), 'primary' );
1004         ?>
1005         </form>
1006         <?php
1007         return true;
1008 }
1009
1010 /**
1011  * Print JavaScript in the header on the Network Settings screen.
1012  *
1013  * @since 4.1.0
1014  */
1015 function network_settings_add_js() {
1016 ?>
1017 <script type="text/javascript">
1018 jQuery(document).ready( function($) {
1019         var languageSelect = $( '#WPLANG' );
1020         $( 'form' ).submit( function() {
1021                 // Don't show a spinner for English and installed languages,
1022                 // as there is nothing to download.
1023                 if ( ! languageSelect.find( 'option:selected' ).data( 'installed' ) ) {
1024                         $( '#submit', this ).after( '<span class="spinner language-install-spinner" />' );
1025                 }
1026         });
1027 });
1028 </script>
1029 <?php
1030 }
1031
1032 /**
1033  * Outputs the HTML for a network's "Edit Site" tabular interface.
1034  *
1035  * @since 4.6.0
1036  *
1037  * @param $args {
1038  *     Optional. Array or string of Query parameters. Default empty array.
1039  *
1040  *     @type int    $blog_id  The site ID. Default is the current site.
1041  *     @type array  $links    The tabs to include with (label|url|cap) keys.
1042  *     @type string $selected The ID of the selected link.
1043  * }
1044  */
1045 function network_edit_site_nav( $args = array() ) {
1046
1047         /**
1048          * Filters the links that appear on site-editing network pages.
1049          *
1050          * Default links: 'site-info', 'site-users', 'site-themes', and 'site-settings'.
1051          *
1052          * @since 4.6.0
1053          *
1054          * @param array $links {
1055          *     An array of link data representing individual network admin pages.
1056          *
1057          *     @type array $link_slug {
1058          *         An array of information about the individual link to a page.
1059          *
1060          *         $type string $label Label to use for the link.
1061          *         $type string $url   URL, relative to `network_admin_url()` to use for the link.
1062          *         $type string $cap   Capability required to see the link.
1063          *     }
1064          * }
1065          */
1066         $links = apply_filters( 'network_edit_site_nav_links', array(
1067                 'site-info'     => array( 'label' => __( 'Info' ),     'url' => 'site-info.php',     'cap' => 'manage_sites' ),
1068                 'site-users'    => array( 'label' => __( 'Users' ),    'url' => 'site-users.php',    'cap' => 'manage_sites' ),
1069                 'site-themes'   => array( 'label' => __( 'Themes' ),   'url' => 'site-themes.php',   'cap' => 'manage_sites' ),
1070                 'site-settings' => array( 'label' => __( 'Settings' ), 'url' => 'site-settings.php', 'cap' => 'manage_sites' )
1071         ) );
1072
1073         // Parse arguments
1074         $r = wp_parse_args( $args, array(
1075                 'blog_id'  => isset( $_GET['blog_id'] ) ? (int) $_GET['blog_id'] : 0,
1076                 'links'    => $links,
1077                 'selected' => 'site-info',
1078         ) );
1079
1080         // Setup the links array
1081         $screen_links = array();
1082
1083         // Loop through tabs
1084         foreach ( $r['links'] as $link_id => $link ) {
1085
1086                 // Skip link if user can't access
1087                 if ( ! current_user_can( $link['cap'], $r['blog_id'] ) ) {
1088                         continue;
1089                 }
1090
1091                 // Link classes
1092                 $classes = array( 'nav-tab' );
1093
1094                 // Selected is set by the parent OR assumed by the $pagenow global
1095                 if ( $r['selected'] === $link_id || $link['url'] === $GLOBALS['pagenow'] ) {
1096                         $classes[] = 'nav-tab-active';
1097                 }
1098
1099                 // Escape each class
1100                 $esc_classes = implode( ' ', $classes );
1101
1102                 // Get the URL for this link
1103                 $url = add_query_arg( array( 'id' => $r['blog_id'] ), network_admin_url( $link['url'] ) );
1104
1105                 // Add link to nav links
1106                 $screen_links[ $link_id ] = '<a href="' . esc_url( $url ) . '" id="' . esc_attr( $link_id ) . '" class="' . $esc_classes . '">' . esc_html( $link['label'] ) . '</a>';
1107         }
1108
1109         // All done!
1110         echo '<h2 class="nav-tab-wrapper wp-clearfix">';
1111         echo implode( '', $screen_links );
1112         echo '</h2>';
1113 }