Version Description
- 05/29/2012 =
- Implementation of a logging mechanism to allow easier debugging and development
Download this release
Release Info
Developer | DavidAnderson |
Plugin | UpdraftPlus WordPress Backup Plugin |
Version | 0.7.7 |
Comparing to | |
See all releases |
Code changes from version 0.7.4 to 0.7.7
- readme.txt +6 -3
- updraftplus.php +63 -16
readme.txt
CHANGED
@@ -3,7 +3,7 @@ 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.
|
7 |
License: GPLv2 or later
|
8 |
|
9 |
== Description ==
|
@@ -39,13 +39,16 @@ Nothing, probably. That's the point of an encryption key - people who don't have
|
|
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
|
43 |
|
44 |
== Upgrade Notice ==
|
45 |
-
|
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
|
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.7
|
7 |
License: GPLv2 or later
|
8 |
|
9 |
== Description ==
|
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 turn on debugging mode and send me the log if you can find it. 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 |
+
Added a logging mechanism for easier development
|
46 |
|
47 |
== Changelog ==
|
48 |
|
49 |
+
= 0.7.7 - 05/29/2012 =
|
50 |
+
* Implementation of a logging mechanism to allow easier debugging and development
|
51 |
+
|
52 |
= 0.7.4 - 05/21/2012 =
|
53 |
* Removed CloudFront method; I have no way of testing this
|
54 |
* Backup all tables found in the database that have this site's table prefix
|
updraftplus.php
CHANGED
@@ -2,13 +2,16 @@
|
|
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
|
6 |
Author: David Anderson.
|
7 |
-
Version: 0.7.
|
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
|
@@ -25,7 +28,6 @@ Author URI: http://wordshell.net
|
|
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
|
@@ -50,7 +52,7 @@ 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
|
54 |
|
55 |
$updraft = new UpdraftPlus();
|
56 |
|
@@ -61,11 +63,13 @@ if(!$updraft->memory_check(192)) {
|
|
61 |
|
62 |
class UpdraftPlus {
|
63 |
|
64 |
-
var $version = '0.7.
|
65 |
|
66 |
var $dbhandle;
|
67 |
var $errors = array();
|
68 |
var $nonce;
|
|
|
|
|
69 |
var $backup_time;
|
70 |
|
71 |
function __construct() {
|
@@ -92,37 +96,69 @@ class UpdraftPlus {
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
}
|
|
|
|
|
|
|
|
|
126 |
}
|
127 |
|
128 |
function save_last_backup($backup_array) {
|
@@ -134,12 +170,18 @@ class UpdraftPlus {
|
|
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;
|
@@ -276,6 +318,7 @@ class UpdraftPlus {
|
|
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)) {
|
@@ -285,6 +328,7 @@ class UpdraftPlus {
|
|
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)) {
|
@@ -294,6 +338,7 @@ class UpdraftPlus {
|
|
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)) {
|
@@ -306,7 +351,7 @@ class UpdraftPlus {
|
|
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
|
310 |
$backup_history = get_option('updraft_backup_history');
|
311 |
$backup_history = (!is_array($backup_history))?array():$backup_history;
|
312 |
if(is_array($backup_array)) {
|
@@ -645,7 +690,7 @@ class UpdraftPlus {
|
|
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.
|
649 |
*/
|
650 |
function schedule_backup($interval) {
|
651 |
//clear schedule and add new so we don't stack up scheduled backups
|
@@ -654,7 +699,7 @@ class UpdraftPlus {
|
|
654 |
case 'daily':
|
655 |
case 'weekly':
|
656 |
case 'monthly':
|
657 |
-
wp_schedule_event(time()+
|
658 |
break;
|
659 |
}
|
660 |
return wp_filter_nohtml_kses($interval);
|
@@ -980,17 +1025,19 @@ class UpdraftPlus {
|
|
980 |
array($this,"settings_output"));
|
981 |
}
|
982 |
|
983 |
-
function wordshell_random_advert() {
|
|
|
|
|
984 |
if (rand(0,1) == 0) {
|
985 |
-
return
|
986 |
} else {
|
987 |
-
return
|
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;
|
@@ -1133,7 +1180,7 @@ ENDHERE;
|
|
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
|
1137 |
</form>
|
1138 |
<div style="position:relative">
|
1139 |
<div style="position:absolute;top:0;left:0">
|
@@ -1156,7 +1203,7 @@ ENDHERE;
|
|
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
|
1160 |
</form>
|
1161 |
</div>
|
1162 |
</div>
|
@@ -1375,7 +1422,7 @@ ENDHERE;
|
|
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>
|
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 site. 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.7
|
8 |
Author URI: http://wordshell.net
|
9 |
*/
|
10 |
|
11 |
//TODO:
|
12 |
+
//Put DB and file backups onto separate schedules. If the option is set identically then do in one run, otherwise do separately.
|
13 |
+
//Add DropBox support
|
14 |
+
//Add more logging
|
15 |
//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).
|
16 |
//More logging
|
17 |
//improve error reporting. s3 and dir backup have decent reporting now, but not sure i know what to do from here
|
28 |
/* More TODO:
|
29 |
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
|
30 |
Add turn-off-foreign-key-checks stuff into mysql dump (does WP even use these?)
|
|
|
31 |
Use only one entry in WP options database
|
32 |
Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
|
33 |
More verbose debug reports, send debug report in the email
|
52 |
*/
|
53 |
// TODO: Note this might *lower* the limit - should check first.
|
54 |
|
55 |
+
@set_time_limit(900); //15 minutes max. i'm not sure how long a really big site could take to back up?
|
56 |
|
57 |
$updraft = new UpdraftPlus();
|
58 |
|
63 |
|
64 |
class UpdraftPlus {
|
65 |
|
66 |
+
var $version = '0.7.7';
|
67 |
|
68 |
var $dbhandle;
|
69 |
var $errors = array();
|
70 |
var $nonce;
|
71 |
+
var $logfile_name = "";
|
72 |
+
var $logfile_handle = false;
|
73 |
var $backup_time;
|
74 |
|
75 |
function __construct() {
|
96 |
$this->backup_time = time();
|
97 |
$this->nonce = substr(md5(time().rand()),20);
|
98 |
}
|
99 |
+
|
100 |
+
# Logs the given line, adding date stamp and newline
|
101 |
+
function log($line) {
|
102 |
+
if ($this->logfile_handle) {
|
103 |
+
fwrite($this->logfile_handle,date('r')." ".$line."\n");
|
104 |
+
}
|
105 |
+
}
|
106 |
|
107 |
//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.
|
108 |
function backup() {
|
109 |
//generate backup information
|
110 |
$this->backup_time_nonce();
|
111 |
|
112 |
+
//set log file name
|
113 |
+
$updraft_dir = $this->backups_dir_location();
|
114 |
+
$this->logfile_name = $updraft_dir. "/log." . $this->nonce . ".txt";
|
115 |
+
|
116 |
+
# Use append mode in case it already exists
|
117 |
+
$this->logfile_handle = fopen($this->logfile_name, 'a');
|
118 |
+
// Some information that may be helpful
|
119 |
+
global $wp_version;
|
120 |
+
$this->log("PHP version: ".phpversion()." WordPress version: ".$wp_version);
|
121 |
//backup directories and return a numerically indexed array of file paths to the backup files
|
122 |
+
$this->log("Beginning backup of directories");
|
123 |
$backup_array = $this->backup_dirs();
|
124 |
//backup DB and return string of file path
|
125 |
+
$this->log("Beginning backup of database");
|
126 |
$db_backup = $this->backup_db();
|
127 |
//add db path to rest of files
|
128 |
if(is_array($backup_array)) { $backup_array['db'] = $db_backup; }
|
129 |
//save this to our history so we can track backups for the retain feature
|
130 |
+
$this->log("Saving backup history");
|
131 |
$this->save_backup_history($backup_array);
|
132 |
|
133 |
//cloud operations (S3,FTP,email,nothing)
|
134 |
//this also calls the retain feature at the end (done in this method to reuse existing cloud connections)
|
135 |
if(is_array($backup_array) && count($backup_array) >0) {
|
136 |
+
$this->log("Beginning dispatch of backup to remote");
|
137 |
$this->cloud_backup($backup_array);
|
138 |
}
|
139 |
//delete local files if the pref is set
|
140 |
foreach($backup_array as $file) {
|
141 |
+
$this->log("Deleting local file: $file");
|
142 |
$this->delete_local($file);
|
143 |
}
|
144 |
|
145 |
//save the last backup info, including errors, if any
|
146 |
+
$this->log("Saving last backup information into WordPress db");
|
147 |
$this->save_last_backup($backup_array);
|
148 |
|
149 |
if(get_option('updraft_email') != "" && get_option('updraft_service') != 'email') {
|
150 |
+
$sendmail_to = get_option('updraft_email');
|
151 |
+
$this->log("Sending email report to: ".$sendmail_to);
|
152 |
+
$append_log = "";
|
153 |
+
if(get_option('updraft_debug_mode') && $this->logfile_name != "") {
|
154 |
+
$append_log .= "\r\nLog contents:\r\n".file_get_contents($this->logfile_name);
|
155 |
+
}
|
156 |
+
wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus) '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
|
157 |
}
|
158 |
+
|
159 |
+
// Close log file
|
160 |
+
close($this->logfile_handle);
|
161 |
+
if (!get_option('updraft_debug_mode')) { @unlink($this->logfile_name); }
|
162 |
}
|
163 |
|
164 |
function save_last_backup($backup_array) {
|
170 |
function cloud_backup($backup_array) {
|
171 |
switch(get_option('updraft_service')) {
|
172 |
case 's3':
|
173 |
+
@set_time_limit(900);
|
174 |
+
$this->log("Cloud backup: S3");
|
175 |
if (count($backup_array) >0) { $this->s3_backup($backup_array); }
|
176 |
break;
|
177 |
case 'ftp':
|
178 |
+
@set_time_limit(900);
|
179 |
+
$this->log("Cloud backup: FTP");
|
180 |
if (count($backup_array) >0) { $this->ftp_backup($backup_array); }
|
181 |
break;
|
182 |
case 'email':
|
183 |
+
@set_time_limit(900);
|
184 |
+
$this->log("Cloud backup: Email");
|
185 |
//files can easily get way too big for this...
|
186 |
foreach($backup_array as $type=>$file) {
|
187 |
$fullpath = trailingslashit(get_option('updraft_dir')).$file;
|
318 |
$backup_array = array();
|
319 |
|
320 |
# Plugins
|
321 |
+
@set_time_limit(900);
|
322 |
if (get_option('updraft_include_plugins', true)) {
|
323 |
$plugins = new PclZip($backup_file_base.'-plugins.zip');
|
324 |
if (!$plugins->create($wp_plugins_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
|
328 |
}
|
329 |
|
330 |
# Themes
|
331 |
+
@set_time_limit(900);
|
332 |
if (get_option('updraft_include_themes', true)) {
|
333 |
$themes = new PclZip($backup_file_base.'-themes.zip');
|
334 |
if (!$themes->create($wp_themes_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
|
338 |
}
|
339 |
|
340 |
# Uploads
|
341 |
+
@set_time_limit(900);
|
342 |
if (get_option('updraft_include_uploads', true)) {
|
343 |
$uploads = new PclZip($backup_file_base.'-uploads.zip');
|
344 |
if (!$uploads->create($wp_upload_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
|
351 |
}
|
352 |
|
353 |
function save_backup_history($backup_array) {
|
354 |
+
//this stores full paths right now. should probably concatenate with ABSPATH to make it easier to move sites
|
355 |
$backup_history = get_option('updraft_backup_history');
|
356 |
$backup_history = (!is_array($backup_history))?array():$backup_history;
|
357 |
if(is_array($backup_array)) {
|
690 |
this function is both the backup scheduler and ostensibly a filter callback for saving the option.
|
691 |
it is called in the register_setting for the updraft_interval, which means when the admin settings
|
692 |
are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter)
|
693 |
+
so the option can be properly saved.
|
694 |
*/
|
695 |
function schedule_backup($interval) {
|
696 |
//clear schedule and add new so we don't stack up scheduled backups
|
699 |
case 'daily':
|
700 |
case 'weekly':
|
701 |
case 'monthly':
|
702 |
+
wp_schedule_event(time()+30, $interval, 'updraft_backup');
|
703 |
break;
|
704 |
}
|
705 |
return wp_filter_nohtml_kses($interval);
|
1025 |
array($this,"settings_output"));
|
1026 |
}
|
1027 |
|
1028 |
+
function wordshell_random_advert($urls) {
|
1029 |
+
$url_start = ($urls) ? '<a href="http://wordshell.net">' : "";
|
1030 |
+
$url_end = ($urls) ? '</a>' : " (www.wordshell.net)";
|
1031 |
if (rand(0,1) == 0) {
|
1032 |
+
return "Like automating WordPress operations? Use the CLI? ${url_start}You will love WordShell${url_end} - saves time and money fast.";
|
1033 |
} else {
|
1034 |
+
return "${url_start}Check out WordShell${url_end} - manage WordPress from the command line - huge time-saver";
|
1035 |
}
|
1036 |
}
|
1037 |
|
1038 |
function settings_output() {
|
1039 |
|
1040 |
+
$ws_advert = $this->wordshell_random_advert(1);
|
1041 |
echo <<<ENDHERE
|
1042 |
<div class="updated fade" style="font-size:140%; padding:14px;">${ws_advert}</div>
|
1043 |
ENDHERE;
|
1180 |
<div style="float:left;width:200px">
|
1181 |
<form method="post" action="">
|
1182 |
<input type="hidden" name="action" value="updraft_backup" />
|
1183 |
+
<p><input type="submit" <?php echo $backup_disabled ?> class="button-primary" value="Backup Now!" style="padding-top:7px;padding-bottom:7px;font-size:24px !important" onclick="return(confirm('This will schedule a one time backup. To trigger the backup immediately you may need to load a page on your site.'))" /></p>
|
1184 |
</form>
|
1185 |
<div style="position:relative">
|
1186 |
<div style="position:absolute;top:0;left:0">
|
1203 |
</select>
|
1204 |
|
1205 |
<input type="hidden" name="action" value="updraft_restore" />
|
1206 |
+
<input type="submit" <?php echo $restore_disabled ?> class="button-primary" value="Restore Now!" style="padding-top:7px;margin-top:5px;padding-bottom:7px;font-size:24px !important" onclick="return(confirm('Restoring from backup will replace this site\'s themes, plugins, and uploads directories. DB restoration must be done separately at this time. Continue with the restoration process?'))" />
|
1207 |
</form>
|
1208 |
</div>
|
1209 |
</div>
|
1422 |
</tr>
|
1423 |
<tr>
|
1424 |
<th>Debug mode:</th>
|
1425 |
+
<td><input type="checkbox" name="updraft_debug_mode" value="1" <?php echo $debug_mode; ?> /> <br />Check this for more information, if something is going wrong. Will also drop a log file in your backup directory which you can examine.</td>
|
1426 |
</tr>
|
1427 |
<tr>
|
1428 |
<td>
|