From 80b24cd753cdb8f5b690e8cc06cfbce408d85e8f Mon Sep 17 00:00:00 2001 From: Peter Papp Date: Mon, 22 Mar 2021 08:19:50 +0100 Subject: [PATCH] - added CzechRegisterSearchService - oasis RouteServiceProvider.php - oasis admincontroller --- .../Controllers/Oasis/AdminController.php | 30 +++ app/Providers/RouteServiceProvider.php | 10 + app/Services/CzechRegisterSearchService.php | 255 ++++++++++++++++++ routes/oasis.php | 8 + tests/Feature/Oasis/OasisAdminTest.php | 49 ++++ 5 files changed, 352 insertions(+) create mode 100644 app/Http/Controllers/Oasis/AdminController.php create mode 100644 app/Services/CzechRegisterSearchService.php create mode 100644 routes/oasis.php create mode 100644 tests/Feature/Oasis/OasisAdminTest.php diff --git a/app/Http/Controllers/Oasis/AdminController.php b/app/Http/Controllers/Oasis/AdminController.php new file mode 100644 index 00000000..4fd1b042 --- /dev/null +++ b/app/Http/Controllers/Oasis/AdminController.php @@ -0,0 +1,30 @@ +findByIco( + request()->get('ico') + ); + + if (empty($result)) { + return response('Not Found', 404); + } + + return response($result[0], 200); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 62414734..ed7bfe97 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -40,6 +40,8 @@ class RouteServiceProvider extends ServiceProvider */ public function map() { + $this->mapOasisRoutes(); + $this->mapApiRoutes(); $this->mapShareRoutes(); @@ -100,6 +102,14 @@ class RouteServiceProvider extends ServiceProvider ->group(base_path('routes/api.php')); } + protected function mapOasisRoutes() + { + Route::prefix('api/oasis') + ->middleware('api') + ->namespace($this->namespace) + ->group(base_path('routes/oasis.php')); + } + protected function mapShareRoutes() { Route::prefix('api') diff --git a/app/Services/CzechRegisterSearchService.php b/app/Services/CzechRegisterSearchService.php new file mode 100644 index 00000000..976d7a70 --- /dev/null +++ b/app/Services/CzechRegisterSearchService.php @@ -0,0 +1,255 @@ +findByNazev('auto'); + * $out = $connector->findByIco('44315945'); + * echo ''.print_r($out, 1).''; + */ + +namespace App\Services; + +class CzechRegisterSearchService +{ + /** + * @var string Target server URL base + */ + const URL_SERVER = 'https://or.justice.cz/ias/ui/rejstrik-$firma'; + + /** + * @var int Max. dlzka nazvu pre autocomplete options + */ + public $labelMaxChars = 30; + + /** + * Vyhlada zoznam subjektov podla presneho ICO, ciastkove ICO nie je povolene + * @param string $ico 8-miestne cislo, e.g. 29243831 or 64612023 + */ + public function findByIco($ico) + { + $response = []; + $ico = preg_replace('/[^\d]/', '', $ico); + + if (preg_match('/^\d{8}$/', $ico)) { + $url = self::URL_SERVER . '?ico=' . $ico; + $response = file_get_contents($url); + $response = self::extractSubjects($response); + } + + return $response; + } + + /** + * Vyhlada zoznam subjektov podla ciastkoveho nazvu + * @param string $nazev , e.g. "pojisteni" + */ + public function findByNazev($nazev) + { + $response = []; + + if ($nazev) { + $nazev = trim($nazev); + $url = self::URL_SERVER . '?nazev=' . urlencode($nazev); + $response = file_get_contents($url); + $response = self::extractSubjects($response); + } + + return $response; + } + + /** + * Vyhlada zoznam subjektov podla presneho ICO, ciastkove ICO nie je povolene + * @param string $ico 8-miestne cislo, e.g. 12345678 + */ + public function getDetailByICO($ico) + { + $response = []; + $ico = preg_replace('/[^\d]/', '', $ico); + + if (preg_match('/^\d{8}$/', $ico)) { + $url = self::URL_SERVER . '?ico=' . $ico; + $response = file_get_contents($url); + if ($response) { + $response = self::extractSubjects($response); + if (!empty($response[0])) { + $response = $response[0]; + } + } + } + + return $response; + } + + + /** + * Return matched formatted for autocomplete dropdown list + * @param string $term Searched matching string + * @param int how many items to return, 1 - 50 (justice vrati max. 50 zaznamov) + */ + public function findForAutocomplete($term, $size = 15) + { + $out = []; + $size = intval($size); + + if ($term && $size > 0 && mb_strlen($term, 'utf-8') >= 3) { + // Justice vrati vysledok pre min. 3 znaky + if (preg_match('/^\d{8}$/', $term)) { + $subjects = $this->findByIco($term); + } else { + $subjects = $this->findByNazev($term); + } + } + + if (!empty($subjects) && is_array($subjects)) { + + $subjects = array_slice($subjects, 0, $size); // return first $size matches + + foreach ($subjects as &$subject) { + $subject['shortname'] = $subject['name']; + // cut off too long names + if (mb_strlen($subject['shortname'], 'utf-8') > $this->labelMaxChars) { + $subject['shortname'] = mb_substr($subject['shortname'], 0, $this->labelMaxChars - 3, 'utf-8') . ' ..'; + } + } + + foreach ($subjects as $subject) { + if (!empty($subject['ico'])) { + $out[] = [ + 'value' => $subject['ico'], + 'label' => "{$subject['shortname']} (IČO: {$subject['ico']})", + ]; + } + } + } + + return $out; + } + + /** + * Vrati zoznam najdenych subjektov s udajmi + * @param string $html HTML response zo servera justice.cz + * @return [name, ico, city, addr_streetnr, addr_city, addr_zip, ..] + */ + protected static function extractSubjects($html) + { + // ensure valid XHTML markup + if (!extension_loaded('tidy')) { + throw new \Exception('Missing extension [tidy].'); + } + + $tidy = new \tidy(); + $html = $tidy->repairString($html, array( + 'output-xhtml' => true, + 'show-body-only' => true, + ), 'utf8'); + + // purify whitespaces - vkladaju \n alebo + $html = strtr($html, [ + ' ' => ' ', + ]); + $html = preg_replace('/\s+/', ' ', $html); + + // load XHTML into DOM document + $xml = new \DOMDocument('1.0', 'utf-8'); + $xml->loadXML($html); + $xpath = new \DOMXPath($xml); + $rows = $xpath->query('//table[@class="result-details"]/tbody'); + + $out = []; + + if ($rows->length) { + + foreach ($rows as $row) { + + // Nazev + $nodeList = $xpath->query("./tr[1]/td[1]", $row); + if (!$nodeList->length) { + continue; // nazev je povinny + } + $name = $nodeList->item(0)->nodeValue; + $name = preg_replace('/\s+/', ' ', $name); // viacnasobne inside spaces + + // ICO + $nodeList = $xpath->query("./tr[1]/td[2]", $row); + $ico = $nodeList->length ? $nodeList->item(0)->nodeValue : ''; + + // adresa - neda sa spolahnut na poradie prvkov :-( + $city = ''; + $nodeList = $xpath->query("./tr[3]/td[1]", $row); + if ($nodeList->length) { + + $addr = trim($nodeList->item(0)->nodeValue); + + if (preg_match('/,\s*(\d{3} ?\d{2})\s+(.+)$/', $addr, $match)) { + // Příborská 597, Místek, 738 01 Frýdek-Místek - nazov obce za PSC, prva je ulice a cislo + $city = $addr_city = $match[2]; + list($addr_streetnr) = explode(',', $addr); + $addr_zip = $match[1]; + } elseif (preg_match('/,\s*PSČ\s+(\d{3} ?\d{2})$/', $addr, $match)) { + // Řevnice, ČSLA 118, okres Praha-západ, PSČ 25230 - PSC na konci, obec je prva, ulice a cislo druha + list($city, $addr_streetnr) = explode(',', $addr); + $addr_city = $city; + $addr_zip = $match[1]; + } elseif (!preg_match('/\d{3} ?\d{2}/', $addr, $match)) { + // Ústí nad Labem, Masarykova 74 - bez PSC - obec, ulice a cislo + $addr_streetnr = $addr_zip = ''; + if (false !== strpos($addr, ',')) { + list($city, $addr_streetnr) = explode(',', $addr); + } else { + list($city) = explode(',', $addr); + } + $addr_city = $city; + } + + // "Praha 10 - Dolní Měcholupy" -> Praha 10, pozn: Frydek-Mistek nema medzeru okolo pomlcky + // whoops, avsak ani Ostrana-Hontice a dalsie .. :-( Pre city potrebujeme kratky nazov do 10-15 pismen + list($city) = explode('-', $city); + // Praha 5 -> Praha + $city = preg_replace('/\d/', '', $city); + // viacnasobne spaces + $city = preg_replace('/\s+/', ' ', $city); + } + + $out[] = [ + 'name' => self::trimQuotes($name), + 'ico' => preg_replace('/[^\d]/', '', $ico), + 'city' => self::trimQuotes($city), + // pre polia s adresou konzistentne so smartform naseptavacem + 'addr_city' => self::trimQuotes($addr_city), + 'addr_zip' => preg_replace('/[^\d]/', '', $addr_zip), + 'addr_streetnr' => self::trimQuotes($addr_streetnr), + // len pre kontrolu - plna povodna adresa + 'addr_full' => self::trimQuotes($addr), + ]; + } + } + + return $out; + } + + /** + * Vyhodi quotes z textu, aby neposkodilo HTML atributy + * @param string $s + */ + protected static function trimQuotes($s) + { + return trim(strtr($s, ['"' => '', "'" => ''])); + } + +} \ No newline at end of file diff --git a/routes/oasis.php b/routes/oasis.php new file mode 100644 index 00000000..ce10a8dc --- /dev/null +++ b/routes/oasis.php @@ -0,0 +1,8 @@ + 'admin'], function () { + + Route::get('/company-details', [AdminController::class, 'get_company_details']); +}); diff --git a/tests/Feature/Oasis/OasisAdminTest.php b/tests/Feature/Oasis/OasisAdminTest.php new file mode 100644 index 00000000..ecf1dcb1 --- /dev/null +++ b/tests/Feature/Oasis/OasisAdminTest.php @@ -0,0 +1,49 @@ + "GDPR Cloud Solution, s.r.o.", + "ico" => "08995281", + "city" => "Praha", + "addr_city" => "Praha 5", + "addr_zip" => "15900", + "addr_streetnr" => "Zbraslavská 12/11", + "addr_full" => "Zbraslavská 12/11, Malá Chuchle, 159 00 Praha 5", + ]; + + Http::fake([ + 'https://or.justice.cz/ias/ui/rejstrik-08995281' => Http::response($response, 200), + ]); + + $this->getJson('/api/oasis/admin/company-details?ico=08995281') + ->assertStatus(200) + ->assertExactJson($response); + } + + /** + * @test + */ + public function it_try_to_get_register_by_ico_with_not_found_result() + { + Http::fake([ + 'https://or.justice.cz/ias/ui/rejstrik-0899528199' => Http::response([], 200), + ]); + + $this->getJson('/api/oasis/admin/company-details?ico=0899528199') + ->assertNotFound(); + } +}