Compare commits
133 Commits
98f89762ff
...
master
Author | SHA1 | Date | |
---|---|---|---|
119f216907 | |||
25668b7b16
|
|||
8a31cf216e
|
|||
ff474341ed
|
|||
1051e10732
|
|||
057cac0f1b
|
|||
0053fbe030
|
|||
7a2856ffad
|
|||
00638152cf
|
|||
dba60dbce6
|
|||
f84fe1bca5
|
|||
423959ac94
|
|||
e8edb4a459
|
|||
8491b62a83
|
|||
bb2b1c2c32
|
|||
5054d3c62f
|
|||
beba838c0d
|
|||
54dd958073
|
|||
5b99304b5c
|
|||
5cce98f15b
|
|||
5036c77f32
|
|||
e95415be8f
|
|||
5192781494
|
|||
d9d9f7fcdc
|
|||
dbfa0df17f
|
|||
3409ec7afe
|
|||
27ac3f255a
|
|||
9d7d53a858
|
|||
28f6db67ff
|
|||
2f3bc5f2fa
|
|||
39362f537a | |||
de0357473a
|
|||
ef43270fb2
|
|||
338e09f001
|
|||
4cf9eaf4fa
|
|||
18438251a7
|
|||
1cf0f9858a
|
|||
1d50027809
|
|||
d1cee17ef5
|
|||
baf7905e0b
|
|||
9bf14d7d91
|
|||
a621534136
|
|||
51fe9a5e45
|
|||
9c2eaa2929
|
|||
546c17031a
|
|||
bf22f5541d
|
|||
e036d7881a
|
|||
d8db941485
|
|||
b7bb7364d4
|
|||
4dcd93b947
|
|||
c72ad51dda | |||
d6dea3e280
|
|||
6f7ad13c4e
|
|||
48355a6a36
|
|||
7cfe752c94
|
|||
29ca05f552
|
|||
eb371d4270
|
|||
16f36c0852
|
|||
669a8a1459
|
|||
a9c506497c
|
|||
06f13c1a71
|
|||
cd49f194f2
|
|||
9b63a4d95d
|
|||
96ba020514
|
|||
c650e57eda
|
|||
dea09b17cd
|
|||
cc0997f412
|
|||
f18e4c3edc
|
|||
f220a77ad7
|
|||
58d3569718
|
|||
27528c9cf7
|
|||
205e77da0e
|
|||
f36938b8dd
|
|||
cfa9cee60d
|
|||
83a557b989
|
|||
7f342ed3c4 | |||
7d303e219f
|
|||
ce480f8b9f
|
|||
eb14615917
|
|||
bd775bec45
|
|||
2cdfa2e948 | |||
81a7d64e27
|
|||
6af51323e7
|
|||
3c43a3f2d3
|
|||
09518a0a6e
|
|||
93f8f13e62
|
|||
8a8f55b38c
|
|||
f3831965a3
|
|||
4a49c7a4e7
|
|||
3e9f675fdc
|
|||
2ab0e75111
|
|||
27e259072c
|
|||
50cb33720b
|
|||
a677c605c5
|
|||
681002d844
|
|||
40cf5a5112
|
|||
b14f2e14eb
|
|||
55931ba3ef
|
|||
2badc67405
|
|||
b4c941f441
|
|||
d9a0985feb
|
|||
b36a504277
|
|||
e610723107
|
|||
f6409322e5
|
|||
61e83022ae
|
|||
3d58ce3edf
|
|||
a33c354ad6
|
|||
d6c514c208
|
|||
4496a35f5c
|
|||
78d5137b96
|
|||
a102758606
|
|||
700fefd28c
|
|||
4582f6a697
|
|||
7ef05a55c5
|
|||
54a30e683e
|
|||
c9b0e7085f
|
|||
1a7dbcd6f6
|
|||
93c0ff3fa0
|
|||
631debcfd8
|
|||
53c645b132
|
|||
95de1615ef
|
|||
bbbc84015b
|
|||
d55361e29b
|
|||
37715bc3e8
|
|||
9942c58b12
|
|||
0a18449e06
|
|||
efc67b57d3
|
|||
835da05c38
|
|||
12a7937218
|
|||
a68a03e628
|
|||
107a4cd640
|
|||
869e0f263d | |||
0b5d5bdd12
|
36
phpstan-baseline.neon
Normal file
36
phpstan-baseline.neon
Normal file
@ -0,0 +1,36 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Constant DATABASENAME_NODA not found\\.$#"
|
||||
count: 3
|
||||
path: src/NodaBlacklistedTerms.php
|
||||
|
||||
-
|
||||
message: "#^Constant DATABASENAME_NODA not found\\.$#"
|
||||
count: 2
|
||||
path: src/NodaMailChecker.php
|
||||
|
||||
-
|
||||
message: "#^Variable \\$timeInfoToCopy in empty\\(\\) always exists and is not falsy\\.$#"
|
||||
count: 1
|
||||
path: src/NodaTimeAutotranslater.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/NodaWikidataFetcher.php
|
||||
|
||||
-
|
||||
message: "#^Function printHTMLEnd not found\\.$#"
|
||||
count: 1
|
||||
path: src/NodaWikidataFetcher.php
|
||||
|
||||
-
|
||||
message: "#^Function write_get_vars not found\\.$#"
|
||||
count: 9
|
||||
path: src/NodaWikidataFetcher.php
|
||||
|
||||
-
|
||||
message: "#^Match expression does not handle remaining value\\: string$#"
|
||||
count: 1
|
||||
path: src/enums/NodaTimeAutotranslaterLocales.php
|
@ -8,3 +8,5 @@ parameters:
|
||||
- ../
|
||||
ignoreErrors:
|
||||
excludePaths:
|
||||
includes:
|
||||
- phpstan-baseline.neon
|
||||
|
14
phpunit.xml
Normal file
14
phpunit.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd" backupGlobals="false" beStrictAboutChangesToGlobalState="true" beStrictAboutOutputDuringTests="true" bootstrap="tests/bootstrap.php" cacheResult="false" colors="true" enforceTimeLimit="true" failOnWarning="true" processIsolation="true" stopOnError="true" stopOnFailure="true" stopOnIncomplete="true" stopOnSkipped="true" stopOnRisky="true" testdox="false" timeoutForSmallTests="1" timeoutForMediumTests="10" timeoutForLargeTests="60" cacheDirectory=".phpunit.cache" backupStaticProperties="false" requireCoverageMetadata="false" beStrictAboutCoverageMetadata="false">
|
||||
<testsuites>
|
||||
<testsuite name="tests">
|
||||
<directory>tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<coverage/>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
99
scripts/get_wikidata_country_names.php
Normal file
99
scripts/get_wikidata_country_names.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?PHP
|
||||
/**
|
||||
* This file contains tools for fetching data from Wikidata.
|
||||
*
|
||||
* @file
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
require_once __DIR__ . '/../src/NodaWikidataFetcher.php';
|
||||
require_once __DIR__ . '/../../MD_STD/src/MD_STD.php';
|
||||
|
||||
/**
|
||||
* Queries wikidata for instances of a Q-ID.
|
||||
*
|
||||
* @param string $lang Query language.
|
||||
* @param string $instanceOf Q-ID.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
function query(string $lang, string $instanceOf):array {
|
||||
|
||||
$sparqlQueryString = 'SELECT ?item ?itemLabel
|
||||
WHERE
|
||||
{
|
||||
?item wdt:P31/wdt:P279* wd:' . $instanceOf . '.
|
||||
SERVICE wikibase:label { bd:serviceParam wikibase:language "' . $lang . ',[AUTO_LANGUAGE],en". } # Helps get the label in your language, if not, then en language
|
||||
}';
|
||||
|
||||
return NodaWikidataFetcher::sparqlQuery($sparqlQueryString);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns names from a query.
|
||||
*
|
||||
* @param array<mixed> $data Wikidata output values.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
function getNames(array $data):array {
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($data['results']['bindings'] as $entry) {
|
||||
$output[] = $entry['itemLabel']['value'];
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
// Q6256 => country
|
||||
|
||||
$targets = [
|
||||
'Q6256' => 'countries',
|
||||
'Q3024240' => 'historical_countries',
|
||||
'Q10864048' => 'first_lvl_administrative_units',
|
||||
];
|
||||
|
||||
$langs = ['ar', 'bg', 'bn', 'cs', 'da', 'de', 'el', 'en', 'es', 'fa', 'fi', 'fr', 'ha', 'he', 'hi', 'hu', 'id', 'it', 'ja', 'ka', 'ko', 'nl', 'pl', 'pt', 'ro', 'ru', 'sv', 'sw', 'ta', 'th', 'tl', 'tr', 'uk', 'ur', 'vi', 'zh'];
|
||||
|
||||
foreach ($langs as $lang) {
|
||||
foreach ($targets as $qid => $filename) {
|
||||
|
||||
$regionNames = getNames(query($lang, $qid));
|
||||
file_put_contents(__DIR__ . '/../static/' . $filename . '.' . $lang . '.json', MD_STD::json_encode($regionNames));
|
||||
echo "Fetched $lang : $filename ($qid)" . PHP_EOL;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// The following should be lists of terms that are independent of language
|
||||
$targetsForMerge = [
|
||||
'Q23718' => 'cardinal_directions',
|
||||
];
|
||||
$mergedValues = [];
|
||||
foreach ($langs as $lang) {
|
||||
foreach ($targetsForMerge as $qid => $filename) {
|
||||
|
||||
if (!isset($mergedValues[$filename])) {
|
||||
$mergedValues[$filename] = [];
|
||||
}
|
||||
$mergedValues[$filename] = array_merge($mergedValues[$filename], getNames(query($lang, $qid)));
|
||||
echo "Fetched $lang : $filename ($qid)" . PHP_EOL;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$mergedValues['cardinal_directions'][] = 'Nord';
|
||||
$mergedValues['cardinal_directions'][] = 'Ost';
|
||||
$mergedValues['cardinal_directions'][] = 'West';
|
||||
$mergedValues['cardinal_directions'][] = 'Süd';
|
||||
|
||||
foreach ($mergedValues as $filename => $values) {
|
||||
|
||||
file_put_contents(__DIR__ . '/../static/' . $filename . '.json', MD_STD::json_encode(array_values(array_unique($values))));
|
||||
|
||||
}
|
@ -13,7 +13,7 @@ final class NodaBlacklistedTerms {
|
||||
/**
|
||||
* A blacklist of disallowed tags. All entries are listed in full lowercase.
|
||||
*/
|
||||
const TAG_BLACKLIST = [
|
||||
public const TAG_BLACKLIST = [
|
||||
'de' => [
|
||||
'andere',
|
||||
'anderes',
|
||||
@ -32,16 +32,36 @@ final class NodaBlacklistedTerms {
|
||||
'noch nicht bestimmte objekte',
|
||||
'ding',
|
||||
'dinge',
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'nichtmünzliches',
|
||||
'unbestimmt',
|
||||
'AA',
|
||||
'BB',
|
||||
'CC',
|
||||
'DD',
|
||||
'EE',
|
||||
'FF',
|
||||
'GG',
|
||||
'HH',
|
||||
'LL',
|
||||
'-',
|
||||
'?',
|
||||
],
|
||||
'en' => [
|
||||
'other',
|
||||
'others',
|
||||
'unknown',
|
||||
'various',
|
||||
'-',
|
||||
'?',
|
||||
],
|
||||
'hu' => [
|
||||
'ism.',
|
||||
'ismeretlen',
|
||||
'-',
|
||||
'?',
|
||||
],
|
||||
];
|
||||
|
||||
@ -122,4 +142,31 @@ final class NodaBlacklistedTerms {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a time name is blacklisted in the DB.
|
||||
*
|
||||
* @param MDMysqli $mysqli DB connection.
|
||||
* @param string $lang The user's currently used language.
|
||||
* @param string $name The name entered by the user.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function checkTimeBlacklistedInDb(MDMysqli $mysqli, string $lang, string $name):bool {
|
||||
|
||||
$result = $mysqli->query_by_stmt("SELECT 1
|
||||
FROM `" . DATABASENAME_NODA . "`.`zeiten_blacklist`
|
||||
WHERE `language` = ?
|
||||
AND `zeit_name` = ?
|
||||
LIMIT 1", "ss", $lang, $name);
|
||||
|
||||
if ($result->num_rows === 0) {
|
||||
$result->close();
|
||||
return false;
|
||||
}
|
||||
|
||||
$result->close();
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
67
src/NodaConsolidatedNamesAbstract.php
Normal file
67
src/NodaConsolidatedNamesAbstract.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Abstract class to be inherited by classes for writing consolidated vocabulary names.
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Abstract class to be inherited by classes for writing consolidated vocabulary names.
|
||||
*/
|
||||
abstract class NodaConsolidatedNamesAbstract {
|
||||
/**
|
||||
* This function sanitizes a string.
|
||||
*
|
||||
* @param string $inputString Input string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final protected static function _sanitizeInputStringStatic(string $inputString):string {
|
||||
|
||||
$string = trim($inputString, "; \t" . PHP_EOL);
|
||||
$string = strtr($string, ["<" => "[", ">" => "]", "\t" => " ", '\n' => ' ',
|
||||
'<br />' => ' ', '<br/>' => ' ', '<br>' => ' ',
|
||||
"<br />" => ' ', '§' => '"'
|
||||
]);
|
||||
|
||||
$string = str_replace(PHP_EOL, ' ', $string);
|
||||
while (strpos($string, " ") !== false) {
|
||||
$string = str_replace(" ", " ", $string);
|
||||
}
|
||||
|
||||
$string = strip_tags((string)$string);
|
||||
|
||||
return trim(trim($string), ',| ');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Does general cleanup for vocabulary entries.
|
||||
*
|
||||
* @param string $input Input string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public static function sanitizeInputString(string $input):string {
|
||||
|
||||
$output = strtr(
|
||||
self::_sanitizeInputStringStatic($input),
|
||||
[
|
||||
'<' => '(',
|
||||
'>' => ')',
|
||||
'[' => '(',
|
||||
']' => ')',
|
||||
"unbekannt" => "",
|
||||
],
|
||||
);
|
||||
|
||||
// If the first and last character of the name are brackets, remove those.
|
||||
if (substr($output, 0, 1) === '(' && substr($output, -1) === ')') {
|
||||
$output = trim($output, '()');
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
}
|
124
src/NodaConsolidatedNamesForPersinst.php
Normal file
124
src/NodaConsolidatedNamesForPersinst.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Gathers functions for setting uniform actor names.
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Gathers functions for setting uniform actor names.
|
||||
*/
|
||||
final class NodaConsolidatedNamesForPersinst extends NodaConsolidatedNamesAbstract {
|
||||
|
||||
/**
|
||||
* Substrings of an actor name listed as a key in this array will be replaced
|
||||
* by the corresponding value.
|
||||
*/
|
||||
private const _NAME_SANITIZATIONS = [
|
||||
"mythologische Figur" => "Mythologie",
|
||||
"Mythologische Figur" => "Mythologie",
|
||||
"Mythologische Gestalt" => "Mythologie",
|
||||
"()" => "",
|
||||
];
|
||||
|
||||
/**
|
||||
* Replaces last characters of a string if $from matches the end of the string,
|
||||
*
|
||||
* @param string $from Replace from.
|
||||
* @param string $to Replace to.
|
||||
* @param string $name Input name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _replaceFromEnd(string $from, string $to, string $name):string {
|
||||
|
||||
$length = mb_strlen($from);
|
||||
if (str_ends_with($name, $from) === true && substr($name, -1 * $length - 1, 1) !== '.') {
|
||||
$name = str_replace(" ", " ", substr($name, 0, -1 * $length) . $to);
|
||||
}
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans and consolidates name parts appearing regularly in German names
|
||||
* that have a default writing in md.
|
||||
*
|
||||
* @param string $name Name of an actor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _clean_german_abbreviations(string $name):string {
|
||||
|
||||
$name = self::_replaceFromEnd(" d.Ä.", " (der Ältere)", $name);
|
||||
$name = self::_replaceFromEnd(" d. Ä.", " (der Ältere)", $name);
|
||||
$name = self::_replaceFromEnd(" (d.Ä.)", " (der Ältere)", $name);
|
||||
$name = self::_replaceFromEnd(" (d. Ä.)", " (der Ältere)", $name);
|
||||
|
||||
$name = self::_replaceFromEnd(" d.J.", " (der Jüngere)", $name);
|
||||
$name = self::_replaceFromEnd(" d. J.", " (der Jüngere)", $name);
|
||||
$name = self::_replaceFromEnd(" (d.J.)", " (der Jüngere)", $name);
|
||||
$name = self::_replaceFromEnd(" (d. J.)", " (der Jüngere)", $name);
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to make sense of life dates in brackets at the end of an actor's name.
|
||||
*
|
||||
* @param string $name Input name.
|
||||
*
|
||||
* @return array{name: string, birth: string, death: string}|array{}
|
||||
*/
|
||||
public static function parse_life_dates_from_name(string $name):array {
|
||||
|
||||
if (str_contains($name, "(") === false || str_ends_with($name, ")") === false) return [];
|
||||
|
||||
$parts = explode("(", $name);
|
||||
if (count($parts) !== 2) return [];
|
||||
|
||||
$nameOnly = trim($parts[0]);
|
||||
$dateString = trim(rtrim($parts[1], ')')); //
|
||||
|
||||
if (!empty($dates = NodaTimeSplitter::is_timespan($dateString))
|
||||
&& $dates->start_year !== '?'
|
||||
&& $dates->end_year !== '?'
|
||||
&& $dates->start_year !== $dates->end_year
|
||||
&& intval($dates->end_year) - intval($dates->start_year) < 150
|
||||
) {
|
||||
return [
|
||||
'name' => $nameOnly,
|
||||
'birth' => $dates->start_year,
|
||||
'death' => $dates->end_year,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans a persinst name by trimming etc. Also removes uncertainty indicators.
|
||||
*
|
||||
* @param string $lang Instance language.
|
||||
* @param string $persinst_name Input string to clean.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function consolidate_name(string $lang, string $persinst_name):string {
|
||||
|
||||
// Run basic replacements
|
||||
$name = \strtr(self::sanitizeInputString($persinst_name),
|
||||
self::_NAME_SANITIZATIONS);
|
||||
$name = NodaUncertaintyHelper::cleanUncertaintyIndicatorsPersinst($name);
|
||||
|
||||
if (mb_strlen($name) > 10 && $lang === 'de') {
|
||||
$name = self::_clean_german_abbreviations($name);
|
||||
}
|
||||
|
||||
// If the persinst name is empty, unset persinst ID
|
||||
return \trim($name, " ;.\t" . PHP_EOL);
|
||||
|
||||
}
|
||||
}
|
506
src/NodaConsolidatedNamesForPlaces.php
Normal file
506
src/NodaConsolidatedNamesForPlaces.php
Normal file
@ -0,0 +1,506 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Gathers functions for setting uniform place names.
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Gathers functions for setting uniform place names.
|
||||
*/
|
||||
final class NodaConsolidatedNamesForPlaces extends NodaConsolidatedNamesAbstract {
|
||||
|
||||
/**
|
||||
* Substrings of an place name listed as a key in this array will be replaced
|
||||
* by the corresponding value.
|
||||
*/
|
||||
private const _NAME_SANITIZATIONS = [
|
||||
" - " => "-",
|
||||
"unbekannt" => "",
|
||||
"Unbekannt" => "",
|
||||
"unknown" => "",
|
||||
"Unknown" => "",
|
||||
];
|
||||
|
||||
/** Blacklist for comparison with country names */
|
||||
private const _COUNTRY_REWRITE_BLACKLISTED_TERMS = [
|
||||
'District',
|
||||
'Distrikt',
|
||||
'India',
|
||||
'Indien',
|
||||
'Insel',
|
||||
'Inseln',
|
||||
'Tal',
|
||||
'Yue',
|
||||
];
|
||||
|
||||
private const _PLACE_TYPE_INDICATORS_GERMAN = [
|
||||
'Insel',
|
||||
'Stadt',
|
||||
];
|
||||
|
||||
// Indicators signifying that a place is likely subordinate to the other
|
||||
// if two places are provided in a comma-separated list
|
||||
private const _PLACE_NARROWER_LOCATION_INDICATORS_GERMAN = [
|
||||
'gasse',
|
||||
'straße',
|
||||
' Straße',
|
||||
];
|
||||
|
||||
// Indicators signifying that a place is likely subordinate to the other
|
||||
// if two places are provided in a comma-separated list
|
||||
private const _PLACE_NARROWER_LOCATION_INDICATORS_HUNGARIAN = [
|
||||
' körut ',
|
||||
' utca ',
|
||||
' út ',
|
||||
];
|
||||
|
||||
private const _RELEVANT_ROMAN_NUMERALS = [
|
||||
'I' => '1',
|
||||
'II' => '2',
|
||||
'III' => '3',
|
||||
'IV' => '4',
|
||||
'V' => '5',
|
||||
'VI' => '6',
|
||||
'VII' => '7',
|
||||
'VIII' => '8',
|
||||
'IX' => '9',
|
||||
'X' => '10',
|
||||
'XI' => '11',
|
||||
'XII' => '12',
|
||||
'XIII' => '13',
|
||||
'XIV' => '14',
|
||||
'XV' => '15',
|
||||
'XVI' => '16',
|
||||
'XVII' => '17',
|
||||
'XVIII' => '18',
|
||||
'XIX' => '19',
|
||||
'XX' => '20',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private static $_placeNameListCaches = [];
|
||||
|
||||
/**
|
||||
* Rewrites indicators for narrower locations paired with a superordinate location
|
||||
* into the format "Narrower (Broader)".
|
||||
* E.g.: "Adalbrechtstr. 12, Berlin" > Adalbrechtstraße 12 (Berlin).
|
||||
*
|
||||
* @param string $name Name in which to rewrite.
|
||||
* @param string $indicator Indicator for narrower place. E.g. "straße".
|
||||
* @param string $separator Separating character between narrower and broader, e.g. ', '.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _rewrite_narrower_broader_pairs_to_brackets(string $name, string $indicator, string $separator = ', '):string {
|
||||
|
||||
if (str_contains($name, $indicator)
|
||||
&& substr_count($name, $indicator) === 1
|
||||
&& substr_count($name, $separator) === 1
|
||||
&& !str_contains($name, "(")
|
||||
) {
|
||||
|
||||
$parts = explode(', ', $name);
|
||||
|
||||
// Skip entries like "Vaci utca 12 Budapest, Vaci utca"
|
||||
$indicatorTrimmed = trim($indicator);
|
||||
if ((str_ends_with($parts[0], $indicatorTrimmed) && str_contains($parts[1], $indicatorTrimmed))
|
||||
|| (str_ends_with($parts[1], $indicatorTrimmed) && str_contains($parts[0], $indicatorTrimmed))
|
||||
) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Prevent errors in case of "Adalbrechtstraße 12, "
|
||||
if (!empty($parts[0]) && !empty($parts[1])) {
|
||||
|
||||
if (str_contains($parts[0], $indicator)) { // Adalberthstraße 12, Berlin
|
||||
$street = $parts[0];
|
||||
$town = $parts[1];
|
||||
}
|
||||
else { // Berlin, Adalberthstraße 12
|
||||
$street = $parts[1];
|
||||
$town = $parts[0];
|
||||
}
|
||||
|
||||
// Prevent rewrites in cases like "Deák Ferenc utca 16-18. Budapest, V."
|
||||
if (str_contains($town, '.')) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return $street . ' (' . $town . ')';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans and consolidates name parts appearing regularly in German place names.
|
||||
*
|
||||
* @param string $name Name of a place.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _clean_german_abbreviations(string $name):string {
|
||||
|
||||
// ABC, Inseln > ABC (Inseln)
|
||||
foreach (self::_PLACE_TYPE_INDICATORS_GERMAN as $indicator) {
|
||||
if (str_ends_with($name, ', ' . $indicator)) {
|
||||
$name = str_replace(', ' . $indicator, ' (' . $indicator . ')', $name);
|
||||
}
|
||||
}
|
||||
|
||||
// Adalbrechtstr. 12 > Adalbrechtstraße 12
|
||||
if (str_contains($name, "str. ") && \preg_match("/[a-zA-Z]str. [0-9]/", $name)) {
|
||||
$name = str_replace("str. ", "straße ", $name);
|
||||
}
|
||||
|
||||
// "Adalbrechtstraße. 12, Berlin" > Adalbrechtstraße 12 (Berlin)
|
||||
|
||||
foreach (self::_PLACE_NARROWER_LOCATION_INDICATORS_GERMAN as $indicator) {
|
||||
$name = self::_rewrite_narrower_broader_pairs_to_brackets($name, $indicator, ', ');
|
||||
}
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans and consolidates name parts appearing regularly in Hungarian place names.
|
||||
*
|
||||
* @param string $name Name of a place.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _clean_hungarian_abbreviations(string $name):string {
|
||||
|
||||
if (str_contains($name, " krt. ") && \preg_match("/\ krt\.\ [0-9]/", $name)) {
|
||||
$name = str_replace(" krt. ", " körut ", $name);
|
||||
}
|
||||
if (str_contains($name, " u. ") && \preg_match("/\ u\.\ [0-9]/", $name)) {
|
||||
$name = str_replace(" u. ", " utca ", $name);
|
||||
}
|
||||
if (str_contains($name, " ucca ") && \preg_match("/\ ucca\ [0-9]/", $name)) {
|
||||
$name = str_replace(" ucca ", " utca ", $name);
|
||||
}
|
||||
if (str_contains($name, " utcza ") && \preg_match("/\ utcza\ [0-9]/", $name)) {
|
||||
$name = str_replace(" utcza ", " utca ", $name);
|
||||
}
|
||||
if (str_contains($name, " rkp. ") && \preg_match("/\ rkp\.\ [0-9]/", $name)) {
|
||||
$name = str_replace(" rkp. ", " rakpart ", $name);
|
||||
}
|
||||
|
||||
// "Adalbrecht utca. 12, Berlin" > Adalbrecht utca 12 (Berlin)
|
||||
|
||||
foreach (self::_PLACE_NARROWER_LOCATION_INDICATORS_HUNGARIAN as $indicator) {
|
||||
$name = self::_rewrite_narrower_broader_pairs_to_brackets($name, $indicator, ', ');
|
||||
}
|
||||
|
||||
if (str_contains($name, 'Budapest') && substr_count($name, 'Budapest') === 1) {
|
||||
foreach(self::_RELEVANT_ROMAN_NUMERALS as $roman_numeral => $arabic) {
|
||||
|
||||
$to_match = ' Budapest, ' . $roman_numeral . '.';
|
||||
if (str_ends_with($name, $to_match)) {
|
||||
$name = str_replace($to_match, ' (Budapest, ' . $arabic . '. kerület)', $name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites a Ukrainian language name based on abbreviations explaining the
|
||||
* hierarchy of named places.
|
||||
*
|
||||
* @param string $name Input name to rewrite.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _rewrite_ukrainian_names_by_hierarchy(string $name):string {
|
||||
|
||||
$identifiersByLevel = [
|
||||
'state' => [' РСР', 'РСР ', ' АРСР', 'АРСР ', ' губернія', 'губернія '],
|
||||
'oblast' => ['обл.', 'область', 'області', 'округа', 'губернії'],
|
||||
'region' => ['р-н', 'район'],
|
||||
'county' => ['повіт'],
|
||||
'city' => ['м.', 'м '],
|
||||
'parish' => ['волость'],
|
||||
'village' => ['смт', 'сільська', 'с. ', 'село'],
|
||||
'district' => [], // Is also р-н; which it is is determined based on position
|
||||
'street' => ['вул. '],
|
||||
];
|
||||
|
||||
$levels = [
|
||||
'country' => '',
|
||||
'state' => '',
|
||||
'oblast' => '',
|
||||
'region' => '',
|
||||
'county' => '',
|
||||
'city' => '',
|
||||
'parish' => '',
|
||||
'village' => '',
|
||||
'district' => '',
|
||||
'street' => '',
|
||||
];
|
||||
|
||||
$parts = explode(',', $name);
|
||||
foreach ($parts as $part) {
|
||||
$part = trim($part);
|
||||
foreach ($identifiersByLevel as $level => $identifiers) {
|
||||
foreach ($identifiers as $identifier) {
|
||||
|
||||
if (str_starts_with($part, $identifier) || str_ends_with($part, $identifier)) {
|
||||
|
||||
// Special case: Region can both be rajon or a district within a city
|
||||
// If both oblast and city are already known, the region will be a
|
||||
// district within the city.
|
||||
// Otherwise, it is to be assumed that it is a super-city region.
|
||||
if ($level === 'region' && !empty($levels['oblast'])
|
||||
&& (!empty($levels['city']) || !empty($levels['village']))
|
||||
) {
|
||||
$level = 'district';
|
||||
}
|
||||
|
||||
if (!empty($levels[$level])) {
|
||||
# throw new Exception("Used the same level (" . $level . ") twice");
|
||||
return $name;
|
||||
}
|
||||
$levels[$level] = $part;
|
||||
continue 3;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: Abbreviated SSRs
|
||||
if (in_array($part, ['УРСР', 'УССР', 'УСРР'], true)) {
|
||||
$levels['state'] = $part;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unspecified part level: Attempt identifying country
|
||||
if (!isset($countryNames)) {
|
||||
$countryNames = self::_loadJsonList(__DIR__ . "/../static/countries.uk.json") + self::_loadJsonList(__DIR__ . "/../static/historical_countries.uk.json");
|
||||
$countryNames[] = 'СРСР';
|
||||
$countryNames[] = 'УНР';
|
||||
$countryNames[] = 'Російська імперія';
|
||||
$countryNames[] = 'Рос.імперія';
|
||||
$countryNames[] = 'Рос.имперія';
|
||||
$countryNames[] = 'Російська імперія-УНР';
|
||||
}
|
||||
if (in_array($part, $countryNames, true)) {
|
||||
$levels['country'] = $part;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unspecified level; return
|
||||
return $name;
|
||||
}
|
||||
|
||||
$main_name = '';
|
||||
$specifiers = [];
|
||||
|
||||
foreach (array_reverse($levels) as $level => $partname) {
|
||||
if (empty($partname)) continue;
|
||||
|
||||
if ($level === 'city' || $level === 'village') {
|
||||
$strtr = [];
|
||||
foreach ($identifiersByLevel[$level] as $identifier) $strtr[$identifier] = '';
|
||||
$partname = trim(strtr($partname, $strtr));
|
||||
}
|
||||
|
||||
if (empty($main_name)) {
|
||||
$main_name = $partname;
|
||||
}
|
||||
else {
|
||||
$specifiers[] = $partname;
|
||||
}
|
||||
}
|
||||
|
||||
$output = $main_name;
|
||||
if (!empty($specifiers)) {
|
||||
$output .= ' (' . implode(', ', $specifiers) . ')';
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans and consolidates name parts appearing regularly in Ukrainian place names.
|
||||
*
|
||||
* @param string $name Name of an place.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _clean_ukrainian_abbreviations(string $name):string {
|
||||
|
||||
if (str_contains($name, " р-н,") || str_contains($name, " р-н ") || str_ends_with($name, " р-н")) {
|
||||
$name = str_replace(" р-н", " район", $name);
|
||||
}
|
||||
|
||||
if (str_contains($name, ',')) {
|
||||
$name = self::_rewrite_ukrainian_names_by_hierarchy($name);
|
||||
}
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a JSON file, optionally loading it cached through a private static variable
|
||||
* if reuse is expectable (= in the case of CLI usage).
|
||||
*
|
||||
* @param non-empty-string $filename File name to load.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function _loadJsonList(string $filename):array {
|
||||
|
||||
if (PHP_SAPI === 'cli' && isset(self::$_placeNameListCaches[$filename])) {
|
||||
return self::$_placeNameListCaches[$filename];
|
||||
}
|
||||
|
||||
try {
|
||||
$output = json_decode(MD_STD::file_get_contents($filename), true);
|
||||
}
|
||||
catch (MDFileDoesNotExist $e) {
|
||||
self::$_placeNameListCaches[$filename] = [];
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($output === false) {
|
||||
throw new Exception("Failed to get list");
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli') {
|
||||
self::$_placeNameListCaches[$filename] = $output;
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves names of regions to brackets using pre-generated lists of countries,
|
||||
* historical country names, etc.
|
||||
*
|
||||
* @param string $lang Instance language.
|
||||
* @param string $name Input string to clean.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _move_region_names_to_brackets(string $lang, string $name):string {
|
||||
|
||||
$separators = ['-', ', '];
|
||||
|
||||
foreach ($separators as $separator) {
|
||||
|
||||
if (!str_contains($name, $separator) || substr_count($name, $separator) !== 1) continue;
|
||||
|
||||
// Get parts and trim them
|
||||
$parts = explode($separator, $name);
|
||||
foreach ($parts as $key => $value) {
|
||||
$parts[$key] = trim($value);
|
||||
}
|
||||
|
||||
// Load place names
|
||||
$countryNames = self::_loadJsonList(__DIR__ . "/../static/countries.$lang.json") + self::_loadJsonList(__DIR__ . "/../static/historical_countries.$lang.json");
|
||||
$cardinal_directions = self::_loadJsonList(__DIR__ . "/../static/cardinal_directions.json");
|
||||
|
||||
$part0IsCountry = in_array($parts[0], $countryNames, true);
|
||||
$part1IsCountry = in_array($parts[1], $countryNames, true);
|
||||
|
||||
// Skip if the full name is in the list of country names
|
||||
if (in_array($name, $countryNames, true)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
// If one of the parts is a blacklisted term or a cardinal directions, skip this
|
||||
|
||||
if ((in_array($parts[0], self::_COUNTRY_REWRITE_BLACKLISTED_TERMS, true)
|
||||
|| in_array($parts[0], $cardinal_directions, true)
|
||||
|| in_array(strtolower($parts[0]), $cardinal_directions, true))
|
||||
|| (in_array($parts[1], self::_COUNTRY_REWRITE_BLACKLISTED_TERMS, true)
|
||||
|| in_array($parts[1], $cardinal_directions, true)
|
||||
|| in_array(strtolower($parts[1]), $cardinal_directions, true))
|
||||
) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if ($part0IsCountry === true && $part1IsCountry === false) {
|
||||
return $parts[1] . ' (' . $parts[0] . ')';
|
||||
}
|
||||
else if ($part0IsCountry === false && $part1IsCountry === true) {
|
||||
return $parts[0] . ' (' . $parts[1] . ')';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes duplicates after commas.
|
||||
*
|
||||
* @param string $ort_name Place name to clean.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _remove_duplicates_after_commas(string $ort_name):string {
|
||||
|
||||
if (str_contains($ort_name, ',') === false) {
|
||||
return $ort_name;
|
||||
}
|
||||
|
||||
$parts = explode(', ', $ort_name);
|
||||
|
||||
return implode(', ', array_unique($parts));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans a place name by trimming etc. Also removes uncertainty indicators.
|
||||
*
|
||||
* @param string $lang Instance language.
|
||||
* @param string $ort_name Input string to clean.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function consolidate_name(string $lang, string $ort_name):string {
|
||||
|
||||
// Run basic replacements
|
||||
$nameSanitizations = self::_NAME_SANITIZATIONS;
|
||||
/*
|
||||
if (substr_count($ort_name, "/") === 1 && !str_contains($ort_name, '.')) {
|
||||
$nameSanitizations["/"] = "-";
|
||||
}
|
||||
*/
|
||||
$ort_name = strtr(self::sanitizeInputString($ort_name), $nameSanitizations);
|
||||
$ort_name = self::sanitizeInputString(NodaUncertaintyHelper::cleanUncertaintyIndicatorsPlace($ort_name));
|
||||
|
||||
// Remove duplicates after commas
|
||||
// Västerdås, Schweden, Schweden > Västerdås, Schweden
|
||||
$ort_name = self::_remove_duplicates_after_commas($ort_name);
|
||||
|
||||
$ort_name = match ($lang) {
|
||||
'de' => self::_clean_german_abbreviations($ort_name),
|
||||
'hu' => self::_clean_hungarian_abbreviations($ort_name),
|
||||
'uk' => self::_clean_ukrainian_abbreviations($ort_name),
|
||||
default => $ort_name,
|
||||
};
|
||||
|
||||
$ort_name = self::_move_region_names_to_brackets($lang, $ort_name);
|
||||
|
||||
return $ort_name;
|
||||
|
||||
}
|
||||
}
|
74
src/NodaCountingTimeIndicator.php
Normal file
74
src/NodaCountingTimeIndicator.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Represents a time indicator (CE / BCE).
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Represents a time indicator (CE / BCE).
|
||||
*/
|
||||
enum NodaCountingTimeIndicator implements JsonSerializable {
|
||||
|
||||
case ce;
|
||||
case bce;
|
||||
|
||||
/**
|
||||
* Returns a value of this type based on a string.
|
||||
*
|
||||
* @param string $input Input to get a value from.
|
||||
*
|
||||
* @return NodaCountingTimeIndicator
|
||||
*/
|
||||
public static function fromString(string $input):NodaCountingTimeIndicator {
|
||||
|
||||
return match($input) {
|
||||
'+' => self::ce,
|
||||
'-' => self::bce,
|
||||
'ce' => self::ce,
|
||||
'bce' => self::bce,
|
||||
default => throw new MDpageParameterNotFromListException("Unknown counting time indicator (bc / bce)"),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a canonical string representation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toString():string {
|
||||
|
||||
return match($this) {
|
||||
self::ce => '+',
|
||||
self::bce => '-',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a canonical string representation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toGerman():string {
|
||||
|
||||
return match($this) {
|
||||
self::ce => ' n. Chr.',
|
||||
self::bce => ' v. Chr.',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the option to serialize as a string during json_encode().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function jsonSerialize():string {
|
||||
|
||||
return $this->toString();
|
||||
|
||||
}
|
||||
}
|
@ -26,7 +26,13 @@ final class NodaDbAdmin {
|
||||
`language` string,
|
||||
`name` text,
|
||||
`description` text,
|
||||
`timestamp` timestamp) min_infix_len = '3'");
|
||||
`timestamp` timestamp)
|
||||
min_infix_len = '3'
|
||||
charset_table='" . MDMysqli::MANTICORE_CHARSET_TABLE . "'
|
||||
expansion_limit = '32'
|
||||
blend_chars = '" . MDMysqliSetup::DEFAULT_MANTICORE_BLEND_CHARS . "'
|
||||
blend_mode = '" . MDMysqliSetup::DEFAULT_MANTICORE_BLEND_MODE . "'
|
||||
rt_mem_limit = '2G'");
|
||||
MDConsole::write("Create table `" . $index_name . "`");
|
||||
|
||||
}
|
||||
@ -46,7 +52,13 @@ final class NodaDbAdmin {
|
||||
`language` string,
|
||||
`name` text,
|
||||
`description` text,
|
||||
`timestamp` timestamp) min_infix_len = '3'");
|
||||
`timestamp` timestamp)
|
||||
min_infix_len = '3'
|
||||
expansion_limit = '32'
|
||||
blend_chars = '" . MDMysqliSetup::DEFAULT_MANTICORE_BLEND_CHARS . "'
|
||||
blend_mode = '" . MDMysqliSetup::DEFAULT_MANTICORE_BLEND_MODE . "'
|
||||
charset_table='" . MDMysqli::MANTICORE_CHARSET_TABLE . "'
|
||||
rt_mem_limit = '2G'");
|
||||
MDConsole::write("Create table `" . $index_name . "`");
|
||||
|
||||
}
|
||||
|
42
src/NodaDistinctlyTypedStrings.php
Normal file
42
src/NodaDistinctlyTypedStrings.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Checks the distinctly_typed_strings table for whether it
|
||||
* contains a string.
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Checks the distinctly_typed_strings table for whether it
|
||||
* contains a string.
|
||||
*/
|
||||
final class NodaDistinctlyTypedStrings {
|
||||
/**
|
||||
* Checks the vocabulary database whether it contains a given string.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda DB connection.
|
||||
* @param string $lang Language to check in.
|
||||
* @param string $search_term Search term.
|
||||
*
|
||||
* @return 'persinst'|'zeiten'|'orte'|'tag'|''
|
||||
*/
|
||||
public static function lookup(MDMysqli $mysqli_noda, string $lang, string $search_term):string {
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("SELECT `type`
|
||||
FROM `distinctly_typed_strings`
|
||||
WHERE `input_string` = ?
|
||||
AND `language` = ?", "ss", $search_term, $lang);
|
||||
|
||||
if (!($cur = $result->fetch_row())) {
|
||||
$result->close();
|
||||
return '';
|
||||
}
|
||||
$result->close();
|
||||
|
||||
if (!in_array($cur[0], ['persinst', 'zeiten', 'orte', 'tag', ''], true)) {
|
||||
throw new Exception("Distinctly typed string of unknown type: " . $cur[0]);
|
||||
}
|
||||
|
||||
return $cur[0];
|
||||
|
||||
}
|
||||
}
|
322
src/NodaGroup.php
Normal file
322
src/NodaGroup.php
Normal file
@ -0,0 +1,322 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Manages vocabulary groups.
|
||||
*
|
||||
* @file
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Manages vocabulary groups.
|
||||
*/
|
||||
final class NodaGroup {
|
||||
|
||||
private MDMysqli $_mysqli_noda;
|
||||
|
||||
/**
|
||||
* Lists all active groups.
|
||||
*
|
||||
* @param integer $limit Limit.
|
||||
* @param integer $offset Offset.
|
||||
*
|
||||
* @return array<array{id: int, name: string}>
|
||||
*/
|
||||
public function list(int $limit = 50, int $offset = 0):array {
|
||||
|
||||
$output = [];
|
||||
|
||||
$result = $this->_mysqli_noda->query_by_stmt("SELECT `group_id`, `group_name`
|
||||
FROM `group`
|
||||
ORDER BY `group_id` DESC
|
||||
LIMIT ?
|
||||
OFFSET ?", "ii", $limit, $offset);
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
$output[] = [
|
||||
'id' => (int)$cur[0],
|
||||
'name' => $cur[1],
|
||||
];
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns basic description of a group.
|
||||
*
|
||||
* @param integer $group_id Group ID.
|
||||
*
|
||||
* @return array{name: string, comment: string}
|
||||
*/
|
||||
public function getDescription(int $group_id):array {
|
||||
|
||||
$result = $this->_mysqli_noda->query_by_stmt("SELECT `group_name`, `comment`
|
||||
FROM `group`
|
||||
WHERE `group_id` = ?", "i", $group_id);
|
||||
|
||||
if (!($cur = $result->fetch_row())) {
|
||||
$result->close();
|
||||
throw new MDmainEntityNotExistentException("This group does not seem to exist");
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return [
|
||||
'name' => $cur[0],
|
||||
'comment' => $cur[1],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a group.
|
||||
*
|
||||
* @param string $name Name of the group.
|
||||
* @param string $comment Comment / note on the group.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function insert(string $name, string $comment = ''):int {
|
||||
|
||||
if (empty($name)) {
|
||||
throw new MDpageParameterMissingException("Name cannot be empty when adding groups.");
|
||||
}
|
||||
|
||||
$insertStmt = $this->_mysqli_noda->do_prepare("INSERT INTO `group`
|
||||
(`group_name`, `comment`)
|
||||
VALUES
|
||||
(?, ?)");
|
||||
|
||||
$insertStmt->bind_param("ss", $name, $comment);
|
||||
$insertStmt->execute();
|
||||
$output = $insertStmt->get_insert_id();
|
||||
$insertStmt->close();
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a group.
|
||||
*
|
||||
* @param integer $group_id ID of the group to update.
|
||||
* @param string $name Name of the group.
|
||||
* @param string $comment Optional: Comment for the group.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update(int $group_id, string $name, string $comment = ''):void {
|
||||
|
||||
if (empty($name)) {
|
||||
throw new MDpageParameterMissingException("Name cannot be empty when adding groups.");
|
||||
}
|
||||
|
||||
$insertStmt = $this->_mysqli_noda->do_prepare("UPDATE `group`
|
||||
SET `group_name` = ?,
|
||||
`comment` = ?
|
||||
WHERE `group_id` = ?");
|
||||
|
||||
$insertStmt->bind_param("ssi", $name, $comment, $group_id);
|
||||
$insertStmt->execute();
|
||||
$insertStmt->close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a group.
|
||||
*
|
||||
* @param integer $group_id ID of the group to delete.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(int $group_id):void {
|
||||
|
||||
$this->_mysqli_noda->update_query_by_stmt("DELETE FROM `v_group_persinst`
|
||||
WHERE `group_id` = ?", "i", $group_id);
|
||||
$this->_mysqli_noda->update_query_by_stmt("DELETE FROM `v_group_orte`
|
||||
WHERE `group_id` = ?", "i", $group_id);
|
||||
$this->_mysqli_noda->update_query_by_stmt("DELETE FROM `v_group_zeiten`
|
||||
WHERE `group_id` = ?", "i", $group_id);
|
||||
$this->_mysqli_noda->update_query_by_stmt("DELETE FROM `v_group_tag`
|
||||
WHERE `group_id` = ?", "i", $group_id);
|
||||
|
||||
$this->_mysqli_noda->update_query_by_stmt("DELETE FROM `group`
|
||||
WHERE `group_id` = ?", "i", $group_id);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds actors to a nodac group.
|
||||
*
|
||||
* @param integer $group_id Group ID.
|
||||
* @param array<integer> $input_ids List of entries to link to the group.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function linkActors(int $group_id, array $input_ids):void {
|
||||
|
||||
if (empty($input_ids = MD_STD_IN::sanitize_id_array($input_ids))) return;
|
||||
|
||||
// Check which entries actually exist
|
||||
$idsToLink = [];
|
||||
$result = $this->_mysqli_noda->do_read_query("SELECT `persinst_id`
|
||||
FROM `persinst`
|
||||
WHERE `persinst_id` IN (" . $this->_mysqli_noda->escape_in($input_ids) . ")");
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
$idsToLink[] = (int)$cur[0];
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$this->_mysqli_noda->autocommit(false);
|
||||
|
||||
$linkStmt = $this->_mysqli_noda->do_prepare("INSERT INTO `v_group_persinst`
|
||||
(`group_id`, `persinst_id`)
|
||||
VALUES (?, ?)");
|
||||
|
||||
foreach ($idsToLink as $id) {
|
||||
$linkStmt->bind_param("ii", $group_id, $id);
|
||||
$linkStmt->execute();
|
||||
}
|
||||
$linkStmt->close();
|
||||
|
||||
$this->_mysqli_noda->commit();
|
||||
$this->_mysqli_noda->autocommit(true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds places to a nodac group.
|
||||
*
|
||||
* @param integer $group_id Group ID.
|
||||
* @param array<integer> $input_ids List of entries to link to the group.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function linkPlaces(int $group_id, array $input_ids):void {
|
||||
|
||||
if (empty($input_ids = MD_STD_IN::sanitize_id_array($input_ids))) return;
|
||||
|
||||
// Check which entries actually exist
|
||||
$idsToLink = [];
|
||||
$result = $this->_mysqli_noda->do_read_query("SELECT `ort_id`
|
||||
FROM `orte`
|
||||
WHERE `ort_id` IN (" . $this->_mysqli_noda->escape_in($input_ids) . ")");
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
$idsToLink[] = (int)$cur[0];
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$this->_mysqli_noda->autocommit(false);
|
||||
|
||||
$linkStmt = $this->_mysqli_noda->do_prepare("INSERT INTO `v_group_orte`
|
||||
(`group_id`, `ort_id`)
|
||||
VALUES (?, ?)");
|
||||
|
||||
foreach ($idsToLink as $id) {
|
||||
$linkStmt->bind_param("ii", $group_id, $id);
|
||||
$linkStmt->execute();
|
||||
}
|
||||
$linkStmt->close();
|
||||
|
||||
$this->_mysqli_noda->commit();
|
||||
$this->_mysqli_noda->autocommit(true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds times to a nodac group.
|
||||
*
|
||||
* @param integer $group_id Group ID.
|
||||
* @param array<integer> $input_ids List of entries to link to the group.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function linkTimes(int $group_id, array $input_ids):void {
|
||||
|
||||
if (empty($input_ids = MD_STD_IN::sanitize_id_array($input_ids))) return;
|
||||
|
||||
// Check which entries actually exist
|
||||
$idsToLink = [];
|
||||
$result = $this->_mysqli_noda->do_read_query("SELECT `zeit_id`
|
||||
FROM `zeiten`
|
||||
WHERE `zeit_id` IN (" . $this->_mysqli_noda->escape_in($input_ids) . ")");
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
$idsToLink[] = (int)$cur[0];
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$this->_mysqli_noda->autocommit(false);
|
||||
|
||||
$linkStmt = $this->_mysqli_noda->do_prepare("INSERT INTO `v_group_zeiten`
|
||||
(`group_id`, `zeit_id`)
|
||||
VALUES (?, ?)");
|
||||
|
||||
foreach ($idsToLink as $id) {
|
||||
$linkStmt->bind_param("ii", $group_id, $id);
|
||||
$linkStmt->execute();
|
||||
}
|
||||
$linkStmt->close();
|
||||
|
||||
$this->_mysqli_noda->commit();
|
||||
$this->_mysqli_noda->autocommit(true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tags to a nodac group.
|
||||
*
|
||||
* @param integer $group_id Group ID.
|
||||
* @param array<integer> $input_ids List of entries to link to the group.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function linkTags(int $group_id, array $input_ids):void {
|
||||
|
||||
if (empty($input_ids = MD_STD_IN::sanitize_id_array($input_ids))) return;
|
||||
|
||||
// Check which entries actually exist
|
||||
$idsToLink = [];
|
||||
$result = $this->_mysqli_noda->do_read_query("SELECT `tag_id`
|
||||
FROM `tag`
|
||||
WHERE `tag_id` IN (" . $this->_mysqli_noda->escape_in($input_ids) . ")");
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
$idsToLink[] = (int)$cur[0];
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$this->_mysqli_noda->autocommit(false);
|
||||
|
||||
$linkStmt = $this->_mysqli_noda->do_prepare("INSERT INTO `v_group_tag`
|
||||
(`group_id`, `tag_id`)
|
||||
VALUES (?, ?)");
|
||||
|
||||
foreach ($idsToLink as $id) {
|
||||
$linkStmt->bind_param("ii", $group_id, $id);
|
||||
$linkStmt->execute();
|
||||
}
|
||||
$linkStmt->close();
|
||||
|
||||
$this->_mysqli_noda->commit();
|
||||
$this->_mysqli_noda->autocommit(true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda DB connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(MDMysqli $mysqli_noda) {
|
||||
|
||||
$this->_mysqli_noda = $mysqli_noda;
|
||||
|
||||
}
|
||||
}
|
@ -10,6 +10,30 @@ declare(strict_types = 1);
|
||||
* Contains static functions for getting IDs for noda entries by various means.
|
||||
*/
|
||||
final class NodaIDGetter {
|
||||
/**
|
||||
* Comparison operation that allows for equality but also returns true
|
||||
* if capitalization differs. If diacritics are available in one case
|
||||
* and not in the other, there will be no match.
|
||||
* This is needed to identify sufficiently exact matches despite a table
|
||||
* collation of *_ci in MySQL.
|
||||
*
|
||||
* @param string $string_one First string in comparison.
|
||||
* @param string $string_two Second string in comparison.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private static function _stri_matches(string $string_one, string $string_two):bool {
|
||||
|
||||
if ($string_one === $string_two) {
|
||||
return true;
|
||||
}
|
||||
if (\strtolower($string_one) === \strtolower($string_two)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// Actors
|
||||
@ -40,6 +64,33 @@ final class NodaIDGetter {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns persinst ID by name, checking the different options for it.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $lang Language to check in.
|
||||
* @param string $name Name of the persinst to search for.
|
||||
* @param string $birth Birth year.
|
||||
* @param string $death Death year.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getPersinstIDByNamePlusYears(MDMysqli $mysqli_noda, string $lang, string $name, string $birth, string $death):int {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
if ($persinstByTransName = self::getPersinstIDByTransNamePlusYears($mysqli_noda, $lang, $name, $birth, $death)) {
|
||||
return $persinstByTransName;
|
||||
}
|
||||
|
||||
if ($persinstByBaseName = self::getPersinstIDByBaseNamePlusYears($mysqli_noda, $name, $birth, $death)) {
|
||||
return $persinstByBaseName;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns persinst ID by entry in persinst name rewriting table.
|
||||
*
|
||||
@ -53,21 +104,22 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$persinstRewriteResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`, `input_name`
|
||||
FROM `persinst_rewriting`
|
||||
WHERE `language` = ?
|
||||
AND `input_name` = ?
|
||||
LIMIT 1", "ss", $lang, $name);
|
||||
|
||||
if ($persinstRewriteData = $persinstRewriteResult->fetch_row()) {
|
||||
$output = $persinstRewriteData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$persinstRewriteResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -84,21 +136,91 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$persinstByTLNameResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`, `trans_name`
|
||||
FROM `persinst_translation`
|
||||
WHERE `trans_name` = ?
|
||||
AND `trans_language` = ?
|
||||
LIMIT 2", "ss", $name, $lang);
|
||||
|
||||
if ($persinstByTlData = $persinstByTLNameResult->fetch_row()) {
|
||||
$output = $persinstByTlData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$persinstByTLNameResult->close();
|
||||
return 0;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns persinst ID by entry in persinst translations table,
|
||||
* irrespective of language.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $name Name of the persinst to search for.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getPersinstIDByAnyTransName(MDMysqli $mysqli_noda, string $name):int {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`, `trans_name`
|
||||
FROM `persinst_translation`
|
||||
WHERE `trans_name` = ?
|
||||
LIMIT 2", "s", $name);
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns persinst ID by entry in persinst translations table
|
||||
* plus birth and death.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $lang Language to check in.
|
||||
* @param string $name Name of the persinst to search for.
|
||||
* @param string $birth Birth year.
|
||||
* @param string $death Death year.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getPersinstIDByTransNamePlusYears(MDMysqli $mysqli_noda, string $lang, string $name, string $birth, string $death):int {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_translation`.`persinst_id`, `trans_name`
|
||||
FROM `persinst_translation`, `persinst`
|
||||
WHERE `persinst_translation`.`persinst_id` = `persinst`.`persinst_id`
|
||||
AND `trans_name` = ?
|
||||
AND `trans_language` = ?
|
||||
AND `persinst_geburtsjahr` = ?
|
||||
AND `persinst_sterbejahr` = ?
|
||||
LIMIT 2", "ssss", $name, $lang, $birth, $death);
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -114,22 +236,61 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$persinstByBaseNameResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`, `persinst_anzeigename`, `persinst_name`, CONCAT(`persinst_name`, ' (', `persinst_geburtsjahr`, '-', `persinst_sterbejahr`, ')')
|
||||
FROM `persinst`
|
||||
WHERE `persinst_anzeigename` = ?
|
||||
OR `persinst_name` = ?
|
||||
OR CONCAT(`persinst_name`, ' (', `persinst_geburtsjahr`, '-', `persinst_sterbejahr`, ')') = ?
|
||||
LIMIT 2", "sss", $name, $name, $name);
|
||||
|
||||
if ($persinstByBaseData = $persinstByBaseNameResult->fetch_row()) {
|
||||
$output = $persinstByBaseData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name) || self::_stri_matches($cur[2], $name) || self::_stri_matches($cur[3], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$persinstByBaseNameResult->close();
|
||||
return 0;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns persinst ID by base name.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $name Name of the persinst to search for.
|
||||
* @param string $birth Birth year.
|
||||
* @param string $death Death year.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getPersinstIDByBaseNamePlusYears(MDMysqli $mysqli_noda, string $name, string $birth, string $death):int {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`, `persinst_anzeigename`, `persinst_name`, CONCAT(`persinst_name`, ' (', `persinst_geburtsjahr`, '-', `persinst_sterbejahr`, ')')
|
||||
FROM `persinst`
|
||||
WHERE (
|
||||
`persinst_anzeigename` = ?
|
||||
OR `persinst_name` = ?
|
||||
OR CONCAT(`persinst_name`, ' (', `persinst_geburtsjahr`, '-', `persinst_sterbejahr`, ')') = ?
|
||||
)
|
||||
AND `persinst_geburtsjahr` = ?
|
||||
AND `persinst_sterbejahr` = ?
|
||||
LIMIT 2", "sssss", $name, $name, $name, $birth, $death);
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name) || self::_stri_matches($cur[2], $name) || self::_stri_matches($cur[3], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -183,22 +344,23 @@ final class NodaIDGetter {
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$lookUpName = $name . $birthYear . $deathYear;
|
||||
$persinstByImportLogResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `persinst_id`, `input_string`
|
||||
FROM `persinst_logged_imports`
|
||||
WHERE `instance` = ?
|
||||
AND `institution_id` = ?
|
||||
AND `input_string` = ?
|
||||
LIMIT 2", "sis", $instance, $institution_id, $lookUpName);
|
||||
|
||||
if ($persinstByImportLogData = $persinstByImportLogResult->fetch_row()) {
|
||||
$output = $persinstByImportLogData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $lookUpName)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$persinstByImportLogResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -244,21 +406,22 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$placeRewriteResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`, `input_name`
|
||||
FROM `ort_rewriting`
|
||||
WHERE `language` = ?
|
||||
AND `input_name` = ?
|
||||
LIMIT 1", "ss", $lang, $name);
|
||||
|
||||
if ($placeRewriteData = $placeRewriteResult->fetch_row()) {
|
||||
$output = $placeRewriteData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$placeRewriteResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -274,20 +437,21 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$placeByBaseNameResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`, `ort_name`
|
||||
FROM `orte`
|
||||
WHERE `ort_name` = ?
|
||||
LIMIT 2", "s", $name);
|
||||
|
||||
if ($placeByBaseData = $placeByBaseNameResult->fetch_row()) {
|
||||
$output = $placeByBaseData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$placeByBaseNameResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -304,21 +468,53 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$placeByTLNameResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`, `trans_name`
|
||||
FROM `ort_translation`
|
||||
WHERE `trans_name` = ?
|
||||
AND `trans_language` = ?
|
||||
LIMIT 2", "ss", $name, $lang);
|
||||
|
||||
if ($placeByTlData = $placeByTLNameResult->fetch_row()) {
|
||||
$output = $placeByTlData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$placeByTLNameResult->close();
|
||||
return 0;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns place ID by entry in place translations table, irrespective of
|
||||
* language.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $name Name of the place to search for.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getPlaceIDByAnyTransName(MDMysqli $mysqli_noda, string $name):int {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`, `trans_name`
|
||||
FROM `ort_translation`
|
||||
WHERE `trans_name` = ?
|
||||
LIMIT 2", "s", $name);
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -369,22 +565,23 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$placeByImportLogResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `ort_id`, `input_string`
|
||||
FROM `orte_logged_imports`
|
||||
WHERE `instance` = ?
|
||||
AND `institution_id` = ?
|
||||
AND `input_string` = ?
|
||||
LIMIT 2", "sis", $instance, $institution_id, $name);
|
||||
|
||||
if ($placeByImportLogData = $placeByImportLogResult->fetch_row()) {
|
||||
$output = $placeByImportLogData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($cur[1], $name)) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$placeByImportLogResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -432,17 +629,19 @@ final class NodaIDGetter {
|
||||
|
||||
$output = [];
|
||||
|
||||
$tagRewriteResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`, `input_name`
|
||||
FROM `tag_rewriting`
|
||||
WHERE `tag_language` = ?
|
||||
AND `input_name` = ?", "ss", $lang, $name);
|
||||
|
||||
while ($tagRewriteData = $tagRewriteResult->fetch_row()) {
|
||||
$output[] = $tagRewriteData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$output[] = $cur[0];
|
||||
}
|
||||
}
|
||||
|
||||
$tagRewriteResult->close();
|
||||
$result->close();
|
||||
|
||||
return $output;
|
||||
|
||||
@ -460,20 +659,21 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$tagByBaseNameResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`, `tag_name`
|
||||
FROM `tag`
|
||||
WHERE `tag_name` = ?
|
||||
LIMIT 2", "s", $name);
|
||||
|
||||
if ($tagByBaseData = $tagByBaseNameResult->fetch_row()) {
|
||||
$output = $tagByBaseData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$tagByBaseNameResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -490,21 +690,53 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$tagByTLNameResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`, `trans_name`
|
||||
FROM `tag_translation`
|
||||
WHERE `trans_name` = ?
|
||||
AND `trans_language` = ?
|
||||
LIMIT 2", "ss", $name, $lang);
|
||||
|
||||
if ($tagByTlData = $tagByTLNameResult->fetch_row()) {
|
||||
$output = $tagByTlData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$tagByTLNameResult->close();
|
||||
return 0;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tag ID by entry in tag translations table,
|
||||
* irrespective of language.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $name Name of the tag to search for.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getTagIDByAnyTransName(MDMysqli $mysqli_noda, string $name):int {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`, `trans_name`
|
||||
FROM `tag_translation`
|
||||
WHERE `trans_name` = ?
|
||||
LIMIT 2", "s", $name);
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -555,22 +787,23 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$tagByImportLogResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `tag_id`, `input_string`
|
||||
FROM `tag_logged_imports`
|
||||
WHERE `instance` = ?
|
||||
AND `institution_id` = ?
|
||||
AND `input_string` = ?
|
||||
LIMIT 2", "sis", $instance, $institution_id, $name);
|
||||
|
||||
if ($tagByImportLogData = $tagByImportLogResult->fetch_row()) {
|
||||
$output = $tagByImportLogData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$tagByImportLogResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -603,6 +836,39 @@ final class NodaIDGetter {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time ID by entry in time name rewriting table.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $lang Language to check in.
|
||||
* @param string $name Name of the time to search for.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getTimeIDByRewrite(MDMysqli $mysqli_noda, string $lang, string $name):int {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$output = [];
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `zeit_id`, `input_name`
|
||||
FROM `zeit_rewriting`
|
||||
WHERE `language` = ?
|
||||
AND `input_name` = ?", "ss", $lang, $name);
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time ID by base name.
|
||||
*
|
||||
@ -615,20 +881,21 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$timeByBaseNameResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `zeit_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `zeit_id`, `zeit_name`
|
||||
FROM `zeiten`
|
||||
WHERE `zeit_name` = ?
|
||||
LIMIT 2", "s", $name);
|
||||
|
||||
if ($timeByBaseData = $timeByBaseNameResult->fetch_row()) {
|
||||
$output = $timeByBaseData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$timeByBaseNameResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -645,21 +912,52 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$timeByTLNameResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `zeit_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `zeit_id`, `trans_name`
|
||||
FROM `zeit_translation`
|
||||
WHERE `trans_name` = ?
|
||||
AND `trans_language` = ?
|
||||
LIMIT 2", "ss", $name, $lang);
|
||||
|
||||
if ($timeByTlData = $timeByTLNameResult->fetch_row()) {
|
||||
$output = $timeByTlData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$timeByTLNameResult->close();
|
||||
return 0;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time ID by entry in time translations table.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $name Name of the time to search for.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getTimeIDByAnyTransName(MDMysqli $mysqli_noda, string $name):int {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `zeit_id`, `trans_name`
|
||||
FROM `zeit_translation`
|
||||
WHERE `trans_name` = ?
|
||||
LIMIT 2", "s", $name);
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -677,22 +975,23 @@ final class NodaIDGetter {
|
||||
|
||||
if (empty($name)) return 0;
|
||||
|
||||
$timeByImportLogResult = $mysqli_noda->query_by_stmt("
|
||||
SELECT `zeit_id`
|
||||
$result = $mysqli_noda->query_by_stmt("
|
||||
SELECT `zeit_id`, `input_string`
|
||||
FROM `zeiten_logged_imports`
|
||||
WHERE `instance` = ?
|
||||
AND `institution_id` = ?
|
||||
AND `input_string` = ?
|
||||
LIMIT 2", "sis", $instance, $institution_id, $name);
|
||||
|
||||
if ($timeByImportLogData = $timeByImportLogResult->fetch_row()) {
|
||||
$output = $timeByImportLogData[0];
|
||||
while ($cur = $result->fetch_row()) {
|
||||
if (self::_stri_matches($name, $cur[1])) {
|
||||
$result->close();
|
||||
return (int)$cur[0];
|
||||
}
|
||||
else $output = 0;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
$timeByImportLogResult->close();
|
||||
|
||||
return $output;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
@ -810,6 +1109,10 @@ final class NodaIDGetter {
|
||||
return $timeIdByName;
|
||||
}
|
||||
|
||||
if (!empty($timeIdByRewrite = self::getTimeIDByRewrite($mysqli_noda, $lang, $name))) {
|
||||
return $timeIdByRewrite;
|
||||
}
|
||||
|
||||
if ($instance !== "") {
|
||||
if (($timeIdByImportLog = self::getTimeIDByImportLog($mysqli_noda, $instance, $institution_id, $name)) !== 0) {
|
||||
return $timeIdByImportLog;
|
||||
@ -819,4 +1122,79 @@ final class NodaIDGetter {
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks each string in a list of strings for its existence as a tag name.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Database connection.
|
||||
* @param string $lang Language to check in.
|
||||
* @param non-empty-array<string> $phrases List of phrases to check.
|
||||
*
|
||||
* @return array{count: int, tag: integer[], actor: integer[], time: integer[], place: integer[]}
|
||||
*/
|
||||
public static function searchEntryNamesByList(MDMysqli $mysqli_noda, string $lang, array $phrases):array {
|
||||
|
||||
$output = [
|
||||
'count' => 0,
|
||||
'tag' => [],
|
||||
'actor' => [],
|
||||
'time' => [],
|
||||
'place' => [],
|
||||
];
|
||||
|
||||
foreach ($phrases as $phrase) {
|
||||
|
||||
if (($tag_id = NodaIDGetter::getTagIDByNamesAndRewrites($mysqli_noda, $lang, $phrase)) !== 0 && !in_array($tag_id, $output['tag'], true)) {
|
||||
$output['tag'][] = $tag_id;
|
||||
++$output['count'];
|
||||
}
|
||||
else if (($tag_id_by_tl = NodaIDGetter::getTagIDByAnyTransName($mysqli_noda, $phrase)) !== 0 && !in_array($tag_id_by_tl, $output['tag'], true)) {
|
||||
$output['tag'][] = $tag_id_by_tl;
|
||||
++$output['count'];
|
||||
}
|
||||
else if (($place_id = NodaIDGetter::getPlaceIDByNamesAndRewrites($mysqli_noda, $lang, $phrase)) !== 0 && !in_array($place_id, $output['place'], true)) {
|
||||
$output['place'][] = $place_id;
|
||||
++$output['count'];
|
||||
}
|
||||
else if (($place_id = NodaIDGetter::getPlaceIDByAnyTransName($mysqli_noda, $phrase)) !== 0 && !in_array($place_id, $output['place'], true)) {
|
||||
$output['place'][] = $place_id;
|
||||
++$output['count'];
|
||||
}
|
||||
else if (($persinst_id = NodaIDGetter::getPersinstIDByNamesAndRewrites($mysqli_noda, $lang, $phrase, '', '')) !== 0 && !in_array($persinst_id, $output['actor'], true)) {
|
||||
$output['actor'][] = $persinst_id;
|
||||
++$output['count'];
|
||||
}
|
||||
else if (($persinst_id = NodaIDGetter::getPersinstIDByAnyTransName($mysqli_noda, $phrase)) !== 0 && !in_array($persinst_id, $output['actor'], true)) {
|
||||
$output['actor'][] = $persinst_id;
|
||||
++$output['count'];
|
||||
}
|
||||
else if (($time_id = NodaIDGetter::getTimeIDByNamesAndRewrites($mysqli_noda, $lang, $phrase)) !== 0 && !in_array($time_id, $output['time'], true)) {
|
||||
$output['time'][] = $time_id;
|
||||
++$output['count'];
|
||||
}
|
||||
else if (($time_id = NodaIDGetter::getTimeIDByAnyTransName($mysqli_noda, $phrase)) !== 0 && !in_array($time_id, $output['time'], true)) {
|
||||
$output['time'][] = $time_id;
|
||||
++$output['count'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (count($phrases) !== $output['count']) {
|
||||
return [
|
||||
'count' => 0,
|
||||
'tag' => [],
|
||||
'actor' => [],
|
||||
'time' => [],
|
||||
'place' => [],
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($output['tag'])) sort($output['tag']);
|
||||
if (!empty($output['actor'])) sort($output['actor']);
|
||||
if (!empty($output['time'])) sort($output['time']);
|
||||
if (!empty($output['place'])) sort($output['place']);
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,12 @@ final class NodaImportLogger {
|
||||
$logStmt = $mysqli_noda->do_prepare("INSERT INTO `persinst_logged_imports`
|
||||
(`instance`, `institution_id`, `input_string`, `persinst_id`)
|
||||
VALUES (?, ?, ?, ?)");
|
||||
try {
|
||||
$logStmt->bind_param("sisi", $instance, $institution_id, $loggedName, $persinst_id);
|
||||
$logStmt->execute();
|
||||
}
|
||||
catch (MDMysqliDuplicateKeysError $e) {
|
||||
}
|
||||
$logStmt->close();
|
||||
|
||||
}
|
||||
@ -54,8 +58,12 @@ final class NodaImportLogger {
|
||||
$logStmt = $mysqli_noda->do_prepare("INSERT INTO `orte_logged_imports`
|
||||
(`instance`, `institution_id`, `input_string`, `ort_id`)
|
||||
VALUES (?, ?, ?, ?)");
|
||||
try {
|
||||
$logStmt->bind_param("sisi", $instance, $institution_id, $name, $ort_id);
|
||||
$logStmt->execute();
|
||||
}
|
||||
catch (MDMysqliDuplicateKeysError $e) {
|
||||
}
|
||||
$logStmt->close();
|
||||
|
||||
}
|
||||
@ -76,8 +84,12 @@ final class NodaImportLogger {
|
||||
$logStmt = $mysqli_noda->do_prepare("INSERT INTO `zeiten_logged_imports`
|
||||
(`instance`, `institution_id`, `input_string`, `zeit_id`)
|
||||
VALUES (?, ?, ?, ?)");
|
||||
try {
|
||||
$logStmt->bind_param("sisi", $instance, $institution_id, $name, $zeit_id);
|
||||
$logStmt->execute();
|
||||
}
|
||||
catch (MDMysqliDuplicateKeysError $e) {
|
||||
}
|
||||
$logStmt->close();
|
||||
|
||||
}
|
||||
@ -98,8 +110,12 @@ final class NodaImportLogger {
|
||||
$logStmt = $mysqli_noda->do_prepare("INSERT INTO `tag_logged_imports`
|
||||
(`instance`, `institution_id`, `input_string`, `tag_id`)
|
||||
VALUES (?, ?, ?, ?)");
|
||||
try {
|
||||
$logStmt->bind_param("sisi", $instance, $institution_id, $name, $tag_id);
|
||||
$logStmt->execute();
|
||||
}
|
||||
catch (MDMysqliDuplicateKeysError $e) {
|
||||
}
|
||||
$logStmt->close();
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ final class NodaLogEdit {
|
||||
|
||||
const SECTION_WHITELIST = [
|
||||
'base',
|
||||
'addition',
|
||||
'noda_link',
|
||||
'name_variant',
|
||||
'logged_import_concordances',
|
||||
@ -30,6 +31,7 @@ final class NodaLogEdit {
|
||||
'synchronize',
|
||||
'status',
|
||||
'translation',
|
||||
'group',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -107,4 +107,26 @@ final class NodaMailChecker {
|
||||
return self::validateMailDomainAccessibilityCached($mysqli, $domain);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates mail address, throwing an exception if the mail address is not valid.
|
||||
*
|
||||
* @param MDMysqli $mysqli DB connection.
|
||||
* @param string $mail_address Address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function validateMail(MDMysqli $mysqli, string $mail_address):string {
|
||||
|
||||
if (empty($mail_address)) return $mail_address;
|
||||
|
||||
$mail_address = MD_STD_IN::sanitize_email($mail_address);
|
||||
|
||||
if (self::validateMailByDomainAccessibilityCached($mysqli, $mail_address) === false) {
|
||||
throw new MDInvalidEmail("The host name of the entered mail address is invalid");
|
||||
}
|
||||
|
||||
return $mail_address;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -143,6 +143,54 @@ final class NodaNameGetter {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function for getting persinst names in bulk.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda DB connection.
|
||||
* @param string $lang Language.
|
||||
* @param array<integer> $persinst_ids Persinst IDs.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function getBatchPersinstNamesWithLifeDates(MDMysqli $mysqli_noda, string $lang, array $persinst_ids):array {
|
||||
|
||||
$output = [];
|
||||
|
||||
// Get translations
|
||||
$result = $mysqli_noda->do_read_query("SELECT `persinst`.`persinst_id`, `trans_name`, `persinst_geburtsjahr`, `persinst_sterbejahr`
|
||||
FROM `persinst_translation`, `persinst`
|
||||
WHERE `trans_language` = '" . $mysqli_noda->escape_string($lang) . "'
|
||||
AND `persinst`.`persinst_id` = `persinst_translation`.`persinst_id`
|
||||
AND `persinst`.`persinst_id` IN (" . implode(', ', $persinst_ids) . ")");
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
$name = (string)$cur[1];
|
||||
if (!empty($cur[2]) || !empty($cur[3])) {
|
||||
$name .= ' (' . $cur[2] . '-' . $cur[3] . ')';
|
||||
}
|
||||
$output[(int)$cur[0]] = $name;
|
||||
}
|
||||
$result->close();
|
||||
|
||||
if (!empty($persinst_ids_left = array_diff($persinst_ids, array_keys($output)))) {
|
||||
$result = $mysqli_noda->do_read_query("SELECT `persinst_id`, `persinst_anzeigename`
|
||||
FROM `persinst`
|
||||
WHERE `persinst_id` IN (" . implode(', ', $persinst_ids_left) . ")");
|
||||
|
||||
while ($cur = $result->fetch_row()) {
|
||||
$output[(int)$cur[0]] = (string)$cur[1];
|
||||
}
|
||||
$result->close();
|
||||
}
|
||||
|
||||
if (count($output) > 1) {
|
||||
asort($output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function for getting time names in bulk.
|
||||
*
|
||||
@ -335,4 +383,28 @@ final class NodaNameGetter {
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function for getting the name for a group.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda DB connection.
|
||||
* @param integer $group_id Group ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getGroupName(MDMysqli $mysqli_noda, int $group_id):string {
|
||||
|
||||
$result = $mysqli_noda->query_by_stmt("SELECT `group_name`
|
||||
FROM `group`
|
||||
WHERE `group_id` = ?", "i", $group_id);
|
||||
|
||||
if ($cur = $result->fetch_row()) {
|
||||
$result->close();
|
||||
return MD_STD_IN::sanitize_text($cur[0]);
|
||||
}
|
||||
$result->close();
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
}
|
||||
|
407
src/NodaSplitTime.php
Normal file
407
src/NodaSplitTime.php
Normal file
@ -0,0 +1,407 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Describes a time after splitting / transfer object.
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Describes a time after splitting / transfer object.
|
||||
*/
|
||||
final class NodaSplitTime {
|
||||
|
||||
public const DEFAULT_DATE = '0001-01-01';
|
||||
|
||||
public readonly string $start_year;
|
||||
public readonly string $end_year;
|
||||
|
||||
public readonly string $counting_time_month;
|
||||
public readonly string $counting_time_day;
|
||||
|
||||
public readonly NodaCountingTimeIndicator $counting_time_indicator;
|
||||
public readonly NodaTimeBeforeAfterIndicator $before_after_indicator;
|
||||
|
||||
public string $start_date;
|
||||
public string $end_date;
|
||||
|
||||
/**
|
||||
* Returns a single, exact date.
|
||||
*
|
||||
* @param string $year Year.
|
||||
* @param string $month Month.
|
||||
* @param string $day Day.
|
||||
* @param NodaTimeBeforeAfterIndicator $before_after_indicator Determines if the time is exact or before / after.
|
||||
*
|
||||
* @return NodaSplitTime
|
||||
*/
|
||||
public static function genExactDate(string $year, string $month, string $day, NodaTimeBeforeAfterIndicator $before_after_indicator = NodaTimeBeforeAfterIndicator::none):NodaSplitTime {
|
||||
|
||||
$start_year = $end_year = $year;
|
||||
$start_date = $end_date = $year . '-' . $month . '-' . $day;
|
||||
|
||||
if ($before_after_indicator === NodaTimeBeforeAfterIndicator::before
|
||||
|| $before_after_indicator === NodaTimeBeforeAfterIndicator::until
|
||||
) {
|
||||
$start_year = $start_date = '?';
|
||||
}
|
||||
if ($before_after_indicator === NodaTimeBeforeAfterIndicator::after
|
||||
|| $before_after_indicator === NodaTimeBeforeAfterIndicator::since
|
||||
) {
|
||||
$end_year = $end_date = '?';
|
||||
}
|
||||
|
||||
return new NodaSplitTime($start_year, $end_year, $month, $day, start_date: $start_date, end_date: $end_date);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the entered start year.
|
||||
*
|
||||
* @param string $input Input to validate.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _validateStartYear(string $input):void {
|
||||
|
||||
if (strlen($input) > 6) {
|
||||
throw new MDgenericInvalidInputsException("Time statement longer than 6 characters is impossible");
|
||||
}
|
||||
|
||||
if (($this->before_after_indicator === NodaTimeBeforeAfterIndicator::before
|
||||
|| $this->before_after_indicator === NodaTimeBeforeAfterIndicator::until)
|
||||
&& $input !== '?'
|
||||
) {
|
||||
throw new MDgenericInvalidInputsException("Times with no certain start need to have a question mark (?) entered as a start date");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the entered end year.
|
||||
*
|
||||
* @param string $input Input to validate.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _validateEndYear(string $input):void {
|
||||
|
||||
if (strlen($input) > 6) {
|
||||
throw new MDgenericInvalidInputsException("Time statement longer than 6 characters is impossible");
|
||||
}
|
||||
|
||||
if (($this->before_after_indicator === NodaTimeBeforeAfterIndicator::after
|
||||
|| $this->before_after_indicator === NodaTimeBeforeAfterIndicator::since)
|
||||
&& $input !== '?'
|
||||
) {
|
||||
throw new MDgenericInvalidInputsException("Times with no certain end need to have a question mark (?) entered as a end date");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads to four digits. E.g. 2 > 02.
|
||||
*
|
||||
* @param string $input Input string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function pad_to_two(string $input):string {
|
||||
|
||||
return \substr("00" . $input, -2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the entered month string.
|
||||
*
|
||||
* @param string $input Input to validate.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function _validateCountingTimeMonth(string $input):string {
|
||||
|
||||
if (strlen($input) > 2) throw new MDgenericInvalidInputsException("Input too long");
|
||||
|
||||
if ($input === '00') {
|
||||
return $input;
|
||||
}
|
||||
|
||||
if (($parsedInt = filter_var(ltrim($input, "0"), FILTER_VALIDATE_INT)) === false) {
|
||||
throw new MDgenericInvalidInputsException("Input value is not numeric");
|
||||
}
|
||||
if ($parsedInt > 12) {
|
||||
throw new MDgenericInvalidInputsException("Attempted to set a month number beyond 12");
|
||||
}
|
||||
|
||||
return self::pad_to_two($input);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the entered day string.
|
||||
*
|
||||
* @param string $input Input to validate.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function _validateCountingTimeDay(string $input):string {
|
||||
|
||||
if (strlen($input) > 2) throw new MDgenericInvalidInputsException("Input too long");
|
||||
|
||||
if ($input === '00') {
|
||||
return $input;
|
||||
}
|
||||
|
||||
if (($parsedInt = filter_var(ltrim($input, "0"), FILTER_VALIDATE_INT)) === false) {
|
||||
throw new MDgenericInvalidInputsException("Input value is not numeric");
|
||||
}
|
||||
if ($parsedInt > 31) {
|
||||
throw new MDgenericInvalidInputsException("Attempted to set a day number beyond 31");
|
||||
}
|
||||
|
||||
return self::pad_to_two($input);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a date time for the start of the time.
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function startToDateTime():DateTime {
|
||||
|
||||
if ($this->counting_time_indicator === NodaCountingTimeIndicator::bce) {
|
||||
return new DateTime('-' . $this->start_date);
|
||||
}
|
||||
else {
|
||||
return new DateTime($this->start_date);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a date time for the end of the time.
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function endToDateTime():DateTime {
|
||||
|
||||
if ($this->counting_time_indicator === NodaCountingTimeIndicator::bce) {
|
||||
return new DateTime('-' . $this->end_date);
|
||||
}
|
||||
else {
|
||||
return new DateTime($this->end_date);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a time name based on the current entry.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toTimeName():string {
|
||||
|
||||
$prefix = $this->before_after_indicator->toString();
|
||||
if (!empty($prefix)) $prefix .= ' ';
|
||||
|
||||
// Determine start and end for display
|
||||
if ($this->start_year === '?') {
|
||||
$start = (int)$this->end_year;
|
||||
}
|
||||
else $start = (int)$this->start_year;
|
||||
|
||||
if ($this->end_year === '?') {
|
||||
$end = (int)$this->start_year;
|
||||
}
|
||||
else $end = (int)$this->end_year;
|
||||
|
||||
if ($this->before_after_indicator === NodaTimeBeforeAfterIndicator::before && $this->counting_time_month === '00') {
|
||||
$start++;
|
||||
$end++;
|
||||
}
|
||||
else if ($this->before_after_indicator === NodaTimeBeforeAfterIndicator::after && $this->counting_time_month === '00') {
|
||||
$start--;
|
||||
$end--;
|
||||
}
|
||||
|
||||
// Determine suffix
|
||||
if ($start < 0 && $end < 0) {
|
||||
$suffix = " v. Chr.";
|
||||
}
|
||||
else if ($end < 1000) {
|
||||
$suffix = " n. Chr.";
|
||||
}
|
||||
else $suffix = "";
|
||||
|
||||
$start = \abs($start);
|
||||
$end = \abs($end);
|
||||
|
||||
if ($start !== $end) {
|
||||
return "{$prefix}{$start}-{$end}{$suffix}";
|
||||
}
|
||||
|
||||
// A single day of a given month of a given (single) year
|
||||
else if (\intval($this->counting_time_month) !== 0 and \intval($this->counting_time_day) !== 0) {
|
||||
return "{$prefix}{$this->counting_time_day}.{$this->counting_time_month}.{$start}{$suffix}";
|
||||
}
|
||||
|
||||
// A single year
|
||||
else if ($start === $end && trim((string)$this->counting_time_month, " 0") === "" && trim((string)$this->counting_time_day, " 0") === "") {
|
||||
return "{$prefix}{$start}{$suffix}";
|
||||
}
|
||||
|
||||
// Single month of a given year
|
||||
else if ($start === $end && trim((string)$this->counting_time_month, " 0") !== "" && trim((string)$this->counting_time_day, " 0") === "") {
|
||||
|
||||
$fmt = new IntlDateFormatter(
|
||||
'de-DE',
|
||||
IntlDateFormatter::FULL,
|
||||
IntlDateFormatter::FULL,
|
||||
null,
|
||||
IntlDateFormatter::GREGORIAN,
|
||||
'MMMM Y'
|
||||
);
|
||||
|
||||
try {
|
||||
return $prefix . $fmt->format(MD_STD::strtotime("{$start}-{$this->counting_time_month}-15 01:01:01")) . $suffix;
|
||||
}
|
||||
catch (MDInvalidInputDate $e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array in the old time splitter format (array with sex values).
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function toOldFormat():array {
|
||||
|
||||
return [
|
||||
$this->start_year,
|
||||
$this->end_year,
|
||||
$this->counting_time_month,
|
||||
$this->counting_time_day,
|
||||
$this->counting_time_indicator->toString(),
|
||||
$this->before_after_indicator->toString(),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $start_year Year.
|
||||
* @param string $end_year Year.
|
||||
* @param string $counting_time_month Month.
|
||||
* @param string $counting_time_day Day.
|
||||
* @param NodaCountingTimeIndicator $counting_time_indicator Determines if the time is BCE or CCE.
|
||||
* @param NodaTimeBeforeAfterIndicator $before_after_indicator Determines if the time is inexact to one direction.
|
||||
* @param false|string $start_date Start date.
|
||||
* @param false|string $end_date End date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $start_year, string $end_year,
|
||||
string $counting_time_month = "00", string $counting_time_day = "00",
|
||||
NodaCountingTimeIndicator $counting_time_indicator = NodaCountingTimeIndicator::ce,
|
||||
NodaTimeBeforeAfterIndicator $before_after_indicator = NodaTimeBeforeAfterIndicator::none,
|
||||
false|string $start_date = false,
|
||||
false|string $end_date = false,
|
||||
) {
|
||||
|
||||
if (substr($start_year, 0, 1) === '-') {
|
||||
if (strlen($start_year) > 5) $start_year = '-' . str_pad(trim($start_year, '-'), 4, '-', STR_PAD_LEFT);
|
||||
}
|
||||
if ($start_date !== false && str_starts_with($start_date, '-')) {
|
||||
$parts = explode('-', trim($start_date, '-'));
|
||||
$parts[0] = str_pad($parts[0], 4, '0', STR_PAD_LEFT);
|
||||
$start_date = '-' . implode('-', $parts);
|
||||
}
|
||||
if (substr($end_year, 0, 1) === '-') {
|
||||
if (strlen($end_year) > 5) $end_year = '-' . str_pad(trim($end_year, '-'), 4, '-', STR_PAD_LEFT);
|
||||
}
|
||||
if ($end_date !== false && str_starts_with($end_date, '-')) {
|
||||
$parts = explode('-', trim($end_date, '-'));
|
||||
$parts[0] = str_pad($parts[0], 4, '0', STR_PAD_LEFT);
|
||||
$end_date = '-' . implode('-', $parts);
|
||||
}
|
||||
|
||||
$this->counting_time_indicator = $counting_time_indicator;
|
||||
$this->before_after_indicator = $before_after_indicator;
|
||||
|
||||
$this->_validateStartYear($start_year);
|
||||
$this->start_year = $start_year;
|
||||
|
||||
$this->_validateEndYear($end_year);
|
||||
$this->end_year = $end_year;
|
||||
|
||||
$this->counting_time_month = $this->_validateCountingTimeMonth($counting_time_month);
|
||||
$this->counting_time_day = $this->_validateCountingTimeDay($counting_time_day);
|
||||
|
||||
// Calculate start date and end date
|
||||
if (($this->before_after_indicator === NodaTimeBeforeAfterIndicator::before
|
||||
|| $this->before_after_indicator === NodaTimeBeforeAfterIndicator::until)
|
||||
|| $start_date === '?'
|
||||
) {
|
||||
$this->start_date = '-9999-12-31';
|
||||
}
|
||||
if (($this->before_after_indicator === NodaTimeBeforeAfterIndicator::after
|
||||
|| $this->before_after_indicator === NodaTimeBeforeAfterIndicator::since)
|
||||
|| $end_date === '?'
|
||||
) {
|
||||
$this->end_date = '9999-12-31';
|
||||
}
|
||||
|
||||
if (!isset($this->start_date) && false !== $start_date) {
|
||||
$this->start_date = date("Y-m-d", MD_STD::strtotime($start_date));
|
||||
}
|
||||
if (!isset($this->end_date) && false !== $end_date) {
|
||||
$this->end_date = date("Y-m-d", MD_STD::strtotime($end_date));
|
||||
}
|
||||
|
||||
if (!isset($this->start_date)) {
|
||||
if ($this->counting_time_month === "00") {
|
||||
$this->start_date = $this->start_year . '-01-01';
|
||||
}
|
||||
else if ($this->counting_time_day === "00") {
|
||||
$this->start_date = $this->start_year . '-' . $this->counting_time_month . '-01';
|
||||
}
|
||||
else {
|
||||
throw new MDgenericInvalidInputsException("Cannot identify start date automatically");
|
||||
}
|
||||
}
|
||||
if (!isset($this->end_date)) {
|
||||
if ($this->counting_time_month === "00") {
|
||||
$this->end_date = $this->end_year . '-12-31';
|
||||
}
|
||||
else if ($this->counting_time_day === "00") {
|
||||
$this->end_date = $this->end_year . '-' . $this->counting_time_month . '-31';
|
||||
}
|
||||
else {
|
||||
throw new MDgenericInvalidInputsException("Cannot identify end date automatically");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate
|
||||
$startDateTime = MD_STD::strtotime("2000-" . substr($this->start_date, -5));
|
||||
if (checkdate((int)date('m', $startDateTime), (int)date('d', $startDateTime), (int)date('Y', $startDateTime)) === false) {
|
||||
throw new MDgenericInvalidInputsException("Invalid start date: " . $this->start_date);
|
||||
}
|
||||
|
||||
if (!empty((int)$this->counting_time_day)) {
|
||||
// The year 2000 is used here as it is a leap year and lots of years accepted in md are not accepted
|
||||
// by checkdate.
|
||||
if (checkdate((int)$this->counting_time_month, (int)$this->counting_time_day, 2000) === false) {
|
||||
throw new MDgenericInvalidInputsException("Invalid date formed by counting time: " . $this->counting_time_month . ' -- ' . $this->counting_time_day);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
64
src/NodaTagRelationIdentifier.php
Normal file
64
src/NodaTagRelationIdentifier.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Identifies the type of tag relation to an object based on known suffixes.
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Contains static functions for identifying uncertainty or blocking
|
||||
* completely uncertain inputs for actors, times, and places.
|
||||
*/
|
||||
final class NodaTagRelationIdentifier {
|
||||
|
||||
private const SUFFIXES = [
|
||||
'de' => [
|
||||
' (Motiv)' => MDTagRelationType::display_subject,
|
||||
' [Motiv]' => MDTagRelationType::display_subject,
|
||||
' <Motiv>' => MDTagRelationType::display_subject,
|
||||
|
||||
' (Material)' => MDTagRelationType::material,
|
||||
' [Material]' => MDTagRelationType::material,
|
||||
' <Material>' => MDTagRelationType::material,
|
||||
|
||||
' (Technik)' => MDTagRelationType::technique,
|
||||
' [Technik]' => MDTagRelationType::technique,
|
||||
' <Technik>' => MDTagRelationType::technique,
|
||||
]
|
||||
];
|
||||
|
||||
public readonly string $name;
|
||||
public readonly MDTagRelationType|false $relation;
|
||||
|
||||
/**
|
||||
* Constructor: Removes identifiers for well-known tag relations and determines cleaned name and relation type.
|
||||
*
|
||||
* @param string $lang Current language.
|
||||
* @param string $input_string Input string to clean.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $lang, string $input_string) {
|
||||
|
||||
if (empty(self::SUFFIXES[$lang])) {
|
||||
$this->name = $input_string;
|
||||
$this->relation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$relation = false;
|
||||
|
||||
$suffixes = self::SUFFIXES[$lang];
|
||||
foreach (array_keys($suffixes) as $suffix) {
|
||||
if (\mb_substr($input_string, \mb_strlen($suffix) * -1) === "$suffix") {
|
||||
$input_string = \mb_substr($input_string, 0, \mb_strlen($suffix) * -1);
|
||||
$relation = $suffixes[$suffix];
|
||||
}
|
||||
}
|
||||
|
||||
$this->name = $input_string;
|
||||
$this->relation = $relation;
|
||||
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ final class NodaTimeAutotranslater {
|
||||
|
||||
// TODO: Move these to NodaTimeAutotranslaterLocales
|
||||
|
||||
const LANGS_SYLLABLE_CLEANING = [
|
||||
public const LANGS_SYLLABLE_CLEANING = [
|
||||
"hu" => [
|
||||
"10-as évek" => "10-es évek",
|
||||
"40-as évek" => "40-es évek",
|
||||
@ -463,45 +463,52 @@ final class NodaTimeAutotranslater {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets translations for a given entry type.
|
||||
* Prepares translations for each available language.
|
||||
*
|
||||
* @param array<integer|string> $timeInfo Time information.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function getTranslations(array $timeInfo):array {
|
||||
public static function prepareTranslations(array $timeInfo):array {
|
||||
|
||||
if (!empty($timeInfo['zeit_name']) and strlen((string)$timeInfo['zeit_name']) > 10 and !empty($timespanDates = NodaTimeSplitter::attempt_splitting_from_till((string)$timeInfo['zeit_name']))) {
|
||||
|
||||
$output = [];
|
||||
|
||||
$start = NodaTimeSplitter::attempt_splitting($timespanDates['start_name']);
|
||||
if (empty($start = NodaTimeSplitter::attempt_splitting($timespanDates['start_name']))) {
|
||||
return [];
|
||||
}
|
||||
$startTimeInfo = [
|
||||
"zeit_name" => $timespanDates['start_name'],
|
||||
"zeit_beginn" => $start[0],
|
||||
"zeit_ende" => $start[1],
|
||||
"zeit_beginn" => $start->start_year,
|
||||
"zeit_ende" => $start->end_year,
|
||||
"zeit_zaehlzeit_jahr" => NodaTimeSplitter::timePartsToCountingYear($start),
|
||||
"zeit_zaehlzeit_monat" => $start[2],
|
||||
"zeit_zaehlzeit_tag" => $start[3],
|
||||
"zeit_zaehlzeit_vorzeichen" => $start[4],
|
||||
"zeit_zaehlzeit_monat" => $start->counting_time_month,
|
||||
"zeit_zaehlzeit_tag" => $start->counting_time_day,
|
||||
"zeit_zaehlzeit_vorzeichen" => $start->counting_time_indicator->toString(),
|
||||
];
|
||||
|
||||
$end = NodaTimeSplitter::attempt_splitting($timespanDates['end_name']);
|
||||
if (empty($end = NodaTimeSplitter::attempt_splitting($timespanDates['end_name']))) {
|
||||
return [];
|
||||
}
|
||||
$endTimeInfo = [
|
||||
"zeit_name" => $timespanDates['end_name'],
|
||||
"zeit_beginn" => $end[0],
|
||||
"zeit_ende" => $end[1],
|
||||
"zeit_beginn" => $end->start_year,
|
||||
"zeit_ende" => $end->end_year,
|
||||
"zeit_zaehlzeit_jahr" => NodaTimeSplitter::timePartsToCountingYear($end),
|
||||
"zeit_zaehlzeit_monat" => $end[2],
|
||||
"zeit_zaehlzeit_tag" => $end[3],
|
||||
"zeit_zaehlzeit_vorzeichen" => $end[4],
|
||||
"zeit_zaehlzeit_monat" => $end->counting_time_month,
|
||||
"zeit_zaehlzeit_tag" => $end->counting_time_day,
|
||||
"zeit_zaehlzeit_vorzeichen" => $end->counting_time_indicator->toString(),
|
||||
];
|
||||
|
||||
$output = [];
|
||||
$cases = NodaTimeAutotranslaterLocales::cases();
|
||||
foreach ($cases as $tLang) {
|
||||
$start_term = self::getTranslations($startTimeInfo)[$tLang->name];
|
||||
$end_term = self::getTranslations($endTimeInfo)[$tLang->name];
|
||||
$startTls = self::getTranslations($startTimeInfo);
|
||||
$endTls = self::getTranslations($endTimeInfo);
|
||||
if (empty($startTls) || empty($endTls)) return [];
|
||||
$start_term = $startTls[$tLang->name];
|
||||
$end_term = $endTls[$tLang->name];
|
||||
|
||||
$output[$tLang->name] = \sprintf($tLang->formatYearspanForSprintf(), $start_term, $end_term);
|
||||
}
|
||||
@ -600,6 +607,78 @@ final class NodaTimeAutotranslater {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates correctness of years in translation strings.
|
||||
*
|
||||
* @param string|integer $start Start year.
|
||||
* @param string|integer $end End year.
|
||||
* @param array<string, string> $translations Translations.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function validateTranslations(string|int $start, string|int $end, array $translations):bool {
|
||||
|
||||
$start = ltrim((string)$start, ' 0-');
|
||||
$end = ltrim((string)$end, ' 0-');
|
||||
|
||||
// Edge cases: Centuries and decades have special translations
|
||||
// and can thus not be validated properly
|
||||
// Century BCE
|
||||
if (substr($start, -1) === "0" && substr($end, -1) === '1' && $start > $end) {
|
||||
return true;
|
||||
}
|
||||
// Century CE
|
||||
if (substr($start, -1) === "1" && substr($end, -1) === '0' && $start < $end) {
|
||||
return true;
|
||||
}
|
||||
// Decade
|
||||
if (substr($start, -1) === "0" && substr($end, -1) === '9' && $start < $end) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1920 + ? can be both Since 1920 and After 1919, so validation
|
||||
// is impossible there, too
|
||||
if ($start === '?' || $end === '?') return true;
|
||||
|
||||
// Unset unvalidatable languages
|
||||
unset($translations['ar'], $translations['fa']);
|
||||
|
||||
if ($start !== '?') {
|
||||
foreach ($translations as $t) {
|
||||
if (!str_contains($t, $start)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($end !== '?' && $start !== $end) {
|
||||
foreach ($translations as $t) {
|
||||
if (!str_contains($t, $end)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets translations for a given entry type.
|
||||
*
|
||||
* @param array<integer|string> $timeInfo Time information.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function getTranslations(array $timeInfo):array {
|
||||
|
||||
$output = self::prepareTranslations($timeInfo);
|
||||
|
||||
if (self::validateTranslations($timeInfo['zeit_beginn'], $timeInfo['zeit_ende'], $output) === false) return [];
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs autotranslater.
|
||||
*
|
||||
@ -609,7 +688,9 @@ final class NodaTimeAutotranslater {
|
||||
*/
|
||||
public function translate(array $timeInfo):void {
|
||||
|
||||
$translations = self::getTranslations($timeInfo);
|
||||
if (empty($translations = self::getTranslations($timeInfo))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_mysqli_noda->autocommit(false);
|
||||
|
||||
|
67
src/NodaTimeBeforeAfterIndicator.php
Normal file
67
src/NodaTimeBeforeAfterIndicator.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Represents a time indicator (CE / BCE).
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Represents a time indicator (CE / BCE).
|
||||
*/
|
||||
enum NodaTimeBeforeAfterIndicator implements JsonSerializable {
|
||||
|
||||
case none;
|
||||
case after;
|
||||
case before;
|
||||
case since;
|
||||
case until;
|
||||
|
||||
/**
|
||||
* Returns a value of this type based on a string.
|
||||
*
|
||||
* @param string $input Input to get a value from.
|
||||
*
|
||||
* @return NodaTimeBeforeAfterIndicator
|
||||
*/
|
||||
public static function fromString(string $input):NodaTimeBeforeAfterIndicator {
|
||||
|
||||
return match($input) {
|
||||
'' => self::none,
|
||||
'Nach' => self::after,
|
||||
'Vor' => self::before,
|
||||
'Seit' => self::since,
|
||||
'Bis' => self::until,
|
||||
default => throw new MDpageParameterNotFromListException("Unknown before / after indicator"),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a canonical string representation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toString():string {
|
||||
|
||||
return match($this) {
|
||||
self::none => '',
|
||||
self::after => 'Nach',
|
||||
self::before => 'Vor',
|
||||
self::since => 'Seit',
|
||||
self::until => 'Bis',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the option to serialize as a string during json_encode().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function jsonSerialize():string {
|
||||
|
||||
return $this->name;
|
||||
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ declare(strict_types = 1);
|
||||
*/
|
||||
final class NodaUncertaintyHelper {
|
||||
|
||||
const PERSINST_INDICATORS_DISALLOWED = [
|
||||
public const PERSINST_INDICATORS_DISALLOWED = [
|
||||
"Unbekannt",
|
||||
"unbekannt",
|
||||
"Anonymus",
|
||||
@ -31,30 +31,42 @@ final class NodaUncertaintyHelper {
|
||||
"ismeretlen.",
|
||||
"Ismeretlen.",
|
||||
"ism.",
|
||||
"невизначена", // "Uncertain" (ukr)
|
||||
"невизначений", // "Unspecified" (ukr)
|
||||
"не встановлено", // "Not established" (ukr)
|
||||
"невизначено", // "Not established" (ukr)
|
||||
"Невідом", // Unknown
|
||||
"невизн", // Unknown
|
||||
"Не визначен", // Not determined
|
||||
"Невідомий артист", // Unknown artist
|
||||
];
|
||||
|
||||
const PERSINST_UNCERTAINTY_PREFIXES = [
|
||||
public const PERSINST_UNCERTAINTY_PREFIXES = [
|
||||
"verm. ",
|
||||
"Verm. ",
|
||||
"vermtl. ",
|
||||
"Vermtl. ",
|
||||
"vermutl. ",
|
||||
"Vermutl. ",
|
||||
"vermutlich ",
|
||||
"Vermutlich ",
|
||||
"wahrscheinlich ",
|
||||
"Wahrscheinlich ",
|
||||
"wohl ",
|
||||
"Wohl ",
|
||||
"?",
|
||||
];
|
||||
|
||||
const PERSINST_UNCERTAINTY_SUFFIXES = [
|
||||
public const PERSINST_UNCERTAINTY_SUFFIXES = [
|
||||
"(?)",
|
||||
"?",
|
||||
" [vermutlich]",
|
||||
" vermutlich",
|
||||
" [verm.]",
|
||||
" [wahrscheinlich]",
|
||||
];
|
||||
|
||||
const TIME_INDICATORS_DISALLOWED = [
|
||||
public const TIME_INDICATORS_DISALLOWED = [
|
||||
"Nachgewiesen",
|
||||
"nachgewiesen",
|
||||
"o.D.",
|
||||
@ -76,12 +88,20 @@ final class NodaUncertaintyHelper {
|
||||
"Neu",
|
||||
"Neu hergestellt",
|
||||
"zeitl. nicht faßbar",
|
||||
"Без року", // Without a year
|
||||
"Без дати", // Unknown date
|
||||
"Не датовано", // Not dated
|
||||
"Н.д.", // Not dated
|
||||
"Без датування", // No dating
|
||||
"б.р.", // No dating
|
||||
"б.д.", // No dating
|
||||
];
|
||||
|
||||
const TIME_UNCERTAINTY_PREFIXES = [
|
||||
public const TIME_UNCERTAINTY_PREFIXES = [
|
||||
"c. ",
|
||||
"ca ",
|
||||
"ca. ",
|
||||
"ca.",
|
||||
"Ca ",
|
||||
"Ca. ",
|
||||
"za. ",
|
||||
@ -94,6 +114,8 @@ final class NodaUncertaintyHelper {
|
||||
"Verm. ",
|
||||
"vermtl. ",
|
||||
"Vermtl. ",
|
||||
"vermutl. ",
|
||||
"Vermutl. ",
|
||||
"vermutlich ",
|
||||
"Vermutlich ",
|
||||
"vermutlich um ",
|
||||
@ -102,9 +124,17 @@ final class NodaUncertaintyHelper {
|
||||
"wohl um ",
|
||||
"Wohl ",
|
||||
"Wohl um ",
|
||||
"Приблизно", // UK: About / approximately
|
||||
"близько", // UK: About
|
||||
"около", // UK: About
|
||||
"коло", // UK: About
|
||||
"неточно", // UK: Inaccurate
|
||||
"майже", // UK: Almost / nearly / about
|
||||
"орієнтовно", // UK: approximately
|
||||
"Прибл.", // UK: approximately
|
||||
];
|
||||
|
||||
const TIME_UNCERTAINTY_SUFFIXES = [
|
||||
public const TIME_UNCERTAINTY_SUFFIXES = [
|
||||
"(?)",
|
||||
"?",
|
||||
" (ca.)",
|
||||
@ -113,15 +143,19 @@ final class NodaUncertaintyHelper {
|
||||
" [circa]",
|
||||
" (verm.)",
|
||||
" (vermutl.)",
|
||||
" vermutlich",
|
||||
" körül",
|
||||
", um",
|
||||
", ca.",
|
||||
", ca",
|
||||
" (um)",
|
||||
" (ок.)",
|
||||
];
|
||||
|
||||
/**
|
||||
* Substrings used to express uncertainty about the validity of a place name.
|
||||
*/
|
||||
const PLACE_INDICATORS_DISALLOWED = [
|
||||
public const PLACE_INDICATORS_DISALLOWED = [
|
||||
"Unbekannt",
|
||||
"unbekannt",
|
||||
"Unknown",
|
||||
@ -140,25 +174,50 @@ final class NodaUncertaintyHelper {
|
||||
"o. O.",
|
||||
"Diverse O. u. o.O.",
|
||||
"o.O.",
|
||||
"Без місця", // No place
|
||||
"не вказано", // No place
|
||||
"не вказане", // No place
|
||||
"невідоме", // No place
|
||||
];
|
||||
|
||||
const PLACE_UNCERTAINTY_PREFIXES = [
|
||||
public const PLACE_UNCERTAINTY_PREFIXES = [
|
||||
"ca ",
|
||||
"Ca ",
|
||||
"ca. ",
|
||||
"Ca. ",
|
||||
"circa ",
|
||||
"Circa ",
|
||||
"evtl ",
|
||||
"Evtl ",
|
||||
"evtl. ",
|
||||
"Evtl. ",
|
||||
"möglicherweise ",
|
||||
"Möglicherweise ",
|
||||
"vlt. ",
|
||||
"wohl ",
|
||||
"Wohl ",
|
||||
"Vlt. ",
|
||||
"verm. ",
|
||||
"Verm. ",
|
||||
"vermut. ",
|
||||
"Vermut. ",
|
||||
"vermtl. ",
|
||||
"Vermtl. ",
|
||||
"vermutl. ",
|
||||
"Vermutl. ",
|
||||
"vermutlich ",
|
||||
"Vermutlich ",
|
||||
"vermutlich: ",
|
||||
"Vermutlich: ",
|
||||
"wohl ",
|
||||
"Wohl ",
|
||||
"wahrsch. ",
|
||||
"Wahrsch. ",
|
||||
"wahrscheinlich ",
|
||||
"Wahrscheinlich ",
|
||||
"можливо",
|
||||
"?",
|
||||
];
|
||||
|
||||
const PLACE_UNCERTAINTY_SUFFIXES = [
|
||||
public const PLACE_UNCERTAINTY_SUFFIXES = [
|
||||
"(?)",
|
||||
"(vermutl.)",
|
||||
"[vermutl.]",
|
||||
@ -178,8 +237,7 @@ final class NodaUncertaintyHelper {
|
||||
*/
|
||||
public static function trim(string $input):string {
|
||||
|
||||
$input = \trim($input, ", \t\n\r\n;-:");
|
||||
return $input;
|
||||
return \trim($input, ", \t\n\r\n;-:");
|
||||
|
||||
}
|
||||
|
||||
@ -226,7 +284,7 @@ final class NodaUncertaintyHelper {
|
||||
*/
|
||||
public static function guessTimeCertainty(string $zeit_name):bool {
|
||||
|
||||
$zeit_name = \strtolower($zeit_name);
|
||||
$zeit_name = self::trim(strtolower($zeit_name));
|
||||
|
||||
// Attempt to guess uncertainty based on prefixes.
|
||||
foreach (self::TIME_UNCERTAINTY_PREFIXES as $prefix) {
|
||||
@ -275,6 +333,14 @@ final class NodaUncertaintyHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// If brackets are included in the name, try removing prefixes and suffixes
|
||||
// from the beginning.
|
||||
if (($bracketPos = strpos($ort_name, "(")) !== false) {
|
||||
$start = substr($ort_name, 0, $bracketPos);
|
||||
$end = substr($ort_name, $bracketPos);
|
||||
$ort_name = self::cleanUncertaintyIndicatorsPlace($start) . ' ' . $end;
|
||||
}
|
||||
|
||||
return self::trim($ort_name);
|
||||
|
||||
}
|
||||
@ -289,7 +355,7 @@ final class NodaUncertaintyHelper {
|
||||
*/
|
||||
public static function guessPlaceCertainty(string $ort_name):bool {
|
||||
|
||||
$ort_name = \strtolower($ort_name);
|
||||
$ort_name = self::trim(\strtolower($ort_name));
|
||||
|
||||
// Attempt to guess uncertainty based on prefixes.
|
||||
foreach (NodaUncertaintyHelper::PLACE_UNCERTAINTY_PREFIXES as $prefix) {
|
||||
@ -305,6 +371,13 @@ final class NodaUncertaintyHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// If brackets are included in the name, try the same for everything up to the
|
||||
// first brackets.
|
||||
if (($bracketPos = strpos($ort_name, "(")) !== false) {
|
||||
$name = substr($ort_name, 0, $bracketPos);
|
||||
return self::guessPlaceCertainty($name);
|
||||
}
|
||||
|
||||
return true; // Certain / no uncertainty found
|
||||
|
||||
}
|
||||
@ -350,7 +423,7 @@ final class NodaUncertaintyHelper {
|
||||
*/
|
||||
public static function guessPersinstCertainty(string $name):bool {
|
||||
|
||||
$name = \trim(\strtolower($name));
|
||||
$name = self::trim(\strtolower($name));
|
||||
|
||||
// Attempt to guess uncertainty based on prefixes.
|
||||
foreach (NodaUncertaintyHelper::PERSINST_UNCERTAINTY_PREFIXES as $prefix) {
|
||||
|
@ -11,7 +11,59 @@ declare(strict_types = 1);
|
||||
*/
|
||||
final class NodaValidationHelper {
|
||||
|
||||
const ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS = 2;
|
||||
const ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS = 3;
|
||||
|
||||
/**
|
||||
* Validates an actor description for completeness. Of course, only an informed
|
||||
* guess based on the length and character composition of the description can be
|
||||
* made.
|
||||
*
|
||||
* @param string $description Input descrition.
|
||||
* @param string $name Names of the actor. Optional. Setting this enables
|
||||
* checks e.g. to prevent duplicating the actor name
|
||||
* as a description.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function validateTagDescription(string $description, string $name = ""):void {
|
||||
|
||||
// For nodac, empty tag descriptions are to be allowed. Thus, a
|
||||
// dedicated check for fully empty ones with a dedicated exception
|
||||
// is needed.
|
||||
if (empty($description)) {
|
||||
throw new MDInvalidEmptyInputException("No tag name is provided");
|
||||
}
|
||||
|
||||
// Throw error on descriptions that are too short
|
||||
if (\mb_strlen($description) < 10) {
|
||||
throw new MDgenericInvalidInputsException("Tag description is too short");
|
||||
}
|
||||
|
||||
// Validate tag description based on character composition.
|
||||
|
||||
// Ensure more than 3 distinct characters are used.
|
||||
$chars = \str_split($description);
|
||||
|
||||
$uniqueChars = array_unique($chars);
|
||||
if (count($uniqueChars) <= self::ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS) {
|
||||
throw new MDgenericInvalidInputsException("There need to be more than " . self::ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS . " distinct characters.");
|
||||
}
|
||||
|
||||
// Ensure more than the actor name is used.
|
||||
$clearedChars = [' ' => ' ', ',' => ' ', ';' => ' ', '.' => ' '];
|
||||
|
||||
$uniqueNames = array_unique(array_diff(explode(' ', strtr($name, $clearedChars)), ['']));
|
||||
sort($uniqueNames);
|
||||
|
||||
$descCleared = strtr($description, $clearedChars);
|
||||
$descWords = array_unique(array_diff(explode(' ', $descCleared), ['']));
|
||||
sort($descWords);
|
||||
|
||||
if ($uniqueNames === $descWords) {
|
||||
throw new MDgenericInvalidInputsException("The tag name was simply repeated in the description. This is not enough.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an actor description for completeness. Of course, only an informed
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
||||
<?PHP
|
||||
/**
|
||||
* This file contains an exception class to be thrown if a user attempts to load
|
||||
* data from a Wikidata item specifically established for a disambiguation page.
|
||||
*
|
||||
* @file
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Exception class to be thrown if a user attempts to load
|
||||
* data from a Wikidata item specifically established for a disambiguation page.
|
||||
*/
|
||||
final class NodaWikidataFetcherDisambiguationIsDisallowedException extends MDgenericInvalidInputsException {
|
||||
/**
|
||||
* Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function errorMessage() {
|
||||
//error message
|
||||
return 'Attempted to load a disambiguation page. Please select the specific item you want to fetch to enrich the given entry: ' . $this->getMessage();
|
||||
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ declare(strict_types = 1);
|
||||
*/
|
||||
final class NodaPersinstFulltextSyncManticore {
|
||||
|
||||
const FULL_SYNC_COMMIT_AFTER = 30000;
|
||||
private const FULL_SYNC_COMMIT_AFTER = 30000;
|
||||
|
||||
/**
|
||||
* Returns all names and descriptions in the different languages of a actor.
|
||||
@ -188,6 +188,10 @@ final class NodaPersinstFulltextSyncManticore {
|
||||
|
||||
$mysqli_manticore->commit();
|
||||
|
||||
if (PHP_SAPI === 'cli' && $mysqli_noda->ping() === false) {
|
||||
$mysqli_noda->reconnect();
|
||||
}
|
||||
|
||||
// Sync translations
|
||||
|
||||
$result = $mysqli_noda->do_read_query("SELECT `persinst`.`persinst_id`, `trans_language`,
|
||||
|
@ -11,7 +11,7 @@ declare(strict_types = 1);
|
||||
*/
|
||||
final class NodaTagFulltextSyncManticore {
|
||||
|
||||
const FULL_SYNC_COMMIT_AFTER = 30000;
|
||||
private const FULL_SYNC_COMMIT_AFTER = 30000;
|
||||
|
||||
/**
|
||||
* Returns all names and descriptions in the different languages of a tag.
|
||||
@ -139,6 +139,10 @@ final class NodaTagFulltextSyncManticore {
|
||||
/**
|
||||
* Synchronizes base entries.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Connection to MySQL DB.
|
||||
* @param MDMysqli $mysqli_manticore Connection to Manticore DB.
|
||||
* @param string $databasename Name of the main noda database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function runFullSyncForBaseEntries(MDMysqli $mysqli_noda, MDMysqli $mysqli_manticore, string $databasename):void {
|
||||
@ -189,6 +193,10 @@ final class NodaTagFulltextSyncManticore {
|
||||
/**
|
||||
* Synchronizes translated entries.
|
||||
*
|
||||
* @param MDMysqli $mysqli_noda Connection to MySQL DB.
|
||||
* @param MDMysqli $mysqli_manticore Connection to Manticore DB.
|
||||
* @param string $databasename Name of the main noda database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function runFullSyncForTranslatedEntries(MDMysqli $mysqli_noda, MDMysqli $mysqli_manticore, string $databasename):void {
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
enum NodaTimeAutotranslaterLocales {
|
||||
case ar;
|
||||
case crh;
|
||||
case de;
|
||||
case en;
|
||||
case es;
|
||||
@ -40,6 +41,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($lang) {
|
||||
'ar' => static::ar,
|
||||
'crh' => static::crh,
|
||||
'de' => static::de,
|
||||
'en' => static::en,
|
||||
'es' => static::es,
|
||||
@ -73,6 +75,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => 'ar_SY.utf8',
|
||||
self::crh => 'uk_UA.utf8',
|
||||
self::de => 'de_DE.utf8',
|
||||
self::en => 'en_US.utf8',
|
||||
self::es => 'es_ES.utf8',
|
||||
@ -108,6 +111,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => 'ar-SY',
|
||||
self::crh => 'uk-UA',
|
||||
self::de => 'de-DE',
|
||||
self::en => 'en-US',
|
||||
self::es => 'es-ES',
|
||||
@ -143,6 +147,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '%s',
|
||||
self::crh => '%s',
|
||||
self::de => '%s n. Chr.',
|
||||
self::en => '%s CE',
|
||||
self::es => '%s d.C.',
|
||||
@ -176,6 +181,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '-%s',
|
||||
self::crh => '%s рік до нашої ери',
|
||||
self::de => '%s v. Chr.',
|
||||
self::en => '%s BC',
|
||||
self::es => '%s a.C.',
|
||||
@ -211,6 +217,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '%s',
|
||||
self::crh => '%s',
|
||||
self::de => '%s',
|
||||
self::en => '%s',
|
||||
self::es => '%s',
|
||||
@ -244,6 +251,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '%s-%s',
|
||||
self::crh => '%s-%s',
|
||||
self::de => '%s-%s',
|
||||
self::en => '%s-%s',
|
||||
self::es => '%s-%s',
|
||||
@ -279,6 +287,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '%s-',
|
||||
self::crh => 'з %s року',
|
||||
self::de => 'Seit %s',
|
||||
self::en => 'Since %s',
|
||||
self::es => 'Desde %s',
|
||||
@ -315,6 +324,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '%s-',
|
||||
self::crh => 'після %s року',
|
||||
self::de => 'Nach %s',
|
||||
self::en => 'After %s',
|
||||
self::es => 'Despues de %s',
|
||||
@ -350,6 +360,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '-%s',
|
||||
self::crh => 'до %s року',
|
||||
self::de => 'Bis %s',
|
||||
self::en => 'Until %s',
|
||||
self::es => 'Hasta %s',
|
||||
@ -384,6 +395,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => 'القرن ال %s',
|
||||
self::crh => '%s століття',
|
||||
self::de => '%s. Jahrhundert',
|
||||
self::en => '%s. century',
|
||||
self::es => 'Siglo %s',
|
||||
@ -418,6 +430,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => 'القرن ال %s-%s',
|
||||
self::crh => '%s-%s століття',
|
||||
self::de => '%s.-%s. Jahrhundert',
|
||||
self::en => '%s.-%s. century',
|
||||
self::es => 'Siglo %s-%s',
|
||||
@ -452,6 +465,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '%s-%s',
|
||||
self::crh => '%s-ті роки',
|
||||
self::de => '%ser Jahre',
|
||||
self::en => '%ss',
|
||||
self::es => '%s-%s',
|
||||
@ -486,6 +500,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '%s-%s',
|
||||
self::crh => '%s-%s-ті роки',
|
||||
self::de => '%s-%ser Jahre',
|
||||
self::en => '%s-%ss',
|
||||
self::es => '%s-%s',
|
||||
@ -521,6 +536,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
|
||||
return match($this) {
|
||||
self::ar => '-%s',
|
||||
self::crh => 'до %s року',
|
||||
self::de => 'Vor %s',
|
||||
self::en => 'Before %s',
|
||||
self::es => 'Antes de %s',
|
||||
@ -558,6 +574,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
# self::be => '%d.%B.%Y',
|
||||
# self::bg => '%Y-%B-%d',
|
||||
# self::ca => '%d/%m/%Y',
|
||||
self::crh => '%d.%m.%Y',
|
||||
# self::cs => '%d.%B.%Y',
|
||||
# self::da => '%d-%m-%Y',
|
||||
self::de => '%d.%m.%Y',
|
||||
@ -618,6 +635,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
# self::be => '%d.%B.%Y',
|
||||
# self::bg => '%Y-%B-%d',
|
||||
# self::ca => '%d/%m/%Y',
|
||||
self::crh => 'dd.MM.Y',
|
||||
# self::cs => '%d.%B.%Y',
|
||||
# self::da => '%d-%m-%Y',
|
||||
self::de => 'dd.MM.Y',
|
||||
@ -679,6 +697,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
# self::bg => '%Y-%B',
|
||||
# self::ca => '%m/%Y',
|
||||
# self::cs => '%B.%Y',
|
||||
self::crh => '%m %Y',
|
||||
# self::da => '%m-%Y',
|
||||
self::de => '%B %Y',
|
||||
# self::el => '%B %Y',
|
||||
@ -735,6 +754,7 @@ enum NodaTimeAutotranslaterLocales {
|
||||
# self::bg => 'Y-MMMM',
|
||||
# self::ca => 'MM/Y',
|
||||
# self::cs => 'MMMM.Y',
|
||||
self::crh => 'MMMM Y',
|
||||
# self::da => 'MM-Y',
|
||||
self::de => 'MMMM Y',
|
||||
# self::el => 'MMMM Y',
|
||||
|
1
static/cardinal_directions.json
Normal file
1
static/cardinal_directions.json
Normal file
@ -0,0 +1 @@
|
||||
["\u0634\u0645\u0627\u0644","\u062c\u0646\u0648\u0628","\u063a\u0631\u0628","\u0634\u0631\u0642","\u0441\u0435\u0432\u0435\u0440","\u042e\u0433","\u0417\u0430\u043f\u0430\u0434","\u0418\u0437\u0442\u043e\u043a","\u0989\u09a4\u09cd\u09a4\u09b0","\u09a6\u0995\u09cd\u09b7\u09bf\u09a3","\u09aa\u09b6\u09cd\u099a\u09bf\u09ae","\u09aa\u09c2\u09b0\u09cd\u09ac","sever","jih","z\u00e1pad","v\u00fdchod","nord","syd","vest","\u00f8st","Norden","S\u00fcden","Westen","Osten","\u03b2\u03bf\u03c1\u03c1\u03ac\u03c2","\u03bd\u03cc\u03c4\u03bf\u03c2","\u03b4\u03cd\u03c3\u03b7","\u03b1\u03bd\u03b1\u03c4\u03bf\u03bb\u03ae","north","south","west","east","norte","sur","oeste","este","\u0628\u0627\u062e\u062a\u0631","\u062e\u0627\u0648\u0631","pohjoinen","etel\u00e4","l\u00e4nsi","it\u00e4","sud","ouest","est","Arewa","Kudu","Yamma","Gabas","\u05e6\u05e4\u05d5\u05df","\u05d3\u05e8\u05d5\u05dd","\u05de\u05e2\u05e8\u05d1","\u05de\u05d6\u05e8\u05d7","\u0909\u0924\u094d\u0924\u0930","\u0926\u0915\u094d\u0937\u093f\u0923","\u092a\u0936\u094d\u091a\u093f\u092e","\u092a\u0942\u0930\u094d\u0935","\u00e9szak","d\u00e9l","nyugat","kelet","utara","selatan","barat","Timur","ovest","\u5317","\u5357","\u897f","\u6771","\u10e9\u10e0\u10d3\u10d8\u10da\u10dd\u10d4\u10d7\u10d8","\u10e1\u10d0\u10db\u10ee\u10e0\u10d4\u10d7\u10d8","\u10d3\u10d0\u10e1\u10d0\u10d5\u10da\u10d4\u10d7\u10d8","\u10d0\u10e6\u10db\u10dd\u10e1\u10d0\u10d5\u10da\u10d4\u10d7\u10d8","\ubd81\ucabd","\ub0a8\ucabd","\uc11c\ucabd","\ub3d9\ucabd","noord","zuid","oost","p\u00f3\u0142noc","po\u0142udnie","zach\u00f3d","wsch\u00f3d","sul","leste","Sud","Vest","Est","\u044e\u0433","\u0437\u0430\u043f\u0430\u0434","\u0432\u043e\u0441\u0442\u043e\u043a","norr","s\u00f6der","v\u00e4ster","\u00f6ster","kaskazini","Kusini","Magharibi","Mashariki","\u0bb5\u0b9f\u0b95\u0bcd\u0b95\u0bc1","\u0ba4\u0bc6\u0bb1\u0bcd\u0b95\u0bc1","\u0bae\u0bc7\u0bb1\u0bcd\u0b95\u0bc1","\u0b95\u0bbf\u0bb4\u0b95\u0bcd\u0b95\u0bc1","\u0e17\u0e34\u0e28\u0e40\u0e2b\u0e19\u0e37\u0e2d","\u0e17\u0e34\u0e28\u0e43\u0e15\u0e49","\u0e17\u0e34\u0e28\u0e15\u0e30\u0e27\u0e31\u0e19\u0e15\u0e01","\u0e17\u0e34\u0e28\u0e15\u0e30\u0e27\u0e31\u0e19\u0e2d\u0e2d\u0e01","Hilaga","Timog","kanluran","kuzey","g\u00fcney","bat\u0131","do\u011fu","\u043f\u0456\u0432\u043d\u0456\u0447","\u043f\u0456\u0432\u0434\u0435\u043d\u044c","\u0437\u0430\u0445\u0456\u0434","\u0441\u0445\u0456\u0434","\u0645\u063a\u0631\u0628-\u0633\u0645\u062a","\u0645\u0634\u0631\u0642","h\u01b0\u1edbng b\u1eafc","h\u01b0\u1edbng nam","h\u01b0\u1edbng t\u00e2y","h\u01b0\u1edbng \u0111\u00f4ng","Nord","Ost","West","S\u00fcd"]
|
1
static/countries.ar.json
Normal file
1
static/countries.ar.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.bg.json
Normal file
1
static/countries.bg.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.bn.json
Normal file
1
static/countries.bn.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.cs.json
Normal file
1
static/countries.cs.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.da.json
Normal file
1
static/countries.da.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.de.json
Normal file
1
static/countries.de.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.el.json
Normal file
1
static/countries.el.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.en.json
Normal file
1
static/countries.en.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.es.json
Normal file
1
static/countries.es.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.fa.json
Normal file
1
static/countries.fa.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.fi.json
Normal file
1
static/countries.fi.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.fr.json
Normal file
1
static/countries.fr.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.ha.json
Normal file
1
static/countries.ha.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.he.json
Normal file
1
static/countries.he.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.hi.json
Normal file
1
static/countries.hi.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.hu.json
Normal file
1
static/countries.hu.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.id.json
Normal file
1
static/countries.id.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.it.json
Normal file
1
static/countries.it.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.ja.json
Normal file
1
static/countries.ja.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.ka.json
Normal file
1
static/countries.ka.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.ko.json
Normal file
1
static/countries.ko.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.nl.json
Normal file
1
static/countries.nl.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.pl.json
Normal file
1
static/countries.pl.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.pt.json
Normal file
1
static/countries.pt.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.ro.json
Normal file
1
static/countries.ro.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.ru.json
Normal file
1
static/countries.ru.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.sv.json
Normal file
1
static/countries.sv.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.sw.json
Normal file
1
static/countries.sw.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.ta.json
Normal file
1
static/countries.ta.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.th.json
Normal file
1
static/countries.th.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.tl.json
Normal file
1
static/countries.tl.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.tr.json
Normal file
1
static/countries.tr.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.uk.json
Normal file
1
static/countries.uk.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.ur.json
Normal file
1
static/countries.ur.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.vi.json
Normal file
1
static/countries.vi.json
Normal file
File diff suppressed because one or more lines are too long
1
static/countries.zh.json
Normal file
1
static/countries.zh.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ar.json
Normal file
1
static/first_lvl_administrative_units.ar.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.bg.json
Normal file
1
static/first_lvl_administrative_units.bg.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.bn.json
Normal file
1
static/first_lvl_administrative_units.bn.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.cs.json
Normal file
1
static/first_lvl_administrative_units.cs.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.da.json
Normal file
1
static/first_lvl_administrative_units.da.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.de.json
Normal file
1
static/first_lvl_administrative_units.de.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.el.json
Normal file
1
static/first_lvl_administrative_units.el.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.en.json
Normal file
1
static/first_lvl_administrative_units.en.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.es.json
Normal file
1
static/first_lvl_administrative_units.es.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.fa.json
Normal file
1
static/first_lvl_administrative_units.fa.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.fi.json
Normal file
1
static/first_lvl_administrative_units.fi.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.fr.json
Normal file
1
static/first_lvl_administrative_units.fr.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ha.json
Normal file
1
static/first_lvl_administrative_units.ha.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.he.json
Normal file
1
static/first_lvl_administrative_units.he.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.hi.json
Normal file
1
static/first_lvl_administrative_units.hi.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.hu.json
Normal file
1
static/first_lvl_administrative_units.hu.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.id.json
Normal file
1
static/first_lvl_administrative_units.id.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.it.json
Normal file
1
static/first_lvl_administrative_units.it.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ja.json
Normal file
1
static/first_lvl_administrative_units.ja.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ka.json
Normal file
1
static/first_lvl_administrative_units.ka.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ko.json
Normal file
1
static/first_lvl_administrative_units.ko.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.nl.json
Normal file
1
static/first_lvl_administrative_units.nl.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.pl.json
Normal file
1
static/first_lvl_administrative_units.pl.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.pt.json
Normal file
1
static/first_lvl_administrative_units.pt.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ro.json
Normal file
1
static/first_lvl_administrative_units.ro.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ru.json
Normal file
1
static/first_lvl_administrative_units.ru.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.sv.json
Normal file
1
static/first_lvl_administrative_units.sv.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.sw.json
Normal file
1
static/first_lvl_administrative_units.sw.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ta.json
Normal file
1
static/first_lvl_administrative_units.ta.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.th.json
Normal file
1
static/first_lvl_administrative_units.th.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.tl.json
Normal file
1
static/first_lvl_administrative_units.tl.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.tr.json
Normal file
1
static/first_lvl_administrative_units.tr.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.uk.json
Normal file
1
static/first_lvl_administrative_units.uk.json
Normal file
File diff suppressed because one or more lines are too long
1
static/first_lvl_administrative_units.ur.json
Normal file
1
static/first_lvl_administrative_units.ur.json
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user