-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathrestore_issue.php
194 lines (171 loc) · 5.19 KB
/
restore_issue.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
<?php
/**
* Generate SQL script to restore deleted Issues from a database backup.
*
* This helper program generates a SQL script to help recovering individual
* issues from a database backup, e.g. after accidental deletion.
*
* Usage instructions:
*
* 1. Restore the backup into a new, temporary database
*
* 2. Identify the ID(s) of the issue(s) to recover, e.g.
* ```sql
* SELECT b1.id
* FROM production.mantis_bug_table b1
* LEFT JOIN backup.mantis_bug_table b2 ON b2.id = b1.id
* WHERE b1.id IS NULL;
* ```
*
* 3. Set up a temporary MantisBT instance
* - Copy of the production MantisBT instance's directory to the location
* of your choice,
* - Update config_inc.php to point to the DB restored in 1.
*
* 4. Generate the recovery script
* - Copy this program to the root of the temporary MantisBT instance,
* - Populate the $g_bug_list variable below with the list of issue IDs
* identified in step 2,
* - if necessary, update $g_filename to set the path to where the script
* should be saved (by default, `restore.sql` in the current directory),
* - Save the modified program,
* - Run it from CLI (`php restore_issue.php`).
*
* 4. Review the generated SQL script.
*
* 5. Backup the target database.
*
* 5. Manually execute the script in the production database.
*
* 6. Delete the temporary MantisBT instance and drop the restored database.
*/
/**
* @global int[] $g_bug_list List of Issues to restore
*/
$g_bug_list = array(
);
/**
* @global string $g_filename Path where to save the generated SQL script.
*/
$g_filename = 'restore.sql';
# ----------------------------------------------------------------------------
# No edit below this line
#
echo "Generating restore script...\n";
if( !$g_bug_list ) {
echo "Update the '\$g_bug_list' array with the issues to restore\n";
exit( 1 );
}
echo "Issues to restore: " . implode( ', ', $g_bug_list ) . "\n";
if( file_exists( $g_filename ) ) {
/** @noinspection PhpComposerExtensionStubsInspection */
$t_reply = readline( "File '$g_filename' already exists. Overwrite ? " );
if( strtolower( $t_reply[0] ?? '' ) !== 'y' ) {
echo "Aborting." . PHP_EOL;
exit( 1 );
}
}
global $g_bypass_headers;
$g_bypass_headers = 1;
include 'core.php';
# List of tables to restore with corresponding key field for bug id
$t_tables = array(
'bug' => 'id',
'bug_text' => 'id', # requires special handling
'bug_file' => 'bug_id',
'bugnote' => 'bug_id',
'bugnote_text' => 'bug_id', # requires special handling
'bug_relationship' => 'source_bug_id', # requires special handling
'sponsorship' => 'bug_id',
'bug_revision' => 'bug_id',
'bug_history' => 'bug_id',
'bug_monitor' => 'bug_id',
'bug_tag' => 'bug_id',
'custom_field_string' => 'bug_id',
);
$t_has_attachments = false;
$t_file = fopen( $g_filename, 'w' );
fwrite( $t_file, "-- MantisBT Issue Restore script" . PHP_EOL );
fwrite( $t_file, "-- Generated by " . basename( __FILE__ )
. " on " . date( "c" ) . PHP_EOL
);
fwrite( $t_file, "-- Issues to restore: " . implode( ', ', $g_bug_list ) . PHP_EOL . PHP_EOL );
# Main loop
foreach( $t_tables as $t_table => $t_field ) {
# Build query to retrieve rows to recover
$t_query = 'SELECT t.* FROM {' . $t_table . '} t';
$t_where = '';
switch( $t_table ) {
case 'bug_text':
$t_query .= ' JOIN {bug} b ON b.bug_text_id = t.id';
$t_field = 'b.' . $t_field;
break;
case 'bugnote_text':
$t_query .= ' JOIN {bugnote} n ON n.bugnote_text_id = t.id';
$t_field = 'n.' . $t_field;
break;
case 'bug_relationship':
$t_where = ' OR ' . where_clause( 'destination_bug_id' );
break;
}
$t_query .= ' WHERE ' . where_clause( $t_field ) . $t_where;
$t_result = db_query( $t_query );
$t_row = db_fetch_array( $t_result );
# Nothing to recover for this table
if( !$t_row ) {
continue;
}
if( $t_table == 'bug_file' ) {
$t_has_attachments = true;
}
# Generate Insert statement SQL
fwrite( $t_file,
'INSERT INTO ' . db_get_table( $t_table )
. ' VALUES ' . PHP_EOL . insert_values( $t_row )
);
while( $t_row = db_fetch_array( $t_result ) ) {
fwrite( $t_file, ',' . PHP_EOL . insert_values( $t_row ) );
}
fwrite( $t_file, ';' . PHP_EOL . PHP_EOL );
}
fclose( $t_file );
echo "Restore script saved in: $g_filename\n";
if( $t_has_attachments ) {
echo "WARNING: Issues with attachments - restore these to the file system manually\n";
}
/**
* Generate where clause to select issue IDs.
*
* @param string $p_field Field name for Bug ID
*
* @return string SQL where clause
*/
function where_clause( string $p_field ): string
{
static $s_where;
if( !$s_where ) {
global $g_bug_list;
$s_where = ' IN (' . implode( ',', $g_bug_list ) . ')';
}
return $p_field . $s_where;
}
/**
* Returns row data ready for use in insert statement values.
*
* @param array $p_row Data row
*
* @return string Escaped list of values
*/
function insert_values( array $p_row ): string
{
array_walk( $p_row,
function( &$p_str ) {
global $g_db;
if( !is_numeric( $p_str ) ) {
$p_str = $g_db->qStr( $p_str );
}
echo $p_str, "\n";
}
);
return '(' . implode( ',', $p_row ) . ')';
}