root/trunk/midgard/tools/midrepository/DataflowMidgardVC.php

Revision 7101, 115.9 kB (checked in by bergius, 5 years ago)

Initial commit of the MidRepository? workflow extension
Issue number:
Obtained from:
Submitted by:
Reviewed by:

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php
2 // +----------------------------------------------------------------------+
3 // | MidRepository: Version Control                                       |
4 // +----------------------------------------------------------------------+
5 // | Copyright (c) 2003 David Schmitter, Dataflow Solutions GmbH          |
6 // +----------------------------------------------------------------------+
7 // | This program is free software; you can redistribute it and/or modify |
8 // | it under the terms of the GNU General Public License as published    |
9 // | by the Free Software Foundation; either version 2 of the License,    |
10 // | or (at your option) any later version.                               |
11 // |                                                                      |
12 // | This program is distributed in the hope that it will be useful,      |
13 // | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
14 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        |
15 // | GNU General Public License for more details.                         |
16 // |                                                                      |
17 // | You should have received a copy of the GNU General Public License    |
18 // | along with this program; if not, write to the Free Software          |
19 // | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, |
20 // | USA                                                                  |
21 // +----------------------------------------------------------------------+
22 // | Author: David Schmitter <schmitt@dataflow.ch>                        |
23 // +----------------------------------------------------------------------+
24 //
25
26 if (DEBUG) {
27   ini_set("error_reporting", E_ALL);
28 }
29
30 define('MGD_ERR_ERROR', 0);
31 define('MGD_ERR_ACCESS_DENIED', 0);
32 define('MGD_ERR_LOCK', 0);
33 define('MGD_ERR_NOT_EXISTS', 0);
34 define('MGD_ERR_NOT_DEFINED', 0);
35 define('MGD_ERR_NOT_IMPLEMENTED', 0);
36
37 define('PART_REVISION_TABLE', 'part_branch_revision');
38 define('PART_TABLE', 'part_branch');
39 define('DEEP_COLLECTION_TABLE', 'collection');
40
41 define('PART_CURRENT', 1);
42 define('PART_NEW_DB', 2);
43 define('PART_NEW_CVS', 3);
44 define('PART_NEW_CVS_FROM_WF', 4);
45 define('PART_AUTO', 5);
46 define('PART_UPDATE', 6);
47 define('PART_MISSING_DEP', 7);
48 define('PART_MISSING_DEP_FROM_WF', 8);
49
50 define('DB_USER', 1);
51 define('MGD_USER', 2);
52 define('SPECIFY_USER', 3);
53
54 require_once 'lib/paths.php';
55 require_once 'lib/common.php';
56 require_once 'DB.php';
57
58 $i18nTables = array('article', 'element', 'page', 'pageelement', 'snippet');
59
60 PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handle_pear_error');
61
62 $GLOBALS['PartLocks'] = array();
63
64
65 /**
66  * Global registry for MgdPart locks. On $MgdPart->lock() a unique identifier is placed here and later read to determine if the current script is the locker.
67  */
68 class PartLockRegistry {
69   /**
70    * Adds a lock for a given PartBranchId.
71    * @param int $pdId
72    * @param string $uniqId
73    */
74   function addLock($pbId, $uniqId) {
75     $GLOBALS['PartLocks'][$pbId] = $uniqId;
76   }
77  
78   /**
79    * Gets unique-lock-id for a given PartBranchId, if present
80    * @param int $pdId
81    * @return mixed $uniqId the lock id, or false if not present
82    */
83   function getLock($pbId) {
84     if (isset($GLOBALS['PartLocks'][$pbId])) {
85       return $GLOBALS['PartLocks'][$pbId];
86     } else {
87       return false;
88     }
89   }
90
91 }
92
93 $GLOBALS['VCSessions'] = array();
94
95 /**
96  * Global registry easy access to and reuse of VCSessions (and also WorkflowSessions)
97  */
98 class VCSessionRegistry {
99
100   /**
101    * Add a VCSession to the global registry
102    * @param string $key the key under which it should be registered
103    * @param mixed $vcs
104    */
105   function addVCSession($key, &$vcs) {
106     $GLOBALS['VCSessions'][$key] = &$vcs;
107   }
108
109   /**
110    * Add the VCSession that should be used as a default to the global registry
111    * @param mixed $vcs
112    */
113   function addDefaultVCSession(&$vcs) {
114     $GLOBALS['VCSessions']['default'] = &$vcs;
115   }
116
117   /**
118    * Get VCSession by key
119    * @param string $key
120    * @return mixed $vcs the VCSession or false if not present
121    */
122   function &getVCSession($key) {
123     if (isset($GLOBALS['VCSessions'][$key])) {
124       return $GLOBALS['VCSessions'][$key];
125     } else {
126       return false;
127     }
128   }
129
130   /**
131    * Get default VCSession
132    * @return mixed $vcs the VCSession or false if not present
133    */
134   function &getDefaultVCSession() {
135     if (isset($GLOBALS['VCSessions']['default'])) {
136       return $GLOBALS['VCSessions']['default'];
137     } else {
138       return false;
139     }
140   }
141
142 }
143
144
145 class DepGraph {
146   var $_matrix;
147  
148   function DepGraph() {
149     $this->_matrix = array();
150   }
151  
152   function addEmptyNode($guid, $orig = false) {
153     $keys = array_keys($this->_matrix);
154     $this->_matrix[$guid] = array();
155     foreach($keys as $key) {
156       $this->_matrix[$key][$guid] = false;
157       $this->_matrix[$guid][$key] = false;
158     }
159     $this->_matrix[$guid]['orig'] = $orig;
160   }
161
162   /**
163    * $guidsdeps : string $guid => 'deps' => array $deps, 'orig' => bool $orig (if $guid is scheduled for publication)
164    */
165   function addNodes(&$guidsdeps) {
166     $fakePath = '';
167     if (is_array($guidsdeps)) {
168       foreach ($guidsdeps as $guid => $info) {
169     if (!in_array($guid, array_keys($this->_matrix))) {
170       $this->addEmptyNode($guid, $info['orig']);
171     } else {
172       $this->_matrix[$guid]['orig'] = $info['orig'];
173     }
174     foreach ($info['deps'] as $dep) {
175       echo "DEP: $dep<br>";
176       if (!in_array($dep, array_keys($this->_matrix))) {     
177         echo "DEP_EMPTY: $dep<br>";
178         $this->addEmptyNode($dep);
179       }
180       $this->_matrix[$guid][$dep] = true;
181     }
182       }
183     }
184   }
185  
186   /*   function add() {
187     $this->_
188   }*/
189
190   function warshallComputePaths() {
191     foreach ($this->_matrix as $row) {
192       $keys = array_keys($this->_matrix);
193       foreach($keys as $k) {
194     foreach ($keys as $i) {
195       foreach ($keys as $j) {
196         $this->_matrix[$i][$j] = ($this->_matrix[$i][$j] or ($this->_matrix[$i][$k] and $this->_matrix[$k][$j]));
197       }
198     }
199       }
200     }
201   }
202     
203   /**
204    * return array: string $unresolvedGuid => array $deps
205    */
206   function &getMissingDeps() {
207     $deps = array();
208     $keys = array_keys($this->_matrix);
209     foreach ($keys as $i) {
210       if ($this->_matrix[$i]['orig']) {
211     $deps[$i] = array();
212     foreach ($keys as $j) {
213       if ($this->_matrix[$i][$j] and !$this->_matrix[$j]['orig']) {
214         $deps[$i][] = $j;
215       }
216     }
217       }
218     }
219     return $deps;
220   }
221
222   function printHTML() {
223     echo "<table border='1'>";
224     foreach ($this->_matrix as $guid => $stuff) {
225       $color = $stuff['orig']?'green':'red';
226       echo "<tr><td style='background-color:$color'>$guid</td>"; // style='color:" . $stuff['orig']?'blue':'red' . "'>$guid</td>";
227       foreach ($stuff as $key=> $val) {
228     if ($key == 'orig') continue;
229     echo "<td style='background-color:$color'>$key : $val</td>";
230       }
231       echo "</tr>";
232     }
233     echo "</table>";
234     /**     foreach (*/
235   }
236     
237 }
238
239
240 /**
241  * Manages configuration and reuse of db connections, MgdDBs, VersionControl objects.
242  */
243 class VCSession {
244   var $_dbh;
245   var $_glob_dbh;
246   var $_mgddb;
247   var $_vc;
248   var $_nSpace;
249   var $_branch;
250   var $_branchid;
251   var $_branchlabel;
252   var $_user;
253   var $_outgoing;
254   var $_conf;
255
256   /**
257    * Construct a new VCSession.
258    * @param mixed $arr optional can be an array or a name of a global array that contains configuration data under the keys dbconf, globdbconf, vcconf, fsconf. (see config script)
259    * @return object $vcSession
260    */
261   function VCSession($conf = 0) {
262     if (is_array($conf)) {
263       $this->_conf = & $conf;
264       extract($conf);
265     } else {
266       if ($conf == 0) {
267     $conf = 'VCSessionConf';
268       }
269       $this->_conf = & $GLOBALS[$conf];
270       extract ($GLOBALS[$conf]);
271     }
272     if (!isset($dbconf['dbhost'])) $dbconf['dbhost'] = 'localhost';
273     if (!isset($dbconf['globdbhost'])) $dbconf['globdbhost'] = 'localhost';
274     $this->_dbh = DB::connect("mysql://{$dbconf['dbuser']}:{$dbconf['dbpass']}@{$dbconf['dbhost']}/{$dbconf['dbname']}");
275     $this->_dbh->setFetchMode(DB_FETCHMODE_ASSOC);
276     $this->_glob_dbh = DB::connect("mysql://{$dbconf['globdbuser']}:{$dbconf['globdbpass']}@{$dbconf['globdbhost']}/{$dbconf['globdbname']}");
277     $this->_glob_dbh->setFetchMode(DB_FETCHMODE_ASSOC);
278     $this->_mgddb = &new MgdDB($this->_dbh, $dbconf, $fsconf);
279     $this->_branchid = &$branchid;
280     $this->_branchlabel = $this->_glob_dbh->getOne("SELECT label FROM branch WHERE id = $branchid");
281     if (isset($vcconf)) {
282       $this->_vc = &new CVSVersionControl($vcconf, $fsconf);
283       $this->_nSpace = $this->_vc->getNamespace($vcconf['cvsmodule']);
284       $this->_branch = $this->_nSpace->getBranch($vcconf['cvsbranch']);
285       $this->_outgoing = &$fsconf['outgoing'];
286     }
287     global $midgard;
288     if (is_object($midgard) and $midgard->user != 0) {
289       $person = mgd_get_person($midgard->user);
290       $this->_user = $person->guid();
291     }   
292   }
293  
294   /**
295    * Get the DB handle of the Midgard DB.
296    * @return object $dbh the handle
297    */
298   function &getDbh() {
299     return $this->_dbh;
300   }
301  
302   /**
303    * Get the DB handle of the global DB (that contains meta-data about objects, collections and workflow data)
304    * @return object $dbh the handle
305    */   
306   function &getGlobDbh() {
307     return $this->_glob_dbh;
308   }
309
310   /**
311    * Get the Midgard DB as a MgdDB object.
312    * @return object $mgddb
313    */
314   function &getMgdDB() {
315     return $this->_mgddb;
316   }
317
318   /**
319    * Get the VersionControl object.
320    * @return object $vc
321    */
322   function &getVersionControl() {
323     return $this->_vc;
324   }
325
326   /**
327    * Get the Namespace object (in CVS, a module)
328    * @return object $nSpace
329    */
330   function &getNamespace() {
331     return $this->_nSpace;
332   }
333
334   /**
335    * Get the Branch object
336    * @return object $branch
337    */
338   function &getBranch(){
339     return $this->_branch;
340   }
341
342   /**
343    * Get the id of the current branch.
344    * @return int $id
345    */
346   function &getBranchId() {
347     return $this->_branchid;
348   }
349
350   /**
351    * Get the id of the current branch.
352    * @return int $id
353    */
354   function &getBranchLabel() {
355     return $this->_branchlabel;
356   }
357
358   /**
359    * Get the current user (guid)
360    * @return int $id
361    */
362   function &getUser() {
363     return $this->_user;
364   }
365
366   /**
367    * Get the outgoing directory
368    * @return int $id
369    */
370   function &getOutgoing() {
371     return $this->_outgoing;
372   }
373
374
375   /**
376    * Get the URL of the spooler on this branch
377    * @return string $url
378    */
379   function getSpoolerUrl() {
380     return $this->_conf['urlconf']['spooler'];
381   }
382
383   /**
384    * Get the URL of the global status handler script
385    * @return string $path
386    */
387   function getStatusHandler() {
388     return $this->_conf['status_handler'];
389   }
390
391   /**
392    * Notify the spooler on this branch
393    */
394   function notifySpooler() {
395     batchStart("curl {$this->_conf['urlconf']['spooler']}");
396   }
397
398   /**
399    * Notify the global status handler
400    */
401   function notifyStatusHandler() {
402     //    batchStart($this->_conf['statusHandler'] . " >> /tmp/everymin.log");
403   }
404
405 }
406
407 /**
408  * Represents a Midgard DB.
409  * Can import and export objects via repligard and check their dependencies (uses a repligard config file for configuration)
410  */
411 class MgdDB {
412   var $_dbh;
413   var $_dbconf;
414   var $_fsconf;
415   var $_lup;
416   var $_ldown;
417   var $_xmlcontext;
418
419   /**
420    * Construct a MgdDB
421    *
422    * @param mixed $dbh connection to the Midgard DB
423    * @param array $dbconf db configuration
424    * @param array $dbconf filesystem configuration
425    */
426   function MgdDB(&$dbh, &$dbconf, &$fsconf) {
427     $this->_dbh = $dbh;
428     $this->_dbconf = $dbconf;
429     $this->_fsconf = $fsconf;
430     $repliconf_xml = file_get_contents($dbconf['repliconffile']);
431     $p = xml_parser_create();
432     xml_parser_set_option($p,XML_OPTION_SKIP_WHITE,1);
433     xml_set_object($p, $this);
434     xml_set_element_handler($p, '_init_xml_handler', false);
435     @xml_parse($p, $repliconf_xml, true);
436     xml_parser_free($p);
437   }
438
439   /**
440    * To be called only from the XML parser. Uses the tag and attribute information to fill $_lup and $_ldown with links
441    *
442    * @param mixed $parser the xml parser
443    * @param string $tag the name of the current tag
444    * @param array $attrs[] attribute names => values
445    */
446   function _init_xml_handler($parser, $tag, $attrs) {
447     if ($tag == 'TYPE') {
448       $this->_xmlcontext = $attrs['NAME'];
449     } else if ($tag == 'LINK') {
450       $this->_lup[$this->_xmlcontext][$attrs['NAME']] = $attrs['LINK'];
451       if ($attrs['REVERSE'] != 'no') {
452     $this->_ldown[$attrs['LINK']][$attrs['NAME']] = $this->_xmlcontext;
453       }
454     } else if ($tag == 'RLINK') {
455       $fields = explode(' ',$attrs['LINK']);
456       $this->_lup[$this->_xmlcontext][$attrs['NAME']] = $fields;
457       $rlink = array();
458       $rlink['tablefrom'] = $fields[0];
459       $rlink['fieldfrom'] = $fields[1];
460       $rlink['fieldto'] = $attrs['NAME'];
461       if ($attrs['REVERSE'] != 'no') {
462     $this->_ldown['RLINK'][$this->_xmlcontext] = $rlink;
463       }
464     }
465   }
466  
467   /**
468    * Returns a list of objects that this object depends on but do not belong to it.
469    * @param string $guid the guid of the object
470    * @return array $guids the guids of its dependencies
471    */
472   function computeDepsUp($guid) {
473     global $i18nTables;
474     /* RLINK: the second part of the link (field) is ignored, always id */
475     $deps = array();
476     @extract($this->_dbh->getRow("SELECT realm,id FROM repligard WHERE guid = '$guid'"));
477     if (!@$id) {
478       trigger_error("Object does not exist.", E_USER_NOTICE);
479       return MGD_ERR_ERROR;
480     }
481     /** content in lang 0 is a requirement if you want to instantiate the i18nd object **/
482     echo "\n";
483     var_dump($i18nTables);
484     echo "\n";
485     var_dump($realm);
486     echo "\n";
487     if (in_array($realm, $i18nTables)) {
488       echo "WINTER.\n";
489       echo "SELECT repligard.guid FROM {$realm}_i,repligard WHERE {$realm}_i.sid = $id AND {$realm}_i.lang = 0 AND {$realm}_i.id = repligard.id AND repligard.realm = '{$realm}_i'";
490       $newdep = $this->_dbh->getOne("SELECT repligard.guid FROM {$realm}_i,repligard WHERE {$realm}_i.sid = $id AND {$realm}_i.lang = 0 AND {$realm}_i.id = repligard.id AND repligard.realm = '{$realm}_i'");
491       echo "NEWDEP: $newdep";
492       if (is_string($newdep)) {
493     $deps[] = $newdep;
494       }
495     }
496     if (is_array($this->_lup[$realm])) {
497       foreach($this->_lup[$realm] as $key => $val) {
498     if (is_string($val)) {
499       $newdep = $this->_dbh->getOne("SELECT repligard.guid FROM $realm,repligard WHERE $realm.id = $id AND $realm.$key = repligard.id AND repligard.realm = '$val'");
500     } elseif (is_array($val)) {
501       $newdep = $this->_dbh->getOne("SELECT repligard.guid FROM $realm,repligard WHERE $realm.id = $id AND $realm.$key = repligard.id AND repligard.realm = '$realm.$val[0]'");
502     }
503     if (is_string($newdep)) {
504       $deps[] = $newdep;
505     }
506       }
507     }
508     return array_unique($deps);
509   }
510
511  
512   /**
513    * Returns a list of objects that depend on / belong to this object.
514    * @param string $guid the guid of the object
515    * @return array $guids keys: the guids of the objects that depend on it => values: the language ids of the objects (0 if undefined)
516    */
517   function computeDepsDown($guid) {
518     /* RLINK: the second part of the link (field) is ignored, always id */
519     $deps = array();
520     $deps_lang = array();
521     @extract($this->_dbh->getRow("SELECT realm,id FROM repligard WHERE guid = '$guid'"));
522     if (!@$id) {
523       trigger_error("Object does not exist.", E_USER_NOTICE);
524       return MGD_ERR_ERROR;
525     }
526     if (is_array(@$this->_ldown[$realm])) {
527       foreach($this->_ldown[$realm] as $key => $val) {
528     if (substr($val, -2) == '_i') {
529       $langfield = ', link.lang';
530     } else {
531       $langfield = '';
532     }
533     $newdeps = $this->_dbh->query("SELECT repligard.guid $langfield FROM $val AS link,repligard WHERE link.$key = $id AND repligard.realm = '$val' AND repligard.id = link.id");
534     while ($row = $newdeps->fetchRow()) {
535       if (!in_array($row['guid'], $deps)) {
536         $deps[] = $row['guid'];
537         $deps_lang[$row['guid']] = isset($row['lang'])?$row['lang']:0;
538       }
539     }
540       }
541     }
542     if (is_array(@$this->_ldown['RLINK'])) {
543       foreach($this->_ldown['RLINK'] as $key => $val) {
544     if (substr($val['tablefrom'], -2) == '_i') {
545       $langfield = ', link.lang';
546     } else {
547       $langfield = '';
548     }
549     $newdeps = $this->_dbh->query("SELECT repligard.guid $langfield FROM $key AS link,repligard WHERE link.{$val['fieldto']} = $id AND link.{$val['tablefrom']} = '$realm' AND repligard.realm = '$key' AND repligard.id = link.id");
550     while (is_object($newdeps) and $row = $newdeps->fetchRow()) {
551       if (!in_array($row['guid'], $deps)) {
552         $deps[] = $row['guid'];
553         $deps_lang[$row['guid']] = isset($row['lang'])?$row['lang']:0;
554       }
555     }
556       }
557     }
558     return $deps_lang;
559   }
560  
561   /**
562    * Returns a list of objects that this object belongs to.
563    * (Should improve the naming)
564    * @param string $guid
565    * @return array $guids
566    */
567   function computeDepsDownReverse($guid) {
568     $deps = array();
569     extract(@$this->_dbh->getRow("SELECT realm,id FROM repligard WHERE guid = '$guid'"));
570     foreach(