diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php index 98499b4b90ff62b5ccaf352e0cad569fb3000c63..109535f25e027d8368e2a186bbc37be06fde5de7 100644 --- a/extension/qfq/qfq/AbstractBuildForm.php +++ b/extension/qfq/qfq/AbstractBuildForm.php @@ -727,10 +727,13 @@ abstract class AbstractBuildForm { $attribute = ''; $class = 'form-control'; - if (isset($formElement[FE_TYPE_AHEAD_SQL])) { - $class .= ' qfq-type-ahead'; - $dataSip = $this->sip->queryStringToSip(TYPE_AHEAD_SELECT . '=' . $formElement[FE_TYPE_AHEAD_SQL], RETURN_SIP); - $attribute .= Support::doAttribute(DATA_SIP, $dataSip); + $typeAheadUrlParam = $this->typeAheadBuildParam($formElement); + if ($typeAheadUrlParam != '') { + $class .= ' ' . CLASS_TYPEAHEAD; + $dataSip = $this->sip->queryStringToSip($typeAheadUrlParam, RETURN_SIP); + $attribute .= Support::doAttribute(DATA_TYPEAHEAD_SIP, $dataSip); + $attribute .= Support::doAttribute(DATA_TYPEAHEAD_LIMIT, $formElement[FE_TYPEAHEAD_LIMIT]); + $attribute .= Support::doAttribute(DATA_TYPEAHEAD_MINLENGTH, $formElement[FE_TYPEAHEAD_MINLENGTH]); } $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]); @@ -781,6 +784,47 @@ abstract class AbstractBuildForm { } + /** + * Check $formElement for FE_TYPE_AHEAD_SQL or FE_TYPE_AHEAD_LDAP_SERVER. + * If one of them is given: fill $urlParam. + * + * @param array $formElement + * @return string + */ + private function typeAheadBuildParam(array $formElement) { + + $urlParam = ''; + + if (isset($formElement[FE_TYPEAHEAD_SQL])) { + $urlParam = FE_TYPEAHEAD_SQL . '=' . $formElement[FE_TYPEAHEAD_SQL]; + } elseif (isset($formElement[FE_TYPEAHEAD_LDAP_SERVER])) { + $formElement[FE_TYPEAHEAD_LDAP_SERVER] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_SERVER); + $formElement[FE_TYPEAHEAD_LDAP_BASE_DN] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_BASE_DN); + $formElement[FE_TYPEAHEAD_LDAP_SEARCH] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_SEARCH); + $formElement[FE_TYPEAHEAD_LDAP_VALUE_PRINTF] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_VALUE_PRINTF); + $formElement[FE_TYPEAHEAD_LDAP_KEY_PRINTF] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_KEY_PRINTF); + $formElement[FE_TYPEAHEAD_LIMIT] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LIMIT, TYPEAHEAD_DEFAULT_LIMIT); + + $arr = [ + FE_TYPEAHEAD_LDAP_SERVER => $formElement[FE_TYPEAHEAD_LDAP_SERVER], + FE_TYPEAHEAD_LDAP_BASE_DN => $formElement[FE_TYPEAHEAD_LDAP_BASE_DN], + FE_TYPEAHEAD_LDAP_SEARCH => $formElement[FE_TYPEAHEAD_LDAP_SEARCH], + FE_TYPEAHEAD_LDAP_VALUE_PRINTF => $formElement[FE_TYPEAHEAD_LDAP_VALUE_PRINTF], + FE_TYPEAHEAD_LDAP_KEY_PRINTF => $formElement[FE_TYPEAHEAD_LDAP_KEY_PRINTF], + FE_TYPEAHEAD_LIMIT => $formElement[FE_TYPEAHEAD_LIMIT], + ]; + + $urlParam = OnArray::toString($arr); + } + + if ($urlParam != '') { + $formElement[FE_TYPEAHEAD_LIMIT] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LIMIT, TYPEAHEAD_DEFAULT_LIMIT); + $formElement[FE_TYPEAHEAD_MINLENGTH] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_MINLENGTH, 2); + } + + return $urlParam; + } + /** * Calculates the maxlength of an input field, based on formElement type, formElement user definition and table.field definition. * diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index 8b0de6dfaa6a1d7364bf22c88c2cc0fcd2a7b746..4ae06fad2732aaee4d054ec0e1df14c43187df44 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -162,6 +162,7 @@ const ERROR_OVERWRITE_RECORD_ID = 1073; const ERROR_MISSING_SLAVE_ID_DEFINITION = 1074; const ERROR_MISSING_INTL = 1075; const ERROR_HTML_TOKEN_TOO_SHORT = 1076; +const ERROR_MISSING_PRINTF_ARGUMENTS = 1077; // Subrecord const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100; @@ -198,6 +199,8 @@ const ERROR_UPLOAD = 1500; const ERROR_UNKNOWN_ACTION = 1502; const ERROR_NO_TARGET_PATH_FILE_NAME = 1503; +const ERROR_LDAP_CONNECT = 1600; + // KeyValueParser const ERROR_KVP_VALUE_HAS_NO_KEY = 1900; @@ -374,15 +377,17 @@ const VAR_SLAVE_ID = ACTION_KEYWORD_SLAVE_ID; const VAR_FILENAME = 'filename'; // Original filename of an uploaded file. -// Class DB can operate in these modes +// PHP class DB can operate in these modes const MODE_DB_REGULAR = 'regular'; const MODE_DB_NO_LOG = 'noLog'; -// CLASS TypeAhead -const TYPE_AHEAD_API_QUERY = 'query'; // Name of parameter in API call of typeahead.php?query=...&s=... -const TYPE_AHEAD_API_SIP = 'sip'; // Name of parameter in API call of typeahead.php?query=...&s=... -const TYPE_AHEAD_SELECT = 'select'; // Value of FE_TYPE_AHEAD_SQL, stored in a SIP under the name TYPE_AHEAD_SELECT. -const TYPE_AHEAD_LDAP = 'ldap'; // Value of FE_TYPE_AHEAD_LDAP, stored in a SIP under the name TYPE_AHEAD_LDAP. +// PHPO class Typeahead +const TYPEAHEAD_API_QUERY = 'query'; // Name of parameter in API call of typeahead.php?query=...&s=... - See also FE_TYPE_AHEAD_SQL +const TYPEAHEAD_API_SIP = 'sip'; // Name of parameter in API call of typeahead.php?query=...&s=... +const TYPEAHEAD_DEFAULT_LIMIT = 20; + +const SINGLE_TICK = "'"; +const DOUBLE_TICK = '"'; // TOKEN evaluate const TOKEN_ESCAPE_SINGLE_TICK = 's'; @@ -440,18 +445,25 @@ const API_JSON_HIDDEN = 'hidden'; const API_JSON_DISABLED = 'disabled'; const API_JSON_REQUIRED = 'required'; -const DATA_HIDDEN = 'data-hidden'; -const DATA_DISABLED = 'data-disabled'; -const DATA_REQUIRED = 'data-required'; - -const DATA_SIP = 'data-sip'; // Used for typeAhead - const API_ANSWER_STATUS_SUCCESS = 'success'; const API_ANSWER_STATUS_ERROR = 'error'; const API_ANSWER_REDIRECT_CLIENT = 'client'; const API_ANSWER_REDIRECT_NO = 'no'; const API_ANSWER_REDIRECT_URL = 'url'; +const API_TYPEAHEAD_KEY = 'key'; +const API_TYPEAHEAD_VALUE = 'value'; + +const DATA_HIDDEN = 'data-hidden'; +const DATA_DISABLED = 'data-disabled'; +const DATA_REQUIRED = 'data-required'; + +const DATA_TYPEAHEAD_SIP = 'data-typeahead-sip'; // Used for typeAhead +const DATA_TYPEAHEAD_LIMIT = 'data-typeahead-limit'; +const DATA_TYPEAHEAD_MINLENGTH = 'data-typeahead-minlength'; +const CLASS_TYPEAHEAD = 'qfq-typeahead'; + + // BuildForm const SYMBOL_NEW = 'new'; const SYMBOL_EDIT = 'edit'; @@ -591,12 +603,18 @@ const FE_TEMPLATE_GROUP_CLASS = 'tgClass'; const FE_TEMPLATE_GROUP_DEFAULT_MAX_LENGTH = 5; const FE_TEMPLATE_GROUP_NAME_PATTERN = '%d'; const FE_BUTTON_CLASS = 'buttonClass'; -const FE_TYPE_AHEAD_SQL = 'typeAheadSql'; +const FE_TYPEAHEAD_LIMIT = 'typeaheadLimit'; +const FE_TYPEAHEAD_MINLENGTH = 'typeaheadMinLength'; +const FE_TYPEAHEAD_SQL = 'typeAheadSql'; +const FE_TYPEAHEAD_LDAP_SERVER = 'typeAheadLdapServer'; +const FE_TYPEAHEAD_LDAP_BASE_DN = 'typeAheadLdapBaseDn'; +const FE_TYPEAHEAD_LDAP_SEARCH = 'typeAheadLdapSearch'; +const FE_TYPEAHEAD_LDAP_VALUE_PRINTF = 'typeAheadLdapValuePrintf'; +const FE_TYPEAHEAD_LDAP_KEY_PRINTF = 'typeAheadLdapKeyPrintf'; const RETYPE_FE_NAME_EXTENSION = 'RETYPE'; const FE_HTML_ID = 'htmlId'; // Will be dynamically computed during runtime. - // FormElement Types const FE_TYPE_UPLOAD = 'upload'; const FE_TYPE_EXTRA = 'extra'; diff --git a/extension/qfq/qfq/form/TypeAhead.php b/extension/qfq/qfq/form/TypeAhead.php index d5152d00dc393d678252ce9a2b387048f9500b0b..fc8d032bfb49dafe1d5f2e56f9f5830c519b0aa7 100644 --- a/extension/qfq/qfq/form/TypeAhead.php +++ b/extension/qfq/qfq/form/TypeAhead.php @@ -8,12 +8,15 @@ namespace qfq; +use TYPO3\CMS\Core\FormProtection\Exception; + require_once(__DIR__ . '/../store/Sip.php'); //require_once(__DIR__ . '/store/FillStoreForm.php'); require_once(__DIR__ . '/../store/Session.php'); require_once(__DIR__ . '/../Constants.php'); //require_once(__DIR__ . '/Save.php'); //require_once(__DIR__ . '/helper/KeyValueStringParser.php'); +require_once(__DIR__ . '/../helper/Ldap.php'); //require_once(__DIR__ . '/helper/HelperFormElement.php'); //require_once(__DIR__ . '/exceptions/UserFormException.php'); //require_once(__DIR__ . '/exceptions/CodeException.php'); @@ -47,12 +50,12 @@ class TypeAhead { */ public function __construct($phpUnit = false) { - if (!isset($_GET[TYPE_AHEAD_API_QUERY]) || !isset($_GET[TYPE_AHEAD_API_SIP])) { - throw new CodeException('Missing GET parameter "' . TYPE_AHEAD_API_SIP . '" or "' . TYPE_AHEAD_API_QUERY . '"'); + if (!isset($_GET[TYPEAHEAD_API_QUERY]) || !isset($_GET[TYPEAHEAD_API_SIP])) { + throw new CodeException('Missing GET parameter "' . TYPEAHEAD_API_SIP . '" or "' . TYPEAHEAD_API_QUERY . '"'); } - $this->vars[TYPE_AHEAD_API_QUERY] = $_GET[TYPE_AHEAD_API_QUERY]; - $this->vars[TYPE_AHEAD_API_SIP] = $_GET[TYPE_AHEAD_API_SIP]; + $this->vars[TYPEAHEAD_API_QUERY] = $_GET[TYPEAHEAD_API_QUERY]; + $this->vars[TYPEAHEAD_API_SIP] = $_GET[TYPEAHEAD_API_SIP]; $session = Session::getInstance($phpUnit); @@ -67,25 +70,41 @@ class TypeAhead { * @throws UserFormException */ public function process() { - //test + $arr = array(); $values = array(); $sipClass = new Sip(); - $sipVars = $sipClass->getVarsFromSip($this->vars[TYPE_AHEAD_API_SIP]); + $sipVars = $sipClass->getVarsFromSip($this->vars[TYPEAHEAD_API_SIP]); - $query = '%' . $this->vars[TYPE_AHEAD_API_QUERY] . '%'; - $cnt = substr_count($sipVars[TYPE_AHEAD_SELECT], '?'); - for ($ii = 0; $ii < $cnt; $ii++) { - $values[] = $query; + if (isset($sipVars[FE_TYPEAHEAD_SQL])) { + $arr = typeAheadSql($sipVars, $this->vars[TYPEAHEAD_API_QUERY]); + } elseif (isset($sipVars[FE_TYPEAHEAD_LDAP_SERVER])) { + $ldap = new Ldap(); + $arr = $ldap->process($sipVars, $this->vars[TYPEAHEAD_API_QUERY]); } - if (isset($sipVars[TYPE_AHEAD_SELECT])) { - $arr = $this->db->sql($sipVars[TYPE_AHEAD_SELECT], ROW_REGULAR, $values); + return $arr; + } + + /** + * @param array $config + * @param $query + * @return array|int + * @throws CodeException + * @throws DbException + */ + private function typeAheadSql(array $config, $query) { + $values = array(); + + $query = '%' . $query . '%'; + $cnt = substr_count($config[FE_TYPEAHEAD_SQL], '?'); + for ($ii = 0; $ii < $cnt; $ii++) { + $values[] = $query; } - return $arr; + return $this->db->sql($config[FE_TYPEAHEAD_SQL], ROW_REGULAR, $values); } } \ No newline at end of file diff --git a/extension/qfq/qfq/helper/KeyValueStringParser.php b/extension/qfq/qfq/helper/KeyValueStringParser.php index b44f9ab9c46f7b682ebd433e46a51d690f0c8df6..c940cd6ff6e189a701e1e9d5c85e0fee251c00ad 100644 --- a/extension/qfq/qfq/helper/KeyValueStringParser.php +++ b/extension/qfq/qfq/helper/KeyValueStringParser.php @@ -147,7 +147,89 @@ class KeyValueStringParser { if (in_array($string[0], $quotes) === true && self::isFirstAndLastCharacterIdentical($string)) { return substr($string, 1, strlen($string) - 2); } - return $string; } + + /** + * Works like PHP 'explode()', but respects $delimeter wrapped in ticks (single or double): those are not interpreted as delimiter. + * + * E.g.: "a,b,'c,d',e" with delimiter ',' will result in [ 'a', 'b', 'c,d', 'e' ] + * + * @param $delimeter + * @param $str + * @param int $limit + * @return array|bool + * @throws CodeException + */ + public static function explodeWrapped($delimeter, $str, $limit=PHP_INT_MAX ) { + + if($delimeter=='') { + return false; + } + + if($limit<0) { + throw new CodeException("Not Implemented: limit<0", ERROR_NOT_IMPLEMENTED); + } + + if($limit==0) { + $limit = 1; + } + + $final = array(); + $startToken=''; + $onHold = ''; + + $cnt = 0; + $arr = explode($delimeter, $str, PHP_INT_MAX); + foreach($arr as $value) { + $trimmed=trim($value); + if($value=='' && $startToken=='') { + + if($cnt<$limit) { + $final[] = ''; + $cnt++; + } + continue; + } + + if($startToken=='') { + switch ($trimmed[0]) { + case SINGLE_TICK: + case DOUBLE_TICK: + if($trimmed[0] == substr($trimmed, -1)) { + break; // In case start and end token is in one exploded item + } + $startToken = $trimmed[0]; + $onHold = $value; + continue 2; + default: + break; + } + + if($cnt>=$limit) { + $final[$cnt-1] .= $delimeter . $value; + } else { + $final[] = $value; + $cnt++; + } + continue; + } else { + $onHold .= $delimeter . $value; + $lastChar = substr($trimmed,-1); + if($startToken == $lastChar) { + + if($cnt>=$limit) { + $final[$cnt-1] .= $delimeter . $onHold; + } else { + $final[] = $onHold; + $cnt++; + } + $startToken = ''; + $onHold = ''; + } + } + } + + return $final; + } } diff --git a/extension/qfq/qfq/helper/Ldap.php b/extension/qfq/qfq/helper/Ldap.php new file mode 100644 index 0000000000000000000000000000000000000000..1425f7def6b0d037e746e72bda262cf430dbdd64 --- /dev/null +++ b/extension/qfq/qfq/helper/Ldap.php @@ -0,0 +1,89 @@ +<?php +/** + * Created by PhpStorm. + * User: crose + * Date: 3/14/17 + * Time: 11:32 PM + */ + +namespace qfq; + +use qfq; + +require_once(__DIR__ . '/KeyValueStringParser.php'); + +class Ldap { + + /** + * @param $query + * @return array + * @throws UserFormException + */ + public function process($config, $query) { + $arr = array(); + + $ldapServer = $config[FE_TYPEAHEAD_LDAP_SERVER]; + $ldapBaseDn = $config[FE_TYPEAHEAD_LDAP_BASE_DN]; + $ldapSearch = $config[FE_TYPEAHEAD_LDAP_SEARCH]; + $ldapSearch = str_replace('?', $query, $ldapSearch); + $ldapLimit = $config[FE_TYPEAHEAD_LIMIT]; + + $ds = ldap_connect($ldapServer); // must be a valid LDAP server! + if (!$ds) { + throw new UserFormException("Unable to connect to LDAP server: $ldapServer", ERROR_LDAP_CONNECT); + } + + $sr = ldap_search($ds, $ldapBaseDn, $ldapSearch, null, null, $ldapLimit); + $info = ldap_get_entries($ds, $sr); + + for ($i = 0; $i < $info["count"]; $i++) { + + $key = $this->printfResult($config[FE_TYPEAHEAD_LDAP_KEY_PRINTF], $info[$i]); + $value = $this->printfResult($config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF], $info[$i]); + + $arr[] = [API_TYPEAHEAD_KEY => $key, API_TYPEAHEAD_VALUE => $value]; + } + + ldap_close($ds); + + return $arr; + } + + /** + * Collects values from $inforElelement and sprintf them as described in $format. + * + * @param $format + * @param $infoElement + * @return string output of sprintf + * @throws CodeException + * @throws UserFormException + */ + private function printfResult($format, $infoElement) { + + // $format: "'%s / %s / %s', cn, mail, telephonenumber". + // keyArr[0]: printf format string + // keyArr[1..x]: columnnames + $keyArr = KeyValueStringParser::explodeWrapped(',', $format); + if (count($keyArr) <= 2) { + throw new UserFormException("Expect a sprintf compatible format string with a least one argument. Got: '" . $format . "'", ERROR_MISSING_PRINTF_ARGUMENTS); + } + + $args = array(); + $args[] = $keyArr[0]; + $keyArr = array_shift($keyArr); + foreach ($keyArr as $arg) { + $keyName = trim($arg); + $args[] = $infoElement[$keyName][0]; + } + +// if(isset($info[$i]["mail"][0])) { +// $key = $info[$i]["mail"][0]; +// if($key===false || $key===null || $key=='') { +// continue; +// } +// $arr[] = [ 'key' => $info[$i]["mail"][0], 'value' => $info[$i]["cn"][0] . ' / ' . $info[$i]["mail"][0] . ' / ' . $info[$i]["telephonenumber"][0]]; +// } + + return call_user_func_array('sprintf', $args); + } +} \ No newline at end of file diff --git a/extension/qfq/qfq/helper/OnArray.php b/extension/qfq/qfq/helper/OnArray.php index 54b48b57f16c8e9b6566e6803690e22c7a29dd64..0da2d056500e88658f9dcde13ef3064d096c89ca 100644 --- a/extension/qfq/qfq/helper/OnArray.php +++ b/extension/qfq/qfq/helper/OnArray.php @@ -29,6 +29,7 @@ class OnArray { * @param array $dataArray * @param string $keyValueGlue * @param string $rowGlue + * @param string $encloseValue - char (or string) to enclose the value with. * @return string */ public static function toString(array $dataArray, $keyValueGlue = '=', $rowGlue = '&', $encloseValue = '') { diff --git a/extension/qfq/qfq/helper/Support.php b/extension/qfq/qfq/helper/Support.php index 7526a468bc2e53cd57b2fe609e96bbd0464aa137..1fc32d719d2c8725828dc71f385da52c417c36f8 100644 --- a/extension/qfq/qfq/helper/Support.php +++ b/extension/qfq/qfq/helper/Support.php @@ -625,6 +625,9 @@ class Support { } /** + * Looks in $arr if there is an element $index. If not, set it to $value. + * If $overwriteThis!=false, replace the the original value with $value, if $arr[$index]==$overwriteThis. + * * @param array $arr * @param string $index * @param string $value @@ -692,6 +695,8 @@ class Support { } /** + * Convert 'false' and 'empty' to '0'. + * * @param $val * @return string */ diff --git a/extension/qfq/tests/phpunit/KeyValueStringParserTest.php b/extension/qfq/tests/phpunit/KeyValueStringParserTest.php index bebcb8339347d42fe821179e75c2c71a767a4098..c19b1c48be886cd87467283a761f5384a5742c3a 100644 --- a/extension/qfq/tests/phpunit/KeyValueStringParserTest.php +++ b/extension/qfq/tests/phpunit/KeyValueStringParserTest.php @@ -169,4 +169,85 @@ class KeyValueStringParserTest extends \PHPUnit_Framework_TestCase { $this->assertEquals($expected, $actual); $this->assertCount(2, $actual); } + + public function testExplodeContent() { + $actual = keyValueStringParser::explodeWrapped('', ''); + $this->assertEquals(false, $actual); + + $actual = keyValueStringParser::explodeWrapped(':', ''); + $this->assertEquals([''], $actual); + + $actual = keyValueStringParser::explodeWrapped(':', 'a,b,c'); + $this->assertEquals(['a,b,c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', 'a,b,c'); + $this->assertEquals(['a' , 'b' ,'c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', ' a,b,c'); + $this->assertEquals([' a' , 'b' ,'c'], $actual); + $actual = keyValueStringParser::explodeWrapped(',', 'a ,b,c'); + $this->assertEquals(['a ' , 'b' ,'c'], $actual); + $actual = keyValueStringParser::explodeWrapped(',', ' a ,b,c'); + $this->assertEquals([' a ' , 'b' ,'c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', 'a, b,c'); + $this->assertEquals(['a' , ' b' ,'c'], $actual); + $actual = keyValueStringParser::explodeWrapped(',', 'a,b ,c'); + $this->assertEquals(['a' , 'b ' ,'c'], $actual); + $actual = keyValueStringParser::explodeWrapped(',', 'a, b ,c'); + $this->assertEquals(['a' , ' b ' ,'c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', 'a,b, c'); + $this->assertEquals(['a' , 'b' ,' c'], $actual); + $actual = keyValueStringParser::explodeWrapped(',', 'a,b,c '); + $this->assertEquals(['a' , 'b' ,'c '], $actual); + $actual = keyValueStringParser::explodeWrapped(',', 'a,b, c '); + $this->assertEquals(['a' , 'b' ,' c '], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', 'a,"b",c'); + $this->assertEquals(['a' , '"b"','c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', 'a,"b,b",c'); + $this->assertEquals(['a' , '"b,b"','c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "a,'b',c"); + $this->assertEquals(['a' , "'b'",'c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "a,'b,b',c"); + $this->assertEquals(['a' , "'b,b'",'c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "a,'b,b,b',c"); + $this->assertEquals(['a' , "'b,b,b'",'c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "'a,a,a,a','b','c,c,c,c,c'"); + $this->assertEquals(["'a,a,a,a'" , "'b'","'c,c,c,c,c'"], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', " 'a,a,a' , 'b' , 'c,c' "); + $this->assertEquals([" 'a,a,a' " , " 'b' "," 'c,c' "], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', 'a,b,c', 2); + $this->assertEquals(['a' , 'b,c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "'a,a',b,c", 2); + $this->assertEquals(["'a,a'" , 'b,c'], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "a,'b,b',c", 2); + $this->assertEquals(['a' , "'b,b',c" ], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "a,b,'c,c'", 2); + $this->assertEquals(['a' , "b,'c,c'" ], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "a,b,c", 0); + $this->assertEquals(['a,b,c' ], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "a,b,c", 1); + $this->assertEquals(['a,b,c' ], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "'a,b',c", 1); + $this->assertEquals(["'a,b',c" ], $actual); + + $actual = keyValueStringParser::explodeWrapped(',', "a,'b,c'", 1); + $this->assertEquals(["a,'b,c'" ], $actual); + + } }