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/infolog/inc
/srv/http/vyvoj.adent.cz/egroupware/pfi/infolog/inc/
Upload File:
Current File : /srv/http/vyvoj.adent.cz/egroupware/pfi/infolog/inc/class.boinfolog.inc.php
<?php
/**
 * InfoLog - Business object
 *
 * @link http://www.egroupware.org
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
 * @package infolog
 * @copyright (c) 2003-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.boinfolog.inc.php 24792 2007-12-11 09:30:55Z pbf $
 */
	
include_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.soinfolog.inc.php');

define('EGW_ACL_UNDELETE',EGW_ACL_CUSTOM_1);	// undelete right

/**
 * This class is the BO-layer of InfoLog, it also handles xmlrpc requests
 */
class boinfolog
{
	var $enums;
	/**
	 * Instance of our so class
	 *
	 * @var soinfolog
	 */
	var $so;
	/**
	 * Instance of the link class
	 *
	 * @var bolink
	 */
	var $link;
	var $vfs;
	var $vfs_basedir='/infolog';
	var $link_pathes = array();
	var $send_file_ips = array();

	var $xmlrpc_methods = array();
	var $soap_functions = array(
		'read' => array(
			'in'  => array('int'),
			'out' => array('array')
		),
		'search' => array(
			'in'  => array('array'),
			'out' => array('array')
		),
		'write' => array(
			'in'  => array('array'),
			'out' => array()
		),
		'delete' => array(
			'in'  => array('int'),
			'out' => array()
		),
		'categories' => array(
			'in'  => array('bool'),
			'out' => array('array')
		),
	);
	var $xmlrpc = False;	// called via xmlrpc

	var $tz_offset = 0;
	/**
	 * 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 = 0;
	var $user_time_now;
	/**
	 * name of timestamps in an InfoLog entry
	 * 
	 * @var array
	 */
	var $timestamps = array('info_startdate','info_enddate','info_datemodified','info_datecompleted');
	/**
	 * instance of the config class for infolog
	 * 
	 * @var config
	 */
	var $config;
	/**
	 * fields the responsible user can change
	 * 
	 * @var array
	 */
	var $responsible_edit=array('info_status','info_percent','info_datecompleted');
	/**
	 * implicit ACL rights of the responsible user: read or edit
	 * 
	 * @var string
	 */
	var $implicit_rights='read';
	/**
	 * Custom fields read from the infolog config
	 *
	 * @var array
	 */
	var $customfields=array();
	/**
	 * Group owners for certain types read from the infolog config                      
	 *
	 * @var array
	 */
	var $group_owners=array();
	/**
	 * Current user
	 *
	 * @var int
	 */
	var $user;
	/**
	 * History loggin: ''=no, 'history'=history & delete allowed, 'history_admin_delete', 'history_no_delete'
	 *
	 * @var string
	 */
	var $history;
	/**
	 * Instance of infolog_tracking, only instaciated if needed!
	 *
	 * @var infolog_tracking
	 */
	var $tracking;
	/**
	 * Maximum number of line characters (-_+=~) allowed in a mail, to not stall the layout.
	 * Longer lines / biger number of these chars are truncated to that max. number or chars.
	 *
	 * @var int
	 */
	var $max_line_chars = 40;
	
	/**
	 * Constructor Infolog BO
	 *
	 * @param int $info_id
	 * @param boolean $instanciate_link=true should the link class be instanciated, used by the link-registry to prevent infinit recursion
	 */
	function boinfolog($info_id = 0,$instanciate_link=true)
	{
		$this->enums = $this->stock_enums = array(
			'priority' => array (
				3 => 'urgent',
				2 => 'high',
				1 => 'normal',
				0 => 'low' 
			),
			'confirm'   => array(
				'not' => 'not','accept' => 'accept','finish' => 'finish',
				'both' => 'both' ),
			'type'      => array(
				'task' => 'task','phone' => 'phone','note' => 'note','email' => 'email'
			/*	,'confirm' => 'confirm','reject' => 'reject','fax' => 'fax' not implemented so far */ )
		);
		$this->status = $this->stock_status = array(
			'defaults' => array(
				'task' => 'not-started', 'phone' => 'not-started', 'note' => 'done','email' => 'done'),
			'task' => array(
				'offer' => 'offer',				// -->  NEEDS-ACTION
				'not-started' => 'not-started',	// iCal NEEDS-ACTION
				'ongoing' => 'ongoing',			// iCal IN-PROCESS
				'done' => 'done',				// iCal COMPLETED
				'cancelled' => 'cancelled',		// iCal CANCELLED
				'billed' => 'billed' ),			// -->  DONE
			'phone' => array(
				'not-started' => 'call',		// iCal NEEDS-ACTION
				'ongoing' => 'will-call',		// iCal IN-PROCESS
				'done' => 'done', 				// iCal COMPLETED
				'billed' => 'billed' ),			// -->  DONE
			'note' => array(
				'ongoing' => 'ongoing',			// iCal has no status on notes
				'done' => 'done' ),
			'email' => array(
				'ongoing' => 'ongoing',			// iCal has no status on notes
				'done' => 'done' ),
		);

		if (!is_object($GLOBALS['egw']->link) && $instanciate_link)
		{
			$GLOBALS['egw']->link =& CreateObject('phpgwapi.bolink');
		}
		$this->link =& $GLOBALS['egw']->link;

		$this->config =& CreateObject('phpgwapi.config','infolog');
		$this->config->read_repository();

		if ($this->config->config_data)
		{
			$this->link_pathes   = $this->config->config_data['link_pathes'];
			$this->send_file_ips = $this->config->config_data['send_file_ips'];

			if (isset($this->config->config_data['status']) && is_array($this->config->config_data['status']))
			{
				foreach($this->config->config_data['status'] as $key => $data)
				{
					if (!is_array($this->status[$key]))
					{
						$this->status[$key] = array();
					}
					$this->status[$key] = array_merge($this->status[$key],$this->config->config_data['status'][$key]);
				}
			}
			if (isset($this->config->config_data['types']) && is_array($this->config->config_data['types']))
			{
				//echo "stock-types:<pre>"; print_r($this->enums['type']); echo "</pre>\n";
				//echo "config-types:<pre>"; print_r($this->config->config_data['types']); echo "</pre>\n";
				$this->enums['type'] += $this->config->config_data['types'];
				//echo "types:<pre>"; print_r($this->enums['type']); echo "</pre>\n";
			}
			if ($this->config->config_data['group_owners']) $this->group_owners = $this->config->config_data['group_owners'];

			if (isset($this->config->config_data['customfields']) && is_array($this->config->config_data['customfields']))
			{
				if (!($this->customfields = $this->config->config_data['customfields'])) $this->customfields = array();
				foreach($this->customfields as $name => $field)
				{
					// old infolog customefield record
					if(empty($field['type']))
					{
						if (count($field['values'])) $field['type'] = 'select'; // selectbox
						elseif ($field['rows'] > 1) $field['type'] = 'textarea'; // textarea
						elseif (intval($field['len']) > 0) $field['type'] = 'text'; // regular input field
						else $field['type'] = 'label'; // header-row
						$field['type2'] = $field['typ'];
						unset($field['typ']);
						$this->customfields[$name] = $this->config->config_data['customfields'][$name] = $field;
						$save_config = true;
					}
				}
				if ($save_config) $this->config->save_repository();
			}
			if (is_array($this->config->config_data['responsible_edit']))
			{
				$this->responsible_edit = array_merge($this->responsible_edit,$this->config->config_data['responsible_edit']);
			}
			if ($this->config->config_data['implicit_rights'] == 'edit')
			{
				$this->implicit_rights = 'edit';
			}
			$this->history = $this->config->config_data['history'];
		}
		// sort types by there translation
		foreach($this->enums['type'] as $key => $val)
		{
			if (($val = lang($key)) != $key.'*') $this->enums['type'][$key] = lang($key);
		}
		natcasesort($this->enums['type']);

		$this->user = $GLOBALS['egw_info']['user']['account_id'];

		$this->tz_offset = $GLOBALS['egw_info']['user']['preferences']['common']['tz_offset'];
		$this->tz_offset_s = 60*60*$this->tz_offset;
		$this->user_time_now = time() + $this->tz_offset_s;

		$this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true);
		$this->so =& new soinfolog($this->grants);

		// are we called via xmlrpc?
		$this->xmlrpc = is_object($GLOBALS['server']) && $GLOBALS['server']->last_method;

		if ($info_id)
		{
			$this->read( $info_id );
		}
		else
		{
			$this->init();
		}
	}

	/**
	 * checks if there are customfields for typ $typ
	 *
	 * @param string $type
	 * @param boolean $links=false if true check only customfields containing links, default false = all custom fields
	 * @return boolean True if there are customfields for $typ, else False
	 */
	function has_customfields($type,$links=false)
	{
		if ($links) $link_types = $this->get_customfield_link_types();

		foreach($this->customfields as $name => $field)
		{
			if ((!$type || empty($field['type2']) || in_array($type,explode(',',$field['type2']))) &&
				(!$links || in_array($field['type'],$link_types)))
			{
				return True;
			}
		}
		return False;
	}

	/**
	 * Get the customfield types containing links
	 *
	 * @return array with customefield types as values
	 */
	function get_customfield_link_types()
	{
		static $link_types;
		
		if (is_null($link_types))
		{
			$link_types = array_keys($this->link->app_list());
			$link_types[] = 'link-entry';
		}
		return $link_types;
	}
	
	/**
	 * check's if user has the requiered rights on entry $info_id
	 *
	 * @param int/array $info data or info_id of infolog entry to check
	 * @param int $required_rights EGW_ACL_{READ|EDIT|ADD|DELETE}
	 * @return boolean
	 */
	function check_access( $info,$required_rights )
	{
		static $cache = array();
		
		$info_id = is_array($info) ? $info['info_id'] : $info;
		
		if (isset($cache[$info_id][$required_rights]))
		{
			return $cache[$info_id][$required_rights];
		}
		// handle delete for the various history modes
		if ($this->history)
		{
			if (!is_array($info) && !($info = $this->so->read($info_id))) return false;
			
			if ($info['info_status'] == 'deleted' && 
				($required_rights == EGW_ACL_EDIT ||		// no edit rights for deleted entries
				 $required_rights == EGW_ACL_ADD  ||		// no add rights for deleted entries
				 $required_rights == EGW_ACL_DELETE && ($this->history == 'history_no_delete' || // no delete at all!
				 $this->history == 'history_admin_delete' && !isset($GLOBALS['egw_info']['user']['apps']['admin']))))	// delete only for admins
			{
				return $cache[$info_id][$required_rights] = false;
			}
			if ($required_rights == EGW_ACL_UNDELETE)
			{
				if ($info['info_status'] != 'deleted')
				{
					return $cache[$info_id][$required_rights] = false;	// can only undelete deleted items
				}
				// undelete requires edit rights 
				return $cache[$info_id][$required_rights] = $this->so->check_access( $info,EGW_ACL_EDIT,$this->implicit_rights == 'edit' );
			}
		}
		elseif ($required_rights == EGW_ACL_UNDELETE)
		{
			return $cache[$info_id][$required_rights] = false;
		}
		return $cache[$info_id][$required_rights] = $this->so->check_access( $info,$required_rights,$this->implicit_rights == 'edit' );
	}
	
	/**
	 * Check if use is responsible for an entry: he or one of his memberships is in responsible
	 *
	 * @param array $info infolog entry as array
	 * @return boolean
	 */
	function is_responsible($info)
	{
		return $this->so->is_responsible($info);
	}

	/**
	 * init internal data to be empty
	 */
	function init()
	{
		$this->so->init();
	}

	/**
	 * convert a link_id value into an info_from text
	 *
	 * @param array &$info infolog entry, key info_from gets set by this function
	 * @param string $not_app='' app to exclude
	 * @param string $not_id='' id to exclude
	 * @return boolean True if we have a linked item, False otherwise
	 */
	function link_id2from(&$info,$not_app='',$not_id='')
	{
		//echo "<p>boinfolog::link_id2from(subject='$info[info_subject]', link_id='$info[info_link_id]', from='$info[info_from]', not_app='$not_app', not_id='$not_id')";
		if ($info['info_link_id'] > 0 &&
			 ($link = $this->link->get_link($info['info_link_id'])) !== False)
		{
			$nr = $link['link_app1'] == 'infolog' && $link['link_id1'] == $info['info_id'] ? '2' : '1';
			$title = $this->link->title($link['link_app'.$nr],$link['link_id'.$nr]);

			if ((string)$info['info_custom_from'] === '')	// old entry
			{
				$info['info_custom_from'] = (int) ($title != $info['info_from'] && @htmlentities($title) != $info['info_from']);
			}
			if (!$info['info_custom_from'])
			{
				$info['info_from'] = '';
				$info['info_custom_from'] = 0;
			}
			if ($link['link_app'.$nr] == $not_app && $link['link_id'.$nr] == $not_id)
			{
				return False;
			}
			$info['info_link'] = array(
				'app'   => $link['link_app'.$nr],
				'id'    => $link['link_id'.$nr],
				'title' => (!empty($info['info_from']) ? $info['info_from'] : $title),
			);
			$info['info_contact'] = $link['link_app'.$nr].':'.$link['link_id'.$nr];

			//echo " title='$title'</p>\n";
			return $info['blur_title'] = $title;
		}
		else
		{
			$info['info_link'] = array('title' => $info['info_from']);
			$info['info_link_id'] = 0;	// link might have been deleted
			$info['info_custom_from'] = (int)!!$info['info_from'];
		}
		return False;
	}

	/**
	 * Create a subject from a description: truncate it and add ' ...'
	 */
	function subject_from_des($des)
	{
		return substr($des,0,60).' ...';
	}

	/**
	 * Read an infolog entry specified by $info_id
	 *
	 * @param int/array $info_id integer id or array with key 'info_id' of the entry to read
	 * @param boolean $run_link_id2from=true should link_id2from run, default yes, 
	 *	need to be set to false if called from link-title to prevent an infinit recursion
	 * @return array/boolean infolog entry, null if not found or false if no permission to read it
	 */
	function &read($info_id,$run_link_id2from=true)
	{
		if (is_array($info_id))
		{
			$info_id = (int) (isset($info_id['info_id']) ? $info_id['info_id'] : $info_id[0]);
		}

		if ($this->so->read($info_id) === False)
		{
			if ($this->xmlrpc)
			{
				$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['not_exist'],$GLOBALS['xmlrpcstr']['not_exist']);
			}
			return null;
		}
		if (!$this->check_access($info_id,EGW_ACL_READ))	// check behind read, to prevent a double read
		{
			if ($this->xmlrpc)
			{
				$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['no_access'],$GLOBALS['xmlrpcstr']['no_access']);
			}
			return False;
		}
		$data = $this->so->data;

		if ($data['info_subject'] == $this->subject_from_des($data['info_des']))
		{
			$data['info_subject'] = '';
		}
		if ($run_link_id2from) $this->link_id2from($data);

		// convert system- to user-time
		foreach($this->timestamps as $time)
		{
			if ($data[$time]) $data[$time] += $this->tz_offset_s;
		}
		if ($this->xmlrpc)
		{
			$data = $this->data2xmlrpc($data);
		}
		return $data;
	}

	/**
	 * Delete an infolog entry, evtl. incl. it's children / subs
	 *
	 * @param int/array $info_id int id or array with keys 'info_id', 'delete_children' and 'new_parent' setting all 3 params
	 * @param boolean $delete_children should the children be deleted
	 * @param int/boolean $new_parent parent to use for not deleted children if > 0
	 * @return boolean True if delete was successful, False otherwise ($info_id does not exist or no rights)
	 */
	function delete($info_id,$delete_children=False,$new_parent=False)
	{
		if (is_array($info_id))
		{
			$delete_children = $info_id['delete_children'];
			$new_parent = $info_id['new_parent'];
			$info_id = (int)(isset($info_id[0]) ? $info_id[0] : (isset($info_id['info_id']) ? $info_id['info_id'] : $info_id['info_id']));
		}
		if ($this->so->read($info_id) === False)
		{
			if ($this->xmlrpc)
			{
				$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['not_exist'],$GLOBALS['xmlrpcstr']['not_exist']);
			}
			return False;
		}
		if (!$this->check_access($info_id,EGW_ACL_DELETE))
		{
			if ($this->xmlrpc)
			{
				$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['no_access'],$GLOBALS['xmlrpcstr']['no_access']);
			}
			return False;
		}
		// check if we have children and delete or re-parent them
		if (($children = $this->so->get_children($info_id)))
		{
			foreach($children as $id => $owner)
			{
				if ($delete_children && $this->so->grants[$owner] & EGW_ACL_DELETE)
				{
					$this->delete($id,$delete_children,$new_parent);	// call ourself recursive to delete the child
				}
				else	// dont delete or no rights to delete the child --> re-parent it
				{
					$this->so->write(array(
						'info_id' => $id,
						'info_parent_id' => $new_parent,
					));
				}
			}
		}
		if (!($info = $this->read($info_id))) return false;			// should not happen
		
		$deleted = $info;
		$deleted['info_status'] = 'deleted';
		$deleted['info_datemodified'] = time();
		$deleted['info_modifier'] = $this->user;

		// if we have history switched on and not an already deleted item --> set only status deleted
		if ($this->history && $info['info_status'] != 'deleted')
		{
			if ($info['info_status'] == 'deleted') return false;	// entry already deleted

			$this->so->write($deleted);

			$this->link->unlink(0,'infolog',$info_id,'','!file');	// keep the file attachments, only delete the rest
		}
		else
		{
			$this->so->delete($info_id,false);	// we delete the children via bo to get all notifications!
			
			$this->link->unlink(0,'infolog',$info_id);
		}
		if ($info['info_status'] != 'deleted')	// dont notify of final purge of already deleted items
		{
			$GLOBALS['egw']->contenthistory->updateTimeStamp('infolog_'.$info['info_type'], $info_id, 'delete', time());
			
			// send email notifications and do the history logging
			require_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.infolog_tracking.inc.php');
			if (!is_object($this->tracking))
			{
				$this->tracking =& new infolog_tracking($this);
			}
			$this->tracking->track($deleted,$info,$this->user,true);
		}
		return True;
	}
	
	/**
	* writes the given $values to InfoLog, a new entry gets created if info_id is not set or 0
	*
	* checks and asures ACL
	*
	* @param array &$values values to write, if contains values for check_defaults and touch_modified, 
	*	they have precedens over the parameters. The 
	* @param boolean $check_defaults=true check and set certain defaults
	* @param boolean $touch_modified=true touch the modification data and sets the modiefier's user-id
	* @return int/boolean info_id on a successfull write or false
	*/
	function write(&$values,$check_defaults=True,$touch_modified=True)
	{
		//echo "boinfolog::write()values="; _debug_array($values);
		// allow to (un)set check_defaults and touch_modified via values, eg. via xmlrpc
		foreach(array('check_defaults','touch_modified') as $var)
		{
			if(isset($values[$var]))
			{
				$$var = $values[$var];
				unset($values[$var]);
			}
		}
		if ($status_only = $values['info_id'] && !$this->check_access($values['info_id'],EGW_ACL_EDIT))
		{
			if (!isset($values['info_responsible']))
			{
				if (!($values_read = $this->read($values['info_id']))) return false;
				$responsible =& $values_read['info_responsible'];
			}
			else
			{
				$responsible =& $values['info_responsible'];
			}
			if (!($status_only = in_array($this->user, $responsible)))	// responsible has implicit right to change status
			{
				$status_only = !!array_intersect($responsible,array_keys($GLOBALS['egw']->accounts->memberships($this->user)));
			}
			if (!$status_only && $values['info_status'] != 'deleted')
			{
				$status_only = $undelete = $this->check_access($values['info_id'],EGW_ACL_UNDELETE);
			}
		}
		if ($values['info_id'] && !$this->check_access($values['info_id'],EGW_ACL_EDIT) && !$status_only ||
		    !$values['info_id'] && $values['info_id_parent'] && !$this->check_access($values['info_id_parent'],EGW_ACL_ADD))
		{
			if ($this->xmlrpc)
			{
				$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['no_access'],$GLOBALS['xmlrpcstr']['no_access']);
			}
			return False;
		}
		if ($this->xmlrpc)
		{
			$values = $this->xmlrpc2data($values);
		}
		if ($status_only && !$undelete)	// make sure only status gets writen
		{
			$set_completed = !$values['info_datecompleted'] &&	// set date completed of finished job, only if its not already set 
				(in_array($values['info_status'],array('done','billed','cancelled')) || (int)$values['info_percent'] == 100);

			$backup_values = $values;	// to return the full values
			$values = array(
				'info_id'     => $values['info_id'],
				'info_datemodified' => $values['info_datemodified'],
			);
			foreach($this->responsible_edit as $name)
			{
				if (isset($backup_values[$name])) $values[$name] = $backup_values[$name];
			}
			if ($set_completed)
			{
				$values['info_datecompleted'] = $this->user_time_now;
				$values['info_percent'] = '100%';
				if (!in_array($values['info_status'],array('done','billed','cancelled'))) $values['info_status'] = 'done';
			}
			$check_defaults = False;
		}
		if ($check_defaults)
		{
			if (!$values['info_datecompleted'] && 
				(in_array($values['info_status'],array('done','billed')) || (int)$values['info_percent'] == 100))
			{
				$values['info_datecompleted'] = $this->user_time_now;	// set date completed to today if status == done
			}
			if (in_array($values['info_status'],array('done','billed')))
			{
				$values['info_percent'] == '100%';
			}
			if ((int)$values['info_percent'] == 100 && !in_array($values['info_status'],array('done','billed','cancelled')))
			{
				$values['info_status'] = 'done';
			}
			if ($values['info_responsible'] && $values['info_status'] == 'offer')
			{
				$values['info_status'] = 'not-started';   // have to match if not finished
			}
			if (isset($values['info_subject']) && empty($values['info_subject']))
			{
				$values['info_subject'] = $this->subject_from_des($values['info_des']);
			}
		}
		if (isset($this->group_owners[$values['info_type']]))
		{
			$values['info_owner'] = $this->group_owners[$values['info_type']];
			if (!($this->grants[$this->group_owners[$values['info_type']]] & EGW_ACL_EDIT))
			{
				return false;	// no edit rights from the group-owner
			}
		}
		elseif (!$values['info_id'] && !$values['info_owner'] || $GLOBALS['egw']->accounts->get_type($values['info_owner']) == 'g')
		{
			$values['info_owner'] = $this->so->user;
		}
		if ($info_from_set = ($values['info_link_id'] && isset($values['info_from']) && empty($values['info_from'])))
		{
			$values['info_from'] = $this->link_id2from($values);
		}
		if ($touch_modified || !$values['info_datemodified'])
		{
			// Should only an entry be updated which includes the original modification date?
			// Used in the web-GUI to check against a modification by an other user while editing the entry.
			// It's now disabled for xmlrpc, as otherwise the xmlrpc code need to be changed!
			$check_modified = $values['info_datemodified'] && !$this->xmlrpc ? $values['info_datemodified']-$this->tz_offset_s : false;
			$values['info_datemodified'] = $this->user_time_now;
		}
		if ($touch_modified || !$values['info_modifier'])
		{
			$values['info_modifier'] = $this->so->user;
		}
		$to_write = $values;
		if ($status_only && !$undelete) $values = array_merge($backup_values,$values);
		// convert user- to system-time
		foreach($this->timestamps as $time)
		{
			if ($to_write[$time]) $to_write[$time] -= $this->tz_offset_s;
		}
		// we need to get the old values to update the links in customfields and for the tracking
		if ($values['info_id'])
		{
			$old = $this->read($values['info_id'],false);
		}
		if(($info_id = $this->so->write($to_write,$check_modified)))
		{
			if (!isset($values['info_type']) || $status_only)
			{
				$values = $this->read($info_id);
			}
			if($values['info_id'] && $old['info_status'] != 'deleted')
			{
				// update
				$GLOBALS['egw']->contenthistory->updateTimeStamp(
					'infolog_'.$values['info_type'], 
					$info_id, 'modify', time()
				);
			}
			else
			{
				// add
				$GLOBALS['egw']->contenthistory->updateTimeStamp(
					'infolog_'.$values['info_type'], 
					$info_id, 'add', time()
				);
			}
			$values['info_id'] = $info_id;

			if (!is_array($values['info_responsible']))		// this should not happen, bug it does ;-)
			{
				$values['info_responsible'] = $values['info_responsible'] ? explode(',',$values['info_responsible']) : array();
			}
			// create (and remove) links in custom fields
			$this->update_customfield_links($values,$old);

			// notify the link-class about the update, as other apps may be subscribt to it
			$this->link->notify_update('infolog',$info_id,$values);
			
			// send email notifications and do the history logging
			require_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.infolog_tracking.inc.php');
			if (!is_object($this->tracking))
			{
				$this->tracking =& new infolog_tracking($this);
			}
			$this->tracking->track($values,$old,$this->user,$values['info_status'] == 'deleted' || $old['info_status'] == 'deleted');
		}
		if ($info_from_set) $values['info_from'] = '';

		return $info_id;
	}

	/**
	 * Check if there are links in the custom fields and update them
	 *
	 * @param array $values new values including the custom fields
	 * @param array $old=null old values before the update, if existing
	 */
	function update_customfield_links($values,$old=null)
	{
		$link_types = $this->get_customfield_link_types();
		
		foreach($this->customfields as $name => $data)
		{
			if (!in_array($data['type'],$link_types)) continue;
			
			// do we have a different old value --> delete that link
			if ($old && $old['#'.$name] && $old['#'.$name] != $values['#'.$name])
			{
				if ($data['type'] == 'link-entry')
				{
					list($app,$id) = explode(':',$old['#'.$name]);
				}
				else
				{
					$app = $data['type'];
					$id = $old['#'.$name];
				}
				$this->link->unlink(false,'infolog',$values['info_id'],'',$app,$id);
			}
			if ($data['type'] == 'link-entry')
			{
				list($app,$id) = explode(':',$values['#'.$name]);
			}
			else
			{
				$app = $data['type'];
				$id = $values['#'.$name];
			}
			if ($id)	// create new link, does nothing for already existing links 
			{
				$this->link->link('infolog',$values['info_id'],$app,$id);
			}
		}
	}

	/**
	 * Query the number of children / subs
	 *
	 * @param int $info_id id 
	 * @return int number of subs
	 */
	function anzSubs( $info_id )
	{
		return $this->so->anzSubs( $info_id );
	}

	/**
	 * searches InfoLog for a certain pattern in $query
	 *
	 * @param $query[order] column-name to sort after
	 * @param $query[sort] sort-order DESC or ASC
	 * @param $query[filter] string with combination of acl-, date- and status-filters, eg. 'own-open-today' or ''
	 * @param $query[cat_id] category to use or 0 or unset
	 * @param $query[search] pattern to search, search is done in info_from, info_subject and info_des
	 * @param $query[action] / $query[action_id] if only entries linked to a specified app/entry show be used
	 * @param &$query[start], &$query[total] nextmatch-parameters will be used and set if query returns less entries
	 * @param $query[col_filter] array with column-name - data pairs, data == '' means no filter (!)
	 * @return array with id's as key of the matching log-entries
	 */
	function &search(&$query)
	{
		//echo "<p>boinfolog::search(".print_r($query,True).")</p>\n";
		$ret = $this->so->search($query);
		
		// convert system- to user-time
		if (is_array($ret) && $this->tz_offset_s)
		{
			foreach($ret as $id => $data)
			{
				foreach($this->timestamps as $time)
				{
					if ($data[$time]) $ret[$id][$time] += $this->tz_offset_s;
				}
			}
		}
		if ($this->xmlrpc && is_array($ret))
		{
			$infos =& $ret;
			unset($ret);
			$ret = array();
			foreach($infos as $id => $data)
			{
				$ret[] = $this->data2xmlrpc($data);
			}
		}
		//echo "<p>boinfolog::search(".print_r($query,True).")=<pre>".print_r($ret,True)."</pre>\n";
		return $ret;
	}

	
	/**
	 * imports a mail identified by uid as infolog
	 *
	 * @author Cornelius Weiss <nelius@cwtech.de>
	 * @todo search if infolog with from and subject allready exists ->appned body & inform user
	 * @param string $_email_address rfc822 conform emailaddresses
	 * @param string $_subject
	 * @param string $_message
	 * @param array $_attachments
	 * @param string $_date
	 * @return array $content array for uiinfolog
	 */
	function import_mail($_email_address,$_subject,$_message,$_attachments,$_date)
	{
		$address_array = imap_rfc822_parse_adrlist($_email_address,'');
		foreach ((array)$address_array as $address) 
		{
			$email[] = $emailadr = sprintf('%s@%s',
				trim($address->mailbox),
				trim($address->host));
				$name[] = !empty($address->personal) ? $address->personal : $emailadr;
		}
		// shorten long (> $this->max_line_chars) lines of "line" chars (-_+=~) in mails
		$_message = preg_replace_callback('/[-_+=~\.]{'.$this->max_line_chars.',}/m',
			create_function('$matches',"return substr(\$matches[0],0,$this->max_line_chars);"),$_message);
		$type = isset($this->enums['type']['email']) ? 'email' : 'note';
		$status = isset($this->status['defaults'][$type]) ? $this->status['defaults'][$type] : 'done';
		$info = array(
			'info_id' => 0,
			'info_type' => $type,
			'info_from' => implode(',',$name),
			'info_addr' => implode(',',$email),
			'info_subject' => $_subject,
			'info_des' => $_message,
			'info_startdate' => $_date,
			'info_status' => $status,
			'info_priority' => 1,
			'info_percent' => $status == 'done' ? 100 : 0,
			'referer' => false,
			'link_to' => array(
				'to_app' => 'infolog',
				'to_id' => 0,
			),
		);
		// find the addressbookentry to link with
		$addressbook =& CreateObject('addressbook.bocontacts');
		$contacts = array();
		foreach ($email as $mailadr)
		{
			$contacts = array_merge($contacts,(array)$addressbook->search(
				array(
					'email' => $mailadr,
					'email_home' => $mailadr
				),True,'','','',false,'OR',false,null,'',false));
		}
		if (!$contacts || !is_array($contacts) || !is_array($contacts[0]))
		{
			$info['msg'] = lang('Attention: No Contact with address %1 found.',$info['info_addr']);
			$info['info_custom_from'] = true;	// show the info_from line and NOT only the link
		}
		else 
		{
			// create the first address as info_contact
			$contact = array_shift($contacts);
			$info['info_contact'] = 'addressbook:'.$contact['id'];
			// create the rest a "ordinary" links
			foreach ($contacts as $contact)
			{
				$this->link->link('infolog',$info['link_to']['to_id'],'addressbook',$contact['id']);
			}
		}
		if (is_array($_attachments))
		{
			foreach ($_attachments as $attachment)
			{
				if(is_readable($attachment['tmp_name']))
				{
					$this->link->link('infolog',$info['link_to']['to_id'],'file',$attachment);
				}
			}
		}
		return $info;			
	}

	/**
	 * Hook called by link-class to include infolog in the appregistry of the linkage
	 *
	 * @param array/string $location location and other parameters (not used)
	 * @return array with method-names
	 */
	function search_link($location)
	{
		return array(
			'query'      => 'infolog.boinfolog.link_query',
			'title'      => 'infolog.boinfolog.link_title',
			'view'       => array(
				'menuaction' => 'infolog.uiinfolog.index',
				'action' => 'sp'
			),
			'view_id'    => 'action_id',
			'add' => array(
				'menuaction' => 'infolog.uiinfolog.edit',
				'type'   => 'task'
			),
			'add_app'    => 'action',
			'add_id'     => 'action_id',
			'add_popup'  => '750x550',			
		);
	}

	/**
	 * get title for an infolog entry identified by $info
	 * 
	 * Is called as hook to participate in the linking
	 *
	 * @param int/array $info int info_id or array with infolog entry
	 * @return string/boolean string with the title, null if $info not found, false if no perms to view
	 */
	function link_title( $info )
	{
		if (!is_array($info))
		{
			$info = $this->read( $info,false );
		}
		if (!$info)
		{
			return $info;
		}
		return !empty($info['info_subject']) ? $info['info_subject'] :
			$this->subject_from_des($info['info_descr']);
	}

	/**
	 * query infolog for entries matching $pattern
	 *
	 * Is called as hook to participate in the linking
	 *
	 * @param string $pattern pattern to search
	 * @return array with info_id - title pairs of the matching entries
	 */
	function link_query( $pattern )
	{
		$query = array(
			'search' => $pattern,
			'start'  => 0,
			'subs'   => true,
		);
		$ids = $this->search($query);
		$content = array();
		if (is_array($ids))
		{
			foreach($ids as $id => $info )
			{
				$content[$id] = $this->link_title($id);
			}
		}
		return $content;
	}

	/**
	 * hook called be calendar to include events or todos in the cal-dayview
	 *
	 * @param int $args[year], $args[month], $args[day] date of the events
	 * @param int $args[owner] owner of the events
	 * @param string $args[location] calendar_include_{events|todos}
	 * @return array of events (array with keys starttime, endtime, title, view, icon, content)
	 */
	function cal_to_include($args)
	{
		//echo "<p>cal_to_include("; print_r($args); echo ")</p>\n";
		$user = (int) $args['owner'];
		if ($user <= 0 && !checkdate($args['month'],$args['day'],$args['year']))
		{
			return False;
		}
		if (!is_object($GLOBALS['egw']->html))
		{
			$GLOBALS['egw']->html =& CreateObject('phpgwapi.html');
		}
		$GLOBALS['egw']->translation->add_app('infolog');

		$do_events = $args['location'] == 'calendar_include_events';
		$to_include = array();
		$date_wanted = sprintf('%04d/%02d/%02d',$args['year'],$args['month'],$args['day']);
		$query = array(
			'order' => 'info_startdate',
			'sort'  => $do_events ? 'ASC' : 'DESC',
			'filter'=> "user$user".($do_events ? 'date' : 'opentoday').$date_wanted,
			'start' => 0,
		);
		if ($this->customfields && !$GLOBALS['egw_info']['user']['preferences']['infolog']['cal_show_custom'])
		{
			$query['col_filter']['info_type'] = array('task','phone','note','email');
		}
		while ($infos = $this->search($query))
		{
			foreach($infos as $info)
			{
				$time = (int) adodb_date('Hi',$info['info_startdate']);
				$date = adodb_date('Y/m/d',$info['info_startdate']);
				/* As event-like infologs are not showen in current calendar, 
				we need to present all open infologs to the user! (2006-06-27 nelius)
				if ($do_events && !$time ||
				    !$do_events && $time && $date == $date_wanted)
				{
					continue;
				}*/
				$title = ($do_events?$GLOBALS['egw']->common->formattime(adodb_date('H',$info['info_startdate']),adodb_date('i',$info['info_startdate'])).' ':'').
					$info['info_subject'];
				$view = $this->link->view('infolog',$info['info_id']);
				$content=array();
				foreach($icons = array(
					$info['info_type']   => 'infolog',
					$this->status[$info['info_type']][$info['info_status']] => 'infolog',
				) as $name => $app)
				{
					$content[] = $GLOBALS['egw']->html->image($app,$name,lang($name),'border="0" width="15" height="15"').' ';
				}
				$content[] = $GLOBALS['egw']->html->a_href($title,$view);
				$content = $GLOBALS['egw']->html->table(array(1 => $content));

				$to_include[] = array(
					'starttime' => $info['info_startdate'],
					'endtime'   => ($info['info_enddate'] ? $info['info_enddate'] : $info['info_startdate']),
					'title'     => $title,
					'view'      => $view,
					'icons'     => $icons,
					'content'   => $content
				);
			}
			if ($query['total'] <= ($query['start']+=count($infos)))
			{
				break;	// no more availible
			}
		}
		//echo "boinfolog::cal_to_include("; print_r($args); echo ")<pre>"; print_r($to_include); echo "</pre>\n";
		return $to_include;
	}

	/**
	 * handles introspection or discovery by the logged in client,
	 *  in which case the input might be an array.  The server always calls
	 *  this function to fill the server dispatch map using a string.
	 *
	 * @param string $_type='xmlrpc' xmlrpc or soap
	 * @return array
	 */
	function list_methods($_type='xmlrpc')
	{
		if (is_array($_type))
		{
			$_type = $_type['type'] ? $_type['type'] : $_type[0];
		}

		switch($_type)
		{
			case 'xmlrpc':
				$xml_functions = array(
					'read' => array(
						'function'  => 'read',
						'signature' => array(array(xmlrpcInt,xmlrpcInt)),
						'docstring' => lang('Read one record by passing its id.')
					),
					'search' => array(
						'function'  => 'search',
						'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
						'docstring' => lang('Returns a list / search for records.')
					),
					'write' => array(
						'function'  => 'write',
						'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
						'docstring' => lang('Write (add or update) a record by passing its fields.')
					),
					'delete' => array(
						'function'  => 'delete',
						'signature' => array(array(xmlrpcInt,xmlrpcInt)),
						'docstring' => lang('Delete one record by passing its id.')
					),
					'categories' => array(
						'function'  => 'categories',
						'signature' => array(array(xmlrpcBoolean,xmlrpcBoolean)),
						'docstring' => lang('List all categories')
					),
					'list_methods' => array(
						'function'  => 'list_methods',
						'signature' => array(array(xmlrpcStruct,xmlrpcString)),
						'docstring' => lang('Read this list of methods.')
					)
				);
				return $xml_functions;
				break;
			case 'soap':
				return $this->soap_functions;
				break;
			default:
				return array();
				break;
		}
	}

	/**
	 * Convert an InfoLog entry into its xmlrpc representation, eg. convert timestamps to datetime.iso8601
	 *
	 * @param array $data infolog entry
	 * @param array xmlrpc infolog entry
	 */
	function data2xmlrpc($data)
	{
		$data['rights'] = $this->so->grants[$data['info_owner']];
		
		// translate timestamps
		if($data['info_enddate'] == 0) unset($data['info_enddate']);
		foreach($this->timestamps as $name)
		{
			if (isset($data[$name]))
			{
				$data[$name] = $GLOBALS['server']->date2iso8601($data[$name]);
			}
		}
		$ret[$id]['info_percent'] = (int)$data['info_percent'].'%';

		// translate cat_id
		if (isset($data['info_cat']))
		{
			$data['info_cat'] = $GLOBALS['server']->cats2xmlrpc(array($data['info_cat']));
		}
		foreach($data as $name => $val)
		{
			if (substr($name,0,5) == 'info_')
			{
				unset($data[$name]);
				$data[substr($name,5)] = $val;
			}
		}
		// unsetting everything which could result in an typeless <value />
		foreach($data as $key => $value)
		{
			if (is_null($value) || is_array($value) && !$value)
			{
				unset($data[$key]);
			}
		}
		return $data;
	}

	/**
	 * Convert an InfoLog xmlrpc representation into the internal one, eg. convert datetime.iso8601 to timestamps
	 *
	 * @param array $data infolog entry
	 * @param array xmlrpc infolog entry
	 */
	function xmlrpc2data($data)
	{
		foreach($data as $name => $val)
		{
			if (substr($name,0,5) != 'info_')
			{
				unset($data[$name]);
				$data['info_'.$name] = $val;
			}
		}
		// translate timestamps
		foreach($this->timestamps as $name)
		{
			if (isset($data[$name]))
			{
				$data[$name] = $GLOBALS['server']->iso86012date($data[$name],True);
			}
		}
		// translate cat_id
		if (isset($data['info_cat']))
		{
			$cats = $GLOBALS['server']->xmlrpc2cats($data['info_cat']);
			$data['info_cat'] = (int)$cats[0];
		}
		return $data;
	}

	/**
	 * return array with all infolog categories (for xmlrpc)
	 *
	 * @param boolean $complete true returns array with all data for each cat, else only the title is returned
	 * @return array with cat_id / title or data pairs (see above)
	 */
	function categories($complete = False)
	{
		return $this->xmlrpc ? $GLOBALS['server']->categories($complete) : False;
	}
	
	/**
	 * Returm InfoLog (custom) status icons for projectmanager
	 *
	 * @param array $args array with id's in $args['infolog']
	 * @return array with id => icon pairs
	 */
	function pm_icons($args)
	{
		if (isset($args['infolog']) && count($args['infolog']))
		{
			$icons = $this->so->get_status($args['infolog']);
			foreach((array) $icons as $id => $status)
			{
				if ($status && substr($status,-1) != '%')
				{
					$icons[$id] = 'infolog/'.$status;
				}
			}
		}
		return $icons;
	}

	var $categories;

	function find_or_add_categories($catname_list)
	{
		if (!is_object($this->categories))
		{
			$this->categories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'infolog');
		}

		$cat_id_list = array();
		foreach($catname_list as $cat_name)
		{
			$cat_name = trim($cat_name);
			$cat_id = $this->categories->name2id($cat_name, 'X-');
			if (!$cat_id)
			{
				$cat_id = $this->categories->add(array('name' => $cat_name,'descr' => $cat_name));
			}

			if ($cat_id)
			{
				$cat_id_list[] = $cat_id;
			}
		}

		if (count($cat_id_list) > 1)
		{
			$cat_id_list = array_unique($cat_id_list);
			sort($cat_id_list, SORT_NUMERIC);
		}
		return $cat_id_list;
	}

	/**
	 * Get names for categories specified by their id's
	 *
	 * @param array|string $cat_id_list array or comma-sparated list of id's
	 * @return array with names
	 */
	function get_categories($cat_id_list)
	{
		if (!is_object($this->categories))
		{
			$this->categories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'infolog');
		}

		if (!is_array($cat_id_list))
		{
			$cat_id_list = explode(',',$cat_id_list);
		}
		$cat_list = array();
		foreach($cat_id_list as $cat_id)
		{
			if ($cat_data = $this->categories->return_single($cat_id))
			{
				$cat_list[] = $cat_data[0]['name'];
			}
		}

		return $cat_list;
	}

	/**
	 * Send all async infolog notification
	 *
	 * Called via the async service job 'infolog-async-notification'
	 */
	function async_notification()
	{
		if (!($users = $this->so->users_with_open_entries()))
		{
			return;
		}
		error_log("boinfolog::async_notification() users with open entries: ".implode(', ',$users));
		
		$save_account_id = $GLOBALS['egw_info']['user']['account_id'];
		$save_prefs      = $GLOBALS['egw_info']['user']['preferences'];
		foreach($users as $user)
		{
			if (!($email = $GLOBALS['egw']->accounts->id2name($user,'account_email'))) continue;
			// create the enviroment for $user
			$this->user = $GLOBALS['egw_info']['user']['account_id'] = $user;
			$GLOBALS['egw']->preferences->preferences($user);
			$GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository();
			$GLOBALS['egw']->acl->acl($user);
			$GLOBALS['egw']->acl->read_repository();
			$this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true);
			$this->so =& new soinfolog($this->grants);	// so caches it's filters
			
			$notified_info_ids = array();
			foreach(array(
				'notify_due_responsible'   => 'open-responsible-enddate',
				'notify_due_delegated'     => 'open-delegated-enddate',
				'notify_start_responsible' => 'open-responsible-date',
				'notify_start_delegated'   => 'open-delegated-date',
			) as $pref => $filter)
			{
				if (!($pref_value = $GLOBALS['egw_info']['user']['preferences']['infolog'][$pref])) continue;
				
				$filter .= date('Y-m-d',time()+24*60*60*(int)$pref_value);
				error_log("boinfolog::async_notification() checking with filter '$filter' ($pref_value) for user $user ($email)");
				
				$params = array('filter' => $filter);
				foreach($this->so->search($params) as $info)
				{
					// check if we already send a notification for that infolog entry, eg. starting and due on same day
					if (in_array($info['info_id'],$notified_info_ids)) continue;
					
					if (is_null($tracking) || $tracking->user != $user)
					{
						require_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.infolog_tracking.inc.php');
						$tracking = new infolog_tracking($this);
					}
					switch($pref)
					{
						case 'notify_due_responsible':
							$info['message'] = lang('%1 you are responsible for is due at %2',$this->enums['type'][$info['info_type']],
								$tracking->datetime($info['info_enddate']-$this->tz_offset_s,false));
							break;
						case 'notify_due_delegated':
							$info['message'] = lang('%1 you delegated is due at %2',$this->enums['type'][$info['info_type']],
								$tracking->datetime($info['info_enddate']-$this->tz_offset_s,false));
							break;
						case 'notify_start_responsible':
							$info['message'] = lang('%1 you are responsible for is starting at %2',$this->enums['type'][$info['info_type']],
								$tracking->datetime($info['info_startdate']-$this->tz_offset_s,null));
							break;
						case 'notify_start_delegated':
							$info['message'] = lang('%1 you delegated is starting at %2',$this->enums['type'][$info['info_type']],
								$tracking->datetime($info['info_startdate']-$this->tz_offset_s,null));
							break;
					}
					error_log("notifiying $user($email) about $info[info_subject]: $info[message]");
					$tracking->send_notification($info,null,$email,$user,$pref);
					
					$notified_info_ids[] = $info['info_id'];
				}
			}
		}
		$GLOBALS['egw_info']['user']['account_id']  = $save_account_id;
		$GLOBALS['egw_info']['user']['preferences'] = $save_prefs;
	}
}