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

HOME


Mini Shell 1.0
DIR:/home/htlwork.com/www/dev/pricelow/vendor/firebear/importexport/Model/Export/
Upload File :
Current File : /home/htlwork.com/www/dev/pricelow/vendor/firebear/importexport/Model/Export/Product.php
<?php
/**
 * @copyright: Copyright © 2017 Firebear Studio. All rights reserved.
 * @author   : Firebear Studio <fbeardev@gmail.com>
 */

namespace Firebear\ImportExport\Model\Export;

use DateTime;
use Exception;
use Firebear\ImportExport\Model\Export\Product\Additional;
use Firebear\ImportExport\Model\Export\RowCustomizer\ProductVideo;
use Firebear\ImportExport\Model\ExportJob\Processor;
use Firebear\ImportExport\Traits\Export\Entity as ExportTrait;
use Firebear\ImportExport\Api\Data\SeparatorFormatterInterface;
use IntlDateFormatter;
use Magento\Catalog\Model\Product\LinkTypeProvider;
use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory;
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
use Magento\Catalog\Model\ResourceModel\ProductFactory;
use Magento\CatalogImportExport\Model\Export\Product\Type\Factory;
use Magento\CatalogImportExport\Model\Export\RowCustomizerInterface;
use Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
use Magento\CatalogImportExport\Model\Import\Product\CategoryProcessor;
use Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory;
use Magento\Eav\Model\Config;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
use Magento\Framework\App\CacheInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Data\Collection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Module\Manager;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\ImportExport\Model\Export;
use Magento\ImportExport\Model\Export\ConfigInterface;
use Magento\ImportExport\Model\Import;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Swatches\Helper\Data;
use Magento\Swatches\Model\ResourceModel\Swatch\CollectionFactory as SwatchCollectionFactory;
use Magestore\InventorySuccess\Api\Warehouse\WarehouseStockRegistryInterface;
use Firebear\ImportExport\Api\Export\History\GetListInterface;
use Psr\Log\LoggerInterface;
use Zend_Db_Statement_Exception;
use function array_keys;
use function array_merge;
use Firebear\ImportExport\Model\Export\Product\FieldsPool\Msi;
use Magento\Catalog\Model\Product\Attribute\Repository as AttributeRepository;

/**
 * Class Product
 *
 * @package Firebear\ImportExport\Model\Export
 */
class Product extends \Magento\CatalogImportExport\Model\Export\Product implements EntityInterface
{
    use ExportTrait;

    const CACHE_TAG = 'config_scopes';
    const COL_CATEGORY_IDS = 'category_ids';
    const COLUMN_ID = 'opt_id';
    const COLUMN_ROW_ID = 'opt_row_id';
    const COL_CATEGORY_BY_STORE = '_category_store_name';

    /**
     * @var array
     */
    protected $attributeStoreValues = [];

    protected $headColumns;

    protected $additional;
    /**
     * @var SwatchCollectionFactory
     */
    protected $swatchCollectionFactory;
    /**
     * @var Data
     */
    protected $swatchesHelperData;

    private $userDefinedAttributes = [];

    protected $keysAdditional;

    /** @var Manager */
    protected $moduleManager;

    /** @var string */
    protected $multipleValueSeparator;

    /**
     * Product media gallery cache
     *
     * @var array[]
     */
    protected $mediaGalleryCache = [];

    /**
     * @var CacheInterface
     */
    protected $cache;

    /** @var array */
    private $cachedSwatchOptions = [];

    /**
     * Total entities limit to be fetched during export job. NULL to disable
     *
     * @var null|int
     */
    private $totalEntitiesLimit = null;

    /**
     * @var array|null
     */
    private $stores = null;

    /**
     * @var array
     */
    private $isLastPageExported = [];

    /**
     * Json Serializer
     *
     * @var SerializerInterface
     */
    protected $serializer;

    /**
     * @var Product\AdditionalFieldsPool
     */
    protected $additionalFieldsPool;

    /**
     * Attribute labels
     *
     * @var array
     */
    protected $attributeLabels = [];

    /**
     * Tier Price Attributes
     *
     * @var array
     */
    protected $tierPriceAttributes = [
        'tier_prices:fixed',
        'tier_prices:quantity',
        'tier_prices:discount'
    ];

    /**
     * @var SeparatorFormatterInterface
     */
    private $separatorFormatter;

    /**
     * @var GetListInterface
     */
    protected $historyList;

    /**
     * @var AttributeRepository
     */
    protected $attributeRepository;

    /**
     * Categories ID to text-path hash by store.
     *
     * @var array
     */
    protected $_categoriesByStore = [];

    /**
     * Product constructor.
     * @param TimezoneInterface $localeDate
     * @param Config $config
     * @param ResourceConnection $resource
     * @param StoreManagerInterface $storeManager
     * @param LoggerInterface $logger
     * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory
     * @param ConfigInterface $exportConfig
     * @param ProductFactory $productFactory
     * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFactory
     * @param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryColFactory
     * @param ItemFactory $itemFactory
     * @param \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory $optionColFactory
     * @param CollectionFactory $attributeColFactory
     * @param Factory $_typeFactory
     * @param LinkTypeProvider $linkTypeProvider
     * @param RowCustomizerInterface $rowCustomizer
     * @param Additional $additional
     * @param Manager $moduleManager
     * @param Data $swatchesHelperData
     * @param SwatchCollectionFactory $swatchCollectionFactory
     * @param CacheInterface $cache
     * @param SerializerInterface $serializer
     * @param Product\AdditionalFieldsPool $additionalFieldsPool
     * @param SeparatorFormatterInterface $separatorFormatter
     * @param GetListInterface $historyList
     * @param AttributeRepository $attributeRepository
     * @param array $dateAttrCodes
     * @throws LocalizedException
     */
    public function __construct(
        TimezoneInterface $localeDate,
        Config $config,
        ResourceConnection $resource,
        StoreManagerInterface $storeManager,
        LoggerInterface $logger,
        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory,
        ConfigInterface $exportConfig,
        ProductFactory $productFactory,
        \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFactory,
        \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryColFactory,
        ItemFactory $itemFactory,
        \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory $optionColFactory,
        CollectionFactory $attributeColFactory,
        Factory $_typeFactory,
        LinkTypeProvider $linkTypeProvider,
        RowCustomizerInterface $rowCustomizer,
        Product\Additional $additional,
        Manager $moduleManager,
        Data $swatchesHelperData,
        SwatchCollectionFactory $swatchCollectionFactory,
        CacheInterface $cache,
        SerializerInterface $serializer,
        Product\AdditionalFieldsPool $additionalFieldsPool,
        SeparatorFormatterInterface $separatorFormatter,
        GetListInterface $historyList,
        AttributeRepository $attributeRepository,
        array $dateAttrCodes = []
    ) {
        $this->swatchCollectionFactory = $swatchCollectionFactory;
        $this->swatchesHelperData = $swatchesHelperData;
        $this->_fieldsMap += [
            self::COL_CATEGORY_BY_STORE => $this->_fieldsMap[self::COL_CATEGORY] . '_store_name',
            self::COL_CATEGORY . '_position' => $this->_fieldsMap[self::COL_CATEGORY] . '_position',
            'swatch_label' => 'swatch_image_label'
        ];
        $this->additionalFieldsPool = $additionalFieldsPool;
        $this->separatorFormatter = $separatorFormatter;

        parent::__construct(
            $localeDate,
            $config,
            $resource,
            $storeManager,
            $logger,
            $collectionFactory,
            $exportConfig,
            $productFactory,
            $attrSetColFactory,
            $categoryColFactory,
            $itemFactory,
            $optionColFactory,
            $attributeColFactory,
            $_typeFactory,
            $linkTypeProvider,
            $rowCustomizer,
            $dateAttrCodes
        );

        $this->additional = $additional;
        $this->moduleManager = $moduleManager;
        $this->multipleValueSeparator = Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
        $this->cache = $cache;
        $this->serializer = $serializer;
        $this->historyList = $historyList;
        $this->attributeRepository = $attributeRepository;
    }

    /**
     * Initialize categories ID to text-path hash.
     *
     * @return \Magento\CatalogImportExport\Model\Export\Product
     */
    protected function initCategories()
    {
        foreach ($this->_storeIdToCode as $storeId => $storeCode) {
            $collection = $this->_categoryColFactory->create()->addNameToResult()->setStoreId($storeId);
            /* @var $collection \Magento\Catalog\Model\ResourceModel\Category\Collection */
            foreach ($collection as $category) {
                $structure = preg_split('#/+#', $category->getPath());
                $pathSize = count($structure);
                if ($pathSize > 1) {
                    $path = [];
                    for ($i = 1; $i < $pathSize; $i++) {
                        $childCategory = $collection->getItemById($structure[$i]);
                        if ($childCategory) {
                            $name = $childCategory->getName();
                            $path[] = $name !== null ? $this->quoteCategoryDelimiter($name) : '';
                        }
                    }
                    $this->_rootCategories[$category->getId()] = array_shift($path);
                    if ($pathSize > 2) {
                        if ($storeId == Store::DEFAULT_STORE_ID) {
                            $this->_categories[$category->getId()] =
                                implode(CategoryProcessor::DELIMITER_CATEGORY, $path);
                        }
                        $this->_categoriesByStore[$storeId][$category->getId()] =
                            implode(CategoryProcessor::DELIMITER_CATEGORY, $path);
                    }
                }
            }
        }
        return $this;
    }

    /**
     * Quoting category delimiter character in string.
     *
     * @param string $string
     * @return string
     */
    private function quoteCategoryDelimiter($string)
    {
        return str_replace(
            CategoryProcessor::DELIMITER_CATEGORY,
            '\\' . CategoryProcessor::DELIMITER_CATEGORY,
            $string
        );
    }

    /**
     * Initialize attribute option values and types.
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function initAttributes()
    {
        /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
        foreach ($this->getAttributeCollection() as $attribute) {
            if ($attribute->usesSource() && $this->isInNamespace('Temando', $attribute->getSource())) {
                // Skip Temando attributes
                continue;
            }

            $options = $this->getAttributeOptions($attribute);
            $this->attributeLabels[$attribute->getAttributeCode()] = $attribute->getFrontendLabel();
            $this->_attributeValues[$attribute->getAttributeCode()] = isset($options[Store::DEFAULT_STORE_ID])
                ? $options[Store::DEFAULT_STORE_ID] : [];
            $this->attributeStoreValues[$attribute->getAttributeCode()] = $options;
            $this->_attributeTypes[$attribute->getAttributeCode()] =
                Import::getAttributeType($attribute);
            if ($attribute->getIsUserDefined()) {
                $this->userDefinedAttributes[] = $attribute->getAttributeCode();
            }
        }
        return $this;
    }

    /**
     * Retrieve entity field for filter
     *
     * @return array
     */
    public function getFieldsForFilter()
    {
        $fields = $this->additional->toOptionArray();
        foreach ($this->attributeLabels as $value => $label) {
            if ($value == 'tier_price') {
                continue;
            }
            $fields[] = ['value' => $value, 'label' => $label];
        }
        return [$this->getEntityTypeCode() => $fields];
    }

    /**
     * Retrieve entity field columns
     *
     * @return array
     * @throws LocalizedException
     */
    public function getFieldColumns()
    {
        $fields = $this->additional->getAdditionalFields();
        foreach ($this->_attributeTypes as $field => $type) {
            if ($field == 'tier_price') {
                continue;
            }
            $option = [];
            foreach ($this->_attributeValues[$field] ?? [] as $value => $label) {
                $option[] = ['value' => $value, 'label' => $label];
            }
            $fields[] = [
                'field' => $field,
                'type' => ($field == self::COL_CATEGORY_IDS) ? 'range' : $this->getAttributeType($type),
                'select' => $option
            ];
        }
        return [$this->getEntityTypeCode() => $fields];
    }

    /**
     * Check if the object belongs to namespace
     *
     * @param string $namespace
     * @param mixed $object
     * @return bool
     */
    protected function isInNamespace($namespace, $object)
    {
        $class = get_class($object);
        return strpos($class, $namespace . '\\') === 0;
    }

    /**
     * Plugins may be attached. Be careful in renaming this function
     *
     * @return array
     */
    public function getExportData()
    {
        $exportData = [];
        try {
            $rawData = $this->collectRawData();
            $multirawData = $this->collectMultirawData();

            $productIds = array_keys($rawData);

            $stockItemRows = $this->prepareCatalogInventory($productIds);

            $this->rowCustomizer->prepareData(
                $this->_prepareEntityCollection($this->_entityCollectionFactory->create()),
                $productIds
            );

            $this->clearMediaGalleryCache();
            $this->warmUpMediaGalleryCache(array_keys($rawData));

            $this->setAddHeaderColumns($stockItemRows);
            $rawData = $this->addAdditionalFields($rawData);
            $prevData = [];
            foreach ($rawData as $productId => $productData) {
                foreach ($productData as $storeId => $dataRow) {
                    if (isset($stockItemRows[$productId])) {
                        $dataRow = array_merge($dataRow, $stockItemRows[$productId]);
                    }
                    $this->addMediaDataToRow($dataRow);
                    $this->appendMultirowData($dataRow, $multirawData);

                    if ($dataRow) {
                        if (Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR !== $this->multipleValueSeparator) {
                            $fields = [
                                'bundle_values',
                                'configurable_variations',
                                'configurable_variation_labels'
                            ];

                            foreach ($fields as $field) {
                                if (!empty($dataRow[$field])) {
                                    $dataRow[$field] = str_replace(
                                        Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
                                        $this->multipleValueSeparator,
                                        $dataRow[$field]
                                    );
                                }
                            }
                        }

                        if (!empty($prevData)) {
                            if (isset($prevData['sku']) && isset($dataRow['sku'])) {
                                if ($prevData['sku'] == $dataRow['sku']) {
                                    $dataRow = array_merge($prevData, $dataRow);
                                }
                            }
                        }
                        if (empty($dataRow['website_id'])) {
                            $store = $this->_storeManager->getStore($storeId);
                            if (!empty($store)) {
                                $dataRow['website_id'] = (int)$store->getWebsiteId();
                            }
                        }
                        $exportData[] = $dataRow;
                    }
                    if ($storeId == Store::DEFAULT_STORE_ID
                        && in_array(Store::DEFAULT_STORE_ID, array_keys($productData))) {
                        $prevData = $dataRow;
                    } elseif (!in_array(Store::DEFAULT_STORE_ID, array_keys($productData))) {
                        $prevData = $dataRow;
                    }
                }
            }
        } catch (Exception $e) {
            $this->_logger->critical($e);
        }
        $newData = $this->changeData($exportData, 'product_id');
        $this->addHeaderColumns();
        $this->_headerColumns = $this->changeHeaders($this->_headerColumns);

        return $newData;
    }

    /**
     * Prepare processor list data parameters
     */
    protected function prepareProcessorListDataParam()
    {
        if (!empty($this->_parameters[Processor::LIST_DATA])) {
            $processorListData = [];
            $flipFieldsMap = array_flip($this->_fieldsMap);
            foreach ($this->_parameters[Processor::LIST_DATA] as $field) {
                $field = $flipFieldsMap[$field] ?? $field;
                $processorListData[] = $field;
            }
            $this->_parameters[Processor::LIST_DATA] = $processorListData;
        }
    }

    protected function _customHeadersMapping($rowData)
    {
        $rowData = parent::_customHeadersMapping($rowData);

        return ($this->_parameters[Processor::ALL_FIELDS]) ? $this->_headerColumns : array_unique($rowData);
    }

    /**
     * @return array
     * @throws LocalizedException
     */
    public function export()
    {
        $this->keysAdditional = [];

        set_time_limit(0);

        $writer = $this->getWriter();
        $page = 0;
        $this->_parameters['export_by_page'] = true;
        $this->prepareProcessorListDataParam();
        $jobId = $this->_parameters['job_id'] ?? '';

        $counts = 0;
        if (!empty($this->_parameters[Processor::BEHAVIOR_DATA]['multiple_value_separator'])) {
            $this->multipleValueSeparator = $this->separatorFormatter->format(
                $this->_parameters[Processor::BEHAVIOR_DATA]['multiple_value_separator']
            );
        }

        $historyType = $this->getLastHistoryType();

        if (!empty($this->_parameters[Processor::BEHAVIOR_DATA]['export_by_page']) &&
            $this->_parameters[Processor::BEHAVIOR_DATA]['file_format'] == 'csv' &&
            $historyType == 'admin') {
            $page = $this->cache->load('current_page');

            if ($page == 1) {
                $this->cacheSave('', 'last_page_exported');
            }
            $isAllStoresExported = $this->getAllStoresExported();
            if (!$isAllStoresExported) {
                $entityCollection = $this->_getEntityCollection(true);
                $entityCollection->setOrder('entity_id', 'asc');
                $entityCollection->setStoreId(Store::DEFAULT_STORE_ID);
                if (isset($this->_parameters[Processor::LAST_ENTITY_ID])
                    && $this->_parameters[Processor::LAST_ENTITY_ID] > 0
                    && $this->_parameters[Processor::LAST_ENTITY_SWITCH] > 0
                ) {
                    $entityCollection->addFieldToFilter(
                        'entity_id',
                        ['gt' => $this->_parameters[Processor::LAST_ENTITY_ID]]
                    );
                }
                $this->_prepareEntityCollection($entityCollection);

                if (!empty($this->_parameters[Processor::BEHAVIOR_DATA]['page_size'])) {
                    $pageSize = $this->_parameters[Processor::BEHAVIOR_DATA]['page_size'];
                } else {
                    $pageSize = 500;
                }

                $this->paginateCollection($page, $pageSize);

                if ($entityCollection->count()) {
                    $exportData = $this->getExportData();
                    if ($page == 1) {
                        $writer->setHeaderCols($this->_getHeaderColumns());
                    }
                    $exportData = $this->customBunchesData($exportData);
                    foreach ($exportData as $dataRow) {
                        if ($this->_parameters[Processor::LAST_ENTITY_SWITCH] > 0) {
                            $this->lastEntityId = $dataRow['product_id'];
                        }
                        $writer->writeRow($this->_customFieldsMapping($dataRow));
                        $counts++;
                    }
                }

                $isAllStoresExported = $this->getAllStoresExported();
                if ($page == $entityCollection->getLastPageNumber() || $isAllStoresExported) {
                    $this->cacheSave(0, 'export_by_page' . $jobId);
                } else {
                    $this->cacheSave(1, 'export_by_page' . $jobId);
                }
            }
        } else {
            while (true) {
                ++$page;

                $entityCollection = $this->_getEntityCollection(true);
                $entityCollection->setOrder('entity_id', 'asc');
                $entityCollection->setStoreId(Store::DEFAULT_STORE_ID);
                if ($page == 1) {
                    $this->cacheSave('', 'last_page_exported');
                }
                if (isset($this->_parameters[Processor::LAST_ENTITY_ID])
                    && $this->_parameters[Processor::LAST_ENTITY_ID] > 0
                    && $this->_parameters[Processor::LAST_ENTITY_SWITCH] > 0
                ) {
                    $entityCollection->addFieldToFilter(
                        'entity_id',
                        ['gt' => $this->_parameters[Processor::LAST_ENTITY_ID]]
                    );
                }

                $this->_prepareEntityCollection($entityCollection);
                $this->paginateCollection($page, $this->getItemsPerPage());

                $entitiesCount = $entityCollection->count();
                if ($entitiesCount == 0) {
                    break;
                }

                $this->cache->save($entitiesCount, 'export_entities_count');

                $exportData = $this->getExportData();
                if ($page == 1) {
                    $writer->setHeaderCols($this->_getHeaderColumns());
                }

                $exportData = $this->customBunchesData($exportData);
                foreach ($exportData as $dataRow) {
                    if ($this->_parameters[Processor::LAST_ENTITY_SWITCH] > 0) {
                        $this->lastEntityId = $dataRow['product_id'];
                    }
                    $writer->writeRow($this->_customFieldsMapping($dataRow));
                    $counts++;
                }

                if ($this->isCollectionLastPage($entityCollection)) {
                    break;
                }
            }

            $this->cache->remove('export_entities_count');
        }

        return [$writer->getContents(), $counts, $this->lastEntityId];
    }

    /**
     * @return string
     */
    protected function getLastHistoryType()
    {
        $historyType = 'admin';
        $historyList = $this->historyList->execute();
        if (!empty($historyList)) {
            $historyListItems = $historyList->getItems();
            $history = end($historyListItems);
            $historyType = $history->getType();
        }
        return $historyType;
    }

    /**
     * @inheritdoc
     */
    protected function getItemsPerPage()
    {
        if (!empty($this->_parameters[Processor::BEHAVIOR_DATA]['page_size'])) {
            $pageSize = $this->_parameters[Processor::BEHAVIOR_DATA]['page_size'];
        } else {
            $pageSize = 500;
            $default = parent::getItemsPerPage();
            if ($default < $pageSize) {
                $pageSize = $default;
            }
        }

        return $pageSize;
    }

    /**
     * Make it public to allow usage of this method in plugins.
     * Plugins may be attached. Be careful in renaming this function
     *
     * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found
     * @inheritdoc
     */
    public function _getEntityCollection($resetCollection = false)
    {
        return parent::_getEntityCollection($resetCollection);
    }

    /**
     * Add support for total entities limit
     * Plugins may be attached. Be careful in renaming this function
     *
     * @inheritdoc
     */
    public function paginateCollection($page, $pageSize)
    {
        $totalLimit = $this->getTotalEntitiesLimit();
        if ($totalLimit !== null && $pageSize > $totalLimit) {
            $pageSize = $totalLimit;
        }

        parent::paginateCollection($page, $pageSize);
    }

    /**
     * Set total entities limit to be fetched during export.
     * Set to null to disable
     * Plugins may be attached. Be careful in renaming this function
     *
     * @param int $totalEntitiesLimit
     * @return Product
     */
    public function setTotalEntitiesLimit($totalEntitiesLimit)
    {
        $this->totalEntitiesLimit = $totalEntitiesLimit;
        return $this;
    }

    /**
     * Get total entities limit to be fetched during export.
     *
     * @return int|null
     */
    protected function getTotalEntitiesLimit()
    {
        return $this->totalEntitiesLimit;
    }

    /**
     * Is collection last page
     *
     * @param \Magento\Framework\Data\Collection\AbstractDb $entityCollection
     * @return bool
     */
    protected function isCollectionLastPage($entityCollection)
    {
        $pageSize = $entityCollection->getPageSize();
        $currentPage = $entityCollection->getCurPage();

        return $currentPage >= $entityCollection->getLastPageNumber()
            || $this->totalEntitiesLimit !== null
            && $currentPage * $pageSize >= $this->totalEntitiesLimit;
    }

    /**
     * Plugins may be attached. Be careful in renaming this function
     *
     * @param array $rowData
     * @return array
     */
    public function _customFieldsMapping($rowData)
    {
        $headerColumns = $this->_getHeaderColumns();

        $rowData = parent::_customFieldsMapping($rowData);
        if (count($headerColumns) != count(array_keys($rowData))) {
            $newData = [];
            foreach ($headerColumns as $code) {
                $fieldCode = isset($this->_fieldsMap[$code]) ? $this->_fieldsMap[$code] : null;
                if ($fieldCode && isset($rowData[$fieldCode])) {
                    $newData[$code] = $rowData[$fieldCode];
                } else {
                    if (!isset($rowData[$code])) {
                        $newData[$code] = '';
                    } else {
                        $newData[$code] = $rowData[$code];
                    }
                }
            }
            $rowData = $newData;
        }

        return $rowData;
    }

    /**
     * @inheritdoc
     */
    private function joinTierPriceTable(AbstractCollection $collection)
    {
        $joinField = $this->getProductEntityLinkField();
        $collection->distinct(true)->getSelect()->join(
            ['tp' => $collection->getResource()->getTable('catalog_product_entity_tier_price')],
            "e.{$joinField} = tp.{$joinField}",
            []
        );
    }

    /**
     * Plugins may be attached. Be careful in renaming this function
     *
     * @inheritdoc
     */
    public function _prepareEntityCollection(AbstractCollection $collection)
    {
        if (!isset($this->_parameters[Export::FILTER_ELEMENT_GROUP])
            || !is_array($this->_parameters[Export::FILTER_ELEMENT_GROUP])) {
            $exportFilter = [];
        } else {
            $exportFilter = $this->_parameters[Export::FILTER_ELEMENT_GROUP];
            /* The value of multiselect filter must be an array */
            foreach ($this->filterAttributeCollection($this->getAttributeCollection()) as $attribute) {
                $attrCode = $attribute->getAttributeCode();
                $filterFlagName = $attrCode . '_filter_applied';
                if (isset($exportFilter[$attrCode])) {
                    $attrFilterType = Export::getAttributeFilterType($attribute);
                    if (Export::FILTER_TYPE_MULTISELECT == $attrFilterType &&
                        !is_array($exportFilter[$attrCode])
                    ) {
                        /* delegate new value to parent handler */
                        $this->_parameters[Export::FILTER_ELEMENT_GROUP][$attrCode] = [$exportFilter[$attrCode]];
                    }
                    if (Export::FILTER_TYPE_SELECT == $attrFilterType
                        && is_scalar($exportFilter[$attrCode])
                        && (int)$exportFilter[$attrCode] >= 0) {

                        $attribute = $this->attributeRepository->get($attrCode);
                        $defaultValue = $attribute->getDefaultValue();
                        if ($defaultValue === $exportFilter[$attrCode]) {
                            $collection->addAttributeToFilter(
                                $attrCode,
                                [
                                    ['eq' => (int)$exportFilter[$attrCode]],
                                    ['null' => true]
                                ],
                                'left'
                            );
                        } else {
                            $collection->addAttributeToFilter($attrCode, ['eq' => (int)$exportFilter[$attrCode]]);
                        }
                        $collection->setFlag($filterFlagName);
                    }
                }
            }
        }

        $collection = Export\Entity\AbstractEntity::_prepareEntityCollection($collection);

        foreach ($this->additional->fields as $field) {
            if (isset($exportFilter[$field]) && !empty($exportFilter[$field])) {
                if ($field == 'store') {
                    $collection->addStoreFilter($exportFilter['store']);
                } else {
                    if (in_array($field, $this->tierPriceAttributes)) {
                        $this->joinTierPriceTable($collection);
                    }
                    $collection->getSelect()->where(
                        $this->additional->convertFields($field) . "=?",
                        $exportFilter[$field]
                    );
                }
            }
        }

        if (isset($exportFilter['category_ids']) &&
            is_array($exportFilter['category_ids']) &&
            count($exportFilter['category_ids']) >= 2) {
            $from = array_shift($exportFilter['category_ids']);
            $to = array_shift($exportFilter['category_ids']);
            if (!empty($exportFilter['category_ids'])) {
                $exclude = array_shift($exportFilter['category_ids']);
            }

            $categorySelect = $collection->getConnection()->select()->from(
                ['cat' => $collection->getResource()->getTable('catalog_category_product')],
                'cat.product_id'
            );

            if (is_numeric($from)) {
                $categorySelect->where('cat.category_id >= ?', $from);
            }
            if (is_numeric($to)) {
                $categorySelect->where('cat.category_id <= ?', $to);
            }
            if (!empty($exclude)) {
                $exclude = explode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $exclude);
                $categorySelect->where('cat.category_id NOT IN (?)', $exclude);
            }

            $collection->getSelect()->where(
                $collection->getConnection()->prepareSqlCondition(
                    'e.entity_id',
                    ['in' => $categorySelect]
                )
            );
        }

        $attrCode = 'quantity_and_stock_status';
        if (isset($exportFilter[$attrCode])) {
            $inStock = (int)$exportFilter[$attrCode];
            $collection->getSelect()
                ->join(
                    ['csi' => $collection->getTable('cataloginventory_stock_item')],
                    'csi.product_id = e.entity_id',
                    ['is_in_stock']
                )
                ->where('is_in_stock =?', $inStock);
        }

        return $collection;
    }

    protected function collectMultirawData()
    {
        $productIds = [];
        $rowWebsites = [];
        $rowCategoriesPosition = [];
        $productLinkIds = [];

        $entityCollection = $this->_getEntityCollection();
        $entityCollection->setStoreId(Store::DEFAULT_STORE_ID);
        $entityCollection->addCategoryIds()->addWebsiteNamesToResult();
        $this->addCategoryPosition($entityCollection);

        /** @var \Magento\Catalog\Model\Product $item */
        foreach ($entityCollection as $item) {
            $productLinkIds[] = $item->getData($this->getProductEntityLinkField());
            $productIds[] = $item->getId();
            $rowWebsites[$item->getId()] = array_intersect(
                array_keys($this->_websiteIdToCode),
                $item->getWebsites()
            ) ?? [];

            $rowCategoriesPosition[$item->getId()] = $item->getCategoryPosition();
        }
        $entityCollection->clear();

        $rowCategories = $this->getRowCategoriesData($productIds);
        $categoryIds = array_merge(array_keys($this->_categories), array_keys($this->_rootCategories));
        $categoryIds = array_combine($categoryIds, $categoryIds);
        foreach ($rowCategories as &$categories) {
            $categories = array_intersect_key($categories, $categoryIds);
        }

        $data['rowWebsites'] = $rowWebsites;
        $data['rowCategories'] = $rowCategories;
        $data['rowCategoriesPosition'] = $rowCategoriesPosition;
        $data['rowCategoryIds'] = $rowCategories;
        $data['linksRows'] = $this->prepareLinks($productLinkIds);
        $data['customOptionsData'] = $this->getCustomOptionsData($productLinkIds);
        return $data;
    }

    /**
     * @param array $rows
     * @return array
     */
    protected function addAdditionalFields(array $rows): array
    {
        foreach ($this->additionalFieldsPool->getEntities() as $keyEntity => $entity) {
            if ($entity instanceof Msi && empty($this->_parameters[Processor::BEHAVIOR_DATA]['import_msi'])
            ) {
                continue;
            }
            $entity->addFields($rows);
            $this->_headerColumns = array_merge($this->_headerColumns, $entity->getHeaders());
        }
        return $rows;
    }

    /**
     * @param int[] $productIds
     * @return array
     */
    protected function getCustomOptionsData($productIds)
    {
        $customOptionsData = [];
        $defaultOptionsData = [];

        foreach (array_keys($this->_storeIdToCode) as $storeId) {
            $options = $this->_optionColFactory->create();
            /* @var Collection $options*/
            $options->reset()
                ->addOrder('sort_order', Collection::SORT_ORDER_ASC)
                ->addTitleToResult($storeId)
                ->addPriceToResult($storeId)
                ->addProductToFilter($productIds)
                ->addValuesToResult($storeId);

            foreach ($options as $option) {
                $optionData = $option->toArray();
                $row = [];
                $productId = $option['product_id'];
                $row['name'] = $option['title'];
                $row['type'] = $option['type'];
                $row[self::COLUMN_ID] = $option['option_id'];

                $row['required'] = $this->getOptionValue('is_require', $defaultOptionsData, $optionData);
                $row['price'] = $this->getOptionValue('price', $defaultOptionsData, $optionData);
                $row['sku'] = $this->getOptionValue('sku', $defaultOptionsData, $optionData);
                if (array_key_exists('max_characters', $optionData)
                    || array_key_exists('max_characters', $defaultOptionsData)
                ) {
                    $row['max_characters'] = $this->getOptionValue('max_characters', $defaultOptionsData, $optionData);
                }
                foreach (['file_extension', 'image_size_x', 'image_size_y'] as $fileOptionKey) {
                    if (isset($option[$fileOptionKey]) || isset($defaultOptionsData[$fileOptionKey])) {
                        $row[$fileOptionKey] = $this->getOptionValue($fileOptionKey, $defaultOptionsData, $optionData);
                    }
                }
                $percentType = $this->getOptionValue('price_type', $defaultOptionsData, $optionData);
                $row['price_type'] = ($percentType === 'percent') ? 'percent' : 'fixed';

                if (Store::DEFAULT_STORE_ID === $storeId) {
                    $optionId = $option['option_id'];
                    $defaultOptionsData[$optionId] = $option->toArray();
                }

                $values = $option->getValues();

                if ($values) {
                    foreach ($values as $value) {
                        $row['option_title'] = $value['title'];
                        $row['option_title'] = $value['title'];
                        $row['price'] = $value['price'];
                        $row['price_type'] = ($value['price_type'] === 'percent') ? 'percent' : 'fixed';
                        $row['sku'] = $value['sku'];
                        $row[self::COLUMN_ROW_ID] = $value['option_type_id'];
                        $customOptionsData[$productId][$storeId][] = $this->optionRowToCellString($row);

                    }
                } else {
                    $customOptionsData[$productId][$storeId][] = $this->optionRowToCellString($row);
                }
                $option = null;
            }
            $options = null;
        }

        return $customOptionsData;
    }

    /**
     * @param string $optionName
     * @param array $defaultOptionsData
     * @param array $optionData
     * @return mixed|null
     */
    private function getOptionValue($optionName, $defaultOptionsData, $optionData)
    {
        $optionId = $optionData['option_id'];

        if (array_key_exists($optionName, $optionData) && $optionData[$optionName] !== null) {
            return $optionData[$optionName];
        }

        if (array_key_exists($optionId, $defaultOptionsData)
            && array_key_exists($optionName, $defaultOptionsData[$optionId])
        ) {
            return $defaultOptionsData[$optionId][$optionName];
        }

        return null;
    }

    /**
     * Get row category data by product ids
     *
     * @param array $productIds
     * @return array
     */
    protected function getRowCategoriesData($productIds)
    {
        $rowCategories = [];
        if (empty($productIds)) {
            return $rowCategories;
        }

        $select = $this->_resourceModel->getConnection()->select();
        $tableName = $this->_resourceModel->getTableName('catalog_category_product');
        $select->from($tableName, [
            'product_id',
            'category_ids' => new \Zend_Db_Expr('GROUP_CONCAT(category_id)')
        ]);
        $select->where('product_id IN (?)', $productIds);
        $select->group('product_id');

        $data = $this->_resourceModel->getConnection()->fetchAll($select);
        foreach ($data as $row) {
            $categories = explode(',', $row['category_ids']);
            $rowCategories[$row['product_id']] = array_combine($categories, $categories);
        }

        return $rowCategories;
    }

    /**
     * Add category position to loaded items
     *
     * @param AbstractCollection $collection
     * @return AbstractCollection
     */
    public function addCategoryPosition($collection)
    {
        if ($collection->getFlag('category_position_added')) {
            return $collection;
        }

        $productIds = [];
        foreach ($collection as $item) {
            $productIds[] = $item->getId();
        }

        if (!$productIds) {
            return $collection;
        }

        $select = $collection->getConnection()->select();

        $tableName = $collection->getResource()->getTable('catalog_category_product');
        $select->from($tableName, ['product_id', 'category_id', 'position']);
        $select->where('product_id IN (?)', $productIds);
        $data = $collection->getConnection()->fetchAll($select);

        $categoryPosition = [];
        foreach ($data as $info) {
            $categoryPosition[$info['product_id']][$info['category_id']] = $info['position'];
        }

        foreach ($collection as $item) {
            $productId = $item->getId();
            if (isset($categoryPosition[$productId])) {
                $item->setCategoryPosition($categoryPosition[$productId]);
            } else {
                $item->setCategoryPosition([]);
            }
        }

        $collection->setFlag('category_position_added', true);
        return $collection;
    }

    /**
     * @return array
     * @throws LocalizedException
     */
    protected function fieldsCatalogInventory()
    {
        $fields = $this->_connection->describeTable($this->_itemFactory->create()->getMainTable());
        $rows = [];
        $row = [];
        unset(
            $fields['item_id'],
            $fields['product_id'],
            $fields['low_stock_date'],
            $fields['stock_id'],
            $fields['stock_status_changed_auto']
        );
        foreach ($fields as $key => $field) {
            $row[$key] = $key;
        }

        $rows[] = $row;
        return $rows;
    }

    protected function collectRawData()
    {
        $data = [];
        $items = $this->fireloadCollection();
        $stores = $this->getStores();

        $this->updatePageByPageExportStatus();

        foreach ($items as $itemId => $itemByStore) {
            /**
             * @var int $itemId
             * @var ProductEntity $item
             */
            foreach ($stores as $storeId => $storeCode) {
                if (!isset($itemByStore[$storeId])) {
                    continue;
                }
                /** @var \Magento\Catalog\Model\Product $item */
                $item = $itemByStore[$storeId];
                $addtionalFields = [];
                $additionalAttributes = [];
                $productLinkId = $item->getData($this->getProductEntityLinkField());

                $exportAttrCodes = array_unique($this->_getExportAttrCodes());
                foreach ($exportAttrCodes as $attrCodes) {
                    $attrValue = $item->getData($attrCodes);
                    if (isset($this->_attributeTypes[$attrCodes]) &&
                        $this->_attributeTypes[$attrCodes] != 'text' &&
                        !empty($attrValue)
                    ) {
                        $attrValue = str_replace(["\r\n", "\n\r", "\n", "\r"], '', $attrValue);
                    }
                    if (!$this->isValidAttributeValue($attrCodes, $attrValue)) {
                        continue;
                    }

                    if (isset($this->attributeStoreValues[$attrCodes][$storeId][$attrValue])
                        && !empty($this->attributeStoreValues[$attrCodes][$storeId])
                    ) {
                        $attrValue = $this->attributeStoreValues[$attrCodes][$storeId][$attrValue];
                    }
                    $fieldName = isset($this->_fieldsMap[$attrCodes]) ? $this->_fieldsMap[$attrCodes] : $attrCodes;

                    if ($this->_attributeTypes[$attrCodes] == 'datetime') {
                        if (in_array($attrCodes, $this->dateAttrCodes) ||
                            in_array($attrCodes, $this->userDefinedAttributes)) {
                            $attrValue = $this->_localeDate
                                ->formatDateTime(
                                    new DateTime($attrValue),
                                    IntlDateFormatter::SHORT,
                                    IntlDateFormatter::NONE,
                                    null,
                                    date_default_timezone_get()
                                );
                        } else {
                            $attrValue = $this->_localeDate
                                ->formatDateTime(
                                    new DateTime($attrValue),
                                    IntlDateFormatter::SHORT,
                                    IntlDateFormatter::SHORT
                                );
                        }
                    }

                    if ($storeId != Store::DEFAULT_STORE_ID
                        && isset($data[$itemId][Store::DEFAULT_STORE_ID][$fieldName])
                        && $data[$itemId][Store::DEFAULT_STORE_ID][$fieldName] == htmlspecialchars_decode($attrValue)
                    ) {
                        continue;
                    }

                    if ($this->_attributeTypes[$attrCodes] !== 'multiselect') {
                        if (is_scalar($attrValue)) {
                            if (!in_array($fieldName, $this->_getExportMainAttrCodes())) {
                                $additionalAttributes[$fieldName] = $fieldName .
                                    ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $this->wrapValue($attrValue);
                                if ($this->checkDivideofAttributes()) {
                                    $addtionalFields[$fieldName] = $attrValue;
                                    if (!in_array($fieldName, $this->keysAdditional)) {
                                        $this->keysAdditional[] = $fieldName;
                                    }
                                }
                            }
                            $data[$itemId][$storeId][$fieldName] = htmlspecialchars_decode($attrValue);
                        }
                    } else {
                        $this->collectMultiselectValues($item, $attrCodes, $storeId);
                        if (!empty($this->collectedMultiselectsData[$storeId][$productLinkId][$attrCodes])) {
                            $additionalAttributes[$attrCodes] = $fieldName .
                                ImportProduct::PAIR_NAME_VALUE_SEPARATOR . implode(
                                    ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR,
                                    $this->wrapValue(
                                        $this->collectedMultiselectsData[$storeId][$productLinkId][$attrCodes]
                                    )
                                );
                            if ($this->checkDivideofAttributes()) {
                                if (!in_array($attrCodes, $this->keysAdditional)) {
                                    $this->keysAdditional[] = $attrCodes;
                                }
                                $addtionalFields[$attrCodes] =
                                    $this->collectedMultiselectsData[$storeId][$productLinkId][$attrCodes];
                            }
                        }
                    }
                }
                if (!empty($additionalAttributes)) {
                    $additionalAttributes = array_map('htmlspecialchars_decode', $additionalAttributes);
                    $data[$itemId][$storeId][self::COL_ADDITIONAL_ATTRIBUTES] =
                        implode($this->multipleValueSeparator, $additionalAttributes);
                } else {
                    unset($data[$itemId][$storeId][self::COL_ADDITIONAL_ATTRIBUTES]);
                }

                if (!empty($data[$itemId][$storeId]) || $this->hasMultiselectData($item, $storeId)) {
                    $attrSetId = $item->getAttributeSetId();
                    $data[$itemId][$storeId][self::COL_STORE] = $storeCode;
                    $data[$itemId][$storeId][self::COL_ATTR_SET] = $this->_attrSetIdToName[$attrSetId];
                    $data[$itemId][$storeId][self::COL_TYPE] = $item->getTypeId();
                }
                if (!empty($addtionalFields)) {
                    foreach ($addtionalFields as $key => $value) {
                        $data[$itemId][$storeId][$key] = $value;
                    }
                }
                $data[$itemId][$storeId][self::COL_SKU] = htmlspecialchars_decode($item->getSku());
                $data[$itemId][$storeId]['status'] = $item->getStatus();
                $data[$itemId][$storeId]['product_online'] = $item->getStatus();
                $data[$itemId][$storeId]['store_id'] = $storeId;
                $data[$itemId][$storeId]['product_id'] = $itemId;
                $data[$itemId][$storeId]['product_link_id'] = $productLinkId;
            }
        }

        return $data;
    }

    /**
     * Update PageByPageExportStatus
     */
    protected function updatePageByPageExportStatus()
    {
        $stores = $this->getStores();
        $pageByPageExportStatus = 'complete';
        if (isset($this->getParameters()['only_admin'])
            && $this->getParameters()['only_admin'] == 1
        ) {
            if (!$this->getLastPageExportedStatus(Store::DEFAULT_STORE_ID)) {
                $pageByPageExportStatus = '';
            }
        } else {
            foreach ($stores as $storeId => $storeCode) {
                if (!$this->getLastPageExportedStatus($storeId)) {
                    $pageByPageExportStatus = '';
                }
            }
        }
        $this->cacheSave($pageByPageExportStatus, 'page_by_page_export_status');
    }

    /**
     * {@inheritDoc}
     */
    protected function collectMultiselectValues($item, $attrCode, $storeId)
    {
        $attrValue = $item->getData($attrCode);
        $optionIds = explode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $attrValue);
        $options = array_intersect_key(
            $this->_attributeValues[$attrCode],
            array_flip($optionIds)
        );
        $linkId = $item->getData($this->getProductEntityLinkField());
        if (!(isset($this->collectedMultiselectsData[Store::DEFAULT_STORE_ID][$linkId][$attrCode])
            && $this->collectedMultiselectsData[Store::DEFAULT_STORE_ID][$linkId][$attrCode] == $options)
        ) {
            $this->collectedMultiselectsData[$storeId][$linkId][$attrCode] = $options;
        }

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    protected function optionRowToCellString($option)
    {
        $result = [];

        foreach ($option as $key => $value) {
            $result[] = $key . ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $value;
        }

        return implode($this->multipleValueSeparator, $result);
    }

    private function wrapValue(
        $value
    ) {
        if (!empty($this->_parameters[Export::FIELDS_ENCLOSURE])) {
            $wrap = function ($value) {
                return sprintf('"%s"', str_replace('"', '""', $value));
            };

            $value = is_array($value) ? array_map($wrap, $value) : $wrap($value);
        }

        return $value;
    }

    /**
     * @param $stockItemRows
     */
    protected function setAddHeaderColumns($stockItemRows)
    {
        $addData = [];

        if (!empty($stockItemRows)) {
            if (reset($stockItemRows)) {
                $addData = array_keys(end($stockItemRows));
                foreach ($addData as $key => $value) {
                    if (is_numeric($value)) {
                        unset($addData[$key]);
                    }
                }
            }
        }
        if (!$this->_headerColumns) {
            $this->_headerColumns = array_merge(
                [
                    self::COL_SKU,
                    self::COL_STORE,
                    self::COL_ATTR_SET,
                    self::COL_TYPE,
                    self::COL_CATEGORY,
                    self::COL_CATEGORY_BY_STORE,
                    self::COL_CATEGORY . '_position',
                    self::COL_CATEGORY_IDS,
                    self::COL_PRODUCT_WEBSITES,
                ],
                $this->_getExportMainAttrCodes(),
                $addData,
                [
                    'related_skus',
                    'related_position',
                    'crosssell_skus',
                    'crosssell_position',
                    'upsell_skus',
                    'upsell_position',
                    'additional_images',
                    'additional_image_labels',
                    'hide_from_product_page',
                    'custom_options',
                ]
            );
            if (!$this->checkDivideofAttributes()) {
                $this->_headerColumns = array_merge(
                    $this->_headerColumns,
                    [
                        self::COL_ADDITIONAL_ATTRIBUTES
                    ]
                );
            }
        }
    }

    protected function addHeaderColumns()
    {
        if ($this->checkDivideofAttributes()) {
            $this->_headerColumns = array_merge($this->_headerColumns, $this->keysAdditional);
        }

        $this->_headerColumns = array_unique(array_merge(
            $this->_headerColumns,
            $this->_getExportAttrCodes()
        ));
    }

    /**
     * @return array
     */
    protected function fireloadCollection()
    {
        $data = [];
        /** @var ProductCollection $collection */
        $collection = $this->_getEntityCollection()->clear();

        if (isset($this->getParameters()['only_admin'])
            && $this->getParameters()['only_admin'] == 1
        ) {
            $collection->addAttributeToSelect('*');
            $collection->addStoreFilter(Store::DEFAULT_STORE_ID);
            /**
             * @var int $itemId
             * @var \Magento\Catalog\Model\Product $item
             */
            $this->setLastPageExportedStatus($collection, Store::DEFAULT_STORE_ID);
            foreach ($collection as $itemId => $item) {
                $data[$itemId][Store::DEFAULT_STORE_ID] = $item;
            }
            $collection->clear();
        } else {
            $collectionByStore = clone $collection;
            foreach (array_keys($this->getStores()) as $storeId) {
                $collectionByStore->addStoreFilter($storeId);
                if ($this->getLastPageExportedStatus($storeId)) {
                    continue;
                }
                $this->setLastPageExportedStatus($collectionByStore, $storeId);
                foreach ($collectionByStore as $itemId => $item) {
                    $data[$itemId][$storeId] = $item;
                }
                $collectionByStore->clear();
            }
            unset($collectionByStore);
        }
        return $data;
    }

    /**
     * @param $storeId
     * @return bool
     */
    protected function getLastPageExportedStatus($storeId)
    {
        $result = false;
        if (isset($this->isLastPageExported[$storeId])) {
            $result = true;
        } else {
            $lastPageExportedByStores = $this->cache->load('last_page_exported');
            if ($lastPageExportedByStores) {
                $lastPageExportedByStores = $this->serializer->unserialize($lastPageExportedByStores);
                foreach ($lastPageExportedByStores as $key => $value) {
                    $this->isLastPageExported[$key] = $value;
                }
            }
            if (isset($lastPageExportedByStores[$storeId])) {
                $result = true;
            }
        }

        return $result;
    }

    /**
     * @param $collectionByStore
     * @param $storeId
     */
    protected function setLastPageExportedStatus($collectionByStore, $storeId)
    {
        $curPage = $collectionByStore->getCurPage();
        $lastPage = $collectionByStore->getLastPageNumber();

        if ($curPage == $lastPage) {
            $this->isLastPageExported[$storeId] = true;
            $isLastPageExportedSerial = $this->serializer->serialize($this->isLastPageExported);
            $this->cacheSave($isLastPageExportedSerial, 'last_page_exported');
        }
    }

    /**
     * @param $cacheData
     * @param $identifier
     * @return bool
     */
    protected function cacheSave($cacheData, $identifier)
    {
        return $this->cache->save(
            $cacheData,
            $identifier,
            [self::CACHE_TAG]
        );
    }

    /**
     * @return bool
     */
    private function getAllStoresExported()
    {
        $lastPageExportedByStores = $this->cache->load('last_page_exported');
        if ($lastPageExportedByStores) {
            $lastPageExportedByStores = $this->serializer->unserialize($lastPageExportedByStores);
        }
        $isAllStoresExported = true;
        foreach (array_keys($this->getStores()) as $storeId) {
            if (!isset($lastPageExportedByStores[$storeId])) {
                $isAllStoresExported = false;
            }
        }

        return $isAllStoresExported;
    }

    /**
     * @return bool
     */
    protected function checkDivideofAttributes()
    {
        return isset($this->_parameters[Processor::DIVIDED_ATTRIBUTES]) &&
            $this->_parameters[Processor::DIVIDED_ATTRIBUTES];
    }

    /**
     * Clear media gallery cache by each page of collection items loaded to not overuse ram
     */
    protected function clearMediaGalleryCache()
    {
        $this->mediaGalleryCache = [];
    }

    /**
     * Warm up media gallery cache
     *
     * @param array $productIds
     * @throws Zend_Db_Statement_Exception
     */
    protected function warmUpMediaGalleryCache($productIds)
    {
        $this->getMediaGallery($productIds);
    }

    /**
     * Prepare products media gallery
     *
     * @param int[] $productIds
     * @return array
     * @throws Zend_Db_Statement_Exception
     */
    protected function getMediaGallery(array $productIds)
    {
        if (empty($productIds)) {
            return [];
        }

        $idsToLoad = $productIds;
        if (!empty($this->mediaGalleryCache)) {
            // Compare cached products media and requested
            $diff = array_diff($productIds, array_keys($this->mediaGalleryCache));
            if (empty($diff)) {
                // Gallery data for requested product ids is in cache
                return $this->getCachedMediaByIds($productIds);
            } else {
                // Load only not cached entities data
                $idsToLoad = $diff;
            }
        }

        $productEntityJoinField = $this->getProductEntityLinkField();
        $select = $this->_connection->select()->from(
            ['mgvte' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery_value_to_entity')],
            [
                "mgvte.$productEntityJoinField",
                'mgvte.value_id'
            ]
        )->joinLeft(
            ['mg' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery')],
            '(mg.value_id = mgvte.value_id)',
            [
                'mg.attribute_id',
                'filename' => 'mg.value',
                'mg.media_type'
            ]
        )->joinLeft(
            ['mgv' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery_value')],
            "(mg.value_id = mgv.value_id) and (mgvte.$productEntityJoinField = mgv.$productEntityJoinField)",
            [
                'mgv.label',
                'mgv.position',
                'mgv.disabled',
                'mgv.store_id',
            ]
        )->joinLeft(
            ['ev' => $this->_resourceModel->getTableName('catalog_product_entity_varchar')],
            "(mgvte.$productEntityJoinField = ev.$productEntityJoinField) and (mg.value = ev.value)",
            []
        )->joinLeft(
            ['ea' => $this->_resourceModel->getTableName('eav_attribute')],
            "(ea.attribute_id = ev.attribute_id)",
            [
                'ea.frontend_label'
            ]
        )->where(
            "mgvte.$productEntityJoinField IN (?)",
            $idsToLoad
        );

        $stmt = $this->_connection->query($select);
        while ($mediaRow = $stmt->fetch()) {
            if ($mediaRow['media_type'] === ProductVideo::EXTERNAL_VIDEO) {
                continue;
            }

            // Save data to cache
            $this->mediaGalleryCache[$mediaRow[$productEntityJoinField]][] = [
                '_media_attribute_id' => $mediaRow['attribute_id'],
                '_media_image' => $mediaRow['filename'],
                '_media_label' => $mediaRow['label'],
                '_media_position' => $mediaRow['position'],
                '_media_is_disabled' => $mediaRow['disabled'],
                '_media_store_id' => $mediaRow['store_id'],
                '_media_frontend_label' => $mediaRow['frontend_label'],
            ];
        }

        return $this->getCachedMediaByIds($productIds);
    }

    /**
     * Fetch media data from cache
     *
     * @param array $productIds
     * @return array
     */
    protected function getCachedMediaByIds($productIds)
    {
        if (empty($productIds) || empty($this->mediaGalleryCache)) {
            return [];
        }

        $data = array_intersect_key($this->mediaGalleryCache, array_flip($productIds));
        return $data;
    }

    /**
     * @param array $dataRow
     * @param array $multiRawData
     * @return array|null
     * @throws Zend_Db_Statement_Exception
     */
    protected function appendMultirowData(&$dataRow, &$multiRawData)
    {
        $pId = $dataRow['product_id'];
        $productLinkId = $dataRow['product_link_id'];
        $storeId = $dataRow['store_id'];
        $sku = $dataRow[self::COL_SKU];

        unset($dataRow['product_link_id']);
        unset($dataRow[self::COL_SKU]);
        unset($dataRow[self::COL_STORE]);

        $this->updateDataWithCategoryColumns($dataRow, $multiRawData['rowCategories'], $pId);
        $this->updateDataWithCategoryPositionColumns($dataRow, $multiRawData['rowCategoriesPosition'], $pId);
        $this->updateDataWithCategoryIdsColumns($dataRow, $multiRawData['rowCategoryIds'], $pId);

        unset($dataRow['store_id']);

        if (!empty($multiRawData['rowWebsites'][$pId])) {
            $websiteCodes = [];
            foreach ($multiRawData['rowWebsites'][$pId] as $productWebsite) {
                $websiteCodes[] = $this->_websiteIdToCode[$productWebsite];
            }
            $dataRow[self::COL_PRODUCT_WEBSITES] =
                implode($this->multipleValueSeparator, $websiteCodes);
            $multiRawData['rowWebsites'][$pId] = [];
        }

        $multiRawData['mediaGalery'] = $this->getMediaGallery([$productLinkId]);
        if (!empty($multiRawData['mediaGalery'][$productLinkId])) {
            $additionalImages = $additionalLabels = [];
            $baseImages = $baseLabels = [];
            $additionalImageIsDisabled = [];
            foreach ($multiRawData['mediaGalery'][$productLinkId] as $mediaItem) {
                if ($mediaItem['_media_frontend_label'] == 'Base') {
                    $baseImages[] = $mediaItem['_media_image'];
                    $baseLabels[] = $mediaItem['_media_label'];
                } else {
                    $additionalImages[] = $mediaItem['_media_image'];
                    $additionalLabels[] = $mediaItem['_media_label'];
                }
                if ($mediaItem['_media_is_disabled'] == true && $mediaItem['_media_store_id'] == $storeId) {
                    $additionalImageIsDisabled[] = $mediaItem['_media_image'];
                }
            }

            $dataRow['base_image'] = implode(
                $this->multipleValueSeparator,
                array_filter(array_unique($baseImages))
            );
            $dataRow['base_image_labels'] = implode(
                $this->multipleValueSeparator,
                array_filter(array_unique($baseLabels))
            );
            $dataRow['additional_images'] = implode(
                $this->multipleValueSeparator,
                array_filter(array_unique($additionalImages))
            );
            $dataRow['additional_image_labels'] = implode(
                $this->multipleValueSeparator,
                array_filter(array_unique($additionalLabels))
            );
            $dataRow['hide_from_product_page'] = implode(
                $this->multipleValueSeparator,
                array_filter(array_unique($additionalImageIsDisabled))
            );
            $multiRawData['mediaGalery'][$productLinkId] = [];
        }
        foreach ($this->_linkTypeProvider->getLinkTypes() as $typeName => $linkId) {
            if (!empty($multiRawData['linksRows'][$productLinkId][$linkId])) {
                $colPrefix = $typeName . '_';
                $associations = [];
                foreach ($multiRawData['linksRows'][$productLinkId][$linkId] as $linkData) {
                    if ($linkData['default_qty'] !== null) {
                        $skuItem = $linkData['sku']
                            . ImportProduct::PAIR_NAME_VALUE_SEPARATOR
                            . $linkData['default_qty'];
                    } else {
                        $skuItem = $linkData['sku'];
                    }
                    $associations[$skuItem] = $linkData['position'];
                }
                $multiRawData['linksRows'][$productLinkId][$linkId] = [];
                asort($associations);
                $dataRow[$colPrefix . 'skus'] = implode(
                    $this->multipleValueSeparator,
                    array_keys($associations)
                );
                $dataRow[$colPrefix . 'position'] = implode(
                    $this->multipleValueSeparator,
                    array_values($associations)
                );
            }
        }
        $dataRow = $this->rowCustomizer->addData($dataRow, $pId);

        if (!empty($this->collectedMultiselectsData[$storeId][$productLinkId])) {
            foreach (array_keys($this->collectedMultiselectsData[$storeId][$productLinkId]) as $attrKey) {
                if (!empty($this->collectedMultiselectsData[$storeId][$productLinkId][$attrKey])) {
                    $dataRow[$attrKey] =
                        implode(
                            $this->multipleValueSeparator,
                            $this->collectedMultiselectsData[$storeId][$productLinkId][$attrKey]
                        );
                }
            }
        }

        if (!empty($multiRawData['customOptionsData'][$productLinkId][$storeId])) {
            $customOptionsRows =
                $multiRawData['customOptionsData'][$productLinkId][$storeId];
            $multiRawData['customOptionsData'][$productLinkId][$storeId] = [];
            $customOptions =
                implode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, $customOptionsRows);

            $dataRow = array_merge(
                $dataRow,
                ['custom_options' => $customOptions]
            );
        }

        if (empty($dataRow)) {
            return null;
        } elseif ($storeId != Store::DEFAULT_STORE_ID) {
            $dataRow[self::COL_STORE] = $this->_storeIdToCode[$storeId];
        }
        $dataRow[self::COL_SKU] = $sku;

        return $dataRow;
    }

    /**
     * @return array
     */
    public function getMediaAttributesMap()
    {
        return [
            'base_image' => 'image',
            'base_image_label' => 'image_label',
            'thumbnail_image' => 'thumbnail',
            'thumbnail_image_label' => 'thumbnail_label',
            'swatch_image_label' => 'swatch_label'
        ];
    }

    /**
     * Add media gallery data to row
     *
     * @param $rowData
     */
    protected function addMediaDataToRow(&$rowData)
    {
        $productId = $rowData['product_link_id'] ?? '';
        if ($productId) {
            $mediaGalleryData = $this->getMediaGallery([$productId]);
            if (!empty($mediaGalleryData[$productId])) {
                foreach ($mediaGalleryData[$productId] as $galleryData) {
                    switch ($galleryData['_media_frontend_label']) {
                        case 'Base':
                            if (empty($rowData['base_image'])) {
                                $rowData['base_image'] = $galleryData['_media_image'] ?? '';
                            }
                            if (empty($rowData['base_image_label'])) {
                                $rowData['base_image_label'] = $galleryData['_media_label'] ?? '';
                            }
                            break;
                        case 'Swatch':
                            if (empty($rowData['swatch_image'])) {
                                $rowData['small_image'] = $galleryData['_media_image'] ?? '';
                            }
                            if (empty($rowData['swatch_image_label'])) {
                                $rowData['swatch_image_label'] = $galleryData['_media_label'] ?? '';
                            }
                            break;
                        case 'Small':
                            if (empty($rowData['small_image'])) {
                                $rowData['small_image'] = $galleryData['_media_image'] ?? '';
                            }
                            if (empty($rowData['small_image_label'])) {
                                $rowData['small_image_label'] = $galleryData['_media_label'] ?? '';
                            }
                            break;
                        case 'Thumbnail':
                            if (empty($rowData['thumbnail_image'])) {
                                $rowData['thumbnail_image'] = $galleryData['_media_image'] ?? '';
                            }
                            if (empty($rowData['small_image_label'])) {
                                $rowData['thumbnail_image_label'] = $galleryData['_media_label'] ?? '';
                            }
                            break;
                    }
                }
            }
        }
    }

    /**
     * @param array $parameters
     * @param array $attributes
     * @return array
     */
    public function addMediaAttributes(array $parameters, array $attributes)
    {
        foreach ($attributes as $parameter => $attribute) {
            if (array_search($parameter, $parameters) !== false) {
                $parameters[] = $attribute;
            }
        }
        return $parameters;
    }

    protected function _getExportAttrCodes()
    {
        if (null === self::$attrCodes) {
            $attrCodes = [];
            $parameters = $this->_parameters;

            if (isset($parameters[Processor::ALL_FIELDS]) && $parameters[Processor::ALL_FIELDS] &&
                isset($parameters[Processor::LIST_DATA]) && is_array($parameters[Processor::LIST_DATA])) {
                $attrCodes = array_merge(
                    $this->_permanentAttributes,
                    $this->addMediaAttributes($parameters[Processor::LIST_DATA], $this->getMediaAttributesMap())
                );
            } else {
                foreach ($this->filterAttributeCollection($this->getAttributeCollection()) as $attribute) {
                    $attrCodes[] = $attribute->getAttributeCode();
                }
            }

            self::$attrCodes = $attrCodes;
        }

        return self::$attrCodes;
    }

    /**
     * {@inheritDoc}
     */
    protected function updateDataWithCategoryColumns(&$dataRow, &$rowCategories, $productId)
    {
        if (!isset($rowCategories[$productId])) {
            return false;
        }
        $categories = [];
        $categoriesByStore = [];

        if (isset($this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator'])
            && $this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator']) {
            $categoryLevelsSeparator = $this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator'];
        } else {
            $categoryLevelsSeparator = '/';
        }

        if (isset($this->_parameters[Processor::BEHAVIOR_DATA]['categories_separator'])
            && $this->_parameters[Processor::BEHAVIOR_DATA]['categories_separator']) {
            $categoriesSeparator = $this->_parameters[Processor::BEHAVIOR_DATA]['categories_separator'];
        } else {
            $categoriesSeparator = Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
        }

        foreach ($rowCategories[$productId] as $categoryId) {
            if (!isset($this->_rootCategories[$categoryId])) {
                continue;
            }
            $categoryPath = $this->_rootCategories[$categoryId];
            $categoryByStorePath = $this->_rootCategories[$categoryId];
            if (isset($this->_categoriesByStore[$dataRow['store_id']][$categoryId])) {
                $categoryName = $this->prepareCategoryName(
                    $this->_categoriesByStore[$dataRow['store_id']][$categoryId]
                );
                $categoryByStorePath .= $categoryLevelsSeparator . $categoryName;
            }
            $categoriesByStore[] = $categoryByStorePath;
            if (isset($this->_categories[$categoryId])) {
                $categoryName = $this->prepareCategoryName($this->_categories[$categoryId]);
                $categoryPath .= $categoryLevelsSeparator . $categoryName;
            }
            $categories[] = $categoryPath;
        }
        $dataRow[self::COL_CATEGORY] = implode($categoriesSeparator, $categories);
        $dataRow[self::COL_CATEGORY_BY_STORE] = implode($categoriesSeparator, $categoriesByStore);

        return true;
    }

    /**
     * Prepare CategoryName
     *
     * @param string $name
     * @return string|string[]
     */
    protected function prepareCategoryName($name)
    {
        if (isset($this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator'])
            && $this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator']) {
            $categoryLevelsSeparator = $this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator'];
        } else {
            $categoryLevelsSeparator = '/';
        }
        $quotedDelimiterPosition = strpos(
            $name,
            '\\' . CategoryProcessor::DELIMITER_CATEGORY
        );
        $result = str_replace(
            CategoryProcessor::DELIMITER_CATEGORY,
            $categoryLevelsSeparator,
            $name
        );
        if ($quotedDelimiterPosition !== false) {
            $result = substr_replace(
                $result,
                '\\'.CategoryProcessor::DELIMITER_CATEGORY,
                $quotedDelimiterPosition,
                2
            );
        }
        return $result;
    }

    /**
     * @param $dataRow
     * @param $rowCategoriesPosition
     * @param $productId
     * @return bool
     */
    protected function updateDataWithCategoryPositionColumns(&$dataRow, &$rowCategoriesPosition, $productId)
    {
        if (isset($this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator'])
            && $this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator']) {
            $categoryLevelsSeparator = $this->_parameters[Processor::BEHAVIOR_DATA]['category_levels_separator'];
        } else {
            $categoryLevelsSeparator = '/';
        }

        if (isset($this->_parameters[Processor::BEHAVIOR_DATA]['categories_separator'])
            && $this->_parameters[Processor::BEHAVIOR_DATA]['categories_separator']) {
            $categoriesSeparator = $this->_parameters[Processor::BEHAVIOR_DATA]['categories_separator'];
        } else {
            $categoriesSeparator = Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
        }

        if (!isset($rowCategoriesPosition[$productId])) {
            return false;
        }
        $positions = [];
        foreach ($rowCategoriesPosition[$productId] as $categoryId => $position) {
            if (!isset($this->_rootCategories[$categoryId])) {
                continue;
            }
            $categoryPath = $this->_rootCategories[$categoryId];
            if (isset($this->_categories[$categoryId])) {
                $categoryName = $this->prepareCategoryName(
                    $this->_categoriesByStore[$dataRow['store_id']][$categoryId]
                );
                $categoryPath .= $categoryLevelsSeparator . $categoryName;
            }
            $positions[] = $categoryPath . ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $position;
        }
        $dataRow[self::COL_CATEGORY . '_position'] = implode($categoriesSeparator, $positions);
        unset($rowCategoriesPosition[$productId]);

        return true;
    }

    /**
     * @param $dataRow
     * @param $rowCategoryIds
     * @param $productId
     * @return bool
     */
    protected function updateDataWithCategoryIdsColumns(&$dataRow, &$rowCategoryIds, $productId)
    {
        if (!isset($rowCategoryIds[$productId])) {
            return false;
        }

        $category_ids = [];
        foreach ($rowCategoryIds[$productId] as $categoryId) {
            if (isset($this->_categories[$categoryId])) {
                $category_ids[] = $categoryId;
            }
        }

        $dataRow[self::COL_CATEGORY_IDS] = implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $category_ids);
        unset($rowCategoryIds[$productId]);

        return true;
    }

    /**
     * Filter by stores
     *
     * @return array
     */
    protected function getStores()
    {
        if (null === $this->stores) {
            $this->stores = [];
            if (isset($this->_parameters[Processor::BEHAVIOR_DATA]['store_ids'])
                && is_array($this->_parameters[Processor::BEHAVIOR_DATA]['store_ids'])
                && !in_array('0', $this->_parameters[Processor::BEHAVIOR_DATA]['store_ids'])
            ) {
                $storeIds = $this->_parameters[Processor::BEHAVIOR_DATA]['store_ids'];
                foreach ($this->_storeIdToCode as $id => $code) {
                    if (in_array($id, $storeIds)) {
                        $this->stores[$id] = $code;
                    }
                }
            } else {
                $this->stores = $this->_storeIdToCode;
            }
        }

        return $this->stores;
    }

    /**
     * Returns Swatch option data for Attribute Option Ids
     *
     * @param array $optionIds
     * @param int $attributeId
     *
     * @return array
     */
    protected function getSwatchesByOptionsId($optionIds, $attributeId)
    {
        if (!isset($this->cachedSwatchOptions[$attributeId]) || empty($this->cachedSwatchOptions[$attributeId])) {
            $this->cachedSwatchOptions[$attributeId] = [];
            $swatchCollection = $this->swatchCollectionFactory->create();
            $swatchCollection->addFilterByOptionsIds($optionIds);
            foreach ($swatchCollection as $item) {
                $this->cachedSwatchOptions[$attributeId][$item['option_id']] = $item->getData();
            }
        }

        return $this->cachedSwatchOptions[$attributeId];
    }

    /**
     * @return array
     */
    protected function getStoreWithCodes()
    {
        $stores = [];
        foreach ($this->_storeManager->getStores() as $store) {
            $stores[$store->getId()] = $store->getCode();
        }
        return $stores;
    }

    /**
     * @param AbstractAttribute $attribute
     *
     * @return array
     */
    public function getAttributeOptions(AbstractAttribute $attribute)
    {
        $options = [];

        if ($attribute->usesSource()) {
            // should attribute has index (option value) instead of a label?
            $index = in_array($attribute->getAttributeCode(), $this->_indexValueAttributes) ? 'value' : 'label';
            $stores = $this->getStoreWithCodes();
            $stores[0] = 'admin'; // We add admin store here for backward compatibility

            foreach ($stores as $id => $code) {
                // only default (admin) store values used
                $attribute->setStoreId($id);

                try {
                    foreach ($attribute->getSource()->getAllOptions(false) as $option) {
                        foreach (is_array($option['value']) ? $option['value'] : [$option] as $innerOption) {
                            if (strlen($innerOption['value'])) {
                                // skip ' -- Please Select -- ' option
                                /**
                                 * fix problem with ShipperHQ when $innerOption['value'] is float
                                 */
                                $options[$id][(string)$innerOption['value']] = (string)$innerOption[$index];
                            }
                        }
                    }

                    if ($this->swatchesHelperData->isTextSwatch($attribute)) {
                        $swatchOptionsIds = array_keys($options[$id]);
                        $swatchOption = $this->getSwatchesByOptionsId($swatchOptionsIds, $attribute->getAttributeId());
                        foreach ($swatchOption as $optionId => $optionValue) {
                            if ($optionValue['value'] !== '') {
                                $options[$id][$optionId] = (string)$optionValue['value'];
                            }
                        }
                    }
                } catch (Exception $e) {
                    $this->addLogWriteln($e->getMessage(), $this->getOutput(), 'error');
                }
            }
        }

        return $options;
    }

    /**
     * Retrieve entity field for export
     *
     * @return array
     * @throws LocalizedException
     */
    public function getFieldsForExport()
    {
        $stockItemRows = $this->fieldsCatalogInventory();
        $this->setHeaderColumns(1, $stockItemRows);
        $this->_headerColumns = $this->rowCustomizer->addHeaderColumns($this->_headerColumns);
        $this->_headerColumns = array_merge($this->_headerColumns, [self::COL_CATEGORY_BY_STORE]);
        $this->_headerColumns = array_merge($this->_headerColumns, [self::COL_CATEGORY . '_position']);
        $this->_headerColumns = array_merge($this->_headerColumns, [self::COL_CATEGORY_IDS]);

        $subOptions = [];
        if (isset($this->_attributeColFactory)) {
            $attributeCollection = $this->_attributeColFactory->create()->addVisibleFilter()
                ->setOrder('attribute_code', Collection::SORT_ORDER_ASC);
            /** @var \Magento\Eav\Model\Entity\Attribute $attribute */
            foreach ($attributeCollection as $attribute) {
                $subOptions[] = $attribute->getAttributeCode();
            }
            $this->_headerColumns = array_merge($this->_headerColumns, $subOptions);
        }

        sort($this->_headerColumns);
        return array_unique($this->_headerColumns);
    }

    /**
     * @return string
     */
    public function _getProductEntityLinkField()
    {
        return $this->getProductEntityLinkField();
    }
}