source: contrib/ProjectManager/inc/class.boprojectelements.inc.php @ 3594

Revision 3594, 24.4 KB checked in by afernandes, 13 years ago (diff)

Ticket #1416 - Disponibilizado o módulo ProjectManager? para a comunidade

  • Property svn:executable set to *
Line 
1<?php
2/**
3 * ProjectManager - Elements business object
4 *
5 * @link http://www.egroupware.org
6 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7 * @package projectmanager
8 * @copyright (c) 2005/6 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10 * @version $Id: class.boprojectelements.inc.php 23913 2007-05-22 04:04:13Z lkneschke $
11 */
12
13include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.soprojectelements.inc.php');
14
15/**
16 * Elements business object of the projectmanager
17 */
18class boprojectelements extends soprojectelements
19{
20        /**
21         * Debuglevel: 0 = no debug-messages, 1 = main, 2 = more, 3 = all, 4 = all incl. so_sql, or string with function-name to debug
22         *
23         * @var int/string
24         */
25        var $debug=false;
26        /**
27         * Instance of the link-class
28         *
29         * @var bolink
30         */
31        var $link;
32        /**
33         * Instance of the boprojectmanager-class
34         *
35         * @var boprojectmanager
36         */
37        var $project;
38        /**
39         * Summary information of the current project
40         *
41         * @var array
42         */
43        var $project_summary;
44        /**
45         * Instance of the soconstraints-class
46         *
47         * @var soconstraints
48         */
49        var $constraints;
50        /**
51         * Instance of the somilestones-class
52         *
53         * @var somilestones
54         */
55        var $milestones;
56        /**
57         * Instances of the different datasources
58         *
59         * @var array
60         */
61        var $datasources = array();
62        /**
63         * Timestaps that need to be adjusted to user-time on reading or saving
64         *
65         * @var array
66         */
67        var $timestamps = array(
68                'pe_synced','pe_modified','pe_planned_start','pe_real_start','pe_planned_end','pe_real_end',
69        );
70        /**
71         * Offset in secconds between user and server-time,     it need to be add to a server-time to get the user-time
72         * or substracted from a user-time to get the server-time
73         *
74         * @var int
75         */
76        var $tz_offset_s;
77        /**
78         * Current time as timestamp in user-time
79         *
80         * @var int
81         */
82        var $now_su;
83        /**
84         * Translates filter-values to allowed stati
85         *
86         * @var array
87         */
88        var $status_filter = array(
89                'all'     => false,
90                'used'    => array('new','regular'),
91                'new'     => 'new',
92                'ignored' => 'ignore',
93        );
94        /**
95         * Or'ed id's of the values set by the last call to the updated method
96         *
97         * @var int
98         */
99        var $updated = 0;
100
101        /**
102         * Constructor, class the constructor of the extended class
103         *
104         * @param int $pm_id pm_id of the project to use, default null
105         * @param int $pe_id pe_id of the project-element to load, default null
106         * @return boprojectelements
107         */
108        function boprojectelements($pm_id=null,$pe_id=null)
109        {
110                if (!is_object($GLOBALS['phpgw']->datetime))
111                {
112                        $GLOBALS['phpgw']->datetime =& CreateObject('phpgwapi.datetime');
113                }
114                $this->tz_offset_s = $GLOBALS['phpgw']->datetime->tz_offset;
115                $this->now_su = time() + $this->tz_offset_s;
116               
117                $this->soprojectelements($pm_id,$pe_id);
118               
119                if (!is_object($GLOBALS['phpgw']->link))
120                {
121                        $GLOBALS['phpgw']->link =& CreateObject('phpgwapi.bolink');
122                }
123                $this->link =& $GLOBALS['phpgw']->link;
124                $this->links_table = $this->link->link_table;
125
126                $this->project =& CreateObject('projectmanager.boprojectmanager',$pm_id);
127                $this->config =& $this->project->config;
128               
129                $this->project->instanciate('constraints,milestones');
130                $this->constraints =& $this->project->constraints;
131                $this->milestones  =& $this->project->milestones;
132               
133                $this->project_summary = $this->summary();
134               
135                if ((int)$this->debug >= 3 || $this->debug == 'boprojectelements')
136                {
137                        $this->debug_message(function_backtrace()."\nboprojectelements::boprojectelements($pm_id,$pe_id) data=".print_r($this->data,true));
138                }
139                // save us in $GLOBALS['boprojectselements'] for ExecMethod used in hooks
140                if (!is_object($GLOBALS['boprojectselements']))
141                {
142                        $GLOBALS['boprojectselements'] =& $this;
143                }
144        }
145       
146        /**
147         * receives notifications from the link-class: new, deleted links to pm entries, or updated content of linked entries
148         *
149         * We only process link- & update-notifications to parent-projects!
150         * A project P is the parent of an other project C, if link_id1=P.pm_id and link_id2=C.pm_id !
151         *
152         * @param array $data array with keys type, id, target_app, target_id, link_id, data
153         */
154        function notify($data)
155        {
156                if ((int) $this->debug >= 2 || $this->debug == 'notify') $this->debug_message("boprojectelements::notify(link_id=$data[link_id], type=$data[type], target=$data[target_app]-$data[target_id])");
157
158                switch($data['type'])
159                {
160                        case 'link':
161                        case 'update':
162                                // for projectmanager we need to check the direction of the link
163                                if ($data['target_app'] == 'projectmanager')
164                                {
165                                        $link = $this->link->get_link($data['link_id']);
166                                        if ($link['link_id2'] == $data['id'])
167                                        {
168                                                return; // this is a notification to a child / subproject --> ignore it
169                                        }
170                                        // for new links we need to make sure the new child is not an ancestor of us
171                                        if ($data['type'] == 'link')
172                                        {
173                                                if (($ancestors = $this->project->ancestors($data['id'])) && in_array($data['target_id'],$ancestors))
174                                                {
175                                                        if ((int) $this->debug >= 2 || $this->debug == 'notify') $this->debug_message("boprojectelements::notify: cant use pm_id=$data[target_id] as child as it's one of our (pm_id=$data[id]) ancestors=".print_r($ancestors,true));
176                                                        return; // the link is not used as an project-element, thought it's still a regular link
177                                                }
178                                                if ((int) $this->debug >= 3 || $this->debug == 'notify') $this->debug_message("boprojectelements::notify: ancestors($data[id])=".print_r($ancestors,true));
179                                        }
180                                }
181                                $this->update($data['target_app'],$data['target_id'],$data['link_id'],$data['id']);
182                                break;
183
184                        case 'unlink':
185                                $this->delete(array('pm_id' => $data['id'],'pe_id' => $data['link_id']));
186                                break;
187                               
188                }
189        }
190
191        /**
192         * Updates / creates a project-element with the data of it's datasource
193         *
194         * Sets additionally $this->updated with the or'ed id's of the updated values
195         *
196         * ToDo: if end-date changed, update elements which have "us" as start-constrain
197         *
198         * @param string $app appname
199         * @param string $id id of $app as used by the link-class and the datasource
200         * @param int $pe_id=0 element- / link-id or 0 to only read and return the entry, but not save it!
201         * @param int $pm_id=null project-id, default $this->pm_id
202         * @param boolean $update_project=true update the data in the project if necessary
203         * @return array/boolean the updated project-element or false on error (eg. no read access)
204         */
205        function &update($app,$id,$pe_id=0,$pm_id=null,$update_project=true)
206        {
207                if (!$pm_id) $pm_id = $this->pm_id;
208               
209                if ((int) $this->debug >= 2 || $this->debug == 'update') $this->debug_message("boprojectelements::update(app='$app',id='$id',pe_id=$pe_id,pm_id=$pm_id)");
210
211                if (!$app || !$id || !(int) $pm_id)
212                {
213                        return false;
214                }
215                $this->init();
216                $need_save_anyway = false;
217                // check if entry already exists and set basic values if not
218                if (!$pe_id || ($need_save_anyway = !$this->read(array('pm_id'=>$pm_id,'pe_id'=>$pe_id))))
219                {
220                        $this->data['pm_id'] = $pm_id;
221                        $this->data['pe_id'] = $pe_id;
222                        $this->data['pe_overwrite'] = 0;                // none set so far
223                       
224                        // only set status if it's not set by the datasource
225                        if (!isset($this->data['pe_status']))
226                        {
227                                $this->data['pe_status']= 'new';
228                        }
229                        // if user linking has no ADD rights, the entry is set to ignored
230                        if (!$this->check_acl(PHPGW_ACL_ADD,array('pm_id'=>$pm_id)))
231                        {
232                                $this->data['pe_status']= 'ignore';
233                        }
234                }
235                $datasource =& $this->datasource($app);
236                $this->updated = 0;
237
238                if (!($data = $datasource->read($id,$this->data)))
239                {
240                        return false;   // eg. no read access, so I cant update
241                }
242                foreach($data as $name => $value)
243                {
244                        if (isset($datasource->name2id[$name]) && !($this->data['pe_overwrite'] & $datasource->name2id[$name]) &&
245                                $this->data[$name] != $value)
246                        {
247                                //if ((int) $pe_id) echo "<p>boprojectelements::update($app,$id,$pe_id,$pm_id) $name updated: '{$this->data[$name]}' != '$value'</p>\n";
248                                $this->data[$name] = $value;
249                                $this->updated |= $datasource->name2id[$name];
250                        }
251                }
252                $this->data['pe_synced'] = $this->now_su;
253               
254                if((int) $pe_id && ($need_save_anyway || $this->updated))
255                {
256                        $this->save(null,false,$update_project ? $this->updated & ~PM_TITLE & ~PM_DETAILS & ~PM_RESOURCES : 0); // dont set modified, only synced
257                }
258                return $this->data;
259        }
260
261        /**
262         * sync all project-elements
263         *
264         * The sync of the elements is done by calling the update-method for each (not ignored) element
265         * in the order of their planned starts and after that calling the projects update methode only
266         * once if necessary!
267         *
268         * @param int $pm_id=null id of project to use, default null=use $this->pm_id
269         * @return int number of updated elements
270         */
271        function &sync_all($pm_id=null)
272        {
273                if (!is_array($GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited']))
274                {
275                        $GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'] = array();
276                }
277                if (!$pm_id && !($pm_id = $this->pm_id)) return 0;
278               
279                if ((int) $this->debug >= 2 || $this->debug == 'sync_all') $this->debug_message("boprojectelements::sync_all(pm_id=$pm_id)");
280
281                if ($GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id])        // project already visited
282                {
283                        if ((int) $this->debug >= 2 || $this->debug == 'sync_all') $this->debug_message("boprojectelements::sync_all(pm_id=$pm_id) stoped recursion, as pm_id in (".implode(',',array_keys($GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'])).")");
284                        return 0;                                                       // no further recursion, might lead to an infinit loop
285                }
286                $GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id] = true;
287               
288                $save_project = $this->project->data;
289               
290                $updated = $update_project = 0;
291                ++$GLOBALS['phpgw_info']['flags']['projectmanager']['pm_ds_ignore_elements'];
292                foreach((array) $this->search(array('pm_id'=>$pm_id,"pe_status != 'ignore'"),false,'pe_planned_start') as $data)
293                {
294                        $this->update($data['pe_app'],$data['pe_app_id'],$data['pe_id'],$pm_id,false);
295                       
296                        $update_project |= $this->updated & ~PM_TITLE;
297                        if ($this->updated) $updated++;
298                }
299                --$GLOBALS['phpgw_info']['flags']['projectmanager']['pm_ds_ignore_elements'];
300                if ($update_project)
301                {
302                        $this->project->update($pm_id,$update_project);
303                }
304                if ($this->project->data['pm_id'] != $save_project['pm_id']) $this->project->data =& $save_project;
305
306                unset($GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id]);
307
308                return $updated;
309        }
310
311        /**
312         * checks if the user has enough rights for a certain operation
313         *
314         * The rights on a project-element depend on the rigths on the parent-project:
315         *      - One can only read an element, if he can read the project (any rights, at least READ on the project)
316         *      - Adding, editing and deleting of elements require the ADD right of the project (deleting requires the element to exist pe_id!=0)
317         *      - reading or editing of budgets require the concerned rights of the project
318         *
319         * @param int $required PHPGW_ACL_READ, PHPGW_ACL_WRITE, PHPGW_ACL_ADD, PHPGW_ACL_DELETE, PHPGW_ACL_BUDGET or PHPGW_ACL_EDIT_BUDGET
320         * @param array/int $data=null project-element or pe_id to use, default the project-element in $this->data
321         * @return boolean true if the rights are ok, false if not
322         */
323        function check_acl($required,$data=0)
324        {
325                $pe_id = is_array($data) ? $data['pe_id'] : ($data ? $data : $this->data['pe_id']);
326                $pm_id = is_array($data) ? $data['pm_id'] : ($data ? 0 : $this->data['pm_id']);
327               
328                if (!$pe_id && (!$pm_id || $required == PHPGW_ACL_DELETE))
329                {
330                        return false;
331                }
332                if (!$pm_id)
333                {
334                        $data_backup =& $this->data; unset($this->data);
335                        $data =& $this->read($pe_id);
336                        $this->data =& $data_backup; unset($data_backup);
337               
338                        if (!$data) return false;       // not found ==> no rights
339                       
340                        $pm_id = $data['pm_id'];
341                }
342                if ($required == PHPGW_ACL_EDIT ||$required ==  PHPGW_ACL_DELETE)
343                {
344                        $required = PHPGW_ACL_ADD;      // edit or delete of elements is handled by the ADD right of the project
345                }
346                return $this->project->check_acl($required,$pm_id);
347        }
348       
349        /**
350         * Get reference to instance of the datasource used for $app
351         *
352         * The class has to be named datasource_$app and is search first in the App's inc-dir and then in the one of
353         * ProjectManager. If it's not found PM's datasource baseclass is used.
354         *
355         * @param string $app appname
356         * @return object
357         */
358        function &datasource($app)
359        {
360                if (!isset($this->datasources[$app]))
361                {               
362                        if (!file_exists($classfile = PHPGW_INCLUDE_ROOT.'/'.$app.'/inc/class.'.($class='datasource_'.$app).'.inc.php') &&
363                                !file_exists($classfile = PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.'.($class='datasource_'.$app).'.inc.php'))
364                        {
365                                $classfile = PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.'.($class='datasource').'.inc.php';
366                        }
367                        include_once($classfile);
368                        $this->datasources[$app] =& new $class($app);
369                        // make the project availible for the datasource
370                        $this->datasources[$app]->project =& $this->project;
371                }
372                return $this->datasources[$app];       
373        }
374
375        /**
376         * changes the data from the db-format to your work-format
377         *
378         * reimplemented to adjust the timezone of the timestamps (adding $this->tz_offset_s to get user-time)
379         * Please note, we do NOT call the method of the parent or so_sql !!!
380         *
381         * @param array $data if given works on that array and returns result, else works on internal data-array
382         * @return array with changed data
383         */
384        function db2data($data=null)
385        {
386                if (!is_array($data))
387                {
388                        $data = &$this->data;
389                }
390                foreach($this->timestamps as $name)
391                {
392                        if (isset($data[$name]) && $data[$name]) $data[$name] += $this->tz_offset_s;
393                }
394                if (is_numeric($data['pe_completion'])) $data['pe_completion'] .= '%';
395                if ($data['pe_app']) $data['pe_icon'] = $data['pe_app'].'/navbar';
396                if ($data['pe_resources']) $data['pe_resources'] = explode(',',$data['pe_resources']);
397
398                return $data;
399        }
400
401        /**
402         * changes the data from your work-format to the db-format
403         *
404         * reimplemented to adjust the timezone of the timestamps (subtraction $this->tz_offset_s to get server-time)
405         * Please note, we do NOT call the method of the parent or so_sql !!!
406         *
407         * @param array $data if given works on that array and returns result, else works on internal data-array
408         * @return array with changed data
409         */
410        function data2db($data=null)
411        {
412                if ($intern = !is_array($data))
413                {
414                        $data = &$this->data;
415                }
416                foreach($this->timestamps as $name)
417                {
418                        if (isset($data[$name]))
419                        {
420                                if ($data[$name])
421                                {
422                                        $data[$name] -= $this->tz_offset_s;
423                                }
424                                else
425                                {
426                                        $data[$name] = null;    // so it's not used for min or max dates
427                                }
428                        }
429                }
430                if (substr($data['pe_completion'],-1) == '%') $data['pe_completion'] = (int) substr($data['pe_completion'],0,-1);
431
432                if (is_array($data['pe_resources']))
433                {
434                        $data['pe_resources'] = count($data['pe_resources']) ? implode(',',$data['pe_resources']) : null;
435                }
436                return $data;
437        }
438       
439        /**
440         * saves an project-element, reimplemented from SO, to save the remark in the link, if $keys['update_remark']
441         *
442         * @param array $keys=null if given $keys are copied to data before saveing => allows a save as
443         * @param boolean $touch_modified=true should modification date+user be set, default yes
444         * @param int $update_project=-1 update the data in the project (or'ed PM_ id's), default -1=everything
445         * @return int 0 on success and errno != 0 else
446         */
447        function save($keys=null,$touch_modified=true,$update_project=-1)
448        {
449                if ((int) $this->debug >= 1 || $this->debug == 'save') $this->debug_message("boprojectelements::save(".print_r($keys,true).','.(int)$touch_modified.",$update_project) data=".print_r($this->data,true));
450
451                if ($keys['update_remark'] || $this->data['update_remark'])
452                {
453                        unset($keys['update_remark']);
454                        unset($this->data['update_remark']);
455                        $this->link->update_remark($this->data['pe_id'],$this->data['pe_remark']);
456                }
457                if ($keys) $this->data_merge($keys);
458               
459                if ($touch_modified || !$this->data['pe_modified'] || !$this->data['pe_modifier'])
460                {
461                        $this->data['pe_modified'] = $this->now_su;
462                        $this->data['pe_modifier'] = $GLOBALS['phpgw_info']['user']['account_id'];
463                }
464                if (!$this->data['pm_id']) $this->data['pm_id'] = $this->pm_id;
465
466                if (!($err = parent::save()))
467                {
468                        if (is_array($this->data['pe_constraints']))
469                        {
470                                $this->constraints->save(array(
471                                        'pm_id' => $this->data['pm_id'],
472                                        'pe_id' => $this->data['pe_id'],
473                                ) + $this->data['pe_constraints']);
474                        }
475                        if ($update_project)
476                        {
477                                $this->project->update($this->data['pm_id'],$update_project,$this->data);
478                        }
479                }
480                return $err;
481        }
482
483        /**
484         * deletes a project-element or all project-elements of a project, reimplemented to remove the link too
485         *
486         * @param array/int $keys if given array with pm_id and/or pe_id or just an integer pe_id
487         * @return int affected rows, should be 1 if ok, 0 if an error
488         */
489        function delete($keys=null)
490        {
491                if (!is_array($keys) && (int) $keys)
492                {
493                        $keys = array('pe_id' => (int) $keys);
494                }
495                if (!is_null($keys))
496                {
497                        $pm_id = $keys['pm_id'];
498                        $pe_id = $keys['pe_id'];
499                }
500                else
501                {
502                        $pe_id = $this->data['pe_id'];
503                        $pm_id = $this->data['pm_id'];
504                }
505                $ret = parent::delete($keys);
506               
507                if ($pe_id)
508                {
509                        // delete one link
510                        $this->link->unlink($pe_id);
511                        // update the project
512                        $this->project->update($pm_id);
513                       
514                        $this->constraints->delete(array('pe_id' => $pe_id));
515                }
516                elseif ($pm_id)
517                {
518                        // delete all links to project $pm_id
519                        $this->link->unlink(0,'projectmanager',$pm_id);
520                }       
521                return $ret;
522        }
523       
524        /**
525         * reads row matched by key and puts all cols in the data array, reimplemented to also read the constraints
526         *
527         * @param array $keys array with keys in form internalName => value, may be a scalar value if only one key
528         * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
529         * @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or
530         * @return array/boolean data if row could be retrived else False
531        */
532        function read($keys,$extra_cols='',$join=true)
533        {
534                if (!($data = parent::read($keys,$extra_cols,$join)))
535                {
536                        return false;
537                }
538                $this->data['pe_constraints'] = $this->constraints->read(array(
539                        'pm_id' => $this->data['pm_id'],
540                        'pe_id' => $this->data['pe_id'],
541                ));
542                return $this->data;
543        }
544       
545        /**
546         * reads the titles of all project-elements specified by $keys
547         *
548         * @param array $keys keys of elements to read, default empty = all of the project the class is instanciated for
549         * @return array with pe_id => lang(pe_app): pe_title pairs
550         */
551        function &titles($keys=array())
552        {
553                $titles = array();
554                foreach((array) $this->search(array(),'pe_id,pe_title','pe_app,pe_title','','',false,'AND',false,$keys) as $element)
555                {
556                        if ($element) $titles[$element['pe_id']] = lang($element['pe_app']).': '.$element['pe_title'];
557                }
558                return $titles;
559        }
560
561        /**
562         * echos a (preformatted / no-html) debug-message and evtl. log it to a file
563         *
564         * It uses the debug_message method of boprojectmanager
565         *
566         * @param string $msg
567         */
568        function debug_message($msg)
569        {
570                $this->project->debug_message($msg);
571        }
572
573        /**
574         * Copies the elementtree from an other project
575         *
576         * This is done by calling the copy method of the datasource (if existent) and then calling update with the (new) app_id
577         *
578         * @param int $source
579         * @return boolean true on success, false otherwise
580         */
581        function copytree($source)
582        {
583                if ((int) $this->debug >= 2 || $this->debug == 'copytree') $this->debug_message("boprojectelements::copytree($source) this->pm_id=$this->pm_id");
584
585                $elements =& $this->search(array('pm_id' => $source),false,'pe_planned_start');
586                if (!$elements) return true;
587               
588                foreach($elements as $element)
589                {
590                        $ds =& $this->datasource($element['pe_app']);
591                       
592                        if (method_exists($ds,'copy'))
593                        {
594                                if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("copying $element[pe_app]:$element[pe_app_id] $element[pe_title]");
595                                list($app_id,$link_id) = $ds->copy($element,$this->pm_id,$this->project->data);
596                        }
597                        else    // no copy method, we just link again with that entry
598                        {
599                                if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("linking $element[pe_app]:$element[pe_app_id] $element[pe_title]");
600                                $app_id = $element['pe_app_id'];
601                                $link_id = $this->link->link('projectmanager',$this->pm_id,$element['pe_app'],$app_id,$element['pe_remark'],0,0,1);
602                        }
603                        if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("calling update($element[pe_app],$app_id,$link_id,$this->pm_id,false);");
604
605                        if (!$app_id || !$link_id) continue;    // something went wrong, eg. element no longer exists
606
607                        $this->update($element['pe_app'],$app_id,$link_id,$this->pm_id,false);  // false=no update of project itself => done once at the end
608
609                        // copy evtl. overwriten content from the element
610                        if (($need_save = $element['pe_overwrite'] != 0))
611                        {
612                                foreach($ds->name2id as $name => $id)
613                                {
614                                        if ($element['pe_overwrite'] & $id)
615                                        {
616                                                $this->data[$name] = $element[$name];
617                                        }
618                                }
619                                $this->data['pe_overwrite'] = $element['pe_overwrite'];
620                        }
621                        // copy other element data
622                        foreach(array('pl_id','pe_cost_per_time','cat_id','pe_share','pe_status') as $name)
623                        {
624                                if ($name == 'pe_status' && $element['pe_status'] != 'ignore') continue;        // only copy ignored
625
626                                if ($this->data[$name] != $element[$name])
627                                {
628                                        $this->data[$name] = $element[$name];
629                                        $need_save = true;
630                                }
631                        }       
632                        if ($need_save) $this->save(null,true,false);
633                }
634                // now we do one update of our project
635                if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("calling project->update() this->pm_id=$this->pm_id");
636                $this->project->update();
637
638                return true;
639        }
640
641        /**
642         * Search elements
643         *
644         * Reimplemented to cumulate eg. timesheets in also included infologs, if $filter['cumulate'] is true.
645         *
646         * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
647         * @param boolean $only_keys True returns only keys, False returns all cols
648         * @param string $order_by fieldnames + {ASC|DESC} separated by colons ','
649         * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
650         * @param string $wildcard appended befor and after each criteria
651         * @param boolean $empty False=empty criteria are ignored in query, True=empty have to be empty in row
652         * @param string $op defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
653         * @param int/boolean $start if != false, return only maxmatch rows begining with start
654         * @param array $filter if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
655         * @param string/boolean $join=true default join with links-table or string as in so_sql
656         * @return array of matching rows (the row is an array of the cols) or False
657         */
658        function search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join=true)
659        {
660                if ($this->pm_id && (!isset($filter['pm_id']) || !$filter['pm_id']))
661                {
662                        $filter['pm_id'] = $this->pm_id;
663                }
664                if ($filter['cumulate'])
665                {
666                        $cumulate = array();
667                        foreach((array)$GLOBALS['phpgw']->hooks->process(array(
668                                'location' => 'pm_cumulate',
669                                'pm_id' => $filter['pm_id'],
670                        )) as $app => $data)
671                        {
672                                if (is_array($data)) $cumulate += $data;
673                        }
674                        if ($cumulate)
675                        {
676                                //echo "<p align=right>cumulate-filter: ".$this->db->expression($this->table_name,'NOT ',array('pe_id' => array_keys($cumulate)))."</p>\n";
677                                $filter[] = $this->db->expression($this->table_name,'NOT (',array('pe_id' => array_keys($cumulate)),')');
678                        }
679                }
680                $rows = parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join);
681               
682                if ($rows && $cumulate)
683                {
684                        // get the pe_id of all returned rows
685                        $row_pe_ids = array();
686                        foreach($rows as $k => $row)
687                        {
688                                $row_pe_ids[$k] = $row['pe_id'];
689                        }
690                        // get pe_id's of to cumulate entries which are in $rows
691                        $cumulate_in = array();
692                        foreach($cumulate as $pe_id => $data)
693                        {
694                                if (in_array($data['other_id'],$row_pe_ids))
695                                {
696                                        $cumulate_in[$pe_id] = $data['other_id'];
697                                }
698                        }
699                        if ($cumulate_in)       // do we have something (timesheets) to cumulate
700                        {
701                                foreach(parent::search(array('pe_id' => array_keys($cumulate_in)),false) as $to_cumulate)
702                                {
703                                        // get the row, where the entry cumulates
704                                        if (($k = array_search($cumulate_in[$to_cumulate['pe_id']],$row_pe_ids)) !== false)
705                                        {
706                                                //echo "kumulated in ".$rows[$k]['pe_title']; _debug_array($rows);
707                                                foreach(array('pe_planned_time','pe_used_time','pe_planned_budget','pe_used_budget') as $name)
708                                                {
709                                                        if ($to_cumulate[$name]) $rows[$k][$name] += $to_cumulate[$name];
710                                                }
711                                                //echo "-->"; _debug_array($rows);
712                                        }
713                                }
714                        }
715                }
716                return $rows;
717        }}
Note: See TracBrowser for help on using the repository browser.