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

Revision 7101, 27.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 <?
2 // +----------------------------------------------------------------------+
3 // | MidRepository: Workflow                                              |
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 require_once('lib/DataflowMidgardVC.php');
27
28 define('WF_PROC_TABLE', 'proc');
29
30 define('WF_NEEDS_APPROVE', 1);
31 define('WF_TO_BE_PUBLISHED', 2);
32 define('WF_PUBLISHED', 3);
33 define('WF_TO_BE_UNPUBLISHED', 4);
34 define('WF_UNPUBLISHED', 5);
35 define('WF_CANCELLED', 6);
36
37 define('COMMENT_OK', "OK von ");
38 define('COMMENT_BACK', "Zurückgegeben von ");
39 define('COMMENT_PUBLISHED', "Zum Publizieren freigegeben von ");
40 define('COMMENT_UNPUBLISHED', "Publikation rückgängig gemacht von ");
41 define('COMMENT_AUTO_PUBLISHED', "Automatisch publiziert");
42
43 $GLOBALS['PartLocks'] = array();
44
45 /**
46  * For use with usort
47  */
48 function mgd_path_child_cmp($p1, $p2) {
49   return mgd_path_is_child_of($p1->path, $p2->path);
50 }
51                     
52
53 function mgd_path_is_child_of($parent, $child) {
54   $parent = preg_replace('/\/lang:[^\/]+/', '', $parent);
55   $child = preg_replace('/\/lang:[^\/]+/', '', $child);
56   if ($child == $parent) {
57     return 0;
58   } else if (strstr($child, $parent) == $child) {
59     return 1;
60   } else {
61     return -1;
62   }
63 }
64
65 function mgd_path_fits($constraints, $path) {
66   preg_match('/\/(([a-z]+?):)?([^\/]*)/', $constraints, $atoms);
67   $langspec = strstr($constraints, '/lang:');
68   if ($langspec === false) {
69     return true;
70   } else {
71     if (strstr($path, $langspec) == $langspec) {
72       return true;
73     } else {
74       return false;
75     }
76   }
77 }
78
79 function wfInit() {
80   $WFSession = new WorkflowSession();
81   VCSessionRegistry::addDefaultVCSession($WFSession);
82 }
83
84 /**
85  * Handles configuration and "global" stuff.
86  */
87 class WorkflowSession extends VCSession {
88   var $_confPath = "/home/schmitt/local-cvs/midgard-current/workflow/workflow.xml";
89   var $_confDoc;
90   var $_confCtx;
91   var $_mgdPaths;
92   var $_mgdUser;
93   var $_mgdUserGuid;
94   var $_mgdGroups;
95  
96   /**
97    * Construct a WorkflowSession
98    * @param string $arrname
99    * @see VCSession
100    */
101   function WorkflowSession ($arrname = 'WFSessionConf') {
102     $this->VCSession();
103     global $$arrname;
104     extract($$arrname);
105     global $midgard;
106     PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handle_pear_error');
107     $mgdGroups = mgd_list_memberships($midgard->user);
108     while ($mgdGroups and $mgdGroups->fetch()) {
109       $this->_mgdGroups[] = preg_replace('/\/lang:.*/','',mgd_get_path(mgd_get_group($mgdGroups->gid)));
110     }
111     $user = mgd_get_person($midgard->user);
112     $userPath = mgd_get_path($user);
113     $this->_mgdUser = preg_replace('/\/lang:.*/','',$userPath);
114     $this->_mgdUserGuid = $user->guid();
115     $this->_confPath = $wfconf['confpath'];
116     $conf = file_get_contents ($this->_confPath);
117     $this->_confDoc = xmldoc($conf);
118     $this->_confCtx = xpath_new_context($this->_confDoc);
119   }
120  
121   function &getVCSession($branch) {
122     global $VCSConf;
123     if (isset($VCSConf[$branch])) {
124       $vcs = &new VCSession(&$VCSConf[$branch]);
125       return $vcs;
126     } else {
127       trigger_error("Branch $branch not configured", E_USER_WARNING);
128       return MGD_ERR_ERROR;
129     }
130   }
131
132   /**
133    * Get the matching MgdPath for the given path, i.e. the "range" defined in workflow configuration.
134    * @param int $destId the id of the destination branch
135    * @param string $path
136    * @return MgdPath $mgdPath
137    */
138   function &getMgdPath($destId, $path) {
139     $mgdPaths = $this->listMgdPaths($destId);
140     if (!is_array($mgdPaths)) {
141       trigger_error("No matching path in workflow configuration", E_USER_NOTICE);
142       return false;
143     }
144     usort($mgdPaths, 'mgd_path_child_cmp');
145     foreach($mgdPaths as $mgdPath) {
146       if (mgd_path_is_child_of($mgdPath->path, $path) > -1 and mgd_path_fits($mgdPath->path, $path)) {
147     return $mgdPath;
148       }
149     }
150   }
151  
152   /**
153    * List all MgdPaths defined for the source branch = current branch
154    * @return array $mgdPaths
155    */
156   function listMgdPaths($destId) {
157     if (!is_numeric($destId)) {
158       trigger_error("param 1 $destId must be numeric", E_USER_WARNING);
159       return MGD_ERR_ERROR;
160     }
161     if (!$this->_mgdPaths[$destId]) {
162       $mgdPaths = xpath_eval_expression($this->_confCtx, '//object');
163       $list = array();
164       if ($mgdPaths) {
165     foreach ($mgdPaths->nodeset as $mgdPathNode) {
166       $mgdPath = &new MgdPath($this, $mgdPathNode->get_attribute('id'));
167       if ($mgdPath->src == $this->_branch->getName() and $mgdPath->dest == $this->getBranchLabel($destId)) {
168         $this->_mgdPaths[$destId][] = &$mgdPath;
169       }
170     }
171       }
172     }
173     return $this->_mgdPaths[$destId];
174   }
175
176
177   /**
178    * Checks permission for the current user to do something with the StepDef
179    * @param int $stepDefId the id of the StepDef
180    * @param bool $auto (optional) if auto or normal permissions should be checked
181    * @return bool $ok
182    */
183   function mayDoStepDef($stepDefId, $auto = false) {
184     if (DEBUG) echo "stepDefId: $stepDefId<br>";
185     $stepdef = new StepDef($this, $stepDefId);
186     if ($auto) {
187       if ($stepdef->type == 'auto') {
188     if (DEBUG) echo "true!";
189     return true;
190       }
191     } else {
192       if ($stepdef->type == 'auto') {
193     return false;
194       }
195       if (!is_array($stepdef->subStepDefs) and !$stepdef->subStepDefs[0]) {
196     return true;
197       }
198       foreach ($stepdef->subStepDefs as $subStepDef_id) {
199     $subStepDef = new SubStepDef($this, $subStepDef_id);
200     if ($subStepDef->user_group == 'group') {
201       if (array_search($subStepDef->path, $this->_mgdGroups) !== false)  {
202         return true;
203       }
204     } else {
205       if (DEBUG) echo $this->_mgdUser . " " . $subStepDef->path . "<br>";
206       if ($this->_mgdUser == $subStepDef->path) {
207         return true;
208       }       
209     }
210       }
211     }
212     return false;
213   }
214  
215     
216   /**
217    * Lists all pending WorkflowProcs for the current user.
218    * @return array $pendingProcIds
219    */
220   function listPendingProcs() {
221     $stepDefs = array();
222     $users = xpath_eval_expression($this->_confCtx, '//user');
223     if (isset($users->nodeset)) {
224       foreach ($users->nodeset as $user) {
225     if ($user->get_attribute('path') == $this->_mgdUser) {
226       $step = $user->parent_node();
227       $stepDefs[] = $step->get_attribute('id');
228     }
229       }
230     }
231     $groups = xpath_eval_expression($this->_confCtx, '//group');
232     if (isset($groups->nodeset)) {
233       foreach ($groups->nodeset as $group) {
234     if (array_search($group->get_attribute('path'), $this->_mgdGroups) !== false) {
235       $step = $group->parent_node();
236       $stepDefs[] = $step->get_attribute('id');
237     }
238       }
239     }
240     if (!$stepDefs) {
241       return false;
242     }
243     return $this->_glob_dbh->getCol('SELECT id FROM proc WHERE step_def_id IN (' . implode(',', $stepDefs) . ')');
244   }
245
246  
247   /**
248    * Do stuff as another user. Only root may do that
249    * @param int $newuser the id of the person
250    */
251   function su($newuser) {
252     if (isRoot()) {
253       $mgdGroups = mgd_list_memberships($newuser);
254       while ($mgdGroups and $mgdGroups->fetch()) {
255     $this->_mgdGroups[] = mgd_get_path(mgd_get_group($mgdGroups->gid));
256       }
257       $user = mgd_get_person($newuser);
258       $this->_mgdUser = mgd_get_path($user);
259       $this->_mgdUserGuid = $user->guid();
260       return true;
261     } else {
262       return MGD_ERR_ACCESS_DENIED;
263     }
264   }
265  
266   function getBranchLabel($id) {
267     foreach($GLOBALS['VCSConf'] as $key => $conf) {
268       if (isset($conf['vcconf']['cvsbranchid']) and $conf['vcconf']['cvsbranchid'] == $id) {
269     return $key;
270       }
271     }
272     trigger_error("No such branch", E_USER_NOTICE);
273     return MGD_ERR_ERROR;
274     /*
275     return $this->_glob_dbh->getOne("SELECT label FROM branch WHERE id = $id");
276     */
277   }
278
279   function getUserPath() {
280     return $this->_mgdUser;
281   }
282
283 }
284
285 /**
286  * Base class.
287  */
288 class WorkflowDefObject  {
289   var $ctx;
290   var $xmlsnipp;
291   var $node;
292   var $session;
293   var $id;
294
295   function WorkflowDefObject(&$session, $id) {
296     $this->session = &$session;
297     $this->id = $id;
298     $my_result = xpath_eval_expression($session->_confCtx, "//*[@id='" . $id . "']");
299     if (!isset($my_result->nodeset) or !isset($my_result->nodeset[0])) {
300       trigger_error("Node not found: $id", E_USER_WARNING);
301       $this = MGD_ERR_ERROR;
302       return;
303     }
304     $this->node = $my_result->nodeset[0];
305     $this->xmlsnipp = $session->_confDoc->dump_node($this->node);
306     $doc = xmldoc($this->xmlsnipp);
307     $this->ctx = xpath_new_context($doc);
308   }
309
310 }
311
312 /**
313  * Represents an <object> in the XML config file.
314  */
315 class MgdPath extends WorkflowDefObject {
316   var $procDefs;
317   var $path;
318   var $src;
319   var $dest;
320   function MgdPath(&$session, $id) {
321     $this->WorkflowDefObject($session, $id);
322     $this->path = $this->node->get_attribute('path');
323     $parent = $this->node->parent_node();
324     $this->src = $parent->get_attribute('src');
325     $this->dest = $parent->get_attribute('dest');
326     $child = $this->node->first_child();
327     while($child) {
328       if (is_a($child, 'DomElement')) {
329     $this->procDefs[] = $child->get_attribute('procdef');
330       }
331       $child = $child->next_sibling();
332     }
333   }
334
335   function listProcDefs($auto = false) {
336     $list = array();
337     foreach ($this->procDefs as $procDefId) {
338       $procDef = new ProcDef($this->session, $procDefId);
339       if ($this->session->mayDoStepDef($procDef->stepDefs[0], $auto)) {
340     $list[] = $procDef->id;
341       }
342     }
343     return $list;
344   }
345  
346 }
347 /**
348  * Represents a <procdef> in the XML config file.
349  */
350 class ProcDef extends WorkflowDefObject {
351   var $stepDefs;
352   function ProcDef(&$session, $id) {
353     $this->WorkflowDefObject($session, $id);
354     $child = $this->node->first_child();
355     while($child) {
356       if (is_a($child, 'DomElement')) {
357     $this->stepDefs[] = $child->get_attribute('id');
358       }
359       $child = $child->next_sibling();
360     }
361   }
362 }
363
364 /**
365  * Represents a <step> in the XML config file.
366  */
367 class StepDef extends WorkflowDefObject {
368   var $subStepDefs;
369   var $op;
370   var $type;
371   function StepDef(&$session, $id) {
372     $this->WorkflowDefObject($session, $id);
373     $this->op = $this->node->get_attribute('operator');
374     $this->type = $this->node->get_attribute('type');
375     $child = $this->node->first_child();
376     while($child) {
377       if (is_a($child, 'DomElement')) {
378     $this->subStepDefs[] = $child->get_attribute('id');
379       }
380       $child = $child->next_sibling();
381     }
382   }
383
384 }
385
386 /**
387  * Represents a <user> / <group> in the XML config file.
388  */
389 class SubStepDef extends WorkflowDefObject {
390   var $path;
391   var $user_group;
392   function SubStepDef(&$session, $id) {
393     $this->WorkflowDefObject($session, $id);
394     $this->path = $this->node->get_attribute('path');
395     $this->user_group = $this->node->node_name();
396   }
397 }
398  
399 /**
400  * Represents a Workflow process.
401  */
402 class WorkflowProc extends MgdVCObject {
403   var $_collectionId;
404   var $_stepDefId;
405   var $_procDefId;
406   var $_destId;
407   var $_closed;
408   var $_table = WF_PROC_TABLE;
409  
410    /**
411    * Construct a new WorkflowProc
412    * @param array $db the new contents of the fields, name => value
413    * @param object $wfs the WorkflowSession (optional) omit if you want to use the default WorkflowSession
414    * @return object $wfProc
415    */
416   function WorkflowProc(&$db, $wfs = 0) {
417     $this->MgdVCObject(&$wfs);
418     $this->_db = &$db;
419     $this->_collectionId = &$this->_db['collection_id'];
420     $this->_stepDefId = &$this->_db['step_def_id'];
421     $this->_procDefId = &$this->_db['proc_def_id'];
422     $this->_destId = &$this->_db['dest_id'];
423     $this->_srcId = &$this->_db['src_id'];
424     $this->_status = &$this->_db['status'];
425     $this->_id = &$this->_db['id'];
426     $procDef = new ProcDef($this->_vcs, $this->_db['proc_def_id']);
427     if ($db['dest_id'] == $db['src_id']) {
428       trigger_error("Source and destination branch are the same", E_USER_WARNING);
429       return MGD_ERR_ERROR;
430     }
431     if (!is_array($procDef->stepDefs)) {
432       trigger_error("Invalid procdef: No steps", E_USER_WARNING);
433       $this = MGD_ERR_ERROR;
434       return;
435     }
436     return;
437   }
438
439   /**
440    * Static method
441    * Start a new WorkflowProc
442    * @param int $procDef id of the procdef / id of the proc
443    * @param int $destId: the id of the dest branch
444    * @param string $spec: guid or path
445    * @param array $mode @see MgdDeepCollection
446    * @param object $wfs the WorkflowSession (optional) omit if you want to use the default WorkflowSession
447    * @return object $wfProc
448    */
449   function &brew($procDefId, $destId, $spec, $mode = 0, $wfs = 0) {
450     MgdVCObject::realVcs($wfs);
451     $db['src_id'] = $wfs->getBranchId();
452     $db['dest_id'] = $destId;
453     $db['proc_def_id'] = $procDefId;
454     $coll = MgdDeepCollection::brew($spec, $mode, &$wfs);
455     if (!is_a($coll, 'MgdCollection')) {
456       trigger_error("Can't construct the MgdCollection", E_USER_WARNING);
457       return MGD_ERR_ERROR;
458     }
459     $parts_list = $coll->flatListElements();
460     foreach ($parts_list as $part) {
461       echo ($part->getPath() . " is part of collection<br>");
462       $mgdPart = MgdPart::readByGuid($part->getGuid(), &$wfs);
463       $partpath = $mgdPart->getPath();
464       $mgdPath = $wfs->getMgdPath($db['dest_id'], $partpath);
465       echo $procDefId;
466       if (!in_array($procDefId, $mgdPath->procDefs)) {
467     trigger_error("Proc cannot be started on object $partpath", E_USER_WARNING);
468     return MGD_ERR_ERROR;
469       }
470     }
471     $coll->write();
472     $db['collection_id'] = $coll->getId();
473     $db['status'] = WF_NEEDS_APPROVE;
474     $ret = &new WorkflowProc($db, &$wfs);
475     $ret->writeDB();
476     $procDef = new ProcDef($wfs, $procDefId);
477     $stepDef = new StepDef($wfs, $procDef->stepDefs[0]);
478     if ($stepDef->type == 'auto') {
479       if (isset($procDef->stepDefs[1])) {
480     $ret->addComment(COMMENT_OK . $this->_vcs->getUserPath());
481     $ret->_prepare_step($procDef->stepDefs[1]);
482       } else {
483     $ret->addComment(($stepDef->type == "auto")?(COMMENT_AUTO_PUBLISHED):(COMMENT_PUBLISHED . $this->_vcs->getUserPath()));
484     $ret->_setStatus(WF_TO_BE_PUBLISHED);
485     $wfs->notifyStatusHandler();
486       }
487     } else {
488       $ret->_prepare_step($procDef->stepDefs[0]);
489     }
490     return $ret;
491   }
492
493   /**
494    * Static method
495    * Construct a WorkflowProc from the DB by id
496    * @param int $id
497    * @param object $wfs (optional) omit if you want to use the default WorkflowSession
498    * @return object $wfProc
499    */
500   function &readById($id, $vcs = 0) {
501     MgdVCObject::realVcs($vcs);
502     $db = DBObject::readDBById($vcs->getGlobDbh(), WF_PROC_TABLE, $id);
503     $ret = &new WorkflowProc(&$db, $vcs);
504     return $ret;
505   }