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