Version Description
- 12/20/2012 =
- Fixed bug that set 1Tb (instead of 1Mb) chunk sizes for Google Drive uploads
Download this release
Release Info
Developer | DavidAnderson |
Plugin | UpdraftPlus WordPress Backup Plugin |
Version | 1.0.7 |
Comparing to | |
See all releases |
Code changes from version 1.0.6 to 1.0.7
- readme.txt +4 -1
- trunk/example-decrypt.php +41 -0
- trunk/includes/Rijndael.php +1424 -0
- trunk/includes/S3.php +2211 -0
- trunk/includes/class-gdocs.php +630 -0
- trunk/includes/ftp.class.php +193 -0
- trunk/includes/updraft-restorer.php +91 -0
- trunk/readme.txt +189 -0
- trunk/updraftplus.php +2418 -0
- updraftplus.php +5 -5
readme.txt
CHANGED
@@ -3,7 +3,7 @@ Contributors: David Anderson
|
|
3 |
Tags: backup, restore, database, cloud, amazon, s3, Amazon S3, google drive, google, gdrive, ftp, cloud, updraft, back up
|
4 |
Requires at least: 3.2
|
5 |
Tested up to: 3.5
|
6 |
-
Stable tag: 1.0.
|
7 |
Donate link: http://david.dw-perspective.org.uk/donate
|
8 |
License: GPLv3 or later
|
9 |
|
@@ -81,6 +81,9 @@ No, there's no warranty or guarantee, etc. It's completely up to you to verify t
|
|
81 |
|
82 |
== Changelog ==
|
83 |
|
|
|
|
|
|
|
84 |
= 1.0.5 - 12/13/2012 =
|
85 |
* Tweaked default Google Drive options
|
86 |
|
3 |
Tags: backup, restore, database, cloud, amazon, s3, Amazon S3, google drive, google, gdrive, ftp, cloud, updraft, back up
|
4 |
Requires at least: 3.2
|
5 |
Tested up to: 3.5
|
6 |
+
Stable tag: 1.0.7
|
7 |
Donate link: http://david.dw-perspective.org.uk/donate
|
8 |
License: GPLv3 or later
|
9 |
|
81 |
|
82 |
== Changelog ==
|
83 |
|
84 |
+
= 1.0.7 - 12/20/2012 =
|
85 |
+
* Fixed bug that set 1Tb (instead of 1Mb) chunk sizes for Google Drive uploads
|
86 |
+
|
87 |
= 1.0.5 - 12/13/2012 =
|
88 |
* Tweaked default Google Drive options
|
89 |
|
trunk/example-decrypt.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*
|
4 |
+
|
5 |
+
To dump the decrypted file using the given key on stdout, call:
|
6 |
+
|
7 |
+
rijndael_decrypt_file( '../path/to/file.crypt' , 'mykey' );
|
8 |
+
|
9 |
+
Thus, here are the easy instructions:
|
10 |
+
|
11 |
+
1) Add a line like the above into this PHP file (not inside these comments, but outside)
|
12 |
+
e.g.
|
13 |
+
rijndael_decrypt_file( '/home/myself/myfile.crypt' , 'MYKEY' );
|
14 |
+
|
15 |
+
2) Run this file (and make sure that includes/Rijndael.php is available, if you are moving this file around)
|
16 |
+
e.g.
|
17 |
+
php /home/myself/example-decrypt.php >output.sql.gz
|
18 |
+
|
19 |
+
3) You may then want to gunzip the resulting file to have a standard SQL file.
|
20 |
+
e.g.
|
21 |
+
gunzip output.sql.gz
|
22 |
+
|
23 |
+
*/
|
24 |
+
|
25 |
+
function rijndael_decrypt_file($file, $key) {
|
26 |
+
|
27 |
+
require_once(dirname(__FILE__).'/includes/Rijndael.php');
|
28 |
+
|
29 |
+
$rijndael = new Crypt_Rijndael();
|
30 |
+
$rijndael->setKey($key);
|
31 |
+
$in_handle = fopen($file,'r');
|
32 |
+
$ciphertext = "";
|
33 |
+
while (!feof ($in_handle)) {
|
34 |
+
$ciphertext .= fread($in_handle, 16384);
|
35 |
+
}
|
36 |
+
fclose ($in_handle);
|
37 |
+
print $rijndael->decrypt($ciphertext);
|
38 |
+
|
39 |
+
}
|
40 |
+
|
41 |
+
?>
|
trunk/includes/Rijndael.php
ADDED
@@ -0,0 +1,1424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Pure-PHP implementation of Rijndael.
|
6 |
+
*
|
7 |
+
* Does not use mcrypt, even when available, for reasons that are explained below.
|
8 |
+
*
|
9 |
+
* PHP versions 4 and 5
|
10 |
+
*
|
11 |
+
* If {@link Crypt_Rijndael::setBlockLength() setBlockLength()} isn't called, it'll be assumed to be 128 bits. If
|
12 |
+
* {@link Crypt_Rijndael::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
|
13 |
+
* {@link Crypt_Rijndael::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's
|
14 |
+
* 136-bits it'll be null-padded to 160-bits and 160 bits will be the key length until
|
15 |
+
* {@link Crypt_Rijndael::setKey() setKey()} is called, again, at which point, it'll be recalculated.
|
16 |
+
*
|
17 |
+
* Not all Rijndael implementations may support 160-bits or 224-bits as the block length / key length. mcrypt, for example,
|
18 |
+
* does not. AES, itself, only supports block lengths of 128 and key lengths of 128, 192, and 256.
|
19 |
+
* {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=10 Rijndael-ammended.pdf#page=10} defines the
|
20 |
+
* algorithm for block lengths of 192 and 256 but not for block lengths / key lengths of 160 and 224. Indeed, 160 and 224
|
21 |
+
* are first defined as valid key / block lengths in
|
22 |
+
* {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=44 Rijndael-ammended.pdf#page=44}:
|
23 |
+
* Extensions: Other block and Cipher Key lengths.
|
24 |
+
*
|
25 |
+
* {@internal The variable names are the same as those in
|
26 |
+
* {@link http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf#page=10 fips-197.pdf#page=10}.}}
|
27 |
+
*
|
28 |
+
* Here's a short example of how to use this library:
|
29 |
+
* <code>
|
30 |
+
* <?php
|
31 |
+
* include('Crypt/Rijndael.php');
|
32 |
+
*
|
33 |
+
* $rijndael = new Crypt_Rijndael();
|
34 |
+
*
|
35 |
+
* $rijndael->setKey('abcdefghijklmnop');
|
36 |
+
*
|
37 |
+
* $size = 10 * 1024;
|
38 |
+
* $plaintext = '';
|
39 |
+
* for ($i = 0; $i < $size; $i++) {
|
40 |
+
* $plaintext.= 'a';
|
41 |
+
* }
|
42 |
+
*
|
43 |
+
* echo $rijndael->decrypt($rijndael->encrypt($plaintext));
|
44 |
+
* ?>
|
45 |
+
* </code>
|
46 |
+
*
|
47 |
+
* LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
|
48 |
+
* of this software and associated documentation files (the "Software"), to deal
|
49 |
+
* in the Software without restriction, including without limitation the rights
|
50 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
51 |
+
* copies of the Software, and to permit persons to whom the Software is
|
52 |
+
* furnished to do so, subject to the following conditions:
|
53 |
+
*
|
54 |
+
* The above copyright notice and this permission notice shall be included in
|
55 |
+
* all copies or substantial portions of the Software.
|
56 |
+
*
|
57 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
58 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
59 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
60 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
61 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
62 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
63 |
+
* THE SOFTWARE.
|
64 |
+
*
|
65 |
+
* @category Crypt
|
66 |
+
* @package Crypt_Rijndael
|
67 |
+
* @author Jim Wigginton <terrafrost@php.net>
|
68 |
+
* @copyright MMVIII Jim Wigginton
|
69 |
+
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
70 |
+
* @version $Id: Rijndael.php,v 1.12 2010/02/09 06:10:26 terrafrost Exp $
|
71 |
+
* @link http://phpseclib.sourceforge.net
|
72 |
+
*/
|
73 |
+
|
74 |
+
/**#@+
|
75 |
+
* @access public
|
76 |
+
* @see Crypt_Rijndael::encrypt()
|
77 |
+
* @see Crypt_Rijndael::decrypt()
|
78 |
+
*/
|
79 |
+
/**
|
80 |
+
* Encrypt / decrypt using the Counter mode.
|
81 |
+
*
|
82 |
+
* Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
|
83 |
+
*
|
84 |
+
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
|
85 |
+
*/
|
86 |
+
define('CRYPT_RIJNDAEL_MODE_CTR', -1);
|
87 |
+
/**
|
88 |
+
* Encrypt / decrypt using the Electronic Code Book mode.
|
89 |
+
*
|
90 |
+
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
|
91 |
+
*/
|
92 |
+
define('CRYPT_RIJNDAEL_MODE_ECB', 1);
|
93 |
+
/**
|
94 |
+
* Encrypt / decrypt using the Code Book Chaining mode.
|
95 |
+
*
|
96 |
+
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
|
97 |
+
*/
|
98 |
+
define('CRYPT_RIJNDAEL_MODE_CBC', 2);
|
99 |
+
/**
|
100 |
+
* Encrypt / decrypt using the Cipher Feedback mode.
|
101 |
+
*
|
102 |
+
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
|
103 |
+
*/
|
104 |
+
define('CRYPT_RIJNDAEL_MODE_CFB', 3);
|
105 |
+
/**
|
106 |
+
* Encrypt / decrypt using the Cipher Feedback mode.
|
107 |
+
*
|
108 |
+
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
|
109 |
+
*/
|
110 |
+
define('CRYPT_RIJNDAEL_MODE_OFB', 4);
|
111 |
+
/**#@-*/
|
112 |
+
|
113 |
+
/**#@+
|
114 |
+
* @access private
|
115 |
+
* @see Crypt_Rijndael::Crypt_Rijndael()
|
116 |
+
*/
|
117 |
+
/**
|
118 |
+
* Toggles the internal implementation
|
119 |
+
*/
|
120 |
+
define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1);
|
121 |
+
/**
|
122 |
+
* Toggles the mcrypt implementation
|
123 |
+
*/
|
124 |
+
define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2);
|
125 |
+
/**#@-*/
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Pure-PHP implementation of Rijndael.
|
129 |
+
*
|
130 |
+
* @author Jim Wigginton <terrafrost@php.net>
|
131 |
+
* @version 0.1.0
|
132 |
+
* @access public
|
133 |
+
* @package Crypt_Rijndael
|
134 |
+
*/
|
135 |
+
class Crypt_Rijndael {
|
136 |
+
/**
|
137 |
+
* The Encryption Mode
|
138 |
+
*
|
139 |
+
* @see Crypt_Rijndael::Crypt_Rijndael()
|
140 |
+
* @var Integer
|
141 |
+
* @access private
|
142 |
+
*/
|
143 |
+
var $mode;
|
144 |
+
|
145 |
+
/**
|
146 |
+
* The Key
|
147 |
+
*
|
148 |
+
* @see Crypt_Rijndael::setKey()
|
149 |
+
* @var String
|
150 |
+
* @access private
|
151 |
+
*/
|
152 |
+
var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
153 |
+
|
154 |
+
/**
|
155 |
+
* The Initialization Vector
|
156 |
+
*
|
157 |
+
* @see Crypt_Rijndael::setIV()
|
158 |
+
* @var String
|
159 |
+
* @access private
|
160 |
+
*/
|
161 |
+
var $iv = '';
|
162 |
+
|
163 |
+
/**
|
164 |
+
* A "sliding" Initialization Vector
|
165 |
+
*
|
166 |
+
* @see Crypt_Rijndael::enableContinuousBuffer()
|
167 |
+
* @var String
|
168 |
+
* @access private
|
169 |
+
*/
|
170 |
+
var $encryptIV = '';
|
171 |
+
|
172 |
+
/**
|
173 |
+
* A "sliding" Initialization Vector
|
174 |
+
*
|
175 |
+
* @see Crypt_Rijndael::enableContinuousBuffer()
|
176 |
+
* @var String
|
177 |
+
* @access private
|
178 |
+
*/
|
179 |
+
var $decryptIV = '';
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Continuous Buffer status
|
183 |
+
*
|
184 |
+
* @see Crypt_Rijndael::enableContinuousBuffer()
|
185 |
+
* @var Boolean
|
186 |
+
* @access private
|
187 |
+
*/
|
188 |
+
var $continuousBuffer = false;
|
189 |
+
|
190 |
+
/**
|
191 |
+
* Padding status
|
192 |
+
*
|
193 |
+
* @see Crypt_Rijndael::enablePadding()
|
194 |
+
* @var Boolean
|
195 |
+
* @access private
|
196 |
+
*/
|
197 |
+
var $padding = true;
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Does the key schedule need to be (re)calculated?
|
201 |
+
*
|
202 |
+
* @see setKey()
|
203 |
+
* @see setBlockLength()
|
204 |
+
* @see setKeyLength()
|
205 |
+
* @var Boolean
|
206 |
+
* @access private
|
207 |
+
*/
|
208 |
+
var $changed = true;
|
209 |
+
|
210 |
+
/**
|
211 |
+
* Has the key length explicitly been set or should it be derived from the key, itself?
|
212 |
+
*
|
213 |
+
* @see setKeyLength()
|
214 |
+
* @var Boolean
|
215 |
+
* @access private
|
216 |
+
*/
|
217 |
+
var $explicit_key_length = false;
|
218 |
+
|
219 |
+
/**
|
220 |
+
* The Key Schedule
|
221 |
+
*
|
222 |
+
* @see _setup()
|
223 |
+
* @var Array
|
224 |
+
* @access private
|
225 |
+
*/
|
226 |
+
var $w;
|
227 |
+
|
228 |
+
/**
|
229 |
+
* The Inverse Key Schedule
|
230 |
+
*
|
231 |
+
* @see _setup()
|
232 |
+
* @var Array
|
233 |
+
* @access private
|
234 |
+
*/
|
235 |
+
var $dw;
|
236 |
+
|
237 |
+
/**
|
238 |
+
* The Block Length
|
239 |
+
*
|
240 |
+
* @see setBlockLength()
|
241 |
+
* @var Integer
|
242 |
+
* @access private
|
243 |
+
* @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with
|
244 |
+
* $Nb because we need this value and not $Nb to pad strings appropriately.
|
245 |
+
*/
|
246 |
+
var $block_size = 16;
|
247 |
+
|
248 |
+
/**
|
249 |
+
* The Block Length divided by 32
|
250 |
+
*
|
251 |
+
* @see setBlockLength()
|
252 |
+
* @var Integer
|
253 |
+
* @access private
|
254 |
+
* @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size
|
255 |
+
* because the encryption / decryption / key schedule creation requires this number and not $block_size. We could
|
256 |
+
* derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
|
257 |
+
* of that, we'll just precompute it once.
|
258 |
+
*
|
259 |
+
*/
|
260 |
+
var $Nb = 4;
|
261 |
+
|
262 |
+
/**
|
263 |
+
* The Key Length
|
264 |
+
*
|
265 |
+
* @see setKeyLength()
|
266 |
+
* @var Integer
|
267 |
+
* @access private
|
268 |
+
* @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size
|
269 |
+
* because the encryption / decryption / key schedule creation requires this number and not $key_size. We could
|
270 |
+
* derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
|
271 |
+
* of that, we'll just precompute it once.
|
272 |
+
*/
|
273 |
+
var $key_size = 16;
|
274 |
+
|
275 |
+
/**
|
276 |
+
* The Key Length divided by 32
|
277 |
+
*
|
278 |
+
* @see setKeyLength()
|
279 |
+
* @var Integer
|
280 |
+
* @access private
|
281 |
+
* @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4
|
282 |
+
*/
|
283 |
+
var $Nk = 4;
|
284 |
+
|
285 |
+
/**
|
286 |
+
* The Number of Rounds
|
287 |
+
*
|
288 |
+
* @var Integer
|
289 |
+
* @access private
|
290 |
+
* @internal The max value is 14, the min value is 10.
|
291 |
+
*/
|
292 |
+
var $Nr;
|
293 |
+
|
294 |
+
/**
|
295 |
+
* Shift offsets
|
296 |
+
*
|
297 |
+
* @var Array
|
298 |
+
* @access private
|
299 |
+
*/
|
300 |
+
var $c;
|
301 |
+
|
302 |
+
/**
|
303 |
+
* Precomputed mixColumns table
|
304 |
+
*
|
305 |
+
* @see Crypt_Rijndael()
|
306 |
+
* @var Array
|
307 |
+
* @access private
|
308 |
+
*/
|
309 |
+
var $t0;
|
310 |
+
|
311 |
+
/**
|
312 |
+
* Precomputed mixColumns table
|
313 |
+
*
|
314 |
+
* @see Crypt_Rijndael()
|
315 |
+
* @var Array
|
316 |
+
* @access private
|
317 |
+
*/
|
318 |
+
var $t1;
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Precomputed mixColumns table
|
322 |
+
*
|
323 |
+
* @see Crypt_Rijndael()
|
324 |
+
* @var Array
|
325 |
+
* @access private
|
326 |
+
*/
|
327 |
+
var $t2;
|
328 |
+
|
329 |
+
/**
|
330 |
+
* Precomputed mixColumns table
|
331 |
+
*
|
332 |
+
* @see Crypt_Rijndael()
|
333 |
+
* @var Array
|
334 |
+
* @access private
|
335 |
+
*/
|
336 |
+
var $t3;
|
337 |
+
|
338 |
+
/**
|
339 |
+
* Precomputed invMixColumns table
|
340 |
+
*
|
341 |
+
* @see Crypt_Rijndael()
|
342 |
+
* @var Array
|
343 |
+
* @access private
|
344 |
+
*/
|
345 |
+
var $dt0;
|
346 |
+
|
347 |
+
/**
|
348 |
+
* Precomputed invMixColumns table
|
349 |
+
*
|
350 |
+
* @see Crypt_Rijndael()
|
351 |
+
* @var Array
|
352 |
+
* @access private
|
353 |
+
*/
|
354 |
+
var $dt1;
|
355 |
+
|
356 |
+
/**
|
357 |
+
* Precomputed invMixColumns table
|
358 |
+
*
|
359 |
+
* @see Crypt_Rijndael()
|
360 |
+
* @var Array
|
361 |
+
* @access private
|
362 |
+
*/
|
363 |
+
var $dt2;
|
364 |
+
|
365 |
+
/**
|
366 |
+
* Precomputed invMixColumns table
|
367 |
+
*
|
368 |
+
* @see Crypt_Rijndael()
|
369 |
+
* @var Array
|
370 |
+
* @access private
|
371 |
+
*/
|
372 |
+
var $dt3;
|
373 |
+
|
374 |
+
/**
|
375 |
+
* Is the mode one that is paddable?
|
376 |
+
*
|
377 |
+
* @see Crypt_Rijndael::Crypt_Rijndael()
|
378 |
+
* @var Boolean
|
379 |
+
* @access private
|
380 |
+
*/
|
381 |
+
var $paddable = false;
|
382 |
+
|
383 |
+
/**
|
384 |
+
* Encryption buffer for CTR, OFB and CFB modes
|
385 |
+
*
|
386 |
+
* @see Crypt_Rijndael::encrypt()
|
387 |
+
* @var String
|
388 |
+
* @access private
|
389 |
+
*/
|
390 |
+
var $enbuffer = array('encrypted' => '', 'xor' => '');
|
391 |
+
|
392 |
+
/**
|
393 |
+
* Decryption buffer for CTR, OFB and CFB modes
|
394 |
+
*
|
395 |
+
* @see Crypt_Rijndael::decrypt()
|
396 |
+
* @var String
|
397 |
+
* @access private
|
398 |
+
*/
|
399 |
+
var $debuffer = array('ciphertext' => '');
|
400 |
+
|
401 |
+
/**
|
402 |
+
* Default Constructor.
|
403 |
+
*
|
404 |
+
* Determines whether or not the mcrypt extension should be used. $mode should only, at present, be
|
405 |
+
* CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used.
|
406 |
+
*
|
407 |
+
* @param optional Integer $mode
|
408 |
+
* @return Crypt_Rijndael
|
409 |
+
* @access public
|
410 |
+
*/
|
411 |
+
function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC)
|
412 |
+
{
|
413 |
+
switch ($mode) {
|
414 |
+
case CRYPT_RIJNDAEL_MODE_ECB:
|
415 |
+
case CRYPT_RIJNDAEL_MODE_CBC:
|
416 |
+
$this->paddable = true;
|
417 |
+
$this->mode = $mode;
|
418 |
+
break;
|
419 |
+
case CRYPT_RIJNDAEL_MODE_CTR:
|
420 |
+
case CRYPT_RIJNDAEL_MODE_CFB:
|
421 |
+
case CRYPT_RIJNDAEL_MODE_OFB:
|
422 |
+
$this->mode = $mode;
|
423 |
+
break;
|
424 |
+
default:
|
425 |
+
$this->paddable = true;
|
426 |
+
$this->mode = CRYPT_RIJNDAEL_MODE_CBC;
|
427 |
+
}
|
428 |
+
|
429 |
+
$t3 = &$this->t3;
|
430 |
+
$t2 = &$this->t2;
|
431 |
+
$t1 = &$this->t1;
|
432 |
+
$t0 = &$this->t0;
|
433 |
+
|
434 |
+
$dt3 = &$this->dt3;
|
435 |
+
$dt2 = &$this->dt2;
|
436 |
+
$dt1 = &$this->dt1;
|
437 |
+
$dt0 = &$this->dt0;
|
438 |
+
|
439 |
+
// according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=19> (section 5.2.1),
|
440 |
+
// precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so
|
441 |
+
// those are the names we'll use.
|
442 |
+
$t3 = array(
|
443 |
+
0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491,
|
444 |
+
0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC,
|
445 |
+
0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB,
|
446 |
+
0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B,
|
447 |
+
0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83,
|
448 |
+
0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A,
|
449 |
+
0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F,
|
450 |
+
0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA,
|
451 |
+
0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B,
|
452 |
+
0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713,
|
453 |
+
0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6,
|
454 |
+
0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85,
|
455 |
+
0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411,
|
456 |
+
0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B,
|
457 |
+
0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1,
|
458 |
+
0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF,
|
459 |
+
0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E,
|
460 |
+
0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6,
|
461 |
+
0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B,
|
462 |
+
0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD,
|
463 |
+
0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8,
|
464 |
+
0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2,
|
465 |
+
0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049,
|
466 |
+
0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810,
|
467 |
+
0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197,
|
468 |
+
0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F,
|
469 |
+
0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C,
|
470 |
+
0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927,
|
471 |
+
0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733,
|
472 |
+
0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5,
|
473 |
+
0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0,
|
474 |
+
0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C
|
475 |
+
);
|
476 |
+
|
477 |
+
$dt3 = array(
|
478 |
+
0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B,
|
479 |
+
0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5,
|
480 |
+
0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B,
|
481 |
+
0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E,
|
482 |
+
0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D,
|
483 |
+
0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9,
|
484 |
+
0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66,
|
485 |
+
0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED,
|
486 |
+
0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4,
|
487 |
+
0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD,
|
488 |
+
0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60,
|
489 |
+
0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79,
|
490 |
+
0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C,
|
491 |
+
0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24,
|
492 |
+
0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C,
|
493 |
+
0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814,
|
494 |
+
0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B,
|
495 |
+
0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084,
|
496 |
+
0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077,
|
497 |
+
0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22,
|
498 |
+
0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F,
|
499 |
+
0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582,
|
500 |
+
0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB,
|
501 |
+
0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF,
|
502 |
+
0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035,
|
503 |
+
0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17,
|
504 |
+
0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46,
|
505 |
+
0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D,
|
506 |
+
0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A,
|
507 |
+
0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678,
|
508 |
+
0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF,
|
509 |
+
0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0
|
510 |
+
);
|
511 |
+
|
512 |
+
for ($i = 0; $i < 256; $i++) {
|
513 |
+
$t2[$i << 8] = (($t3[$i] << 8) & 0xFFFFFF00) | (($t3[$i] >> 24) & 0x000000FF);
|
514 |
+
$t1[$i << 16] = (($t3[$i] << 16) & 0xFFFF0000) | (($t3[$i] >> 16) & 0x0000FFFF);
|
515 |
+
$t0[$i << 24] = (($t3[$i] << 24) & 0xFF000000) | (($t3[$i] >> 8) & 0x00FFFFFF);
|
516 |
+
|
517 |
+
$dt2[$i << 8] = (($this->dt3[$i] << 8) & 0xFFFFFF00) | (($dt3[$i] >> 24) & 0x000000FF);
|
518 |
+
$dt1[$i << 16] = (($this->dt3[$i] << 16) & 0xFFFF0000) | (($dt3[$i] >> 16) & 0x0000FFFF);
|
519 |
+
$dt0[$i << 24] = (($this->dt3[$i] << 24) & 0xFF000000) | (($dt3[$i] >> 8) & 0x00FFFFFF);
|
520 |
+
}
|
521 |
+
}
|
522 |
+
|
523 |
+
/**
|
524 |
+
* Sets the key.
|
525 |
+
*
|
526 |
+
* Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and
|
527 |
+
* whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length
|
528 |
+
* up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the
|
529 |
+
* excess bits.
|
530 |
+
*
|
531 |
+
* If the key is not explicitly set, it'll be assumed to be all null bytes.
|
532 |
+
*
|
533 |
+
* @access public
|
534 |
+
* @param String $key
|
535 |
+
*/
|
536 |
+
function setKey($key)
|
537 |
+
{
|
538 |
+
$this->key = $key;
|
539 |
+
$this->changed = true;
|
540 |
+
}
|
541 |
+
|
542 |
+
/**
|
543 |
+
* Sets the initialization vector. (optional)
|
544 |
+
*
|
545 |
+
* SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed
|
546 |
+
* to be all zero's.
|
547 |
+
*
|
548 |
+
* @access public
|
549 |
+
* @param String $iv
|
550 |
+
*/
|
551 |
+
function setIV($iv)
|
552 |
+
{
|
553 |
+
$this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0));;
|
554 |
+
}
|
555 |
+
|
556 |
+
/**
|
557 |
+
* Sets the key length
|
558 |
+
*
|
559 |
+
* Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
|
560 |
+
* 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount.
|
561 |
+
*
|
562 |
+
* @access public
|
563 |
+
* @param Integer $length
|
564 |
+
*/
|
565 |
+
function setKeyLength($length)
|
566 |
+
{
|
567 |
+
$length >>= 5;
|
568 |
+
if ($length > 8) {
|
569 |
+
$length = 8;
|
570 |
+
} else if ($length < 4) {
|
571 |
+
$length = 4;
|
572 |
+
}
|
573 |
+
$this->Nk = $length;
|
574 |
+
$this->key_size = $length << 2;
|
575 |
+
|
576 |
+
$this->explicit_key_length = true;
|
577 |
+
$this->changed = true;
|
578 |
+
}
|
579 |
+
|
580 |
+
/**
|
581 |
+
* Sets the block length
|
582 |
+
*
|
583 |
+
* Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
|
584 |
+
* 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount.
|
585 |
+
*
|
586 |
+
* @access public
|
587 |
+
* @param Integer $length
|
588 |
+
*/
|
589 |
+
function setBlockLength($length)
|
590 |
+
{
|
591 |
+
$length >>= 5;
|
592 |
+
if ($length > 8) {
|
593 |
+
$length = 8;
|
594 |
+
} else if ($length < 4) {
|
595 |
+
$length = 4;
|
596 |
+
}
|
597 |
+
$this->Nb = $length;
|
598 |
+
$this->block_size = $length << 2;
|
599 |
+
$this->changed = true;
|
600 |
+
}
|
601 |
+
|
602 |
+
/**
|
603 |
+
* Generate CTR XOR encryption key
|
604 |
+
*
|
605 |
+
* Encrypt the output of this and XOR it against the ciphertext / plaintext to get the
|
606 |
+
* plaintext / ciphertext in CTR mode.
|
607 |
+
*
|
608 |
+
* @see Crypt_Rijndael::decrypt()
|
609 |
+
* @see Crypt_Rijndael::encrypt()
|
610 |
+
* @access public
|
611 |
+
* @param Integer $length
|
612 |
+
* @param String $iv
|
613 |
+
*/
|
614 |
+
function _generate_xor($length, &$iv)
|
615 |
+
{
|
616 |
+
$xor = '';
|
617 |
+
$block_size = $this->block_size;
|
618 |
+
$num_blocks = floor(($length + ($block_size - 1)) / $block_size);
|
619 |
+
for ($i = 0; $i < $num_blocks; $i++) {
|
620 |
+
$xor.= $iv;
|
621 |
+
for ($j = 4; $j <= $block_size; $j+=4) {
|
622 |
+
$temp = substr($iv, -$j, 4);
|
623 |
+
switch ($temp) {
|
624 |
+
case "\xFF\xFF\xFF\xFF":
|
625 |
+
$iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4);
|
626 |
+
break;
|
627 |
+
case "\x7F\xFF\xFF\xFF":
|
628 |
+
$iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4);
|
629 |
+
break 2;
|
630 |
+
default:
|
631 |
+
extract(unpack('Ncount', $temp));
|
632 |
+
$iv = substr_replace($iv, pack('N', $count + 1), -$j, 4);
|
633 |
+
break 2;
|
634 |
+
}
|
635 |
+
}
|
636 |
+
}
|
637 |
+
|
638 |
+
return $xor;
|
639 |
+
}
|
640 |
+
|
641 |
+
/**
|
642 |
+
* Encrypts a message.
|
643 |
+
*
|
644 |
+
* $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael
|
645 |
+
* implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's
|
646 |
+
* necessary are discussed in the following
|
647 |
+
* URL:
|
648 |
+
*
|
649 |
+
* {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
|
650 |
+
*
|
651 |
+
* An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does.
|
652 |
+
* strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that
|
653 |
+
* length.
|
654 |
+
*
|
655 |
+
* @see Crypt_Rijndael::decrypt()
|
656 |
+
* @access public
|
657 |
+
* @param String $plaintext
|
658 |
+
*/
|
659 |
+
function encrypt($plaintext)
|
660 |
+
{
|
661 |
+
$this->_setup();
|
662 |
+
if ($this->paddable) {
|
663 |
+
$plaintext = $this->_pad($plaintext);
|
664 |
+
}
|
665 |
+
|
666 |
+
$block_size = $this->block_size;
|
667 |
+
$buffer = &$this->enbuffer;
|
668 |
+
$continuousBuffer = $this->continuousBuffer;
|
669 |
+
$ciphertext = '';
|
670 |
+
switch ($this->mode) {
|
671 |
+
case CRYPT_RIJNDAEL_MODE_ECB:
|
672 |
+
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
|
673 |
+
$ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size));
|
674 |
+
}
|
675 |
+
break;
|
676 |
+
case CRYPT_RIJNDAEL_MODE_CBC:
|
677 |
+
$xor = $this->encryptIV;
|
678 |
+
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
|
679 |
+
$block = substr($plaintext, $i, $block_size);
|
680 |
+
$block = $this->_encryptBlock($block ^ $xor);
|
681 |
+
$xor = $block;
|
682 |
+
$ciphertext.= $block;
|
683 |
+
}
|
684 |
+
if ($this->continuousBuffer) {
|
685 |
+
$this->encryptIV = $xor;
|
686 |
+
}
|
687 |
+
break;
|
688 |
+
case CRYPT_RIJNDAEL_MODE_CTR:
|
689 |
+
$xor = $this->encryptIV;
|
690 |
+
if (!empty($buffer)) {
|
691 |
+
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
|
692 |
+
$block = substr($plaintext, $i, $block_size);
|
693 |
+
$buffer.= $this->_encryptBlock($this->_generate_xor($block_size, $xor));
|
694 |
+
$key = $this->_string_shift($buffer, $block_size);
|
695 |
+
$ciphertext.= $block ^ $key;
|
696 |
+
}
|
697 |
+
} else {
|
698 |
+
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
|
699 |
+
$block = substr($plaintext, $i, $block_size);
|
700 |
+
$key = $this->_encryptBlock($this->_generate_xor($block_size, $xor));
|
701 |
+
$ciphertext.= $block ^ $key;
|
702 |
+
}
|
703 |
+
}
|
704 |
+
if ($this->continuousBuffer) {
|
705 |
+
$this->encryptIV = $xor;
|
706 |
+
if ($start = strlen($plaintext) % $block_size) {
|
707 |
+
$buffer = substr($key, $start) . $buffer;
|
708 |
+
}
|
709 |
+
}
|
710 |
+
break;
|
711 |
+
case CRYPT_RIJNDAEL_MODE_CFB:
|
712 |
+
if (!empty($buffer['xor'])) {
|
713 |
+
$ciphertext = $plaintext ^ $buffer['xor'];
|
714 |
+
$iv = $buffer['encrypted'] . $ciphertext;
|
715 |
+
$start = strlen($ciphertext);
|
716 |
+
$buffer['encrypted'].= $ciphertext;
|
717 |
+
$buffer['xor'] = substr($buffer['xor'], strlen($ciphertext));
|
718 |
+
} else {
|
719 |
+
$ciphertext = '';
|
720 |
+
$iv = $this->encryptIV;
|
721 |
+
$start = 0;
|
722 |
+
}
|
723 |
+
|
724 |
+
for ($i = $start; $i < strlen($plaintext); $i+=$block_size) {
|
725 |
+
$block = substr($plaintext, $i, $block_size);
|
726 |
+
$xor = $this->_encryptBlock($iv);
|
727 |
+
$iv = $block ^ $xor;
|
728 |
+
if ($continuousBuffer && strlen($iv) != $block_size) {
|
729 |
+
$buffer = array(
|
730 |
+
'encrypted' => $iv,
|
731 |
+
'xor' => substr($xor, strlen($iv))
|
732 |
+
);
|
733 |
+
}
|
734 |
+
$ciphertext.= $iv;
|
735 |
+
}
|
736 |
+
|
737 |
+
if ($this->continuousBuffer) {
|
738 |
+
$this->encryptIV = $iv;
|
739 |
+
}
|
740 |
+
break;
|
741 |
+
case CRYPT_RIJNDAEL_MODE_OFB:
|
742 |
+
$xor = $this->encryptIV;
|
743 |
+
if (strlen($buffer)) {
|
744 |
+
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
|
745 |
+
$xor = $this->_encryptBlock($xor);
|
746 |
+
$buffer.= $xor;
|
747 |
+
$key = $this->_string_shift($buffer, $block_size);
|
748 |
+
$ciphertext.= substr($plaintext, $i, $block_size) ^ $key;
|
749 |
+
}
|
750 |
+
} else {
|
751 |
+
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
|
752 |
+
$xor = $this->_encryptBlock($xor);
|
753 |
+
$ciphertext.= substr($plaintext, $i, $block_size) ^ $xor;
|
754 |
+
}
|
755 |
+
$key = $xor;
|
756 |
+
}
|
757 |
+
if ($this->continuousBuffer) {
|
758 |
+
$this->encryptIV = $xor;
|
759 |
+
if ($start = strlen($plaintext) % $block_size) {
|
760 |
+
$buffer = substr($key, $start) . $buffer;
|
761 |
+
}
|
762 |
+
}
|
763 |
+
}
|
764 |
+
|
765 |
+
return $ciphertext;
|
766 |
+
}
|
767 |
+
|
768 |
+
/**
|
769 |
+
* Decrypts a message.
|
770 |
+
*
|
771 |
+
* If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until
|
772 |
+
* it is.
|
773 |
+
*
|
774 |
+
* @see Crypt_Rijndael::encrypt()
|
775 |
+
* @access public
|
776 |
+
* @param String $ciphertext
|
777 |
+
*/
|
778 |
+
function decrypt($ciphertext)
|
779 |
+
{
|
780 |
+
$this->_setup();
|
781 |
+
|
782 |
+
if ($this->paddable) {
|
783 |
+
// we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic :
|
784 |
+
// "The data is padded with "\0" to make sure the length of the data is n * blocksize."
|
785 |
+
$ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0));
|
786 |
+
}
|
787 |
+
|
788 |
+
$block_size = $this->block_size;
|
789 |
+
$buffer = &$this->debuffer;
|
790 |
+
$continuousBuffer = $this->continuousBuffer;
|
791 |
+
$plaintext = '';
|
792 |
+
switch ($this->mode) {
|
793 |
+
case CRYPT_RIJNDAEL_MODE_ECB:
|
794 |
+
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
|
795 |
+
$plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size));
|
796 |
+
}
|
797 |
+
break;
|
798 |
+
case CRYPT_RIJNDAEL_MODE_CBC:
|
799 |
+
$xor = $this->decryptIV;
|
800 |
+
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
|
801 |
+
$block = substr($ciphertext, $i, $block_size);
|
802 |
+
$plaintext.= $this->_decryptBlock($block) ^ $xor;
|
803 |
+
$xor = $block;
|
804 |
+
}
|
805 |
+
if ($this->continuousBuffer) {
|
806 |
+
$this->decryptIV = $xor;
|
807 |
+
}
|
808 |
+
break;
|
809 |
+
case CRYPT_RIJNDAEL_MODE_CTR:
|
810 |
+
$xor = $this->decryptIV;
|
811 |
+
if (strlen($buffer)) {
|
812 |
+
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
|
813 |
+
$block = substr($ciphertext, $i, $block_size);
|
814 |
+
$buffer.= $this->_encryptBlock($this->_generate_xor($block_size, $xor));
|
815 |
+
$key = $this->_string_shift($buffer, $block_size);
|
816 |
+
$plaintext.= $block ^ $key;
|
817 |
+
}
|
818 |
+
} else {
|
819 |
+
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
|
820 |
+
$block = substr($ciphertext, $i, $block_size);
|
821 |
+
$key = $this->_encryptBlock($this->_generate_xor($block_size, $xor));
|
822 |
+
$plaintext.= $block ^ $key;
|
823 |
+
}
|
824 |
+
}
|
825 |
+
if ($this->continuousBuffer) {
|
826 |
+
$this->decryptIV = $xor;
|
827 |
+
if ($start = strlen($ciphertext) % $block_size) {
|
828 |
+
$buffer = substr($key, $start) . $buffer;
|
829 |
+
}
|
830 |
+
}
|
831 |
+
break;
|
832 |
+
case CRYPT_RIJNDAEL_MODE_CFB:
|
833 |
+
if (!empty($buffer['ciphertext'])) {
|
834 |
+
$plaintext = $ciphertext ^ substr($this->decryptIV, strlen($buffer['ciphertext']));
|
835 |
+
$buffer['ciphertext'].= substr($ciphertext, 0, strlen($plaintext));
|
836 |
+
if (strlen($buffer['ciphertext']) == $block_size) {
|
837 |
+
$xor = $this->_encryptBlock($buffer['ciphertext']);
|
838 |
+
$buffer['ciphertext'] = '';
|
839 |
+
}
|
840 |
+
$start = strlen($plaintext);
|
841 |
+
$block = $this->decryptIV;
|
842 |
+
} else {
|
843 |
+
$plaintext = '';
|
844 |
+
$xor = $this->_encryptBlock($this->decryptIV);
|
845 |
+
$start = 0;
|
846 |
+
}
|
847 |
+
|
848 |
+
for ($i = $start; $i < strlen($ciphertext); $i+=$block_size) {
|
849 |
+
$block = substr($ciphertext, $i, $block_size);
|
850 |
+
$plaintext.= $block ^ $xor;
|
851 |
+
if ($continuousBuffer && strlen($block) != $block_size) {
|
852 |
+
$buffer['ciphertext'].= $block;
|
853 |
+
$block = $xor;
|
854 |
+
} else if (strlen($block) == $block_size) {
|
855 |
+
$xor = $this->_encryptBlock($block);
|
856 |
+
}
|
857 |
+
}
|
858 |
+
if ($this->continuousBuffer) {
|
859 |
+
$this->decryptIV = $block;
|
860 |
+
}
|
861 |
+
break;
|
862 |
+
case CRYPT_RIJNDAEL_MODE_OFB:
|
863 |
+
$xor = $this->decryptIV;
|
864 |
+
if (strlen($buffer)) {
|
865 |
+
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
|
866 |
+
$xor = $this->_encryptBlock($xor);
|
867 |
+
$buffer.= $xor;
|
868 |
+
$key = $this->_string_shift($buffer, $block_size);
|
869 |
+
$plaintext.= substr($ciphertext, $i, $block_size) ^ $key;
|
870 |
+
}
|
871 |
+
} else {
|
872 |
+
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
|
873 |
+
$xor = $this->_encryptBlock($xor);
|
874 |
+
$plaintext.= substr($ciphertext, $i, $block_size) ^ $xor;
|
875 |
+
}
|
876 |
+
$key = $xor;
|
877 |
+
}
|
878 |
+
if ($this->continuousBuffer) {
|
879 |
+
$this->decryptIV = $xor;
|
880 |
+
if ($start = strlen($ciphertext) % $block_size) {
|
881 |
+
$buffer = substr($key, $start) . $buffer;
|
882 |
+
}
|
883 |
+
}
|
884 |
+
}
|
885 |
+
|
886 |
+
return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
|
887 |
+
}
|
888 |
+
|
889 |
+
/**
|
890 |
+
* Encrypts a block
|
891 |
+
*
|
892 |
+
* @access private
|
893 |
+
* @param String $in
|
894 |
+
* @return String
|
895 |
+
*/
|
896 |
+
function _encryptBlock($in)
|
897 |
+
{
|
898 |
+
$state = array();
|
899 |
+
$words = unpack('N*word', $in);
|
900 |
+
|
901 |
+
$w = $this->w;
|
902 |
+
$t0 = $this->t0;
|
903 |
+
$t1 = $this->t1;
|
904 |
+
$t2 = $this->t2;
|
905 |
+
$t3 = $this->t3;
|
906 |
+
$Nb = $this->Nb;
|
907 |
+
$Nr = $this->Nr;
|
908 |
+
$c = $this->c;
|
909 |
+
|
910 |
+
// addRoundKey
|
911 |
+
$i = 0;
|
912 |
+
foreach ($words as $word) {
|
913 |
+
$state[] = $word ^ $w[0][$i++];
|
914 |
+
}
|
915 |
+
|
916 |
+
// fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components -
|
917 |
+
// subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding
|
918 |
+
// Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf.
|
919 |
+
// Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization.
|
920 |
+
// Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1],
|
921 |
+
// equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well.
|
922 |
+
|
923 |
+
// [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf
|
924 |
+
$temp = array();
|
925 |
+
for ($round = 1; $round < $Nr; $round++) {
|
926 |
+
$i = 0; // $c[0] == 0
|
927 |
+
$j = $c[1];
|
928 |
+
$k = $c[2];
|
929 |
+
$l = $c[3];
|
930 |
+
|
931 |
+
while ($i < $this->Nb) {
|
932 |
+
$temp[$i] = $t0[$state[$i] & 0xFF000000] ^
|
933 |
+
$t1[$state[$j] & 0x00FF0000] ^
|
934 |
+
$t2[$state[$k] & 0x0000FF00] ^
|
935 |
+
$t3[$state[$l] & 0x000000FF] ^
|
936 |
+
$w[$round][$i];
|
937 |
+
$i++;
|
938 |
+
$j = ($j + 1) % $Nb;
|
939 |
+
$k = ($k + 1) % $Nb;
|
940 |
+
$l = ($l + 1) % $Nb;
|
941 |
+
}
|
942 |
+
|
943 |
+
for ($i = 0; $i < $Nb; $i++) {
|
944 |
+
$state[$i] = $temp[$i];
|
945 |
+
}
|
946 |
+
}
|
947 |
+
|
948 |
+
// subWord
|
949 |
+
for ($i = 0; $i < $Nb; $i++) {
|
950 |
+
$state[$i] = $this->_subWord($state[$i]);
|
951 |
+
}
|
952 |
+
|
953 |
+
// shiftRows + addRoundKey
|
954 |
+
$i = 0; // $c[0] == 0
|
955 |
+
$j = $c[1];
|
956 |
+
$k = $c[2];
|
957 |
+
$l = $c[3];
|
958 |
+
while ($i < $this->Nb) {
|
959 |
+
$temp[$i] = ($state[$i] & 0xFF000000) ^
|
960 |
+
($state[$j] & 0x00FF0000) ^
|
961 |
+
($state[$k] & 0x0000FF00) ^
|
962 |
+
($state[$l] & 0x000000FF) ^
|
963 |
+
$w[$Nr][$i];
|
964 |
+
$i++;
|
965 |
+
$j = ($j + 1) % $Nb;
|
966 |
+
$k = ($k + 1) % $Nb;
|
967 |
+
$l = ($l + 1) % $Nb;
|
968 |
+
}
|
969 |
+
$state = $temp;
|
970 |
+
|
971 |
+
array_unshift($state, 'N*');
|
972 |
+
|
973 |
+
return call_user_func_array('pack', $state);
|
974 |
+
}
|
975 |
+
|
976 |
+
/**
|
977 |
+
* Decrypts a block
|
978 |
+
*
|
979 |
+
* @access private
|
980 |
+
* @param String $in
|
981 |
+
* @return String
|
982 |
+
*/
|
983 |
+
function _decryptBlock($in)
|
984 |
+
{
|
985 |
+
$state = array();
|
986 |
+
$words = unpack('N*word', $in);
|
987 |
+
|
988 |
+
$num_states = count($state);
|
989 |
+
$dw = $this->dw;
|
990 |
+
$dt0 = $this->dt0;
|
991 |
+
$dt1 = $this->dt1;
|
992 |
+
$dt2 = $this->dt2;
|
993 |
+
$dt3 = $this->dt3;
|
994 |
+
$Nb = $this->Nb;
|
995 |
+
$Nr = $this->Nr;
|
996 |
+
$c = $this->c;
|
997 |
+
|
998 |
+
// addRoundKey
|
999 |
+
$i = 0;
|
1000 |
+
foreach ($words as $word) {
|
1001 |
+
$state[] = $word ^ $dw[$Nr][$i++];
|
1002 |
+
}
|
1003 |
+
|
1004 |
+
$temp = array();
|
1005 |
+
for ($round = $Nr - 1; $round > 0; $round--) {
|
1006 |
+
$i = 0; // $c[0] == 0
|
1007 |
+
$j = $Nb - $c[1];
|
1008 |
+
$k = $Nb - $c[2];
|
1009 |
+
$l = $Nb - $c[3];
|
1010 |
+
|
1011 |
+
while ($i < $Nb) {
|
1012 |
+
$temp[$i] = $dt0[$state[$i] & 0xFF000000] ^
|
1013 |
+
$dt1[$state[$j] & 0x00FF0000] ^
|
1014 |
+
$dt2[$state[$k] & 0x0000FF00] ^
|
1015 |
+
$dt3[$state[$l] & 0x000000FF] ^
|
1016 |
+
$dw[$round][$i];
|
1017 |
+
$i++;
|
1018 |
+
$j = ($j + 1) % $Nb;
|
1019 |
+
$k = ($k + 1) % $Nb;
|
1020 |
+
$l = ($l + 1) % $Nb;
|
1021 |
+
}
|
1022 |
+
|
1023 |
+
for ($i = 0; $i < $Nb; $i++) {
|
1024 |
+
$state[$i] = $temp[$i];
|
1025 |
+
}
|
1026 |
+
}
|
1027 |
+
|
1028 |
+
// invShiftRows + invSubWord + addRoundKey
|
1029 |
+
$i = 0; // $c[0] == 0
|
1030 |
+
$j = $Nb - $c[1];
|
1031 |
+
$k = $Nb - $c[2];
|
1032 |
+
$l = $Nb - $c[3];
|
1033 |
+
|
1034 |
+
while ($i < $Nb) {
|
1035 |
+
$temp[$i] = $dw[0][$i] ^
|
1036 |
+
$this->_invSubWord(($state[$i] & 0xFF000000) |
|
1037 |
+
($state[$j] & 0x00FF0000) |
|
1038 |
+
($state[$k] & 0x0000FF00) |
|
1039 |
+
($state[$l] & 0x000000FF));
|
1040 |
+
$i++;
|
1041 |
+
$j = ($j + 1) % $Nb;
|
1042 |
+
$k = ($k + 1) % $Nb;
|
1043 |
+
$l = ($l + 1) % $Nb;
|
1044 |
+
}
|
1045 |
+
|
1046 |
+
$state = $temp;
|
1047 |
+
|
1048 |
+
array_unshift($state, 'N*');
|
1049 |
+
|
1050 |
+
return call_user_func_array('pack', $state);
|
1051 |
+
}
|
1052 |
+
|
1053 |
+
/**
|
1054 |
+
* Setup Rijndael
|
1055 |
+
*
|
1056 |
+
* Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key
|
1057 |
+
* key schedule.
|
1058 |
+
*
|
1059 |
+
* @access private
|
1060 |
+
*/
|
1061 |
+
function _setup()
|
1062 |
+
{
|
1063 |
+
// Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field.
|
1064 |
+
// See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse
|
1065 |
+
static $rcon = array(0,
|
1066 |
+
0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
|
1067 |
+
0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000,
|
1068 |
+
0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000,
|
1069 |
+
0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000,
|
1070 |
+
0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000,
|
1071 |
+
0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000
|
1072 |
+
);
|
1073 |
+
|
1074 |
+
if (!$this->changed) {
|
1075 |
+
return;
|
1076 |
+
}
|
1077 |
+
|
1078 |
+
if (!$this->explicit_key_length) {
|
1079 |
+
// we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits
|
1080 |
+
$length = strlen($this->key) >> 2;
|
1081 |
+
if ($length > 8) {
|
1082 |
+
$length = 8;
|
1083 |
+
} else if ($length < 4) {
|
1084 |
+
$length = 4;
|
1085 |
+
}
|
1086 |
+
$this->Nk = $length;
|
1087 |
+
$this->key_size = $length << 2;
|
1088 |
+
}
|
1089 |
+
|
1090 |
+
$this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0));
|
1091 |
+
$this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0));
|
1092 |
+
|
1093 |
+
// see Rijndael-ammended.pdf#page=44
|
1094 |
+
$this->Nr = max($this->Nk, $this->Nb) + 6;
|
1095 |
+
|
1096 |
+
// shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44,
|
1097 |
+
// "Table 8: Shift offsets in Shiftrow for the alternative block lengths"
|
1098 |
+
// shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14,
|
1099 |
+
// "Table 2: Shift offsets for different block lengths"
|
1100 |
+
switch ($this->Nb) {
|
1101 |
+
case 4:
|
1102 |
+
case 5:
|
1103 |
+
case 6:
|
1104 |
+
$this->c = array(0, 1, 2, 3);
|
1105 |
+
break;
|
1106 |
+
case 7:
|
1107 |
+
$this->c = array(0, 1, 2, 4);
|
1108 |
+
break;
|
1109 |
+
case 8:
|
1110 |
+
$this->c = array(0, 1, 3, 4);
|
1111 |
+
}
|
1112 |
+
|
1113 |
+
$key = $this->key;
|
1114 |
+
|
1115 |
+
$w = array_values(unpack('N*words', $key));
|
1116 |
+
|
1117 |
+
$length = $this->Nb * ($this->Nr + 1);
|
1118 |
+
for ($i = $this->Nk; $i < $length; $i++) {
|
1119 |
+
$temp = $w[$i - 1];
|
1120 |
+
if ($i % $this->Nk == 0) {
|
1121 |
+
// according to <http://php.net/language.types.integer>, "the size of an integer is platform-dependent".
|
1122 |
+
// on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine,
|
1123 |
+
// 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and'
|
1124 |
+
// with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is.
|
1125 |
+
$temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord
|
1126 |
+
$temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk];
|
1127 |
+
} else if ($this->Nk > 6 && $i % $this->Nk == 4) {
|
1128 |
+
$temp = $this->_subWord($temp);
|
1129 |
+
}
|
1130 |
+
$w[$i] = $w[$i - $this->Nk] ^ $temp;
|
1131 |
+
}
|
1132 |
+
|
1133 |
+
// convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns
|
1134 |
+
// and generate the inverse key schedule. more specifically,
|
1135 |
+
// according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=23> (section 5.3.3),
|
1136 |
+
// "The key expansion for the Inverse Cipher is defined as follows:
|
1137 |
+
// 1. Apply the Key Expansion.
|
1138 |
+
// 2. Apply InvMixColumn to all Round Keys except the first and the last one."
|
1139 |
+
// also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher"
|
1140 |
+
$temp = array();
|
1141 |
+
for ($i = $row = $col = 0; $i < $length; $i++, $col++) {
|
1142 |
+
if ($col == $this->Nb) {
|
1143 |
+
if ($row == 0) {
|
1144 |
+
$this->dw[0] = $this->w[0];
|
1145 |
+
} else {
|
1146 |
+
// subWord + invMixColumn + invSubWord = invMixColumn
|
1147 |
+
$j = 0;
|
1148 |
+
while ($j < $this->Nb) {
|
1149 |
+
$dw = $this->_subWord($this->w[$row][$j]);
|
1150 |
+
$temp[$j] = $this->dt0[$dw & 0xFF000000] ^
|
1151 |
+
$this->dt1[$dw & 0x00FF0000] ^
|
1152 |
+
$this->dt2[$dw & 0x0000FF00] ^
|
1153 |
+
$this->dt3[$dw & 0x000000FF];
|
1154 |
+
$j++;
|
1155 |
+
}
|
1156 |
+
$this->dw[$row] = $temp;
|
1157 |
+
}
|
1158 |
+
|
1159 |
+
$col = 0;
|
1160 |
+
$row++;
|
1161 |
+
}
|
1162 |
+
$this->w[$row][$col] = $w[$i];
|
1163 |
+
}
|
1164 |
+
|
1165 |
+
$this->dw[$row] = $this->w[$row];
|
1166 |
+
|
1167 |
+
$this->changed = false;
|
1168 |
+
}
|
1169 |
+
|
1170 |
+
/**
|
1171 |
+
* Performs S-Box substitutions
|
1172 |
+
*
|
1173 |
+
* @access private
|
1174 |
+
*/
|
1175 |
+
function _subWord($word)
|
1176 |
+
{
|
1177 |
+
static $sbox0, $sbox1, $sbox2, $sbox3;
|
1178 |
+
|
1179 |
+
if (empty($sbox0)) {
|
1180 |
+
$sbox0 = array(
|
1181 |
+
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
|
1182 |
+
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
|
1183 |
+
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
|
1184 |
+
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
|
1185 |
+
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
|
1186 |
+
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
|
1187 |
+
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
|
1188 |
+
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
|
1189 |
+
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
|
1190 |
+
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
|
1191 |
+
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
|
1192 |
+
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
|
1193 |
+
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
|
1194 |
+
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
|
1195 |
+
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
|
1196 |
+
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
|
1197 |
+
);
|
1198 |
+
|
1199 |
+
$sbox1 = array();
|
1200 |
+
$sbox2 = array();
|
1201 |
+
$sbox3 = array();
|
1202 |
+
|
1203 |
+
for ($i = 0; $i < 256; $i++) {
|
1204 |
+
$sbox1[$i << 8] = $sbox0[$i] << 8;
|
1205 |
+
$sbox2[$i << 16] = $sbox0[$i] << 16;
|
1206 |
+
$sbox3[$i << 24] = $sbox0[$i] << 24;
|
1207 |
+
}
|
1208 |
+
}
|
1209 |
+
|
1210 |
+
return $sbox0[$word & 0x000000FF] |
|
1211 |
+
$sbox1[$word & 0x0000FF00] |
|
1212 |
+
$sbox2[$word & 0x00FF0000] |
|
1213 |
+
$sbox3[$word & 0xFF000000];
|
1214 |
+
}
|
1215 |
+
|
1216 |
+
/**
|
1217 |
+
* Performs inverse S-Box substitutions
|
1218 |
+
*
|
1219 |
+
* @access private
|
1220 |
+
*/
|
1221 |
+
function _invSubWord($word)
|
1222 |
+
{
|
1223 |
+
static $sbox0, $sbox1, $sbox2, $sbox3;
|
1224 |
+
|
1225 |
+
if (empty($sbox0)) {
|
1226 |
+
$sbox0 = array(
|
1227 |
+
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
|
1228 |
+
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
|
1229 |
+
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
|
1230 |
+
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
|
1231 |
+
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
|
1232 |
+
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
|
1233 |
+
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
|
1234 |
+
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
|
1235 |
+
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
|
1236 |
+
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
|
1237 |
+
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
|
1238 |
+
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
|
1239 |
+
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
|
1240 |
+
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
|
1241 |
+
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
|
1242 |
+
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
|
1243 |
+
);
|
1244 |
+
|
1245 |
+
$sbox1 = array();
|
1246 |
+
$sbox2 = array();
|
1247 |
+
$sbox3 = array();
|
1248 |
+
|
1249 |
+
for ($i = 0; $i < 256; $i++) {
|
1250 |
+
$sbox1[$i << 8] = $sbox0[$i] << 8;
|
1251 |
+
$sbox2[$i << 16] = $sbox0[$i] << 16;
|
1252 |
+
$sbox3[$i << 24] = $sbox0[$i] << 24;
|
1253 |
+
}
|
1254 |
+
}
|
1255 |
+
|
1256 |
+
return $sbox0[$word & 0x000000FF] |
|
1257 |
+
$sbox1[$word & 0x0000FF00] |
|
1258 |
+
$sbox2[$word & 0x00FF0000] |
|
1259 |
+
$sbox3[$word & 0xFF000000];
|
1260 |
+
}
|
1261 |
+
|
1262 |
+
/**
|
1263 |
+
* Pad "packets".
|
1264 |
+
*
|
1265 |
+
* Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple
|
1266 |
+
* of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to
|
1267 |
+
* pad the input so that it is of the proper length.
|
1268 |
+
*
|
1269 |
+
* Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH,
|
1270 |
+
* where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping
|
1271 |
+
* away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is
|
1272 |
+
* transmitted separately)
|
1273 |
+
*
|
1274 |
+
* @see Crypt_Rijndael::disablePadding()
|
1275 |
+
* @access public
|
1276 |
+
*/
|
1277 |
+
function enablePadding()
|
1278 |
+
{
|
1279 |
+
$this->padding = true;
|
1280 |
+
}
|
1281 |
+
|
1282 |
+
/**
|
1283 |
+
* Do not pad packets.
|
1284 |
+
*
|
1285 |
+
* @see Crypt_Rijndael::enablePadding()
|
1286 |
+
* @access public
|
1287 |
+
*/
|
1288 |
+
function disablePadding()
|
1289 |
+
{
|
1290 |
+
$this->padding = false;
|
1291 |
+
}
|
1292 |
+
|
1293 |
+
/**
|
1294 |
+
* Pads a string
|
1295 |
+
*
|
1296 |
+
* Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize.
|
1297 |
+
* $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to
|
1298 |
+
* chr($block_size - (strlen($text) % $block_size)
|
1299 |
+
*
|
1300 |
+
* If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless
|
1301 |
+
* and padding will, hence forth, be enabled.
|
1302 |
+
*
|
1303 |
+
* @see Crypt_Rijndael::_unpad()
|
1304 |
+
* @access private
|
1305 |
+
*/
|
1306 |
+
function _pad($text)
|
1307 |
+
{
|
1308 |
+
$length = strlen($text);
|
1309 |
+
|
1310 |
+
if (!$this->padding) {
|
1311 |
+
if ($length % $this->block_size == 0) {
|
1312 |
+
return $text;
|
1313 |
+
} else {
|
1314 |
+
user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})", E_USER_NOTICE);
|
1315 |
+
$this->padding = true;
|
1316 |
+
}
|
1317 |
+
}
|
1318 |
+
|
1319 |
+
$pad = $this->block_size - ($length % $this->block_size);
|
1320 |
+
|
1321 |
+
return str_pad($text, $length + $pad, chr($pad));
|
1322 |
+
}
|
1323 |
+
|
1324 |
+
/**
|
1325 |
+
* Unpads a string.
|
1326 |
+
*
|
1327 |
+
* If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong
|
1328 |
+
* and false will be returned.
|
1329 |
+
*
|
1330 |
+
* @see Crypt_Rijndael::_pad()
|
1331 |
+
* @access private
|
1332 |
+
*/
|
1333 |
+
function _unpad($text)
|
1334 |
+
{
|
1335 |
+
if (!$this->padding) {
|
1336 |
+
return $text;
|
1337 |
+
}
|
1338 |
+
|
1339 |
+
$length = ord($text[strlen($text) - 1]);
|
1340 |
+
|
1341 |
+
if (!$length || $length > $this->block_size) {
|
1342 |
+
return false;
|
1343 |
+
}
|
1344 |
+
|
1345 |
+
return substr($text, 0, -$length);
|
1346 |
+
}
|
1347 |
+
|
1348 |
+
/**
|
1349 |
+
* Treat consecutive "packets" as if they are a continuous buffer.
|
1350 |
+
*
|
1351 |
+
* Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets
|
1352 |
+
* will yield different outputs:
|
1353 |
+
*
|
1354 |
+
* <code>
|
1355 |
+
* echo $rijndael->encrypt(substr($plaintext, 0, 16));
|
1356 |
+
* echo $rijndael->encrypt(substr($plaintext, 16, 16));
|
1357 |
+
* </code>
|
1358 |
+
* <code>
|
1359 |
+
* echo $rijndael->encrypt($plaintext);
|
1360 |
+
* </code>
|
1361 |
+
*
|
1362 |
+
* The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
|
1363 |
+
* another, as demonstrated with the following:
|
1364 |
+
*
|
1365 |
+
* <code>
|
1366 |
+
* $rijndael->encrypt(substr($plaintext, 0, 16));
|
1367 |
+
* echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16)));
|
1368 |
+
* </code>
|
1369 |
+
* <code>
|
1370 |
+
* echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16)));
|
1371 |
+
* </code>
|
1372 |
+
*
|
1373 |
+
* With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different
|
1374 |
+
* outputs. The reason is due to the fact that the initialization vector's change after every encryption /
|
1375 |
+
* decryption round when the continuous buffer is enabled. When it's disabled, they remain constant.
|
1376 |
+
*
|
1377 |
+
* Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each
|
1378 |
+
* encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that
|
1379 |
+
* continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them),
|
1380 |
+
* however, they are also less intuitive and more likely to cause you problems.
|
1381 |
+
*
|
1382 |
+
* @see Crypt_Rijndael::disableContinuousBuffer()
|
1383 |
+
* @access public
|
1384 |
+
*/
|
1385 |
+
function enableContinuousBuffer()
|
1386 |
+
{
|
1387 |
+
$this->continuousBuffer = true;
|
1388 |
+
}
|
1389 |
+
|
1390 |
+
/**
|
1391 |
+
* Treat consecutive packets as if they are a discontinuous buffer.
|
1392 |
+
*
|
1393 |
+
* The default behavior.
|
1394 |
+
*
|
1395 |
+
* @see Crypt_Rijndael::enableContinuousBuffer()
|
1396 |
+
* @access public
|
1397 |
+
*/
|
1398 |
+
function disableContinuousBuffer()
|
1399 |
+
{
|
1400 |
+
$this->continuousBuffer = false;
|
1401 |
+
$this->encryptIV = $this->iv;
|
1402 |
+
$this->decryptIV = $this->iv;
|
1403 |
+
}
|
1404 |
+
|
1405 |
+
/**
|
1406 |
+
* String Shift
|
1407 |
+
*
|
1408 |
+
* Inspired by array_shift
|
1409 |
+
*
|
1410 |
+
* @param String $string
|
1411 |
+
* @param optional Integer $index
|
1412 |
+
* @return String
|
1413 |
+
* @access private
|
1414 |
+
*/
|
1415 |
+
function _string_shift(&$string, $index = 1)
|
1416 |
+
{
|
1417 |
+
$substr = substr($string, 0, $index);
|
1418 |
+
$string = substr($string, $index);
|
1419 |
+
return $substr;
|
1420 |
+
}
|
1421 |
+
}
|
1422 |
+
|
1423 |
+
// vim: ts=4:sw=4:et:
|
1424 |
+
// vim6: fdl=1:
|
trunk/includes/S3.php
ADDED
@@ -0,0 +1,2211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* $Id$
|
4 |
+
*
|
5 |
+
* Copyright (c) 2011, Donovan Schönknecht. All rights reserved.
|
6 |
+
* Portions copyright (c) 2012, David Anderson (http://www.simbahosting.co.uk). All rights reserved.
|
7 |
+
*
|
8 |
+
* Redistribution and use in source and binary forms, with or without
|
9 |
+
* modification, are permitted provided that the following conditions are met:
|
10 |
+
*
|
11 |
+
* - Redistributions of source code must retain the above copyright notice,
|
12 |
+
* this list of conditions and the following disclaimer.
|
13 |
+
* - Redistributions in binary form must reproduce the above copyright
|
14 |
+
* notice, this list of conditions and the following disclaimer in the
|
15 |
+
* documentation and/or other materials provided with the distribution.
|
16 |
+
*
|
17 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
28 |
+
*
|
29 |
+
* Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
|
30 |
+
*/
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Amazon S3 PHP class
|
34 |
+
*
|
35 |
+
* @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
|
36 |
+
* @version 0.5.0-dev
|
37 |
+
*/
|
38 |
+
class S3
|
39 |
+
{
|
40 |
+
// ACL flags
|
41 |
+
const ACL_PRIVATE = 'private';
|
42 |
+
const ACL_PUBLIC_READ = 'public-read';
|
43 |
+
const ACL_PUBLIC_READ_WRITE = 'public-read-write';
|
44 |
+
const ACL_AUTHENTICATED_READ = 'authenticated-read';
|
45 |
+
|
46 |
+
const STORAGE_CLASS_STANDARD = 'STANDARD';
|
47 |
+
const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
|
48 |
+
|
49 |
+
private static $__accessKey = null; // AWS Access key
|
50 |
+
private static $__secretKey = null; // AWS Secret key
|
51 |
+
private static $__sslKey = null;
|
52 |
+
|
53 |
+
public static $endpoint = 's3.amazonaws.com';
|
54 |
+
public static $proxy = null;
|
55 |
+
|
56 |
+
public static $useSSL = false;
|
57 |
+
public static $useSSLValidation = true;
|
58 |
+
public static $useExceptions = false;
|
59 |
+
|
60 |
+
// SSL CURL SSL options - only needed if you are experiencing problems with your OpenSSL configuration
|
61 |
+
public static $sslKey = null;
|
62 |
+
public static $sslCert = null;
|
63 |
+
public static $sslCACert = null;
|
64 |
+
|
65 |
+
private static $__signingKeyPairId = null; // AWS Key Pair ID
|
66 |
+
private static $__signingKeyResource = false; // Key resource, freeSigningKey() must be called to clear it from memory
|
67 |
+
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Constructor - if you're not using the class statically
|
71 |
+
*
|
72 |
+
* @param string $accessKey Access key
|
73 |
+
* @param string $secretKey Secret key
|
74 |
+
* @param boolean $useSSL Enable SSL
|
75 |
+
* @return void
|
76 |
+
*/
|
77 |
+
public function __construct($accessKey = null, $secretKey = null, $useSSL = false, $endpoint = 's3.amazonaws.com')
|
78 |
+
{
|
79 |
+
if ($accessKey !== null && $secretKey !== null)
|
80 |
+
self::setAuth($accessKey, $secretKey);
|
81 |
+
self::$useSSL = $useSSL;
|
82 |
+
self::$endpoint = $endpoint;
|
83 |
+
}
|
84 |
+
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Set the sertvice endpoint
|
88 |
+
*
|
89 |
+
* @param string $host Hostname
|
90 |
+
* @return void
|
91 |
+
*/
|
92 |
+
public function setEndpoint($host)
|
93 |
+
{
|
94 |
+
self::$endpoint = $host;
|
95 |
+
}
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Set AWS access key and secret key
|
99 |
+
*
|
100 |
+
* @param string $accessKey Access key
|
101 |
+
* @param string $secretKey Secret key
|
102 |
+
* @return void
|
103 |
+
*/
|
104 |
+
public static function setAuth($accessKey, $secretKey)
|
105 |
+
{
|
106 |
+
self::$__accessKey = $accessKey;
|
107 |
+
self::$__secretKey = $secretKey;
|
108 |
+
}
|
109 |
+
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Check if AWS keys have been set
|
113 |
+
*
|
114 |
+
* @return boolean
|
115 |
+
*/
|
116 |
+
public static function hasAuth() {
|
117 |
+
return (self::$__accessKey !== null && self::$__secretKey !== null);
|
118 |
+
}
|
119 |
+
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Set SSL on or off
|
123 |
+
*
|
124 |
+
* @param boolean $enabled SSL enabled
|
125 |
+
* @param boolean $validate SSL certificate validation
|
126 |
+
* @return void
|
127 |
+
*/
|
128 |
+
public static function setSSL($enabled, $validate = true)
|
129 |
+
{
|
130 |
+
self::$useSSL = $enabled;
|
131 |
+
self::$useSSLValidation = $validate;
|
132 |
+
}
|
133 |
+
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Set SSL client certificates (experimental)
|
137 |
+
*
|
138 |
+
* @param string $sslCert SSL client certificate
|
139 |
+
* @param string $sslKey SSL client key
|
140 |
+
* @param string $sslCACert SSL CA cert (only required if you are having problems with your system CA cert)
|
141 |
+
* @return void
|
142 |
+
*/
|
143 |
+
public static function setSSLAuth($sslCert = null, $sslKey = null, $sslCACert = null)
|
144 |
+
{
|
145 |
+
self::$sslCert = $sslCert;
|
146 |
+
self::$sslKey = $sslKey;
|
147 |
+
self::$sslCACert = $sslCACert;
|
148 |
+
}
|
149 |
+
|
150 |
+
|
151 |
+
/**
|
152 |
+
* Set proxy information
|
153 |
+
*
|
154 |
+
* @param string $host Proxy hostname and port (localhost:1234)
|
155 |
+
* @param string $user Proxy username
|
156 |
+
* @param string $pass Proxy password
|
157 |
+
* @param constant $type CURL proxy type
|
158 |
+
* @return void
|
159 |
+
*/
|
160 |
+
public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
|
161 |
+
{
|
162 |
+
self::$proxy = array('host' => $host, 'type' => $type, 'user' => null, 'pass' => 'null');
|
163 |
+
}
|
164 |
+
|
165 |
+
|
166 |
+
/**
|
167 |
+
* Set the error mode to exceptions
|
168 |
+
*
|
169 |
+
* @param boolean $enabled Enable exceptions
|
170 |
+
* @return void
|
171 |
+
*/
|
172 |
+
public static function setExceptions($enabled = true)
|
173 |
+
{
|
174 |
+
self::$useExceptions = $enabled;
|
175 |
+
}
|
176 |
+
|
177 |
+
|
178 |
+
/**
|
179 |
+
* Set signing key
|
180 |
+
*
|
181 |
+
* @param string $keyPairId AWS Key Pair ID
|
182 |
+
* @param string $signingKey Private Key
|
183 |
+
* @param boolean $isFile Load private key from file, set to false to load string
|
184 |
+
* @return boolean
|
185 |
+
*/
|
186 |
+
public static function setSigningKey($keyPairId, $signingKey, $isFile = true)
|
187 |
+
{
|
188 |
+
self::$__signingKeyPairId = $keyPairId;
|
189 |
+
if ((self::$__signingKeyResource = openssl_pkey_get_private($isFile ?
|
190 |
+
file_get_contents($signingKey) : $signingKey)) !== false) return true;
|
191 |
+
self::__triggerError('S3::setSigningKey(): Unable to open load private key: '.$signingKey, __FILE__, __LINE__);
|
192 |
+
return false;
|
193 |
+
}
|
194 |
+
|
195 |
+
|
196 |
+
/**
|
197 |
+
* Free signing key from memory, MUST be called if you are using setSigningKey()
|
198 |
+
*
|
199 |
+
* @return void
|
200 |
+
*/
|
201 |
+
public static function freeSigningKey()
|
202 |
+
{
|
203 |
+
if (self::$__signingKeyResource !== false)
|
204 |
+
openssl_free_key(self::$__signingKeyResource);
|
205 |
+
}
|
206 |
+
|
207 |
+
|
208 |
+
/**
|
209 |
+
* Internal error handler
|
210 |
+
*
|
211 |
+
* @internal Internal error handler
|
212 |
+
* @param string $message Error message
|
213 |
+
* @param string $file Filename
|
214 |
+
* @param integer $line Line number
|
215 |
+
* @param integer $code Error code
|
216 |
+
* @return void
|
217 |
+
*/
|
218 |
+
private static function __triggerError($message, $file, $line, $code = 0)
|
219 |
+
{
|
220 |
+
if (self::$useExceptions)
|
221 |
+
throw new S3Exception($message, $file, $line, $code);
|
222 |
+
else
|
223 |
+
trigger_error($message, E_USER_WARNING);
|
224 |
+
}
|
225 |
+
|
226 |
+
|
227 |
+
/**
|
228 |
+
* Get a list of buckets
|
229 |
+
*
|
230 |
+
* @param boolean $detailed Returns detailed bucket list when true
|
231 |
+
* @return array | false
|
232 |
+
*/
|
233 |
+
public static function listBuckets($detailed = false)
|
234 |
+
{
|
235 |
+
$rest = new S3Request('GET', '', '', self::$endpoint);
|
236 |
+
$rest = $rest->getResponse();
|
237 |
+
if ($rest->error === false && $rest->code !== 200)
|
238 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
239 |
+
if ($rest->error !== false)
|
240 |
+
{
|
241 |
+
self::__triggerError(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'],
|
242 |
+
$rest->error['message']), __FILE__, __LINE__);
|
243 |
+
return false;
|
244 |
+
}
|
245 |
+
$results = array();
|
246 |
+
if (!isset($rest->body->Buckets)) return $results;
|
247 |
+
|
248 |
+
if ($detailed)
|
249 |
+
{
|
250 |
+
if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
|
251 |
+
$results['owner'] = array(
|
252 |
+
'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
|
253 |
+
);
|
254 |
+
$results['buckets'] = array();
|
255 |
+
foreach ($rest->body->Buckets->Bucket as $b)
|
256 |
+
$results['buckets'][] = array(
|
257 |
+
'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
|
258 |
+
);
|
259 |
+
} else
|
260 |
+
foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
|
261 |
+
|
262 |
+
return $results;
|
263 |
+
}
|
264 |
+
|
265 |
+
|
266 |
+
/*
|
267 |
+
* Get contents for a bucket
|
268 |
+
*
|
269 |
+
* If maxKeys is null this method will loop through truncated result sets
|
270 |
+
*
|
271 |
+
* @param string $bucket Bucket name
|
272 |
+
* @param string $prefix Prefix
|
273 |
+
* @param string $marker Marker (last file listed)
|
274 |
+
* @param string $maxKeys Max keys (maximum number of keys to return)
|
275 |
+
* @param string $delimiter Delimiter
|
276 |
+
* @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
|
277 |
+
* @return array | false
|
278 |
+
*/
|
279 |
+
public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false)
|
280 |
+
{
|
281 |
+
$rest = new S3Request('GET', $bucket, '', self::$endpoint);
|
282 |
+
if ($maxKeys == 0) $maxKeys = null;
|
283 |
+
if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
|
284 |
+
if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
|
285 |
+
if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
|
286 |
+
if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
|
287 |
+
$response = $rest->getResponse();
|
288 |
+
if ($response->error === false && $response->code !== 200)
|
289 |
+
$response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
|
290 |
+
if ($response->error !== false)
|
291 |
+
{
|
292 |
+
self::__triggerError(sprintf("S3::getBucket(): [%s] %s",
|
293 |
+
$response->error['code'], $response->error['message']), __FILE__, __LINE__);
|
294 |
+
return false;
|
295 |
+
}
|
296 |
+
|
297 |
+
$results = array();
|
298 |
+
|
299 |
+
$nextMarker = null;
|
300 |
+
if (isset($response->body, $response->body->Contents))
|
301 |
+
foreach ($response->body->Contents as $c)
|
302 |
+
{
|
303 |
+
$results[(string)$c->Key] = array(
|
304 |
+
'name' => (string)$c->Key,
|
305 |
+
'time' => strtotime((string)$c->LastModified),
|
306 |
+
'size' => (int)$c->Size,
|
307 |
+
'hash' => substr((string)$c->ETag, 1, -1)
|
308 |
+
);
|
309 |
+
$nextMarker = (string)$c->Key;
|
310 |
+
}
|
311 |
+
|
312 |
+
if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
|
313 |
+
foreach ($response->body->CommonPrefixes as $c)
|
314 |
+
$results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
|
315 |
+
|
316 |
+
if (isset($response->body, $response->body->IsTruncated) &&
|
317 |
+
(string)$response->body->IsTruncated == 'false') return $results;
|
318 |
+
|
319 |
+
if (isset($response->body, $response->body->NextMarker))
|
320 |
+
$nextMarker = (string)$response->body->NextMarker;
|
321 |
+
|
322 |
+
// Loop through truncated results if maxKeys isn't specified
|
323 |
+
if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
|
324 |
+
do
|
325 |
+
{
|
326 |
+
$rest = new S3Request('GET', $bucket, '', self::$endpoint);
|
327 |
+
if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
|
328 |
+
$rest->setParameter('marker', $nextMarker);
|
329 |
+
if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
|
330 |
+
|
331 |
+
if (($response = $rest->getResponse()) == false || $response->code !== 200) break;
|
332 |
+
|
333 |
+
if (isset($response->body, $response->body->Contents))
|
334 |
+
foreach ($response->body->Contents as $c)
|
335 |
+
{
|
336 |
+
$results[(string)$c->Key] = array(
|
337 |
+
'name' => (string)$c->Key,
|
338 |
+
'time' => strtotime((string)$c->LastModified),
|
339 |
+
'size' => (int)$c->Size,
|
340 |
+
'hash' => substr((string)$c->ETag, 1, -1)
|
341 |
+
);
|
342 |
+
$nextMarker = (string)$c->Key;
|
343 |
+
}
|
344 |
+
|
345 |
+
if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
|
346 |
+
foreach ($response->body->CommonPrefixes as $c)
|
347 |
+
$results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
|
348 |
+
|
349 |
+
if (isset($response->body, $response->body->NextMarker))
|
350 |
+
$nextMarker = (string)$response->body->NextMarker;
|
351 |
+
|
352 |
+
} while ($response !== false && (string)$response->body->IsTruncated == 'true');
|
353 |
+
|
354 |
+
return $results;
|
355 |
+
}
|
356 |
+
|
357 |
+
|
358 |
+
/**
|
359 |
+
* Put a bucket
|
360 |
+
*
|
361 |
+
* @param string $bucket Bucket name
|
362 |
+
* @param constant $acl ACL flag
|
363 |
+
* @param string $location Set as "EU" to create buckets hosted in Europe
|
364 |
+
* @return boolean
|
365 |
+
*/
|
366 |
+
public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false)
|
367 |
+
{
|
368 |
+
$rest = new S3Request('PUT', $bucket, '', self::$endpoint);
|
369 |
+
$rest->setAmzHeader('x-amz-acl', $acl);
|
370 |
+
|
371 |
+
if ($location !== false)
|
372 |
+
{
|
373 |
+
$dom = new DOMDocument;
|
374 |
+
$createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
|
375 |
+
$locationConstraint = $dom->createElement('LocationConstraint', $location);
|
376 |
+
$createBucketConfiguration->appendChild($locationConstraint);
|
377 |
+
$dom->appendChild($createBucketConfiguration);
|
378 |
+
$rest->data = $dom->saveXML();
|
379 |
+
$rest->size = strlen($rest->data);
|
380 |
+
$rest->setHeader('Content-Type', 'application/xml');
|
381 |
+
}
|
382 |
+
$rest = $rest->getResponse();
|
383 |
+
|
384 |
+
if ($rest->error === false && $rest->code !== 200)
|
385 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
386 |
+
if ($rest->error !== false)
|
387 |
+
{
|
388 |
+
self::__triggerError(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
|
389 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
390 |
+
return false;
|
391 |
+
}
|
392 |
+
return true;
|
393 |
+
}
|
394 |
+
|
395 |
+
|
396 |
+
/**
|
397 |
+
* Delete an empty bucket
|
398 |
+
*
|
399 |
+
* @param string $bucket Bucket name
|
400 |
+
* @return boolean
|
401 |
+
*/
|
402 |
+
public static function deleteBucket($bucket)
|
403 |
+
{
|
404 |
+
$rest = new S3Request('DELETE', $bucket, '', self::$endpoint);
|
405 |
+
$rest = $rest->getResponse();
|
406 |
+
if ($rest->error === false && $rest->code !== 204)
|
407 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
408 |
+
if ($rest->error !== false)
|
409 |
+
{
|
410 |
+
self::__triggerError(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
|
411 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
412 |
+
return false;
|
413 |
+
}
|
414 |
+
return true;
|
415 |
+
}
|
416 |
+
|
417 |
+
|
418 |
+
/**
|
419 |
+
* Create input info array for putObject()
|
420 |
+
*
|
421 |
+
* @param string $file Input file
|
422 |
+
* @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
|
423 |
+
* @return array | false
|
424 |
+
*/
|
425 |
+
public static function inputFile($file, $md5sum = true)
|
426 |
+
{
|
427 |
+
if (!file_exists($file) || !is_file($file) || !is_readable($file))
|
428 |
+
{
|
429 |
+
self::__triggerError('S3::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
|
430 |
+
return false;
|
431 |
+
}
|
432 |
+
return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
|
433 |
+
(is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
|
434 |
+
}
|
435 |
+
|
436 |
+
|
437 |
+
/**
|
438 |
+
* Create input array info for putObject() with a resource
|
439 |
+
*
|
440 |
+
* @param string $resource Input resource to read from
|
441 |
+
* @param integer $bufferSize Input byte size
|
442 |
+
* @param string $md5sum MD5 hash to send (optional)
|
443 |
+
* @return array | false
|
444 |
+
*/
|
445 |
+
public static function inputResource(&$resource, $bufferSize, $md5sum = '')
|
446 |
+
{
|
447 |
+
if (!is_resource($resource) || $bufferSize < 0)
|
448 |
+
{
|
449 |
+
self::__triggerError('S3::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
|
450 |
+
return false;
|
451 |
+
}
|
452 |
+
$input = array('size' => $bufferSize, 'md5sum' => $md5sum);
|
453 |
+
$input['fp'] =& $resource;
|
454 |
+
return $input;
|
455 |
+
}
|
456 |
+
|
457 |
+
/**
|
458 |
+
* Initiate a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadInitiate.html)
|
459 |
+
*
|
460 |
+
* @param string $bucket Bucket name
|
461 |
+
* @param string $uri Object URI
|
462 |
+
* @param constant $acl ACL constant
|
463 |
+
* @param array $metaHeaders Array of x-amz-meta-* headers
|
464 |
+
* @param array $requestHeaders Array of request headers or content type as a string
|
465 |
+
* @param constant $storageClass Storage class constant
|
466 |
+
* @return string | false
|
467 |
+
*/
|
468 |
+
|
469 |
+
public static function initiateMultipartUpload ($bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
|
470 |
+
{
|
471 |
+
|
472 |
+
$rest = new S3Request('POST', $bucket, $uri, self::$endpoint);
|
473 |
+
$rest->setParameter('uploads','');
|
474 |
+
|
475 |
+
// Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
|
476 |
+
if (is_array($requestHeaders))
|
477 |
+
foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
|
478 |
+
|
479 |
+
// Set storage class
|
480 |
+
if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
|
481 |
+
$rest->setAmzHeader('x-amz-storage-class', $storageClass);
|
482 |
+
|
483 |
+
// Set ACL headers
|
484 |
+
$rest->setAmzHeader('x-amz-acl', $acl);
|
485 |
+
foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
|
486 |
+
|
487 |
+
// Carry out the HTTP operation
|
488 |
+
$rest->getResponse();
|
489 |
+
|
490 |
+
if ($rest->response->error === false && $rest->response->code !== 200)
|
491 |
+
$rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
|
492 |
+
if ($rest->response->error !== false)
|
493 |
+
{
|
494 |
+
self::__triggerError(sprintf("S3::initiateMultipartUpload(): [%s] %s",
|
495 |
+
$rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
|
496 |
+
return false;
|
497 |
+
} elseif (isset($rest->response->body))
|
498 |
+
{
|
499 |
+
$body = new SimpleXMLElement($rest->response->body);
|
500 |
+
return (string) $body->UploadId;
|
501 |
+
}
|
502 |
+
|
503 |
+
// It is a programming error if we reach this line
|
504 |
+
return false;
|
505 |
+
|
506 |
+
}
|
507 |
+
|
508 |
+
/**
|
509 |
+
/* Upload a part of a multi-part set (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadUploadPart.html)
|
510 |
+
* The chunk is read into memory, so make sure that you have enough (or patch this function to work another way!)
|
511 |
+
*
|
512 |
+
* @param string $bucket Bucket name
|
513 |
+
* @param string $uri Object URI
|
514 |
+
* @param string $uploadId uploadId returned previously from initiateMultipartUpload
|
515 |
+
* @param integer $partNumber sequential part number to upload
|
516 |
+
* @param string $filePath file to upload content from
|
517 |
+
* @param integer $partSize number of bytes in each part (though final part may have fewer) - pass the same value each time (for this particular upload) - default 5Mb (which is Amazon's minimum)
|
518 |
+
* @return string (ETag) | false
|
519 |
+
*/
|
520 |
+
|
521 |
+
public static function uploadPart ($bucket, $uri, $uploadId, $filePath, $partNumber, $partSize = 5242880)
|
522 |
+
{
|
523 |
+
|
524 |
+
$rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
|
525 |
+
$rest->setParameter('partNumber', $partNumber);
|
526 |
+
$rest->setParameter('uploadId', $uploadId);
|
527 |
+
|
528 |
+
// Where to begin
|
529 |
+
$fileOffset = ($partNumber - 1 ) * $partSize;
|
530 |
+
|
531 |
+
// Download the smallest of the remaining bytes and the part size
|
532 |
+
$fileBytes = min(filesize($filePath) - $fileOffset, $partSize);
|
533 |
+
if ($fileBytes < 0) $fileBytes = 0;
|
534 |
+
|
535 |
+
$rest->setHeader('Content-Type', 'application/octet-stream');
|
536 |
+
$rest->data = "";
|
537 |
+
|
538 |
+
$handle = fopen($filePath, "rb");
|
539 |
+
if ($fileOffset >0) fseek($handle, $fileOffset);
|
540 |
+
$bytes_read = 0;
|
541 |
+
|
542 |
+
while ($fileBytes >0) {
|
543 |
+
$read = fread($handle, min($fileBytes, 32768));
|
544 |
+
$fileBytes = $fileBytes - strlen($read);
|
545 |
+
$bytes_read += strlen($read);
|
546 |
+
$rest->data = $rest->data . $read;
|
547 |
+
}
|
548 |
+
|
549 |
+
fclose($handle);
|
550 |
+
|
551 |
+
$rest->setHeader('Content-MD5', base64_encode(md5($rest->data, true)));
|
552 |
+
$rest->size = $bytes_read;
|
553 |
+
|
554 |
+
$rest = $rest->getResponse();
|
555 |
+
if ($rest->error === false && $rest->code !== 200)
|
556 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
557 |
+
if ($rest->error !== false)
|
558 |
+
{
|
559 |
+
self::__triggerError(sprintf("S3::uploadPart(): [%s] %s",
|
560 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
561 |
+
return false;
|
562 |
+
}
|
563 |
+
return $rest->headers['hash'];
|
564 |
+
|
565 |
+
}
|
566 |
+
|
567 |
+
/**
|
568 |
+
* Complete a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadComplete.html)
|
569 |
+
*
|
570 |
+
* @param string $bucket Bucket name
|
571 |
+
* @param string $uri Object URI
|
572 |
+
* @param string $uploadId uploadId returned previously from initiateMultipartUpload
|
573 |
+
* @param array $parts an ordered list of eTags of previously uploaded parts from uploadPart
|
574 |
+
* @return boolean
|
575 |
+
*/
|
576 |
+
|
577 |
+
public static function completeMultipartUpload ($bucket, $uri, $uploadId, $parts)
|
578 |
+
{
|
579 |
+
$rest = new S3Request('POST', $bucket, $uri, self::$endpoint);
|
580 |
+
$rest->setParameter('uploadId', $uploadId);
|
581 |
+
|
582 |
+
$xml = "<CompleteMultipartUpload>\n";
|
583 |
+
$partno = 1;
|
584 |
+
foreach ($parts as $etag) {
|
585 |
+
$xml .= "<Part><PartNumber>$partno</PartNumber><ETag>$etag</ETag></Part>\n";
|
586 |
+
$partno++;
|
587 |
+
}
|
588 |
+
$xml .= "</CompleteMultipartUpload>";
|
589 |
+
|
590 |
+
$rest->data = $xml;
|
591 |
+
$rest->size = strlen($rest->data);
|
592 |
+
$rest->setHeader('Content-Type', 'application/xml');
|
593 |
+
|
594 |
+
$rest = $rest->getResponse();
|
595 |
+
if ($rest->error === false && $rest->code !== 200)
|
596 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
597 |
+
if ($rest->error !== false)
|
598 |
+
{
|
599 |
+
self::__triggerError(sprintf("S3::completeMultipartUpload(): [%s] %s",
|
600 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
601 |
+
return false;
|
602 |
+
}
|
603 |
+
return true;
|
604 |
+
|
605 |
+
}
|
606 |
+
|
607 |
+
/**
|
608 |
+
* Abort a multi-part upload (http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadAbort.html)
|
609 |
+
*
|
610 |
+
* @param string $bucket Bucket name
|
611 |
+
* @param string $uri Object URI
|
612 |
+
* @param string $uploadId uploadId returned previously from initiateMultipartUpload
|
613 |
+
* @return boolean
|
614 |
+
*/
|
615 |
+
|
616 |
+
public static function abortMultipartUpload ($bucket, $uri, $uploadId)
|
617 |
+
{
|
618 |
+
$rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
|
619 |
+
$rest->setParameter('uploadId', $uploadId);
|
620 |
+
$rest = $rest->getResponse();
|
621 |
+
if ($rest->error === false && $rest->code !== 204)
|
622 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
623 |
+
if ($rest->error !== false)
|
624 |
+
{
|
625 |
+
self::__triggerError(sprintf("S3::abortMultipartUpload(): [%s] %s",
|
626 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
627 |
+
return false;
|
628 |
+
}
|
629 |
+
return true;
|
630 |
+
}
|
631 |
+
|
632 |
+
/**
|
633 |
+
* Put an object
|
634 |
+
*
|
635 |
+
* @param mixed $input Input data
|
636 |
+
* @param string $bucket Bucket name
|
637 |
+
* @param string $uri Object URI
|
638 |
+
* @param constant $acl ACL constant
|
639 |
+
* @param array $metaHeaders Array of x-amz-meta-* headers
|
640 |
+
* @param array $requestHeaders Array of request headers or content type as a string
|
641 |
+
* @param constant $storageClass Storage class constant
|
642 |
+
* @return boolean
|
643 |
+
*/
|
644 |
+
public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
|
645 |
+
{
|
646 |
+
if ($input === false) return false;
|
647 |
+
$rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
|
648 |
+
|
649 |
+
if (!is_array($input)) $input = array(
|
650 |
+
'data' => $input, 'size' => strlen($input),
|
651 |
+
'md5sum' => base64_encode(md5($input, true))
|
652 |
+
);
|
653 |
+
|
654 |
+
// Data
|
655 |
+
if (isset($input['fp']))
|
656 |
+
$rest->fp =& $input['fp'];
|
657 |
+
elseif (isset($input['file']))
|
658 |
+
$rest->fp = @fopen($input['file'], 'rb');
|
659 |
+
elseif (isset($input['data']))
|
660 |
+
$rest->data = $input['data'];
|
661 |
+
|
662 |
+
// Content-Length (required)
|
663 |
+
if (isset($input['size']) && $input['size'] >= 0)
|
664 |
+
$rest->size = $input['size'];
|
665 |
+
else {
|
666 |
+
if (isset($input['file']))
|
667 |
+
$rest->size = filesize($input['file']);
|
668 |
+
elseif (isset($input['data']))
|
669 |
+
$rest->size = strlen($input['data']);
|
670 |
+
}
|
671 |
+
|
672 |
+
// Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
|
673 |
+
if (is_array($requestHeaders))
|
674 |
+
foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
|
675 |
+
elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
|
676 |
+
$input['type'] = $requestHeaders;
|
677 |
+
|
678 |
+
// Content-Type
|
679 |
+
if (!isset($input['type']))
|
680 |
+
{
|
681 |
+
if (isset($requestHeaders['Content-Type']))
|
682 |
+
$input['type'] =& $requestHeaders['Content-Type'];
|
683 |
+
elseif (isset($input['file']))
|
684 |
+
$input['type'] = self::__getMimeType($input['file']);
|
685 |
+
else
|
686 |
+
$input['type'] = 'application/octet-stream';
|
687 |
+
}
|
688 |
+
|
689 |
+
if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
|
690 |
+
$rest->setAmzHeader('x-amz-storage-class', $storageClass);
|
691 |
+
|
692 |
+
// We need to post with Content-Length and Content-Type, MD5 is optional
|
693 |
+
if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
|
694 |
+
{
|
695 |
+
$rest->setHeader('Content-Type', $input['type']);
|
696 |
+
if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
|
697 |
+
|
698 |
+
$rest->setAmzHeader('x-amz-acl', $acl);
|
699 |
+
foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
|
700 |
+
$rest->getResponse();
|
701 |
+
} else
|
702 |
+
$rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
|
703 |
+
|
704 |
+
if ($rest->response->error === false && $rest->response->code !== 200)
|
705 |
+
$rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
|
706 |
+
if ($rest->response->error !== false)
|
707 |
+
{
|
708 |
+
self::__triggerError(sprintf("S3::putObject(): [%s] %s",
|
709 |
+
$rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
|
710 |
+
return false;
|
711 |
+
}
|
712 |
+
return true;
|
713 |
+
}
|
714 |
+
|
715 |
+
|
716 |
+
/**
|
717 |
+
* Put an object from a file (legacy function)
|
718 |
+
*
|
719 |
+
* @param string $file Input file path
|
720 |
+
* @param string $bucket Bucket name
|
721 |
+
* @param string $uri Object URI
|
722 |
+
* @param constant $acl ACL constant
|
723 |
+
* @param array $metaHeaders Array of x-amz-meta-* headers
|
724 |
+
* @param string $contentType Content type
|
725 |
+
* @return boolean
|
726 |
+
*/
|
727 |
+
public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null)
|
728 |
+
{
|
729 |
+
return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
|
730 |
+
}
|
731 |
+
|
732 |
+
|
733 |
+
/**
|
734 |
+
* Put an object from a string (legacy function)
|
735 |
+
*
|
736 |
+
* @param string $string Input data
|
737 |
+
* @param string $bucket Bucket name
|
738 |
+
* @param string $uri Object URI
|
739 |
+
* @param constant $acl ACL constant
|
740 |
+
* @param array $metaHeaders Array of x-amz-meta-* headers
|
741 |
+
* @param string $contentType Content type
|
742 |
+
* @return boolean
|
743 |
+
*/
|
744 |
+
public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain')
|
745 |
+
{
|
746 |
+
return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
|
747 |
+
}
|
748 |
+
|
749 |
+
|
750 |
+
/**
|
751 |
+
* Get an object
|
752 |
+
*
|
753 |
+
* @param string $bucket Bucket name
|
754 |
+
* @param string $uri Object URI
|
755 |
+
* @param mixed $saveTo Filename or resource to write to
|
756 |
+
* @return mixed
|
757 |
+
*/
|
758 |
+
public static function getObject($bucket, $uri, $saveTo = false)
|
759 |
+
{
|
760 |
+
$rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
|
761 |
+
if ($saveTo !== false)
|
762 |
+
{
|
763 |
+
if (is_resource($saveTo))
|
764 |
+
$rest->fp =& $saveTo;
|
765 |
+
else
|
766 |
+
if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
|
767 |
+
$rest->file = realpath($saveTo);
|
768 |
+
else
|
769 |
+
$rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
|
770 |
+
}
|
771 |
+
if ($rest->response->error === false) $rest->getResponse();
|
772 |
+
|
773 |
+
if ($rest->response->error === false && $rest->response->code !== 200)
|
774 |
+
$rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
|
775 |
+
if ($rest->response->error !== false)
|
776 |
+
{
|
777 |
+
self::__triggerError(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
|
778 |
+
$rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
|
779 |
+
return false;
|
780 |
+
}
|
781 |
+
return $rest->response;
|
782 |
+
}
|
783 |
+
|
784 |
+
|
785 |
+
/**
|
786 |
+
* Get object information
|
787 |
+
*
|
788 |
+
* @param string $bucket Bucket name
|
789 |
+
* @param string $uri Object URI
|
790 |
+
* @param boolean $returnInfo Return response information
|
791 |
+
* @return mixed | false
|
792 |
+
*/
|
793 |
+
public static function getObjectInfo($bucket, $uri, $returnInfo = true)
|
794 |
+
{
|
795 |
+
$rest = new S3Request('HEAD', $bucket, $uri, self::$endpoint);
|
796 |
+
$rest = $rest->getResponse();
|
797 |
+
if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
|
798 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
799 |
+
if ($rest->error !== false)
|
800 |
+
{
|
801 |
+
self::__triggerError(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
|
802 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
803 |
+
return false;
|
804 |
+
}
|
805 |
+
return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
|
806 |
+
}
|
807 |
+
|
808 |
+
|
809 |
+
/**
|
810 |
+
* Copy an object
|
811 |
+
*
|
812 |
+
* @param string $bucket Source bucket name
|
813 |
+
* @param string $uri Source object URI
|
814 |
+
* @param string $bucket Destination bucket name
|
815 |
+
* @param string $uri Destination object URI
|
816 |
+
* @param constant $acl ACL constant
|
817 |
+
* @param array $metaHeaders Optional array of x-amz-meta-* headers
|
818 |
+
* @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
|
819 |
+
* @param constant $storageClass Storage class constant
|
820 |
+
* @return mixed | false
|
821 |
+
*/
|
822 |
+
public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array(), $storageClass = self::STORAGE_CLASS_STANDARD)
|
823 |
+
{
|
824 |
+
$rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
|
825 |
+
$rest->setHeader('Content-Length', 0);
|
826 |
+
foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
|
827 |
+
foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
|
828 |
+
if ($storageClass !== self::STORAGE_CLASS_STANDARD) // Storage class
|
829 |
+
$rest->setAmzHeader('x-amz-storage-class', $storageClass);
|
830 |
+
$rest->setAmzHeader('x-amz-acl', $acl);
|
831 |
+
$rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
|
832 |
+
if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
|
833 |
+
$rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
|
834 |
+
|
835 |
+
$rest = $rest->getResponse();
|
836 |
+
if ($rest->error === false && $rest->code !== 200)
|
837 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
838 |
+
if ($rest->error !== false)
|
839 |
+
{
|
840 |
+
self::__triggerError(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
|
841 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
842 |
+
return false;
|
843 |
+
}
|
844 |
+
return isset($rest->body->LastModified, $rest->body->ETag) ? array(
|
845 |
+
'time' => strtotime((string)$rest->body->LastModified),
|
846 |
+
'hash' => substr((string)$rest->body->ETag, 1, -1)
|
847 |
+
) : false;
|
848 |
+
}
|
849 |
+
|
850 |
+
|
851 |
+
/**
|
852 |
+
* Set logging for a bucket
|
853 |
+
*
|
854 |
+
* @param string $bucket Bucket name
|
855 |
+
* @param string $targetBucket Target bucket (where logs are stored)
|
856 |
+
* @param string $targetPrefix Log prefix (e,g; domain.com-)
|
857 |
+
* @return boolean
|
858 |
+
*/
|
859 |
+
public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null)
|
860 |
+
{
|
861 |
+
// The S3 log delivery group has to be added to the target bucket's ACP
|
862 |
+
if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false)
|
863 |
+
{
|
864 |
+
// Only add permissions to the target bucket when they do not exist
|
865 |
+
$aclWriteSet = false;
|
866 |
+
$aclReadSet = false;
|
867 |
+
foreach ($acp['acl'] as $acl)
|
868 |
+
if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery')
|
869 |
+
{
|
870 |
+
if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
|
871 |
+
elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
|
872 |
+
}
|
873 |
+
if (!$aclWriteSet) $acp['acl'][] = array(
|
874 |
+
'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
|
875 |
+
);
|
876 |
+
if (!$aclReadSet) $acp['acl'][] = array(
|
877 |
+
'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
|
878 |
+
);
|
879 |
+
if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
|
880 |
+
}
|
881 |
+
|
882 |
+
$dom = new DOMDocument;
|
883 |
+
$bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
|
884 |
+
$bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
|
885 |
+
if ($targetBucket !== null)
|
886 |
+
{
|
887 |
+
if ($targetPrefix == null) $targetPrefix = $bucket . '-';
|
888 |
+
$loggingEnabled = $dom->createElement('LoggingEnabled');
|
889 |
+
$loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
|
890 |
+
$loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
|
891 |
+
// TODO: Add TargetGrants?
|
892 |
+
$bucketLoggingStatus->appendChild($loggingEnabled);
|
893 |
+
}
|
894 |
+
$dom->appendChild($bucketLoggingStatus);
|
895 |
+
|
896 |
+
$rest = new S3Request('PUT', $bucket, '', self::$endpoint);
|
897 |
+
$rest->setParameter('logging', null);
|
898 |
+
$rest->data = $dom->saveXML();
|
899 |
+
$rest->size = strlen($rest->data);
|
900 |
+
$rest->setHeader('Content-Type', 'application/xml');
|
901 |
+
$rest = $rest->getResponse();
|
902 |
+
if ($rest->error === false && $rest->code !== 200)
|
903 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
904 |
+
if ($rest->error !== false)
|
905 |
+
{
|
906 |
+
self::__triggerError(sprintf("S3::setBucketLogging({$bucket}, {$targetBucket}): [%s] %s",
|
907 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
908 |
+
return false;
|
909 |
+
}
|
910 |
+
return true;
|
911 |
+
}
|
912 |
+
|
913 |
+
|
914 |
+
/**
|
915 |
+
* Get logging status for a bucket
|
916 |
+
*
|
917 |
+
* This will return false if logging is not enabled.
|
918 |
+
* Note: To enable logging, you also need to grant write access to the log group
|
919 |
+
*
|
920 |
+
* @param string $bucket Bucket name
|
921 |
+
* @return array | false
|
922 |
+
*/
|
923 |
+
public static function getBucketLogging($bucket)
|
924 |
+
{
|
925 |
+
$rest = new S3Request('GET', $bucket, '', self::$endpoint);
|
926 |
+
$rest->setParameter('logging', null);
|
927 |
+
$rest = $rest->getResponse();
|
928 |
+
if ($rest->error === false && $rest->code !== 200)
|
929 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
930 |
+
if ($rest->error !== false)
|
931 |
+
{
|
932 |
+
self::__triggerError(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
|
933 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
934 |
+
return false;
|
935 |
+
}
|
936 |
+
if (!isset($rest->body->LoggingEnabled)) return false; // No logging
|
937 |
+
return array(
|
938 |
+
'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
|
939 |
+
'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
|
940 |
+
);
|
941 |
+
}
|
942 |
+
|
943 |
+
|
944 |
+
/**
|
945 |
+
* Disable bucket logging
|
946 |
+
*
|
947 |
+
* @param string $bucket Bucket name
|
948 |
+
* @return boolean
|
949 |
+
*/
|
950 |
+
public static function disableBucketLogging($bucket)
|
951 |
+
{
|
952 |
+
return self::setBucketLogging($bucket, null);
|
953 |
+
}
|
954 |
+
|
955 |
+
|
956 |
+
/**
|
957 |
+
* Get a bucket's location
|
958 |
+
*
|
959 |
+
* @param string $bucket Bucket name
|
960 |
+
* @return string | false
|
961 |
+
*/
|
962 |
+
public static function getBucketLocation($bucket)
|
963 |
+
{
|
964 |
+
$rest = new S3Request('GET', $bucket, '', self::$endpoint);
|
965 |
+
$rest->setParameter('location', null);
|
966 |
+
$rest = $rest->getResponse();
|
967 |
+
if ($rest->error === false && $rest->code !== 200)
|
968 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
969 |
+
if ($rest->error !== false)
|
970 |
+
{
|
971 |
+
self::__triggerError(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
|
972 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
973 |
+
return false;
|
974 |
+
}
|
975 |
+
return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
|
976 |
+
}
|
977 |
+
|
978 |
+
|
979 |
+
/**
|
980 |
+
* Set object or bucket Access Control Policy
|
981 |
+
*
|
982 |
+
* @param string $bucket Bucket name
|
983 |
+
* @param string $uri Object URI
|
984 |
+
* @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
|
985 |
+
* @return boolean
|
986 |
+
*/
|
987 |
+
public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
|
988 |
+
{
|
989 |
+
$dom = new DOMDocument;
|
990 |
+
$dom->formatOutput = true;
|
991 |
+
$accessControlPolicy = $dom->createElement('AccessControlPolicy');
|
992 |
+
$accessControlList = $dom->createElement('AccessControlList');
|
993 |
+
|
994 |
+
// It seems the owner has to be passed along too
|
995 |
+
$owner = $dom->createElement('Owner');
|
996 |
+
$owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
|
997 |
+
$owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
|
998 |
+
$accessControlPolicy->appendChild($owner);
|
999 |
+
|
1000 |
+
foreach ($acp['acl'] as $g)
|
1001 |
+
{
|
1002 |
+
$grant = $dom->createElement('Grant');
|
1003 |
+
$grantee = $dom->createElement('Grantee');
|
1004 |
+
$grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
|
1005 |
+
if (isset($g['id']))
|
1006 |
+
{ // CanonicalUser (DisplayName is omitted)
|
1007 |
+
$grantee->setAttribute('xsi:type', 'CanonicalUser');
|
1008 |
+
$grantee->appendChild($dom->createElement('ID', $g['id']));
|
1009 |
+
}
|
1010 |
+
elseif (isset($g['email']))
|
1011 |
+
{ // AmazonCustomerByEmail
|
1012 |
+
$grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
|
1013 |
+
$grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
|
1014 |
+
}
|
1015 |
+
elseif ($g['type'] == 'Group')
|
1016 |
+
{ // Group
|
1017 |
+
$grantee->setAttribute('xsi:type', 'Group');
|
1018 |
+
$grantee->appendChild($dom->createElement('URI', $g['uri']));
|
1019 |
+
}
|
1020 |
+
$grant->appendChild($grantee);
|
1021 |
+
$grant->appendChild($dom->createElement('Permission', $g['permission']));
|
1022 |
+
$accessControlList->appendChild($grant);
|
1023 |
+
}
|
1024 |
+
|
1025 |
+
$accessControlPolicy->appendChild($accessControlList);
|
1026 |
+
$dom->appendChild($accessControlPolicy);
|
1027 |
+
|
1028 |
+
$rest = new S3Request('PUT', $bucket, $uri, self::$endpoint);
|
1029 |
+
$rest->setParameter('acl', null);
|
1030 |
+
$rest->data = $dom->saveXML();
|
1031 |
+
$rest->size = strlen($rest->data);
|
1032 |
+
$rest->setHeader('Content-Type', 'application/xml');
|
1033 |
+
$rest = $rest->getResponse();
|
1034 |
+
if ($rest->error === false && $rest->code !== 200)
|
1035 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1036 |
+
if ($rest->error !== false)
|
1037 |
+
{
|
1038 |
+
self::__triggerError(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
|
1039 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
1040 |
+
return false;
|
1041 |
+
}
|
1042 |
+
return true;
|
1043 |
+
}
|
1044 |
+
|
1045 |
+
|
1046 |
+
/**
|
1047 |
+
* Get object or bucket Access Control Policy
|
1048 |
+
*
|
1049 |
+
* @param string $bucket Bucket name
|
1050 |
+
* @param string $uri Object URI
|
1051 |
+
* @return mixed | false
|
1052 |
+
*/
|
1053 |
+
public static function getAccessControlPolicy($bucket, $uri = '')
|
1054 |
+
{
|
1055 |
+
$rest = new S3Request('GET', $bucket, $uri, self::$endpoint);
|
1056 |
+
$rest->setParameter('acl', null);
|
1057 |
+
$rest = $rest->getResponse();
|
1058 |
+
if ($rest->error === false && $rest->code !== 200)
|
1059 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1060 |
+
if ($rest->error !== false)
|
1061 |
+
{
|
1062 |
+
self::__triggerError(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
|
1063 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
1064 |
+
return false;
|
1065 |
+
}
|
1066 |
+
|
1067 |
+
$acp = array();
|
1068 |
+
if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
|
1069 |
+
$acp['owner'] = array(
|
1070 |
+
'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
|
1071 |
+
);
|
1072 |
+
|
1073 |
+
if (isset($rest->body->AccessControlList))
|
1074 |
+
{
|
1075 |
+
$acp['acl'] = array();
|
1076 |
+
foreach ($rest->body->AccessControlList->Grant as $grant)
|
1077 |
+
{
|
1078 |
+
foreach ($grant->Grantee as $grantee)
|
1079 |
+
{
|
1080 |
+
if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
|
1081 |
+
$acp['acl'][] = array(
|
1082 |
+
'type' => 'CanonicalUser',
|
1083 |
+
'id' => (string)$grantee->ID,
|
1084 |
+
'name' => (string)$grantee->DisplayName,
|
1085 |
+
'permission' => (string)$grant->Permission
|
1086 |
+
);
|
1087 |
+
elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
|
1088 |
+
$acp['acl'][] = array(
|
1089 |
+
'type' => 'AmazonCustomerByEmail',
|
1090 |
+
'email' => (string)$grantee->EmailAddress,
|
1091 |
+
'permission' => (string)$grant->Permission
|
1092 |
+
);
|
1093 |
+
elseif (isset($grantee->URI)) // Group
|
1094 |
+
$acp['acl'][] = array(
|
1095 |
+
'type' => 'Group',
|
1096 |
+
'uri' => (string)$grantee->URI,
|
1097 |
+
'permission' => (string)$grant->Permission
|
1098 |
+
);
|
1099 |
+
else continue;
|
1100 |
+
}
|
1101 |
+
}
|
1102 |
+
}
|
1103 |
+
return $acp;
|
1104 |
+
}
|
1105 |
+
|
1106 |
+
|
1107 |
+
/**
|
1108 |
+
* Delete an object
|
1109 |
+
*
|
1110 |
+
* @param string $bucket Bucket name
|
1111 |
+
* @param string $uri Object URI
|
1112 |
+
* @return boolean
|
1113 |
+
*/
|
1114 |
+
public static function deleteObject($bucket, $uri)
|
1115 |
+
{
|
1116 |
+
$rest = new S3Request('DELETE', $bucket, $uri, self::$endpoint);
|
1117 |
+
$rest = $rest->getResponse();
|
1118 |
+
if ($rest->error === false && $rest->code !== 204)
|
1119 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1120 |
+
if ($rest->error !== false)
|
1121 |
+
{
|
1122 |
+
self::__triggerError(sprintf("S3::deleteObject(): [%s] %s",
|
1123 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
1124 |
+
return false;
|
1125 |
+
}
|
1126 |
+
return true;
|
1127 |
+
}
|
1128 |
+
|
1129 |
+
|
1130 |
+
/**
|
1131 |
+
* Get a query string authenticated URL
|
1132 |
+
*
|
1133 |
+
* @param string $bucket Bucket name
|
1134 |
+
* @param string $uri Object URI
|
1135 |
+
* @param integer $lifetime Lifetime in seconds
|
1136 |
+
* @param boolean $hostBucket Use the bucket name as the hostname
|
1137 |
+
* @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
|
1138 |
+
* @return string
|
1139 |
+
*/
|
1140 |
+
public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false)
|
1141 |
+
{
|
1142 |
+
$expires = time() + $lifetime;
|
1143 |
+
$uri = str_replace(array('%2F', '%2B'), array('/', '+'), rawurlencode($uri));
|
1144 |
+
return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
|
1145 |
+
// $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
|
1146 |
+
$hostBucket ? $bucket : 's3.amazonaws.com/'.$bucket, $uri, self::$__accessKey, $expires,
|
1147 |
+
urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
|
1148 |
+
}
|
1149 |
+
|
1150 |
+
|
1151 |
+
/**
|
1152 |
+
* Get a CloudFront signed policy URL
|
1153 |
+
*
|
1154 |
+
* @param array $policy Policy
|
1155 |
+
* @return string
|
1156 |
+
*/
|
1157 |
+
public static function getSignedPolicyURL($policy)
|
1158 |
+
{
|
1159 |
+
$data = json_encode($policy);
|
1160 |
+
$signature = '';
|
1161 |
+
if (!openssl_sign($data, $signature, self::$__signingKeyResource)) return false;
|
1162 |
+
|
1163 |
+
$encoded = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($data));
|
1164 |
+
$signature = str_replace(array('+', '='), array('-', '_', '~'), base64_encode($signature));
|
1165 |
+
|
1166 |
+
$url = $policy['Statement'][0]['Resource'] . '?';
|
1167 |
+
foreach (array('Policy' => $encoded, 'Signature' => $signature, 'Key-Pair-Id' => self::$__signingKeyPairId) as $k => $v)
|
1168 |
+
$url .= $k.'='.str_replace('%2F', '/', rawurlencode($v)).'&';
|
1169 |
+
return substr($url, 0, -1);
|
1170 |
+
}
|
1171 |
+
|
1172 |
+
|
1173 |
+
/**
|
1174 |
+
* Get a CloudFront canned policy URL
|
1175 |
+
*
|
1176 |
+
* @param string $string URL to sign
|
1177 |
+
* @param integer $lifetime URL lifetime
|
1178 |
+
* @return string
|
1179 |
+
*/
|
1180 |
+
public static function getSignedCannedURL($url, $lifetime)
|
1181 |
+
{
|
1182 |
+
return self::getSignedPolicyURL(array(
|
1183 |
+
'Statement' => array(
|
1184 |
+
array('Resource' => $url, 'Condition' => array(
|
1185 |
+
'DateLessThan' => array('AWS:EpochTime' => time() + $lifetime)
|
1186 |
+
))
|
1187 |
+
)
|
1188 |
+
));
|
1189 |
+
}
|
1190 |
+
|
1191 |
+
|
1192 |
+
/**
|
1193 |
+
* Get upload POST parameters for form uploads
|
1194 |
+
*
|
1195 |
+
* @param string $bucket Bucket name
|
1196 |
+
* @param string $uriPrefix Object URI prefix
|
1197 |
+
* @param constant $acl ACL constant
|
1198 |
+
* @param integer $lifetime Lifetime in seconds
|
1199 |
+
* @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
|
1200 |
+
* @param string $successRedirect Redirect URL or 200 / 201 status code
|
1201 |
+
* @param array $amzHeaders Array of x-amz-meta-* headers
|
1202 |
+
* @param array $headers Array of request headers or content type as a string
|
1203 |
+
* @param boolean $flashVars Includes additional "Filename" variable posted by Flash
|
1204 |
+
* @return object
|
1205 |
+
*/
|
1206 |
+
public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600,
|
1207 |
+
$maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false)
|
1208 |
+
{
|
1209 |
+
// Create policy object
|
1210 |
+
$policy = new stdClass;
|
1211 |
+
$policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
|
1212 |
+
$policy->conditions = array();
|
1213 |
+
$obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
|
1214 |
+
$obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
|
1215 |
+
|
1216 |
+
$obj = new stdClass; // 200 for non-redirect uploads
|
1217 |
+
if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
|
1218 |
+
$obj->success_action_status = (string)$successRedirect;
|
1219 |
+
else // URL
|
1220 |
+
$obj->success_action_redirect = $successRedirect;
|
1221 |
+
array_push($policy->conditions, $obj);
|
1222 |
+
|
1223 |
+
if ($acl !== self::ACL_PUBLIC_READ)
|
1224 |
+
array_push($policy->conditions, array('eq', '$acl', $acl));
|
1225 |
+
|
1226 |
+
array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
|
1227 |
+
if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
|
1228 |
+
foreach (array_keys($headers) as $headerKey)
|
1229 |
+
array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
|
1230 |
+
foreach ($amzHeaders as $headerKey => $headerVal)
|
1231 |
+
{
|
1232 |
+
$obj = new stdClass;
|
1233 |
+
$obj->{$headerKey} = (string)$headerVal;
|
1234 |
+
array_push($policy->conditions, $obj);
|
1235 |
+
}
|
1236 |
+
array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
|
1237 |
+
$policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
|
1238 |
+
|
1239 |
+
// Create parameters
|
1240 |
+
$params = new stdClass;
|
1241 |
+
$params->AWSAccessKeyId = self::$__accessKey;
|
1242 |
+
$params->key = $uriPrefix.'${filename}';
|
1243 |
+
$params->acl = $acl;
|
1244 |
+
$params->policy = $policy; unset($policy);
|
1245 |
+
$params->signature = self::__getHash($params->policy);
|
1246 |
+
if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
|
1247 |
+
$params->success_action_status = (string)$successRedirect;
|
1248 |
+
else
|
1249 |
+
$params->success_action_redirect = $successRedirect;
|
1250 |
+
foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
|
1251 |
+
foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
|
1252 |
+
return $params;
|
1253 |
+
}
|
1254 |
+
|
1255 |
+
|
1256 |
+
/**
|
1257 |
+
* Create a CloudFront distribution
|
1258 |
+
*
|
1259 |
+
* @param string $bucket Bucket name
|
1260 |
+
* @param boolean $enabled Enabled (true/false)
|
1261 |
+
* @param array $cnames Array containing CNAME aliases
|
1262 |
+
* @param string $comment Use the bucket name as the hostname
|
1263 |
+
* @param string $defaultRootObject Default root object
|
1264 |
+
* @param string $originAccessIdentity Origin access identity
|
1265 |
+
* @param array $trustedSigners Array of trusted signers
|
1266 |
+
* @return array | false
|
1267 |
+
*/
|
1268 |
+
public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = null, $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
|
1269 |
+
{
|
1270 |
+
if (!extension_loaded('openssl'))
|
1271 |
+
{
|
1272 |
+
self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): %s",
|
1273 |
+
"CloudFront functionality requires SSL"), __FILE__, __LINE__);
|
1274 |
+
return false;
|
1275 |
+
}
|
1276 |
+
$useSSL = self::$useSSL;
|
1277 |
+
|
1278 |
+
self::$useSSL = true; // CloudFront requires SSL
|
1279 |
+
$rest = new S3Request('POST', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
|
1280 |
+
$rest->data = self::__getCloudFrontDistributionConfigXML(
|
1281 |
+
$bucket.'.s3.amazonaws.com',
|
1282 |
+
$enabled,
|
1283 |
+
(string)$comment,
|
1284 |
+
(string)microtime(true),
|
1285 |
+
$cnames,
|
1286 |
+
$defaultRootObject,
|
1287 |
+
$originAccessIdentity,
|
1288 |
+
$trustedSigners
|
1289 |
+
);
|
1290 |
+
|
1291 |
+
$rest->size = strlen($rest->data);
|
1292 |
+
$rest->setHeader('Content-Type', 'application/xml');
|
1293 |
+
$rest = self::__getCloudFrontResponse($rest);
|
1294 |
+
|
1295 |
+
self::$useSSL = $useSSL;
|
1296 |
+
|
1297 |
+
if ($rest->error === false && $rest->code !== 201)
|
1298 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1299 |
+
if ($rest->error !== false)
|
1300 |
+
{
|
1301 |
+
self::__triggerError(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", [], '$comment'): [%s] %s",
|
1302 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
1303 |
+
return false;
|
1304 |
+
} elseif ($rest->body instanceof SimpleXMLElement)
|
1305 |
+
return self::__parseCloudFrontDistributionConfig($rest->body);
|
1306 |
+
return false;
|
1307 |
+
}
|
1308 |
+
|
1309 |
+
|
1310 |
+
/**
|
1311 |
+
* Get CloudFront distribution info
|
1312 |
+
*
|
1313 |
+
* @param string $distributionId Distribution ID from listDistributions()
|
1314 |
+
* @return array | false
|
1315 |
+
*/
|
1316 |
+
public static function getDistribution($distributionId)
|
1317 |
+
{
|
1318 |
+
if (!extension_loaded('openssl'))
|
1319 |
+
{
|
1320 |
+
self::__triggerError(sprintf("S3::getDistribution($distributionId): %s",
|
1321 |
+
"CloudFront functionality requires SSL"), __FILE__, __LINE__);
|
1322 |
+
return false;
|
1323 |
+
}
|
1324 |
+
$useSSL = self::$useSSL;
|
1325 |
+
|
1326 |
+
self::$useSSL = true; // CloudFront requires SSL
|
1327 |
+
$rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
|
1328 |
+
$rest = self::__getCloudFrontResponse($rest);
|
1329 |
+
|
1330 |
+
self::$useSSL = $useSSL;
|
1331 |
+
|
1332 |
+
if ($rest->error === false && $rest->code !== 200)
|
1333 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1334 |
+
if ($rest->error !== false)
|
1335 |
+
{
|
1336 |
+
self::__triggerError(sprintf("S3::getDistribution($distributionId): [%s] %s",
|
1337 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
1338 |
+
return false;
|
1339 |
+
}
|
1340 |
+
elseif ($rest->body instanceof SimpleXMLElement)
|
1341 |
+
{
|
1342 |
+
$dist = self::__parseCloudFrontDistributionConfig($rest->body);
|
1343 |
+
$dist['hash'] = $rest->headers['hash'];
|
1344 |
+
$dist['id'] = $distributionId;
|
1345 |
+
return $dist;
|
1346 |
+
}
|
1347 |
+
return false;
|
1348 |
+
}
|
1349 |
+
|
1350 |
+
|
1351 |
+
/**
|
1352 |
+
* Update a CloudFront distribution
|
1353 |
+
*
|
1354 |
+
* @param array $dist Distribution array info identical to output of getDistribution()
|
1355 |
+
* @return array | false
|
1356 |
+
*/
|
1357 |
+
public static function updateDistribution($dist)
|
1358 |
+
{
|
1359 |
+
if (!extension_loaded('openssl'))
|
1360 |
+
{
|
1361 |
+
self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): %s",
|
1362 |
+
"CloudFront functionality requires SSL"), __FILE__, __LINE__);
|
1363 |
+
return false;
|
1364 |
+
}
|
1365 |
+
|
1366 |
+
$useSSL = self::$useSSL;
|
1367 |
+
|
1368 |
+
self::$useSSL = true; // CloudFront requires SSL
|
1369 |
+
$rest = new S3Request('PUT', '', '2010-11-01/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
|
1370 |
+
$rest->data = self::__getCloudFrontDistributionConfigXML(
|
1371 |
+
$dist['origin'],
|
1372 |
+
$dist['enabled'],
|
1373 |
+
$dist['comment'],
|
1374 |
+
$dist['callerReference'],
|
1375 |
+
$dist['cnames'],
|
1376 |
+
$dist['defaultRootObject'],
|
1377 |
+
$dist['originAccessIdentity'],
|
1378 |
+
$dist['trustedSigners']
|
1379 |
+
);
|
1380 |
+
|
1381 |
+
$rest->size = strlen($rest->data);
|
1382 |
+
$rest->setHeader('If-Match', $dist['hash']);
|
1383 |
+
$rest = self::__getCloudFrontResponse($rest);
|
1384 |
+
|
1385 |
+
self::$useSSL = $useSSL;
|
1386 |
+
|
1387 |
+
if ($rest->error === false && $rest->code !== 200)
|
1388 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1389 |
+
if ($rest->error !== false)
|
1390 |
+
{
|
1391 |
+
self::__triggerError(sprintf("S3::updateDistribution({$dist['id']}): [%s] %s",
|
1392 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
1393 |
+
return false;
|
1394 |
+
} else {
|
1395 |
+
$dist = self::__parseCloudFrontDistributionConfig($rest->body);
|
1396 |
+
$dist['hash'] = $rest->headers['hash'];
|
1397 |
+
return $dist;
|
1398 |
+
}
|
1399 |
+
return false;
|
1400 |
+
}
|
1401 |
+
|
1402 |
+
|
1403 |
+
/**
|
1404 |
+
* Delete a CloudFront distribution
|
1405 |
+
*
|
1406 |
+
* @param array $dist Distribution array info identical to output of getDistribution()
|
1407 |
+
* @return boolean
|
1408 |
+
*/
|
1409 |
+
public static function deleteDistribution($dist)
|
1410 |
+
{
|
1411 |
+
if (!extension_loaded('openssl'))
|
1412 |
+
{
|
1413 |
+
self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): %s",
|
1414 |
+
"CloudFront functionality requires SSL"), __FILE__, __LINE__);
|
1415 |
+
return false;
|
1416 |
+
}
|
1417 |
+
|
1418 |
+
$useSSL = self::$useSSL;
|
1419 |
+
|
1420 |
+
self::$useSSL = true; // CloudFront requires SSL
|
1421 |
+
$rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
|
1422 |
+
$rest->setHeader('If-Match', $dist['hash']);
|
1423 |
+
$rest = self::__getCloudFrontResponse($rest);
|
1424 |
+
|
1425 |
+
self::$useSSL = $useSSL;
|
1426 |
+
|
1427 |
+
if ($rest->error === false && $rest->code !== 204)
|
1428 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1429 |
+
if ($rest->error !== false)
|
1430 |
+
{
|
1431 |
+
self::__triggerError(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
|
1432 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
1433 |
+
return false;
|
1434 |
+
}
|
1435 |
+
return true;
|
1436 |
+
}
|
1437 |
+
|
1438 |
+
|
1439 |
+
/**
|
1440 |
+
* Get a list of CloudFront distributions
|
1441 |
+
*
|
1442 |
+
* @return array
|
1443 |
+
*/
|
1444 |
+
public static function listDistributions()
|
1445 |
+
{
|
1446 |
+
if (!extension_loaded('openssl'))
|
1447 |
+
{
|
1448 |
+
self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
|
1449 |
+
"CloudFront functionality requires SSL"), __FILE__, __LINE__);
|
1450 |
+
return false;
|
1451 |
+
}
|
1452 |
+
|
1453 |
+
$useSSL = self::$useSSL;
|
1454 |
+
self::$useSSL = true; // CloudFront requires SSL
|
1455 |
+
$rest = new S3Request('GET', '', '2010-11-01/distribution', 'cloudfront.amazonaws.com');
|
1456 |
+
$rest = self::__getCloudFrontResponse($rest);
|
1457 |
+
self::$useSSL = $useSSL;
|
1458 |
+
|
1459 |
+
if ($rest->error === false && $rest->code !== 200)
|
1460 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1461 |
+
if ($rest->error !== false)
|
1462 |
+
{
|
1463 |
+
self::__triggerError(sprintf("S3::listDistributions(): [%s] %s",
|
1464 |
+
$rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
|
1465 |
+
return false;
|
1466 |
+
}
|
1467 |
+
elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary))
|
1468 |
+
{
|
1469 |
+
$list = array();
|
1470 |
+
if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated))
|
1471 |
+
{
|
1472 |
+
//$info['marker'] = (string)$rest->body->Marker;
|
1473 |
+
//$info['maxItems'] = (int)$rest->body->MaxItems;
|
1474 |
+
//$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
|
1475 |
+
}
|
1476 |
+
foreach ($rest->body->DistributionSummary as $summary)
|
1477 |
+
$list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
|
1478 |
+
|
1479 |
+
return $list;
|
1480 |
+
}
|
1481 |
+
return array();
|
1482 |
+
}
|
1483 |
+
|
1484 |
+
/**
|
1485 |
+
* List CloudFront Origin Access Identities
|
1486 |
+
*
|
1487 |
+
* @return array
|
1488 |
+
*/
|
1489 |
+
public static function listOriginAccessIdentities()
|
1490 |
+
{
|
1491 |
+
if (!extension_loaded('openssl'))
|
1492 |
+
{
|
1493 |
+
self::__triggerError(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
|
1494 |
+
"CloudFront functionality requires SSL"), __FILE__, __LINE__);
|
1495 |
+
return false;
|
1496 |
+
}
|
1497 |
+
|
1498 |
+
self::$useSSL = true; // CloudFront requires SSL
|
1499 |
+
$rest = new S3Request('GET', '', '2010-11-01/origin-access-identity/cloudfront', 'cloudfront.amazonaws.com');
|
1500 |
+
$rest = self::__getCloudFrontResponse($rest);
|
1501 |
+
$useSSL = self::$useSSL;
|
1502 |
+
|
1503 |
+
if ($rest->error === false && $rest->code !== 200)
|
1504 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1505 |
+
if ($rest->error !== false)
|
1506 |
+
{
|
1507 |
+
trigger_error(sprintf("S3::listOriginAccessIdentities(): [%s] %s",
|
1508 |
+
$rest->error['code'], $rest->error['message']), E_USER_WARNING);
|
1509 |
+
return false;
|
1510 |
+
}
|
1511 |
+
|
1512 |
+
if (isset($rest->body->CloudFrontOriginAccessIdentitySummary))
|
1513 |
+
{
|
1514 |
+
$identities = array();
|
1515 |
+
foreach ($rest->body->CloudFrontOriginAccessIdentitySummary as $identity)
|
1516 |
+
if (isset($identity->S3CanonicalUserId))
|
1517 |
+
$identities[(string)$identity->Id] = array('id' => (string)$identity->Id, 's3CanonicalUserId' => (string)$identity->S3CanonicalUserId);
|
1518 |
+
return $identities;
|
1519 |
+
}
|
1520 |
+
return false;
|
1521 |
+
}
|
1522 |
+
|
1523 |
+
|
1524 |
+
/**
|
1525 |
+
* Invalidate objects in a CloudFront distribution
|
1526 |
+
*
|
1527 |
+
* Thanks to Martin Lindkvist for S3::invalidateDistribution()
|
1528 |
+
*
|
1529 |
+
* @param string $distributionId Distribution ID from listDistributions()
|
1530 |
+
* @param array $paths Array of object paths to invalidate
|
1531 |
+
* @return boolean
|
1532 |
+
*/
|
1533 |
+
public static function invalidateDistribution($distributionId, $paths)
|
1534 |
+
{
|
1535 |
+
if (!extension_loaded('openssl'))
|
1536 |
+
{
|
1537 |
+
self::__triggerError(sprintf("S3::invalidateDistribution(): [%s] %s",
|
1538 |
+
"CloudFront functionality requires SSL"), __FILE__, __LINE__);
|
1539 |
+
return false;
|
1540 |
+
}
|
1541 |
+
|
1542 |
+
$useSSL = self::$useSSL;
|
1543 |
+
self::$useSSL = true; // CloudFront requires SSL
|
1544 |
+
$rest = new S3Request('POST', '', '2010-08-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
|
1545 |
+
$rest->data = self::__getCloudFrontInvalidationBatchXML($paths, (string)microtime(true));
|
1546 |
+
$rest->size = strlen($rest->data);
|
1547 |
+
$rest = self::__getCloudFrontResponse($rest);
|
1548 |
+
self::$useSSL = $useSSL;
|
1549 |
+
|
1550 |
+
if ($rest->error === false && $rest->code !== 201)
|
1551 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1552 |
+
if ($rest->error !== false)
|
1553 |
+
{
|
1554 |
+
trigger_error(sprintf("S3::invalidate('{$distributionId}',{$paths}): [%s] %s",
|
1555 |
+
$rest->error['code'], $rest->error['message']), E_USER_WARNING);
|
1556 |
+
return false;
|
1557 |
+
}
|
1558 |
+
return true;
|
1559 |
+
}
|
1560 |
+
|
1561 |
+
|
1562 |
+
/**
|
1563 |
+
* Get a InvalidationBatch DOMDocument
|
1564 |
+
*
|
1565 |
+
* @internal Used to create XML in invalidateDistribution()
|
1566 |
+
* @param array $paths Paths to objects to invalidateDistribution
|
1567 |
+
* @return string
|
1568 |
+
*/
|
1569 |
+
private static function __getCloudFrontInvalidationBatchXML($paths, $callerReference = '0') {
|
1570 |
+
$dom = new DOMDocument('1.0', 'UTF-8');
|
1571 |
+
$dom->formatOutput = true;
|
1572 |
+
$invalidationBatch = $dom->createElement('InvalidationBatch');
|
1573 |
+
foreach ($paths as $path)
|
1574 |
+
$invalidationBatch->appendChild($dom->createElement('Path', $path));
|
1575 |
+
|
1576 |
+
$invalidationBatch->appendChild($dom->createElement('CallerReference', $callerReference));
|
1577 |
+
$dom->appendChild($invalidationBatch);
|
1578 |
+
return $dom->saveXML();
|
1579 |
+
}
|
1580 |
+
|
1581 |
+
|
1582 |
+
/**
|
1583 |
+
* List your invalidation batches for invalidateDistribution() in a CloudFront distribution
|
1584 |
+
*
|
1585 |
+
* http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/ListInvalidation.html
|
1586 |
+
* returned array looks like this:
|
1587 |
+
* Array
|
1588 |
+
* (
|
1589 |
+
* [I31TWB0CN9V6XD] => InProgress
|
1590 |
+
* [IT3TFE31M0IHZ] => Completed
|
1591 |
+
* [I12HK7MPO1UQDA] => Completed
|
1592 |
+
* [I1IA7R6JKTC3L2] => Completed
|
1593 |
+
* )
|
1594 |
+
*
|
1595 |
+
* @param string $distributionId Distribution ID from listDistributions()
|
1596 |
+
* @return array
|
1597 |
+
*/
|
1598 |
+
public static function getDistributionInvalidationList($distributionId)
|
1599 |
+
{
|
1600 |
+
if (!extension_loaded('openssl'))
|
1601 |
+
{
|
1602 |
+
self::__triggerError(sprintf("S3::getDistributionInvalidationList(): [%s] %s",
|
1603 |
+
"CloudFront functionality requires SSL"), __FILE__, __LINE__);
|
1604 |
+
return false;
|
1605 |
+
}
|
1606 |
+
|
1607 |
+
$useSSL = self::$useSSL;
|
1608 |
+
self::$useSSL = true; // CloudFront requires SSL
|
1609 |
+
$rest = new S3Request('GET', '', '2010-11-01/distribution/'.$distributionId.'/invalidation', 'cloudfront.amazonaws.com');
|
1610 |
+
$rest = self::__getCloudFrontResponse($rest);
|
1611 |
+
self::$useSSL = $useSSL;
|
1612 |
+
|
1613 |
+
if ($rest->error === false && $rest->code !== 200)
|
1614 |
+
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
1615 |
+
if ($rest->error !== false)
|
1616 |
+
{
|
1617 |
+
trigger_error(sprintf("S3::getDistributionInvalidationList('{$distributionId}'): [%s]",
|
1618 |
+
$rest->error['code'], $rest->error['message']), E_USER_WARNING);
|
1619 |
+
return false;
|
1620 |
+
}
|
1621 |
+
elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->InvalidationSummary))
|
1622 |
+
{
|
1623 |
+
$list = array();
|
1624 |
+
foreach ($rest->body->InvalidationSummary as $summary)
|
1625 |
+
$list[(string)$summary->Id] = (string)$summary->Status;
|
1626 |
+
|
1627 |
+
return $list;
|
1628 |
+
}
|
1629 |
+
return array();
|
1630 |
+
}
|
1631 |
+
|
1632 |
+
|
1633 |
+
/**
|
1634 |
+
* Get a DistributionConfig DOMDocument
|
1635 |
+
*
|
1636 |
+
* http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?PutConfig.html
|
1637 |
+
*
|
1638 |
+
* @internal Used to create XML in createDistribution() and updateDistribution()
|
1639 |
+
* @param string $bucket S3 Origin bucket
|
1640 |
+
* @param boolean $enabled Enabled (true/false)
|
1641 |
+
* @param string $comment Comment to append
|
1642 |
+
* @param string $callerReference Caller reference
|
1643 |
+
* @param array $cnames Array of CNAME aliases
|
1644 |
+
* @param string $defaultRootObject Default root object
|
1645 |
+
* @param string $originAccessIdentity Origin access identity
|
1646 |
+
* @param array $trustedSigners Array of trusted signers
|
1647 |
+
* @return string
|
1648 |
+
*/
|
1649 |
+
private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array(), $defaultRootObject = null, $originAccessIdentity = null, $trustedSigners = array())
|
1650 |
+
{
|
1651 |
+
$dom = new DOMDocument('1.0', 'UTF-8');
|
1652 |
+
$dom->formatOutput = true;
|
1653 |
+
$distributionConfig = $dom->createElement('DistributionConfig');
|
1654 |
+
$distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2010-11-01/');
|
1655 |
+
|
1656 |
+
$origin = $dom->createElement('S3Origin');
|
1657 |
+
$origin->appendChild($dom->createElement('DNSName', $bucket));
|
1658 |
+
if ($originAccessIdentity !== null) $origin->appendChild($dom->createElement('OriginAccessIdentity', $originAccessIdentity));
|
1659 |
+
$distributionConfig->appendChild($origin);
|
1660 |
+
|
1661 |
+
if ($defaultRootObject !== null) $distributionConfig->appendChild($dom->createElement('DefaultRootObject', $defaultRootObject));
|
1662 |
+
|
1663 |
+
$distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
|
1664 |
+
foreach ($cnames as $cname)
|
1665 |
+
$distributionConfig->appendChild($dom->createElement('CNAME', $cname));
|
1666 |
+
if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
|
1667 |
+
$distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
|
1668 |
+
|
1669 |
+
$trusted = $dom->createElement('TrustedSigners');
|
1670 |
+
foreach ($trustedSigners as $id => $type)
|
1671 |
+
$trusted->appendChild($id !== '' ? $dom->createElement($type, $id) : $dom->createElement($type));
|
1672 |
+
$distributionConfig->appendChild($trusted);
|
1673 |
+
|
1674 |
+
$dom->appendChild($distributionConfig);
|
1675 |
+
//var_dump($dom->saveXML());
|
1676 |
+
return $dom->saveXML();
|
1677 |
+
}
|
1678 |
+
|
1679 |
+
|
1680 |
+
/**
|
1681 |
+
* Parse a CloudFront distribution config
|
1682 |
+
*
|
1683 |
+
* See http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?GetDistribution.html
|
1684 |
+
*
|
1685 |
+
* @internal Used to parse the CloudFront DistributionConfig node to an array
|
1686 |
+
* @param object &$node DOMNode
|
1687 |
+
* @return array
|
1688 |
+
*/
|
1689 |
+
private static function __parseCloudFrontDistributionConfig(&$node)
|
1690 |
+
{
|
1691 |
+
if (isset($node->DistributionConfig))
|
1692 |
+
return self::__parseCloudFrontDistributionConfig($node->DistributionConfig);
|
1693 |
+
|
1694 |
+
$dist = array();
|
1695 |
+
if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName))
|
1696 |
+
{
|
1697 |
+
$dist['id'] = (string)$node->Id;
|
1698 |
+
$dist['status'] = (string)$node->Status;
|
1699 |
+
$dist['time'] = strtotime((string)$node->LastModifiedTime);
|
1700 |
+
$dist['domain'] = (string)$node->DomainName;
|
1701 |
+
}
|
1702 |
+
|
1703 |
+
if (isset($node->CallerReference))
|
1704 |
+
$dist['callerReference'] = (string)$node->CallerReference;
|
1705 |
+
|
1706 |
+
if (isset($node->Enabled))
|
1707 |
+
$dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
|
1708 |
+
|
1709 |
+
if (isset($node->S3Origin))
|
1710 |
+
{
|
1711 |
+
if (isset($node->S3Origin->DNSName))
|
1712 |
+
$dist['origin'] = (string)$node->S3Origin->DNSName;
|
1713 |
+
|
1714 |
+
$dist['originAccessIdentity'] = isset($node->S3Origin->OriginAccessIdentity) ?
|
1715 |
+
(string)$node->S3Origin->OriginAccessIdentity : null;
|
1716 |
+
}
|
1717 |
+
|
1718 |
+
$dist['defaultRootObject'] = isset($node->DefaultRootObject) ? (string)$node->DefaultRootObject : null;
|
1719 |
+
|
1720 |
+
$dist['cnames'] = array();
|
1721 |
+
if (isset($node->CNAME))
|
1722 |
+
foreach ($node->CNAME as $cname)
|
1723 |
+
$dist['cnames'][(string)$cname] = (string)$cname;
|
1724 |
+
|
1725 |
+
$dist['trustedSigners'] = array();
|
1726 |
+
if (isset($node->TrustedSigners))
|
1727 |
+
foreach ($node->TrustedSigners as $signer)
|
1728 |
+
{
|
1729 |
+
if (isset($signer->Self))
|
1730 |
+
$dist['trustedSigners'][''] = 'Self';
|
1731 |
+
elseif (isset($signer->KeyPairId))
|
1732 |
+
$dist['trustedSigners'][(string)$signer->KeyPairId] = 'KeyPairId';
|
1733 |
+
elseif (isset($signer->AwsAccountNumber))
|
1734 |
+
$dist['trustedSigners'][(string)$signer->AwsAccountNumber] = 'AwsAccountNumber';
|
1735 |
+
}
|
1736 |
+
|
1737 |
+
$dist['comment'] = isset($node->Comment) ? (string)$node->Comment : null;
|
1738 |
+
return $dist;
|
1739 |
+
}
|
1740 |
+
|
1741 |
+
|
1742 |
+
/**
|
1743 |
+
* Grab CloudFront response
|
1744 |
+
*
|
1745 |
+
* @internal Used to parse the CloudFront S3Request::getResponse() output
|
1746 |
+
* @param object &$rest S3Request instance
|
1747 |
+
* @return object
|
1748 |
+
*/
|
1749 |
+
private static function __getCloudFrontResponse(&$rest)
|
1750 |
+
{
|
1751 |
+
$rest->getResponse();
|
1752 |
+
if ($rest->response->error === false && isset($rest->response->body) &&
|
1753 |
+
is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml')
|
1754 |
+
{
|
1755 |
+
$rest->response->body = simplexml_load_string($rest->response->body);
|
1756 |
+
// Grab CloudFront errors
|
1757 |
+
if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
|
1758 |
+
$rest->response->body->Error->Message))
|
1759 |
+
{
|
1760 |
+
$rest->response->error = array(
|
1761 |
+
'code' => (string)$rest->response->body->Error->Code,
|
1762 |
+
'message' => (string)$rest->response->body->Error->Message
|
1763 |
+
);
|
1764 |
+
unset($rest->response->body);
|
1765 |
+
}
|
1766 |
+
}
|
1767 |
+
return $rest->response;
|
1768 |
+
}
|
1769 |
+
|
1770 |
+
|
1771 |
+
/**
|
1772 |
+
* Get MIME type for file
|
1773 |
+
*
|
1774 |
+
* @internal Used to get mime types
|
1775 |
+
* @param string &$file File path
|
1776 |
+
* @return string
|
1777 |
+
*/
|
1778 |
+
public static function __getMimeType(&$file)
|
1779 |
+
{
|
1780 |
+
$type = false;
|
1781 |
+
// Fileinfo documentation says fileinfo_open() will use the
|
1782 |
+
// MAGIC env var for the magic file
|
1783 |
+
if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
|
1784 |
+
($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
|
1785 |
+
{
|
1786 |
+
if (($type = finfo_file($finfo, $file)) !== false)
|
1787 |
+
{
|
1788 |
+
// Remove the charset and grab the last content-type
|
1789 |
+
$type = explode(' ', str_replace('; charset=', ';charset=', $type));
|
1790 |
+
$type = array_pop($type);
|
1791 |
+
$type = explode(';', $type);
|
1792 |
+
$type = trim(array_shift($type));
|
1793 |
+
}
|
1794 |
+
finfo_close($finfo);
|
1795 |
+
|
1796 |
+
// If anyone is still using mime_content_type()
|
1797 |
+
} elseif (function_exists('mime_content_type'))
|
1798 |
+
$type = trim(mime_content_type($file));
|
1799 |
+
|
1800 |
+
if ($type !== false && strlen($type) > 0) return $type;
|
1801 |
+
|
1802 |
+
// Otherwise do it the old fashioned way
|
1803 |
+
static $exts = array(
|
1804 |
+
'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
|
1805 |
+
'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
|
1806 |
+
'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
|
1807 |
+
'zip' => 'application/zip', 'gz' => 'application/x-gzip',
|
1808 |
+
'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
|
1809 |
+
'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
|
1810 |
+
'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
|
1811 |
+
'css' => 'text/css', 'js' => 'text/javascript',
|
1812 |
+
'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
|
1813 |
+
'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
|
1814 |
+
'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
|
1815 |
+
'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
|
1816 |
+
);
|
1817 |
+
$ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
|
1818 |
+
return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
|
1819 |
+
}
|
1820 |
+
|
1821 |
+
|
1822 |
+
/**
|
1823 |
+
* Generate the auth string: "AWS AccessKey:Signature"
|
1824 |
+
*
|
1825 |
+
* @internal Used by S3Request::getResponse()
|
1826 |
+
* @param string $string String to sign
|
1827 |
+
* @return string
|
1828 |
+
*/
|
1829 |
+
public static function __getSignature($string)
|
1830 |
+
{
|
1831 |
+
return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
|
1832 |
+
}
|
1833 |
+
|
1834 |
+
|
1835 |
+
/**
|
1836 |
+
* Creates a HMAC-SHA1 hash
|
1837 |
+
*
|
1838 |
+
* This uses the hash extension if loaded
|
1839 |
+
*
|
1840 |
+
* @internal Used by __getSignature()
|
1841 |
+
* @param string $string String to sign
|
1842 |
+
* @return string
|
1843 |
+
*/
|
1844 |
+
private static function __getHash($string)
|
1845 |
+
{
|
1846 |
+
return base64_encode(extension_loaded('hash') ?
|
1847 |
+
hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
|
1848 |
+
(str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
|
1849 |
+
pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
|
1850 |
+
(str_repeat(chr(0x36), 64))) . $string)))));
|
1851 |
+
}
|
1852 |
+
|
1853 |
+
}
|
1854 |
+
|
1855 |
+
final class S3Request
|
1856 |
+
{
|
1857 |
+
private $endpoint, $verb, $bucket, $uri, $resource = '', $parameters = array(),
|
1858 |
+
$amzHeaders = array(), $headers = array(
|
1859 |
+
'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
|
1860 |
+
);
|
1861 |
+
public $fp = false, $size = 0, $data = false, $response;
|
1862 |
+
|
1863 |
+
|
1864 |
+
/**
|
1865 |
+
* Constructor
|
1866 |
+
*
|
1867 |
+
* @param string $verb Verb
|
1868 |
+
* @param string $bucket Bucket name
|
1869 |
+
* @param string $uri Object URI
|
1870 |
+
* @return mixed
|
1871 |
+
*/
|
1872 |
+
function __construct($verb, $bucket = '', $uri = '', $endpoint = 's3.amazonaws.com')
|
1873 |
+
{
|
1874 |
+
$this->endpoint = $endpoint;
|
1875 |
+
$this->verb = $verb;
|
1876 |
+
$this->bucket = $bucket;
|
1877 |
+
$this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
|
1878 |
+
|
1879 |
+
//if ($this->bucket !== '')
|
1880 |
+
// $this->resource = '/'.$this->bucket.$this->uri;
|
1881 |
+
//else
|
1882 |
+
// $this->resource = $this->uri;
|
1883 |
+
|
1884 |
+
if ($this->bucket !== '')
|
1885 |
+
{
|
1886 |
+
if ($this->__dnsBucketName($this->bucket))
|
1887 |
+
{
|
1888 |
+
$this->headers['Host'] = $this->bucket.'.'.$this->endpoint;
|
1889 |
+
$this->resource = '/'.$this->bucket.$this->uri;
|
1890 |
+
}
|
1891 |
+
else
|
1892 |
+
{
|
1893 |
+
$this->headers['Host'] = $this->endpoint;
|
1894 |
+
$this->uri = $this->uri;
|
1895 |
+
if ($this->bucket !== '') $this->uri = '/'.$this->bucket.$this->uri;
|
1896 |
+
$this->bucket = '';
|
1897 |
+
$this->resource = $this->uri;
|
1898 |
+
}
|
1899 |
+
}
|
1900 |
+
else
|
1901 |
+
{
|
1902 |
+
$this->headers['Host'] = $this->endpoint;
|
1903 |
+
$this->resource = $this->uri;
|
1904 |
+
}
|
1905 |
+
|
1906 |
+
|
1907 |
+
$this->headers['Date'] = gmdate('D, d M Y H:i:s T');
|
1908 |
+
$this->response = new STDClass;
|
1909 |
+
$this->response->error = false;
|
1910 |
+
}
|
1911 |
+
|
1912 |
+
|
1913 |
+
/**
|
1914 |
+
* Set request parameter
|
1915 |
+
*
|
1916 |
+
* @param string $key Key
|
1917 |
+
* @param string $value Value
|
1918 |
+
* @return void
|
1919 |
+
*/
|
1920 |
+
public function setParameter($key, $value)
|
1921 |
+
{
|
1922 |
+
$this->parameters[$key] = $value;
|
1923 |
+
}
|
1924 |
+
|
1925 |
+
|
1926 |
+
/**
|
1927 |
+
* Set request header
|
1928 |
+
*
|
1929 |
+
* @param string $key Key
|
1930 |
+
* @param string $value Value
|
1931 |
+
* @return void
|
1932 |
+
*/
|
1933 |
+
public function setHeader($key, $value)
|
1934 |
+
{
|
1935 |
+
$this->headers[$key] = $value;
|
1936 |
+
}
|
1937 |
+
|
1938 |
+
|
1939 |
+
/**
|
1940 |
+
* Set x-amz-meta-* header
|
1941 |
+
*
|
1942 |
+
* @param string $key Key
|
1943 |
+
* @param string $value Value
|
1944 |
+
* @return void
|
1945 |
+
*/
|
1946 |
+
public function setAmzHeader($key, $value)
|
1947 |
+
{
|
1948 |
+
$this->amzHeaders[$key] = $value;
|
1949 |
+
}
|
1950 |
+
|
1951 |
+
|
1952 |
+
/**
|
1953 |
+
* Get the S3 response
|
1954 |
+
*
|
1955 |
+
* @return object | false
|
1956 |
+
*/
|
1957 |
+
public function getResponse()
|
1958 |
+
{
|
1959 |
+
$query = '';
|
1960 |
+
if (sizeof($this->parameters) > 0)
|
1961 |
+
{
|
1962 |
+
$query = substr($this->uri, -1) !== '?' ? '?' : '&';
|
1963 |
+
foreach ($this->parameters as $var => $value)
|
1964 |
+
if ($value == null || $value == '') $query .= $var.'&';
|
1965 |
+
else $query .= $var.'='.rawurlencode($value).'&';
|
1966 |
+
$query = substr($query, 0, -1);
|
1967 |
+
$this->uri .= $query;
|
1968 |
+
|
1969 |
+
if (array_key_exists('acl', $this->parameters) ||
|
1970 |
+
array_key_exists('location', $this->parameters) ||
|
1971 |
+
array_key_exists('torrent', $this->parameters) ||
|
1972 |
+
array_key_exists('logging', $this->parameters) ||
|
1973 |
+
array_key_exists('partNumber', $this->parameters) ||
|
1974 |
+
array_key_exists('uploads', $this->parameters) ||
|
1975 |
+
array_key_exists('uploadId', $this->parameters))
|
1976 |
+
$this->resource .= $query;
|
1977 |
+
}
|
1978 |
+
$url = (S3::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;
|
1979 |
+
|
1980 |
+
//var_dump('bucket: ' . $this->bucket, 'uri: ' . $this->uri, 'resource: ' . $this->resource, 'url: ' . $url);
|
1981 |
+
|
1982 |
+
// Basic setup
|
1983 |
+
$curl = curl_init();
|
1984 |
+
curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
|
1985 |
+
|
1986 |
+
if (S3::$useSSL)
|
1987 |
+
{
|
1988 |
+
// SSL Validation can now be optional for those with broken OpenSSL installations
|
1989 |
+
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, S3::$useSSLValidation ? 1 : 0);
|
1990 |
+
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, S3::$useSSLValidation ? 1 : 0);
|
1991 |
+
|
1992 |
+
if (S3::$sslKey !== null) curl_setopt($curl, CURLOPT_SSLKEY, S3::$sslKey);
|
1993 |
+
if (S3::$sslCert !== null) curl_setopt($curl, CURLOPT_SSLCERT, S3::$sslCert);
|
1994 |
+
if (S3::$sslCACert !== null) curl_setopt($curl, CURLOPT_CAINFO, S3::$sslCACert);
|
1995 |
+
}
|
1996 |
+
|
1997 |
+
curl_setopt($curl, CURLOPT_URL, $url);
|
1998 |
+
|
1999 |
+
if (S3::$proxy != null && isset(S3::$proxy['host']))
|
2000 |
+
{
|
2001 |
+
curl_setopt($curl, CURLOPT_PROXY, S3::$proxy['host']);
|
2002 |
+
curl_setopt($curl, CURLOPT_PROXYTYPE, S3::$proxy['type']);
|
2003 |
+
if (isset(S3::$proxy['user'], S3::$proxy['pass']) && S3::$proxy['user'] != null && S3::$proxy['pass'] != null)
|
2004 |
+
curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', S3::$proxy['user'], S3::$proxy['pass']));
|
2005 |
+
}
|
2006 |
+
|
2007 |
+
// Headers
|
2008 |
+
$headers = array(); $amz = array();
|
2009 |
+
foreach ($this->amzHeaders as $header => $value)
|
2010 |
+
if (strlen($value) > 0) $headers[] = $header.': '.$value;
|
2011 |
+
foreach ($this->headers as $header => $value)
|
2012 |
+
if (strlen($value) > 0) $headers[] = $header.': '.$value;
|
2013 |
+
|
2014 |
+
// Collect AMZ headers for signature
|
2015 |
+
foreach ($this->amzHeaders as $header => $value)
|
2016 |
+
if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
|
2017 |
+
|
2018 |
+
// AMZ headers must be sorted
|
2019 |
+
if (sizeof($amz) > 0)
|
2020 |
+
{
|
2021 |
+
//sort($amz);
|
2022 |
+
usort($amz, array(&$this, '__sortMetaHeadersCmp'));
|
2023 |
+
$amz = "\n".implode("\n", $amz);
|
2024 |
+
} else $amz = '';
|
2025 |
+
|
2026 |
+
if (S3::hasAuth())
|
2027 |
+
{
|
2028 |
+
// Authorization string (CloudFront stringToSign should only contain a date)
|
2029 |
+
if ($this->headers['Host'] == 'cloudfront.amazonaws.com')
|
2030 |
+
$headers[] = 'Authorization: ' . S3::__getSignature($this->headers['Date']);
|
2031 |
+
else
|
2032 |
+
{
|
2033 |
+
$headers[] = 'Authorization: ' . S3::__getSignature(
|
2034 |
+
$this->verb."\n".
|
2035 |
+
$this->headers['Content-MD5']."\n".
|
2036 |
+
$this->headers['Content-Type']."\n".
|
2037 |
+
$this->headers['Date'].$amz."\n".
|
2038 |
+
$this->resource
|
2039 |
+
);
|
2040 |
+
}
|
2041 |
+
}
|
2042 |
+
|
2043 |
+
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
2044 |
+
curl_setopt($curl, CURLOPT_HEADER, false);
|
2045 |
+
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
|
2046 |
+
curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
|
2047 |
+
curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
|
2048 |
+
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
2049 |
+
|
2050 |
+
// Request types
|
2051 |
+
switch ($this->verb)
|
2052 |
+
{
|
2053 |
+
case 'GET': break;
|
2054 |
+
case 'PUT': case 'POST':
|
2055 |
+
if ($this->fp !== false)
|
2056 |
+
{
|
2057 |
+
curl_setopt($curl, CURLOPT_PUT, true);
|
2058 |
+
curl_setopt($curl, CURLOPT_INFILE, $this->fp);
|
2059 |
+
if ($this->size >= 0)
|
2060 |
+
curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
|
2061 |
+
}
|
2062 |
+
elseif ($this->data !== false)
|
2063 |
+
{
|
2064 |
+
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
|
2065 |
+
curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
|
2066 |
+
}
|
2067 |
+
else
|
2068 |
+
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
|
2069 |
+
break;
|
2070 |
+
case 'HEAD':
|
2071 |
+
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
|
2072 |
+
curl_setopt($curl, CURLOPT_NOBODY, true);
|
2073 |
+
break;
|
2074 |
+
case 'DELETE':
|
2075 |
+
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
2076 |
+
break;
|
2077 |
+
default: break;
|
2078 |
+
}
|
2079 |
+
|
2080 |
+
// Execute, grab errors
|
2081 |
+
if (curl_exec($curl))
|
2082 |
+
$this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
2083 |
+
else
|
2084 |
+
$this->response->error = array(
|
2085 |
+
'code' => curl_errno($curl),
|
2086 |
+
'message' => curl_error($curl),
|
2087 |
+
'resource' => $this->resource
|
2088 |
+
);
|
2089 |
+
|
2090 |
+
@curl_close($curl);
|
2091 |
+
|
2092 |
+
// Parse body into XML
|
2093 |
+
if ($this->response->error === false && isset($this->response->headers['type']) &&
|
2094 |
+
$this->response->headers['type'] == 'application/xml' && isset($this->response->body))
|
2095 |
+
{
|
2096 |
+
$this->response->body = simplexml_load_string($this->response->body);
|
2097 |
+
|
2098 |
+
// Grab S3 errors
|
2099 |
+
if (!in_array($this->response->code, array(200, 204, 206)) &&
|
2100 |
+
isset($this->response->body->Code, $this->response->body->Message))
|
2101 |
+
{
|
2102 |
+
$this->response->error = array(
|
2103 |
+
'code' => (string)$this->response->body->Code,
|
2104 |
+
'message' => (string)$this->response->body->Message
|
2105 |
+
);
|
2106 |
+
if (isset($this->response->body->Resource))
|
2107 |
+
$this->response->error['resource'] = (string)$this->response->body->Resource;
|
2108 |
+
unset($this->response->body);
|
2109 |
+
}
|
2110 |
+
}
|
2111 |
+
|
2112 |
+
// Clean up file resources
|
2113 |
+
if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
|
2114 |
+
|
2115 |
+
return $this->response;
|
2116 |
+
}
|
2117 |
+
|
2118 |
+
/**
|
2119 |
+
* Sort compare for meta headers
|
2120 |
+
*
|
2121 |
+
* @internal Used to sort x-amz meta headers
|
2122 |
+
* @param string $a String A
|
2123 |
+
* @param string $b String B
|
2124 |
+
* @return integer
|
2125 |
+
*/
|
2126 |
+
private function __sortMetaHeadersCmp($a, $b)
|
2127 |
+
{
|
2128 |
+
$lenA = strpos($a, ':');
|
2129 |
+
$lenB = strpos($b, ':');
|
2130 |
+
$minLen = min($lenA, $lenB);
|
2131 |
+
$ncmp = strncmp($a, $b, $minLen);
|
2132 |
+
if ($lenA == $lenB) return $ncmp;
|
2133 |
+
if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
|
2134 |
+
return $ncmp;
|
2135 |
+
}
|
2136 |
+
|
2137 |
+
/**
|
2138 |
+
* CURL write callback
|
2139 |
+
*
|
2140 |
+
* @param resource &$curl CURL resource
|
2141 |
+
* @param string &$data Data
|
2142 |
+
* @return integer
|
2143 |
+
*/
|
2144 |
+
private function __responseWriteCallback(&$curl, &$data)
|
2145 |
+
{
|
2146 |
+
if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
|
2147 |
+
return fwrite($this->fp, $data);
|
2148 |
+
else
|
2149 |
+
$this->response->body .= $data;
|
2150 |
+
return strlen($data);
|
2151 |
+
}
|
2152 |
+
|
2153 |
+
|
2154 |
+
/**
|
2155 |
+
* Check DNS conformity
|
2156 |
+
*
|
2157 |
+
* @param string $bucket Bucket name
|
2158 |
+
* @return boolean
|
2159 |
+
*/
|
2160 |
+
private function __dnsBucketName($bucket)
|
2161 |
+
{
|
2162 |
+
if (strlen($bucket) > 63 || !preg_match("/[^a-z0-9\.-]/", $bucket)) return false;
|
2163 |
+
if (strstr($bucket, '-.') !== false) return false;
|
2164 |
+
if (strstr($bucket, '..') !== false) return false;
|
2165 |
+
if (!preg_match("/^[0-9a-z]/", $bucket)) return false;
|
2166 |
+
if (!preg_match("/[0-9a-z]$/", $bucket)) return false;
|
2167 |
+
return true;
|
2168 |
+
}
|
2169 |
+
|
2170 |
+
|
2171 |
+
/**
|
2172 |
+
* CURL header callback
|
2173 |
+
*
|
2174 |
+
* @param resource &$curl CURL resource
|
2175 |
+
* @param string &$data Data
|
2176 |
+
* @return integer
|
2177 |
+
*/
|
2178 |
+
private function __responseHeaderCallback(&$curl, &$data)
|
2179 |
+
{
|
2180 |
+
if (($strlen = strlen($data)) <= 2) return $strlen;
|
2181 |
+
if (substr($data, 0, 4) == 'HTTP')
|
2182 |
+
$this->response->code = (int)substr($data, 9, 3);
|
2183 |
+
else
|
2184 |
+
{
|
2185 |
+
$data = trim($data);
|
2186 |
+
if (strpos($data, ': ') === false) return $strlen;
|
2187 |
+
list($header, $value) = explode(': ', $data, 2);
|
2188 |
+
if ($header == 'Last-Modified')
|
2189 |
+
$this->response->headers['time'] = strtotime($value);
|
2190 |
+
elseif ($header == 'Content-Length')
|
2191 |
+
$this->response->headers['size'] = (int)$value;
|
2192 |
+
elseif ($header == 'Content-Type')
|
2193 |
+
$this->response->headers['type'] = $value;
|
2194 |
+
elseif ($header == 'ETag')
|
2195 |
+
$this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
|
2196 |
+
elseif (preg_match('/^x-amz-meta-.*$/', $header))
|
2197 |
+
$this->response->headers[$header] = $value;
|
2198 |
+
}
|
2199 |
+
return $strlen;
|
2200 |
+
}
|
2201 |
+
|
2202 |
+
}
|
2203 |
+
|
2204 |
+
class S3Exception extends Exception {
|
2205 |
+
function __construct($message, $file, $line, $code = 0)
|
2206 |
+
{
|
2207 |
+
parent::__construct($message, $code);
|
2208 |
+
$this->file = $file;
|
2209 |
+
$this->line = $line;
|
2210 |
+
}
|
2211 |
+
}
|
trunk/includes/class-gdocs.php
ADDED
@@ -0,0 +1,630 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// Originally contained: GDocs class
|
4 |
+
// Contains: UpdraftPlus_GDocs class (new methods added - could not extend, as too much was private)
|
5 |
+
|
6 |
+
// The following copyright notice is reproduced exactly as found in the "Backup" plugin (http://wordpress.org/extend/plugins/backup)
|
7 |
+
// It applies to the code apart from the methods we added (get_content_link, download_data)
|
8 |
+
|
9 |
+
/*
|
10 |
+
Copyright 2012 Sorin Iclanzan (email : sorin@hel.io)
|
11 |
+
|
12 |
+
This file is part of Backup.
|
13 |
+
|
14 |
+
Backup is free software: you can redistribute it and/or modify
|
15 |
+
it under the terms of the GNU General Public License as published by
|
16 |
+
the Free Software Foundation, either version 3 of the License, or
|
17 |
+
(at your option) any later version.
|
18 |
+
|
19 |
+
Backup is distributed in the hope that it will be useful,
|
20 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
21 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
22 |
+
GNU General Public License for more details.
|
23 |
+
|
24 |
+
You should have received a copy of the GNU General Public License
|
25 |
+
along with Backup. If not, see http://www.gnu.org/licenses/gpl.html.
|
26 |
+
*/
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Google Docs class
|
30 |
+
*
|
31 |
+
* Implements communication with Google Docs via the Google Documents List v3 API.
|
32 |
+
*
|
33 |
+
* Currently uploading, resuming and deleting resources is implemented as well as retrieving quotas.
|
34 |
+
*
|
35 |
+
* @uses WP_Error for storing error messages.
|
36 |
+
*/
|
37 |
+
class UpdraftPlus_GDocs {
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Stores the API version.
|
41 |
+
*
|
42 |
+
* @var string
|
43 |
+
* @access private
|
44 |
+
*/
|
45 |
+
private $gdata_version;
|
46 |
+
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Stores the base URL for the API requests.
|
50 |
+
*
|
51 |
+
* @var string
|
52 |
+
* @access private
|
53 |
+
*/
|
54 |
+
private $base_url;
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Stores the URL to the metadata feed.
|
58 |
+
* @var string
|
59 |
+
*/
|
60 |
+
private $metadata_url;
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Stores the token needed to access the API.
|
64 |
+
* @var string
|
65 |
+
* @access private
|
66 |
+
*/
|
67 |
+
private $token;
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Stores feeds to avoid requesting them again for successive use.
|
71 |
+
*
|
72 |
+
* @var array
|
73 |
+
* @access private
|
74 |
+
*/
|
75 |
+
private $cache = array();
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Files are uploadded in chunks of this size in bytes.
|
79 |
+
*
|
80 |
+
* @var integer
|
81 |
+
* @access private
|
82 |
+
*/
|
83 |
+
private $chunk_size;
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Stores whether or not to verify host SSL certificate.
|
87 |
+
*
|
88 |
+
* @var boolean
|
89 |
+
* @access private
|
90 |
+
*/
|
91 |
+
private $ssl_verify;
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Stores the number of seconds to wait for a response before timing out.
|
95 |
+
*
|
96 |
+
* @var integer
|
97 |
+
* @access private
|
98 |
+
*/
|
99 |
+
private $request_timeout;
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Stores the MIME type of the file that is uploading
|
103 |
+
*
|
104 |
+
* @var string
|
105 |
+
* @access private
|
106 |
+
*/
|
107 |
+
private $upload_file_type;
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Stores info about the file being uploaded.
|
111 |
+
*
|
112 |
+
* @var array
|
113 |
+
* @access private
|
114 |
+
*/
|
115 |
+
private $file;
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Stores the number of seconds the upload process is allowed to run
|
119 |
+
*
|
120 |
+
* @var integer
|
121 |
+
* @access private
|
122 |
+
*/
|
123 |
+
private $time_limit;
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Stores a timer for upload processes
|
127 |
+
*
|
128 |
+
* @var array
|
129 |
+
*/
|
130 |
+
private $timer;
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Constructor - Sets the access token.
|
134 |
+
*
|
135 |
+
* @param string $token Access token
|
136 |
+
*/
|
137 |
+
function __construct( $token ) {
|
138 |
+
$this->token = $token;
|
139 |
+
$this->gdata_version = '3.0';
|
140 |
+
$this->base_url = 'https://docs.google.com/feeds/default/private/full/';
|
141 |
+
$this->metadata_url = 'https://docs.google.com/feeds/metadata/default';
|
142 |
+
$this->chunk_size = 524288; // 512 KiB
|
143 |
+
$this->max_resume_attempts = 5;
|
144 |
+
$this->request_timeout = 5;
|
145 |
+
$this->ssl_verify = true;
|
146 |
+
$this->timer = array(
|
147 |
+
'start' => 0,
|
148 |
+
'stop' => 0,
|
149 |
+
'delta' => 0,
|
150 |
+
'cycle' => 0
|
151 |
+
);
|
152 |
+
$this->time_limit = @ini_get( 'max_execution_time' );
|
153 |
+
if ( ! $this->time_limit && '0' !== $this->time_limit )
|
154 |
+
$this->time_limit = 30; // default php max exec time
|
155 |
+
}
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Sets an option.
|
159 |
+
*
|
160 |
+
* @access public
|
161 |
+
* @param string $option The option to set.
|
162 |
+
* @param mixed $value The value to set the option to.
|
163 |
+
*/
|
164 |
+
public function set_option( $option, $value ) {
|
165 |
+
switch ( $option ) {
|
166 |
+
case 'chunk_size':
|
167 |
+
if ( floatval($value) >= 0.5 ) {
|
168 |
+
$this->chunk_size = floatval($value) * 1024 * 1024; // Transform from MiB to bytes
|
169 |
+
return true;
|
170 |
+
}
|
171 |
+
break;
|
172 |
+
case 'ssl_verify':
|
173 |
+
$this->ssl_verify = ( bool ) $value;
|
174 |
+
return true;
|
175 |
+
case 'request_timeout':
|
176 |
+
if ( intval( $value ) > 0 ) {
|
177 |
+
$this->request_timeout = intval( $value );
|
178 |
+
return true;
|
179 |
+
}
|
180 |
+
break;
|
181 |
+
case 'max_resume_attempts':
|
182 |
+
$this->max_resume_attempts = intval($value);
|
183 |
+
return true;
|
184 |
+
}
|
185 |
+
return false;
|
186 |
+
}
|
187 |
+
|
188 |
+
/**
|
189 |
+
* Gets an option.
|
190 |
+
*
|
191 |
+
* @access public
|
192 |
+
* @param string $option The option to get.
|
193 |
+
*/
|
194 |
+
public function get_option( $option ) {
|
195 |
+
switch ( $option ) {
|
196 |
+
case 'chunk_size':
|
197 |
+
return $this->chunk_size;
|
198 |
+
case 'ssl_verify':
|
199 |
+
return $this->ssl_verify;
|
200 |
+
case 'request_timeout':
|
201 |
+
return $this->request_timeout;
|
202 |
+
case 'max_resume_attempts':
|
203 |
+
return $this->max_resume_attempts;
|
204 |
+
}
|
205 |
+
return false;
|
206 |
+
}
|
207 |
+
|
208 |
+
/**
|
209 |
+
* This function makes all the requests to the API.
|
210 |
+
*
|
211 |
+
* @uses wp_remote_request
|
212 |
+
* @access private
|
213 |
+
* @param string $url The URL where the request is sent.
|
214 |
+
* @param string $method The HTTP request method, defaults to 'GET'.
|
215 |
+
* @param array $headers Headers to be sent.
|
216 |
+
* @param string $body The body of the request.
|
217 |
+
* @return mixed Returns an array containing the response on success or an instance of WP_Error on failure.
|
218 |
+
*/
|
219 |
+
private function request( $url, $method = 'GET', $headers = array(), $body = NULL ) {
|
220 |
+
$args = array(
|
221 |
+
'method' => $method,
|
222 |
+
'timeout' => $this->request_timeout,
|
223 |
+
'httpversion' => '1.1',
|
224 |
+
'redirection' => 0,
|
225 |
+
'sslverify' => $this->ssl_verify,
|
226 |
+
'headers' => array(
|
227 |
+
'Authorization' => 'Bearer ' . $this->token,
|
228 |
+
'GData-Version' => $this->gdata_version
|
229 |
+
)
|
230 |
+
);
|
231 |
+
if ( ! empty( $headers ) )
|
232 |
+
$args['headers'] = array_merge( $args['headers'], $headers );
|
233 |
+
if ( ! empty( $body ) )
|
234 |
+
$args['body'] = $body;
|
235 |
+
|
236 |
+
return wp_remote_request( $url, $args );
|
237 |
+
}
|
238 |
+
|
239 |
+
/**
|
240 |
+
* Returns the feed from a URL.
|
241 |
+
*
|
242 |
+
* @access public
|
243 |
+
* @param string $url The feed URL.
|
244 |
+
* @return mixed Returns the feed as an instance of SimpleXMLElement on success or an instance of WP_Error on failure.
|
245 |
+
*/
|
246 |
+
public function get_feed( $url ) {
|
247 |
+
if ( ! isset( $this->cache[$url] ) ) {
|
248 |
+
$result = $this->cache_feed( $url );
|
249 |
+
if ( is_wp_error( $result ) )
|
250 |
+
return $result;
|
251 |
+
}
|
252 |
+
|
253 |
+
return $this->cache[$url];
|
254 |
+
}
|
255 |
+
|
256 |
+
/**
|
257 |
+
* Requests a feed and adds it to cache.
|
258 |
+
*
|
259 |
+
* @access private
|
260 |
+
* @param string $url The feed URL.
|
261 |
+
* @return mixed Returns TRUE on success or an instance of WP_Error on failure.
|
262 |
+
*/
|
263 |
+
private function cache_feed( $url ) {
|
264 |
+
$result = $this->request( $url );
|
265 |
+
|
266 |
+
if ( is_wp_error( $result ) )
|
267 |
+
return $result;
|
268 |
+
|
269 |
+
if ( $result['response']['code'] == '200' ) {
|
270 |
+
$feed = @simplexml_load_string( $result['body'] );
|
271 |
+
if ( $feed === false )
|
272 |
+
return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
|
273 |
+
|
274 |
+
$this->cache[$url] = $feed;
|
275 |
+
return true;
|
276 |
+
}
|
277 |
+
return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'. Response body: " . $result['body'] );
|
278 |
+
|
279 |
+
}
|
280 |
+
|
281 |
+
/**
|
282 |
+
* Deletes a resource from Google Docs.
|
283 |
+
*
|
284 |
+
* @access public
|
285 |
+
* @param string $id Gdata Id of the resource to be deleted.
|
286 |
+
* @return mixed Returns TRUE on success, an instance of WP_Error on failure.
|
287 |
+
*/
|
288 |
+
public function delete_resource( $id ) {
|
289 |
+
$headers = array( 'If-Match' => '*' );
|
290 |
+
|
291 |
+
$result = $this->request( $this->base_url . $id . '?delete=true', 'DELETE', $headers );
|
292 |
+
if ( is_wp_error( $result ) )
|
293 |
+
return $result;
|
294 |
+
|
295 |
+
if ( $result['response']['code'] == '200' )
|
296 |
+
return true;
|
297 |
+
return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to delete resource '" . $id . "'. The resource might not have been deleted." );
|
298 |
+
}
|
299 |
+
|
300 |
+
/**
|
301 |
+
* Get the resumable-create-media link needed to upload files.
|
302 |
+
*
|
303 |
+
* @access private
|
304 |
+
* @param string $parent The Id of the folder where the upload is to be made. Default is empty string.
|
305 |
+
* @return mixed Returns a link on success, instance of WP_Error on failure.
|
306 |
+
*/
|
307 |
+
private function get_resumable_create_media_link( $parent = '' ) {
|
308 |
+
$url = $this->base_url;
|
309 |
+
if ( $parent )
|
310 |
+
$url .= $parent;
|
311 |
+
|
312 |
+
$feed = $this->get_feed( $url );
|
313 |
+
|
314 |
+
if ( is_wp_error( $feed ) )
|
315 |
+
return $feed;
|
316 |
+
|
317 |
+
foreach ( $feed->link as $link )
|
318 |
+
if ( $link['rel'] == 'http://schemas.google.com/g/2005#resumable-create-media' )
|
319 |
+
return ( string ) $link['href'];
|
320 |
+
return new WP_Error( 'not_found', "The 'resumable_create_media_link' was not found in feed." );
|
321 |
+
}
|
322 |
+
|
323 |
+
/**
|
324 |
+
* Get used quota in bytes.
|
325 |
+
*
|
326 |
+
* @access public
|
327 |
+
* @return mixed Returns the number of bytes used in Google Docs on success or an instance of WP_Error on failure.
|
328 |
+
*/
|
329 |
+
public function get_quota_used() {
|
330 |
+
$feed = $this->get_feed( $this->metadata_url );
|
331 |
+
if ( is_wp_error( $feed ) )
|
332 |
+
return $feed;
|
333 |
+
return ( string ) $feed->children( "http://schemas.google.com/g/2005" )->quotaBytesUsed;
|
334 |
+
}
|
335 |
+
|
336 |
+
/**
|
337 |
+
* Get total quota in bytes.
|
338 |
+
*
|
339 |
+
* @access public
|
340 |
+
* @return string|WP_Error Returns the total quota in bytes in Google Docs on success or an instance of WP_Error on failure.
|
341 |
+
*/
|
342 |
+
public function get_quota_total() {
|
343 |
+
$feed = $this->get_feed( $this->metadata_url );
|
344 |
+
if ( is_wp_error( $feed ) )
|
345 |
+
return $feed;
|
346 |
+
return ( string ) $feed->children( "http://schemas.google.com/g/2005" )->quotaBytesTotal;
|
347 |
+
}
|
348 |
+
|
349 |
+
/**
|
350 |
+
* Function to prepare a file to be uploaded to Google Docs.
|
351 |
+
*
|
352 |
+
* The function requests a URI for uploading and prepends a new element in the resume_list array.
|
353 |
+
*
|
354 |
+
* @uses wp_check_filetype
|
355 |
+
* @access public
|
356 |
+
*
|
357 |
+
* @param string $file Path to the file that is to be uploaded.
|
358 |
+
* @param string $title Title to be given to the file.
|
359 |
+
* @param string $parent ID of the folder in which to upload the file.
|
360 |
+
* @param string $type MIME type of the file to be uploaded. The function tries to identify the type if it is omitted.
|
361 |
+
* @return mixed Returns the URI where to upload on success, an instance of WP_Error on failure.
|
362 |
+
*/
|
363 |
+
public function prepare_upload( $file, $title, $parent = '', $type = '' ) {
|
364 |
+
if ( ! @is_readable( $file ) )
|
365 |
+
return new WP_Error( 'not_file', "The path '" . $file . "' does not point to a readable file." );
|
366 |
+
|
367 |
+
// If a mime type wasn't passed try to guess it from the extension based on the WordPress allowed mime types
|
368 |
+
if ( empty( $type ) ) {
|
369 |
+
$check = wp_check_filetype( $file );
|
370 |
+
$this->upload_file_type = $type = $check['type'];
|
371 |
+
}
|
372 |
+
|
373 |
+
$size = filesize( $file );
|
374 |
+
|
375 |
+
$body = '<?xml version=\'1.0\' encoding=\'UTF-8\'?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007"><category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/docs/2007#file"/><title>' . $title . '</title></entry>';
|
376 |
+
|
377 |
+
$headers = array(
|
378 |
+
'Content-Type' => 'application/atom+xml',
|
379 |
+
'X-Upload-Content-Type' => $type,
|
380 |
+
'X-Upload-Content-Length' => (string) $size
|
381 |
+
);
|
382 |
+
|
383 |
+
$url = $this->get_resumable_create_media_link( $parent );
|
384 |
+
|
385 |
+
if ( is_wp_error( $url ) )
|
386 |
+
return $url;
|
387 |
+
|
388 |
+
$url .= '?convert=false'; // needed to upload a file
|
389 |
+
|
390 |
+
$result = $this->request( $url, 'POST', $headers, $body );
|
391 |
+
|
392 |
+
if ( is_wp_error( $result ) )
|
393 |
+
return $result;
|
394 |
+
|
395 |
+
if ( $result['response']['code'] != '200' )
|
396 |
+
return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'." );
|
397 |
+
|
398 |
+
$this->file = array(
|
399 |
+
'path' => $file,
|
400 |
+
'size' => $size,
|
401 |
+
'location' => $result['headers']['location'],
|
402 |
+
'pointer' => 0
|
403 |
+
);
|
404 |
+
|
405 |
+
// Open file for reading.
|
406 |
+
if ( !$this->file['handle'] = fopen( $file, "rb" ) )
|
407 |
+
return new WP_Error( 'open_error', "Could not open file '" . $file . "' for reading." );
|
408 |
+
|
409 |
+
// Start timer
|
410 |
+
$this->timer['start'] = microtime( true );
|
411 |
+
|
412 |
+
return $result['headers']['location'];
|
413 |
+
}
|
414 |
+
|
415 |
+
|
416 |
+
/**
|
417 |
+
* Resume an upload.
|
418 |
+
*
|
419 |
+
* @access public
|
420 |
+
* @param string $file Path to the file which needs to be uploaded
|
421 |
+
* @param string $location URI where to upload the file
|
422 |
+
* @return mixed Returns the next location URI on success, an instance of WP_Error on failure.
|
423 |
+
*/
|
424 |
+
public function resume_upload( $file, $location ) {
|
425 |
+
|
426 |
+
if ( ! @is_readable( $file ) )
|
427 |
+
return new WP_Error( 'not_file', "The path '" . $this->resume_list[$id]['path'] . "' does not point to a readable file. Upload has been canceled." );
|
428 |
+
|
429 |
+
$size = filesize( $file );
|
430 |
+
|
431 |
+
$headers = array( 'Content-Range' => 'bytes */' . $size );
|
432 |
+
$result = $this->request( $location, 'PUT', $headers );
|
433 |
+
if( is_wp_error( $result ) )
|
434 |
+
return $result;
|
435 |
+
|
436 |
+
if ( '308' != $result['response']['code'] ) {
|
437 |
+
if ( '201' == $result['response']['code'] ) {
|
438 |
+
$feed = @simplexml_load_string( $result['body'] );
|
439 |
+
if ( $feed === false )
|
440 |
+
return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
|
441 |
+
$this->file['id'] = substr( ( string ) $feed->children( "http://schemas.google.com/g/2005" )->resourceId, 5 );
|
442 |
+
return true;
|
443 |
+
}
|
444 |
+
return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to resume the upload of file '" . $file . "'." );
|
445 |
+
}
|
446 |
+
if( isset( $result['headers']['location'] ) )
|
447 |
+
$location = $result['headers']['location'];
|
448 |
+
$pointer = $this->pointer( $result['headers']['range'] );
|
449 |
+
|
450 |
+
$this->file = array(
|
451 |
+
'path' => $file,
|
452 |
+
'size' => $size,
|
453 |
+
'location' => $location,
|
454 |
+
'pointer' => $pointer
|
455 |
+
);
|
456 |
+
|
457 |
+
// Open file for reading.
|
458 |
+
if ( !$this->file['handle'] = fopen( $file, "rb" ) )
|
459 |
+
return new WP_Error( 'open_error', "Could not open file '" . $file . "' for reading." );
|
460 |
+
|
461 |
+
// Start timer
|
462 |
+
$this->timer['start'] = microtime( true );
|
463 |
+
|
464 |
+
return $location;
|
465 |
+
}
|
466 |
+
|
467 |
+
/**
|
468 |
+
* Work out where the file pointer should be from the range header.
|
469 |
+
*
|
470 |
+
* @access private
|
471 |
+
* @param string $range The range HTTP response header.
|
472 |
+
* @return integer Returns the number of bytes that have been uploaded.
|
473 |
+
*/
|
474 |
+
private function pointer( $range ) {
|
475 |
+
return intval(substr( $range, strpos( $range, '-' ) + 1 )) + 1;
|
476 |
+
}
|
477 |
+
|
478 |
+
/**
|
479 |
+
* Uploads a chunk of the file being uploaded.
|
480 |
+
*
|
481 |
+
* @access public
|
482 |
+
* @return mixed Returns TRUE if the chunk was uploaded successfully;
|
483 |
+
* returns Google Docs resource ID if the file upload finished;
|
484 |
+
* returns an instance of WP_Error on failure.
|
485 |
+
*/
|
486 |
+
public function upload_chunk() {
|
487 |
+
if ( !isset( $this->file['handle'] ) )
|
488 |
+
return new WP_Error( "no_upload", "There is no file being uploaded." );
|
489 |
+
|
490 |
+
$cycle_start = microtime( true );
|
491 |
+
fseek( $this->file['handle'], $this->file['pointer'] );
|
492 |
+
$chunk = @fread( $this->file['handle'], $this->chunk_size );
|
493 |
+
if ( false === $chunk ) {
|
494 |
+
$is_file = (is_file($this->file['path'])) ? 1 : 0;
|
495 |
+
$is_readable = (is_readable($this->file['path'])) ? 1 : 0;
|
496 |
+
return new WP_Error( 'read_error', "Failed to read from file (path: ".$this->file['path'].", size: ".$this->file['size'].", pointer: ".$this->file['pointer'].", is_file: $is_file, is_readable: $is_readable)");
|
497 |
+
}
|
498 |
+
|
499 |
+
$chunk_size = strlen( $chunk );
|
500 |
+
$bytes = 'bytes ' . (string)$this->file['pointer'] . '-' . (string)($this->file['pointer'] + $chunk_size - 1) . '/' . (string)$this->file['size'];
|
501 |
+
|
502 |
+
$headers = array( 'Content-Range' => $bytes );
|
503 |
+
|
504 |
+
$result = $this->request( $this->file['location'], 'PUT', $headers, $chunk );
|
505 |
+
|
506 |
+
if ( !is_wp_error( $result ) )
|
507 |
+
if ( '308' == $result['response']['code'] ) {
|
508 |
+
if ( isset( $result['headers']['range'] ) )
|
509 |
+
$this->file['pointer'] = $this->pointer( $result['headers']['range'] );
|
510 |
+
else
|
511 |
+
$this->file['pointer'] += $chunk_size;
|
512 |
+
|
513 |
+
if ( isset( $result['headers']['location'] ) )
|
514 |
+
$this->file['location'] = $result['headers']['location'];
|
515 |
+
|
516 |
+
if ( $this->timer['cycle'] )
|
517 |
+
$this->timer['cycle'] = ( microtime( true ) - $cycle_start + $this->timer['cycle'] ) / 2;
|
518 |
+
else
|
519 |
+
$this->timer['cycle'] = microtime(true) - $cycle_start;
|
520 |
+
|
521 |
+
return $this->file['location'];
|
522 |
+
}
|
523 |
+
elseif ( '201' == $result['response']['code'] ) {
|
524 |
+
fclose( $this->file['handle'] );
|
525 |
+
|
526 |
+
// Stop timer
|
527 |
+
$this->timer['stop'] = microtime(true);
|
528 |
+
$this->timer['delta'] = $this->timer['stop'] - $this->timer['start'];
|
529 |
+
|
530 |
+
if ( $this->timer['cycle'] )
|
531 |
+
$this->timer['cycle'] = ( microtime( true ) - $cycle_start + $this->timer['cycle'] ) / 2;
|
532 |
+
else
|
533 |
+
$this->timer['cycle'] = microtime(true) - $cycle_start;
|
534 |
+
|
535 |
+
$this->file['pointer'] = $this->file['size'];
|
536 |
+
|
537 |
+
$feed = @simplexml_load_string( $result['body'] );
|
538 |
+
if ( $feed === false )
|
539 |
+
return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
|
540 |
+
$this->file['id'] = substr( ( string ) $feed->children( "http://schemas.google.com/g/2005" )->resourceId, 5 );
|
541 |
+
return true;
|
542 |
+
}
|
543 |
+
|
544 |
+
// If we got to this point it means the upload wasn't successful.
|
545 |
+
fclose( $this->file['handle'] );
|
546 |
+
if ( is_wp_error( $result ) )
|
547 |
+
return $result;
|
548 |
+
return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to upload a file chunk." );
|
549 |
+
}
|
550 |
+
|
551 |
+
/**
|
552 |
+
* Get the resource ID of the most recent uploaded file.
|
553 |
+
*
|
554 |
+
* @access public
|
555 |
+
* @return string The ID of the uploaded file or an empty string.
|
556 |
+
*/
|
557 |
+
public function get_file_id() {
|
558 |
+
if ( isset( $this->file['id'] ) )
|
559 |
+
return $this->file['id'];
|
560 |
+
return '';
|
561 |
+
}
|
562 |
+
|
563 |
+
/**
|
564 |
+
* Get the upload speed recorded on the last upload performed.
|
565 |
+
*
|
566 |
+
* @access public
|
567 |
+
* @return integer Returns the upload speed in bytes/second or 0.
|
568 |
+
*/
|
569 |
+
public function get_upload_speed() {
|
570 |
+
if ( $this->timer['cycle'] > 0 )
|
571 |
+
if ( $this->file['size'] < $this->chunk_size )
|
572 |
+
return $this->file['size'] / $this->timer['cycle'];
|
573 |
+
else
|
574 |
+
return $this->chunk_size / $this->timer['cycle'];
|
575 |
+
return 0;
|
576 |
+
}
|
577 |
+
|
578 |
+
/**
|
579 |
+
* Get the percentage of the file uploaded.
|
580 |
+
*
|
581 |
+
* @return float Returns a percentage on success, 0 on failure.
|
582 |
+
*/
|
583 |
+
public function get_upload_percentage() {
|
584 |
+
if ( isset( $this->file['path'] ) )
|
585 |
+
return $this->file['pointer'] * 100 / $this->file['size'];
|
586 |
+
return 0;
|
587 |
+
}
|
588 |
+
|
589 |
+
/**
|
590 |
+
* Returns the time taken for an upload to complete.
|
591 |
+
*
|
592 |
+
* @access public
|
593 |
+
* @return float Returns the number of seconds the last upload took to complete, 0 if there has been no completed upload.
|
594 |
+
*/
|
595 |
+
public function time_taken() {
|
596 |
+
return $this->timer['delta'];
|
597 |
+
}
|
598 |
+
|
599 |
+
public function get_content_link( $id, $title ) {
|
600 |
+
|
601 |
+
$feed = $this->get_feed($this->base_url . $id);
|
602 |
+
|
603 |
+
if ( is_wp_error( $feed ) )
|
604 |
+
return $feed;
|
605 |
+
|
606 |
+
if ( $feed->title != $title )
|
607 |
+
return new WP_Error( 'bad_response', "Unexpected response");
|
608 |
+
|
609 |
+
$att = $feed->content->attributes();
|
610 |
+
return $att['src'];
|
611 |
+
|
612 |
+
}
|
613 |
+
|
614 |
+
public function download_data( $link, $saveas ) {
|
615 |
+
|
616 |
+
$result = $this->request( $link );
|
617 |
+
|
618 |
+
if ( is_wp_error( $result ) )
|
619 |
+
return $result;
|
620 |
+
|
621 |
+
if ( $result['response']['code'] != '200' )
|
622 |
+
return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'." );
|
623 |
+
|
624 |
+
file_put_contents($saveas, $result['body']);
|
625 |
+
|
626 |
+
}
|
627 |
+
|
628 |
+
|
629 |
+
|
630 |
+
}
|
trunk/includes/ftp.class.php
ADDED
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/* from http://www.solutionbot.com/2009/01/02/php-ftp-class/ */
|
3 |
+
class ftp_wrapper
|
4 |
+
{
|
5 |
+
private $conn_id;
|
6 |
+
private $host;
|
7 |
+
private $username;
|
8 |
+
private $password;
|
9 |
+
private $port;
|
10 |
+
public $timeout = 90;
|
11 |
+
public $passive = false;
|
12 |
+
public $ssl = false;
|
13 |
+
public $system_type = '';
|
14 |
+
|
15 |
+
|
16 |
+
public function __construct($host, $username, $password, $port = 21)
|
17 |
+
{
|
18 |
+
$this->host = $host;
|
19 |
+
$this->username = $username;
|
20 |
+
$this->password = $password;
|
21 |
+
$this->port = $port;
|
22 |
+
}
|
23 |
+
|
24 |
+
public function connect()
|
25 |
+
{
|
26 |
+
if ($this->ssl == false)
|
27 |
+
{
|
28 |
+
$this->conn_id = ftp_connect($this->host, $this->port);
|
29 |
+
}
|
30 |
+
else
|
31 |
+
{
|
32 |
+
if (function_exists('ftp_ssl_connect'))
|
33 |
+
{
|
34 |
+
$this->conn_id = ftp_ssl_connect($this->host, $this->port);
|
35 |
+
}
|
36 |
+
else
|
37 |
+
{
|
38 |
+
return false;
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
$result = ftp_login($this->conn_id, $this->username, $this->password);
|
43 |
+
|
44 |
+
if ($result == true)
|
45 |
+
{
|
46 |
+
ftp_set_option($this->conn_id, FTP_TIMEOUT_SEC, $this->timeout);
|
47 |
+
|
48 |
+
if ($this->passive == true)
|
49 |
+
{
|
50 |
+
ftp_pasv($this->conn_id, true);
|
51 |
+
}
|
52 |
+
else
|
53 |
+
{
|
54 |
+
ftp_pasv($this->conn_id, false);
|
55 |
+
}
|
56 |
+
|
57 |
+
$this->system_type = ftp_systype($this->conn_id);
|
58 |
+
|
59 |
+
return true;
|
60 |
+
}
|
61 |
+
else
|
62 |
+
{
|
63 |
+
return false;
|
64 |
+
}
|
65 |
+
}
|
66 |
+
|
67 |
+
public function put($local_file_path, $remote_file_path, $mode = FTP_ASCII)
|
68 |
+
{
|
69 |
+
if (ftp_put($this->conn_id, $remote_file_path, $local_file_path, $mode))
|
70 |
+
{
|
71 |
+
return true;
|
72 |
+
}
|
73 |
+
else
|
74 |
+
{
|
75 |
+
return false;
|
76 |
+
}
|
77 |
+
}
|
78 |
+
|
79 |
+
public function get($local_file_path, $remote_file_path, $mode = FTP_ASCII)
|
80 |
+
{
|
81 |
+
if (ftp_get($this->conn_id, $local_file_path, $remote_file_path, $mode))
|
82 |
+
{
|
83 |
+
return true;
|
84 |
+
}
|
85 |
+
else
|
86 |
+
{
|
87 |
+
return false;
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
public function chmod($permissions, $remote_filename)
|
92 |
+
{
|
93 |
+
if ($this->is_octal($permissions))
|
94 |
+
{
|
95 |
+
$result = ftp_chmod($this->conn_id, $permissions, $remote_filename);
|
96 |
+
if ($result)
|
97 |
+
{
|
98 |
+
return true;
|
99 |
+
}
|
100 |
+
else
|
101 |
+
{
|
102 |
+
return false;
|
103 |
+
}
|
104 |
+
}
|
105 |
+
else
|
106 |
+
{
|
107 |
+
throw new Exception('$permissions must be an octal number');
|
108 |
+
}
|
109 |
+
}
|
110 |
+
|
111 |
+
public function chdir($directory)
|
112 |
+
{
|
113 |
+
ftp_chdir($this->conn_id, $directory);
|
114 |
+
}
|
115 |
+
|
116 |
+
public function delete($remote_file_path)
|
117 |
+
{
|
118 |
+
if (ftp_delete($this->conn_id, $remote_file_path))
|
119 |
+
{
|
120 |
+
return true;
|
121 |
+
}
|
122 |
+
else
|
123 |
+
{
|
124 |
+
return false;
|
125 |
+
}
|
126 |
+
}
|
127 |
+
|
128 |
+
public function make_dir($directory)
|
129 |
+
{
|
130 |
+
if (ftp_mkdir($this->conn_id, $directory))
|
131 |
+
{
|
132 |
+
return true;
|
133 |
+
}
|
134 |
+
else
|
135 |
+
{
|
136 |
+
return false;
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
public function rename($old_name, $new_name)
|
141 |
+
{
|
142 |
+
if (ftp_rename($this->conn_id, $old_name, $new_name))
|
143 |
+
{
|
144 |
+
return true;
|
145 |
+
}
|
146 |
+
else
|
147 |
+
{
|
148 |
+
return false;
|
149 |
+
}
|
150 |
+
}
|
151 |
+
|
152 |
+
public function remove_dir($directory)
|
153 |
+
{
|
154 |
+
if (ftp_rmdir($this->conn_id, $directory))
|
155 |
+
{
|
156 |
+
return true;
|
157 |
+
}
|
158 |
+
else
|
159 |
+
{
|
160 |
+
return false;
|
161 |
+
}
|
162 |
+
}
|
163 |
+
|
164 |
+
public function dir_list($directory)
|
165 |
+
{
|
166 |
+
$contents = ftp_nlist($this->conn_id, $directory);
|
167 |
+
return $contents;
|
168 |
+
}
|
169 |
+
|
170 |
+
public function cdup()
|
171 |
+
{
|
172 |
+
ftp_cdup($this->conn_id);
|
173 |
+
}
|
174 |
+
|
175 |
+
public function current_dir()
|
176 |
+
{
|
177 |
+
return ftp_pwd($this->conn_id);
|
178 |
+
}
|
179 |
+
|
180 |
+
private function is_octal($i)
|
181 |
+
{
|
182 |
+
return decoct(octdec($i)) == $i;
|
183 |
+
}
|
184 |
+
|
185 |
+
public function __destruct()
|
186 |
+
{
|
187 |
+
if ($this->conn_id)
|
188 |
+
{
|
189 |
+
ftp_close($this->conn_id);
|
190 |
+
}
|
191 |
+
}
|
192 |
+
}
|
193 |
+
?>
|
trunk/includes/updraft-restorer.php
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
class Updraft_Restorer extends WP_Upgrader {
|
3 |
+
|
4 |
+
function backup_strings() {
|
5 |
+
$this->strings['no_package'] = __('Backup file not available.');
|
6 |
+
$this->strings['unpack_package'] = __('Unpacking backup...');
|
7 |
+
$this->strings['moving_old'] = __('Moving old directory out of the way...');
|
8 |
+
$this->strings['moving_backup'] = __('Moving unpackaged backup in place...');
|
9 |
+
$this->strings['cleaning_up'] = __('Cleaning up detritus...');
|
10 |
+
$this->strings['old_move_failed'] = __('Could not move old dir out of the way.');
|
11 |
+
$this->strings['new_move_failed'] = __('Could not move new dir into place. Check your wp-content/upgrade folder.');
|
12 |
+
$this->strings['delete_failed'] = __('Failed to delete working directory after restoring.');
|
13 |
+
}
|
14 |
+
|
15 |
+
function restore_backup($backup_file,$type) {
|
16 |
+
global $wp_filesystem;
|
17 |
+
$this->init();
|
18 |
+
$this->backup_strings();
|
19 |
+
|
20 |
+
$res = $this->fs_connect( array(ABSPATH, WP_CONTENT_DIR) );
|
21 |
+
if(!$res) {
|
22 |
+
exit;
|
23 |
+
}
|
24 |
+
|
25 |
+
$wp_dir = trailingslashit($wp_filesystem->abspath());
|
26 |
+
|
27 |
+
$download = $this->download_package( $backup_file );
|
28 |
+
if ( is_wp_error($download) )
|
29 |
+
return $download;
|
30 |
+
|
31 |
+
$delete = (get_option('updraft_delete_local'))?true:false;
|
32 |
+
|
33 |
+
$working_dir = $this->unpack_package( $download , $delete );
|
34 |
+
if ( is_wp_error($working_dir) )
|
35 |
+
return $working_dir;
|
36 |
+
|
37 |
+
if ($type == "others" ) {
|
38 |
+
|
39 |
+
// In this special case, the backup contents are not in a folder, so it is not simply a case of moving the folder around, but rather looping over all that we find
|
40 |
+
|
41 |
+
$upgrade_files = $wp_filesystem->dirlist($working_dir);
|
42 |
+
if ( !empty($upgrade_files) ) {
|
43 |
+
foreach ( $upgrade_files as $filestruc ) {
|
44 |
+
$file = $filestruc['name'];
|
45 |
+
# Sanity check (should not be possible as these were excluded at backup time)
|
46 |
+
if ($file != "plugins" && $file != "themes" && $file != "uploads" && $file != "upgrade") {
|
47 |
+
# First, move the existing one, if necessary (may not be present)
|
48 |
+
if ($wp_filesystem->exists($wp_dir . "wp-content/$file")) {
|
49 |
+
if ( !$wp_filesystem->move($wp_dir . "wp-content/$file", $wp_dir . "wp-content/$file-old", true) ) {
|
50 |
+
return new WP_Error('old_move_failed', $this->strings['old_move_failed']);
|
51 |
+
}
|
52 |
+
}
|
53 |
+
# Now, move in the new one
|
54 |
+
if ( !$wp_filesystem->move($working_dir . "/$file", $wp_dir . "wp-content/$file", true) ) {
|
55 |
+
return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
|
56 |
+
}
|
57 |
+
}
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
} else {
|
62 |
+
|
63 |
+
show_message($this->strings['moving_old']);
|
64 |
+
if ( !$wp_filesystem->move($wp_dir . "wp-content/$type", $wp_dir . "wp-content/$type-old", true) ) {
|
65 |
+
return new WP_Error('old_move_failed', $this->strings['old_move_failed']);
|
66 |
+
}
|
67 |
+
|
68 |
+
show_message($this->strings['moving_backup']);
|
69 |
+
if ( !$wp_filesystem->move($working_dir . "/$type", $wp_dir . "wp-content/$type", true) ) {
|
70 |
+
return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
|
71 |
+
}
|
72 |
+
|
73 |
+
}
|
74 |
+
|
75 |
+
show_message($this->strings['cleaning_up']);
|
76 |
+
if ( !$wp_filesystem->delete($working_dir) ) {
|
77 |
+
return new WP_Error('delete_failed', $this->strings['delete_failed']);
|
78 |
+
}
|
79 |
+
|
80 |
+
|
81 |
+
switch($type) {
|
82 |
+
case 'uploads':
|
83 |
+
$wp_filesystem->chmod($wp_dir . "wp-content/$type", 0777, true);
|
84 |
+
break;
|
85 |
+
default:
|
86 |
+
$wp_filesystem->chmod($wp_dir . "wp-content/$type", FS_CHMOD_DIR);
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
}
|
91 |
+
?>
|
trunk/readme.txt
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== UpdraftPlus Backup ===
|
2 |
+
Contributors: David Anderson
|
3 |
+
Tags: backup, restore, database, cloud, amazon, s3, Amazon S3, google drive, google, gdrive, ftp, cloud, updraft, back up
|
4 |
+
Requires at least: 3.2
|
5 |
+
Tested up to: 3.5
|
6 |
+
Stable tag: 1.0.8
|
7 |
+
Donate link: http://david.dw-perspective.org.uk/donate
|
8 |
+
License: GPLv3 or later
|
9 |
+
|
10 |
+
== Upgrade Notice ==
|
11 |
+
Fixed bug with Google Drive uploads
|
12 |
+
|
13 |
+
== Description ==
|
14 |
+
|
15 |
+
UpdraftPlus simplifies backups (and restoration). Backup into the cloud (Amazon S3, Google Drive, FTP, and email) and restore with a single click. Backups of files and database can have separate schedules.
|
16 |
+
|
17 |
+
* Supports backups to Amazon S3, Google Drive, FTP, email
|
18 |
+
* One-click restore
|
19 |
+
* Backup automatically on a repeating schedule
|
20 |
+
* Files and databases can have separate schedules
|
21 |
+
* Failed uploads are automatically resumed/retried
|
22 |
+
* Select which files to backup (plugins, themes, content, other)
|
23 |
+
* Databases can be encrypted for security
|
24 |
+
* Debug mode that gives full logging
|
25 |
+
|
26 |
+
== Installation ==
|
27 |
+
|
28 |
+
Standard WordPress plugin installation:
|
29 |
+
|
30 |
+
1. Search for "UpdraftPlus" in your site's admin area plugin page
|
31 |
+
2. Press 'Install'
|
32 |
+
3. Go to the options page and go through the questions there
|
33 |
+
|
34 |
+
== Frequently Asked Questions ==
|
35 |
+
|
36 |
+
= How is this better than the original Updraft? =
|
37 |
+
|
38 |
+
You can check the changelog for changes; but the original Updraft, before I forked it, had three major problems. Firstly, it only backed up WP core tables from the database; if any of your plugins stored data in extra tables, then they were not backed up. Secondly, it only backed up your plugins/themes/uploads and not any further directories inside wp-content that other plugins might have created. Thirdly, the database backup did not include charset information, which meant that you needed to know some SQL wizardry to actually be able to use the backup. I made UpdraftPlus out of my experience of trying to back up several sites with Updraft. Then, I added encryption for the database file for extra peace of mind, and future-proofed by getting rid of some deprecated aspects.
|
39 |
+
|
40 |
+
= I like automating WordPress, and using the command-line. Please tell me more. =
|
41 |
+
|
42 |
+
That's very good of you, thank you. You are looking for WordShell, <a href="http://wordshell.net">http://wordshell.net</a>.
|
43 |
+
|
44 |
+
= I found a bug. What do I do? =
|
45 |
+
|
46 |
+
Contact me! This is a complex plugin and the only way I can ensure it's robust is to get bug reports and fix the problems that crop up. Please turn on debugging mode (in the UpdraftPlus options page) and then try again, and after that send me the log if you can find it (it is in the directory wp-content/updraft, so FTP in and look for it there). If you cannot find the log, then I may not be able to help, but you can try - include as much information as you can when reporting (PHP version, your blog's site, the error you saw and how you got to the page that caused it, etcetera). If you can send a patch, that's even better.
|
47 |
+
|
48 |
+
= Some of my files have uploaded into my cloud storage, but not others. =
|
49 |
+
|
50 |
+
From version 0.9.0, UpdraftPlus features a resumption feature - if you wait 5 minutes and visit a page on your site, then it should re-try not-yet-uploaded files. If that fails, then turn on debugging and paste the debug log (log in via FTP, and look in wp-content/updraft) into the support forum.
|
51 |
+
|
52 |
+
= I want to restore, but have failed to do so from the WP Admin console =
|
53 |
+
|
54 |
+
That's no problem. If you have your backed files, then you simply need to unzip them into the right places. UpdraftPlus does not back up the WordPress core - you can just get a fresh copy of that from www.wordpress.org. After installing that, then unzip the zip files for your uploads, themes and plugins back into the wp-content directory. Then re-install the database (e.g. by running it through PHPMyAdmin). Please don't ask me how to carry out these steps - they are basic operations which you can hire any of hundreds of thousands of people to show you how to do.
|
55 |
+
|
56 |
+
= Anything essential to know? =
|
57 |
+
|
58 |
+
After you have set up UpdraftPlus, you must check that your backups are taking place successfully. WordPress is a complex piece of software that runs in many situations. Don't wait until you need your backups before you find out that they never worked in the first place. Remember, there's no warranty and no guarantees - this is free software.
|
59 |
+
|
60 |
+
= What exactly does UpdraftPlus back up ? =
|
61 |
+
|
62 |
+
Unless you disable any of these, it will back up your database (all tables which have been prefixed with the prefix for this WordPress installation, both core tables and extra ones added by plugins), your plugins folder, your themes folder, your uploads folder and any extra folders that other plugins have created in the WordPress content directory.
|
63 |
+
|
64 |
+
= What does UpdraftPlus not back up ? =
|
65 |
+
|
66 |
+
It does not back up WordPress core (since you can always get another copy of this from wordpress.org), and does not back up any extra files which you have added outside of the WordPress content directory (files which, by their nature, are unknown to WordPress). By default the WordPress content directory is "wp-content" in your WordPress root. It will not back up database tables which do not have the WordPress prefix (i.e. database tables from other applications but sharing a database with WordPress).
|
67 |
+
|
68 |
+
= Any known bugs ? =
|
69 |
+
Not a bug as such, but one major issue to be aware of is that backups of very large sites (lots of uploaded media) can fail due to timing out. This depends on how many seconds your web host allows a PHP process to run. With such sites, you need to use Amazon S3, which UpdraftPlus supports (since 0.9.20) or Google Drive (since 0.9.21) with chunked, resumable uploads. Other backup methods have code (since 0.9.0) to retry failed uploads of an archive, but the upload cannot be chunked, so if an archive is enormous (i.e. cannot be completely uploaded in the time that PHP is allowed for running on your web host) it cannot work.
|
70 |
+
|
71 |
+
= I encrypted my database - how do I decrypt it? =
|
72 |
+
|
73 |
+
If you have the encryption key entered in your settings and you are restoring from the settings interface, then it will automatically decrypt. Otherwise, use the file example-decrypt.php found in the plugin directory.
|
74 |
+
|
75 |
+
= I lost my encryption key - what can I do? =
|
76 |
+
|
77 |
+
Nothing, probably. That's the point of an encryption key - people who don't have it can't get the data. Hire an encryption expert to build a super computer to try to break the encryption by brute force, at a price.
|
78 |
+
|
79 |
+
= My site was hacked, and I have no backups! I thought UpdraftPlus was working! Can I kill you? =
|
80 |
+
No, there's no warranty or guarantee, etc. It's completely up to you to verify that UpdraftPlus is working correctly. If it doesn't then that's unfortunate, but this is a free plugin.
|
81 |
+
|
82 |
+
== Changelog ==
|
83 |
+
|
84 |
+
= 1.0.8 - 12/20/2012 =
|
85 |
+
* Fixed bug that set 1Tb (instead of 1Mb) chunk sizes for Google Drive uploads
|
86 |
+
* Added link to some screenshots to help with Google Drive setup
|
87 |
+
|
88 |
+
= 1.0.5 - 12/13/2012 =
|
89 |
+
* Tweaked default Google Drive options
|
90 |
+
|
91 |
+
= 1.0.4 - 12/10/2012 =
|
92 |
+
* Implemented resumption/chunked uploading on Google Drive - much bigger sites can now be backed up
|
93 |
+
* Fixed bug whereby setting for deleting local backups was lost
|
94 |
+
* Now marked as 1.0, since we are feature-complete with targeted features for this release
|
95 |
+
* Made description fuller
|
96 |
+
|
97 |
+
= 0.9.20 - 12/06/2012 =
|
98 |
+
* Updated to latest S3.php library with chunked uploading patch
|
99 |
+
* Implemented chunked uploading on Amazon S3 - much bigger sites can now be backed up with S3
|
100 |
+
|
101 |
+
= 0.9.10 - 11/22/2012 =
|
102 |
+
* Completed basic Google Drive support (thanks to Sorin Iclanzan, code taken from "Backup" plugin under GPLv3+); now supporting uploading, purging and restoring - i.e. full UpdraftPlus functionality
|
103 |
+
* Licence change to GPLv3+ (from GPLv2+) to allow incorporating Sorin's code
|
104 |
+
* Tidied/organised the settings screen further
|
105 |
+
|
106 |
+
= 0.9.2 - 11/21/2012 =
|
107 |
+
* Failed uploads can now be re-tried, giving really big blogs a better opportunity to eventually succeed uploading
|
108 |
+
|
109 |
+
= 0.8.51 - 11/19/2012 =
|
110 |
+
* Moved screenshot into assets, reducing plugin download size
|
111 |
+
|
112 |
+
= 0.8.50 - 10/13/2012 =
|
113 |
+
* Important new feature: back up other directories found in the WP content (wp-content) directory (not just plugins/themes/uploads, as in original Updraft)
|
114 |
+
|
115 |
+
= 0.8.37 - 10/12/2012 =
|
116 |
+
* Don't whinge about Google Drive authentication if that method is not current
|
117 |
+
|
118 |
+
= 0.8.36 - 10/03/2012 =
|
119 |
+
* Support using sub-directories in Amazon S3
|
120 |
+
* Some more debug logging for Amazon S3
|
121 |
+
|
122 |
+
= 0.8.33 - 09/19/2012 =
|
123 |
+
* Work around some web hosts with invalid safe_mode configurations
|
124 |
+
|
125 |
+
= 0.8.32 - 09/17/2012 =
|
126 |
+
* Fix a subtle bug that caused database tables from outside of this WordPress install to be backed up
|
127 |
+
|
128 |
+
= 0.8.31 - 09/08/2012 =
|
129 |
+
* Fixed error deleting old S3 backups. If your expired S3 backups were not deleted, they should be in future - but you will need to delete manually those that expired before you installed this update.
|
130 |
+
* Fixed minor bug closing log file
|
131 |
+
* Marked as working with WordPress 3.4.2
|
132 |
+
|
133 |
+
= 0.8.29 - 06/29/2012 =
|
134 |
+
* Marking as tested up to WordPress 3.4.1
|
135 |
+
|
136 |
+
= 0.8.28 - 06/06/2012 =
|
137 |
+
* Now experimentally supports Google Drive (thanks to Sorin Iclanzan, code re-used from his Google Drive-only 'backup' plugin)
|
138 |
+
* New feature: backup files and database on separate schedules
|
139 |
+
* Tidied and improved retain behaviour
|
140 |
+
|
141 |
+
= 0.7.7 - 05/29/2012 =
|
142 |
+
* Implementation of a logging mechanism to allow easier debugging and development
|
143 |
+
|
144 |
+
= 0.7.4 - 05/21/2012 =
|
145 |
+
* Removed CloudFront method; I have no way of testing this
|
146 |
+
* Backup all tables found in the database that have this site's table prefix
|
147 |
+
* If encryption fails, then abort (don't revert to not encrypting)
|
148 |
+
* Added ability to decrypt encrypted database backups
|
149 |
+
* Added ability to opt out of backing up each file group
|
150 |
+
* Now adds database character set, the lack of which before made database backups unusable without modifications
|
151 |
+
* Version number bump to make clear that this is an improvement on the original Updraft, and is now tried and tested
|
152 |
+
|
153 |
+
= 0.1.3 - 01/16/2012 =
|
154 |
+
* Force backup of all tables found in database (vanilla Updraft only backed up WP core tables)
|
155 |
+
* Tweak notification email to include site name
|
156 |
+
|
157 |
+
= 0.1 - 08/10/2011 =
|
158 |
+
|
159 |
+
* A fork of Updraft 0.6.1 by Paul Kehrer with the following improvements
|
160 |
+
* Replaced deprecated function calls (in WordPress 3.2.1)
|
161 |
+
* Removed all warnings from basic admin page with WP_DEBUG on
|
162 |
+
* Implemented encrypted backup (but not yet automatic restoration) on database
|
163 |
+
* Some de-uglification of admin interface
|
164 |
+
|
165 |
+
|
166 |
+
== Screenshots ==
|
167 |
+
|
168 |
+
1. Configuration page
|
169 |
+
|
170 |
+
|
171 |
+
== License ==
|
172 |
+
|
173 |
+
Portions copyright 2011-2 David Anderson
|
174 |
+
Portions copyright 2010 Paul Kehrer
|
175 |
+
|
176 |
+
This program is free software; you can redistribute it and/or modify
|
177 |
+
it under the terms of the GNU General Public License as published by
|
178 |
+
the Free Software Foundation; either version 2 of the License, or
|
179 |
+
(at your option) any later version.
|
180 |
+
|
181 |
+
This program is distributed in the hope that it will be useful,
|
182 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
183 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
184 |
+
GNU General Public License for more details.
|
185 |
+
|
186 |
+
You should have received a copy of the GNU General Public License
|
187 |
+
along with this program; if not, write to the Free Software
|
188 |
+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
189 |
+
|
trunk/updraftplus.php
ADDED
@@ -0,0 +1,2418 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
Plugin Name: UpdraftPlus - Backup/Restore
|
4 |
+
Plugin URI: http://wordpress.org/extend/plugins/updraftplus
|
5 |
+
Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed, on separate schedules.
|
6 |
+
Author: David Anderson.
|
7 |
+
Version: 1.0.8
|
8 |
+
Donate link: http://david.dw-perspective.org.uk/donate
|
9 |
+
License: GPLv3 or later
|
10 |
+
Author URI: http://wordshell.net
|
11 |
+
*/
|
12 |
+
|
13 |
+
/*
|
14 |
+
TODO (some of these items mine, some from original Updraft awaiting review):
|
15 |
+
//Add DropBox and Microsoft Skydrive support
|
16 |
+
//Backup record should include *where* the backup was placed, so that we don't attempt to delete old ones from the wrong place? (Or would that be unexpected to users to have things from elsewhere deleted?)
|
17 |
+
//improve error reporting. s3 and dir backup have decent reporting now, but not sure i know what to do from here
|
18 |
+
//list backups that aren't tracked (helps with double backup problem)
|
19 |
+
//investigate $php_errormsg further
|
20 |
+
//pretty up return messages in admin area
|
21 |
+
//check s3/ftp download
|
22 |
+
|
23 |
+
//Rip out the "last backup" bit, and/or put in a display of the last log
|
24 |
+
|
25 |
+
Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
|
26 |
+
// Does not delete old custom directories upon a restore?
|
27 |
+
*/
|
28 |
+
|
29 |
+
/* Portions copyright 2010 Paul Kehrer
|
30 |
+
Portions copyright 2011-12 David Anderson
|
31 |
+
Other portions copyright as indicated authors in the relevant files
|
32 |
+
Particular thanks to Sorin Iclanzan, author of the "Backup" plugin, from which much Google Drive code was taken under the GPLv3+
|
33 |
+
|
34 |
+
This program is free software; you can redistribute it and/or modify
|
35 |
+
it under the terms of the GNU General Public License as published by
|
36 |
+
the Free Software Foundation; either version 3 of the License, or
|
37 |
+
(at your option) any later version.
|
38 |
+
|
39 |
+
This program is distributed in the hope that it will be useful,
|
40 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
41 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
42 |
+
GNU General Public License for more details.
|
43 |
+
|
44 |
+
You should have received a copy of the GNU General Public License
|
45 |
+
along with this program; if not, write to the Free Software
|
46 |
+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
47 |
+
*/
|
48 |
+
// TODO: Note this might *lower* the limit - should check first.
|
49 |
+
|
50 |
+
@set_time_limit(900); //15 minutes max. i'm not sure how long a really big site could take to back up?
|
51 |
+
|
52 |
+
$updraft = new UpdraftPlus();
|
53 |
+
|
54 |
+
if(!$updraft->memory_check(192)) {
|
55 |
+
# TODO: Better solution is to split the backup set into manageable chunks based on this limit
|
56 |
+
@ini_set('memory_limit', '192M'); //up the memory limit for large backup files... should split the backup set into manageable chunks based on the limit
|
57 |
+
}
|
58 |
+
|
59 |
+
define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php');
|
60 |
+
|
61 |
+
class UpdraftPlus {
|
62 |
+
|
63 |
+
var $version = '1.0.8';
|
64 |
+
|
65 |
+
var $dbhandle;
|
66 |
+
var $errors = array();
|
67 |
+
var $nonce;
|
68 |
+
var $logfile_name = "";
|
69 |
+
var $logfile_handle = false;
|
70 |
+
var $backup_time;
|
71 |
+
var $gdocs;
|
72 |
+
var $gdocs_access_token;
|
73 |
+
|
74 |
+
function __construct() {
|
75 |
+
// Initialisation actions
|
76 |
+
# Create admin page
|
77 |
+
add_action('admin_menu', array($this,'add_admin_pages'));
|
78 |
+
add_action('admin_init', array($this,'admin_init'));
|
79 |
+
add_action('updraft_backup', array($this,'backup_files'));
|
80 |
+
add_action('updraft_backup_database', array($this,'backup_database'));
|
81 |
+
# backup_all is used by the manual "Backup Now" button
|
82 |
+
add_action('updraft_backup_all', array($this,'backup_all'));
|
83 |
+
# this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc.
|
84 |
+
add_action('updraft_backup_resume', array($this,'backup_resume'));
|
85 |
+
add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
|
86 |
+
# http://codex.wordpress.org/Plugin_API/Filter_Reference/cron_schedules
|
87 |
+
add_filter('cron_schedules', array($this,'modify_cron_schedules'));
|
88 |
+
add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
|
89 |
+
add_action('init', array($this, 'googledrive_backup_auth'));
|
90 |
+
}
|
91 |
+
|
92 |
+
// Handle Google OAuth 2.0 - ?page=updraftplus&action=auth
|
93 |
+
function googledrive_backup_auth() {
|
94 |
+
if ( is_admin() && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset( $_GET['action'] ) && $_GET['action'] == 'auth' ) {
|
95 |
+
if ( isset( $_GET['state'] ) ) {
|
96 |
+
if ( $_GET['state'] == 'token' )
|
97 |
+
$this->gdrive_auth_token();
|
98 |
+
elseif ( $_GET['state'] == 'revoke' )
|
99 |
+
$this->gdrive_auth_revoke();
|
100 |
+
} elseif (isset($_GET['updraftplus_googleauth'])) {
|
101 |
+
$this->gdrive_auth_request();
|
102 |
+
}
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Acquire single-use authorization code from Google OAuth 2.0
|
108 |
+
*/
|
109 |
+
function gdrive_auth_request() {
|
110 |
+
$params = array(
|
111 |
+
'response_type' => 'code',
|
112 |
+
'client_id' => get_option('updraft_googledrive_clientid'),
|
113 |
+
'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
|
114 |
+
'scope' => 'https://www.googleapis.com/auth/drive.file https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/',
|
115 |
+
'state' => 'token',
|
116 |
+
'access_type' => 'offline',
|
117 |
+
'approval_prompt' => 'auto'
|
118 |
+
);
|
119 |
+
header('Location: https://accounts.google.com/o/oauth2/auth?'.http_build_query($params));
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* Get a Google account access token using the refresh token
|
124 |
+
*/
|
125 |
+
function access_token( $token, $client_id, $client_secret ) {
|
126 |
+
$context = array(
|
127 |
+
'http' => array(
|
128 |
+
'method' => 'POST',
|
129 |
+
'header' => 'Content-type: application/x-www-form-urlencoded',
|
130 |
+
'content' => http_build_query( array(
|
131 |
+
'refresh_token' => $token,
|
132 |
+
'client_id' => $client_id,
|
133 |
+
'client_secret' => $client_secret,
|
134 |
+
'grant_type' => 'refresh_token'
|
135 |
+
) )
|
136 |
+
)
|
137 |
+
);
|
138 |
+
$this->log("Google Drive: requesting access token: client_id=$client_id");
|
139 |
+
$result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));
|
140 |
+
if($result) {
|
141 |
+
$result = json_decode( $result, true );
|
142 |
+
if ( isset( $result['access_token'] ) ) {
|
143 |
+
$this->log("Google Drive: successfully obtained access token");
|
144 |
+
return $result['access_token'];
|
145 |
+
} else {
|
146 |
+
$this->log("Google Drive error when requesting access token: response does not contain access_token");
|
147 |
+
return false;
|
148 |
+
}
|
149 |
+
} else {
|
150 |
+
$this->log("Google Drive error when requesting access token: no response");
|
151 |
+
return false;
|
152 |
+
}
|
153 |
+
}
|
154 |
+
|
155 |
+
function googledrive_delete_file( $file, $token) {
|
156 |
+
$ids = get_option('updraft_file_ids', array());
|
157 |
+
if (!isset($ids[$file])) {
|
158 |
+
$this->log("Could not delete: could not find a record of the Google Drive file ID for this file");
|
159 |
+
return;
|
160 |
+
} else {
|
161 |
+
$del == $this->gdocs->delete_resource($ids[$file]);
|
162 |
+
if (is_wp_error($del)) {
|
163 |
+
foreach ($del->get_error_messages() as $msg) {
|
164 |
+
$this->log("Deletion failed: $msg");
|
165 |
+
}
|
166 |
+
} else {
|
167 |
+
$this->log("Deletion successful");
|
168 |
+
unset($ids[$file]);
|
169 |
+
update_option('updraft_file_ids', $ids);
|
170 |
+
}
|
171 |
+
}
|
172 |
+
return;
|
173 |
+
}
|
174 |
+
|
175 |
+
/**
|
176 |
+
* Get a Google account refresh token using the code received from gdrive_auth_request
|
177 |
+
*/
|
178 |
+
function gdrive_auth_token() {
|
179 |
+
if( isset( $_GET['code'] ) ) {
|
180 |
+
$context = array(
|
181 |
+
'http' => array(
|
182 |
+
'timeout' => 30,
|
183 |
+
'method' => 'POST',
|
184 |
+
'header' => 'Content-type: application/x-www-form-urlencoded',
|
185 |
+
'content' => http_build_query( array(
|
186 |
+
'code' => $_GET['code'],
|
187 |
+
'client_id' => get_option('updraft_googledrive_clientid'),
|
188 |
+
'client_secret' => get_option('updraft_googledrive_secret'),
|
189 |
+
'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
|
190 |
+
'grant_type' => 'authorization_code'
|
191 |
+
) )
|
192 |
+
)
|
193 |
+
);
|
194 |
+
// TODO Use curl??
|
195 |
+
$result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));
|
196 |
+
# Oddly, sometimes fails and then trying again works...
|
197 |
+
/*
|
198 |
+
if (!$result) { sleep(1); $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));}
|
199 |
+
if (!$result) { sleep(1); $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));}
|
200 |
+
*/
|
201 |
+
if($result) {
|
202 |
+
$result = json_decode( $result, true );
|
203 |
+
if ( isset( $result['refresh_token'] ) ) {
|
204 |
+
update_option('updraft_googledrive_token',$result['refresh_token']); // Save token
|
205 |
+
header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Google Drive authorization was successful.', 'updraftplus' ) ) );
|
206 |
+
}
|
207 |
+
else {
|
208 |
+
header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
|
209 |
+
}
|
210 |
+
} else {
|
211 |
+
header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Bad response!', 'backup' ) ) );
|
212 |
+
}
|
213 |
+
}
|
214 |
+
else {
|
215 |
+
header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Authorisation failed!', 'backup' ) ) );
|
216 |
+
}
|
217 |
+
}
|
218 |
+
|
219 |
+
/**
|
220 |
+
* Revoke a Google account refresh token
|
221 |
+
*/
|
222 |
+
function gdrive_auth_revoke() {
|
223 |
+
@file_get_contents( 'https://accounts.google.com/o/oauth2/revoke?token=' . get_option('updraft_googledrive_token') );
|
224 |
+
update_option('updraft_googledrive_token','');
|
225 |
+
header( 'Location: '.admin_url( 'options-general.php?page=updraftplus&message=' . __( 'Authorization revoked.', 'backup' ) ) );
|
226 |
+
}
|
227 |
+
|
228 |
+
# Adds the settings link under the plugin on the plugin screen.
|
229 |
+
function plugin_action_links($links, $file) {
|
230 |
+
if ($file == plugin_basename(__FILE__)){
|
231 |
+
$settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraftplus">'.__("Settings", "UpdraftPlus").'</a>';
|
232 |
+
array_unshift($links, $settings_link);
|
233 |
+
$settings_link = '<a href="http://david.dw-perspective.org.uk/donate">'.__("Donate","UpdraftPlus").'</a>';
|
234 |
+
array_unshift($links, $settings_link);
|
235 |
+
}
|
236 |
+
return $links;
|
237 |
+
}
|
238 |
+
|
239 |
+
function backup_time_nonce() {
|
240 |
+
$this->backup_time = time();
|
241 |
+
$this->nonce = substr(md5(time().rand()),20);
|
242 |
+
}
|
243 |
+
|
244 |
+
# Logs the given line, adding date stamp and newline
|
245 |
+
function log($line) {
|
246 |
+
if ($this->logfile_handle) fwrite($this->logfile_handle,date('r')." ".$line."\n");
|
247 |
+
}
|
248 |
+
|
249 |
+
function backup_resume($resumption_no) {
|
250 |
+
@ignore_user_abort(true);
|
251 |
+
// This is scheduled for 5 minutes after a backup job starts
|
252 |
+
$bnonce = get_transient('updraftplus_backup_job_nonce');
|
253 |
+
if (!$bnonce) return;
|
254 |
+
$this->nonce = $bnonce;
|
255 |
+
$this->logfile_open($bnonce);
|
256 |
+
$this->log("Resume backup ($resumption_no): begin run (will check for any remaining jobs)");
|
257 |
+
$btime = get_transient('updraftplus_backup_job_time');
|
258 |
+
if (!$btime) {
|
259 |
+
$this->log("Did not find stored time setting - aborting");
|
260 |
+
return;
|
261 |
+
}
|
262 |
+
$this->log("Resuming backup: resumption=$resumption_no, nonce=$bnonce, begun at=$btime");
|
263 |
+
// Schedule again, to run in 5 minutes again, in case we again fail
|
264 |
+
$resume_delay = 300;
|
265 |
+
// A different argument than before is needed otherwise the event is ignored
|
266 |
+
$next_resumption = $resumption_no+1;
|
267 |
+
if ($next_resumption < 10) {
|
268 |
+
wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume' ,array($next_resumption));
|
269 |
+
} else {
|
270 |
+
$this->log("This is our tenth attempt - will not try again");
|
271 |
+
}
|
272 |
+
$this->backup_time = $btime;
|
273 |
+
|
274 |
+
// Returns an array, most recent first, of backup sets
|
275 |
+
$backup_history = $this->get_backup_history();
|
276 |
+
if (!isset($backup_history[$btime])) $this->log("Error: Could not find a record in the database of a backup with this timestamp");
|
277 |
+
|
278 |
+
$our_files=$backup_history[$btime];
|
279 |
+
$undone_files = array();
|
280 |
+
foreach ($our_files as $key => $file) {
|
281 |
+
$hash = md5($file);
|
282 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
283 |
+
if (get_transient('updraft_'.$hash) === "yes") {
|
284 |
+
$this->log("$file: $key: This file has been successfully uploaded in the last 3 hours");
|
285 |
+
} elseif (is_file($fullpath)) {
|
286 |
+
$this->log("$file: $key: This file has NOT been successfully uploaded in the last 3 hours: will retry");
|
287 |
+
$undone_files[$key] = $file;
|
288 |
+
} else {
|
289 |
+
$this->log("$file: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem");
|
290 |
+
$this->uploaded_file($file);
|
291 |
+
}
|
292 |
+
}
|
293 |
+
|
294 |
+
if (count($undone_files) == 0) {
|
295 |
+
$this->log("There were no files that needed uploading; backup job is finished");
|
296 |
+
return;
|
297 |
+
}
|
298 |
+
|
299 |
+
$this->log("Requesting backup of the files that were not successfully uploaded");
|
300 |
+
$this->cloud_backup($undone_files);
|
301 |
+
$this->cloud_backup_finish($undone_files);
|
302 |
+
|
303 |
+
$this->log("Resume backup ($resumption_no): finish run");
|
304 |
+
|
305 |
+
$this->backup_finish($next_resumption);
|
306 |
+
|
307 |
+
}
|
308 |
+
|
309 |
+
function backup_all() {
|
310 |
+
$this->backup(true,true);
|
311 |
+
}
|
312 |
+
|
313 |
+
function backup_files() {
|
314 |
+
# Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
|
315 |
+
$this->backup(true,false);
|
316 |
+
}
|
317 |
+
|
318 |
+
function backup_database() {
|
319 |
+
# Note that nothing will happen if the file backup had the same schedule
|
320 |
+
$this->backup(false,true);
|
321 |
+
}
|
322 |
+
|
323 |
+
function logfile_open($nonce) {
|
324 |
+
//set log file name and open log file
|
325 |
+
$updraft_dir = $this->backups_dir_location();
|
326 |
+
$this->logfile_name = $updraft_dir. "/log.$nonce.txt";
|
327 |
+
// Use append mode in case it already exists
|
328 |
+
$this->logfile_handle = fopen($this->logfile_name, 'a');
|
329 |
+
}
|
330 |
+
|
331 |
+
//scheduled wp-cron events can have a race condition here if page loads are coming fast enough, but there's nothing we can do about it. TODO: I reckon there is. Store a transient based on the backup schedule. Then as the backup proceeds, check for its existence; if it has changed, then another task has begun, so abort.
|
332 |
+
function backup($backup_files, $backup_database) {
|
333 |
+
|
334 |
+
@ignore_user_abort(true);
|
335 |
+
//generate backup information
|
336 |
+
$this->backup_time_nonce();
|
337 |
+
// If we don't finish in 3 hours, then we won't finish
|
338 |
+
// This transient indicates the identity of the current backup job (which can be used to find the files and logfile)
|
339 |
+
set_transient("updraftplus_backup_job_nonce",$this->nonce,3600*3);
|
340 |
+
set_transient("updraftplus_backup_job_time",$this->backup_time,3600*3);
|
341 |
+
$this->logfile_open($this->nonce);
|
342 |
+
|
343 |
+
// Schedule the even to run later, which checks on success and can resume the backup
|
344 |
+
// We save the time to a variable because it is needed for un-scheduling
|
345 |
+
// $resume_delay = (get_option('updraft_debug_mode')) ? 60 : 300;
|
346 |
+
$resume_delay = 300;
|
347 |
+
wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume', array(1));
|
348 |
+
$this->log("In case we run out of time, scheduled a resumption at: $resume_delay seconds from now");
|
349 |
+
|
350 |
+
// Log some information that may be helpful
|
351 |
+
global $wp_version;
|
352 |
+
$this->log("PHP version: ".phpversion()." WordPress version: ".$wp_version." Updraft version: ".$this->version." Backup files: $backup_files (schedule: ".get_option('updraft_interval','unset').") Backup DB: $backup_database (schedule: ".get_option('updraft_interval_database','unset').")");
|
353 |
+
|
354 |
+
# If the files and database schedules are the same, and if this the file one, then we rope in database too.
|
355 |
+
# On the other hand, if the schedules were the same and this was the database run, then there is nothing to do.
|
356 |
+
if (get_option('updraft_interval') == get_option('updraft_interval_database') || get_option('updraft_interval_database','xyz') == 'xyz' ) {
|
357 |
+
$backup_database = ($backup_files == true) ? true : false;
|
358 |
+
}
|
359 |
+
|
360 |
+
$this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database");
|
361 |
+
|
362 |
+
# Possibly now nothing is to be done, except to close the log file
|
363 |
+
if ($backup_files || $backup_database) {
|
364 |
+
|
365 |
+
$backup_contains = "";
|
366 |
+
|
367 |
+
$backup_array = array();
|
368 |
+
|
369 |
+
//backup directories and return a numerically indexed array of file paths to the backup files
|
370 |
+
if ($backup_files) {
|
371 |
+
$this->log("Beginning backup of directories");
|
372 |
+
$backup_array = $this->backup_dirs();
|
373 |
+
$backup_contains = "Files only (no database)";
|
374 |
+
}
|
375 |
+
|
376 |
+
//backup DB and return string of file path
|
377 |
+
if ($backup_database) {
|
378 |
+
$this->log("Beginning backup of database");
|
379 |
+
$db_backup = $this->backup_db();
|
380 |
+
//add db path to rest of files
|
381 |
+
if(is_array($backup_array)) { $backup_array['db'] = $db_backup; }
|
382 |
+
$backup_contains = ($backup_files) ? "Files and database" : "Database only (no files)";
|
383 |
+
}
|
384 |
+
|
385 |
+
set_transient("updraftplus_backupcontains", $backup_contains, 3600*3);
|
386 |
+
|
387 |
+
//save this to our history so we can track backups for the retain feature
|
388 |
+
$this->log("Saving backup history");
|
389 |
+
// This is done before cloud despatch, because we want a record of what *should* be in the backup. Whether it actually makes it there or not is not yet known.
|
390 |
+
$this->save_backup_history($backup_array);
|
391 |
+
|
392 |
+
//cloud operations (S3,Google Drive,FTP,email,nothing)
|
393 |
+
//this also calls the retain (prune) feature at the end (done in this method to reuse existing cloud connections)
|
394 |
+
if(is_array($backup_array) && count($backup_array) >0) {
|
395 |
+
$this->log("Beginning dispatch of backup to remote");
|
396 |
+
$this->cloud_backup($backup_array);
|
397 |
+
}
|
398 |
+
|
399 |
+
//save the last backup info, including errors, if any
|
400 |
+
$this->log("Saving last backup information into WordPress db");
|
401 |
+
$this->save_last_backup($backup_array);
|
402 |
+
|
403 |
+
// Send the email
|
404 |
+
$this->cloud_backup_finish($backup_array);
|
405 |
+
|
406 |
+
}
|
407 |
+
|
408 |
+
// Close log file; delete and also delete transients if not in debug mode
|
409 |
+
$this->backup_finish(1);
|
410 |
+
|
411 |
+
}
|
412 |
+
|
413 |
+
function backup_finish($cancel_event) {
|
414 |
+
|
415 |
+
// In fact, leaving the hook to run (if debug is set) is harmless, as the resume job should only do tasks that were left unfinished, which at this stage is none.
|
416 |
+
if (empty($this->errors)) {
|
417 |
+
$this->log("There were no errors in the uploads, so the 'resume' event is being unscheduled");
|
418 |
+
wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event));
|
419 |
+
delete_transient("updraftplus_backup_job_nonce");
|
420 |
+
delete_transient("updraftplus_backup_job_time");
|
421 |
+
} else {
|
422 |
+
$this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
|
423 |
+
}
|
424 |
+
|
425 |
+
@fclose($this->logfile_handle);
|
426 |
+
|
427 |
+
if (!get_option('updraft_debug_mode')) @unlink($this->logfile_name);
|
428 |
+
|
429 |
+
}
|
430 |
+
|
431 |
+
function cloud_backup_finish($backup_array) {
|
432 |
+
|
433 |
+
// Send the results email if requested
|
434 |
+
if(get_option('updraft_email') != "" && get_option('updraft_service') != 'email') $this->send_results_email();
|
435 |
+
|
436 |
+
}
|
437 |
+
|
438 |
+
function send_results_email() {
|
439 |
+
|
440 |
+
$sendmail_to = get_option('updraft_email');
|
441 |
+
|
442 |
+
$this->log("Sending email report to: ".$sendmail_to);
|
443 |
+
|
444 |
+
$append_log = (get_option('updraft_debug_mode') && $this->logfile_name != "") ? "\r\nLog contents:\r\n".file_get_contents($this->logfile_name) : "" ;
|
445 |
+
|
446 |
+
wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus '.$this->version.') '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\nBackup contains: ".get_transient("updraftplus_backupcontains")."\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
|
447 |
+
|
448 |
+
}
|
449 |
+
|
450 |
+
function save_last_backup($backup_array) {
|
451 |
+
$success = (empty($this->errors))?1:0;
|
452 |
+
|
453 |
+
$last_backup = array('backup_time'=>$this->backup_time, 'backup_array'=>$backup_array, 'success'=>$success, 'errors'=>$this->errors);
|
454 |
+
|
455 |
+
update_option('updraft_last_backup', $last_backup);
|
456 |
+
}
|
457 |
+
|
458 |
+
// This should be called whenever a file is successfully uploaded
|
459 |
+
function uploaded_file($file, $id = false) {
|
460 |
+
# We take an MD5 hash because set_transient wants a name of 45 characters or less
|
461 |
+
$hash = md5($file);
|
462 |
+
$this->log("$file: $hash: recording as successfully uploaded");
|
463 |
+
set_transient("updraft_".$hash, "yes", 3600*4);
|
464 |
+
if ($id) {
|
465 |
+
$ids = get_option('updraft_file_ids', array() );
|
466 |
+
$ids[$file] = $id;
|
467 |
+
update_option('updraft_file_ids',$ids);
|
468 |
+
$this->log("Stored file<->id correlation in database ($file <-> $id)");
|
469 |
+
}
|
470 |
+
// Delete local files if the option is set
|
471 |
+
$this->delete_local($file);
|
472 |
+
|
473 |
+
}
|
474 |
+
|
475 |
+
function cloud_backup($backup_array) {
|
476 |
+
switch(get_option('updraft_service')) {
|
477 |
+
case 's3':
|
478 |
+
@set_time_limit(900);
|
479 |
+
$this->log("Cloud backup: S3");
|
480 |
+
if (count($backup_array) >0) $this->s3_backup($backup_array);
|
481 |
+
break;
|
482 |
+
case 'googledrive':
|
483 |
+
@set_time_limit(900);
|
484 |
+
$this->log("Cloud backup: Google Drive");
|
485 |
+
if (count($backup_array) >0) $this->googledrive_backup($backup_array);
|
486 |
+
break;
|
487 |
+
case 'ftp':
|
488 |
+
@set_time_limit(900);
|
489 |
+
$this->log("Cloud backup: FTP");
|
490 |
+
if (count($backup_array) >0) $this->ftp_backup($backup_array);
|
491 |
+
break;
|
492 |
+
case 'email':
|
493 |
+
@set_time_limit(900);
|
494 |
+
$this->log("Cloud backup: Email");
|
495 |
+
//files can easily get way too big for this...
|
496 |
+
foreach($backup_array as $type=>$file) {
|
497 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
498 |
+
wp_mail(get_option('updraft_email'),"WordPress Backup ".date('Y-m-d H:i',$this->backup_time),"Backup is of the $type. Be wary; email backups may fail because of file size limitations on mail servers.",null,array($fullpath));
|
499 |
+
$this->uploaded_file($file);
|
500 |
+
}
|
501 |
+
//we don't break here so it goes and executes all the default behavior below as well. this gives us retain behavior for email
|
502 |
+
default:
|
503 |
+
$this->prune_retained_backups("local");
|
504 |
+
break;
|
505 |
+
}
|
506 |
+
}
|
507 |
+
|
508 |
+
// Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
|
509 |
+
function prune_retained_backups($updraft_service,$remote_object,$remote_path) {
|
510 |
+
$this->log("Retain: beginning examination of existing backup sets");
|
511 |
+
$updraft_retain = get_option('updraft_retain');
|
512 |
+
// Number of backups to retain
|
513 |
+
$retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
|
514 |
+
$this->log("Retain: user setting: number to retain = $retain");
|
515 |
+
// Returns an array, most recent first, of backup sets
|
516 |
+
$backup_history = $this->get_backup_history();
|
517 |
+
$db_backups_found = 0;
|
518 |
+
$file_backups_found = 0;
|
519 |
+
$this->log("Number of backup sets in history: ".count($backup_history));
|
520 |
+
foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
|
521 |
+
// $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
|
522 |
+
// The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
|
523 |
+
$this->log("Examining backup set with datestamp: $backup_datestamp");
|
524 |
+
if (isset($backup_to_examine['db'])) {
|
525 |
+
$db_backups_found++;
|
526 |
+
$this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found");
|
527 |
+
if ($db_backups_found > $retain) {
|
528 |
+
$this->log("$backup_datestamp: over retain limit; will delete this database");
|
529 |
+
$file = $backup_to_examine['db'];
|
530 |
+
$this->log("$backup_datestamp: Delete this file: $file");
|
531 |
+
if ($file != '') {
|
532 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
533 |
+
@unlink($fullpath); //delete it if it's locally available
|
534 |
+
if ($updraft_service == "s3") {
|
535 |
+
if (preg_match("#^([^/]+)/(.*)$#",$remote_path,$bmatches)) {
|
536 |
+
$s3_bucket=$bmatches[1];
|
537 |
+
$s3_uri = $bmatches[2]."/".$file;
|
538 |
+
} else {
|
539 |
+
$s3_bucket = $remote_path;
|
540 |
+
$s3_uri = $file;
|
541 |
+
}
|
542 |
+
$this->log("$backup_datestamp: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
|
543 |
+
# Here we brought in the function deleteObject in order to get more direct access to any error
|
544 |
+
$rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
|
545 |
+
$rest = $rest->getResponse();
|
546 |
+
if ($rest->error === false && $rest->code !== 204) {
|
547 |
+
$this->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
|
548 |
+
$this->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
|
549 |
+
} elseif ($rest->error !== false) {
|
550 |
+
$this->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
|
551 |
+
$this->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
|
552 |
+
}
|
553 |
+
} elseif ($updraft_service == "ftp") {
|
554 |
+
$this->log("$backup_datestamp: Delete remote ftp: $remote_path/$file");
|
555 |
+
@$remote_object->delete($remote_path.$file);
|
556 |
+
} elseif ($updraft_service == "googledrive") {
|
557 |
+
$this->log("$backup_datestamp: Delete remote file from Google Drive: $file");
|
558 |
+
$this->googledrive_delete_file($file,$remote_object);
|
559 |
+
}
|
560 |
+
}
|
561 |
+
unset($backup_to_examine['db']);
|
562 |
+
}
|
563 |
+
}
|
564 |
+
if (isset($backup_to_examine['plugins']) || isset($backup_to_examine['themes']) || isset($backup_to_examine['uploads']) || isset($backup_to_examine['others'])) {
|
565 |
+
$file_backups_found++;
|
566 |
+
$this->log("$backup_datestamp: this set includes files; fileset count is now $file_backups_found");
|
567 |
+
if ($file_backups_found > $retain) {
|
568 |
+
$this->log("$backup_datestamp: over retain limit; will delete this file set");
|
569 |
+
$file = isset($backup_to_examine['plugins']) ? $backup_to_examine['plugins'] : "";
|
570 |
+
$file2 = isset($backup_to_examine['themes']) ? $backup_to_examine['themes'] : "";
|
571 |
+
$file3 = isset($backup_to_examine['uploads']) ? $backup_to_examine['uploads'] : "";
|
572 |
+
$file4 = isset($backup_to_examine['others']) ? $backup_to_examine['others'] : "";
|
573 |
+
foreach (array($file,$file2,$file3,$file4) as $dofile) {
|
574 |
+
if ($dofile) {
|
575 |
+
$this->log("$backup_datestamp: Delete this file: $dofile");
|
576 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$dofile;
|
577 |
+
@unlink($fullpath); //delete it if it's locally available
|
578 |
+
if ($updraft_service == "s3") {
|
579 |
+
if (preg_match("#^([^/]+)/(.*)$#",$remote_path,$bmatches)) {
|
580 |
+
$s3_bucket=$bmatches[1];
|
581 |
+
$s3_uri = $bmatches[2]."/".$dofile;
|
582 |
+
} else {
|
583 |
+
$s3_bucket = $remote_path;
|
584 |
+
$s3_uri = $dofile;
|
585 |
+
}
|
586 |
+
$this->log("$backup_datestamp: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
|
587 |
+
# Here we brought in the function deleteObject in order to get more direct access to any error
|
588 |
+
$rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
|
589 |
+
$rest = $rest->getResponse();
|
590 |
+
if ($rest->error === false && $rest->code !== 204) {
|
591 |
+
$this->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
|
592 |
+
$this->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
|
593 |
+
} elseif ($rest->error !== false) {
|
594 |
+
$this->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
|
595 |
+
$this->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
|
596 |
+
}
|
597 |
+
} elseif ($updraft_service == "ftp") {
|
598 |
+
$this->log("$backup_datestamp: Delete remote ftp: $remote_path/$dofile");
|
599 |
+
@$remote_object->delete($remote_path.$dofile);
|
600 |
+
} elseif ($updraft_service == "googledrive") {
|
601 |
+
$this->log("$backup_datestamp: Delete remote file from Google Drive: $dofile");
|
602 |
+
$this->googledrive_delete_file($dofile,$remote_object);
|
603 |
+
}
|
604 |
+
}
|
605 |
+
}
|
606 |
+
unset($backup_to_examine['plugins']);
|
607 |
+
unset($backup_to_examine['themes']);
|
608 |
+
unset($backup_to_examine['uploads']);
|
609 |
+
unset($backup_to_examine['others']);
|
610 |
+
}
|
611 |
+
}
|
612 |
+
// Delete backup set completely if empty, o/w just remove DB
|
613 |
+
if (count($backup_to_examine)==0) {
|
614 |
+
$this->log("$backup_datestamp: this backup set is now empty; will remove from history");
|
615 |
+
unset($backup_history[$backup_datestamp]);
|
616 |
+
} else {
|
617 |
+
$this->log("$backup_datestamp: this backup set remains non-empty; will retain in history");
|
618 |
+
$backup_history[$backup_datestamp] = $backup_to_examine;
|
619 |
+
}
|
620 |
+
}
|
621 |
+
$this->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation");
|
622 |
+
update_option('updraft_backup_history',$backup_history);
|
623 |
+
}
|
624 |
+
|
625 |
+
function s3_backup($backup_array) {
|
626 |
+
|
627 |
+
if(!class_exists('S3')) require_once(dirname(__FILE__).'/includes/S3.php');
|
628 |
+
|
629 |
+
$s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
|
630 |
+
|
631 |
+
$bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
|
632 |
+
$bucket_path = "";
|
633 |
+
$orig_bucket_name = $bucket_name;
|
634 |
+
|
635 |
+
if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
|
636 |
+
$bucket_name = $bmatches[1];
|
637 |
+
$bucket_path = $bmatches[2]."/";
|
638 |
+
}
|
639 |
+
|
640 |
+
if (@$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
|
641 |
+
|
642 |
+
foreach($backup_array as $file) {
|
643 |
+
|
644 |
+
// We upload in 5Mb chunks to allow more efficient resuming and hence uploading of larger files
|
645 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
646 |
+
$chunks = floor(filesize($fullpath) / 5242880)+1;
|
647 |
+
$hash = md5($file);
|
648 |
+
|
649 |
+
$this->log("S3 upload: $fullpath (chunks: $chunks) -> s3://$bucket_name/$bucket_path$file");
|
650 |
+
|
651 |
+
$filepath = $bucket_path.$file;
|
652 |
+
|
653 |
+
// This is extra code for the 1-chunk case, but less overhead (no bothering with transients)
|
654 |
+
if ($chunks < 2) {
|
655 |
+
if (!$s3->putObjectFile($fullpath, $bucket_name, $filepath)) {
|
656 |
+
$this->log("S3 regular upload: failed");
|
657 |
+
$this->error("S3 Error: Failed to upload $fullpath. Error was ".$php_errormsg);
|
658 |
+
} else {
|
659 |
+
$this->log("S3 regular upload: success");
|
660 |
+
$this->uploaded_file($file);
|
661 |
+
}
|
662 |
+
} else {
|
663 |
+
|
664 |
+
// Retrieve the upload ID
|
665 |
+
$uploadId = get_transient("updraft_${hash}_uid");
|
666 |
+
if (empty($uploadId)) {
|
667 |
+
$uploadId = $s3->initiateMultipartUpload($bucket_name, $filepath);
|
668 |
+
if (empty($uploadId)) {
|
669 |
+
$this->log("S3 upload: failed: could not get uploadId for multipart upload");
|
670 |
+
continue;
|
671 |
+
} else {
|
672 |
+
$this->log("S3 chunked upload: got multipart ID: $uploadId");
|
673 |
+
set_transient("updraft_${hash}_uid", $uploadId, 3600*3);
|
674 |
+
}
|
675 |
+
} else {
|
676 |
+
$this->log("S3 chunked upload: retrieved previously obtained multipart ID: $uploadId");
|
677 |
+
}
|
678 |
+
|
679 |
+
$successes = 0;
|
680 |
+
$etags = array();
|
681 |
+
for ($i = 1 ; $i <= $chunks; $i++) {
|
682 |
+
# Shorted to upd here to avoid hitting the 45-character limit
|
683 |
+
$etag = get_transient("upd_${hash}_e$i");
|
684 |
+
if (strlen($etag) > 0) {
|
685 |
+
$this->log("S3 chunk $i: was already completed (etag: $etag)");
|
686 |
+
$successes++;
|
687 |
+
array_push($etags, $etag);
|
688 |
+
} else {
|
689 |
+
$etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
|
690 |
+
if (is_string($etag)) {
|
691 |
+
$this->log("S3 chunk $i: uploaded (etag: $etag)");
|
692 |
+
array_push($etags, $etag);
|
693 |
+
set_transient("upd_${hash}_e$i", $etag, 3600*3);
|
694 |
+
$successes++;
|
695 |
+
} else {
|
696 |
+
$this->error("S3 chunk $i: upload failed");
|
697 |
+
$this->log("S3 chunk $i: upload failed");
|
698 |
+
}
|
699 |
+
}
|
700 |
+
}
|
701 |
+
if ($successes >= $chunks) {
|
702 |
+
$this->log("S3 upload: all chunks uploaded; will now instruct S3 to re-assemble");
|
703 |
+
if ($s3->completeMultipartUpload ($bucket_name, $filepath, $uploadId, $etags)) {
|
704 |
+
$this->log("S3 upload: re-assembly succeeded");
|
705 |
+
$this->uploaded_file($file);
|
706 |
+
} else {
|
707 |
+
$this->log("S3 upload: re-assembly failed");
|
708 |
+
$this->error("S3 upload: re-assembly failed");
|
709 |
+
}
|
710 |
+
} else {
|
711 |
+
$this->log("S3 upload: upload was not completely successful on this run");
|
712 |
+
}
|
713 |
+
}
|
714 |
+
}
|
715 |
+
$this->prune_retained_backups('s3',$s3,$orig_bucket_name);
|
716 |
+
} else {
|
717 |
+
$this->log("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
|
718 |
+
$this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
|
719 |
+
}
|
720 |
+
}
|
721 |
+
|
722 |
+
// This function taken from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
|
723 |
+
function is_gdocs( $thing ) {
|
724 |
+
if ( is_object( $thing ) && is_a( $thing, 'UpdraftPlus_GDocs' ) )
|
725 |
+
return true;
|
726 |
+
return false;
|
727 |
+
}
|
728 |
+
|
729 |
+
// This function modified from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
|
730 |
+
function need_gdocs() {
|
731 |
+
|
732 |
+
if ( ! $this->is_gdocs( $this->gdocs ) ) {
|
733 |
+
if ( get_option('updraft_googledrive_token') == "" || get_option('updraft_googledrive_clientid') == "" || get_option('updraft_googledrive_secret') == "" ) {
|
734 |
+
$this->log("GoogleDrive: this account is not authorised");
|
735 |
+
return new WP_Error( "not_authorized", "Account is not authorized." );
|
736 |
+
}
|
737 |
+
|
738 |
+
if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token;
|
739 |
+
|
740 |
+
$this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token );
|
741 |
+
$this->gdocs->set_option( 'chunk_size', 1 ); # 1Mb; change from default of 512Kb
|
742 |
+
$this->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
|
743 |
+
$this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
|
744 |
+
}
|
745 |
+
return true;
|
746 |
+
}
|
747 |
+
|
748 |
+
// Returns:
|
749 |
+
// true = already uploaded
|
750 |
+
// false = failure
|
751 |
+
// otherwise, the file ID
|
752 |
+
function googledrive_upload_file( $file, $title, $parent = '') {
|
753 |
+
|
754 |
+
// Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
|
755 |
+
if ( is_wp_error( $e = $this->need_gdocs() ) ) return false;
|
756 |
+
|
757 |
+
$hash = md5($file);
|
758 |
+
$transkey = 'upd_'.$hash.'_gloc';
|
759 |
+
// This is unset upon completion, so if it is set then we are resuming
|
760 |
+
$possible_location = get_transient($transkey);
|
761 |
+
|
762 |
+
if ( empty( $possible_location ) ) {
|
763 |
+
$this->log("$file: Attempting to upload file to Google Drive.");
|
764 |
+
$location = $this->gdocs->prepare_upload( $file, $title, $parent );
|
765 |
+
} else {
|
766 |
+
$this->log("$file: Attempting to resume upload.");
|
767 |
+
$location = $this->gdocs->resume_upload( $file, $possible_location );
|
768 |
+
}
|
769 |
+
|
770 |
+
if ( is_wp_error( $location ) ) {
|
771 |
+
$this->log("GoogleDrive upload: an error occurred");
|
772 |
+
foreach ($location->get_error_messages() as $msg) {
|
773 |
+
$this->error($msg);
|
774 |
+
$this->log("Error details: ".$msg);
|
775 |
+
}
|
776 |
+
return false;
|
777 |
+
}
|
778 |
+
|
779 |
+
if (!is_string($location) && true == $location) {
|
780 |
+
$this->log("$file: this file is already uploaded");
|
781 |
+
return true;
|
782 |
+
}
|
783 |
+
|
784 |
+
if ( is_string( $location ) ) {
|
785 |
+
$res = $location;
|
786 |
+
$this->log("Uploading file with title ".$title);
|
787 |
+
$d = 0;
|
788 |
+
do {
|
789 |
+
$this->log("Google Drive upload: chunk d: $d, loc: $res");
|
790 |
+
$res = $this->gdocs->upload_chunk();
|
791 |
+
if (is_string($res)) set_transient($transkey, $res, 3600*3);
|
792 |
+
$p = $this->gdocs->get_upload_percentage();
|
793 |
+
if ( $p - $d >= 1 ) {
|
794 |
+
$b = intval( $p - $d );
|
795 |
+
// echo '<span style="width:' . $b . '%"></span>';
|
796 |
+
$d += $b;
|
797 |
+
}
|
798 |
+
// $this->options['backup_list'][$id]['speed'] = $this->gdocs->get_upload_speed();
|
799 |
+
} while ( is_string( $res ) );
|
800 |
+
// echo '</div>';
|
801 |
+
|
802 |
+
if ( is_wp_error( $res ) || $res !== true) {
|
803 |
+
$this->log( "An error occurred during GoogleDrive upload (2)" );
|
804 |
+
$this->error( "An error occurred during GoogleDrive upload (2)" );
|
805 |
+
if (is_wp_error( $res )) {
|
806 |
+
foreach ($res->get_error_messages() as $msg) { $this->log($msg); }
|
807 |
+
}
|
808 |
+
return false;
|
809 |
+
}
|
810 |
+
|
811 |
+
$this->log("The file was successfully uploaded to Google Drive in ".number_format_i18n( $this->gdocs->time_taken(), 3)." seconds at an upload speed of ".size_format( $this->gdocs->get_upload_speed() )."/s.");
|
812 |
+
|
813 |
+
delete_transient($transkey);
|
814 |
+
// unset( $this->options['backup_list'][$id]['location'], $this->options['backup_list'][$id]['attempt'] );
|
815 |
+
}
|
816 |
+
|
817 |
+
return $this->gdocs->get_file_id();
|
818 |
+
|
819 |
+
// $this->update_quota();
|
820 |
+
// Google's "user info" service
|
821 |
+
// if ( empty( $this->options['user_info'] ) ) $this->set_user_info();
|
822 |
+
|
823 |
+
}
|
824 |
+
|
825 |
+
// This function just does the formalities, and off-loads the main work to googledrive_upload_file
|
826 |
+
function googledrive_backup($backup_array) {
|
827 |
+
|
828 |
+
require_once(dirname(__FILE__).'/includes/class-gdocs.php');
|
829 |
+
|
830 |
+
// Do we have an access token?
|
831 |
+
if ( !$access_token = $this->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
|
832 |
+
$this->log('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
|
833 |
+
return new WP_Error( "no_access_token", "Have not yet obtained an access token from Google (has the user authorised?");
|
834 |
+
}
|
835 |
+
|
836 |
+
$this->gdocs_access_token = $access_token;
|
837 |
+
|
838 |
+
foreach ($backup_array as $file) {
|
839 |
+
$file_path = trailingslashit(get_option('updraft_dir')).$file;
|
840 |
+
$file_name = basename($file_path);
|
841 |
+
$this->log("$file_name: Attempting to upload to Google Drive");
|
842 |
+
$timer_start = microtime(true);
|
843 |
+
if ( $id = $this->googledrive_upload_file( $file_path, $file_name, get_option('updraft_googledrive_remotepath')) ) {
|
844 |
+
$this->log('OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round(microtime( true ) - $timer_start,2) ) . ' seconds (id: '.$id.')' );
|
845 |
+
$this->uploaded_file($file, $id);
|
846 |
+
} else {
|
847 |
+
$this->error("$file_name: Failed to upload to Google Drive" );
|
848 |
+
$this->log("ERROR: $file_name: Failed to upload to Google Drive" );
|
849 |
+
}
|
850 |
+
}
|
851 |
+
$this->prune_retained_backups("googledrive",$access_token,get_option('updraft_googledrive_remotepath'));
|
852 |
+
}
|
853 |
+
|
854 |
+
function ftp_backup($backup_array) {
|
855 |
+
if( !class_exists('ftp_wrapper')) {
|
856 |
+
require_once(dirname(__FILE__).'/includes/ftp.class.php');
|
857 |
+
}
|
858 |
+
//handle SSL and errors at some point TODO
|
859 |
+
$ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
|
860 |
+
$ftp->passive = true;
|
861 |
+
$ftp->connect();
|
862 |
+
//$ftp->make_dir(); we may need to recursively create dirs? TODO
|
863 |
+
|
864 |
+
$ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
|
865 |
+
foreach($backup_array as $file) {
|
866 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
867 |
+
if ($ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY)) {
|
868 |
+
$this->log("ERROR: $file_name: Successfully uploaded via FTP");
|
869 |
+
$this->uploaded_file($file);
|
870 |
+
} else {
|
871 |
+
$this->error("$file_name: Failed to upload to FTP" );
|
872 |
+
$this->log("ERROR: $file_name: Failed to upload to FTP" );
|
873 |
+
}
|
874 |
+
}
|
875 |
+
$this->prune_retained_backups("ftp",$ftp,$ftp_remote_path);
|
876 |
+
}
|
877 |
+
|
878 |
+
function delete_local($file) {
|
879 |
+
if(get_option('updraft_delete_local')) {
|
880 |
+
$this->log("Deleting local file: $file");
|
881 |
+
//need error checking so we don't delete what isn't successfully uploaded?
|
882 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
883 |
+
return unlink($fullpath);
|
884 |
+
}
|
885 |
+
return true;
|
886 |
+
}
|
887 |
+
|
888 |
+
function backup_dirs() {
|
889 |
+
if(!$this->backup_time) $this->backup_time_nonce();
|
890 |
+
$wp_themes_dir = WP_CONTENT_DIR.'/themes';
|
891 |
+
$wp_upload_dir = wp_upload_dir();
|
892 |
+
$wp_upload_dir = $wp_upload_dir['basedir'];
|
893 |
+
$wp_plugins_dir = WP_PLUGIN_DIR;
|
894 |
+
|
895 |
+
if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
|
896 |
+
|
897 |
+
$updraft_dir = $this->backups_dir_location();
|
898 |
+
if(!is_writable($updraft_dir)) $this->error('Backup directory is not writable, or does not exist.','fatal');
|
899 |
+
|
900 |
+
//get the blog name and rip out all non-alphanumeric chars other than _
|
901 |
+
$blog_name = str_replace(' ','_',get_bloginfo());
|
902 |
+
$blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
|
903 |
+
if(!$blog_name) $blog_name = 'non_alpha_name';
|
904 |
+
|
905 |
+
$backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
|
906 |
+
|
907 |
+
$backup_array = array();
|
908 |
+
|
909 |
+
# Plugins
|
910 |
+
@set_time_limit(900);
|
911 |
+
if (get_option('updraft_include_plugins', true)) {
|
912 |
+
$this->log("Beginning backup of plugins");
|
913 |
+
$full_path = $backup_file_base.'-plugins.zip';
|
914 |
+
$plugins = new PclZip($full_path);
|
915 |
+
# The paths in the zip should then begin with 'plugins', having removed WP_CONTENT_DIR from the front
|
916 |
+
if (!$plugins->create($wp_plugins_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
|
917 |
+
$this->error('Could not create plugins zip. Error was '.$php_errmsg,'fatal');
|
918 |
+
$this->log('ERROR: PclZip failure: Could not create plugins zip');
|
919 |
+
} else {
|
920 |
+
$this->log("Created plugins zip - file size is ".filesize($full_path)." bytes");
|
921 |
+
}
|
922 |
+
$backup_array['plugins'] = basename($full_path);
|
923 |
+
} else {
|
924 |
+
$this->log("No backup of plugins: excluded by user's options");
|
925 |
+
}
|
926 |
+
|
927 |
+
# Themes
|
928 |
+
@set_time_limit(900);
|
929 |
+
if (get_option('updraft_include_themes', true)) {
|
930 |
+
$this->log("Beginning backup of themes");
|
931 |
+
$full_path = $backup_file_base.'-themes.zip';
|
932 |
+
$themes = new PclZip($full_path);
|
933 |
+
if (!$themes->create($wp_themes_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
|
934 |
+
$this->error('Could not create themes zip. Error was '.$php_errmsg,'fatal');
|
935 |
+
$this->log('ERROR: PclZip failure: Could not create themes zip');
|
936 |
+
} else {
|
937 |
+
$this->log("Created themes zip - file size is ".filesize($full_path)." bytes");
|
938 |
+
}
|
939 |
+
$backup_array['themes'] = basename($full_path);
|
940 |
+
} else {
|
941 |
+
$this->log("No backup of themes: excluded by user's options");
|
942 |
+
}
|
943 |
+
|
944 |
+
# Uploads
|
945 |
+
@set_time_limit(900);
|
946 |
+
if (get_option('updraft_include_uploads', true)) {
|
947 |
+
$this->log("Beginning backup of uploads");
|
948 |
+
$full_path = $backup_file_base.'-uploads.zip';
|
949 |
+
$uploads = new PclZip($full_path);
|
950 |
+
if (!$uploads->create($wp_upload_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
|
951 |
+
$this->error('Could not create uploads zip. Error was '.$php_errmsg,'fatal');
|
952 |
+
$this->log('ERROR: PclZip failure: Could not create uploads zip');
|
953 |
+
} else {
|
954 |
+
$this->log("Created uploads zip - file size is ".filesize($full_path)." bytes");
|
955 |
+
}
|
956 |
+
$backup_array['uploads'] = basename($full_path);
|
957 |
+
} else {
|
958 |
+
$this->log("No backup of uploads: excluded by user's options");
|
959 |
+
}
|
960 |
+
|
961 |
+
# Others
|
962 |
+
@set_time_limit(900);
|
963 |
+
if (get_option('updraft_include_others', true)) {
|
964 |
+
$this->log("Beginning backup of other directories found in the content directory");
|
965 |
+
$full_path=$backup_file_base.'-others.zip';
|
966 |
+
$others = new PclZip($full_path);
|
967 |
+
// http://www.phpconcept.net/pclzip/user-guide/53
|
968 |
+
/* First parameter to create is:
|
969 |
+
An array of filenames or dirnames,
|
970 |
+
or
|
971 |
+
A string containing the filename or a dirname,
|
972 |
+
or
|
973 |
+
A string containing a list of filename or dirname separated by a comma.
|
974 |
+
*/
|
975 |
+
// First, see what we can find. We always want to exclude these:
|
976 |
+
$wp_themes_dir = WP_CONTENT_DIR.'/themes';
|
977 |
+
$wp_upload_dir = wp_upload_dir();
|
978 |
+
$wp_upload_dir = $wp_upload_dir['basedir'];
|
979 |
+
$wp_plugins_dir = WP_PLUGIN_DIR;
|
980 |
+
$updraft_dir = untrailingslashit(get_option('updraft_dir'));
|
981 |
+
|
982 |
+
# Initialise
|
983 |
+
$other_dirlist = array();
|
984 |
+
|
985 |
+
$others_skip = preg_split("/,/",get_option('updraft_include_others_exclude',UPDRAFT_DEFAULT_OTHERS_EXCLUDE));
|
986 |
+
# Make the values into the keys
|
987 |
+
$others_skip = array_flip($others_skip);
|
988 |
+
|
989 |
+
$this->log('Looking for candidates to back up in: '.WP_CONTENT_DIR);
|
990 |
+
if ($handle = opendir(WP_CONTENT_DIR)) {
|
991 |
+
while (false !== ($entry = readdir($handle))) {
|
992 |
+
$candidate = WP_CONTENT_DIR.'/'.$entry;
|
993 |
+
if ($entry == "." || $entry == "..") { ; }
|
994 |
+
elseif ($candidate == $updraft_dir) { $this->log("$entry: skipping: this is the updraft directory"); }
|
995 |
+
elseif ($candidate == $wp_themes_dir) { $this->log("$entry: skipping: this is the themes directory"); }
|
996 |
+
elseif ($candidate == $wp_upload_dir) { $this->log("$entry: skipping: this is the uploads directory"); }
|
997 |
+
elseif ($candidate == $wp_plugins_dir) { $this->log("$entry: skipping: this is the plugins directory"); }
|
998 |
+
elseif (isset($others_skip[$entry])) { $this->log("$entry: skipping: excluded by options"); }
|
999 |
+
else { $this->log("$entry: adding to list"); array_push($other_dirlist,$candidate); }
|
1000 |
+
}
|
1001 |
+
} else {
|
1002 |
+
$this->log('ERROR: Could not read the content directory: '.WP_CONTENT_DIR);
|
1003 |
+
}
|
1004 |
+
|
1005 |
+
if (count($other_dirlist)>0) {
|
1006 |
+
if (!$others->create($other_dirlist,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
|
1007 |
+
$this->error('Could not create other zip. Error was '.$php_errmsg,'fatal');
|
1008 |
+
$this->log('ERROR: PclZip failure: Could not create other zip');
|
1009 |
+
} else {
|
1010 |
+
$this->log("Created other directories zip - file size is ".filesize($full_path)." bytes");
|
1011 |
+
}
|
1012 |
+
$backup_array['others'] = basename($full_path);
|
1013 |
+
} else {
|
1014 |
+
$this->log("No backup of other directories: there was nothing found to back up");
|
1015 |
+
}
|
1016 |
+
} else {
|
1017 |
+
$this->log("No backup of other directories: excluded by user's options");
|
1018 |
+
}
|
1019 |
+
return $backup_array;
|
1020 |
+
}
|
1021 |
+
|
1022 |
+
function save_backup_history($backup_array) {
|
1023 |
+
//TODO: this stores full paths right now. should probably concatenate with ABSPATH to make it easier to move sites
|
1024 |
+
if(is_array($backup_array)) {
|
1025 |
+
$backup_history = get_option('updraft_backup_history');
|
1026 |
+
$backup_history = (is_array($backup_history)) ? $backup_history : array();
|
1027 |
+
$backup_history[$this->backup_time] = $backup_array;
|
1028 |
+
update_option('updraft_backup_history',$backup_history);
|
1029 |
+
} else {
|
1030 |
+
$this->error('Could not save backup history because we have no backup array. Backup probably failed.');
|
1031 |
+
}
|
1032 |
+
}
|
1033 |
+
|
1034 |
+
function get_backup_history() {
|
1035 |
+
//$backup_history = get_option('updraft_backup_history');
|
1036 |
+
//by doing a raw DB query to get the most up-to-date data from this option we slightly narrow the window for the multiple-cron race condition
|
1037 |
+
global $wpdb;
|
1038 |
+
$backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
|
1039 |
+
if(is_array($backup_history)) {
|
1040 |
+
krsort($backup_history); //reverse sort so earliest backup is last on the array. this way we can array_pop
|
1041 |
+
} else {
|
1042 |
+
$backup_history = array();
|
1043 |
+
}
|
1044 |
+
return $backup_history;
|
1045 |
+
}
|
1046 |
+
|
1047 |
+
|
1048 |
+
/*START OF WB-DB-BACKUP BLOCK*/
|
1049 |
+
|
1050 |
+
function backup_db() {
|
1051 |
+
|
1052 |
+
$total_tables = 0;
|
1053 |
+
|
1054 |
+
global $table_prefix, $wpdb;
|
1055 |
+
if(!$this->backup_time) {
|
1056 |
+
$this->backup_time_nonce();
|
1057 |
+
}
|
1058 |
+
|
1059 |
+
$all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
|
1060 |
+
$all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
|
1061 |
+
|
1062 |
+
$updraft_dir = $this->backups_dir_location();
|
1063 |
+
//get the blog name and rip out all non-alphanumeric chars other than _
|
1064 |
+
$blog_name = str_replace(' ','_',get_bloginfo());
|
1065 |
+
$blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
|
1066 |
+
if(!$blog_name) {
|
1067 |
+
$blog_name = 'non_alpha_name';
|
1068 |
+
}
|
1069 |
+
|
1070 |
+
$backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
|
1071 |
+
if (is_writable($updraft_dir)) {
|
1072 |
+
if (function_exists('gzopen')) {
|
1073 |
+
$this->dbhandle = @gzopen($backup_file_base.'-db.gz','w');
|
1074 |
+
} else {
|
1075 |
+
$this->dbhandle = @fopen($backup_file_base.'-db.gz', 'w');
|
1076 |
+
}
|
1077 |
+
if(!$this->dbhandle) {
|
1078 |
+
//$this->error(__('Could not open the backup file for writing!','wp-db-backup'));
|
1079 |
+
}
|
1080 |
+
} else {
|
1081 |
+
//$this->error(__('The backup directory is not writable!','wp-db-backup'));
|
1082 |
+
}
|
1083 |
+
|
1084 |
+
//Begin new backup of MySql
|
1085 |
+
$this->stow("# " . __('WordPress MySQL database backup','wp-db-backup') . "\n");
|
1086 |
+
$this->stow("#\n");
|
1087 |
+
$this->stow("# " . sprintf(__('Generated: %s','wp-db-backup'),date("l j. F Y H:i T")) . "\n");
|
1088 |
+
$this->stow("# " . sprintf(__('Hostname: %s','wp-db-backup'),DB_HOST) . "\n");
|
1089 |
+
$this->stow("# " . sprintf(__('Database: %s','wp-db-backup'),$this->backquote(DB_NAME)) . "\n");
|
1090 |
+
$this->stow("# --------------------------------------------------------\n");
|
1091 |
+
|
1092 |
+
|
1093 |
+
if (defined("DB_CHARSET")) {
|
1094 |
+
$this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n");
|
1095 |
+
$this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n");
|
1096 |
+
$this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n");
|
1097 |
+
$this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n");
|
1098 |
+
}
|
1099 |
+
$this->stow("/*!40101 SET foreign_key_checks = 0 */;\n");
|
1100 |
+
|
1101 |
+
foreach ($all_tables as $table) {
|
1102 |
+
$total_tables++;
|
1103 |
+
// Increase script execution time-limit to 15 min for every table.
|
1104 |
+
if ( !ini_get('safe_mode') || strtolower(ini_get('safe_mode')) == "off") @set_time_limit(15*60);
|
1105 |
+
# === is needed, otherwise 'false' matches (i.e. prefix does not match)
|
1106 |
+
if ( strpos($table, $table_prefix) === 0 ) {
|
1107 |
+
// Create the SQL statements
|
1108 |
+
$this->stow("# --------------------------------------------------------\n");
|
1109 |
+
$this->stow("# " . sprintf(__('Table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
|
1110 |
+
$this->stow("# --------------------------------------------------------\n");
|
1111 |
+
$this->backup_table($table);
|
1112 |
+
} else {
|
1113 |
+
$this->stow("# --------------------------------------------------------\n");
|
1114 |
+
$this->stow("# " . sprintf(__('Skipping non-WP table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
|
1115 |
+
$this->stow("# --------------------------------------------------------\n");
|
1116 |
+
}
|
1117 |
+
}
|
1118 |
+
|
1119 |
+
if (defined("DB_CHARSET")) {
|
1120 |
+
$this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n");
|
1121 |
+
$this->stow("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n");
|
1122 |
+
$this->stow("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
|
1123 |
+
}
|
1124 |
+
|
1125 |
+
$this->close($this->dbhandle);
|
1126 |
+
|
1127 |
+
if (count($this->errors)) {
|
1128 |
+
return false;
|
1129 |
+
} else {
|
1130 |
+
# Encrypt, if requested
|
1131 |
+
$encryption = get_option('updraft_encryptionphrase');
|
1132 |
+
if (strlen($encryption) > 0) {
|
1133 |
+
$this->log("Database: applying encryption");
|
1134 |
+
$encryption_error = 0;
|
1135 |
+
require_once(dirname(__FILE__).'/includes/Rijndael.php');
|
1136 |
+
$rijndael = new Crypt_Rijndael();
|
1137 |
+
$rijndael->setKey($encryption);
|
1138 |
+
$in_handle = @fopen($backup_file_base.'-db.gz','r');
|
1139 |
+
$buffer = "";
|
1140 |
+
while (!feof ($in_handle)) {
|
1141 |
+
$buffer .= fread($in_handle, 16384);
|
1142 |
+
}
|
1143 |
+
fclose ($in_handle);
|
1144 |
+
$out_handle = @fopen($backup_file_base.'-db.gz.crypt','w');
|
1145 |
+
if (!fwrite($out_handle, $rijndael->encrypt($buffer))) {$encryption_error = 1;}
|
1146 |
+
fclose ($out_handle);
|
1147 |
+
if (0 == $encryption_error) {
|
1148 |
+
# Delete unencrypted file
|
1149 |
+
@unlink($backup_file_base.'-db.gz');
|
1150 |
+
return basename($backup_file_base.'-db.gz.crypt');
|
1151 |
+
} else {
|
1152 |
+
$this->error("Encryption error occurred when encrypting database. Aborted.");
|
1153 |
+
}
|
1154 |
+
} else {
|
1155 |
+
return basename($backup_file_base.'-db.gz');
|
1156 |
+
}
|
1157 |
+
}
|
1158 |
+
$this->log("Total database tables backed up: $total_tables");
|
1159 |
+
|
1160 |
+
} //wp_db_backup
|
1161 |
+
|
1162 |
+
/**
|
1163 |
+
* Taken partially from phpMyAdmin and partially from
|
1164 |
+
* Alain Wolf, Zurich - Switzerland
|
1165 |
+
* Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
|
1166 |
+
* Modified by Scott Merrill (http://www.skippy.net/)
|
1167 |
+
* to use the WordPress $wpdb object
|
1168 |
+
* @param string $table
|
1169 |
+
* @param string $segment
|
1170 |
+
* @return void
|
1171 |
+
*/
|
1172 |
+
function backup_table($table, $segment = 'none') {
|
1173 |
+
global $wpdb;
|
1174 |
+
|
1175 |
+
$total_rows = 0;
|
1176 |
+
|
1177 |
+
$table_structure = $wpdb->get_results("DESCRIBE $table");
|
1178 |
+
if (! $table_structure) {
|
1179 |
+
//$this->error(__('Error getting table details','wp-db-backup') . ": $table");
|
1180 |
+
return false;
|
1181 |
+
}
|
1182 |
+
|
1183 |
+
if(($segment == 'none') || ($segment == 0)) {
|
1184 |
+
// Add SQL statement to drop existing table
|
1185 |
+
$this->stow("\n\n");
|
1186 |
+
$this->stow("#\n");
|
1187 |
+
$this->stow("# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$this->backquote($table)) . "\n");
|
1188 |
+
$this->stow("#\n");
|
1189 |
+
$this->stow("\n");
|
1190 |
+
$this->stow("DROP TABLE IF EXISTS " . $this->backquote($table) . ";\n");
|
1191 |
+
|
1192 |
+
// Table structure
|
1193 |
+
// Comment in SQL-file
|
1194 |
+
$this->stow("\n\n");
|
1195 |
+
$this->stow("#\n");
|
1196 |
+
$this->stow("# " . sprintf(__('Table structure of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
|
1197 |
+
$this->stow("#\n");
|
1198 |
+
$this->stow("\n");
|
1199 |
+
|
1200 |
+
$create_table = $wpdb->get_results("SHOW CREATE TABLE $table", ARRAY_N);
|
1201 |
+
if (false === $create_table) {
|
1202 |
+
$err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table);
|
1203 |
+
//$this->error($err_msg);
|
1204 |
+
$this->stow("#\n# $err_msg\n#\n");
|
1205 |
+
}
|
1206 |
+
$this->stow($create_table[0][1] . ' ;');
|
1207 |
+
|
1208 |
+
if (false === $table_structure) {
|
1209 |
+
$err_msg = sprintf(__('Error getting table structure of %s','wp-db-backup'), $table);
|
1210 |
+
//$this->error($err_msg);
|
1211 |
+
$this->stow("#\n# $err_msg\n#\n");
|
1212 |
+
}
|
1213 |
+
|
1214 |
+
// Comment in SQL-file
|
1215 |
+
$this->stow("\n\n");
|
1216 |
+
$this->stow("#\n");
|
1217 |
+
$this->stow('# ' . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
|
1218 |
+
$this->stow("#\n");
|
1219 |
+
}
|
1220 |
+
|
1221 |
+
if(($segment == 'none') || ($segment >= 0)) {
|
1222 |
+
$defs = array();
|
1223 |
+
$ints = array();
|
1224 |
+
foreach ($table_structure as $struct) {
|
1225 |
+
if ( (0 === strpos($struct->Type, 'tinyint')) ||
|
1226 |
+
(0 === strpos(strtolower($struct->Type), 'smallint')) ||
|
1227 |
+
(0 === strpos(strtolower($struct->Type), 'mediumint')) ||
|
1228 |
+
(0 === strpos(strtolower($struct->Type), 'int')) ||
|
1229 |
+
(0 === strpos(strtolower($struct->Type), 'bigint')) ) {
|
1230 |
+
$defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
|
1231 |
+
$ints[strtolower($struct->Field)] = "1";
|
1232 |
+
}
|
1233 |
+
}
|
1234 |
+
|
1235 |
+
|
1236 |
+
// Batch by $row_inc
|
1237 |
+
if ( ! defined('ROWS_PER_SEGMENT') ) {
|
1238 |
+
define('ROWS_PER_SEGMENT', 100);
|
1239 |
+
}
|
1240 |
+
|
1241 |
+
if($segment == 'none') {
|
1242 |
+
$row_start = 0;
|
1243 |
+
$row_inc = ROWS_PER_SEGMENT;
|
1244 |
+
} else {
|
1245 |
+
$row_start = $segment * ROWS_PER_SEGMENT;
|
1246 |
+
$row_inc = ROWS_PER_SEGMENT;
|
1247 |
+
}
|
1248 |
+
do {
|
1249 |
+
// don't include extra stuff, if so requested
|
1250 |
+
$excs = array('revisions' => 0, 'spam' => 1); //TODO, FIX THIS
|
1251 |
+
$where = '';
|
1252 |
+
if ( is_array($excs['spam'] ) && in_array($table, $excs['spam']) ) {
|
1253 |
+
$where = ' WHERE comment_approved != "spam"';
|
1254 |
+
} elseif ( is_array($excs['revisions'] ) && in_array($table, $excs['revisions']) ) {
|
1255 |
+
$where = ' WHERE post_type != "revision"';
|
1256 |
+
}
|
1257 |
+
|
1258 |
+
if ( !ini_get('safe_mode') || strtolower(ini_get('safe_mode')) == "off") @set_time_limit(15*60);
|
1259 |
+
$table_data = $wpdb->get_results("SELECT * FROM $table $where LIMIT {$row_start}, {$row_inc}", ARRAY_A);
|
1260 |
+
$entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
|
1261 |
+
// \x08\\x09, not required
|
1262 |
+
$search = array("\x00", "\x0a", "\x0d", "\x1a");
|
1263 |
+
$replace = array('\0', '\n', '\r', '\Z');
|
1264 |
+
if($table_data) {
|
1265 |
+
foreach ($table_data as $row) {
|
1266 |
+
$total_rows++;
|
1267 |
+
$values = array();
|
1268 |
+
foreach ($row as $key => $value) {
|
1269 |
+
if ($ints[strtolower($key)]) {
|
1270 |
+
// make sure there are no blank spots in the insert syntax,
|
1271 |
+
// yet try to avoid quotation marks around integers
|
1272 |
+
$value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value;
|
1273 |
+
$values[] = ( '' === $value ) ? "''" : $value;
|
1274 |
+
} else {
|
1275 |
+
$values[] = "'" . str_replace($search, $replace, $this->sql_addslashes($value)) . "'";
|
1276 |
+
}
|
1277 |
+
}
|
1278 |
+
$this->stow(" \n" . $entries . implode(', ', $values) . ');');
|
1279 |
+
}
|
1280 |
+
$row_start += $row_inc;
|
1281 |
+
}
|
1282 |
+
} while((count($table_data) > 0) and ($segment=='none'));
|
1283 |
+
}
|
1284 |
+
|
1285 |
+
if(($segment == 'none') || ($segment < 0)) {
|
1286 |
+
// Create footer/closing comment in SQL-file
|
1287 |
+
$this->stow("\n");
|
1288 |
+
$this->stow("#\n");
|
1289 |
+
$this->stow("# " . sprintf(__('End of data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
|
1290 |
+
$this->stow("# --------------------------------------------------------\n");
|
1291 |
+
$this->stow("\n");
|
1292 |
+
}
|
1293 |
+
$this->log("Table $table: Total rows added: $total_rows");
|
1294 |
+
|
1295 |
+
} // end backup_table()
|
1296 |
+
|
1297 |
+
|
1298 |
+
function stow($query_line) {
|
1299 |
+
if (function_exists('gzopen')) {
|
1300 |
+
if(! @gzwrite($this->dbhandle, $query_line)) {
|
1301 |
+
//$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
|
1302 |
+
}
|
1303 |
+
} else {
|
1304 |
+
if(false === @fwrite($this->dbhandle, $query_line)) {
|
1305 |
+
//$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
|
1306 |
+
}
|
1307 |
+
}
|
1308 |
+
}
|
1309 |
+
|
1310 |
+
|
1311 |
+
function close($handle) {
|
1312 |
+
if (function_exists('gzopen')) {
|
1313 |
+
gzclose($handle);
|
1314 |
+
} else {
|
1315 |
+
fclose($handle);
|
1316 |
+
}
|
1317 |
+
}
|
1318 |
+
|
1319 |
+
function error($error,$severity='') {
|
1320 |
+
$this->errors[] = $error;
|
1321 |
+
return true;
|
1322 |
+
}
|
1323 |
+
|
1324 |
+
/**
|
1325 |
+
* Add backquotes to tables and db-names in
|
1326 |
+
* SQL queries. Taken from phpMyAdmin.
|
1327 |
+
*/
|
1328 |
+
function backquote($a_name) {
|
1329 |
+
if (!empty($a_name) && $a_name != '*') {
|
1330 |
+
if (is_array($a_name)) {
|
1331 |
+
$result = array();
|
1332 |
+
reset($a_name);
|
1333 |
+
while(list($key, $val) = each($a_name))
|
1334 |
+
$result[$key] = '`' . $val . '`';
|
1335 |
+
return $result;
|
1336 |
+
} else {
|
1337 |
+
return '`' . $a_name . '`';
|
1338 |
+
}
|
1339 |
+
} else {
|
1340 |
+
return $a_name;
|
1341 |
+
}
|
1342 |
+
}
|
1343 |
+
|
1344 |
+
/**
|
1345 |
+
* Better addslashes for SQL queries.
|
1346 |
+
* Taken from phpMyAdmin.
|
1347 |
+
*/
|
1348 |
+
function sql_addslashes($a_string = '', $is_like = false) {
|
1349 |
+
if ($is_like) $a_string = str_replace('\\', '\\\\\\\\', $a_string);
|
1350 |
+
else $a_string = str_replace('\\', '\\\\', $a_string);
|
1351 |
+
return str_replace('\'', '\\\'', $a_string);
|
1352 |
+
}
|
1353 |
+
|
1354 |
+
/*END OF WP-DB-BACKUP BLOCK */
|
1355 |
+
|
1356 |
+
/*
|
1357 |
+
this function is both the backup scheduler and ostensibly a filter callback for saving the option.
|
1358 |
+
it is called in the register_setting for the updraft_interval, which means when the admin settings
|
1359 |
+
are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter)
|
1360 |
+
so the option can be properly saved.
|
1361 |
+
*/
|
1362 |
+
function schedule_backup($interval) {
|
1363 |
+
//clear schedule and add new so we don't stack up scheduled backups
|
1364 |
+
wp_clear_scheduled_hook('updraft_backup');
|
1365 |
+
switch($interval) {
|
1366 |
+
case 'daily':
|
1367 |
+
case 'weekly':
|
1368 |
+
case 'monthly':
|
1369 |
+
wp_schedule_event(time()+30, $interval, 'updraft_backup');
|
1370 |
+
break;
|
1371 |
+
}
|
1372 |
+
return wp_filter_nohtml_kses($interval);
|
1373 |
+
}
|
1374 |
+
|
1375 |
+
function schedule_backup_database($interval) {
|
1376 |
+
//clear schedule and add new so we don't stack up scheduled backups
|
1377 |
+
wp_clear_scheduled_hook('updraft_backup_database');
|
1378 |
+
switch($interval) {
|
1379 |
+
case 'daily':
|
1380 |
+
case 'weekly':
|
1381 |
+
case 'monthly':
|
1382 |
+
wp_schedule_event(time()+30, $interval, 'updraft_backup_database');
|
1383 |
+
break;
|
1384 |
+
}
|
1385 |
+
return wp_filter_nohtml_kses($interval);
|
1386 |
+
}
|
1387 |
+
|
1388 |
+
//wp-cron only has hourly, daily and twicedaily, so we need to add weekly and monthly.
|
1389 |
+
function modify_cron_schedules($schedules) {
|
1390 |
+
$schedules['weekly'] = array(
|
1391 |
+
'interval' => 604800,
|
1392 |
+
'display' => 'Once Weekly'
|
1393 |
+
);
|
1394 |
+
$schedules['monthly'] = array(
|
1395 |
+
'interval' => 2592000,
|
1396 |
+
'display' => 'Once Monthly'
|
1397 |
+
);
|
1398 |
+
return $schedules;
|
1399 |
+
}
|
1400 |
+
|
1401 |
+
function backups_dir_location() {
|
1402 |
+
$updraft_dir = untrailingslashit(get_option('updraft_dir'));
|
1403 |
+
$default_backup_dir = WP_CONTENT_DIR.'/updraft';
|
1404 |
+
//if the option isn't set, default it to /backups inside the upload dir
|
1405 |
+
$updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
|
1406 |
+
//check for the existence of the dir and an enumeration preventer.
|
1407 |
+
if(!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) {
|
1408 |
+
@mkdir($updraft_dir,0777,true); //recursively create the dir with 0777 permissions. 0777 is default for php creation. not ideal, but I'll get back to this
|
1409 |
+
@file_put_contents($updraft_dir.'/index.html','Nothing to see here.');
|
1410 |
+
@file_put_contents($updraft_dir.'/.htaccess','deny from all');
|
1411 |
+
}
|
1412 |
+
return $updraft_dir;
|
1413 |
+
}
|
1414 |
+
|
1415 |
+
function updraft_download_backup() {
|
1416 |
+
$type = $_POST['type'];
|
1417 |
+
$timestamp = (int)$_POST['timestamp'];
|
1418 |
+
$backup_history = $this->get_backup_history();
|
1419 |
+
$file = $backup_history[$timestamp][$type];
|
1420 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
1421 |
+
if(!is_readable($fullpath)) {
|
1422 |
+
//if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
|
1423 |
+
$this->download_backup($file);
|
1424 |
+
}
|
1425 |
+
if(@is_readable($fullpath) && is_file($fullpath)) {
|
1426 |
+
$len = filesize($fullpath);
|
1427 |
+
|
1428 |
+
$filearr = explode('.',$file);
|
1429 |
+
// //we've only got zip and gz...for now
|
1430 |
+
$file_ext = array_pop($filearr);
|
1431 |
+
if($file_ext == 'zip') {
|
1432 |
+
header('Content-type: application/zip');
|
1433 |
+
} else {
|
1434 |
+
// This catches both when what was popped was 'crypt' (*-db.gz.crypt) and when it was 'gz' (unencrypted)
|
1435 |
+
header('Content-type: application/x-gzip');
|
1436 |
+
}
|
1437 |
+
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
|
1438 |
+
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
1439 |
+
header("Content-Length: $len;");
|
1440 |
+
if ($file_ext == 'crypt') {
|
1441 |
+
header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";");
|
1442 |
+
} else {
|
1443 |
+
header("Content-Disposition: attachment; filename=\"$file\";");
|
1444 |
+
}
|
1445 |
+
ob_end_flush();
|
1446 |
+
if ($file_ext == 'crypt') {
|
1447 |
+
$encryption = get_option('updraft_encryptionphrase');
|
1448 |
+
if ($encryption == "") {
|
1449 |
+
$this->error('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.');
|
1450 |
+
} else {
|
1451 |
+
require_once(dirname(__FILE__).'/includes/Rijndael.php');
|
1452 |
+
$rijndael = new Crypt_Rijndael();
|
1453 |
+
$rijndael->setKey($encryption);
|
1454 |
+
$in_handle = fopen($fullpath,'r');
|
1455 |
+
$ciphertext = "";
|
1456 |
+
while (!feof ($in_handle)) {
|
1457 |
+
$ciphertext .= fread($in_handle, 16384);
|
1458 |
+
}
|
1459 |
+
fclose ($in_handle);
|
1460 |
+
print $rijndael->decrypt($ciphertext);
|
1461 |
+
}
|
1462 |
+
} else {
|
1463 |
+
readfile($fullpath);
|
1464 |
+
}
|
1465 |
+
$this->delete_local($file);
|
1466 |
+
exit; //we exit immediately because otherwise admin-ajax appends an additional zero to the end for some reason I don't understand. seriously, why die('0')?
|
1467 |
+
} else {
|
1468 |
+
echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then S3 or Google Drive or FTP retrieval may have failed. (Note that Google Drive downloading is not yet supported - you need to download manually if you use Google Drive).';
|
1469 |
+
}
|
1470 |
+
}
|
1471 |
+
|
1472 |
+
function download_backup($file) {
|
1473 |
+
switch(get_option('updraft_service')) {
|
1474 |
+
case 'googledrive':
|
1475 |
+
$this->download_googledrive_backup($file);
|
1476 |
+
break;
|
1477 |
+
case 's3':
|
1478 |
+
$this->download_s3_backup($file);
|
1479 |
+
break;
|
1480 |
+
case 'ftp':
|
1481 |
+
$this->download_ftp_backup($file);
|
1482 |
+
break;
|
1483 |
+
default:
|
1484 |
+
$this->error('Automatic backup restoration is only available via S3, FTP, and local. Email and downloaded backup restoration must be performed manually.');
|
1485 |
+
}
|
1486 |
+
}
|
1487 |
+
|
1488 |
+
function download_googledrive_backup($file) {
|
1489 |
+
|
1490 |
+
require_once(dirname(__FILE__).'/includes/class-gdocs.php');
|
1491 |
+
|
1492 |
+
// Do we have an access token?
|
1493 |
+
if ( !$access_token = $this->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
|
1494 |
+
$this->error('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
|
1495 |
+
return false;
|
1496 |
+
}
|
1497 |
+
|
1498 |
+
$this->gdocs_access_token = $access_token;
|
1499 |
+
|
1500 |
+
// Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
|
1501 |
+
if ( is_wp_error( $e = $this->need_gdocs() ) ) return false;
|
1502 |
+
|
1503 |
+
$ids = get_option('updraft_file_ids', array());
|
1504 |
+
if (!isset($ids[$file])) {
|
1505 |
+
$this->error("Google Drive error: $file: could not download: could not find a record of the Google Drive file ID for this file");
|
1506 |
+
return;
|
1507 |
+
} else {
|
1508 |
+
$content_link = $this->gdocs->get_content_link( $ids[$file], $file );
|
1509 |
+
if (is_wp_error($content_link)) {
|
1510 |
+
$this->error("Could not find $file in order to download it (id: ".$ids[$file].")");
|
1511 |
+
foreach ($content_link->get_error_messages() as $msg) {
|
1512 |
+
$this->error($msg);
|
1513 |
+
}
|
1514 |
+
return false;
|
1515 |
+
}
|
1516 |
+
// Actually download the thing
|
1517 |
+
$download_to = trailingslashit(get_option('updraft_dir')).$file;
|
1518 |
+
$this->gdocs->download_data($content_link, $download_to);
|
1519 |
+
|
1520 |
+
if (filesize($download_to) >0) {
|
1521 |
+
return true;
|
1522 |
+
} else {
|
1523 |
+
$this->error("Google Drive error: zero-size file was downloaded");
|
1524 |
+
return false;
|
1525 |
+
}
|
1526 |
+
|
1527 |
+
}
|
1528 |
+
|
1529 |
+
return;
|
1530 |
+
|
1531 |
+
}
|
1532 |
+
|
1533 |
+
function download_s3_backup($file) {
|
1534 |
+
if(!class_exists('S3')) {
|
1535 |
+
require_once(dirname(__FILE__).'/includes/S3.php');
|
1536 |
+
}
|
1537 |
+
$s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
|
1538 |
+
$bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
|
1539 |
+
$bucket_path = "";
|
1540 |
+
if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
|
1541 |
+
$bucket_name = $bmatches[1];
|
1542 |
+
$bucket_path = $bmatches[2]."/";
|
1543 |
+
}
|
1544 |
+
if (@$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
|
1545 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
1546 |
+
if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath)) {
|
1547 |
+
$this->error("S3 Error: Failed to download $fullpath. Error was ".$php_errormsg);
|
1548 |
+
}
|
1549 |
+
} else {
|
1550 |
+
$this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
|
1551 |
+
}
|
1552 |
+
}
|
1553 |
+
|
1554 |
+
function download_ftp_backup($file) {
|
1555 |
+
if( !class_exists('ftp_wrapper')) require_once(dirname(__FILE__).'/includes/ftp.class.php');
|
1556 |
+
|
1557 |
+
//handle SSL and errors at some point TODO
|
1558 |
+
$ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
|
1559 |
+
$ftp->passive = true;
|
1560 |
+
$ftp->connect();
|
1561 |
+
//$ftp->make_dir(); we may need to recursively create dirs? TODO
|
1562 |
+
|
1563 |
+
$ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
|
1564 |
+
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
1565 |
+
$ftp->get($fullpath,$ftp_remote_path.$file,FTP_BINARY);
|
1566 |
+
}
|
1567 |
+
|
1568 |
+
function restore_backup($timestamp) {
|
1569 |
+
global $wp_filesystem;
|
1570 |
+
$backup_history = get_option('updraft_backup_history');
|
1571 |
+
if(!is_array($backup_history[$timestamp])) {
|
1572 |
+
echo '<p>This backup does not exist in the backup history - restoration aborted. Timestamp: '.$timestamp.'</p><br/>';
|
1573 |
+
return false;
|
1574 |
+
}
|
1575 |
+
|
1576 |
+
$credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_restore&backup_timestamp=$timestamp");
|
1577 |
+
WP_Filesystem($credentials);
|
1578 |
+
if ( $wp_filesystem->errors->get_error_code() ) {
|
1579 |
+
foreach ( $wp_filesystem->errors->get_error_messages() as $message )
|
1580 |
+
show_message($message);
|
1581 |
+
exit;
|
1582 |
+
}
|
1583 |
+
|
1584 |
+
//if we make it this far then WP_Filesystem has been instantiated and is functional (tested with ftpext, what about suPHP and other situations where direct may work?)
|
1585 |
+
echo '<span style="font-weight:bold">Restoration Progress </span><div id="updraft-restore-progress">';
|
1586 |
+
|
1587 |
+
$updraft_dir = trailingslashit(get_option('updraft_dir'));
|
1588 |
+
foreach($backup_history[$timestamp] as $type=>$file) {
|
1589 |
+
$fullpath = $updraft_dir.$file;
|
1590 |
+
if(!is_readable($fullpath) && $type != 'db') {
|
1591 |
+
$this->download_backup($file);
|
1592 |
+
}
|
1593 |
+
# Types: uploads, themes, plugins, others, db
|
1594 |
+
if(is_readable($fullpath) && $type != 'db') {
|
1595 |
+
if(!class_exists('WP_Upgrader')) {
|
1596 |
+
require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
|
1597 |
+
}
|
1598 |
+
require_once('includes/updraft-restorer.php');
|
1599 |
+
$restorer = new Updraft_Restorer();
|
1600 |
+
$val = $restorer->restore_backup($fullpath,$type);
|
1601 |
+
if(is_wp_error($val)) {
|
1602 |
+
print_r($val);
|
1603 |
+
echo '</div>'; //close the updraft_restore_progress div even if we error
|
1604 |
+
return false;
|
1605 |
+
}
|
1606 |
+
}
|
1607 |
+
}
|
1608 |
+
echo '</div>'; //close the updraft_restore_progress div
|
1609 |
+
# The 'off' check is for badly configured setups - http://wordpress.org/support/topic/plugin-wp-super-cache-warning-php-safe-mode-enabled-but-safe-mode-is-off
|
1610 |
+
if(ini_get('safe_mode') && strtolower(ini_get('safe_mode')) != "off") {
|
1611 |
+
echo "<p>DB could not be restored because safe_mode is active on your server. You will need to manually restore the file via phpMyAdmin or another method.</p><br/>";
|
1612 |
+
return false;
|
1613 |
+
}
|
1614 |
+
return true;
|
1615 |
+
}
|
1616 |
+
|
1617 |
+
//deletes the -old directories that are created when a backup is restored.
|
1618 |
+
function delete_old_dirs() {
|
1619 |
+
global $wp_filesystem;
|
1620 |
+
$credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_delete_old_dirs");
|
1621 |
+
WP_Filesystem($credentials);
|
1622 |
+
if ( $wp_filesystem->errors->get_error_code() ) {
|
1623 |
+
foreach ( $wp_filesystem->errors->get_error_messages() as $message )
|
1624 |
+
show_message($message);
|
1625 |
+
exit;
|
1626 |
+
}
|
1627 |
+
|
1628 |
+
$to_delete = array('themes-old','plugins-old','uploads-old','others-old');
|
1629 |
+
|
1630 |
+
foreach($to_delete as $name) {
|
1631 |
+
//recursively delete
|
1632 |
+
if(!$wp_filesystem->delete(WP_CONTENT_DIR.'/'.$name, true)) {
|
1633 |
+
return false;
|
1634 |
+
}
|
1635 |
+
}
|
1636 |
+
return true;
|
1637 |
+
}
|
1638 |
+
|
1639 |
+
//scans the content dir to see if any -old dirs are present
|
1640 |
+
function scan_old_dirs() {
|
1641 |
+
$dirArr = scandir(WP_CONTENT_DIR);
|
1642 |
+
foreach($dirArr as $dir) {
|
1643 |
+
if(strpos($dir,'-old') !== false) {
|
1644 |
+
return true;
|
1645 |
+
}
|
1646 |
+
}
|
1647 |
+
return false;
|
1648 |
+
}
|
1649 |
+
|
1650 |
+
|
1651 |
+
function retain_range($input) {
|
1652 |
+
$input = (int)$input;
|
1653 |
+
if($input > 0 && $input < 3650) {
|
1654 |
+
return $input;
|
1655 |
+
} else {
|
1656 |
+
return 1;
|
1657 |
+
}
|
1658 |
+
}
|
1659 |
+
|
1660 |
+
function create_backup_dir() {
|
1661 |
+
global $wp_filesystem;
|
1662 |
+
$credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_create_backup_dir");
|
1663 |
+
WP_Filesystem($credentials);
|
1664 |
+
if ( $wp_filesystem->errors->get_error_code() ) {
|
1665 |
+
foreach ( $wp_filesystem->errors->get_error_messages() as $message )
|
1666 |
+
show_message($message);
|
1667 |
+
exit;
|
1668 |
+
}
|
1669 |
+
|
1670 |
+
$updraft_dir = untrailingslashit(get_option('updraft_dir'));
|
1671 |
+
$default_backup_dir = WP_CONTENT_DIR.'/updraft';
|
1672 |
+
$updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
|
1673 |
+
|
1674 |
+
//chmod the backup dir to 0777. ideally we'd rather chgrp it but i'm not sure if it's possible to detect the group apache is running under (or what if it's not apache...)
|
1675 |
+
if(!$wp_filesystem->mkdir($updraft_dir, 0777)) {
|
1676 |
+
return false;
|
1677 |
+
}
|
1678 |
+
return true;
|
1679 |
+
}
|
1680 |
+
|
1681 |
+
|
1682 |
+
function memory_check_current() {
|
1683 |
+
# Returns in megabytes
|
1684 |
+
$memory_limit = ini_get('memory_limit');
|
1685 |
+
$memory_unit = $memory_limit[strlen($memory_limit)-1];
|
1686 |
+
$memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
|
1687 |
+
switch($memory_unit) {
|
1688 |
+
case 'K':
|
1689 |
+
$memory_limit = $memory_limit/1024;
|
1690 |
+
break;
|
1691 |
+
case 'G':
|
1692 |
+
$memory_limit = $memory_limit*1024;
|
1693 |
+
break;
|
1694 |
+
case 'M':
|
1695 |
+
//assumed size, no change needed
|
1696 |
+
break;
|
1697 |
+
}
|
1698 |
+
return $memory_limit;
|
1699 |
+
}
|
1700 |
+
|
1701 |
+
function memory_check($memory) {
|
1702 |
+
$memory_limit = $this->memory_check_current();
|
1703 |
+
return ($memory_limit >= $memory)?true:false;
|
1704 |
+
}
|
1705 |
+
|
1706 |
+
function execution_time_check($time) {
|
1707 |
+
return (ini_get('max_execution_time') >= $time)?true:false;
|
1708 |
+
}
|
1709 |
+
|
1710 |
+
function admin_init() {
|
1711 |
+
if(get_option('updraft_debug_mode')) {
|
1712 |
+
ini_set('display_errors',1);
|
1713 |
+
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
|
1714 |
+
ini_set('track_errors',1);
|
1715 |
+
}
|
1716 |
+
wp_enqueue_script('jquery');
|
1717 |
+
register_setting( 'updraft-options-group', 'updraft_interval', array($this,'schedule_backup') );
|
1718 |
+
register_setting( 'updraft-options-group', 'updraft_interval_database', array($this,'schedule_backup_database') );
|
1719 |
+
register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
|
1720 |
+
register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
|
1721 |
+
register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
|
1722 |
+
register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
|
1723 |
+
register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
|
1724 |
+
register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
|
1725 |
+
register_setting( 'updraft-options-group', 'updraft_googledrive_clientid', 'wp_filter_nohtml_kses' );
|
1726 |
+
register_setting( 'updraft-options-group', 'updraft_googledrive_secret', 'wp_filter_nohtml_kses' );
|
1727 |
+
register_setting( 'updraft-options-group', 'updraft_googledrive_remotepath', 'wp_filter_nohtml_kses' );
|
1728 |
+
register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
|
1729 |
+
register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
|
1730 |
+
register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
|
1731 |
+
register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
|
1732 |
+
register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
|
1733 |
+
register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
|
1734 |
+
register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
|
1735 |
+
register_setting( 'updraft-options-group', 'updraft_debug_mode', 'absint' );
|
1736 |
+
register_setting( 'updraft-options-group', 'updraft_include_plugins', 'absint' );
|
1737 |
+
register_setting( 'updraft-options-group', 'updraft_include_themes', 'absint' );
|
1738 |
+
register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
|
1739 |
+
register_setting( 'updraft-options-group', 'updraft_include_others', 'absint' );
|
1740 |
+
register_setting( 'updraft-options-group', 'updraft_include_others_exclude', 'wp_filter_nohtml_kses' );
|
1741 |
+
|
1742 |
+
/* I see no need for this check; people can only download backups/logs if they can guess a nonce formed from a random number and if .htaccess files have no effect. The database will be encrypted. Very unlikely.
|
1743 |
+
if (current_user_can('manage_options')) {
|
1744 |
+
$updraft_dir = $this->backups_dir_location();
|
1745 |
+
if(strpos($updraft_dir,WP_CONTENT_DIR) !== false) {
|
1746 |
+
$relative_dir = str_replace(WP_CONTENT_DIR,'',$updraft_dir);
|
1747 |
+
$possible_updraft_url = WP_CONTENT_URL.$relative_dir;
|
1748 |
+
$resp = wp_remote_request($possible_updraft_url, array('timeout' => 15));
|
1749 |
+
if ( is_wp_error($resp) ) {
|
1750 |
+
add_action('admin_notices', array($this,'show_admin_warning_accessible_unknownresult') );
|
1751 |
+
} else {
|
1752 |
+
if(strpos($resp['response']['code'],'403') === false) {
|
1753 |
+
add_action('admin_notices', array($this,'show_admin_warning_accessible') );
|
1754 |
+
}
|
1755 |
+
}
|
1756 |
+
}
|
1757 |
+
}
|
1758 |
+
*/
|
1759 |
+
if (current_user_can('manage_options') && get_option('updraft_service') == "googledrive" && get_option('updraft_googledrive_clientid') != "" && get_option('updraft_googledrive_token','xyz') == 'xyz') {
|
1760 |
+
add_action('admin_notices', array($this,'show_admin_warning_googledrive') );
|
1761 |
+
}
|
1762 |
+
}
|
1763 |
+
|
1764 |
+
function add_admin_pages() {
|
1765 |
+
add_submenu_page('options-general.php', "UpdraftPlus", "UpdraftPlus", "manage_options", "updraftplus",
|
1766 |
+
array($this,"settings_output"));
|
1767 |
+
}
|
1768 |
+
|
1769 |
+
function wordshell_random_advert($urls) {
|
1770 |
+
$url_start = ($urls) ? '<a href="http://wordshell.net">' : "";
|
1771 |
+
$url_end = ($urls) ? '</a>' : " (www.wordshell.net)";
|
1772 |
+
if (rand(0,1) == 0) {
|
1773 |
+
return "Like automating WordPress operations? Use the CLI? ${url_start}You will love WordShell${url_end} - saves time and money fast.";
|
1774 |
+
} else {
|
1775 |
+
return "${url_start}Check out WordShell${url_end} - manage WordPress from the command line - huge time-saver";
|
1776 |
+
}
|
1777 |
+
}
|
1778 |
+
|
1779 |
+
function settings_output() {
|
1780 |
+
|
1781 |
+
/*
|
1782 |
+
we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
|
1783 |
+
for the WP_Filesystem. to do this WP outputs a form that we can't insert variables into (apparently). So the values are
|
1784 |
+
passed back in as GET parameters. REQUEST covers both GET and POST so this weird logic works.
|
1785 |
+
*/
|
1786 |
+
if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_restore' && isset($_REQUEST['backup_timestamp'])) {
|
1787 |
+
$backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
|
1788 |
+
if(empty($this->errors) && $backup_success == true) {
|
1789 |
+
echo '<p>Restore successful!</p><br/>';
|
1790 |
+
echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus&updraft_restore_success=true">Return to Updraft Configuration</a>.';
|
1791 |
+
return;
|
1792 |
+
} else {
|
1793 |
+
echo '<p>Restore failed...</p><ul>';
|
1794 |
+
foreach ($this->errors as $err) {
|
1795 |
+
echo "<li>";
|
1796 |
+
if (is_string($err)) { echo htmlspecialchars($err); } else {
|
1797 |
+
print_r($err);
|
1798 |
+
}
|
1799 |
+
echo "</li>";
|
1800 |
+
}
|
1801 |
+
echo '</ul><b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
|
1802 |
+
return;
|
1803 |
+
}
|
1804 |
+
//uncomment the below once i figure out how i want the flow of a restoration to work.
|
1805 |
+
//echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
|
1806 |
+
}
|
1807 |
+
$deleted_old_dirs = false;
|
1808 |
+
if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
|
1809 |
+
if($this->delete_old_dirs()) {
|
1810 |
+
$deleted_old_dirs = true;
|
1811 |
+
} else {
|
1812 |
+
echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
|
1813 |
+
}
|
1814 |
+
echo '<p>Old directories successfully removed.</p><br/>';
|
1815 |
+
echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
|
1816 |
+
return;
|
1817 |
+
}
|
1818 |
+
|
1819 |
+
if(isset($_GET['error'])) {
|
1820 |
+
echo "<p><strong>ERROR:</strong> ".htmlspecialchars($_GET['error'])."</p>";
|
1821 |
+
}
|
1822 |
+
if(isset($_GET['message'])) {
|
1823 |
+
echo "<p><strong>Note:</strong> ".htmlspecialchars($_GET['message'])."</p>";
|
1824 |
+
}
|
1825 |
+
|
1826 |
+
if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
|
1827 |
+
if(!$this->create_backup_dir()) {
|
1828 |
+
echo '<p>Backup directory could not be created...</p><br/>';
|
1829 |
+
}
|
1830 |
+
echo '<p>Backup directory successfully created.</p><br/>';
|
1831 |
+
echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
|
1832 |
+
return;
|
1833 |
+
}
|
1834 |
+
|
1835 |
+
if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
|
1836 |
+
echo '<div class="updated fade" style="max-width: 800px; font-size:140%; padding:14px; clear:left;"><strong>Schedule backup:</strong> ';
|
1837 |
+
if (wp_schedule_single_event(time()+5, 'updraft_backup_all') === false) {
|
1838 |
+
echo "Failed.";
|
1839 |
+
} else {
|
1840 |
+
echo "OK. Now load a page from your site to make sure the schedule can trigger.";
|
1841 |
+
}
|
1842 |
+
echo '</div>';
|
1843 |
+
}
|
1844 |
+
if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') {
|
1845 |
+
$this->backup(true,true);
|
1846 |
+
}
|
1847 |
+
if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') {
|
1848 |
+
$this->backup_db();
|
1849 |
+
}
|
1850 |
+
|
1851 |
+
?>
|
1852 |
+
<div class="wrap">
|
1853 |
+
<h1>UpdraftPlus - Backup/Restore</h1>
|
1854 |
+
|
1855 |
+
<!-- Version: <b><?php echo $this->version; ?></b><br>-->
|
1856 |
+
Maintained by <b>David Anderson</b> (<a href="http://david.dw-perspective.org.uk">Homepage</a> | <a href="http://wordshell.net">WordShell - WordPress command line</a> | <a href="http://david.dw-perspective.org.uk/donate">Donate</a> | <a href="http://wordpress.org/extend/plugins/updraftplus/faq/">FAQs</a> | <a href="http://profiles.wordpress.org/davidanderson/">My other WordPress plugins</a>). Version: <?php echo $this->version; ?>
|
1857 |
+
<br>
|
1858 |
+
<?php
|
1859 |
+
if(isset($_GET['updraft_restore_success'])) {
|
1860 |
+
echo "<div style=\"color:blue\">Your backup has been restored. Your old themes, uploads, and plugins directories have been retained with \"-old\" appended to their name. Remove them when you are satisfied that the backup worked properly. At this time Updraft does not automatically restore your DB. You will need to use an external tool like phpMyAdmin to perform that task.</div>";
|
1861 |
+
}
|
1862 |
+
|
1863 |
+
$ws_advert = $this->wordshell_random_advert(1);
|
1864 |
+
echo <<<ENDHERE
|
1865 |
+
<div class="updated fade" style="max-width: 800px; font-size:140%; padding:14px; clear:left;">${ws_advert}</div>
|
1866 |
+
ENDHERE;
|
1867 |
+
|
1868 |
+
|
1869 |
+
if($deleted_old_dirs) {
|
1870 |
+
echo '<div style="color:blue">Old directories successfully deleted.</div>';
|
1871 |
+
}
|
1872 |
+
if(!$this->memory_check(96)) {?>
|
1873 |
+
<div style="color:orange">Your PHP memory limit is too low. Updraft attempted to raise it but was unsuccessful. This plugin may not work properly with a memory limit of less than 96 Mb (though on the other hand, it has been used successfully with a 32Mb limit - your mileage may vary, but don't blame us!). Current limit is: <?php echo $this->memory_check_current(); ?> Mb</div>
|
1874 |
+
<?php
|
1875 |
+
}
|
1876 |
+
if(!$this->execution_time_check(300)) {?>
|
1877 |
+
<div style="color:orange">Your PHP max_execution_time is less than 300 seconds. This probably means you're running in safe_mode. Either disable safe_mode or modify your php.ini to set max_execution_time to a higher number. If you do not, there is a chance Updraft will be unable to complete a backup. Present limit is: <?php echo ini_get('max_execution_time'); ?> seconds.</div>
|
1878 |
+
<?php
|
1879 |
+
}
|
1880 |
+
|
1881 |
+
if($this->scan_old_dirs()) {?>
|
1882 |
+
<div style="color:orange">You have old directories from a previous backup. Click to delete them after you have verified that the restoration worked.</div>
|
1883 |
+
<form method="post" action="<?php echo remove_query_arg(array('updraft_restore_success','action')) ?>">
|
1884 |
+
<input type="hidden" name="action" value="updraft_delete_old_dirs" />
|
1885 |
+
<input type="submit" class="button-primary" value="Delete Old Dirs" onclick="return(confirm('Are you sure you want to delete the old directories? This cannot be undone.'))" />
|
1886 |
+
</form>
|
1887 |
+
<?php
|
1888 |
+
}
|
1889 |
+
if(!empty($this->errors)) {
|
1890 |
+
foreach($this->errors as $error) {
|
1891 |
+
//ignoring severity here right now
|
1892 |
+
echo '<div style="color:red">'.$error['error'].'</div>';
|
1893 |
+
}
|
1894 |
+
}
|
1895 |
+
?>
|
1896 |
+
|
1897 |
+
<h2 style="clear:left;">Existing Schedule And Backups</h2>
|
1898 |
+
<table class="form-table" style="float:left; clear: both; width:475px">
|
1899 |
+
<tr>
|
1900 |
+
<?php
|
1901 |
+
$next_scheduled_backup = wp_next_scheduled('updraft_backup');
|
1902 |
+
$next_scheduled_backup = ($next_scheduled_backup) ? date('D, F j, Y H:i T',$next_scheduled_backup) : 'No backups are scheduled at this time.';
|
1903 |
+
$next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database');
|
1904 |
+
if (get_option('updraft_interval_database',get_option('updraft_interval')) == get_option('updraft_interval')) {
|
1905 |
+
$next_scheduled_backup_database = "Will take place at the same time as the files backup.";
|
1906 |
+
} else {
|
1907 |
+
$next_scheduled_backup_database = ($next_scheduled_backup_database) ? date('D, F j, Y H:i T',$next_scheduled_backup_database) : 'No backups are scheduled at this time.';
|
1908 |
+
}
|
1909 |
+
$current_time = date('D, F j, Y H:i T',time());
|
1910 |
+
$updraft_last_backup = get_option('updraft_last_backup');
|
1911 |
+
if($updraft_last_backup) {
|
1912 |
+
if($updraft_last_backup['success']) {
|
1913 |
+
$last_backup = date('D, F j, Y H:i T',$updraft_last_backup['backup_time']);
|
1914 |
+
$last_backup_color = 'green';
|
1915 |
+
} else {
|
1916 |
+
$last_backup = print_r($updraft_last_backup['errors'],true);
|
1917 |
+
$last_backup_color = 'red';
|
1918 |
+
}
|
1919 |
+
} else {
|
1920 |
+
$last_backup = 'No backup has been completed.';
|
1921 |
+
$last_backup_color = 'blue';
|
1922 |
+
}
|
1923 |
+
|
1924 |
+
$updraft_dir = $this->backups_dir_location();
|
1925 |
+
if(is_writable($updraft_dir)) {
|
1926 |
+
$dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
|
1927 |
+
$backup_disabled = "";
|
1928 |
+
} else {
|
1929 |
+
$backup_disabled = 'disabled="disabled"';
|
1930 |
+
$dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable, or does not exist. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraftplus&action=updraft_create_backup_dir">Click here</a></span> to attempt to create the directory and set the permissions. If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.</span>';
|
1931 |
+
}
|
1932 |
+
?>
|
1933 |
+
|
1934 |
+
<th>The Time Now:</th>
|
1935 |
+
<td style="color:blue"><?php echo $current_time?></td>
|
1936 |
+
</tr>
|
1937 |
+
<tr>
|
1938 |
+
<th>Next Scheduled Files Backup:</th>
|
1939 |
+
<td style="color:blue"><?php echo $next_scheduled_backup?></td>
|
1940 |
+
</tr>
|
1941 |
+
<tr>
|
1942 |
+
<th>Next Scheduled DB Backup:</th>
|
1943 |
+
<td style="color:blue"><?php echo $next_scheduled_backup_database?></td>
|
1944 |
+
</tr>
|
1945 |
+
<tr>
|
1946 |
+
<th>Last Backup:</th>
|
1947 |
+
<td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
|
1948 |
+
</tr>
|
1949 |
+
</table>
|
1950 |
+
<div style="float:left; width:200px; padding-top: 100px;">
|
1951 |
+
<form method="post" action="">
|
1952 |
+
<input type="hidden" name="action" value="updraft_backup" />
|
1953 |
+
<p><input type="submit" <?php echo $backup_disabled ?> class="button-primary" value="Backup Now!" style="padding-top:7px;padding-bottom:7px;font-size:24px !important" onclick="return(confirm('This will schedule a one-time backup. To trigger the backup immediately you may need to load a page on your site.'))" /></p>
|
1954 |
+
</form>
|
1955 |
+
<div style="position:relative">
|
1956 |
+
<div style="position:absolute;top:0;left:0">
|
1957 |
+
<?php
|
1958 |
+
$backup_history = get_option('updraft_backup_history');
|
1959 |
+
$backup_history = (is_array($backup_history))?$backup_history:array();
|
1960 |
+
$restore_disabled = (count($backup_history) == 0) ? 'disabled="disabled"' : "";
|
1961 |
+
?>
|
1962 |
+
<input type="button" class="button-primary" <?php echo $restore_disabled ?> value="Restore" style="padding-top:7px;padding-bottom:7px;font-size:24px !important" onclick="jQuery('#backup-restore').fadeIn('slow');jQuery(this).parent().fadeOut('slow')" />
|
1963 |
+
</div>
|
1964 |
+
<div style="display:none;position:absolute;top:0;left:0" id="backup-restore">
|
1965 |
+
<form method="post" action="">
|
1966 |
+
<b>Choose: </b>
|
1967 |
+
<select name="backup_timestamp" style="display:inline">
|
1968 |
+
<?php
|
1969 |
+
foreach($backup_history as $key=>$value) {
|
1970 |
+
echo "<option value='$key'>".date('Y-m-d G:i',$key)."</option>\n";
|
1971 |
+
}
|
1972 |
+
?>
|
1973 |
+
</select>
|
1974 |
+
|
1975 |
+
<input type="hidden" name="action" value="updraft_restore" />
|
1976 |
+
<input type="submit" <?php echo $restore_disabled ?> class="button-primary" value="Restore Now!" style="padding-top:7px;margin-top:5px;padding-bottom:7px;font-size:24px !important" onclick="return(confirm('Restoring from backup will replace this site\'s themes, plugins, uploads and other content directories (according to what is contained in the backup set which you select). Database restoration cannot be done through this process - you must download the database and import yourself (e.g. through PHPMyAdmin). Do you wish to continue with the restoration process?'))" />
|
1977 |
+
</form>
|
1978 |
+
</div>
|
1979 |
+
</div>
|
1980 |
+
</div>
|
1981 |
+
<br style="clear:both" />
|
1982 |
+
<table class="form-table">
|
1983 |
+
<tr>
|
1984 |
+
<th>Download Backups</th>
|
1985 |
+
<td><a href="#" title="Click to see available backups" onclick="jQuery('.download-backups').toggle();return false;"><?php echo count($backup_history)?> available</a></td>
|
1986 |
+
</tr>
|
1987 |
+
<tr>
|
1988 |
+
<td></td><td class="download-backups" style="display:none">
|
1989 |
+
<em>Click on a button to download the corresponding file to your computer. If you are using Opera, you should turn Turbo mode off.</em>
|
1990 |
+
<table>
|
1991 |
+
<?php
|
1992 |
+
foreach($backup_history as $key=>$value) {
|
1993 |
+
?>
|
1994 |
+
<tr>
|
1995 |
+
<td><b><?php echo date('Y-m-d G:i',$key)?></b></td>
|
1996 |
+
<td>
|
1997 |
+
<?php if (isset($value['db'])) { ?>
|
1998 |
+
<form action="admin-ajax.php" method="post">
|
1999 |
+
<input type="hidden" name="action" value="updraft_download_backup" />
|
2000 |
+
<input type="hidden" name="type" value="db" />
|
2001 |
+
<input type="hidden" name="timestamp" value="<?php echo $key?>" />
|
2002 |
+
<input type="submit" value="Database" />
|
2003 |
+
</form>
|
2004 |
+
<?php } else { echo "(No database in backup)"; } ?>
|
2005 |
+
</td>
|
2006 |
+
<td>
|
2007 |
+
<?php if (isset($value['plugins'])) { ?>
|
2008 |
+
<form action="admin-ajax.php" method="post">
|
2009 |
+
<input type="hidden" name="action" value="updraft_download_backup" />
|
2010 |
+
<input type="hidden" name="type" value="plugins" />
|
2011 |
+
<input type="hidden" name="timestamp" value="<?php echo $key?>" />
|
2012 |
+
<input type="submit" value="Plugins" />
|
2013 |
+
</form>
|
2014 |
+
<?php } else { echo "(No plugins in backup)"; } ?>
|
2015 |
+
</td>
|
2016 |
+
<td>
|
2017 |
+
<?php if (isset($value['themes'])) { ?>
|
2018 |
+
<form action="admin-ajax.php" method="post">
|
2019 |
+
<input type="hidden" name="action" value="updraft_download_backup" />
|
2020 |
+
<input type="hidden" name="type" value="themes" />
|
2021 |
+
<input type="hidden" name="timestamp" value="<?php echo $key?>" />
|
2022 |
+
<input type="submit" value="Themes" />
|
2023 |
+
</form>
|
2024 |
+
<?php } else { echo "(No themes in backup)"; } ?>
|
2025 |
+
</td>
|
2026 |
+
<td>
|
2027 |
+
<?php if (isset($value['uploads'])) { ?>
|
2028 |
+
<form action="admin-ajax.php" method="post">
|
2029 |
+
<input type="hidden" name="action" value="updraft_download_backup" />
|
2030 |
+
<input type="hidden" name="type" value="uploads" />
|
2031 |
+
<input type="hidden" name="timestamp" value="<?php echo $key?>" />
|
2032 |
+
<input type="submit" value="Uploads" />
|
2033 |
+
</form>
|
2034 |
+
<?php } else { echo "(No uploads in backup)"; } ?>
|
2035 |
+
</td>
|
2036 |
+
<td>
|
2037 |
+
<?php if (isset($value['others'])) { ?>
|
2038 |
+
<form action="admin-ajax.php" method="post">
|
2039 |
+
<input type="hidden" name="action" value="updraft_download_backup" />
|
2040 |
+
<input type="hidden" name="type" value="others" />
|
2041 |
+
<input type="hidden" name="timestamp" value="<?php echo $key?>" />
|
2042 |
+
<input type="submit" value="Others" />
|
2043 |
+
</form>
|
2044 |
+
<?php } else { echo "(No others in backup)"; } ?>
|
2045 |
+
</td>
|
2046 |
+
</tr>
|
2047 |
+
<?php }?>
|
2048 |
+
</table>
|
2049 |
+
</td>
|
2050 |
+
</tr>
|
2051 |
+
</table>
|
2052 |
+
<form method="post" action="options.php">
|
2053 |
+
<?php settings_fields('updraft-options-group'); ?>
|
2054 |
+
<h2>Configure Backup Contents And Schedule</h2>
|
2055 |
+
<table class="form-table" style="width:850px;">
|
2056 |
+
<tr>
|
2057 |
+
<th>File Backup Intervals:</th>
|
2058 |
+
<td><select name="updraft_interval">
|
2059 |
+
<?php
|
2060 |
+
$intervals = array ("manual", "daily", "weekly", "monthly");
|
2061 |
+
foreach ($intervals as $ival) {
|
2062 |
+
echo "<option value=\"$ival\" ";
|
2063 |
+
if ($ival == get_option('updraft_interval','manual')) { echo 'selected="selected"';}
|
2064 |
+
echo ">".ucfirst($ival)."</option>\n";
|
2065 |
+
}
|
2066 |
+
?>
|
2067 |
+
</select></td>
|
2068 |
+
</tr>
|
2069 |
+
<tr>
|
2070 |
+
<th>Database Backup Intervals:</th>
|
2071 |
+
<td><select name="updraft_interval_database">
|
2072 |
+
<?php
|
2073 |
+
$intervals = array ("manual", "daily", "weekly", "monthly");
|
2074 |
+
foreach ($intervals as $ival) {
|
2075 |
+
echo "<option value=\"$ival\" ";
|
2076 |
+
if ($ival == get_option('updraft_interval_database',get_option('updraft_interval'))) { echo 'selected="selected"';}
|
2077 |
+
echo ">".ucfirst($ival)."</option>\n";
|
2078 |
+
}
|
2079 |
+
?>
|
2080 |
+
</select></td>
|
2081 |
+
</tr>
|
2082 |
+
<tr class="backup-interval-description">
|
2083 |
+
<td></td><td>If you would like to automatically schedule backups, choose schedules from the dropdown above. Backups will occur at the interval specified starting just after the current time. If you choose manual you must click the "Backup Now!" button whenever you wish a backup to occur. If the two schedules are the same, then the two backups will take place together.</td>
|
2084 |
+
</tr>
|
2085 |
+
<?php
|
2086 |
+
# The true (default value if non-existent) here has the effect of forcing a default of on.
|
2087 |
+
$include_themes = (get_option('updraft_include_themes',true)) ? 'checked="checked"' : "";
|
2088 |
+
$include_plugins = (get_option('updraft_include_plugins',true)) ? 'checked="checked"' : "";
|
2089 |
+
$include_uploads = (get_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
|
2090 |
+
$include_others = (get_option('updraft_include_others',true)) ? 'checked="checked"' : "";
|
2091 |
+
$include_others_exclude = get_option('updraft_include_others_exclude',UPDRAFT_DEFAULT_OTHERS_EXCLUDE);
|
2092 |
+
?>
|
2093 |
+
<tr>
|
2094 |
+
<th>Include in Files Backup:</th>
|
2095 |
+
<td>
|
2096 |
+
<input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br>
|
2097 |
+
<input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br>
|
2098 |
+
<input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br>
|
2099 |
+
<input type="checkbox" name="updraft_include_others" value="1" <?php echo $include_others; ?> /> Any other directories found inside wp-content - but exclude these directories: <input type="text" name="updraft_include_others_exclude" size="32" value="<?php echo htmlspecialchars($include_others_exclude); ?>"/><br>
|
2100 |
+
Include all of these, unless you are backing them up separately. Note that presently UpdraftPlus backs up these directories only - which is usually everything (except for WordPress core itself which you can download afresh from WordPress.org). But if you have made customised modifications outside of these directories, you need to back them up another way.<br>(<a href="http://wordshell.net">Use WordShell</a> for automatic backup, version control and patching).<br></td>
|
2101 |
+
</td>
|
2102 |
+
</tr>
|
2103 |
+
<tr>
|
2104 |
+
<th>Retain Backups:</th>
|
2105 |
+
<?php
|
2106 |
+
$updraft_retain = get_option('updraft_retain');
|
2107 |
+
$retain = ((int)$updraft_retain > 0)?get_option('updraft_retain'):1;
|
2108 |
+
?>
|
2109 |
+
<td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
|
2110 |
+
</tr>
|
2111 |
+
<tr class="email" <?php echo $email_display?>>
|
2112 |
+
<th>Email:</th>
|
2113 |
+
<td><input type="text" style="width:260px" name="updraft_email" value="<?php echo get_option('updraft_email'); ?>" /> <br>Enter an address here to have a report sent (and the whole backup, if you choose) to it.</td>
|
2114 |
+
</tr>
|
2115 |
+
<tr class="deletelocal s3 ftp email" <?php echo $display_delete_local?>>
|
2116 |
+
<th>Delete local backup:</th>
|
2117 |
+
<td><input type="checkbox" name="updraft_delete_local" value="1" <?php $delete_local = (get_option('updraft_delete_local')) ? 'checked="checked"' : "";
|
2118 |
+
echo $delete_local; ?> /> <br>Check this to delete the local backup file (only sensible if you have enabled a remote backup (below), otherwise you will have no backup remaining).</td>
|
2119 |
+
</tr>
|
2120 |
+
|
2121 |
+
<tr class="backup-retain-description">
|
2122 |
+
<td></td><td>By default only the most recent backup is retained. If you'd like to preserve more, specify the number here. (This many of <strong>both</strong> files and database backups will be retained.)</td>
|
2123 |
+
</tr>
|
2124 |
+
<tr>
|
2125 |
+
<th>Database encryption phrase:</th>
|
2126 |
+
<?php
|
2127 |
+
$updraft_encryptionphrase = get_option('updraft_encryptionphrase');
|
2128 |
+
?>
|
2129 |
+
<td><input type="text" name="updraft_encryptionphrase" value="<?php echo $updraft_encryptionphrase ?>" style="width:132px" /></td>
|
2130 |
+
</tr>
|
2131 |
+
<tr class="backup-crypt-description">
|
2132 |
+
<td></td><td>If you enter a string here, it is used to encrypt backups (Rijndael). Do not lose it, or all your backups will be useless. Presently, only the database file is encrypted. This is also the key used to decrypt backups from this admin interface (so if you change it, then automatic decryption will not work until you change it back). You can also use the file example-decrypt.php from inside the UpdraftPlus plugin directory to decrypt manually.</td>
|
2133 |
+
</tr>
|
2134 |
+
</table>
|
2135 |
+
|
2136 |
+
<h2>Copying Your Backup To Remote Storage</h2>
|
2137 |
+
|
2138 |
+
<table class="form-table" style="width:850px;">
|
2139 |
+
<tr>
|
2140 |
+
<th>Remote backup:</th>
|
2141 |
+
<td><select name="updraft_service" id="updraft-service">
|
2142 |
+
<?php
|
2143 |
+
$debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
|
2144 |
+
|
2145 |
+
$display_none = 'style="display:none"';
|
2146 |
+
$s3 = ""; $ftp = ""; $email = ""; $googledrive="";
|
2147 |
+
$email_display="";
|
2148 |
+
$display_email_complete = "";
|
2149 |
+
$set = 'selected="selected"';
|
2150 |
+
switch(get_option('updraft_service')) {
|
2151 |
+
case 's3':
|
2152 |
+
$s3 = $set;
|
2153 |
+
$googledrive_display = $display_none;
|
2154 |
+
$ftp_display = $display_none;
|
2155 |
+
break;
|
2156 |
+
case 'googledrive':
|
2157 |
+
$googledrive = $set;
|
2158 |
+
$s3_display = $display_none;
|
2159 |
+
$ftp_display = $display_none;
|
2160 |
+
break;
|
2161 |
+
case 'ftp':
|
2162 |
+
$ftp = $set;
|
2163 |
+
$googledrive_display = $display_none;
|
2164 |
+
$s3_display = $display_none;
|
2165 |
+
break;
|
2166 |
+
case 'email':
|
2167 |
+
$email = $set;
|
2168 |
+
$ftp_display = $display_none;
|
2169 |
+
$s3_display = $display_none;
|
2170 |
+
$googledrive_display = $display_none;
|
2171 |
+
$display_email_complete = $display_none;
|
2172 |
+
break;
|
2173 |
+
default:
|
2174 |
+
$none = $set;
|
2175 |
+
$ftp_display = $display_none;
|
2176 |
+
$googledrive_display = $display_none;
|
2177 |
+
$s3_display = $display_none;
|
2178 |
+
$display_delete_local = $display_none;
|
2179 |
+
break;
|
2180 |
+
}
|
2181 |
+
?>
|
2182 |
+
<option value="none" <?php echo $none?>>None</option>
|
2183 |
+
<option value="s3" <?php echo $s3?>>Amazon S3</option>
|
2184 |
+
<option value="googledrive" <?php echo $googledrive?>>Google Drive</option>
|
2185 |
+
<option value="ftp" <?php echo $ftp?>>FTP</option>
|
2186 |
+
<option value="email" <?php echo $email?>>E-mail</option>
|
2187 |
+
</select></td>
|
2188 |
+
</tr>
|
2189 |
+
<tr class="backup-service-description">
|
2190 |
+
<td></td><td>Choose your backup method. If choosing "E-Mail", then be aware that mail servers tend to have size limits; typically around 10-20Mb; backups larger than any limits will not arrive.</td>
|
2191 |
+
|
2192 |
+
</tr>
|
2193 |
+
|
2194 |
+
<!-- Amazon S3 -->
|
2195 |
+
<tr class="s3" <?php echo $s3_display?>>
|
2196 |
+
<td></td>
|
2197 |
+
<td><em>Amazon S3 is a great choice, because UpdraftPlus supports chunked uploads - no matter how big your blog is, UpdraftPlus can upload it a little at a time, and not get thwarted by timeouts.</em></td>
|
2198 |
+
</tr>
|
2199 |
+
<tr class="s3" <?php echo $s3_display?>>
|
2200 |
+
<th>S3 access key:</th>
|
2201 |
+
<td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_login" value="<?php echo get_option('updraft_s3_login') ?>" /></td>
|
2202 |
+
</tr>
|
2203 |
+
<tr class="s3" <?php echo $s3_display?>>
|
2204 |
+
<th>S3 secret key:</th>
|
2205 |
+
<td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_pass" value="<?php echo get_option('updraft_s3_pass'); ?>" /></td>
|
2206 |
+
</tr>
|
2207 |
+
<tr class="s3" <?php echo $s3_display?>>
|
2208 |
+
<th>S3 location:</th>
|
2209 |
+
<td>s3://<input type="text" style="width:292px" name="updraft_s3_remote_path" value="<?php echo get_option('updraft_s3_remote_path'); ?>" /></td>
|
2210 |
+
</tr>
|
2211 |
+
<tr class="s3" <?php echo $s3_display?>>
|
2212 |
+
<th></th>
|
2213 |
+
<td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) (and optionally a path) to use for storage.</p></td>
|
2214 |
+
</tr>
|
2215 |
+
|
2216 |
+
<!-- Google Drive -->
|
2217 |
+
|
2218 |
+
<tr class="googledrive" <?php echo $googledrive_display?>>
|
2219 |
+
<th>Google Drive:</th>
|
2220 |
+
<td>
|
2221 |
+
<p><a href="http://david.dw-perspective.org.uk/da/index.php/computer-resources/updraftplus-googledrive-authorisation/"><strong>For longer help, including screenshots, follow this link. The description below is sufficient for more expert users.</strong></a></p>
|
2222 |
+
<p><a href="https://code.google.com/apis/console/">Follow this link to your Google API Console</a>, and there create a Client ID in the API Access section. Select 'Web Application' as the application type.</p><p>You must add <kbd><?php echo admin_url('options-general.php?page=updraftplus&action=auth'); ?></kbd> as the authorised redirect URI when asked.
|
2223 |
+
|
2224 |
+
<?php
|
2225 |
+
if (!class_exists('SimpleXMLElement')) { echo " <b>WARNING:</b> You do not have the SimpleXMLElement installed. Google Drive backups will <b>not</b> work until you do."; }
|
2226 |
+
?>
|
2227 |
+
</p>
|
2228 |
+
</td>
|
2229 |
+
</tr>
|
2230 |
+
|
2231 |
+
<tr class="googledrive" <?php echo $googledrive_display?>>
|
2232 |
+
<th>Google Drive Client ID:</th>
|
2233 |
+
<td><input type="text" autocomplete="off" style="width:332px" name="updraft_googledrive_clientid" value="<?php echo get_option('updraft_googledrive_clientid') ?>" /></td>
|
2234 |
+
</tr>
|
2235 |
+
<tr class="googledrive" <?php echo $googledrive_display?>>
|
2236 |
+
<th>Google Drive Client Secret:</th>
|
2237 |
+
<td><input type="text" style="width:332px" name="updraft_googledrive_secret" value="<?php echo get_option('updraft_googledrive_secret'); ?>" /></td>
|
2238 |
+
</tr>
|
2239 |
+
<tr class="googledrive" <?php echo $googledrive_display?>>
|
2240 |
+
<th>Google Drive Folder ID:</th>
|
2241 |
+
<td><input type="text" style="width:332px" name="updraft_googledrive_remotepath" value="<?php echo get_option('updraft_googledrive_remotepath'); ?>" /> <em>(To get a folder's ID navigate to that folder in Google Drive in your web browser and copy the ID from your browser's address bar. It is the part that comes after <kbd>#folders/.</kbd> Leave empty to use your root folder)</em></td>
|
2242 |
+
</tr>
|
2243 |
+
<tr class="googledrive" <?php echo $googledrive_display?>>
|
2244 |
+
<th>Authenticate with Google:</th>
|
2245 |
+
<td><p><?php if (get_option('updraft_googledrive_token','xyz') != 'xyz') echo "<strong>(You appear to be already authenticated).</strong>"; ?> <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit"><strong>After</strong> you have saved your settings (by clicking "Save Changes" below), then come back here once and click this link to complete authentication with Google.</a>
|
2246 |
+
|
2247 |
+
</p>
|
2248 |
+
</td>
|
2249 |
+
</tr>
|
2250 |
+
|
2251 |
+
<tr class="ftp" <?php echo $ftp_display?>>
|
2252 |
+
<th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Server:</a></th>
|
2253 |
+
<td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo get_option('updraft_server_address'); ?>" /></td>
|
2254 |
+
</tr>
|
2255 |
+
<tr class="ftp" <?php echo $ftp_display?>>
|
2256 |
+
<th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Login:</a></th>
|
2257 |
+
<td><input type="text" autocomplete="off" name="updraft_ftp_login" value="<?php echo get_option('updraft_ftp_login') ?>" /></td>
|
2258 |
+
</tr>
|
2259 |
+
<tr class="ftp" <?php echo $ftp_display?>>
|
2260 |
+
<th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Password:</a></th>
|
2261 |
+
<td><input type="text" autocomplete="off" style="width:260px" name="updraft_ftp_pass" value="<?php echo get_option('updraft_ftp_pass'); ?>" /></td>
|
2262 |
+
</tr>
|
2263 |
+
<tr class="ftp" <?php echo $ftp_display?>>
|
2264 |
+
<th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">Remote Path:</a></th>
|
2265 |
+
<td><input type="text" style="width:260px" name="updraft_ftp_remote_path" value="<?php echo get_option('updraft_ftp_remote_path'); ?>" /></td>
|
2266 |
+
</tr>
|
2267 |
+
<tr class="ftp-description" style="display:none">
|
2268 |
+
<td colspan="2">An FTP remote path will look like '/home/backup/some/folder'</td>
|
2269 |
+
</tr>
|
2270 |
+
</table>
|
2271 |
+
<table class="form-table" style="width:850px;">
|
2272 |
+
<tr><td colspan="2"><h2>Advanced / Debugging Settings</h2></td></tr>
|
2273 |
+
<tr>
|
2274 |
+
<th>Backup Directory:</th>
|
2275 |
+
<td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo $updraft_dir ?>" /></td>
|
2276 |
+
</tr>
|
2277 |
+
<tr>
|
2278 |
+
<td></td><td><?php echo $dir_info ?> This is where Updraft Backup/Restore will write the zip files it creates initially. This directory must be writable by your web server. Typically you'll want to have it inside your wp-content folder (this is the default). <b>Do not</b> place it inside your uploads dir, as that will cause recursion issues (backups of backups of backups of...).</td>
|
2279 |
+
</tr>
|
2280 |
+
<tr>
|
2281 |
+
<th>Debug mode:</th>
|
2282 |
+
<td><input type="checkbox" name="updraft_debug_mode" value="1" <?php echo $debug_mode; ?> /> <br>Check this for more information, if something is going wrong. Will also drop a log file in your backup directory which you can examine.</td>
|
2283 |
+
</tr>
|
2284 |
+
<tr>
|
2285 |
+
<td>
|
2286 |
+
<input type="hidden" name="action" value="update" />
|
2287 |
+
<input type="submit" class="button-primary" value="Save Changes" />
|
2288 |
+
</td>
|
2289 |
+
</tr>
|
2290 |
+
</table>
|
2291 |
+
</form>
|
2292 |
+
<?php
|
2293 |
+
if(get_option('updraft_debug_mode')) {
|
2294 |
+
?>
|
2295 |
+
<div style="padding-top: 40px;">
|
2296 |
+
<hr>
|
2297 |
+
<h3>Debug Information</h3>
|
2298 |
+
<?php
|
2299 |
+
$peak_memory_usage = memory_get_peak_usage(true)/1024/1024;
|
2300 |
+
$memory_usage = memory_get_usage(true)/1024/1024;
|
2301 |
+
echo 'Peak memory usage: '.$peak_memory_usage.' MB<br/>';
|
2302 |
+
echo 'Current memory usage: '.$memory_usage.' MB<br/>';
|
2303 |
+
echo 'PHP memory limit: '.ini_get('memory_limit').' <br/>';
|
2304 |
+
?>
|
2305 |
+
<form method="post" action="">
|
2306 |
+
<input type="hidden" name="action" value="updraft_backup_debug_all" />
|
2307 |
+
<p><input type="submit" class="button-primary" <?php echo $backup_disabled ?> value="Debug Backup" onclick="return(confirm('This will cause an immediate backup. The page will stall loading until it finishes (ie, unscheduled). Use this if you\'re trying to see peak memory usage.'))" /></p>
|
2308 |
+
</form>
|
2309 |
+
<form method="post" action="">
|
2310 |
+
<input type="hidden" name="action" value="updraft_backup_debug_db" />
|
2311 |
+
<p><input type="submit" class="button-primary" <?php echo $backup_disabled ?> value="Debug DB Backup" onclick="return(confirm('This will cause an immediate DB backup. The page will stall loading until it finishes (ie, unscheduled). The backup will remain locally despite your prefs and will not go into the backup history or up into the cloud.'))" /></p>
|
2312 |
+
</form>
|
2313 |
+
</div>
|
2314 |
+
<?php } ?>
|
2315 |
+
|
2316 |
+
<p><em>UpdraftPlus is based on the original Updraft by <b>Paul Kehrer</b> (<a href="http://langui.sh" target="_blank">Blog</a> | <a href="http://twitter.com/reaperhulk" target="_blank">Twitter</a> )</em></p>
|
2317 |
+
|
2318 |
+
|
2319 |
+
<script type="text/javascript">
|
2320 |
+
jQuery(document).ready(function() {
|
2321 |
+
jQuery('#updraft-service').change(function() {
|
2322 |
+
switch(jQuery(this).val()) {
|
2323 |
+
case 'none':
|
2324 |
+
jQuery('.deletelocal,.s3,.ftp,.googledrive,.s3-description,.ftp-description').fadeOut()
|
2325 |
+
jQuery('.email,.email-complete').fadeIn()
|
2326 |
+
break;
|
2327 |
+
case 's3':
|
2328 |
+
jQuery('.ftp,.ftp-description,.googledrive').fadeOut()
|
2329 |
+
jQuery('.s3,.deletelocal,.email,.email-complete').fadeIn()
|
2330 |
+
break;
|
2331 |
+
case 'googledrive':
|
2332 |
+
jQuery('.ftp,.ftp-description,.s3').fadeOut()
|
2333 |
+
jQuery('.googledrive,.deletelocal,.googledrive,.email,.email-complete').fadeIn()
|
2334 |
+
break;
|
2335 |
+
case 'ftp':
|
2336 |
+
jQuery('.googledrive,.s3,.s3-description').fadeOut()
|
2337 |
+
jQuery('.ftp,.deletelocal,.email,.email-complete').fadeIn()
|
2338 |
+
break;
|
2339 |
+
case 'email':
|
2340 |
+
jQuery('.s3,.ftp,.s3-description,.googledrive,.ftp-description,.email-complete').fadeOut()
|
2341 |
+
jQuery('.email,.deletelocal').fadeIn()
|
2342 |
+
break;
|
2343 |
+
}
|
2344 |
+
})
|
2345 |
+
})
|
2346 |
+
jQuery(window).load(function() {
|
2347 |
+
//this is for hiding the restore progress at the top after it is done
|
2348 |
+
setTimeout('jQuery("#updraft-restore-progress").toggle(1000)',3000)
|
2349 |
+
jQuery('#updraft-restore-progress-toggle').click(function() {
|
2350 |
+
jQuery('#updraft-restore-progress').toggle(500)
|
2351 |
+
})
|
2352 |
+
})
|
2353 |
+
</script>
|
2354 |
+
<?php
|
2355 |
+
}
|
2356 |
+
|
2357 |
+
/*array2json provided by bin-co.com under BSD license*/
|
2358 |
+
function array2json($arr) {
|
2359 |
+
if(function_exists('json_encode')) return stripslashes(json_encode($arr)); //Latest versions of PHP already have this functionality.
|
2360 |
+
$parts = array();
|
2361 |
+
$is_list = false;
|
2362 |
+
|
2363 |
+
//Find out if the given array is a numerical array
|
2364 |
+
$keys = array_keys($arr);
|
2365 |
+
$max_length = count($arr)-1;
|
2366 |
+
if(($keys[0] == 0) and ($keys[$max_length] == $max_length)) {//See if the first key is 0 and last key is length - 1
|
2367 |
+
$is_list = true;
|
2368 |
+
for($i=0; $i<count($keys); $i++) { //See if each key correspondes to its position
|
2369 |
+
if($i != $keys[$i]) { //A key fails at position check.
|
2370 |
+
$is_list = false; //It is an associative array.
|
2371 |
+
break;
|
2372 |
+
}
|
2373 |
+
}
|
2374 |
+
}
|
2375 |
+
|
2376 |
+
foreach($arr as $key=>$value) {
|
2377 |
+
if(is_array($value)) { //Custom handling for arrays
|
2378 |
+
if($is_list) $parts[] = $this->array2json($value); /* :RECURSION: */
|
2379 |
+
else $parts[] = '"' . $key . '":' . $this->array2json($value); /* :RECURSION: */
|
2380 |
+
} else {
|
2381 |
+
$str = '';
|
2382 |
+
if(!$is_list) $str = '"' . $key . '":';
|
2383 |
+
|
2384 |
+
//Custom handling for multiple data types
|
2385 |
+
if(is_numeric($value)) $str .= $value; //Numbers
|
2386 |
+
elseif($value === false) $str .= 'false'; //The booleans
|
2387 |
+
elseif($value === true) $str .= 'true';
|
2388 |
+
else $str .= '"' . addslashes($value) . '"'; //All other things
|
2389 |
+
// :TODO: Is there any more datatype we should be in the lookout for? (Object?)
|
2390 |
+
|
2391 |
+
$parts[] = $str;
|
2392 |
+
}
|
2393 |
+
}
|
2394 |
+
$json = implode(',',$parts);
|
2395 |
+
|
2396 |
+
if($is_list) return '[' . $json . ']';//Return numerical JSON
|
2397 |
+
return '{' . $json . '}';//Return associative JSON
|
2398 |
+
}
|
2399 |
+
|
2400 |
+
function show_admin_warning($message) {
|
2401 |
+
echo '<div id="updraftmessage" class="updated fade">';
|
2402 |
+
echo "<p>$message</p></div>";
|
2403 |
+
}
|
2404 |
+
function show_admin_warning_accessible() {
|
2405 |
+
$this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
|
2406 |
+
}
|
2407 |
+
function show_admin_warning_googledrive() {
|
2408 |
+
$this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit">Click here to authenticate your Google Drive account (you will not be able to back up to Google Drive without it).</a>');
|
2409 |
+
}
|
2410 |
+
function show_admin_warning_accessible_unknownresult() {
|
2411 |
+
$this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
|
2412 |
+
}
|
2413 |
+
|
2414 |
+
|
2415 |
+
}
|
2416 |
+
|
2417 |
+
|
2418 |
+
?>
|
updraftplus.php
CHANGED
@@ -4,7 +4,7 @@ Plugin Name: UpdraftPlus - Backup/Restore
|
|
4 |
Plugin URI: http://wordpress.org/extend/plugins/updraftplus
|
5 |
Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed, on separate schedules.
|
6 |
Author: David Anderson.
|
7 |
-
Version: 1.0.
|
8 |
Donate link: http://david.dw-perspective.org.uk/donate
|
9 |
License: GPLv3 or later
|
10 |
Author URI: http://wordshell.net
|
@@ -60,7 +60,7 @@ define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php');
|
|
60 |
|
61 |
class UpdraftPlus {
|
62 |
|
63 |
-
var $version = '1.0.
|
64 |
|
65 |
var $dbhandle;
|
66 |
var $errors = array();
|
@@ -202,7 +202,7 @@ class UpdraftPlus {
|
|
202 |
$result = json_decode( $result, true );
|
203 |
if ( isset( $result['refresh_token'] ) ) {
|
204 |
update_option('updraft_googledrive_token',$result['refresh_token']); // Save token
|
205 |
-
header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( '
|
206 |
}
|
207 |
else {
|
208 |
header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
|
@@ -738,7 +738,7 @@ class UpdraftPlus {
|
|
738 |
if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token;
|
739 |
|
740 |
$this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token );
|
741 |
-
$this->gdocs->set_option( 'chunk_size', 1
|
742 |
$this->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
|
743 |
$this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
|
744 |
}
|
@@ -2404,7 +2404,7 @@ echo $delete_local; ?> /> <br>Check this to delete the local backup file (only s
|
|
2404 |
$this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
|
2405 |
}
|
2406 |
function show_admin_warning_googledrive() {
|
2407 |
-
$this->show_admin_warning('UpdraftPlus notice
|
2408 |
}
|
2409 |
function show_admin_warning_accessible_unknownresult() {
|
2410 |
$this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
|
4 |
Plugin URI: http://wordpress.org/extend/plugins/updraftplus
|
5 |
Description: Uploads, themes, plugins, and your DB can be automatically backed up to Amazon S3, Google Drive, FTP, or emailed, on separate schedules.
|
6 |
Author: David Anderson.
|
7 |
+
Version: 1.0.7
|
8 |
Donate link: http://david.dw-perspective.org.uk/donate
|
9 |
License: GPLv3 or later
|
10 |
Author URI: http://wordshell.net
|
60 |
|
61 |
class UpdraftPlus {
|
62 |
|
63 |
+
var $version = '1.0.7';
|
64 |
|
65 |
var $dbhandle;
|
66 |
var $errors = array();
|
202 |
$result = json_decode( $result, true );
|
203 |
if ( isset( $result['refresh_token'] ) ) {
|
204 |
update_option('updraft_googledrive_token',$result['refresh_token']); // Save token
|
205 |
+
header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Google Drive authorization was successful.', 'updraftplus' ) ) );
|
206 |
}
|
207 |
else {
|
208 |
header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
|
738 |
if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token;
|
739 |
|
740 |
$this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token );
|
741 |
+
$this->gdocs->set_option( 'chunk_size', 1 ); # 1Mb; change from default of 512Kb
|
742 |
$this->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
|
743 |
$this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
|
744 |
}
|
2404 |
$this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
|
2405 |
}
|
2406 |
function show_admin_warning_googledrive() {
|
2407 |
+
$this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit">Click here to authenticate your Google Drive account (you will not be able to back up to Google Drive without it).</a>');
|
2408 |
}
|
2409 |
function show_admin_warning_accessible_unknownresult() {
|
2410 |
$this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
|