Compare commits

..

5 Commits

11 changed files with 260 additions and 25 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2023 museum-digital Copyright (c) 2023-2025 museum-digital
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -0,0 +1,18 @@
<?PHP
declare(strict_types = 1);
/**
* Reports a failure to create a new directory.
*/
final class MDFailedToDeleteFile extends Exception {
/**
* Error message.
*
* @return string
*/
public function errorMessage() {
//error message
return 'Failed to delete file';
}
}

View File

@ -0,0 +1,18 @@
<?PHP
declare(strict_types = 1);
/**
* Reports a failure due to insufficient permissions in the file system.
*/
final class MDFilePermissionsException extends Exception {
/**
* Error message.
*
* @return string
*/
public function errorMessage() {
//error message
return 'Insufficient file system permissions';
}
}

0
phpstan-baseline.neon Normal file
View File

12
phpstan.neon Normal file
View File

@ -0,0 +1,12 @@
parameters:
level: 8
bootstrapFiles:
- ./tests/bootstrap.php
paths:
- src
- tests
dynamicConstantNames:
- DATABASENAME
- DATABASENAME_NODA
includes:
- phpstan-baseline.neon

View File

@ -110,7 +110,15 @@ final class MD_STD {
public static function unlink(string $filename):void { public static function unlink(string $filename):void {
if (\unlink($filename) === false) { if (\unlink($filename) === false) {
throw new MDFileDoesNotExist("Failed to delete: $filename"); if (!\is_file($filename)) {
throw new MDFileDoesNotExist("Failed to delete $filename, it did not exist");
}
else if (!\is_writable(dirname($filename))) {
throw new MDFilePermissionsException("Failed to delete $filename");
}
else {
throw new MDFailedToDeleteFile("Failed to delete $filename");
}
} }
} }
@ -986,4 +994,43 @@ final class MD_STD {
} . $year . '-' . $month . '-' . $day; } . $year . '-' . $month . '-' . $day;
} }
/**
* Finds the next occurence of any of a given set of substrings.
*
* @param string $haystack The string to search in.
* @param non-empty-array<non-empty-string> $needles The strings to search for.
* @param integer $offset If specified, search will
* start this number of
* characters counted from
* the beginning of the string.
* If the offset is negative,
* the search will start this
* number of characters
* counted from the end of
* the string.
*
* @return array{position: integer, needle: string}|array{}
*/
public static function strpos_multi(string $haystack, array $needles, int $offset = 0):array {
$lowest_option = [];
foreach ($needles as $needle) {
if (($pos = strpos($haystack, $needle, $offset)) !== false) {
if (empty($lowest_option)) {
$lowest_option = ['position' => $pos, 'needle' => $needle];
}
else if ($pos < $lowest_option['position']) {
$lowest_option = ['position' => $pos, 'needle' => $needle];
}
}
}
return $lowest_option;
}
} }

39
src/MD_STD_STRINGS.php Normal file
View File

@ -0,0 +1,39 @@
<?PHP
/**
* Gathers wrappers for handling strings.
*/
declare(strict_types = 1);
/**
* Encapsulates functions for handling strings.
*/
final class MD_STD_STRINGS {
/**
* Duplicates words ending in a given set of strings (e.g. dots) that serve as bind chars
* to allow indexing to then allow searching for them in either form.
*
* @param string $input Input string.
*
* @return string
*/
public static function duplicate_words_with_dots_for_indexing(string $input):string {
$charsToDuplicateOn = ',.!-';
$wordsToAdd = [];
$words = explode(' ', $input);
foreach ($words as $word) {
$trimmed = trim($word, $charsToDuplicateOn);
if ($trimmed !== $word) {
$wordsToAdd[] = $trimmed;
}
}
if (empty($wordsToAdd)) {
return $input;
}
return $input . ' ' . implode(' ', $wordsToAdd);
}
}

View File

@ -62,7 +62,7 @@ final class MD_STD_TEST_PROVIDERS {
/** /**
* Data provider for working mail addresses. * Data provider for working mail addresses.
* *
* @return array<array{0: string, 1: string}> * @return array<array{0: string}>
*/ */
public static function invalid_email_provider():array { public static function invalid_email_provider():array {

View File

@ -116,7 +116,7 @@ final class MD_STD_IN_Test extends TestCase {
/** /**
* Data provider for invalid latitudes. * Data provider for invalid latitudes.
* *
* @return array<array{0: mixed, 1: string}> * @return array<array{0: mixed, 1: class-string<Throwable>}>
*/ */
public static function invalid_latitude_provider():array { public static function invalid_latitude_provider():array {
@ -588,8 +588,8 @@ final class MD_STD_IN_Test extends TestCase {
/** /**
* Function for testing validate_longitude(). * Function for testing validate_longitude().
* *
* @param mixed $to_validate Input to validate. * @param mixed $to_validate Input to validate.
* @param string $exceptionClass Exception class. * @param class-string<Throwable> $exceptionClass Exception class.
* *
* @return void * @return void
*/ */
@ -619,8 +619,8 @@ final class MD_STD_IN_Test extends TestCase {
/** /**
* Function for testing validate_latitude(). * Function for testing validate_latitude().
* *
* @param mixed $to_validate Input to validate. * @param mixed $to_validate Input to validate.
* @param string $exceptionClass Exception class. * @param class-string<Throwable> $exceptionClass Exception class.
* *
* @return void * @return void
*/ */
@ -677,9 +677,16 @@ final class MD_STD_IN_Test extends TestCase {
*/ */
public function test_ensureStringIsUtf8():void { public function test_ensureStringIsUtf8():void {
if (empty($convToIso8859 = iconv("UTF-8", 'ISO-8859-1//TRANSLIT', "ä"))) {
throw new Exception("Iconv returned empty result");
}
if (empty($convToIso2022 = iconv("UTF-8", 'ISO-2022-JP//TRANSLIT', "ä"))) {
throw new Exception("Iconv returned empty result");
}
self::assertEquals("ä", MD_STD_IN::ensureStringIsUtf8("ä")); self::assertEquals("ä", MD_STD_IN::ensureStringIsUtf8("ä"));
self::assertEquals("ä", MD_STD_IN::ensureStringIsUtf8(iconv("UTF-8", 'ISO-8859-1//TRANSLIT', "ä"))); self::assertEquals("ä", MD_STD_IN::ensureStringIsUtf8($convToIso8859));
self::assertEquals("a", MD_STD_IN::ensureStringIsUtf8(iconv("UTF-8", 'ISO-2022-JP//TRANSLIT', "ä"))); self::assertEquals("a", MD_STD_IN::ensureStringIsUtf8($convToIso2022));
} }
} }

View File

@ -0,0 +1,53 @@
<?PHP
/**
* Tests for MD_STD_IN.
*
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
*/
declare(strict_types = 1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Small;
use PHPUnit\Framework\Attributes\CoversClass;
/**
* Tests for MD_STD_STRINGS.
*/
#[small]
#[CoversClass(\MD_STD_STRINGS::class)]
final class MD_STD_STRINGS_Test extends TestCase {
/**
* Data provider for strings to duplicate words with dots in.
*
* @return array<array{0: string, 1: string}>
*/
public static function duplicated_words_with_dots_provider():array {
$values = [
["hallo test. hallo", "hallo test. hallo test"],
];
$output = [];
foreach ($values as $value) {
$output[$value[0] . ' > ' . $value[1]] = $value;
}
return $output;
}
/**
* Function for testing duplicate_words_with_dots_for_indexing().
*
* @param string $input Input.
* @param string $expected Expected output.
*
* @return void
*/
#[DataProvider('duplicated_words_with_dots_provider')]
public function test_duplicate_words_with_dots_for_indexing(string $input, string $expected):void {
self::assertEquals($expected, MD_STD_STRINGS::duplicate_words_with_dots_for_indexing($input));
}
}

View File

@ -21,7 +21,7 @@ final class MD_STD_Test extends TestCase {
/** /**
* Data provider for returning a tmp file. * Data provider for returning a tmp file.
* *
* @return array<string, array{0: string, 1: string}> * @return array<string, array{0: non-empty-string}>
*/ */
public static function file_name_in_tmp_provider():array { public static function file_name_in_tmp_provider():array {
@ -37,7 +37,7 @@ final class MD_STD_Test extends TestCase {
/** /**
* Data provider for invalid directories. * Data provider for invalid directories.
* *
* @return array<string, array{0: string}> * @return array<non-empty-string, array{0: non-empty-string}>
*/ */
public static function invalid_directory_provider():array { public static function invalid_directory_provider():array {
@ -118,7 +118,7 @@ final class MD_STD_Test extends TestCase {
/** /**
* Checks if a file can be written. * Checks if a file can be written.
* *
* @param string $temp_file Tmp file. * @param non-empty-string $temp_file Tmp file.
* *
* @return void * @return void
*/ */
@ -146,8 +146,8 @@ final class MD_STD_Test extends TestCase {
/** /**
* Check MD_STD::realpath throws exception on non-existing path. * Check MD_STD::realpath throws exception on non-existing path.
* *
* @param string $to_validate Input to validate. * @param non-empty-string $to_validate Input to validate.
* @param string $exceptionClass Exception class. * @param class-string<Throwable> $exceptionClass Exception class.
* *
* @return void * @return void
*/ */
@ -162,8 +162,8 @@ final class MD_STD_Test extends TestCase {
/** /**
* Checks if a file can be read. * Checks if a file can be read.
* *
* @param string $to_validate Input to validate. * @param non-empty-string $to_validate Input to validate.
* @param string $exceptionClass Exception class. * @param class-string<Throwable> $exceptionClass Exception class.
* *
* @return void * @return void
*/ */
@ -229,7 +229,7 @@ final class MD_STD_Test extends TestCase {
/** /**
* Checks if nothing happens if a file does not exist. * Checks if nothing happens if a file does not exist.
* *
* @param string $temp_file Tmp file. * @param non-empty-string $temp_file Tmp file.
* *
* @return void * @return void
*/ */
@ -248,7 +248,7 @@ final class MD_STD_Test extends TestCase {
/** /**
* Checks unlink_if_exists works. * Checks unlink_if_exists works.
* *
* @param string $temp_file Tmp file. * @param non-empty-string $temp_file Tmp file.
* *
* @return void * @return void
*/ */
@ -268,7 +268,10 @@ final class MD_STD_Test extends TestCase {
*/ */
public function test_scandir():void { public function test_scandir():void {
$files = scandir(__DIR__); if (empty($files = scandir(__DIR__))) {
throw new Exception("Running regular scandir() failed on script directory: " . __DIR__);
}
$filesChecked = MD_STD::scandir(__DIR__); $filesChecked = MD_STD::scandir(__DIR__);
foreach ($filesChecked as $file) { foreach ($filesChecked as $file) {
@ -284,7 +287,7 @@ final class MD_STD_Test extends TestCase {
/** /**
* Checks invalid directory. * Checks invalid directory.
* *
* @param string $dir Dir name. * @param non-empty-string $dir Dir name.
* *
* @return void * @return void
*/ */
@ -520,9 +523,9 @@ final class MD_STD_Test extends TestCase {
/** /**
* Checks that minimizeHTMLString works. * Checks that minimizeHTMLString works.
* *
* @param string $filepath File path. * @param non-empty-string $filepath File path.
* @param array<string> $mime_types Mime types expected. * @param array<string> $mime_types Mime types expected.
* @param string $exception Exception class name. * @param class-string<Throwable> $exception Exception class.
* *
* @return void * @return void
*/ */
@ -568,7 +571,7 @@ final class MD_STD_Test extends TestCase {
/** /**
* Checks check_is_writable does not work with non-existent or non-directory paths. * Checks check_is_writable does not work with non-existent or non-directory paths.
* *
* @param string $dir Dir name. * @param non-empty-string $dir Dir name.
* *
* @return void * @return void
*/ */
@ -703,4 +706,42 @@ final class MD_STD_Test extends TestCase {
self::assertEquals("en", MD_STD::get_user_lang_no_cookie(["de", "en"], "en")); self::assertEquals("en", MD_STD::get_user_lang_no_cookie(["de", "en"], "en"));
} }
/**
* Data provider for returning a tmp file.
*
* @return array<string, array{0: non-empty-string, 1: non-empty-array<non-empty-string>, 2: int}>
*/
public static function strpos_multi_provider():array {
return [
"Search quotation markes on when='" => ["when='", ['"', '\''], 5],
"Search quotation markes on when=\"" => ["when=\"", ['"', '\''], 5],
"Search quotation markes on when=\"'" => ["when=\"'", ['"', '\''], 5],
"Search quotation markes on when= (non-existent)" => ["when=", ['"', '\''], -1],
];
}
/**
* Checks unlink_if_exists works.
*
* @param non-empty-string $haystack Haystack.
* @param non-empty-array<non-empty-string> $needles Needles.
* @param integer $expected Expected position.
*
* @return void
*/
#[DataProvider('strpos_multi_provider')]
public function test_strpos_multi(string $haystack, array $needles, int $expected):void {
if ($expected === -1) {
self::assertEmpty(MD_STD::strpos_multi($haystack, $needles));
}
else {
self::assertNotEmpty(MD_STD::strpos_multi($haystack, $needles));
self::assertEquals($expected, MD_STD::strpos_multi($haystack, $needles)['position']);
}
}
} }