Dump.php
[Pman.Admin] / Dump.php
1 <?php
2
3
4 /**
5  * This should allow you to dump a series of data, so it could be restored later...
6  * The format will be an SQL file...
7  *
8  * usage:
9  *    php index.php  Admin/Dump --table=Project --where="id=123"
10  *              --dump-dir=/directory_to_put_sql+shell files
11  *
12  *    outputs list of generated files.
13  *    
14  *     RESTORE FILES:
15  *      {DATE}.sql - the recreate sql including all dependancies, run with mysql DB -f  < ....
16  *      {DATE}.restore.sh - shell script to recreate files that where removed (excluding thumgs)
17  *      
18  *     BACKUP
19  *      {DATE}.copy.sh - shell script that backs up all the related files.
20  *      
21  *     DESTROY
22  *      {DATE}.delete.sql - the delete data sql.
23  *      {DATE}.delete.sh - shell script to delete the files related to these records
24  
25  *    
26  *  Basically it has to output all the records and their dependants. (parent and children)
27  *
28  *  Then when deleting it deletes record + children..
29  *
30  *
31  *  Each ouput is simple insert statement..
32  *
33  *
34  *  TODO - handle Images table (or similar) where we use tablename=XXXX, tid=.... etc..
35  *
36  *
37  *  INTERFACES :
38  *
39  *      DataObjects->archivePaths() - returns array ( sourcedirectory, remainder of path to dependant file )
40  *      DataObjects->listThumbs() - returns array ( list of full path to thumbnail urls. )
41  *
42  *
43  *  DISCOVERY
44  *    
45  *
46  * 
47  */
48
49 require_once 'Pman.php';
50
51 class Pman_Admin_Dump extends Pman {
52     
53     function getAuth()
54     {
55         
56         if (!HTML_FlexyFramework::get()->cli) {
57             die("Access only permitted from cli");
58         }
59         
60     }
61     var $args = array();
62     var $deps = array(); // list of dependants
63     var $out = array(); // list of created sql/shell scripts.
64     
65     function get($path )
66     {
67         $argv = $_SERVER['argv'];
68         array_shift($argv);
69         array_shift($argv);
70         
71         $opts = explode(',', 'table==,where==,dump-dir==,dump-dir==,--debug=');
72         require_once 'Console/Getopt.php';
73         $go = Console_Getopt::getopt2($argv, '', $opts );
74         if (is_object($go)) {
75             die($go->toString());
76         }
77          
78         foreach($go[0] as $ar) {
79             $args[substr($ar[0],2)] = $ar[1];
80         }
81         $errs = array();
82         foreach($opts as $req) {
83             if (substr($req,-2, 2) != '==') { // skip optional arguments
84                 continue;
85             }
86             if (empty($args[substr($req,0, -2)])) {
87                 $errs[] = "--".substr($req,0, -2) . ' is required';
88             }
89         }
90         if (!empty($errs)) {
91             die(print_R($errs,true));
92         }
93         
94         $this->args = $args;
95         $this->out = array();
96         $this->discoverChildren($this->args['table'], $this->args['where'], true);
97         //print_R($this->deletes);
98         //print_r($this->dumps);
99         //exit;
100         
101         $this->discover($this->args['table'], $this->args['where'], true);
102          
103         
104         if (!file_exists($args['dump-dir'])) {
105             mkdir($args['dump-dir'], 0777, true);
106         }
107          
108         $this->generateInsert();
109         $this->generateDelete();
110         $this->generateShell();
111
112         echo "DELETING:\n";
113         foreach($this->deletes as $tbl => $ar) {
114             if (empty($ar)) { continue; }
115             echo "   " .$tbl . ' -> ' . count(array_keys($ar)) . " Records\n";
116         }
117         echo "DUMPING:\n";
118         foreach($this->dumps as $tbl => $ar) {
119             if (empty($ar)) { continue; }
120             echo "   " .$tbl . ' -> ' . count(array_keys($ar)) . " Records\n";
121         }
122         echo "GENERATED FILES:\n";
123         // summary
124         echo "    ". implode("\n    ", $this->out). "\n";
125         
126         
127         exit;
128         
129       
130          
131     }
132      
133     var $deletes = array(); // TABLE => [key] => TRUE|FALSE
134     var $dumps = array(); // TABLE => [key] => TRUE|FALSE - if it's been scanned..
135     var $dscan = array(); // TABLE:COL => [value => TRUE|FALSE] - if its been scanned..
136     var $childfiles = array(); // array of [ 'sourcedirectory' , 'subdirectory(s) and filename' ]
137     var $childthumbs = array(); // array of [ 'filename', 'filename' ,......]
138     /**
139      * scan table for
140      * a) what depends on it (eg. child elements) - which will be deleted.
141      * b) what it depends on it (eg. parent elements) - which will be dumped..
142      */
143         
144     function discover($table, $where, $is_delete = false )
145     {
146         
147         if (!isset($this->dumps[$table])) {
148             $this->dumps[$table] = array();
149         }
150         if ($is_delete && !isset($this->deletes[$table])) {
151             $this->deletes[$table] = array();
152         }
153         //DB_DataObject::debugLevel(1);
154         $x = DB_DataObject::factory($table);
155         if (PEAR::isError($x)) {
156             if (isset($this->dumps[$table])) {
157                 unset($this->dumps[$table]); // links to non-existant tables..
158             }
159             return;
160         }
161         
162         $keys = $x->keys();
163           
164         if (is_array( $where)) {
165             $x->whereAddIn($keys[0] , $where, 'int');
166         } else {
167         
168             $x->whereAdd($where);
169         }
170         // what we need
171         // a) id's of elements in this table
172         // b) columns which point to other tables..
173         $links = $x->links();
174         $cols = array_keys($links);
175       
176          array_push($cols, $keys[0]);
177          
178         
179         $x->selectAdd();
180         $x->selectAdd('`'.  implode('`,`', $cols) . '`');
181         $x->find();
182         //DB_DataObject::debugLevel(0);
183         while ($x->fetch()) {
184             foreach($cols as $k) {
185                 if (empty($x->$k)) { // skip blanks.
186                     continue;
187                 }
188                 if (isset($links[$k])) {
189                     // it's a sublink..
190                     $kv = explode(':', $links[$k]);
191                     if (!isset($this->dumps[$kv[0]])) {
192                         $this->dumps[$kv[0]] = array();
193                     }
194                     if (!isset($this->dumps[$kv[0]][$x->$k])) {
195                         $this->dumps[$kv[0]][$x->$k] = 0; // not checked yet..
196                     }
197                     continue;
198                 }
199                 // assume it's the key..
200                 if (empty($this->dumps[$table][$x->$k])) {
201                     $this->dumps[$table][$x->$k] = 1; // we have checked this one...
202                 }
203                 //if ($is_delete && !isset($this->deletes[$table][$x->$k])) {
204                 //    
205                 //    $this->deletes[$table][$x->$k] = 0 ; // not checked yet..
206                 //}
207                 
208                 
209             }
210             
211         }
212         // flag as checked if we where given an array.. - as some links might have been broken.
213         if (is_array($where)) {
214             foreach($where as $k) {
215                 $this->dumps[$table][$k] = 1;
216             }
217         }
218         
219         
220         // itterate through dumps to find what needs discovering
221         foreach($this->dumps as $k=>$v) {
222             $ar = array();
223             foreach($v as $id => $fetched) {
224                 if (!$fetched) {
225                     $ar[] = $id;
226                 }
227             }
228             if (count($ar)) { 
229                 $this->discover($k, $ar,false);
230             }
231              
232         }
233         
234         
235          
236         
237         
238     }
239     
240      
241     function discoverChildren($table, $where, $col=false  )
242     {
243         global $_DB_DATAOBJECT;
244         $do = DB_DataObject::factory($table);
245         if (PEAR::isError($do)) {
246             if (isset($this->dumps[$table])) {
247                 unset($this->dumps[$table]); // links to non-existant tables..
248             }
249             return;
250         }
251         if (!isset($this->dumps[$table])) {
252             $this->dumps[$table] = array();
253         }
254         if (!isset($this->deletes[$table])) {
255             $this->deletes[$table] = array();
256         }
257         
258         
259         $keys = $do->keys();
260           
261         if (is_array( $where)) {
262             $do->whereAddIn($col ? $col : $keys[0] , $where, 'int');
263         } else {
264         
265             $do->whereAdd($where);
266         }
267         
268         static $children = array();
269         
270         if (!isset($children[$table])) { 
271             
272             // force load of linsk
273             $do->links();
274             foreach($_DB_DATAOBJECT['LINKS'][$do->database()] as $tbl => $links) {
275                 // hack.. - we should get rid of this hack..
276                 if ($tbl == 'database__render') {
277                     continue;
278                 }
279                 //if ($tbl == $tn) { // skip same table 
280                 //    continue;
281                 //}
282                 foreach ($links as $tk => $kv) {
283                     
284                    // var_dump($tbl);
285                     list($k,$v) = explode(':', $kv);
286                     if ($k != $table) {
287                         continue;
288                     }
289                     $add = implode(':', array($tbl, $tk));
290                     //echo "ADD $tbl $tk=>$kv : $add\n";
291                     $children[$table][$add] = true;
292                     
293                 }
294                 
295             }
296         }
297         if (empty($children[$table])) {
298             // BLANK deletes???
299             return;
300         }
301        // DB_DataObject::debugLevel(1);
302         $do->selectAdd();
303         $key = $keys[0];
304         $do->selectAdd($key);
305         $do->find();
306         while ($do->fetch()) {
307             $this->dumps[$table][$do->$key] = 0;
308             if (!isset($this->deletes[$table][$do->$key])) {
309                 $this->deletes[$table][$do->$key] = 0;
310             }
311            
312             foreach($children[$table] as $kv=>$t) {
313                 if (!isset($this->dscan[$kv])) {
314                     $this->dscan[$kv] = array();
315                 }
316                 if (!isset($this->dscan[$kv][$do->$key])) {
317                     $this->dscan[$kv][$do->$key]= 0; // unscanned.
318                 }
319             }
320         }
321         
322         
323         // now iterate throught dependants. and scan them.
324         
325         
326         foreach($this->dscan as $kv => $ids) {
327             $ar = array();
328             foreach($ids as $id => $checked) {
329                 if (!$checked) {
330                     $this->dscan[$kv][$id] = 1; // flag it as checked.
331                     $ar[] = $id;
332                 }
333             }
334             
335             if (empty($ar)) {
336                 continue;
337                 
338             }
339             list($k, $v) = explode(':', $kv);
340             $this->discoverChildren($k, $ar, $v);
341             
342         }
343         
344         
345         
346         
347     }
348      
349     function generateDelete() {  
350         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.delete.sql';
351         $this->out[] = $target;
352         $fh = fopen($target, 'w');
353         
354         
355         
356         foreach($this->deletes as $tbl=>$ar) {
357             
358             $do = DB_DataObject::factory($tbl);
359             $tbl = $do->tableName();
360             $keys = $do->keys();
361             $key = $keys[0];
362             $do->whereAddIn($keys[0] , array_keys($ar), 'int');
363             $do->find();
364             $archivePaths = method_exists($do,'archivePaths');
365             $listThumbs = method_exists($do,'listThumbs');
366             while ($do->fetch()) {
367                 
368                 if ($archivePaths) {
369                     $ct = $do->archivePaths();
370                     if ($ct) {
371                         $this->childfiles[] = $ct;
372                     }
373                 }
374                 if ($listThumbs) {
375                     $ct = $do->listThumbs();
376                     if($ct) {
377                         $this->childthumbs[] = $ct;
378                     }
379                 }
380                 $id = $do->$key;
381                 
382                 fwrite($fh, "DELETE FROM `$tbl` WHERE `$key` = $id;\n"); // we assume id's and nice column names...
383             }
384         }
385         fclose($fh);
386     }
387     function generateShell() {
388         
389         if (empty($this->childfiles) && empty($this->childthumbs)) {
390             return;
391         }
392         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.copy.sh';
393         $this->out[] = $target;
394         $fh = fopen($target, 'w');
395         
396         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.delete.sh';
397         $this->out[] = $target;
398         $fh2 = fopen($target, 'w');
399         
400         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.restore.sh';
401         $this->out[] = $target;
402         $fh3 = fopen($target, 'w');
403         
404         
405         foreach($this->childfiles as  $v) {
406             fwrite($fh,"mkdir -p " . escapeshellarg(dirname($args['dump-dir'] .'/'.$v[1])) ."\n" );
407             fwrite($fh,"cp " . escapeshellarg($v[0].'/'.$v[1]) . ' ' . escapeshellarg($args['dump-dir'] .'/'.$v[1]) ."\n" );
408             
409             fwrite($fh3,"mkdir -p " . escapeshellarg(dirname($v[0].'/'.$v[1])) ."\n" );
410             fwrite($fh3,"cp " .  escapeshellarg($args['dump-dir'] .'/'.$v[1]) . ' ' . escapeshellarg($v[0].'/'.$v[1]) . "\n" );
411             
412             fwrite($fh2,"rm " . escapeshellarg($v[0].'/'.$v[1]) ."\n" );
413         }
414         fclose($fh);
415         fclose($fh3); // restore does not need to bother with thumbnails.
416         
417         
418         
419         foreach($this->childthumbs as  $v) {
420             foreach($v as $vv) { 
421                 fwrite($fh2,"rm " . escapeshellarg($vv). "\n");
422             }
423         }
424         fclose($fh2);
425     }
426      
427     function generateInsert()
428     {
429         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.sql';
430         $this->out[] = $target;
431         $fh = fopen($target,'w');
432          
433          
434         
435         foreach($this->dumps as $tbl => $ar) {
436             if (empty($ar)) {
437                 continue;
438             }
439             $do = DB_DataObject::factory($tbl);
440              
441             $keys = $do->keys();
442          
443             $do->whereAddIn($keys[0] , array_keys($ar), 'int');
444             $do->find();
445             while ($do->fetch()) {
446                 fwrite($fh,$this->toInsert($do));
447             }
448              
449             
450         }
451         fclose($fh);
452         
453         
454     }
455
456     
457       
458     /**
459      * toInsert - does not handle NULLS... 
460      */
461     function toInsert($do)
462     {
463         $kcol = array_shift($do->keys());
464          
465         // for auto_inc column we need to use a 'set argument'...
466         $items = $do->table();
467         //print_R($items);
468         
469         // for
470         $leftq     = '';
471         $rightq    = '';
472         
473         $table = $do->tableName();
474         
475          
476         foreach(  $items  as $k=>$v)
477         {
478             if ($leftq) {
479                 $leftq  .= ', ';
480                 $rightq .= ', ';
481             }
482             
483             $leftq .= '`' . $k . '`';
484             
485              
486             
487             
488             if ($v & DB_DATAOBJECT_STR) {
489                 $rightq .= $do->_quote((string) (
490                         ($v & DB_DATAOBJECT_BOOL) ? 
491                             // this is thanks to the braindead idea of postgres to 
492                             // use t/f for boolean.
493                             (($do->$k === 'f') ? 0 : (int)(bool) $do->$k) :  
494                             $do->$k
495                     )) . " ";
496                 continue;
497             }
498             if (is_numeric($do->$k)) {
499                 $rightq .=" {$do->$k} ";
500                 continue;
501             }
502             $rightq .= ' ' . intval($do->$k) . ' ';
503         }
504         
505         return "INSERT INTO `{$table}` ($leftq) VALUES ($rightq);\n";
506         
507     }
508     
509     
510 }