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