PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB`Mini Shell

HOME


Mini Shell 1.0
DIR:/home/psrcpvtltd.com/www/wp-content/plugins/newsletter/includes/
Upload File :
Current File : /home/psrcpvtltd.com/www/wp-content/plugins/newsletter/includes/module.php
<?php

defined('ABSPATH') || exit;

require_once __DIR__ . '/logger.php';
require_once __DIR__ . '/store.php';
require_once __DIR__ . '/composer.php';
require_once __DIR__ . '/addon.php';
require_once __DIR__ . '/mailer.php';
require_once __DIR__ . '/themes.php';

class TNP_Media {

    var $url;
    var $width;
    var $height;
    var $alt;
    var $link;

    /** Sets the width recalculating the height */
    public function set_width($width) {
        $this->height = floor($width / $this->width * $this->height);
        $this->width = $width;
    }

}

/**
 * @property int $id The list unique identifier
 * @property string $name The list name
 * @property bool $forced If the list must be added to every new subscriber
 * @property int $status When and how the list is visible to the subscriber - see constants
 * @property bool $checked If it must be pre-checked on subscription form
 * @property array $languages The list of language used to pre-assign this list
 */
class TNP_List {

    const STATUS_PRIVATE = 0;
    const STATUS_PUBLIC = 1;
    const SUBSCRIPTION_HIDE = 0;
    const SUBSCRIPTION_SHOW = 1;
    const SUBSCRIPTION_SHOW_CHECKED = 2;
    const PROFILE_HIDE = 0;
    const PROFILE_SHOW = 1;

    var $id;
    var $name;
    var $status;
    var $forced;
    var $checked;
    var $show_on_subscription;
    var $show_on_profile;

}

/**
 * @property int $id The list unique identifier
 * @property string $name The list name
 * @property int $status When and how the list is visible to the subscriber - see constants
 * @property string $type Field type: text or select
 * @property array $options Field options (usually the select items)
 */
class TNP_Profile {

    const STATUS_PRIVATE = 0;
    const STATUS_PUBLIC = 2;
    const STATUS_PROFILE_ONLY = 1;
    const STATUS_HIDDEN = 3; // Public but never shown (can be set with a hidden form field)
    const TYPE_TEXT = 'text';
    const TYPE_SELECT = 'select';

    public $id;
    public $name;
    public $status;
    public $type;
    public $options;
    public $placeholder;
    public $rule;

    public function __construct($id, $name, $status, $type, $options, $placeholder, $rule) {
        $this->id = $id;
        $this->name = $name;
        $this->status = $status;
        $this->type = $type;
        $this->options = $options;
        $this->placeholder = $placeholder;
        $this->rule = $rule;
    }

    function is_select() {
        return $this->type == self::TYPE_SELECT;
    }

    function is_text() {
        return $this->type == self::TYPE_TEXT;
    }

    function is_required() {
        return $this->rule == 1;
    }

}

class TNP_Profile_Service {

    /**
     * 
     * @param string $language
     * @param string $type
     * @return TNP_Profile[]
     */
    static function get_profiles($language = '', $type = '') {

        static $profiles = [];
        $k = $language . $type;

        if (isset($profiles[$k])) {
            return $profiles[$k];
        }

        $profiles[$k] = [];
        $profile_options = NewsletterSubscription::instance()->get_options('profile', $language);
        for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
            if (empty($profile_options['profile_' . $i])) {
                continue;
            }
            $profile = self::create_profile_from_options($profile_options, $i);

            if (empty($type) ||
                    ( $type == TNP_Profile::TYPE_SELECT && $profile->is_select() ) ||
                    ( $type == TNP_Profile::TYPE_TEXT && $profile->is_text() )) {
                $profiles[$k]['' . $i] = $profile;
            }
        }

        return $profiles[$k];
    }

    static function get_profile_by_id($id, $language = '') {

        $profiles = self::get_profiles($language);
        if (isset($profiles[$id])) return $profiles[$id];
        return null;
    }

    /**
     * @return TNP_Profile
     */
    private static function create_profile_from_options($options, $id) {
        return new TNP_Profile(
                $id,
                $options['profile_' . $id],
                (int) $options['profile_' . $id . '_status'],
                $options['profile_' . $id . '_type'],
                self::string_db_options_to_array($options['profile_' . $id . '_options']),
                $options['profile_' . $id . '_placeholder'],
                $options['profile_' . $id . '_rules']
        );
    }

    /**
     * Returns a list of strings which are the items for the select field.
     * @return array
     */
    private static function string_db_options_to_array($string_options) {
        $items = array_map('trim', explode(',', $string_options));
        $items = array_combine($items, $items);

        return $items;
    }

}

/**
 * @property int $id The subscriber unique identifier
 * @property string $email The subscriber email
 * @property string $name The subscriber name or first name
 * @property string $surname The subscriber last name
 * @property string $status The subscriber status
 * @property string $language The subscriber language code 2 chars lowercase
 * @property string $token The subscriber secret token
 */
abstract class TNP_User {

    const STATUS_CONFIRMED = 'C';
    const STATUS_NOT_CONFIRMED = 'S';
    const STATUS_UNSUBSCRIBED = 'U';
    const STATUS_BOUNCED = 'B';

}

/**
 * @property int $id The subscriber unique identifier
 * @property string $subject The subscriber email
 * @property string $message The subscriber name or first name
 * @property string $track The subscriber last name
 * @property array $options The subscriber status
 * */
abstract class TNP_Email {

    const STATUS_DRAFT = 'new';
    const STATUS_SENT = 'sent';
    const STATUS_SENDING = 'sending';
    const STATUS_PAUSED = 'paused';

}

class NewsletterModule {

    /**
     * @var NewsletterLogger
     */
    var $logger;

    /**
     * @var NewsletterStore
     */
    var $store;

    /**
     * The main module options
     * @var array
     */
    var $options;

    /**
     * @var string The module name
     */
    var $module;

    /**
     * The module version
     * @var string
     */
    var $version;
    var $old_version;

    /**
     * Prefix for all options stored on WordPress options table.
     * @var string
     */
    var $prefix;

    /**
     * @var NewsletterThemes
     */
    var $themes;
    var $components;

    function __construct($module, $version, $module_id = null, $components = array()) {
        $this->module = $module;
        $this->version = $version;
        $this->prefix = 'newsletter_' . $module;
        array_unshift($components, '');
        $this->components = $components;


        $this->logger = new NewsletterLogger($module);
        $this->options = $this->get_options();
        $this->store = NewsletterStore::singleton();

        //$this->logger->debug($module . ' constructed');
        // Version check
        if (is_admin()) {
            $this->old_version = get_option($this->prefix . '_version', '0.0.0');

            if ($this->old_version == '0.0.0') {
                require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
                $this->first_install();
                update_option($this->prefix . "_first_install_time", time(), FALSE);
            }

            if (strcmp($this->old_version, $this->version) != 0) {
                require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
                $this->logger->info('Version changed from ' . $this->old_version . ' to ' . $this->version);
                // Do all the stuff for this version change
                $this->upgrade();
                update_option($this->prefix . '_version', $this->version);
            }

            add_action('admin_menu', array($this, 'admin_menu'));
        }
    }

    /**
     *
     * @global wpdb $wpdb
     * @param string $query
     */
    function query($query) {
        global $wpdb;

        $this->logger->debug($query);
        //$start = microtime(true);
        $r = $wpdb->query($query);
        //$this->logger->debug($wpdb->last_query);
        //$this->logger->debug('Execution time: ' . (microtime(true)-$start));
        //$this->logger->debug('Result: ' . $r);
        if ($r === false) {
            $this->logger->fatal($query);
            $this->logger->fatal($wpdb->last_error);
        }
        return $r;
    }

    function get_results($query) {
        global $wpdb;
        $r = $wpdb->get_results($query);
        if ($r === false) {
            $this->logger->fatal($query);
            $this->logger->fatal($wpdb->last_error);
        }
        return $r;
    }

    /**
     *
     * @global wpdb $wpdb
     * @param string $table
     * @param array $data
     */
    function insert($table, $data) {
        global $wpdb;
        $this->logger->debug("inserting into table $table");
        $r = $wpdb->insert($table, $data);
        if ($r === false) {
            $this->logger->fatal($wpdb->last_error);
        }
    }

    function first_install() {
        $this->logger->debug('First install');
    }

    /**
     * Does a basic upgrade work, checking if the options is already present and if not (first
     * installation), recovering the defaults, saving them on database and initializing the
     * internal $options.
     */
    function upgrade() {
        foreach ($this->components as $component) {
            $this->logger->debug('Upgrading component ' . $component);
            $this->init_options($component);
        }
    }

    function init_options($component = '', $autoload = true) {
        global $wpdb;
        $default_options = $this->get_default_options($component);
        $options = $this->get_options($component);
        $options = array_merge($default_options, $options);
        $this->save_options($options, $component, $autoload);
    }

    function upgrade_query($query) {
        global $wpdb, $charset_collate;

        $this->logger->info('upgrade_query> Executing ' . $query);
        $suppress_errors = $wpdb->suppress_errors(true);
        $wpdb->query($query);
        if ($wpdb->last_error) {
            $this->logger->debug($wpdb->last_error);
        }
        $wpdb->suppress_errors($suppress_errors);
    }

    /** Returns a prefix to be used for option names and other things which need to be uniquely named. The parameter
     * "sub" should be used when a sub name is needed for another set of options or like.
     *
     * @param string $sub
     * @return string The prefix for names
     */
    function get_prefix($sub = '', $language = '') {
        return $this->prefix . (!empty($sub) ? '_' : '') . $sub . (!empty($language) ? '_' : '') . $language;
    }

    /**
     * Returns the options of a module, if not found an empty array.
     */
    function get_options($sub = '', $language = '') {
        $options = get_option($this->get_prefix($sub, $language), array());
        // Protection against scarmled database...
        if (!is_array($options)) {
            $options = array();
        }
        if ($language) {
            $main_options = get_option($this->get_prefix($sub));
            // Protection against scarmled database...
            if (!is_array($main_options))
                $main_options = array();
            //$options = array_merge($main_options, array_filter($options));
            $options = array_merge($main_options, $options);
        }
        return $options;
    }

    function get_default_options($sub = '') {
        if (!empty($sub)) {
            $sub = '-' . $sub;
        }
        $file = NEWSLETTER_DIR . '/' . $this->module . '/defaults' . $sub . '.php';
        if (file_exists($file)) {
            @include $file;
        }

        if (!isset($options) || !is_array($options)) {
            return array();
        }
        return $options;
    }

    function reset_options($sub = '') {
        $this->save_options(array_merge($this->get_options($sub), $this->get_default_options($sub)), $sub);
        return $this->get_options($sub);
    }

    /**
     * Saves the module options (or eventually a subset names as per parameter $sub). $options
     * should be an array (even if it can work with non array options.
     * The internal module options variable IS initialized with those new options only for the main
     * options (empty $sub parameter).
     * If the options contain a "theme" value, the theme-related options contained are saved as well
     * (used by some modules).
     *
     * @param array $options
     * @param string $sub
     */
    function save_options($options, $sub = '', $autoload = null, $language = '') {
        update_option($this->get_prefix($sub, $language), $options, $autoload);
        if (empty($sub) && empty($language)) {
            $this->options = $options;
            if (isset($this->themes) && isset($options['theme'])) {
                $this->themes->save_options($options['theme'], $options);
            }
        }
    }

    function delete_options($sub = '') {
        delete_option($this->get_prefix($sub));
        if (empty($sub)) {
            $this->options = array();
        }
    }

    function merge_options($options, $sub = '', $language = '') {
        if (!is_array($options)) {
            $options = array();
        }
        $old_options = $this->get_options($sub, $language);
        $this->save_options(array_merge($old_options, $options), $sub, null, $language);
    }

    function backup_options($sub) {
        $options = $this->get_options($sub);
        update_option($this->get_prefix($sub) . '_backup', $options, false);
    }

    function get_last_run($sub = '') {
        return get_option($this->get_prefix($sub) . '_last_run', 0);
    }

    /**
     * Save the module last run value. Used to store a timestamp for some modules,
     * for example the Feed by Mail module.
     *
     * @param int $time Unix timestamp (as returned by time() for example)
     * @param string $sub Sub module name (default empty)
     */
    function save_last_run($time, $sub = '') {
        update_option($this->get_prefix($sub) . '_last_run', $time);
    }

    /**
     * Sums $delta seconds to the last run time.
     * @param int $delta Seconds
     * @param string $sub Sub module name (default empty)
     */
    function add_to_last_run($delta, $sub = '') {
        $time = $this->get_last_run($sub);
        $this->save_last_run($time + $delta, $sub);
    }

    /**
     * Checks if the semaphore of that name (for this module) is still red. If it is active the method
     * returns false. If it is not active, it will be activated for $time seconds.
     *
     * Since this method activate the semaphore when called, it's name is a bit confusing.
     *
     * @param string $name Sempahore name (local to this module)
     * @param int $time Max time in second this semaphore should stay red
     * @return boolean False if the semaphore is red and you should not proceed, true is it was not active and has been activated.
     */
    function check_transient($name, $time) {
        if ($time < 60)
            $time = 60;
        //usleep(rand(0, 1000000));
        if (($value = get_transient($this->get_prefix() . '_' . $name)) !== false) {
            list($t, $v) = explode(';', $value, 2);
            $this->logger->error('Blocked by transient ' . $this->get_prefix() . '_' . $name . ' set ' . (time() - $t) . ' seconds ago by ' . $v);
            return false;
        }
        //$ip = ''; //gethostbyname(gethostname());
        $value = time() . ";" . ABSPATH . ';' . gethostname();
        set_transient($this->get_prefix() . '_' . $name, $value, $time);
        return true;
    }

    function delete_transient($name = '') {
        delete_transient($this->get_prefix() . '_' . $name);
    }

    /** Returns a random token of the specified size (or 10 characters if size is not specified).
     *
     * @param int $size
     * @return string
     */
    static function get_token($size = 10) {
        return substr(md5(rand()), 0, $size);
    }

    /**
     * Adds query string parameters to an URL checing id there are already other parameters.
     *
     * @param string $url
     * @param string $qs The part of query-string to add (param1=value1&param2=value2...)
     * @param boolean $amp If the method must use the &amp; instead of the plain & (default true)
     * @return string
     */
    static function add_qs($url, $qs, $amp = true) {
        if (strpos($url, '?') !== false) {
            if ($amp)
                return $url . '&amp;' . $qs;
            else
                return $url . '&' . $qs;
        } else
            return $url . '?' . $qs;
    }

    /**
     * Returns the email address normalized, lowercase with no spaces. If it's not a valid email
     * returns false.
     */
    static function normalize_email($email) {
        if (!is_string($email))
            return false;
        $email = strtolower(trim($email));
        if (!is_email($email)) {
            return false;
        }
        //$email = apply_filters('newsletter_normalize_email', $email);
        return $email;
    }

    static function normalize_name($name) {
        $name = html_entity_decode($name, ENT_QUOTES);
        $name = str_replace(';', ' ', $name);
        $name = strip_tags($name);

        return $name;
    }

    static function normalize_sex($sex) {
        $sex = trim(strtolower($sex));
        if ($sex != 'f' && $sex != 'm') {
            $sex = 'n';
        }
        return $sex;
    }

    static function is_email($email, $empty_ok = false) {

        if (!is_string($email))
            return false;
        $email = strtolower(trim($email));

        if ($email == '') {
            return $empty_ok;
        }

        if (!is_email($email)) {
            return false;
        }

        // TODO: To be moved on the subscription module and make configurable
        if (strpos($email, 'mailinator.com') !== false) {
            return false;
        }
        if (strpos($email, 'guerrillamailblock.com') !== false) {
            return false;
        }
        if (strpos($email, 'emailtemporanea.net') !== false) {
            return false;
        }
        return true;
    }

    /**
     * Converts a GMT date from mysql (see the posts table columns) into a timestamp.
     *
     * @param string $s GMT date with format yyyy-mm-dd hh:mm:ss
     * @return int A timestamp
     */
    static function m2t($s) {

        // TODO: use the wordpress function I don't remember the name
        $s = explode(' ', $s);
        $d = explode('-', $s[0]);
        $t = explode(':', $s[1]);
        return gmmktime((int) $t[0], (int) $t[1], (int) $t[2], (int) $d[1], (int) $d[2], (int) $d[0]);
    }

    static function format_date($time) {
        if (empty($time)) {
            return '-';
        }
        return gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
    }

    static function format_time_delta($delta) {
        $days = floor($delta / (3600 * 24));
        $hours = floor(($delta % (3600 * 24)) / 3600);
        $minutes = floor(($delta % 3600) / 60);
        $seconds = floor(($delta % 60));
        $buffer = $days . ' days, ' . $hours . ' hours, ' . $minutes . ' minutes, ' . $seconds . ' seconds';
        return $buffer;
    }

    /**
     * Formats a scheduler returned "next execution" time, managing negative or false values. Many times
     * used in conjuction with "last run".
     *
     * @param string $name The scheduler name
     * @return string
     */
    static function format_scheduler_time($name) {
        $time = wp_next_scheduled($name);
        if ($time === false) {
            return 'No next run scheduled';
        }
        $delta = $time - time();
        // If less 10 minutes late it can be a cron problem but now it is working
        if ($delta < 0 && $delta > -600) {
            return 'Probably running now';
        } else if ($delta <= -600) {
            return 'It seems the cron system is not working. Reload the page to see if this message change.';
        }
        return 'Runs in ' . self::format_time_delta($delta);
    }

    static function date($time = null, $now = false, $left = false) {
        if (is_null($time)) {
            $time = time();
        }
        if ($time == false) {
            $buffer = 'none';
        } else {
            $buffer = gmdate(get_option('date_format') . ' ' . get_option('time_format'), $time + get_option('gmt_offset') * 3600);
        }
        if ($now) {
            $buffer .= ' (now: ' . gmdate(get_option('date_format') . ' ' .
                            get_option('time_format'), time() + get_option('gmt_offset') * 3600);
            $buffer .= ')';
        }
        if ($left) {
            $buffer .= ', ' . gmdate('H:i:s', $time - time()) . ' left';
        }
        return $buffer;
    }

    /**
     * Return an array of array with on first element the array of recent post and on second element the array
     * of old posts.
     *
     * @param array $posts
     * @param int $time
     */
    static function split_posts(&$posts, $time = 0) {
        if ($time < 0) {
            return array_chunk($posts, ceil(count($posts) / 2));
        }

        $result = array(array(), array());

        if (empty($posts))
            return $result;

        foreach ($posts as &$post) {
            if (self::is_post_old($post, $time))
                $result[1][] = $post;
            else
                $result[0][] = $post;
        }
        return $result;
    }

    static function is_post_old(&$post, $time = 0) {
        return self::m2t($post->post_date_gmt) <= $time;
    }

    static function get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
        global $post;

        if (empty($post_id))
            $post_id = $post->ID;
        if (empty($post_id))
            return $alternative;

        $image_id = function_exists('get_post_thumbnail_id') ? get_post_thumbnail_id($post_id) : false;
        if ($image_id) {
            $image = wp_get_attachment_image_src($image_id, $size);
            return $image[0];
        } else {
            $attachments = get_children(array('post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID'));

            if (empty($attachments)) {
                return $alternative;
            }

            foreach ($attachments as $id => $attachment) {
                $image = wp_get_attachment_image_src($id, $size);
                return $image[0];
            }
        }
    }

    /**
     * Cleans up a text containing url tags with appended the absolute URL (due to
     * the editor behavior) moving back them to the simple form.
     */
    static function clean_url_tags($text) {
        $text = str_replace('%7B', '{', $text);
        $text = str_replace('%7D', '}', $text);

        // Only tags which are {*_url}
        $text = preg_replace("/[\"']http[^\"']+(\\{[^\\}]+_url\\})[\"']/i", "\"\\1\"", $text);
        return $text;
    }

    function admin_menu() {
        
    }

    function add_menu_page($page, $title, $capability = '') {
        if (!Newsletter::instance()->is_allowed())
            return;
        $name = 'newsletter_' . $this->module . '_' . $page;
        add_submenu_page('newsletter_main_index', $title, $title, 'exist', $name, array($this, 'menu_page'));
    }

    function add_admin_page($page, $title) {
        if (!Newsletter::instance()->is_allowed()) {
            return;
        }
        $name = 'newsletter_' . $this->module . '_' . $page;
        add_submenu_page(null, $title, $title, 'exist', $name, array($this, 'menu_page'));
    }

    function sanitize_file_name($name) {
        return preg_replace('/[^a-z_\\-]/i', '', $name);
    }

    function menu_page() {
        global $plugin_page, $newsletter, $wpdb;

        $parts = explode('_', $plugin_page, 3);
        $module = $this->sanitize_file_name($parts[1]);
        $page = $this->sanitize_file_name($parts[2]);
        $page = str_replace('_', '-', $page);

        $file = NEWSLETTER_DIR . '/' . $module . '/' . $page . '.php';

        require $file;
    }

    function get_admin_page_url($page) {
        return admin_url('admin.php') . '?page=newsletter_' . $this->module . '_' . $page;
    }

    /** Returns all the emails of the give type (message, feed, followup, ...) and in the given format
     * (default as objects). Return false on error or at least an empty array. Errors should never
     * occur.
     *
     * @global wpdb $wpdb
     * @param string $type
     * @return boolean|array
     */
    function get_emails($type = null, $format = OBJECT) {
        global $wpdb;
        if ($type == null) {
            $list = $wpdb->get_results("select * from " . NEWSLETTER_EMAILS_TABLE . " order by id desc", $format);
        } else {
            $type = (string) $type;
            $list = $wpdb->get_results($wpdb->prepare("select * from " . NEWSLETTER_EMAILS_TABLE . " where type=%s order by id desc", $type), $format);
        }
        if ($wpdb->last_error) {
            $this->logger->error($wpdb->last_error);
            return false;
        }
        if (empty($list)) {
            return array();
        }
        return $list;
    }

    /**
     * Retrieves an email from DB and unserialize the options.
     *
     * @param mixed $id
     * @param string $format
     * @return TNP_Email An object with the same fields of TNP_Email, but not actually of that type
     */
    function get_email($id, $format = OBJECT) {
        $email = $this->store->get_single(NEWSLETTER_EMAILS_TABLE, $id, $format);
        if (!$email) {
            return null;
        }
        if ($format == OBJECT) {
            $email->options = maybe_unserialize($email->options);
            if (!is_array($email->options)) {
                $email->options = array();
            }
            if (empty($email->query)) {
                $email->query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
            }
        } else if ($format == ARRAY_A) {
            $email['options'] = maybe_unserialize($email['options']);
            if (!is_array($email['options'])) {
                $email['options'] = array();
            }
            if (empty($email['query'])) {
                $email['query'] = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
            }
        }
        return $email;
    }

    /**
     * Save an email and provide serialization, if needed, of $email['options'].
     * @return TNP_Email
     */
    function save_email($email, $return_format = OBJECT) {
        if (is_object($email)) {
            $email = (array) $email;
        }

        if (isset($email['subject'])) {
            if (mb_strlen($email['subject'], 'UTF-8') > 250) {
                $email['subject'] = mb_substr($email['subject'], 0, 250, 'UTF-8');
            }
        }
        if (isset($email['options']) && is_array($email['options'])) {
            $email['options'] = serialize($email['options']);
        }
        $email = $this->store->save(NEWSLETTER_EMAILS_TABLE, $email, $return_format);
        if ($return_format == OBJECT) {
            $email->options = maybe_unserialize($email->options);
            if (!is_array($email->options))
                $email->options = array();
        } else if ($return_format == ARRAY_A) {
            $email['options'] = maybe_unserialize($email['options']);
            if (!is_array($email['options']))
                $email['options'] = array();
        }
        return $email;
    }

    function get_email_from_request() {

        if (isset($_REQUEST['nek'])) {
            list($id, $token) = @explode('-', $_REQUEST['nek'], 2);
        } else if (isset($_COOKIE['tnpe'])) {
            list($id, $token) = @explode('-', $_COOKIE['tnpe'], 2);
        } else {
            return null;
        }

        $email = $this->get_email($id);

        // TODO: Check the token? It's really useful?

        return $email;
    }

    /**
     * Delete one or more emails identified by ID (single value or array of ID)
     * @global wpdb $wpdb
     * @param int|array $id
     * @return boolean
     */
    function delete_email($id) {
        global $wpdb;
        $r = $this->store->delete(NEWSLETTER_EMAILS_TABLE, $id);
        if ($r !== false) {
            // $id could be an array if IDs
            $id = (array) $id;
            foreach ($id as $email_id) {
                $wpdb->delete(NEWSLETTER_STATS_TABLE, array('email_id' => $email_id));
                $wpdb->delete(NEWSLETTER_SENT_TABLE, array('email_id' => $email_id));
            }
        }
        return $r;
    }

    function get_email_field($id, $field_name) {
        return $this->store->get_field(NEWSLETTER_EMAILS_TABLE, $id, $field_name);
    }

    function get_email_status_slug($email) {
        $email = (object) $email;
        if ($email->status == 'sending' && $email->send_on > time()) {
            return 'scheduled';
        }
        return $email->status;
    }

    function get_email_status_label($email) {
        $email = (object) $email;
        $status = $this->get_email_status_slug($email);
        switch ($status) {
            case 'sending':
                return __('Sending', 'newsletter');
            case 'scheduled':
                return __('Scheduled', 'newsletter');
            case 'sent':
                return __('Sent', 'newsletter');
            case 'paused':
                return __('Paused', 'newsletter');
            case 'new':
                return __('Draft', 'newsletter');
            default:
                return ucfirst($email->status);
        }
    }

    function show_email_status_label($email) {
        echo '<span class="tnp-email-status-', $this->get_email_status_slug($email), '">', esc_html($this->get_email_status_label($email)), '</span>';
    }

    function get_email_progress($email, $format = 'percent') {
        return $email->total > 0 ? intval($email->sent / $email->total * 100) : 0;
    }

    function show_email_progress_bar($email, $attrs = array()) {

        $email = (object) $email;

        $attrs = array_merge(array('format' => 'percent', 'numbers' => false, 'scheduled' => false), $attrs);

        if ($email->status == 'sending' && $email->send_on > time()) {
            if ($attrs['scheduled']) {
                echo '<span class="tnp-progress-date">', $this->format_date($email->send_on), '</span>';
            }
        } else if ($email->status == 'new') {
            echo '';
            return;
        } else if ($email->status == 'sent') {
            $percent = 100;
        } else {
            $percent = $this->get_email_progress($email);
        }


        $label = $percent;
        if ($attrs['format'] == 'numbers') {
            $label = $email->sent . ' ' . __('of', 'newsletter') . ' ' . $email->total;
        }
        echo '<div class="tnp-progress ', $email->status, '">';
        echo '<div class="tnp-progress-bar" role="progressbar" style="width: ', $percent, '%;">&nbsp;', $percent, '%&nbsp;</div>';
        echo '</div>';
        if ($attrs['numbers']) {
            if ($email->status == 'sent') {
                echo '<div class="tnp-progress-numbers">', $email->total, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
            } else {
                echo '<div class="tnp-progress-numbers">', $email->sent, ' ', __('of', 'newsletter'), ' ', $email->total, '</div>';
            }
        }
    }

    function get_email_type_label($type) {

// Is an email?
        if (is_object($type))
            $type = $type->type;

        $label = apply_filters('newsletter_email_type', '', $type);

        if (!empty($label))
            return $label;

        switch ($type) {
            case 'followup':
                return 'Followup';
            case 'message':
                return 'Standard Newsletter';
            case 'feed':
                return 'Feed by Mail';
        }

        if (strpos($type, 'automated') === 0) {
            list($a, $id) = explode('_', $type);
            return 'Automated Channel ' . $id;
        }

        return ucfirst($type);
    }

    function get_email_progress_label($email) {
        if ($email->status == 'sent' || $email->status == 'sending') {
            return $email->sent . ' ' . __('of', 'newsletter') . ' ' . $email->total;
        }
        return '-';
    }

    /**
     * Returns the email unique key
     * @param TNP_User $user
     * @return string
     */
    function get_email_key($email) {
        if (!isset($email->token)) {
            return $email->id . '-';
        }
        return $email->id . '-' . $email->token;
    }

    /** Searches for a user using the nk parameter or the ni and nt parameters. Tries even with the newsletter cookie.
     * If found, the user object is returned or null.
     * The user is returned without regards to his status that should be checked by caller.
     *
     * DO NOT REMOVE EVEN IF OLD
     *
     * @return TNP_User
     */
    function check_user($context = '') {
        global $wpdb;

        $user = null;

        if (isset($_REQUEST['nk'])) {
            list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
        } else if (isset($_COOKIE['newsletter'])) {
            list ($id, $token) = @explode('-', $_COOKIE['newsletter'], 2);
        }

        if (isset($id)) {
            $user = $this->get_user($id);
            if ($user) {
                if ($context == 'preconfirm') {
                    if ($token != md5($user->token)) {
                        $user = null;
                    }
                } else {
                    if ($token != $user->token) {
                        $user = null;
                    }
                }
            }
        }

        if ($user == null && is_user_logged_in()) {
            $user = $this->get_user_by_wp_user_id(get_current_user_id());
        }
        return $user;
    }

    /** Returns the user identify by an id or an email. If $id_or_email is an object or an array, it is assumed it contains
     * the "id" attribute or key and that is used to load the user.
     *
     * @global type $wpdb
     * @param string|int|object|array $id_or_email
     * @param string $format
     * @return TNP_User|null
     */
    function get_user($id_or_email, $format = OBJECT) {
        global $wpdb;

        if (empty($id_or_email))
            return null;

// To simplify the reaload of a user passing the user it self.
        if (is_object($id_or_email)) {
            $id_or_email = $id_or_email->id;
        } else if (is_array($id_or_email)) {
            $id_or_email = $id_or_email['id'];
        }

        $id_or_email = strtolower(trim($id_or_email));

        if (is_numeric($id_or_email)) {
            $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where id=%d limit 1", $id_or_email), $format);
        } else {
            $r = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_USERS_TABLE . " where email=%s limit 1", $id_or_email), $format);
        }

        if ($wpdb->last_error) {
            $this->logger->error($wpdb->last_error);
            return null;
        }
        return $r;
    }

    /**
     * Accepts a user ID or a TNP_User object. Does not check if the user really exists.
     *
     * @param type $user
     */
    function get_user_edit_url($user) {
        $id = $this->to_int_id($user);
        return admin_url('admin.php') . '?page=newsletter_users_edit&id=' . $id;
    }

    /**
     * Returns the user unique key
     * @param TNP_User $user
     * @return string
     */
    function get_user_key($user, $context = '') {
        if (empty($user->token)) {
            $this->refresh_user_token($user);
        }

        if ($context == 'preconfirm') {
            return $user->id . '-' . md5($user->token);
        }
        return $user->id . '-' . $user->token;
    }

    function get_user_status_label($user) {
        switch ($user->status) {
            case 'S': return __('NOT CONFIRMED', 'newsletter');
                break;
            case 'C': return __('CONFIRMED', 'newsletter');
                break;
            case 'U': return __('UNSUBSCRIBED', 'newsletter');
                break;
            case 'B': return __('BOUNCED', 'newsletter');
                break;
        }
        return '';
    }

    /**
     * Return the user identified by the "nk" parameter (POST or GET).
     * If no user can be found or the token is not matching, returns null.
     * If die_on_fail is true it dies instead of return null.
     *
     * @param bool $die_on_fail
     * @return TNP_User
     */
    function get_user_from_request($die_on_fail = false, $context = '') {
        $id = 0;
        if (isset($_REQUEST['nk'])) {
            list($id, $token) = @explode('-', $_REQUEST['nk'], 2);
        }
        $user = $this->get_user($id);

        if ($user == null) {
            if ($die_on_fail) {
                die(__('No subscriber found.', 'newsletter'));
            } else {
                return $this->get_user_from_logged_in_user();
            }
        }

        if ($context == 'preconfirm') {
            $user_token = md5($user->token);
        } else {
            $user_token = $user->token;
        }

        if ($token != $user_token) {
            if ($die_on_fail) {
                die(__('No subscriber found.', 'newsletter'));
            } else {
                return $this->get_user_from_logged_in_user();
            }
        }
        return $user;
    }

    function get_user_from_logged_in_user() {
        if (is_user_logged_in()) {
            return $this->get_user_by_wp_user_id(get_current_user_id());
        }
        return null;
    }

    function get_user_count($refresh = false) {
        global $wpdb;
        $user_count = get_transient('newsletter_user_count');
        if ($user_count === false || $refresh) {
            $user_count = $wpdb->get_var("select count(*) from " . NEWSLETTER_USERS_TABLE . " where status='C'");
            set_transient('newsletter_user_count', $user_count, DAY_IN_SECONDS);
        }
        return $user_count;
    }

    /**
     * @param string $language The language for the list labels (it does not affect the lists returned)
     * @return TNP_Profile[]
     */
    function get_profiles($language = '') {
        return TNP_Profile_Service::get_profiles($language);
    }

    /**
     * @param string $language The language for the list labels (it does not affect the lists returned)
     * @return TNP_List[]
     */
    function get_lists($language = '') {
        static $lists = array();
        if (isset($lists[$language])) {
            return $lists[$language];
        }

        $lists[$language] = array();
        $data = NewsletterSubscription::instance()->get_options('lists', $language);
        for ($i = 1; $i <= NEWSLETTER_LIST_MAX; $i++) {
            if (empty($data['list_' . $i])) {
                continue;
            }
            $list = new TNP_List();
            $list->name = $data['list_' . $i];
            $list->id = $i;

            // New format
            if (isset($data['list_' . $i . '_subscription'])) {
                $list->forced = !empty($data['list_' . $i . '_forced']);
                $list->status = empty($data['list_' . $i . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
                $list->checked = $data['list_' . $i . '_subscription'] == 2;
                $list->show_on_subscription = $list->status != TNP_List::STATUS_PRIVATE && !empty($data['list_' . $i . '_subscription']) && !$list->forced;
                $list->show_on_profile = $list->status != TNP_List::STATUS_PRIVATE && !empty($data['list_' . $i . '_profile']);
            } else {
                $list->forced = !empty($data['list_' . $i . '_forced']);
                $list->status = empty($data['list_' . $i . '_status']) ? TNP_List::STATUS_PRIVATE : TNP_List::STATUS_PUBLIC;
                $list->checked = !empty($data['list_' . $i . '_checked']);
                $list->show_on_subscription = $data['list_' . $i . '_status'] == 2 && !$list->forced;
                $list->show_on_profile = $data['list_' . $i . '_status'] == 1 || $data['list_' . $i . '_status'] == 2;
            }
            if (empty($data['list_' . $i . '_languages'])) {
                $list->languages = array();
            } else {
                $list->languages = $data['list_' . $i . '_languages'];
            }
            $lists[$language]['' . $list->id] = $list;
        }
        return $lists[$language];
    }

    /**
     * Returns an array of TNP_List objects of lists that are public.
     * @return TNP_List[]
     */
    function get_lists_public($language = '') {
        static $lists = array();
        if (isset($lists[$language])) {
            return $lists[$language];
        }

        $lists[$language] = array();
        $all = $this->get_lists($language);
        foreach ($all as $list) {
            if ($list->status == TNP_List::STATUS_PRIVATE) {
                continue;
            }
            $lists[$language]['' . $list->id] = $list;
        }
        return $lists[$language];
    }

    /**
     * Lists to be shown on subscription form.
     *
     * @return TNP_List[]
     */
    function get_lists_for_subscription($language = '') {
        static $lists = array();
        if (isset($lists[$language])) {
            return $lists[$language];
        }

        $lists[$language] = array();
        $all = $this->get_lists($language);
        foreach ($all as $list) {
            if (!$list->show_on_subscription) {
                continue;
            }
            $lists[$language]['' . $list->id] = $list;
        }
        return $lists[$language];
    }

    /**
     * Returns the lists to be shown in the profile page. The list is associative with
     * the list ID as key.
     *
     * @return TNP_List[]
     */
    function get_lists_for_profile($language = '') {
        static $lists = array();
        if (isset($lists[$language])) {
            return $lists[$language];
        }

        $lists[$language] = array();
        $all = $this->get_lists($language);
        foreach ($all as $list) {
            if (!$list->show_on_profile) {
                continue;
            }
            $lists[$language]['' . $list->id] = $list;
        }
        return $lists[$language];
    }

    /**
     * Returns the list object or null if not found.
     *
     * @param int $id
     * @return TNP_List
     */
    function get_list($id, $language = '') {
        $lists = $this->get_lists($language);
        if (!isset($lists['' . $id])) {
            return null;
        }

        return $lists['' . $id];
    }

    /**
     * NEVER CHANGE THIS METHOD SIGNATURE, USER BY THIRD PARTY PLUGINS.
     *
     * Saves a new user on the database. Return false if the email (that must be unique) is already
     * there. For a new users set the token and creation time if not passed.
     *
     * @param array $user
     * @return TNP_User|array|boolean Returns the subscriber reloaded from DB in the specified format. Flase on failure (duplicate email).
     */
    function save_user($user, $return_format = OBJECT) {
        if (is_object($user)) {
            $user = (array) $user;
        }
        if (empty($user['id'])) {
            $existing = $this->get_user($user['email']);
            if ($existing != null) {
                return false;
            }
            if (empty($user['token'])) {
                $user['token'] = NewsletterModule::get_token();
            }
        }
// Due to the unique index on email field, this can fail.
        return $this->store->save(NEWSLETTER_USERS_TABLE, $user, $return_format);
    }

    /**
     * Updates the user last activity timestamp.
     *
     * @global wpdb $wpdb
     * @param TNP_User $user
     */
    function update_user_last_activity($user) {
        global $wpdb;
        $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set last_activity=%d where id=%d limit 1", time(), $user->id));
    }

    function update_user_ip($user, $ip) {
        global $wpdb;
// Only if changed
        $r = $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set ip=%s, geo=0 where ip<>%s and id=%d limit 1", $ip, $ip, $user->id));
    }

    /**
     * Finds single style blocks and adds a style attribute to every HTML tag with a class exactly matching the rules in the style
     * block. HTML tags can use the attribute "inline-class" to exact match a style rules if they need a composite class definition.
     *
     * @param string $content
     * @param boolean $strip_style_blocks
     * @return string
     */
    function inline_css($content, $strip_style_blocks = false) {
        $matches = array();
        // "s" skips line breaks
        $styles = preg_match('|<style>(.*?)</style>|s', $content, $matches);
        if (isset($matches[1])) {
            $style = str_replace(array("\n", "\r"), '', $matches[1]);
            $rules = array();
            preg_match_all('|\s*\.(.*?)\{(.*?)\}\s*|s', $style, $rules);
            for ($i = 0; $i < count($rules[1]); $i++) {
                $class = trim($rules[1][$i]);
                $value = trim($rules[2][$i]);
                $value = preg_replace('|\s+|', ' ', $value);
                $content = str_replace('class="' . $class . '"', 'class="' . $class . '" style="' . $value . '"', $content);
                $content = str_replace('inline-class="' . $class . '"', 'style="' . $value . '"', $content);
            }
        }

        if ($strip_style_blocks) {
            return trim(preg_replace('|<style>.*?</style>|s', '', $content));
        } else {
            return $content;
        }
    }

    /**
     * Returns a list of users marked as "test user".
     * @return TNP_User[]
     */
    function get_test_users() {
        return $this->store->get_all(NEWSLETTER_USERS_TABLE, "where test=1");
    }

    /**
     * Deletes a subscriber and cleans up all the stats table with his correlated data.
     *
     * @global wpdb $wpdb
     * @param int|id[] $id
     */
    function delete_user($id) {
        global $wpdb;
        $id = (array) $id;
        foreach ($id as $user_id) {
            $user = $this->get_user($user_id);
            if ($user) {
                $r = $this->store->delete(NEWSLETTER_USERS_TABLE, $user_id);
                $wpdb->delete(NEWSLETTER_STATS_TABLE, array('user_id' => $user_id));
                $wpdb->delete(NEWSLETTER_SENT_TABLE, array('user_id' => $user_id));
                do_action('newsletter_user_deleted', $user);
            }
        }

        return count($id);
    }

    /**
     * Add to a destination URL the parameters to identify the user, the email and to show
     * an alert message, if required. The parameters are then managed by the [newsletter] shortcode.
     *
     * @param string $url If empty the standard newsletter page URL is used (usually it is empty, but sometime a custom URL has been specified)
     * @param string $message_key The message identifier
     * @param TNP_User|int $user
     * @param TNP_Email|int $email
     * @param string $alert An optional alter message to be shown. Does not work with custom URLs
     * @return string The final URL with parameters
     */
    function build_message_url($url = '', $message_key = '', $user = null, $email = null, $alert = '') {
        $params = 'nm=' . urlencode($message_key);
        $language = '';
        if ($user) {
            if (!is_object($user)) {
                $user = $this->get_user($user);
            }
            if ($message_key == 'confirmation') {
                $params .= '&nk=' . urlencode($this->get_user_key($user, 'preconfirm'));
            } else {
                $params .= '&nk=' . urlencode($this->get_user_key($user));
            }

            $language = $this->get_user_language($user);
        }

        if ($email) {
            if (!is_object($email)) {
                $email = $this->get_email($email);
            }
            $params .= '&nek=' . urlencode($this->get_email_key($email));
        }

        if ($alert) {
            $params .= '&alert=' . urlencode($alert);
        }

        if (empty($url)) {
            $url = Newsletter::instance()->get_newsletter_page_url($language);
        }

        return self::add_qs($url, $params, false);
    }

    /**
     * Builds a standard Newsletter action URL for the specified action.
     *
     * @param string $action
     * @param TNP_User $user
     * @param TNP_Email $email
     * @return string
     */
    function build_action_url($action, $user = null, $email = null) {
        $url = $this->add_qs($this->get_home_url(), 'na=' . urlencode($action));
        //$url = $this->add_qs(admin_url('admin-ajax.php'), 'action=newsletter&na=' . urlencode($action));
        if ($user) {
            $url .= '&nk=' . urlencode($this->get_user_key($user));
        }
        if ($email) {
            $url .= '&nek=' . urlencode($this->get_email_key($email));
        }
        return $url;
    }

    function get_subscribe_url() {
        return $this->build_action_url('s');
    }

    function clean_stats_table() {
        global $wpdb;
        $this->logger->info('Cleaning up stats table');
        $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
        $this->query("delete s from `{$wpdb->prefix}newsletter_stats` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
    }

    function clean_sent_table() {
        global $wpdb;
        $this->logger->info('Cleaning up sent table');
        $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter` u on s.user_id=u.id where u.id is null");
        $this->query("delete s from `{$wpdb->prefix}newsletter_sent` s left join `{$wpdb->prefix}newsletter_emails` e on s.email_id=e.id where e.id is null");
    }

    function clean_user_logs_table() {
//global $wpdb;
    }

    function clean_tables() {
        $this->clean_sent_table();
        $this->clean_stats_table();
        $this->clean_user_logs_table();
    }

    function anonymize_ip($ip) {
        if (empty($ip)) {
            return $ip;
        }
        $parts = explode('.', $ip);
        array_pop($parts);
        return implode('.', $parts) . '.0';
    }

    function process_ip($ip) {
        $option = Newsletter::instance()->options['ip'];
        if (empty($option)) {
            return $ip;
        }
        if ($option == 'anonymize') {
            return $this->anonymize_ip($ip);
        }
        return '';
    }

    function anonymize_user($id) {
        global $wpdb;
        $user = $this->get_user($id);
        if (!$user) {
            return null;
        }

        $user->name = '';
        $user->surname = '';
        $user->ip = $this->anonymize_ip($user->ip);

        for ($i = 1; $i <= NEWSLETTER_PROFILE_MAX; $i++) {
            $field = 'profile_' . $i;
            $user->$field = '';
        }

// [TODO] Status?
        $user->status = TNP_User::STATUS_UNSUBSCRIBED;
        $user->email = $user->id . '@anonymi.zed';

        $user = $this->save_user($user);

        return $user;
    }

    /**
     * Changes a user status. Accept a user object, user id or user email.
     *
     * @param TNP_User $user
     * @param string $status
     * @return TNP_User
     */
    function set_user_status($user, $status) {
        global $wpdb;

        $this->logger->debug('Status change to ' . $status . ' of subscriber ' . $user->id . ' from ' . $_SERVER['REQUEST_URI']);

        $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set status=%s where id=%d limit 1", $status, $user->id));
        $user->status = $status;
        return $this->get_user($user);
    }

    /**
     *
     * @global wpdb $wpdb
     * @param TNP_User $user
     * @return TNP_User
     */
    function refresh_user_token($user) {
        global $wpdb;

        $token = $this->get_token();

        $this->query($wpdb->prepare("update " . NEWSLETTER_USERS_TABLE . " set token=%s where id=%d limit 1", $token, $user->id));
        $user->token = $token;
    }

    /**
     * Create a log entry with the meaningful user data.
     *
     * @global wpdb $wpdb
     * @param TNP_User $user
     * @param string $source
     * @return type
     */
    function add_user_log($user, $source = '') {
        global $wpdb;

        $lists = $this->get_lists_public();
        foreach ($lists as $list) {
            $field_name = 'list_' . $list->id;
            $data[$field_name] = $user->$field_name;
        }
        $data['status'] = $user->status;
        $ip = $this->get_remote_ip();
        $ip = $this->process_ip($ip);
        $this->store->save($wpdb->prefix . 'newsletter_user_logs', array('ip' => $ip, 'user_id' => $user->id, 'source' => $source, 'created' => time(), 'data' => json_encode($data)));
    }

    /**
     *
     * @global wpdb $wpdb
     * @param TNP_User $user
     * @param int $list
     * @param type $value
     */
    function set_user_list($user, $list, $value) {
        global $wpdb;

        $list = (int) $list;
        $value = $value ? 1 : 0;
        $r = $wpdb->update(NEWSLETTER_USERS_TABLE, array('list_' . $list => $value), array('id' => $user->id));
    }

    function set_user_field($id, $field, $value) {
        $this->store->set_field(NEWSLETTER_USERS_TABLE, $id, $field, $value);
    }

    function set_user_wp_user_id($user_id, $wp_user_id) {
        $this->store->set_field(NEWSLETTER_USERS_TABLE, $user_id, 'wp_user_id', $wp_user_id);
    }

    /**
     *
     * @param int $wp_user_id
     * @param string $format
     * @return TNP_User
     */
    function get_user_by_wp_user_id($wp_user_id, $format = OBJECT) {
        return $this->store->get_single_by_field(NEWSLETTER_USERS_TABLE, 'wp_user_id', $wp_user_id, $format);
    }

    /**
     * Returns the user language IF there is a supported mutilanguage plugin installed.
     * @param TNP_User $user
     * @return string Language code or empty
     */
    function get_user_language($user) {
        if ($user && $this->is_multilanguage()) {
            return $user->language;
        }
        return '';
    }

    /**
     * Replaces every possible Newsletter tag ({...}) in a piece of text or HTML.
     *
     * @global wpdb $wpdb
     * @param string $text
     * @param mixed $user Can be an object, associative array or id
     * @param mixed $email Can be an object, associative array or id
     * @param type $referrer
     * @return type
     */
    function replace($text, $user = null, $email = null, $referrer = null) {
        global $wpdb;

        if (strpos($text, '<p') !== false) {
            $esc_html = true;
        } else {
            $esc_html = false;
        }

        static $home_url = false;

        if (!$home_url) {
            $home_url = home_url('/');
        }

//$this->logger->debug('Replace start');
        if ($user !== null && !is_object($user)) {
            if (is_array($user)) {
                $user = (object) $user;
            } else if (is_numeric($user)) {
                $user = $this->get_user($user);
            } else {
                $user = null;
            }
        }

        if ($email !== null && !is_object($email)) {
            if (is_array($email)) {
                $email = (object) $email;
            } else if (is_numeric($email)) {
                $email = $this->get_email($email);
            } else {
                $email = null;
            }
        }

        $initial_language = $this->get_current_language();

        if ($user && $user->language) {
            $this->switch_language($user->language);
        }


        $text = apply_filters('newsletter_replace', $text, $user, $email, $esc_html);

        $text = $this->replace_url($text, 'BLOG_URL', $home_url);
        $text = $this->replace_url($text, 'HOME_URL', $home_url);

        $text = str_replace('{blog_title}', html_entity_decode(get_bloginfo('name')), $text);
        $text = str_replace('{blog_description}', get_option('blogdescription'), $text);

        $text = $this->replace_date($text);

        if ($user) {
            $nk = $this->get_user_key($user);
            $options_profile = NewsletterSubscription::instance()->get_options('profile', $this->get_user_language($user));
            $text = str_replace('{email}', $user->email, $text);
            $name = apply_filters('newsletter_replace_name', $user->name, $user);
            if (empty($name)) {
                $text = str_replace(' {name}', '', $text);
                $text = str_replace('{name}', '', $text);
            } else {
                $text = str_replace('{name}', esc_html($name), $text);
            }

            switch ($user->sex) {
                case 'm': $text = str_replace('{title}', $options_profile['title_male'], $text);
                    break;
                case 'f': $text = str_replace('{title}', $options_profile['title_female'], $text);
                    break;
                case 'n': $text = str_replace('{title}', $options_profile['title_none'], $text);
                    break;
                default:
                    $text = str_replace('{title}', '', $text);
            }


            // Deprecated
            $text = str_replace('{surname}', esc_html($user->surname), $text);
            $text = str_replace('{last_name}', esc_html($user->surname), $text);

            $full_name = esc_html(trim($user->name . ' ' . $user->surname));
            if (empty($full_name)) {
                $text = str_replace(' {full_name}', '', $text);
                $text = str_replace('{full_name}', '', $text);
            } else {
                $text = str_replace('{full_name}', $full_name, $text);
            }

            $text = str_replace('{token}', $user->token, $text);
            $text = str_replace('%7Btoken%7D', $user->token, $text);
            $text = str_replace('{id}', $user->id, $text);
            $text = str_replace('%7Bid%7D', $user->id, $text);
            $text = str_replace('{ip}', $user->ip, $text);
            $text = str_replace('{key}', $nk, $text);
            $text = str_replace('%7Bkey%7D', $nk, $text);

            for ($i = 1; $i < NEWSLETTER_PROFILE_MAX; $i++) {
                $p = 'profile_' . $i;
                $text = str_replace('{profile_' . $i . '}', $user->$p, $text);
            }

            $base = (empty($this->options_main['url']) ? get_option('home') : $this->options_main['url']);
            $id_token = '&amp;ni=' . $user->id . '&amp;nt=' . $user->token;

            $text = $this->replace_url($text, 'SUBSCRIPTION_CONFIRM_URL', $this->build_action_url('c', $user));
            $text = $this->replace_url($text, 'ACTIVATION_URL', $this->build_action_url('c', $user));

// Obsolete.
            $text = $this->replace_url($text, 'FOLLOWUP_SUBSCRIPTION_URL', self::add_qs($base, 'nm=fs' . $id_token));
            $text = $this->replace_url($text, 'FOLLOWUP_UNSUBSCRIPTION_URL', self::add_qs($base, 'nm=fu' . $id_token));

            $text = $this->replace_url($text, 'UNLOCK_URL', $this->build_action_url('ul', $user));
        } else {
            $text = $this->replace_url($text, 'SUBSCRIPTION_CONFIRM_URL', '#');
            $text = $this->replace_url($text, 'ACTIVATION_URL', '#');
        }

        if ($email) {
            $nek = $this->get_email_key($email);
            $text = str_replace('{email_id}', $email->id, $text);
            $text = str_replace('{email_key}', $nek, $text);
            $text = str_replace('{email_subject}', $email->subject, $text);
            // Deprecated
            $text = str_replace('{subject}', $email->subject, $text);
            $text = $this->replace_url($text, 'EMAIL_URL', $this->build_action_url('v', $user) . '&id=' . $email->id);
        } else {
            $text = $this->replace_url($text, 'EMAIL_URL', '#');
        }

        if (strpos($text, '{subscription_form}') !== false) {
            $text = str_replace('{subscription_form}', NewsletterSubscription::instance()->get_subscription_form($referrer), $text);
        } else {
            for ($i = 1; $i <= 10; $i++) {
                if (strpos($text, "{subscription_form_$i}") !== false) {
                    $text = str_replace("{subscription_form_$i}", NewsletterSubscription::instance()->get_form($i), $text);
                    break;
                }
            }
        }

// Company info
// TODO: Move to another module
        $options = Newsletter::instance()->get_options('info');
        $text = str_replace('{company_address}', $options['footer_contact'], $text);
        $text = str_replace('{company_name}', $options['footer_title'], $text);
        $text = str_replace('{company_legal}', $options['footer_legal'], $text);


        $this->switch_language($initial_language);
//$this->logger->debug('Replace end');
        return $text;
    }

    function replace_date($text) {
        $text = str_replace('{date}', date_i18n(get_option('date_format')), $text);

// Date processing
        $x = 0;
        while (($x = strpos($text, '{date_', $x)) !== false) {
            $y = strpos($text, '}', $x);
            if ($y === false)
                continue;
            $f = substr($text, $x + 6, $y - $x - 6);
            $text = substr($text, 0, $x) . date_i18n($f) . substr($text, $y + 1);
        }
        return $text;
    }

    function replace_url($text, $tag, $url) {
        static $home = false;
        if (!$home) {
            $home = trailingslashit(home_url());
        }
        $tag_lower = strtolower($tag);
        $text = str_replace('http://{' . $tag_lower . '}', $url, $text);
        $text = str_replace('https://{' . $tag_lower . '}', $url, $text);
        $text = str_replace($home . '{' . $tag_lower . '}', $url, $text);
        $text = str_replace($home . '%7B' . $tag_lower . '%7D', $url, $text);
        $text = str_replace('{' . $tag_lower . '}', $url, $text);
        $text = str_replace('%7B' . $tag_lower . '%7D', $url, $text);

        $url_encoded = urlencode($url);
        $text = str_replace('%7B' . $tag_lower . '_encoded%7D', $url_encoded, $text);
        $text = str_replace('{' . $tag_lower . '_encoded}', $url_encoded, $text);

// for compatibility
        $text = str_replace($home . $tag, $url, $text);

        return $text;
    }

    public static function antibot_form_check($captcha = false) {

        if (!NEWSLETTER_ANTIBOT)
            return true;

        if (strtolower($_SERVER['REQUEST_METHOD']) != 'post') {
            return false;
        }

        if (!isset($_POST['ts']) || time() - $_POST['ts'] > 60) {
            return false;
        }

        if ($captcha) {
            $n1 = (int) $_POST['n1'];
            if (empty($n1)) {
                return false;
            }
            $n2 = (int) $_POST['n2'];
            if (empty($n2)) {
                return false;
            }
            $n3 = (int) $_POST['n3'];
            if ($n1 + $n2 != $n3) {
                return false;
            }
        }

        return true;
    }

    public static function request_to_antibot_form($submit_label = 'Continue...', $captcha = false) {
        header('Content-Type: text/html;charset=UTF-8');
        header('X-Robots-Tag: noindex,nofollow,noarchive');
        header('Cache-Control: no-cache,no-store,private');
        echo "<!DOCTYPE html>\n";
        echo '<html><head>'
        . '<style type="text/css">'
        . '.tnp-captcha {text-align: center; margin: 200px auto 0 auto !important; max-width: 300px !important; padding: 10px !important; font-family: "Open Sans", sans-serif; background: #ECF0F1; border-radius: 5px; padding: 50px !important; border: none !important;}'
        . 'p {text-align: center; padding: 10px; color: #7F8C8D;}'
        . 'input[type=text] {width: 50px; padding: 10px 10px; border: none; border-radius: 2px; margin: 0px 5px;}'
        . 'input[type=submit] {text-align: center; border: none; padding: 10px 15px; font-family: "Open Sans", sans-serif; background-color: #27AE60; color: white; cursor: pointer;}'
        . '</style>'
        . '</head><body>';
        echo '<form method="post" action="https://www.domain.tld" id="form">';
        echo '<div style="width: 1px; height: 1px; overflow: hidden">';
        foreach ($_REQUEST as $name => $value) {
            if ($name == 'submit')
                continue;
            if (is_array($value)) {
                foreach ($value as $element) {
                    echo '<input type="text" name="';
                    echo esc_attr($name);
                    echo '[]" value="';
                    echo esc_attr(stripslashes($element));
                    echo '">';
                }
            } else {
                echo '<input type="hidden" name="', esc_attr($name), '" value="', esc_attr(stripslashes($value)), '">';
            }
        }
        if (isset($_SERVER['HTTP_REFERER'])) {
            echo '<input type="hidden" name="nhr" value="' . esc_attr($_SERVER['HTTP_REFERER']) . '">';
        }
        echo '<input type="hidden" name="ts" value="' . time() . '">';
        echo '</div>';

        if ($captcha) {
            echo '<div class="tnp-captcha">';
            echo '<p>', __('Math question', 'newsletter'), '</p>';
            echo '<input type="text" name="n1" value="', rand(1, 9), '" readonly style="width: 50px">';
            echo '+';
            echo '<input type="text" name="n2" value="', rand(1, 9), '" readonly style="width: 50px">';
            echo '=';
            echo '<input type="text" name="n3" value="?" style="width: 50px">';
            echo '<br><br>';
            echo '<input type="submit" value="', esc_attr($submit_label), '">';
            echo '</div>';
        }
        echo '<noscript><input type="submit" value="';
        echo esc_attr($submit_label);
        echo '"></noscript></form>';
        echo '<script>';
        echo 'document.getElementById("form").action="' . home_url('/') . '";';
        if (!$captcha) {
            echo 'document.getElementById("form").submit();';
        }
        echo '</script>';
        echo '</body></html>';
        die();
    }

    static function extract_body($html) {
        $x = stripos($html, '<body');
        if ($x !== false) {
            $x = strpos($html, '>', $x);
            $y = strpos($html, '</body>');
            return substr($html, $x + 1, $y - $x - 1);
        } else {
            return $html;
        }
    }

    /** Returns a percentage as string */
    static function percent($value, $total) {
        if ($total == 0)
            return '-';
        return sprintf("%.2f", $value / $total * 100) . '%';
    }

    /** Returns a percentage as integer value */
    static function percentValue($value, $total) {
        if ($total == 0)
            return 0;
        return round($value / $total * 100);
    }

    /**
     * Takes in a variable and checks if object, array or scalar and return the integer representing
     * a database record id.
     *
     * @param mixed $var
     * @return in
     */
    static function to_int_id($var) {
        if (is_object($var)) {
            return (int) $var->id;
        }
        if (is_array($var)) {
            return (int) $var['id'];
        }
        return (int) $var;
    }

    static function to_array($text) {
        $text = trim($text);
        if (empty($text)) {
            return array();
        }
        $text = preg_split("/\\r\\n/", $text);
        $text = array_map('trim', $text);
        $text = array_map('strtolower', $text);
        $text = array_filter($text);

        return $text;
    }

    static function sanitize_ip($ip) {
        if (empty($ip)) {
            return '';
        }
        return preg_replace('/[^0-9a-fA-F:., ]/', '', $ip);
    }

    static function get_remote_ip() {
        return self::sanitize_ip($_SERVER['REMOTE_ADDR']);
    }

    static function get_signature($text) {
        $key = NewsletterStatistics::instance()->options['key'];
        return md5($text . $key);
    }

    static function check_signature($text, $signature) {
        if (empty($signature)) {
            return false;
        }
        $key = NewsletterStatistics::instance()->options['key'];
        return md5($text . $key) === $signature;
    }

    static function get_home_url() {
        static $url = false;
        if (!$url) {
            $url = home_url('/');
        }
        return $url;
    }

    static function clean_eol($text) {
        $text = str_replace("\r\n", "\n", $text);
        $text = str_replace("\r", "\n", $text);
        $text = str_replace("\n", "\r\n", $text);
        return $text;
    }

    /**
     * Return the current language code. Optionally, if a user is passed and it has a language
     * the user language is returned.
     * If there is no language available, an empty string is returned.
     *
     * @param TNP_User $user
     * @return string The language code
     */
    function get_current_language($user = null) {

        if ($user && $user->language) {
            return $user->language;
        }

        // WPML
        if (class_exists('SitePress')) {
            $current_language = apply_filters('wpml_current_language', '');
            if ($current_language == 'all') {
                $current_language = '';
            }
            return $current_language;
        }

        // Polylang
        if (function_exists('pll_current_language')) {
            return pll_current_language();
        }

        // Trnslatepress and/or others
        $current_language = apply_filters('newsletter_current_language', '');

        return $current_language;
    }

    function get_default_language() {
        if (class_exists('SitePress')) {
            return $current_language = apply_filters('wpml_current_language', '');
        } else if (function_exists('pll_default_language')) {
            return pll_default_language();
        } else if (class_exists('TRP_Translate_Press')) {
// TODO: Find the default language
        }
        return '';
    }

    function is_all_languages() {
        return $this->get_current_language() == '';
    }

    function is_default_language() {
        return $this->get_current_language() == $this->get_default_language();
    }

    /**
     * Returns an array of languages with key the language code and value the language name.
     * An empty array is returned if no language is available.
     */
    function get_languages() {
        $language_options = array();

        if (class_exists('SitePress')) {
            $languages = apply_filters('wpml_active_languages', null);
            foreach ($languages as $language) {
                $language_options[$language['language_code']] = $language['translated_name'];
            }
            return $language_options;
        } else if (function_exists('icl_get_languages')) {
            $languages = icl_get_languages();
            foreach ($languages as $code => $language) {
                $language_options[$code] = $language['native_name'];
            }
            return $language_options;
        }

        return apply_filters('newsletter_languages', $language_options);
    }

    function get_language_label($language) {
        $languages = $this->get_languages();
        if (isset($languages[$language])) {
            return $languages[$language];
        }
        return '';
    }

    /**
     * Changes the current language usually before extracting the posts since WPML
     * does not support the language filter in the post query (or at least we didn't
     * find it).
     *
     * @param string $language
     */
    function switch_language($language) {
        if (class_exists('SitePress')) {
            if (empty($language)) {
                $language = 'all';
            }
            do_action('wpml_switch_language', $language);
            return;
        }
    }

    function is_multilanguage() {
        return class_exists('SitePress') || function_exists('pll_default_language') || class_exists('TRP_Translate_Press');
    }

    function get_posts($filters = array(), $language = '') {
        $current_language = $this->get_current_language();

// Language switch for WPML
        if ($language) {
            if (class_exists('SitePress')) {
                $this->switch_language($language);
                $filters['suppress_filters'] = false;
            }
            if (class_exists('Polylang')) {
                $filters['lang'] = $language;
            }
        }
        $posts = get_posts($filters);
        if ($language) {
            if (class_exists('SitePress')) {
                $this->switch_language($current_language);
            }
        }
        return $posts;
    }

    protected function generate_admin_notification_message($user) {

        $message = file_get_contents(__DIR__ . '/notification.html');

        $message = $this->replace($message, $user);
        $message = str_replace('{user_admin_url}', admin_url('admin.php?page=newsletter_users_edit&id=' . $user->id), $message);

        return $message;
    }

    protected function generate_admin_notification_subject($subject) {
        $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);

        return '[' . $blogname . '] ' . $subject;
    }

    function dienow($message, $admin_message = null, $http_code = 200) {
        if ($admin_message && current_user_can('administrator')) {
            $message .= '<br><br><strong>Text below only visibile to administrarors</strong><br>';
            $message .= $admin_message;
        }
        wp_die($message, $http_code);
    }

    function dump($var) {
        if (NEWSLETTER_DEBUG) {
            var_dump($var);
        }
    }

    function dump_die($var) {
        if (NEWSLETTER_DEBUG) {
            var_dump($var);
            die();
        }
    }

}

/**
 * Kept for compatibility.
 *
 * @param type $post_id
 * @param type $size
 * @param type $alternative
 * @return type
 */
function nt_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
    return NewsletterModule::get_post_image($post_id, $size, $alternative);
}

function newsletter_get_post_image($post_id = null, $size = 'thumbnail', $alternative = null) {
    echo NewsletterModule::get_post_image($post_id, $size, $alternative);
}

/**
 * Accepts a post or a post ID.
 *
 * @param WP_Post $post
 */
function newsletter_the_excerpt($post, $words = 30) {
    $post = get_post($post);
    $excerpt = $post->post_excerpt;
    if (empty($excerpt)) {
        $excerpt = $post->post_content;
        $excerpt = strip_shortcodes($excerpt);
        $excerpt = wp_strip_all_tags($excerpt, true);
    }
    echo '<p>' . wp_trim_words($excerpt, $words) . '</p>';
}