UpdraftPlus WordPress Backup Plugin - Version 0.7.4

Version Description

  • 05/21/2012 =
  • Removed CloudFront method; I have no way of testing this
  • Backup all tables found in the database that have this site's table prefix
  • If encryption fails, then abort (don't revert to not encrypting)
  • Added ability to decrypt encrypted database backups
  • Added ability to opt out of backing up each file group
  • Now adds database character set, the lack of which before made database backups unusable without modifications
  • Version number bump to make sure that this is an improvement on the original Updraft, and is now tried and tested
Download this release

Release Info

Developer DavidAnderson
Plugin Icon 128x128 UpdraftPlus WordPress Backup Plugin
Version 0.7.4
Comparing to
See all releases

Version 0.7.4

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
+ ?>
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:
includes/S3.php ADDED
@@ -0,0 +1,1365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * $Id: S3.php 47 2009-07-20 01:25:40Z don.schonknecht $
4
+ *
5
+ * Copyright (c) 2008, Donovan Schönknecht. All rights reserved.
6
+ *
7
+ * Redistribution and use in source and binary forms, with or without
8
+ * modification, are permitted provided that the following conditions are met:
9
+ *
10
+ * - Redistributions of source code must retain the above copyright notice,
11
+ * this list of conditions and the following disclaimer.
12
+ * - Redistributions in binary form must reproduce the above copyright
13
+ * notice, this list of conditions and the following disclaimer in the
14
+ * documentation and/or other materials provided with the distribution.
15
+ *
16
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ * POSSIBILITY OF SUCH DAMAGE.
27
+ *
28
+ * Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates.
29
+ */
30
+
31
+ /**
32
+ * Amazon S3 PHP class
33
+ *
34
+ * @link http://undesigned.org.za/2007/10/22/amazon-s3-php-class
35
+ * @version 0.4.0
36
+ */
37
+ class S3 {
38
+ // ACL flags
39
+ const ACL_PRIVATE = 'private';
40
+ const ACL_PUBLIC_READ = 'public-read';
41
+ const ACL_PUBLIC_READ_WRITE = 'public-read-write';
42
+ const ACL_AUTHENTICATED_READ = 'authenticated-read';
43
+
44
+ public static $useSSL = true;
45
+
46
+ private static $__accessKey; // AWS Access key
47
+ private static $__secretKey; // AWS Secret key
48
+
49
+
50
+ /**
51
+ * Constructor - if you're not using the class statically
52
+ *
53
+ * @param string $accessKey Access key
54
+ * @param string $secretKey Secret key
55
+ * @param boolean $useSSL Enable SSL
56
+ * @return void
57
+ */
58
+ public function __construct($accessKey = null, $secretKey = null, $useSSL = true) {
59
+ if ($accessKey !== null && $secretKey !== null)
60
+ self::setAuth($accessKey, $secretKey);
61
+ self::$useSSL = $useSSL;
62
+ }
63
+
64
+
65
+ /**
66
+ * Set AWS access key and secret key
67
+ *
68
+ * @param string $accessKey Access key
69
+ * @param string $secretKey Secret key
70
+ * @return void
71
+ */
72
+ public static function setAuth($accessKey, $secretKey) {
73
+ self::$__accessKey = $accessKey;
74
+ self::$__secretKey = $secretKey;
75
+ }
76
+
77
+
78
+ /**
79
+ * Get a list of buckets
80
+ *
81
+ * @param boolean $detailed Returns detailed bucket list when true
82
+ * @return array | false
83
+ */
84
+ public static function listBuckets($detailed = false) {
85
+ $rest = new S3Request('GET', '', '');
86
+ $rest = $rest->getResponse();
87
+ if ($rest->error === false && $rest->code !== 200)
88
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
89
+ if ($rest->error !== false) {
90
+ trigger_error(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING);
91
+ return false;
92
+ }
93
+ $results = array();
94
+ if (!isset($rest->body->Buckets)) return $results;
95
+
96
+ if ($detailed) {
97
+ if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
98
+ $results['owner'] = array(
99
+ 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID
100
+ );
101
+ $results['buckets'] = array();
102
+ foreach ($rest->body->Buckets->Bucket as $b)
103
+ $results['buckets'][] = array(
104
+ 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate)
105
+ );
106
+ } else
107
+ foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name;
108
+
109
+ return $results;
110
+ }
111
+
112
+
113
+ /*
114
+ * Get contents for a bucket
115
+ *
116
+ * If maxKeys is null this method will loop through truncated result sets
117
+ *
118
+ * @param string $bucket Bucket name
119
+ * @param string $prefix Prefix
120
+ * @param string $marker Marker (last file listed)
121
+ * @param string $maxKeys Max keys (maximum number of keys to return)
122
+ * @param string $delimiter Delimiter
123
+ * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes
124
+ * @return array | false
125
+ */
126
+ public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false) {
127
+ $rest = new S3Request('GET', $bucket, '');
128
+ if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
129
+ if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
130
+ if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys);
131
+ if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
132
+ $response = $rest->getResponse();
133
+ if ($response->error === false && $response->code !== 200)
134
+ $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
135
+ if ($response->error !== false) {
136
+ trigger_error(sprintf("S3::getBucket(): [%s] %s", $response->error['code'], $response->error['message']), E_USER_WARNING);
137
+ return false;
138
+ }
139
+
140
+ $results = array();
141
+
142
+ $nextMarker = null;
143
+ if (isset($response->body, $response->body->Contents))
144
+ foreach ($response->body->Contents as $c) {
145
+ $results[(string)$c->Key] = array(
146
+ 'name' => (string)$c->Key,
147
+ 'time' => strtotime((string)$c->LastModified),
148
+ 'size' => (int)$c->Size,
149
+ 'hash' => substr((string)$c->ETag, 1, -1)
150
+ );
151
+ $nextMarker = (string)$c->Key;
152
+ }
153
+
154
+ if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
155
+ foreach ($response->body->CommonPrefixes as $c)
156
+ $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
157
+
158
+ if (isset($response->body, $response->body->IsTruncated) &&
159
+ (string)$response->body->IsTruncated == 'false') return $results;
160
+
161
+ if (isset($response->body, $response->body->NextMarker))
162
+ $nextMarker = (string)$response->body->NextMarker;
163
+
164
+ // Loop through truncated results if maxKeys isn't specified
165
+ if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true')
166
+ do {
167
+ $rest = new S3Request('GET', $bucket, '');
168
+ if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
169
+ $rest->setParameter('marker', $nextMarker);
170
+ if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
171
+
172
+ if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break;
173
+
174
+ if (isset($response->body, $response->body->Contents))
175
+ foreach ($response->body->Contents as $c) {
176
+ $results[(string)$c->Key] = array(
177
+ 'name' => (string)$c->Key,
178
+ 'time' => strtotime((string)$c->LastModified),
179
+ 'size' => (int)$c->Size,
180
+ 'hash' => substr((string)$c->ETag, 1, -1)
181
+ );
182
+ $nextMarker = (string)$c->Key;
183
+ }
184
+
185
+ if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes))
186
+ foreach ($response->body->CommonPrefixes as $c)
187
+ $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix);
188
+
189
+ if (isset($response->body, $response->body->NextMarker))
190
+ $nextMarker = (string)$response->body->NextMarker;
191
+
192
+ } while ($response !== false && (string)$response->body->IsTruncated == 'true');
193
+
194
+ return $results;
195
+ }
196
+
197
+
198
+ /**
199
+ * Put a bucket
200
+ *
201
+ * @param string $bucket Bucket name
202
+ * @param constant $acl ACL flag
203
+ * @param string $location Set as "EU" to create buckets hosted in Europe
204
+ * @return boolean
205
+ */
206
+ public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) {
207
+ $rest = new S3Request('PUT', $bucket, '');
208
+ $rest->setAmzHeader('x-amz-acl', $acl);
209
+
210
+ if ($location !== false) {
211
+ $dom = new DOMDocument;
212
+ $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration');
213
+ $locationConstraint = $dom->createElement('LocationConstraint', strtoupper($location));
214
+ $createBucketConfiguration->appendChild($locationConstraint);
215
+ $dom->appendChild($createBucketConfiguration);
216
+ $rest->data = $dom->saveXML();
217
+ $rest->size = strlen($rest->data);
218
+ $rest->setHeader('Content-Type', 'application/xml');
219
+ }
220
+ $rest = $rest->getResponse();
221
+
222
+ if ($rest->error === false && $rest->code !== 200)
223
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
224
+ if ($rest->error !== false) {
225
+ trigger_error(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s",
226
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
227
+ return false;
228
+ }
229
+ return true;
230
+ }
231
+
232
+
233
+ /**
234
+ * Delete an empty bucket
235
+ *
236
+ * @param string $bucket Bucket name
237
+ * @return boolean
238
+ */
239
+ public static function deleteBucket($bucket) {
240
+ $rest = new S3Request('DELETE', $bucket);
241
+ $rest = $rest->getResponse();
242
+ if ($rest->error === false && $rest->code !== 204)
243
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
244
+ if ($rest->error !== false) {
245
+ trigger_error(sprintf("S3::deleteBucket({$bucket}): [%s] %s",
246
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
247
+ return false;
248
+ }
249
+ return true;
250
+ }
251
+
252
+
253
+ /**
254
+ * Create input info array for putObject()
255
+ *
256
+ * @param string $file Input file
257
+ * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
258
+ * @return array | false
259
+ */
260
+ public static function inputFile($file, $md5sum = true) {
261
+ if (!file_exists($file) || !is_file($file) || !is_readable($file)) {
262
+ trigger_error('S3::inputFile(): Unable to open input file: '.$file, E_USER_WARNING);
263
+ return false;
264
+ }
265
+ return array('file' => $file, 'size' => filesize($file),
266
+ 'md5sum' => $md5sum !== false ? (is_string($md5sum) ? $md5sum :
267
+ base64_encode(md5_file($file, true))) : '');
268
+ }
269
+
270
+
271
+ /**
272
+ * Create input array info for putObject() with a resource
273
+ *
274
+ * @param string $resource Input resource to read from
275
+ * @param integer $bufferSize Input byte size
276
+ * @param string $md5sum MD5 hash to send (optional)
277
+ * @return array | false
278
+ */
279
+ public static function inputResource(&$resource, $bufferSize, $md5sum = '') {
280
+ if (!is_resource($resource) || $bufferSize < 0) {
281
+ trigger_error('S3::inputResource(): Invalid resource or buffer size', E_USER_WARNING);
282
+ return false;
283
+ }
284
+ $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
285
+ $input['fp'] =& $resource;
286
+ return $input;
287
+ }
288
+
289
+
290
+ /**
291
+ * Put an object
292
+ *
293
+ * @param mixed $input Input data
294
+ * @param string $bucket Bucket name
295
+ * @param string $uri Object URI
296
+ * @param constant $acl ACL constant
297
+ * @param array $metaHeaders Array of x-amz-meta-* headers
298
+ * @param array $requestHeaders Array of request headers or content type as a string
299
+ * @return boolean
300
+ */
301
+ public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) {
302
+ if ($input === false) return false;
303
+ $rest = new S3Request('PUT', $bucket, $uri);
304
+
305
+ if (is_string($input)) $input = array(
306
+ 'data' => $input, 'size' => strlen($input),
307
+ 'md5sum' => base64_encode(md5($input, true))
308
+ );
309
+
310
+ // Data
311
+ if (isset($input['fp']))
312
+ $rest->fp =& $input['fp'];
313
+ elseif (isset($input['file']))
314
+ $rest->fp = @fopen($input['file'], 'rb');
315
+ elseif (isset($input['data']))
316
+ $rest->data = $input['data'];
317
+
318
+ // Content-Length (required)
319
+ if (isset($input['size']) && $input['size'] >= 0)
320
+ $rest->size = $input['size'];
321
+ else {
322
+ if (isset($input['file']))
323
+ $rest->size = filesize($input['file']);
324
+ elseif (isset($input['data']))
325
+ $rest->size = strlen($input['data']);
326
+ }
327
+
328
+ // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
329
+ if (is_array($requestHeaders))
330
+ foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
331
+ elseif (is_string($requestHeaders)) // Support for legacy contentType parameter
332
+ $input['type'] = $requestHeaders;
333
+
334
+ // Content-Type
335
+ if (!isset($input['type'])) {
336
+ if (isset($requestHeaders['Content-Type']))
337
+ $input['type'] =& $requestHeaders['Content-Type'];
338
+ elseif (isset($input['file']))
339
+ $input['type'] = self::__getMimeType($input['file']);
340
+ else
341
+ $input['type'] = 'application/octet-stream';
342
+ }
343
+
344
+ // We need to post with Content-Length and Content-Type, MD5 is optional
345
+ if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false)) {
346
+ $rest->setHeader('Content-Type', $input['type']);
347
+ if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);
348
+
349
+ $rest->setAmzHeader('x-amz-acl', $acl);
350
+ foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
351
+ $rest->getResponse();
352
+ } else
353
+ $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');
354
+
355
+ if ($rest->response->error === false && $rest->response->code !== 200)
356
+ $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
357
+ if ($rest->response->error !== false) {
358
+ trigger_error(sprintf("S3::putObject(): [%s] %s", $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING);
359
+ return false;
360
+ }
361
+ return true;
362
+ }
363
+
364
+
365
+ /**
366
+ * Put an object from a file (legacy function)
367
+ *
368
+ * @param string $file Input file path
369
+ * @param string $bucket Bucket name
370
+ * @param string $uri Object URI
371
+ * @param constant $acl ACL constant
372
+ * @param array $metaHeaders Array of x-amz-meta-* headers
373
+ * @param string $contentType Content type
374
+ * @return boolean
375
+ */
376
+ public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) {
377
+ return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType);
378
+ }
379
+
380
+
381
+ /**
382
+ * Put an object from a string (legacy function)
383
+ *
384
+ * @param string $string Input data
385
+ * @param string $bucket Bucket name
386
+ * @param string $uri Object URI
387
+ * @param constant $acl ACL constant
388
+ * @param array $metaHeaders Array of x-amz-meta-* headers
389
+ * @param string $contentType Content type
390
+ * @return boolean
391
+ */
392
+ public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain') {
393
+ return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType);
394
+ }
395
+
396
+
397
+ /**
398
+ * Get an object
399
+ *
400
+ * @param string $bucket Bucket name
401
+ * @param string $uri Object URI
402
+ * @param mixed $saveTo Filename or resource to write to
403
+ * @return mixed
404
+ */
405
+ public static function getObject($bucket, $uri, $saveTo = false) {
406
+ $rest = new S3Request('GET', $bucket, $uri);
407
+ if ($saveTo !== false) {
408
+ if (is_resource($saveTo))
409
+ $rest->fp =& $saveTo;
410
+ else
411
+ if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
412
+ $rest->file = realpath($saveTo);
413
+ else
414
+ $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
415
+ }
416
+ if ($rest->response->error === false) $rest->getResponse();
417
+
418
+ if ($rest->response->error === false && $rest->response->code !== 200)
419
+ $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
420
+ if ($rest->response->error !== false) {
421
+ trigger_error(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s",
422
+ $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING);
423
+ return false;
424
+ }
425
+ return $rest->response;
426
+ }
427
+
428
+
429
+ /**
430
+ * Get object information
431
+ *
432
+ * @param string $bucket Bucket name
433
+ * @param string $uri Object URI
434
+ * @param boolean $returnInfo Return response information
435
+ * @return mixed | false
436
+ */
437
+ public static function getObjectInfo($bucket, $uri, $returnInfo = true) {
438
+ $rest = new S3Request('HEAD', $bucket, $uri);
439
+ $rest = $rest->getResponse();
440
+ if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
441
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
442
+ if ($rest->error !== false) {
443
+ trigger_error(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s",
444
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
445
+ return false;
446
+ }
447
+ return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
448
+ }
449
+
450
+
451
+ /**
452
+ * Copy an object
453
+ *
454
+ * @param string $bucket Source bucket name
455
+ * @param string $uri Source object URI
456
+ * @param string $bucket Destination bucket name
457
+ * @param string $uri Destination object URI
458
+ * @param constant $acl ACL constant
459
+ * @param array $metaHeaders Optional array of x-amz-meta-* headers
460
+ * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
461
+ * @return mixed | false
462
+ */
463
+ public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) {
464
+ $rest = new S3Request('PUT', $bucket, $uri);
465
+ $rest->setHeader('Content-Length', 0);
466
+ foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v);
467
+ foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v);
468
+ $rest->setAmzHeader('x-amz-acl', $acl);
469
+ $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, $srcUri));
470
+ if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0)
471
+ $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE');
472
+ $rest = $rest->getResponse();
473
+ if ($rest->error === false && $rest->code !== 200)
474
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
475
+ if ($rest->error !== false) {
476
+ trigger_error(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
477
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
478
+ return false;
479
+ }
480
+ return isset($rest->body->LastModified, $rest->body->ETag) ? array(
481
+ 'time' => strtotime((string)$rest->body->LastModified),
482
+ 'hash' => substr((string)$rest->body->ETag, 1, -1)
483
+ ) : false;
484
+ }
485
+
486
+
487
+ /**
488
+ * Set logging for a bucket
489
+ *
490
+ * @param string $bucket Bucket name
491
+ * @param string $targetBucket Target bucket (where logs are stored)
492
+ * @param string $targetPrefix Log prefix (e,g; domain.com-)
493
+ * @return boolean
494
+ */
495
+ public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null) {
496
+ // The S3 log delivery group has to be added to the target bucket's ACP
497
+ if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false) {
498
+ // Only add permissions to the target bucket when they do not exist
499
+ $aclWriteSet = false;
500
+ $aclReadSet = false;
501
+ foreach ($acp['acl'] as $acl)
502
+ if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery') {
503
+ if ($acl['permission'] == 'WRITE') $aclWriteSet = true;
504
+ elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true;
505
+ }
506
+ if (!$aclWriteSet) $acp['acl'][] = array(
507
+ 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE'
508
+ );
509
+ if (!$aclReadSet) $acp['acl'][] = array(
510
+ 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP'
511
+ );
512
+ if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp);
513
+ }
514
+
515
+ $dom = new DOMDocument;
516
+ $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus');
517
+ $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/');
518
+ if ($targetBucket !== null) {
519
+ if ($targetPrefix == null) $targetPrefix = $bucket . '-';
520
+ $loggingEnabled = $dom->createElement('LoggingEnabled');
521
+ $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket));
522
+ $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix));
523
+ // TODO: Add TargetGrants?
524
+ $bucketLoggingStatus->appendChild($loggingEnabled);
525
+ }
526
+ $dom->appendChild($bucketLoggingStatus);
527
+
528
+ $rest = new S3Request('PUT', $bucket, '');
529
+ $rest->setParameter('logging', null);
530
+ $rest->data = $dom->saveXML();
531
+ $rest->size = strlen($rest->data);
532
+ $rest->setHeader('Content-Type', 'application/xml');
533
+ $rest = $rest->getResponse();
534
+ if ($rest->error === false && $rest->code !== 200)
535
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
536
+ if ($rest->error !== false) {
537
+ trigger_error(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s",
538
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
539
+ return false;
540
+ }
541
+ return true;
542
+ }
543
+
544
+
545
+ /**
546
+ * Get logging status for a bucket
547
+ *
548
+ * This will return false if logging is not enabled.
549
+ * Note: To enable logging, you also need to grant write access to the log group
550
+ *
551
+ * @param string $bucket Bucket name
552
+ * @return array | false
553
+ */
554
+ public static function getBucketLogging($bucket) {
555
+ $rest = new S3Request('GET', $bucket, '');
556
+ $rest->setParameter('logging', null);
557
+ $rest = $rest->getResponse();
558
+ if ($rest->error === false && $rest->code !== 200)
559
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
560
+ if ($rest->error !== false) {
561
+ trigger_error(sprintf("S3::getBucketLogging({$bucket}): [%s] %s",
562
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
563
+ return false;
564
+ }
565
+ if (!isset($rest->body->LoggingEnabled)) return false; // No logging
566
+ return array(
567
+ 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket,
568
+ 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix,
569
+ );
570
+ }
571
+
572
+
573
+ /**
574
+ * Disable bucket logging
575
+ *
576
+ * @param string $bucket Bucket name
577
+ * @return boolean
578
+ */
579
+ public static function disableBucketLogging($bucket) {
580
+ return self::setBucketLogging($bucket, null);
581
+ }
582
+
583
+
584
+ /**
585
+ * Get a bucket's location
586
+ *
587
+ * @param string $bucket Bucket name
588
+ * @return string | false
589
+ */
590
+ public static function getBucketLocation($bucket) {
591
+ $rest = new S3Request('GET', $bucket, '');
592
+ $rest->setParameter('location', null);
593
+ $rest = $rest->getResponse();
594
+ if ($rest->error === false && $rest->code !== 200)
595
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
596
+ if ($rest->error !== false) {
597
+ trigger_error(sprintf("S3::getBucketLocation({$bucket}): [%s] %s",
598
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
599
+ return false;
600
+ }
601
+ return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US';
602
+ }
603
+
604
+
605
+ /**
606
+ * Set object or bucket Access Control Policy
607
+ *
608
+ * @param string $bucket Bucket name
609
+ * @param string $uri Object URI
610
+ * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
611
+ * @return boolean
612
+ */
613
+ public static function setAccessControlPolicy($bucket, $uri = '', $acp = array()) {
614
+ $dom = new DOMDocument;
615
+ $dom->formatOutput = true;
616
+ $accessControlPolicy = $dom->createElement('AccessControlPolicy');
617
+ $accessControlList = $dom->createElement('AccessControlList');
618
+
619
+ // It seems the owner has to be passed along too
620
+ $owner = $dom->createElement('Owner');
621
+ $owner->appendChild($dom->createElement('ID', $acp['owner']['id']));
622
+ $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name']));
623
+ $accessControlPolicy->appendChild($owner);
624
+
625
+ foreach ($acp['acl'] as $g) {
626
+ $grant = $dom->createElement('Grant');
627
+ $grantee = $dom->createElement('Grantee');
628
+ $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
629
+ if (isset($g['id'])) { // CanonicalUser (DisplayName is omitted)
630
+ $grantee->setAttribute('xsi:type', 'CanonicalUser');
631
+ $grantee->appendChild($dom->createElement('ID', $g['id']));
632
+ } elseif (isset($g['email'])) { // AmazonCustomerByEmail
633
+ $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail');
634
+ $grantee->appendChild($dom->createElement('EmailAddress', $g['email']));
635
+ } elseif ($g['type'] == 'Group') { // Group
636
+ $grantee->setAttribute('xsi:type', 'Group');
637
+ $grantee->appendChild($dom->createElement('URI', $g['uri']));
638
+ }
639
+ $grant->appendChild($grantee);
640
+ $grant->appendChild($dom->createElement('Permission', $g['permission']));
641
+ $accessControlList->appendChild($grant);
642
+ }
643
+
644
+ $accessControlPolicy->appendChild($accessControlList);
645
+ $dom->appendChild($accessControlPolicy);
646
+
647
+ $rest = new S3Request('PUT', $bucket, $uri);
648
+ $rest->setParameter('acl', null);
649
+ $rest->data = $dom->saveXML();
650
+ $rest->size = strlen($rest->data);
651
+ $rest->setHeader('Content-Type', 'application/xml');
652
+ $rest = $rest->getResponse();
653
+ if ($rest->error === false && $rest->code !== 200)
654
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
655
+ if ($rest->error !== false) {
656
+ trigger_error(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
657
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
658
+ return false;
659
+ }
660
+ return true;
661
+ }
662
+
663
+
664
+ /**
665
+ * Get object or bucket Access Control Policy
666
+ *
667
+ * @param string $bucket Bucket name
668
+ * @param string $uri Object URI
669
+ * @return mixed | false
670
+ */
671
+ public static function getAccessControlPolicy($bucket, $uri = '') {
672
+ $rest = new S3Request('GET', $bucket, $uri);
673
+ $rest->setParameter('acl', null);
674
+ $rest = $rest->getResponse();
675
+ if ($rest->error === false && $rest->code !== 200)
676
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
677
+ if ($rest->error !== false) {
678
+ trigger_error(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s",
679
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
680
+ return false;
681
+ }
682
+
683
+ $acp = array();
684
+ if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) {
685
+ $acp['owner'] = array(
686
+ 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName
687
+ );
688
+ }
689
+ if (isset($rest->body->AccessControlList)) {
690
+ $acp['acl'] = array();
691
+ foreach ($rest->body->AccessControlList->Grant as $grant) {
692
+ foreach ($grant->Grantee as $grantee) {
693
+ if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser
694
+ $acp['acl'][] = array(
695
+ 'type' => 'CanonicalUser',
696
+ 'id' => (string)$grantee->ID,
697
+ 'name' => (string)$grantee->DisplayName,
698
+ 'permission' => (string)$grant->Permission
699
+ );
700
+ elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail
701
+ $acp['acl'][] = array(
702
+ 'type' => 'AmazonCustomerByEmail',
703
+ 'email' => (string)$grantee->EmailAddress,
704
+ 'permission' => (string)$grant->Permission
705
+ );
706
+ elseif (isset($grantee->URI)) // Group
707
+ $acp['acl'][] = array(
708
+ 'type' => 'Group',
709
+ 'uri' => (string)$grantee->URI,
710
+ 'permission' => (string)$grant->Permission
711
+ );
712
+ else continue;
713
+ }
714
+ }
715
+ }
716
+ return $acp;
717
+ }
718
+
719
+
720
+ /**
721
+ * Delete an object
722
+ *
723
+ * @param string $bucket Bucket name
724
+ * @param string $uri Object URI
725
+ * @return boolean
726
+ */
727
+ public static function deleteObject($bucket, $uri) {
728
+ $rest = new S3Request('DELETE', $bucket, $uri);
729
+ $rest = $rest->getResponse();
730
+ if ($rest->error === false && $rest->code !== 204)
731
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
732
+ if ($rest->error !== false) {
733
+ trigger_error(sprintf("S3::deleteObject(): [%s] %s",
734
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
735
+ return false;
736
+ }
737
+ return true;
738
+ }
739
+
740
+
741
+ /**
742
+ * Get a query string authenticated URL
743
+ *
744
+ * @param string $bucket Bucket name
745
+ * @param string $uri Object URI
746
+ * @param integer $lifetime Lifetime in seconds
747
+ * @param boolean $hostBucket Use the bucket name as the hostname
748
+ * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification)
749
+ * @return string
750
+ */
751
+ public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) {
752
+ $expires = time() + $lifetime;
753
+ $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
754
+ return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s',
755
+ $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
756
+ urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
757
+ }
758
+
759
+ /**
760
+ * Get upload POST parameters for form uploads
761
+ *
762
+ * @param string $bucket Bucket name
763
+ * @param string $uriPrefix Object URI prefix
764
+ * @param constant $acl ACL constant
765
+ * @param integer $lifetime Lifetime in seconds
766
+ * @param integer $maxFileSize Maximum filesize in bytes (default 5MB)
767
+ * @param string $successRedirect Redirect URL or 200 / 201 status code
768
+ * @param array $amzHeaders Array of x-amz-meta-* headers
769
+ * @param array $headers Array of request headers or content type as a string
770
+ * @param boolean $flashVars Includes additional "Filename" variable posted by Flash
771
+ * @return object
772
+ */
773
+ public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600, $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false) {
774
+ // Create policy object
775
+ $policy = new stdClass;
776
+ $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime));
777
+ $policy->conditions = array();
778
+ $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj);
779
+ $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj);
780
+
781
+ $obj = new stdClass; // 200 for non-redirect uploads
782
+ if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
783
+ $obj->success_action_status = (string)$successRedirect;
784
+ else // URL
785
+ $obj->success_action_redirect = $successRedirect;
786
+ array_push($policy->conditions, $obj);
787
+
788
+ array_push($policy->conditions, array('starts-with', '$key', $uriPrefix));
789
+ if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', ''));
790
+ foreach (array_keys($headers) as $headerKey)
791
+ array_push($policy->conditions, array('starts-with', '$'.$headerKey, ''));
792
+ foreach ($amzHeaders as $headerKey => $headerVal) {
793
+ $obj = new stdClass; $obj->{$headerKey} = (string)$headerVal; array_push($policy->conditions, $obj);
794
+ }
795
+ array_push($policy->conditions, array('content-length-range', 0, $maxFileSize));
796
+ $policy = base64_encode(str_replace('\/', '/', json_encode($policy)));
797
+
798
+ // Create parameters
799
+ $params = new stdClass;
800
+ $params->AWSAccessKeyId = self::$__accessKey;
801
+ $params->key = $uriPrefix.'${filename}';
802
+ $params->acl = $acl;
803
+ $params->policy = $policy; unset($policy);
804
+ $params->signature = self::__getHash($params->policy);
805
+ if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201)))
806
+ $params->success_action_status = (string)$successRedirect;
807
+ else
808
+ $params->success_action_redirect = $successRedirect;
809
+ foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
810
+ foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal;
811
+ return $params;
812
+ }
813
+
814
+ /**
815
+ * Create a CloudFront distribution
816
+ *
817
+ * @param string $bucket Bucket name
818
+ * @param boolean $enabled Enabled (true/false)
819
+ * @param array $cnames Array containing CNAME aliases
820
+ * @param string $comment Use the bucket name as the hostname
821
+ * @return array | false
822
+ */
823
+ public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = '') {
824
+ self::$useSSL = true; // CloudFront requires SSL
825
+ $rest = new S3Request('POST', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com');
826
+ $rest->data = self::__getCloudFrontDistributionConfigXML($bucket.'.s3.amazonaws.com', $enabled, $comment, (string)microtime(true), $cnames);
827
+ $rest->size = strlen($rest->data);
828
+ $rest->setHeader('Content-Type', 'application/xml');
829
+ $rest = self::__getCloudFrontResponse($rest);
830
+
831
+ if ($rest->error === false && $rest->code !== 201)
832
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
833
+ if ($rest->error !== false) {
834
+ trigger_error(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", '$comment'): [%s] %s",
835
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
836
+ return false;
837
+ } elseif ($rest->body instanceof SimpleXMLElement)
838
+ return self::__parseCloudFrontDistributionConfig($rest->body);
839
+ return false;
840
+ }
841
+
842
+
843
+ /**
844
+ * Get CloudFront distribution info
845
+ *
846
+ * @param string $distributionId Distribution ID from listDistributions()
847
+ * @return array | false
848
+ */
849
+ public static function getDistribution($distributionId) {
850
+ self::$useSSL = true; // CloudFront requires SSL
851
+ $rest = new S3Request('GET', '', '2008-06-30/distribution/'.$distributionId, 'cloudfront.amazonaws.com');
852
+ $rest = self::__getCloudFrontResponse($rest);
853
+
854
+ if ($rest->error === false && $rest->code !== 200)
855
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
856
+ if ($rest->error !== false) {
857
+ trigger_error(sprintf("S3::getDistribution($distributionId): [%s] %s",
858
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
859
+ return false;
860
+ } elseif ($rest->body instanceof SimpleXMLElement) {
861
+ $dist = self::__parseCloudFrontDistributionConfig($rest->body);
862
+ $dist['hash'] = $rest->headers['hash'];
863
+ return $dist;
864
+ }
865
+ return false;
866
+ }
867
+
868
+
869
+ /**
870
+ * Update a CloudFront distribution
871
+ *
872
+ * @param array $dist Distribution array info identical to output of getDistribution()
873
+ * @return array | false
874
+ */
875
+ public static function updateDistribution($dist) {
876
+ self::$useSSL = true; // CloudFront requires SSL
877
+ $rest = new S3Request('PUT', '', '2008-06-30/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com');
878
+ $rest->data = self::__getCloudFrontDistributionConfigXML($dist['origin'], $dist['enabled'], $dist['comment'], $dist['callerReference'], $dist['cnames']);
879
+ $rest->size = strlen($rest->data);
880
+ $rest->setHeader('If-Match', $dist['hash']);
881
+ $rest = self::__getCloudFrontResponse($rest);
882
+
883
+ if ($rest->error === false && $rest->code !== 200)
884
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
885
+ if ($rest->error !== false) {
886
+ trigger_error(sprintf("S3::updateDistribution({$dist['id']}, ".(int)$enabled.", '$comment'): [%s] %s",
887
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
888
+ return false;
889
+ } else {
890
+ $dist = self::__parseCloudFrontDistributionConfig($rest->body);
891
+ $dist['hash'] = $rest->headers['hash'];
892
+ return $dist;
893
+ }
894
+ return false;
895
+ }
896
+
897
+
898
+ /**
899
+ * Delete a CloudFront distribution
900
+ *
901
+ * @param array $dist Distribution array info identical to output of getDistribution()
902
+ * @return boolean
903
+ */
904
+ public static function deleteDistribution($dist) {
905
+ self::$useSSL = true; // CloudFront requires SSL
906
+ $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com');
907
+ $rest->setHeader('If-Match', $dist['hash']);
908
+ $rest = self::__getCloudFrontResponse($rest);
909
+
910
+ if ($rest->error === false && $rest->code !== 204)
911
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
912
+ if ($rest->error !== false) {
913
+ trigger_error(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s",
914
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
915
+ return false;
916
+ }
917
+ return true;
918
+ }
919
+
920
+
921
+ /**
922
+ * Get a list of CloudFront distributions
923
+ *
924
+ * @return array
925
+ */
926
+ public static function listDistributions() {
927
+ self::$useSSL = true; // CloudFront requires SSL
928
+ $rest = new S3Request('GET', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com');
929
+ $rest = self::__getCloudFrontResponse($rest);
930
+
931
+ if ($rest->error === false && $rest->code !== 200)
932
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
933
+ if ($rest->error !== false) {
934
+ trigger_error(sprintf("S3::listDistributions(): [%s] %s",
935
+ $rest->error['code'], $rest->error['message']), E_USER_WARNING);
936
+ return false;
937
+ } elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary)) {
938
+ $list = array();
939
+ if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated)) {
940
+ //$info['marker'] = (string)$rest->body->Marker;
941
+ //$info['maxItems'] = (int)$rest->body->MaxItems;
942
+ //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false;
943
+ }
944
+ foreach ($rest->body->DistributionSummary as $summary) {
945
+ $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary);
946
+ }
947
+ return $list;
948
+ }
949
+ return array();
950
+ }
951
+
952
+
953
+ /**
954
+ * Get a DistributionConfig DOMDocument
955
+ *
956
+ * @internal Used to create XML in createDistribution() and updateDistribution()
957
+ * @param string $bucket Origin bucket
958
+ * @param boolean $enabled Enabled (true/false)
959
+ * @param string $comment Comment to append
960
+ * @param string $callerReference Caller reference
961
+ * @param array $cnames Array of CNAME aliases
962
+ * @return string
963
+ */
964
+ private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array()) {
965
+ $dom = new DOMDocument('1.0', 'UTF-8');
966
+ $dom->formatOutput = true;
967
+ $distributionConfig = $dom->createElement('DistributionConfig');
968
+ $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2008-06-30/');
969
+ $distributionConfig->appendChild($dom->createElement('Origin', $bucket));
970
+ $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference));
971
+ foreach ($cnames as $cname)
972
+ $distributionConfig->appendChild($dom->createElement('CNAME', $cname));
973
+ if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment));
974
+ $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false'));
975
+ $dom->appendChild($distributionConfig);
976
+ return $dom->saveXML();
977
+ }
978
+
979
+
980
+ /**
981
+ * Parse a CloudFront distribution config
982
+ *
983
+ * @internal Used to parse the CloudFront DistributionConfig node to an array
984
+ * @param object &$node DOMNode
985
+ * @return array
986
+ */
987
+ private static function __parseCloudFrontDistributionConfig(&$node) {
988
+ $dist = array();
989
+ if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName)) {
990
+ $dist['id'] = (string)$node->Id;
991
+ $dist['status'] = (string)$node->Status;
992
+ $dist['time'] = strtotime((string)$node->LastModifiedTime);
993
+ $dist['domain'] = (string)$node->DomainName;
994
+ }
995
+ if (isset($node->CallerReference))
996
+ $dist['callerReference'] = (string)$node->CallerReference;
997
+ if (isset($node->Comment))
998
+ $dist['comment'] = (string)$node->Comment;
999
+ if (isset($node->Enabled, $node->Origin)) {
1000
+ $dist['origin'] = (string)$node->Origin;
1001
+ $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false;
1002
+ } elseif (isset($node->DistributionConfig)) {
1003
+ $dist = array_merge($dist, self::__parseCloudFrontDistributionConfig($node->DistributionConfig));
1004
+ }
1005
+ if (isset($node->CNAME)) {
1006
+ $dist['cnames'] = array();
1007
+ foreach ($node->CNAME as $cname) $dist['cnames'][(string)$cname] = (string)$cname;
1008
+ }
1009
+ return $dist;
1010
+ }
1011
+
1012
+
1013
+ /**
1014
+ * Grab CloudFront response
1015
+ *
1016
+ * @internal Used to parse the CloudFront S3Request::getResponse() output
1017
+ * @param object &$rest S3Request instance
1018
+ * @return object
1019
+ */
1020
+ private static function __getCloudFrontResponse(&$rest) {
1021
+ $rest->getResponse();
1022
+ if ($rest->response->error === false && isset($rest->response->body) &&
1023
+ is_string($rest->response->body) && substr($rest->response->body, 0, 5) == '<?xml') {
1024
+ $rest->response->body = simplexml_load_string($rest->response->body);
1025
+ // Grab CloudFront errors
1026
+ if (isset($rest->response->body->Error, $rest->response->body->Error->Code,
1027
+ $rest->response->body->Error->Message)) {
1028
+ $rest->response->error = array(
1029
+ 'code' => (string)$rest->response->body->Error->Code,
1030
+ 'message' => (string)$rest->response->body->Error->Message
1031
+ );
1032
+ unset($rest->response->body);
1033
+ }
1034
+ }
1035
+ return $rest->response;
1036
+ }
1037
+
1038
+
1039
+ /**
1040
+ * Get MIME type for file
1041
+ *
1042
+ * @internal Used to get mime types
1043
+ * @param string &$file File path
1044
+ * @return string
1045
+ */
1046
+ public static function __getMimeType(&$file) {
1047
+ $type = false;
1048
+ // Fileinfo documentation says fileinfo_open() will use the
1049
+ // MAGIC env var for the magic file
1050
+ if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
1051
+ ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) {
1052
+ if (($type = finfo_file($finfo, $file)) !== false) {
1053
+ // Remove the charset and grab the last content-type
1054
+ $type = explode(' ', str_replace('; charset=', ';charset=', $type));
1055
+ $type = array_pop($type);
1056
+ $type = explode(';', $type);
1057
+ $type = trim(array_shift($type));
1058
+ }
1059
+ finfo_close($finfo);
1060
+
1061
+ // If anyone is still using mime_content_type()
1062
+ } elseif (function_exists('mime_content_type'))
1063
+ $type = trim(mime_content_type($file));
1064
+
1065
+ if ($type !== false && strlen($type) > 0) return $type;
1066
+
1067
+ // Otherwise do it the old fashioned way
1068
+ static $exts = array(
1069
+ 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
1070
+ 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
1071
+ 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
1072
+ 'zip' => 'application/zip', 'gz' => 'application/x-gzip',
1073
+ 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
1074
+ 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
1075
+ 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
1076
+ 'css' => 'text/css', 'js' => 'text/javascript',
1077
+ 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
1078
+ 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
1079
+ 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
1080
+ 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
1081
+ );
1082
+ $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
1083
+ return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
1084
+ }
1085
+
1086
+
1087
+ /**
1088
+ * Generate the auth string: "AWS AccessKey:Signature"
1089
+ *
1090
+ * @internal Used by S3Request::getResponse()
1091
+ * @param string $string String to sign
1092
+ * @return string
1093
+ */
1094
+ public static function __getSignature($string) {
1095
+ return 'AWS '.self::$__accessKey.':'.self::__getHash($string);
1096
+ }
1097
+
1098
+
1099
+ /**
1100
+ * Creates a HMAC-SHA1 hash
1101
+ *
1102
+ * This uses the hash extension if loaded
1103
+ *
1104
+ * @internal Used by __getSignature()
1105
+ * @param string $string String to sign
1106
+ * @return string
1107
+ */
1108
+ private static function __getHash($string) {
1109
+ return base64_encode(extension_loaded('hash') ?
1110
+ hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
1111
+ (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
1112
+ pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
1113
+ (str_repeat(chr(0x36), 64))) . $string)))));
1114
+ }
1115
+
1116
+ }
1117
+
1118
+ final class S3Request {
1119
+ private $verb, $bucket, $uri, $resource = '', $parameters = array(),
1120
+ $amzHeaders = array(), $headers = array(
1121
+ 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => ''
1122
+ );
1123
+ public $fp = false, $size = 0, $data = false, $response;
1124
+
1125
+
1126
+ /**
1127
+ * Constructor
1128
+ *
1129
+ * @param string $verb Verb
1130
+ * @param string $bucket Bucket name
1131
+ * @param string $uri Object URI
1132
+ * @return mixed
1133
+ */
1134
+ function __construct($verb, $bucket = '', $uri = '', $defaultHost = 's3.amazonaws.com') {
1135
+ $this->verb = $verb;
1136
+ $this->bucket = strtolower($bucket);
1137
+ $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/';
1138
+
1139
+ if ($this->bucket !== '') {
1140
+ $this->headers['Host'] = $this->bucket.'.'.$defaultHost;
1141
+ $this->resource = '/'.$this->bucket.$this->uri;
1142
+ } else {
1143
+ $this->headers['Host'] = $defaultHost;
1144
+ //$this->resource = strlen($this->uri) > 1 ? '/'.$this->bucket.$this->uri : $this->uri;
1145
+ $this->resource = $this->uri;
1146
+ }
1147
+ $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
1148
+
1149
+ $this->response = new STDClass;
1150
+ $this->response->error = false;
1151
+ }
1152
+
1153
+
1154
+ /**
1155
+ * Set request parameter
1156
+ *
1157
+ * @param string $key Key
1158
+ * @param string $value Value
1159
+ * @return void
1160
+ */
1161
+ public function setParameter($key, $value) {
1162
+ $this->parameters[$key] = $value;
1163
+ }
1164
+
1165
+
1166
+ /**
1167
+ * Set request header
1168
+ *
1169
+ * @param string $key Key
1170
+ * @param string $value Value
1171
+ * @return void
1172
+ */
1173
+ public function setHeader($key, $value) {
1174
+ $this->headers[$key] = $value;
1175
+ }
1176
+
1177
+
1178
+ /**
1179
+ * Set x-amz-meta-* header
1180
+ *
1181
+ * @param string $key Key
1182
+ * @param string $value Value
1183
+ * @return void
1184
+ */
1185
+ public function setAmzHeader($key, $value) {
1186
+ $this->amzHeaders[$key] = $value;
1187
+ }
1188
+
1189
+
1190
+ /**
1191
+ * Get the S3 response
1192
+ *
1193
+ * @return object | false
1194
+ */
1195
+ public function getResponse() {
1196
+ $query = '';
1197
+ if (sizeof($this->parameters) > 0) {
1198
+ $query = substr($this->uri, -1) !== '?' ? '?' : '&';
1199
+ foreach ($this->parameters as $var => $value)
1200
+ if ($value == null || $value == '') $query .= $var.'&';
1201
+ // Parameters should be encoded (thanks Sean O'Dea)
1202
+ else $query .= $var.'='.rawurlencode($value).'&';
1203
+ $query = substr($query, 0, -1);
1204
+ $this->uri .= $query;
1205
+
1206
+ if (array_key_exists('acl', $this->parameters) ||
1207
+ array_key_exists('location', $this->parameters) ||
1208
+ array_key_exists('torrent', $this->parameters) ||
1209
+ array_key_exists('logging', $this->parameters))
1210
+ $this->resource .= $query;
1211
+ }
1212
+ $url = ((S3::$useSSL && extension_loaded('openssl')) ?
1213
+ 'https://':'http://').$this->headers['Host'].$this->uri;
1214
+ //var_dump($this->bucket, $this->uri, $this->resource, $url);
1215
+
1216
+ // Basic setup
1217
+ $curl = curl_init();
1218
+ curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php');
1219
+
1220
+ if (S3::$useSSL) {
1221
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1);
1222
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
1223
+ }
1224
+
1225
+ curl_setopt($curl, CURLOPT_URL, $url);
1226
+
1227
+ // Headers
1228
+ $headers = array(); $amz = array();
1229
+ foreach ($this->amzHeaders as $header => $value)
1230
+ if (strlen($value) > 0) $headers[] = $header.': '.$value;
1231
+ foreach ($this->headers as $header => $value)
1232
+ if (strlen($value) > 0) $headers[] = $header.': '.$value;
1233
+
1234
+ // Collect AMZ headers for signature
1235
+ foreach ($this->amzHeaders as $header => $value)
1236
+ if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value;
1237
+
1238
+ // AMZ headers must be sorted
1239
+ if (sizeof($amz) > 0) {
1240
+ sort($amz);
1241
+ $amz = "\n".implode("\n", $amz);
1242
+ } else $amz = '';
1243
+
1244
+ // Authorization string (CloudFront stringToSign should only contain a date)
1245
+ $headers[] = 'Authorization: ' . S3::__getSignature(
1246
+ $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] :
1247
+ $this->verb."\n".$this->headers['Content-MD5']."\n".
1248
+ $this->headers['Content-Type']."\n".$this->headers['Date'].$amz."\n".$this->resource
1249
+ );
1250
+
1251
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1252
+ curl_setopt($curl, CURLOPT_HEADER, false);
1253
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
1254
+ curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
1255
+ curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));
1256
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
1257
+
1258
+ // Request types
1259
+ switch ($this->verb) {
1260
+ case 'GET': break;
1261
+ case 'PUT': case 'POST': // POST only used for CloudFront
1262
+ if ($this->fp !== false) {
1263
+ curl_setopt($curl, CURLOPT_PUT, true);
1264
+ curl_setopt($curl, CURLOPT_INFILE, $this->fp);
1265
+ if ($this->size >= 0)
1266
+ curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
1267
+ } elseif ($this->data !== false) {
1268
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1269
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
1270
+ if ($this->size >= 0)
1271
+ curl_setopt($curl, CURLOPT_BUFFERSIZE, $this->size);
1272
+ } else
1273
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
1274
+ break;
1275
+ case 'HEAD':
1276
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
1277
+ curl_setopt($curl, CURLOPT_NOBODY, true);
1278
+ break;
1279
+ case 'DELETE':
1280
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
1281
+ break;
1282
+ default: break;
1283
+ }
1284
+
1285
+ // Execute, grab errors
1286
+ if (curl_exec($curl))
1287
+ $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1288
+ else
1289
+ $this->response->error = array(
1290
+ 'code' => curl_errno($curl),
1291
+ 'message' => curl_error($curl),
1292
+ 'resource' => $this->resource
1293
+ );
1294
+
1295
+ @curl_close($curl);
1296
+
1297
+ // Parse body into XML
1298
+ if ($this->response->error === false && isset($this->response->headers['type']) &&
1299
+ $this->response->headers['type'] == 'application/xml' && isset($this->response->body)) {
1300
+ $this->response->body = simplexml_load_string($this->response->body);
1301
+
1302
+ // Grab S3 errors
1303
+ if (!in_array($this->response->code, array(200, 204)) &&
1304
+ isset($this->response->body->Code, $this->response->body->Message)) {
1305
+ $this->response->error = array(
1306
+ 'code' => (string)$this->response->body->Code,
1307
+ 'message' => (string)$this->response->body->Message
1308
+ );
1309
+ if (isset($this->response->body->Resource))
1310
+ $this->response->error['resource'] = (string)$this->response->body->Resource;
1311
+ unset($this->response->body);
1312
+ }
1313
+ }
1314
+
1315
+ // Clean up file resources
1316
+ if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);
1317
+
1318
+ return $this->response;
1319
+ }
1320
+
1321
+
1322
+ /**
1323
+ * CURL write callback
1324
+ *
1325
+ * @param resource &$curl CURL resource
1326
+ * @param string &$data Data
1327
+ * @return integer
1328
+ */
1329
+ private function __responseWriteCallback(&$curl, &$data) {
1330
+ if ($this->response->code == 200 && $this->fp !== false)
1331
+ return fwrite($this->fp, $data);
1332
+ else
1333
+ $this->response->body .= $data;
1334
+ return strlen($data);
1335
+ }
1336
+
1337
+
1338
+ /**
1339
+ * CURL header callback
1340
+ *
1341
+ * @param resource &$curl CURL resource
1342
+ * @param string &$data Data
1343
+ * @return integer
1344
+ */
1345
+ private function __responseHeaderCallback(&$curl, &$data) {
1346
+ if (($strlen = strlen($data)) <= 2) return $strlen;
1347
+ if (substr($data, 0, 4) == 'HTTP')
1348
+ $this->response->code = (int)substr($data, 9, 3);
1349
+ else {
1350
+ list($header, $value) = explode(': ', trim($data), 2);
1351
+ if ($header == 'Last-Modified')
1352
+ $this->response->headers['time'] = strtotime($value);
1353
+ elseif ($header == 'Content-Length')
1354
+ $this->response->headers['size'] = (int)$value;
1355
+ elseif ($header == 'Content-Type')
1356
+ $this->response->headers['type'] = $value;
1357
+ elseif ($header == 'ETag')
1358
+ $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
1359
+ elseif (preg_match('/^x-amz-meta-.*$/', $header))
1360
+ $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value;
1361
+ }
1362
+ return $strlen;
1363
+ }
1364
+
1365
+ }
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
+ ?>
includes/updraft-restorer.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ show_message($this->strings['moving_old']);
38
+ if ( !$wp_filesystem->move($wp_dir . "wp-content/$type", $wp_dir . "wp-content/$type-old", true) ) {
39
+ return new WP_Error('old_move_failed', $this->strings['old_move_failed']);
40
+ }
41
+
42
+ show_message($this->strings['moving_backup']);
43
+ if ( !$wp_filesystem->move($working_dir . "/$type", $wp_dir . "wp-content/$type", true) ) {
44
+ return new WP_Error('new_move_failed', $this->strings['new_move_failed']);
45
+ }
46
+
47
+ show_message($this->strings['cleaning_up']);
48
+ if ( !$wp_filesystem->delete($working_dir) ) {
49
+ return new WP_Error('delete_failed', $this->strings['delete_failed']);
50
+ }
51
+
52
+ switch($type) {
53
+ case 'uploads':
54
+ $wp_filesystem->chmod($wp_dir . "wp-content/$type", 0777, true);
55
+ break;
56
+ default:
57
+ $wp_filesystem->chmod($wp_dir . "wp-content/$type", FS_CHMOD_DIR);
58
+ }
59
+ }
60
+
61
+ }
62
+ ?>
readme.txt ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === UpdraftPlus ===
2
+ Contributors: David Anderson
3
+ Tags: backup, restore, database, cloud, amazon, s3, ftp, cloud
4
+ Requires at least: 3.2
5
+ Tested up to: 3.3.2
6
+ Stable tag: 0.7.4
7
+ License: GPLv2 or later
8
+
9
+ == Description ==
10
+
11
+ UpdraftPlus simplifies backups (and restoration) for your blog. Backup into the cloud (S3, FTP, and email) and restore with a single click.
12
+
13
+ == Installation ==
14
+
15
+ Standard WordPress plugin installation:
16
+
17
+ 1. Upload updraftplus/ into wp-content/plugins/ (or use the built-in installers)
18
+ 2. Activate the plugin via the 'Plugins' menu.
19
+ 3. Go to the 'UpdraftPlus' option under settings.
20
+ 4. Follow the instructions.
21
+
22
+ == Frequently Asked Questions ==
23
+
24
+ = How is this better than the original Updraft? =
25
+
26
+ You can check the changelog for changes; but the original Updraft, before I forked it, had two 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, 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.
27
+
28
+ = I like automating WordPress, and using the command-line. Please advertise to me. =
29
+
30
+ That's very good of you, thank you. You are looking for WordShell, <a href="http://wordshell.net">http://wordshell.net</a>.
31
+
32
+ = I encrypted my database - how do I decrypt it? =
33
+
34
+ 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.
35
+
36
+ = I lost my encryption key - what can I do? =
37
+
38
+ 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 tremendous price.
39
+
40
+ = I found a bug. What do I do? =
41
+
42
+ 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 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.
43
+
44
+ == Upgrade Notice ==
45
+ New fork of Updraft, fixing some serious bugs and adding new features
46
+
47
+ == Changelog ==
48
+
49
+ = 0.7.4 - 05/21/2012 =
50
+ * Removed CloudFront method; I have no way of testing this
51
+ * Backup all tables found in the database that have this site's table prefix
52
+ * If encryption fails, then abort (don't revert to not encrypting)
53
+ * Added ability to decrypt encrypted database backups
54
+ * Added ability to opt out of backing up each file group
55
+ * Now adds database character set, the lack of which before made database backups unusable without modifications
56
+ * Version number bump to make sure that this is an improvement on the original Updraft, and is now tried and tested
57
+
58
+ = 0.1.3 - 01/16/2012 =
59
+ * Force backup of all tables found in database (vanilla Updraft only backed up WP core tables)
60
+ * Tweak notification email to include site name
61
+
62
+ = 0.1 - 08/10/2011 =
63
+
64
+ * A fork of Updraft 0.6.1 by Paul Kehrer with the following improvements
65
+ * Replaced deprecated function calls (in WordPress 3.2.1)
66
+ * Removed all warnings from basic admin page with WP_DEBUG on
67
+ * Implemented encrypted backup (but not yet automatic restoration) on database
68
+ * Some de-uglification of admin interface
69
+
70
+
71
+ == Screenshots ==
72
+
73
+ 1. Configuration page
74
+
75
+
76
+ == License ==
77
+
78
+ Portions copyright 2011-2 David Anderson
79
+ Portions copyright 2010 Paul Kehrer
80
+
81
+ This program is free software; you can redistribute it and/or modify
82
+ it under the terms of the GNU General Public License as published by
83
+ the Free Software Foundation; either version 2 of the License, or
84
+ (at your option) any later version.
85
+
86
+ This program is distributed in the hope that it will be useful,
87
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
88
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
89
+ GNU General Public License for more details.
90
+
91
+ You should have received a copy of the GNU General Public License
92
+ along with this program; if not, write to the Free Software
93
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
94
+
screenshot-1.png ADDED
Binary file
updraftplus.php ADDED
@@ -0,0 +1,1501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: UpdraftPlus - Backup/Restore
4
+ Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5
+ Description: UpdraftPlus - Backup/Restore is a plugin designed to back up your WordPress blog. Uploads, themes, plugins, and your DB can be backed up to Amazon S3, sent to an FTP server, or even emailed to you on a scheduled basis.
6
+ Author: David Anderson.
7
+ Version: 0.7.4
8
+ Author URI: http://wordshell.net
9
+ */
10
+
11
+ //TODO:
12
+ //Struggles with large uploads - runs out of time before finishing. Break into chunks? Resume download on later run? (Add a new scheduled event to check on progress? Separate the upload from the creation?). Add in some logging (in a .php file that exists first).
13
+ //More logging
14
+ //improve error reporting. s3 and dir backup have decent reporting now, but not sure i know what to do from here
15
+ //better implementation of retain. one that isn't dependent on being inside the cloud_backup method
16
+ //list backups that aren't tracked (helps with double backup problem)
17
+ //refactor db backup methods a bit. give full credit to wp-db-backup
18
+ //investigate $php_errormsg further
19
+ //pretty up return messages in admin area
20
+ //check s3/ftp download
21
+ //allow upload of backup files too. (specify 1-4 files to restore)
22
+ //Add back donate link in readme.txt header. Donate link: URL
23
+ //user permissions for WP users if ( function_exists('is_site_admin') && ! is_site_admin() ) around backups?
24
+
25
+ /* More TODO:
26
+ Are all directories in wp-content covered? No; only plugins, themes, content. We should check for others and allow the user the chance to choose which ones he wants
27
+ Add turn-off-foreign-key-checks stuff into mysql dump (does WP even use these?)
28
+ Put DB and file backups onto separate schedules
29
+ Use only one entry in WP options database
30
+ Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
31
+ More verbose debug reports, send debug report in the email
32
+ */
33
+
34
+ /* Portions copyright 2010 Paul Kehrer
35
+ Portions copyright 2011-12 David Anderson
36
+
37
+ This program is free software; you can redistribute it and/or modify
38
+ it under the terms of the GNU General Public License as published by
39
+ the Free Software Foundation; either version 2 of the License, or
40
+ (at your option) any later version.
41
+
42
+ This program is distributed in the hope that it will be useful,
43
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
44
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45
+ GNU General Public License for more details.
46
+
47
+ You should have received a copy of the GNU General Public License
48
+ along with this program; if not, write to the Free Software
49
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
50
+ */
51
+ // TODO: Note this might *lower* the limit - should check first.
52
+
53
+ @set_time_limit(900); //15 minutes max. i'm not sure how long a really big blog could take to back up?
54
+
55
+ $updraft = new UpdraftPlus();
56
+
57
+ if(!$updraft->memory_check(192)) {
58
+ # TODO: Better solution is to split the backup set into manageable chunks based on this limit
59
+ @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
60
+ }
61
+
62
+ class UpdraftPlus {
63
+
64
+ var $version = '0.7.4';
65
+
66
+ var $dbhandle;
67
+ var $errors = array();
68
+ var $nonce;
69
+ var $backup_time;
70
+
71
+ function __construct() {
72
+ // Initialisation actions
73
+ # Create admin page
74
+ add_action('admin_menu', array($this,'add_admin_pages'));
75
+ add_action('admin_init', array($this,'admin_init'));
76
+ add_action('updraft_backup', array($this,'backup'));
77
+ add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
78
+ add_filter('cron_schedules', array($this,'modify_cron_schedules'));
79
+ add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
80
+ }
81
+
82
+ # Adds the settings link under the plugin on the plugin screen.
83
+ function plugin_action_links($links, $file) {
84
+ if ($file == plugin_basename(__FILE__)){
85
+ $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraft-backuprestore.php">'.__("Settings", "wp-updates-notifier").'</a>';
86
+ array_unshift($links, $settings_link);
87
+ }
88
+ return $links;
89
+ }
90
+
91
+ function backup_time_nonce() {
92
+ $this->backup_time = time();
93
+ $this->nonce = substr(md5(time().rand()),20);
94
+ }
95
+
96
+ //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.
97
+ function backup() {
98
+ //generate backup information
99
+ $this->backup_time_nonce();
100
+
101
+ //backup directories and return a numerically indexed array of file paths to the backup files
102
+ $backup_array = $this->backup_dirs();
103
+ //backup DB and return string of file path
104
+ $db_backup = $this->backup_db();
105
+ //add db path to rest of files
106
+ if(is_array($backup_array)) { $backup_array['db'] = $db_backup; }
107
+ //save this to our history so we can track backups for the retain feature
108
+ $this->save_backup_history($backup_array);
109
+
110
+ //cloud operations (S3,FTP,email,nothing)
111
+ //this also calls the retain feature at the end (done in this method to reuse existing cloud connections)
112
+ if(is_array($backup_array) && count($backup_array) >0) {
113
+ $this->cloud_backup($backup_array);
114
+ }
115
+ //delete local files if the pref is set
116
+ foreach($backup_array as $file) {
117
+ $this->delete_local($file);
118
+ }
119
+
120
+ //save the last backup info, including errors, if any
121
+ $this->save_last_backup($backup_array);
122
+
123
+ if(get_option('updraft_email') != "" && get_option('updraft_service') != 'email') {
124
+ wp_mail(get_option('updraft_email'),'Backed up: '.get_bloginfo('name').' (UpdraftPlus) '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.");
125
+ }
126
+ }
127
+
128
+ function save_last_backup($backup_array) {
129
+ $success = (empty($this->errors))?1:0;
130
+ $last_backup = array('backup_time'=>$this->backup_time,'backup_array'=>$backup_array,'success'=>$success,'errors'=>$this->errors);
131
+ update_option('updraft_last_backup',$last_backup);
132
+ }
133
+
134
+ function cloud_backup($backup_array) {
135
+ switch(get_option('updraft_service')) {
136
+ case 's3':
137
+ if (count($backup_array) >0) { $this->s3_backup($backup_array); }
138
+ break;
139
+ case 'ftp':
140
+ if (count($backup_array) >0) { $this->ftp_backup($backup_array); }
141
+ break;
142
+ case 'email':
143
+ //files can easily get way too big for this...
144
+ foreach($backup_array as $type=>$file) {
145
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
146
+ 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));
147
+ }
148
+ //we don't break here so it goes and executes all the default behavior below as well. this gives us retain behavior for email
149
+ default:
150
+ /*retain behavior*/
151
+ $updraft_retain = get_option('updraft_retain');
152
+ $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
153
+ $backup_history = $this->get_backup_history();
154
+ while (count($backup_history) > $retain) {
155
+ $backup_to_delete = array_pop($backup_history);
156
+ foreach($backup_to_delete as $file) {
157
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
158
+ @unlink($fullpath); //delete it if it's locally available
159
+ }
160
+ }
161
+ update_option('updraft_backup_history',$backup_history);
162
+ /*retain behavior*/
163
+ break;
164
+ }
165
+ }
166
+
167
+ function s3_backup($backup_array) {
168
+ if(!class_exists('S3')) {
169
+ require_once(dirname(__FILE__).'/includes/S3.php');
170
+ }
171
+ $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
172
+ $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
173
+ if (@$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
174
+ foreach($backup_array as $file) {
175
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
176
+ if (!$s3->putObjectFile($fullpath, $bucket_name, $file)) {
177
+ $this->error("S3 Error: Failed to upload $fullpath. Error was ".$php_errormsg);
178
+ }
179
+ }
180
+ } else {
181
+ $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
182
+ }
183
+ /*retain behavior*/
184
+ $updraft_retain = get_option('updraft_retain');
185
+ $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
186
+ $backup_history = $this->get_backup_history();
187
+ while (count($backup_history) > $retain) {
188
+ $backup_to_delete = array_pop($backup_history);
189
+ foreach($backup_to_delete as $file) {
190
+ //if for some reason one of the backup files is an empty string let's skip it.
191
+ if($file == '') {
192
+ continue;
193
+ }
194
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
195
+ @unlink($fullpath); //delete it if it's locally available
196
+ if (!$s3->deleteObject($bucket_name, $file)) {
197
+ $this->error("S3 Error: Failed to delete object $file. Error was ".$php_errormsg);
198
+ }
199
+ }
200
+ }
201
+ update_option('updraft_backup_history',$backup_history);
202
+ /*retain behavior*/
203
+ }
204
+
205
+ function ftp_backup($backup_array) {
206
+ if( !class_exists('ftp_wrapper')) {
207
+ require_once(dirname(__FILE__).'/includes/ftp.class.php');
208
+ }
209
+ //handle SSL and errors at some point TODO
210
+ $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
211
+ $ftp->passive = true;
212
+ $ftp->connect();
213
+ //$ftp->make_dir(); we may need to recursively create dirs? TODO
214
+
215
+ $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
216
+ foreach($backup_array as $file) {
217
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
218
+ $ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY);
219
+ }
220
+
221
+ /*retain behavior*/
222
+ $updraft_retain = get_option('updraft_retain');
223
+ $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
224
+ $backup_history = $this->get_backup_history();
225
+ while (count($backup_history) > $retain) {
226
+ $backup_to_delete = array_pop($backup_history);
227
+ foreach($backup_to_delete as $file) {
228
+ //if for some reason one of the backup files is an empty string let's skip it.
229
+ if($file == '') {
230
+ continue;
231
+ }
232
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
233
+ @unlink($fullpath); //delete it if it's locally available
234
+ @$ftp->delete($ftp_remote_path.$file);
235
+ }
236
+ }
237
+ update_option('updraft_backup_history',$backup_history);
238
+ /*retain behavior*/
239
+ }
240
+
241
+ function delete_local($file) {
242
+ if(get_option('updraft_delete_local')) {
243
+ //need error checking so we don't delete what isn't successfully uploaded?
244
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
245
+ return unlink($fullpath);
246
+ }
247
+ return true;
248
+ }
249
+
250
+ function backup_dirs() {
251
+ if(!$this->backup_time) {
252
+ $this->backup_time_nonce();
253
+ }
254
+ $wp_themes_dir = WP_CONTENT_DIR.'/themes';
255
+ $wp_upload_dir = wp_upload_dir();
256
+ $wp_upload_dir = $wp_upload_dir['basedir'];
257
+ $wp_plugins_dir = WP_PLUGIN_DIR;
258
+ if(!class_exists('PclZip')) {
259
+ if (file_exists(ABSPATH.'/wp-admin/includes/class-pclzip.php')) {
260
+ require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
261
+ }
262
+ }
263
+ $updraft_dir = $this->backups_dir_location();
264
+ if(!is_writable($updraft_dir)) {
265
+ $this->error('Backup directory is not writable.','fatal');
266
+ }
267
+ //get the blog name and rip out all non-alphanumeric chars other than _
268
+ $blog_name = str_replace(' ','_',get_bloginfo());
269
+ $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
270
+ if(!$blog_name) {
271
+ $blog_name = 'non_alpha_name';
272
+ }
273
+
274
+ $backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
275
+
276
+ $backup_array = array();
277
+
278
+ # Plugins
279
+ if (get_option('updraft_include_plugins', true)) {
280
+ $plugins = new PclZip($backup_file_base.'-plugins.zip');
281
+ if (!$plugins->create($wp_plugins_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
282
+ $this->error('Could not create plugins zip. Error was '.$php_errmsg,'fatal');
283
+ }
284
+ $backup_array['plugins'] = basename($backup_file_base.'-plugins.zip');
285
+ }
286
+
287
+ # Themes
288
+ if (get_option('updraft_include_themes', true)) {
289
+ $themes = new PclZip($backup_file_base.'-themes.zip');
290
+ if (!$themes->create($wp_themes_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
291
+ $this->error('Could not create themes zip. Error was '.$php_errmsg,'fatal');
292
+ }
293
+ $backup_array['themes'] = basename($backup_file_base.'-themes.zip');
294
+ }
295
+
296
+ # Uploads
297
+ if (get_option('updraft_include_uploads', true)) {
298
+ $uploads = new PclZip($backup_file_base.'-uploads.zip');
299
+ if (!$uploads->create($wp_upload_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
300
+ $this->error('Could not create uploads zip. Error was '.$php_errmsg,'fatal');
301
+ }
302
+ $backup_array['uploads'] = basename($backup_file_base.'-uploads.zip');
303
+ }
304
+
305
+ return $backup_array;
306
+ }
307
+
308
+ function save_backup_history($backup_array) {
309
+ //this stores full paths right now. should probably concatenate with ABSPATH to make it easier to move blogs
310
+ $backup_history = get_option('updraft_backup_history');
311
+ $backup_history = (!is_array($backup_history))?array():$backup_history;
312
+ if(is_array($backup_array)) {
313
+ $backup_history[$this->backup_time] = $backup_array;
314
+ update_option('updraft_backup_history',$backup_history);
315
+ } else {
316
+ $this->error('Could not save backup history because we have no backup array. Backup probably failed.');
317
+ }
318
+ }
319
+
320
+ function get_backup_history() {
321
+ //$backup_history = get_option('updraft_backup_history');
322
+ //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
323
+ global $wpdb;
324
+ $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
325
+ if(is_array($backup_history)) {
326
+ krsort($backup_history); //reverse sort so earliest backup is last on the array. this way we can array_pop
327
+ } else {
328
+ $backup_history = array();
329
+ }
330
+ return $backup_history;
331
+ }
332
+
333
+
334
+ /*START OF WB-DB-BACKUP BLOCK*/
335
+
336
+ function backup_db() {
337
+ global $table_prefix, $wpdb;
338
+ if(!$this->backup_time) {
339
+ $this->backup_time_nonce();
340
+ }
341
+
342
+ $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
343
+ $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
344
+
345
+ $updraft_dir = $this->backups_dir_location();
346
+ //get the blog name and rip out all non-alphanumeric chars other than _
347
+ $blog_name = str_replace(' ','_',get_bloginfo());
348
+ $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
349
+ if(!$blog_name) {
350
+ $blog_name = 'non_alpha_name';
351
+ }
352
+
353
+ $backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
354
+ if (is_writable($updraft_dir)) {
355
+ if (function_exists('gzopen')) {
356
+ $this->dbhandle = @gzopen($backup_file_base.'-db.gz','w');
357
+ } else {
358
+ $this->dbhandle = @fopen($backup_file_base.'-db.gz', 'w');
359
+ }
360
+ if(!$this->dbhandle) {
361
+ //$this->error(__('Could not open the backup file for writing!','wp-db-backup'));
362
+ }
363
+ } else {
364
+ //$this->error(__('The backup directory is not writable!','wp-db-backup'));
365
+ }
366
+
367
+
368
+ //Begin new backup of MySql
369
+ $this->stow("# " . __('WordPress MySQL database backup','wp-db-backup') . "\n");
370
+ $this->stow("#\n");
371
+ $this->stow("# " . sprintf(__('Generated: %s','wp-db-backup'),date("l j. F Y H:i T")) . "\n");
372
+ $this->stow("# " . sprintf(__('Hostname: %s','wp-db-backup'),DB_HOST) . "\n");
373
+ $this->stow("# " . sprintf(__('Database: %s','wp-db-backup'),$this->backquote(DB_NAME)) . "\n");
374
+ $this->stow("# --------------------------------------------------------\n");
375
+
376
+
377
+ if (defined("DB_CHARSET")) {
378
+ $this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n");
379
+ $this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n");
380
+ $this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n");
381
+ $this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n");
382
+ }
383
+
384
+ foreach ($all_tables as $table) {
385
+ // Increase script execution time-limit to 15 min for every table.
386
+ if ( !ini_get('safe_mode')) @set_time_limit(15*60);
387
+ if ( strpos($table, $table_prefix) == 0 ) {
388
+ // Create the SQL statements
389
+ $this->stow("# --------------------------------------------------------\n");
390
+ $this->stow("# " . sprintf(__('Table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
391
+ $this->stow("# --------------------------------------------------------\n");
392
+ $this->backup_table($table);
393
+ } else {
394
+ $this->stow("# --------------------------------------------------------\n");
395
+ $this->stow("# " . sprintf(__('Skipping non-WP table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
396
+ $this->stow("# --------------------------------------------------------\n");
397
+ }
398
+ }
399
+
400
+ if (defined("DB_CHARSET")) {
401
+ $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n");
402
+ $this->stow("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n");
403
+ $this->stow("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
404
+ }
405
+
406
+ $this->close($this->dbhandle);
407
+
408
+ if (count($this->errors)) {
409
+ return false;
410
+ } else {
411
+ # Encrypt, if requested
412
+ $encryption = get_option('updraft_encryptionphrase');
413
+ if (strlen($encryption) > 0) {
414
+ $encryption_error = 0;
415
+ require_once(dirname(__FILE__).'/includes/Rijndael.php');
416
+ $rijndael = new Crypt_Rijndael();
417
+ $rijndael->setKey($encryption);
418
+ $in_handle = @fopen($backup_file_base.'-db.gz','r');
419
+ $buffer = "";
420
+ while (!feof ($in_handle)) {
421
+ $buffer .= fread($in_handle, 16384);
422
+ }
423
+ fclose ($in_handle);
424
+ $out_handle = @fopen($backup_file_base.'-db.gz.crypt','w');
425
+ if (!fwrite($out_handle, $rijndael->encrypt($buffer))) {$encryption_error = 1;}
426
+ fclose ($out_handle);
427
+ if (0 == $encryption_error) {
428
+ # Delete unencrypted file
429
+ @unlink($backup_file_base.'-db.gz');
430
+ return basename($backup_file_base.'-db.gz.crypt');
431
+ } else {
432
+ $this->error("Encryption error occurred when encrypting database. Aborted.");
433
+ }
434
+ } else {
435
+ return basename($backup_file_base.'-db.gz');
436
+ }
437
+ }
438
+
439
+ } //wp_db_backup
440
+
441
+ /**
442
+ * Taken partially from phpMyAdmin and partially from
443
+ * Alain Wolf, Zurich - Switzerland
444
+ * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
445
+ * Modified by Scott Merrill (http://www.skippy.net/)
446
+ * to use the WordPress $wpdb object
447
+ * @param string $table
448
+ * @param string $segment
449
+ * @return void
450
+ */
451
+ function backup_table($table, $segment = 'none') {
452
+ global $wpdb;
453
+
454
+ $table_structure = $wpdb->get_results("DESCRIBE $table");
455
+ if (! $table_structure) {
456
+ //$this->error(__('Error getting table details','wp-db-backup') . ": $table");
457
+ return false;
458
+ }
459
+
460
+ if(($segment == 'none') || ($segment == 0)) {
461
+ // Add SQL statement to drop existing table
462
+ $this->stow("\n\n");
463
+ $this->stow("#\n");
464
+ $this->stow("# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$this->backquote($table)) . "\n");
465
+ $this->stow("#\n");
466
+ $this->stow("\n");
467
+ $this->stow("DROP TABLE IF EXISTS " . $this->backquote($table) . ";\n");
468
+
469
+ // Table structure
470
+ // Comment in SQL-file
471
+ $this->stow("\n\n");
472
+ $this->stow("#\n");
473
+ $this->stow("# " . sprintf(__('Table structure of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
474
+ $this->stow("#\n");
475
+ $this->stow("\n");
476
+
477
+ $create_table = $wpdb->get_results("SHOW CREATE TABLE $table", ARRAY_N);
478
+ if (false === $create_table) {
479
+ $err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table);
480
+ //$this->error($err_msg);
481
+ $this->stow("#\n# $err_msg\n#\n");
482
+ }
483
+ $this->stow($create_table[0][1] . ' ;');
484
+
485
+ if (false === $table_structure) {
486
+ $err_msg = sprintf(__('Error getting table structure of %s','wp-db-backup'), $table);
487
+ //$this->error($err_msg);
488
+ $this->stow("#\n# $err_msg\n#\n");
489
+ }
490
+
491
+ // Comment in SQL-file
492
+ $this->stow("\n\n");
493
+ $this->stow("#\n");
494
+ $this->stow('# ' . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
495
+ $this->stow("#\n");
496
+ }
497
+
498
+ if(($segment == 'none') || ($segment >= 0)) {
499
+ $defs = array();
500
+ $ints = array();
501
+ foreach ($table_structure as $struct) {
502
+ if ( (0 === strpos($struct->Type, 'tinyint')) ||
503
+ (0 === strpos(strtolower($struct->Type), 'smallint')) ||
504
+ (0 === strpos(strtolower($struct->Type), 'mediumint')) ||
505
+ (0 === strpos(strtolower($struct->Type), 'int')) ||
506
+ (0 === strpos(strtolower($struct->Type), 'bigint')) ) {
507
+ $defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
508
+ $ints[strtolower($struct->Field)] = "1";
509
+ }
510
+ }
511
+
512
+
513
+ // Batch by $row_inc
514
+ if ( ! defined('ROWS_PER_SEGMENT') ) {
515
+ define('ROWS_PER_SEGMENT', 100);
516
+ }
517
+
518
+ if($segment == 'none') {
519
+ $row_start = 0;
520
+ $row_inc = ROWS_PER_SEGMENT;
521
+ } else {
522
+ $row_start = $segment * ROWS_PER_SEGMENT;
523
+ $row_inc = ROWS_PER_SEGMENT;
524
+ }
525
+ do {
526
+ // don't include extra stuff, if so requested
527
+ $excs = array('revisions' => 0, 'spam' => 1); //TODO, FIX THIS
528
+ $where = '';
529
+ if ( is_array($excs['spam'] ) && in_array($table, $excs['spam']) ) {
530
+ $where = ' WHERE comment_approved != "spam"';
531
+ } elseif ( is_array($excs['revisions'] ) && in_array($table, $excs['revisions']) ) {
532
+ $where = ' WHERE post_type != "revision"';
533
+ }
534
+
535
+ if ( !ini_get('safe_mode')) @set_time_limit(15*60);
536
+ $table_data = $wpdb->get_results("SELECT * FROM $table $where LIMIT {$row_start}, {$row_inc}", ARRAY_A);
537
+ $entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
538
+ // \x08\\x09, not required
539
+ $search = array("\x00", "\x0a", "\x0d", "\x1a");
540
+ $replace = array('\0', '\n', '\r', '\Z');
541
+ if($table_data) {
542
+ foreach ($table_data as $row) {
543
+ $values = array();
544
+ foreach ($row as $key => $value) {
545
+ if ($ints[strtolower($key)]) {
546
+ // make sure there are no blank spots in the insert syntax,
547
+ // yet try to avoid quotation marks around integers
548
+ $value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value;
549
+ $values[] = ( '' === $value ) ? "''" : $value;
550
+ } else {
551
+ $values[] = "'" . str_replace($search, $replace, $this->sql_addslashes($value)) . "'";
552
+ }
553
+ }
554
+ $this->stow(" \n" . $entries . implode(', ', $values) . ');');
555
+ }
556
+ $row_start += $row_inc;
557
+ }
558
+ } while((count($table_data) > 0) and ($segment=='none'));
559
+ }
560
+
561
+ if(($segment == 'none') || ($segment < 0)) {
562
+ // Create footer/closing comment in SQL-file
563
+ $this->stow("\n");
564
+ $this->stow("#\n");
565
+ $this->stow("# " . sprintf(__('End of data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
566
+ $this->stow("# --------------------------------------------------------\n");
567
+ $this->stow("\n");
568
+ }
569
+ } // end backup_table()
570
+
571
+
572
+ function stow($query_line) {
573
+ if (function_exists('gzopen')) {
574
+ if(! @gzwrite($this->dbhandle, $query_line)) {
575
+ //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
576
+ }
577
+ } else {
578
+ if(false === @fwrite($this->dbhandle, $query_line)) {
579
+ //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
580
+ }
581
+ }
582
+ }
583
+
584
+
585
+ function close($handle) {
586
+ if (function_exists('gzopen')) {
587
+ gzclose($handle);
588
+ } else {
589
+ fclose($handle);
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Logs any error messages
595
+ * @param array $args
596
+ * @return bool
597
+ */
598
+ function error($error,$severity='') {
599
+ $this->errors[] = array('error'=>$error,'severity'=>$severity);
600
+ if ($severity == 'fatal') {
601
+ //do something...
602
+ }
603
+ return true;
604
+ }
605
+
606
+
607
+
608
+ /**
609
+ * Add backquotes to tables and db-names in
610
+ * SQL queries. Taken from phpMyAdmin.
611
+ */
612
+ function backquote($a_name) {
613
+ if (!empty($a_name) && $a_name != '*') {
614
+ if (is_array($a_name)) {
615
+ $result = array();
616
+ reset($a_name);
617
+ while(list($key, $val) = each($a_name))
618
+ $result[$key] = '`' . $val . '`';
619
+ return $result;
620
+ } else {
621
+ return '`' . $a_name . '`';
622
+ }
623
+ } else {
624
+ return $a_name;
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Better addslashes for SQL queries.
630
+ * Taken from phpMyAdmin.
631
+ */
632
+ function sql_addslashes($a_string = '', $is_like = false) {
633
+ if ($is_like) $a_string = str_replace('\\', '\\\\\\\\', $a_string);
634
+ else $a_string = str_replace('\\', '\\\\', $a_string);
635
+ return str_replace('\'', '\\\'', $a_string);
636
+ }
637
+
638
+ /*END OF WP-DB-BACKUP BLOCK */
639
+ /*END OF WP-DB-BACKUP BLOCK */
640
+ /*END OF WP-DB-BACKUP BLOCK */
641
+ /*END OF WP-DB-BACKUP BLOCK */
642
+ /*END OF WP-DB-BACKUP BLOCK */
643
+
644
+ /*
645
+ this function is both the backup scheduler and ostensibly a filter callback for saving the option.
646
+ it is called in the register_setting for the updraft_interval, which means when the admin settings
647
+ are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter)
648
+ so the option can be properly saved. this is an UGLY HACK and there must be a better way.
649
+ */
650
+ function schedule_backup($interval) {
651
+ //clear schedule and add new so we don't stack up scheduled backups
652
+ wp_clear_scheduled_hook('updraft_backup');
653
+ switch($interval) {
654
+ case 'daily':
655
+ case 'weekly':
656
+ case 'monthly':
657
+ wp_schedule_event(time()+300, $interval, 'updraft_backup');
658
+ break;
659
+ }
660
+ return wp_filter_nohtml_kses($interval);
661
+ }
662
+
663
+ //wp-cron only has hourly, daily and twicedaily, so we need to add weekly and monthly.
664
+ function modify_cron_schedules($schedules) {
665
+ $schedules['weekly'] = array(
666
+ 'interval' => 604800,
667
+ 'display' => 'Once Weekly'
668
+ );
669
+ $schedules['monthly'] = array(
670
+ 'interval' => 2592000,
671
+ 'display' => 'Once Monthly'
672
+ );
673
+ return $schedules;
674
+ }
675
+
676
+ function backups_dir_location() {
677
+ $updraft_dir = untrailingslashit(get_option('updraft_dir'));
678
+ $default_backup_dir = WP_CONTENT_DIR.'/updraft';
679
+ //if the option isn't set, default it to /backups inside the upload dir
680
+ $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
681
+ //check for the existence of the dir and an enumeration preventer.
682
+ if(!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) {
683
+ @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
684
+ @file_put_contents($updraft_dir.'/index.html','Nothing to see here.');
685
+ @file_put_contents($updraft_dir.'/.htaccess','deny from all');
686
+ }
687
+ return $updraft_dir;
688
+ }
689
+
690
+ function updraft_download_backup() {
691
+ $type = $_POST['type'];
692
+ $timestamp = (int)$_POST['timestamp'];
693
+ $backup_history = $this->get_backup_history();
694
+ $file = $backup_history[$timestamp][$type];
695
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
696
+ if(!is_readable($fullpath)) {
697
+ //if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
698
+ $this->download_backup($file);
699
+ }
700
+ if(@is_readable($fullpath) && is_file($fullpath)) {
701
+ $len = filesize($fullpath);
702
+
703
+ $filearr = explode('.',$file);
704
+ //we've only got zip and gz...for now
705
+ $file_ext = array_pop($filearr);
706
+ if($file_ext == 'zip') {
707
+ header('Content-type: application/zip');
708
+ } else {
709
+ // This catches both when what was popped was 'crypt' (*-db.gz.crypt) and when it was 'gz' (unencrypted)
710
+ header('Content-type: application/x-gzip');
711
+ }
712
+ header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
713
+ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
714
+ header("Content-Length: $len;");
715
+ if ($file_ext == 'crypt') {
716
+ header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";");
717
+ } else {
718
+ header("Content-Disposition: attachment; filename=\"$file\";");
719
+ }
720
+ ob_end_flush();
721
+ if ($file_ext == 'crypt') {
722
+ $encryption = get_option('updraft_encryptionphrase');
723
+ if ($encryption == "") {
724
+ $this->error('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.');
725
+ } else {
726
+ require_once(dirname(__FILE__).'/includes/Rijndael.php');
727
+ $rijndael = new Crypt_Rijndael();
728
+ $rijndael->setKey($encryption);
729
+ $in_handle = fopen($fullpath,'r');
730
+ $ciphertext = "";
731
+ while (!feof ($in_handle)) {
732
+ $ciphertext .= fread($in_handle, 16384);
733
+ }
734
+ fclose ($in_handle);
735
+ print $rijndael->decrypt($ciphertext);
736
+ }
737
+ } else {
738
+ readfile($fullpath);
739
+ }
740
+ $this->delete_local($file);
741
+ 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')?
742
+ } else {
743
+ echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then S3 or FTP retrieval may have failed.';
744
+ }
745
+ }
746
+
747
+ function download_backup($file) {
748
+ switch(get_option('updraft_service')) {
749
+ case 's3':
750
+ $this->download_s3_backup($file);
751
+ break;
752
+ case 'ftp':
753
+ $this->download_ftp_backup($file);
754
+ break;
755
+ default:
756
+ $this->error('Automatic backup restoration is only available via S3, FTP, and local. Email and downloaded backup restoration must be performed manually.');
757
+ }
758
+ }
759
+
760
+ function download_s3_backup($file) {
761
+ if(!class_exists('S3')) {
762
+ require_once(dirname(__FILE__).'/includes/S3.php');
763
+ }
764
+ $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
765
+ $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
766
+ if (@$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
767
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
768
+ if (!$s3->getObject($bucket_name, $file, $fullpath)) {
769
+ $this->error("S3 Error: Failed to download $fullpath. Error was ".$php_errormsg);
770
+ }
771
+ } else {
772
+ $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
773
+ }
774
+ }
775
+
776
+ function download_ftp_backup($file) {
777
+ if( !class_exists('ftp_wrapper')) {
778
+ require_once(dirname(__FILE__).'/includes/ftp.class.php');
779
+ }
780
+ //handle SSL and errors at some point TODO
781
+ $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
782
+ $ftp->passive = true;
783
+ $ftp->connect();
784
+ //$ftp->make_dir(); we may need to recursively create dirs? TODO
785
+
786
+ $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
787
+ $fullpath = trailingslashit(get_option('updraft_dir')).$file;
788
+ $ftp->get($fullpath,$ftp_remote_path.$file,FTP_BINARY);
789
+ }
790
+
791
+ function restore_backup($timestamp) {
792
+ global $wp_filesystem;
793
+ $backup_history = get_option('updraft_backup_history');
794
+ if(!is_array($backup_history[$timestamp])) {
795
+ echo '<p>This backup does not exist in the backup history -- restoration aborted! timestamp: '.$timestamp.'</p><br/>';
796
+ return false;
797
+ }
798
+
799
+ $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_restore&backup_timestamp=$timestamp");
800
+ WP_Filesystem($credentials);
801
+ if ( $wp_filesystem->errors->get_error_code() ) {
802
+ foreach ( $wp_filesystem->errors->get_error_messages() as $message )
803
+ show_message($message);
804
+ exit;
805
+ }
806
+
807
+ //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?)
808
+ echo '<span style="font-weight:bold">Restoration Progress </span><div id="updraft-restore-progress">';
809
+
810
+ $updraft_dir = trailingslashit(get_option('updraft_dir'));
811
+ foreach($backup_history[$timestamp] as $type=>$file) {
812
+ $fullpath = $updraft_dir.$file;
813
+ if(!is_readable($fullpath) && $type != 'db') {
814
+ $this->download_backup($file);
815
+ }
816
+ if(is_readable($fullpath) && $type != 'db') {
817
+ if(!class_exists('WP_Upgrader')) {
818
+ require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
819
+ }
820
+ require_once('includes/updraft-restorer.php');
821
+ $restorer = new Updraft_Restorer();
822
+ $val = $restorer->restore_backup($fullpath,$type);
823
+ if(is_wp_error($val)) {
824
+ print_r($val);
825
+ echo '</div>'; //close the updraft_restore_progress div even if we error
826
+ return false;
827
+ }
828
+ }
829
+ }
830
+ echo '</div>'; //close the updraft_restore_progress div
831
+ if(ini_get('safe_mode')) {
832
+ 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/>";
833
+ return false;
834
+ }
835
+ return true;
836
+ }
837
+
838
+
839
+ //deletes the -old directories that are created when a backup is restored.
840
+ function delete_old_dirs() {
841
+ global $wp_filesystem;
842
+ $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_delete_old_dirs");
843
+ WP_Filesystem($credentials);
844
+ if ( $wp_filesystem->errors->get_error_code() ) {
845
+ foreach ( $wp_filesystem->errors->get_error_messages() as $message )
846
+ show_message($message);
847
+ exit;
848
+ }
849
+
850
+ $to_delete = array('themes-old','plugins-old','uploads-old');
851
+
852
+ foreach($to_delete as $name) {
853
+ //recursively delete
854
+ if(!$wp_filesystem->delete(WP_CONTENT_DIR.'/'.$name, true)) {
855
+ return false;
856
+ }
857
+ }
858
+ return true;
859
+ }
860
+
861
+ //scans the content dir to see if any -old dirs are present
862
+ function scan_old_dirs() {
863
+ $dirArr = scandir(WP_CONTENT_DIR);
864
+ foreach($dirArr as $dir) {
865
+ if(strpos($dir,'-old') !== false) {
866
+ return true;
867
+ }
868
+ }
869
+ return false;
870
+ }
871
+
872
+
873
+ function retain_range($input) {
874
+ $input = (int)$input;
875
+ if($input > 0 && $input < 3650) {
876
+ return $input;
877
+ } else {
878
+ return 1;
879
+ }
880
+ }
881
+
882
+ function create_backup_dir() {
883
+ global $wp_filesystem;
884
+ $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_create_backup_dir");
885
+ WP_Filesystem($credentials);
886
+ if ( $wp_filesystem->errors->get_error_code() ) {
887
+ foreach ( $wp_filesystem->errors->get_error_messages() as $message )
888
+ show_message($message);
889
+ exit;
890
+ }
891
+
892
+ $updraft_dir = untrailingslashit(get_option('updraft_dir'));
893
+ $default_backup_dir = WP_CONTENT_DIR.'/updraft';
894
+ $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
895
+
896
+ //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...)
897
+ if(!$wp_filesystem->mkdir($updraft_dir, 0777)) {
898
+ return false;
899
+ }
900
+ return true;
901
+ }
902
+
903
+
904
+ function memory_check_current() {
905
+ # Returns in megabytes
906
+ $memory_limit = ini_get('memory_limit');
907
+ $memory_unit = $memory_limit[strlen($memory_limit)-1];
908
+ $memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
909
+ switch($memory_unit) {
910
+ case 'K':
911
+ $memory_limit = $memory_limit/1024;
912
+ break;
913
+ case 'G':
914
+ $memory_limit = $memory_limit*1024;
915
+ break;
916
+ case 'M':
917
+ //assumed size, no change needed
918
+ break;
919
+ }
920
+ return $memory_limit;
921
+ }
922
+
923
+ function memory_check($memory) {
924
+ $memory_limit = $this->memory_check_current();
925
+ return ($memory_limit >= $memory)?true:false;
926
+ }
927
+
928
+ function execution_time_check($time) {
929
+ return (ini_get('max_execution_time') >= $time)?true:false;
930
+ }
931
+
932
+ function admin_init() {
933
+ if(get_option('updraft_debug_mode')) {
934
+ ini_set('display_errors',1);
935
+ error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
936
+ ini_set('track_errors',1);
937
+ }
938
+ wp_enqueue_script('jquery');
939
+ register_setting( 'updraft-options-group', 'updraft_interval', array($this,'schedule_backup') );
940
+ register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
941
+ register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
942
+ register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
943
+ register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
944
+ register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
945
+ register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
946
+ register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
947
+ register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
948
+ register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
949
+ register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
950
+ register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
951
+ register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
952
+ register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
953
+ register_setting( 'updraft-options-group', 'updraft_debug_mode', 'absint' );
954
+ register_setting( 'updraft-options-group', 'updraft_include_plugins', 'absint' );
955
+ register_setting( 'updraft-options-group', 'updraft_include_themes', 'absint' );
956
+ register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
957
+
958
+ if (current_user_can('manage_options')) {
959
+ $updraft_dir = $this->backups_dir_location();
960
+ if(strpos($updraft_dir,WP_CONTENT_DIR) !== false) {
961
+ $relative_dir = str_replace(WP_CONTENT_DIR,'',$updraft_dir);
962
+ $possible_updraft_url = WP_CONTENT_URL.$relative_dir;
963
+ $resp = wp_remote_request($possible_updraft_url, array('timeout' => 15));
964
+ if ( is_wp_error($resp) ) {
965
+ add_action('admin_notices', array($this,'show_admin_warning_accessible_unknownresult') );
966
+ } else {
967
+ if(strpos($resp['response']['code'],'403') === false) {
968
+ add_action('admin_notices', array($this,'show_admin_warning_accessible') );
969
+ }
970
+ }
971
+ if (isset($dir_protection_info)) {
972
+ }
973
+ }
974
+ }
975
+ }
976
+
977
+
978
+ function add_admin_pages() {
979
+ add_submenu_page('options-general.php', "UpdraftPlus", "UpdraftPlus", "manage_options", "updraft-backuprestore.php",
980
+ array($this,"settings_output"));
981
+ }
982
+
983
+ function wordshell_random_advert() {
984
+ if (rand(0,1) == 0) {
985
+ return 'Like automating WordPress operations? Use the CLI? <a href="http://wordshell.net">You will love WordShell</a> - saves time and money fast.';
986
+ } else {
987
+ return '<a href="http://wordshell.net">Check out WordShell</a> - manage WordPress from the command line - huge time-saver';
988
+ }
989
+ }
990
+
991
+ function settings_output() {
992
+
993
+ $ws_advert = $this->wordshell_random_advert();
994
+ echo <<<ENDHERE
995
+ <div class="updated fade" style="font-size:140%; padding:14px;">${ws_advert}</div>
996
+ ENDHERE;
997
+
998
+ /*
999
+ we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
1000
+ for the WP_Filesystem. to do this WP outputs a form that we can't insert variables into (apparently). So the values are
1001
+ passed back in as GET parameters. REQUEST covers both GET and POST so this weird logic works.
1002
+ */
1003
+ if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_restore' && isset($_REQUEST['backup_timestamp'])) {
1004
+ $backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
1005
+ if(empty($this->errors) && $backup_success == true) {
1006
+ echo '<p>Restore successful!</p><br/>';
1007
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php&updraft_restore_success=true">Return to Updraft Configuration</a>.';
1008
+ return;
1009
+ } else {
1010
+ echo '<p>Restore failed...</p><br/>';
1011
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1012
+ return;
1013
+ }
1014
+ //uncomment the below once i figure out how i want the flow of a restoration to work.
1015
+ //echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1016
+ }
1017
+ $deleted_old_dirs = false;
1018
+ if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
1019
+ if($this->delete_old_dirs()) {
1020
+ $deleted_old_dirs = true;
1021
+ } else {
1022
+ echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
1023
+ }
1024
+ echo '<p>Old directories successfully removed.</p><br/>';
1025
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1026
+ return;
1027
+ }
1028
+
1029
+ if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
1030
+ if(!$this->create_backup_dir()) {
1031
+ echo '<p>Backup directory could not be created...</p><br/>';
1032
+ }
1033
+ echo '<p>Backup directory successfully created.</p><br/>';
1034
+ echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1035
+ return;
1036
+ }
1037
+
1038
+ if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
1039
+ wp_schedule_single_event(time()+3, 'updraft_backup');
1040
+ }
1041
+ if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') {
1042
+ $this->backup();
1043
+ }
1044
+ if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') {
1045
+ $this->backup_db();
1046
+ }
1047
+
1048
+ ?>
1049
+ <div class="wrap">
1050
+ <h2>UpdraftPlus - Backup/Restore</h2>
1051
+
1052
+ Version: <b><?php echo $this->version; ?></b><br />
1053
+ 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> )
1054
+ <br />
1055
+ Based on 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> )
1056
+ <br />
1057
+ <?php
1058
+ if(isset($_GET['updraft_restore_success'])) {
1059
+ 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>";
1060
+ }
1061
+ if($deleted_old_dirs) {
1062
+ echo "<div style=\"color:blue\">Old directories successfully deleted.</div>";
1063
+ }
1064
+ if(!$this->memory_check(96)) {?>
1065
+ <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. Current limit is: <?php echo $this->memory_check_current(); ?> Mb</div>
1066
+ <?php
1067
+ }
1068
+ if(!$this->execution_time_check(300)) {?>
1069
+ <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>
1070
+ <?php
1071
+ }
1072
+
1073
+ if($this->scan_old_dirs()) {?>
1074
+ <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>
1075
+ <form method="post" action="<?php echo remove_query_arg(array('updraft_restore_success','action')) ?>">
1076
+ <input type="hidden" name="action" value="updraft_delete_old_dirs" />
1077
+ <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.'))" />
1078
+ </form>
1079
+ <?php
1080
+ }
1081
+ if(!empty($this->errors)) {
1082
+ foreach($this->errors as $error) {
1083
+ //ignoring severity here right now
1084
+ echo '<div style="color:red">'.$error['error'].'</div>';
1085
+ }
1086
+ }
1087
+ ?>
1088
+ <table class="form-table" style="float:left;width:475px">
1089
+ <tr>
1090
+ <?php
1091
+ $next_scheduled_backup = wp_next_scheduled('updraft_backup');
1092
+ if($next_scheduled_backup) {
1093
+ $next_scheduled_backup = date('D, F j, Y H:i T',$next_scheduled_backup);
1094
+ } else {
1095
+ $next_scheduled_backup = 'No backups are scheduled at this time.';
1096
+ }
1097
+ $current_time = date('D, F j, Y H:i T',time());
1098
+ $updraft_last_backup = get_option('updraft_last_backup');
1099
+ if($updraft_last_backup) {
1100
+ if($updraft_last_backup['success']) {
1101
+ $last_backup = date('D, F j, Y H:i T',$updraft_last_backup['backup_time']);
1102
+ $last_backup_color = 'green';
1103
+ } else {
1104
+ $last_backup = print_r($updraft_last_backup['errors'],true);
1105
+ $last_backup_color = 'red';
1106
+ }
1107
+ } else {
1108
+ $last_backup = 'No backup has been completed.';
1109
+ $last_backup_color = 'blue';
1110
+ }
1111
+
1112
+ $updraft_dir = $this->backups_dir_location();
1113
+ if(is_writable($updraft_dir)) {
1114
+ $dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
1115
+ $backup_disabled = "";
1116
+ } else {
1117
+ $backup_disabled = 'disabled="disabled"';
1118
+ $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraft-backuprestore.php&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>';
1119
+ }
1120
+ ?>
1121
+ <th>Current Time:</th>
1122
+ <td style="color:blue"><?php echo $current_time?></td>
1123
+ </tr>
1124
+ <tr>
1125
+ <th>Next Scheduled Backup:</th>
1126
+ <td style="color:blue"><?php echo $next_scheduled_backup?></td>
1127
+ </tr>
1128
+ <tr>
1129
+ <th>Last Backup:</th>
1130
+ <td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
1131
+ </tr>
1132
+ </table>
1133
+ <div style="float:left;width:200px">
1134
+ <form method="post" action="">
1135
+ <input type="hidden" name="action" value="updraft_backup" />
1136
+ <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 blog.'))" /></p>
1137
+ </form>
1138
+ <div style="position:relative">
1139
+ <div style="position:absolute;top:0;left:0">
1140
+ <?php
1141
+ $backup_history = get_option('updraft_backup_history');
1142
+ $backup_history = (is_array($backup_history))?$backup_history:array();
1143
+ $restore_disabled = (count($backup_history) == 0) ? 'disabled="disabled"' : "";
1144
+ ?>
1145
+ <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')" />
1146
+ </div>
1147
+ <div style="display:none;position:absolute;top:0;left:0" id="backup-restore">
1148
+ <form method="post" action="">
1149
+ <b>Choose: </b>
1150
+ <select name="backup_timestamp" style="display:inline">
1151
+ <?php
1152
+ foreach($backup_history as $key=>$value) {
1153
+ echo "<option value='$key'>".date('Y-m-d G:i',$key)."</option>\n";
1154
+ }
1155
+ ?>
1156
+ </select>
1157
+
1158
+ <input type="hidden" name="action" value="updraft_restore" />
1159
+ <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 blog\'s themes, plugins, and uploads directories. DB restoration must be done separately at this time. Continue with the restoration process?'))" />
1160
+ </form>
1161
+ </div>
1162
+ </div>
1163
+ </div>
1164
+ <br style="clear:both" />
1165
+ <table class="form-table">
1166
+ <tr>
1167
+ <th>Download Backups</th>
1168
+ <td><a href="#" title="Click to see available backups" onclick="jQuery('.download-backups').toggle();return false;"><?php echo count($backup_history)?> available</a></td>
1169
+ </tr>
1170
+ <tr>
1171
+ <td></td><td class="download-backups" style="display:none">
1172
+ <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>
1173
+ <table>
1174
+ <?php
1175
+ foreach($backup_history as $key=>$value) {
1176
+ ?>
1177
+ <tr>
1178
+ <td><b><?php echo date('Y-m-d G:i',$key)?></b></td>
1179
+ <td>
1180
+ <?php if (isset($value['db'])) { ?>
1181
+ <form action="admin-ajax.php" method="post">
1182
+ <input type="hidden" name="action" value="updraft_download_backup" />
1183
+ <input type="hidden" name="type" value="db" />
1184
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
1185
+ <input type="submit" value="Database" />
1186
+ </form>
1187
+ <?php } else { echo "(No database in backup)"; } ?>
1188
+ </td>
1189
+ <td>
1190
+ <?php if (isset($value['plugins'])) { ?>
1191
+ <form action="admin-ajax.php" method="post">
1192
+ <input type="hidden" name="action" value="updraft_download_backup" />
1193
+ <input type="hidden" name="type" value="plugins" />
1194
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
1195
+ <input type="submit" value="Plugins" />
1196
+ </form>
1197
+ <?php } else { echo "(No plugins in backup)"; } ?>
1198
+ </td>
1199
+ <td>
1200
+ <?php if (isset($value['themes'])) { ?>
1201
+ <form action="admin-ajax.php" method="post">
1202
+ <input type="hidden" name="action" value="updraft_download_backup" />
1203
+ <input type="hidden" name="type" value="themes" />
1204
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
1205
+ <input type="submit" value="Themes" />
1206
+ </form>
1207
+ <?php } else { echo "(No themes in backup)"; } ?>
1208
+ </td>
1209
+ <td>
1210
+ <?php if (isset($value['uploads'])) { ?>
1211
+ <form action="admin-ajax.php" method="post">
1212
+ <input type="hidden" name="action" value="updraft_download_backup" />
1213
+ <input type="hidden" name="type" value="uploads" />
1214
+ <input type="hidden" name="timestamp" value="<?php echo $key?>" />
1215
+ <input type="submit" value="Uploads" />
1216
+ </form>
1217
+ <?php } else { echo "(No uploads in backup)"; } ?>
1218
+ </td>
1219
+ </tr>
1220
+ <?php }?>
1221
+ </table>
1222
+ </td>
1223
+ </tr>
1224
+ </table>
1225
+ <form method="post" action="options.php">
1226
+ <?php settings_fields('updraft-options-group'); ?>
1227
+ <table class="form-table">
1228
+ <tr>
1229
+ <th>Backup Directory:</th>
1230
+ <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo $updraft_dir ?>" /></td>
1231
+ </tr>
1232
+ <tr>
1233
+ <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>
1234
+ </tr>
1235
+ <tr>
1236
+ <th>Backup Intervals:</th>
1237
+ <td><select name="updraft_interval">
1238
+ <?php
1239
+ $intervals = array ("manual", "daily", "weekly", "monthly");
1240
+ foreach ($intervals as $ival) {
1241
+ echo "<option value=\"$ival\" ";
1242
+ if ($ival == get_option('updraft_interval')) { echo 'selected="selected"';}
1243
+ echo ">".ucfirst($ival)."</option>\n";
1244
+ }
1245
+ ?>
1246
+ </select></td>
1247
+ </tr>
1248
+ <tr class="backup-interval-description">
1249
+ <td></td><td>If you would like to automatically schedule backups, choose a schedule from the dropdown above. Backups will occur at the interval specified starting five minutes after the current time. If you choose manual you must click the &quot;Backup Now!&quot; button to cause a backup to occur.</td>
1250
+ </tr>
1251
+ <?php
1252
+ # The true (default value if non-existent) here has the effect of forcing a default of on.
1253
+ $include_themes = (get_option('updraft_include_themes',true)) ? 'checked="checked"' : "";
1254
+ $include_plugins = (get_option('updraft_include_plugins',true)) ? 'checked="checked"' : "";
1255
+ $include_uploads = (get_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
1256
+ ?>
1257
+ <tr>
1258
+ <th>Include in Backup:</th>
1259
+ <td>
1260
+ <input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br />
1261
+ <input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br />
1262
+ <input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br />
1263
+ 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. The database is always included.<br />(<a href="http://wordshell.net">Use WordShell</a> for automatic backup, version control and patching).<br /></td>
1264
+ </td>
1265
+ </tr>
1266
+ <tr>
1267
+ <th>Retain Backups:</th>
1268
+ <?php
1269
+ $updraft_retain = get_option('updraft_retain');
1270
+ $retain = ((int)$updraft_retain > 0)?get_option('updraft_retain'):1;
1271
+ ?>
1272
+ <td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
1273
+ </tr>
1274
+ <tr class="backup-retain-description">
1275
+ <td></td><td>By default only the most recent backup is retained. If you'd like to preserve more, specify the number here.</td>
1276
+ </tr>
1277
+ <tr>
1278
+ <th>Encryption phrase:</th>
1279
+ <?php
1280
+ $updraft_encryptionphrase = get_option('updraft_encryptionphrase');
1281
+ ?>
1282
+ <td><input type="text" name="updraft_encryptionphrase" value="<?php echo $updraft_encryptionphrase ?>" style="width:132px" /></td>
1283
+ </tr>
1284
+ <tr class="backup-crypt-description">
1285
+ <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>
1286
+ </tr>
1287
+
1288
+ <tr>
1289
+ <th>Remote backup:</th>
1290
+ <td><select name="updraft_service" id="updraft-service">
1291
+ <?php
1292
+ $delete_local = (get_option('updraft_delete_local')) ? 'checked="checked"' : "";
1293
+ $debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
1294
+
1295
+ $display_none = 'style="display:none"';
1296
+ $s3 = ""; $ftp = ""; $email = "";
1297
+ $email_display="";
1298
+ $display_email_complete = "";
1299
+ $set = 'selected="selected"';
1300
+ switch(get_option('updraft_service')) {
1301
+ case 's3':
1302
+ $s3 = $set;
1303
+ $ftp_display = $display_none;
1304
+ break;
1305
+ case 'ftp':
1306
+ $ftp = $set;
1307
+ $s3_display = $display_none;
1308
+ break;
1309
+ case 'email':
1310
+ $email = $set;
1311
+ $ftp_display = $display_none;
1312
+ $s3_display = $display_none;
1313
+ $display_email_complete = $display_none;
1314
+ break;
1315
+ default:
1316
+ $none = $set;
1317
+ $ftp_display = $display_none;
1318
+ $s3_display = $display_none;
1319
+ $display_delete_local = $display_none;
1320
+ break;
1321
+ }
1322
+ ?>
1323
+ <option value="none" <?php echo $none?>>None</option>
1324
+ <option value="s3" <?php echo $s3?>>Amazon S3</option>
1325
+ <option value="ftp" <?php echo $ftp?>>FTP</option>
1326
+ <option value="email" <?php echo $email?>>E-mail</option>
1327
+ </select></td>
1328
+ </tr>
1329
+ <tr class="backup-service-description">
1330
+ <td></td><td>Choose which backup method you would like to employ. Be aware that email servers tend to have strict file size limitations and it is possible you will not receive your backup emails (>10MB is a typical threshold). Select none if you do not wish to send your backups anywhere. <b>Not recommended.</b></td>
1331
+
1332
+ </tr>
1333
+ <tr class="s3" <?php echo $s3_display?>>
1334
+ <th>S3 access key:</th>
1335
+ <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_login" value="<?php echo get_option('updraft_s3_login') ?>" /></td>
1336
+ </tr>
1337
+ <tr class="s3" <?php echo $s3_display?>>
1338
+ <th>S3 secret key:</th>
1339
+ <td><input type="password" autocomplete="off" style="width:292px" name="updraft_s3_pass" value="<?php echo get_option('updraft_s3_pass'); ?>" /></td>
1340
+ </tr>
1341
+ <tr class="s3" <?php echo $s3_display?>>
1342
+ <th>S3 bucket:</th>
1343
+ <td><input type="text" style="width:292px" name="updraft_s3_remote_path" value="<?php echo get_option('updraft_s3_remote_path'); ?>" /></td>
1344
+ </tr>
1345
+ <tr class="s3" <?php echo $s3_display?>>
1346
+ <th></th>
1347
+ <td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) to use for storage. (Do not enter the s3:// prefix).</p></td>
1348
+ </tr>
1349
+ <tr class="ftp" <?php echo $ftp_display?>>
1350
+ <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Server:</a></th>
1351
+ <td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo get_option('updraft_server_address'); ?>" /></td>
1352
+ </tr>
1353
+ <tr class="ftp" <?php echo $ftp_display?>>
1354
+ <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Login:</a></th>
1355
+ <td><input type="text" autocomplete="off" name="updraft_ftp_login" value="<?php echo get_option('updraft_ftp_login') ?>" /></td>
1356
+ </tr>
1357
+ <tr class="ftp" <?php echo $ftp_display?>>
1358
+ <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Password:</a></th>
1359
+ <td><input type="password" autocomplete="off" style="width:260px" name="updraft_ftp_pass" value="<?php echo get_option('updraft_ftp_pass'); ?>" /></td>
1360
+ </tr>
1361
+ <tr class="ftp" <?php echo $ftp_display?>>
1362
+ <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">Remote Path:</a></th>
1363
+ <td><input type="text" style="width:260px" name="updraft_ftp_remote_path" value="<?php echo get_option('updraft_ftp_remote_path'); ?>" /></td>
1364
+ </tr>
1365
+ <tr class="ftp-description" style="display:none">
1366
+ <td colspan="2">An FTP remote path will look like '/home/backup/some/folder'</td>
1367
+ </tr>
1368
+ <tr class="email" <?php echo $email_display?>>
1369
+ <th>Email:</th>
1370
+ <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>
1371
+ </tr>
1372
+ <tr class="deletelocal s3 ftp email" <?php echo $display_delete_local?>>
1373
+ <th>Delete local backup:</th>
1374
+ <td><input type="checkbox" name="updraft_delete_local" value="1" <?php echo $delete_local; ?> /> <br />Check this to delete the local backup file after it has been sent off the server.</td>
1375
+ </tr>
1376
+ <tr>
1377
+ <th>Debug mode:</th>
1378
+ <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.</td>
1379
+ </tr>
1380
+ <tr>
1381
+ <td>
1382
+ <input type="hidden" name="action" value="update" />
1383
+ <input type="submit" class="button-primary" value="Save Changes" />
1384
+ </td>
1385
+ </tr>
1386
+ </table>
1387
+ </form>
1388
+ <?php
1389
+ if(get_option('updraft_debug_mode')) {
1390
+ ?>
1391
+ <div>
1392
+ <h3>Debug Information</h3>
1393
+ <?php
1394
+ $peak_memory_usage = memory_get_peak_usage(true)/1024/1024;
1395
+ $memory_usage = memory_get_usage(true)/1024/1024;
1396
+ echo 'Peak memory usage: '.$peak_memory_usage.' MB<br/>';
1397
+ echo 'Current memory usage: '.$memory_usage.' MB<br/>';
1398
+ echo 'PHP memory limit: '.ini_get('memory_limit').' <br/>';
1399
+ ?>
1400
+ <form method="post" action="">
1401
+ <input type="hidden" name="action" value="updraft_backup_debug_all" />
1402
+ <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>
1403
+ </form>
1404
+ <form method="post" action="">
1405
+ <input type="hidden" name="action" value="updraft_backup_debug_db" />
1406
+ <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>
1407
+ </form>
1408
+ </div>
1409
+ <?php } ?>
1410
+ <script type="text/javascript">
1411
+ jQuery(document).ready(function() {
1412
+ jQuery('#updraft-service').change(function() {
1413
+ switch(jQuery(this).val()) {
1414
+ case 'none':
1415
+ jQuery('.deletelocal,.s3,.ftp,.s3-description,.ftp-description').hide()
1416
+ jQuery('.email,.email-complete').show()
1417
+ break;
1418
+ case 's3':
1419
+ jQuery('.ftp,.ftp-description').hide()
1420
+ jQuery('.s3,.deletelocal,.email,.email-complete').show()
1421
+ break;
1422
+ case 'ftp':
1423
+ jQuery('.s3,.s3-description').hide()
1424
+ jQuery('.ftp,.deletelocal,.email,.email-complete').show()
1425
+ break;
1426
+ case 'email':
1427
+ jQuery('.s3,.ftp,.s3-description,.ftp-description,.email-complete').hide()
1428
+ jQuery('.email,.deletelocal').show()
1429
+ break;
1430
+ }
1431
+ })
1432
+ })
1433
+ jQuery(window).load(function() {
1434
+ //this is for hiding the restore progress at the top after it is done
1435
+ setTimeout('jQuery("#updraft-restore-progress").toggle(1000)',3000)
1436
+ jQuery('#updraft-restore-progress-toggle').click(function() {
1437
+ jQuery('#updraft-restore-progress').toggle(500)
1438
+ })
1439
+ })
1440
+ </script>
1441
+ <?php
1442
+ }
1443
+
1444
+ /*array2json provided by bin-co.com under BSD license*/
1445
+ function array2json($arr) {
1446
+ if(function_exists('json_encode')) return stripslashes(json_encode($arr)); //Latest versions of PHP already have this functionality.
1447
+ $parts = array();
1448
+ $is_list = false;
1449
+
1450
+ //Find out if the given array is a numerical array
1451
+ $keys = array_keys($arr);
1452
+ $max_length = count($arr)-1;
1453
+ if(($keys[0] == 0) and ($keys[$max_length] == $max_length)) {//See if the first key is 0 and last key is length - 1
1454
+ $is_list = true;
1455
+ for($i=0; $i<count($keys); $i++) { //See if each key correspondes to its position
1456
+ if($i != $keys[$i]) { //A key fails at position check.
1457
+ $is_list = false; //It is an associative array.
1458
+ break;
1459
+ }
1460
+ }
1461
+ }
1462
+
1463
+ foreach($arr as $key=>$value) {
1464
+ if(is_array($value)) { //Custom handling for arrays
1465
+ if($is_list) $parts[] = $this->array2json($value); /* :RECURSION: */
1466
+ else $parts[] = '"' . $key . '":' . $this->array2json($value); /* :RECURSION: */
1467
+ } else {
1468
+ $str = '';
1469
+ if(!$is_list) $str = '"' . $key . '":';
1470
+
1471
+ //Custom handling for multiple data types
1472
+ if(is_numeric($value)) $str .= $value; //Numbers
1473
+ elseif($value === false) $str .= 'false'; //The booleans
1474
+ elseif($value === true) $str .= 'true';
1475
+ else $str .= '"' . addslashes($value) . '"'; //All other things
1476
+ // :TODO: Is there any more datatype we should be in the lookout for? (Object?)
1477
+
1478
+ $parts[] = $str;
1479
+ }
1480
+ }
1481
+ $json = implode(',',$parts);
1482
+
1483
+ if($is_list) return '[' . $json . ']';//Return numerical JSON
1484
+ return '{' . $json . '}';//Return associative JSON
1485
+ }
1486
+
1487
+ function show_admin_warning($message) {
1488
+ echo '<div id="updraftmessage" class="updated fade">';
1489
+ echo "<p>$message</p></div>";
1490
+ }
1491
+ function show_admin_warning_accessible() {
1492
+ $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.");
1493
+ }
1494
+ function show_admin_warning_accessible_unknownresult() {
1495
+ $this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
1496
+ }
1497
+
1498
+
1499
+ }
1500
+
1501
+ ?>