From 427360759471f0fe6412dfd4c637f93d7fe18510 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Wed, 17 Mar 2021 15:57:07 +0100 Subject: [PATCH] add blog post CRUD --- assets/img/blank.png | Bin 0 -> 163 bytes assets/img/no-image.png | Bin 0 -> 1536 bytes composer.json | 1 + .../post/2021/20210317-035213ovh_feu1.jpg | Bin 0 -> 15109 bytes .../Account/AccountAdminController.php | 12 +- src/Controller/Auth/AuthController.php | 4 +- .../Blog/CategoryAdminController.php | 7 +- src/Controller/Blog/PostAdminController.php | 102 ++++++++- src/Entity/Blog/Category.php | 1 + src/Entity/Blog/Post.php | 7 +- src/Entity/User.php | 12 +- .../BlogPostEventSubscriber.php | 78 +++++++ .../EntityManagerEventSubscriber.php | 2 +- src/Factory/Blog/PostFactory.php | 25 +++ src/Form/Blog/PostType.php | 208 ++++++++++++++++++ src/Form/FileUploadHandler.php | 28 +++ src/Manager/EntityManager.php | 3 + templates/blog/category_admin/edit.html.twig | 6 +- templates/blog/category_admin/index.html.twig | 8 +- templates/blog/category_admin/new.html.twig | 6 +- templates/blog/category_admin/show.html.twig | 14 +- templates/blog/post_admin/_form.html.twig | 36 +++ templates/blog/post_admin/edit.html.twig | 57 +++++ templates/blog/post_admin/index.html.twig | 128 ++++++----- templates/blog/post_admin/new.html.twig | 39 ++++ templates/blog/post_admin/show.html.twig | 106 +++++++++ 26 files changed, 798 insertions(+), 92 deletions(-) create mode 100644 assets/img/blank.png create mode 100644 assets/img/no-image.png create mode 100644 public/uploads/post/2021/20210317-035213ovh_feu1.jpg create mode 100644 src/EventSuscriber/BlogPostEventSubscriber.php create mode 100644 src/Factory/Blog/PostFactory.php create mode 100644 src/Form/Blog/PostType.php create mode 100644 src/Form/FileUploadHandler.php create mode 100644 templates/blog/post_admin/_form.html.twig create mode 100644 templates/blog/post_admin/edit.html.twig create mode 100644 templates/blog/post_admin/new.html.twig create mode 100644 templates/blog/post_admin/show.html.twig diff --git a/assets/img/blank.png b/assets/img/blank.png new file mode 100644 index 0000000000000000000000000000000000000000..5e21c8a9d64b6b89586c0cd1e2a0ced98ee35c98 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-mSQK*5Dp-y;YjHK@;M7UB8wRq zIOIW?@rCpD)j&bX64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1SPJY5_^ sG8*3=WMl;LniT%8&+~8qF~Q)#Vn%f*2F8>=Lpu=P)78&qol`;+02@6fmjD0& literal 0 HcmV?d00001 diff --git a/assets/img/no-image.png b/assets/img/no-image.png new file mode 100644 index 0000000000000000000000000000000000000000..7957221866d0ebf40bc0649eb2b32f0eb5fe8c2b GIT binary patch literal 1536 zcmai!dr(qo7{<|}5xgVWz}wGNm{94s7E~M(I7KZED$v+Q&$u-yR1`z zrL}2}ipbE>COc}E4Kgb#-C86x*UN@xtXh_9<)uCCzdO78&b;q$p6{FY`TqKzYpzV!ljoo(%FE;uPW__*{sz3UXqY&;0k*?8L_!_VbR$W$foG?B)!;zi@l3Demgx znuuIO2D`g4#b=>PH6U*4nA}jJZ=HtZ6OX4X7J5t1sIm7LzudAewC1dN7~pk}C}y&E zL`L2@?fT_i?TMecFT9?x0Ze3Meeam*zzszWVAZo33wMv~E4}~WVl~{=cq z+lM4>@8sE6wk26<9DCiS-pOjq3R%XjxZ&ndgqzG%X)KQ~uPohoc;lteYpJ(x-UOG| zoFBY$Bqv95xbgM4{3!$pg+i@&Wg;glJG)u^aHNIRA6>m5Ecbm$ht?dUR`PO(=ki+Y#|fTI<|QS+r_9j$P#vyBmSP`bH;ZctOX^a0Pm2@$if1g&&j zWG=wF%dqCYM+9?}*3yafAKZxW7cqbWlxQ|94FR`;7Xp5xLwk>kLccN|&P;A90oF6r;bR&X}^O+T& zX$wKv4<(Zh!i!PvJP7j+L#&Aw2LG-Y$$xXuq_xgL^<+Zx;5|dJ-S8&%yB{%N^j8GukL+X+zfe$*e%F^b^9OxspkCwns%cLsX#7ItJa3 zeFy(MuY@zR9iIJQhrWA>=i)U{ABe+sULN%8ro?3S?oPouw^vHn__W>!-oXscDV$Lf zsA~xDUJC6Z+v?{S&Wp%+!RR?{SBySfwFYcIdHWtLTq!S-PW<6(=$dklP%Qwtn~!M8 zTmr22*eJ84nsnn74`NZ6$?{jdFs;{@z?`&1=57AekY7!gP;NgQv(9nH)8MQUh^y zBp`n6K#>DCNqJwaBa_wPVW-UbbP8BiNzr4YNO1m}#I=3Vt_n^MGXtvAI;p0rP71t1 z7MZI;xVco|c`(5^0Ou#i)%Hh|6r2rF!H9Dn74WH&3J4%8AqJ1UVFr@sKD*k zlX`mvXIX?Afp|DWLu;XaQ6~h%8gT~VhCD$_PwCY*5;xX(E$mXLhrWtrf}p9HpPDsn z<8FN2{+NpBrY5AT(L45-r~1iW;&gPyxWGZM? ztO+NoETxV6O({M=WUE*8xtK6q=}aeP4;vsSSN|mVWEqni&Ndy2dP>c6;&|aF4iAWu`(uz!b|o` zb~_rAMbcE1GCgz`&}Hag!UHf^GT_mfkB;JEPpQ?(lH^1se?aY1p({$_b!Xo?`To(O zmJC)CO(63dWk9cIpt=Z{ocN}bnZO7^CXc~(V;zb^1_a)_UrqigEVo;**PBDC0iX_kf$8Lv*(D6CpBj_7smSDw0a*WrrCbUafciJ9>s67uCMH;v zvH%h?41F7ob?<88oNVh%U3SX<^sxqEACz^-R(EBe=>jc9sU?4*!^M%amT*ck(X9rY zh>xTMtEGGwC6R;1ke6aFKFxXVaP8cXibTSVgoei0Cx^f(b{nGHRN@+}@B~?>L}QTw z&~FkV=!@wT8w&54EOIsyzm6bKlA${nVxUKvrAVtP=wxsDZq5adP|7Bti~HS1gb>l` z3`a_CDm5LR7n-L;%R;CM3DcYZU>{;U(HNiMS@CIquBW0bW)K$z6Q^Gy$(Lypu|>$w z@N>?!wD6axVP>X8K>2S#muP@>UDXN}`W%=_12Pzse(UIbcbLPztfI7}=Dqn*Q2h63 zv%M)(&Qwm0jA?0#6!TiELZ>g&?jV)=sK@Ym#ZpU0j`Dkp!FBJyjTtuGra_!6GkMF| z36}Xqk7z17dAWq+MPbh#*3=7)6R%LWG1@75&h9E7Un)ArOh#(yH=KQko>HF+c^F!k-u2k5eR%U5H3xEqjMY#9aTddGef&R>p0}s=A+jL zvUe}9QwpHW+Yf-y$xB|Yfep~UvAw-jeKn9Y=u8|f39~7PTipHmJW!zqRXEUX@BUgV z6S$BKe2+(NxqW=yKz#mVM3ST++ZeL>?3l}}`~isH6j9C<+T#J<_XOZ?qE3zrPSR9b z-K6a#iO(vINBPHVmk!m`?7F_e{j-pEPtbIx_nCB1Fn@&C}I*`^;XFQFC~3aBs5j(ixX4q7WAeRJgpJcxi8K+|Xz6 z?aAL9xL8c;deE1q%+X$zs}kxG z&YnBJy4xO_y<>Y_Ug)Wt4-k5oYhVuKf6aBu{^BcK)Bo`FlWT#ZROvOQ-GOK0WGe6| zSLW|Zw>Dn-9p6yF@;|?Wn(I{`(CQJ@*uJ;svr5K|;H2E0uP|t2sL-*wXnyQ~`g!9f zQ2pZJwrMka@2Ts3`GNJKuVJUVqh+7vHc)EnMo`%3&-1$xT=tmbtFU%9ekYT6^!DhO zQpt+vg@4O0hZd1i-;24!sp4~gmWJsYVTL`yz?{7shg?C=DS}0ulG%DEo5m9Rg_pAy z$DH=f_8dfzMNjGS;TzigBk9lAn$ah}#$pD=OS)ex3tjt7vnLz(nWxXyMrIWbkKN(9 zxjo$R6g88+ZD(ixQz1_Wc{`5*HDl`8jEMtdh8>0z)q`n`8xPq$&-PupE4Q0x0n+{| z%-JrT>s>!NAB0~H0tIh)d4=rYy2qabVtwylJ^@ds|(rBW9Y1IjSPcH|5|wdQp_cMpr>dw-RId(i7trq8X7L+$F!i&4&# z;8A+hj>VXAW;c=hO)`f!OoE6K< z;l%Y`X(|W9u>68z)2!!_&s%QG zrp=;$M~6^6+UnMI`O@x4g-YNZY51f3Le4ABAFi&3Uta3mOymr5zU#g!ffYxO>l;Sd zEh>!a%GF980oj93Bk4xOuCdJEZj@qUD)WIF_Imy|ht>|h+QTsdHTl)0+W^zyRYX<0 z3C+u58<tHsO2#B~Q|YISCY z)l$JtvVxM)l4+8|QG*@zn?0pP@p)e=e^kqovr`OE)_;S*5Q_6YyyL84Mh{kyS3{3X zG$JQAMZ(e~JW}L5KbvD@3nr&r$^>BSQL&Lp{z2i&^**^LI`=ykjm*Y`q^x{OLI99A zU4A;>FN;(F69*jv8Hua77&Km&O+a@V;sFL((g-GmHU^nS5Kjk>iusoOfNVn2EHz~? z*^VqUX10NBi^uh3nJ5A#We_xB~{CYV^XjEMcQ)ucV@i({BueOoo zZ05;Wl$x}ppNx#)KWywj_J0W(;D3zlzl01D{j(b;j6FpMIAR9=KFO{@0Lfp5yp@0( z%%37PNp1>IFPBvO{S_W{@7MS+^bhpgfr6iZpuVEtB1xbJVWJC!g-$}iCqlI|%~YC4 zM0_6LfwXMmWD_yPJMiaxn==MYD4kDh*SwA8!)kv6v=sWBqu zZ9eg7;XS~#q*kx#7|E&vuFuEmSjtgmJv`MdBDN@0Y5df+lq1>D_ua>yz3|`|p*D|~ z?##Ivs;^>hY4|c~Dx;zjUb&SgXIrE2YZ0FU>Ex=Z5C2Y2%5#IS-BONX?jSdw6xZLj zvEws75DR}#^5MID@P7Si>2EtHvGk~W_uNI8vC!E)6d%s6=d-BfI(c*5aPeZ$(AN{^ zRGMUWV7=h4GhpSb#m_H?NB39vZ#&C!Ge}JwCp^&%~)AE*U0=kL;Yk$*@>uhsQgZ z#RRWZfCPIO0%eZ7avcRTH+aWq8;e)Ru7%TU<`!$RbD14oB5kbO*Y<9NyZw{(pA04Y z`;X+Ak68?Vx^Z|{nZ-uL{CBkYJ;L)vc}V~Db4hLYF|zY74nK^_h;0G(}ekNmx%HS6H z6dZ|&NQGh;Z1pV45Fjmm8DE&CWcx}~aR{yA9&C$sg`_dE1(ltLf_$8tXn%&bo%|D_y3_CJ*4J|Xp`+Ru z`r=un^zs8h?$^*$a>2iG=lvJPZu#N))oJozqPqHlaHE0?u<#aG{M5s<>o#~dHB-61 zGK|Cg{GPt!_3n%3*CprJ_#2|?jRh_}UaIps8vGlO+e5=-qb|PcJql@Ya%J#s+Is(+ zTx?y36pUgJvaMDbEpQity3Ni9^80UCN4Gf*m|9i{{Os8v`;gDqO@$7CfQE$n-|FqZ zU;zPugu;MEhruL+#bU)4#lijdf7Wmikbotj{ydwsJO50&Kue*dkX`y44RDL5Ucr-A z^#urYihi$1^&;^)3$DSDPb37tp4^c1qQSCHT`!!h)31}C+CL$Hxr3e-PYj)8JTxC; zN!iN6vz8tVZ5#bJw+#P$Ewjz|N&**EED!F|)P6(+-{bhJ7^5g+6m2&dR%mL1HTA$X zS3mlpBRGl(dLI|}G|IKgH8nK@xOQo8cL!l%(KIUVYq-E*@Opse0+&ev&$>^Qb{2S^ z9z3tFF!}pbZqASGm^<27JguO zq-oow9YtTRX^mJcOO4x3L*9O6bP5Ka7%0Glt7aVZ46y#x40bI(g2C;HQq>mp^Ym`A zHl`*YfbDtuc}-kWauoS_g6M$&idP&>Lpn{{R9zbPMOog+=`R;$QnO=l9Yx%UJsuoTz!Qgq@&zmb(mBQXt z6}^Wi`v7!~Q?fVf)a%@78q!z1dVJmicxG7=9ert0v=1GfADoJ!8PEu>nAcPTOJTc9 zX&aG456o0rUm2EbsudeWI7LZn8`el+v{yIPLW!DMqk!kVs}?>lfdN)Bj-IDiwt>O+ z00PKpZOUWKh<5`#j6!WWvtD6YFHNzC5?q zvc_P-uBuq?J(m6mbZ_5Xu_pK$>Wiw227t%<)QfE3?v55rc1Vml1i878>X>R<6f2ky zr5Ua>GcBeDCJUyIqR_&VH_N7-euc}@+^43&ERRKg&F~Xvni;(e*i2^#ueGS|`FoWRpTRM;2!2MZ9S6nm(yM43A0@wWWIT(D%%@*c zjAh*?mXw7oBRd2@3pP>=Ukiz0ZOB_k{^&Z)gEG5=t5~|nrT(HNXJ6U3lRc^&{_pdA z4#hOGv5Jd~%+f3Kg`y@cBNL;Y*In+b7sK=`Ej23wr5Ia`*@hg=Wg{18|91AbiMvzi z^gwLXczMSZHLBA#Hz%~q7<0y;9J3jvNX~ofWa_{vOl?QCG6O1m-_E{pSort+IGaW+ zQenAd0*%0aLEuhaI@}C0a$htO*%#8Gt4yEzY_!2#eDVlKaMxzyF&_4? zX@nB`ew|mhq2J6ka%D@Wvt6&Tf&%Vo= z48)Wf%3=bPY2cvkQIBd_5?UuVUG0c@wP z&#R=c7FMrqk0X~)xgr#Vv59Shi|Cv!EzK-OAIz8f z=RHmqOBWR8WK~DflNJHfm$!!SAV6=-eOHvDsrJ<@5wj{6Ow4aKiqIUxQAP~-OcD+Y zd`U*>-Nv$XR$dQ5J~ODDsWh9O9xS>iqm7X|BT5w&MTj7GJ3y`BlC$oW#MMtj$S-n< z*Ql}`G~Xu&2Y2i8B9>SS-gU5`lEYQ@&5?szRky#??;J(2&&_Eo6DQ|NBecmj6vscg z6tEJp5kXv+mYIq{qfak>X}6HRXN$L*AMJ_XWBq43`J@j0<@JC#HiVloLHwQ$mzx`C z_2pDC=BZ6J9eZu=a?sez2MJZTh~3gkD(g%%d#70#4zI5V>+B_9i)a18e-ng4yC=}Y zmgz9Edt>$c12E>&Iw$##^0<_!IEUQn$TIk-5_|m`uO~%$R(qBo{0psb;CMa&UN>h?SOF?L z!}#MLfVGfa*6X6(8^62{fE4NQ2jG_J34PD`sqATuXu%9Ha>M5+k&R#e^8xa9Z1G;j2;n?mv8n4D_oGn4-4xbXtX$oC>Y~Z zoWKq{Fw~F`pZz;119=KRzAwOykJc26`Kk07Uj&XWaCnp6*L6D^@8{!XZ!%!J1A8lm z#%0d*&7VGFnzkgokRL`<1ARtE>IM`hEjZT4Z*l`q9{9yAp88d{et$Q2JaRwgInB1aXZa* zFHN99hQ2`MTdq`p?W^}}&wfp=mC&TkZfs`?N{ADc)?kNW^&2!cEm^)8?#DhwAe%&%X4Qo5Y=$6XUic+#sP{R_pt#k11DlY=g z|5%d8I5Y6ocdwJ!txC(p+}+G~8|ac;+8oE9s1-K!<@i|IFzCt%69v_}AZw;?`_bJU zFYaB`>y=%-OZxaYuGL|?Vj^P_rzXer93!@sjtZ9zt*O!GE8RqZL zJlnYvB*JkOzA9(%2`&17HPw~!BKI%EfWp;xApg|$mF~Cqsg@oOLW;qW0dvOiBf2S; zvy}@?F{LmrPa`XkXJyC0W0z6=`CE{&+1T$;R^H)jxmw4Io`5UM{XI|*obh^U0Fz<$ zo$>m4sz+=P@Aj5bI1;~7%s-HnrnW5k)J{9Y*=CQgmLRdg@tM&M$b1#1?Jbj%O?uUL z?Rwi64-Ry(+T%vGS#UmoczVd74Qgm6^X(R+RO9w+ms1!!KYv}g&<5F_pOND2Pi1Qv z*_6>CntipvR=@RM4~N=p30SW_zvyvXJbwdrbTt+5^@n2q8TZ??$r#yeaa`<>06iL( zYgf#yYifV58eo;4j+E*@rPusN*m34x{o+aeVEdMb=U+W7CRhT$gq5>0GVj&V(Xn39 z@er7(?F$yg432?T$XmJ4wml!J=+Fj%K(-g4!2D%eSU~Q}YxV0!^@X-)l~LmMD+&sX zPYkqr7bz&P^$l&`v;VJp;>@*Qp4rT@%+M#xmiQ0L20+2Uz(B*nK|_76q5j9RG0D-f zzOsqIkcq0AV6(ETxlo9I3&No^bqy}4gQen-_#DO9EvcR}x%dBYa3~SLs-8G)?v@nz zUTp^Iah^duftGZT!DP8~2N*xt^lJM7z^@q$o;TVdIBj?w?BE1`02Ug9;jW@rWZpCl z&zrNgMDS`W9P`3$&a2esEPEo4)*88&H6Q0~wR*yh+7|6Z6>pSoMBf?VIBkUZ_;_7u z@5DBv4QDZyOSj>AswXsYdLK98V#mJ``_XU}bu2b8*bPo!_I9m%!iG9t<*4*5_CyS{ zodvUBFYJqm7AGdPRW=Vo<@(hqHHWQSrLWJepKLPi9zSDy6p6veE#ypB%Ln)1Wqw-* zqQMHZ)Tct@Ja!gYd{q^`zh7*dSEC}u0p_IORA|;d+AW*l9yfJmc?EVg&}uh#!o1|8 zGZi#M>ddKbHQm;vm;tp zToxBjanwKAH`&o@PLKx5-x~!CuF&$u7JvBxG5)B)7dIKwmcYVg)oXJeA%H+$HiV?_ zM`9++Nk0vO@}Qr8WO&dWwf?m@C_6Kfn4oW8#OL5g=Ji!Mf-Sd<(`LeOqjX?lRpaN{ zuWMQ<-0Bu@mS#6^LS^43TXS{kh2~bSf`fmq(Sp;)QiJ!vhQkWcb~KDq@7=>+=JwXj{6H_!gkJkC7W2nWB+c_9RGSdoHH<7Hr8S$j!34%sZl*f zMkD#;tj@aZX+URpQCS(YnAMHZJf}CoR^i2ghJDZ+E-9Ujl;BKOSv?BOp+WL4H}@1O~>JNoUmCLp4p% z8@;HMjL!`ltj+mYS>FQ04awZ6!xIk1a6>|!0mC}%5clnBS+|V8boQza&LBUdapEKW zidhm&qY-GS<_h%SXdM44>~L~m#}KX3$;>Bi&t@St^!bVX&0nz8ZTapx9?IuuBwf=I zxVODdkxiIZ(Cci?nDRgZ#QD^Ccv4>V@pn>MR6>O*r1-+P<&{s{WUYJ)9R}sVI`xnH zu&v2tLn9r0S=LE>a3p5yE9(tx`wclYp7{%rTc?zBs(y^mOq6f`xpQ#-an)Plt&Q>GU z87p&_;r-?n;ww}s6N|WJ8gW zX;~>(Z~_O+Dl)5n7b>^#HE#irg`1c9xrEyL6h*rp zSj$L9Q8|(C8C;QtZ=j&0E~G9)_H)h8o+x+Gzz6A@GQk{@7+i*RQxE}eV`~d{ffzM6 zZIPP}$&5^)OTVK52Z<>ka6c}HKcK(2?ZWY`jR1{dqL@+A{K7GtqKJ~jW5UEJ%uRoU zXhZQy+x&}F7^p3=)dF9-z!*OyfCuu47~RXEqr)6%2NNr&i814B2xRb5NznYmecUw4 zH;eqE(QO^4NElpO+3l@)aWYZ17S&g?Yh-`1rh?ZuuYzq-X;I2|?&5%{McnEcOq@r% z+cG}OZa7P9?5<3PX@9Z~PyIx#-Pd3ciUAemGCwSg%n5R|_Nb6Cp`?!;9h?l5%4>4& zyQh3tq|bssutQ!GwQ*YY74UdV4fT7=MQKJPtVadIPDHG8ZIc-&gM`rg2TpH$uaX@7W>x3VTWp z?c1PouUOX;N=(^Nx@Da_5^MOxdcyUnOOolP+#vk25W=Sx^MUm)d7;F z_eG_#5pQcZnfy9to(67C*IlGsIi?h?J?6sKbsk#hfipBk*LDht5~V^0i~6jpNGlHy zsX}kpFD;u1QG!_mhshJjY-7tIJG|mB}A$%=n3%v~|<8BdxjXK}X8X*vz|xl+&x9G7~ls-M6;e z!1#R{84MYGW+i^mG|QLom?v|NnkN<8-*q{4Uk95D*@DvFbY&iJ@*V4;b0MJL*Sw^4 z8?|MxY(@b&ZhR7BAtf}s7k?z1X;CfLg_H1s@&oXkEi z;(U1&llsb8vf~w6dk;%~psywKglU4k>bte$2OzR%UJKoOt_Qq`@yZZ;@&VAi-~8Rs zz~-99BfGQ$PoWuQ#-5RZm>4&IuW*=st=hNzZENL;rxLA@&&6GuSIi8iZD6HD=zvEG zZ8VdpW|Se6ZGWcn)*^_R|Ye}y_5_~Uw0wAk71Pdd@#8hLEW^g0~B`r-BfrPKJA z+(BIUug^0dfK+ZJvdGeL$kL2v9&3PLuo8M@GfhEBCF@)1j6@XOqi- z`ngmnci6h#5c@FG5irP<{R8?8OGuxS>W(x9ZP8biL`_{rd&`fvD5!onp8Pc4} zBoJ~Nhd*@QJqHiq$$Ie~J{IS_+n%2}r<2yT^s`;SXM;4LRw3=B{xNY!>Xc`>vU58}hwn1B*c7Gh_k z0F z%jJ>#7LT7n4)EKPHguELewW;Su*u$<@eE1^2|g+&)#`=d`y3W$37ShyhY2$lEW?`L z%O02#A}t%aN7vmkz`=Z`!a6F@fWmfY5pJY}Uxol;1q#@1yGerG#VX+=gjoo;5+W-N zNs&G7w7<;C3l~tihYw^b^n6-IjK^Ffc6g5VYk&6XFj=5k^T?7+^sP3b&swKr4eVxE zBm}nw9HV4bb6wI7KMt(eA?Z`v`Z^TcGfYj$9`WWG6N(K?5&&UHESj}XX^=`v0kD!6 zsdB}QYC<}>Vql$N!-D~VCE04sI~k9f6sZEU|$5q1&w8`uBrDZpuX zO=ljwjMg6jjweR9XnmvMm7$-_?+T4Tzy|XyY~R2euCS+t@2Nqh9><;s+>K5PTMSnU zV*v3SF0V$|#2dU$#xVJ!0a4xk2JE-%+PcRG)7W$D@wM>rI`cs@bNk`ZEuuGV8z`}H zWWhs5i@Jfz=_f|_pT+rlK#!&fB0e!Be5hVyK&4*|)vW^@a(#0e{;r_gbJ;(K5HYxP zZSr|`Bx{1_a;n&6eVFz#Z@S2tV_7lxq^#$X)?7^S0L7~IYbrCOpOU|Tc?|9?sFn*1 zd+Ni)4n=8=^u{GnbSnElRWBj9P;#Rt0KX3Z{Hpt{@n*(=eXd$*vqvy65z*IIq*xET zl8q9XOSJq$g{g6W3qLZopX(vz2k{s0j0MHDwEHi+n+qUKHFUTQie$0ftZ^h4ST%hg zwP1bJ$F{AfPYuQVl$r#e+{Ay7=;g?V&NvSZ`ihh9>Y!j(9n>0>)Z&sv>rIzgK#;i0 zU*6YtBWnH+M-2j~HiAS%FxG7jnU^uA`vHj1*@|$E z4PnXG$)uX#+E`f+%gk!Q+tY7KoGJv22;qmhwA#JaMFx zFr{nCHq2L-D_x0|jEy$L5=r4Q+bqK(5Ni>aL%|^~UPD)c^!63wS0xQcxAOK9h!Ivd z+xQ}N8OcTKEL@4YRVk7)U=K(qV+zcyZ*MebVWrz)1#_sbyTUulb^oRsXt6~C;cRkn zheVC?JcCgYj%EdZp?MQWIgPkmWVb%G_Ue9_g0{vi`op5o^~v076$1kf^jqyl&xZ!ui3*T4fx@fB?S&ERwp8@ zAta+9Nl>d+x^wyuz1X~nW!f;?7g}EH-K~{YQQ{Kko}SB7Tn6NmyZUGsRlAZS%mZD9 zliGcRW%9TA>ah#EwYgTI3z7-(d7SbV@@Fk{1Mw)~bs^H5_LR+;CF=BqooQkeX#cox zL;=ue<9If`@lfM#^tBk9f}=0oxF=V9utY+yRl0Sx=qH3>Ebl56nq4hrquJt5A*N!>f^VRs@MbTl$`8Lkn+sD81jt3%M;MJEq z{_D-5yV|d9l606ca`uw^?mR&; zuVu#=ko zqRm*!N&Hua85m!Vdys|Tv_RG`jrccw5A3K3u(|<0IqDl!cIMx|`#=Vkt1uaR9%_#keerDU zMUl^yK{&C*2R>2#I?L zx>d$|AxJql+@7^?tbG!1Ymm{dCvm`<2+rYC*=-`X)KNT&f8P3^yJOB$nk`Ng0_~!S)?dRent`@_{QS!JU8c_ zQxLz~!n-60%Q6*g_fi>u095*T>f5;m5s+kvH}i|b0@(I(MW(rq$ zU%PO-CU8HMm4}*Mff-&@2kCceV5Oc|F1O$-8cibI`>es)II{_Jty2=m)+Ia1f{9mr zq7ypOss5s8EzK|+Zy#Zx0^1K1IC^!q>h8Z*2N<-N(=I{pL77;tJZIuDX@3`l5b>^- z=&E`ewcc$8rO)r2OKtrUlB}>uWL!NE|I&V6uzgKIwdqt=KSt~oI_v&xd^`HD)=SY6 z)7sZ2j(i*{C;H@x5zC+_zOlbv_y)MRO$dp|5gJr@>fJG6+jcfL01dZJ1u+rDa*J(r z2!mYT8pBO|K2}5t5>V}W{&#sw_Bsmxd(D{4ZVV=7@7U{$Icxv4kLHa|vo!M;(mM!+ z`|RNmu9w(r-6z(~G6@PdyC>R>D+ho-Vl;Pb!JXuI#osUq(JVa!%bXuPKtAK9Vr{9a7g7O!Jsa`9cV+vh3OjI5$bzj1Z>0HC+^9d`(z zt$dphbloM9`t%i6pgSAy%FR|^u1e9 zSXrOX@y)^0J+z~fBh;-H&oaz;>jvY7;~lY2{w4{2=u(YmHnB=MqO^6{MfoZ5DZ##q z8pP@`>QVfCv%`b2#=*^{UPFi|khPFvr>0ER^y+eJydN$L8tLz~${XbxRNYn6+0#IP z5|wHGP&@vE=LCVy@84p(?kWf)!;LGLbyZA%QO~ncN2TwN{`J@(&5try>jYHOqh*>9 zoO3T3_bxM5+57^NnESRX*HhL z3jK<9h=!3QIc+guv<4l?dMncv{-TcG@Z&Kn|s3BG~4SwS02pPxj3l(3zfbx+6 z?Cl)6YY_(8j=z-&bu~d-I|>_|vFgiL5Hja=g{lzL zl?4SI?|rx0X?|*Vvz#1k|KCg8En~elwG}JgISr;1p675~%WWGeC4F6$X(4fqDAyXI z$`oPciVK!o6f^5LYa0_pmeLJu6)yQqO5amsQ^!9`Y0?A*(E}oTV*`HRVN4ka1fjY6 zu}P+=BJ`JwjuQP7LY(G36p~3o5#{oE7&m#{N+ z7gaQSdD_0K03~GeIP7hW%pIYOEUfn;N&!gRmtkA{4~M7L*j5zZgYlcQrBh6Gib_B35QDV%JDL*)W|*pdB(?fenUpe zagA={hJtx!utFUH6sEu4KJg+eXgXYjLg%sD>#D^Hr)JymEDx$+TcT+8w=i0i8>UuO zRiRoj1N(YI(Y98HmCe4LdW1MJ*UYTDawH~=fs&)?;J4N5@iwFTq{3Q2&Ow6PmSI*_ zjE@~yGLmZ!m8om0m&1G!8pgtIMPbF^3JvRENdA~QM+lQfGIEZD`*h4T^^Bq^r8>5L z;TgmGaxs^~9L`@mQ>VhFECb(+~<{$sn6c2n=a&0*a1 z4sqfjG|CFB%<4!?%D=_P9_JRP>uc5AYd>86a^0nGsn{8~wAZm~JW;Ey{0lR%pmVAu zG;6mDGXrSv+ht%ib43bRB8xxh{mZP`bQC6+@?a28rAl{WKNlW$SI%&doL@F0ys~)? z<4$A$woyWAv~FNt`jmD@gyKz26b3vEHIbdpX&_E%xYA@Eh|R+iBWB zc;U_o5|CRv2Z>MDLD~{7p_dCNDFCjfc~==pb?z6QXcr@acmS`>p=}>~7-rAvo=)1) zZQW!7mol^Z!*pcLGvR|Vg`Ui)sulYuYBTLRM*dVK*YTzD zr1^86Qn$tz(X0cvZ`7s_{R?SlMwNYWchxlxwFrfte>#5$Cwl#@vL$M0t{c)w`C}*1 zUrlI#);6?l(R<>y?d$eg_#V;SGCSAQ{Q=mwylZ3EZrMt4<=)!YMpd|OPZc0NZWOYx zUXY z9oNNe=cU$b@qQj}tT|g=raFF%WW!dH<=25504c;*s{B=S&t8f1^5UW|*_zY$Uqt7~yGQ zR}Qp$otkUjlq{*+IoQiy6{3HzwNiV2vDGEeu@G4cJi#hqwRO~o)vJz&8a_;B7~ux( zBFH;oy&W92X6beDrVX#B8jR!4p9o3y%(tw%fOOS!daNgIXs?)WNU{%B`B|{Y1nY+l z5W-RPea}<2&V=Eic{4h)b4oTdGxgJ!p%*exm+HJj`Wx`#vgf$HS=5N}O?fW@zoC$< zzab^yZJ>2k7OlQT%W5us(QMtG7wLvb%IsEtT8KSq*`=kB}kRuv2Z zyS2^-?uiCxBm1F2iAe*(97CS$e{fTV&(~_~tBX#$oBmIPo4H+$qE?Een6763@Fu=d zr)JG?yYcy5dsF&x88OLq)pD(;)s?plwAy83{dPo)xMW^d-;07aXehqkgz@J?OSF>$Stbnu|^L~}DBr)Up_l>-1RS15yI|7}$vrE#HL%F#ZH09>(jlivR78VTLghuHI zLSwIQ)tlM^4vyRhEG-Oqg&JVF`Gq-URTKg+7oom&bd7fKz6k1M3(?;DS zt@0g9-DbOU?6T?~wp~kO#BXc!6%MbgG3DkW5yIT>qc=uCf z&gMa+FInMG{!&ISp4_y5+qu=LCZf*x&&)7%Y4xT~S8?SzW_MB=Jt)U~m3zW1W@0j{_n(4Ahyk^(S2IIBAvZBev*A@+D)(TU@*Q+XKrJl)L_46kQHkdn^LG_sh>$~H=Wfp z)PFqEb#6qJ?o`<=W3UxKlE#1K=zjUcGOw=p0q7s-b6=|e#0l3AfOVzoY)*(vi|$gM zFfS-@nMhKY@jjz5R-83fnI`DeFAJ-Nj@?`TQ!u3UMOBYw*!?2g{k(g06f zU*OmxUu$ZFm2?~Y5Mar5Qr?r9-h~fDD`zOGLrRdmxnnZwXZrvsh?MumvfQ2pSo)9- zlm30~LYc|*Sg0k{a*?hx5W)JPXIw!W=ri^K7!Yx73Ke%xAakAJ^j7=|N(Iku9-DDx z9~Hf1^1SA~SuLQQXz3phzf(6VGZnUwUPbD5-FXupkKq^*mk-bBFe(>oCt zs9f~2yn$XaIr4@*Usb@n$0d=o7$}PK$3tcYjgusKTP1HETyT!@H`qi+L?TOh>Z$|^9R$+db_Zf0JvM&^bpc7Q}* z>fM=V29Blod)L)l^<^rt>`L!wQ}SNqe?5g;ikn9>!Qn7DrnQig||`jSjZ@%>mRxx*~v1aj)OOMt+g*JnQ!VURHN<#<8;xX=o!@b h@|a9YE%!NHS%addFlash('success', 'Double authentification activée.'); - $entityManager->update($account)->flush()->clear(); + $entityManager->update($account); return $this->redirectToRoute('admin_account'); } @@ -78,7 +78,7 @@ class AccountAdminController extends AdminController if (!$enable && $account->isTotpAuthenticationEnabled()) { $account->setTotpSecret(null); - $entityManager->update($account)->flush()->clear(); + $entityManager->update($account); $this->addFlash('success', 'Double authentification désactivée.'); @@ -100,7 +100,8 @@ class AccountAdminController extends AdminController Request $request, UserRepository $repository, TokenGeneratorInterface $tokenGenerator, - UserPasswordEncoderInterface $encoder + UserPasswordEncoderInterface $encoder, + EntityManager $entityManager ): Response { $account = $this->getUser(); $csrfToken = $request->request->get('_csrf_token'); @@ -129,10 +130,7 @@ class AccountAdminController extends AdminController ->setConfirmationToken($tokenGenerator->generateToken()) ; - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($account); - $entityManager->flush(); - $entityManager->clear(); + $entityManager->update($account); $this->addFlash('success', 'Mot de passe modifié !'); diff --git a/src/Controller/Auth/AuthController.php b/src/Controller/Auth/AuthController.php index 8d98b91..ce4ec79 100644 --- a/src/Controller/Auth/AuthController.php +++ b/src/Controller/Auth/AuthController.php @@ -71,7 +71,7 @@ class AuthController extends AbstractController $account->setConfirmationToken($tokenGenerator->generateToken()); $account->setPasswordRequestedAt(new \DateTime('now')); - $entityManager->update($account)->flush()->clear(); + $entityManager->update($account); $eventDispatcher->dispatch(new PasswordRequestEvent($account), PasswordRequestEvent::EVENT); $emailSent = true; @@ -135,7 +135,7 @@ class AuthController extends AbstractController ->setPasswordRequestedAt(new \DateTime('now')) ; - $entityManager->update($account)->flush()->clear(); + $entityManager->update($account); $passwordUpdated = true; } diff --git a/src/Controller/Blog/CategoryAdminController.php b/src/Controller/Blog/CategoryAdminController.php index d9fc9bd..4d81499 100644 --- a/src/Controller/Blog/CategoryAdminController.php +++ b/src/Controller/Blog/CategoryAdminController.php @@ -42,7 +42,7 @@ class CategoryAdminController extends AdminController $form->handleRequest($request); if ($form->isValid()) { - $entityManager->create($entity)->flush()->clear(); + $entityManager->create($entity); $this->addFlash('success', 'Donnée enregistrée.'); return $this->redirectToRoute('admin_blog_category_edit', [ @@ -54,6 +54,7 @@ class CategoryAdminController extends AdminController return $this->render('blog/category_admin/new.html.twig', [ 'form' => $form->createView(), + 'entity' => $entity, ]); } @@ -68,7 +69,7 @@ class CategoryAdminController extends AdminController $form->handleRequest($request); if ($form->isValid()) { - $entityManager->update($entity)->flush()->clear(); + $entityManager->update($entity); $this->addFlash('success', 'Donnée enregistrée.'); return $this->redirectToRoute('admin_blog_category_edit', [ @@ -107,7 +108,7 @@ class CategoryAdminController extends AdminController public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response { if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) { - $entityManager->delete($entity)->flush()->clear(); + $entityManager->delete($entity); $this->addFlash('success', 'Données supprimées.'); } diff --git a/src/Controller/Blog/PostAdminController.php b/src/Controller/Blog/PostAdminController.php index c07e7bf..677be20 100644 --- a/src/Controller/Blog/PostAdminController.php +++ b/src/Controller/Blog/PostAdminController.php @@ -3,6 +3,14 @@ namespace App\Controller\Blog; use App\Controller\Admin\AdminController; +use App\Entity\Blog\Post as Entity; +use App\Factory\Blog\PostFactory as EntityFactory; +use App\Form\Blog\PostType as EntityType; +use App\Manager\EntityManager; +use App\Repository\Blog\PostRepositoryQuery; +use App\Repository\Blog\PostRepositoryQuery as RepositoryQuery; +use App\Form\FileUploadHandler; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -12,14 +20,104 @@ use Symfony\Component\Routing\Annotation\Route; class PostAdminController extends AdminController { /** - * @Route("/", name="admin_blog_post_index") + * @Route("/{page}", name="admin_blog_post_index", requirements={"page": "\d+"}) */ - public function index(): Response + public function index(int $page = 1, RepositoryQuery $query, Request $request): Response { + $pager = $query->paginate($page); + return $this->render('blog/post_admin/index.html.twig', [ + 'pager' => $pager, ]); } + /** + * @Route("/new", name="admin_blog_post_new") + */ + public function new(EntityFactory $factory, EntityManager $entityManager, Request $request): Response + { + $entity = $factory->create($this->getUser()); + $form = $this->createForm(EntityType::class, $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $entityManager->create($entity); + $this->addFlash('success', 'Donnée enregistrée.'); + + return $this->redirectToRoute('admin_blog_post_edit', [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'Le formulaire est invalide.'); + } + + return $this->render('blog/post_admin/new.html.twig', [ + 'form' => $form->createView(), + 'entity' => $entity, + ]); + } + + /** + * @Route("/edit/{entity}", name="admin_blog_post_edit") + */ + public function edit(Entity $entity, EntityManager $entityManager, FileUploadHandler $fileUpload, Request $request): Response + { + $form = $this->createForm(EntityType::class, $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $fileUpload->handleForm( + $form->get('image')->getData(), + 'uploads/post/'.date('Y'), + function ($filename) use ($entity) { + $entity->setImage('post/'.date('Y').'/'.$filename); + } + ); + + $entityManager->update($entity); + $this->addFlash('success', 'Donnée enregistrée.'); + + return $this->redirectToRoute('admin_blog_post_edit', [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'Le formulaire est invalide.'); + } + + return $this->render('blog/post_admin/edit.html.twig', [ + 'form' => $form->createView(), + 'entity' => $entity, + ]); + } + + /** + * @Route("/show/{entity}", name="admin_blog_post_show") + */ + public function show(Entity $entity, PostRepositoryQuery $postQuery): Response + { + return $this->render('blog/post_admin/show.html.twig', [ + 'entity' => $entity, + ]); + } + + /** + * @Route("/delete/{entity}", name="admin_blog_post_delete", methods={"DELETE"}) + */ + public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response + { + if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) { + $entityManager->delete($entity); + + $this->addFlash('success', 'Données supprimées.'); + } + + return $this->redirectToRoute('admin_blog_post_index'); + } + public function getSection(): string { return 'blog_post'; diff --git a/src/Entity/Blog/Category.php b/src/Entity/Blog/Category.php index 4324f31..a2ab1ff 100644 --- a/src/Entity/Blog/Category.php +++ b/src/Entity/Blog/Category.php @@ -10,6 +10,7 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass=CategoryRepository::class) + * @ORM\HasLifecycleCallbacks */ class Category implements EntityInterface { diff --git a/src/Entity/Blog/Post.php b/src/Entity/Blog/Post.php index 27d463d..8daafd2 100644 --- a/src/Entity/Blog/Post.php +++ b/src/Entity/Blog/Post.php @@ -12,6 +12,7 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass=PostRepository::class) + * @ORM\HasLifecycleCallbacks */ class Post implements EntityInterface { @@ -153,8 +154,12 @@ class Post implements EntityInterface return $this->image; } - public function setImage(?string $image): self + public function setImage(?string $image, bool $force = false): self { + if (false === $force && null === $image) { + return $this; + } + $this->image = $image; return $this; diff --git a/src/Entity/User.php b/src/Entity/User.php index 8d03289..1afabed 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -9,11 +9,11 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; -use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration; use Symfony\Component\Security\Core\User\UserInterface; /** * @ORM\Entity(repositoryClass=UserRepository::class) + * @ORM\HasLifecycleCallbacks * @ORM\Table(name="`user`") */ class User implements UserInterface, TwoFactorInterface, EntityInterface @@ -191,16 +191,6 @@ class User implements UserInterface, TwoFactorInterface, EntityInterface return null !== $this->getTotpSecret(); } - public function getTotpAuthenticationUsername(): string - { - return $this->getEmail(); - } - - public function getTotpAuthenticationConfiguration(): TotpConfigurationInterface - { - return new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, 30, 6); - } - public function isGoogleAuthenticatorEnabled(): bool { return $this->isTotpAuthenticationEnabled(); diff --git a/src/EventSuscriber/BlogPostEventSubscriber.php b/src/EventSuscriber/BlogPostEventSubscriber.php new file mode 100644 index 0000000..297f433 --- /dev/null +++ b/src/EventSuscriber/BlogPostEventSubscriber.php @@ -0,0 +1,78 @@ + + */ +class BlogPostEventSubscriber implements EventSubscriberInterface +{ + protected Filesystem $filesystem; + protected PostRepositoryQuery $query; + + public function __construct(Filesystem $filesystem, PostRepositoryQuery $query) + { + $this->filesystem = $filesystem; + $this->query = $query; + } + + public static function getSubscribedEvents() + { + return [ + EntityManagerEvent::UPDATE_EVENT => 'onUpdate', + EntityManagerEvent::DELETE_EVENT => 'onDelete', + ]; + } + + public function support(EntityInterface $entity) + { + return $entity instanceof Post; + } + + public function onUpdate(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + $this->removeOrphanUploads(); + } + + public function onDelete(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + $this->removeOrphanUploads(); + } + + protected function removeOrphanUploads() + { + $finder = new Finder(); + $finder->files()->in('uploads/post'); + + foreach ($finder as $file) { + $image = str_replace('uploads/', '', $file->getPathname()); + + $post = $this->query->create() + ->where('.image = :image') + ->setParameter(':image', $image) + ->findOne(); + + if (null === $post) { + $this->filesystem->remove($file->getRealPath()); + } + } + } +} diff --git a/src/EventSuscriber/EntityManagerEventSubscriber.php b/src/EventSuscriber/EntityManagerEventSubscriber.php index 6044a30..ef819f1 100644 --- a/src/EventSuscriber/EntityManagerEventSubscriber.php +++ b/src/EventSuscriber/EntityManagerEventSubscriber.php @@ -6,7 +6,7 @@ use App\Event\EntityManager\EntityManagerEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** - * class EventListener. + * class EntityManagerEventSubscriber. * * @author Simon Vieille */ diff --git a/src/Factory/Blog/PostFactory.php b/src/Factory/Blog/PostFactory.php new file mode 100644 index 0000000..d1e31ca --- /dev/null +++ b/src/Factory/Blog/PostFactory.php @@ -0,0 +1,25 @@ + + */ +class PostFactory +{ + public function create(?User $author = null): Post + { + $entity = new Post(); + + $entity + ->setAuthor($author) + ->setStatus(0); + + return $entity; + } +} diff --git a/src/Form/Blog/PostType.php b/src/Form/Blog/PostType.php new file mode 100644 index 0000000..a788d7e --- /dev/null +++ b/src/Form/Blog/PostType.php @@ -0,0 +1,208 @@ +add( + 'title', + TextType::class, + [ + 'label' => 'Titre', + 'required' => true, + 'attr' => [ + ], + 'constraints' => [ + new NotBlank(), + ], + ] + ); + + $builder->add( + 'subTitle', + TextareaType::class, + [ + 'label' => 'Sous-titre', + 'required' => false, + 'attr' => [ + 'rows' => 5, + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'metaDescription', + TextType::class, + [ + 'label' => 'Meta description', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'content', + TextareaType::class, + [ + 'label' => 'Contenu', + 'required' => false, + 'attr' => [ + 'data-tinymce' => '', + 'rows' => 20, + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'slug', + TextType::class, + [ + 'label' => 'Slug', + 'required' => false, + 'help' => 'Laisser vide pour une génération automatique', + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'categories', + EntityType::class, + [ + 'label' => 'Catégories', + 'class' => Category::class, + 'choice_label' => 'title', + 'required' => false, + 'multiple' => true, + 'attr' => [ + 'data-jschoice' => '', + ], + 'query_builder' => function (EntityRepository $repo) { + return $repo->createQueryBuilder('a') + ->orderBy('a.title', 'ASC') + ; + }, + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'status', + ChoiceType::class, + [ + 'label' => 'Statut', + 'required' => true, + 'choices' => [ + 'Brouillon' => 0, + 'Publié' => 1, + ], + 'attr' => [ + ], + 'constraints' => [ + new NotBlank(), + ], + ] + ); + + $builder->add( + 'imageCaption', + TextType::class, + [ + 'label' => 'Titre de l\'image', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'publishedAt', + DateType::class, + [ + 'label' => 'Date de publication', + 'required' => false, + 'html5' => true, + 'widget' => 'single_text', + 'attr' => [ + ], + 'constraints' => [ + new Date(), + ], + ] + ); + + $builder->add( + 'author', + EntityType::class, + [ + 'label' => 'Auteur', + 'class' => User::class, + 'choice_label' => 'displayName', + 'required' => true, + 'attr' => [ + ], + 'query_builder' => function (EntityRepository $repo) { + return $repo->createQueryBuilder('u') + ->orderBy('u.displayName', 'ASC') + ; + }, + 'constraints' => [ + new NotBlank(), + ], + ] + ); + + $builder->add( + 'image', + FileType::class, + [ + 'label' => 'Image', + 'required' => false, + 'data_class' => null, + 'attr' => [ + ], + 'constraints' => [ + new Image(), + ], + ] + ); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Post::class, + ]); + } +} diff --git a/src/Form/FileUploadHandler.php b/src/Form/FileUploadHandler.php new file mode 100644 index 0000000..940ea28 --- /dev/null +++ b/src/Form/FileUploadHandler.php @@ -0,0 +1,28 @@ + + */ +class FileUploadHandler +{ + public function handleForm(?UploadedFile $uploadedFile, string $path, callable $afterUploadCallback): void + { + if (null === $uploadedFile) { + return; + } + + $originalFilename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME); + $safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename); + $filename = date('Ymd-his').$safeFilename.'.'.$uploadedFile->guessExtension(); + + $uploadedFile->move($path, $filename); + + $afterUploadCallback($filename); + } +} diff --git a/src/Manager/EntityManager.php b/src/Manager/EntityManager.php index a841dbd..8216aab 100644 --- a/src/Manager/EntityManager.php +++ b/src/Manager/EntityManager.php @@ -44,6 +44,8 @@ class EntityManager public function delete(EntityInterface $entity): self { $this->entityManager->remove($entity); + $this->flush(); + $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT); return $this; @@ -66,5 +68,6 @@ class EntityManager protected function persist(EntityInterface $entity) { $this->entityManager->persist($entity); + $this->flush(); } } diff --git a/templates/blog/category_admin/edit.html.twig b/templates/blog/category_admin/edit.html.twig index 503b8ea..a469cfa 100644 --- a/templates/blog/category_admin/edit.html.twig +++ b/templates/blog/category_admin/edit.html.twig @@ -3,8 +3,8 @@ {% block body %}
-
-

{{ entity.title }}

+
+

{{ entity.title }}

@@ -38,7 +38,7 @@
-
+
diff --git a/templates/blog/category_admin/index.html.twig b/templates/blog/category_admin/index.html.twig index 27b9c49..1f6b0a3 100644 --- a/templates/blog/category_admin/index.html.twig +++ b/templates/blog/category_admin/index.html.twig @@ -1,10 +1,10 @@ {% extends 'admin/layout.html.twig' %} {% block body %} -
+
-
-

Catégories

+
+

Catégories

@@ -35,7 +35,7 @@ - + {{ item.title }} diff --git a/templates/blog/category_admin/new.html.twig b/templates/blog/category_admin/new.html.twig index c8eb21b..682b25b 100644 --- a/templates/blog/category_admin/new.html.twig +++ b/templates/blog/category_admin/new.html.twig @@ -3,8 +3,8 @@ {% block body %}
-
-

Nouvelle catégorie

+
+

Nouvelle catégorie

@@ -25,7 +25,7 @@
- +
diff --git a/templates/blog/category_admin/show.html.twig b/templates/blog/category_admin/show.html.twig index c7e3779..c80ad38 100644 --- a/templates/blog/category_admin/show.html.twig +++ b/templates/blog/category_admin/show.html.twig @@ -3,8 +3,8 @@ {% block body %}
-
-

{{ entity.title }}

+
+

{{ entity.title }}

@@ -28,17 +28,17 @@
  • - Titre
    + Titre {{ entity.title }}
  • - Sous-titre
    + Sous-titre {{ entity.subTitle }}
  • - URL
    + URL {{ absolute_url('/' ~ entity.slug) }}
  • @@ -61,7 +61,9 @@ {% for item in posts %} - {{ item.post }} + + {{ item.title }} + {% else %} diff --git a/templates/blog/post_admin/_form.html.twig b/templates/blog/post_admin/_form.html.twig new file mode 100644 index 0000000..684bf52 --- /dev/null +++ b/templates/blog/post_admin/_form.html.twig @@ -0,0 +1,36 @@ +
    +
    +
    + {% for item in ['title', 'subTitle', 'categories', 'metaDescription', 'slug'] %} +
    + {{ form_row(form[item]) }} +
    + {% endfor %} +
    +
    +
    +
    + {% for item in ['content'] %} +
    + {{ form_row(form[item]) }} +
    + {% endfor %} +
    +
    +
    +
    + {% for item in ['image', 'imageCaption', 'status', 'publishedAt', 'author'] %} +
    + {{ form_row(form[item]) }} + + {% if item == 'image' %} + {% if entity.image %} + + {% endif %} + {% endif %} +
    + {% endfor %} +
    +
    +
    + diff --git a/templates/blog/post_admin/edit.html.twig b/templates/blog/post_admin/edit.html.twig new file mode 100644 index 0000000..d2d4d4d --- /dev/null +++ b/templates/blog/post_admin/edit.html.twig @@ -0,0 +1,57 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    {{ entity.title }}

    +
    + +
    +
    + + + Retour à la liste + + + + Voir + + + + + + +
    +
    +
    +
    + + +
    +
    +
    + {{ include('blog/post_admin/_form.html.twig') }} +
    +
    +
    + + {{ form_rest(form) }} + + +
    + + +
    +{% endblock %} diff --git a/templates/blog/post_admin/index.html.twig b/templates/blog/post_admin/index.html.twig index 10f369e..23e0506 100644 --- a/templates/blog/post_admin/index.html.twig +++ b/templates/blog/post_admin/index.html.twig @@ -1,32 +1,23 @@ {% extends 'admin/layout.html.twig' %} {% block body %} -
    +
    -
    -

    Articles

    +
    +

    Articles

    - +
    -
    - -
    + {{ knp_pagination_render(pager) }}
    @@ -37,37 +28,76 @@ - - - {% for item in range(1, 20) %} - - + {% for item in pager %} + {% set edit = path('admin_blog_post_edit', {entity: item.id}) %} + {% set show = path('admin_blog_post_show', {entity: item.id}) %} - Titre de l'article {{ item }}
    - Dans Nom de la catégorie par Mark - - - - - - {% endfor %} + + + + + + + {% else %} + + + + {% endfor %}
    Statut Actions
    - + +
    - - - 27/03/2021 09:10 - - - - - - -
    + {% if item.image %} + {% set image = asset('uploads/' ~ item.image) %} + {% else %} + {% set image = asset('build/images/no-image.png') %} + {% endif %} + + + + + {{ item.title }} + + + {% set categories = [] %} + + {% for category in item.categories %} + {% set url = path('admin_blog_category_show', {entity: category.id}) %} + {% set categories = categories|merge(['' ~ category.title ~ '']) %} + {% endfor %} + + Dans {{ categories|join(', ')|raw }} par {{ item.author.displayName }} + + + + {{ item.updatedAt|date('d/m/Y H:i') }} + + + {% set map = { + 0: ['warning', 'Brouillon'], + 1: ['success', 'Publié'], + } %} + + + + + + + +
    + + +
    +
    +
    + +
    +
    + Aucun résultat +
    +
    {% endblock %} diff --git a/templates/blog/post_admin/new.html.twig b/templates/blog/post_admin/new.html.twig new file mode 100644 index 0000000..dd684d1 --- /dev/null +++ b/templates/blog/post_admin/new.html.twig @@ -0,0 +1,39 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    Nouvel article

    +
    + +
    +
    + + + + Retour à la liste + + + +
    +
    +
    +
    + +
    +
    +
    +
    + {{ include('blog/post_admin/_form.html.twig') }} +
    +
    +
    + + {{ form_rest(form) }} +
    +{% endblock %} diff --git a/templates/blog/post_admin/show.html.twig b/templates/blog/post_admin/show.html.twig new file mode 100644 index 0000000..733e00f --- /dev/null +++ b/templates/blog/post_admin/show.html.twig @@ -0,0 +1,106 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    {{ entity.title }}

    +
    + + +
    +
    + +
    +
    +
      +
    • + Titre + + {{ entity.title }} +
    • +
    • + Sous-titre + + {{ entity.subTitle }} +
    • +
    • + Catégories + + {% for category in entity.categories %} + {{ category.title }} + {% endfor %} +
    • +
    • + URL + + {{ absolute_url('/' ~ entity.slug) }} +
    • +
    • + Meta description + + {{ entity.metaDescription }} +
    • +
    +
    +
    +
    Contenu
    + + {{ entity.content|raw|nl2br }} +
    +
    +
      +
    • + Image + + {% if entity.image %} +
      + {{ entity.imageCaption }} + +
      + {{ entity.imageCaption }} +
      +
      + {% else %} + - + {% endif %} +
    • + +
    • + Statut + + {% if entity.status == 0 %} + Brouillon + {% else %} + Publié + {% endif %} +
    • +
    • + Date de publication + + {{ entity.publishedAt ? entity.publishedAt|date('d/m/Y H:i') : '-' }} +
    • +
    • + Auteur + + + {{ entity.author.displayName }} + +
    • +
    +
    +
    +{% endblock %}