| 1 |
<?php |
| 2 |
/** |
| 3 |
* Utility Classs |
| 4 |
* |
| 5 |
* @copyright Copyright (c) 2010 - 2013, Usability Dynamics, Inc. |
| 6 |
* |
| 7 |
* @author team@UD |
| 8 |
* @namespace UsabilityDynamics |
| 9 |
* @module Utility |
| 10 |
*/ |
| 11 |
namespace UsabilityDynamics { |
| 12 |
|
| 13 |
if( !class_exists( 'UsabilityDynamics\Utility' ) ) { |
| 14 |
|
| 15 |
/** |
| 16 |
* Utility Library. |
| 17 |
* |
| 18 |
* @submodule Utility |
| 19 |
* @version 0.2.2 |
| 20 |
* @class Utility |
| 21 |
*/ |
| 22 |
class Utility { |
| 23 |
|
| 24 |
/** |
| 25 |
* Class version. |
| 26 |
* |
| 27 |
* @static |
| 28 |
* @property $version |
| 29 |
* @type string |
| 30 |
*/ |
| 31 |
public static $version = '0.4.0'; |
| 32 |
|
| 33 |
/** |
| 34 |
* Textdomain String |
| 35 |
* |
| 36 |
* @public |
| 37 |
* @property text_domain |
| 38 |
* @var string |
| 39 |
*/ |
| 40 |
public static $text_domain = 'lib-utility'; |
| 41 |
|
| 42 |
/** |
| 43 |
* Constructor for initializing class, in static mode as well as dynamic. |
| 44 |
* |
| 45 |
* @todo Should make the transdomain configuraiton. |
| 46 |
* |
| 47 |
* @since 0.1.1 |
| 48 |
* @author potanin@UD |
| 49 |
*/ |
| 50 |
public function __construct() {} |
| 51 |
|
| 52 |
/** |
| 53 |
* |
| 54 |
* call_user_func( 'UsabilityDynamics\Utility::optimizeDatabase', 'flush-duplicate-meta' ); |
| 55 |
* |
| 56 |
* UsabilityDynamics\Utility::optimizeDatabase( 'flush-duplicate-meta' ); |
| 57 |
* |
| 58 |
* @param $action |
| 59 |
*/ |
| 60 |
static public function optimizeDatabase( $action ) { |
| 61 |
global $wpdb; |
| 62 |
//die('optimizeDatabase'); |
| 63 |
|
| 64 |
switch( $action ) { |
| 65 |
|
| 66 |
case 'flush-duplicate-meta': |
| 67 |
|
| 68 |
$allposts = get_posts(array( |
| 69 |
"numberposts" => 10, |
| 70 |
"offset" => 10, |
| 71 |
"post_type" => 'any', |
| 72 |
"post_status" => 'any' |
| 73 |
)); |
| 74 |
|
| 75 |
foreach ( $allposts as $_post ) { |
| 76 |
|
| 77 |
$postmeta = get_post_meta($postinfo->ID, $key); |
| 78 |
|
| 79 |
die( '<pre>' . print_r( $postmeta, true ) . '</pre>'); |
| 80 |
|
| 81 |
} |
| 82 |
|
| 83 |
$keys = array('address', 'address2', 'city', 'state', 'zip'); //Add post meta keys here |
| 84 |
|
| 85 |
foreach ( $keys as $key ) { |
| 86 |
|
| 87 |
foreach( $allposts as $postinfo) { |
| 88 |
|
| 89 |
// Fetch array of custom field values |
| 90 |
|
| 91 |
|
| 92 |
//print_r($postinfo); |
| 93 |
|
| 94 |
if (!empty($postmeta) ) { |
| 95 |
|
| 96 |
// Delete the custom field for this post (all occurrences) |
| 97 |
delete_post_meta($postinfo->ID, $key); |
| 98 |
|
| 99 |
// Insert one and only one custom field |
| 100 |
update_post_meta($postinfo->ID, $key, $postmeta[0]); |
| 101 |
|
| 102 |
} |
| 103 |
} |
| 104 |
|
| 105 |
} |
| 106 |
|
| 107 |
|
| 108 |
break; |
| 109 |
|
| 110 |
default: |
| 111 |
|
| 112 |
break; |
| 113 |
|
| 114 |
} |
| 115 |
|
| 116 |
//$wpdb->query( "DELETE a,b,c FROM {$wpdb->posts} a LEFT JOIN wp_term_relationships b ON (a.ID = b.object_id) LEFT JOIN {$wpdb->postmeta} c ON (a.ID = c.post_id) WHERE a.post_type = 'revision'" ); |
| 117 |
//$wpdb->query( "DELETE pm FROM wp_postmeta pm LEFT JOIN wp_posts wp ON wp.ID = pm.post_id WHERE wp.ID IS NULL;" ); |
| 118 |
//$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE ('_site_transient_%');" ); |
| 119 |
//$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE ('_transient_%');" ); |
| 120 |
|
| 121 |
|
| 122 |
} |
| 123 |
|
| 124 |
/** |
| 125 |
* Looks for a json or a php file in specified directory, if the file is not found traverse up and look for it again until its found or until a document root is reached |
| 126 |
* |
| 127 |
* |
| 128 |
* @todo Honor the "nocase" option setting. |
| 129 |
* @todo Perhaps wrap simplexml_load_file into - potanin@UD |
| 130 |
* |
| 131 |
* @since 0.3.2 |
| 132 |
* @method findUp |
| 133 |
* @param string $name - Name of file to find. |
| 134 |
* @param string $cwd - Directory to start seeking from. Defaults to __DIR__ |
| 135 |
* @param string $required - If the file is required - if not found, will trigger an error. |
| 136 |
* @author tosheen@UD |
| 137 |
* @return array|bool|mixed|\SimpleXMLElement |
| 138 |
*/ |
| 139 |
static public function findUp( $name = false, $cwd = false, $required = false ) { |
| 140 |
|
| 141 |
// No name provided, bail. |
| 142 |
if( !$name ) { |
| 143 |
return false; |
| 144 |
} |
| 145 |
|
| 146 |
// If first argument appears to be an object/array, treat it as a configuration object. |
| 147 |
if( is_array( $name ) || is_object( $name ) ) { |
| 148 |
|
| 149 |
// Apply default settings to passed argument. |
| 150 |
$_settings = self::defaults( $name, array( |
| 151 |
"cwd" => $cwd ? $cwd : null, |
| 152 |
"required" => $required ? $required : false, |
| 153 |
"nocase" => true |
| 154 |
)); |
| 155 |
|
| 156 |
$name = $_settings->name; |
| 157 |
$cwd = $_settings->cwd; |
| 158 |
$required = $_settings->required; |
| 159 |
$nocase = $_settings->nocase; |
| 160 |
|
| 161 |
} |
| 162 |
|
| 163 |
// No CWD set, backtrace to determine caller. |
| 164 |
$cwd = $cwd ? $cwd : dirname( self::backtrace_caller()->file ); |
| 165 |
|
| 166 |
// Determine if file is JSON |
| 167 |
$fileData = explode( '.', $name ); |
| 168 |
$fileExtension = $fileData[ count( $fileData ) - 1 ]; |
| 169 |
|
| 170 |
// Determine traverse path |
| 171 |
$_path = ( !empty( $cwd ) ? $cwd : $_SERVER[ 'DOCUMENT_ROOT' ] ); |
| 172 |
|
| 173 |
$file = $_path . DIRECTORY_SEPARATOR . $name; |
| 174 |
|
| 175 |
// Trigger Error and bail on failures. |
| 176 |
if( !is_dir( $_path ) ) { |
| 177 |
|
| 178 |
// Trigger error if required, otherwise fail silently. |
| 179 |
if( $required ) { |
| 180 |
trigger_error( __( 'Required file not found.', self::$text_domain ), E_USER_ERROR ); |
| 181 |
} |
| 182 |
|
| 183 |
return false; |
| 184 |
|
| 185 |
} |
| 186 |
|
| 187 |
if( file_exists( $file ) ) { |
| 188 |
|
| 189 |
// Fetch and parse JSON. |
| 190 |
if( $fileExtension === 'json' ) { |
| 191 |
return json_decode( file_get_contents( $file ) ); |
| 192 |
} |
| 193 |
|
| 194 |
// Fetch and parse XML. |
| 195 |
if( $fileExtension === 'xml' ) { |
| 196 |
return simplexml_load_file( $file ); |
| 197 |
} |
| 198 |
|
| 199 |
// Include all others. |
| 200 |
return include_once( $file ); |
| 201 |
|
| 202 |
} |
| 203 |
|
| 204 |
if( $_path != $_SERVER[ 'DOCUMENT_ROOT' ] ) { |
| 205 |
$lastDirSeparator = strrpos( $_path, DIRECTORY_SEPARATOR, -1 ); |
| 206 |
$_path = substr( $_path, 0, $lastDirSeparator ); |
| 207 |
return self::findUp( $name, $_path ); |
| 208 |
} |
| 209 |
|
| 210 |
// Trigger error if file was required. |
| 211 |
if( $required ) { |
| 212 |
trigger_error( __( 'Required file not found.', self::$text_domain ), E_USER_ERROR ); |
| 213 |
} |
| 214 |
|
| 215 |
// Couldn't find anything |
| 216 |
return false; |
| 217 |
|
| 218 |
} |
| 219 |
|
| 220 |
/** |
| 221 |
* Get Caller Object |
| 222 |
* |
| 223 |
* @method backtrace_caller |
| 224 |
* @since 0.3.2 |
| 225 |
* @author potanin@UD |
| 226 |
* @param int $depth |
| 227 |
* @return object |
| 228 |
*/ |
| 229 |
static public function backtrace_caller( $depth = 1 ) { |
| 230 |
|
| 231 |
// Always add one level to backtrace. |
| 232 |
$_backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, ( $depth ? $depth : 1 ) + 1 ); |
| 233 |
|
| 234 |
// Return first hit as object, or a scaffolded object. |
| 235 |
return $_backtrace[1] ? (object) $_backtrace[1] : (object) array( 'file' => null ); |
| 236 |
|
| 237 |
} |
| 238 |
|
| 239 |
/** |
| 240 |
* Set Dot-Notated Array Value |
| 241 |
* |
| 242 |
* @param array $arr |
| 243 |
* @param $path |
| 244 |
* @param $val |
| 245 |
* |
| 246 |
* @return mixed |
| 247 |
*/ |
| 248 |
static public function _set_val( array &$arr, $path, $val ) { |
| 249 |
$loc = & $arr; |
| 250 |
|
| 251 |
foreach( explode( '.', $path ) as $step ) { |
| 252 |
$loc = & $loc[ $step ]; |
| 253 |
} |
| 254 |
|
| 255 |
return $loc = $val; |
| 256 |
|
| 257 |
} |
| 258 |
|
| 259 |
/** |
| 260 |
* Convert dot-notated array key->value items to nested object |
| 261 |
* |
| 262 |
* @param $items |
| 263 |
* @return array |
| 264 |
*/ |
| 265 |
static public function unwrap( $items ) { |
| 266 |
|
| 267 |
$_result = array(); |
| 268 |
|
| 269 |
foreach( (array) $items as $key => $value ) { |
| 270 |
self::_set_val( $_result, $key, $value ); |
| 271 |
} |
| 272 |
|
| 273 |
return (array) $_result; |
| 274 |
|
| 275 |
} |
| 276 |
|
| 277 |
/** |
| 278 |
* Wrapper for wp_parse_args. |
| 279 |
* |
| 280 |
* @author potanin@UD |
| 281 |
* @since 0.3.0 |
| 282 |
* @param $args |
| 283 |
* @param $defaults |
| 284 |
* |
| 285 |
* @return object |
| 286 |
*/ |
| 287 |
static public function parse_args( $args, $defaults ) { |
| 288 |
|
| 289 |
return (object) wp_parse_args( $args, $defaults ); |
| 290 |
|
| 291 |
} |
| 292 |
|
| 293 |
/** |
| 294 |
* Parses Query. |
| 295 |
* HACK. The current logic solves the issue of max_input_vars in the case if query is huge. |
| 296 |
* |
| 297 |
* @see parse_str() Default PHP function |
| 298 |
* @version 1.1 |
| 299 |
* @author peshkov@UD |
| 300 |
* @param $request |
| 301 |
* @return array|mixed |
| 302 |
*/ |
| 303 |
static public function parse_str( $request ) { |
| 304 |
$data = array(); |
| 305 |
$tokens = explode( "&", $request ); |
| 306 |
foreach ( $tokens as $token ) { |
| 307 |
$token = str_replace( '%2B', md5( '%2B' ), $token ); |
| 308 |
$arr = array(); |
| 309 |
parse_str( $token, $arr ); |
| 310 |
array_walk_recursive( $arr, function( &$value,$key ) { |
| 311 |
$value = str_replace( md5( "%2B" ), "+", $value ); |
| 312 |
}); |
| 313 |
$data = self::extend( $data, $arr ); |
| 314 |
} |
| 315 |
return $data; |
| 316 |
} |
| 317 |
|
| 318 |
/** |
| 319 |
* Deep Conversion |
| 320 |
* |
| 321 |
* @updated 2.0 |
| 322 |
* @since 0.1 |
| 323 |
*/ |
| 324 |
static public function array_to_object( $array = array() ) { |
| 325 |
return json_decode( json_encode( $array ) ); |
| 326 |
} |
| 327 |
|
| 328 |
/** |
| 329 |
* Recursively converts object to array |
| 330 |
* |
| 331 |
* @since 2.0 |
| 332 |
* @author peshkov@UD |
| 333 |
*/ |
| 334 |
static public function object_to_array( $data ) { |
| 335 |
|
| 336 |
if ( is_object( $data ) ) { |
| 337 |
$data = get_object_vars( $data ); |
| 338 |
} |
| 339 |
|
| 340 |
if ( is_array( $data ) ) { |
| 341 |
foreach ( $data as $k => $v ) { |
| 342 |
if ( is_object( $v ) ) { |
| 343 |
$data[ $k ] = self::object_to_array( $v ); |
| 344 |
} |
| 345 |
} |
| 346 |
} |
| 347 |
|
| 348 |
return $data; |
| 349 |
|
| 350 |
} |
| 351 |
|
| 352 |
/** |
| 353 |
* Parse standard WordPress readme file |
| 354 |
* |
| 355 |
* @source Readme Parser ( http://www.tomsdimension.de/wp-plugins/readme-parser ) |
| 356 |
* @author potanin@UD |
| 357 |
*/ |
| 358 |
static public function parse_readme( $readme_file = false ) { |
| 359 |
|
| 360 |
if( !$readme_file ) { |
| 361 |
$readme_file = untrailingslashit( TEMPLATEPATH ) . '/readme.txt'; |
| 362 |
} |
| 363 |
|
| 364 |
$file = @file_get_contents( $readme_file ); |
| 365 |
|
| 366 |
if( !$file ) { |
| 367 |
return false; |
| 368 |
} |
| 369 |
|
| 370 |
$file = preg_replace( "/(\n\r|\r\n|\r|\n)/", "\n", $file ); |
| 371 |
|
| 372 |
// headlines |
| 373 |
$s = array( '===', '==', '=' ); |
| 374 |
$r = array( 'h2', 'h3', 'h4' ); |
| 375 |
for( $x = 0; $x < sizeof( $s ); $x++ ) { |
| 376 |
$file = preg_replace( '/(.*?)' . $s[ $x ] . '(?!\")(.*?)' . $s[ $x ] . '(.*?)/', '$1<' . $r[ $x ] . '>$2</' . $r[ $x ] . '>$3', $file ); |
| 377 |
} |
| 378 |
|
| 379 |
// inline |
| 380 |
$s = array( '\*\*', '\'' ); |
| 381 |
$r = array( 'b', 'code' ); |
| 382 |
for( $x = 0; $x < sizeof( $s ); $x++ ) { |
| 383 |
$file = preg_replace( '/(.*?)' . $s[ $x ] . '(?!\s)(.*?)(?!\s )' . $s[ $x ] . '(.*?)/', '$1<' . $r[ $x ] . '>$2</' . $r[ $x ] . '>$3', $file ); |
| 384 |
} |
| 385 |
|
| 386 |
// ' _italic_ ' |
| 387 |
$file = preg_replace( '/(\s)_(\S.*?\S)_(\s|$)/', '<em>$2</em> ', $file ); |
| 388 |
|
| 389 |
// ul lists |
| 390 |
$s = array( '\*', '\+', '\-' ); |
| 391 |
for( $x = 0; $x < sizeof( $s ); $x++ ) { |
| 392 |
$file = preg_replace( '/^[ ' . $s[ $x ] . ' ](\s)(.*?)(\n|$)/m', '<li>$2</li>', $file ); |
| 393 |
} |
| 394 |
|
| 395 |
$file = preg_replace( '/\n<li>(.*?)/', '<ul><li>$1', $file ); |
| 396 |
$file = preg_replace( '/(<\/li>)(?!<li>)/', '$1</ul>', $file ); |
| 397 |
|
| 398 |
// ol lists |
| 399 |
$file = preg_replace( '/(\d{1,2}\. )\s(.*?)(\n|$)/', '<li>$2</li>', $file ); |
| 400 |
$file = preg_replace( '/\n<li>(.*?)/', '<ol><li>$1', $file ); |
| 401 |
$file = preg_replace( '/(<\/li>)(?!(\<li\>|\<\/ul\> ))/', '$1</ol>', $file ); |
| 402 |
|
| 403 |
// ol screenshots style |
| 404 |
$file = preg_replace( '/(?=Screenshots)(.*?)<ol>/', '$1<ol class="readme-parser-screenshots">', $file ); |
| 405 |
|
| 406 |
// line breaks |
| 407 |
$file = preg_replace( '/(.*?)(\n)/', "$1<br/>\n", $file ); |
| 408 |
$file = preg_replace( '/(1|2|3|4)(><br\/>)/', '$1>', $file ); |
| 409 |
$file = str_replace( '</ul><br/>', '</ul>', $file ); |
| 410 |
$file = str_replace( '<br/><br/>', '<br/>', $file ); |
| 411 |
|
| 412 |
// urls |
| 413 |
$file = str_replace( 'http://www.', 'www.', $file ); |
| 414 |
$file = str_replace( 'www.', 'http://www.', $file ); |
| 415 |
$file = preg_replace( '#(^|[^\"=]{1})(http://|ftp://|mailto:|https://)([^\s<>]+)([\s\n<>]|$)#', '$1<a href="$2$3">$3</a>$4', $file ); |
| 416 |
|
| 417 |
// divs |
| 418 |
$file = preg_replace( '/(<h3> Description <\/h3>)/', "$1\n<div class=\"readme-description readme-div\">\n", $file ); |
| 419 |
$file = preg_replace( '/(<h3> Installation <\/h3>)/', "</div>\n$1\n<div id=\"readme-installation\" class=\"readme-div\">\n", $file ); |
| 420 |
$file = preg_replace( '/(<h3> Frequently Asked Questions <\/h3>)/', "</div>\n$1\n<div id=\"readme-faq\" class=\"readme-div\">\n", $file ); |
| 421 |
$file = preg_replace( '/(<h3> Screenshots <\/h3>)/', "</div>\n$1\n<div id=\"readme-screenshots\" class=\"readme-div\">\n", $file ); |
| 422 |
$file = preg_replace( '/(<h3> Arbitrary section <\/h3>)/', "</div>\n$1\n<div id=\"readme-arbitrary\" class=\"readme-div\">\n", $file ); |
| 423 |
$file = preg_replace( '/(<h3> Changelog <\/h3>)/', "</div>\n$1\n<div id=\"readme-changelog\" class=\"readme-changelog readme-div\">\n", $file ); |
| 424 |
$file = $file . '</div>'; |
| 425 |
|
| 426 |
return $file; |
| 427 |
|
| 428 |
} |
| 429 |
|
| 430 |
/** |
| 431 |
* Detects Variable Type. |
| 432 |
* |
| 433 |
* Distinguishes between object and array based on associative status. |
| 434 |
* |
| 435 |
* @source http://php.net/manual/en/function.gettype.php |
| 436 |
* @since 1.0.4 |
| 437 |
*/ |
| 438 |
static public function get_type( $var ) { |
| 439 |
|
| 440 |
if( is_object( $var ) ) return get_class( $var ); |
| 441 |
if( is_null( $var ) ) return 'null'; |
| 442 |
if( is_string( $var ) ) return 'string'; |
| 443 |
|
| 444 |
if( is_array( $var ) ) { |
| 445 |
|
| 446 |
if( self::is_associative( $var ) ) { |
| 447 |
return 'object'; |
| 448 |
} |
| 449 |
|
| 450 |
return 'array'; |
| 451 |
|
| 452 |
} |
| 453 |
|
| 454 |
if( is_int( $var ) ) return 'integer'; |
| 455 |
if( is_bool( $var ) ) return 'boolean'; |
| 456 |
if( is_float( $var ) ) return 'float'; |
| 457 |
if( is_resource( $var ) ) return 'resource'; |
| 458 |
|
| 459 |
} |
| 460 |
|
| 461 |
/** |
| 462 |
* Test if Array is Associative |
| 463 |
* |
| 464 |
* @param $arr |
| 465 |
* @return bool |
| 466 |
*/ |
| 467 |
static public function is_associative( $arr ) { |
| 468 |
|
| 469 |
if( !$arr ) { |
| 470 |
return false; |
| 471 |
} |
| 472 |
|
| 473 |
return array_keys($arr) !== range(0, count($arr) - 1) ? true : false; |
| 474 |
|
| 475 |
} |
| 476 |
|
| 477 |
/** |
| 478 |
* Port of Lo-dash defaults function. |
| 479 |
* |
| 480 |
* Basically switches the order of arguments used by Utility::extend(); |
| 481 |
* |
| 482 |
* @method defaults |
| 483 |
* |
| 484 |
* @param array $data Data to be applied against defaults. $data Data to be applied against defaults. |
| 485 |
* @param array $defaults Default array|object. $defaults Default array|object. |
| 486 |
* |
| 487 |
* @return object Extended data wtih defaults.@since 0.2.5 |
| 488 |
*/ |
| 489 |
static public function defaults( $data = array(), $defaults = array() ) { |
| 490 |
|
| 491 |
// Extend and return data object/arary with defaults. |
| 492 |
return (object) self::extend( (array) $defaults, (array) $data ); |
| 493 |
|
| 494 |
} |
| 495 |
|
| 496 |
/** |
| 497 |
* Fix Serialized (Broken) Array Strings |
| 498 |
* |
| 499 |
* @example |
| 500 |
* |
| 501 |
* foreach( $wpdb->get_results( "SELECT meta_id, meta_value from {$wpdb->postmeta} WHERE meta_key = '_cfct_build_data'" ) as $row ) { |
| 502 |
* $fixed = \UsabilityDynamics\Utility::repair_serialized_object( $row->meta_value ); |
| 503 |
* $wpdb->query("UPDATE {$wpdb->postmeta} SET meta_value = {$fixed} WHERE meta_key = {$row->meta_key}"); |
| 504 |
* } |
| 505 |
* |
| 506 |
* @param $input |
| 507 |
* |
| 508 |
* @return mixed |
| 509 |
*/ |
| 510 |
static public function repair_serialized_object( $input ) { |
| 511 |
return preg_replace( '!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'", $input ); |
| 512 |
} |
| 513 |
|
| 514 |
/** |
| 515 |
* Rename uploaded files as the hash of their original. |
| 516 |
* |
| 517 |
* @public |
| 518 |
* @since 0.2.4 |
| 519 |
* @method hashify_file_name |
| 520 |
* @for UsabilityDynamics\Utility |
| 521 |
* |
| 522 |
* @param string $filename Original filename to hashify.. |
| 523 |
* @param array|object $args Configuration arguments. |
| 524 |
* |
| 525 |
* @return string |
| 526 |
* @author sopp@ID |
| 527 |
*/ |
| 528 |
public static function hashify_file_name( $filename = null, $args = array() ) { |
| 529 |
|
| 530 |
if( !$filename ) { |
| 531 |
return ''; |
| 532 |
} |
| 533 |
|
| 534 |
$args = wp_parse_args( $args, array( |
| 535 |
'limit' => 99 |
| 536 |
) ); |
| 537 |
|
| 538 |
$info = pathinfo( $filename ); |
| 539 |
$ext = empty( $info[ 'extension' ] ) ? '' : '.' . $info[ 'extension' ]; |
| 540 |
|
| 541 |
return md5( basename( $filename, $ext ) ) . rand( 0, $args[ 'limit' ] ) . $ext; |
| 542 |
} |
| 543 |
|
| 544 |
/** |
| 545 |
* Tests if remote image can be loaded. |
| 546 |
* |
| 547 |
* Returns URL to image if valid. |
| 548 |
* Return false if image is invalid or could not be reached. |
| 549 |
* |
| 550 |
* @example |
| 551 |
* |
| 552 |
* // Try to load image. |
| 553 |
* if( $url = Utility::can_get_image( $theme_settings->logo_url ) ) { |
| 554 |
* echo "Image Found: $url."; |
| 555 |
* } |
| 556 |
* |
| 557 |
* |
| 558 |
* @method can_get_image |
| 559 |
* @for Utility |
| 560 |
* |
| 561 |
* @param bool $url Valid URL to an image. |
| 562 |
* |
| 563 |
* @since 0.2.2 |
| 564 |
* @return bool|int|string |
| 565 |
*/ |
| 566 |
static public function can_get_image( $url = false ) { |
| 567 |
|
| 568 |
if( !is_string( $url ) ) { |
| 569 |
return false; |
| 570 |
} |
| 571 |
|
| 572 |
if( empty( $url ) ) { |
| 573 |
return false; |
| 574 |
} |
| 575 |
|
| 576 |
//** Test if post_id */ |
| 577 |
if( is_numeric( $url ) && $image_attributes = wp_get_attachment_image_src( $url, 'full' ) ) { |
| 578 |
$url = $image_attributes[ 0 ]; |
| 579 |
} |
| 580 |
|
| 581 |
$result = wp_remote_get( $url, array( 'timeout' => 10 ) ); |
| 582 |
|
| 583 |
if( is_wp_error( $result ) ) { |
| 584 |
return false; |
| 585 |
} |
| 586 |
|
| 587 |
//** Image content types should always begin with 'image' ( I hope ) */ |
| 588 |
if( strpos( $result[ 'headers' ][ 'content-type' ], 'image' ) !== 0 ) { |
| 589 |
return false; |
| 590 |
} |
| 591 |
|
| 592 |
return $url; |
| 593 |
|
| 594 |
} |
| 595 |
|
| 596 |
/** |
| 597 |
* Return array of active plugins for current instance |
| 598 |
* |
| 599 |
* Improvement over wp_get_active_and_valid_plugins() which doesn't return any plugins when in MS |
| 600 |
* |
| 601 |
* @method get_active_plugins |
| 602 |
* @for Utility |
| 603 |
* |
| 604 |
* @since 0.2.0 |
| 605 |
*/ |
| 606 |
static public function get_active_plugins() { |
| 607 |
$mu_plugins = (array) wp_get_mu_plugins(); |
| 608 |
$regular_plugins = (array) wp_get_active_and_valid_plugins(); |
| 609 |
|
| 610 |
if( is_multisite() ) { |
| 611 |
$network_plugins = (array) wp_get_active_network_plugins(); |
| 612 |
} else { |
| 613 |
$network_plugins = array(); |
| 614 |
} |
| 615 |
|
| 616 |
return array_merge( $regular_plugins, $mu_plugins, $network_plugins ); |
| 617 |
|
| 618 |
} |
| 619 |
|
| 620 |
/** |
| 621 |
* Validate URL |
| 622 |
* |
| 623 |
* @for Utility |
| 624 |
* @since 0.1.1 |
| 625 |
* |
| 626 |
* @param string $url |
| 627 |
* |
| 628 |
* @param string $url |
| 629 |
* |
| 630 |
* @return bool |
| 631 |
*/ |
| 632 |
static public function is_url( $url = '' ) { |
| 633 |
return esc_url( $url ); |
| 634 |
} |
| 635 |
|
| 636 |
/** |
| 637 |
* Strip out protected keys from an associative array. |
| 638 |
* |
| 639 |
* Example below will remove all keys from array that being with $$: |
| 640 |
* |
| 641 |
* <code> |
| 642 |
* strip_protected_keys( $my_array, array( 'prefix' => '$$' ) ); |
| 643 |
* </code> |
| 644 |
* |
| 645 |
* @since 2.0 |
| 646 |
* @author potanin@UD |
| 647 |
*/ |
| 648 |
static public function strip_protected_keys( $array, $args = '' ) { |
| 649 |
|
| 650 |
$args = wp_parse_args( $args, array( 'prefix' => '_' ) ); |
| 651 |
|
| 652 |
foreach( (array) $array as $key => $value ) { |
| 653 |
|
| 654 |
if( strpos( $key, $args[ 'prefix' ] ) === 0 ) { |
| 655 |
unset( $array[ $key ] ); |
| 656 |
continue; |
| 657 |
} |
| 658 |
|
| 659 |
if( is_array( $value ) ) { |
| 660 |
$array[ $key ] = self::strip_protected_keys( $value, $args ); |
| 661 |
} |
| 662 |
|
| 663 |
} |
| 664 |
|
| 665 |
$array = array_filter( $array ); |
| 666 |
|
| 667 |
return $array; |
| 668 |
|
| 669 |
} |
| 670 |
|
| 671 |
/** |
| 672 |
* Recursively remove empty values from array. |
| 673 |
* |
| 674 |
* @method array_filter_deep |
| 675 |
* @for Utility |
| 676 |
* |
| 677 |
* @version 1.0.1 |
| 678 |
* @since 1.0.3 |
| 679 |
* @author potanin@UD |
| 680 |
*/ |
| 681 |
static public function array_filter_deep( $haystack = array() ) { |
| 682 |
|
| 683 |
foreach( (array) $haystack as $key => $value ) { |
| 684 |
|
| 685 |
if( is_object( $value ) || is_array( $value ) ) { |
| 686 |
|
| 687 |
if( is_object( $haystack ) ) { |
| 688 |
$haystack->{$key} = self::array_filter_deep( (array) $value ); |
| 689 |
} else if( is_array( $haystack ) ) { |
| 690 |
$haystack[ $key ] = self::array_filter_deep( (array) $value ); |
| 691 |
} |
| 692 |
|
| 693 |
} |
| 694 |
|
| 695 |
} |
| 696 |
|
| 697 |
return array_filter( (array) $haystack ); |
| 698 |
|
| 699 |
} |
| 700 |
|
| 701 |
/** |
| 702 |
* Determines if a passed timestamp is newer than a requirement. |
| 703 |
* |
| 704 |
* Usage: UD_API::is_fresher_than( $timestamp, '5 minutes' ); |
| 705 |
* |
| 706 |
* @since 1.0.3 |
| 707 |
*/ |
| 708 |
static public function fresher_than( $time, $ago = '1 week' ) { |
| 709 |
return ( strtotime( "-" . $ago ) < $time ) ? true : false; |
| 710 |
} |
| 711 |
|
| 712 |
/** |
| 713 |
* Starts a timer for the passed string. |
| 714 |
* |
| 715 |
* @since 1.0.0.2 |
| 716 |
* @author potanin@UD |
| 717 |
*/ |
| 718 |
static public function timer_start( $function = 'global' ) { |
| 719 |
global $ud_api; |
| 720 |
|
| 721 |
return $ud_api[ 'timers' ][ $function ][ 'start' ] = microtime( true ); |
| 722 |
} |
| 723 |
|
| 724 |
/** |
| 725 |
* Stop a timer. |
| 726 |
* |
| 727 |
* @since 1.0.0.2 |
| 728 |
* @author potanin@UD |
| 729 |
*/ |
| 730 |
static public function timer_stop( $function = 'global', $precision = 2 ) { |
| 731 |
global $ud_api; |
| 732 |
|
| 733 |
return $ud_api[ 'timers' ][ $function ][ 'start' ] ? round( microtime( true ) - $ud_api[ 'timers' ][ $function ][ 'start' ], $precision ) : false; |
| 734 |
} |
| 735 |
|
| 736 |
/** |
| 737 |
* Start Profiling, can also double as timer. |
| 738 |
* |
| 739 |
* Profiling will only start if another profiling process is not already running. |
| 740 |
* XHProf is required, other profilers may be added later. |
| 741 |
* |
| 742 |
* @updated 1.0.4 |
| 743 |
* @since 1.0.0.2 |
| 744 |
* @author potanin@UD |
| 745 |
*/ |
| 746 |
static public function profiler_start( $method = false, $args = false ) { |
| 747 |
global $ud_api; |
| 748 |
|
| 749 |
if( $ud_api[ 'profiling_now' ] && ( $ud_api[ 'profiling_now' ] != $method ) ) { |
| 750 |
return; |
| 751 |
} |
| 752 |
|
| 753 |
define( 'UD_API_Profiling', true ); |
| 754 |
|
| 755 |
if( extension_loaded( 'xhprof' ) && function_exists( 'xhprof_enable' ) ) { |
| 756 |
xhprof_enable( XHPROF_FLAGS_CPU | XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_MEMORY, $args ); |
| 757 |
} |
| 758 |
|
| 759 |
return self::timer_start( $ud_api[ 'profiling_now' ] = $method ); |
| 760 |
|
| 761 |
} |
| 762 |
|
| 763 |
/** |
| 764 |
* Stop Profiling. |
| 765 |
* |
| 766 |
* @since 1.0.0.2 |
| 767 |
* @author potanin@UD |
| 768 |
*/ |
| 769 |
static public function profiler_stop( $method = false, $args = false ) { |
| 770 |
global $ud_api; |
| 771 |
|
| 772 |
if( $ud_api[ 'profiling_now' ] && ( $ud_api[ 'profiling_now' ] != $method ) ) { |
| 773 |
return; |
| 774 |
} |
| 775 |
|
| 776 |
if( extension_loaded( 'xhprof' ) && class_exists( 'XHProfRuns_Default' ) ) { |
| 777 |
$xhprof_data = xhprof_disable(); |
| 778 |
$xhprof_runs = new XHProfRuns_Default(); |
| 779 |
$xhprof_runs->save_run( $xhprof_data, $method ); |
| 780 |
} |
| 781 |
|
| 782 |
unset( $ud_api[ 'profiling_now' ] ); |
| 783 |
|
| 784 |
return self::timer_stop( $method ); |
| 785 |
|
| 786 |
} |
| 787 |
|
| 788 |
/** |
| 789 |
* Attempt to download a remote files attachments |
| 790 |
* |
| 791 |
* @param bool $images |
| 792 |
* @param array $args |
| 793 |
* |
| 794 |
* @return bool|object |
| 795 |
*/ |
| 796 |
static public function image_fetch( $images = false, $args = array() ) { |
| 797 |
|
| 798 |
$images = array_filter( (array) $images ); |
| 799 |
|
| 800 |
//** Image URLs may be passed as string or array, or none at all */ |
| 801 |
if( count( $images ) < 1 ) { |
| 802 |
return false; |
| 803 |
} |
| 804 |
|
| 805 |
self::timer_start( __METHOD__ ); |
| 806 |
|
| 807 |
$args = wp_parse_args( $args, array( |
| 808 |
'upload_dir' => false, |
| 809 |
'timeout' => 30, |
| 810 |
) ); |
| 811 |
|
| 812 |
/** |
| 813 |
* Regular Image Download. |
| 814 |
*/ |
| 815 |
foreach( (array) $images as $count => $url ) { |
| 816 |
|
| 817 |
$url = esc_url_raw( $url ); |
| 818 |
|
| 819 |
$_image = array( |
| 820 |
'source_url' => $url, |
| 821 |
'error' => false |
| 822 |
); |
| 823 |
|
| 824 |
//** Set correct filename ( some URLs can have not valid file extensions ) */ |
| 825 |
$filename = sanitize_file_name( basename( $url ) ); |
| 826 |
$ext = false; |
| 827 |
$filetype = wp_check_filetype( $filename ); |
| 828 |
if( !$filetype[ 'ext' ] ) { |
| 829 |
$file_headers = get_headers( $url, 1 ); |
| 830 |
if( strpos( $file_headers[ 0 ], '200 OK' ) ) { |
| 831 |
if( isset( $file_headers[ 'Content-Type' ] ) ) { |
| 832 |
$file_mime = sanitize_mime_type( $file_headers[ 'Content-Type' ] ); |
| 833 |
switch( $file_mime ) { |
| 834 |
case "image/gif": |
| 835 |
$ext = 'gif'; |
| 836 |
break; |
| 837 |
case "image/jpeg": |
| 838 |
$ext = 'jpg'; |
| 839 |
break; |
| 840 |
case "image/png": |
| 841 |
$ext = 'png'; |
| 842 |
break; |
| 843 |
case "image/bmp": |
| 844 |
$ext = 'bmp'; |
| 845 |
break; |
| 846 |
} |
| 847 |
if( $ext ) { |
| 848 |
$filename .= '.' . $ext; |
| 849 |
} |
| 850 |
} |
| 851 |
} |
| 852 |
} else { |
| 853 |
$ext = $filetype[ 'ext' ]; |
| 854 |
} |
| 855 |
|
| 856 |
$_wp_error_data = array( |
| 857 |
'url' => $url, |
| 858 |
'filename' => $filename, |
| 859 |
'file_type' => $ext, |
| 860 |
); |
| 861 |
|
| 862 |
//** We MUST NOT allow to upload not-image files */ |
| 863 |
if( !$ext || !in_array( $ext, array( 'gif', 'jpg', 'png', 'bmp', 'jpeg' ) ) ) { |
| 864 |
$_image[ 'error' ] = new WP_Error( __METHOD__, __( 'Invalid file type.', self::$text_domain ), $_wp_error_data ); |
| 865 |
} |
| 866 |
|
| 867 |
//** Set file path */ |
| 868 |
if( !empty( $args[ 'upload_dir' ] ) ) { |
| 869 |
|
| 870 |
if( wp_mkdir_p( $args[ 'upload_dir' ] ) ) { |
| 871 |
$_image[ 'file' ] = trailingslashit( $args[ 'upload_dir' ] ) . wp_unique_filename( $args[ 'upload_dir' ], $filename ); |
| 872 |
} else { |
| 873 |
$_image[ 'error' ] = new WP_Error( __METHOD__, __( 'Could not create mentioned directory.', self::$text_domain ) ); |
| 874 |
} |
| 875 |
|
| 876 |
} else { |
| 877 |
|
| 878 |
$wp_upload_bits = wp_upload_bits( $filename, null, '' ); |
| 879 |
if( $wp_upload_bits[ 'error' ] ) { |
| 880 |
$_image[ 'error' ] = new WP_Error( __METHOD__, $wp_upload_bits[ 'error' ], $wp_upload_bits ); |
| 881 |
} |
| 882 |
$_image = self::extend( $_image, $wp_upload_bits ); |
| 883 |
|
| 884 |
} |
| 885 |
|
| 886 |
if( !is_wp_error( $_image[ 'error' ] ) ) { |
| 887 |
|
| 888 |
$wp_remote_request = wp_remote_request( $url, array_filter( array( |
| 889 |
'method' => 'GET', |
| 890 |
'timeout' => $args[ 'timeout' ], |
| 891 |
'stream' => true, |
| 892 |
'filename' => $_image[ 'file' ] |
| 893 |
) ) ); |
| 894 |
|
| 895 |
if( is_wp_error( $wp_remote_request ) ) { |
| 896 |
$wp_remote_request->add_data( $_wp_error_data ); |
| 897 |
$_image[ 'error' ] = $wp_remote_request; |
| 898 |
} else { |
| 899 |
|
| 900 |
$_image[ 'file' ] = $wp_remote_request[ 'filename' ]; |
| 901 |
$_image[ 'filesize' ] = filesize( $_image[ 'file' ] ); |
| 902 |
|
| 903 |
/* Disabled. Was failing multiple images |
| 904 |
if( isset( $wp_remote_request[ 'headers' ][ 'content-length'] ) && $_image[ 'filesize' ] != $wp_remote_request[ 'headers' ][ 'content-length'] ) { |
| 905 |
$_image[ 'error' ] = new WP_Error( 'image_fetch', __( 'Remote file has incorrect size', self::$text_domain ), array( |
| 906 |
'headers' => $wp_remote_request[ 'headers' ], |
| 907 |
'image' => $_image |
| 908 |
)); |
| 909 |
}*/ |
| 910 |
|
| 911 |
if( 0 == $_image[ 'filesize' ] ) { |
| 912 |
$_image[ 'error' ] = new WP_Error( __METHOD__, __( 'Zero size file downloaded', self::$text_domain ) ); |
| 913 |
} |
| 914 |
|
| 915 |
$_image = self::extend( $_image, wp_check_filetype( $_image[ 'file' ] ) ); |
| 916 |
|
| 917 |
//require_once( ABSPATH . 'wp-admin/includes/image.php' ); |
| 918 |
//wp_update_attachment_metadata( $row->attachment_id, wp_generate_attachment_metadata( $row->attachment_id, $upload[ 'file' ] ) ); |
| 919 |
} |
| 920 |
|
| 921 |
} |
| 922 |
|
| 923 |
if( is_wp_error( $_image[ 'error' ] ) ) { |
| 924 |
@unlink( $_image[ 'file' ] ); |
| 925 |
} |
| 926 |
|
| 927 |
$return[ $count ] = (object) array_filter( $_image ); |
| 928 |
|
| 929 |
} //** End foreach */ |
| 930 |
|
| 931 |
return (object) array( |
| 932 |
'images' => $return, |
| 933 |
'timer' => self::timer_stop( __METHOD__ ) |
| 934 |
); |
| 935 |
|
| 936 |
} |
| 937 |
|
| 938 |
/** |
| 939 |
* Checks if images exist and returns images dimensions |
| 940 |
* |
| 941 |
* @param mixed $images Image url |
| 942 |
* @param mixed $args |
| 943 |
* |
| 944 |
* @return array |
| 945 |
* @author peshkov@UD |
| 946 |
*/ |
| 947 |
static public function image_dimensions( $images = false, $args = array() ) { |
| 948 |
|
| 949 |
$result = array(); |
| 950 |
$images = array_filter( (array) $images ); |
| 951 |
|
| 952 |
//** Image URLs may be passed as string or array, or none at all */ |
| 953 |
if( count( $images ) < 1 ) { |
| 954 |
return $result; |
| 955 |
} |
| 956 |
|
| 957 |
self::timer_start( __METHOD__ ); |
| 958 |
|
| 959 |
//** Params below are used only by RIM ( getMultiImageTypeAndSize ) **/ |
| 960 |
$args = wp_parse_args( $args, array( |
| 961 |
'max_num_of_threads' => 10, |
| 962 |
'time_limit' => 30, |
| 963 |
'curl_connect_timeout' => 2, |
| 964 |
'curl_timeout' => 3, |
| 965 |
) ); |
| 966 |
|
| 967 |
//** If PHP 5.3.0, and rim class found, we use it. In other case we use default function getimagesize() */ |
| 968 |
if( version_compare( PHP_VERSION, '5.3.0' ) >= 0 && method_exists( 'rim', 'getMultiImageTypeAndSize' ) ) { |
| 969 |
$rim = new rim(); |
| 970 |
$response = $rim->getMultiImageTypeAndSize( $images, $args ); |
| 971 |
if( is_array( $response ) ) { |
| 972 |
foreach( $response as $r ) { |
| 973 |
$result[ ] = array( |
| 974 |
'width' => isset( $r[ 'image_data' ][ 'width' ] ) ? $r[ 'image_data' ][ 'width' ] : 0, |
| 975 |
'height' => isset( $r[ 'image_data' ][ 'height' ] ) ? $r[ 'image_data' ][ 'height' ] : 0, |
| 976 |
'url' => isset( $r[ 'url' ] ) ? $r[ 'url' ] : false, |
| 977 |
'error' => !empty( $r[ 'error' ] ) ? new WP_Error( 'image_fetch', __( 'Could not get image dimensions (headers)', 'wpp' ), $r[ 'error' ] ) : false |
| 978 |
); |
| 979 |
} |
| 980 |
} |
| 981 |
} else { |
| 982 |
$result = array(); |
| 983 |
foreach( $images as $image ) { |
| 984 |
$r = @getimagesize( $image ); |
| 985 |
$result[ ] = array( |
| 986 |
'width' => isset( $r[ 0 ] ) ? $r[ 0 ] : 0, |
| 987 |
'height' => isset( $r[ 1 ] ) ? $r[ 1 ] : 0, |
| 988 |
'url' => $image, |
| 989 |
'error' => empty( $r ) ? new WP_Error( 'image_fetch', __( 'Could not get image dimensions (headers)', 'wpp' ) ) : false |
| 990 |
); |
| 991 |
} |
| 992 |
|
| 993 |
} |
| 994 |
|
| 995 |
return $result; |
| 996 |
} |
| 997 |
|
| 998 |
/** |
| 999 |
* Converts slashes for Windows paths. |
| 1000 |
* |
| 1001 |
* @since 1.0.0.0 |
| 1002 |
* @source Flawless |
| 1003 |
* @author potanin@UD |
| 1004 |
*/ |
| 1005 |
static public function fix_path( $path ) { |
| 1006 |
return str_replace( '\\', '/', $path ); |
| 1007 |
} |
| 1008 |
|
| 1009 |
/** |
| 1010 |
* Applies trim() function to all values in an array |
| 1011 |
* |
| 1012 |
* @source WP-Property |
| 1013 |
* @since 0.6.0 |
| 1014 |
*/ |
| 1015 |
static public function trim_array( $array = array() ) { |
| 1016 |
|
| 1017 |
foreach( (array) $array as $key => $value ) { |
| 1018 |
|
| 1019 |
if( is_object( $value ) ) { |
| 1020 |
continue; |
| 1021 |
} |
| 1022 |
|
| 1023 |
$array[ $key ] = is_array( $value ) ? self::trim_array( $value ) : trim( $value ); |
| 1024 |
} |
| 1025 |
|
| 1026 |
return $array; |
| 1027 |
|
| 1028 |
} |
| 1029 |
|
| 1030 |
/** |
| 1031 |
* Returns all available image sizes |
| 1032 |
* |
| 1033 |
* @method all_image_sizes |
| 1034 |
* @for Utility |
| 1035 |
* |
| 1036 |
* @param $size {String} |
| 1037 |
* |
| 1038 |
* @returns array keys: 'width' and 'height' |
| 1039 |
*/ |
| 1040 |
static public function all_image_sizes( $size = '' ) { |
| 1041 |
global $_wp_additional_image_sizes; |
| 1042 |
|
| 1043 |
$image_sizes = (array) $_wp_additional_image_sizes; |
| 1044 |
|
| 1045 |
$image_sizes[ 'thumbnail' ] = array( |
| 1046 |
'width' => intval( get_option( 'thumbnail_size_w' ) ), |
| 1047 |
'height' => intval( get_option( 'thumbnail_size_h' ) ) |
| 1048 |
); |
| 1049 |
|
| 1050 |
$image_sizes[ 'medium' ] = array( |
| 1051 |
'width' => intval( get_option( 'medium_size_w' ) ), |
| 1052 |
'height' => intval( get_option( 'medium_size_h' ) ) |
| 1053 |
); |
| 1054 |
|
| 1055 |
$image_sizes[ 'large' ] = array( |
| 1056 |
'width' => intval( get_option( 'large_size_w' ) ), |
| 1057 |
'height' => intval( get_option( 'large_size_h' ) ) |
| 1058 |
); |
| 1059 |
|
| 1060 |
foreach( (array) $image_sizes as $_size => $data ) { |
| 1061 |
$image_sizes[ $_size ] = array_filter( (array) $data ); |
| 1062 |
$image_sizes[ $_size ][ 'label' ] = self::de_slug( $_size ); |
| 1063 |
$image_sizes[ $_size ][ 'crop' ] = isset( $image_sizes[ $_size ][ 'crop' ] ) && $image_sizes[ $_size ][ 'crop' ] ? $image_sizes[ $_size ][ 'crop' ] : false; |
| 1064 |
} |
| 1065 |
|
| 1066 |
if( $size ) { |
| 1067 |
return array_filter( (array) $image_sizes[ $size ] ); |
| 1068 |
} |
| 1069 |
|
| 1070 |
return array_filter( (array) $image_sizes ); |
| 1071 |
|
| 1072 |
} |
| 1073 |
|
| 1074 |
/** |
| 1075 |
* Retrieves the attachment ID from the file URL ( guid ) |
| 1076 |
* |
| 1077 |
* @global object $wpdb |
| 1078 |
* |
| 1079 |
* @param string $guid |
| 1080 |
* |
| 1081 |
* @return string |
| 1082 |
* @author peshkovUD |
| 1083 |
*/ |
| 1084 |
static public function get_image_id_by_guid( $guid ) { |
| 1085 |
global $wpdb; |
| 1086 |
$attachment = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->prefix}posts WHERE guid='%s';", $guid ) ); |
| 1087 |
|
| 1088 |
return !empty( $attachment[ 0 ] ) ? $attachment[ 0 ] : false; |
| 1089 |
} |
| 1090 |
|
| 1091 |
/** |
| 1092 |
* Returns Image link (url) |
| 1093 |
* |
| 1094 |
* If image with the current size doesn't exist, we try to generate it. |
| 1095 |
* If image cannot be resized, the URL to the main image (original) is returned. |
| 1096 |
* |
| 1097 |
* @todo Add something to check if requested image size is bigger than the original, in which case cannot be "resized" |
| 1098 |
* @todo Add a check to see if the specified image dimensions have changed. Right now only checks if slug exists, not the actualy size. |
| 1099 |
* |
| 1100 |
* @param bool $attachment_id |
| 1101 |
* @param bool $size |
| 1102 |
* @param array $args |
| 1103 |
* |
| 1104 |
* @return array|bool|mixed |
| 1105 |
*/ |
| 1106 |
static public function get_image_link( $attachment_id = false, $size = false, $args = array() ) { |
| 1107 |
global $wp_properties; |
| 1108 |
|
| 1109 |
if( !$size || !$attachment_id ) { |
| 1110 |
return false; |
| 1111 |
} |
| 1112 |
|
| 1113 |
$image_sizes = self::all_image_sizes( $size ); |
| 1114 |
|
| 1115 |
$args = wp_parse_args( $args, array( |
| 1116 |
'cache_id' => sanitize_title( $attachment_id . $size ), |
| 1117 |
'return' => 'string', |
| 1118 |
'default' => '', |
| 1119 |
'cache_group' => 'ud_api' |
| 1120 |
) ); |
| 1121 |
|
| 1122 |
//** Added 'return' arg to avoid cache problems odokienko@UD */ |
| 1123 |
$args[ 'cache_id' ] .= $args[ 'return' ]; |
| 1124 |
|
| 1125 |
if( $return = wp_cache_get( $args[ 'cache_id' ], $args[ 'cache_group' ] ) ) { |
| 1126 |
return $return; |
| 1127 |
} |
| 1128 |
|
| 1129 |
$attachment_image_src = ( array ) wp_get_attachment_image_src( $attachment_id, $size ); |
| 1130 |
|
| 1131 |
//** If wp_get_attachment_image_src() returned the information we need, we return it */ |
| 1132 |
if( empty( $image_sizes ) || ( is_array( $attachment_image_src ) && $attachment_image_src[ 1 ] == $image_sizes[ $size ][ 'width' ] ) ) { |
| 1133 |
|
| 1134 |
$return = $args[ 'return' ] == 'string' ? $attachment_image_src[ 0 ] : array( |
| 1135 |
'url' => $attachment_image_src[ 0 ], |
| 1136 |
'link' => $attachment_image_src[ 0 ], |
| 1137 |
'width' => $attachment_image_src[ 1 ], |
| 1138 |
'height' => $attachment_image_src[ 2 ], |
| 1139 |
'crop' => $attachment_image_src[ 3 ] |
| 1140 |
); |
| 1141 |
|
| 1142 |
wp_cache_set( $args[ 'cache_id' ], $return, $args[ 'cache_group' ] ); |
| 1143 |
|
| 1144 |
return $return; |
| 1145 |
} |
| 1146 |
|
| 1147 |
//** If we are this far, that means that the returned image, if any, was not the right size, so we regenreate */ |
| 1148 |
$image_resize = image_resize( get_attached_file( $attachment_id, true ), $image_sizes[ $size ][ 'width' ], $image_sizes[ $size ][ 'height' ], $image_sizes[ $size ][ 'crop' ] ); |
| 1149 |
|
| 1150 |
if( is_wp_error( $image_resize ) || !file_exists( $image_resize ) ) { |
| 1151 |
|
| 1152 |
if( $attachment_image_src[ 0 ] ) { |
| 1153 |
$return = $args[ 'default' ] ? $args[ 'default' ] : $attachment_image_src[ 0 ]; |
| 1154 |
} else { |
| 1155 |
$return = $args[ 'default' ]; |
| 1156 |
} |
| 1157 |
|
| 1158 |
} |
| 1159 |
|
| 1160 |
//** If image was resized, we update metadata, cache our result, and return */ |
| 1161 |
require_once( ABSPATH . 'wp-admin/includes/image.php' ); |
| 1162 |
|
| 1163 |
if( function_exists( 'wp_update_attachment_metadata' ) ) { |
| 1164 |
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, get_attached_file( $attachment_id, true ) ) ); |
| 1165 |
} |
| 1166 |
|
| 1167 |
$attachment_image_src = (array) wp_get_attachment_image_src( $attachment_id, $size ); |
| 1168 |
|
| 1169 |
$return = $args[ 'return' ] == 'string' ? $attachment_image_src[ 0 ] : array( |
| 1170 |
'url' => $attachment_image_src[ 0 ], |
| 1171 |
'link' => $attachment_image_src[ 0 ], |
| 1172 |
'width' => $attachment_image_src[ 1 ], |
| 1173 |
'height' => $attachment_image_src[ 2 ], |
| 1174 |
'crop' => $attachment_image_src[ 3 ] |
| 1175 |
); |
| 1176 |
|
| 1177 |
wp_cache_set( $args[ 'cache_id' ], $return, $args[ 'cache_group' ] ); |
| 1178 |
|
| 1179 |
return $return; |
| 1180 |
|
| 1181 |
} |
| 1182 |
|
| 1183 |
/** |
| 1184 |
* Returns Image link (url) with custom size. |
| 1185 |
* Almost the same as get_image_link, but the current function can generate images with custom sizes. |
| 1186 |
* It generates image with custom size only once. |
| 1187 |
* |
| 1188 |
* @global $wpdb |
| 1189 |
* |
| 1190 |
* @param type $atts |
| 1191 |
* |
| 1192 |
* @return string |
| 1193 |
* @author peshkov@UD |
| 1194 |
* @since 0.2.5 |
| 1195 |
*/ |
| 1196 |
static function get_image_link_with_custom_size( $attachment_id, $width, $height, $crop = false ) { |
| 1197 |
global $wpdb; |
| 1198 |
|
| 1199 |
// Sanitize |
| 1200 |
$height = absint( $height ); |
| 1201 |
$width = absint( $width ); |
| 1202 |
$needs_resize = true; |
| 1203 |
|
| 1204 |
// Look through the attachment meta data for an image that fits our size. |
| 1205 |
$meta = wp_get_attachment_metadata( $attachment_id ); |
| 1206 |
$upload_dir = wp_upload_dir(); |
| 1207 |
$base_url = strtolower( $upload_dir[ 'baseurl' ] ); |
| 1208 |
$src = trailingslashit( $base_url ) . $meta[ 'file' ]; |
| 1209 |
foreach( $meta[ 'sizes' ] as $key => $size ) { |
| 1210 |
if( ( $size[ 'width' ] == $width && $size[ 'height' ] == $height ) || $key == sprintf( 'resized-%dx%d', $width, $height ) ) { |
| 1211 |
if( !empty( $size[ 'file' ] ) ) { |
| 1212 |
$src = str_replace( basename( $src ), $size[ 'file' ], $src ); |
| 1213 |
} |
| 1214 |
$needs_resize = false; |
| 1215 |
break; |
| 1216 |
} |
| 1217 |
} |
| 1218 |
|
| 1219 |
// If an image of such size was not found, we can create one. |
| 1220 |
if( $needs_resize ) { |
| 1221 |
$attached_file = get_attached_file( $attachment_id ); |
| 1222 |
$resized = image_make_intermediate_size( $attached_file, $width, $height, $crop ); |
| 1223 |
if( is_wp_error( $resized ) ) { |
| 1224 |
return $resized; |
| 1225 |
} |
| 1226 |
|
| 1227 |
// Let metadata know about our new size. |
| 1228 |
$key = sprintf( 'resized-%dx%d', $width, $height ); |
| 1229 |
$meta[ 'sizes' ][ $key ] = $resized; |
| 1230 |
if( !empty( $resized[ 'file' ] ) ) { |
| 1231 |
$src = str_replace( basename( $src ), $resized[ 'file' ], $src ); |
| 1232 |
} |
| 1233 |
wp_update_attachment_metadata( $attachment_id, $meta ); |
| 1234 |
|
| 1235 |
// Record in backup sizes so everything's cleaned up when attachment is deleted. |
| 1236 |
$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true ); |
| 1237 |
if( !is_array( $backup_sizes ) ) $backup_sizes = array(); |
| 1238 |
$backup_sizes[ $key ] = $resized; |
| 1239 |
update_post_meta( $attachment_id, '_wp_attachment_backup_sizes', $backup_sizes ); |
| 1240 |
|
| 1241 |
} |
| 1242 |
|
| 1243 |
return array( |
| 1244 |
'url' => esc_url( $src ), |
| 1245 |
'width' => absint( $width ), |
| 1246 |
'height' => absint( $height ), |
| 1247 |
); |
| 1248 |
} |
| 1249 |
|
| 1250 |
/** |
| 1251 |
* Insert array into an associative array before a specific key |
| 1252 |
* |
| 1253 |
* @source http://stackoverflow.com/questions/6501845/php-need-help-inserting-arrays-into-associative-arrays-at-given-keys |
| 1254 |
* @author potanin@UD |
| 1255 |
*/ |
| 1256 |
static public function array_insert_before( $array, $key, $new ) { |
| 1257 |
$array = (array) $array; |
| 1258 |
$keys = array_keys( $array ); |
| 1259 |
$pos = (int) array_search( $key, $keys ); |
| 1260 |
|
| 1261 |
return array_merge( |
| 1262 |
array_slice( $array, 0, $pos ), |
| 1263 |
$new, |
| 1264 |
array_slice( $array, $pos ) |
| 1265 |
); |
| 1266 |
} |
| 1267 |
|
| 1268 |
/** |
| 1269 |
* Insert array into an associative array after a specific key |
| 1270 |
* |
| 1271 |
* @source http://stackoverflow.com/questions/6501845/php-need-help-inserting-arrays-into-associative-arrays-at-given-keys |
| 1272 |
* @author potanin@UD |
| 1273 |
*/ |
| 1274 |
static public function array_insert_after( $array, $key, $new ) { |
| 1275 |
$array = (array) $array; |
| 1276 |
$keys = array_keys( $array ); |
| 1277 |
$pos = (int) array_search( $key, $keys ) + 1; |
| 1278 |
|
| 1279 |
return array_merge( |
| 1280 |
array_slice( $array, 0, $pos ), |
| 1281 |
$new, |
| 1282 |
array_slice( $array, $pos ) |
| 1283 |
); |
| 1284 |
} |
| 1285 |
|
| 1286 |
/** |
| 1287 |
* Attemp to convert a plural US word into a singular. |
| 1288 |
* |
| 1289 |
* @todo API Service Candidate since we ideally need a dictionary reference. |
| 1290 |
* @author potanin@UD |
| 1291 |
*/ |
| 1292 |
static public function depluralize( $word ) { |
| 1293 |
$rules = array( 'ss' => false, 'os' => 'o', 'ies' => 'y', 'xes' => 'x', 'oes' => 'o', 'ies' => 'y', 'ves' => 'f', 's' => '' ); |
| 1294 |
|
| 1295 |
foreach( array_keys( $rules ) as $key ) { |
| 1296 |
|
| 1297 |
if( substr( $word, ( strlen( $key ) * -1 ) ) != $key ) |
| 1298 |
continue; |
| 1299 |
|
| 1300 |
if( $key === false ) |
| 1301 |
return $word; |
| 1302 |
|
| 1303 |
return substr( $word, 0, strlen( $word ) - strlen( $key ) ) . $rules[ $key ]; |
| 1304 |
|
| 1305 |
} |
| 1306 |
|
| 1307 |
return $word; |
| 1308 |
|
| 1309 |
} |
| 1310 |
|
| 1311 |
/** |
| 1312 |
* Convert bytes into the logical unit of measure based on size. |
| 1313 |
* |
| 1314 |
* @source Flawless |
| 1315 |
* @since 1.0.0.0 |
| 1316 |
* @author potanin@UD |
| 1317 |
*/ |
| 1318 |
static public function format_bytes( $bytes, $precision = 2 ) { |
| 1319 |
_deprecated_function( __FUNCTION__, '2.3.0', 'size_format()' ); |
| 1320 |
|
| 1321 |
return size_format( $bytes, $precision ); |
| 1322 |
} |
| 1323 |
|
| 1324 |
/** |
| 1325 |
* Used to enable/disable/print SQL log |
| 1326 |
* |
| 1327 |
* Usage: |
| 1328 |
* self::sql_log( 'enable' ); |
| 1329 |
* self::sql_log( 'disable' ); |
| 1330 |
* $queries= self::sql_log( 'print_log' ); |
| 1331 |
* |
| 1332 |
* @since 0.1.0 |
| 1333 |
*/ |
| 1334 |
static public function sql_log( $action = 'attach_filter' ) { |
| 1335 |
global $wpdb; |
| 1336 |
|
| 1337 |
if( !in_array( $action, array( 'enable', 'disable', 'print_log' ) ) ) { |
| 1338 |
$wpdb->ud_queries[ ] = array( $action, $wpdb->timer_stop(), $wpdb->get_caller() ); |
| 1339 |
|
| 1340 |
return $action; |
| 1341 |
} |
| 1342 |
|
| 1343 |
if( $action == 'enable' ) { |
| 1344 |
add_filter( 'query', array( __CLASS__, 'sql_log' ), 75 ); |
| 1345 |
} |
| 1346 |
|
| 1347 |
if( $action == 'disable' ) { |
| 1348 |
remove_filter( 'query', array( __CLASS__, 'sql_log' ), 75 ); |
| 1349 |
} |
| 1350 |
|
| 1351 |
if( $action == 'print_log' ) { |
| 1352 |
$result = array(); |
| 1353 |
foreach( (array) $wpdb->ud_queries as $query ) { |
| 1354 |
$result[ ] = $query[ 0 ] ? $query[ 0 ] . ' (' . $query[ 1 ] . ')' : $query[ 2 ]; |
| 1355 |
} |
| 1356 |
|
| 1357 |
return $result; |
| 1358 |
} |
| 1359 |
|
| 1360 |
} |
| 1361 |
|
| 1362 |
/** |
| 1363 |
* Return data for UD Log |
| 1364 |
* |
| 1365 |
* @updated 1.04 |
| 1366 |
* @sincde 1.03 |
| 1367 |
* @note This is a proof of concept, in future it should be able to support AJAX calls so can be displayed via Dynamic Filter. |
| 1368 |
* @author potanin@UD |
| 1369 |
*/ |
| 1370 |
static public function log( $message = '', $args = array() ) { |
| 1371 |
global $wpdb; |
| 1372 |
|
| 1373 |
//** Prevents MySQL Gone Away. @todo Should check if connection exists before automatically connecting. */ |
| 1374 |
//$wpdb->db_connect(); |
| 1375 |
|
| 1376 |
//** Create Log if it does not exist */ |
| 1377 |
if( !$wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}ud_log';" ) ) { |
| 1378 |
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); |
| 1379 |
dbDelta( "CREATE TABLE {$wpdb->prefix}ud_log ( |
| 1380 |
id mediumint(9) NOT NULL AUTO_INCREMENT, |
| 1381 |
post_id mediumint(9) DEFAULT NULL COMMENT 'ID of related post.', |
| 1382 |
product VARCHAR(100) DEFAULT '' NOT NULL COMMENT 'Slug of related product.', |
| 1383 |
feature VARCHAR(100) DEFAULT '' NOT NULL COMMENT 'Slug of specific feature, if applicable.', |
| 1384 |
message text NOT NULL COMMENT 'Long description of log entry.', |
| 1385 |
type VARCHAR(100) DEFAULT '' NOT NULL COMMENT 'Type of variable stored in message. May be concatentaetd with other data for additional information.', |
| 1386 |
action VARCHAR(128) DEFAULT '' NOT NULL COMMENT 'If applicable, a slug for a specific action that triggered the entry.', |
| 1387 |
method VARCHAR(128) DEFAULT '' NOT NULL COMMENT 'If applicable, PHP method that triggered log entry.', |
| 1388 |
time int(11) NOT NULL, |
| 1389 |
UNIQUE KEY id (id), |
| 1390 |
KEY post_id (post_id), |
| 1391 |
KEY type (type) |
| 1392 |
);" ); |
| 1393 |
} |
| 1394 |
|
| 1395 |
$args = array_filter( (array) shortcode_atts( array( |
| 1396 |
'post_id' => null, |
| 1397 |
'type' => gettype( $message ), |
| 1398 |
'message' => maybe_serialize( $message ), |
| 1399 |
'product' => null, |
| 1400 |
'feature' => null, |
| 1401 |
'action' => null, |
| 1402 |
'method' => null, |
| 1403 |
'time' => time() |
| 1404 |
), $args ) ); |
| 1405 |
|
| 1406 |
//** Only the keys below may be updated via $args */ |
| 1407 |
$wpdb->insert( $wpdb->prefix . 'ud_log', $args ); |
| 1408 |
|
| 1409 |
return $wpdb->insert_id ? $message : false; |
| 1410 |
} |
| 1411 |
|
| 1412 |
/** |
| 1413 |
* Return data for UD Log |
| 1414 |
* |
| 1415 |
* @note This is a proof of concept, in future it should be able to support AJAX calls so can be displayed via Dynamic Filter. |
| 1416 |
* @author potanin@UD |
| 1417 |
*/ |
| 1418 |
static public function get_log( $args = false ) { |
| 1419 |
global $wpdb; |
| 1420 |
|
| 1421 |
$args = wp_parse_args( $args, array( |
| 1422 |
'offset' => 0, |
| 1423 |
'limit' => 100, |
| 1424 |
'last_id' => false, |
| 1425 |
'sort_type' => 'ASC', |
| 1426 |
'direction' => 'greater', |
| 1427 |
'product' => '', |
| 1428 |
'post_id' => false, |
| 1429 |
) ); |
| 1430 |
|
| 1431 |
$where = array(); |
| 1432 |
if( $args[ 'last_id' ] && $args[ 'last_id' ] > 1 ) { |
| 1433 |
$direction = ''; |
| 1434 |
switch( $args[ 'direction' ] ) { |
| 1435 |
case 'greater': |
| 1436 |
$direction = '>'; |
| 1437 |
break; |
| 1438 |
case 'less': |
| 1439 |
$direction = '<'; |
| 1440 |
break; |
| 1441 |
} |
| 1442 |
if( !empty( $direction ) ) { |
| 1443 |
$where[ ] = " l.id {$direction} {$args['last_id']} "; |
| 1444 |
} |
| 1445 |
} |
| 1446 |
|
| 1447 |
foreach( $args as $k => $v ) { |
| 1448 |
if( in_array( $k, array( 'product', 'post_id' ) ) && !empty( $v ) ) { |
| 1449 |
if( is_array( $v ) ) { |
| 1450 |
$where[ ] = " l.{$k} IN ( '" . implode( "','", $v ) . "' ) "; |
| 1451 |
} else { |
| 1452 |
$where[ ] = " l.{$k} = '{$v}' "; |
| 1453 |
} |
| 1454 |
} |
| 1455 |
} |
| 1456 |
|
| 1457 |
if( !empty( $where ) ) { |
| 1458 |
$where = " WHERE " . implode( " AND ", $where ) . " "; |
| 1459 |
} else { |
| 1460 |
$where = ''; |
| 1461 |
} |
| 1462 |
|
| 1463 |
$response = $wpdb->get_results( " |
| 1464 |
SELECT l.*, p.post_title |
| 1465 |
FROM {$wpdb->prefix}ud_log l |
| 1466 |
LEFT JOIN {$wpdb->posts} p ON l.post_id = p.ID |
| 1467 |
{$where} |
| 1468 |
ORDER BY l.id {$args['sort_type']} |
| 1469 |
LIMIT {$args['offset']}, {$args['limit']}; |
| 1470 |
" ); |
| 1471 |
|
| 1472 |
//die( '<pre>' . print_r( $wpdb->last_query ,true) . '</pre>' ); |
| 1473 |
|
| 1474 |
return $response; |
| 1475 |
|
| 1476 |
} |
| 1477 |
|
| 1478 |
/** |
| 1479 |
* Removes data from Logs table |
| 1480 |
* |
| 1481 |
* @param mixed $args |
| 1482 |
* |
| 1483 |
* @author peshkov@UD |
| 1484 |
*/ |
| 1485 |
static public function clear_log( $args = array() ) { |
| 1486 |
global $wpdb; |
| 1487 |
|
| 1488 |
$args = array_filter( self::prepare_to_sql( wp_parse_args( $args, array( |
| 1489 |
'id' => false, |
| 1490 |
'product' => false, |
| 1491 |
'feature' => false, |
| 1492 |
'product_id' => false, |
| 1493 |
'type' => false, |
| 1494 |
'action' => false, |
| 1495 |
) ) ) ); |
| 1496 |
|
| 1497 |
$where = ""; |
| 1498 |
foreach( $args as $k => $v ) { |
| 1499 |
$where .= empty( $where ) ? " WHERE " : " AND "; |
| 1500 |
$where .= " {$k} = '{$v}' "; |
| 1501 |
} |
| 1502 |
|
| 1503 |
return $wpdb->query( "DELETE FROM {$wpdb->prefix}ud_log {$where}" ); |
| 1504 |
} |
| 1505 |
|
| 1506 |
/** |
| 1507 |
* Add an entry to the plugin-specifig log. |
| 1508 |
* |
| 1509 |
* Creates log if one does not exist. |
| 1510 |
* |
| 1511 |
* <code> |
| 1512 |
* UD\Utility::log( "Settings updated." ); |
| 1513 |
* </code> |
| 1514 |
* |
| 1515 |
* @depreciated peshkov@UD |
| 1516 |
*/ |
| 1517 |
static public function _log( $message = false, $args = array() ) { |
| 1518 |
|
| 1519 |
$args = wp_parse_args( $args, array( |
| 1520 |
'type' => 'default', |
| 1521 |
'object' => false, |
| 1522 |
'prefix' => 'ud', |
| 1523 |
) ); |
| 1524 |
|
| 1525 |
extract( $args ); |
| 1526 |
|
| 1527 |
$log = "{$prefix}_log"; |
| 1528 |
|
| 1529 |
if( !did_action( 'init' ) ) { |
| 1530 |
_doing_it_wrong( __FUNCTION__, sprintf( __( 'You cannot call UD\Utility::log() before the %1$s hook, since the current user is not yet known.' ), 'init' ), '3.4' ); |
| 1531 |
} |
| 1532 |
|
| 1533 |
$current_user = wp_get_current_user(); |
| 1534 |
|
| 1535 |
$this_log = get_option( $log ); |
| 1536 |
|
| 1537 |
if( empty( $this_log ) ) { |
| 1538 |
|
| 1539 |
$this_log = array(); |
| 1540 |
|
| 1541 |
$entry = array( |
| 1542 |
'time' => time(), |
| 1543 |
'message' => __( 'Log Started.', self::$text_domain ), |
| 1544 |
'user' => $current_user->ID, |
| 1545 |
'type' => $type |
| 1546 |
); |
| 1547 |
|
| 1548 |
} |
| 1549 |
|
| 1550 |
if( $message ) { |
| 1551 |
|
| 1552 |
$entry = array( |
| 1553 |
'time' => time(), |
| 1554 |
'message' => $message, |
| 1555 |
'user' => $type == 'system' ? 'system' : $current_user->ID, |
| 1556 |
'type' => $type, |
| 1557 |
'object' => $object |
| 1558 |
); |
| 1559 |
|
| 1560 |
} |
| 1561 |
|
| 1562 |
if( !is_array( $entry ) ) { |
| 1563 |
return false; |
| 1564 |
} |
| 1565 |
|
| 1566 |
array_push( $this_log, $entry ); |
| 1567 |
|
| 1568 |
$this_log = array_filter( $this_log ); |
| 1569 |
|
| 1570 |
update_option( $log, $this_log ); |
| 1571 |
|
| 1572 |
return true; |
| 1573 |
|
| 1574 |
} |
| 1575 |
|
| 1576 |
/** |
| 1577 |
* Helpder function for figuring out if another specific function is a predecesor of current function. |
| 1578 |
* |
| 1579 |
* @since 1.0.0.0 |
| 1580 |
* @author potanin@UD |
| 1581 |
*/ |
| 1582 |
static public function _backtrace_function( $function = false ) { |
| 1583 |
|
| 1584 |
foreach( debug_backtrace() as $step ) { |
| 1585 |
if( $function && $step[ 'function' ] == $function ) { |
| 1586 |
return true; |
| 1587 |
} |
| 1588 |
} |
| 1589 |
|
| 1590 |
} |
| 1591 |
|
| 1592 |
/** |
| 1593 |
* Helpder function for figuring out if a specific file is a predecesor of current file. |
| 1594 |
* |
| 1595 |
* @since 1.0.0.0 |
| 1596 |
* @author potanin@UD |
| 1597 |
*/ |
| 1598 |
static public function _backtrace_file( $file = false ) { |
| 1599 |
|
| 1600 |
foreach( debug_backtrace() as $step ) { |
| 1601 |
if( $file && basename( $step[ 'file' ] ) == $file ) { |
| 1602 |
return true; |
| 1603 |
} |
| 1604 |
} |
| 1605 |
|
| 1606 |
} |
| 1607 |
|
| 1608 |
/** |
| 1609 |
* Fixed serialized arrays which sometimes get messed up in WordPress |
| 1610 |
* |
| 1611 |
* @source http://shauninman.com/archive/2008/01/08/recovering_truncated_php_serialized_arrays |
| 1612 |
*/ |
| 1613 |
static public function repair_serialized_array( $serialized ) { |
| 1614 |
$tmp = preg_replace( '/^a:\d+:\{/', '', $serialized ); |
| 1615 |
|
| 1616 |
return self::repair_serialized_array_callback( $tmp ); // operates on and whittles down the actual argument |
| 1617 |
} |
| 1618 |
|
| 1619 |
/** |
| 1620 |
* The recursive function that does all of the heavy lifing. Do not call directly. |
| 1621 |
|
| 1622 |
|
| 1623 |
*/ |
| 1624 |
static public function repair_serialized_array_callback( &$broken ) { |
| 1625 |
|
| 1626 |
$data = array(); |
| 1627 |
$index = null; |
| 1628 |
$len = strlen( $broken ); |
| 1629 |
$i = 0; |
| 1630 |
|
| 1631 |
while( strlen( $broken ) ) { |
| 1632 |
$i++; |
| 1633 |
if( $i > $len ) { |
| 1634 |
break; |
| 1635 |
} |
| 1636 |
|
| 1637 |
if( substr( $broken, 0, 1 ) == '}' ) // end of array |
| 1638 |
{ |
| 1639 |
$broken = substr( $broken, 1 ); |
| 1640 |
|
| 1641 |
return $data; |
| 1642 |
} else { |
| 1643 |
$bite = substr( $broken, 0, 2 ); |
| 1644 |
switch( $bite ) { |
| 1645 |
case 's:': // key or value |
| 1646 |
$re = '/^s:\d+:"([^\"]*)";/'; |
| 1647 |
if( preg_match( $re, $broken, $m ) ) { |
| 1648 |
if( $index === null ) { |
| 1649 |
$index = $m[ 1 ]; |
| 1650 |
} else { |
| 1651 |
$data[ $index ] = $m[ 1 ]; |
| 1652 |
$index = null; |
| 1653 |
} |
| 1654 |
$broken = preg_replace( $re, '', $broken ); |
| 1655 |
} |
| 1656 |
break; |
| 1657 |
|
| 1658 |
case 'i:': // key or value |
| 1659 |
$re = '/^i:(\d+);/'; |
| 1660 |
if( preg_match( $re, $broken, $m ) ) { |
| 1661 |
if( $index === null ) { |
| 1662 |
$index = (int) $m[ 1 ]; |
| 1663 |
} else { |
| 1664 |
$data[ $index ] = (int) $m[ 1 ]; |
| 1665 |
$index = null; |
| 1666 |
} |
| 1667 |
$broken = preg_replace( $re, '', $broken ); |
| 1668 |
} |
| 1669 |
break; |
| 1670 |
|
| 1671 |
case 'b:': // value only |
| 1672 |
$re = '/^b:[01];/'; |
| 1673 |
if( preg_match( $re, $broken, $m ) ) { |
| 1674 |
$data[ $index ] = (bool) $m[ 1 ]; |
| 1675 |
$index = null; |
| 1676 |
$broken = preg_replace( $re, '', $broken ); |
| 1677 |
} |
| 1678 |
break; |
| 1679 |
|
| 1680 |
case 'a:': // value only |
| 1681 |
$re = '/^a:\d+:\{/'; |
| 1682 |
if( preg_match( $re, $broken, $m ) ) { |
| 1683 |
$broken = preg_replace( '/^a:\d+:\{/', '', $broken ); |
| 1684 |
$data[ $index ] = self::repair_serialized_array_callback( $broken ); |
| 1685 |
$index = null; |
| 1686 |
} |
| 1687 |
break; |
| 1688 |
|
| 1689 |
case 'N;': // value only |
| 1690 |
$broken = substr( $broken, 2 ); |
| 1691 |
$data[ $index ] = null; |
| 1692 |
$index = null; |
| 1693 |
break; |
| 1694 |
} |
| 1695 |
} |
| 1696 |
} |
| 1697 |
|
| 1698 |
return $data; |
| 1699 |
} |
| 1700 |
|
| 1701 |
/** |
| 1702 |
* Determine if an item is in array and return checked |
| 1703 |
* |
| 1704 |
* @since 0.5.0 |
| 1705 |
*/ |
| 1706 |
static public function checked_in_array( $item, $array ) { |
| 1707 |
|
| 1708 |
if( is_array( $array ) && in_array( $item, $array ) ) { |
| 1709 |
echo ' checked="checked" '; |
| 1710 |
} |
| 1711 |
|
| 1712 |
} |
| 1713 |
|
| 1714 |
/** |
| 1715 |
* Check if the current WP version is older then given parameter $version. |
| 1716 |
* |
| 1717 |
* @param string $version |
| 1718 |
* |
| 1719 |
* @return bool |
| 1720 |
* @since 1.0.0.0 |
| 1721 |
* @author peshkov@UD |
| 1722 |
*/ |
| 1723 |
static public function is_older_wp_version( $version = '' ) { |
| 1724 |
if( empty( $version ) || (float) $version == 0 ) return false; |
| 1725 |
$current_version = get_bloginfo( 'version' ); |
| 1726 |
/** Clear version numbers */ |
| 1727 |
$current_version = preg_replace( "/^([0-9\.]+)-(.)+$/", "$1", $current_version ); |
| 1728 |
$version = preg_replace( "/^([0-9\.]+)-(.)+$/", "$1", $version ); |
| 1729 |
|
| 1730 |
return ( (float) $current_version < (float) $version ) ? true : false; |
| 1731 |
} |
| 1732 |
|
| 1733 |
/** |
| 1734 |
* Determine if any requested template exists and return path to it. |
| 1735 |
* |
| 1736 |
* == Usage == |
| 1737 |
* The function will search through: STYLESHEETPATH, TEMPLATEPATH, and any custom paths you pass as second argument. |
| 1738 |
* |
| 1739 |
* $best_template = UD\Utility::get_template_part( array( |
| 1740 |
* 'template-ideal-match', |
| 1741 |
* 'template-default', |
| 1742 |
* ), array( PATH_TO_MY_TEMPLATES ); |
| 1743 |
* |
| 1744 |
* Note: load_template() extracts $wp_query->query_vars into the loaded template, so to add any global variables to the template, add them to |
| 1745 |
* $wp_query->query_vars prior to calling this function. |
| 1746 |
* |
| 1747 |
* @param mixed $name List of requested templates. Will be return the first found |
| 1748 |
* @param array $path [optional]. Method tries to find template in theme, but also it can be found in given list of pathes. |
| 1749 |
* @param array $opts [optional]. Set of additional params: |
| 1750 |
* - string $instance. Template can depend on instance. For example: facebook, PDF, etc. Uses filter: ud::template_part::{instance} |
| 1751 |
* - boolean $load. if true, rendered HTML will be returned, in other case, only found template's path. |
| 1752 |
* @load boolean [optional]. If true and a template is found, the template will be loaded via load_template() and returned as a string |
| 1753 |
* @author peshkov@UD |
| 1754 |
* @version 1.1 |
| 1755 |
*/ |
| 1756 |
static public function get_template_part( $name, $path = array(), $opts = array() ) { |
| 1757 |
$name = (array)$name; |
| 1758 |
$template = ""; |
| 1759 |
|
| 1760 |
/** |
| 1761 |
* Set default instance. |
| 1762 |
* Template can depend on instance. For example: facebook, PDF, etc. |
| 1763 |
*/ |
| 1764 |
$instance = apply_filters( "ud::current_instance", "default" ); |
| 1765 |
|
| 1766 |
$opts = wp_parse_args( $opts, array( |
| 1767 |
'instance' => $instance, |
| 1768 |
'load' => false, |
| 1769 |
) ); |
| 1770 |
|
| 1771 |
//** Allows to add/change templates storage directory. */ |
| 1772 |
$path = apply_filters( "ud::template_part::path", $path, $name, $opts ); |
| 1773 |
|
| 1774 |
foreach ( $name as $n ) { |
| 1775 |
$n = "{$n}.php"; |
| 1776 |
$template = locate_template( $n, false ); |
| 1777 |
if ( empty( $template ) && !empty( $path ) ) { |
| 1778 |
foreach ( (array)$path as $p ) { |
| 1779 |
if ( file_exists( $p . "/" . $n ) ) { |
| 1780 |
$template = $p . "/" . $n; |
| 1781 |
break( 2 ); |
| 1782 |
} |
| 1783 |
} |
| 1784 |
} |
| 1785 |
if ( !empty( $template ) ) break; |
| 1786 |
} |
| 1787 |
|
| 1788 |
$template = apply_filters( "ud::template_part::{$opts['instance']}", $template, array( 'name' => $name, 'path' => $path, 'opts' => $opts ) ); |
| 1789 |
|
| 1790 |
//** If match and load was requested, get template and return */ |
| 1791 |
if( !empty( $template ) && $opts[ 'load' ] == true ) { |
| 1792 |
ob_start(); |
| 1793 |
load_template( $template, false ); |
| 1794 |
return ob_get_clean(); |
| 1795 |
} |
| 1796 |
|
| 1797 |
return !empty( $template ) ? $template : false; |
| 1798 |
|
| 1799 |
} |
| 1800 |
|
| 1801 |
/** |
| 1802 |
* The goal of function is going through specific filters and return (or print) classes. |
| 1803 |
* This function should not be called directly. |
| 1804 |
* Every ud plugin/theme should have own short function ( wrapper ) for calling it. E.g., see: wpp_css(). |
| 1805 |
* So, use it in template as: <div id="my_element" class="<?php wpp_css("{name_of_template}::my_element"); ?>"> </div> |
| 1806 |
* |
| 1807 |
* Arguments: |
| 1808 |
* - instance [string] - UD plugin|theme's slug. E.g.: wpp, denali, wpi, etc |
| 1809 |
* - element [string] - specific element in template which will use the current classes. |
| 1810 |
* Element should be called as {template}::{specific_name_of_element}. Where {template} is name of template, |
| 1811 |
* where current classes will be used. This standart is optional. You can set any element's name if you want. |
| 1812 |
* - classes [array] - set of classes which will be used for element. |
| 1813 |
* - return [boolean] - If false, the function prints all classes like 'class1 class2 class3' |
| 1814 |
* |
| 1815 |
* @param array $args |
| 1816 |
* |
| 1817 |
* @return string |
| 1818 |
* @author peshkov@UD |
| 1819 |
* @version 0.1 |
| 1820 |
*/ |
| 1821 |
static public function get_css_classes( $args = array() ) { |
| 1822 |
|
| 1823 |
//** Set arguments */ |
| 1824 |
$args = wp_parse_args( (array) $args, array( |
| 1825 |
'classes' => array(), |
| 1826 |
'instance' => '', |
| 1827 |
'element' => '', |
| 1828 |
'return' => false, |
| 1829 |
) ); |
| 1830 |
|
| 1831 |
extract( $args ); |
| 1832 |
|
| 1833 |
//** Cast (set correct types) to avoid issues */ |
| 1834 |
if( !is_array( $classes ) ) { |
| 1835 |
$classes = trim( $classes ); |
| 1836 |
$classes = str_replace( ',', ' ', $classes ); |
| 1837 |
$classes = explode( ' ', $classes ); |
| 1838 |
} |
| 1839 |
|
| 1840 |
foreach( $classes as &$c ) { |
| 1841 |
$c = trim( $c ); |
| 1842 |
} |
| 1843 |
|
| 1844 |
$instance = (string) $instance; |
| 1845 |
$element = (string) $element; |
| 1846 |
|
| 1847 |
//** Now go through the filters */ |
| 1848 |
$classes = apply_filters( "$instance::css::$element", $classes, $args ); |
| 1849 |
|
| 1850 |
if( !$return ) { |
| 1851 |
echo implode( " ", (array) $classes ); |
| 1852 |
} |
| 1853 |
|
| 1854 |
return implode( " ", (array) $classes ); |
| 1855 |
|
| 1856 |
} |
| 1857 |
|
| 1858 |
/** |
| 1859 |
* Return simple array of column tables in a table |
| 1860 |
* |
| 1861 |
* @version 0.6 |
| 1862 |
*/ |
| 1863 |
static public function get_column_names( $table ) { |
| 1864 |
|
| 1865 |
global $wpdb; |
| 1866 |
|
| 1867 |
$table_info = $wpdb->get_results( "SHOW COLUMNS FROM $table" ); |
| 1868 |
|
| 1869 |
if( empty( $table_info ) ) { |
| 1870 |
return array(); |
| 1871 |
} |
| 1872 |
|
| 1873 |
foreach( (array) $table_info as $row ) { |
| 1874 |
$columns[ ] = $row->Field; |
| 1875 |
} |
| 1876 |
|
| 1877 |
return $columns; |
| 1878 |
|
| 1879 |
} |
| 1880 |
|
| 1881 |
/** |
| 1882 |
* Port of jQuery.extend() function. |
| 1883 |
* |
| 1884 |
* @since 1.0.3 |
| 1885 |
*/ |
| 1886 |
static public function extend() { |
| 1887 |
|
| 1888 |
$arrays = func_get_args(); |
| 1889 |
$base = array_shift( $arrays ); |
| 1890 |
if( !is_array( $base ) ) $base = empty( $base ) ? array() : array( $base ); |
| 1891 |
foreach( (array) $arrays as $append ) { |
| 1892 |
if( !is_array( $append ) ) $append = array( $append ); |
| 1893 |
foreach( (array) $append as $key => $value ) { |
| 1894 |
if( !array_key_exists( $key, $base ) and !is_numeric( $key ) ) { |
| 1895 |
$base[ $key ] = $append[ $key ]; |
| 1896 |
continue; |
| 1897 |
} |
| 1898 |
if( ( isset( $value ) && @is_array( $value ) ) || ( isset( $base[ $key ] ) && @is_array( $base[ $key ] ) ) ) { |
| 1899 |
|
| 1900 |
// extend if exists, otherwise create. |
| 1901 |
if( isset( $base[ $key ] ) ) { |
| 1902 |
$base[ $key ] = self::extend( $base[ $key ], $append[ $key ] ); |
| 1903 |
} else { |
| 1904 |
$base[ $key ] = $append[ $key ]; |
| 1905 |
} |
| 1906 |
|
| 1907 |
} else if( is_numeric( $key ) ) { |
| 1908 |
if( !in_array( $value, $base ) ) $base[ ] = $value; |
| 1909 |
} else { |
| 1910 |
$base[ $key ] = $value; |
| 1911 |
} |
| 1912 |
} |
| 1913 |
} |
| 1914 |
|
| 1915 |
return $base; |
| 1916 |
} |
| 1917 |
|
| 1918 |
/** |
| 1919 |
* Returns a URL to a post object based on passed variable. |
| 1920 |
* |
| 1921 |
* If its a number, then assumes its the id, If it resembles a slug, then get the first slug match. |
| 1922 |
* |
| 1923 |
* @since 1.0.0 |
| 1924 |
* |
| 1925 |
* @param bool|string $title A page title, although ID integer can be passed as well |
| 1926 |
* |
| 1927 |
* @return string The page's URL if found, otherwise the general blog URL |
| 1928 |
*/ |
| 1929 |
static public function post_link( $title = false ) { |
| 1930 |
|
| 1931 |
global $wpdb; |
| 1932 |
|
| 1933 |
if( !$title ) return get_bloginfo( 'url' ); |
| 1934 |
|
| 1935 |
if( is_numeric( $title ) ) return get_permalink( $title ); |
| 1936 |
|
| 1937 |
if( $id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_name = %s AND post_status='publish'", $title ) ) ) return get_permalink( $id ); |
| 1938 |
|
| 1939 |
if( $id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE LOWER(post_title) = %s AND post_status='publish'", strtolower( $title ) ) ) ) return get_permalink( $id ); |
| 1940 |
|
| 1941 |
} |
| 1942 |
|
| 1943 |
/** |
| 1944 |
* Used to get the current plugin's log created via UD class |
| 1945 |
* |
| 1946 |
* If no log exists, it creates one, and then returns it in chronological order. |
| 1947 |
* |
| 1948 |
* Example to view log: |
| 1949 |
* <code> |
| 1950 |
* print_r( self::get_log() ); |
| 1951 |
* </code> |
| 1952 |
* |
| 1953 |
* $param string Event description |
| 1954 |
* |
| 1955 |
* @depreciated peshkov@UD |
| 1956 |
* @uses get_option() |
| 1957 |
* @uses update_option() |
| 1958 |
* @return array Using the get_option function returns the contents of the log. |
| 1959 |
* |
| 1960 |
* @param bool $args |
| 1961 |
* |
| 1962 |
* @return array Using the get_option function returns the contents of the log. |
| 1963 |
*/ |
| 1964 |
static public function _get_log( $args = false ) { |
| 1965 |
|
| 1966 |
$args = wp_parse_args( $args, array( |
| 1967 |
'limit' => 20, |
| 1968 |
'prefix' => 'ud' |
| 1969 |
) ); |
| 1970 |
|
| 1971 |
extract( $args ); |
| 1972 |
|
| 1973 |
$this_log = get_option( $prefix . '_log' ); |
| 1974 |
|
| 1975 |
if( empty( $this_log ) ) { |
| 1976 |
$this_log = self::log( false, array( 'prefix' => $prefix ) ); |
| 1977 |
} |
| 1978 |
|
| 1979 |
$entries = (array) get_option( $prefix . '_log' ); |
| 1980 |
|
| 1981 |
$entries = array_reverse( $entries ); |
| 1982 |
|
| 1983 |
$entries = array_slice( $entries, 0, $args[ 'args' ] ? $args[ 'args' ] : $args[ 'limit' ] ); |
| 1984 |
|
| 1985 |
return $entries; |
| 1986 |
|
| 1987 |
} |
| 1988 |
|
| 1989 |
/** |
| 1990 |
* Delete UD log for this plugin. |
| 1991 |
* |
| 1992 |
* @uses update_option() |
| 1993 |
*/ |
| 1994 |
static public function delete_log( $args = array() ) { |
| 1995 |
|
| 1996 |
$args = wp_parse_args( $args, array( |
| 1997 |
'prefix' => 'ud' |
| 1998 |
) ); |
| 1999 |
|
| 2000 |
extract( $args ); |
| 2001 |
|
| 2002 |
$log = "{$prefix}_log"; |
| 2003 |
|
| 2004 |
delete_option( $log ); |
| 2005 |
} |
| 2006 |
|
| 2007 |
/** |
| 2008 |
* Creates Admin Menu page for UD Log |
| 2009 |
* |
| 2010 |
* @todo Need to make sure this will work if multiple plugins utilize the UD classes |
| 2011 |
* @see function show_log_page |
| 2012 |
* @since 1.0.0 |
| 2013 |
* @uses add_action() Calls 'admin_menu' hook with an anonymous ( lambda-style ) function which uses add_menu_page to create a UI Log page |
| 2014 |
*/ |
| 2015 |
static public function add_log_page() { |
| 2016 |
if( did_action( 'admin_menu' ) ) { |
| 2017 |
_doing_it_wrong( __FUNCTION__, sprintf( __( 'You cannot call UD\Utility::add_log_page() after the %1$s hook.' ), 'init' ), '3.4' ); |
| 2018 |
|
| 2019 |
return false; |
| 2020 |
} |
| 2021 |
add_action( 'admin_menu', function() { |
| 2022 |
add_menu_page( __( 'Log', UD_API_Transdomain ), __( 'Log', UD_API_Transdomain ), current_user_can( 'manage_options' ), 'ud_log', array( 'UD_API', 'show_log_page' ) ); |
| 2023 |
}); |
| 2024 |
} |
| 2025 |
|
| 2026 |
/** |
| 2027 |
* Displays the UD UI log page. |
| 2028 |
* |
| 2029 |
* @todo Add button or link to delete log |
| 2030 |
* @todo Add nonce to clear_log functions |
| 2031 |
* @todo Should be refactored to implement adding LOG tabs for different instances (wpp, wpi, wp-crm). peshkov@UD |
| 2032 |
* |
| 2033 |
* @since 1.0.0.0 |
| 2034 |
*/ |
| 2035 |
static public function show_log_page() { |
| 2036 |
|
| 2037 |
if( $_REQUEST[ 'ud_action' ] == 'clear_log' ) { |
| 2038 |
self::delete_log(); |
| 2039 |
} |
| 2040 |
|
| 2041 |
$output = array(); |
| 2042 |
|
| 2043 |
$output[ ] = '<style type="text/css">.ud_event_row b { background:none repeat scroll 0 0 #F6F7DC; padding:2px 6px;}</style>'; |
| 2044 |
|
| 2045 |
$output[ ] = '<div class="wrap">'; |
| 2046 |
$output[ ] = '<h2>' . __( 'Log Page for', self::$text_domain ) . ' ud_log '; |
| 2047 |
$output[ ] = '<a href="' . admin_url( "admin.php?page=ud_log&ud_action=clear_log" ) . '" class="button">' . __( 'Clear Log', self::$text_domain ) . '</a></h2>'; |
| 2048 |
|
| 2049 |
$output[ ] = '<table class="widefat"><thead><tr>'; |
| 2050 |
$output[ ] = '<th style="width: 150px">' . __( 'Timestamp', self::$text_domain ) . '</th>'; |
| 2051 |
$output[ ] = '<th>' . __( 'Type', self::$text_domain ) . '</th>'; |
| 2052 |
$output[ ] = '<th>' . __( 'Event', self::$text_domain ) . '</th>'; |
| 2053 |
$output[ ] = '<th>' . __( 'User', self::$text_domain ) . '</th>'; |
| 2054 |
$output[ ] = '<th>' . __( 'Related Object', self::$text_domain ) . '</th>'; |
| 2055 |
$output[ ] = '</tr></thead>'; |
| 2056 |
|
| 2057 |
$output[ ] = '<tbody>'; |
| 2058 |
|
| 2059 |
foreach( (array) self::_get_log() as $event ) { |
| 2060 |
$output[ ] = '<tr class="ud_event_row">'; |
| 2061 |
$output[ ] = '<td>' . self::nice_time( $event[ 'time' ] ) . '</td>'; |
| 2062 |
$output[ ] = '<td>' . $event[ 'type' ] . '</td>'; |
| 2063 |
$output[ ] = '<td>' . $event[ 'message' ] . '</td>'; |
| 2064 |
$output[ ] = '<td>' . ( is_numeric( $event[ 'user' ] ) ? get_userdata( $event[ 'user' ] )->display_name : __( 'None' ) ) . '</td>'; |
| 2065 |
$output[ ] = '<td>' . $event[ 'object' ] . '</td>'; |
| 2066 |
$output[ ] = '</tr>'; |
| 2067 |
} |
| 2068 |
|
| 2069 |
$output[ ] = '</tbody></table>'; |
| 2070 |
|
| 2071 |
$output[ ] = '</div>'; |
| 2072 |
|
| 2073 |
echo implode( '', (array) $output ); |
| 2074 |
|
| 2075 |
} |
| 2076 |
|
| 2077 |
/** |
| 2078 |
* Replace in $str all entries of keys of the given $values |
| 2079 |
* where each key will be rounded by $brackets['left'] and $brackets['right'] |
| 2080 |
* with the relevant values of the $values |
| 2081 |
* |
| 2082 |
* @param string|array $str |
| 2083 |
* @param array $values |
| 2084 |
* @param array $brackets |
| 2085 |
* |
| 2086 |
* @return string|array |
| 2087 |
* @author odokienko@UD |
| 2088 |
*/ |
| 2089 |
static public function replace_data( $str = '', $values = array(), $brackets = array( 'left' => '[', 'right' => ']' ) ) { |
| 2090 |
$values = (array) $values; |
| 2091 |
$replacements = array_keys( $values ); |
| 2092 |
array_walk( $replacements, function(&$val) use ($brackets){ |
| 2093 |
$val = $brackets[ 'left' ] . $val . $brackets[ 'right' ]; |
| 2094 |
} ); |
| 2095 |
|
| 2096 |
return str_replace( $replacements, array_values( $values ), $str ); |
| 2097 |
} |
| 2098 |
|
| 2099 |
/** |
| 2100 |
* Wrapper function to send notification with WP-CRM or without one |
| 2101 |
* |
| 2102 |
* @param array|mixed $args ['message'] using in email notification |
| 2103 |
* |
| 2104 |
* @uses self::replace_data() |
| 2105 |
* @uses wp_crm_send_notification() |
| 2106 |
* @return boolean false if notification was not sent successfully |
| 2107 |
* @autor odokienko@UD |
| 2108 |
*/ |
| 2109 |
static public function send_notification( $args = array() ) { |
| 2110 |
|
| 2111 |
$args = wp_parse_args( $args, array( |
| 2112 |
'ignore_wp_crm' => false, |
| 2113 |
'user' => false, |
| 2114 |
'trigger_action' => false, |
| 2115 |
'data' => array(), |
| 2116 |
'message' => '', |
| 2117 |
'subject' => '', |
| 2118 |
'crm_log_message' => '' |
| 2119 |
) ); |
| 2120 |
|
| 2121 |
if( is_numeric( $args[ 'user' ] ) ) { |
| 2122 |
$args[ 'user' ] = get_user_by( 'id', $args[ 'user' ] ); |
| 2123 |
} elseif( filter_var( $args[ 'user' ], FILTER_VALIDATE_EMAIL ) ) { |
| 2124 |
$args[ 'user' ] = get_user_by( 'email', $args[ 'user' ] ); |
| 2125 |
} elseif( is_string( $args[ 'user' ] ) ) { |
| 2126 |
$args[ 'user' ] = get_user_by( 'login', $args[ 'user' ] ); |
| 2127 |
} |
| 2128 |
|
| 2129 |
if( !is_object( $args[ 'user' ] ) || empty( $args[ 'user' ]->data->user_email ) ) { |
| 2130 |
return false; |
| 2131 |
} |
| 2132 |
|
| 2133 |
if( function_exists( 'wp_crm_send_notification' ) && |
| 2134 |
empty( $args[ 'ignore_wp_crm' ] ) |
| 2135 |
) { |
| 2136 |
|
| 2137 |
if( !empty( $args[ 'crm_log_message' ] ) ) { |
| 2138 |
wp_crm_add_to_user_log( $args[ 'user' ]->ID, self::replace_data( $args[ 'crm_log_message' ], $args[ 'data' ] ) ); |
| 2139 |
} |
| 2140 |
|
| 2141 |
if ( !empty( $args[ 'trigger_action' ] ) && is_callable( 'WP_CRM_N', 'get_trigger_action_notification' ) ) { |
| 2142 |
$notifications = \WP_CRM_N::get_trigger_action_notification( $args[ 'trigger_action' ] ); |
| 2143 |
if ( !empty( $notifications ) ) { |
| 2144 |
return wp_crm_send_notification( $args[ 'trigger_action' ], $args[ 'data' ] ); |
| 2145 |
} |
| 2146 |
} |
| 2147 |
|
| 2148 |
} |
| 2149 |
|
| 2150 |
if( empty( $args[ 'message' ] ) ) { |
| 2151 |
return false; |
| 2152 |
} |
| 2153 |
|
| 2154 |
return wp_mail( $args[ 'user' ]->data->user_email, self::replace_data( $args[ 'subject' ], $args[ 'data' ] ), self::replace_data( $args[ 'message' ], $args[ 'data' ] ) ); |
| 2155 |
|
| 2156 |
} |
| 2157 |
|
| 2158 |
/** |
| 2159 |
* Turns a passed string into a URL slug |
| 2160 |
* |
| 2161 |
* @param bool|string $content |
| 2162 |
* @param bool|string $args Optional list of arguments to overwrite the defaults. |
| 2163 |
* |
| 2164 |
* @author potanin@UD |
| 2165 |
* @version 1.1.0 |
| 2166 |
* @since 1.0.0 |
| 2167 |
* @return string |
| 2168 |
*/ |
| 2169 |
static public function create_slug( $content = null, $args = false ) { |
| 2170 |
|
| 2171 |
if( !$content ) { |
| 2172 |
return null; |
| 2173 |
} |
| 2174 |
|
| 2175 |
$args = wp_parse_args( $args, array( |
| 2176 |
'separator' => '-' |
| 2177 |
) ); |
| 2178 |
|
| 2179 |
$content = preg_replace( '~[^\\pL0-9_]+~u', $args[ 'separator' ], $content ); // substitutes anything but letters, numbers and '_' with separator |
| 2180 |
$content = trim( $content, $args[ 'separator' ] ); |
| 2181 |
$content = iconv( "utf-8", "us-ascii//TRANSLIT", $content ); // TRANSLIT does the whole job |
| 2182 |
$content = strtolower( $content ); |
| 2183 |
|
| 2184 |
// Removed the following logic because it was removing custom separators such as ":". - potanin@UD | October 17, 2013 |
| 2185 |
// return preg_replace( '~[^-a-z0-9_]+~', '', $content ); // keep only letters, numbers, '_' and separator |
| 2186 |
|
| 2187 |
return $content; |
| 2188 |
|
| 2189 |
} |
| 2190 |
|
| 2191 |
/** |
| 2192 |
* Convert a slug to a more readable string |
| 2193 |
* |
| 2194 |
* @since 1.3 |
| 2195 |
* |
| 2196 |
* @param $string |
| 2197 |
* |
| 2198 |
* @return string |
| 2199 |
*/ |
| 2200 |
static public function de_slug( $string ) { |
| 2201 |
return ucwords( str_replace( "_", " ", $string ) ); |
| 2202 |
} |
| 2203 |
|
| 2204 |
/** |
| 2205 |
* Returns current url |
| 2206 |
* |
| 2207 |
* @param mixed $args GET args which should be added to url |
| 2208 |
* @param mixed $except_args GET args which will be removed from URL if they exist |
| 2209 |
* |
| 2210 |
* @return string |
| 2211 |
* @author peshkov@UD |
| 2212 |
*/ |
| 2213 |
static public function current_url( $args = array(), $except_args = array() ) { |
| 2214 |
$url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER[ 'HTTP_HOST' ] . $_SERVER[ 'REQUEST_URI' ]; |
| 2215 |
|
| 2216 |
$args = wp_parse_args( $args ); |
| 2217 |
$except_args = wp_parse_args( $except_args ); |
| 2218 |
|
| 2219 |
if( !empty( $args ) ) { |
| 2220 |
foreach( (array) $args as $k => $v ) { |
| 2221 |
if( is_string( $v ) ) $url = add_query_arg( $k, $v, $url ); |
| 2222 |
} |
| 2223 |
} |
| 2224 |
|
| 2225 |
if( !empty( $except_args ) ) { |
| 2226 |
foreach( (array) $except_args as $arg ) { |
| 2227 |
if( is_string( $arg ) ) $url = remove_query_arg( $arg, $url ); |
| 2228 |
} |
| 2229 |
} |
| 2230 |
|
| 2231 |
return $url; |
| 2232 |
} |
| 2233 |
|
| 2234 |
/** |
| 2235 |
* Prepares data for SQL query. |
| 2236 |
* |
| 2237 |
* i.e. It should be used when $pwdb->prepare cannot be used. |
| 2238 |
* For example: |
| 2239 |
* we have situation when SQL query could not be prepared by default $wpdb->prepare: |
| 2240 |
* $titles = array( "John's appartment", " '; DELETE FROM $wpdb->posts;# " ); |
| 2241 |
* $wpdb->query( "SELECT ID FROM $wpdb->posts WHERE post_title IN ( '" . implode ("','", UD_Utility::prepare_to_sql( $titles ) ) . "' ) " ); |
| 2242 |
* |
| 2243 |
* @global type $wpdb |
| 2244 |
* |
| 2245 |
* @param mixed $args data which should be prepared for SQL query |
| 2246 |
* |
| 2247 |
* @return mixed prepared data |
| 2248 |
* @author peshkov@UD |
| 2249 |
*/ |
| 2250 |
static public function prepare_to_sql( $args ) { |
| 2251 |
global $wpdb; |
| 2252 |
|
| 2253 |
$prepared = $args; |
| 2254 |
if( is_array( $prepared ) ) { |
| 2255 |
foreach( $prepared as $k => $v ) { |
| 2256 |
if( is_string( $v ) ) { |
| 2257 |
$prepared[ $k ] = $wpdb->_real_escape( $v ); |
| 2258 |
} else if( is_array( $v ) ) { |
| 2259 |
$prepared[ $k ] = self::prepare_to_sql( $v ); |
| 2260 |
} |
| 2261 |
} |
| 2262 |
} else if( is_string( $prepared ) ) { |
| 2263 |
$prepared = $wpdb->_real_escape( $prepared ); |
| 2264 |
} |
| 2265 |
|
| 2266 |
return $prepared; |
| 2267 |
} |
| 2268 |
|
| 2269 |
/** |
| 2270 |
* Returns date and/or time using the WordPress date or time format, as configured. |
| 2271 |
* |
| 2272 |
* @param bool|string $time Date or time to use for calculation. |
| 2273 |
* @param bool|string $args List of arguments to overwrite the defaults. |
| 2274 |
* |
| 2275 |
* @uses wp_parse_args() |
| 2276 |
* @uses get_option() |
| 2277 |
* @return string|bool Returns formatted date or time, or false if no time passed. |
| 2278 |
* @updated 3.0 |
| 2279 |
*/ |
| 2280 |
static public function nice_time( $time = false, $args = false ) { |
| 2281 |
|
| 2282 |
$args = wp_parse_args( $args, array( |
| 2283 |
'format' => 'date_and_time' |
| 2284 |
) ); |
| 2285 |
|
| 2286 |
if( !$time ) { |
| 2287 |
return false; |
| 2288 |
} |
| 2289 |
|
| 2290 |
if( $args[ 'format' ] == 'date' ) { |
| 2291 |
return date( get_option( 'date_format' ), $time ); |
| 2292 |
} |
| 2293 |
|
| 2294 |
if( $args[ 'format' ] == 'time' ) { |
| 2295 |
return date( get_option( 'time_format' ), $time ); |
| 2296 |
} |
| 2297 |
|
| 2298 |
if( $args[ 'format' ] == 'date_and_time' ) { |
| 2299 |
return date( get_option( 'date_format' ), $time ) . ' ' . date( get_option( 'time_format' ), $time ); |
| 2300 |
} |
| 2301 |
|
| 2302 |
return false; |
| 2303 |
|
| 2304 |
} |
| 2305 |
|
| 2306 |
/** |
| 2307 |
* This function is for the encryption of data |
| 2308 |
* |
| 2309 |
* @source http://stackoverflow.com/questions/1289061/best-way-to-use-php-to-encrypt-and-decrypt |
| 2310 |
* @source http://php.net/manual/en/function.base64-encode.php |
| 2311 |
* @author williams@ud |
| 2312 |
* |
| 2313 |
* @param mixed $pt Object or plain text string |
| 2314 |
* @param bool|string $salt The salt to use |
| 2315 |
* |
| 2316 |
* @return mixed |
| 2317 |
*/ |
| 2318 |
static public function encrypt( $pt, $salt = false ) { |
| 2319 |
|
| 2320 |
if( !$salt ) $salt = AUTH_SALT; |
| 2321 |
$encrypted = base64_encode( mcrypt_encrypt( MCRYPT_RIJNDAEL_256, md5( $salt ), $pt, MCRYPT_MODE_CBC, md5( md5( $salt ) ) ) ); |
| 2322 |
$encrypted = str_replace( array( '+', '/', '=' ), array( '-', '_', '' ), $encrypted ); |
| 2323 |
|
| 2324 |
return $encrypted; |
| 2325 |
|
| 2326 |
} |
| 2327 |
|
| 2328 |
/** |
| 2329 |
* This function decrypts data |
| 2330 |
* |
| 2331 |
* @source http://stackoverflow.com/questions/1289061/best-way-to-use-php-to-encrypt-and-decrypt |
| 2332 |
* @source http://php.net/manual/en/function.base64-encode.php |
| 2333 |
* @author williams@ud |
| 2334 |
* |
| 2335 |
* @param mixed $ct Ciphertext |
| 2336 |
* @param bool|string $salt The salt to use |
| 2337 |
* |
| 2338 |
* @return string |
| 2339 |
*/ |
| 2340 |
static public function decrypt( $ct, $salt = false ) { |
| 2341 |
|
| 2342 |
if( !$salt ) $salt = AUTH_SALT; |
| 2343 |
$data = str_replace( array( '-', '_' ), array( '+', '/' ), $ct ); |
| 2344 |
$mod4 = strlen( $data ) % 4; |
| 2345 |
if( $mod4 ) { |
| 2346 |
$data .= substr( '====', $mod4 ); |
| 2347 |
} |
| 2348 |
$decrypted = rtrim( mcrypt_decrypt( MCRYPT_RIJNDAEL_256, md5( $salt ), base64_decode( $data ), MCRYPT_MODE_CBC, md5( md5( $salt ) ) ), "\0" ); |
| 2349 |
|
| 2350 |
return ( $decrypted ); |
| 2351 |
|
| 2352 |
} |
| 2353 |
|
| 2354 |
/** |
| 2355 |
* Returns array of full pathes of files or directories which we try to find. |
| 2356 |
* |
| 2357 |
* @param mixed $needle Directory(ies) or file(s) which we want to find |
| 2358 |
* @param string $path The path where we try to find it |
| 2359 |
* @param boolean $_is_dir We're finding dir or file. Default is file. |
| 2360 |
* |
| 2361 |
* @return array |
| 2362 |
* @author peshkov@UD |
| 2363 |
*/ |
| 2364 |
static public function find_file_in_system( $needle, $path, $_is_dir = false ) { |
| 2365 |
$return = array(); |
| 2366 |
$needle = (array) $needle; |
| 2367 |
$dir = @opendir( $path ); |
| 2368 |
|
| 2369 |
if( $dir ) { |
| 2370 |
while( ( $file = readdir( $dir ) ) !== false ) { |
| 2371 |
if( $file[ 0 ] == '.' ) { |
| 2372 |
continue; |
| 2373 |
} |
| 2374 |
$fullpath = trailingslashit( $path ) . $file; |
| 2375 |
if( is_dir( $fullpath ) ) { |
| 2376 |
if( $_is_dir && in_array( $file, $needle ) ) { |
| 2377 |
$return[ ] = $fullpath; |
| 2378 |
} |
| 2379 |
$return = array_merge( $return, self::find_file_in_system( $needle, $fullpath, $_is_dir ) ); |
| 2380 |
} else { |
| 2381 |
if( !$_is_dir && in_array( $file, $needle ) ) { |
| 2382 |
$return[ ] = $fullpath; |
| 2383 |
} |
| 2384 |
} |
| 2385 |
} |
| 2386 |
} |
| 2387 |
|
| 2388 |
return $return; |
| 2389 |
} |
| 2390 |
|
| 2391 |
/** |
| 2392 |
* Gets complicated html entity e.g. Table and ou|ol |
| 2393 |
* and removes whitespace characters include new line. |
| 2394 |
* we should to do this before use nl2br |
| 2395 |
* |
| 2396 |
* @author odokienko@UD |
| 2397 |
*/ |
| 2398 |
static public function cleanup_extra_whitespace( $content ) { |
| 2399 |
$content = preg_replace_callback( '~<(?:table|ul|ol )[^>]*>.*?<\/( ?:table|ul|ol )>~ims', function($matches){ |
| 2400 |
return preg_replace('~>[\s]+<((?:t[rdh]|li|\/tr|/table|/ul ))~ims', '><$1', $matches[0]); |
| 2401 |
}, $content ); |
| 2402 |
|
| 2403 |
return $content; |
| 2404 |
} |
| 2405 |
|
| 2406 |
/** |
| 2407 |
* Parses passed directory for widget files |
| 2408 |
* includes and registers widget if they exist |
| 2409 |
* |
| 2410 |
* @param string $path |
| 2411 |
* @param boolean $cache |
| 2412 |
* |
| 2413 |
* @return null |
| 2414 |
* @author peshkov@UD |
| 2415 |
*/ |
| 2416 |
static public function maybe_load_widgets( $path, $cache = true ) { |
| 2417 |
if ( !is_dir( $path ) ) { |
| 2418 |
return null; |
| 2419 |
} |
| 2420 |
|
| 2421 |
$_widgets = wp_cache_get( 'widgets', 'usabilitydynamics' ); |
| 2422 |
if( !is_array( $_widgets ) ) { |
| 2423 |
$_widgets = array(); |
| 2424 |
} |
| 2425 |
|
| 2426 |
if( $cache && !empty( $_widgets[ $path ] ) && is_array( $_widgets[ $path ] ) ) { |
| 2427 |
foreach( $_widgets[ $path ] as $_widget ) { |
| 2428 |
include_once( $path . "/" . $_widget[ 'file' ] ); |
| 2429 |
register_widget( $_widget[ 'headers' ][ 'class' ] ); |
| 2430 |
} |
| 2431 |
return null; |
| 2432 |
} |
| 2433 |
|
| 2434 |
$_widgets[ $path ] = array(); |
| 2435 |
|
| 2436 |
if ( $dir = @opendir( $path ) ) { |
| 2437 |
$headers = array( |
| 2438 |
'name' => 'Name', |
| 2439 |
'id' => 'ID', |
| 2440 |
'type' => 'Type', |
| 2441 |
'group' => 'Group', |
| 2442 |
'class' => 'Class', |
| 2443 |
'version' => 'Version', |
| 2444 |
'description' => 'Description', |
| 2445 |
); |
| 2446 |
while ( false !== ( $file = readdir( $dir ) ) ) { |
| 2447 |
$data = @get_file_data( $path . "/" . $file, $headers, 'widget' ); |
| 2448 |
if( $data[ 'type' ] == 'widget' && !empty( $data[ 'class' ] ) ) { |
| 2449 |
include_once( $path . "/" . $file ); |
| 2450 |
if( class_exists( $data[ 'class' ] ) ) { |
| 2451 |
//var_dump( $data[ 'class' ] ); |
| 2452 |
array_push( $_widgets[ $path ], array( |
| 2453 |
'file' => $file, |
| 2454 |
'headers' => $data, |
| 2455 |
) ); |
| 2456 |
register_widget( $data[ 'class' ] ); |
| 2457 |
} |
| 2458 |
} |
| 2459 |
} |
| 2460 |
} |
| 2461 |
|
| 2462 |
wp_cache_set( 'widgets', $_widgets, 'usabilitydynamics' ); |
| 2463 |
|
| 2464 |
} |
| 2465 |
|
| 2466 |
/** |
| 2467 |
* Includes all PHP files from specific folder |
| 2468 |
* |
| 2469 |
* @param string $dir Directory's path |
| 2470 |
* @author peshkov@UD |
| 2471 |
* @version 0.2 |
| 2472 |
*/ |
| 2473 |
static public function load_files ( $dir = '' ) { |
| 2474 |
if ( !empty( $dir ) && is_dir( $dir ) ) { |
| 2475 |
if ( $dh = opendir( $dir ) ) { |
| 2476 |
while (($file = readdir($dh)) !== false) { |
| 2477 |
$path = trailingslashit( $dir ) . $file; |
| 2478 |
if( !in_array( $file, array( '.', '..' ) ) && is_file( $path ) && 'php' == pathinfo( $path, PATHINFO_EXTENSION ) ) { |
| 2479 |
include_once( $path ); |
| 2480 |
} |
| 2481 |
} |
| 2482 |
closedir( $dh ); |
| 2483 |
} |
| 2484 |
} |
| 2485 |
} |
| 2486 |
|
| 2487 |
/** |
| 2488 |
* Localization Functionality. |
| 2489 |
* |
| 2490 |
* Replaces array's l10n data. |
| 2491 |
* Helpful for localization of data which is stored in JSON files ( see /schemas ) |
| 2492 |
* |
| 2493 |
* Usage: |
| 2494 |
* |
| 2495 |
* add_filter( 'ud::schema::localization', function($locals){ |
| 2496 |
* return array_merge( array( 'value_for_translating' => __( 'Blah Blah' ) ), $locals ); |
| 2497 |
* }); |
| 2498 |
* |
| 2499 |
* $result = self::l10n_localize (array( |
| 2500 |
* 'key' => 'l10n.value_for_translating' |
| 2501 |
* ) ); |
| 2502 |
* |
| 2503 |
* |
| 2504 |
* @param array $data |
| 2505 |
* @param array $l10n translated values |
| 2506 |
* @return array |
| 2507 |
* @author peshkov@UD |
| 2508 |
*/ |
| 2509 |
static public function l10n_localize( $data, $l10n = array() ) { |
| 2510 |
|
| 2511 |
if ( !is_array( $data ) && !is_object( $data ) ) { |
| 2512 |
return $data; |
| 2513 |
} |
| 2514 |
|
| 2515 |
//** The Localization's list. */ |
| 2516 |
$l10n = apply_filters( 'ud::schema::localization', $l10n ); |
| 2517 |
|
| 2518 |
//** Replace l10n entries */ |
| 2519 |
foreach( $data as $k => $v ) { |
| 2520 |
if ( is_array( $v ) ) { |
| 2521 |
$data[ $k ] = self::l10n_localize( $v, $l10n ); |
| 2522 |
} elseif ( is_string( $v ) ) { |
| 2523 |
if ( strpos( $v, 'l10n' ) !== false ) { |
| 2524 |
preg_match_all( '/l10n\.([^\s]*)/', $v, $matches ); |
| 2525 |
if ( !empty( $matches[ 1 ] ) ) { |
| 2526 |
foreach ( $matches[ 1 ] as $i => $m ) { |
| 2527 |
if ( array_key_exists( $m, $l10n ) ) { |
| 2528 |
$data[ $k ] = str_replace( $matches[ 0 ][ $i ], $l10n[ $m ], $data[ $k ] ); |
| 2529 |
} |
| 2530 |
} |
| 2531 |
} |
| 2532 |
} |
| 2533 |
} |
| 2534 |
} |
| 2535 |
|
| 2536 |
return $data; |
| 2537 |
} |
| 2538 |
|
| 2539 |
/** |
| 2540 |
* Wrapper for json_encode function. |
| 2541 |
* Emulates JSON_UNESCAPED_UNICODE. |
| 2542 |
* |
| 2543 |
* @param type $arr |
| 2544 |
* @return JSON |
| 2545 |
* @author peshkov@UD |
| 2546 |
*/ |
| 2547 |
static public function json_encode( $arr ) { |
| 2548 |
// convmap since 0x80 char codes so it takes all multibyte codes (above ASCII 127). So such characters are being "hidden" from normal json_encoding |
| 2549 |
array_walk_recursive( $arr, function(&$item, $key){ |
| 2550 |
if (is_string($item)) |
| 2551 |
$item = mb_encode_numericentity($item, array (0x80, 0xffff, 0, 0xffff), "UTF-8"); |
| 2552 |
}); |
| 2553 |
return mb_decode_numericentity( json_encode( $arr ), array( 0x80, 0xffff, 0, 0xffff ), 'UTF-8' ); |
| 2554 |
} |
| 2555 |
|
| 2556 |
/** |
| 2557 |
* Merges any number of arrays / parameters recursively, |
| 2558 |
* |
| 2559 |
* Replacing entries with string keys with values from latter arrays. |
| 2560 |
* If the entry or the next value to be assigned is an array, then it |
| 2561 |
* automagically treats both arguments as an array. |
| 2562 |
* Numeric entries are appended, not replaced, but only if they are |
| 2563 |
* unique |
| 2564 |
* |
| 2565 |
* @source http://us3.php.net/array_merge_recursive |
| 2566 |
* @version 0.4 |
| 2567 |
*/ |
| 2568 |
static public function array_merge_recursive_distinct() { |
| 2569 |
$arrays = func_get_args(); |
| 2570 |
$base = array_shift( $arrays ); |
| 2571 |
if ( !is_array( $base ) ) $base = empty( $base ) ? array() : array( $base ); |
| 2572 |
foreach ( (array)$arrays as $append ) { |
| 2573 |
if ( !is_array( $append ) ) $append = empty( $append ) ? array() : array( $append ); |
| 2574 |
foreach ( (array)$append as $key => $value ) { |
| 2575 |
if ( !array_key_exists( $key, $base ) and !is_numeric( $key ) ) { |
| 2576 |
$base[ $key ] = $append[ $key ]; |
| 2577 |
continue; |
| 2578 |
} |
| 2579 |
if ( @is_array( $value ) && isset( $base[ $key ] ) && isset( $append[ $key ] ) && is_array( $base[ $key ] ) && is_array( $append[ $key ] ) ) { |
| 2580 |
$base[ $key ] = self::array_merge_recursive_distinct( $base[ $key ], $append[ $key ] ); |
| 2581 |
} else if ( is_numeric( $key ) ) { |
| 2582 |
if ( !in_array( $value, $base ) ) $base[ ] = $value; |
| 2583 |
} else { |
| 2584 |
$base[ $key ] = $value; |
| 2585 |
} |
| 2586 |
} |
| 2587 |
} |
| 2588 |
return $base; |
| 2589 |
} |
| 2590 |
|
| 2591 |
} |
| 2592 |
|
| 2593 |
} |
| 2594 |
|
| 2595 |
} |
| 2596 |
|