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