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