PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.2.9
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.2.9
3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 All 156 releases
notificationx / vendor / wpdeveloper / lib-utility / lib / class-utility.php

class-utility.php in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar 3.2.9, at vendor/wpdeveloper/lib-utility/lib/class-utility.php

2,596 lines 82.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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