diff --git a/common/functions.php b/common/functions.php index 27720de26..73944655a 100644 --- a/common/functions.php +++ b/common/functions.php @@ -280,8 +280,11 @@ function explode_with_escape($delimiter, $str, $limit = 0, $escape_char = '\\') */ function starts_with($needle, $haystack, $case_sensitive = true) { - if (strlen($needle) > strlen($haystack)) return false; - if ($case_sensitive) + if (strlen($needle) > strlen($haystack)) + { + return false; + } + elseif ($case_sensitive) { return !strncmp($needle, $haystack, strlen($needle)); } @@ -301,14 +304,17 @@ function starts_with($needle, $haystack, $case_sensitive = true) */ function ends_with($needle, $haystack, $case_sensitive = true) { - if (strlen($needle) > strlen($haystack)) return false; - if ($case_sensitive) + if (strlen($needle) > strlen($haystack)) { - return (substr($haystack, -strlen($needle)) === $needle); + return false; + } + elseif ($case_sensitive) + { + return strval($needle) === '' || substr($haystack, -strlen($needle)) === $needle; } else { - return (strtolower(substr($haystack, -strlen($needle))) === strtolower($needle)); + return strval($needle) === '' || strtolower(substr($haystack, -strlen($needle))) === strtolower($needle); } } @@ -322,7 +328,18 @@ function ends_with($needle, $haystack, $case_sensitive = true) */ function contains($needle, $haystack, $case_sensitive = true) { - return $case_sensitive ? (strpos($haystack, $needle) !== false) : (stripos($haystack, $needle) !== false); + if (strlen($needle) > strlen($haystack)) + { + return false; + } + elseif ($case_sensitive) + { + return (strpos($haystack, $needle) !== false); + } + else + { + return (stripos($haystack, $needle) !== false); + } } /** @@ -714,12 +731,9 @@ function is_empty_html_content($str) } /** - * Check if there is 'is_countable' function (PHP 7.3) - * If there is no 'is_countable' function, define it to check for Countable objects. + * Polyfill for is_countable() in PHP < 7.3 * - * cf. https://wiki.php.net/rfc/is-countable - * - * @param string $str The input string + * @param mixed $var * @return bool **/ if (!function_exists('is_countable')) @@ -729,3 +743,48 @@ if (!function_exists('is_countable')) return is_array($var) || $var instanceof Countable; } } + +/** + * Polyfill for str_starts_with() in PHP < 8.0 + * + * @param string $haystack + * @param string $needle + * @return bool + */ +if (!function_exists('str_starts_with')) +{ + function str_starts_with($haystack, $needle) + { + return strval($needle) === '' || strncmp($haystack, $needle, strlen($needle)) === 0; + } +} + +/** + * Polyfill for str_starts_with() in PHP < 8.0 + * + * @param string $haystack + * @param string $needle + * @return bool + */ +if (!function_exists('str_ends_with')) +{ + function str_ends_with($haystack, $needle) + { + return strval($needle) === '' || @substr_compare($haystack, $needle, -strlen($needle), strlen($needle)) === 0; + } +} + +/** + * Polyfill for str_starts_with() in PHP < 8.0 + * + * @param string $haystack + * @param string $needle + * @return bool + */ +if (!function_exists('str_contains')) +{ + function str_contains($haystack, $needle) + { + return strval($needle) === '' || strpos($haystack, $needle) !== false; + } +} diff --git a/tests/unit/functions/FunctionsTest.php b/tests/unit/functions/FunctionsTest.php index 22295c06e..e5447ac49 100644 --- a/tests/unit/functions/FunctionsTest.php +++ b/tests/unit/functions/FunctionsTest.php @@ -1,5 +1,13 @@ assertFalse(starts_with('FOO', 'foobar')); $this->assertTrue(starts_with('FOO', 'foobar', false)); $this->assertFalse(starts_with('bar', 'foobar')); + $this->assertFalse(starts_with('foobar', 'foo')); + $this->assertTrue(starts_with('', 'foobar')); $this->assertTrue(ends_with('bar', 'foobar')); $this->assertFalse(ends_with('BAR', 'foobar')); $this->assertTrue(ends_with('BAR', 'foobar', false)); $this->assertFalse(ends_with('foo', 'foobar')); + $this->assertFalse(ends_with('foobar', 'bar')); + $this->assertTrue(ends_with('', 'foobar')); $this->assertTrue(contains('foo', 'foo bar baz rhymix rocks')); $this->assertFalse(contains('barbaz', 'foo bar baz rhymix rocks')); $this->assertTrue(contains('RHYMIX', 'foo bar baz rhymix rocks', false)); $this->assertFalse(contains('ROCKS', 'foo bar baz rhymix rocks')); + $this->assertFalse(contains('foobar', 'bar')); + $this->assertTrue(contains('', 'foobar')); } public function testRangeFunctions() @@ -188,6 +202,8 @@ class FunctionsTest extends \Codeception\TestCase\Test $this->assertEquals(3, countobj(array('foo' => 1, 'bar' => 2, 'baz' => 3))); $this->assertEquals(3, countobj((object)array('foo' => 1, 'bar' => 2, 'baz' => 3))); $this->assertEquals(1, countobj('foobar')); + $this->assertEquals(42, countobj(new CountableTest)); + $this->assertEquals(0, countobj(new stdClass)); } public function testUTF8Functions() @@ -243,4 +259,31 @@ class FunctionsTest extends \Codeception\TestCase\Test $this->assertFalse(is_empty_html_content('
')); $this->assertFalse(is_empty_html_content('')); } + + public function testPolyfillFunctions() + { + $this->assertTrue(function_exists('is_countable')); + $this->assertTrue(is_countable(['foo', 'bar'])); + $this->assertFalse(is_countable('hello world')); + $this->assertTrue(is_countable(new CountableTest)); + $this->assertFalse(is_countable(new stdClass)); + + $this->assertTrue(function_exists('str_starts_with')); + $this->assertTrue(str_starts_with('foobar', 'foo')); + $this->assertFalse(str_starts_with('Foobar', 'FOO')); + $this->assertFalse(str_starts_with('', 'bar')); + $this->assertTrue(str_starts_with('foobar', '')); + + $this->assertTrue(function_exists('str_ends_with')); + $this->assertTrue(str_ends_with('foobar', 'bar')); + $this->assertFalse(str_ends_with('Foobar', 'BAR')); + $this->assertFalse(str_ends_with('', 'bar')); + $this->assertTrue(str_ends_with('foobar', '')); + + $this->assertTrue(function_exists('str_contains')); + $this->assertTrue(str_contains('foobarbazz', 'bar')); + $this->assertFalse(str_contains('foobarbazz', 'BAR')); + $this->assertFalse(str_contains('', 'foobar')); + $this->assertTrue(str_contains('foobar', '')); + } }