PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / 7.3.7
Hustle – Email Marketing, Lead Generation, Optins, Popups v7.3.7
7.8.13 7.8.13.1 trunk 3.0 3.1 3.1.1 3.1.2 3.1.3 3.1.4 4.3.2 4.4.4 4.4.5 4.4.5.1 4.4.5.4 4.6 4.6.1.1 4.6.1.4 4.7.0.2 4.7.0.3 4.7.0.7 4.7.0.9 4.7.1.0 4.7.1.1 4.8.0.0 5.0.0 5.0.1 5.0.1.1 5.0.1.2 5.1 5.1.1 5.1.2 5.1.3 5.1.3.1 5.1.3.2 5.1.4 5.1.5 6.0 6.0.1 6.0.2 6.0.3 6.0.4.2 6.0.5 6.0.6.1 6.0.7 6.0.8.1 6.0.9 7.0.0.1 7.0.2 7.0.3 7.0.4 7.1.0 7.1.1 7.2.0 7.2.1 7.3.0 7.3.1 7.3.3 7.3.5 7.3.6 7.3.7 7.4.0 7.4.1 7.4.11 7.4.13 7.4.13.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.5.1 7.4.5.2 7.4.6 7.4.7 7.5.0 7.6.0 7.6.1 7.6.3 7.6.4 7.6.6 7.7.0 7.7.1 7.8.0 7.8.1 7.8.10 7.8.10.1 7.8.10.2 7.8.11 7.8.12 7.8.12.1 7.8.2 7.8.3 7.8.4 7.8.5 7.8.6 7.8.7 7.8.8 7.8.9 7.8.9.1 7.8.9.2 7.8.9.3
wordpress-popup / lib / wpmu-lib / inc / class-thelib-updates.php
wordpress-popup / lib / wpmu-lib / inc Last commit date
class-thelib-array.php 5 years ago class-thelib-core.php 5 years ago class-thelib-debug.php 5 years ago class-thelib-html.php 5 years ago class-thelib-net.php 5 years ago class-thelib-session.php 5 years ago class-thelib-ui.php 5 years ago class-thelib-updates.php 5 years ago class-thelib.php 5 years ago
class-thelib-updates.php
664 lines
1 <?php
2 /**
3 * The Queued Update component.
4 *
5 * This component makes it saver to perform plugin updates via code, e.g. when
6 * database keys need to be changed when updating a plugin.
7 *
8 * First all updates are added to the update-queue and only when all updates are
9 * prepared the queue is executed! This decreases the risk of being stuck with
10 * an unknown database state when an error happens during the preparation.
11 *
12 * @since 1.1.4
13 * @example inc/class-thelib-updates.php 20 11 Demo workflow.
14 */
15 class TheLib_Updates extends TheLib {
16 /*
17 Example:
18
19 // make sure the queue is empty
20 lib3()->updates->clear();
21
22 // Enqueue some updates
23 lib3()->updates->add( 'update_post_meta', 123, 'key', 'new-value' );
24 $the_post = get_post( 123 );
25 $the_post['post_type'] = 'new_type';
26 lib3()->updates->add( 'update_post', $the_post );
27
28 // Execute the changes
29 lib3()->updates->execute();
30 */
31
32 /**
33 * A list of all transaction commands that are queued up already
34 *
35 * @since 1.1.4
36 * @internal
37 *
38 * @type array
39 */
40 protected $commands = array();
41
42 /**
43 * Holds the last error message if something goes wrong during execution.
44 *
45 * @since 1.1.4
46 * @internal
47 *
48 * @var Exception|false
49 */
50 protected $error = false;
51
52 /**
53 * The plugin name. Used to log data in the uploads directory.
54 * @see write_to_file()
55 *
56 * @since 2.0.0
57 * @internal
58 *
59 * @type string
60 */
61 protected $plugin = '';
62
63 /**
64 * Clears all commands from the transaction queue.
65 *
66 * This should always be used before starting an update to guarantee that
67 * we start at a known (= empty) state.
68 *
69 * @since 1.1.4
70 * @api
71 */
72 public function clear() {
73 $this->commands = array();
74 $this->error = false;
75 }
76
77 /**
78 * Adds a command to the transaction queue.
79 *
80 * We use this workflow to first collect all update-statements before
81 * the first update is actually written to database.
82 * This way we have opportunity to parse all available data and not end
83 * with inconsistent data when there is an error somehwere during upgrading.
84 *
85 * @since 1.1.4
86 * @api
87 *
88 * @param callable $command The command to execute.
89 * @param mixed $args Optional. All params that come after $command will be
90 * passed as function parameters to that function.
91 */
92 public function add( $command ) {
93 $this->commands[] = func_get_args();
94 }
95
96 /**
97 * Executes each command that is in the transaction queue.
98 *
99 * Each command that was executed without an error will be removed from the
100 * queue. All commands are executed in the same sequence as they were added.
101 *
102 * @since 1.1.4
103 * @api
104 */
105 public function execute() {
106 $this->error = false;
107
108 foreach ( $this->commands as $key => $transaction ) {
109 $done = false;
110 $log_line = '';
111
112 if ( count( $transaction ) < 1 ) {
113 $done = false;
114 } elseif ( ! is_callable( $transaction[0] ) ) {
115 $done = false;
116 } else {
117 $func = array_shift( $transaction );
118
119 if ( is_array( $func ) ) {
120 if ( is_object( $func[0] ) ) {
121 $log_line = get_class( $func[0] ) . '->';
122 } elseif ( is_scalar( $func[0] ) ) {
123 $log_line = $func[0] . '::';
124 }
125 if ( is_scalar( $func[1] ) ) {
126 $log_line .= $func[1];
127 }
128 } else {
129 $log_line = $func;
130 }
131 $log_line .= '(' . json_encode( $transaction ) . ')';
132
133 try {
134 call_user_func_array( $func, $transaction );
135 $done = true;
136 } catch( Exception $ex ) {
137 $this->set_error( $ex, $func );
138 return false;
139 }
140 }
141
142 if ( $done ) {
143 $this->log_action( $log_line );
144 unset( $this->commands[$key] );
145 }
146 }
147
148 return true;
149 }
150
151 /**
152 * Saves error details if a command fails during execution.
153 * Only one error can be saved.
154 *
155 * @since 1.1.0
156 * @internal
157 *
158 * @param Exception $exception The error that was raised.
159 * @param array $command The command that was executed.
160 */
161 protected function set_error( $exception, $command ) {
162 $this->error = $exception;
163 $this->error->command = $command;
164 }
165
166 /**
167 * Returns the last error and resets the error-flag.
168 *
169 * @since 1.1.4
170 * @api
171 *
172 * @return Exception|false The error object
173 */
174 public function last_error() {
175 $error = $this->error;
176 $this->error = false;
177
178 return $error;
179 }
180
181 /**
182 * Debug function that will display the contents of the current queue.
183 *
184 * @since 1.1.4
185 */
186 public function debug() {
187 self::$core->debug->dump( $this->commands );
188 }
189
190 /**
191 * Sets the plugin name (i.e. the sub-folder for the write_to_file function)
192 *
193 * @since 2.0.0
194 * @api
195 *
196 * @param string $name The plugin name, must be a valid folder name.
197 */
198 public function plugin( $name ) {
199 $this->plugin = sanitize_html_class( $name );
200 }
201
202 /**
203 * Writes data to a file in the uploads directory.
204 *
205 * @since 2.0.0
206 * @internal
207 *
208 * @param string $name Snapshot name, used as file-name.
209 * @param string $ext File extension.
210 * @param string $data Data to write into the file.
211 * @param bool $silent_fail See $this->snapshot()
212 *
213 * @return string|false The filename that was created. False on failure.
214 */
215 private function write_to_file( $file, $ext, $data, $silent_fail = false ) {
216 // Find the uploads-folder.
217 $upload = wp_upload_dir();
218
219 if ( false !== $upload['error'] ) {
220 return $this->write_file_failed(
221 $silent_fail,
222 1,
223 $upload['error']
224 );
225 }
226
227 // Create the Snapshot sub-folder.
228 if ( empty( $this->plugin ) ) {
229 $this->plugin( 'wpmudev-plugin' );
230 }
231 $target = trailingslashit( $upload['basedir'] ) . $this->plugin . '/';
232
233 if ( ! is_dir( $target ) ) {
234 mkdir( $target );
235 }
236
237 if ( ! is_dir( $target ) ) {
238 return $this->write_file_failed(
239 $silent_fail,
240 2,
241 'Could not create sub-directory ' . $target
242 );
243 }
244
245 // Create the empty snapshot file.
246 $filename = sanitize_html_class( $file );
247 $filename .= '-' . date( 'Ymd-His' );
248 $ext = '.' . $ext;
249 $i = '';
250 $sep = '';
251
252 while ( file_exists( $target . $filename . $sep . $i . $ext ) ) {
253 if ( empty( $i ) ) { $i = 1; }
254 else { $i += 1; }
255 $sep = '-';
256 }
257 $filename = $target . $filename . $sep . $i . $ext;
258
259 file_put_contents( $filename, '' );
260
261 if ( ! file_exists( $filename ) ) {
262 return $this->write_file_failed(
263 $silent_fail,
264 3,
265 'Could not create file ' . $filename
266 );
267 }
268
269 // Write data to file.
270 file_put_contents( $filename, $data );
271
272 return $filename;
273 }
274
275 /**
276 * Reads data from a file in the uploads directory.
277 *
278 * @since 2.0.0
279 * @internal
280 *
281 * @param string $name Snapshot name, used as file-name.
282 * @return string The file contents as string.
283 */
284 private function read_from_file( $file ) {
285 // Find the uploads-folder.
286 $upload = wp_upload_dir();
287
288 if ( false !== $upload['error'] ) {
289 return '';
290 }
291
292 // Build the full file name.
293 if ( empty( $this->plugin ) ) {
294 $this->plugin( 'wpmudev-plugin' );
295 }
296 $target = trailingslashit( $upload['basedir'] ) . $this->plugin . '/';
297
298 if ( ! is_dir( $target ) ) {
299 return '';
300 }
301
302 $filename = $target . $file;
303
304 if ( ! is_file( $filename ) ) {
305 return '';
306 }
307
308 $data = file_get_contents( $filename );
309
310 return $data;
311 }
312
313 /**
314 * Returns a list with all backup files in the plugins upload folder.
315 *
316 * @since 2.0.0
317 * @api
318 *
319 * @param string $ext File extension to include. Empty means all files.
320 * Only specify extension, without the dot (e.g. 'txt')
321 * @return array List of filenames (only file name without path)
322 */
323 public function list_files( $ext = '' ) {
324 $res = array();
325
326 // Find the uploads-folder.
327 $upload = wp_upload_dir();
328
329 if ( false !== $upload['error'] ) {
330 return $res;
331 }
332
333 // Build the full file name.
334 if ( empty( $this->plugin ) ) {
335 $this->plugin( 'wpmudev-plugin' );
336 }
337 $target = trailingslashit( $upload['basedir'] ) . $this->plugin . '/';
338
339 if ( ! is_dir( $target ) ) {
340 return $res;
341 }
342
343 if ( empty( $ext ) ) {
344 $ext = '*';
345 }
346
347 $pattern = $target . '*.' . $ext;
348 $res = glob( $pattern );
349 foreach ( $res as $key => $path ) {
350 $res[$key] = str_replace( $target, '', $path );
351 }
352
353 return $res;
354 }
355
356 /**
357 * Saves a snapshot of certain database values to the uploads directory.
358 *
359 * @since 2.0.0
360 * @api
361 *
362 * @param string $name Snapshot name, used as file-name.
363 * @param array $data_list {
364 * List of db-items to backup.
365 *
366 * @options .. array of option keys
367 * @posts .. array of post_ids
368 * }
369 * @param bool $silent_fail If set to false then failure will be silent.
370 * Otherwise the script will wp_die() on failure (default)
371 */
372 public function log_action( $data, $silent_fail = false ) {
373 static $Logfile = null;
374
375 $data .= "\n-----\n";
376 if ( null === $Logfile ) {
377 $Logfile = $this->write_to_file( 'update_log', 'log', $data, $silent_fail );
378 } else {
379 file_put_contents( $Logfile, $data, FILE_APPEND );
380 }
381 }
382
383 /**
384 * Saves a snapshot of certain database values to the uploads directory.
385 *
386 * CAREFUL! THIS FUNCTION CAN CAUSE MEMORY ISSUES IF THE $data_list IS TOO
387 * LARGE.
388 * FUNCTION IS STILL EXPERIMENTAL.
389 *
390 * @since 2.0.0
391 * @api
392 *
393 * @param string $name Snapshot name, used as file-name.
394 * @param array $data_list {
395 * List of db-items to backup.
396 *
397 * @options .. array of option keys
398 * @posts .. array of post_ids
399 * }
400 * @param bool $silent_fail If set to false then failure will be silent.
401 * Otherwise the script will wp_die() on failure (default)
402 */
403 public function snapshot( $name, $data_list, $silent_fail = false ) {
404 // Collect data from the DB that was specified by the user.
405 $data = $this->snapshot_collect( $data_list );
406
407 /*
408 * Data is serialized using json_encode.
409 * While is method is slightly slowe then phps `serialize` the output
410 * string is about 30% smaller, resulting in smaller backup files...
411 *
412 * http://techblog.procurios.nl/k/618/news/view/34972/14863/cache-a-large-array-json-serialize-or-var_export.html
413 */
414 $data = json_encode( $data );
415
416 $this->write_to_file( $name, 'json', $data, $silent_fail );
417 }
418
419 /**
420 * Displays an error message (Snapshot failed) and then die()
421 *
422 * @since 2.0.0
423 * @internal
424 *
425 * @param bool $silent_fail See $this->snapshot()
426 * @param string $err_code An error-code to display.
427 * @param string $error The full error message.
428 */
429 private function write_file_failed( $silent_fail, $err_code, $error = '' ) {
430 if ( $silent_fail ) { return false; }
431
432 if ( empty( $this->plugin ) ) {
433 $this->plugin( 'wpmudev-plugin' );
434 }
435
436 $msg = sprintf(
437 '<b>Abborting update of %s!</b> '.
438 'Could not create a restore-point [%s]<br />%s',
439 ucwords( $this->plugin ),
440 $err_code,
441 $error
442 );
443
444 wp_die( $msg );
445 }
446
447 /**
448 * Collects data from the current sites DB and returns a structured object.
449 *
450 * @since 2.0.0
451 * @internal
452 *
453 * @param array $data_list See $this->snapshot()
454 */
455 private function snapshot_collect( $data_list ) {
456 $dump = (object) array();
457
458 // Options.
459 $dump->options = array();
460 if ( isset( $data_list->options )
461 && is_array( $data_list->options )
462 ) {
463 foreach ( $data_list->options as $option ) {
464 $dump->options[$option] = get_option( $option );
465 }
466 }
467
468 // Posts and Post-Meta
469 $dump->posts = array();
470 $dump->postmeta = array();
471 if ( isset( $data_list->posts )
472 && is_array( $data_list->posts )
473 ) {
474 foreach ( $data_list->posts as $id ) {
475 $post = get_post( $id );
476 $meta = get_post_meta( $id );
477
478 // Flatten the meta values.
479 foreach ( $meta as $key => $values ) {
480 if ( is_array( $values ) && isset( $values[0] ) ) {
481 $meta[ $key ] = $values[0];
482 }
483 }
484
485 // Append the data to the dump.
486 if ( ! isset( $dump->posts[$post->post_type] ) ) {
487 $dump->posts[$post->post_type] = array();
488 $dump->postmeta[$post->post_type] = array();
489 }
490 $dump->posts[$post->post_type][$post->ID] = $post;
491 $dump->postmeta[$post->post_type][$post->ID] = $meta;
492 }
493 }
494
495 return $dump;
496 }
497
498 /**
499 * Restores a saved snapshot.
500 *
501 * We're using a lot of SQL queries here to get as much performance as
502 * possible. Using functions like wp_set_option() takes much longer than
503 * a direct SQL query.
504 *
505 * NOTE THAT THIS FUNCTION IS FOR DEVELOPMENT AND DEBUGGING. IT MIGHT CAUSE
506 * MEMORY ISSUES FOR LARGE SNAPSHOTS OR EVEN BREAK THINGS.
507 * FUNCTION IS STILL EXPERIMENTAL.
508 *
509 * @since 2.0.0
510 * @api
511 *
512 * @param string $snapshot Exact filename of the snapshot, including ext.
513 * @return bool True, if the restore-process was successful
514 */
515 public function restore( $snapshot ) {
516 global $wpdb;
517
518 // Get the contents of the snapshot file.
519 $data = $this->read_from_file( $snapshot );
520 if ( empty( $data ) ) {
521 return false;
522 }
523
524 // Decode the snapshot data to an PHP object.
525 $data = json_decode( $data, true );
526 if ( empty( $data ) ) {
527 return false;
528 }
529
530 // The restore-process is handled as execution transaction.
531 $this->clear();
532
533 // Options
534 if ( ! empty( $data['options'] ) && is_array( $data['options'] ) ) {
535 $sql_delete = "DELETE FROM {$wpdb->options} WHERE option_name IN ";
536 $sql_idlist = array();
537 $sql_insert = "INSERT INTO {$wpdb->options} (option_name, option_value) VALUES ";
538 $sql_values = array();
539
540 foreach ( $data['options'] as $key => $value ) {
541 $sql_idlist[] = $wpdb->prepare( '%s', $key );
542 $sql_values[] = $wpdb->prepare( '(%s,%s)', $key, maybe_serialize( $value ) );
543 }
544
545 if ( ! empty( $sql_values ) ) {
546 $this->add( $sql_delete . '(' . implode( ',', $sql_idlist ) . ')' );
547 $this->add( $sql_insert . implode( ",\n", $sql_values ) );
548 }
549 }
550
551 // Posts
552 if ( ! empty( $data['posts'] ) && is_array( $data['posts'] ) ) {
553 foreach ( $data['posts'] as $posttype => $items ) {
554 $sql_delete_post = "DELETE FROM {$wpdb->posts} WHERE ID IN ";
555 $sql_delete_meta = "DELETE FROM {$wpdb->postmeta} WHERE post_id IN ";
556 $sql_idlist = array();
557 $sql_insert = "INSERT INTO {$wpdb->posts} (ID, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count) VALUES ";
558 $sql_values = array();
559
560 foreach ( $items as $id => $post ) {
561 self::$core->array->equip(
562 $post,
563 'post_author',
564 'post_date',
565 'post_date_gmt',
566 'post_content',
567 'post_title',
568 'post_excerpt',
569 'post_status',
570 'comment_status',
571 'ping_status',
572 'post_password',
573 'post_name',
574 'to_ping',
575 'pinged',
576 'post_modified',
577 'post_modified_gmt',
578 'post_content_filtered',
579 'post_parent',
580 'guid',
581 'menu_order',
582 'post_type',
583 'post_mime_type',
584 'comment_count'
585 );
586 $sql_idlist[] = $wpdb->prepare( '%s', $id );
587 $sql_values[] = $wpdb->prepare(
588 '(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)',
589 $id,
590 $post['post_author'],
591 $post['post_date'],
592 $post['post_date_gmt'],
593 $post['post_content'],
594 $post['post_title'],
595 $post['post_excerpt'],
596 $post['post_status'],
597 $post['comment_status'],
598 $post['ping_status'],
599 $post['post_password'],
600 $post['post_name'],
601 $post['to_ping'],
602 $post['pinged'],
603 $post['post_modified'],
604 $post['post_modified_gmt'],
605 $post['post_content_filtered'],
606 $post['post_parent'],
607 $post['guid'],
608 $post['menu_order'],
609 $post['post_type'],
610 $post['post_mime_type'],
611 $post['comment_count']
612 );
613 }
614
615 while ( ! empty( $sql_idlist ) ) {
616 $values = array();
617 for ( $i = 0; $i < 100; $i += 1 ) {
618 if ( empty( $sql_idlist ) ) { break; }
619 $values[] = array_shift( $sql_idlist );
620 }
621 $this->add( $sql_delete_post . '(' . implode( ',', $values ) . ')' );
622 $this->add( $sql_delete_meta . '(' . implode( ',', $values ) . ')' );
623 }
624 while ( ! empty( $sql_values ) ) {
625 $values = array();
626 for ( $i = 0; $i < 100; $i += 1 ) {
627 if ( empty( $sql_values ) ) { break; }
628 $values[] = array_shift( $sql_values );
629 }
630 $this->add( $sql_insert . implode( ",\n", $values ) );
631 }
632 }
633 }
634
635 // Postmeta
636 if ( ! empty( $data['postmeta'] ) && is_array( $data['postmeta'] ) ) {
637 foreach ( $data['postmeta'] as $posttype => $items ) {
638 foreach ( $items as $id => $entries ) {
639 $sql_meta = "INSERT INTO {$wpdb->postmeta} (post_id,meta_key,meta_value) VALUES ";
640 $sql_values = array();
641
642 foreach ( $entries as $key => $value ) {
643 $sql_values[] = $wpdb->prepare( '(%s,%s,%s)', $id, $key, $value );
644 }
645
646 if ( ! empty( $sql_values ) ) {
647 $this->add( $sql_meta . implode( ",\n", $sql_values ) );
648 }
649 }
650 }
651 }
652
653 // Run all scheduled queries
654 foreach ( $this->commands as $key => $params ) {
655 if ( ! isset( $params[0] ) ) { continue; }
656 $query = $params[0];
657
658 $res = $wpdb->query( $query );
659 }
660
661 return true;
662 }
663
664 }