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         if (!empty($args['debug'])) {
94             DB_DataObject::debugLevel($args['debug']);
95         }
96         $this->args = $args;
97         $this->out = array();
98         $this->discoverChildren($this->args['table'], $this->args['where'], true);
99         //print_R($this->deletes);
100         //print_r($this->dumps);
101         //exit;
102         
103         $this->discover($this->args['table'], $this->args['where'], true);
104          
105         
106         if (!file_exists($args['dump-dir'])) {
107             mkdir($args['dump-dir'], 0777, true);
108         }
109          
110         $this->generateInsert();
111         $this->generateDelete();
112         $this->generateShell();
113
114         echo "DELETING:\n";
115         foreach($this->deletes as $tbl => $ar) {
116             if (empty($ar)) { continue; }
117             echo "   " .$tbl . ' -> ' . count(array_keys($ar)) . " Records\n";
118         }
119         echo "DUMPING:\n";
120         foreach($this->dumps as $tbl => $ar) {
121             if (empty($ar)) { continue; }
122             echo "   " .$tbl . ' -> ' . count(array_keys($ar)) . " Records\n";
123         }
124         echo "GENERATED FILES:\n";
125         // summary
126         echo "    ". implode("\n    ", $this->out). "\n";
127         
128         
129         exit;
130         
131       
132          
133     }
134      
135     var $deletes = array(); // TABLE => [key] => TRUE|FALSE
136     var $dumps = array(); // TABLE => [key] => TRUE|FALSE - if it's been scanned..
137     var $dscan = array(); // TABLE:COL => [value => TRUE|FALSE] - if its been scanned..
138     var $childfiles = array(); // array of [ 'sourcedirectory' , 'subdirectory(s) and filename' ]
139     var $childthumbs = array(); // array of [ 'filename', 'filename' ,......]
140     /**
141      * scan table for
142      * a) what depends on it (eg. child elements) - which will be deleted.
143      * b) what it depends on it (eg. parent elements) - which will be dumped..
144      */
145         
146     function discover($table, $where, $is_delete = false )
147     {
148         
149         if (!isset($this->dumps[$table])) {
150             $this->dumps[$table] = array();
151         }
152         if ($is_delete && !isset($this->deletes[$table])) {
153             $this->deletes[$table] = array();
154         }
155         //DB_DataObject::debugLevel(1);
156         $x = DB_DataObject::factory($table);
157         if (PEAR::isError($x)) {
158             if (isset($this->dumps[$table])) {
159                 unset($this->dumps[$table]); // links to non-existant tables..
160             }
161             return;
162         }
163         
164         $keys = $x->keys();
165           
166         if (is_array( $where)) {
167             $x->whereAddIn($keys[0] , $where, 'int');
168         } else {
169         
170             $x->whereAdd($where);
171         }
172         // what we need
173         // a) id's of elements in this table
174         // b) columns which point to other tables..
175         $links = $x->links();
176         $cols = array_keys($links);
177       
178          array_push($cols, $keys[0]);
179          
180         
181         $x->selectAdd();
182         $x->selectAdd('`'.  implode('`,`', $cols) . '`');
183         $x->find();
184         //DB_DataObject::debugLevel(0);
185         while ($x->fetch()) {
186             foreach($cols as $k) {
187                 if (empty($x->$k)) { // skip blanks.
188                     continue;
189                 }
190                 if (isset($links[$k])) {
191                     // it's a sublink..
192                     $kv = explode(':', $links[$k]);
193                     if (!isset($this->dumps[$kv[0]])) {
194                         $this->dumps[$kv[0]] = array();
195                     }
196                     if (!isset($this->dumps[$kv[0]][$x->$k])) {
197                         $this->dumps[$kv[0]][$x->$k] = 0; // not checked yet..
198                     }
199                     continue;
200                 }
201                 // assume it's the key..
202                 if (empty($this->dumps[$table][$x->$k])) {
203                     $this->dumps[$table][$x->$k] = 1; // we have checked this one...
204                 }
205                 //if ($is_delete && !isset($this->deletes[$table][$x->$k])) {
206                 //    
207                 //    $this->deletes[$table][$x->$k] = 0 ; // not checked yet..
208                 //}
209                 
210                 
211             }
212             
213         }
214         // flag as checked if we where given an array.. - as some links might have been broken.
215         if (is_array($where)) {
216             foreach($where as $k) {
217                 $this->dumps[$table][$k] = 1;
218             }
219         }
220         
221         
222         // itterate through dumps to find what needs discovering
223         foreach($this->dumps as $k=>$v) {
224             $ar = array();
225             foreach($v as $id => $fetched) {
226                 if (!$fetched) {
227                     $ar[] = $id;
228                 }
229             }
230             if (count($ar)) { 
231                 $this->discover($k, $ar,false);
232             }
233              
234         }
235         
236         
237          
238         
239         
240     }
241     
242      
243     function discoverChildren($table, $where, $col=false  )
244     {
245         global $_DB_DATAOBJECT;
246         $do = DB_DataObject::factory($table);
247         if (PEAR::isError($do)) {
248             if (isset($this->dumps[$table])) {
249                 unset($this->dumps[$table]); // links to non-existant tables..
250             }
251             return;
252         }
253         if (!isset($this->dumps[$table])) {
254             $this->dumps[$table] = array();
255         }
256         if (!isset($this->deletes[$table])) {
257             $this->deletes[$table] = array();
258         }
259         
260         
261         $keys = $do->keys();
262           
263         if (is_array( $where)) {
264             $do->whereAddIn($col ? $col : $keys[0] , $where, 'int');
265         } else {
266         
267             $do->whereAdd($where);
268         }
269         
270         static $children = array();
271         
272         if (!isset($children[$table])) { 
273             
274             // force load of linsk
275             $do->links();
276             foreach($_DB_DATAOBJECT['LINKS'][$do->database()] as $tbl => $links) {
277                 // hack.. - we should get rid of this hack..
278                 if ($tbl == 'database__render') {
279                     continue;
280                 }
281                 //if ($tbl == $tn) { // skip same table 
282                 //    continue;
283                 //}
284                 foreach ($links as $tk => $kv) {
285                     
286                    // var_dump($tbl);
287                     list($k,$v) = explode(':', $kv);
288                     if ($k != $table) {
289                         continue;
290                     }
291                     $add = implode(':', array($tbl, $tk));
292                     //echo "ADD $tbl $tk=>$kv : $add\n";
293                     $children[$table][$add] = true;
294                     
295                 }
296                 
297             }
298             print_R($children);
299         }
300         if (empty($children[$table])) {
301             // BLANK deletes???
302             return;
303         }
304          DB_DataObject::debugLevel(1);
305         $do->selectAdd();
306         $key = $keys[0];
307         $do->selectAdd($key);
308         $do->find();
309         while ($do->fetch()) {
310             $this->dumps[$table][$do->$key] = 0;
311             if (!isset($this->deletes[$table][$do->$key])) {
312                 $this->deletes[$table][$do->$key] = 0;
313             }
314            
315             foreach($children[$table] as $kv=>$t) {
316                 if (!isset($this->dscan[$kv])) {
317                     $this->dscan[$kv] = array();
318                 }
319                 if (!isset($this->dscan[$kv][$do->$key])) {
320                     $this->dscan[$kv][$do->$key]= 0; // unscanned.
321                 }
322             }
323         }
324         
325         
326         // now iterate throught dependants. and scan them.
327         
328         
329         foreach($this->dscan as $kv => $ids) {
330             $ar = array();
331             foreach($ids as $id => $checked) {
332                 if (!$checked) {
333                     $this->dscan[$kv][$id] = 1; // flag it as checked.
334                     $ar[] = $id;
335                 }
336             }
337             
338             if (empty($ar)) {
339                 continue;
340                 
341             }
342             list($k, $v) = explode(':', $kv);
343             $this->discoverChildren($k, $ar, $v);
344             
345         }
346         
347         
348         
349         
350     }
351      
352     function generateDelete() {  
353         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.delete.sql';
354         $this->out[] = $target;
355         $fh = fopen($target, 'w');
356         
357         
358         
359         foreach($this->deletes as $tbl=>$ar) {
360             
361             $do = DB_DataObject::factory($tbl);
362             $tbl = $do->tableName();
363             $keys = $do->keys();
364             $key = $keys[0];
365             $do->whereAddIn($keys[0] , array_keys($ar), 'int');
366             $do->find();
367             $archivePaths = method_exists($do,'archivePaths');
368             $listThumbs = method_exists($do,'listThumbs');
369             while ($do->fetch()) {
370                 
371                 if ($archivePaths) {
372                     $ct = $do->archivePaths();
373                     if ($ct) {
374                         $this->childfiles[] = $ct;
375                     }
376                 }
377                 if ($listThumbs) {
378                     $ct = $do->listThumbs();
379                     if($ct) {
380                         $this->childthumbs[] = $ct;
381                     }
382                 }
383                 $id = $do->$key;
384                 
385                 fwrite($fh, "DELETE FROM `$tbl` WHERE `$key` = $id;\n"); // we assume id's and nice column names...
386             }
387         }
388         fclose($fh);
389     }
390     function generateShell() {
391         
392         if (empty($this->childfiles) && empty($this->childthumbs)) {
393             return;
394         }
395         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.copy.sh';
396         $this->out[] = $target;
397         $fh = fopen($target, 'w');
398         
399         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.delete.sh';
400         $this->out[] = $target;
401         $fh2 = fopen($target, 'w');
402         
403         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.restore.sh';
404         $this->out[] = $target;
405         $fh3 = fopen($target, 'w');
406         
407         
408         foreach($this->childfiles as  $v) {
409             fwrite($fh,"mkdir -p " . escapeshellarg(dirname($args['dump-dir'] .'/'.$v[1])) ."\n" );
410             fwrite($fh,"cp " . escapeshellarg($v[0].'/'.$v[1]) . ' ' . escapeshellarg($args['dump-dir'] .'/'.$v[1]) ."\n" );
411             
412             fwrite($fh3,"mkdir -p " . escapeshellarg(dirname($v[0].'/'.$v[1])) ."\n" );
413             fwrite($fh3,"cp " .  escapeshellarg($args['dump-dir'] .'/'.$v[1]) . ' ' . escapeshellarg($v[0].'/'.$v[1]) . "\n" );
414             
415             fwrite($fh2,"rm " . escapeshellarg($v[0].'/'.$v[1]) ."\n" );
416         }
417         fclose($fh);
418         fclose($fh3); // restore does not need to bother with thumbnails.
419         
420         
421         
422         foreach($this->childthumbs as  $v) {
423             foreach($v as $vv) { 
424                 fwrite($fh2,"rm " . escapeshellarg($vv). "\n");
425             }
426         }
427         fclose($fh2);
428     }
429      
430     function generateInsert()
431     {
432         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.sql';
433         $this->out[] = $target;
434         $fh = fopen($target,'w');
435          
436          
437         
438         foreach($this->dumps as $tbl => $ar) {
439             if (empty($ar)) {
440                 continue;
441             }
442             $do = DB_DataObject::factory($tbl);
443              
444             $keys = $do->keys();
445          
446             $do->whereAddIn($keys[0] , array_keys($ar), 'int');
447             $do->find();
448             while ($do->fetch()) {
449                 fwrite($fh,$this->toInsert($do));
450             }
451              
452             
453         }
454         fclose($fh);
455         
456         
457     }
458
459     
460       
461     /**
462      * toInsert - does not handle NULLS... 
463      */
464     function toInsert($do)
465     {
466         $kcol = array_shift($do->keys());
467          
468         // for auto_inc column we need to use a 'set argument'...
469         $items = $do->table();
470         //print_R($items);
471         
472         // for
473         $leftq     = '';
474         $rightq    = '';
475         
476         $table = $do->tableName();
477         
478          
479         foreach(  $items  as $k=>$v)
480         {
481             if ($leftq) {
482                 $leftq  .= ', ';
483                 $rightq .= ', ';
484             }
485             
486             $leftq .= '`' . $k . '`';
487             
488              
489             
490             
491             if ($v & DB_DATAOBJECT_STR) {
492                 $rightq .= $do->_quote((string) (
493                         ($v & DB_DATAOBJECT_BOOL) ? 
494                             // this is thanks to the braindead idea of postgres to 
495                             // use t/f for boolean.
496                             (($do->$k === 'f') ? 0 : (int)(bool) $do->$k) :  
497                             $do->$k
498                     )) . " ";
499                 continue;
500             }
501             if (is_numeric($do->$k)) {
502                 $rightq .=" {$do->$k} ";
503                 continue;
504             }
505             $rightq .= ' ' . intval($do->$k) . ' ';
506         }
507         
508         return "INSERT INTO `{$table}` ($leftq) VALUES ($rightq);\n";
509         
510     }
511     
512     
513 }