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