ÿØÿà JFIF    ÿÛ „  ( %"1!%)+...383,7(-.+  -+++--++++---+-+-----+---------------+---+-++7-----ÿÀ  ß â" ÿÄ     ÿÄ H    !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ   ÿÄ %  !1AQa"23‘ÿÚ   ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6  öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ "Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷󲍷˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%ÌÁ²h´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ǍýʏTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆѪQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»& î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$˝Úsäÿ ÷Û #°xŸëí(l »ý3—¥5m! rt`†0~'j2(]S¦¦kv,ÚÇ l¦øJA£Šƒ J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉ䢍mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ­‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U  Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD é©¤&‡ïDbàÁôMÁ." ¤‡ú*õ'VŽ|¼´Úgllº¼klz[Æüï÷Aób‡Eÿ dÑ»Xx9ÃÜ£ÁT/`¼¸vI±Ýµ·Ë‚“G³þ*Ÿû´r|*}<¨îºœ @¦mÄ’M¹”.œ«Y–|6ÏU¤jç¥ÕÞqO ˜kDÆÁ¨5ÿ š;ÐЦ¦€GÙk \ –Þ=â¼=SͧµªS°ÚÍpÜãQűÀõ¬?ÃÁ1Ñ•õZà?hóœ€ L¦l{Y*K˜Ù›zc˜–ˆâ ø+¾ ­-Ök¥%ùEÜA'}ˆ><ÊIè“bpÍ/qÞâvoX€w,\úªò6Z[XdÒæ­@Ö—€$òJí#é>'°Ú ôª˜<)4ryÙ£|óAÅn5žêŸyÒäMÝ2{"}‰–¤l÷ûWX\l¾Á¸góÉOÔ /óñB¤f¸çñ[.P˜ZsÊË*ßT܈§QN¢’¡¨§V¼(Üù*eÕ“”5T¨‹Âê¥FŒã½Dü[8'Ò¥a…Ú¶k7a *•›¼'Ò·\8¨ª\@\õ¢¦íq+DÙrmÎ…_ªæ»ŠÓœ¡¯’Ré9MÅ×D™lælffc+ŒÑ,ý™ÿ ¯þǤ=Å’Á7µ÷ÚÛ/“Ü€ñýã¼àí¾ÕÑ+ƒ,uµMâÀÄbm:ÒÎPæ{˜Gz[ƒ¯«® KHà`ߨŠéí¯P8Aq.C‰ à€kòpj´kN¶qô€…Õ,ÜNŠª-­{Zö’æû44‰sŽè‰îVíRœÕm" 6?³D9¡ÇTíÅꋇ`4«¸ÝÁô ï’ýorqКÇZ«x4Žâéþuïf¹µö[P ,Q£éaX±`PÉÍZ ¸äYúg üAx ’6Lê‚xÝÓ*äQ  Ï’¨hÍ =²,6ï#rÃ<¯–£»ƒ‹,–ê•€ aÛsñ'%Æ"®ÛüìBᝠHÚ3ß°©$“XnœÖ’î2ËTeûìxîß ¦å¿çÉ ðK§þ{‘t‚Ϋ¬jéîZ[ ”š7L¥4VÚCE×]m¤Øy”ä4-dz£œ§¸x.*ãÊÊ b÷•h:©‡¦s`BTÁRû¾g⻩‹jø sF¢àJøFl‘È•Xᓁà~*j¯ +(ÚÕ6-£¯÷GŠØy‚<Ç’.F‹Hœw(+)ÜÜâÈzÄäT§FߘãÏ;DmVœ3Àu@mÚüXÝü•3B¨òÌÁÛ<·ÃÜ z,Ì@õÅ·d2]ü8s÷IôÞ¯^Ç9¢u„~ëAŸï4«M? K]­ÅàPl@s_ p:°¬ZR”´›JC[CS.h‹ƒïËœ«Æ]–÷ó‚wR×k7X‰k›‘´ù¦=¡«‰¨¨Â')—71ó’c‡Ðúµ `é.{§p¹ój\Ž{1h{o±Ý=áUÊïGÖŒõ–-BÄm+AZX¶¡ ïHðæ¥JmÙ;…䡟ˆ¦ ° äšiÉg«$üMk5¤L“’çÊvïâï ,=f“"íἊ5ô¬x6{ɏžID0e¸vçmi'︧ºð9$ò¹÷*£’9ÿ ²TÔ…×>JV¥}Œ}$p[bÔ®*[jzS*8 ”·T›Í–ñUîƒwo$áè=LT™ç—~ô·¤ÈÚ$榍q‰„+´kFm)ž‹©i–ËqÞŠ‰à¶ü( ‚•§ •°ò·‡#5ª•µÊ﯅¡X¨šÁ*F#TXJÊ ušJVÍ&=iÄs1‚3•'fý§5Ñ<=[íÞ­ PÚ;ѱÌ_~Ä££8rÞ ²w;’hDT°>ÈG¬8Á²ÚzŽ®ò®qZcqJêäÞ-ö[ܘbň±çb“ж31²n×iƒðÕ;1¶þÉ ªX‰,ßqÏ$>•î íZ¥Z 1{ç൵+ƒÕµ¥°T$§K]á»Ûï*·¤tMI’ÂZbŽÕiÒ˜}bÓ0£ª5›¨ [5Ž^ÝœWøÂÝh° ¢OWun£¤5 a2Z.G2³YL]jåtì”ä ÁÓ‘%"©<Ôúʰsº UZvä‡ÄiÆÒM .÷V·™ø#kèýiíÌ–ª)µT[)BˆõÑ xB¾B€ÖT¨.¥~ð@VĶr#¸ü*åZNDŽH;âi ],©£öØpù(šºãö¼T.uCê•4@ÿ GÕÛ)Cx›®0ø#:ÏðFÒbR\(€€Ä®fã4Þ‰Fä¯HXƒÅ,†öEÑÔÜ]Öv²?tLÃvBY£ú6Êu5ÅAQ³1‘’¬x–HŒÐ‡ ^ ¸KwJôÖŽ5×CÚ¨vÜ«/B0$×k°=ðbÇ(Ï)w±A†Á† 11Í=èQšµ626ŒÜ/`G«µ<}—-Ö7KEHÈÉðóȤmݱû±·ø«Snmá=“䫚mݱŸ¡¶~ó·“äUóJæúòB|E LêŽy´jDÔ$G¢þÐñ7óR8ýÒ…Ç› WVe#·Ÿ p·Fx~•ݤF÷0Èÿ K¯æS<6’¡WШ; ´ÿ ¥Êø\Òuî†åÝ–VNœkÒ7oòX¨Á­Ø÷FÎÑä±g÷ÿ M~Çî=p,X´ ÝÌÚÅ‹’ÃjÖ.ØöÏñ qïQ¤ÓZE†° =6·]܈ s¸>v•Ž^Ý\wq9r‰Î\¸¡kURÒ$­*‹Nq?Þª*!sŠÆ:TU_u±T+øX¡ ®¹¡,ÄâÃBTsÜ$Ø›4m椴zÜK]’’›Pƒ @€#â˜`é¹=I‡fiV•Ôî“nRm+µFPOhÍ0B£ €+¬5c v•:P'ÒyÎ ‰V~‚Ó†ÖuókDoh$å\*ö%Ю=£«…aȼ½÷Û.-½VŒŠ¼'lyî±1¬3ó#ÞE¿ÔS¤gV£m›=§\û"—WU¤ÚǼÿ ÂnÁGŒÃ ‚õN D³õNÚíŒÕ;HôyÄÈ©P¹Ä{:?R‘Ô¨âF÷ø£bÅó® JS|‚R÷ivýáâ€Æé¡è³´IئÑT!§˜•ت‚¬â@q€wnïCWÄ@JU€ê¯m6]Ï:£âx'+ÒðXvÓ¦Úm=–´7œ $ì“B£~p%ÕŸUþ« N@¼üï~w˜ñø5®—'Ôe»¤5ã//€ž~‰Tþ›Å7•#¤× Íö pÄ$ùeåì*«ÓŠEØWEÈsßg ¦ûvžSsLpºÊW–âµEWöˬH; ™!CYõZ ÃÄf æ#1W. \uWâ\,\Çf j’<qTbên›Î[vxx£ë 'ö¨1›˜ÀM¼Pÿ H)ƒêêŒA7s,|F“ 꺸k³9Ìö*ç®;Ö!Ö$Eiž•¹ÒÚ†ýóéÝû¾ÕS®ó$’NÝäŸz¤5r¦ãÄÃD÷Üø!°ø‡Ô&@m™Ì^Ãä­d q5Lnÿ N;.6½·N|#ä"1Nƒx“ã<3('&ñßt  ~ªu”1Tb㫨9ê–›–bìd$ߣ=#ÕãÒmU¯eí$EFù5ýYô櫨æì™Ç—±ssM]·á¿0ÕåJRÓªîiƒ+O58ÖñªŠÒx" \µâá¨i’¤i —Ö ” M+M¤ë9‚‰A¦°Qõ¾ßøK~¼Ã‘g…Ö´~÷Ï[3GUœÒ½#…kàÔ®Ò”‰³·dWV‰IP‰Ú8u¹”E ÖqLj¾êÕCBš{A^Âß;–¨`¯¬ìö ˼ ×tìø.tƐm*n¨y4o&Àx¥n¦×î‡aupáÛj8¿m›è¶ã!o½;ß0y^ý×^EÑ¿ÒjzŒ­)vÚÑnÄL …^ªô× ‡—‚3k Îý­hï]içå–îÏ*÷ñþ»Ô CÒjøjÍznˆ´ ¹#b'Fô‹ ‰v¥'’à'T´ƒHýÍ%M‰ ƒ&ÆÇŒï1 ‘ –Þ ‰i¬s žR-Ÿ kЬá¬7:þ 0ŒÅÒÕ/aÙ¬ÃÝ#Úøœ ©aiVc‰. ¹¦ãµ” ›Yg¦›ÆÎýº°f³7ƒhá·¸­}&D9¡ÂsÉÙÞèŠõØàC™¨ñbFC|´Ü(ŸƒÚÒ-%»'a Ì¿)ËÇn¿úÿ ÞŽX…4ÊÅH^ôΑí@ù¹Eh¶“L8Çjù ¼ÎåVªóR©Ï5uà V4lZß®=€xÖŸ–ÑÈ ÷”¨°¾__yM1tÉ?uÆþIkÄgæ@þ[¢†°XÃJ£j·:nkÅ¢u ‘}âGzö­/IµèЬ¼48q¦F°ŽR¼=ûì{´¯RýicS ÕÛ íNtÍÙï£,w4rêì®»~x(©Uñ§#Ñ&œÕ¤>ÎåÍÓ9’Ö{9eV­[Öjâ²ãu]˜å2›qÑšÕJç0€sÄ|Êëè0튔bÁ>“{×_F`Ø©ºê:µä,v¤ðfc1±"«ÔÍän1#=· Âøv~H½ÐßA¾¿Ü€Óš]Õ; I¾÷ç‚Qi†î¹9ywÔKG˜áñ zQY—§ÃÕZ07§X‚ Áh;ÁM)iÌCH-¯T‘ë|A0{Ò½LÚ–TâÖkÜ’dÀ“rmm»”جPF³ÖcbE§T€ÒxKºû’Ó®7±²(\4ŽÃ¸Uu@j™yĵ;³µ!Á¢b.W¤=mõ´êµK k ¸K^ÜÛ#p*Ü14qkZç5ïë †°5Ï%ÍÛ<Õ¤×Ô¥ê†C Õ´¼ú$ƒÖ“”]Ù¬qÞÚ[4©ý!ûÏ—Áb쳐XµA¬â~`›Çr¸8ìùÝ䫦<>ä÷«?xs´ÇÑ /á;¹øüÊÈÙà{"@Žïzâ¬[âß‚ U_<ÇŸ½4èN˜ú61®qŠu ¦þF£»äJ_ˆÙÎ~ ÞAã–݄ϗrŠD;xTž‘ô`É«…suãO`?³à™ô Lý#Íc5öoæØ‚y´´÷«ZR§<&JÇ+éâô´€i!Àˆ0æAoàðLèÖ-2ŸõW.’t^–(KÁmHµV@xÜÇy®Ñø­â^:Ú3w· 7½¹°ñ¸â¹®:',«Mœ—n­Á+Ãbš LÈ‘ÄnRÓÅœ%¦²‰¨ùQ:¤f‚ "PÕtô¸…cæl…&˜Ú˜Ôkv‹ž+vŠ,=¢v­6—Xy*¥t£«<™:“aîϲ=¦6rO]XI¿Œ÷¤zÚ­›¶ 6÷”w\d ü~v®ˆÌk«^m<ÿ ¢‰Õ\)ùºŽ;… lîÙÅEŠ®cѾ@vnMÏ,¼“ñ•ŽBxðÃzãÇç%3ˆ"}Ù•Åî> BÉú;Ò]V+P˜F_´ßé> Øše|ï‡ÄOmFæÇ ãqÞ$/xÐx­z`ï9"œÜij‚!7.\Td…9M‡•iŽ‹¾‘50ÞŽn¥ß4ÉôO ¹*í^QêËÜÇÌ8=ާs‰'ÂëÙ«á%Pú[O †ÅP¯Vsް.‰,kc¶ ¬A9n˜XÎ-ÞšN["¹QÕ‰ƒMýÁߺXJæÍaLj¾×Ãmã¾ãÚ uñÒþåQô¦¥ /ÄUx:‚ÍÜ’ Đ©ØÝ3V¨‰ÕnÐ6ó*óúK­«…c ¯U òhsý­jóÔj#,ímŒRµ«lbïUTŒÑ8†Ä0œÏr`ð¡¬É Ї ë"À² ™ 6¥ f¶ ¢ÚoܱԷ-<Àî)†a¶ž'Ú»¨TXqØæ¶÷YÄHy˜9ÈIW­YÀuMFë ºÏ’AqÌ4·/Ú †ô'i$øä­=Ä Ý|öK×40è|È6p‘0§)o¥ctî§H+CA-“ xØ|ÐXАç l8íºð3Ø:³¤¬KX¯UÿÙ 'batchAccess', 'language_id' => 'batchLanguage', 'tag' => 'batchTag', ); /** * The context used for the associations table * * @var string * @since 3.4.4 */ protected $associationsContext = null; /** * A flag to indicate if member variables for batch actions (and saveorder) have been initialized * * @var object * @since 3.8.2 */ protected $batchSet = null; /** * The user performing the actions (re-usable in batch methods & saveorder(), initialized via initBatch()) * * @var object * @since 3.8.2 */ protected $user = null; /** * A JTable instance (of appropropriate type) to manage the DB records (re-usable in batch methods & saveorder(), initialized via initBatch()) * * @var object * @since 3.8.2 */ protected $table = null; /** * The class name of the JTable instance managing the DB records (re-usable in batch methods & saveorder(), initialized via initBatch()) * * @var string * @since 3.8.2 */ protected $tableClassName = null; /** * UCM Type corresponding to the current model class (re-usable in batch action methods, initialized via initBatch()) * * @var object * @since 3.8.2 */ protected $contentType = null; /** * DB data of UCM Type corresponding to the current model class (re-usable in batch action methods, initialized via initBatch()) * * @var object * @since 3.8.2 */ protected $type = null; /** * A tags Observer instance to handle assigned tags (re-usable in batch action methods, initialized via initBatch()) * * @var object * @since 3.8.2 */ protected $tagsObserver = null; /** * Constructor. * * @param array $config An optional associative array of configuration settings. * * @see \JModelLegacy * @since 1.6 */ public function __construct($config = array()) { parent::__construct($config); if (isset($config['event_after_delete'])) { $this->event_after_delete = $config['event_after_delete']; } elseif (empty($this->event_after_delete)) { $this->event_after_delete = 'onContentAfterDelete'; } if (isset($config['event_after_save'])) { $this->event_after_save = $config['event_after_save']; } elseif (empty($this->event_after_save)) { $this->event_after_save = 'onContentAfterSave'; } if (isset($config['event_before_delete'])) { $this->event_before_delete = $config['event_before_delete']; } elseif (empty($this->event_before_delete)) { $this->event_before_delete = 'onContentBeforeDelete'; } if (isset($config['event_before_save'])) { $this->event_before_save = $config['event_before_save']; } elseif (empty($this->event_before_save)) { $this->event_before_save = 'onContentBeforeSave'; } if (isset($config['event_change_state'])) { $this->event_change_state = $config['event_change_state']; } elseif (empty($this->event_change_state)) { $this->event_change_state = 'onContentChangeState'; } $config['events_map'] = isset($config['events_map']) ? $config['events_map'] : array(); $this->events_map = array_merge( array( 'delete' => 'content', 'save' => 'content', 'change_state' => 'content', 'validate' => 'content', ), $config['events_map'] ); // Guess the \JText message prefix. Defaults to the option. if (isset($config['text_prefix'])) { $this->text_prefix = strtoupper($config['text_prefix']); } elseif (empty($this->text_prefix)) { $this->text_prefix = strtoupper($this->option); } } /** * Method to perform batch operations on an item or a set of items. * * @param array $commands An array of commands to perform. * @param array $pks An array of item ids. * @param array $contexts An array of item contexts. * * @return boolean Returns true on success, false on failure. * * @since 1.7 */ public function batch($commands, $pks, $contexts) { // Sanitize ids. $pks = array_unique($pks); $pks = ArrayHelper::toInteger($pks); // Remove any values of zero. if (array_search(0, $pks, true)) { unset($pks[array_search(0, $pks, true)]); } if (empty($pks)) { $this->setError(\JText::_('JGLOBAL_NO_ITEM_SELECTED')); return false; } $done = false; // Initialize re-usable member properties $this->initBatch(); if ($this->batch_copymove && !empty($commands[$this->batch_copymove])) { $cmd = ArrayHelper::getValue($commands, 'move_copy', 'c'); if ($cmd === 'c') { $result = $this->batchCopy($commands[$this->batch_copymove], $pks, $contexts); if (is_array($result)) { foreach ($result as $old => $new) { $contexts[$new] = $contexts[$old]; } $pks = array_values($result); } else { return false; } } elseif ($cmd === 'm' && !$this->batchMove($commands[$this->batch_copymove], $pks, $contexts)) { return false; } $done = true; } foreach ($this->batch_commands as $identifier => $command) { if (!empty($commands[$identifier])) { if (!$this->$command($commands[$identifier], $pks, $contexts)) { return false; } $done = true; } } if (!$done) { $this->setError(\JText::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION')); return false; } // Clear the cache $this->cleanCache(); return true; } /** * Batch access level changes for a group of rows. * * @param integer $value The new value matching an Asset Group ID. * @param array $pks An array of row IDs. * @param array $contexts An array of item contexts. * * @return boolean True if successful, false otherwise and internal error is set. * * @since 1.7 */ protected function batchAccess($value, $pks, $contexts) { // Initialize re-usable member properties, and re-usable local variables $this->initBatch(); foreach ($pks as $pk) { if ($this->user->authorise('core.edit', $contexts[$pk])) { $this->table->reset(); $this->table->load($pk); $this->table->access = (int) $value; if (!empty($this->type)) { $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table); } if (!$this->table->store()) { $this->setError($this->table->getError()); return false; } } else { $this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT')); return false; } } // Clean the cache $this->cleanCache(); return true; } /** * Batch copy items to a new category or current. * * @param integer $value The new category. * @param array $pks An array of row IDs. * @param array $contexts An array of item contexts. * * @return array|boolean An array of new IDs on success, boolean false on failure. * * @since 1.7 */ protected function batchCopy($value, $pks, $contexts) { // Initialize re-usable member properties, and re-usable local variables $this->initBatch(); $categoryId = $value; if (!$this->checkCategoryId($categoryId)) { return false; } $newIds = array(); // Parent exists so let's proceed while (!empty($pks)) { // Pop the first ID off the stack $pk = array_shift($pks); $this->table->reset(); // Check that the row actually exists if (!$this->table->load($pk)) { if ($error = $this->table->getError()) { // Fatal error $this->setError($error); return false; } else { // Not fatal error $this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk)); continue; } } $this->generateTitle($categoryId, $this->table); // Reset the ID because we are making a copy $this->table->id = 0; // Unpublish because we are making a copy if (isset($this->table->published)) { $this->table->published = 0; } elseif (isset($this->table->state)) { $this->table->state = 0; } $hitsAlias = $this->table->getColumnAlias('hits'); if (isset($this->table->$hitsAlias)) { $this->table->$hitsAlias = 0; } // New category ID $this->table->catid = $categoryId; // TODO: Deal with ordering? // $this->table->ordering = 1; // Check the row. if (!$this->table->check()) { $this->setError($this->table->getError()); return false; } if (!empty($this->type)) { $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table); } // Store the row. if (!$this->table->store()) { $this->setError($this->table->getError()); return false; } // Get the new item ID $newId = $this->table->get('id'); // Add the new ID to the array $newIds[$pk] = $newId; } // Clean the cache $this->cleanCache(); return $newIds; } /** * Batch language changes for a group of rows. * * @param string $value The new value matching a language. * @param array $pks An array of row IDs. * @param array $contexts An array of item contexts. * * @return boolean True if successful, false otherwise and internal error is set. * * @since 2.5 */ protected function batchLanguage($value, $pks, $contexts) { // Initialize re-usable member properties, and re-usable local variables $this->initBatch(); foreach ($pks as $pk) { if ($this->user->authorise('core.edit', $contexts[$pk])) { $this->table->reset(); $this->table->load($pk); $this->table->language = $value; if (!empty($this->type)) { $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table); } if (!$this->table->store()) { $this->setError($this->table->getError()); return false; } } else { $this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT')); return false; } } // Clean the cache $this->cleanCache(); return true; } /** * Batch move items to a new category * * @param integer $value The new category ID. * @param array $pks An array of row IDs. * @param array $contexts An array of item contexts. * * @return boolean True if successful, false otherwise and internal error is set. * * @since 1.7 */ protected function batchMove($value, $pks, $contexts) { // Initialize re-usable member properties, and re-usable local variables $this->initBatch(); $categoryId = (int) $value; if (!$this->checkCategoryId($categoryId)) { return false; } // Parent exists so we proceed foreach ($pks as $pk) { if (!$this->user->authorise('core.edit', $contexts[$pk])) { $this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT')); return false; } // Check that the row actually exists if (!$this->table->load($pk)) { if ($error = $this->table->getError()) { // Fatal error $this->setError($error); return false; } else { // Not fatal error $this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk)); continue; } } // Set the new category ID $this->table->catid = $categoryId; // Check the row. if (!$this->table->check()) { $this->setError($this->table->getError()); return false; } if (!empty($this->type)) { $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table); } // Store the row. if (!$this->table->store()) { $this->setError($this->table->getError()); return false; } } // Clean the cache $this->cleanCache(); return true; } /** * Batch tag a list of item. * * @param integer $value The value of the new tag. * @param array $pks An array of row IDs. * @param array $contexts An array of item contexts. * * @return boolean True if successful, false otherwise and internal error is set. * * @since 3.1 */ protected function batchTag($value, $pks, $contexts) { // Initialize re-usable member properties, and re-usable local variables $this->initBatch(); $tags = array($value); foreach ($pks as $pk) { if ($this->user->authorise('core.edit', $contexts[$pk])) { $this->table->reset(); $this->table->load($pk); // Add new tags, keeping existing ones $result = $this->tagsObserver->setNewTags($tags, false); if (!$result) { $this->setError($this->table->getError()); return false; } } else { $this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT')); return false; } } // Clean the cache $this->cleanCache(); return true; } /** * Method to test whether a record can be deleted. * * @param object $record A record object. * * @return boolean True if allowed to delete the record. Defaults to the permission for the component. * * @since 1.6 */ protected function canDelete($record) { return \JFactory::getUser()->authorise('core.delete', $this->option); } /** * Method to test whether a record can have its state changed. * * @param object $record A record object. * * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component. * * @since 1.6 */ protected function canEditState($record) { return \JFactory::getUser()->authorise('core.edit.state', $this->option); } /** * Method override to check-in a record or an array of record * * @param mixed $pks The ID of the primary key or an array of IDs * * @return integer|boolean Boolean false if there is an error, otherwise the count of records checked in. * * @since 1.6 */ public function checkin($pks = array()) { $pks = (array) $pks; $table = $this->getTable(); $count = 0; if (empty($pks)) { $pks = array((int) $this->getState($this->getName() . '.id')); } $checkedOutField = $table->getColumnAlias('checked_out'); // Check in all items. foreach ($pks as $pk) { if ($table->load($pk)) { if ($table->{$checkedOutField} > 0) { if (!parent::checkin($pk)) { return false; } $count++; } } else { $this->setError($table->getError()); return false; } } return $count; } /** * Method override to check-out a record. * * @param integer $pk The ID of the primary key. * * @return boolean True if successful, false if an error occurs. * * @since 1.6 */ public function checkout($pk = null) { $pk = (!empty($pk)) ? $pk : (int) $this->getState($this->getName() . '.id'); return parent::checkout($pk); } /** * Method to delete one or more records. * * @param array &$pks An array of record primary keys. * * @return boolean True if successful, false if an error occurs. * * @since 1.6 */ public function delete(&$pks) { $dispatcher = \JEventDispatcher::getInstance(); $pks = (array) $pks; $table = $this->getTable(); // Include the plugins for the delete events. \JPluginHelper::importPlugin($this->events_map['delete']); // Iterate the items to delete each one. foreach ($pks as $i => $pk) { if ($table->load($pk)) { if ($this->canDelete($table)) { $context = $this->option . '.' . $this->name; // Trigger the before delete event. $result = $dispatcher->trigger($this->event_before_delete, array($context, $table)); if (in_array(false, $result, true)) { $this->setError($table->getError()); return false; } // Multilanguage: if associated, delete the item in the _associations table if ($this->associationsContext && \JLanguageAssociations::isEnabled()) { $db = $this->getDbo(); $query = $db->getQuery(true) ->select('COUNT(*) as count, ' . $db->quoteName('as1.key')) ->from($db->quoteName('#__associations') . ' AS as1') ->join('LEFT', $db->quoteName('#__associations') . ' AS as2 ON ' . $db->quoteName('as1.key') . ' = ' . $db->quoteName('as2.key')) ->where($db->quoteName('as1.context') . ' = ' . $db->quote($this->associationsContext)) ->where($db->quoteName('as1.id') . ' = ' . (int) $pk) ->group($db->quoteName('as1.key')); $db->setQuery($query); $row = $db->loadAssoc(); if (!empty($row['count'])) { $query = $db->getQuery(true) ->delete($db->quoteName('#__associations')) ->where($db->quoteName('context') . ' = ' . $db->quote($this->associationsContext)) ->where($db->quoteName('key') . ' = ' . $db->quote($row['key'])); if ($row['count'] > 2) { $query->where($db->quoteName('id') . ' = ' . (int) $pk); } $db->setQuery($query); $db->execute(); } } if (!$table->delete($pk)) { $this->setError($table->getError()); return false; } // Trigger the after event. $dispatcher->trigger($this->event_after_delete, array($context, $table)); } else { // Prune items that you can't change. unset($pks[$i]); $error = $this->getError(); if ($error) { \JLog::add($error, \JLog::WARNING, 'jerror'); return false; } else { \JLog::add(\JText::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), \JLog::WARNING, 'jerror'); return false; } } } else { $this->setError($table->getError()); return false; } } // Clear the component's cache $this->cleanCache(); return true; } /** * Method to change the title & alias. * * @param integer $category_id The id of the category. * @param string $alias The alias. * @param string $title The title. * * @return array Contains the modified title and alias. * * @since 1.7 */ protected function generateNewTitle($category_id, $alias, $title) { // Alter the title & alias $table = $this->getTable(); while ($table->load(array('alias' => $alias, 'catid' => $category_id))) { $title = StringHelper::increment($title); $alias = StringHelper::increment($alias, 'dash'); } return array($title, $alias); } /** * Method to get a single record. * * @param integer $pk The id of the primary key. * * @return \JObject|boolean Object on success, false on failure. * * @since 1.6 */ public function getItem($pk = null) { $pk = (!empty($pk)) ? $pk : (int) $this->getState($this->getName() . '.id'); $table = $this->getTable(); if ($pk > 0) { // Attempt to load the row. $return = $table->load($pk); // Check for a table object error. if ($return === false && $table->getError()) { $this->setError($table->getError()); return false; } } // Convert to the \JObject before adding other data. $properties = $table->getProperties(1); $item = ArrayHelper::toObject($properties, '\JObject'); if (property_exists($item, 'params')) { $registry = new Registry($item->params); $item->params = $registry->toArray(); } return $item; } /** * A protected method to get a set of ordering conditions. * * @param \JTable $table A \JTable object. * * @return array An array of conditions to add to ordering queries. * * @since 1.6 */ protected function getReorderConditions($table) { return array(); } /** * Stock method to auto-populate the model state. * * @return void * * @since 1.6 */ protected function populateState() { $table = $this->getTable(); $key = $table->getKeyName(); // Get the pk of the record from the request. $pk = \JFactory::getApplication()->input->getInt($key); $this->setState($this->getName() . '.id', $pk); // Load the parameters. $value = \JComponentHelper::getParams($this->option); $this->setState('params', $value); } /** * Prepare and sanitise the table data prior to saving. * * @param \JTable $table A reference to a \JTable object. * * @return void * * @since 1.6 */ protected function prepareTable($table) { // Derived class will provide its own implementation if required. } /** * Method to change the published state of one or more records. * * @param array &$pks A list of the primary keys to change. * @param integer $value The value of the published state. * * @return boolean True on success. * * @since 1.6 */ public function publish(&$pks, $value = 1) { $dispatcher = \JEventDispatcher::getInstance(); $user = \JFactory::getUser(); $table = $this->getTable(); $pks = (array) $pks; // Include the plugins for the change of state event. \JPluginHelper::importPlugin($this->events_map['change_state']); // Access checks. foreach ($pks as $i => $pk) { $table->reset(); if ($table->load($pk)) { if (!$this->canEditState($table)) { // Prune items that you can't change. unset($pks[$i]); \JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), \JLog::WARNING, 'jerror'); return false; } // If the table is checked out by another user, drop it and report to the user trying to change its state. if (property_exists($table, 'checked_out') && $table->checked_out && ($table->checked_out != $user->id)) { \JLog::add(\JText::_('JLIB_APPLICATION_ERROR_CHECKIN_USER_MISMATCH'), \JLog::WARNING, 'jerror'); // Prune items that you can't change. unset($pks[$i]); return false; } } } // Attempt to change the state of the records. if (!$table->publish($pks, $value, $user->get('id'))) { $this->setError($table->getError()); return false; } $context = $this->option . '.' . $this->name; // Trigger the change state event. $result = $dispatcher->trigger($this->event_change_state, array($context, $pks, $value)); if (in_array(false, $result, true)) { $this->setError($table->getError()); return false; } // Clear the component's cache $this->cleanCache(); return true; } /** * Method to adjust the ordering of a row. * * Returns NULL if the user did not have edit * privileges for any of the selected primary keys. * * @param integer $pks The ID of the primary key to move. * @param integer $delta Increment, usually +1 or -1 * * @return boolean|null False on failure or error, true on success, null if the $pk is empty (no items selected). * * @since 1.6 */ public function reorder($pks, $delta = 0) { $table = $this->getTable(); $pks = (array) $pks; $result = true; $allowed = true; foreach ($pks as $i => $pk) { $table->reset(); if ($table->load($pk) && $this->checkout($pk)) { // Access checks. if (!$this->canEditState($table)) { // Prune items that you can't change. unset($pks[$i]); $this->checkin($pk); \JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), \JLog::WARNING, 'jerror'); $allowed = false; continue; } $where = $this->getReorderConditions($table); if (!$table->move($delta, $where)) { $this->setError($table->getError()); unset($pks[$i]); $result = false; } $this->checkin($pk); } else { $this->setError($table->getError()); unset($pks[$i]); $result = false; } } if ($allowed === false && empty($pks)) { $result = null; } // Clear the component's cache if ($result == true) { $this->cleanCache(); } return $result; } /** * Method to save the form data. * * @param array $data The form data. * * @return boolean True on success, False on error. * * @since 1.6 */ public function save($data) { $dispatcher = \JEventDispatcher::getInstance(); $table = $this->getTable(); $context = $this->option . '.' . $this->name; if (!empty($data['tags']) && $data['tags'][0] != '') { $table->newTags = $data['tags']; } $key = $table->getKeyName(); $pk = (!empty($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id'); $isNew = true; // Include the plugins for the save events. \JPluginHelper::importPlugin($this->events_map['save']); // Allow an exception to be thrown. try { // Load the row if saving an existing record. if ($pk > 0) { $table->load($pk); $isNew = false; } // Bind the data. if (!$table->bind($data)) { $this->setError($table->getError()); return false; } // Prepare the row for saving $this->prepareTable($table); // Check the data. if (!$table->check()) { $this->setError($table->getError()); return false; } // Trigger the before save event. $result = $dispatcher->trigger($this->event_before_save, array($context, $table, $isNew, $data)); if (in_array(false, $result, true)) { $this->setError($table->getError()); return false; } // Store the data. if (!$table->store()) { $this->setError($table->getError()); return false; } // Clean the cache. $this->cleanCache(); // Trigger the after save event. $dispatcher->trigger($this->event_after_save, array($context, $table, $isNew, $data)); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } if (isset($table->$key)) { $this->setState($this->getName() . '.id', $table->$key); } $this->setState($this->getName() . '.new', $isNew); if ($this->associationsContext && \JLanguageAssociations::isEnabled() && !empty($data['associations'])) { $associations = $data['associations']; // Unset any invalid associations $associations = ArrayHelper::toInteger($associations); // Unset any invalid associations foreach ($associations as $tag => $id) { if (!$id) { unset($associations[$tag]); } } // Show a warning if the item isn't assigned to a language but we have associations. if ($associations && $table->language === '*') { \JFactory::getApplication()->enqueueMessage( \JText::_(strtoupper($this->option) . '_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'warning' ); } // Get associationskey for edited item $db = $this->getDbo(); $query = $db->getQuery(true) ->select($db->qn('key')) ->from($db->qn('#__associations')) ->where($db->qn('context') . ' = ' . $db->quote($this->associationsContext)) ->where($db->qn('id') . ' = ' . (int) $table->$key); $db->setQuery($query); $old_key = $db->loadResult(); // Deleting old associations for the associated items $query = $db->getQuery(true) ->delete($db->qn('#__associations')) ->where($db->qn('context') . ' = ' . $db->quote($this->associationsContext)); if ($associations) { $query->where('(' . $db->qn('id') . ' IN (' . implode(',', $associations) . ') OR ' . $db->qn('key') . ' = ' . $db->q($old_key) . ')'); } else { $query->where($db->qn('key') . ' = ' . $db->q($old_key)); } $db->setQuery($query); $db->execute(); // Adding self to the association if ($table->language !== '*') { $associations[$table->language] = (int) $table->$key; } if (count($associations) > 1) { // Adding new association for these items $key = md5(json_encode($associations)); $query = $db->getQuery(true) ->insert('#__associations'); foreach ($associations as $id) { $query->values(((int) $id) . ',' . $db->quote($this->associationsContext) . ',' . $db->quote($key)); } $db->setQuery($query); $db->execute(); } } return true; } /** * Saves the manually set order of records. * * @param array $pks An array of primary key ids. * @param integer $order +1 or -1 * * @return boolean|\JException Boolean true on success, false on failure, or \JException if no items are selected * * @since 1.6 */ public function saveorder($pks = array(), $order = null) { // Initialize re-usable member properties $this->initBatch(); $conditions = array(); if (empty($pks)) { return \JError::raiseWarning(500, \JText::_($this->text_prefix . '_ERROR_NO_ITEMS_SELECTED')); } $orderingField = $this->table->getColumnAlias('ordering'); // Update ordering values foreach ($pks as $i => $pk) { $this->table->load((int) $pk); // Access checks. if (!$this->canEditState($this->table)) { // Prune items that you can't change. unset($pks[$i]); \JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), \JLog::WARNING, 'jerror'); } elseif ($this->table->$orderingField != $order[$i]) { $this->table->$orderingField = $order[$i]; if ($this->type) { $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table); } if (!$this->table->store()) { $this->setError($this->table->getError()); return false; } // Remember to reorder within position and client_id $condition = $this->getReorderConditions($this->table); $found = false; foreach ($conditions as $cond) { if ($cond[1] == $condition) { $found = true; break; } } if (!$found) { $key = $this->table->getKeyName(); $conditions[] = array($this->table->$key, $condition); } } } // Execute reorder for each category. foreach ($conditions as $cond) { $this->table->load($cond[0]); $this->table->reorder($cond[1]); } // Clear the component's cache $this->cleanCache(); return true; } /** * Method to create a tags helper to ensure proper management of tags * * @param \JTableObserverTags $tagsObserver The tags observer for this table * @param \JUcmType $type The type for the table being processed * @param integer $pk Primary key of the item bing processed * @param string $typeAlias The type alias for this table * @param \JTable $table The \JTable object * * @return void * * @since 3.2 */ public function createTagsHelper($tagsObserver, $type, $pk, $typeAlias, $table) { if (!empty($tagsObserver) && !empty($type)) { $table->tagsHelper = new \JHelperTags; $table->tagsHelper->typeAlias = $typeAlias; $table->tagsHelper->tags = explode(',', $table->tagsHelper->getTagIds($pk, $typeAlias)); } } /** * Method to check the validity of the category ID for batch copy and move * * @param integer $categoryId The category ID to check * * @return boolean * * @since 3.2 */ protected function checkCategoryId($categoryId) { // Check that the category exists if ($categoryId) { $categoryTable = \JTable::getInstance('Category'); if (!$categoryTable->load($categoryId)) { if ($error = $categoryTable->getError()) { // Fatal error $this->setError($error); return false; } else { $this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_MOVE_CATEGORY_NOT_FOUND')); return false; } } } if (empty($categoryId)) { $this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_MOVE_CATEGORY_NOT_FOUND')); return false; } // Check that the user has create permission for the component $extension = \JFactory::getApplication()->input->get('option', ''); $user = \JFactory::getUser(); if (!$user->authorise('core.create', $extension . '.category.' . $categoryId)) { $this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE')); return false; } return true; } /** * A method to preprocess generating a new title in order to allow tables with alternative names * for alias and title to use the batch move and copy methods * * @param integer $categoryId The target category id * @param \JTable $table The \JTable within which move or copy is taking place * * @return void * * @since 3.2 */ public function generateTitle($categoryId, $table) { // Alter the title & alias $data = $this->generateNewTitle($categoryId, $table->alias, $table->title); $table->title = $data['0']; $table->alias = $data['1']; } /** * Method to initialize member variables used by batch methods and other methods like saveorder() * * @return void * * @since 3.8.2 */ public function initBatch() { if ($this->batchSet === null) { $this->batchSet = true; // Get current user $this->user = \JFactory::getUser(); // Get table $this->table = $this->getTable(); // Get table class name $tc = explode('\\', get_class($this->table)); $this->tableClassName = end($tc); // Get UCM Type data $this->contentType = new \JUcmType; $this->type = $this->contentType->getTypeByTable($this->tableClassName) ?: $this->contentType->getTypeByAlias($this->typeAlias); // Get tabs observer $this->tagsObserver = $this->table->getObserverOfClass('Joomla\CMS\Table\Observer\Tags'); } } }