_adapter_name = $force === 'wsdl' ? 'wsdl' : 'xmlrpc'; } elseif(!class_exists('SoapClient')) $this->_adapter_name = 'xmlrpc'; else { $this->_adapter_name = 'wsdl'; } } /** * Set authorization key * @deprecated subject of removal */ public function generateAuthKey($username, $password) { if(trim($username) == '' || trim($password) == '') { return false; } $this->setAuthKey($this->makeAuthKey($username, $password, true)); return $this; } /** * Set authorization key * @deprecated subject of removal */ public function setAuthKey($authkey) { $this->adapter->setAuthKey($authkey); return $this; } /** * @return bool */ public function hasAuthKey() { return $this->adapter->hasAuthKey(); } /** * Make authorization key from user credentials * @deprecated subject of removal */ public function makeAuthKey($username, $password = '', $plain = false) { $now = gmdate('y-m-d H'); if($plain && !empty($password)) $password = md5($password); return sha1($username.$password.$now); } /** * Have the admin enter their e107.org login details in order to create the authorization key. * @deprecated subject of removal */ public function renderLoginForm() { $text = '
Login
'; //TODO Use Form handler for INPUT tags. //XXX TBD OR do we just redirect to the signup page on the website, in an iframe? $text .= '
'; return $text; } /** * Retrieve currently used adapter * @param e_marketplace_adapter_abstract * @return \e_marketplace_adapter_abstract */ public function adapter() { if(null === $this->adapter) { $className = 'e_marketplace_adapter_'.$this->_adapter_name; $this->adapter = new $className(); } return $this->adapter; } /** * Retrieve currently used adapter * @param $method * @param $data * @param bool $apply * @return mixed */ public function call($method, $data, $apply = true) { if(E107_DEBUG_LEVEL > 0) { e107::getDebug()->log("Calling e107.org using ".$this->_adapter_name." adapter"); } return $this->adapter()->call($method, $data, $apply); } /** * Adapter proxy */ public function download($id, $mode, $type) { return $this->adapter()->download($id, $mode, $type); } /** * Direct adapter()->call() execution - experimental stage */ public function __call($method, $arguments) { if(strpos($method, 'get') === 0 || strpos($method, 'do') === 0) { return $this->adapter()->call($method, $arguments); } throw new Exception("Error Processing Request", 10); } /** * */ public function __destruct() { $this->adapter = null; //echo "Adapter destroyed", PHP_EOL; } /** * @param $data - e107.org plugin/theme feed data. * @return bool|string */ public function getDownloadModal($type='plugin',$data=array()) { $url = false; if($type === 'plugin') { if(empty($data['plugin_id'])) { $srcData = array( 'plugin_id' => $data['params']['id'], 'plugin_folder' => $data['folder'], 'plugin_price' => $data['price'], 'plugin_mode' => $data['params']['mode'], 'plugin_url' => $data['url'], ); } else { $srcData = $data; } $d = http_build_query($srcData,false,'&'); // if(deftrue('e_DEBUG_PLUGMANAGER')) { $url = e_ADMIN.'plugin.php?mode=online&action=download&e-token='.e_TOKEN.'&src='.base64_encode($d); } // else { // $url = e_ADMIN.'plugin.php?mode=download&src='.base64_encode($d); } } if($type === 'theme') { $srcData = array( 'id' => $data['params']['id'], 'url' => $data['url'], 'mode' => 'addon', 'price' => $data['price'] ); $d = http_build_query($srcData,false,'&'); $url = e_ADMIN.'theme.php?mode=main&action=download&e-token='.e_TOKEN.'&src='.base64_encode($d);//$url.'&action=download'; } return $url; } /** * @param $type * @return array|string[] */ public function getVersionList($type='plugin') { $cache = e107::getCache(); $cache->setMD5('_', false); $tag = 'Versions_'.$type; if($data = $cache->retrieve($tag,(60 * 12), true, true)) { return e107::unserialize($data); } // $mp = $this->getMarketplace(); // $mp->generateAuthKey($e107SiteUsername, $e107SiteUserpass); e107::getDebug()->log("Retrieving ".$type." version list from e107.org"); $xdata = $this->call('getList', array( 'type' => $type, 'params' => array('limit' => 200, 'search' => null, 'from' => 0) )); $arr = array(); if(!empty($xdata['data'])) { foreach($xdata['data'] as $row) { $k = $row['folder']; $arr[$k] = $row; } } if(empty($arr)) { $arr = array('-unable-to-connect'); // make sure something is cached so further lookups stop. } $data = e107::serialize($arr, 'json'); $cache->set($tag, $data, true, true, true); return $arr; } } /** * */ abstract class e_marketplace_adapter_abstract { /** * e107.org download URL * @var string */ protected $downloadUrl = 'https://e107.org/request/'; /** * e107.org service URL [adapter implementation required] * @var string */ protected $serviceUrl = null; /** * Request method POST || GET [adapter implementation required] * @var string */ public $requestMethod = null; /** * @var eAuth */ protected $_auth = null; /** * e107.org authorization key * @deprecated subject of removal * @var string */ protected $authKey = null; /** * @param $input * @return mixed */ abstract public function test($input); //abstract public function call($method, $data, $apply); /** * @param $method * @param $data * @param $apply * @return mixed */ abstract public function call($method, $data, $apply = true); // Fix issue #490 /** * @param $method * @param $result * @return mixed */ abstract public function fetch($method, &$result); /** * Authorization object * @return eAuth */ public function auth() { if(null === $this->_auth) { $this->_auth = new eAuth; $this->_auth->loadSysCredentials(); $this->_auth->requestMethod = $this->requestMethod; } return $this->_auth; } /** * Set authorization key * @deprecated subject of removal */ public function setAuthKey($authkey) { $this->authKey = $authkey; return $this; } /** * @deprecated subject of removal */ public function hasAuthKey() { return $this->authKey !== null; } /** * @deprecated subject of removal */ public function getAuthKey() { return $this->authKey; } /** * Download a Plugin or Theme to Temp, then test and move to plugin/theme folder and backup to system backup folder. * XXX better way to return status (e.g. getError(), getStatus() service call before download) * XXX temp is not well cleaned * XXX themes/plugins not well tested after unzip (example - Headline 1.0, non-default structure, same applies to most FS net free themes) * This method is direct outputting the status. If not needed - use buffer * @param $id * @param $mode * @param string $type plugin or theme * @return bool */ public function download($id, $mode, $type) { $tp = e107::getParser(); $mes = e107::getMessage(); $fl = e107::getFile(); $id = intval($id); $qry = 'id='.$id.'&type='.$type.'&mode='.$mode; $remotefile = $this->downloadUrl."?auth=".$this->getAuthKey()."&".$qry; $localfile = md5($remotefile.time()).".zip"; $mes->addSuccess(LAN_DOWNLOADING."..."); // FIXME call the service, check status first, then download (if status OK), else retireve the error break and show it $result = $this->getRemoteFile($remotefile, $localfile); if(!$result) { if(filesize(e_TEMP.$localfile)) { $contents = file_get_contents(e_TEMP.$localfile); $contents = explode('REQ_', $contents); $mes->addError('[#'.trim($contents[1]).'] '.trim($contents[0])); flush(); } @unlink(e_TEMP.$localfile); return false; } if(!file_exists(e_TEMP.$localfile)) { $srch = array("[", "]"); $repl = array("", ""); $mes->addError( TPVLAN_83." ".str_replace($srch, $repl, TPVLAN_84)); if(E107_DEBUG_LEVEL > 0) { $mes->addDebug('local='.$localfile); // ; flush(); } return false; } if($fl->unzipArchive($localfile,$type, true)) { $lan = defset('LAN_DOWNLOAD_COMPLETE', 'Download Complete!'); $mes->addSuccess($lan); return true; } else { $mes->addSuccess( "".TPVLAN_84.""); } return false; } // Grab a remote file and save it in the /temp directory. requires CURL /** * @param $remote_url * @param $local_file * @param $type * @return bool */ function getRemoteFile($remote_url, $local_file, $type='temp') { // FIXME - different methods (see xml handler getRemoteFile()), error handling, appropriate error messages, if (!function_exists("curl_init")) { return false; } $path = ($type == 'media') ? e_MEDIA : e_TEMP; $fp = fopen($path.$local_file, 'w'); // media-directory is the root. //$fp1 = fopen(e_TEMP.'/curllog.txt', 'w'); $cp = e107::getFile()->initCurl($remote_url); curl_setopt($cp, CURLOPT_FILE, $fp); /* $cp = curl_init($remote_url); //curl_setopt($ch, CURLOPT_VERBOSE, 1); //curl_setopt($ch, CURLOPT_STDERR, $fp1); curl_setopt($cp, CURLOPT_REFERER, e_REQUEST_HTTP); curl_setopt($cp, CURLOPT_HEADER, 0); curl_setopt($cp, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)"); curl_setopt($cp, CURLOPT_COOKIEFILE, e_SYSTEM.'cookies.txt');*/ $buffer = curl_exec($cp); curl_close($cp); fclose($fp); //fclose($fp1); if($buffer) { $size = filesize($path.$local_file); if($size < 400) $buffer = false; } return ($buffer) ? true : false; } } /** * */ class e_marketplace_adapter_wsdl extends e_marketplace_adapter_abstract { /** * e107.org WSDL URL * @var string */ protected $serviceUrl = 'https://e107.org/service?wsdl'; /** * Request method POST || GET * @var string */ public $requestMethod = 'POST'; /** * Soap client instance * @var SoapClient */ protected $client = null; public function __construct() { ini_set('soap.wsdl_cache_enabled', 0); ini_set('soap.wsdl_cache_ttl', 0); $options = array( "trace" => true, 'exception' => true, "uri" => "http://server.soap.e107.inc.com/", 'cache_wsdl' => WSDL_CACHE_NONE, 'connection_timeout' => 5, ); try { //libxml_disable_entity_loader(false); $this->client = new SoapClient($this->serviceUrl, $options); } catch (Exception $e) { $message = deftrue('LAN_ERROR_CONNECTION', "Unable to connect for updates. Please check firewall and/or internet connection."); e107::getMessage()->addInfo($message); e107::getMessage()->addDebug($e->getMessage()); } if(function_exists('xdebug_disable')) { xdebug_disable(); } } /** * @param $input * @return string */ public function test($input) { try { $res = $this->client->get_echo($input); } catch(Exception $e) { $res = $e->getMessage(); } return $res; } /** * Generic call method */ public function _call($method, $args, $apply = true) { $result = array( 'data' => null, //'error'=> array('code' => 0, 'message' => null) ); $ret = null; // authorize on every call, service class decides what to do on every method call $auth = new stdClass; $auth->authKey = $this->getAuthKey(); $header = new SoapHeader('https://e107.org/services/auth', 'checkAuthHeader', $auth); if(!is_object($this->client)) { $result['exception'] = array(); $result['exception']['message'] = "Unable to connect at this time."; return $result; } try { $this->client->__setSoapHeaders(array($header)); if(is_array($args) && $apply) { $ret = call_user_func_array(array($this->client, $method), $args); } else $ret = $this->client->$method($args); $result = $ret; if(isset($ret['exception'])) { $result['exception'] = array(); $result['exception']['message'] = "API Exception [call::{$method}]: (#".$ret['exception']['code'].") ".$ret['exception']['message']; $result['exception']['code'] = 'API_'.$ret['exception']['code']; } unset($ret); } catch(SoapFault $e) { $result['exception']['message'] = "SoapFault Exception [call::{$method}]: (#".$e->faultcode.") ".$e->faultstring; $result['exception']['code'] = 'SOAP_'.$e->faultcode; if(E107_DEBUG_LEVEL) { $result['exception']['trace'] = $e->getTraceAsString(); $result['exception']['message'] .= ". Header fault: ".($e->headerfault ? $e->headerfault : 'n/a'); } } catch(Exception $e) { $result['exception']['message'] = "Generic Exception [call::{$method}]: (#".$e->getCode().") ".$e->getMessage(); $result['exception']['code'] = 'GEN_'.$e->getCode(); if(E107_DEBUG_LEVEL) { $result['debug']['trace'] = $e->getTraceAsString(); } } if(E107_DEBUG_LEVEL) { $result['debug']['response'] = $this->client->__getLastResponse(); $result['debug']['request'] = $this->client->__getLastRequest(); $result['debug']['request_header'] = $this->client->__getLastRequestHeaders(); } return $result; } /** * Public call method */ public function call($method, $data, $apply = true) { return $this->_call($method, $data, $apply); } /** * Prepare the result, not needed for WSDL * SUBJECT OF REMOVAL */ public function fetch($method, &$result) { if(isset($result['error'])) { return $result; } switch ($method) { case 'getList': break; } return $result; } /** * */ public function __destruct() { $this->client = null; //echo "SOAP Client destroyed", PHP_EOL; } } /** * */ class e_marketplace_adapter_xmlrpc extends e_marketplace_adapter_abstract { /** * e107.org XML-rpc service * @var xmlClass */ protected $serviceUrl = 'https://e107.org/xservice'; /** * Request method POST || GET * @var string */ public $requestMethod = 'GET'; protected $_forceArray = array(); protected $_forceNumericalArray = array(); public function __construct() { } /** * @param $input * @return void */ public function test($input) { } /** * @param $method * @param $data * @param $apply * @return array|string */ public function call($method, $data, $apply = true) { $client = $this->client(); // settings based on current method $this->prepareClient($method, $client); // authorization data // $data['auth'] = $this->getAuthKey(); $data['action'] = $method; foreach($data['params'] as $k=>$v) { $data[$k] = $v; } unset($data['params']); // build the request query $qry = str_replace(array('s%5B', '%5D'), array('[', ']'), http_build_query($data, null, '&')); $url = $this->serviceUrl.'?'.$qry; $result = array(); // call it try { $xmlString = $client->loadXMLfile($url,false); $xml = new SimpleXMLIterator($xmlString); //$result = $client->loadXMLfile($url, 'advanced'); $result = $this->fetch($method, $xml); if(isset($result['exception'])) { $exception = $result['exception']; $result['exception'] = array(); $result['exception']['message'] = "API Exception [call::{$method}]: (#".$exception['code'].") ".$exception['message']; $result['exception']['code'] = 'API_'.$exception['code']; } } catch(Exception $e) { $result['exception']['message'] = "Generic Exception [call::{$method}]: (#".$e->getCode().") ".$e->getMessage(); $result['exception']['code'] = 'GEN_'.$e->getCode(); } return $result; } /** * @param $method * @param $result * @return array|string */ public function fetch($method, &$result) { $ret = $this->parse($result); $this->fetchParams($ret); switch ($method) { // normalize case 'getList': $ret['data'] = $ret['data']['item']; break; } return $ret; } /** * New experimental XML parser, will be moved to XML handlers soon * XXX replace xmlClass::xml2array() after this one passes all tests * @param SimpleXmlIterator $xml * @param string $parentName parent node name - used currently for debug only * @return array|string */ public function parse($xml, $parentName = null) { $ret = array(); $tags = array_keys(get_object_vars($xml)); $count = $xml->count(); $tcount = count($tags); if($count === 0) { $attr = (array) $xml->attributes(); if(!empty($attr)) { $ret['@attributes'] = $attr['@attributes']; $ret['@value'] = (string) $xml; $ret['@value'] = trim($ret['@value']); } else { $ret = (string) $xml; $ret = trim($ret); } return $ret; } /** * * * * */ if($tcount === 1 && $count > 1) { foreach($xml as $name => $node) { $_res = $this->parse($node, $name); if(is_string($_res)) { $_res = trim($_res); } $ret[$name][] = $this->parse($node, $name); } } // default else { foreach($xml as $name => $node) { if(in_array($name, $this->_forceArray)) { $_res = $this->parse($node, $name); if(is_string($_res)) { $_res = trim($_res); } if(empty($_res)) { $ret[$name] = array(); } elseif(is_string($_res)) // empty { $ret[$name][] = $_res; } // string else { if(in_array($name, $this->_forceNumericalArray)) { $ret[$name][] = $_res; } //array - controlled force numerical array else { $ret[$name] = $_res; } //array, no force } } else { $ret[$name] = $this->parse($node, $name); } } } $attr = (array) $xml->attributes(); if(!empty($attr)) { $ret['@attributes'] = $attr['@attributes']; } return $ret; } /** * Normalize parameters/attributes * @param array $result parsed to array XML response data */ public function fetchParams(&$result) { foreach ($result as $tag => $data) { if($tag === 'params') { foreach ($data['param'] as $i => $param) { $result[$tag][$param['@attributes']['name']] = $param['@value']; unset($result[$tag]['param'][$i]); } unset($result[$tag]['param']); } elseif($tag === 'exception') { $result['exception'] = array('code' => (int) $result['exception']['@attributes']['code'], 'message' => $result['exception']['@value']); //unset($result['exception']); } elseif($tag === '@attributes') { $result['params'] = $result['@attributes']; unset($result['@attributes']); } elseif(is_array($data)) { $this->fetchParams($result[$tag]); } } } /** * @param string $method * @param xmlClass $client */ public function prepareClient($method, &$client) { switch ($method) { case 'getList': $this->_forceArray = array('item', 'screenshots', 'image', 'data'); $this->_forceNumericalArray = array('item', 'image'); //$client->setOptArrayTags('item,screenshots,image') // ->setOptStringTags('icon,folder,version,author,authorURL,date,compatibility,url,thumbnail,featured,livedemo,price,name,description,category,image'); break; } } /** * @return xmlClass */ public function client() { return e107::getXml(false); } } /** * */ class eAuth { /** * e107.org manage client credentials (Consumer Key and Secret) URL * @var string */ protected $eauthConsumerUrl = 'https://e107.org/eauth/client'; /** * URL used to make temporary credential request (Request Token and Secret) to e107.org before the authorization phase * @var string */ protected $eauthRequestUrl = 'https://e107.org/eauth/initialize'; /** * URL used to redirect and authorize the resource owner (user) on e107.org using temporary (request) token * @var string */ protected $eauthAuthorizeUrl = 'https://e107.org/eauth/authorize'; /** * URL used to obtain token credentials (Access Token and Secret) from e107.org using temporary (request) token * @var string */ protected $eauthAccessUrl = 'https://e107.org/eauth/token'; /** * Public client key (generated and obtained from e107.org) * @var string */ public $eauthConsumerKey = null; /** * Client shared secret (generated and obtained from e107.org) * @var string */ public $eauthConsumerSecret = null; /** * Public temporary request token (generated and obtained from e107.org) * @var string */ public $eauthRequestKey = null; /** * Temporary request shared secret (generated and obtained from e107.org) * @var string */ public $eauthRequestSecret = null; /** * Public access token (generated and obtained from e107.org) * @var string */ public $eauthAccessToken = null; /** * Access shared secret (generated and obtained from e107.org) * @var string */ public $eauthAccessSecret = null; /** * Request method POST || GET * @var string */ public $requestMethod = null; /** * @return bool */ public function isClient() { $this->loadSysCredentials(); return (!empty($this->eauthConsumerKey) && !empty($this->eauthConsumerSecret)); } /** * @return bool */ public function isInitialized() { $this->loadSysCredentials(); return ($this->isClient() && !empty($this->eauthRequestKey) && !empty($this->eauthRequestSecret)); } /** * @return bool */ public function hasAccess() { $this->loadSysCredentials(); return ($this->isClient() && !empty($this->eauthAccessToken) && !empty($this->eauthAccessSecret)); } /** * @param $method * @param $args * @param $toObject * @return array|stdClass */ public function serviceAuthData($method, $args, $toObject = true) { // The client has previously registered with the server and obtained the client identifier dpf43f3p2l4k3l03 and client secret kd94hf93k423kf44. // It has executed the eAuth workflow and obtained an access token nnch734d00sl2jdk and token secret pfkkdhi9sl3r4s00 $date = gmdate('Y-m-d H:i:s'); $timestamp = $this->gmtTime($date); $nonce = $this->nonce($timestamp); // create nonce $cryptMethod = $this->cryptMethod(); $authData = array( 'eauth_consumer_key' => $this->eauthConsumerKey, // (Client Identifier) Application key 'eauth_token' => $this->eauthAccessToken, // Access Token 'eauth_nonce' => $nonce,//'kllo9940pd9333jh' 'nonce' (number used once) string 'eauth_timestamp' => $timestamp, // timestamp 'eauth_signature_method'=> $cryptMethod, // encryption method 'eauth_version' => '1.0', // signature method ); // current request parameters $args['action'] = $method; // signature data for building the signature $signatureData = $authData; // add request parameters to the signature array $signatureData['eauth_request_params'] = $args; // sort all self::array_kmultisort($signatureData); // signature base string $signatureBaseString = $this->requestMethod.'&'.rawurlencode($this->serviceUrl).'&'.http_build_query($signatureData, false, '&'); $secretKey = rawurlencode($this->eauthConsumerSecret).'&'.rawurlencode($this->eauthAccessSecret); // crypt it $signature = $this->crypt($signatureBaseString, $secretKey); //encode it $authData['eauth_signature'] = base64_encode($signature); if($toObject) return self::toObject($authData); return $authData; } /** * @param $array * @return stdClass */ public static function toObject($array) { $obj = new stdClass; foreach ($array as $key => $value) { $obj->$key = $value; } return $obj; } /** * Load credentials stored in a system file * @param boolean $force * @return eAuth */ public function loadSysCredentials($force = false) { if($force || null === $this->eauthConsumerKey) { $data = e107::getArrayStorage()->load('eauth'); if(empty($data)) $data = array(); $this->eauthConsumerKey = varset($data['consumer_key'], ''); $this->eauthConsumerSecret = varset($data['consumer_secret'], ''); $this->eauthAccessToken = varset($data['access_token'], ''); $this->eauthAccessSecret = varset($data['access_secret'], ''); } return $this; } /** * @param $credentials * @return bool */ public function storeSysCredentials($credentials = null) { if(null === $credentials) { $credentials = array( 'consumer_key' => $this->eauthConsumerKey, 'consumer_secret' => $this->eauthConsumerSecret, 'access_token' => $this->eauthAccessToken, 'access_secret' => $this->eauthAccessSecret, ); } if(!is_array($credentials)) return false; foreach ($credentials as $key => $value) { switch ($key) { case 'consumer_key': case 'consumer_secret': case 'access_token': case 'access_secret': // OK break; default: unset($credentials[$key]); break; } } return e107::getArrayStorage()->store($credentials, 'eauth'); } /** * Retrieve available system credentials or credential value * @param string $key [optional] * return mixed array of all credentials or string credential value */ public function getCredentials($key = null) { $this->loadSysCredentials(); $credentials = array( 'consumer_key' => $this->eauthConsumerKey, 'consumer_secret' => $this->eauthConsumerSecret, 'access_token' => $this->eauthAccessToken, 'access_secret' => $this->eauthAccessSecret, ); if(null !== $key) return varset($credentials[$key], null); return $credentials; } /** * @param $params * @return string * @throws Exception */ public function toAuthHeader($params) { $first = true; $realm = isset($params['realm']) ? $params['realm'] : null; if($realm) { $out = 'Authorization: eAuth realm="'.rawurlencode($realm).'"'; $first = false; } else $out = 'Authorization: eAuth'; $total = array(); foreach($params as $k => $v) { if(strpos($k, "eauth") !== 0) continue; if(is_array($v)) { throw new Exception('Arrays not supported in headers', 200); } $out .= ($first) ? ' ' : ','; $out .= rawurlencode($k).'="'.rawurlencode($v).'"'; $first = false; } return $out; } /** * @return string */ public function cryptMethod() { return function_exists('hash_hmac') ? 'HMAC-SHA1' : 'SHA1'; } /** * @param $bits * @return string */ function random($bits = 256) { $bytes = ceil($bits / 8); $ret = ''; for ($i = 0; $i < $bytes; $i++) { $ret .= chr(mt_rand(0, 255)); } return $ret; } /** * @param $string * @param $secretKey * @return false|string */ public function crypt($string, $secretKey) { $cMethod = $this->cryptMethod(); // Append secret if it's sha1 if($cMethod == 'SHA1') { return sha1($string.$secretKey); } // use secret key if HMAC-SHA1 return hash_hmac('sha1', $string, $secretKey); } /** * @param $timestamp * @return false|string */ public function nonce($timestamp) { return $this->crypt($this->random().$timestamp, $this->eauthAccessSecret.$this->eauthConsumerSecret); } /** * @param $string * @return false|int */ public function gmtTime($string) { $ret = false; // mask - Y-m-d H:i:s if(preg_match('#(.*?)-(.*?)-(.*?) (.*?):(.*?):(.*?)$#', $string, $matches)) { $ret = gmmktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]); } return $ret; } /** * @param $array * @param $order * @return void */ public static function array_kmultisort(&$array, $order = 'asc') { $func = $order == 'asc' ? 'ksort' : 'krsort'; $func($array); foreach ($array as $key => $value) { if(is_array($value)) { self::array_kmultisort($value, $order); $array[$key] = $value; } } } }