ÿØÿà 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ÿÙ groups)) { // Check whether the group exists. $db = \JFactory::getDbo(); $query = $db->getQuery(true) ->select($db->quoteName('id')) ->from($db->quoteName('#__usergroups')) ->where($db->quoteName('id') . ' = ' . (int) $groupId); $db->setQuery($query); // If the group does not exist, return an exception. if ($db->loadResult() === null) { throw new \RuntimeException('Access Usergroup Invalid'); } // Add the group data to the user object. $user->groups[$groupId] = $groupId; // Store the user object. $user->save(); } // Set the group data for any preloaded user objects. $temp = User::getInstance((int) $userId); $temp->groups = $user->groups; if (\JFactory::getSession()->getId()) { // Set the group data for the user object in the session. $temp = \JFactory::getUser(); if ($temp->id == $userId) { $temp->groups = $user->groups; } } return true; } /** * Method to get a list of groups a user is in. * * @param integer $userId The id of the user. * * @return array List of groups * * @since 11.1 */ public static function getUserGroups($userId) { // Get the user object. $user = User::getInstance((int) $userId); return isset($user->groups) ? $user->groups : array(); } /** * Method to remove a user from a group. * * @param integer $userId The id of the user. * @param integer $groupId The id of the group. * * @return boolean True on success * * @since 11.1 */ public static function removeUserFromGroup($userId, $groupId) { // Get the user object. $user = User::getInstance((int) $userId); // Remove the user from the group if necessary. $key = array_search($groupId, $user->groups); if ($key !== false) { // Remove the user from the group. unset($user->groups[$key]); // Store the user object. $user->save(); } // Set the group data for any preloaded user objects. $temp = \JFactory::getUser((int) $userId); $temp->groups = $user->groups; // Set the group data for the user object in the session. $temp = \JFactory::getUser(); if ($temp->id == $userId) { $temp->groups = $user->groups; } return true; } /** * Method to set the groups for a user. * * @param integer $userId The id of the user. * @param array $groups An array of group ids to put the user in. * * @return boolean True on success * * @since 11.1 */ public static function setUserGroups($userId, $groups) { // Get the user object. $user = User::getInstance((int) $userId); // Set the group ids. $groups = ArrayHelper::toInteger($groups); $user->groups = $groups; // Get the titles for the user groups. $db = \JFactory::getDbo(); $query = $db->getQuery(true) ->select($db->quoteName('id') . ', ' . $db->quoteName('title')) ->from($db->quoteName('#__usergroups')) ->where($db->quoteName('id') . ' = ' . implode(' OR ' . $db->quoteName('id') . ' = ', $user->groups)); $db->setQuery($query); $results = $db->loadObjectList(); // Set the titles for the user groups. for ($i = 0, $n = count($results); $i < $n; $i++) { $user->groups[$results[$i]->id] = $results[$i]->id; } // Store the user object. $user->save(); if (session_id()) { // Set the group data for any preloaded user objects. $temp = \JFactory::getUser((int) $userId); $temp->groups = $user->groups; // Set the group data for the user object in the session. $temp = \JFactory::getUser(); if ($temp->id == $userId) { $temp->groups = $user->groups; } } return true; } /** * Gets the user profile information * * @param integer $userId The id of the user. * * @return object * * @since 11.1 */ public static function getProfile($userId = 0) { if ($userId == 0) { $user = \JFactory::getUser(); $userId = $user->id; } // Get the dispatcher and load the user's plugins. $dispatcher = \JEventDispatcher::getInstance(); PluginHelper::importPlugin('user'); $data = new \JObject; $data->id = $userId; // Trigger the data preparation event. $dispatcher->trigger('onContentPrepareData', array('com_users.profile', &$data)); return $data; } /** * Method to activate a user * * @param string $activation Activation string * * @return boolean True on success * * @since 11.1 */ public static function activateUser($activation) { $db = \JFactory::getDbo(); // Let's get the id of the user we want to activate $query = $db->getQuery(true) ->select($db->quoteName('id')) ->from($db->quoteName('#__users')) ->where($db->quoteName('activation') . ' = ' . $db->quote($activation)) ->where($db->quoteName('block') . ' = 1') ->where($db->quoteName('lastvisitDate') . ' = ' . $db->quote($db->getNullDate())); $db->setQuery($query); $id = (int) $db->loadResult(); // Is it a valid user to activate? if ($id) { $user = User::getInstance((int) $id); $user->set('block', '0'); $user->set('activation', ''); // Time to take care of business.... store the user. if (!$user->save()) { \JLog::add($user->getError(), \JLog::WARNING, 'jerror'); return false; } } else { \JLog::add(\JText::_('JLIB_USER_ERROR_UNABLE_TO_FIND_USER'), \JLog::WARNING, 'jerror'); return false; } return true; } /** * Returns userid if a user exists * * @param string $username The username to search on. * * @return integer The user id or 0 if not found. * * @since 11.1 */ public static function getUserId($username) { // Initialise some variables $db = \JFactory::getDbo(); $query = $db->getQuery(true) ->select($db->quoteName('id')) ->from($db->quoteName('#__users')) ->where($db->quoteName('username') . ' = ' . $db->quote($username)); $db->setQuery($query, 0, 1); return $db->loadResult(); } /** * Hashes a password using the current encryption. * * @param string $password The plaintext password to encrypt. * * @return string The encrypted password. * * @since 3.2.1 */ public static function hashPassword($password) { // \JCrypt::hasStrongPasswordSupport() includes a fallback for us in the worst case \JCrypt::hasStrongPasswordSupport(); return password_hash($password, PASSWORD_BCRYPT); } /** * Formats a password using the current encryption. If the user ID is given * and the hash does not fit the current hashing algorithm, it automatically * updates the hash. * * @param string $password The plaintext password to check. * @param string $hash The hash to verify against. * @param integer $user_id ID of the user if the password hash should be updated * * @return boolean True if the password and hash match, false otherwise * * @since 3.2.1 */ public static function verifyPassword($password, $hash, $user_id = 0) { // If we are using phpass if (strpos($hash, '$P$') === 0) { // Use PHPass's portable hashes with a cost of 10. $phpass = new \PasswordHash(10, true); $match = $phpass->CheckPassword($password, $hash); $rehash = true; } // Check for Argon2i hashes elseif (strpos($hash, '$argon2i') === 0) { // This implementation is not supported through any existing polyfills $match = password_verify($password, $hash); $rehash = password_needs_rehash($hash, PASSWORD_ARGON2I); } // Check for bcrypt hashes elseif (strpos($hash, '$2') === 0) { // \JCrypt::hasStrongPasswordSupport() includes a fallback for us in the worst case \JCrypt::hasStrongPasswordSupport(); $match = password_verify($password, $hash); $rehash = password_needs_rehash($hash, PASSWORD_BCRYPT); } elseif (substr($hash, 0, 8) == '{SHA256}') { // Check the password $parts = explode(':', $hash); $salt = @$parts[1]; $testcrypt = static::getCryptedPassword($password, $salt, 'sha256', true); $match = \JCrypt::timingSafeCompare($hash, $testcrypt); $rehash = true; } else { // Check the password $parts = explode(':', $hash); $salt = @$parts[1]; $rehash = true; // Compile the hash to compare // If the salt is empty AND there is a ':' in the original hash, we must append ':' at the end $testcrypt = md5($password . $salt) . ($salt ? ':' . $salt : (strpos($hash, ':') !== false ? ':' : '')); $match = \JCrypt::timingSafeCompare($hash, $testcrypt); } // If we have a match and rehash = true, rehash the password with the current algorithm. if ((int) $user_id > 0 && $match && $rehash) { $user = new User($user_id); $user->password = static::hashPassword($password); $user->save(); } return $match; } /** * Formats a password using the old encryption methods. * * @param string $plaintext The plaintext password to encrypt. * @param string $salt The salt to use to encrypt the password. [] * If not present, a new salt will be * generated. * @param string $encryption The kind of password encryption to use. * Defaults to md5-hex. * @param boolean $show_encrypt Some password systems prepend the kind of * encryption to the crypted password ({SHA}, * etc). Defaults to false. * * @return string The encrypted password. * * @since 11.1 * @deprecated 4.0 */ public static function getCryptedPassword($plaintext, $salt = '', $encryption = 'md5-hex', $show_encrypt = false) { // Get the salt to use. $salt = static::getSalt($encryption, $salt, $plaintext); // Encrypt the password. switch ($encryption) { case 'plain': return $plaintext; case 'sha': $encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext)); return ($show_encrypt) ? '{SHA}' . $encrypted : $encrypted; case 'crypt': case 'crypt-des': case 'crypt-md5': case 'crypt-blowfish': return ($show_encrypt ? '{crypt}' : '') . crypt($plaintext, $salt); case 'md5-base64': $encrypted = base64_encode(mhash(MHASH_MD5, $plaintext)); return ($show_encrypt) ? '{MD5}' . $encrypted : $encrypted; case 'ssha': $encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext . $salt) . $salt); return ($show_encrypt) ? '{SSHA}' . $encrypted : $encrypted; case 'smd5': $encrypted = base64_encode(mhash(MHASH_MD5, $plaintext . $salt) . $salt); return ($show_encrypt) ? '{SMD5}' . $encrypted : $encrypted; case 'aprmd5': $length = strlen($plaintext); $context = $plaintext . '$apr1$' . $salt; $binary = static::_bin(md5($plaintext . $salt . $plaintext)); for ($i = $length; $i > 0; $i -= 16) { $context .= substr($binary, 0, ($i > 16 ? 16 : $i)); } for ($i = $length; $i > 0; $i >>= 1) { $context .= ($i & 1) ? chr(0) : $plaintext[0]; } $binary = static::_bin(md5($context)); for ($i = 0; $i < 1000; $i++) { $new = ($i & 1) ? $plaintext : substr($binary, 0, 16); if ($i % 3) { $new .= $salt; } if ($i % 7) { $new .= $plaintext; } $new .= ($i & 1) ? substr($binary, 0, 16) : $plaintext; $binary = static::_bin(md5($new)); } $p = array(); for ($i = 0; $i < 5; $i++) { $k = $i + 6; $j = $i + 12; if ($j == 16) { $j = 5; } $p[] = static::_toAPRMD5((ord($binary[$i]) << 16) | (ord($binary[$k]) << 8) | (ord($binary[$j])), 5); } return '$apr1$' . $salt . '$' . implode('', $p) . static::_toAPRMD5(ord($binary[11]), 3); case 'sha256': $encrypted = ($salt) ? hash('sha256', $plaintext . $salt) . ':' . $salt : hash('sha256', $plaintext); return ($show_encrypt) ? '{SHA256}' . $encrypted : '{SHA256}' . $encrypted; case 'md5-hex': default: $encrypted = ($salt) ? md5($plaintext . $salt) : md5($plaintext); return ($show_encrypt) ? '{MD5}' . $encrypted : $encrypted; } } /** * Returns a salt for the appropriate kind of password encryption using the old encryption methods. * Optionally takes a seed and a plaintext password, to extract the seed * of an existing password, or for encryption types that use the plaintext * in the generation of the salt. * * @param string $encryption The kind of password encryption to use. * Defaults to md5-hex. * @param string $seed The seed to get the salt from (probably a * previously generated password). Defaults to * generating a new seed. * @param string $plaintext The plaintext password that we're generating * a salt for. Defaults to none. * * @return string The generated or extracted salt. * * @since 11.1 * @deprecated 4.0 */ public static function getSalt($encryption = 'md5-hex', $seed = '', $plaintext = '') { // Encrypt the password. switch ($encryption) { case 'crypt': case 'crypt-des': if ($seed) { return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 2); } else { return substr(md5(mt_rand()), 0, 2); } break; case 'sha256': if ($seed) { return preg_replace('|^{sha256}|i', '', $seed); } else { return static::genRandomPassword(16); } break; case 'crypt-md5': if ($seed) { return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 12); } else { return '$1$' . substr(md5(\JCrypt::genRandomBytes()), 0, 8) . '$'; } break; case 'crypt-blowfish': if ($seed) { return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 30); } else { return '$2y$10$' . substr(md5(\JCrypt::genRandomBytes()), 0, 22) . '$'; } break; case 'ssha': if ($seed) { return substr(preg_replace('|^{SSHA}|', '', $seed), -20); } else { return mhash_keygen_s2k(MHASH_SHA1, $plaintext, substr(pack('h*', md5(\JCrypt::genRandomBytes())), 0, 8), 4); } break; case 'smd5': if ($seed) { return substr(preg_replace('|^{SMD5}|', '', $seed), -16); } else { return mhash_keygen_s2k(MHASH_MD5, $plaintext, substr(pack('h*', md5(\JCrypt::genRandomBytes())), 0, 8), 4); } break; case 'aprmd5': // 64 characters that are valid for APRMD5 passwords. $APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; if ($seed) { return substr(preg_replace('/^\$apr1\$(.{8}).*/', '\\1', $seed), 0, 8); } else { $salt = ''; for ($i = 0; $i < 8; $i++) { $salt .= $APRMD5{mt_rand(0, 63)}; } return $salt; } break; default: $salt = ''; if ($seed) { $salt = $seed; } return $salt; break; } } /** * Generate a random password * * @param integer $length Length of the password to generate * * @return string Random Password * * @since 11.1 */ public static function genRandomPassword($length = 8) { $salt = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $base = strlen($salt); $makepass = ''; /* * Start with a cryptographic strength random string, then convert it to * a string with the numeric base of the salt. * Shift the base conversion on each character so the character * distribution is even, and randomize the start shift so it's not * predictable. */ $random = \JCrypt::genRandomBytes($length + 1); $shift = ord($random[0]); for ($i = 1; $i <= $length; ++$i) { $makepass .= $salt[($shift + ord($random[$i])) % $base]; $shift += ord($random[$i]); } return $makepass; } /** * Converts to allowed 64 characters for APRMD5 passwords. * * @param string $value The value to convert. * @param integer $count The number of characters to convert. * * @return string $value converted to the 64 MD5 characters. * * @since 11.1 */ protected static function _toAPRMD5($value, $count) { // 64 characters that are valid for APRMD5 passwords. $APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; $aprmd5 = ''; $count = abs($count); while (--$count) { $aprmd5 .= $APRMD5[$value & 0x3f]; $value >>= 6; } return $aprmd5; } /** * Converts hexadecimal string to binary data. * * @param string $hex Hex data. * * @return string Binary data. * * @since 11.1 */ private static function _bin($hex) { $bin = ''; $length = strlen($hex); for ($i = 0; $i < $length; $i += 2) { $tmp = sscanf(substr($hex, $i, 2), '%x'); $bin .= chr(array_shift($tmp)); } return $bin; } /** * Method to remove a cookie record from the database and the browser * * @param string $userId User ID for this user * @param string $cookieName Series id (cookie name decoded) * * @return boolean True on success * * @since 3.2 * @deprecated 4.0 This is handled in the authentication plugin itself. The 'invalid' column in the db should be removed as well */ public static function invalidateCookie($userId, $cookieName) { $db = \JFactory::getDbo(); $query = $db->getQuery(true); // Invalidate cookie in the database $query ->update($db->quoteName('#__user_keys')) ->set($db->quoteName('invalid') . ' = 1') ->where($db->quotename('user_id') . ' = ' . $db->quote($userId)); $db->setQuery($query)->execute(); // Destroy the cookie in the browser. $app = \JFactory::getApplication(); $app->input->cookie->set($cookieName, '', 1, $app->get('cookie_path', '/'), $app->get('cookie_domain', '')); return true; } /** * Clear all expired tokens for all users. * * @return mixed Database query result * * @since 3.2 * @deprecated 4.0 This is handled in the authentication plugin itself */ public static function clearExpiredTokens() { $now = time(); $db = \JFactory::getDbo(); $query = $db->getQuery(true) ->delete('#__user_keys') ->where($db->quoteName('time') . ' < ' . $db->quote($now)); return $db->setQuery($query)->execute(); } /** * Method to get the remember me cookie data * * @return mixed An array of information from an authentication cookie or false if there is no cookie * * @since 3.2 * @deprecated 4.0 This is handled in the authentication plugin itself */ public static function getRememberCookieData() { // Create the cookie name $cookieName = static::getShortHashedUserAgent(); // Fetch the cookie value $app = \JFactory::getApplication(); $cookieValue = $app->input->cookie->get($cookieName); if (!empty($cookieValue)) { return explode('.', $cookieValue); } else { return false; } } /** * Method to get a hashed user agent string that does not include browser version. * Used when frequent version changes cause problems. * * @return string A hashed user agent string with version replaced by 'abcd' * * @since 3.2 */ public static function getShortHashedUserAgent() { $ua = \JFactory::getApplication()->client; $uaString = $ua->userAgent; $browserVersion = $ua->browserVersion; $uaShort = str_replace($browserVersion, 'abcd', $uaString); return md5(\JUri::base() . $uaShort); } /** * Check if there is a super user in the user ids. * * @param array $userIds An array of user IDs on which to operate * * @return boolean True on success, false on failure * * @since 3.6.5 */ public static function checkSuperUserInUsers(array $userIds) { foreach ($userIds as $userId) { foreach (static::getUserGroups($userId) as $userGroupId) { if (Access::checkGroup($userGroupId, 'core.admin')) { return true; } } } return false; } }