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/etemplate/inc
/srv/http/vyvoj.adent.cz/egroupware/pfi/etemplate/inc/
Upload File:
Current File : /srv/http/vyvoj.adent.cz/egroupware/pfi/etemplate/inc/class.soetemplate.inc.php
<?php
	/**
	 * eGroupWare EditableTemplates - Storage Objects
	 *
	 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
	 * @package etemplate
	 * @link http://www.egroupware.org
	 * @author Ralf Becker <RalfBecker@outdoor-training.de>
	 * @version $Id: class.soetemplate.inc.php 23865 2007-05-14 20:43:23Z lkneschke $
	 */

	/**
	 * Storage Objects: Everything to store and retrive and eTemplate.
	 *
	 * eTemplates are stored in the db in table 'phpgw_etemplate' and gets distributed
	 * through the file 'etemplates.inc.php' in the setup dir of each app. That file gets
	 * automatically imported in the db, whenever you show a eTemplate of the app. For
	 * performace reasons the timestamp of the file is stored in the db, so 'new'
	 * eTemplates need to have a newer file. The distribution-file is generated with the
	 * function dump, usually by pressing a button in the editor.
	 * writeLangFile writes an lang-file with all Labels, incorporating an existing one.
	 * Beside a name eTemplates use the following keys to find the most suitable template
	 * for an user (in order of precedence):
	 *  1) User-/Group-Id (not yet implemented)
	 *  2) preferd languages of the user (templates for all langs have $lang='')
	 *  3) selected template: verdilak, ... (the default is called '' in the db, not default)
	 *  4) a version-number of the form, eg: '0.9.13.001' (filled up with 0 same size)
	 *
	 * @package etemplate
	 * @subpackage api
	 * @author RalfBecker-AT-outdoor-training.de
	 * @license GPL
	 */
	class soetemplate
	{
		var $debug;		// =1 show some debug-messages, = 'app.name' show messages only for eTemplate 'app.name'
		var $name;		// name of the template, e.g. 'infolog.edit'
		var $template;	// '' = default (not 'default')
		var $lang;		// '' if general template else language short, e.g. 'de'
		var $group;		// 0 = not specific else groupId or if < 0  userId
		var $version;	// like 0.9.13.001
		var $style;		// embeded CSS style-sheet
		var $children;	// array with children
		var $data;		// depricated: first grid of the children
		var $size;		// depricated: witdh,height,border of first grid
		/**
		 * private instance of the db-object
		 *
		 * @var egw_db
		 */
		var $db;
		/**
		 * name of table
		 *
		 * @var string
		 */
		var $table_name = 'egw_etemplate';
		var $db_key_cols = array(
			'et_name' => 'name',
			'et_template' => 'template',
			'et_lang' => 'lang',
			'et_group' => 'group',
			'et_version' => 'version'
		);
		var $db_data_cols = array(
			'et_data' => 'data',
			'et_size' => 'size',
			'et_style' => 'style',
			'et_modified' => 'modified'
		);
		var $db_cols;
		/**
		 * widgets that contain other widgets, eg. for tree_walk method
		 * widget-type is the key, the value specifys how the children are stored.
		 * 
		 * @var array
		 */
		var $widgets_with_children = array(
			'template' => 'template',
			'grid' => 'grid',
			'box' => 'box',
			'vbox' => 'box',
			'hbox' => 'box',
			'groupbox' => 'box',
			'deck' => 'box',
		);
		
		/**
		 * constructor of the class
		 *
		 * calls init or read depending on a name for the template is given
		 *
		 * @param string $name name of the eTemplate or array with the values for all keys
		 * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db
		 * @param string $lang language, '' loads the pref. lang of the user, 'default' loads the default one '' in the db
		 * @param int $group id of the (primary) group of the user or 0 for none, not used at the moment !!!
		 * @param string $version version of the eTemplate
		 * @param int $rows initial size of the template, default 1, only used if no name given !!!
		 * @param int $cols initial size of the template, default 1, only used if no name given !!!
		 * @return soetemplate
		 */
		function soetemplate($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1)
		{
			if (is_object($GLOBALS['egw']->db))
			{
				$this->db = clone($GLOBALS['egw']->db);
				$this->db->set_app('etemplate');
			}
			else
			{
				$GLOBALS['egw_info']['server']['eTemplate-source'] = 'files';
			}
			$this->db_cols = $this->db_key_cols + $this->db_data_cols;

			if (empty($name))
			{
				$this->init($name,$template,$lang,$group,$version,$rows,$cols);
			}
			else
			{
				$this->read($name,$template,$lang,$group,$version);
			}
		}

		/**
		 * generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!)
		 *
		 * @static
		 * @param int $num numerical index to generate name from 1 => 'A'
		 * @return string the name
		 */
		function num2chrs($num)
		{
			$min = ord('A');
			$max = ord('Z') - $min + 1;
			if ($num >= $max)
			{
				$chrs = chr(($num / $max) + $min - 1);
			}
			$chrs .= chr(($num % $max) + $min);

			return $chrs;
		}

		/**
		 * generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!)
		 *
		 * @static
		 * @param string $chrs column letter to generate name from 'A' => 1
		 * @return int the index
		 */
		function chrs2num($chrs)
		{
			$min = ord('A');
			$max = ord('Z') - $min + 1;
			
			$num = 1+ord($chrs{0})-$min;
			if (strlen($chrs) > 1)
			{
				$num *= 1 + $max - $min;
				$num += 1+ord($chrs{1})-$min;
			}
			return $num;
		}

		/**
		 * constructor for a new / empty cell/widget
		 *
		 * nothing fancy so far
		 *
		 * @static
		 * @param string $type type of the widget
		 * @param string $name name of widget
		 * @param array $attributes=null array with further attributes
		 * @return array the cell
		 */
		function empty_cell($type='label',$name='',$attributes=null)
		{
			$cell = array(
				'type' => $type,
				'name' => $name,
			);
			if ($attributes && is_array($attributes))
			{
				return array_merge($attributes,$cell);
			}
			return $cell;
		}

		/**
		 * constructs a new cell in a give row or the last row, not existing rows will be created
		 *
		 * @deprecated as it uses this->data
		 * @param int $row row-number starting with 1 (!)
		 * @param string $type type of the cell
		 * @param string $label label for the cell
		 * @param string $name name of the cell (index in the content-array)
		 * @param array $attributes other attributes for the cell
		 * @return array a reference to the new cell, use $new_cell = &$tpl->new_cell(); (!)
		*/
		function &new_cell($row=False,$type='label',$label='',$name='',$attributes=False)
		{
			$row = $row >= 0 ? intval($row) : 0;
			if ($row && !isset($this->data[$row]) || !isset($this->data[1]))	// new row ?
			{
				if (!$row) $row = 1;

				$this->data[$row] = array();
			}
			if (!$row)	// use last row
			{
				$row = count($this->data);
				while (!isset($this->data[$row]))
				{
					--$row;
				}
			}
			$row = &$this->data[$row];
			$col = $this->num2chrs(count($row));
			$cell = &$row[$col];
			$cell = $this->empty_cell($type,$name);
			if ($label !== '')
			{
				$attributes['label'] = $label;
			}
			if (is_array($attributes))
			{
				foreach($attributes as $name => $value)
				{
					$cell[$name] = $value;
				}
			}
			return $cell;
		}

		/**
		 * adds $cell to it's parent at the parent-type spezific location for childs
		 *
		 * @static
		 * @param array &$parent referenc to the parent
		 * @param array &$cell cell to add (need to be unset after the call to add_child, as it's a referenc !)
		 */
		function add_child(&$parent,&$cell)
		{
			if (is_object($parent))	// parent is the template itself
			{
				$parent->children[] = &$cell;
				return;
			}
			switch($parent['type'])
			{
				case 'vbox':
				case 'hbox':
				case 'groupbox':
				case 'box':
				case 'deck':
					list($n,$options) = explode(',',$parent['size'],2);
					$parent[++$n] = &$cell;
					$parent['size'] = $n . ($options ? ','.$options : '');
					break;

				case 'grid':
					$data = &$parent['data'];
					$cols = &$parent['cols'];
					$rows = &$parent['rows'];
					$row = &$data[$rows];
					$col = count($row);
					if (!$rows || !is_array($cell))	// create a new row
					{
						$row = &$data[++$rows];
						$row = array();
					}
					if (is_array($cell))	// real cell to add
					{
						$row[soetemplate::num2chrs($col++)] = &$cell;
						list($spanned) = explode(',',$cell['span']);
						$spanned = $spanned == 'all' ? 1 + $cols - $col : $spanned;
						while (--$spanned > 0)
						{
							$row[soetemplate::num2chrs($col++)] = soetemplate::empty_cell();
						}
						if ($col > $cols) $cols = $col;
					}
					break;
			}
		}

		/**
		 * initialises internal vars rows & cols from the data of a grid
		 *
		 * @static 
		 * @param array &$grid to calc rows and cols
		 */
		function set_grid_rows_cols(&$grid)
		{
			$grid['rows'] = count($grid['data']) - 1;
			$grid['cols'] = 0;
			for($r = 1; $r <= $grid['rows']; ++$r)
			{
				$cols = count($grid['data'][$r]);
				if ($grid['cols'] < $cols)
				{
					$grid['cols'] = $cols;
				}
			}
		}
		
		/**
		 * initialises internal vars rows & cols from the data of the first (!) grid
		 *
		 * @deprecated as it uses this->data
		 */
		function set_rows_cols()
		{
			if (is_null($this->data))	// tmpl contains no grid
			{
				$this->rows = $this->cols = 0;
			}
			else
			{
				$grid['data'] = &$this->data;
				$grid['rows'] = &$this->rows;
				$grid['cols'] = &$this->cols;
				$this->set_grid_rows_cols($grid);
				unset($grid);
			}
		}

		/**
		 * initialises all internal data-structures of the eTemplate and sets the keys
		 *
		 * @param string $name name of the eTemplate or array with the values for all keys and possibly data
		 * @param string $template template-set or '' for the default one
		 * @param string $lang language or '' for the default one
		 * @param int $group id of the (primary) group of the user or 0 for none, not used at the moment !!!
		 * @param string $version version of the eTemplate
		 * @param int $rows initial size of the template, default 1
		 * @param int $cols initial size of the template, default 1
		 */
		function init($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1)
		{
			// unset children and data as they are referenzes to each other
			unset($this->children); unset($this->data);
			
			foreach($this->db_cols as $db_col => $col)
			{
				if ($col != 'data') $this->$col = is_array($name) ? (string) $name[$col] : $$col;
			}
			if ($this->template == 'default')
			{
				$this->template = '';
			}
			if ($this->lang == 'default')
			{
				$this->lang = '';
			}
			$this->tpls_in_file = is_array($name) ? $name['tpls_in_file'] : 0;
			
			if (is_array($name) && $name['onclick_handler']) $this->onclick_handler = $name['onclick_handler'];

			if (is_array($name)  && isset($name['data']))
			{
				// data/children are in $name['data']
				$this->children = is_array($name['data']) ? $name['data'] : unserialize($name['data']);

				$this->fix_old_template_format();
			}
			else
			{
				$this->size = $this->style = '';
				$this->data = array(0 => array());
				$this->rows = $rows < 0 ? 1 : $rows;
				$this->cols = $cols < 0 ? 1 : $cols;
				for ($row = 1; $row <= $rows; ++$row)
				{
					for ($col = 0; $col < $cols; ++$col)
					{
						$this->data[$row][$this->num2chrs($col)] = $this->empty_cell();
					}
				}
				$this->children[0]['type'] = 'grid';
				$this->children[0]['data'] = &$this->data;
				$this->children[0]['rows'] = &$this->rows;
				$this->children[0]['cols'] = &$this->cols;
				$this->children[0]['size'] = &$this->size;
			}
		}

		/**
		 * reads an eTemplate from the database
		 *
		 * @param string $name name of the eTemplate or array with the values for all keys
		 * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db
		 * @param string $lang language, '' loads the pref. lang of the user, 'default' loads the default one '' in the db
		 * @param int $group id of the (primary) group of the user or 0 for none, not used at the moment !!!
		 * @param string $version version of the eTemplate
		 * @return boolean True if a fitting template is found, else False
		 */
		function read($name,$template='default',$lang='default',$group=0,$version='')
		{
			$this->init($name,$template,$lang,$group,$version);
			if ($this->debug == 1 || $this->debug == $this->name)
			{
				echo "<p>soetemplate::read('$this->name','$this->template','$this->lang',$this->group,'$this->version')</p>\n";
			}
			if (($GLOBALS['egw_info']['server']['eTemplate-source'] == 'files' ||
					 $GLOBALS['egw_info']['server']['eTemplate-source'] == 'xslt') && $this->readfile())
			{
				return True;
			}
			if ($this->name)
			{
				$this->test_import($this->name);	// import updates in setup-dir
			}
			$pref_lang = $GLOBALS['egw_info']['user']['preferences']['common']['lang'];
			$pref_templ = $GLOBALS['egw_info']['server']['template_set'];

			$where = array(
				'et_name' => $this->name,
			);
			if (is_array($name))
			{
				$template = $name['template'];
			}
			if ($template == 'default')
			{
				$where[] = '(et_template='.$this->db->quote($pref_templ)." OR et_template='')";
			}
			else
			{
				$where['et_template'] = $this->template;
			}
			if (is_array($name))
			{
				$lang = $name['lang'];
			}
			if ($lang == 'default' || $name['lang'] == 'default')
			{
				$where[] = '(et_lang='.$this->db->quote($pref_lang)." OR et_lang='')";
			}
			else
			{
				$where['et_lang'] = $this->lang;
			}
			if ($this->version != '')
			{
				$where['et_version'] = $this->version;
			}
			$this->db->select($this->table_name,'*',$where,__LINE__,__FILE__,false,'ORDER BY et_lang DESC,et_template DESC,et_version DESC');
			if (!$this->db->next_record())
			{
				$version = $this->version;
				return $this->readfile() && (empty($version) || $version == $this->version);
			}
			$this->db2obj();
			
			if ($this->debug == $this->name)
			{
				$this->echo_tmpl();
			}
			return True;
		}

		/**
		 * Reads an eTemplate from the filesystem, the keys are already set by init in read
		 *
		 * @return boolean True if a template was found, else False
		 */
		function readfile()
		{
			list($app,$name) = explode('.',$this->name,2);
			$template = $this->template == '' ? 'default' : $this->template;

			if ($this->lang)
			{
				$lang = '.' . $this->lang;
			}
			$first_try = $ext = $GLOBALS['egw_info']['server']['eTemplate-source'] == 'xslt' ? '.xsl' : '.xet';

			while ((!$lang || !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/$template/$name$lang$ext") &&
												!@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/default/$name$lang$ext")) &&
						 !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/$template/$name$ext") &&
						 !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/default/$name$ext"))
			{
				if ($ext == $first_try)
				{
					$ext = $ext == '.xet' ? '.xsl' : '.xet';

					if ($this->debug == 1 || $this->name != '' && $this->debug == $this->name)
					{
						echo "<p>tried '$file' now trying it with extension '$ext' !!!</p>\n";
					}
				}
				else
				{
					break;
				}
			}
			if ($this->name == '' || $app == '' || $name == '' || !@file_exists($file) || !($f = @fopen($file,'r')))
			{
				if ($this->debug == 1 || $this->name != '' && $this->debug == $this->name)
				{
					echo "<p>Can't open template '$this->name' / '$file' !!!</p>\n";
				}
				return False;
			}
			$xml = fread ($f, filesize ($file));
			fclose($f);

			if ($ext == '.xsl')
			{
				$cell = $this->empty_cell();
				$cell['type'] = 'xslt';
				$cell['size'] = $this->name;
				//$cell['xslt'] = &$xml;	xslttemplate class cant use it direct at the moment
				$cell['name'] = '';
				$this->data = array(0 => array(),1 => array('A' => &$cell));
				$this->rows = $this->cols = 1;
			}
			else
			{
				if (!is_object($this->xul_io))
				{
					$this->xul_io =& CreateObject('etemplate.xul_io');
				}
				$loaded = $this->xul_io->import($this,$xml);

				if (!is_array($loaded))
				{
					return False;
				}
				$this->name = $app . '.' . $name;	// if template was copied or app was renamed

				$this->tpls_in_file = count($loaded);
			}
			return True;
		}

		/**
		 * Convert the usual *,? wildcards to the sql ones and quote %,_
		 *
		 * @param string $pattern
		 * @return string
		 */
		function sql_wildcards($pattern)
		{
			return str_replace(array('%','_','*','?'),array('\\%','\\_','%','_'),$pattern);
		}

		/**
		 * Lists the eTemplates matching the given criteria, sql wildcards % and _ possible
		 *
		 * @param string $name name of the eTemplate or array with the values for all keys
		 * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db
		 * @param string $lang language, '' loads the pref. lang of the user, 'default' loads the default one '' in the db
		 * @param int $group id of the (primary) group of the user or 0 for none, not used at the moment !!!
		 * @param string $version version of the eTemplate
		 * @return array of arrays with the template-params
		 */
		function search($name,$template='default',$lang='default',$group=0,$version='')
		{
			if ($this->name)
			{
				$this->test_import($this->name);	// import updates in setup-dir
			}
			if (is_array($name))
			{
				$template = (string) $name['template'];
				$lang     = (string) $name['lang'];
				$group    = (int) $name['group'];
				$version  = (string) $name['version'];
				$name     = (string) $name['name'];
			}
			$where[] = 'et_name LIKE '.$this->db->quote($this->sql_wildcards($name).'%');
			if ($template != '' && $template != 'default')
			{
				$where[] = 'et_template LIKE '.$this->db->quote($this->sql_wildcards($template).'%');
			}
			if ($lang != '' && $lang != 'default')
			{
				$where[] = 'et_lang LIKE '.$this->db->quote($this->sql_wildcards($lang).'%');
			}
			if ($this->version != '')
			{
				$where[] = 'et_version LIKE '.$this->db->quote($this->sql_wildcards($version).'%');
			}
			$this->db->select($this->table_name,'et_name,et_template,et_lang,et_group,et_version',$where,__LINE__,__FILE__,false,'ORDER BY et_name DESC,et_lang DESC,et_template DESC,et_version DESC');
			$result = array();
			while (($row = $this->db->row(true,'et_')))
			{
				if ($row['lang'] != '##')	// exclude or import-time-stamps
				{
					$result[] = $row;
				}
			}
			if ($this->debug)
			{
				_debug_array($result);
			}
			return $result;
		}

		/**
		 * copies all cols into the obj and unserializes the data-array
		 */
		function db2obj()
		{
			// unset children and data as they are referenzes to each other
			unset($this->children); unset($this->data);

			foreach ($this->db_cols as $db_col => $name)
			{
				if ($name != 'data') 
				{
					$this->$name = $this->db->f($db_col);
				}
				else
				{
					$this->children = unserialize($this->db->f($db_col));
				}
			}
			$this->fix_old_template_format();
		}
		
		/**
		 *  test if we have an old/original template-format and fixes it to the new format
		 */
		function fix_old_template_format()
		{
			if (!is_array($this->children)) $this->children = array();
			
			if (!isset($this->children[0]['type']))
			{
				// old templates are treated as having one children of type grid (the original template)
				$this->data = &$this->children;
				unset($this->children);

				$this->children[0]['type'] = 'grid';
				$this->children[0]['data'] = &$this->data;
				$this->children[0]['rows'] = &$this->rows;
				$this->children[0]['cols'] = &$this->cols;
				$this->children[0]['size'] = &$this->size;

				// that code fixes a bug in very old templates, not sure if it's still needed
				if ($this->name[0] != '.' && is_array($this->data))
				{
					reset($this->data); each($this->data);
					while (list($row,$cols) = each($this->data))
					{
						while (list($col,$cell) = each($cols))
						{
							if (is_array($cell['type']))
							{
								$this->data[$row][$col]['type'] = $cell['type'][0];
								//echo "corrected in $this->name cell $col$row attribute type<br>\n";
							}
							if (is_array($cell['align']))
							{
								$this->data[$row][$col]['align'] = $cell['align'][0];
								//echo "corrected in $this->name cell $col$row attribute align<br>\n";
							}
						}
					}
				}
			}
			else
			{
				unset($this->data);
				// for the moment we make $this->data as a referenz to the first grid
				foreach($this->children as $key => $child)
				{
					if ($child['type'] == 'grid')
					{
						$this->data = &$this->children[$key]['data'];
						$this->rows = &$this->children[$key]['rows'];
						$this->cols = &$this->children[$key]['cols'];
						if (!isset($this->children[$key]['size']) && !empty($this->size))
						{
							$this->children[$key]['size'] = &$this->size;
						}
						else
						{
							$this->size = &$this->children[$key]['size'];
						}
						break;
					}
				}
			}
			$this->set_rows_cols();
		}

		/**
		 * all empty values and objects in the array got unset (to save space in the db )
		 *
		 * The never empty type field ensures a cell does not disapear completely.
		 * Calls it self recursivly for arrays / the rows
		 *
		 * @param array $arr the array to compress
		 * @param boolean $remove_all_objs if true unset all objs, on false use as_array to save only the data of objs
		 * @return array
		 */
		function compress_array($arr,$remove_objs=false)
		{
			if (!is_array($arr))
			{
				return $arr;
			}
			foreach($arr as $key => $val)
			{
				if ($remove_objs && $key === 'obj')	// it can be an array too
				{
					unset($arr[$key]);
				}
				elseif (is_array($val))
				{
					$arr[$key] = $this->compress_array($val,$remove_objs);
				}
				elseif (!$remove_objs && $key == 'obj' && is_object($val) && method_exists($val,'as_array') &&
					// this test prevents an infinit recursion of templates calling itself, atm. etemplate.editor.new
					$GLOBALS['egw_info']['etemplate']['as_array'][$this->name]++ < 2)
				{
					$arr['obj'] = $val->as_array(2);
				}
				elseif ($val == '' || is_object($val))
				{
					unset($arr[$key]);
				}
			}
			return $arr;
		}

		/**
		 * returns obj-data/-vars as array
		 *
		 * the returned array ($data_too > 0) can be used with init to recreate the template 
		 *
		 * @param int $data_too -1 = only keys, 0 = no data array, 1 = data array too, 2 = serialize data array, 
		 *	3 = only data values and data serialized
		 * @param boolean $db_keys use db-column-names or internal names, default false=internal names
		 * @return array with template-data
		 */
		function as_array($data_too=0,$db_keys=false)
		{
			//echo "<p>soetemplate::as_array($data_too,$db_keys) name='$this->name', ver='$this->version'</p>\n";
			$arr = array();
			switch($data_too)
			{
				case -1:
					$cols = $this->db_key_cols;
					break;
				case 3:
					$cols = $this->db_data_cols;
					break;
				default:
					$cols = $this->db_cols;
			}
			foreach($cols as $db_col => $col)
			{
				if ($col == 'data')
				{
					if ($data_too > 0)
					{
						$arr[$db_keys ? $db_col : $col] = $data_too < 2 ? $this->children :
							serialize($this->compress_array($this->children,$db_keys));
					}
				}
				else
				{
					$arr[$db_keys ? $db_col : $col] = $this->$col;
				}
			}
			if ($data_too != -1 && $this->tpls_in_file && !$db_keys) 
			{
				$arr['tpls_in_file'] = $this->tpls_in_file;
			}
			if ($data_too != -1 && $this->onclick_handler && !$db_keys) 
			{
				$arr['onclick_handler'] = $this->onclick_handler;
			}
			return $arr;
		}

		/**
		 * saves eTemplate-object to db, can be used as saveAs by giving keys as params
		 *
		 * @param string $name name of the eTemplate or array with the values for all keys
		 * @param string $template template-set
		 * @param string $lang language or ''
		 * @param int $group id of the (primary) group, not used at the moment !!!
		 * @param string $version version of the eTemplate
		 * @return int number of affected rows, 1 should be ok, 0 somethings wrong
		 */
		function save($name='',$template='.',$lang='.',$group='',$version='.')
		{
			if (is_array($name))
			{
				$template = $name['template'];
				$lang     = $name['lang'];
				$group    = $name['group'];
				$version  = $name['version'];
				$name     = $name['name'];
			}
			if ($name != '')
			{
				$this->name = $name;
			}
			if ($lang != '.')
			{
				$this->lang = $lang;
			}
			if ($template != '.')
			{
				$this->template = $template;
			}
			if ($group != '')
			{
				$this->group = $group;
			}
			if ($version != '.')
			{
				$this->version = $version;
			}
			if ($this->name == '')	// name need to be set !!!
			{
				return False;
			}
			if ($this->debug > 0 || $this->debug == $this->name)
			{
				echo "<p>soetemplate::save('$this->name','$this->template','$this->lang',$this->group,'$this->version')</p>\n";
			}
			if ($this->name[0] != '.' && is_array($this->data))		// correct old messed up templates
			{
				reset($this->data); each($this->data);
				while (list($row,$cols) = each($this->data))
				{
					while (list($col,$cell) = each($cols))
					{
						if (is_array($cell['type'])) {
							$this->data[$row][$col]['type'] = $cell['type'][0];
							//echo "corrected in $this->name cell $col$row attribute type<br>\n";
						}
						if (is_array($cell['align'])) {
							$this->data[$row][$col]['align'] = $cell['align'][0];
							//echo "corrected in $this->name cell $col$row attribute align<br>\n";
						}
					}
				}
			}
			if (!$this->modified)
			{
				$this->modified = time();
			}
			if (is_null($this->group) && !is_int($this->group)) $this->group = 0;

			$this->db->insert($this->table_name,$this->as_array(3,true),$this->as_array(-1,true),__LINE__,__FILE__);

			$rows = $this->db->affected_rows();
			
			if (!$rows)
			{
				echo "<p>soetemplate::save('$this->name','$this->template','$this->lang',$this->group,'$this->version') <b>nothing written!!!</b></p>\n";
				function_backtrace();
				_debug_array($this->db);
			}
			return $rows;
		}

		/**
		 * Deletes the eTemplate from the db, object itself is unchanged
		 *
		 * @return int number of affected rows, 1 should be ok, 0 somethings wrong
		 */
		function delete()
		{
			$this->db->delete($this->table_name,$this->as_array(-1,true),__LINE__,__FILE__);

			return $this->db->affected_rows();
		}

		/**
		 * dumps all eTemplates to <app>/setup/etemplates.inc.php for distribution
		 *
		 * @param string $app app- or template-name contain app
		 * @return string translated message with number of dumped templates or error-message (webserver has no write access)
		 */
		function dump4setup($app)
		{
			list($app) = explode('.',$app);

			$this->db->query("SELECT * FROM $this->table_name WHERE et_name LIKE '$app%'");

			$dir = EGW_SERVER_ROOT . "/$app/setup";
			if (!is_writeable($dir))
			{
				return lang("Error: webserver is not allowed to write into '%1' !!!",$dir);
			}
			$file = "$dir/etemplates.inc.php";
			if (file_exists($file))
			{
				$old_file = "$dir/etemplates.old.inc.php";
				if (file_exists($old_file))
				{
					unlink($old_file);
				}
				rename($file,$old_file);
			}

			if (!($f = fopen($file,'w')))
			{
				return 0;
			}
			fwrite($f,'<?php
	/**
	 * eGroupWare - eTemplates for Application '. $app. '
	 * http://www.egroupware.org 
	 * generated by soetemplate::dump4setup() '.date('Y-m-d H:i'). '
	 *
	 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
	 * @package '. $app. '
	 * @subpackage setup
	 * @version $Id: class.soetemplate.inc.php 23865 2007-05-14 20:43:23Z lkneschke $
	 */'."\n\n\$templ_version=1;\n\n");

			for ($n = 0; $this->db->next_record(); ++$n)
			{
				$str = '$templ_data[] = array(';
				foreach ($this->db_cols as $db_col => $name)
				{
					// escape only backslashes and single quotes (in that order)
					$str .= "'$name' => '".str_replace(array('\\','\'',"\r"),array('\\\\','\\\'',''),$this->db->f($db_col))."',";
				}
				$str .= ");\n\n";
				fwrite($f,$str);
			}
			fclose($f);

			return lang("%1 eTemplates for Application '%2' dumped to '%3'",$n,$app,$file);
		}

		/**
		 * extracts all texts: labels and helptexts from the cells of an eTemplate-object
		 *
		 * some extensions use a '|' to squezze multiple texts in a label or help field
		 *
		 * @return array with messages as key AND value
		 */
		function getToTranslate()
		{
			$to_trans = array();

			$this->widget_tree_walk('getToTranslateCell',$to_trans);
			
			//echo '<b>'.$this->name.'</b>'; _debug_array($to_trans);
			return $to_trans;
		}

		/**
		 * Read all eTemplates of an app an extracts the texts to an array
		 *
		 * @param string $app name of the app
		 * @return array with texts
		 */
		function getToTranslateApp($app)
		{
			$to_trans = array();

			$tpls = $this->search($app);

			$tpl =& new soetemplate;	// to not alter our own data
			
			while (list(,$keys) = each($tpls))
			{
				if (($keys['name'] != $last['name'] ||		// write only newest version
					 $keys['template'] != $last['template']) &&
					 strpos($keys['name'],'test') === false)
				{
					$tpl->read($keys);
					$to_trans += $tpl->getToTranslate();
					$last = $keys;
				}
			}
			return $to_trans;
		}

		/**
		 * Write new lang-file using the existing one and all text from the eTemplates
		 *
		 * @param string $app app- or template-name
		 * @param string $lang language the messages in the template are, defaults to 'en'
		 * @param array $additional extra texts to translate, if you pass here an array with all messages and
		 * 	select-options they get writen too (form is <unique key> => <message>)
		 * @return string translated message with number of messages written (total and new), or error-message
		 */
		function writeLangFile($app,$lang='en',$additional='')
		{
			if (!$additional)
			{
				$additional = array();
			}
			list($app) = explode('.',$app);

			if (!file_exists(EGW_SERVER_ROOT.'/developer_tools/inc/class.solangfile.inc.php'))
			{
				$solangfile =& CreateObject('etemplate.solangfile');
			}
			else
			{
				$solangfile =& CreateObject('developer_tools.solangfile');
			}
			$langarr = $solangfile->load_app($app,$lang);
			if (!is_array($langarr))
			{
				$langarr = array();
			}
			$commonarr = $solangfile->load_app('phpgwapi',$lang) + $solangfile->load_app('etemplate',$lang);

			$to_trans = $this->getToTranslateApp($app);
			if (is_array($additional))
			{
				//echo "writeLangFile: additional ="; _debug_array($additional);
				foreach($additional as $msg)
				{
					if (!is_array($msg)) $to_trans[trim(strtolower($msg))] = $msg;
				}
			}
			unset($to_trans['']);

			for ($new = $n = 0; list($message_id,$content) = each($to_trans); ++$n)
			{
				if (!isset($langarr[$message_id]) && !isset($commonarr[$message_id]))
				{
					if (@isset($langarr[$content]))	// caused by not lowercased-message_id's
					{
						unset($langarr[$content]);
					}
					$langarr[$message_id] = array(
						'message_id' => $message_id,
						'app_name'   => $app,
						'content'    => $content
					);
					++$new;
				}
			}
			ksort($langarr);

			$dir = EGW_SERVER_ROOT . "/$app/setup";
			if (!is_writeable($dir))
			{
				return lang("Error: webserver is not allowed to write into '%1' !!!",$dir);
			}
			$file = "$dir/phpgw_$lang.lang";
			if (file_exists($file))
			{
				$old_file = "$dir/phpgw_$lang.old.lang";
				if (file_exists($old_file))
				{
					unlink($old_file);
				}
				rename($file,$old_file);
			}
			$solangfile->write_file($app,$langarr,$lang);
			$solangfile->loaddb($app,$lang);

			return lang("%1 (%2 new) Messages writen for Application '%3' and Languages '%4'",$n,$new,$app,$lang);
		}

		/**
		 * Imports the dump-file /$app/setup/etempplates.inc.php unconditional (!)
		 *
		 * @param string $app app name
		 * @return string translated message with number of templates imported
		 */
		function import_dump($app)
		{
			$templ_version=0;

			include($path = EGW_SERVER_ROOT."/$app/setup/etemplates.inc.php");
			$templ =& new etemplate($app);

			foreach($templ_data as $data)
			{
				if ((int)$templ_version < 1)	// we need to stripslashes
				{
					$data['data'] = stripslashes($data['data']);
				}
				$templ->init($data);

				if (!$templ->modified)
				{
					$templ->modified = filemtime($path);
				}
				$templ->save();
			}
			return lang("%1 new eTemplates imported for Application '%2'",$n,$app);
		}

		/**
		 * test if new template-import necessary for app and does the import
		 *
		 * Get called on every read of a eTemplate, caches the result in phpgw_info.
		 * The timestamp of the last import for app gets written into the db.
		 *
		 * @param string $app app- or template-name
		 * @return string translated message with number of templates imported
		 */
		function test_import($app)	// should be done from the setup-App
		{
			list($app) = explode('.',$app);

			if (!$app || $GLOBALS['egw_info']['etemplate']['import_tested'][$app])
			{
				return '';	// ensure test is done only once per call and app
			}
			$GLOBALS['egw_info']['etemplate']['import_tested'][$app] = True;	// need to be done before new ...

			$path = EGW_SERVER_ROOT."/$app/setup/etemplates.inc.php";

			if ($time = @filemtime($path))
			{
				$templ =& new soetemplate(".$app",'','##');
				if ($templ->lang != '##' || $templ->modified < $time) // need to import
				{
					$ret = $this->import_dump($app);
					$templ->modified = $time;
					$templ->save('.'.$app,'','##');
				}
			}
			return $ret;
		}
		
		/**
		 * prints/echos the template's content, eg. for debuging
		 *
		 * @param boolean $backtrace = true give a function backtrace
		 * @param boolean $no_other_objs = true dump other objs (db, html, ...) too
		 */
		function echo_tmpl($backtrace=true,$no_other_objs=true)
		{
			static $objs = array('db','html','xul_io');
			
			if ($backtrace) echo "<p>".function_backtrace(1)."</p>\n";

			if ($no_other_objs)
			{
				foreach($objs as $obj)
				{
					$$obj = &$this->$obj;
					unset($this->$obj);
				}
			}
			_debug_array($this);
			
			if ($no_other_objs)
			{
				foreach($objs as $obj)
				{
					$this->$obj = &$$obj;
					unset($$obj);
				}
			}
		}

		/**
		 * applys a function to each widget in the children tree of the template
		 *
		 * The function should be defined as [&]func([&]$widget,[&]$extra[,$path])
		 * If the function returns anything but null or sets $extra['__RETURN__NOW__'] (func has to reference $extra !!!), 
		 * the walk stops imediatly and returns that result
		 *
		 * Only some widgets have a sub-tree of children: *box, grid, template, ...
		 * For them we call tree_walk($widget,$func,$extra) instead of func direct
		 * 
		 * Please note: as call_user_func_array does not return references, methods ($func is an array) can not either!!!
		 *
		 * @param string/array $func function to use or array($obj,'method')
		 * @param mixed &$extra extra parameter passed to function
		 * @param string $path='/' start-path
		 * @return mixed return-value of func or null if nothing returned at all
		 */
		function &widget_tree_walk($func,&$extra,$path='/')
		{
			if (!is_callable($func))
			{
				echo "<p><b>boetemplate($this->name)::widget_tree_walk</b>(".print_r($func,true).", ".print_r($extra,true).", ".print_r($opts,true).") func is not callable !!!<br>".function_backtrace()."</p>";
				return false;
			}
			foreach($this->children as $c => $nul)
			{
				$child = &$this->children[$c];
				if (isset($this->widgets_with_children[$child['type']]))
				{
					$result =& $this->tree_walk($child,$func,$extra,$path.$c);
				}
				elseif (is_array($func))
				{
					$result =& call_user_func_array($func,array(&$child,&$extra,$path.$c));
				}
				else
				{
					$result =& $func($child,$extra,$path.$c);
				}
				if (!is_null($result) || is_array($extra) && isset($extra['__RETURN_NOW__'])) break;
			}
			return $result;
		}
				
		/**
		 * applys a function to each child in the tree of a widget (incl. the widget itself) 
		 *
		 * The function should be defined as [&]func([&]$widget,[&]$extra[,$path]) [] = optional
		 * If the function returns anything but null or sets $extra['__RETURN__NOW__'] (func has to reference $extra !!!), 
		 * the walk stops imediatly and returns that result
		 *
		 * Only some widgets have a sub-tree of children: *box, grid, template, ...
		 * For performance reasons the function use recursion only if a widget with children contains 
		 * a further widget with children.
		 *
		 * @param array $widget the widget(-tree) the function should be applied too
		 * @param string/array $func function to use or array($obj,'method')
		 * @param mixed &$extra extra parameter passed to function
		 * @param string $path path of widget in the widget-tree
		 * @return mixed return-value of func or null if nothing returned at all
		 */
		function &tree_walk(&$widget,$func,&$extra,$path='')
		{
			if (!is_callable($func))
			{
				echo "<p><b>boetemplate::tree_walk</b>(, ".print_r($func,true).", ".print_r($extra,true).", ".print_r($opts,true).") func is not callable !!!<br>".function_backtrace()."</p>";
				return false;
			}
			if(is_array($func))
			{
				$result =& call_user_func_array($func,array(&$widget,&$extra,$path));
			}
			else
			{
				$result =& $func($widget,$extra,$path);
			}
			if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__']) || 
				!isset($this->widgets_with_children[$widget['type']]))
			{
				return $result;
			}
			switch($widget['type'])
			{
				case 'box':
				case 'vbox':
				case 'hbox':
				case 'groupbox':
				case 'deck':
					for($n = 1; is_array($widget[$n]); ++$n)
					{
						$child = &$widget[$n];
						if (isset($this->widgets_with_children[$child['type']]))
						{
							$result =& $this->tree_walk($child,$func,$extra,$path.'/'.$n);
						}
						elseif(is_array($func))
						{
							$result =& call_user_func_array($func,array(&$child,&$extra,$path.'/'.$n));
						}
						else
						{
							$result =& $func($child,$extra,$path.'/'.$n);
						}
						if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__'])) return $result;
					}
					break;

				case 'grid':
					$data = &$widget['data'];
					if (!is_array($data)) break;	// no children

					foreach($data as $r => $row)
					{
						if (!$r || !is_array($row)) continue;

						foreach($row as $c => $col)
						{
							$child = &$data[$r][$c];
							if (isset($this->widgets_with_children[$child['type']]))
							{
								$result =& $this->tree_walk($child,$func,$extra,$path.'/'.$r.$c);
							}
							elseif(is_array($func))
							{
								$result =& call_user_func_array($func,array(&$child,&$extra,$path.'/'.$r.$c));
							}
							else
							{
								$result =& $func($child,$extra,$path.'/'.$r.$c);
							}
							if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__'])) return $result;
							unset($child);
						}
					}
					break;
					
				case 'template':
					if (!isset($widget['obj']) && $widget['name'][0] != '@')
					{
						$widget['obj'] =& new etemplate;
						if (!$widget['obj']->read($widget['name'])) $widget['obj'] = false;
					}
					if (!is_object($widget['obj'])) break;	// cant descent into template
					
					$result =& $widget['obj']->widget_tree_walk($func,$extra,$path.'/');
					break;
			}
			return $result;
		}
	}

	if (!function_exists('getToTranslateCell'))
	{
		/**
		 * extracts all translatable labels from a widget
		 *
		 * @param array $cell the widget
		 * @param array &$to_trans array with (lowercased) label => translation pairs
		 */
		function getToTranslateCell($cell,&$to_trans)
		{
			//echo $cell['name']; _debug_array($cell);
			$strings = explode('|',$cell['help']);

			if ($cell['type'] != 'image')
			{
				$strings = array_merge($strings,explode('|',$cell['label']));
			}
			list($extra_row) = explode(',',$cell['size']);
			if (substr($cell['type'],0,6) == 'select' && !empty($extra_row) && !intval($extra_row))
			{
				$strings[] = $extra_row;
			}
			if (!empty($cell['blur']))
			{
				$strings[] = $cell['blur'];
			}
			foreach($strings as $str)
			{
				if (strlen($str) > 1 && $str{0} != '@' && $str{0} != '$' && 
					strpos($str,'$row') === false && strpos($str,'$cont') === false)
				{
					$to_trans[trim(strtolower($str))] = $str;
				}
			}
		}
	}