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->discover($this->args['table'], $this->args['where']);
94         
95         if (!file_exists($args['dump-dir'])) {
96             mkdir($args['dump-dir'], 0777, true);
97         }
98          
99         $this->generateInsert();
100         $this->generateDelete();
101         $this->generateShell();
102            
103         echo "GENERATED FILES:";
104         print_r($out);
105         exit;
106         
107       
108          
109     }
110      
111     var $deletes = array(); // TABLE => [key] => TRUE|FALSE
112     var $dumps = array(); // TABLE => [key] => TRUE|FALSE - if it's been scanned..
113     
114     /**
115      * scan table for
116      * a) what depends on it (eg. child elements) - which will be deleted.
117      * b) what it depends on it (eg. parent elements) - which will be dumped..
118      */
119         
120     function discover($table, $where, $is_delete = false )
121     {
122         
123         if (!isset($this->dumps[$table])) {
124             $this->dumps[$table] = array();
125         }
126         if ($is_delete && !isset($this->deletes[$table])) {
127             $this->deletes[$table] = array();
128         }
129         
130         $x = DB_DataObject::factory($table);
131         
132         $x->whereAdd($where);
133         // what we need
134         // a) id's of elements in this table
135         // b) columns which point to other tables..
136         $links = $x->links();
137         $cols = array_keys($links);
138         $keys = $x->keys();
139         $cols = array_push($cols, $key[0]);
140          
141         
142         $x->selectAdd();
143         $x->selectAdd('`'.  implode('`,`', $cols) . '`');
144         $x->find();
145         while ($x->fetch()) {
146             foreach($cols as $k) {
147                 if (empty($x->$k)) { // skip blanks.
148                     continue;
149                 }
150                 if (isset($links[$k])) {
151                     // it's a sublink..
152                     $kv = explode(':', $links[$k]);
153                     if (!isset($this->dumps[$kv[0]])) {
154                         $this->dumps[$kv[0]] = array();
155                     }
156                     if (!isset($this->dumps[$kv[0]][$x->$k])) {
157                         $this->dumps[$kv[0]][$x->$k] = false; // not checked yet..
158                     }
159                     continue;
160                 }
161                 // assume it's the key..
162                 if (!isset($this->dumps[$table][$x->$k])) {
163                     $this->dumps[$table][$x->$k] = false; // not checked yet..
164                 }
165                 if ($is_delete) {
166                     $this->deletes[$table][$x->$k] = false; // not checked yet..
167                 }
168                 
169                 
170             }
171             
172         }
173         
174         print_r($this->dumps);
175         exit;
176         
177         
178         
179         
180     }
181     function oldStuff() {
182         
183        
184         
185         $target = $args['dump-dir'] .'/'. date('Y-m-d').'.sql';
186         $out[] = $target;
187         $this->fh = fopen($target,'w');
188         //print_r($args);
189         //DB_DataObject::debugLevel(1);
190         // since we are runnign in cli mode... we will be a bit wild and free with verification
191         $x = DB_DataObject::factory($args['table']);
192         $x->{$args['col']} = $args['val'];
193         
194         $x->find();
195          
196         while ($x->fetch()) {
197         
198             fwrite($this->fh, $this->toInsert($x));
199             $this->dumpChildren($x);
200             
201         }
202         
203          
204         foreach($this->deps as $s=>$status) {
205             if (isset($this->dumped[$s])) {
206                 continue;
207             }
208             list($tbl, $key, $val) = explode(':', $s);
209             $dd = DB_DataObject::factory($tbl);
210             if ($dd->get($key,$val)) {
211                 fwrite($this->fh, $this->toInsert($dd));
212             }
213         }
214         
215         fclose($this->fh);
216     }
217     
218     function generateDelete() {  
219         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.delete.sql';
220         $this->out[] = $target;
221         $fh = fopen($target, 'w');
222         foreach($this->childscanned as $s=>$v) {
223             list($tbl, $key, $val) = explode(':', $s);
224             fwrite($fh, "DELETE FROM $tbl WHERE $key = $val;\n"); // we assume id's and nice column names...
225              
226         }
227         fclose($fh);
228     }
229     function generateShell() {
230         
231         
232         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.copy.sh';
233         $this->out[] = $target;
234         $fh = fopen($target, 'w');
235         
236         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.delete.sh';
237         $this->out[] = $target;
238         $fh2 = fopen($target, 'w');
239         
240         $target = $this->args['dump-dir'] .'/'. date('Y-m-d').'.restore.sh';
241         $this->out[] = $target;
242         $fh3 = fopen($target, 'w');
243         
244         
245         foreach($this->childfiles as $s=>$v) {
246             fwrite($fh,"mkdir -p " . escapeshellarg(dirname($args['dump-dir'] .'/'.$v[1])) ."\n" );
247             fwrite($fh,"cp " . escapeshellarg($v[0].'/'.$v[1]) . ' ' . escapeshellarg($args['dump-dir'] .'/'.$v[1]) ."\n" );
248             
249             fwrite($fh3,"mkdir -p " . escapeshellarg(dirname($v[0].'/'.$v[1])) ."\n" );
250             fwrite($fh3,"cp " .  escapeshellarg($args['dump-dir'] .'/'.$v[1]) . ' ' . escapeshellarg($v[0].'/'.$v[1]) . "\n" );
251             
252             fwrite($fh2,"rm " . escapeshellarg($v[0].'/'.$v[1]) ."\n" );
253         }
254         fclose($fh);
255         fclose($fh3); // restore does not need to bother with thumbnails.
256         
257         
258         
259         foreach($this->childthumbs as $s=>$v) {
260             foreach($v as $vv) { 
261                 fwrite($fh2,"rm " . escapeshellarg($vv). "\n");
262             }
263         }
264         fclose($fh2);
265     }
266      
267     
268     
269     var $children = array(); // map of search->checked
270     var $childscanned = array();
271     var $childfiles = array();
272     var $childthumbs = array();
273     function dumpChildren($do)
274     {
275         $kcol = array_shift($do->keys());
276         $kid = $do->tableName() . ':' . $kcol . ':' . $do->{$kcol};
277         if (isset($this->childscanned[$kid])) {
278             return;
279         }
280         $this->childscanned[$kid] = true;
281         
282         if (method_exists($do,'archivePaths')) {
283             $ct = $do->archivePaths();
284             if ($ct) {
285                 $this->childfiles[$kid] = $ct;
286             }
287         }
288         if (method_exists($do,'listThumbs')) {
289             $ct = $do->listThumbs();
290             if($ct) {
291                 $this->childthumbs[$kid] = $ct;
292             }
293         }
294         
295         global $_DB_DATAOBJECT;
296         $do->links();; //force load
297         $tn = $do->tableName();
298         
299         
300         foreach($_DB_DATAOBJECT['LINKS'][$do->database()] as $tbl => $links) {
301             // hack.. - we should get rid of this hack..
302             if ($tbl == 'database__render') {
303                 continue;
304             }
305             //if ($tbl == $tn) { // skip same table 
306             //    continue;
307             //}
308             foreach ($links as $tk => $kv) {
309                 
310                // var_dump($tbl);
311                 list($k,$v) = explode(':', $kv);
312                 if ($k != $tn) {
313                     continue;
314                 }
315                 $add = implode(':', array($tbl, $tk, $do->$v));
316                 //echo "ADD $tbl $tk=>$kv : $add\n";
317                 $this->children[$add] = 0;
318                 
319             }
320             
321         }
322        // print_r($this->children);exit;
323         $ch = $this->children ;
324         
325         $todo = array();
326         foreach($ch as $s=>$status) {
327             if ($this->children[$s]) {
328                 continue;
329             }
330             // flag it as being done, so we do not recurse..
331             $this->children[$s] = 1;
332             
333             list($tbl, $key, $val) = explode(':', $s);
334             $dd = DB_DataObject::factory($tbl);
335             $dd->$key = $val;
336             $dd->find();
337             
338             while ($dd->fetch()) {
339                 $todo [] = clone($dd);
340                 // if we have dumped this already.. ignore it..
341                 
342             }
343             
344             
345         }
346         foreach($todo as $dd) {
347             fwrite($this->fh, $this->toInsert($dd));
348             $this->dumpChildren($dd);
349         }
350         
351         
352         
353     }
354     
355  
356     var $dumped  = array();
357     /**
358      * toInsert - does not handle NULLS... 
359      */
360     function toInsert($do)
361     {
362          $kcol = array_shift($do->keys());
363         $kid = $do->tableName() . ':' . $kcol . ':' . $do->{$kcol};
364         if (isset($this->dumped[$kid])) {
365             return;
366         }
367         //echo "DUMP: $kid\n";
368         $this->dumped[$kid] = true;
369         
370         // for auto_inc column we need to use a 'set argument'...
371         $items = $do->table();
372         //print_R($items);
373         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
374         // for
375         $leftq     = '';
376         $rightq    = '';
377         
378         
379         $deplinks = $do->links();
380         
381         foreach(  $items  as $k=>$v)
382         {
383             if ($leftq) {
384                 $leftq  .= ', ';
385                 $rightq .= ', ';
386             }
387             
388             $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ')  : "$k ");
389             
390             // only handles numeric links..
391             if (is_numeric($do->$k) && $do->$k && $deplinks && !empty($deplinks[$k])) {
392                // die("got deplink" . $deplinks[$k]);
393                 $l = explode(':', $deplinks[$k]);
394                 $add = $deplinks[$k].':' . $do->$k;
395                 
396                 $this->deps[$add] = 0;
397             }
398             
399             
400             
401             if ($v & DB_DATAOBJECT_STR) {
402                 $rightq .= $do->_quote((string) (
403                         ($v & DB_DATAOBJECT_BOOL) ? 
404                             // this is thanks to the braindead idea of postgres to 
405                             // use t/f for boolean.
406                             (($do->$k === 'f') ? 0 : (int)(bool) $do->$k) :  
407                             $do->$k
408                     )) . " ";
409                 continue;
410             }
411             if (is_numeric($do->$k)) {
412                 $rightq .=" {$do->$k} ";
413                 continue;
414             }
415             $rightq .= ' ' . intval($do->$k) . ' ';
416         }
417         $table = ($quoteIdentifiers ? $DB->quoteIdentifier($do->__table)    : $do->__table);
418         return "INSERT INTO {$table} ($leftq) VALUES ($rightq);\n";
419         
420     }
421     
422     
423 }