Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 231 |
|
0.00% |
0 / 22 |
CRAP | |
0.00% |
0 / 2 |
| get_available_languages | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
| VaultPress_Hotfixes | |
0.00% |
0 / 66 |
|
0.00% |
0 / 12 |
1406 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| protect_rest_type_juggling | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| protect_youtube_embeds | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| safe_embed_handler_youtube | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| disable_jetpack_oembed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_pagenum_link | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| r20493_make_url_clickable_cb | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
| r20493_split_str_by_whitespace | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
| r18346_sanitize_admin_email | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| r18346_sanitize_lang | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| protect_aioseo_ajax | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
56 | |||
| protect_woocommerce_paypal_object_injection | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
72 | |||
| VaultPress_kses | |
0.00% |
0 / 158 |
|
0.00% |
0 / 9 |
3422 | |
0.00% |
0 / 1 |
| wp_kses | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| wp_kses_split | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| _vp_kses_split_callback | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| wp_kses_split2 | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
72 | |||
| wp_kses_attr | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
182 | |||
| wp_kses_hair | |
0.00% |
0 / 62 |
|
0.00% |
0 / 1 |
462 | |||
| wp_kses_bad_protocol | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
| wp_kses_bad_protocol_once | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
| wp_kses_bad_protocol_once2 | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
| 1 | <?php |
| 2 | // don't call the file directly |
| 3 | defined( 'ABSPATH' ) || die( 0 ); |
| 4 | |
| 5 | class VaultPress_Hotfixes { |
| 6 | function __construct() { |
| 7 | |
| 8 | add_filter( 'option_new_admin_email', array( $this, 'r18346_sanitize_admin_email' ) ); |
| 9 | |
| 10 | add_filter( 'get_pagenum_link', array( $this, 'get_pagenum_link' ) ); |
| 11 | |
| 12 | // Protect All-in-one SEO from non-authorized users making changes, and script injection attacks. |
| 13 | add_action( 'wp_ajax_aioseop_ajax_save_meta', array( $this, 'protect_aioseo_ajax' ), 1 ); |
| 14 | |
| 15 | // Protect WooCommerce from object injection via PayPal IPN notifications. Affects 2.0.20 -> 2.3.10 |
| 16 | add_action( 'init', array( $this , 'protect_woocommerce_paypal_object_injection' ), 1 ); |
| 17 | } |
| 18 | |
| 19 | function protect_rest_type_juggling( $replace, $server, $request ) { |
| 20 | if ( isset( $request['id'] ) ) { |
| 21 | $request['id'] = intval( $request['id'] ); |
| 22 | } |
| 23 | |
| 24 | return $replace; |
| 25 | } |
| 26 | |
| 27 | function protect_youtube_embeds() { |
| 28 | if ( ! apply_filters( 'load_default_embeds', true ) ) { |
| 29 | return; |
| 30 | } |
| 31 | |
| 32 | wp_embed_unregister_handler( 'youtube_embed_url' ); |
| 33 | wp_embed_register_handler( 'youtube_embed_url', '#https?://(www.)?youtube\.com/(?:v|embed)/([^/]+)#i', array( $this, 'safe_embed_handler_youtube' ), 9 ); |
| 34 | } |
| 35 | |
| 36 | function safe_embed_handler_youtube( $matches, $attr, $url, $rawattr ) { |
| 37 | $matches[2] = urlencode( $matches[2] ); |
| 38 | return( wp_embed_handler_youtube( $matches, $attr, $url, $rawattr ) ); |
| 39 | } |
| 40 | |
| 41 | function disable_jetpack_oembed( $enabled ) { |
| 42 | return false; |
| 43 | } |
| 44 | |
| 45 | function get_pagenum_link( $url ) { |
| 46 | return esc_url_raw( $url ); |
| 47 | } |
| 48 | |
| 49 | function r20493_make_url_clickable_cb($matches) { |
| 50 | $url = $matches[2]; |
| 51 | |
| 52 | if ( ')' == $matches[3] && strpos( $url, '(' ) ) { |
| 53 | // If the trailing character is a closing parethesis, and the URL has an opening parenthesis in it, add the closing parenthesis to the URL. |
| 54 | // Then we can let the parenthesis balancer do its thing below. |
| 55 | $url .= $matches[3]; |
| 56 | $suffix = ''; |
| 57 | } else { |
| 58 | $suffix = $matches[3]; |
| 59 | } |
| 60 | |
| 61 | // Include parentheses in the URL only if paired |
| 62 | while ( substr_count( $url, '(' ) < substr_count( $url, ')' ) ) { |
| 63 | $suffix = strrchr( $url, ')' ) . $suffix; |
| 64 | $url = substr( $url, 0, strrpos( $url, ')' ) ); |
| 65 | } |
| 66 | |
| 67 | $url = esc_url($url); |
| 68 | if ( empty($url) ) |
| 69 | return $matches[0]; |
| 70 | |
| 71 | return $matches[1] . "<a href=\"$url\" rel=\"nofollow\">$url</a>" . $suffix; |
| 72 | } |
| 73 | |
| 74 | function r20493_split_str_by_whitespace( $string, $goal ) { |
| 75 | $chunks = array(); |
| 76 | |
| 77 | $string_nullspace = strtr( $string, "\r\n\t\v\f ", "\000\000\000\000\000\000" ); |
| 78 | |
| 79 | while ( $goal < strlen( $string_nullspace ) ) { |
| 80 | $pos = strrpos( substr( $string_nullspace, 0, $goal + 1 ), "\000" ); |
| 81 | |
| 82 | if ( false === $pos ) { |
| 83 | $pos = strpos( $string_nullspace, "\000", $goal + 1 ); |
| 84 | if ( false === $pos ) { |
| 85 | break; |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | $chunks[] = substr( $string, 0, $pos + 1 ); |
| 90 | $string = substr( $string, $pos + 1 ); |
| 91 | $string_nullspace = substr( $string_nullspace, $pos + 1 ); |
| 92 | } |
| 93 | |
| 94 | if ( $string ) { |
| 95 | $chunks[] = $string; |
| 96 | } |
| 97 | |
| 98 | return $chunks; |
| 99 | } |
| 100 | |
| 101 | function r18346_sanitize_admin_email( $value ) { |
| 102 | return sanitize_email( $value ); // Is it enough ? |
| 103 | } |
| 104 | |
| 105 | function r18346_sanitize_lang( $value ) { |
| 106 | $allowed = apply_filters( 'available_languages', get_available_languages() ); // add a filter to unit test |
| 107 | if ( !empty( $value ) && !in_array( $value, $allowed ) ) |
| 108 | return false; |
| 109 | else |
| 110 | return $value; |
| 111 | } |
| 112 | |
| 113 | // Protect All-in-one SEO AJAX calls from script injection and changes without privileges. Affects versions <= 2.1.5 |
| 114 | function protect_aioseo_ajax() { |
| 115 | if ( defined( 'AIOSEOP_VERSION' ) && version_compare( AIOSEOP_VERSION, '2.1.5', '>' ) ) |
| 116 | return; |
| 117 | |
| 118 | if ( ! isset( $_POST['post_id'] ) || ! isset( $_POST['target_meta'] ) ) |
| 119 | die( 0 ); |
| 120 | |
| 121 | // Ensure the current user has permission to write to the post. |
| 122 | if ( ! current_user_can( 'edit_post', intval( $_POST['post_id'] ) ) ) |
| 123 | die( 0 ); |
| 124 | |
| 125 | // Limit the fields that can be written to |
| 126 | if ( ! in_array( $_POST['target_meta'], array( 'title', 'description', 'keywords' ) ) ) |
| 127 | die( 0 ); |
| 128 | |
| 129 | // Strip tags from the metadata value. |
| 130 | $_POST['new_meta'] = strip_tags( $_POST['new_meta'] ); |
| 131 | } |
| 132 | |
| 133 | // Protect WooCommerce 2.0.20 - 2.3.10 from PayPal IPN object injection attack. |
| 134 | function protect_woocommerce_paypal_object_injection() { |
| 135 | global $woocommerce; |
| 136 | if ( ! isset( $woocommerce ) ) |
| 137 | return; |
| 138 | |
| 139 | $wc_version = $woocommerce->version; |
| 140 | if ( version_compare( $wc_version, '2.0.20', '<' ) || version_compare( $wc_version, '2.3.11', '>=' ) ) |
| 141 | return; |
| 142 | |
| 143 | if ( isset( $_REQUEST['paypalListener'] ) ) { |
| 144 | $check_fields = array( 'custom', 'cm' ); |
| 145 | foreach ( $check_fields as $field ) { |
| 146 | if ( isset( $_REQUEST[ $field ] ) && preg_match( '/[CO]:\+?[0-9]+:/', $_REQUEST[ $field ] ) ) { |
| 147 | die( 0 ); |
| 148 | } |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | class VaultPress_kses { |
| 155 | static function wp_kses($string, $allowed_html, $allowed_protocols = array ()) { |
| 156 | $string = wp_kses_no_null($string); |
| 157 | $string = wp_kses_js_entities($string); |
| 158 | $string = wp_kses_normalize_entities($string); |
| 159 | return VaultPress_kses::wp_kses_split($string, $allowed_html, $allowed_protocols); |
| 160 | } |
| 161 | |
| 162 | static function wp_kses_split($string, $allowed_html, $allowed_protocols) { |
| 163 | global $pass_allowed_html, $pass_allowed_protocols; |
| 164 | $pass_allowed_html = $allowed_html; |
| 165 | $pass_allowed_protocols = $allowed_protocols; |
| 166 | return preg_replace_callback( '%(<!--.*?(-->|$))|(<[^>]*(>|$)|>)%', 'VaultPress_kses::_vp_kses_split_callback', $string ); |
| 167 | } |
| 168 | |
| 169 | static function _vp_kses_split_callback( $match ) { |
| 170 | global $pass_allowed_html, $pass_allowed_protocols; |
| 171 | return VaultPress_kses::wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols ); |
| 172 | } |
| 173 | |
| 174 | static function wp_kses_split2($string, $allowed_html, $allowed_protocols) { |
| 175 | $string = wp_kses_stripslashes($string); |
| 176 | |
| 177 | if (substr($string, 0, 1) != '<') |
| 178 | return '>'; |
| 179 | # It matched a ">" character |
| 180 | |
| 181 | if ( '<!--' == substr( $string, 0, 4 ) ) { |
| 182 | $string = str_replace( array('<!--', '-->'), '', $string ); |
| 183 | while ( $string != ($newstring = VaultPress_kses::wp_kses($string, $allowed_html, $allowed_protocols)) ) |
| 184 | $string = $newstring; |
| 185 | if ( $string == '' ) |
| 186 | return ''; |
| 187 | // prevent multiple dashes in comments |
| 188 | $string = preg_replace('/--+/', '-', $string); |
| 189 | // prevent three dashes closing a comment |
| 190 | $string = preg_replace('/-$/', '', $string); |
| 191 | return "<!--{$string}-->"; |
| 192 | } |
| 193 | # Allow HTML comments |
| 194 | |
| 195 | if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches)) |
| 196 | return ''; |
| 197 | # It's seriously malformed |
| 198 | |
| 199 | $slash = trim($matches[1]); |
| 200 | $elem = $matches[2]; |
| 201 | $attrlist = $matches[3]; |
| 202 | |
| 203 | if ( ! isset($allowed_html[strtolower($elem)]) ) |
| 204 | return ''; |
| 205 | # They are using a not allowed HTML element |
| 206 | |
| 207 | if ($slash != '') |
| 208 | return "</$elem>"; |
| 209 | # No attributes are allowed for closing elements |
| 210 | |
| 211 | return VaultPress_kses::wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols ); |
| 212 | } |
| 213 | |
| 214 | static function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) { |
| 215 | # Is there a closing XHTML slash at the end of the attributes? |
| 216 | |
| 217 | $xhtml_slash = ''; |
| 218 | if (preg_match('%\s*/\s*$%', $attr)) |
| 219 | $xhtml_slash = ' /'; |
| 220 | |
| 221 | # Are any attributes allowed at all for this element? |
| 222 | if ( ! isset($allowed_html[strtolower($element)]) || count($allowed_html[strtolower($element)]) == 0 ) |
| 223 | return "<$element$xhtml_slash>"; |
| 224 | |
| 225 | # Split it |
| 226 | $attrarr = VaultPress_kses::wp_kses_hair($attr, $allowed_protocols); |
| 227 | |
| 228 | # Go through $attrarr, and save the allowed attributes for this element |
| 229 | # in $attr2 |
| 230 | $attr2 = ''; |
| 231 | |
| 232 | $allowed_attr = $allowed_html[strtolower($element)]; |
| 233 | foreach ($attrarr as $arreach) { |
| 234 | if ( ! isset( $allowed_attr[strtolower($arreach['name'])] ) ) |
| 235 | continue; # the attribute is not allowed |
| 236 | |
| 237 | $current = $allowed_attr[strtolower($arreach['name'])]; |
| 238 | if ( $current == '' ) |
| 239 | continue; # the attribute is not allowed |
| 240 | |
| 241 | if ( strtolower( $arreach['name'] ) == 'style' ) { |
| 242 | $orig_value = $arreach['value']; |
| 243 | $value = safecss_filter_attr( $orig_value ); |
| 244 | |
| 245 | if ( empty( $value ) ) |
| 246 | continue; |
| 247 | |
| 248 | $arreach['value'] = $value; |
| 249 | $arreach['whole'] = str_replace( $orig_value, $value, $arreach['whole'] ); |
| 250 | } |
| 251 | |
| 252 | if ( ! is_array($current) ) { |
| 253 | $attr2 .= ' '.$arreach['whole']; |
| 254 | # there are no checks |
| 255 | |
| 256 | } else { |
| 257 | # there are some checks |
| 258 | $ok = true; |
| 259 | foreach ($current as $currkey => $currval) { |
| 260 | if ( ! wp_kses_check_attr_val($arreach['value'], $arreach['vless'], $currkey, $currval) ) { |
| 261 | $ok = false; |
| 262 | break; |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | if ( $ok ) |
| 267 | $attr2 .= ' '.$arreach['whole']; # it passed them |
| 268 | } # if !is_array($current) |
| 269 | } # foreach |
| 270 | |
| 271 | # Remove any "<" or ">" characters |
| 272 | $attr2 = preg_replace('/[<>]/', '', $attr2); |
| 273 | |
| 274 | return "<$element$attr2$xhtml_slash>"; |
| 275 | } |
| 276 | |
| 277 | static function wp_kses_hair($attr, $allowed_protocols) { |
| 278 | $attrarr = array (); |
| 279 | $mode = 0; |
| 280 | $attrname = ''; |
| 281 | $uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action'); |
| 282 | |
| 283 | # Loop through the whole attribute list |
| 284 | |
| 285 | while (strlen($attr) != 0) { |
| 286 | $working = 0; # Was the last operation successful? |
| 287 | |
| 288 | switch ($mode) { |
| 289 | case 0 : # attribute name, href for instance |
| 290 | |
| 291 | if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { |
| 292 | $attrname = $match[1]; |
| 293 | $working = $mode = 1; |
| 294 | $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr); |
| 295 | } |
| 296 | |
| 297 | break; |
| 298 | |
| 299 | case 1 : # equals sign or valueless ("selected") |
| 300 | |
| 301 | if (preg_match('/^\s*=\s*/', $attr)) # equals sign |
| 302 | { |
| 303 | $working = 1; |
| 304 | $mode = 2; |
| 305 | $attr = preg_replace('/^\s*=\s*/', '', $attr); |
| 306 | break; |
| 307 | } |
| 308 | |
| 309 | if (preg_match('/^\s+/', $attr)) # valueless |
| 310 | { |
| 311 | $working = 1; |
| 312 | $mode = 0; |
| 313 | if(false === array_key_exists($attrname, $attrarr)) { |
| 314 | $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y'); |
| 315 | } |
| 316 | $attr = preg_replace('/^\s+/', '', $attr); |
| 317 | } |
| 318 | |
| 319 | break; |
| 320 | |
| 321 | case 2 : # attribute value, a URL after href= for instance |
| 322 | |
| 323 | if (preg_match('%^"([^"]*)"(\s+|/?$)%', $attr, $match)) |
| 324 | # "value" |
| 325 | { |
| 326 | $thisval = $match[1]; |
| 327 | if ( in_array(strtolower($attrname), $uris) ) |
| 328 | $thisval = VaultPress_kses::wp_kses_bad_protocol($thisval, $allowed_protocols); |
| 329 | |
| 330 | if(false === array_key_exists($attrname, $attrarr)) { |
| 331 | $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n'); |
| 332 | } |
| 333 | $working = 1; |
| 334 | $mode = 0; |
| 335 | $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr); |
| 336 | break; |
| 337 | } |
| 338 | |
| 339 | if (preg_match("%^'([^']*)'(\s+|/?$)%", $attr, $match)) |
| 340 | # 'value' |
| 341 | { |
| 342 | $thisval = $match[1]; |
| 343 | if ( in_array(strtolower($attrname), $uris) ) |
| 344 | $thisval = VaultPress_kses::wp_kses_bad_protocol($thisval, $allowed_protocols); |
| 345 | |
| 346 | if(false === array_key_exists($attrname, $attrarr)) { |
| 347 | $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname='$thisval'", 'vless' => 'n'); |
| 348 | } |
| 349 | $working = 1; |
| 350 | $mode = 0; |
| 351 | $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr); |
| 352 | break; |
| 353 | } |
| 354 | |
| 355 | if (preg_match("%^([^\s\"']+)(\s+|/?$)%", $attr, $match)) |
| 356 | # value |
| 357 | { |
| 358 | $thisval = $match[1]; |
| 359 | if ( in_array(strtolower($attrname), $uris) ) |
| 360 | $thisval = VaultPress_kses::wp_kses_bad_protocol($thisval, $allowed_protocols); |
| 361 | |
| 362 | if(false === array_key_exists($attrname, $attrarr)) { |
| 363 | $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n'); |
| 364 | } |
| 365 | # We add quotes to conform to W3C's HTML spec. |
| 366 | $working = 1; |
| 367 | $mode = 0; |
| 368 | $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr); |
| 369 | } |
| 370 | |
| 371 | break; |
| 372 | } # switch |
| 373 | |
| 374 | if ($working == 0) # not well formed, remove and try again |
| 375 | { |
| 376 | $attr = wp_kses_html_error($attr); |
| 377 | $mode = 0; |
| 378 | } |
| 379 | } # while |
| 380 | |
| 381 | if ($mode == 1 && false === array_key_exists($attrname, $attrarr)) |
| 382 | # special case, for when the attribute list ends with a valueless |
| 383 | # attribute like "selected" |
| 384 | $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y'); |
| 385 | |
| 386 | return $attrarr; |
| 387 | } |
| 388 | |
| 389 | static function wp_kses_bad_protocol($string, $allowed_protocols) { |
| 390 | $string = wp_kses_no_null($string); |
| 391 | $iterations = 0; |
| 392 | |
| 393 | do { |
| 394 | $original_string = $string; |
| 395 | $string = VaultPress_kses::wp_kses_bad_protocol_once($string, $allowed_protocols); |
| 396 | } while ( $original_string != $string && ++$iterations < 6 ); |
| 397 | |
| 398 | if ( $original_string != $string ) |
| 399 | return ''; |
| 400 | |
| 401 | return $string; |
| 402 | } |
| 403 | |
| 404 | static function wp_kses_bad_protocol_once($string, $allowed_protocols, $count = 1) { |
| 405 | $string2 = preg_split( '/:|�*58;|�*3a;/i', $string, 2 ); |
| 406 | if ( isset($string2[1]) && ! preg_match('%/\?%', $string2[0]) ) { |
| 407 | $string = trim( $string2[1] ); |
| 408 | $protocol = VaultPress_kses::wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols ); |
| 409 | if ( 'feed:' == $protocol ) { |
| 410 | if ( $count > 2 ) |
| 411 | return ''; |
| 412 | $string = VaultPress_kses::wp_kses_bad_protocol_once( $string, $allowed_protocols, ++$count ); |
| 413 | if ( empty( $string ) ) |
| 414 | return $string; |
| 415 | } |
| 416 | $string = $protocol . $string; |
| 417 | } |
| 418 | |
| 419 | return $string; |
| 420 | } |
| 421 | |
| 422 | static function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) { |
| 423 | $string2 = wp_kses_decode_entities($string); |
| 424 | $string2 = preg_replace('/\s/', '', $string2); |
| 425 | $string2 = wp_kses_no_null($string2); |
| 426 | $string2 = strtolower($string2); |
| 427 | |
| 428 | $allowed = false; |
| 429 | foreach ( (array) $allowed_protocols as $one_protocol ) { |
| 430 | if ( strtolower( $one_protocol ) == $string2 ) { |
| 431 | $allowed = true; |
| 432 | break; |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | if ($allowed) |
| 437 | return "$string2:"; |
| 438 | else |
| 439 | return ''; |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | if ( !function_exists( 'get_available_languages' ) ) { |
| 444 | function get_available_languages( $dir = null ) { |
| 445 | $languages = array(); |
| 446 | foreach( glob( ( is_null( $dir) ? WP_LANG_DIR : $dir ) . '/*.mo' ) as $lang_file ) |
| 447 | if ( false === strpos( $lang_file, 'continents-cities' ) ) |
| 448 | $languages[] = basename($lang_file, '.mo'); |
| 449 | return $languages; |
| 450 | } |
| 451 | } |