WordPress 4.2
[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) /1024 ) );
33         if ( $file_size > ( 1024 * get_site_option( 'fileupload_maxk', 1500 ) ) )
34                 $file['error'] = sprintf(__('This file is too big. Files must be less than %1$s KB in size.'), get_site_option( 'fileupload_maxk', 1500 ) );
35         if ( upload_is_user_over_quota( false ) ) {
36                 $file['error'] = __( 'You have used your space quota. Please delete files before uploading.' );
37         }
38         if ( $file['error'] != '0' && ! isset( $_POST['html-upload'] ) && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) {
39                 wp_die( $file['error'] . ' <a href="javascript:history.go(-1)">' . __( 'Back' ) . '</a>' );
40         }
41
42         return $file;
43 }
44 add_filter( 'wp_handle_upload_prefilter', 'check_upload_size' );
45
46 /**
47  * Delete a blog.
48  *
49  * @since 3.0.0
50  *
51  * @param int  $blog_id Blog ID.
52  * @param bool $drop    True if blog's table should be dropped. Default is false.
53  */
54 function wpmu_delete_blog( $blog_id, $drop = false ) {
55         global $wpdb;
56
57         $switch = false;
58         if ( get_current_blog_id() != $blog_id ) {
59                 $switch = true;
60                 switch_to_blog( $blog_id );
61         }
62
63         $blog = get_blog_details( $blog_id );
64         /**
65          * Fires before a blog is deleted.
66          *
67          * @since MU
68          *
69          * @param int  $blog_id The blog ID.
70          * @param bool $drop    True if blog's table should be dropped. Default is false.
71          */
72         do_action( 'delete_blog', $blog_id, $drop );
73
74         $users = get_users( array( 'blog_id' => $blog_id, 'fields' => 'ids' ) );
75
76         // Remove users from this blog.
77         if ( ! empty( $users ) ) {
78                 foreach ( $users as $user_id ) {
79                         remove_user_from_blog( $user_id, $blog_id );
80                 }
81         }
82
83         update_blog_status( $blog_id, 'deleted', 1 );
84
85         $current_site = get_current_site();
86
87         // If a full blog object is not available, do not destroy anything.
88         if ( $drop && ! $blog ) {
89                 $drop = false;
90         }
91
92         // Don't destroy the initial, main, or root blog.
93         if ( $drop && ( 1 == $blog_id || is_main_site( $blog_id ) || ( $blog->path == $current_site->path && $blog->domain == $current_site->domain ) ) ) {
94                 $drop = false;
95         }
96
97         $upload_path = trim( get_option( 'upload_path' ) );
98
99         // If ms_files_rewriting is enabled and upload_path is empty, wp_upload_dir is not reliable.
100         if ( $drop && get_site_option( 'ms_files_rewriting' ) && empty( $upload_path ) ) {
101                 $drop = false;
102         }
103
104         if ( $drop ) {
105                 $uploads = wp_upload_dir();
106
107                 $tables = $wpdb->tables( 'blog' );
108                 /**
109                  * Filter the tables to drop when the blog is deleted.
110                  *
111                  * @since MU
112                  *
113                  * @param array $tables  The blog tables to be dropped.
114                  * @param int   $blog_id The ID of the blog to drop tables for.
115                  */
116                 $drop_tables = apply_filters( 'wpmu_drop_tables', $tables, $blog_id );
117
118                 foreach ( (array) $drop_tables as $table ) {
119                         $wpdb->query( "DROP TABLE IF EXISTS `$table`" );
120                 }
121
122                 $wpdb->delete( $wpdb->blogs, array( 'blog_id' => $blog_id ) );
123
124                 /**
125                  * Filter the upload base directory to delete when the blog is deleted.
126                  *
127                  * @since MU
128                  *
129                  * @param string $uploads['basedir'] Uploads path without subdirectory. @see wp_upload_dir()
130                  * @param int    $blog_id            The blog ID.
131                  */
132                 $dir = apply_filters( 'wpmu_delete_blog_upload_dir', $uploads['basedir'], $blog_id );
133                 $dir = rtrim( $dir, DIRECTORY_SEPARATOR );
134                 $top_dir = $dir;
135                 $stack = array($dir);
136                 $index = 0;
137
138                 while ( $index < count( $stack ) ) {
139                         // Get indexed directory from stack
140                         $dir = $stack[$index];
141
142                         $dh = @opendir( $dir );
143                         if ( $dh ) {
144                                 while ( ( $file = @readdir( $dh ) ) !== false ) {
145                                         if ( $file == '.' || $file == '..' )
146                                                 continue;
147
148                                         if ( @is_dir( $dir . DIRECTORY_SEPARATOR . $file ) ) {
149                                                 $stack[] = $dir . DIRECTORY_SEPARATOR . $file;
150                                         } elseif ( @is_file( $dir . DIRECTORY_SEPARATOR . $file ) ) {
151                                                 @unlink( $dir . DIRECTORY_SEPARATOR . $file );
152                                         }
153                                 }
154                                 @closedir( $dh );
155                         }
156                         $index++;
157                 }
158
159                 $stack = array_reverse( $stack ); // Last added dirs are deepest
160                 foreach( (array) $stack as $dir ) {
161                         if ( $dir != $top_dir)
162                         @rmdir( $dir );
163                 }
164
165                 clean_blog_cache( $blog );
166         }
167
168         if ( $switch )
169                 restore_current_blog();
170 }
171
172 /**
173  * Delete a user from the network and remove from all sites.
174  *
175  * @since 3.0.0
176  *
177  * @todo Merge with wp_delete_user() ?
178  *
179  * @param int $id The user ID.
180  * @return bool True if the user was deleted, otherwise false.
181  */
182 function wpmu_delete_user( $id ) {
183         global $wpdb;
184
185         $id = (int) $id;
186         $user = new WP_User( $id );
187
188         if ( !$user->exists() )
189                 return false;
190         /**
191          * Fires before a user is deleted from the network.
192          *
193          * @since MU
194          *
195          * @param int $id ID of the user about to be deleted from the network.
196          */
197         do_action( 'wpmu_delete_user', $id );
198
199         $blogs = get_blogs_of_user( $id );
200
201         if ( ! empty( $blogs ) ) {
202                 foreach ( $blogs as $blog ) {
203                         switch_to_blog( $blog->userblog_id );
204                         remove_user_from_blog( $id, $blog->userblog_id );
205
206                         $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author = %d", $id ) );
207                         foreach ( (array) $post_ids as $post_id ) {
208                                 wp_delete_post( $post_id );
209                         }
210
211                         // Clean links
212                         $link_ids = $wpdb->get_col( $wpdb->prepare( "SELECT link_id FROM $wpdb->links WHERE link_owner = %d", $id ) );
213
214                         if ( $link_ids ) {
215                                 foreach ( $link_ids as $link_id )
216                                         wp_delete_link( $link_id );
217                         }
218
219                         restore_current_blog();
220                 }
221         }
222
223         $meta = $wpdb->get_col( $wpdb->prepare( "SELECT umeta_id FROM $wpdb->usermeta WHERE user_id = %d", $id ) );
224         foreach ( $meta as $mid )
225                 delete_metadata_by_mid( 'user', $mid );
226
227         $wpdb->delete( $wpdb->users, array( 'ID' => $id ) );
228
229         clean_user_cache( $user );
230
231         /** This action is documented in wp-admin/includes/user.php */
232         do_action( 'deleted_user', $id );
233
234         return true;
235 }
236
237 /**
238  * Sends an email when a site administrator email address is changed.
239  *
240  * @since 3.0.0
241  *
242  * @param string $old_value The old email address. Not currently used.
243  * @param string $value     The new email address.
244  */
245 function update_option_new_admin_email( $old_value, $value ) {
246         if ( $value == get_option( 'admin_email' ) || !is_email( $value ) )
247                 return;
248
249         $hash = md5( $value. time() .mt_rand() );
250         $new_admin_email = array(
251                 'hash' => $hash,
252                 'newemail' => $value
253         );
254         update_option( 'adminhash', $new_admin_email );
255
256         $email_text = __( 'Howdy ###USERNAME###,
257
258 You recently requested to have the administration email address on
259 your site changed.
260
261 If this is correct, please click on the following link to change it:
262 ###ADMIN_URL###
263
264 You can safely ignore and delete this email if you do not want to
265 take this action.
266
267 This email has been sent to ###EMAIL###
268
269 Regards,
270 All at ###SITENAME###
271 ###SITEURL###' );
272
273         /**
274          * Filter the email text sent when the site admin email is changed.
275          *
276          * The following strings have a special meaning and will get replaced dynamically:
277          * ###USERNAME###  The current user's username.
278          * ###ADMIN_URL### The link to click on to confirm the email change.
279          * ###EMAIL###     The new email.
280          * ###SITENAME###  The name of the site.
281          * ###SITEURL###   The URL to the site.
282          *
283          * @since MU
284          *
285          * @param string $email_text      Text in the email.
286          * @param string $new_admin_email New admin email that the current administration email was changed to.
287          */
288         $content = apply_filters( 'new_admin_email_content', $email_text, $new_admin_email );
289
290         $content = str_replace( '###USERNAME###', $current_user->user_login, $content );
291         $content = str_replace( '###ADMIN_URL###', esc_url( admin_url( 'options.php?adminhash='.$hash ) ), $content );
292         $content = str_replace( '###EMAIL###', $value, $content );
293         $content = str_replace( '###SITENAME###', get_site_option( 'site_name' ), $content );
294         $content = str_replace( '###SITEURL###', network_home_url(), $content );
295
296         wp_mail( $value, sprintf( __( '[%s] New Admin Email Address' ), wp_specialchars_decode( get_option( 'blogname' ) ) ), $content );
297 }
298 add_action( 'update_option_new_admin_email', 'update_option_new_admin_email', 10, 2 );
299 add_action( 'add_option_new_admin_email', 'update_option_new_admin_email', 10, 2 );
300
301 /**
302  * Sends an email when an email address change is requested.
303  *
304  * @since 3.0.0
305  *
306  * @global object $errors WP_Error object.
307  * @global object $wpdb   WordPress database object.
308  */
309 function send_confirmation_on_profile_email() {
310         global $errors, $wpdb;
311         $current_user = wp_get_current_user();
312         if ( ! is_object($errors) )
313                 $errors = new WP_Error();
314
315         if ( $current_user->ID != $_POST['user_id'] )
316                 return false;
317
318         if ( $current_user->user_email != $_POST['email'] ) {
319                 if ( !is_email( $_POST['email'] ) ) {
320                         $errors->add( 'user_email', __( "<strong>ERROR</strong>: The email address isn&#8217;t correct." ), array( 'form-field' => 'email' ) );
321                         return;
322                 }
323
324                 if ( $wpdb->get_var( $wpdb->prepare( "SELECT user_email FROM {$wpdb->users} WHERE user_email=%s", $_POST['email'] ) ) ) {
325                         $errors->add( 'user_email', __( "<strong>ERROR</strong>: The email address is already used." ), array( 'form-field' => 'email' ) );
326                         delete_option( $current_user->ID . '_new_email' );
327                         return;
328                 }
329
330                 $hash = md5( $_POST['email'] . time() . mt_rand() );
331                 $new_user_email = array(
332                                 'hash' => $hash,
333                                 'newemail' => $_POST['email']
334                                 );
335                 update_option( $current_user->ID . '_new_email', $new_user_email );
336
337                 $email_text = __( 'Howdy ###USERNAME###,
338
339 You recently requested to have the email address on your account changed.
340
341 If this is correct, please click on the following link to change it:
342 ###ADMIN_URL###
343
344 You can safely ignore and delete this email if you do not want to
345 take this action.
346
347 This email has been sent to ###EMAIL###
348
349 Regards,
350 All at ###SITENAME###
351 ###SITEURL###' );
352
353                 /**
354                  * Filter the email text sent when a user changes emails.
355                  *
356                  * The following strings have a special meaning and will get replaced dynamically:
357                  * ###USERNAME###  The current user's username.
358                  * ###ADMIN_URL### The link to click on to confirm the email change.
359                  * ###EMAIL###     The new email.
360                  * ###SITENAME###  The name of the site.
361                  * ###SITEURL###   The URL to the site.
362                  *
363                  * @since MU
364                  *
365                  * @param string $email_text     Text in the email.
366                  * @param string $new_user_email New user email that the current user has changed to.
367                  */
368                 $content = apply_filters( 'new_user_email_content', $email_text, $new_user_email );
369
370                 $content = str_replace( '###USERNAME###', $current_user->user_login, $content );
371                 $content = str_replace( '###ADMIN_URL###', esc_url( admin_url( 'profile.php?newuseremail='.$hash ) ), $content );
372                 $content = str_replace( '###EMAIL###', $_POST['email'], $content);
373                 $content = str_replace( '###SITENAME###', get_site_option( 'site_name' ), $content );
374                 $content = str_replace( '###SITEURL###', network_home_url(), $content );
375
376                 wp_mail( $_POST['email'], sprintf( __( '[%s] New Email Address' ), wp_specialchars_decode( get_option( 'blogname' ) ) ), $content );
377                 $_POST['email'] = $current_user->user_email;
378         }
379 }
380 add_action( 'personal_options_update', 'send_confirmation_on_profile_email' );
381
382 /**
383  * Adds an admin notice alerting the user to check for confirmation email
384  * after email address change.
385  *
386  * @since 3.0.0
387  */
388 function new_user_email_admin_notice() {
389         if ( strpos( $_SERVER['PHP_SELF'], 'profile.php' ) && isset( $_GET['updated'] ) && $email = get_option( get_current_user_id() . '_new_email' ) )
390                 echo "<div class='update-nag'>" . sprintf( __( "Your email address has not been updated yet. Please check your inbox at %s for a confirmation email." ), $email['newemail'] ) . "</div>";
391 }
392 add_action( 'admin_notices', 'new_user_email_admin_notice' );
393
394 /**
395  * Check whether a blog has used its allotted upload space.
396  *
397  * @since MU
398  *
399  * @param bool $echo Optional. If $echo is set and the quota is exceeded, a warning message is echoed. Default is true.
400  * @return bool True if user is over upload space quota, otherwise false.
401  */
402 function upload_is_user_over_quota( $echo = true ) {
403         if ( get_site_option( 'upload_space_check_disabled' ) )
404                 return false;
405
406         $space_allowed = get_space_allowed();
407         if ( empty( $space_allowed ) || !is_numeric( $space_allowed ) )
408                 $space_allowed = 10; // Default space allowed is 10 MB
409
410         $space_used = get_space_used();
411
412         if ( ( $space_allowed - $space_used ) < 0 ) {
413                 if ( $echo )
414                         _e( 'Sorry, you have used your space allocation. Please delete some files to upload more files.' );
415                 return true;
416         } else {
417                 return false;
418         }
419 }
420
421 /**
422  * Displays the amount of disk space used by the current blog. Not used in core.
423  *
424  * @since MU
425  */
426 function display_space_usage() {
427         $space_allowed = get_space_allowed();
428         $space_used = get_space_used();
429
430         $percent_used = ( $space_used / $space_allowed ) * 100;
431
432         if ( $space_allowed > 1000 ) {
433                 $space = number_format( $space_allowed / 1024 );
434                 /* translators: Gigabytes */
435                 $space .= __( 'GB' );
436         } else {
437                 $space = number_format( $space_allowed );
438                 /* translators: Megabytes */
439                 $space .= __( 'MB' );
440         }
441         ?>
442         <strong><?php printf( __( 'Used: %1$s%% of %2$s' ), number_format( $percent_used ), $space ); ?></strong>
443         <?php
444 }
445
446 /**
447  * Get the remaining upload space for this blog.
448  *
449  * @since MU
450  *
451  * @param int $size Current max size in bytes
452  * @return int Max size in bytes
453  */
454 function fix_import_form_size( $size ) {
455         if ( upload_is_user_over_quota( false ) == true )
456                 return 0;
457
458         $available = get_upload_space_available();
459         return min( $size, $available );
460 }
461
462 /**
463  * Displays the edit blog upload space setting form on the Edit Blog screen.
464  *
465  * @since 3.0.0
466  *
467  * @param int $id The ID of the blog to display the setting for.
468  */
469 function upload_space_setting( $id ) {
470         switch_to_blog( $id );
471         $quota = get_option( 'blog_upload_space' );
472         restore_current_blog();
473
474         if ( !$quota )
475                 $quota = '';
476
477         ?>
478         <tr>
479                 <th><label for="blog-upload-space-number"><?php _e( 'Site Upload Space Quota' ); ?></label></th>
480                 <td>
481                         <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; ?>" />
482                         <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>
483                 </td>
484         </tr>
485         <?php
486 }
487 add_action( 'wpmueditblogaction', 'upload_space_setting' );
488
489 /**
490  * Update the status of a user in the database.
491  *
492  * Used in core to mark a user as spam or "ham" (not spam) in Multisite.
493  *
494  * @since 3.0.0
495  *
496  * @param int    $id         The user ID.
497  * @param string $pref       The column in the wp_users table to update the user's status
498  *                           in (presumably user_status, spam, or deleted).
499  * @param int    $value      The new status for the user.
500  * @param null   $deprecated Deprecated as of 3.0.2 and should not be used.
501  * @return int   The initially passed $value.
502  */
503 function update_user_status( $id, $pref, $value, $deprecated = null ) {
504         global $wpdb;
505
506         if ( null !== $deprecated )
507                 _deprecated_argument( __FUNCTION__, '3.1' );
508
509         $wpdb->update( $wpdb->users, array( sanitize_key( $pref ) => $value ), array( 'ID' => $id ) );
510
511         $user = new WP_User( $id );
512         clean_user_cache( $user );
513
514         if ( $pref == 'spam' ) {
515                 if ( $value == 1 ) {
516                         /**
517                          * Fires after the user is marked as a SPAM user.
518                          *
519                          * @since 3.0.0
520                          *
521                          * @param int $id ID of the user marked as SPAM.
522                          */
523                         do_action( 'make_spam_user', $id );
524                 } else {
525                         /**
526                          * Fires after the user is marked as a HAM user. Opposite of SPAM.
527                          *
528                          * @since 3.0.0
529                          *
530                          * @param int $id ID of the user marked as HAM.
531                          */
532                         do_action( 'make_ham_user', $id );
533                 }
534         }
535
536         return $value;
537 }
538
539 /**
540  * Cleans the user cache for a specific user.
541  *
542  * @since 3.0.0
543  *
544  * @param int $id The user ID.
545  * @return bool|int The ID of the refreshed user or false if the user does not exist.
546  */
547 function refresh_user_details( $id ) {
548         $id = (int) $id;
549
550         if ( !$user = get_userdata( $id ) )
551                 return false;
552
553         clean_user_cache( $user );
554
555         return $id;
556 }
557
558 /**
559  * Returns the language for a language code.
560  *
561  * @since 3.0.0
562  *
563  * @param string $code Optional. The two-letter language code. Default empty.
564  * @return string The language corresponding to $code if it exists. If it does not exist,
565  *                then the first two letters of $code is returned.
566  */
567 function format_code_lang( $code = '' ) {
568         $code = strtolower( substr( $code, 0, 2 ) );
569         $lang_codes = array(
570                 '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',
571                 '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',
572                 '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',
573                 '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',
574                 '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',
575                 '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',
576                 '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',
577                 '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',
578                 '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',
579                 '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',
580                 've' => 'Venda', 'vi' => 'Vietnamese', 'vo' => 'Volapük', 'cy' => 'Welsh','wa' => 'Walloon','wo' => 'Wolof', 'xh' => 'Xhosa', 'yi' => 'Yiddish', 'yo' => 'Yoruba', 'za' => 'Zhuang; Chuang', 'zu' => 'Zulu' );
581
582         /**
583          * Filter the language codes.
584          *
585          * @since MU
586          *
587          * @param array  $lang_codes Key/value pair of language codes where key is the short version.
588          * @param string $code       A two-letter designation of the language.
589          */
590         $lang_codes = apply_filters( 'lang_codes', $lang_codes, $code );
591         return strtr( $code, $lang_codes );
592 }
593
594 /**
595  * Synchronize category and post tag slugs when global terms are enabled.
596  *
597  * @since 3.0.0
598  *
599  * @param object $term     The term.
600  * @param string $taxonomy The taxonomy for $term. Should be 'category' or 'post_tag', as these are
601  *                         the only taxonomies which are processed by this function; anything else
602  *                         will be returned untouched.
603  * @return object|array Returns `$term`, after filtering the 'slug' field with {@see sanitize_title()}
604  *                      if $taxonomy is 'category' or 'post_tag'.
605  */
606 function sync_category_tag_slugs( $term, $taxonomy ) {
607         if ( global_terms_enabled() && ( $taxonomy == 'category' || $taxonomy == 'post_tag' ) ) {
608                 if ( is_object( $term ) ) {
609                         $term->slug = sanitize_title( $term->name );
610                 } else {
611                         $term['slug'] = sanitize_title( $term['name'] );
612                 }
613         }
614         return $term;
615 }
616 add_filter( 'get_term', 'sync_category_tag_slugs', 10, 2 );
617
618 /**
619  * Displays an access denied message when a user tries to view a site's dashboard they
620  * do not have access to.
621  *
622  * @since 3.2.0
623  * @access private
624  */
625 function _access_denied_splash() {
626         if ( ! is_user_logged_in() || is_network_admin() )
627                 return;
628
629         $blogs = get_blogs_of_user( get_current_user_id() );
630
631         if ( wp_list_filter( $blogs, array( 'userblog_id' => get_current_blog_id() ) ) )
632                 return;
633
634         $blog_name = get_bloginfo( 'name' );
635
636         if ( empty( $blogs ) )
637                 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 );
638
639         $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>';
640         $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>';
641
642         $output .= '<h3>' . __('Your Sites') . '</h3>';
643         $output .= '<table>';
644
645         foreach ( $blogs as $blog ) {
646                 $output .= '<tr>';
647                 $output .= "<td>{$blog->blogname}</td>";
648                 $output .= '<td><a href="' . esc_url( get_admin_url( $blog->userblog_id ) ) . '">' . __( 'Visit Dashboard' ) . '</a> | ' .
649                         '<a href="' . esc_url( get_home_url( $blog->userblog_id ) ). '">' . __( 'View Site' ) . '</a></td>';
650                 $output .= '</tr>';
651         }
652
653         $output .= '</table>';
654
655         wp_die( $output, 403 );
656 }
657 add_action( 'admin_page_access_denied', '_access_denied_splash', 99 );
658
659 /**
660  * Checks if the current user has permissions to import new users.
661  *
662  * @since 3.0.0
663  *
664  * @param string $permission A permission to be checked. Currently not used.
665  * @return bool True if the user has proper permissions, false if they do not.
666  */
667 function check_import_new_users( $permission ) {
668         if ( !is_super_admin() )
669                 return false;
670         return true;
671 }
672 add_filter( 'import_allow_create_users', 'check_import_new_users' );
673 // See "import_allow_fetch_attachments" and "import_attachment_size_limit" filters too.
674
675 /**
676  * Generates and displays a drop-down of available languages.
677  *
678  * @since 3.0.0
679  *
680  * @param array  $lang_files Optional. An array of the language files. Default empty array.
681  * @param string $current    Optional. The current language code. Default empty.
682  */
683 function mu_dropdown_languages( $lang_files = array(), $current = '' ) {
684         $flag = false;
685         $output = array();
686
687         foreach ( (array) $lang_files as $val ) {
688                 $code_lang = basename( $val, '.mo' );
689
690                 if ( $code_lang == 'en_US' ) { // American English
691                         $flag = true;
692                         $ae = __( 'American English' );
693                         $output[$ae] = '<option value="' . esc_attr( $code_lang ) . '"' . selected( $current, $code_lang, false ) . '> ' . $ae . '</option>';
694                 } elseif ( $code_lang == 'en_GB' ) { // British English
695                         $flag = true;
696                         $be = __( 'British English' );
697                         $output[$be] = '<option value="' . esc_attr( $code_lang ) . '"' . selected( $current, $code_lang, false ) . '> ' . $be . '</option>';
698                 } else {
699                         $translated = format_code_lang( $code_lang );
700                         $output[$translated] = '<option value="' . esc_attr( $code_lang ) . '"' . selected( $current, $code_lang, false ) . '> ' . esc_html ( $translated ) . '</option>';
701                 }
702
703         }
704
705         if ( $flag === false ) // WordPress english
706                 $output[] = '<option value=""' . selected( $current, '', false ) . '>' . __( 'English' ) . "</option>";
707
708         // Order by name
709         uksort( $output, 'strnatcasecmp' );
710
711         /**
712          * Filter the languages available in the dropdown.
713          *
714          * @since MU
715          *
716          * @param array $output     HTML output of the dropdown.
717          * @param array $lang_files Available language files.
718          * @param string $current   The current language code.
719          */
720         $output = apply_filters( 'mu_dropdown_languages', $output, $lang_files, $current );
721
722         echo implode( "\n\t", $output );
723 }
724
725 /**
726  * Displays an admin notice to upgrade all sites after a core upgrade.
727  *
728  * @since 3.0.0
729  *
730  * @global int $wp_db_version The version number of the database.
731  */
732 function site_admin_notice() {
733         global $wp_db_version;
734         if ( !is_super_admin() )
735                 return false;
736         if ( get_site_option( 'wpmu_upgrade_site' ) != $wp_db_version )
737                 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>";
738 }
739 add_action( 'admin_notices', 'site_admin_notice' );
740 add_action( 'network_admin_notices', 'site_admin_notice' );
741
742 /**
743  * Avoids a collision between a site slug and a permalink slug.
744  *
745  * In a subdirectory install this will make sure that a site and a post do not use the
746  * same subdirectory by checking for a site with the same name as a new post.
747  *
748  * @since 3.0.0
749  *
750  * @param array $data    An array of post data.
751  * @param array $postarr An array of posts. Not currently used.
752  * @return array The new array of post data after checking for collisions.
753  */
754 function avoid_blog_page_permalink_collision( $data, $postarr ) {
755         if ( is_subdomain_install() )
756                 return $data;
757         if ( $data['post_type'] != 'page' )
758                 return $data;
759         if ( !isset( $data['post_name'] ) || $data['post_name'] == '' )
760                 return $data;
761         if ( !is_main_site() )
762                 return $data;
763
764         $post_name = $data['post_name'];
765         $c = 0;
766         while( $c < 10 && get_id_from_blogname( $post_name ) ) {
767                 $post_name .= mt_rand( 1, 10 );
768                 $c ++;
769         }
770         if ( $post_name != $data['post_name'] ) {
771                 $data['post_name'] = $post_name;
772         }
773         return $data;
774 }
775 add_filter( 'wp_insert_post_data', 'avoid_blog_page_permalink_collision', 10, 2 );
776
777 /**
778  * Handles the display of choosing a user's primary site.
779  *
780  * This displays the user's primary site and allows the user to choose
781  * which site is primary.
782  *
783  * @since 3.0.0
784  */
785 function choose_primary_blog() {
786         ?>
787         <table class="form-table">
788         <tr>
789         <?php /* translators: My sites label */ ?>
790                 <th scope="row"><label for="primary_blog"><?php _e( 'Primary Site' ); ?></label></th>
791                 <td>
792                 <?php
793                 $all_blogs = get_blogs_of_user( get_current_user_id() );
794                 $primary_blog = get_user_meta( get_current_user_id(), 'primary_blog', true );
795                 if ( count( $all_blogs ) > 1 ) {
796                         $found = false;
797                         ?>
798                         <select name="primary_blog" id="primary_blog">
799                                 <?php foreach( (array) $all_blogs as $blog ) {
800                                         if ( $primary_blog == $blog->userblog_id )
801                                                 $found = true;
802                                         ?><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
803                                 } ?>
804                         </select>
805                         <?php
806                         if ( !$found ) {
807                                 $blog = reset( $all_blogs );
808                                 update_user_meta( get_current_user_id(), 'primary_blog', $blog->userblog_id );
809                         }
810                 } elseif ( count( $all_blogs ) == 1 ) {
811                         $blog = reset( $all_blogs );
812                         echo $blog->domain;
813                         if ( $primary_blog != $blog->userblog_id ) // Set the primary blog again if it's out of sync with blog list.
814                                 update_user_meta( get_current_user_id(), 'primary_blog', $blog->userblog_id );
815                 } else {
816                         echo "N/A";
817                 }
818                 ?>
819                 </td>
820         </tr>
821         <?php if ( in_array( get_site_option( 'registration' ), array( 'all', 'blog' ) ) ) : ?>
822                 <tr>
823                         <th scope="row" colspan="2" class="th-full">
824                                 <?php
825                                 /** This filter is documented in wp-login.php */
826                                 $sign_up_url = apply_filters( 'wp_signup_location', network_site_url( 'wp-signup.php' ) );
827                                 ?>
828                                 <a href="<?php echo esc_url( $sign_up_url ); ?>"><?php _e( 'Create a New Site' ); ?></a>
829                         </th>
830                 </tr>
831         <?php endif; ?>
832         </table>
833         <?php
834 }
835
836 /**
837  * Grants Super Admin privileges.
838  *
839  * @since 3.0.0
840  *
841  * @param int $user_id ID of the user to be granted Super Admin privileges.
842  * @return bool True on success, false on failure. This can fail when the user is
843  *              already a super admin or when the `$super_admins` global is defined.
844  */
845 function grant_super_admin( $user_id ) {
846         // If global super_admins override is defined, there is nothing to do here.
847         if ( isset( $GLOBALS['super_admins'] ) ) {
848                 return false;
849         }
850
851         /**
852          * Fires before the user is granted Super Admin privileges.
853          *
854          * @since 3.0.0
855          *
856          * @param int $user_id ID of the user that is about to be granted Super Admin privileges.
857          */
858         do_action( 'grant_super_admin', $user_id );
859
860         // Directly fetch site_admins instead of using get_super_admins()
861         $super_admins = get_site_option( 'site_admins', array( 'admin' ) );
862
863         $user = get_userdata( $user_id );
864         if ( $user && ! in_array( $user->user_login, $super_admins ) ) {
865                 $super_admins[] = $user->user_login;
866                 update_site_option( 'site_admins' , $super_admins );
867
868                 /**
869                  * Fires after the user is granted Super Admin privileges.
870                  *
871                  * @since 3.0.0
872                  *
873                  * @param int $user_id ID of the user that was granted Super Admin privileges.
874                  */
875                 do_action( 'granted_super_admin', $user_id );
876                 return true;
877         }
878         return false;
879 }
880
881 /**
882  * Revokes Super Admin privileges.
883  *
884  * @since 3.0.0
885  *
886  * @param int $user_id ID of the user Super Admin privileges to be revoked from.
887  * @return bool True on success, false on failure. This can fail when the user's email
888  *              is the network admin email or when the `$super_admins` global is defined.
889  */
890 function revoke_super_admin( $user_id ) {
891         // If global super_admins override is defined, there is nothing to do here.
892         if ( isset( $GLOBALS['super_admins'] ) ) {
893                 return false;
894         }
895
896         /**
897          * Fires before the user's Super Admin privileges are revoked.
898          *
899          * @since 3.0.0
900          *
901          * @param int $user_id ID of the user Super Admin privileges are being revoked from.
902          */
903         do_action( 'revoke_super_admin', $user_id );
904
905         // Directly fetch site_admins instead of using get_super_admins()
906         $super_admins = get_site_option( 'site_admins', array( 'admin' ) );
907
908         $user = get_userdata( $user_id );
909         if ( $user && 0 !== strcasecmp( $user->user_email, get_site_option( 'admin_email' ) ) ) {
910                 if ( false !== ( $key = array_search( $user->user_login, $super_admins ) ) ) {
911                         unset( $super_admins[$key] );
912                         update_site_option( 'site_admins', $super_admins );
913
914                         /**
915                          * Fires after the user's Super Admin privileges are revoked.
916                          *
917                          * @since 3.0.0
918                          *
919                          * @param int $user_id ID of the user Super Admin privileges were revoked from.
920                          */
921                         do_action( 'revoked_super_admin', $user_id );
922                         return true;
923                 }
924         }
925         return false;
926 }
927
928 /**
929  * Whether or not we can edit this network from this page.
930  *
931  * By default editing of network is restricted to the Network Admin for that `$site_id`
932  * this allows for this to be overridden.
933  *
934  * @since 3.1.0
935  *
936  * @param int $site_id The network/site ID to check.
937  * @return bool True if network can be edited, otherwise false.
938  */
939 function can_edit_network( $site_id ) {
940         global $wpdb;
941
942         if ( $site_id == $wpdb->siteid )
943                 $result = true;
944         else
945                 $result = false;
946
947         /**
948          * Filter whether this network can be edited from this page.
949          *
950          * @since 3.1.0
951          *
952          * @param bool $result  Whether the network can be edited from this page.
953          * @param int  $site_id The network/site ID to check.
954          */
955         return apply_filters( 'can_edit_network', $result, $site_id );
956 }
957
958 /**
959  * Thickbox image paths for Network Admin.
960  *
961  * @since 3.1.0
962  *
963  * @access private
964  */
965 function _thickbox_path_admin_subfolder() {
966 ?>
967 <script type="text/javascript">
968 var tb_pathToImage = "<?php echo includes_url( 'js/thickbox/loadingAnimation.gif', 'relative' ); ?>";
969 </script>
970 <?php
971 }