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