PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i5h-3LL

HOME


5h-3LL 1.0
DIR: /srv/http/vyvoj.adent.cz/egroupware/pfi/projectmanager/inc
/srv/http/vyvoj.adent.cz/egroupware/pfi/projectmanager/inc/
Upload File:
Current File : /srv/http/vyvoj.adent.cz/egroupware/pfi/projectmanager/inc/class.boprojectelements.inc.php
<?php
/**
 * ProjectManager - Elements business object
 *
 * @link http://www.egroupware.org
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
 * @package projectmanager
 * @copyright (c) 2005/6 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @version $Id: class.boprojectelements.inc.php 23913 2007-05-22 04:04:13Z lkneschke $ 
 */

include_once(EGW_INCLUDE_ROOT.'/projectmanager/inc/class.soprojectelements.inc.php');

/**
 * Elements business object of the projectmanager
 */
class boprojectelements extends soprojectelements
{
	/**
	 * Debuglevel: 0 = no debug-messages, 1 = main, 2 = more, 3 = all, 4 = all incl. so_sql, or string with function-name to debug
	 * 
	 * @var int/string
	 */
	var $debug=false;
	/**
	 * Instance of the link-class
	 * 
	 * @var bolink
	 */
	var $link;
	/**
	 * Instance of the boprojectmanager-class
	 * 
	 * @var boprojectmanager
	 */
	var $project;
	/**
	 * Summary information of the current project
	 * 
	 * @var array
	 */
	var $project_summary;
	/**
	 * Instance of the soconstraints-class
	 * 
	 * @var soconstraints
	 */
	var $constraints;
	/**
	 * Instance of the somilestones-class
	 * 
	 * @var somilestones
	 */
	var $milestones;
	/**
	 * Instances of the different datasources
	 * 
	 * @var array
	 */
	var $datasources = array();
	/**
	 * Timestaps that need to be adjusted to user-time on reading or saving
	 * 
	 * @var array
	 */
	var $timestamps = array(
		'pe_synced','pe_modified','pe_planned_start','pe_real_start','pe_planned_end','pe_real_end',
	);
	/**
	 * Offset in secconds between user and server-time,	it need to be add to a server-time to get the user-time 
	 * or substracted from a user-time to get the server-time
	 * 
	 * @var int
	 */
	var $tz_offset_s;
	/**
	 * Current time as timestamp in user-time
	 * 
	 * @var int
	 */
	var $now_su;
	/**
	 * Translates filter-values to allowed stati
	 * 
	 * @var array
	 */
	var $status_filter = array(
		'all'     => false,
		'used'    => array('new','regular'),
		'new'     => 'new',
		'ignored' => 'ignore',
	);
	/**
	 * Or'ed id's of the values set by the last call to the updated method
	 * 
	 * @var int
	 */
	var $updated = 0;

	/**
	 * Constructor, class the constructor of the extended class
	 * 
	 * @param int $pm_id pm_id of the project to use, default null
	 * @param int $pe_id pe_id of the project-element to load, default null
	 * @return boprojectelements
	 */
	function boprojectelements($pm_id=null,$pe_id=null)
	{
		if (!is_object($GLOBALS['egw']->datetime))
		{
			$GLOBALS['egw']->datetime =& CreateObject('phpgwapi.datetime');
		}
		$this->tz_offset_s = $GLOBALS['egw']->datetime->tz_offset;
		$this->now_su = time() + $this->tz_offset_s;
		
		$this->soprojectelements($pm_id,$pe_id);
		
		if (!is_object($GLOBALS['egw']->link))
		{
			$GLOBALS['egw']->link =& CreateObject('phpgwapi.bolink');
		}
		$this->link =& $GLOBALS['egw']->link;
		$this->links_table = $this->link->link_table;

		$this->project =& CreateObject('projectmanager.boprojectmanager',$pm_id);
		$this->config =& $this->project->config;
		
		$this->project->instanciate('constraints,milestones');
		$this->constraints =& $this->project->constraints;
		$this->milestones  =& $this->project->milestones;
		
		$this->project_summary = $this->summary();
		
		if ((int)$this->debug >= 3 || $this->debug == 'boprojectelements')
		{
			$this->debug_message(function_backtrace()."\nboprojectelements::boprojectelements($pm_id,$pe_id) data=".print_r($this->data,true));
		}
		// save us in $GLOBALS['boprojectselements'] for ExecMethod used in hooks
		if (!is_object($GLOBALS['boprojectselements']))
		{
			$GLOBALS['boprojectselements'] =& $this;
		}
	}
	
	/**
	 * receives notifications from the link-class: new, deleted links to pm entries, or updated content of linked entries
	 *
	 * We only process link- & update-notifications to parent-projects!
	 * A project P is the parent of an other project C, if link_id1=P.pm_id and link_id2=C.pm_id !
	 *
	 * @param array $data array with keys type, id, target_app, target_id, link_id, data
	 */
	function notify($data)
	{
		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])");

		switch($data['type'])
		{
			case 'link':
			case 'update':
				// for projectmanager we need to check the direction of the link
				if ($data['target_app'] == 'projectmanager')
				{
					$link = $this->link->get_link($data['link_id']);
					if ($link['link_id2'] == $data['id'])
					{
						return;	// this is a notification to a child / subproject --> ignore it
					}
					// for new links we need to make sure the new child is not an ancestor of us
					if ($data['type'] == 'link')
					{
						if (($ancestors = $this->project->ancestors($data['id'])) && in_array($data['target_id'],$ancestors))
						{
							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));
							return;	// the link is not used as an project-element, thought it's still a regular link
						}
						if ((int) $this->debug >= 3 || $this->debug == 'notify') $this->debug_message("boprojectelements::notify: ancestors($data[id])=".print_r($ancestors,true));
					}
				}
				$this->update($data['target_app'],$data['target_id'],$data['link_id'],$data['id']);
				break;

			case 'unlink':
				$this->delete(array('pm_id' => $data['id'],'pe_id' => $data['link_id']));
				break;
				
		}
	}

	/**
	 * Updates / creates a project-element with the data of it's datasource
	 *
	 * Sets additionally $this->updated with the or'ed id's of the updated values
	 *
	 * ToDo: if end-date changed, update elements which have "us" as start-constrain
	 *
	 * @param string $app appname
	 * @param string $id id of $app as used by the link-class and the datasource
	 * @param int $pe_id=0 element- / link-id or 0 to only read and return the entry, but not save it!
	 * @param int $pm_id=null project-id, default $this->pm_id
	 * @param boolean $update_project=true update the data in the project if necessary
	 * @return array/boolean the updated project-element or false on error (eg. no read access)
	 */
	function &update($app,$id,$pe_id=0,$pm_id=null,$update_project=true)
	{
		if (!$pm_id) $pm_id = $this->pm_id;
		
		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)");

		if (!$app || !$id || !(int) $pm_id)
		{
			return false;
		}
		$this->init();
		$need_save_anyway = false;
		// check if entry already exists and set basic values if not
		if (!$pe_id || ($need_save_anyway = !$this->read(array('pm_id'=>$pm_id,'pe_id'=>$pe_id))))
		{
			$this->data['pm_id'] = $pm_id;
			$this->data['pe_id'] = $pe_id;
			$this->data['pe_overwrite'] = 0;		// none set so far
			
			// only set status if it's not set by the datasource
			if (!isset($this->data['pe_status'])) 
			{
				$this->data['pe_status']= 'new';
			}
			// if user linking has no ADD rights, the entry is set to ignored
			if (!$this->check_acl(EGW_ACL_ADD,array('pm_id'=>$pm_id)))
			{
				$this->data['pe_status']= 'ignore';
			}
		}
		$datasource =& $this->datasource($app);
		$this->updated = 0;

		if (!($data = $datasource->read($id,$this->data)))
		{
			return false;	// eg. no read access, so I cant update
		}
		foreach($data as $name => $value)
		{
			if (isset($datasource->name2id[$name]) && !($this->data['pe_overwrite'] & $datasource->name2id[$name]) &&
				$this->data[$name] != $value)
			{
				//if ((int) $pe_id) echo "<p>boprojectelements::update($app,$id,$pe_id,$pm_id) $name updated: '{$this->data[$name]}' != '$value'</p>\n";
				$this->data[$name] = $value;
				$this->updated |= $datasource->name2id[$name];
			}
		}
		$this->data['pe_synced'] = $this->now_su;
		
		if((int) $pe_id && ($need_save_anyway || $this->updated))
		{
			$this->save(null,false,$update_project ? $this->updated & ~PM_TITLE & ~PM_DETAILS & ~PM_RESOURCES : 0);	// dont set modified, only synced
		}
		return $this->data;
	}

	/**
	 * sync all project-elements
	 *
	 * The sync of the elements is done by calling the update-method for each (not ignored) element
	 * in the order of their planned starts and after that calling the projects update methode only 
	 * once if necessary!
	 *
	 * @param int $pm_id=null id of project to use, default null=use $this->pm_id
	 * @return int number of updated elements
	 */
	function &sync_all($pm_id=null)
	{
		if (!is_array($GLOBALS['egw_info']['flags']['projectmanager']['sync_all_pm_id_visited']))
		{
			$GLOBALS['egw_info']['flags']['projectmanager']['sync_all_pm_id_visited'] = array();
		}
		if (!$pm_id && !($pm_id = $this->pm_id)) return 0;
		
		if ((int) $this->debug >= 2 || $this->debug == 'sync_all') $this->debug_message("boprojectelements::sync_all(pm_id=$pm_id)");

		if ($GLOBALS['egw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id])	// project already visited
		{
			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['egw_info']['flags']['projectmanager']['sync_all_pm_id_visited'])).")");
			return 0;							// no further recursion, might lead to an infinit loop
		}
		$GLOBALS['egw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id] = true;
		
		$save_project = $this->project->data;
		
		$updated = $update_project = 0;
		++$GLOBALS['egw_info']['flags']['projectmanager']['pm_ds_ignore_elements'];
		foreach((array) $this->search(array('pm_id'=>$pm_id,"pe_status != 'ignore'"),false,'pe_planned_start') as $data)
		{
			$this->update($data['pe_app'],$data['pe_app_id'],$data['pe_id'],$pm_id,false);
			
			$update_project |= $this->updated & ~PM_TITLE;
			if ($this->updated) $updated++;
		}
		--$GLOBALS['egw_info']['flags']['projectmanager']['pm_ds_ignore_elements'];
		if ($update_project)
		{
			$this->project->update($pm_id,$update_project);
		}
		if ($this->project->data['pm_id'] != $save_project['pm_id']) $this->project->data =& $save_project;

		unset($GLOBALS['egw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id]);

		return $updated;
	}

	/**
	 * checks if the user has enough rights for a certain operation
	 *
	 * The rights on a project-element depend on the rigths on the parent-project:
	 *	- One can only read an element, if he can read the project (any rights, at least READ on the project)
	 *	- Adding, editing and deleting of elements require the ADD right of the project (deleting requires the element to exist pe_id!=0)
	 *	- reading or editing of budgets require the concerned rights of the project
	 *
	 * @param int $required EGW_ACL_READ, EGW_ACL_WRITE, EGW_ACL_ADD, EGW_ACL_DELETE, EGW_ACL_BUDGET or EGW_ACL_EDIT_BUDGET
	 * @param array/int $data=null project-element or pe_id to use, default the project-element in $this->data
	 * @return boolean true if the rights are ok, false if not
	 */
	function check_acl($required,$data=0)
	{
		$pe_id = is_array($data) ? $data['pe_id'] : ($data ? $data : $this->data['pe_id']);
		$pm_id = is_array($data) ? $data['pm_id'] : ($data ? 0 : $this->data['pm_id']);
		
		if (!$pe_id && (!$pm_id || $required == EGW_ACL_DELETE))
		{
			return false;
		}
		if (!$pm_id)
		{
			$data_backup =& $this->data; unset($this->data);
			$data =& $this->read($pe_id);
			$this->data =& $data_backup; unset($data_backup);
		
			if (!$data) return false;	// not found ==> no rights
			
			$pm_id = $data['pm_id'];
		}
		if ($required == EGW_ACL_EDIT ||$required ==  EGW_ACL_DELETE)
		{
			$required = EGW_ACL_ADD;	// edit or delete of elements is handled by the ADD right of the project
		}
		return $this->project->check_acl($required,$pm_id);
	}
	
	/**
	 * Get reference to instance of the datasource used for $app
	 *
	 * The class has to be named datasource_$app and is search first in the App's inc-dir and then in the one of 
	 * ProjectManager. If it's not found PM's datasource baseclass is used.
	 *
	 * @param string $app appname
	 * @return object
	 */
	function &datasource($app)
	{
		if (!isset($this->datasources[$app]))
		{		
			if (!file_exists($classfile = EGW_INCLUDE_ROOT.'/'.$app.'/inc/class.'.($class='datasource_'.$app).'.inc.php') &&
				!file_exists($classfile = EGW_INCLUDE_ROOT.'/projectmanager/inc/class.'.($class='datasource_'.$app).'.inc.php'))
			{
				$classfile = EGW_INCLUDE_ROOT.'/projectmanager/inc/class.'.($class='datasource').'.inc.php';
			}
			include_once($classfile);
			$this->datasources[$app] =& new $class($app);
			// make the project availible for the datasource
			$this->datasources[$app]->project =& $this->project;
		}
		return $this->datasources[$app];	
	}

	/**
	 * changes the data from the db-format to your work-format
	 *
	 * reimplemented to adjust the timezone of the timestamps (adding $this->tz_offset_s to get user-time)
	 * Please note, we do NOT call the method of the parent or so_sql !!!
	 *
	 * @param array $data if given works on that array and returns result, else works on internal data-array
	 * @return array with changed data
	 */
	function db2data($data=null)
	{
		if (!is_array($data))
		{
			$data = &$this->data;
		}
		foreach($this->timestamps as $name)
		{
			if (isset($data[$name]) && $data[$name]) $data[$name] += $this->tz_offset_s;
		}
		if (is_numeric($data['pe_completion'])) $data['pe_completion'] .= '%';
		if ($data['pe_app']) $data['pe_icon'] = $data['pe_app'].'/navbar';
		if ($data['pe_resources']) $data['pe_resources'] = explode(',',$data['pe_resources']);

		return $data;
	}

	/**
	 * changes the data from your work-format to the db-format
	 *
	 * reimplemented to adjust the timezone of the timestamps (subtraction $this->tz_offset_s to get server-time)
	 * Please note, we do NOT call the method of the parent or so_sql !!!
	 *
	 * @param array $data if given works on that array and returns result, else works on internal data-array
	 * @return array with changed data
	 */
	function data2db($data=null)
	{
		if ($intern = !is_array($data))
		{
			$data = &$this->data;
		}
		foreach($this->timestamps as $name)
		{
			if (isset($data[$name]))
			{
				if ($data[$name])
				{
					$data[$name] -= $this->tz_offset_s;
				}
				else
				{
					$data[$name] = null;	// so it's not used for min or max dates
				}
			}
		}
		if (substr($data['pe_completion'],-1) == '%') $data['pe_completion'] = (int) substr($data['pe_completion'],0,-1);

		if (is_array($data['pe_resources']))
		{
			$data['pe_resources'] = count($data['pe_resources']) ? implode(',',$data['pe_resources']) : null;
		}
		return $data;
	}
	
	/**
	 * saves an project-element, reimplemented from SO, to save the remark in the link, if $keys['update_remark']
	 *
	 * @param array $keys=null if given $keys are copied to data before saveing => allows a save as
	 * @param boolean $touch_modified=true should modification date+user be set, default yes
	 * @param int $update_project=-1 update the data in the project (or'ed PM_ id's), default -1=everything
	 * @return int 0 on success and errno != 0 else
	 */
	function save($keys=null,$touch_modified=true,$update_project=-1)
	{
		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));

		if ($keys['update_remark'] || $this->data['update_remark'])
		{
			unset($keys['update_remark']);
			unset($this->data['update_remark']);
			$this->link->update_remark($this->data['pe_id'],$this->data['pe_remark']);
		}
		if ($keys) $this->data_merge($keys);
		
		if ($touch_modified || !$this->data['pe_modified'] || !$this->data['pe_modifier'])
		{
			$this->data['pe_modified'] = $this->now_su;
			$this->data['pe_modifier'] = $GLOBALS['egw_info']['user']['account_id'];
		}
		if (!$this->data['pm_id']) $this->data['pm_id'] = $this->pm_id;

		if (!($err = parent::save()))
		{
			if (is_array($this->data['pe_constraints']))
			{
				$this->constraints->save(array(
					'pm_id' => $this->data['pm_id'],
					'pe_id' => $this->data['pe_id'],
				) + $this->data['pe_constraints']);
			}
			if ($update_project) 
			{
				$this->project->update($this->data['pm_id'],$update_project,$this->data);
			}
		}
		return $err;
	}

	/**
	 * deletes a project-element or all project-elements of a project, reimplemented to remove the link too
	 *
	 * @param array/int $keys if given array with pm_id and/or pe_id or just an integer pe_id
	 * @return int affected rows, should be 1 if ok, 0 if an error
	 */
	function delete($keys=null)
	{
		if (!is_array($keys) && (int) $keys)
		{
			$keys = array('pe_id' => (int) $keys);
		}
		if (!is_null($keys))
		{
			$pm_id = $keys['pm_id'];
			$pe_id = $keys['pe_id'];
		}
		else
		{
			$pe_id = $this->data['pe_id'];
			$pm_id = $this->data['pm_id'];
		}
		$ret = parent::delete($keys);
		
		if ($pe_id)
		{
			// delete one link
			$this->link->unlink($pe_id);
			// update the project
			$this->project->update($pm_id);
			
			$this->constraints->delete(array('pe_id' => $pe_id));
		}
		elseif ($pm_id)
		{
			// delete all links to project $pm_id
			$this->link->unlink(0,'projectmanager',$pm_id);
		}	
		return $ret;
	}
	
	/**
	 * reads row matched by key and puts all cols in the data array, reimplemented to also read the constraints
	 *
	 * @param array $keys array with keys in form internalName => value, may be a scalar value if only one key
	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
	 * @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or 
	 * @return array/boolean data if row could be retrived else False
	*/
	function read($keys,$extra_cols='',$join=true)
	{
		if (!($data = parent::read($keys,$extra_cols,$join)))
		{
			return false;
		}
		$this->data['pe_constraints'] = $this->constraints->read(array(
			'pm_id' => $this->data['pm_id'],
			'pe_id' => $this->data['pe_id'],
		));
		return $this->data;
	}
	
	/**
	 * reads the titles of all project-elements specified by $keys
	 *
	 * @param array $keys keys of elements to read, default empty = all of the project the class is instanciated for
	 * @return array with pe_id => lang(pe_app): pe_title pairs
	 */
	function &titles($keys=array())
	{
		$titles = array();
		foreach((array) $this->search(array(),'pe_id,pe_title','pe_app,pe_title','','',false,'AND',false,$keys) as $element)
		{
			if ($element) $titles[$element['pe_id']] = lang($element['pe_app']).': '.$element['pe_title'];
		}
		return $titles;
	}

	/**
	 * echos a (preformatted / no-html) debug-message and evtl. log it to a file
	 *
	 * It uses the debug_message method of boprojectmanager
	 *
	 * @param string $msg
	 */
	function debug_message($msg)
	{
		$this->project->debug_message($msg);
	}

	/**
	 * Copies the elementtree from an other project
	 *
	 * This is done by calling the copy method of the datasource (if existent) and then calling update with the (new) app_id
	 *
	 * @param int $source
	 * @return boolean true on success, false otherwise
	 */
	function copytree($source)
	{
		if ((int) $this->debug >= 2 || $this->debug == 'copytree') $this->debug_message("boprojectelements::copytree($source) this->pm_id=$this->pm_id");

		$elements =& $this->search(array('pm_id' => $source),false,'pe_planned_start');
		if (!$elements) return true;
		
		foreach($elements as $element)
		{
			$ds =& $this->datasource($element['pe_app']);
			
			if (method_exists($ds,'copy'))
			{
				if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("copying $element[pe_app]:$element[pe_app_id] $element[pe_title]");
				list($app_id,$link_id) = $ds->copy($element,$this->pm_id,$this->project->data);
			}
			else	// no copy method, we just link again with that entry
			{
				if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("linking $element[pe_app]:$element[pe_app_id] $element[pe_title]");
				$app_id = $element['pe_app_id'];
				$link_id = $this->link->link('projectmanager',$this->pm_id,$element['pe_app'],$app_id,$element['pe_remark'],0,0,1);
			}
			if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("calling update($element[pe_app],$app_id,$link_id,$this->pm_id,false);");

			if (!$app_id || !$link_id) continue;	// something went wrong, eg. element no longer exists

			$this->update($element['pe_app'],$app_id,$link_id,$this->pm_id,false);	// false=no update of project itself => done once at the end

			// copy evtl. overwriten content from the element
			if (($need_save = $element['pe_overwrite'] != 0))
			{
				foreach($ds->name2id as $name => $id)
				{
					if ($element['pe_overwrite'] & $id)
					{
						$this->data[$name] = $element[$name];
					}
				}
				$this->data['pe_overwrite'] = $element['pe_overwrite'];
			}
			// copy other element data
			foreach(array('pl_id','pe_cost_per_time','cat_id','pe_share','pe_status') as $name)
			{
				if ($name == 'pe_status' && $element['pe_status'] != 'ignore') continue;	// only copy ignored

				if ($this->data[$name] != $element[$name])
				{
					$this->data[$name] = $element[$name];
					$need_save = true;
				}
			}	
			if ($need_save) $this->save(null,true,false);
		}
		// now we do one update of our project
		if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("calling project->update() this->pm_id=$this->pm_id");
		$this->project->update();

		return true;
	}

	/**
	 * Search elements
	 * 
	 * Reimplemented to cumulate eg. timesheets in also included infologs, if $filter['cumulate'] is true.
	 *
	 * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
	 * @param boolean $only_keys True returns only keys, False returns all cols
	 * @param string $order_by fieldnames + {ASC|DESC} separated by colons ','
	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
	 * @param string $wildcard appended befor and after each criteria
	 * @param boolean $empty False=empty criteria are ignored in query, True=empty have to be empty in row
	 * @param string $op defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
	 * @param int/boolean $start if != false, return only maxmatch rows begining with start
	 * @param array $filter if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
	 * @param string/boolean $join=true default join with links-table or string as in so_sql
	 * @return array of matching rows (the row is an array of the cols) or False
	 */
	function search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join=true)
	{
		if ($this->pm_id && (!isset($filter['pm_id']) || !$filter['pm_id']))
		{
			$filter['pm_id'] = $this->pm_id;
		}
		if ($filter['cumulate'])
		{
			$cumulate = array();
			foreach((array)$GLOBALS['egw']->hooks->process(array(
				'location' => 'pm_cumulate',
				'pm_id' => $filter['pm_id'],
			)) as $app => $data)
			{
				if (is_array($data)) $cumulate += $data;
			}
			if ($cumulate)
			{
				//echo "<p align=right>cumulate-filter: ".$this->db->expression($this->table_name,'NOT ',array('pe_id' => array_keys($cumulate)))."</p>\n";
				$filter[] = $this->db->expression($this->table_name,'NOT (',array('pe_id' => array_keys($cumulate)),')');
			}
		}
		$rows = parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join);
		
		if ($rows && $cumulate)
		{
			// get the pe_id of all returned rows
			$row_pe_ids = array();
			foreach($rows as $k => $row)
			{
				$row_pe_ids[$k] = $row['pe_id'];
			}
			// get pe_id's of to cumulate entries which are in $rows
			$cumulate_in = array();
			foreach($cumulate as $pe_id => $data)
			{
				if (in_array($data['other_id'],$row_pe_ids))
				{
					$cumulate_in[$pe_id] = $data['other_id'];
				}
			}
			if ($cumulate_in)	// do we have something (timesheets) to cumulate
			{
				foreach(parent::search(array('pe_id' => array_keys($cumulate_in)),false) as $to_cumulate)
				{
					// get the row, where the entry cumulates
					if (($k = array_search($cumulate_in[$to_cumulate['pe_id']],$row_pe_ids)) !== false)
					{
						//echo "kumulated in ".$rows[$k]['pe_title']; _debug_array($rows);
						foreach(array('pe_planned_time','pe_used_time','pe_planned_budget','pe_used_budget') as $name)
						{
							if ($to_cumulate[$name]) $rows[$k][$name] += $to_cumulate[$name];
						}
						//echo "-->"; _debug_array($rows);
					}
				}
			}
		}
		return $rows;
	}}