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