/** * Check to ensure a permalink is correct * Accepts a second value of TRUE to simply return a boolean (TRUE means permalink is OK, false means it is not) * By default, it takes action based on your settings * * @access public * @param string Correct SEO title (app_dir) * @param boolean [TRUE, return a boolean (true for OK, false for not). FALSE {default} simply take action based on settings] * @return boolean */ public function checkPermalink($seoTitle, $return = FALSE) { /* Only serve GET requests */ if ($this->request['request_method'] != 'get') { return FALSE; } if (!$this->settings['use_friendly_urls'] or !$seoTitle or !$this->settings['seo_bad_url'] or $this->settings['seo_bad_url'] == 'nothing') { return FALSE; } $_st = $this->seoTemplates['__data__']['start']; $_end = $this->seoTemplates['__data__']['end']; $_sep = $this->seoTemplates['__data__']['varSep']; $_blk = $this->seoTemplates['__data__']['varBlock']; $_qs = $_SERVER['QUERY_STRING'] ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING'); $_uri = $_SERVER['REQUEST_URI'] ? $_SERVER['REQUEST_URI'] : @getenv('REQUEST_URI'); $_toTest = $_qs ? $_qs : $_uri; /* Shouldn't need to check this, but feel better for doing it: Friendly URL? */ if (!strstr($_toTest, $_end)) { return FALSE; } /* Try original */ if (!preg_match("#" . $_st . preg_quote($seoTitle, '#') . '(' . $_end . "\$|" . preg_quote($_blk, '#') . ")#", $_toTest)) { /* Do we need to encode? */ $_toTest = urldecode($_toTest); } if (!preg_match("#" . $_st . preg_quote($seoTitle, '#') . '(' . $_end . "\$|" . preg_quote($_blk, '#') . ")#", $_toTest)) { if ($return === TRUE) { return FALSE; } /* Still here? */ switch ($this->settings['seo_bad_url']) { default: case 'meta': $this->addMetaTag('robots', 'noindex,nofollow'); break; case 'redirect': $uri = array(); foreach ($this->seoTemplates as $key => $data) { if (!$data['in']['regex']) { continue; } if (preg_match($data['in']['regex'], $_toTest, $matches)) { if (is_array($data['in']['matches'])) { foreach ($data['in']['matches'] as $_replace) { $k = IPSText::parseCleanKey($_replace[0]); if (strstr($_replace[1], '$')) { $v = IPSText::parseCleanValue($matches[intval(str_replace('$', '', $_replace[1]))]); } else { $v = IPSText::parseCleanValue($_replace[1]); } $uri[] = $k . '=' . $v; } } if (strstr($_toTest, $_blk)) { $_parse = substr($_toTest, strrpos($_toTest, $_blk) + strlen($_blk)); $_data = explode($_sep, $_parse); $_c = 0; foreach ($_data as $_v) { if (!$_c) { $k = IPSText::parseCleanKey($_v); $v = ''; $_c++; } else { $v = IPSText::parseCleanValue($_v); $_c = 0; $uri[] = $k . '=' . $v; } } } break; } } /* Got something? */ if (count($uri)) { $newurl = $this->registry->getClass('output')->formatUrl($this->registry->getClass('output')->buildUrl(implode('&', $uri), 'public'), $seoTitle, $key); if ($this->settings['base_url'] . $_toTest != $newurl) { $this->registry->getClass('output')->silentRedirect($newurl, $seoTitle, TRUE); } } else { return FALSE; } break; } } return TRUE; }
/** * Check to ensure a permalink is correct * Accepts a second value of TRUE to simply return a boolean (TRUE means permalink is OK, false means it is not) * By default, it takes action based on your settings * * @access public * @param string Correct SEO title (app_dir) * @param boolean [TRUE, return a boolean (true for OK, false for not). FALSE {default} simply take action based on settings] * @return boolean */ public function checkPermalink($seoTitle, $return = FALSE) { /* Only serve GET requests */ if ($this->request['request_method'] != 'get') { return FALSE; } if (!$this->settings['use_friendly_urls'] or !$seoTitle) { return FALSE; } $_st = $this->seoTemplates['__data__']['start']; $_end = $this->seoTemplates['__data__']['end']; $_sep = $this->seoTemplates['__data__']['varSep']; $_join = $this->seoTemplates['__data__']['varJoin']; $_blk = $this->seoTemplates['__data__']['varBlock']; $_qs = $_SERVER['QUERY_STRING'] ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING'); $_uri = $_SERVER['REQUEST_URI'] ? $_SERVER['REQUEST_URI'] : @getenv('REQUEST_URI'); $seoTitle = !empty($seoTitle) && !is_array($seoTitle) ? array($seoTitle) : $seoTitle; /* Bug Fix: #20279 */ if ($this->settings['htaccess_mod_rewrite'] && strpos($_uri, IPS_PUBLIC_SCRIPT . '?/')) { $this->registry->getClass('output')->silentRedirect($this->settings['board_url'] . $_qs, $seoTitle, TRUE); } $_toTest = $_uri ? $_uri : $_qs; /* Now we need to strip off the beginning path so we are left with just the FURL part */ $_path = parse_url($this->settings['board_url'], PHP_URL_PATH); $_toTest = ($_path and $_path != '/') ? preg_replace("#^{$_path}#", '', $_toTest) : $_toTest; $_encodedManually = false; /* Shouldn't need to check this, but feel better for doing it: Friendly URL? */ if (!strstr($_toTest, $_end)) { return FALSE; } /* Got index.php in the URL? */ if (!$this->settings['htaccess_mod_rewrite']) { $_toTest = str_replace(IPS_PUBLIC_SCRIPT . '/', '', $_toTest); } // Removing this - see http://community.invisionpower.com/resources/bugs.html/_/ip-board/topic-furl-redirect-r37445 and http://community.invisionpower.com/resources/bugs.html/_/ip-board/transliteration-r37146 // -- Just a note if this is ever restored for some reason - it does not support $seoTitle as array (i.e. for status updates) // // /* If the SEO title has %hex but the incoming URL doesn't, convert the incoming URL */ /*if ( strstr( $seoTitle[0], '%' ) && ! strstr( $_toTest, '%' ) ) { $_toTest = urlencode( $_toTest ); $_encodedManually = true; }*/ /* @link http://community.invisionpower.com/resources/bugs.html/_/ip-board/having-a-followed-by-a-number-%23-in-a-topic-title-breaks-furl-redirection-r41229 */ foreach ($seoTitle as $essEeeOh) { if (strstr($essEeeOh, '%') && IPS_DOC_CHAR_SET != 'UTF-8') { $_encodedManually = true; } } /* Does it contain unicode? */ if (strstr($_toTest, '%')) { /* Lowercase it as some browsers send %E2 but it will be stored as %e2 */ $_toTest = strtolower($_toTest); } /* Try original */ if ($_encodedManually === false && (is_array($seoTitle) or !preg_match("#" . $_st . preg_quote($seoTitle[0], '#') . '(' . $_end . '$|/\\?|' . $_end . '\\w+?' . $_end . "\$|" . preg_quote($_blk, '#') . ")#", $_toTest))) { /* Do we need to encode? */ $_toTest = urldecode($_toTest); } if ($this->settings['url_type'] == 'query_string') { $_toTest = str_replace(IPS_PUBLIC_SCRIPT . '?/', '', $_toTest); // This ends up making /statuses/id/2 (for instance) as statuses/id/2 and does not match FURL templates //$_toTest = ltrim( $_toTest, '/' ); } #print '#\d+?' . $_st . preg_quote( $seoTitle, '#' ) . '(' . $_end . "$|" . $_end . "\w+?" . $_end . "$|" . preg_quote( $_blk, '#' ) . ")#";exit; if (is_array($seoTitle) or !preg_match('#\\d+?' . $_st . preg_quote($seoTitle, '#') . '(' . $_end . "\$|" . $_end . '\\w+?' . $_end . '$|/\\?|' . preg_quote($_blk, '#') . ")#", $_toTest)) { if ($return === TRUE) { return FALSE; } $uri = array(); $storeKey = ''; $storeData = ''; foreach ($this->seoTemplates as $key => $data) { if (!$data['in']['regex']) { continue; } $data['in']['regex'] = str_replace("\\{__varBlock__\\}", preg_quote($_blk, '#'), $data['in']['regex']); if (preg_match($data['in']['regex'], $_toTest, $matches)) { $storeKey = $key; $storeData = $data; $pageNumber = null; /* Handling pages as a special thing? */ if ($data['isPagesMode'] && strstr($_toTest, $this->seoTemplates['__data__']['varPage'])) { preg_match('#(' . preg_quote($this->seoTemplates['__data__']['varPage'], '#') . '(\\d+?))(?:$|' . preg_quote($this->seoTemplates['__data__']['varBlock'], '#') . ')#', $_toTest, $pageMatches); if ($pageMatches[1]) { $pageNumber = intval($pageMatches[2]); /* We want page-1 to 301 to just / */ $pageNumber = $pageNumber > 1 ? $pageNumber : null; } } if (is_array($data['in']['matches'])) { foreach ($this->seoTemplates[$key]['in']['matches'] as $_replace) { $k = IPSText::parseCleanKey($_replace[0]); if (strstr($_replace[1], '$')) { $v = IPSText::parseCleanValue($matches[intval(str_replace('$', '', $_replace[1]))]); } else { $v = IPSText::parseCleanValue($_replace[1]); } $uri[] = $k . '=' . $v; } } if (strstr($_toTest, $_blk)) { $_parse = substr($_toTest, strrpos($_toTest, $_blk) + strlen($_blk)); $_data = explode($_sep, $_parse); $_c = 0; foreach ($_data as $_v) { list($__k, $__v) = explode($_join, $_v); $k = IPSText::parseCleanKey($__k); $v = IPSText::parseCleanValue($__v); $uri[] = $k . '=' . $v; } } if ($data['newTemplate']) { $key = $data['newTemplate']; } break; } } /* Got something? */ if (count($uri)) { if ($pageNumber !== null) { /* add in page */ $uri[] = 'page=' . $pageNumber; } foreach ($seoTitle as $_k => $_v) { if (preg_match('#\\&[\\#a-z0-9]{2,6};#i', $_v)) { $seoTitle[$_k] = urlencode($_v); } } $newurl = $this->registry->getClass('output')->formatUrl($this->registry->getClass('output')->buildUrl(implode('&', $uri), 'public'), $seoTitle, $key); $base_url = (!IN_ACP and $this->member->session_type != 'cookie') ? preg_replace("/s=([a-zA-Z0-9]{32})(&|&)/", '', $this->settings['base_url']) : $this->settings['base_url']; switch ($this->settings['url_type']) { case 'path_info': if ($this->settings['htaccess_mod_rewrite']) { $base_url = str_replace(IPS_PUBLIC_SCRIPT . '?', '', $base_url); } else { $base_url = str_replace(IPS_PUBLIC_SCRIPT . '?', IPS_PUBLIC_SCRIPT . '/', $base_url); } break; default: case 'query_string': $base_url = str_replace(IPS_PUBLIC_SCRIPT . '?', IPS_PUBLIC_SCRIPT . '?/', $base_url); break; } $base_url = rtrim($base_url, '/'); /* preg_match is to prevent redirecting in older Android and IE browsers (Does not affect IE10). They will take %c5%82, break down to separate characters and re-encode as %c3%85%c2%82, creating an infinite redirect loop. Ticket 848516, 853009 and @link http://community.invisionpower.com/resources/bugs.html/_/ip-board/urls-with-multi-byte-characters-causing-infinite-redirect-on-old-android-devices-r41601 */ if ($base_url . $_toTest != $newurl and !preg_match("/(android 2|msie)/i", $this->member->user_agent)) { /* Load information file */ if ($storeData['app'] && is_file(IPSLib::getAppDir($storeData['app']) . '/extensions/furlRedirect.php')) { $_class = IPSLib::loadLibrary(IPSLib::getAppDir($storeData['app']) . '/extensions/furlRedirect.php', 'furlRedirect_' . $storeData['app'], $storeData['app']); $_furl = new $_class(ipsRegistry::instance()); $_testUrl = strstr($this->settings['base_url'], '?') ? $this->settings['base_url'] . implode('&', $uri) : $this->settings['base_url'] . '?' . implode('&', $uri); $_furl->setKeyByUri($_testUrl); $_seoTitle = $_furl->fetchSeoTitle(); if (preg_match('#\\&[\\#a-z0-9]{2,6};#i', $_seoTitle)) { $_seoTitle = urlencode($_seoTitle); } if ($_seoTitle && empty($this->request['debug'])) { $this->registry->getClass('output')->silentRedirect($_testUrl, $_seoTitle, true, $storeKey); } else { $this->registry->getClass('output')->silentRedirect($_testUrl, $seoTitle, TRUE, $key); } } else { $this->registry->getClass('output')->silentRedirect($newurl, $seoTitle, TRUE, $key); } } } else { return FALSE; } } return TRUE; }
/** * INIT furls * Performs set up and figures out any incoming links * * @return @e void */ protected static function _fUrlInit() { /** * Fix request uri */ self::_fixRequestUri(); if (ipsRegistry::$settings['use_friendly_urls']) { /* Grab and store accessing URL */ self::$_uri = preg_replace("/s=(&|\$)/", '', str_replace('/?', '/' . IPS_PUBLIC_SCRIPT . '?', $_SERVER['REQUEST_URI'])); $_urlBits = array(); /* Grab FURL data... */ if (!IN_DEV and is_file(FURL_CACHE_PATH)) { $templates = array(); include FURL_CACHE_PATH; /*noLibHook*/ self::$_seoTemplates = $templates; } else { /* Attempt to write it */ self::$_seoTemplates = IPSLib::buildFurlTemplates(); try { IPSLib::cacheFurlTemplates(); } catch (Exception $e) { } } if (is_array(self::$_seoTemplates) and count(self::$_seoTemplates)) { $uri = $_SERVER['REQUEST_URI'] ? $_SERVER['REQUEST_URI'] : @getenv('REQUEST_URI'); /* Bug 21295 - remove known URL from test URI */ $_t = !empty(ipsRegistry::$settings['board_url']) ? @parse_url(ipsRegistry::$settings['board_url']) : @parse_url(ipsRegistry::$settings['base_url']); $_toTest = ($_t['path'] and $_t['path'] != '/') ? preg_replace("#^{$_t['path']}#", '', $uri) : str_replace($_t['scheme'] . '://' . $_t['host'], '', $uri); $_404Check = $_toTest; //We want to retain any /index.php for this test later in this block of code $_toTest = str_ireplace(array('//' . IPS_PUBLIC_SCRIPT . '?', '/' . IPS_PUBLIC_SCRIPT . '?', '/' . IPS_PUBLIC_SCRIPT), '', $_toTest); $_gotMatch = false; foreach (self::$_seoTemplates as $key => $data) { if (empty($data['in']['regex'])) { continue; } /* Clean up regex */ $data['in']['regex'] = str_replace('#{__varBlock__}', preg_quote(self::$_seoTemplates['__data__']['varBlock'], '#'), $data['in']['regex']); if (preg_match($data['in']['regex'], $_toTest, $matches)) { $_gotMatch = true; /* Handling pages as a special thing? */ if ($data['isPagesMode']) { if (strstr($_toTest, self::$_seoTemplates['__data__']['varPage'])) { preg_match('#(' . preg_quote(self::$_seoTemplates['__data__']['varPage'], '#') . '(\\d+?))(?:$|' . preg_quote(self::$_seoTemplates['__data__']['varBlock'], '#') . ')#', $_toTest, $pageMatches); if ($pageMatches[1]) { $k = 'page'; $_GET[$k] = intval($pageMatches[2]); $_POST[$k] = intval($pageMatches[2]); $_REQUEST[$k] = intval($pageMatches[2]); $_urlBits[$k] = intval($pageMatches[2]); ipsRegistry::$request[$k] = intval($pageMatches[2]); $_toTest = str_replace($pageMatches[1], '', $_toTest); } } else { /* Redirect all < 3.4 links to the new sexy awesome format if need be */ if (self::$_seoTemplates['__data__']['varBlock'] != '/page__' && $uri && strstr($uri, '/page__')) { preg_match('#(.*)(page__.*)$#', $uri, $matches); $url = $matches[1]; $query = $matches[2]; $newQuery = '?'; $data = explode('__', substr($query, 6)); for ($i = 0, $j = count($data); $i < $j; $i++) { $newQuery .= ($i % 2 == 0 ? '&' : '=') . $data[$i]; } /* Class output not created here */ header("HTTP/1.1 301 Moved Permanently"); header("Location: " . $_t['scheme'] . '://' . $_t['host'] . $url . $newQuery); exit; } } } if (is_array($data['in']['matches'])) { foreach ($data['in']['matches'] as $_replace) { $k = IPSText::parseCleanKey($_replace[0]); if (strpos($_replace[1], '$') !== false) { $v = IPSText::parseCleanValue($matches[intval(str_replace('$', '', $_replace[1]))]); } else { $v = IPSText::parseCleanValue($_replace[1]); } $_GET[$k] = $v; $_POST[$k] = $v; $_REQUEST[$k] = $v; $_urlBits[$k] = $v; ipsRegistry::$request[$k] = $v; } } if (strpos($_toTest, self::$_seoTemplates['__data__']['varBlock']) !== false) { /* Changed how the input variables are parsed based on feedback in bug report 24907 @link http://community.invisionpower.com/tracker/issue-24907-member-list-pagination-not-work-with-checkbox Input variables now preserve array depth properly as a result */ $_parse = substr($_toTest, strpos($_toTest, self::$_seoTemplates['__data__']['varBlock']) + strlen(self::$_seoTemplates['__data__']['varBlock'])); $_data = explode(self::$_seoTemplates['__data__']['varSep'], $_parse); $_query = ''; foreach ($_data as $line) { list($k, $v) = explode(self::$_seoTemplates['__data__']['varJoin'], $line); $_query .= $k . '=' . $v . '&'; } $_data = array(); parse_str($_query, $_data); $_data = IPSLib::parseIncomingRecursively($_data); foreach ($_data as $k => $v) { $_GET[$k] = $v; $_POST[$k] = $v; $_REQUEST[$k] = $v; $_urlBits[$k] = $v; ipsRegistry::$request[$k] = $v; } } break; } } /* Check against the original request for 404 error */ $_404checkPass = false; if (!strstr($_404Check, '&') and !strstr($_404Check, '=') and (strstr($_404Check, IPS_PUBLIC_SCRIPT . '?/') or !strstr($_404Check, '.php'))) { $_404checkPass = true; } if (strstr($_404Check, '/' . IPS_PUBLIC_SCRIPT)) { if (preg_match("#(.+?)/" . preg_quote(IPS_PUBLIC_SCRIPT) . "#", $_404Check, $matches) and !is_file(DOC_IPS_ROOT_PATH . preg_replace('/(.+?)\\?.+/', '$1', $_404Check))) { $_404checkPass = true; } } /* Got a match? */ if (!defined('CCS_GATEWAY_CALLED') and !defined('IPS_ENFORCE_ACCESS') and !defined('LOFIVERSION_CALLED') and IPS_IS_MOBILE_APP === false and IPS_DEFAULT_PUBLIC_APP == 'forums' and $_gotMatch === false and $_toTest and $_toTest != '/' and $_toTest != '/?' and $_404checkPass) { self::$_noFurlMatch = true; } //----------------------------------------- // If using query string furl, extract any // secondary query string. // Ex: http://localhost/index.php?/path/file.html?key=value // Will pull the key=value properly //----------------------------------------- $_qmCount = substr_count($_toTest, '?'); /* We don't want to check for secondary query strings in the ACP */ if (!IN_ACP && $_qmCount > 1) { $_secondQueryString = substr($_toTest, strrpos($_toTest, '?') + 1); $_secondParams = explode('&', $_secondQueryString); if (count($_secondParams)) { foreach ($_secondParams as $_param) { list($k, $v) = explode('=', $_param); $k = IPSText::parseCleanKey($k); $v = IPSText::parseCleanValue($v); $_GET[$k] = $v; $_REQUEST[$k] = $v; $_urlBits[$k] = $v; ipsRegistry::$request[$k] = $v; } } } /* Process URL bits for extra ? in them */ /* We don't want to check for secondary query strings in the ACP */ if (!IN_ACP && is_array($_GET) and count($_GET)) { foreach ($_GET as $k => $v) { /* Nexus sends &url=.... as a parameter, which can have a ? in it, but we don't want to strip that out of the parameter or consider those values part of the input */ if ($k == 'url') { continue; } if (!is_array($v) and strstr($v, '?')) { list($rvalue, $more) = explode('?', $v); if ($rvalue and $more) { //$k = IPSText::parseCleanKey( $_k ); //$v = IPSText::parseCleanValue( $_v ); /* Reset key with correct value */ $_v = IPSText::parseCleanValue($rvalue); $_GET[$k] = $_v; $_REQUEST[$k] = $_v; $_urlBits[$k] = $_v; ipsRegistry::$request[$k] = $_v; /* Now add in the other value */ if (strstr($more, '=')) { list($_k, $_v) = explode('=', $more); if ($_k and $_v) { $_GET[$_k] = $_v; $_REQUEST[$_k] = $_v; $_urlBits[$_k] = $_v; ipsRegistry::$request[$_k] = $_v; } } } } } } } /* Reformat basic URL */ if (is_array($_urlBits)) { ipsRegistry::$settings['query_string_real'] = trim(http_build_query($_urlBits), '&'); } } }
/** * Try and deconstruct the link if it's a FURRY FURL * * @access protected * @param string Incoming URL * @return array Array of request data or false */ protected function _checkForFurl($url) { $_urlBits = array(); $_toTest = $url; $templates = array(); if (is_file(FURL_CACHE_PATH)) { $templates = array(); require FURL_CACHE_PATH; /*noLibHook*/ $_seoTemplates = $templates; } else { /* Attempt to write it */ $_seoTemplates = IPSLib::buildFurlTemplates(); try { IPSLib::cacheFurlTemplates(); } catch (Exception $e) { } } if (is_array($_seoTemplates) and count($_seoTemplates)) { foreach ($_seoTemplates as $key => $data) { if (empty($data['in']['regex'])) { continue; } if (preg_match($data['in']['regex'], $_toTest, $matches)) { if (is_array($data['in']['matches'])) { foreach ($data['in']['matches'] as $_replace) { $k = IPSText::parseCleanKey($_replace[0]); if (strpos($_replace[1], '$') !== false) { $v = IPSText::parseCleanValue($matches[intval(str_replace('$', '', $_replace[1]))]); } else { $v = IPSText::parseCleanValue($_replace[1]); } $_urlBits[$k] = $v; } } if (strpos($_toTest, $_seoTemplates['__data__']['varBlock']) !== false) { $_parse = substr($_toTest, strpos($_toTest, $_seoTemplates['__data__']['varBlock']) + strlen($_seoTemplates['__data__']['varBlock'])); $_data = explode($_seoTemplates['__data__']['varSep'], $_parse); $_c = 0; foreach ($_data as $_v) { if (!$_c) { $k = IPSText::parseCleanKey($_v); $v = ''; $_c++; } else { $v = IPSText::parseCleanValue($_v); $_c = 0; $_urlBits[$k] = $v; } } } break; } } //----------------------------------------- // If using query string furl, extract any // secondary query string. // Ex: http://localhost/index.php?/path/file.html?key=value // Will pull the key=value properly //----------------------------------------- $_qmCount = substr_count($_toTest, '?'); if ($_qmCount > 1) { $_secondQueryString = substr($_toTest, strrpos($_toTest, '?') + 1); $_secondParams = explode('&', $_secondQueryString); if (count($_secondParams)) { foreach ($_secondParams as $_param) { list($k, $v) = explode('=', $_param); $k = IPSText::parseCleanKey($k); $v = IPSText::parseCleanValue($v); $_urlBits[$k] = $v; } } } /* Process URL bits for extra ? in them */ if (is_array($_urlBits) and count($_urlBits)) { foreach ($_urlBits as $k => $v) { if (strstr($v, '?')) { list($rvalue, $more) = explode('?', $v); if ($rvalue and $more) { /* Reset key with correct value */ $_v = $rvalue; $_urlBits[$k] = $_v; /* Now add in the other value */ if (strstr($more, '=')) { list($_k, $_v) = explode('=', $more); if ($_k and $_v) { $_urlBits[$_k] = $_v; } } } } } } } return count($_urlBits) ? $_urlBits : false; }
/** * Recursively cleans keys and values and * inserts them into the input array * * @access public * @param mixed Input data * @param array Storage array for cleaned data * @param integer Current iteration * @return array Cleaned data */ public static function parseIncomingRecursively(&$data, $input = array(), $iteration = 0) { // Crafty hacker could send something like &foo[][][][][][]....to kill Apache process // We should never have an input array deeper than 20.. if ($iteration >= 20) { return $input; } foreach ($data as $k => $v) { if (is_array($v)) { $input[$k] = self::parseIncomingRecursively($data[$k], array(), ++$iteration); } else { $k = IPSText::parseCleanKey($k); $v = IPSText::parseCleanValue($v, false); $input[$k] = $v; } } return $input; }