From 90d81676d779f4b15bd1a3c23d674537e139d97f Mon Sep 17 00:00:00 2001 From: NickCa12 Date: Tue, 19 Nov 2019 16:54:04 +0530 Subject: [PATCH] Feedback system added, readme updated, version bumped. --- analyst/assets/css/customize.css | 280 ++++++++ analyst/assets/img/pencil.png | Bin 0 -> 6626 bytes analyst/assets/img/shield_question.png | Bin 0 -> 22575 bytes analyst/assets/img/shield_success.png | Bin 0 -> 21612 bytes analyst/assets/img/smile.png | Bin 0 -> 3077 bytes analyst/assets/index.php | 2 + analyst/assets/js/customize.js | 29 + analyst/autoload.php | 40 ++ analyst/index.php | 2 + analyst/main.php | 36 ++ analyst/sdk_resolver.php | 79 +++ analyst/src/Account/Account.php | 604 ++++++++++++++++++ analyst/src/Account/AccountData.php | 176 +++++ analyst/src/Account/AccountDataFactory.php | 125 ++++ analyst/src/Analyst.php | 167 +++++ analyst/src/ApiRequestor.php | 257 ++++++++ analyst/src/ApiResponse.php | 44 ++ analyst/src/Cache/DatabaseCache.php | 127 ++++ analyst/src/Collector.php | 217 +++++++ analyst/src/Contracts/AnalystContract.php | 12 + analyst/src/Contracts/CacheContract.php | 47 ++ analyst/src/Contracts/HttpClientContract.php | 25 + analyst/src/Contracts/RequestContract.php | 22 + analyst/src/Contracts/RequestorContract.php | 44 ++ analyst/src/Contracts/TrackerContract.php | 69 ++ analyst/src/Core/AbstractFactory.php | 27 + analyst/src/Http/CurlHttpClient.php | 102 +++ analyst/src/Http/DummyHttpClient.php | 33 + .../Http/Requests/AbstractLoggerRequest.php | 64 ++ analyst/src/Http/Requests/ActivateRequest.php | 42 ++ .../src/Http/Requests/DeactivateRequest.php | 64 ++ analyst/src/Http/Requests/InstallRequest.php | 38 ++ analyst/src/Http/Requests/OptInRequest.php | 42 ++ analyst/src/Http/Requests/OptOutRequest.php | 40 ++ .../src/Http/Requests/UninstallRequest.php | 36 ++ analyst/src/Http/WordPressHttpClient.php | 61 ++ analyst/src/Mutator.php | 103 +++ analyst/src/Notices/Notice.php | 121 ++++ analyst/src/Notices/NoticeFactory.php | 130 ++++ analyst/src/helpers.php | 84 +++ analyst/templates/forms/deactivate.php | 156 +++++ analyst/templates/forms/install.php | 113 ++++ analyst/templates/notice.php | 10 + analyst/templates/optin.php | 60 ++ analyst/templates/optout.php | 109 ++++ analyst/version.php | 15 + readme.txt | 14 +- wordpress-https.php | 17 +- 48 files changed, 3869 insertions(+), 16 deletions(-) create mode 100644 analyst/assets/css/customize.css create mode 100644 analyst/assets/img/pencil.png create mode 100644 analyst/assets/img/shield_question.png create mode 100644 analyst/assets/img/shield_success.png create mode 100644 analyst/assets/img/smile.png create mode 100644 analyst/assets/index.php create mode 100644 analyst/assets/js/customize.js create mode 100644 analyst/autoload.php create mode 100644 analyst/index.php create mode 100644 analyst/main.php create mode 100644 analyst/sdk_resolver.php create mode 100644 analyst/src/Account/Account.php create mode 100644 analyst/src/Account/AccountData.php create mode 100644 analyst/src/Account/AccountDataFactory.php create mode 100644 analyst/src/Analyst.php create mode 100644 analyst/src/ApiRequestor.php create mode 100644 analyst/src/ApiResponse.php create mode 100644 analyst/src/Cache/DatabaseCache.php create mode 100644 analyst/src/Collector.php create mode 100644 analyst/src/Contracts/AnalystContract.php create mode 100644 analyst/src/Contracts/CacheContract.php create mode 100644 analyst/src/Contracts/HttpClientContract.php create mode 100644 analyst/src/Contracts/RequestContract.php create mode 100644 analyst/src/Contracts/RequestorContract.php create mode 100644 analyst/src/Contracts/TrackerContract.php create mode 100644 analyst/src/Core/AbstractFactory.php create mode 100644 analyst/src/Http/CurlHttpClient.php create mode 100644 analyst/src/Http/DummyHttpClient.php create mode 100644 analyst/src/Http/Requests/AbstractLoggerRequest.php create mode 100644 analyst/src/Http/Requests/ActivateRequest.php create mode 100644 analyst/src/Http/Requests/DeactivateRequest.php create mode 100644 analyst/src/Http/Requests/InstallRequest.php create mode 100644 analyst/src/Http/Requests/OptInRequest.php create mode 100644 analyst/src/Http/Requests/OptOutRequest.php create mode 100644 analyst/src/Http/Requests/UninstallRequest.php create mode 100644 analyst/src/Http/WordPressHttpClient.php create mode 100644 analyst/src/Mutator.php create mode 100644 analyst/src/Notices/Notice.php create mode 100644 analyst/src/Notices/NoticeFactory.php create mode 100644 analyst/src/helpers.php create mode 100644 analyst/templates/forms/deactivate.php create mode 100644 analyst/templates/forms/install.php create mode 100644 analyst/templates/notice.php create mode 100644 analyst/templates/optin.php create mode 100644 analyst/templates/optout.php create mode 100644 analyst/version.php diff --git a/analyst/assets/css/customize.css b/analyst/assets/css/customize.css new file mode 100644 index 0000000..f50cc1f --- /dev/null +++ b/analyst/assets/css/customize.css @@ -0,0 +1,280 @@ +.analyst-action-opt { + cursor: pointer; +} + +.analyst-modal { + color: #000000; + display: none; + position: fixed; + z-index: 1000; + padding-top: 100px; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.4); +} + +.analyst-modal-content { + font-family: Helvetica, serif; + position: relative; + background-color: #fefefe; + margin: auto; + padding: 35px 35px 20px; + border: 1px solid #F2F2F2; + width: 40%; + box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); + -webkit-animation-name: analyst-animatetop; + -webkit-animation-duration: 0.4s; + animation-name: analyst-animatetop; + animation-duration: 0.4s +} + +.analyst-btn-success { + cursor: pointer; + color: #ffffff; + background-color: #00AF5E; + border: none; + width: 100%; + font-size: 18px; + padding: 8px; + font-weight: bold; +} + +.analyst-btn-grey { + cursor: pointer; + color: #2D2D2D; + background-color: #D8D8D8; + border: none; + width: 100%; + font-size: 18px; + padding: 8px; + font-weight: bold; +} + +.analyst-btn-secondary-ghost { + cursor: pointer; + background: transparent; + border: none; + color: #898686; + font-size: 18px; +} + +.analyst-modal-def-top-padding { + padding-top: 20px; +} + +.analyst-modal-header { + font-size: 20px; + font-weight: bold; +} + +/*INSTALL STYLES*/ +.analyst-install-footer { + padding-top: 10px; + text-align: center; +} + +.analyst-install-image-block { + width: 140px; +} + +.analyst-install-image-block img { + width: inherit; +} + +.analyst-install-description-block { + padding-left: 40px; + padding-top: 5px +} + +.analyst-install-description-text { + font-size: 16px; + color: #000000; +} + +.analyst-install-permissions-list { + list-style: disc inside; +} + +.analyst-install-permissions-list li { + padding-left: 15px; + margin-bottom: 2px; +} + +.analyst-install-footer span { + color: #8a8787; + padding-right: 10px; + padding-left: 10px; +} + +.analyst-install-footer span:not(:last-child) { + border-right: 1px solid #8a8787; +} + +/*INSTALL STYLES*/ + +.reason-answer { + padding: 7px; + margin-left: 23px; + border: 1px solid #F2F2F2; +} + +.analyst-link { + color: #00AF5E; + text-decoration: none; +} + +.analyst-action-text { + cursor: pointer; +} + +.analyst-action-text:hover { + color: #9d9a9a; +} + +.analyst-disable-modal-mask { + width: 100%; + height: 100%; + opacity: 0.5; + position: absolute; + background: white; + top: 0; + left: 0; +} + +.analyst-smile-image { + vertical-align: middle; + padding-bottom: 3px; + width: 24px; +} + +#analyst-deactivation-reasons li { + padding-bottom: 3px; + font-size: 16px; + color: #000000; +} + +@-webkit-keyframes analyst-animatetop { + from {top:-300px; opacity:0} + to {top:0; opacity:1} +} + +@keyframes analyst-animatetop { + from {top:-300px; opacity:0} + to {top:0; opacity:1} +} + +.analyst-modal-close { + color: #48036F; + font-size: 28px; + font-weight: bold; + top: 12px; + position: absolute; + right: 15px; +} + +.analyst-modal-close:hover, +.analyst-modal-close:focus { + color: #000; + text-decoration: none; + cursor: pointer; +} + +.analyst-modal-body {padding: 2px 16px;} + +.analyst-modal-footer { + padding: 6px 16px; + background-color: #FFE773; + color: white; +} + +#analyst-deactivate-modal .question-answer input, textarea { + margin-top: 5px; + width: 100%; +} + +.analyst-btn-primary { + cursor: pointer; + border: none; + display:inline-block; + padding:0.7em 1.4em; + margin:0 0.3em 0.3em 0; + border-radius:0.15em; + box-sizing: border-box; + text-decoration:none; + font-family:'Roboto',sans-serif; + text-transform:uppercase; + font-weight:400; + color:#FFFFFF; + background-color:#9F3ED5; + box-shadow:inset 0 -0.6em 0 -0.35em rgba(0,0,0,0.17); + text-align:center; + position:relative; +} + +.analyst-btn-primary:disabled { + background-color: #AD66D5; + cursor: not-allowed; +} + +.analyst-btn-primary:active{ + top:0.1em; +} +@media all and (max-width:30em){ + .analyst-btn-primary { + display:block; + margin:0.4em auto; + } +} + +.analyst-btn-secondary { + cursor: pointer; + border: none; + display:inline-block; + padding:0.7em 1.4em; + margin:0 0.3em 0.3em 0; + border-radius:0.15em; + box-sizing: border-box; + text-decoration:none; + font-family:'Roboto',sans-serif; + text-transform:uppercase; + font-weight:400; + color:#FFFFFF; + background-color:#6C8CD5; + box-shadow:inset 0 -0.6em 0 -0.35em rgba(0,0,0,0.17); + text-align:center; + position:relative; +} + +.analyst-btn-secondary:disabled { + background-color: #6C8CD5; + cursor: not-allowed; +} + +.analyst-btn-secondary:active{ + top:0.1em; +} +@media all and (max-width:30em){ + .analyst-btn-secondary { + display:block; + margin:0.4em auto; + } +} + +.analyst-notice { + padding-right: 38px; + position: relative; + margin-bottom: 30px !important; +} + +.analyst-notice .analyst-plugin-name { + background-color: #00000024; + padding-left: 7px; + padding-right: 7px; + position: absolute; + top: 100%; + border-radius: 0 0 5px 5px; +} diff --git a/analyst/assets/img/pencil.png b/analyst/assets/img/pencil.png new file mode 100644 index 0000000000000000000000000000000000000000..45f4f65e97dd9a4b4fbd34aee09a528a3dd984af GIT binary patch literal 6626 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 Kz^ZheV*U>zjN{7y literal 0 HcmV?d00001 diff --git a/analyst/assets/img/shield_question.png b/analyst/assets/img/shield_question.png new file mode 100644 index 0000000000000000000000000000000000000000..a18718978e192e292ac2064e6094ae22fbb356fc GIT binary patch literal 22575 zcmeEuWmME%*EdKFp)hpE03zKXT|^CNDGK`Nl8eEz>M_s z=eq89t>^vtetz!{ESEFq?0wEY`}}sEiP6+he2hbhgMxzcSXoJ48wCZm5BNiZFo92= z)tVUt|DbwmE6SoSj&KA6|G{=sdg+OR!o~RThw57@<&A=3@LgG6M%Op@s1rMjMlbuG zj^%lD_x$IRUy1fjq1PWgYT-eTQ?U080k=qzM{((wxH2MAk7S z)tzze$p8wYkIqoq4|K^zKMQ#K?*~dhlKZZG@Ueeh`W#P2(E~$5p|Qv0BN3_5Nq6hK zD+NRkq|y{|j+I8*Qo(zE{Y*%4C%wC;$qr-ZbJuaecJ}@H5lob_(iQIT#Jbb>T^7{;j zDQovEi2)cw7>iCXGfV*R$9{36R43_jpPr=_xia+6D)Zg3z7XGN)HzJG7gern5S&ly zeC8iOp#=C1>WW|IhWZLm(Y*j4SV;*SGI*vj;uplUAh7q! zVAYi_OFpa}8S!UwsvvK3ciPU61?l%7Uln?7k$VgEuJmtdO?b}@RC_PfnS0&&4vV|_ z2PaD>&l!%-!T}KxJA7O@2i4~7*a^vc4PU-gA9pFJ0i?fP)Gd~fz)Dk|CFM>&Q?)?71(8G(pTFE^jy9I*cYsUI)7sC_ap^uJ=gY z?kl+9l z;K(nd2{;n^)5TLadsANunf}J23d~TczYEsA;yw0E0Jcj`${N+(N*s?fF~hfgBa1VN zV-&V!y`a+ViCK{RmPEDjL8D({2lf1ER~h$x7nyti;kXs%r3|Q<#?>nS-;ZA7bU!5T zfk7ahk1Jt|7_NESP_L=WYT|E&F4%8VH|PT+DT0o5rp4re^Q^Bp#vts#%!QT4e`#Y9 zdCPmn!GCk(#kDpWXaYzzDTFcZHaVrXl9*nLthFHd(I`2f;uM$Jq@->fUM_F9yEFhs z(VK?tG}}^7FF&ZDe)}G=l+`(la_T2bbvHWcyNH+gD_}%%KiAz%7!aMjjJ1rE!XJ~_ z&YqtPy9NSAsm9(qG6l(!uTh7Ta_%f+gaD>>3Z$pP%3Xy+KL9}y=C>uv*cr!S^|!s!CTk^AZ5LVbU`J65LO?NSk$%U|2e%&XVJai; zMF+DT<}_|eg1f^m78?8~t|+D-hXIK%?cPdP&b8=+-V;IM&Zbjl!U%q}DHerJ;(tuj zJ;&gd6D6ffRN?lXypZxR^8pr?lF0Gpp8nEO^N|ycH&?>I(T*NvnS!ngs^ok=L3cF; zcv)~b>21v0iFbETNQv5&JW?X|NdfX#Ry&w|0dv|-(k*1_;a?L3c{}8Wwnk~R1ICsJ zEiJ|v+y`42+&yQb7v^&2807qQmlTE$wq8vOiiFl+`*cVH2X!@Ud5t;3QoZu3RT|xX z2XlZUB}HkU04#xHvZ=zu-B;i$M(?t~3lX~5OJ<;{5=;6?NVl6%Gs41DyX(RNtN}`9 z5Ta`xOjwBWfy_7js|n+%O>OPRi>Wa}YHr!hrC4yif2|OO?Jo7Gr(53SnQuO)iS!awuw!c`%HH8-4WJ+XP&Y%Ux}x*>X(-h-D1@1 zawV^$o<{>b`4iHY>5|sOObF^wxLv5df(&RHI|O1CKCb5w#~GP&%5Z}T4btfdUbH^- z8r7V7UgTN)gZln^XU4+DBN+ng%3;e(p^+H1n*N@-p?cOU^kzKO3d4&D3cO*T?^3}%`d0!i`ffvyJkXyUeYs_!w(PW~8 zsVZ)4V8Yhu(!zSrkg?80?FIaiJVlf#r` z*d9^0&VA`>@`uqVDv$kO6OqDJmbYc#qkbnjl?Xb!VXeIFw8-c6a~a#CX76%2kG?VE z;P{g43r#G}tmk}gs;*AnKm@0hwj2Kpz;-o_^Vb)KzQ_~`oQmOW|LH{|P^ zs{4IXq9UO))^kEII<|@RhJW7nx)<{96_*2f-91yBCwCNa^h8uP9eD{T#>|-xJmyQA z{xhQOMgFyv(Vc#I_Q%Y>sE)=fM^u2};ct-c1W@hfCWYi?+vkP%W0ByBw~HO^^3sVc z1|+|q>81ngu0PfEKGGIq7=0~04=Mbm=KRZg34IB z1AY|vM}hT;6~0|P%ba?H3SK7YtZG`k$WQQH|2YA2`1OT(i0tPD=eVY>{pGe3z*5EX z=pWbb^6I-xjlGQjGxj))qK>WzskMU9)we38nU10t$1}M?*%c~KOJI3Sv)PY|SD8Kh z(#2RJ@_OyTOt|kLxkh%Gq&Cy&rU9r9E0b|cc$BB)U9CxnX6Y5nBekoEG%8I6@oLO) z?Y(of7u#)f&RXJIV}*eD{H%+78@Iz#xQ!`UI$mHp#}UUYQdJ5Zb&xi2WKDAY> zB-amnPW|iAA;tkAgM0HK&0RBduORk%T>R%b!9VZTOWX6ba_-Y$_~w5k!_rhfuAuuE zEuZSL|4?_aQyEM06gR;10*K;_*#*x8=iT&c+)p+tB z9wPScwv>`)M2%F|p`LBHAIXdbr7#7d@GZJbt`mV@?)xIti{1yTtmKtsSNW{F?Z_3D zRTx3ID*@A99<9q1J^m?8gD^twOflOt>%mLM#uk6EO3>mbDLZ^@;l^ zC=4oiDrEZeDmbgORCiRHvm8vKe`0yzexmispq`5@jF{37t#j};!|!uCtQ8rE=JJPP zq}IKsou9CAULUzEheK=Nh2-Sg_qOtD09wYm+#7(tGm=y$Ry|`Dq^=x z&aD#*FC26>b=^N?O!yJkZ&E(C;jxPcELNu0T<6(t2l6WBxbww~@Y=9RA^}sKVSBs3 zfX7*2N@9D$Jyk|8+Dlvd?lFhn*U0+aXFlLuE&+nFacR_dY<=S0AIL{)G^{p5-u3ga zE!#eC)^e;`L5>NC%vV2V`P94_=yKFUtvGOK*j_sPHtmLvz156T;?eUF=P3)nL}6!p zlBBD)TQBwTnkqi)=FI(udNIxn9(*`#$3f{;`c5}*DWj3`#GSazL{-y6GZeI5?UVdAm(qCMgy zi&^|tb$uj>hO-pExH2I{*pwxowfkihTn~3=G?$wCGV#6agEJM+qgigU_U1if7w4;Z z&F9I_sA+8 z?Ais71zLY<9Tih5H77YjW)&)vb=l1(O_rnBmAd`>L{2 zH~a&sj(XWrZtaY2`0*WzftFvDY zRTig~t-})yNEUlYNYcjE)+@h$+rLf!1R9bHe|_vK&NO$-Ibyo0E27I^;jQ_&$VgT& z!V!0tyi#xwqWbTKxzaKic2)f0GN~?&i5ysmiVb0L%j^#5FI$Ak8pW)`X)+!QTlGv3 z;Z(5Hb)m1_%D`uKe~zx?4*W5_Lr_5YbYT^kTAc6S$v4M163?<#zSQlgh}7cTswpQZ zlA^e^giOeNt=-$y_~HLfRC-Ey{oD1`%OL3$eK9uD7LwV|@;dbHZ>?YFete^Q zR)NrZTwkUQ8x1BRL3Wo!MW_i_#7cLa zbn38>8~VO%k2iiUlnNAUERnR)Zlv;tDoHo?)4xcxgGz7K@FBWmg3I{Pumk*}iC?h>@cju?|bW=1S@yj=TsiL;#g?vAvlES-~&%fo7BcQS=mbjXTSk)VDN($v02yM-yb} z`(6?hx$Na|9<8^24IGc@<^1^w{BGhx%p+09L6_~QHX2nV0u}r<3>40^KEt?QeK>HH zXMqO6BNH$QM;PA7^%os7&hG3z*T8)cdc~uIooTVogkxe%|IaTYhjNOrARJF>s>w$r z^>3}ViVj6T2&j;-|G0VlFj|C&ZH1OLVQm}-KT#--GP=&UXl?6_n8Lu3b`rjd%s2a4 zvTX^I*c>9c2XUNEap8S$Ts|Cq>up)Cm2<8{&YXq4mq z9oR)Z6Pc84o8aWw3I5JWlvCL9ATN(*_DL!><-+1c=Dmvf=iGenR)5B>*`%mBa4{#u z1{zIcj{HOE8+4n?8-@BwG(pze*j|YTts@BOM+HBc2?dd0K+t=rfN!42S3DzNKv|Q9 z_ju9ELPJ5QzcC@`WoTeF9C;9+sti0d(VANHvM8ScIqXZrV;_MZ{dg=7Qp1Ai5=RrD zxYEm-ixL#QmxiPGqWzcje+BYi4f*et`G3U_ms%PM0A5D^11}Ra&5YOpHsSpTn^uS@ zD8m2O*MS!REK&UjmKebK-F_el`jw|5`u%EEihAFS5$bwHe(Qwn7;YHL9gl=kpNo4k_S zziIyVUhTRw>}{FY(P?Tg|I80pmZ8rc|-55S{)>|Y|)vQR^!#vFi6DWLy=@y!}nPrrjc zd+mG1BzR}M67-GKude)(z+&Je!ncM7f^G|}G_}3iRyP8~nEL(iOxtY<=O6PIth(@K z-fQM=vQ3O=0&>92tNm;-x`(6*23!Mlh=K4RPJAIXAlfZ_9k>27Ak%;7WRqz@`Vs&# zssADKOc$Qq*7*(KRVm2p1)EU9M_C!Krrqyqb}WHuTqxn6j>Q&r`pmsO4E(&WiH}lt zDr@I4{2#GOh5oy5qKMc!E*I`o7z1rAt>wKfMhF?16RoH~chEM0&m=Jlx4sW^AQJSC ztfd}e#$+t94oxr+bq?K{MguvK!v}eDt%wUeg^GMJ`Hk}1a@F)6Qt}|%f{B0S<_hqz z=;>Mq#JhkAe#HN91Wt#DZnC0}8~AG4TlWe7u4NS%2c&gofN^ul*8k@i7C)Nnm{x88 z8~5=9V(0B%TAd0|aroE!t4@L{WZ5DvEsCi$yj14pS=|DGGfDF_&h*@8)5F&<88_z~ z-3zS}$X6ScnULwYV)~4Sq5jDfE?;>+?asE{C*D!t`dz`!k^8$-lf?z9QUyWyw~|+# zL6SaxwZUNR#|0%{=pLeNK!4_Bp$mN5ua1ziPDjAyl=u4op71;wo2+d9 zcvAtc7V8GrKS~#wX_tf5puL~bSvOttxbLlEmbC%-x!=C$%x?Ks;a0Y9d=T@q`tpjC z+FeBIoS~%mhuY&-tQ%7TBWPa79x4wwnnyh88Du(BzPs6o3Dy!bu7uSwo(4&bdK>pP z@$FfA2I@K_tr_N)vScdSF|*5vfA#Us zS+zQq=gBo*lB%IH(upLHCI?z_G7BTqKFTj?3C>?uT>^I623%h$bEyLZc-#`t2ih#a zI2HV{???9$Iv(3nvr7P{?_KqtE5>CH=R(6E#bJS)eitqW5vh|h8>Ru{t=}!H3g`B_zc(c&1HadRF-d{C# ziA#sF6$%O`qF0C2VfoaLp(rstIUc2M$e7b0*fehYM1|Ga< zP0ps4r<{x#CXF*Fs3F+0)Dh=nF_yLSJey|j6N5{h#~6%ngB_^MAz=z#@p^pw=>G!10d zpx1Oo237WOSu&%hAef{zr%3_ls00u*S?kzMtQ)G(9!4Z8nDpzXG9mXmPZ}66)?Iyv z>7j<=&%n@SEh9*TcR&%u38<7rvl16B0HOBRl%F!wcGYY(&|1Z%J@+Cvgfl5(t-0^` z6~jxe!xm_8_+8#z$Ibou?%pKX>{a<6|E0OGKVBCr#cd`e_9aty&6;2Zth}7t*t*YT z%e<(K08LD1GU88y()iwh&?D-WGo!uw(ICxRPm@=C>yhV_VVWRKbm!TT@ z_N8G3>TlHfuJg70r#s2yCGTdX&%TkwvqQHrU%CGvXLbuG}8v<0zmy2lCgQK~Rx$Oh(1f=hAQ;IoKA37>#8c6!Cdf-f@=`(te=E+tOoqqCxh@ z^V@Feqn_nn`)q5g^pm)c$L%s+o~WjA1AWmvo(#N_DmzP@`V<|{@MrtxNaxvWJ=NixRXHs3OI2+ zvN>vupD5riN+U{6{%s zFR}3DIG~8pL;^{(BFQeIZGV60xm|H&5Giavp=Aytuz34;T7PcVBG;D*x#xL`jR_hG z*b}#*%NsrW;ynQrL6-bjJjJRDm5Ms2=ZB}WI){MEP%{)lVU9_#!JtLPNtRvoG~e=* zYn767dxI}#=bl2ouxPLs?Mt}G_cUc;@xR?GrtHtO?WNcrj5(~s3@o`8hRWZAr(yz! z?FG{J!(Gc^;Yu~v^*viJ^v1vDzYwimvU#?gxOy{hvtpX#UNWV@t_y$T4y!iU0O(_-S0jRC#G}7eF{m;U*-~v#{-gJ=$(m&bIpAUwLbx`yjbrMCc=L`xzD^$dLfd~?w&H(&2CEPgptgHK(dqe(w0 z-U;lR;v6mffXVhK@y75>HOoc4llQp5U%(ZZQuLV3{_{yOJkhqpv%L%$VCrMy`9R3o%2!0>f`w`EwmGjG6@Q-v35y@q)Fnd>xbp-ERa*4KW?b$^>k4yKS0etZm zd+ag}@=T(SqYsVdby%|bm`=kU&6t^QsNb-+%n@T23jw!RUCdPa21P`y8&E8MF$n8> zE|{f&dQPMd_Qn(tdCJ~fE;VZA7TP|H0H@&r7bD$0_Ba2(w*6Hs2jE8pJM;?R6c+Hr zy1n)?p!fXTkSI4zggz7MA1NdLl*WNGR)z#(QgY z2kG0*xrR$<*0wC*K~-G_vj!Sj?B=JNathM;s>mQ#xI6VkeKE5m>!)c-&X*CygS(4~ zV!zMWmXi;5764PoV&XXj^NRaja1+e~)PyVdNKE7OY(o?P!oA*uI?M{9Dz-;c1Gx8` z-6oO3WHSRXno z<;`|w!bBSMs)Wr2r~%-q+SWl!s$7U-1`BqiUc`uF9d2*fy~cja({=W#mJTbN z`VzyjXP{X$%u8k;TI!~P!hj;RwC_(H2u-_zHDNf=FeR#?z2K~Bop?cYv#ZWfiRAaX z@+oV23rXjL6%-5=14c!RHZhFH;uGllqn=}&3i4`mMHjiB81wz>dZb)Pt;$m|k#c+5 zoKZxne>rT~NV7%<-n6n50eEV&F2rZ`AmJ(a2;|lL_cBD@n0c`hheUR_Cfn2YKd?TE zXpdzznG%BKe3BSumWBeIZcVXbMA^rq(QA5|Y$Qe=ABQg`^jDoHG(Aku_O~zbUY|9X z_V88tx$zs1{)j^iZPpjTx}-x=O2Hy4m^wM%dk?+h>$u-29;rLj*8Li!&4b+30eb-2 zvlO7UzeUAqF(`TT;ImrIRYN6I_v5DIda`Khxq&+?qc{Qr7~u+^Jqlh+l~B=XFTee@ zYgVcXSG)pS0NqA)IABW;iK);S$gA&s@4aBmfD88z21B*7(F>$F#o9ZWELj?g&DtmZ zdWXFgk6yeq{^~q`?r#F@KsXjCuHG$b&+sLT>bQ{3HQCub+LEFM}vj)y{%93zPZ?mimo+%TUrDhWifRh{R|gQjov_u zk4+J>6(0+^?roJ6BPwGWeXQ)~;iV7r$ZMNzYJV%yKyDO?XcGZjP@OGZm-SRO{TlDv zRiXuFZf(ksMwT(FgB@=$EH~e|!OoZZ`XYr@q zs9JOAg7u?7iGLXTOwhh|vdOo7MDxUvam#}AIk~#IWxj*B7dAh8YD3fnL)%P_XH0oL zY1J*sAF;&T{DQf&uh(SBKVtoip-WB9f?0CvWk=J56ZK}X@2qMg`_|{{l&Gf0B~y3HNJBllE{%KS0wV^ICt97SF_(dd^s7^Y5Voi z38pu5ER45LP*mHj2!y4NNFV`4L=Q9DpoX-1A|{so7?{K&_e=&s1~)>Cu}6yV(UoZ5ySw^&}ZnU@;0Bs)5L8BVJ&?>6wX?;t5fn1_uh?L zG4*?+&G@dX}+fUg_~9QHk$uUG*S_D@-0&b(w}6^c7X|!_j=bS$8(S*m6+1IBkOD%`QO8 zXe2Rk_;(7>worgWA-NEJlpP`}0?j&HEGt1NX?f)!Eu(`rr0tm9Yio0|kWw{Cnw{La zlo+~s2#dMBB6-woCTIo{rUU81Yw%wDer|7@j01M*)5R$y-~zkE3stpWZY58-O!6lW zG}PK>My)n0T=s1@5rh!c&IfUPucCqd)H$4Y|1ykFJu+0T<>5Q|4y;K?3gSfJQB$d1 z_k!UR_E8FlxR<$aOFBRn_1dtC+vc@Y1m8II;hevC+ID2zLamj| zylIOS2{L^3>U)_qaEP9EEHRE*3{-ApT zc8L+HyhJP$_U$x3i8{=-DQDRORex#N^x_<{@e!}x%~}x&Ouw0LnDS&^ZhSXXjLfBN z0-3QqUr)q)ZP`*P3tvcoR1qQv@2sv$U->;EXxB)_t@mK+b2z+eCY5J6yq@yUO$Kq| zMl3x{f8mP#AZ4i&MfhVhM1k%Fj%(!rI_wAj6L1*`hsfdKIzz0@D0=dee>$y>h(ogB zl}+`~&%@d|PbZf?kpiBfL#GZzpzHaG+QUVW;jeB#%Er`r~##no;-Nx>LPb_UBrnvw2^rG5`HsG!i>UH-0 zsz0Rs9qI!1W?PDoJYD&42vucna%*}!<;e3XF~K`(Dx1e;;rdUz5rRYz;^eP3c6>qi z`+0iU9G!Xv!A6)6M+YX3Vk<}y`PWSS%l=}IXFbMW0x>HE?hl+wo+&Lpj}SEJ)(E&|0JD^W?W#&%`-~{={pr#Z1zfl(zK~|A-Kx zJV?B}nf95X*COlTpbB7P)%LPn?OM64#S}VTzxgqg`NDCT`?B@}enlX=pZL=6o(n)h zo{kM6)a0GVYou!l+}`WJZ!_*4h%`?05wfM9e@k3LIl};d?5lUY_cb5qkdm@XSJ>^l ztB9rclc8egI5sr0BKCf6z}k9EwL@nMvm^FM!X8br%96x6l`|si$XI9hjI0FO_xW0^ z#*fsBSS)BcQlo&$+zHn$@q2_DQkP%`Euu!oN^4)npCn-v?^WUL$nd+&B$b*w23WH8 zW9PsUMgjlSU*vgfIGjUS5?=|9^`*d$yIpjNqu1(Vum|WuGWLCu*%i`*yb7DO?ZKO7 z?i;NLYlcP{$J&<17!Y@obr(Yka)~DRt=#`1VJA5fq#nq)+#ai5HFJS9sZoWW=ufdb z=`!~l*I}||p?;EISZ(-nFP4blyL&`}0ysG%VQ+%#-*OsE1`wn<9RFz;Qh3 zI?2vp(RTm?QaHH+7gf?6#_Z$)GT1Lxg%_LL?JAILHN|Mto7{&8fkiu)7lD=^wlb{+ zAgVqf-sooCBWbBf**I!`m{Ft(ZwX6KOs)EA&8=Y`$#K&*H}ro0)AeQIOYn%^Ix=ul zKL;(E@mfQ_J0bbFXgVGeRNyocBbX$3Efsc7pb~2Y8Ur4Ek zf>zjyyibhb7iYCotbL3e1!FaWK(AO8;YQ0^Q(IYX_tc}tX@izf$S_>O7h6-Pt3TI!|Eb`}KBB51~#yj(@~OrS;!TQnd!G)M7b!_|ctWd|8Xh zglSrjS9bNAM}d-qb??XJD^{{rtdGZ#tlM&{nEL}>-!xb6c+u^mpmBSmiQxC++chzb$uAbw4;DFh-P8hjVpCLU z&_P$Z$bdtxiOkWLdgY?9WaJDhKRMzqANR3!8KW{3_&;6T46*bG8lF(d5Z zl?w_);^biFQI!lh>G6GjJj2>!0eA~G6a9Z|&;1xrYJYGmnU97!pP2SSdetnG>iB^Vo7YjHJ^2jGsv!Y_5trQC|T=y_4opw#qJaTm__I8TdG)hZCnhTU5Eq! ze%c{&UUch3IF2VoveVIfs(EeThHp}30zfUW>W3t^9%MxZ&5n)74fAtKA#`Ex^BPh; zcgdXy#`~!DY4a8-L#{v$I`j+aNcy^!6n<~PZq7|y-Tw4YyS&mF&g3a$ACSt31yaK` zDv@IV5jA>y@Uc$ag15&O^l%Uqzm@%>UM(>z>pcxUKdR$>ml=1Hc=KE61JrM~8a$-R zD^=hFd0@O;)L=$?@E4Lpl>$R|QGok%dzP5-q`@O;3!koM_GxU$%PEhqHrGhbzLo}1 zImqGhH@d%R8_ajPG}^$S9; z8B7Dh>N@7!uDPk~f=Pa1_Hzs)0Hue`e=&UUqwz3iSX({>$os#3Oq(3`1DbWfiu56( zv)|&DXIAsLjR+I!5MF|mV;n|OL{oL@gErPZX#!Uvq$HSW2w4u~9$M_EFQJXn7&r~2(<^V6$Vy0C z{D4!Y!=b&%9zY%Ektv?(ifaf*ySQ`t5ty?Wr}-iIRbb0-{NaxG`dEE6A8*SKL&RBM z%uM6RdZ5764>vI$D%OT#CbFws4R?n(-JJ&ejNjPAA)Gc`%c5ydwBgwswgo3bb9H1z z-z-{`6pu+DTW%dEJ0-7AgnoSfEr$%PHNfkipm4$JqGoNJWFr^xj8Xw_f*l+@fu=b3 z;9I3{hG6d+b8cDY-mtBOIh>1liKp7e^%wfIpYUUq;jT%!<>o)e^~E6=mGXZzx9sT& z$781^Cvr$OJP;7wb0zF=Vj?;z6#IQE)7VpiKai%Ih;?=d6k*Z*j|WwA+^VQM>c*vz zoWaD@r&x>`SLY0df!VwdSI62{ksi67RPvUm!3_mOGlkcM3-W&{M|^HaH7{cMb9kq_ zm_k9SIbIieE5~Dl$|I8=na|Q^Ep1h%W0KxmiedoP#^*!Y#-{g2sF5R3_0MM$cfy41ZJJM&~s-sN1&lkK&VtUC63?>gw`7)T&J9rp`yz1Jo%Oy z9`pX4q6qLp!Uty^&F(!=yi^G-)f@fIou#csh|Vdy&OUbTWXUZ%sNk`at%frW)F|i% zfz*kO6qZG?y?(~?S0%%5wRqhWE*P~pTa45`JoA1;#xO@I_@v_!`}Ro_5v2Lj?r?}! zKi0N-dIUHJ3=F=KKwQpr2**kDpex(&uDUF2BltpKpLkO(<2y?5$gEvGollll-Sxn; z*4?uBbYu6QiZ}|%YZd;)`=d2XYh*ipv<*KqxNpbM>&t9)>{KFw*JOFwB0g%@9G8)!;Vb!2b19($R?Lo7W zL(oP-y04j`69A!soRU$2swq_5mfQ-1VKsxzd7;k~qSzqq8<2a5lb<6^>$)v5YXd%z z1QNnqKvo@-e9KTbOl)ccklT6U7XTg~XoTy@iY*!V7d}GaBX4#a>0Z}MAFZEAq`CN? zySubbh-h1ve@J?;JB3D(s$wY*RS6ML6zFv^$(`$FB-TQlR(+B(9y*tdU19RANF27~ zJ-J8eEyMPm`2Z=1GmUqRW@oX)sXY6RvOU4(lW* z!4*yMy%YG)L?Vk!C4)(clw}7D#n{8D*fL%58Y8&am6N+F3Fag`4|wylfcECjWjox& z(rII85m$Tf<@tUOvr?QxBb^;D!?Hwku$dVX)`62t=4EW!LJrd|o4?uL6h{h|BW0_d z>?FX@#~gEZ(H$}A2jp}=i=bY*l-Lug_y33g>UF12ZoYc|3fCEMT*wLn3!tOD@gS(f zxEXK$SUv7_-5)i3z{%+KzSnuCy<7uWdke)lP>1jXZ<(XDlTIrijsy>wnlgvr>tsws zmm7sbN+c-_WM%a*_)WIgZQvo4^=ABuDI7LO<(4~$)(l%mYRp*0wb7I@XBS}aHjpqX_eVbg>npI|23M^%zUzEOf)*NAM~0T8D|@tdpE-PK09319mJFpXJ2Rz@C1EUve%Jv+KTnYy98Qz?x2q_%s%z3L7iCJ`V+R?q&aWX( zcnmI%iYv9B%?3PyOyKclp(9uyVIjB919*zXCO8>J%YL{p_eauX-`7Y!YwvA&>(keJ z=&%Kxm?#MVAz$fd?MfTNTy%<{8@C}O zd>?`9iMrjo`IL*JBJdkUc|rq}bvK_`2)(bL&3OTWcRBq%EAX z$!F1s6SW#(GW1VKIJnb1U7%nWA5JY1J<6#8G8W z@j+nGcFMaf6EZlS!>_W}402Y}%(9Th#)*63m0^iPC{9RWk@qpu)E(hWvEiMTp%mJw z<a>1ad19GQ&yw1(&nl!&CBJv2|v>E*B4>3Is!XN>dDZ8&v14#oSul1XVdC zSDn7;d+5Bcygn)_w2oE}oRBLAF-~XRNF66LW#sjlR-k}z9M{>7Q-Ol+JkT2Qx@17I zRNWDv zH6=(omK}|PR7w=8M7`A5d%rcE|M+ka*h(Sd=(>Jyt@(CE!!m|=VO_baS|t*w*;x=k z;CK_VtspTHj-EW2_!S8v3?sy`!i3$AW9fXM5f~mvypWvn{uZ|yrCd)s@@JtjoT_+2 z#pPUgmKyP1?;MB@l+pq6*SMkGCg zcf7H4lGM|&E<&e1&dsVxc_iUv;5Dzk6($pDu)g&ZZ!Ab}z_&MNi;4WY|0IwmtfmR+ zI7MHU^xP?XbjebzJ+l!ZOvCe0qFAE{#fy6hhsQ$O8aw+q4gQcdCYk$GW9x=3T2m*# zN(fb(iCC&l;f}Z#qWj^UaGLxGmRM?!F9!j_Cc+Xtc4nk7ttA<>zMr*kt}G!I2ZeJW zVaQ!4dBx5BH{p1PTiwfeiCZm%l~BRrBBH89m^|?BCfD}uZB>GXq(W`jX1wm^!{ zMXxEiqw`}ZknaSp09oJDei9SqkFq*&2uI>v)un=3XWwHoa?O?T0hb_^4p7AKc7`4Q zkH6~hrK-x7f|~38>=jdNlaDLZ{W|?m`x5|ULiXdcWoutTh z(cYPFpzFvU|Jq_8coein(5fR##olT>7+jTMk%VR+ebm{~R!z#N5~mEmnD%(n*{7aK zp}9RCR;K1Q=;9xC`@r)r2(x}sM|BTWDmI6SYg zEzO=j4C}Xa>ODP-_{x+5#P>u+Go9JDe-q?dUMTkVoh>4|g6;T#W(1H+CR|e6)HNl* ztQ0Ri->bk*#`saVeXKNG-uTSlW<5zKz4Z^7Shy09QYrvlzg|pS7XWH&dh=9H53WZP zvHHgq$w)iajEy%kTtgXlHU@;a-ejSm0%uwR#1Ae(j7{k(CBUQX{E;o|IL5sopGDzh zDHW7j=&gLDqx_JmXM}|-dR`Y-Kz1%jRX4Pq;2lB^=D2Tmiv(3%2OFGlhTzbv!9hbGL?VVa_fF!CM9}v5{B4*K9W{lwEaX)vTQ>~ly zhF9!Exn7d!N0!{*2Edlr({-~>z0sU)$qV&A#|)28o1adht4b^CD6j~e;$s?JO5LRV zER0tQiqVMZl(oCp;BEQ6b%>FaGmw(H0EDSz?TPCkYq1hU#qStJidOd62kGf>^;_&V zEM?uMJfZle&@F5sR<0TQPXQ)Ye{+&FepuoLI)%a)b!8}@<-Z@ikhqFoLBn<(Mh4Vh zTz@OMyhxJ|O9IT4(n^q46rKZCoRq*1EeC*I1+~n(=L@Wa>|GP4p!WLZjD~2Q`tz~F zp|6jdr5vLU{KLsvD+N(Ues2O#D@)S!6ru>oW{STZ-SqL25JB78Y~)L>*oY+|)dW_! zQoW`G&y{Psr904hHGeWL{#$%H{-+h{9@9lQ?NwL`P{NWhi)H&D7n$S#ZL?L6T#u3z zObE129r#C_bACEyxO+c5>Xb~;&=kv~clR3YZ4Q~EkIJ(9?M+o%o@PV+BXV^Wd91wW zqEFX|1S-#q_88ntQUCB_>i~A@P`8mUIDb15cM+G7MYLQi$!jS8j{KU}Q}#&R=DN8< zAK}(fxNWublY%YJ>xK0$<(ES)8a^g_vbbM$Za&ZC+iE_jTPuiuCrR!7lCu7|cdb!B z9D^qLmGRQGoc8}!4Rpb$Z!|RxRoM4%f{S&&*U!Xbx2IQ^!2FiQCHsC-yV1;owj9<; zZtO-4E3#&vihBVHV2Slw4M1#JLtD5V?Nnglb73CGSBdd)>Bc$d9hElr4%Axj>>L!e zZ$1rV+VPvfRo`Jjjiw(B`h80X{>hz@%NKB5^+)^=S3b=9m#o%-v4z&P_SH`HLlc$+ z^4alHqFQwNa`Pq?>TnX~w771m1HoghwbyZe7o}oJU%bGCdRlLYlFuGm987s$+Wb8n z>5CDr;5Yx0EYW+dVjA|nMz?j=>-MI=b3S03r{3e5f^BuhXNCTqF;Ks1`?|J~LU=?I zDFYuczou-)uq|QikO2E^Sljg0Hw>?FcerB*KcRLc8EM}ALx<1*JwL5$rcCiYjcV~q zGi^3R-=+LIH7v>EEXh+H_=%Nota3}|t>4#ZBBAg}VI-?bl)#v#E>r$BB@A^Z@@Z#z zaWFW0`*%FLk}-!(xl-ESy{W_JX-CmZsoxaeH|5(;(41`4oDB;w4qkkqw|>?xK-g+< zkj4P?@@l^_gD|T0LuB7`U8VdP($KiqNPc!r4Py{|v?!-mBW8vmq$38(^$i5VvE#kR z)N+7nXNRwTQYvX#r{D3zx!)B;ca-}6Hcg7q+4qP4@zUFI9&IDKlvl)4uzsx4!Y^K< zNg4LVeo=#6r>+4K@b{p!))7R5=<&O^#N@2iNTnc3INn3H ztqOQ*y4uadk5u~OfVKL{;Sd!sF#T znzTzclJiAu{X>U_{X>U_%n?&GA}SCwjziz2X2U^dWGYa%(%GRUCQ)^lbY$yWU2Kv_ zotQMmdppqQ!hCaS!X`1Zz3rN1C6s!P8FopkG(6*|y#FcU%%h?D-#DJgQnm<@ZH6q# zRwUaPG1)>?LIznvvW#VhER(IqZpadbWTHhQTM9!dHCd-dG0co_h!JM8CC2i*zQ6N3 zzyI&K=iGbleV*s@eqQf)pAsT2y0AtQr_xxbq#nw@!ThR|86Fhrf!`eN(%u^Pb?!LL zGxZRaY;GbfH_$01Yj<@hfe$rset%ZsF{j(tFP__Dx~|c~L=dDrWJ}B=eNbYHY56(9 zCVr__d9iPDA>h+AS+c?E&!hGSBgjnhKHiUh0LY9Fp2hXUYYz)yyEk`}Wh^aTi~kNj z>sOM19u3YkgNdX+xA?Q8lLhurcB2>eJ0PkeC+JR~Q+(~o*dL6Rk^t99@|uWGhNUDm zlp-X;=!RDw&TfckzgiA(E9pQDlyZ|5K?op%9o@I8KqegdyY{IX#syTmg-XSGOdK@r z<}fZQS0d0>tc_)Up$NT^>9XQgj2Q7x1v$G|S$7`63zpq2pI~}cgn;MUDL$Tl!!aP> zKjwDz4@#WivemFy9fdt_z(EQO|T4ZFumRMtaMHrN4n_uOeZY6 zgYax!yc|QhmdLMUiM{%vH)Qn~iE(!-?j8{3lzw7hzEIu)(`CTKJ`DnUcs|_siSn}E zYm}1`S6`rcgFX+Nq-rlqg+3H)t9XVgLSJd8b}3;A#Q79jw3M!nOK9g0Rqi5C_DNz< zq~Bajpz{uWZR`0kuxOW{#PFRJe?+Huv+9MLpZN10gZ;}|$o{=YP=XIZkB@p4=8J9d zpolQR-22~lTlSaxVDBNDo&W^+8-MFtESpTuv5gvdct*I>JgDK9PBrujd6=%gkksbw zf7UkFRzd9FuGMeOMVdc9qRl)rDL71>BXwwpkGLu;V;^nC2>~HJp}+#PE*r?uC~Tg@7X1I85Rv0;NyXI!)W3OO7WQ46qA$3BSw+$2Cbvhy| zWzWuy#d^&UURaYRAty{32LsXXm-f0CAP3{FrN*A%L>R+q9#5?>MzGhH5~z|J`JTI!UW zR-8Tc4TcMHM#w$2`JkZC9qRXL*0vYOPoaQ!d+_&9#t#Po92G{tQx@ZZjqiIPdIlNU^f4zEh9r5(N=DQ{GO??r*)logQ!!rtK-nE^2(MIk-JqdF# zeYND@q3dr-u3tSjTm(MCkdwsd^zA)3jG5bd@swm}GcF1FYq36On)>z4<9=yij%eOAn|FZ$&%@NvFRT8>DQi5E@Q zH_}G`i>88j)uI1KQ%#HPEt=0uHnqCT)VO8Ep=11&L%Y-t-O6Zxt04Rfs~I|EYDBzQ z>Rg`A4-9+|1Kq5xa*%qPOVmJ38^JfE?^p6zcaSp(SuYOs)N_~<2h@gCaaZXh`C}qT zEYwvglX@?DO<2GkS99h@*wa(DpaYhHo|BXp0=l&~me+|!uj>|s3*Ap&sMjR8Ca>l$DvIn&2wlQZp=dd zVUrCoHy(*jwSZezZeU&G_jh@m+lNy%@`_@N5~w>Fx(g?Bk4m|bA%Q_MZl$Wgsm>W# zmhzIk-7h*KoUWZ}|7}3L)SsEh*!U7}R{C3BY{S1Q(s2aZ(}FkGdrBxo5V}kK@fM?+ zj+p7vrUUY24t~f#9pnvxh)n#PH>#7IZmx@I07|x7dEKSE1ku@48w`&?Sddk|e~T1s zf4TVE78Ap7tACwY=gQ_03!+b-8G;)YGffE)zdDG_Ta4& zYyEPIbILfh-Nj5BSn}9`0Jue-w3Q^_Y>4-W;7Sn;r zpMjW%`1^2d01dM@RQvgq*4z3MB;vKEV1JdXyDwvSsU*PjQZ@BxY6|j6yb1|jOsdWS zVe(1fbxFG^%dI6nA;T_2amZ=pSlT;0llH|>khyuWET-@BqALCO+w4Io@jX1tR2tv| zuDK!qu?BRx?xy7It@8gFYP`*gJ-v*kqa6)QVp4L9(iGjnnT)vOX49fRd&8S@sF@cK zx~BuxId3tG8I-^^-68O`B&`qP7x8=RSbBqAFTA8Sm3Mr>P4ny8diwCI=P;k1xvlo= zqzT8$6o^QI)@Hm)M+)v>8zYL&eZq>rTS@~cIA`sqXz!PrfuFG57`_^Q=#w6^oD{jj zgR8zXxVyn>L4Wfl@*gIQptdle*k0P#|zon1lqRPkfG;E^=t{9{_Zz3NK~HqMtJ zaoA-xWvHss%FLi;>f-{tYND9V&b|I_zZrS$@qAq)OA&*`_WT~m*Nz~Of9CJuT`X&g zv%ISXw>LOSi&@SS(>dqft?}1@O|W+qaOj2R}z#Z`Is9 zw#r_e__>&GOa$=dD4MTmwqcdAT8iEiyEH#AlB*OrRxR_>*^%+fssm zd320Q=@w|Ew4={m0S3?x0v}U|#$kEmFwbeL9Yc=qp+2G>}`CFJv7#@2G z%!h?F*jGE*$G%Q2`}>b=X)ecqjeWe*mymrJ?>Bi6D|zdC8{eef8$4j9J$TIYoJMc^cm|*IuFC4=Vf2QD*&C{ww;y7)iFxrT) zc^;x6Vnx(UZ9~5GuS&H)=}qJHw>!STdr^stPvO}+gC(?upCs)6=M-Iob%nd7e5rGbYj3yXZybxr4apDEkDYokruQ$eY0T5U@xL z_I-dwBBg_1^4f9+a%MO_R&bJ{brQB`JMgnq9WBr*lv?u9ksg^+wMel>o;VN4)#Ctq zgG;z}KI7&WcHLhi9yuvF1v%9k6GdxmPp|!8Ey^=MT2V1{Fk&dpso!C8I0}O)z3%Sb*j7}?Ngtd1h1FOje5OLkjv1Y zD;=LG2JBgTjvH8WLmj0lXjJ-1LGtvN_)9x7yLJdmXSyU37G?`73pgGiejg}aG^aV@ z68GR4=w)-ntGF!JajY&?|Dw{017Lg!*L>0J(+X)B1nfhjIPHa+^>7FFcPF4+3~C7& z(8>)LWUV&Y%e$1yzMa^ObtXFi4H&96BAYbckb&ZKPv7(~&jW{#YhsRt9)Wi{wj!$H zPBORmvN%s?7M9_64}V4pijG~_$l*{kLzj(#^v!Nj_4}wQfteK-Z_A$Fx-e$ew=EsBuy==4181LIHA9zW`{3ntj?^3U z-JJ!q2y`fLJ&%*TMr+4-m$gH4M77BfV)wz$u_8l@JTcO)NTBfrkLzwi1wiQUJ{y>W KCE=WR@_ztD>vysM literal 0 HcmV?d00001 diff --git a/analyst/assets/img/shield_success.png b/analyst/assets/img/shield_success.png new file mode 100644 index 0000000000000000000000000000000000000000..531abe9314d040dc1cd035c40dc3e2ce58f8dee1 GIT binary patch literal 21612 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

womi`pZy_aKa@QpoV(mAzqUhowx(4 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 literal 0 HcmV?d00001 diff --git a/analyst/assets/img/smile.png b/analyst/assets/img/smile.png new file mode 100644 index 0000000000000000000000000000000000000000..450537845f002abee53a9566c32e6ee0aca6dfa7 GIT binary patch literal 3077 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 Account\Account($options['client-id'], $options['client-secret'], $options['base-dir'])); + } catch (Exception $e) { + error_log('Analyst SDK receive an error: [' . $e->getMessage() . '] Please contact our support at support@analyst.com'); + } + } +} diff --git a/analyst/sdk_resolver.php b/analyst/sdk_resolver.php new file mode 100644 index 0000000..1c041bb --- /dev/null +++ b/analyst/sdk_resolver.php @@ -0,0 +1,79 @@ += 0; + $wpSupported = version_compare($wp_version, $sdk['wp']) >= 0; + + return $phpSupported && $wpSupported; + })); + + // Sort SDK by version in descending order + uasort($supported, function ($x, $y) { + return version_compare($y['sdk'], $x['sdk']); + }); + + // Reset sorted values keys + $supported = array_values($supported); + + if (!isset($supported[0])) { + throw new Exception('There is no SDK which is support current PHP version and WP version'); + } + + // Autoload files for supported SDK + $autoloaderPath = str_replace( + '\\', + '/', + sprintf('%s/autoload.php', $supported[0]['path']) + ); + + require_once $autoloaderPath; + + $loaded = true; + } +} diff --git a/analyst/src/Account/Account.php b/analyst/src/Account/Account.php new file mode 100644 index 0000000..28e8da4 --- /dev/null +++ b/analyst/src/Account/Account.php @@ -0,0 +1,604 @@ +id = $id; + $this->clientSecret = $secret; + + $this->path = $baseDir; + + $this->basePluginPath = plugin_basename($baseDir); + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @param string $path + */ + public function setPath($path) + { + $this->data->setPath($path); + + $this->path = $path; + } + + /** + * @return bool + */ + public function isOptedIn() + { + return $this->isOptedIn; + } + + /** + * @param bool $isOptedIn + */ + public function setIsOptedIn($isOptedIn) + { + $this->data->setIsOptedIn($isOptedIn); + + $this->isOptedIn = $isOptedIn; + } + + /** + * Whether plugin is active + * + * @return bool + */ + public function isActive() + { + return is_plugin_active($this->path); + } + + /** + * @param string $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @return bool + */ + public function isInstalled() + { + return $this->isInstalled; + } + + /** + * @param bool $isInstalled + */ + public function setIsInstalled($isInstalled) + { + $this->data->setIsInstalled($isInstalled); + + $this->isInstalled = $isInstalled; + } + + /** + * Should register activation and deactivation + * event hooks + * + * @return void + */ + public function registerHooks() + { + register_activation_hook($this->basePluginPath, [&$this, 'onActivePluginListener']); + register_uninstall_hook($this->basePluginPath, ['Account\Account', 'onUninstallPluginListener']); + + $this->addFilter('plugin_action_links', [&$this, 'onRenderActionLinksHook']); + + $this->addAjax('analyst_opt_in', [&$this, 'onOptInListener']); + $this->addAjax('analyst_opt_out', [&$this, 'onOptOutListener']); + $this->addAjax('analyst_plugin_deactivate', [&$this, 'onDeactivatePluginListener']); + $this->addAjax('analyst_install', [&$this, 'onInstallListener']); + $this->addAjax('analyst_skip_install', [&$this, 'onSkipInstallListener']); + $this->addAjax('analyst_install_verified', [&$this, 'onInstallVerifiedListener']); + } + + /** + * Will fire when admin activates plugin + * + * @return void + */ + public function onActivePluginListener() + { + if (!$this->isInstallResolved()) { + DatabaseCache::getInstance()->put('plugin_to_install', $this->id); + } + + if (!$this->isAllowingLogging()) return; + + ActivateRequest::make($this->collector, $this->id, $this->path) + ->execute($this->requestor); + + $this->setIsInstalled(true); + + AccountDataFactory::syncData(); + } + + /** + * Will fire when admin deactivates plugin + * + * @return void + */ + public function onDeactivatePluginListener() + { + if (!$this->isAllowingLogging()) return; + + $question = isset($_POST['question']) ? stripslashes($_POST['question']) : null; + $reason = isset($_POST['reason']) ? stripslashes($_POST['reason']) : null; + + $response = DeactivateRequest::make($this->collector, $this->id, $this->path, $question, $reason) + ->execute($this->requestor); + + // Exit if request failed + if (!$response->isSuccess()) { + wp_send_json_error($response->body); + } + + $this->setIsInstalled(false); + + AccountDataFactory::syncData(); + + wp_send_json_success(); + } + + /** + * Will fire when user opted in + * + * @return void + */ + public function onOptInListener() + { + $response = OptInRequest::make($this->collector, $this->id, $this->path)->execute($this->requestor); + + // Exit if request failed + if (!$response->isSuccess()) { + wp_send_json_error($response->body); + } + + $this->setIsOptedIn(true); + + AccountDataFactory::syncData(); + + wp_die(); + } + + /** + * Will fire when user opted out + * + * @return void + */ + public function onOptOutListener() + { + $response = OptOutRequest::make($this->collector, $this->id, $this->path)->execute($this->requestor); + + // Exit if request failed + if (!$response->isSuccess()) { + wp_send_json_error($response->body); + } + + $this->setIsOptedIn(false); + + AccountDataFactory::syncData(); + + wp_send_json_success(); + } + + /** + * Will fire when user accept opt-in + * at first time + * + * @return void + */ + public function onInstallListener() + { + $cache = DatabaseCache::getInstance(); + + // Set flag to true which indicates that install is resolved + // also remove install plugin id from cache + $this->setIsInstallResolved(true); + $cache->delete('plugin_to_install'); + + $response = InstallRequest::make($this->collector, $this->id, $this->path)->execute($this->requestor); + + // Exit if request failed + if (!$response->isSuccess()) { + wp_send_json_error($response->body); + } + + $this->setIsSigned(true); + + $this->setIsOptedIn(true); + + $factory = NoticeFactory::instance(); + + $message = sprintf('Please confirm your email by clicking on the link we sent to %s. This makes sure you’re not a bot.', $this->collector->getGeneralEmailAddress()); + + $notificationId = uniqid(); + + $notice = Notice::make( + $notificationId, + $this->getId(), + $message, + $this->collector->getPluginName($this->path) + ); + + $factory->addNotice($notice); + + AccountDataFactory::syncData(); + + // Set email confirmation notification id to cache + // se we can extract and remove it when user confirmed email + $cache->put( + sprintf('account_email_confirmation_%s', $this->getId()), + $notificationId + ); + + wp_send_json_success(); + } + + /** + * Will fire when user skipped installation + * + * @return void + */ + public function onSkipInstallListener() + { + // Set flag to true which indicates that install is resolved + // also remove install plugin id from cache + $this->setIsInstallResolved(true); + DatabaseCache::getInstance()->delete('plugin_to_install'); + } + + /** + * Will fire when user delete plugin through admin panel. + * This action will happen if admin at least once + * activated the plugin. + * + * @return void + * @throws \Exception + */ + public static function onUninstallPluginListener() + { + $factory = AccountDataFactory::instance(); + + $pluginFile = substr(current_filter(), strlen( 'uninstall_' )); + + $account = $factory->getAccountDataByBasePath($pluginFile); + + // If account somehow is not found, exit the execution + if (!$account) return; + + $analyst = Analyst::getInstance(); + + $collector = new Collector($analyst); + + $requestor = new ApiRequestor($account->getId(), $account->getSecret(), $analyst->getApiBase()); + + // Just send request to log uninstall event not caring about response + UninstallRequest::make($collector, $account->getId(), $account->getPath())->execute($requestor); + + $factory->sync(); + } + + /** + * Fires when used verified his account + */ + public function onInstallVerifiedListener() + { + $factory = NoticeFactory::instance(); + + $notice = Notice::make( + uniqid(), + $this->getId(), + 'Thank you for confirming your email.', + $this->collector->getPluginName($this->path) + ); + + $factory->addNotice($notice); + + // Remove confirmation notification + $confirmationNotificationId = DatabaseCache::getInstance()->pop(sprintf('account_email_confirmation_%s', $this->getId())); + $factory->remove($confirmationNotificationId); + + AccountDataFactory::syncData(); + + wp_send_json_success(); + } + + /** + * Will fire when wp renders plugin + * action buttons + * + * @param $defaultLinks + * @return array + */ + public function onRenderActionLinksHook($defaultLinks) + { + $customLinks = []; + + $customLinks[] = $this->isOptedIn() + ? 'Opt Out' + : 'Opt In'; + + // Append anchor to find specific deactivation link + if (isset($defaultLinks['deactivate'])) { + $defaultLinks['deactivate'] .= ''; + } + + return array_merge($customLinks, $defaultLinks); + } + + /** + * @return AccountData + */ + public function getData() + { + return $this->data; + } + + /** + * @param AccountData $data + */ + public function setData(AccountData $data) + { + $this->data = $data; + + $this->setIsOptedIn($data->isOptedIn()); + $this->setIsInstalled($data->isInstalled()); + $this->setIsSigned($data->isSigned()); + $this->setIsInstallResolved($data->isInstallResolved()); + } + + /** + * Resolves valid action name + * based on client id + * + * @param $action + * @return string + */ + private function resolveActionName($action) + { + return sprintf('%s_%s', $action, $this->id); + } + + /** + * Register action for current plugin + * + * @param $action + * @param $callback + */ + private function addFilter($action, $callback) + { + $validAction = sprintf('%s_%s', $action, $this->basePluginPath); + + add_filter($validAction, $callback, 10); + } + + /** + * Add ajax action for current plugin + * + * @param $action + * @param $callback + * @param bool $raw Format action ?? + */ + private function addAjax($action, $callback, $raw = false) + { + $validAction = $raw ? $action : sprintf('%s%s', 'wp_ajax_', $this->resolveActionName($action)); + + add_action($validAction, $callback); + } + + /** + * @return bool + */ + public function isSigned() + { + return $this->isSigned; + } + + /** + * @param bool $isSigned + */ + public function setIsSigned($isSigned) + { + $this->data->setIsSigned($isSigned); + + $this->isSigned = $isSigned; + } + + /** + * @return RequestorContract + */ + public function getRequestor() + { + return $this->requestor; + } + + /** + * @param RequestorContract $requestor + */ + public function setRequestor(RequestorContract $requestor) + { + $this->requestor = $requestor; + } + + /** + * @return string + */ + public function getClientSecret() + { + return $this->clientSecret; + } + + /** + * @return Collector + */ + public function getCollector() + { + return $this->collector; + } + + /** + * @param Collector $collector + */ + public function setCollector(Collector $collector) + { + $this->collector = $collector; + } + + /** + * Do we allowing logging + * + * @return bool + */ + public function isAllowingLogging() + { + return $this->isOptedIn; + } + + /** + * @return string + */ + public function getBasePluginPath() + { + return $this->basePluginPath; + } + + /** + * @return bool + */ + public function isInstallResolved() + { + return $this->isInstallResolved; + } + + /** + * @param bool $isInstallResolved + */ + public function setIsInstallResolved($isInstallResolved) + { + $this->data->setIsInstallResolved($isInstallResolved); + + $this->isInstallResolved = $isInstallResolved; + } +} diff --git a/analyst/src/Account/AccountData.php b/analyst/src/Account/AccountData.php new file mode 100644 index 0000000..aee975a --- /dev/null +++ b/analyst/src/Account/AccountData.php @@ -0,0 +1,176 @@ +id; + } + + /** + * @param string $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @param string $path + * @return AccountData + */ + public function setPath($path) + { + $this->path = $path; + return $this; + } + + /** + * @return bool + */ + public function isInstalled() + { + return $this->isInstalled; + } + + /** + * @param bool $isInstalled + */ + public function setIsInstalled($isInstalled) + { + $this->isInstalled = $isInstalled; + } + + /** + * @return bool + */ + public function isOptedIn() + { + return $this->isOptedIn; + } + + /** + * @param bool $isOptedIn + */ + public function setIsOptedIn($isOptedIn) + { + $this->isOptedIn = $isOptedIn; + } + + /** + * @return bool + */ + public function isSigned() + { + return $this->isSigned; + } + + /** + * @param bool $isSigned + */ + public function setIsSigned($isSigned) + { + $this->isSigned = $isSigned; + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return string + */ + public function getSecret() + { + return $this->secret; + } + + /** + * @param string $secret + */ + public function setSecret($secret) + { + $this->secret = $secret; + } + + /** + * @return bool + */ + public function isInstallResolved() + { + return $this->isInstallResolved; + } + + /** + * @param bool $isInstallResolved + */ + public function setIsInstallResolved($isInstallResolved) + { + $this->isInstallResolved = $isInstallResolved; + } +} diff --git a/analyst/src/Account/AccountDataFactory.php b/analyst/src/Account/AccountDataFactory.php new file mode 100644 index 0000000..7131ba6 --- /dev/null +++ b/analyst/src/Account/AccountDataFactory.php @@ -0,0 +1,125 @@ +sync(); + } + + /** + * Find plugin account data or create fresh one + * + * @param Account $account + * @return AccountData|null + */ + public function resolvePluginAccountData(Account $account) + { + $accountData = $this->findAccountDataById($account->getId()); + + if (!$accountData) { + $accountData = new AccountData(); + + // Set proper default values + $accountData->setPath($account->getPath()); + $accountData->setId($account->getId()); + $accountData->setSecret($account->getClientSecret()); + + array_push($this->accounts, $accountData); + } + + return $accountData; + } + + /** + * Should return account data by base path + * + * @param $basePath + * @return AccountData + */ + public function getAccountDataByBasePath($basePath) + { + foreach ($this->accounts as $iterable) { + $iterableBasePath = plugin_basename($iterable->getPath()); + + if ($iterableBasePath === $basePath) { + return $iterable; + } + } + + return null; + } + + /** + * Return account by id + * + * @param $id + * @return AccountData|null + */ + private function findAccountDataById($id) + { + foreach ($this->accounts as &$iterable) { + if ($iterable->getId() === $id) { + return $iterable; + } + } + + return null; + } +} diff --git a/analyst/src/Analyst.php b/analyst/src/Analyst.php new file mode 100644 index 0000000..c3777d5 --- /dev/null +++ b/analyst/src/Analyst.php @@ -0,0 +1,167 @@ +mutator = new Mutator(); + + $this->accountDataFactory = AccountDataFactory::instance(); + + $this->mutator->initialize(); + + $this->collector = new Collector($this); + + $this->initialize(); + } + + /** + * Initialize rest of application + */ + public function initialize() + { + add_action('init', function () { + $this->collector->loadCurrentUser(); + }); + } + + /** + * Register new account + * + * @param Account $account + * @return Analyst + * @throws \Exception + */ + public function registerAccount($account) + { + // Stop propagation when account is already registered + if ($this->isAccountRegistered($account)) { + return $this; + } + + // Resolve account data from factory + $accountData = $this->accountDataFactory->resolvePluginAccountData($account); + + $account->setData($accountData); + + $account->setRequestor( + $this->resolveRequestorForAccount($account) + ); + + $account->setCollector($this->collector); + + $account->registerHooks(); + + $this->accounts[$account->getId()] = $account; + + return $this; + } + + /** + * Must return version of analyst + * + * @return string + */ + public static function version() + { + $version = require __DIR__ . '/../version.php'; + + return $version['sdk']; + } + + /** + * Is this account registered + * + * @param Account $account + * @return bool + */ + protected function isAccountRegistered($account) + { + return isset($this->accounts[$account->getId()]); + } + + /** + * Resolves requestor for account + * + * @param Account $account + * @return RequestorContract + * @throws \Exception + */ + protected function resolveRequestorForAccount(Account $account) + { + $requestor = new ApiRequestor($account->getId(), $account->getClientSecret(), $this->apiBase); + + // Set SDK version + $requestor->setDefaultHeader( + 'x-analyst-client-user-agent', + sprintf('Analyst/%s', $this->version()) + ); + + return $requestor; + } + + /** + * @return string + */ + public function getApiBase() + { + return $this->apiBase; + } +} diff --git a/analyst/src/ApiRequestor.php b/analyst/src/ApiRequestor.php new file mode 100644 index 0000000..b842d01 --- /dev/null +++ b/analyst/src/ApiRequestor.php @@ -0,0 +1,257 @@ + 'application/json', + 'content-type' => 'application/json' + ]; + + /** + * Prioritized http clients + * + * @var array + */ + protected $availableClients = [ + 'Analyst\Http\WordPressHttpClient', + 'Analyst\Http\CurlHttpClient', + 'Analyst\Http\DummyHttpClient', + ]; + + /** + * ApiRequestor constructor. + * @param $id + * @param $secret + * @param $apiBase + * @throws \Exception + */ + public function __construct($id, $secret, $apiBase) + { + $this->clientId = $id; + $this->clientSecret = $secret; + + $this->setApiBase($apiBase); + + $this->httpClient = $this->resolveHttpClient(); + } + + /** + * Set api base url + * + * @param $url + */ + public function setApiBase($url) + { + $this->apiBase = $url; + } + + /** + * Get request + * + * @param $url + * @param array $headers + * @return mixed + */ + public function get($url, $headers = []) + { + return $this->request('GET', $url, null, $headers); + } + + /** + * Post request + * + * @param $url + * @param $body + * @param array $headers + * @return mixed + */ + public function post($url, $body = [], $headers = []) + { + return $this->request('POST', $url, $body, $headers); + } + + /** + * Put request + * + * @param $url + * @param $body + * @param array $headers + * @return mixed + */ + public function put($url, $body = [], $headers = []) + { + return $this->request('PUT', $url, $body, $headers); + } + + /** + * Delete request + * + * @param $url + * @param array $headers + * @return mixed + */ + public function delete($url, $headers = []) + { + return $this->request('DELETE', $url, null, $headers); + } + + /** + * Make request to api + * + * @param $method + * @param $url + * @param array $body + * @param array $headers + * @return mixed + */ + protected function request($method, $url, $body = [], $headers = []) + { + $fullUrl = $this->resolveFullUrl($url); + + $date = date('r', time()); + + $headers['date'] = $date; + $headers['signature'] = $this->resolveSignature($this->clientSecret, $method, $fullUrl, $body, $date); + + // Lowercase header names + $headers = $this->prepareHeaders( + array_merge($headers, $this->defaultHeaders) + ); + + $response = $this->httpClient->request($method, $fullUrl, $body, $headers); + + // TODO: Check response code and take actions + + return $response; + } + + /** + * Set one default header + * + * @param $header + * @param $value + */ + public function setDefaultHeader($header, $value) + { + $this->defaultHeaders[ + $this->resolveValidHeaderName($header) + ] = $value; + } + + /** + * Resolves supported http client + * + * @return HttpClientContract + * @throws Exception + */ + protected function resolveHttpClient() + { + $clients = array_filter($this->availableClients, $this->guessClientSupportEnvironment()); + + if (!isset($clients[0])) { + throw new Exception('There is no http client which this application can support'); + } + + // Instantiate first supported http client + return new $clients[0]; + } + + /** + * This will filter out clients which is not supported + * by the current environment + * + * @return \Closure + */ + protected function guessClientSupportEnvironment() + { + return function ($client) { + return forward_static_call([$client, 'hasSupport']); + }; + } + + /** + * Resolves valid header name + * + * @param $headerName + * @return string + */ + private function resolveValidHeaderName($headerName) + { + return strtolower($headerName); + } + + /** + * Lowercase header names + * + * @param $headers + * @return array + */ + private function prepareHeaders($headers) + { + return array_change_key_case($headers, CASE_LOWER); + } + + /** + * Sign request + * + * @param $key + * @param $method + * @param $url + * @param $body + * @param $date + * + * @return false|string + */ + private function resolveSignature($key, $method, $url, $body, $date) + { + $string = implode('\n', [$method, $url, md5(json_encode($body)), $date]); + + $contentSecret = hash_hmac('sha256', $string, $key); + + return sprintf('%s:%s', $this->clientId, $contentSecret); + } + + /** + * Compose full url + * + * @param $url + * @return string + */ + private function resolveFullUrl($url) + { + return sprintf('%s/%s', $this->apiBase, trim($url, '/')); + } +} diff --git a/analyst/src/ApiResponse.php b/analyst/src/ApiResponse.php new file mode 100644 index 0000000..ec741c4 --- /dev/null +++ b/analyst/src/ApiResponse.php @@ -0,0 +1,44 @@ +body = $body; + $this->code = $code; + $this->headers = $headers; + } + + /** + * Whether status code is successful + * + * @return bool + */ + public function isSuccess() + { + return $this->code >= 200 && $this->code < 300; + } +} diff --git a/analyst/src/Cache/DatabaseCache.php b/analyst/src/Cache/DatabaseCache.php new file mode 100644 index 0000000..915da4a --- /dev/null +++ b/analyst/src/Cache/DatabaseCache.php @@ -0,0 +1,127 @@ +values = is_array($raw) ? $raw : @unserialize($raw); + + // In case serialization is failed + // make sure values is an array + if (!is_array($this->values)) { + $this->values = []; + } + } + + /** + * Save value with given key + * + * @param string $key + * @param string $value + * + * @return static + */ + public function put($key, $value) + { + $this->values[$key] = $value; + + $this->sync(); + + return $this; + } + + /** + * Get value by given key + * + * @param $key + * + * @param null $default + * @return string + */ + public function get($key, $default = null) + { + $value = isset($this->values[$key]) ? $this->values[$key] : $default; + + return $value; + } + + /** + * @param $key + * + * @return static + */ + public function delete($key) + { + if (isset($this->values[$key])) { + unset($this->values[$key]); + + $this->sync(); + } + + return $this; + } + + /** + * Update cache in DB + */ + protected function sync() + { + update_option(self::OPTION_KEY, serialize($this->values)); + } + + /** + * Should get value and remove it from cache + * + * @param $key + * @param null $default + * @return mixed + */ + public function pop($key, $default = null) + { + $value = $this->get($key); + + $this->delete($key); + + return $value; + } +} diff --git a/analyst/src/Collector.php b/analyst/src/Collector.php new file mode 100644 index 0000000..8fe1495 --- /dev/null +++ b/analyst/src/Collector.php @@ -0,0 +1,217 @@ +sdk = $sdk; + } + + /** + * Load current user into memory + */ + public function loadCurrentUser() + { + $this->user = wp_get_current_user(); + } + + /** + * Get site url + * + * @return string + */ + public function getSiteUrl() + { + return get_option('siteurl'); + } + + /** + * Get current user email + * + * @return string + */ + public function getCurrentUserEmail() + { + return $this->user->user_email; + } + + /** + * Get's email from general settings + * + * @return string + */ + public function getGeneralEmailAddress() + { + return get_option('admin_email'); + } + + /** + * Is this user administrator + * + * @return bool + */ + public function isUserAdministrator() + { + return in_array('administrator', $this->user->roles); + } + + /** + * User name + * + * @return string + */ + public function getCurrentUserName() + { + return $this->user ? $this->user->user_nicename : 'unknown'; + } + + /** + * WP version + * + * @return string + */ + public function getWordPressVersion() + { + global $wp_version; + + return $wp_version; + } + + /** + * PHP version + * + * @return string + */ + public function getPHPVersion() + { + return phpversion(); + } + + /** + * Resolves plugin information + * + * @param string $path Absolute path to plugin + * @return array + */ + public function resolvePluginData($path) + { + if( !function_exists('get_plugin_data') ){ + require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); + } + + return get_plugin_data($path); + } + + /** + * Get plugin name by path + * + * @param $path + * @return string + */ + public function getPluginName($path) + { + $data = $this->resolvePluginData($path); + + return $data['Name']; + } + + /** + * Get plugin version + * + * @param $path + * @return string + */ + public function getPluginVersion($path) + { + $data = $this->resolvePluginData($path); + + return $data['Version'] ? $data['Version'] : null; + } + + /** + * Get server ip + * + * @return string + */ + public function getServerIp() + { + return $_SERVER['SERVER_ADDR']; + } + + /** + * @return string + */ + public function getSDKVersion() + { + return $this->sdk->version(); + } + + /** + * @return string + */ + public function getMysqlVersion() + { + $conn = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME); + + $version = mysqli_get_server_info($conn); + + return $version ? $version : 'unknown'; + } + + /** + * @return string + */ + public function getSiteLanguage() + { + return get_locale(); + } + + + /** + * Current WP theme + * + * @return false|string + */ + public function getCurrentThemeName() + { + return wp_get_theme()->get('Name'); + } + + /** + * Get active plugins list + * + * @return array + */ + public function getActivePluginsList() + { + if (!function_exists('get_plugins')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $allPlugins = get_plugins(); + + $activePluginsNames = array_map(function ($path) use ($allPlugins) { + return $allPlugins[$path]['Name']; + }, get_option('active_plugins')); + + return $activePluginsNames; + } +} diff --git a/analyst/src/Contracts/AnalystContract.php b/analyst/src/Contracts/AnalystContract.php new file mode 100644 index 0000000..83367fe --- /dev/null +++ b/analyst/src/Contracts/AnalystContract.php @@ -0,0 +1,12 @@ + true, + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => $this->prepareRequestHeaders($headers), + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_FAILONERROR => true, + CURLOPT_HEADER => true, + CURLOPT_TIMEOUT => 30, + ]; + + if ($method === 'POST') { + $options[CURLOPT_POST] = 1; + $options[CURLOPT_POSTFIELDS] = json_encode($body); + } + + $curl = curl_init(); + + curl_setopt_array($curl, $options); + + $response = curl_exec($curl); + + list($rawHeaders, $rawBody) = explode("\r\n\r\n", $response, 2); + + $info = curl_getinfo($curl); + + curl_close($curl); + + $responseHeaders = $this->resolveResponseHeaders($rawHeaders); + $responseBody = json_decode($rawBody, true); + + return new ApiResponse($responseBody, $info['http_code'], $responseHeaders); + } + + /** + * Must return `true` if client is supported + * + * @return bool + */ + public static function hasSupport() + { + return function_exists('curl_version'); + } + + /** + * Modify request headers from key value pair + * to vector array + * + * @param array $headers + * @return array + */ + protected function prepareRequestHeaders ($headers) + { + return array_map(function ($key, $value) { + return sprintf('%s:%s', $key, $value); + }, array_keys($headers), $headers); + } + + /** + * Resolve raw response headers as + * associative array + * + * @param $rawHeaders + * @return array + */ + private function resolveResponseHeaders($rawHeaders) + { + $headers = []; + + foreach (explode("\r\n", $rawHeaders) as $i => $line) { + $parts = explode(': ', $line); + + if (count($parts) === 1) { + continue; + } + + $headers[$parts[0]] = $parts[1]; + } + return $headers; + } +} diff --git a/analyst/src/Http/DummyHttpClient.php b/analyst/src/Http/DummyHttpClient.php new file mode 100644 index 0000000..afd2913 --- /dev/null +++ b/analyst/src/Http/DummyHttpClient.php @@ -0,0 +1,33 @@ +collector = $collector; + $this->id = $pluginId; + $this->path = $path; + } + + /** + * Cast request data to array + * + * @return array + */ + public function toArray() + { + return [ + 'plugin_id' => $this->id, + 'php_version' => $this->collector->getPHPVersion(), + 'wp_version' => $this->collector->getWordPressVersion(), + 'plugin_version' => $this->collector->getPluginVersion($this->path), + 'url' => $this->collector->getSiteUrl(), + 'sdk_version' => $this->collector->getSDKVersion(), + 'ip' => $this->collector->getServerIp(), + 'mysql_version' => $this->collector->getMysqlVersion(), + 'locale' => $this->collector->getSiteLanguage(), + 'current_theme' => $this->collector->getCurrentThemeName(), + 'active_plugins_list' => implode(', ', $this->collector->getActivePluginsList()), + 'email' => $this->collector->getGeneralEmailAddress(), + 'name' => $this->collector->getCurrentUserName() + ]; + } + + /** + * Execute the request + * @param RequestorContract $requestor + * @return ApiResponse + */ + public abstract function execute(RequestorContract $requestor); +} diff --git a/analyst/src/Http/Requests/ActivateRequest.php b/analyst/src/Http/Requests/ActivateRequest.php new file mode 100644 index 0000000..35cda8c --- /dev/null +++ b/analyst/src/Http/Requests/ActivateRequest.php @@ -0,0 +1,42 @@ +post('logger/activate', $this->toArray()); + } + + /** + * Make request instance + * + * @param Collector $collector + * @param $pluginId + * @param $path + * @return static + */ + public static function make(Collector $collector, $pluginId, $path) + { + return new static($collector, $pluginId, $path); + } +} diff --git a/analyst/src/Http/Requests/DeactivateRequest.php b/analyst/src/Http/Requests/DeactivateRequest.php new file mode 100644 index 0000000..39d2052 --- /dev/null +++ b/analyst/src/Http/Requests/DeactivateRequest.php @@ -0,0 +1,64 @@ +question = $question; + $this->answer = $answer; + } + + public function toArray() + { + return array_merge(parent::toArray(), [ + 'question' => $this->question, + 'answer' => $this->answer, + ]); + } + + /** + * Execute the request + * @param RequestorContract $requestor + * @return ApiResponse + */ + public function execute(RequestorContract $requestor) + { + return $requestor->post('logger/deactivate', $this->toArray()); + } +} diff --git a/analyst/src/Http/Requests/InstallRequest.php b/analyst/src/Http/Requests/InstallRequest.php new file mode 100644 index 0000000..773c99c --- /dev/null +++ b/analyst/src/Http/Requests/InstallRequest.php @@ -0,0 +1,38 @@ +post('logger/install', $this->toArray()); + } + + /** + * Make request instance + * + * @param Collector $collector + * @param $pluginId + * @param $path + * @return static + */ + public static function make(Collector $collector, $pluginId, $path) + { + return new static($collector, $pluginId, $path); + } +} diff --git a/analyst/src/Http/Requests/OptInRequest.php b/analyst/src/Http/Requests/OptInRequest.php new file mode 100644 index 0000000..1d38165 --- /dev/null +++ b/analyst/src/Http/Requests/OptInRequest.php @@ -0,0 +1,42 @@ +post('logger/opt-in', $this->toArray()); + } + + /** + * Make request instance + * + * @param Collector $collector + * @param $pluginId + * @param $path + * @return static + */ + public static function make(Collector $collector, $pluginId, $path) + { + return new static($collector, $pluginId, $path); + } +} diff --git a/analyst/src/Http/Requests/OptOutRequest.php b/analyst/src/Http/Requests/OptOutRequest.php new file mode 100644 index 0000000..4396883 --- /dev/null +++ b/analyst/src/Http/Requests/OptOutRequest.php @@ -0,0 +1,40 @@ +post('logger/opt-out', $this->toArray()); + } +} diff --git a/analyst/src/Http/Requests/UninstallRequest.php b/analyst/src/Http/Requests/UninstallRequest.php new file mode 100644 index 0000000..6e19f91 --- /dev/null +++ b/analyst/src/Http/Requests/UninstallRequest.php @@ -0,0 +1,36 @@ +post('logger/uninstall', $this->toArray()); + } +} diff --git a/analyst/src/Http/WordPressHttpClient.php b/analyst/src/Http/WordPressHttpClient.php new file mode 100644 index 0000000..335f869 --- /dev/null +++ b/analyst/src/Http/WordPressHttpClient.php @@ -0,0 +1,61 @@ + json_encode($body), + 'headers' => $headers, + 'method' => $method, + 'timeout' => 30, + ]; + + $response = wp_remote_request($url, $options); + + $body = []; + $responseHeaders = []; + + if ($response instanceof WP_Error) { + $code = $response->get_error_code(); + } else { + /** @var Requests_Utility_CaseInsensitiveDictionary $headers */ + $responseHeaders = $response['headers']->getAll(); + $body = json_decode($response['body'], true); + $code = $response['response']['code']; + } + + + return new ApiResponse( + $body, + $code, + $responseHeaders + ); + } + + /** + * Must return `true` if client is supported + * + * @return bool + */ + public static function hasSupport() + { + return function_exists('wp_remote_request'); + } +} diff --git a/analyst/src/Mutator.php b/analyst/src/Mutator.php new file mode 100644 index 0000000..03ea033 --- /dev/null +++ b/analyst/src/Mutator.php @@ -0,0 +1,103 @@ +factory = NoticeFactory::instance(); + + $this->notices = $this->factory->getNotices(); + + $this->cache = DatabaseCache::getInstance(); + } + + /** + * Register filters all necessary stuff. + * Can be invoked only once. + * + * @return void + */ + public function initialize() + { + $this->registerLinks(); + $this->registerAssets(); + $this->registerHooks(); + } + + /** + * Register all necessary filters and templates + * + * @return void + */ + protected function registerLinks() + { + add_action('admin_footer', function () { + analyst_require_template('optout.php', [ + 'shieldImage' => analyst_assets_url('img/shield_question.png') + ]); + + analyst_require_template('optin.php'); + + analyst_require_template('forms/deactivate.php', [ + 'pencilImage' => analyst_assets_url('img/pencil.png'), + 'smileImage' => analyst_assets_url('img/smile.png'), + ]); + + analyst_require_template('forms/install.php', [ + 'pluginToInstall' => $this->cache->get('plugin_to_install'), + 'shieldImage' => analyst_assets_url('img/shield_success.png'), + ]); + }); + + add_action('admin_notices',function () { + foreach ($this->notices as $notice) { + analyst_require_template('notice.php', ['notice' => $notice]); + } + }); + } + + /** + * Register all assets + */ + public function registerAssets() + { + add_action('admin_enqueue_scripts', function () { + wp_enqueue_style('analyst_custom', analyst_assets_url('/css/customize.css')); + wp_enqueue_script('analyst_custom', analyst_assets_url('/js/customize.js')); + }); + } + + /** + * Register action hooks + */ + public function registerHooks() + { + add_action('wp_ajax_analyst_notification_dismiss', function () { + $this->factory->remove($_POST['id']); + + $this->factory->sync(); + }); + } +} diff --git a/analyst/src/Notices/Notice.php b/analyst/src/Notices/Notice.php new file mode 100644 index 0000000..aed4035 --- /dev/null +++ b/analyst/src/Notices/Notice.php @@ -0,0 +1,121 @@ +setId($id); + $this->setBody($body); + $this->setAccountId($accountId); + $this->setPluginName($pluginName); + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @param string $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * @param string $body + */ + public function setBody($body) + { + $this->body = $body; + } + + /** + * @return string + */ + public function getAccountId() + { + return $this->accountId; + } + + /** + * @param string $accountId + */ + public function setAccountId($accountId) + { + $this->accountId = $accountId; + } + + /** + * @return string|null + */ + public function getPluginName() + { + return $this->pluginName; + } + + /** + * @param string $pluginName + */ + public function setPluginName($pluginName) + { + $this->pluginName = $pluginName; + } +} diff --git a/analyst/src/Notices/NoticeFactory.php b/analyst/src/Notices/NoticeFactory.php new file mode 100644 index 0000000..57619ab --- /dev/null +++ b/analyst/src/Notices/NoticeFactory.php @@ -0,0 +1,130 @@ +sync(); + } + + /** + * @return array + */ + public function getNotices() + { + return $this->notices; + } + + /** + * Filter out notices for certain account + * + * @param $accountId + * @return array + */ + public function getNoticesForAccount($accountId) + { + return array_filter($this->notices, function (Notice $notice) use ($accountId) { + return $notice->getAccountId() === $accountId; + }); + } + + /** + * Add new notice + * + * @param $notice + * + * @return $this + */ + public function addNotice($notice) + { + array_push($this->notices, $notice); + + $this->sync(); + + return $this; + } + + /** + * Find notice by id + * + * @param $id + * @return Notice|null + */ + public function find($id) + { + $notices = array_filter($this->notices, function (Notice $notice) use ($id) { + return $notice->getId() === $id; + }); + + return array_pop($notices); + } + + /** + * Remove notice by it's id + * + * @param $id + */ + public function remove($id) + { + // Get key of notice to remove + $key = array_search( + $this->find($id), + $this->notices + ); + + // Unset notice with key + unset($this->notices[$key]); + + $this->sync(); + } +} diff --git a/analyst/src/helpers.php b/analyst/src/helpers.php new file mode 100644 index 0000000..976039a --- /dev/null +++ b/analyst/src/helpers.php @@ -0,0 +1,84 @@ + +

+ +
+
+ +
+
+ Why do you deactivate? +
+ Please let us know, so we can improve it! Thank you +
+
+
+
+
    +
  • + +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
+ +
+
+ +
+
+ +
+
+ + + diff --git a/analyst/templates/forms/install.php b/analyst/templates/forms/install.php new file mode 100644 index 0000000..3e7f419 --- /dev/null +++ b/analyst/templates/forms/install.php @@ -0,0 +1,113 @@ + + + diff --git a/analyst/templates/notice.php b/analyst/templates/notice.php new file mode 100644 index 0000000..ddc8e76 --- /dev/null +++ b/analyst/templates/notice.php @@ -0,0 +1,10 @@ +
+

+ getPluginName()?> + getBody()?> +

+ + +
diff --git a/analyst/templates/optin.php b/analyst/templates/optin.php new file mode 100644 index 0000000..794dd5a --- /dev/null +++ b/analyst/templates/optin.php @@ -0,0 +1,60 @@ + diff --git a/analyst/templates/optout.php b/analyst/templates/optout.php new file mode 100644 index 0000000..2bd0df3 --- /dev/null +++ b/analyst/templates/optout.php @@ -0,0 +1,109 @@ + + + + diff --git a/analyst/version.php b/analyst/version.php new file mode 100644 index 0000000..70323e1 --- /dev/null +++ b/analyst/version.php @@ -0,0 +1,15 @@ + '1.3.28', + + // Minimum supported WordPress version + 'wp' => '4.7', + + // Supported PHP version + 'php' => '5.4', + + // Path to current SDK$ + 'path' => __DIR__, +); diff --git a/readme.txt b/readme.txt index bf6b5f4..1dffee0 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_i Tags: security, encryption, ssl, shared ssl, private ssl, public ssl, private ssl, http, https Requires at least: 3.5 Tested up to: 4.9.1 -Stable tag: 3.4.2 +Stable tag: 3.4.1 License: GPLv3 WordPress HTTPS is intended to be an all-in-one solution to using SSL on WordPress sites. @@ -104,16 +104,10 @@ add_filter('force_ssl', 'store_force_ssl', 10, 3);` 2. Force SSL checkbox added to add/edit posts screen == Changelog == -= 3.4.2 = -* [Bug] Patch issue causing insecure content errors. If you are still having issues after updating, please revert to 3.4.0 and let me know in the support forum. + = 3.4.1 = -* [Feature] Content Fixer is now optional in the plugin settings. This option should be on by default and is the preferred method to use the plugin. -* [Feature] Output buffering optional via `wordpress_https_parser_ob` filter. #56 -* [Bug] External resources being changed to local URL #59 -* [Bug] Added RSS permalink to filtered URLs. -* [Bug] Remove network defaults on uninstall. -* [Bug] Fixed displaying of Admin CSS when `site_url` is HTTP. #58 -* [Bug] Fixed issues displaying and resetting network settings introduced by 3.4.0. +* Integrated feedback system + = 3.4.0 = * Tested with WordPress v4.9.1 * Many improvements to performance. Special thanks to He Shiming for help with profiling and improvements. diff --git a/wordpress-https.php b/wordpress-https.php index c90ec9c..4f871e2 100644 --- a/wordpress-https.php +++ b/wordpress-https.php @@ -4,12 +4,20 @@ Plugin URI: http://mvied.com/projects/wordpress-https/ Description: WordPress HTTPS is intended to be an all-in-one solution to using SSL on WordPress sites. Author: Mike Ems - Version: 3.4.2 + Version: 3.4.1 Author URI: http://mvied.com/ Text Domain: wordpress-https Domain Path: /lang/ */ +require_once 'analyst/main.php'; + +analyst_init(array( + 'client-id' => 'q6am740nqq3z0vpb', + 'client-secret' => '6143ae024b58969edcde780d52f2e15193c9ecf8', + 'base-dir' => __FILE__ +)); + /* Copyright 2018 Mike Ems (email : mike@mvied.com) @@ -54,7 +62,7 @@ function wphttps_autoloader($class) { if ( ! defined('WP_UNINSTALL_PLUGIN') ) { $wordpress_https = new WordPressHTTPS; $wordpress_https->setSlug('wordpress-https'); - $wordpress_https->setVersion('3.4.2'); + $wordpress_https->setVersion('3.4.0'); $wordpress_https->setLogger(Mvied_Logger::getInstance()); $wordpress_https->setDirectory(dirname(__FILE__)); $wordpress_https->setModuleDirectory(dirname(__FILE__) . '/lib/WordPressHTTPS/Module/'); @@ -66,10 +74,7 @@ function wphttps_autoloader($class) { // If WPHTTPS_RESET global is defined, reset settings if ( defined('WPHTTPS_RESET') && constant('WPHTTPS_RESET') == true ) { foreach($wordpress_https->getSettings() as $key => $default) { - if ($key == 'ssl_host_mapping') - $wordpress_https->setSetting($key, WordPressHTTPS::$ssl_host_mapping); - else - $wordpress_https->setSetting($key, $default); + $wordpress_https->setSetting($key, $default); } }