ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ/* global pluploadL10n, plupload, _wpPluploadSettings */ /** * @namespace wp */ window.wp = window.wp || {}; ( function( exports, $ ) { var Uploader; if ( typeof _wpPluploadSettings === 'undefined' ) { return; } /** * A WordPress uploader. * * The Plupload library provides cross-browser uploader UI integration. * This object bridges the Plupload API to integrate uploads into the * WordPress back end and the WordPress media experience. * * @class * @memberOf wp * @alias wp.Uploader * * @param {object} options The options passed to the new plupload instance. * @param {object} options.container The id of uploader container. * @param {object} options.browser The id of button to trigger the file select. * @param {object} options.dropzone The id of file drop target. * @param {object} options.plupload An object of parameters to pass to the plupload instance. * @param {object} options.params An object of parameters to pass to $_POST when uploading the file. * Extends this.plupload.multipart_params under the hood. */ Uploader = function( options ) { var self = this, isIE, // Not used, back-compat. elements = { container: 'container', browser: 'browse_button', dropzone: 'drop_element' }, tryAgainCount = {}, tryAgain, key, error, fileUploaded; this.supports = { upload: Uploader.browser.supported }; this.supported = this.supports.upload; if ( ! this.supported ) { return; } // Arguments to send to pluplad.Uploader(). // Use deep extend to ensure that multipart_params and other objects are cloned. this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults ); this.container = document.body; // Set default container. /* * Extend the instance with options. * * Use deep extend to allow options.plupload to override individual * default plupload keys. */ $.extend( true, this, options ); // Proxy all methods so this always refers to the current instance. for ( key in this ) { if ( typeof this[ key ] === 'function' ) { this[ key ] = $.proxy( this[ key ], this ); } } // Ensure all elements are jQuery elements and have id attributes, // then set the proper plupload arguments to the ids. for ( key in elements ) { if ( ! this[ key ] ) { continue; } this[ key ] = $( this[ key ] ).first(); if ( ! this[ key ].length ) { delete this[ key ]; continue; } if ( ! this[ key ].prop('id') ) { this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ ); } this.plupload[ elements[ key ] ] = this[ key ].prop('id'); } // If the uploader has neither a browse button nor a dropzone, bail. if ( ! ( this.browser && this.browser.length ) && ! ( this.dropzone && this.dropzone.length ) ) { return; } // Initialize the plupload instance. this.uploader = new plupload.Uploader( this.plupload ); delete this.plupload; // Set default params and remove this.params alias. this.param( this.params || {} ); delete this.params; /** * Attempt to create image sub-sizes when an image was uploaded successfully * but the server responded with HTTP 5xx error. * * @since 5.3.0 * * @param {string} message Error message. * @param {object} data Error data from Plupload. * @param {plupload.File} file File that was uploaded. */ tryAgain = function( message, data, file ) { var times, id; if ( ! data || ! data.responseHeaders ) { error( pluploadL10n.http_error_image, data, file, 'no-retry' ); return; } id = data.responseHeaders.match( /x-wp-upload-attachment-id:\s*(\d+)/i ); if ( id && id[1] ) { id = id[1]; } else { error( pluploadL10n.http_error_image, data, file, 'no-retry' ); return; } times = tryAgainCount[ file.id ]; if ( times && times > 4 ) { /* * The file may have been uploaded and attachment post created, * but post-processing and resizing failed... * Do a cleanup then tell the user to scale down the image and upload it again. */ $.ajax({ type: 'post', url: ajaxurl, dataType: 'json', data: { action: 'media-create-image-subsizes', _wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce, attachment_id: id, _wp_upload_failed_cleanup: true, } }); error( message, data, file, 'no-retry' ); return; } if ( ! times ) { tryAgainCount[ file.id ] = 1; } else { tryAgainCount[ file.id ] = ++times; } // Another request to try to create the missing image sub-sizes. $.ajax({ type: 'post', url: ajaxurl, dataType: 'json', data: { action: 'media-create-image-subsizes', _wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce, attachment_id: id, } }).done( function( response ) { if ( response.success ) { fileUploaded( self.uploader, file, response ); } else { if ( response.data && response.data.message ) { message = response.data.message; } error( message, data, file, 'no-retry' ); } }).fail( function( jqXHR ) { // If another HTTP 5xx error, try try again... if ( jqXHR.status >= 500 && jqXHR.status < 600 ) { tryAgain( message, data, file ); return; } error( message, data, file, 'no-retry' ); }); } /** * Custom error callback. * * Add a new error to the errors collection, so other modules can track * and display errors. @see wp.Uploader.errors. * * @param {string} message Error message. * @param {object} data Error data from Plupload. * @param {plupload.File} file File that was uploaded. * @param {string} retry Whether to try again to create image sub-sizes. Passing 'no-retry' will prevent it. */ error = function( message, data, file, retry ) { var isImage = file.type && file.type.indexOf( 'image/' ) === 0, status = data && data.status; // If the file is an image and the error is HTTP 5xx try to create sub-sizes again. if ( retry !== 'no-retry' && isImage && status >= 500 && status < 600 ) { tryAgain( message, data, file ); return; } if ( file.attachment ) { file.attachment.destroy(); } Uploader.errors.unshift({ message: message || pluploadL10n.default_error, data: data, file: file }); self.error( message, data, file ); }; /** * After a file is successfully uploaded, update its model. * * @param {plupload.Uploader} up Uploader instance. * @param {plupload.File} file File that was uploaded. * @param {Object} response Object with response properties. */ fileUploaded = function( up, file, response ) { var complete; // Remove the "uploading" UI elements. _.each( ['file','loaded','size','percent'], function( key ) { file.attachment.unset( key ); } ); file.attachment.set( _.extend( response.data, { uploading: false } ) ); wp.media.model.Attachment.get( response.data.id, file.attachment ); complete = Uploader.queue.all( function( attachment ) { return ! attachment.get( 'uploading' ); }); if ( complete ) { Uploader.queue.reset(); } self.success( file.attachment ); } /** * After the Uploader has been initialized, initialize some behaviors for the dropzone. * * @param {plupload.Uploader} uploader Uploader instance. */ this.uploader.bind( 'init', function( uploader ) { var timer, active, dragdrop, dropzone = self.dropzone; dragdrop = self.supports.dragdrop = uploader.features.dragdrop && ! Uploader.browser.mobile; // Generate drag/drop helper classes. if ( ! dropzone ) { return; } dropzone.toggleClass( 'supports-drag-drop', !! dragdrop ); if ( ! dragdrop ) { return dropzone.unbind('.wp-uploader'); } // 'dragenter' doesn't fire correctly, simulate it with a limited 'dragover'. dropzone.on( 'dragover.wp-uploader', function() { if ( timer ) { clearTimeout( timer ); } if ( active ) { return; } dropzone.trigger('dropzone:enter').addClass('drag-over'); active = true; }); dropzone.on('dragleave.wp-uploader, drop.wp-uploader', function() { /* * Using an instant timer prevents the drag-over class * from being quickly removed and re-added when elements * inside the dropzone are repositioned. * * @see https://core.trac.wordpress.org/ticket/21705 */ timer = setTimeout( function() { active = false; dropzone.trigger('dropzone:leave').removeClass('drag-over'); }, 0 ); }); self.ready = true; $(self).trigger( 'uploader:ready' ); }); this.uploader.bind( 'postinit', function( up ) { up.refresh(); self.init(); }); this.uploader.init(); if ( this.browser ) { this.browser.on( 'mouseenter', this.refresh ); } else { this.uploader.disableBrowse( true ); } $( self ).on( 'uploader:ready', function() { $( '.moxie-shim-html5 input[type="file"]' ) .attr( { tabIndex: '-1', 'aria-hidden': 'true' } ); } ); /** * After files were filtered and added to the queue, create a model for each. * * @param {plupload.Uploader} up Uploader instance. * @param {Array} files Array of file objects that were added to queue by the user. */ this.uploader.bind( 'FilesAdded', function( up, files ) { _.each( files, function( file ) { var attributes, image; // Ignore failed uploads. if ( plupload.FAILED === file.status ) { return; } if ( file.type === 'image/heic' && up.settings.heic_upload_error ) { // Show error but do not block uploading. Uploader.errors.unshift({ message: pluploadL10n.unsupported_image, data: {}, file: file }); } else if ( file.type === 'image/webp' && up.settings.webp_upload_error ) { // Disallow uploading of WebP images if the server cannot edit them. error( pluploadL10n.noneditable_image, {}, file, 'no-retry' ); up.removeFile( file ); return; } else if ( file.type === 'image/avif' && up.settings.avif_upload_error ) { // Disallow uploading of AVIF images if the server cannot edit them. error( pluploadL10n.noneditable_image, {}, file, 'no-retry' ); up.removeFile( file ); return; } // Generate attributes for a new `Attachment` model. attributes = _.extend({ file: file, uploading: true, date: new Date(), filename: file.name, menuOrder: 0, uploadedTo: wp.media.model.settings.post.id }, _.pick( file, 'loaded', 'size', 'percent' ) ); // Handle early mime type scanning for images. image = /(?:jpe?g|png|gif)$/i.exec( file.name ); // For images set the model's type and subtype attributes. if ( image ) { attributes.type = 'image'; // `jpeg`, `png` and `gif` are valid subtypes. // `jpg` is not, so map it to `jpeg`. attributes.subtype = ( 'jpg' === image[0] ) ? 'jpeg' : image[0]; } // Create a model for the attachment, and add it to the Upload queue collection // so listeners to the upload queue can track and display upload progress. file.attachment = wp.media.model.Attachment.create( attributes ); Uploader.queue.add( file.attachment ); self.added( file.attachment ); }); up.refresh(); up.start(); }); this.uploader.bind( 'UploadProgress', function( up, file ) { file.attachment.set( _.pick( file, 'loaded', 'percent' ) ); self.progress( file.attachment ); }); /** * After a file is successfully uploaded, update its model. * * @param {plupload.Uploader} up Uploader instance. * @param {plupload.File} file File that was uploaded. * @param {Object} response Object with response properties. * @return {mixed} */ this.uploader.bind( 'FileUploaded', function( up, file, response ) { try { response = JSON.parse( response.response ); } catch ( e ) { return error( pluploadL10n.default_error, e, file ); } if ( ! _.isObject( response ) || _.isUndefined( response.success ) ) { return error( pluploadL10n.default_error, null, file ); } else if ( ! response.success ) { return error( response.data && response.data.message, response.data, file ); } // Success. Update the UI with the new attachment. fileUploaded( up, file, response ); }); /** * When plupload surfaces an error, send it to the error handler. * * @param {plupload.Uploader} up Uploader instance. * @param {Object} pluploadError Contains code, message and sometimes file and other details. */ this.uploader.bind( 'Error', function( up, pluploadError ) { var message = pluploadL10n.default_error, key; // Check for plupload errors. for ( key in Uploader.errorMap ) { if ( pluploadError.code === plupload[ key ] ) { message = Uploader.errorMap[ key ]; if ( typeof message === 'function' ) { message = message( pluploadError.file, pluploadError ); } break; } } error( message, pluploadError, pluploadError.file ); up.refresh(); }); }; // Adds the 'defaults' and 'browser' properties. $.extend( Uploader, _wpPluploadSettings ); Uploader.uuid = 0; // Map Plupload error codes to user friendly error messages. Uploader.errorMap = { 'FAILED': pluploadL10n.upload_failed, 'FILE_EXTENSION_ERROR': pluploadL10n.invalid_filetype, 'IMAGE_FORMAT_ERROR': pluploadL10n.not_an_image, 'IMAGE_MEMORY_ERROR': pluploadL10n.image_memory_exceeded, 'IMAGE_DIMENSIONS_ERROR': pluploadL10n.image_dimensions_exceeded, 'GENERIC_ERROR': pluploadL10n.upload_failed, 'IO_ERROR': pluploadL10n.io_error, 'SECURITY_ERROR': pluploadL10n.security_error, 'FILE_SIZE_ERROR': function( file ) { return pluploadL10n.file_exceeds_size_limit.replace( '%s', file.name ); }, 'HTTP_ERROR': function( file ) { if ( file.type && file.type.indexOf( 'image/' ) === 0 ) { return pluploadL10n.http_error_image; } return pluploadL10n.http_error; }, }; $.extend( Uploader.prototype, /** @lends wp.Uploader.prototype */{ /** * Acts as a shortcut to extending the uploader's multipart_params object. * * param( key ) * Returns the value of the key. * * param( key, value ) * Sets the value of a key. * * param( map ) * Sets values for a map of data. */ param: function( key, value ) { if ( arguments.length === 1 && typeof key === 'string' ) { return this.uploader.settings.multipart_params[ key ]; } if ( arguments.length > 1 ) { this.uploader.settings.multipart_params[ key ] = value; } else { $.extend( this.uploader.settings.multipart_params, key ); } }, /** * Make a few internal event callbacks available on the wp.Uploader object * to change the Uploader internals if absolutely necessary. */ init: function() {}, error: function() {}, success: function() {}, added: function() {}, progress: function() {}, complete: function() {}, refresh: function() { var node, attached, container, id; if ( this.browser ) { node = this.browser[0]; // Check if the browser node is in the DOM. while ( node ) { if ( node === document.body ) { attached = true; break; } node = node.parentNode; } /* * If the browser node is not attached to the DOM, * use a temporary container to house it, as the browser button shims * require the button to exist in the DOM at all times. */ if ( ! attached ) { id = 'wp-uploader-browser-' + this.uploader.id; container = $( '#' + id ); if ( ! container.length ) { container = $('
').css({ position: 'fixed', top: '-1000px', left: '-1000px', height: 0, width: 0 }).attr( 'id', 'wp-uploader-browser-' + this.uploader.id ).appendTo('body'); } container.append( this.browser ); } } this.uploader.refresh(); } }); // Create a collection of attachments in the upload queue, // so that other modules can track and display upload progress. Uploader.queue = new wp.media.model.Attachments( [], { query: false }); // Create a collection to collect errors incurred while attempting upload. Uploader.errors = new Backbone.Collection(); exports.Uploader = Uploader; })( wp, jQuery );