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