├── .before_script.sh ├── .bumpversion.cfg ├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── composer.json ├── example ├── data.php └── example.php ├── lib └── ApiClient │ ├── ApiClient.php │ ├── Composer │ └── ScriptHandler.php │ ├── Enum │ └── PhotoLabelEnum.php │ ├── Exception │ ├── InvalidValueException.php │ ├── JSendException.php │ ├── JSendFailException.php │ ├── JWTTokenException.php │ ├── MissingRequirementsException.php │ ├── NotAuthenticatedException.php │ └── ServerErrorException.php │ ├── PhotoSyncResult.php │ └── Service │ ├── ClientFactoryService.php │ ├── Image │ ├── ImageException.php │ ├── ImageGD.php │ ├── ImageImagick.php │ └── ImageInterface.php │ └── PhotoResizeService.php ├── phpunit.xml └── tests ├── ApiClientTest.php ├── PhotoResizeServiceGDTest.php ├── PhotoResizeServiceImagickTest.php ├── PhotoResizeServiceParalellDownloadTest.php ├── PhotoResizeServiceTestAbstract.php ├── bootstrap.php └── mock ├── ClientFactoryMockService.php ├── photos ├── 1.jpg ├── groundplan450.jpg ├── notgroundplan450.jpg └── toosmall.png └── responses ├── checkApiStatus ├── deleteAdSuccess ├── deletePhotoSuccess ├── getAdIdsSuccess ├── getPhotosSuccess ├── loginFail ├── loginSuccess ├── putAdFail ├── putAdSuccess ├── putPhotoFail └── putPhotoSuccess /.before_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -e "\e[4mSetting up PHP Extensions for $TRAVIS_PHP_VERSION\e[0m" 4 | 5 | if [[ "$TRAVIS_PHP_VERSION" =~ "7." ]]; then 6 | echo "Skipping" 7 | exit 0 8 | fi 9 | 10 | printf "\n" | pecl install imagick-3.3.0 11 | echo "extension=imagick.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 12 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 3.2.2 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:lib/ApiClient/ApiClient.php] 7 | parse = CLIENT_VERSION\s*\=\s*['"](?P\d+)\.(?P\d+)\.(?P\d+)['"] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | dist: trusty 4 | 5 | php: 6 | - '5.5' 7 | - '5.6' 8 | - '7.0' 9 | - '7.1' 10 | 11 | install: 12 | - composer install 13 | 14 | before_script: 15 | - ./.before_script.sh 16 | 17 | script: 18 | - vendor/bin/phpunit --verbose 19 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | Az ingatlancom/api-client csomag minden változását ebben a leírásban kell rögzíteni. 3 | 4 | Az ingatlancom/api-client csomag a [Szemantikus verziózás](http://semver.org/) irányelveit követi. 5 | 6 | A Changelog a [Keep a Changelog](http://keepachangelog.com) formátumában íródik. 7 | 8 | ## [3.2.2] - 2021-04-29 9 | 10 | ### Változott 11 | - PhotoResizeService osztályban a maximális kép méret 1920 x 1080 legyen 12 | 13 | ## [3.2.1] - 2017-12-07 14 | 15 | ### Javítva 16 | - a függőségeknek csak az alsó verzióit kötjük meg 17 | 18 | ## [3.2.0] - 2017-05-17 19 | 20 | ### Bekerült 21 | - PhotoLabelEnum osztály, amely tartalmazza a fotóknál beküldhető labelId értékeket 22 | - InvalidValueException kivétel (amelyet akkor dobunk, ha fotófeltöltésnél valamelyik paraméter validációja nem sikerül) 23 | - labelId és title validáció fotófeltöltéskor 24 | 25 | ### Javítva 26 | - a fotófeltöltés során hiányzó paraméterek intelligensebb kezelése 27 | 28 | ## [3.1.0] - 2017-05-10 29 | 30 | ### Bekerült 31 | - ApiClient::checkApiStatus(), amivel az automata betöltés rendszerének állapotát lehet lekérdezni. Visszatérési érték: true / false. (True esetén a rendszereink működnek, false esetén valamilyen probléma van szerver vagy kliens oldalon.) 32 | 33 | ### Javítva 34 | - Képsorrendezés probléma az ApiClient::syncPhotos() hívásakor 35 | 36 | ## [3.0.0] - 2017-05-05 37 | 38 | ### Változott 39 | - A PhotoSync osztály össze lett vonva az ApiClient osztállyal. (Ha valaki külön használta a PhotoSync osztályt, akkor mostantól a syncPhotos() metódusát az ApiClient osztályon kell hívnia.) 40 | - Az ApiClient::syncPhotos() hívás mostantól PhotoSyncResult objektumot ad vissza. 41 | 42 | ## Bekerült 43 | - PhotoSyncResult objektum, amelyet a ApiClient::syncPhotos() metódus ad vissza. Ezen változatlanul meghívhatók az eddig használt getErrors(), getPutPhotoErrors() stb. hibalekérdező metódusok. 44 | - A PhotoSyncResult osztályon új meghívható metódusok, amelyek egy saját id-val indexelt tömbben visszaadják kizárólag a hibaüzeneteket: getFetchPhotoErrorMessages(), getDeletePhotoErrorMessages(), getPutPhotoErrorMessages(), getErrorMessages() 45 | 46 | ## [2.2.2] - 2017-04-27 47 | 48 | ### Javítva 49 | - Szerveridők csúszásából fakadó "lejárt vagy hibás token" hibaüzenet javítása 50 | - null Response kezelése PhotoSync osztályban 51 | 52 | ## [2.2.1] - 2017-04-26 53 | 54 | ### Javítva 55 | - 800x600-nál nagyobb képek GD-vel történő hibás átméretezésének javítása 56 | 57 | ## [2.2.0] - 2017-04-13 58 | 59 | ### Bekerült 60 | - verzió küldése headerben 61 | 62 | ### Javítva 63 | - képek letöltésének javítása 64 | 65 | ## [2.1.0] - 2017-04-06 66 | 67 | ### Bekerült 68 | - a JWT tokent a lejárati idejéig cacheeljük 69 | - token lekérése mindig a cache-ből, illetve ha ott már lejárt, akkor automatikusan új token kérése a szervertől autentikációval 70 | 71 | ### Javítva 72 | - Stash\Pool cache használatának javítása 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 ingatlan.com zrt. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![ingatlan.com](https://ingatlan.com/images/logo.svg) 2 | # Automata Betöltés (API) [![Build Status](https://travis-ci.org/ingatlancom/api-client.svg?branch=master)](https://travis-ci.org/ingatlancom/api-client) [![Latest Stable Version](https://poser.pugx.org/ingatlancom/apiclient/v/stable.svg)](https://packagist.org/packages/ingatlancom/apiclient) [![Total Downloads](https://poser.pugx.org/ingatlancom/apiclient/downloads.svg)](https://packagist.org/packages/ingatlancom/apiclient) [![License](https://poser.pugx.org/ingatlancom/apiclient/license.svg)](https://packagist.org/packages/ingatlancom/apiclient) 3 | 4 | A rendszer célja az, hogy az [ingatlan.com](https://ingatlan.com/) "Automata betöltés" előfizetéssel rendelkező ingatlanközvetítők a saját nyilvántartó rendszerükből interfészen keresztül tölthessék fel és kezelhessék a hirdetéseiket. 5 | 6 | **FONTOS:** 7 | 8 | **Az "Automata betöltés" terméket jelenleg nem értékesítjük, mielőtt bármilyen fejlesztésbe kezdene ezzel kapcsolatban, kérjük keresse fel ingatlan.com-os ügyfélszolgálati kapcsolattartóját.** 9 | 10 | * Automata Betöltés (API) használatával beküldött hibás adatok (amelyek a gépi validáción nem akadnak fent) megjelenéséért a Szolgáltató nem vállal felelősséget. Azok az ügyfelek, akik Automata Betöltéssel töltenek fel hirdetéseket tudomásul veszik, hogy a referensek által az ingatlan.com admin felületen felvitt módosításaik a következő betöltéssel felülírásra kerülnek abban az esetben ha azt a saját rendszerükben nem módosították. 11 | 12 | * Amennyiben az ingatlan.com-on szeretne hirdetést feltölteni / törölni / módosítani, ezt nem az ingatlan.com admin rendszerben kell megtenni, hanem a saját rendszerben kell frissíteni és jelezni a helyi informatikusnak / rendszergazdának, hogy indítsa el a betöltést. 13 | 14 | * Az Automata Betöltés nem kezeli a liciteket, kiemeléseket, referenseket. Ezt továbbra is az adminisztrációs felületen végezheti el a referens. 15 | 16 | * Automata Betöltéssel nem lehet "új építésű" hirdetéseket feladni. Amennyiben a “conditionType=3”-al érkezik hirdetés azt “conditionType=0” állapotba mentjük, és jelezzük, hogy lépjen be a felületre, és ott manuálisan állítsa be, hogy ezek közül melyikeket szeretné új építésű állapotba tenni (csak annyit fog tudni átállítani, amennyi új ép. kiegészítőt vett). Ha egyszer beállította egy hirdetésnél az állapotot (és még van aktív kerete), akkor annál a hirdetésnél újrabetöltéskor nem írjuk felül az állapotot. 17 | 18 | ## Technikai információk 19 | 20 | API URL: [https://api.ingatlan.com](https://api.ingatlan.com/) 21 | 22 | Az API szabványos [REST](https://hu.wikipedia.org/wiki/REST) konvenciókat követ és az adatokat [JSON](http://json.org/) formátumban kell beküldeni. A JSON válaszok a [JSend](https://labs.omniti.com/labs/jsend) ajánlás szerinti formátumot követik. 23 | 24 | Az azonosításra [JSON Web Token](http://jwt.io/) technológiát alkalmaz. Az API login token érvényessége 1 óra. 25 | 26 | Az API végpontjai megtekinthetőek ezek a címen: [https://api.ingatlan.com/v1/doc](https://api.ingatlan.com/v1/doc) 27 | 28 | Az API nem rendelkezik külön CREATE és UPDATE funkciókkal; PUT kérés esetén, ha az adott azonosítóval már létezik erőforrás, akkor frissíti; ha nem, létrehozza azt. 29 | 30 | Kérjük, iratkozzon fel a [github repository](https://github.com/login?return_to=%2Fingatlancom%2Fapi-client) frissítéseire, és új verzió kikerülésekor frissítse a klienst! 31 | 32 | ## Fejlesztés 33 | 34 | Az API ügyfél oldali üzembe állítása során technikai segítséget nyújtunk. 35 | 36 | ### Ez tartalmazza: 37 | 38 | * teszthozzáférés biztosítása 39 | 40 | * éles üzembe állításkor előkészületek 41 | 42 | * az ingatlan.com rendszerében már jelen lévő hirdetések megfeleltetése az API-n keresztül beküldöttekkel ( kizárólag tökéletesen megegyező adatok esetekben párosíthatók). 43 | 44 | ### Nem tartalmazza: 45 | 46 | * egyedi igények ingatlan.com oldali fejlesztése 47 | 48 | * beküldendő paraméterek módosítása 49 | 50 | * tesztirodák létrehozása az éles rendszerekben 51 | 52 | * ügyfél oldali hibák debugolása/javítása 53 | 54 | * éles rendszerbe hibásan küldött hirdetések/adatok visszaállítása 55 | 56 | Korszerű PHP kódoláshoz kiegészítő információk: 57 | 58 | * [http://www.php-fig.org/psr/](http://www.php-fig.org/psr/) 59 | 60 | * [https://phptherightway.com/](https://phptherightway.com/) 61 | 62 | 63 | ## Adattípusok 64 | 65 | Az API két adattípussal dolgozik, ezek a hirdetés és a fotó. 66 | 67 | ### Hirdetés 68 | 69 | Minden hirdetésnek rendelkeznie kell egy (partnerenként egyedi) azonosítóval (ownId), csak így tölthető be az ingatlan.com rendszerébe. Ez egy maximum 15 karakter hosszúságú string, amely lehetőség szerint megfelel az alábbi reguláris kifejezésnek: /^[0-9A-Za-z-_]{1,15}$/ 70 | 71 | A hirdetés paramétereinek listája és magyarázata itt tekinthető meg: [https://api.ingatlan.com/v1/doc/fields](https://api.ingatlan.com/v1/doc/fields) 72 | 73 | A hirdetés paramétereinél értelemszerűen a kötelező mezők kötelezően kitöltendőek a felsorolt értékkészletből. Javasoljuk, hogy az opcionális mezők is kitöltve érkezzenek.** A hibás vagy hiányos paraméterekkel érkező hirdetések nem kerülnek feltöltésre. 74 | ** 75 | Ha valamely paraméter hiányzik vagy hibás, az API visszajelzi a hibát a [JSend](https://labs.omniti.com/labs/jsend) ajánlás szerinti formátumban. A lehetséges hibaüzenetek listája is a fenti dokumentációban látható. 76 | 77 | **A megjegyzés (description) mező** kötelező, legalább 5 és legfeljebb 10000 karakter hosszú lehet. 78 | 79 | **A fűtés (heatingType) mező** egy maximum 2 elemű tömb amibe az értékkészlet szerinti fűtéseket lehet megadni. Default értéke 0 és ha 2-nél több elem érkezik benne, akkor az első kettőt menti el. A fűtés kizárólag lakás, ház, nyaraló és intézmény típusoknál adható meg. 80 | 81 | **Az alábbi mezők nem módosíthatók:** 82 | 83 | * listingType 84 | 85 | * propertyType 86 | 87 | * city 88 | 89 | * projectId 90 | 91 | Amennyiben ezekbe nem megfelelő adat került, a hirdetés törlés után új sajatId-vel adható fel újra. 92 | 93 | #### Intelligens API 94 | 95 | Ha olyan mezőkben is kap adatot, amelyek az adott ingatlantípusnál nem szerepelhetnek, az esetek többségében kijavítja ezeket, 0/NULL értékekre. Ezek megjelenítésére kitérünk a [mintakódban](https://github.com/ingatlancom/api-client/blob/master/example/example.php). 96 | 97 | A hirdetések egyik legfontosabb jellemzője az elhelyezkedés, ezért a feltöltött adatokat az API minden esetben leellenőrzi. A pontatlanul megadott címeket a rendszer megpróbálja valós elhelyezkedési adatokra javítani, de az esetleges hibás megjelenésért a Szolgáltató nem vállal felelősséget. 98 | 99 | A városok és városrészek listája megtekinthető [az alábbi tömörített állományban](https://api.ingatlan.com/doc_references/doc_references.zip). 100 | 101 | Az Automata Betöltés használatakor, a fentiekben jelzett tömörített állományokban található elhelyezkedési adatokat fogadjuk el. Amennyiben pl. "nem megfelelő városrész" hibát tapasztalunk, a fenti állományban lévőre kell azt az ügyfél oldalán javítani. Amennyiben az ingatlan.com térképadatbázisában hibát talál, kérjük jelezze felénk. 102 | 103 | ### Fotó 104 | 105 | Minden fotónak rendelkeznie kell egy (hirdetésenként egyedi) azonosítóval, csak így tölthető be az ingatlan.com rendszerébe. Ez egy maximum 32 karakter hosszú string, amely lehetőség szerint megfelel az alábbi reguláris kifejezésnek: /^[0-9A-Za-z-_]{1,32}$/ 106 | 107 | A fotó tömb kulcsai kép feltöltéskor: 108 | 109 | * ownId: csak válaszban, a kép sajátId-ja 110 | 111 | * title: a kép felirata, string(100) 112 | 113 | * labelId: a képfelirat azonosítója, opcionális. A [lehetséges képfeliratok ebben az állományban találhatók](https://github.com/ingatlancom/api-client/blob/master/lib/ApiClient/Enum/PhotoLabelEnum.php). 114 | 115 | * order: sorrend érték, integer 116 | 117 | * imageData: csak kérésben, a kép fájl tartalma, base64-es kódolásban 118 | 119 | Amennyiben az ügyfél által megadott kép nem elérhető hibaüzenetet adunk vissza. Az ingatlan.com rendszerébe 30 képet lehet feltölteni, ez vonatkozik az Automata Betöltésre is. 120 | 121 | Képek lekérdezésekor a következő kulcsok szerepelnek még a tömbben: 122 | 123 | * md5Hash: a feltöltött, átméretezett kép MD5 hash értéke, segítségével ellenőrizni tudjuk, hogy a kliensnek a későbbi feltöltésekkor szükséges-e újra küldenie a képet 124 | 125 | * hasForbiddenWatermarkOrLogo: a kép megfelel-e az ÁSZF-ben leírtaknak (nem tartalmazhat logót, vízjelet illetve feliratot (az alaprajzokon szereplő jelölések kivételével)). Amennyiben itt true értéket talál, az azt jelenti, hogy 2017. november 15. után az adott képet nem jelenítjük meg a keresők számára. Ilyen esetben kérjük, töltse fel a kép eredeti, nem manipulált változatát. Amennyiben az eredeti képnél is true érték szerepel, kérjük lépjen be az [adminisztrációs felületünkön](https://admin.ingatlan.com/belepes) és a hirdetés szerkesztése oldalon a szabálytalannak jelölt fotón szereplő zászló ikonnal jelentse be a hibás működést. Ezután moderátoraink fogják ellenőrizni és elbírálni a képet. 126 | 127 | ## Referensek kezelése 128 | 129 | A referensek adatait az office kezelőfelületén kell rögzíteni, az "ingatlanreferensek kezelése" menüpont alatt. Az agentId-vel küldött hirdetéseket akkor tudja a rendszer referenshez rendelni, ha az adott agentId a megfelelő referens adatlapján "saját id"-ként fel van tüntetve. A nem megfelelő id-vel küldött hirdetések az iroda adminisztrátorához kerülnek. Kitöltött agentId hiányában nem lehet referenshez betölteni hirdetést. Ennek kitöltése a referens és az iroda felelőssége. 130 | 131 | Amennyiben Automata Betöltést használ, kérjük, ne alkalmazza a referensek vagy hirdetések mozgatását irodák között. Ilyen esetben lépjen kapcsolatba ingatlan.com-os kapcsolattartójával, és kérjen technikai segítséget. 132 | 133 | # ingatlan.com API kliens 134 | 135 | [https://github.com/ingatlancom/api-client](https://github.com/ingatlancom/api-client) 136 | 137 | A kliens egy olyan PHP [composer](https://getcomposer.org/) csomag, amely az API hívások bemutatásán kívül több hasznos funkció implementációját tartalmazza: 138 | 139 | * hirdetések szinkronizálása 140 | 141 | * optimális fotószinkronizálás, átméretezéssel 142 | 143 | ## Telepítés 144 | 145 | 1. [Telepítsük a composert.](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx) (További infók: [https://getcomposer.org/doc/](https://getcomposer.org/doc/)) 146 | 147 | 2. Hozzunk létre egy composer.json fájlt az alábbi tartalommal: 148 | 149 | ```json 150 | { 151 | "require": { 152 | "ingatlancom/apiclient": "~3.0" 153 | } 154 | } 155 | ``` 156 | 157 | 3. A következő paranccsal indítsuk el a telepítést: 158 | 159 | ```bash 160 | php composer.phar install 161 | ``` 162 | 163 | 4. A letöltött csomagok a vendor mappába kerülnek. 164 | 165 | ## Használat 166 | 167 | A kliens használatához felhasználónév és jelszó szükséges, amelyet az ingatlan.com kapcsolattartójától kap meg. 168 | 169 | 1. Az API kliensbe az autentikáció [JWT](https://jwt.io) tokenekkel történik. Sikeres azonosítás után a kliens egy tokent kap az API-tól. Ezt a tokent tároljuk le egy [Stash Pool](http://www.stashphp.com)-ba, hogy ne kelljen minden hívás előtt belépnünk. Pool példányosítása: 170 | 171 | ```php 172 | $driver = new Stash\Driver\FileSystem(['path' => '/tmp/ingatlancom/']); 173 | $pool = new Stash\Pool($driver); 174 | ``` 175 | (A "/tmp/ingatlancom" helyett adja meg azt a könyvtárat, ahol a program ideiglenes fájlokat tárolhat a szerveren.) 176 | 177 | 2. Példányosítsuk az API klienst: 178 | 179 | ```php 180 | $apiUrl = 'https://api.ingatlan.com'; 181 | $apiClient = new \IngatlanCom\ApiClient\ApiClient($apiUrl, $pool); 182 | ``` 183 | 184 | (Éles rendszerre történő betöltés esetén az $apiUrl értéke "https://api.ingatlan.com".) 185 | 186 | 3. Bejelentkezés: 187 | 188 | ```php 189 | $apiClient->login('username', 'password'); 190 | ``` 191 | 192 | Az alább következő műveletek csak a bejelentkezés meghívása után végezhetők el. 193 | 194 | ### Hirdetés feltöltése 195 | 196 | Az $ad tömbben adja meg a hirdetés paramétereit. (A beküldhető mezők pontos leírását az [alábbi linken](https://api.ingatlan.com/v1/doc/parameters) tekintheti meg.) 197 | 198 | ```php 199 | $ad = [ 200 | 'ownId' => 'x149395', 201 | 'listingType' => 1, 202 | 'propertyType' => 1, 203 | 'propertySubtype' => 2, 204 | 'priceHuf' => 17500000 205 | ... 206 | ]; 207 | $apiClient->putAd($ad); 208 | ``` 209 | 210 | ### Hirdetés lekérdezése 211 | 212 | A x149395 saját id-jú hirdetés lekérdezése: 213 | ```php 214 | $ad = $apiClient->getAd('x149395'); 215 | ``` 216 | Sikeres hívás esetén az $ad változó egy tömb lesz a hirdetés értékeivel. 217 | 218 | ### Hirdetés törlése 219 | 220 | A x149395 saját id-jú hirdetés törlése: 221 | ```php 222 | $apiClient->deleteAd('x149395'); 223 | ``` 224 | (Fizikailag a hirdetés nem törlődik, csak a státusza fog "törlöm, de megtartom" státuszra váltani.) 225 | 226 | ### Iroda összes hirdetés azonosítójának lekérdezése 227 | 228 | ```php 229 | $ids = $apiClient->getAdIds(); 230 | ``` 231 | Sikeres hívás esetén az $ids egy tömb lesz a feltöltött hirdetések id-ival. 232 | 233 | ### Hirdetések szinkronizálása 234 | 235 | A syncAds() függvény letörli az ingatlan.com szerveréről az Önök rendszerében már nem szereplő hirdetéseket. 236 | 237 | Az $ads tömbben sorolja fel a rendszerükben létező hirdetések saját id-jait: 238 | ```php 239 | $ads = [ 240 | 'hirdetes1', 241 | 'hirdetes2' 242 | ... 243 | ]; 244 | $ids = $apiClient->syncAds($ads); 245 | ``` 246 | 247 | ### Képek szinkronizálása 248 | 249 | Az x149395 saját id-jú hirdetéshez a fotók szinkronizálása: 250 | ```php 251 | use IngatlanCom\ApiClient\Enum\PhotoLabelEnum; 252 | 253 | $photos = [ 254 | [ 255 | 'ownId' => 'kep1', 256 | 'order' => 1, 257 | 'title' => 'Képfelirat 1', 258 | 'location' => 'http://lorempixel.com/800/600/city/1/', 259 | 'labelId' => PhotoLabelEnum::KORNYEK 260 | ], 261 | [ 262 | 'ownId' => 'kep2', 263 | 'order' => 2, 264 | 'title' => 'Képfelirat 2', 265 | 'location' => 'http://lorempixel.com/800/600/city/2/', 266 | ] 267 | ]; 268 | $ids = $apiClient->syncPhotos( 269 | 'x149395', 270 | $photos, 271 | $forceImageDataUpdate, 272 | $uploadedPhotos, 273 | $paralellDownload 274 | ); 275 | ``` 276 | 277 | ### $photos 278 | A $photos tömbben a feltöltendő fotók [adatai](#fotó) legyenek. A syncPhotos() függvény használatakor a fotó adatai tömbben lehetséges a "location" kulcs használata. Itt meg kell adni a képfájl elérési útját, amely lehet az adott számítógépen elérhető fájl, vagy akár URL is. A kliens a location mező alapján beolvassa a képfájlt, elvégzi rajta az átméretezést (ha szükséges) és feltöltéskor a megfelelő adatként ("imageData") fel fogja küldeni a képfájl tartalmát. 279 | 280 | ### $forceImageUpdate 281 | Alapvető esetben a syncPhotos() metódus a képek md5 hash értéke alapján dönti el, hogy változott-e az adott kép, és szükséges-e újra feltölteni az ingatlan.com szervereire. A syncPhotos() metódus 3. paraméterében kikapcsolhatjuk ezt az ellenőrzést, hogy a kliens minden esetben töltse fel a hirdetés fotóit. 282 | 283 | ### $uploadedPhotos 284 | Az $uploadedPhotos paraméter tömbben a szerveren található fotókat kell megadni. Ha ez utóbbit nem tudjuk, célszerű ezt a paramétert null-ra állítani és a kliens automatikusan lekérdezi a képeket a szerverről. 285 | 286 | ### $paralellDownload 287 | A syncPhotos() metódus 5. paraméterében azt lehet beállítani, hogy - amennyiben a partner fotói http protokollal kerülnek letöltésre - ezt a kliens egyenként, vagy párhuzamosan végezze. Alapvető esetben a funkció ki van kapcsolva, de ha a partner szervereinek ez nem okoz gondot, nyugodtan bekapcsolható. 288 | 289 | A képek átméretezése kliens oldalon történik, ezért [Imagick](http://php.net/manual/en/book.imagick.php) vagy [GD](http://php.net/manual/en/book.image.php) php bővítmény szükséges a használathoz. 290 | 291 | ### Kép feltöltése 292 | 293 | A $photoData tömbben adja meg a fotó adatait. A fotó tömb értékeitről [itt](#fotó) talál információt. 294 | 295 | (A putPhoto() használatakor a képfájl adatainál nem használható a "location" kulcs a syncPhotos() függvénnynel ellentétben. Itt kizárólag az imageData kulcs alatt küldhető a képfájl base64 kódolt tartalma.) 296 | ```php 297 | $photoData = [ 298 | 'ownId' => 'kep3', 299 | 'order' => 3, 300 | 'title' => 'Képfelirat 3', 301 | 'labelId' => null, 302 | 'imageData' => file_get_contents('kepem.jpg') 303 | ]; 304 | $ids = $apiClient->putPhoto('x149395', $photoData); 305 | ``` 306 | 307 | ### Több kép feltöltése 308 | 309 | A $photos tömbbe adjon meg több fotót a [Kép feltöltésénél](#kép-feltöltése) látható elemekből. 310 | ```php 311 | $ids = $apiClient->putPhotosMulti('x149395', $photos); 312 | ``` 313 | 314 | ### Kép törlése 315 | 316 | A x149395 saját id-jú hirdetésnél a kep123 saját id-jú kép törlése. 317 | ```php 318 | $ids = $apiClient->deletePhoto('x149395', 'kep123'); 319 | ``` 320 | 321 | ### Több kép törlése 322 | 323 | A $photoIds tömbben a törlendő képek saját id-jait kell megadni. 324 | 325 | Képek törlése a x149395 saját id-jú hirdetésnél: 326 | ```php 327 | $photoIds = ['kep1', 'kep2']; 328 | $ids = $apiClient->deletePhotosMulti('x149395', $photoIds); 329 | ``` 330 | 331 | ### Hirdetés képeinek lekérdezése 332 | 333 | A x149395 saját id-jú hirdetés képeinek lekérdezése: 334 | ```php 335 | $photos = $apiClient->getPhotos('x149395'); 336 | ``` 337 | Sikeres hívás esetén a $photos egy tömb lesz a hirdetés képeinek adataival. 338 | 339 | ### Hirdetés képeinek sorrendezése 340 | 341 | A képek sorrendezése a x149395 saját id-jú hirdetésnél: 342 | ```php 343 | $photoOrder = ['kep1', 'kep2', 'kep3']; 344 | $ids = $apiClient->putPhotoOrder('x149395', $photoOrder); 345 | ``` 346 | A $photoOrder tömbben a képek saját id-i a kívánt sorrendben legyenek. 347 | 348 | ## API státusz ellenőrzése 349 | 350 | A checkApiStatus() függvény hívásával lehetőség van az API állapotát lekérdezni. A függvény true-t ad vissza, ha minden alrendszerünk működik és false-t, ha a betöltés valamilyen hiba miatt nem üzemel. False-t adunk vissza akkor is, ha kliens oldalon van probléma, tehát például nincs internetkapcsolat és a kliens nem éri el a szervereinket. 351 | ```php 352 | $isOk = $apiClient->checkApiStatus(); 353 | ``` 354 | 355 | ## Példakód 356 | 357 | Egy példa az [example/example.php](https://github.com/ingatlancom/api-client/blob/master/example/example.php) fájlban tekinthető meg. A példakód nem kötelezően használandó minta, csak javaslat. 358 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | ## Migráció 3-as verzióra 2 | 3 | A PhotoSync osztály megszüntetésre került. Funkcióját az ApiClient osztály vette át. Ha korábban példányosított PhotoSyncet és hívta rajta a syncPhotos() metódust, akkor mostantól az ApiClient osztállyal tegye meg ugyanezt. 4 | 5 | Az ApiClient::syncPhotos() metódus a 3-as verziótól kezdve PhotoSyncResult objektumot ad vissza, amelyen ugyanúgy használhatók a lekérdezések, mint a PhotoSync objektumon korábban. 6 | 7 | ## Migráció 2-es verzióra 8 | 9 | **FONTOS:** A függőségek frissítésével az api-client szükséges PHP verziója 5.3-ről **5.5**-re emelkedett. 10 | 11 | Ezenkívül a két alábbi változást le kell követniük a partnereknek: 12 | 13 | 1. A Stash driver példányosítása a következő módra változott: 14 | ```php 15 | $driver = new Stash\Driver\FileSystem(['path' => '/tmp/ingatlancom/']); 16 | ``` 17 | 18 | 1. Küldési hibát elkapni a következő módon lehet: 19 | ```php 20 | } catch (\GuzzleHttp\Exception\RequestException $e) { 21 | print_r($e->getResponse()->getBody()->getContents()); 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ingatlancom/apiclient", 3 | "description": "ingatlan.com rest api client", 4 | "type": "library", 5 | "license": "MIT", 6 | "require": { 7 | "php": ">=5.5", 8 | "guzzlehttp/guzzle": "^6.2", 9 | "nannehuiges/jsend": "^2.1", 10 | "tedivm/stash": "^0.14" 11 | }, 12 | "require-dev": { 13 | "phpunit/phpunit": "^4.8", 14 | "phpmd/phpmd": "^2.6", 15 | "squizlabs/php_codesniffer": "^2.9" 16 | }, 17 | "scripts": { 18 | "ingatlancom-scripts": [ 19 | "IngatlanCom\\ApiClient\\Composer\\ScriptHandler::checkRequirements" 20 | ], 21 | "pre-install-cmd": [ 22 | "@ingatlancom-scripts" 23 | ], 24 | "pre-update-cmd": [ 25 | "@ingatlancom-scripts" 26 | ] 27 | }, 28 | "config": { 29 | "sort-packages": true 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "IngatlanCom\\ApiClient\\": "lib/ApiClient/" 34 | } 35 | }, 36 | "suggest": { 37 | "ext-imagick": "Képműveletek elvégzéséhez szükséges képfeldolgozó bővítmény", 38 | "ext-gd": "Képműveletek elvégzéséhez szükséges képfeldolgozó bővítmény" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/data.php: -------------------------------------------------------------------------------- 1 | 'x149395', 6 | 'listingType' => 1, 7 | 'propertyType' => 1, 8 | 'propertySubtype' => 2, 9 | 'priceHuf' => 17500000, 10 | 'priceEur' => 46755, 11 | 'priceType' => 1, 12 | 'areaSize' => 27, 13 | 'lotSize' => 0, 14 | 'city' => 'Budapest', 15 | 'street' => 'Dévai utca', 16 | 'district' => 13, 17 | 'zone' => 'Lőportárdűlő', 18 | 'isStreetHidden' => 1, 19 | 'isStreetNumberHidden' => 1, 20 | 'houseNumber' => '', 21 | 'roomCount' => 1, 22 | 'smallRoomCount' => 0, 23 | 'buildingFloorCount' => 8, 24 | 'floor' => 11, 25 | 'heatingType' => 4, 26 | 'comfortLevel' => 3, 27 | 'description' => 'Ingatlan leírása', 28 | 'descriptionEn' => '', 29 | 'descriptionDe' => '', 30 | 'elevatorType' => 1, 31 | 'parkingType' => 2, 32 | 'atticType' => 5, 33 | 'viewType' => 1, 34 | 'parkingPrice' => 0, 35 | 'conditionType' => 5, 36 | 'realEstateAgencyOwnId' => 'x149395', 37 | 'balconySize' => 3, 38 | 'gardenSize' => 0, 39 | 'agenciesAccepted' => 0, 40 | 'energyPerformanceCert' => 5, 41 | 'innerHeight' => 1, 42 | 'accessibility' => 2, 43 | 'panelprogram' => 0, 44 | 'gardenAccess' => 2, 45 | 'orientation' => 5, 46 | 'bathroomWithoutToilet' => 0, 47 | 'airConditioner' => 0, 48 | 'isRentRight' => 0, 49 | 'agentId' => 12111 50 | ]; 51 | $testAd2 = [ 52 | 'ownId' => 'x96602', 53 | 'listingType' => 1, 54 | 'propertyType' => 1, 55 | 'propertySubtype' => 1, 56 | 'priceHuf' => 20900000, 57 | 'priceEur' => 69973, 58 | 'priceType' => 1, 59 | 'areaSize' => 99, 60 | 'lotSize' => 0, 61 | 'city' => 'Budapest', 62 | 'street' => 'Thököly út', 63 | 'district' => 7, 64 | 'zone' => 'Külső-Erzsébetváros', 65 | 'isStreetHidden' => 1, 66 | 'isStreetNumberHidden' => 1, 67 | 'houseNumber' => '', 68 | 'roomCount' => 1, 69 | 'smallRoomCount' => 3, 70 | 'buildingFloorCount' => 2, 71 | 'floor' => 4, 72 | 'heatingType' => 2, 73 | 'comfortLevel' => 3, 74 | 'description' => 'Ingatlan leírása', 75 | 'descriptionEn' => '', 76 | 'descriptionDe' => '', 77 | 'elevatorType' => 2, 78 | 'viewType' => 2, 79 | 'parkingPrice' => 0, 80 | 'conditionType' => 7, 81 | 'realEstateAgencyOwnId' => 'x96602', 82 | 'balconySize' => 0, 83 | 'gardenSize' => 0, 84 | 'agenciesAccepted' => 2, 85 | 'innerHeight' => 0, 86 | 'accessibility' => 2, 87 | 'gardenAccess' => 0, 88 | 'bathroomWithoutToilet' => 0, 89 | 'airConditioner' => 0, 90 | 'isRentRight' => 0, 91 | 'agentId' => 739 92 | ]; 93 | $testAds = [ 94 | 'x149395' => $testAd1, 95 | 'x96602' => $testAd2 96 | ]; 97 | 98 | $testPhotos = [ 99 | 'x149395' => [ 100 | [ 101 | 'ownId' => 'kep1', 102 | 'order' => 1, 103 | 'title' => 'Képfelirat', 104 | 'location' => 'http://lorempixel.com/800/600/city/1/', 105 | 'labelId' => PhotoLabelEnum::KORNYEK 106 | ], 107 | [ 108 | 'ownId' => 'kep2', 109 | 'order' => 2, 110 | 'title' => 'Képfelirat', 111 | 'location' => 'http://lorempixel.com/800/600/city/2/', 112 | ] 113 | ], 114 | 'x96602' => [] 115 | ]; 116 | -------------------------------------------------------------------------------- /example/example.php: -------------------------------------------------------------------------------- 1 | '/tmp/ingatlancom/']); 13 | $pool = new Stash\Pool($driver); 14 | 15 | /* 16 | * példányosítjuk a klienst 17 | */ 18 | $apiUrl = 'https://api.ingatlan.com'; 19 | $apiClient = new \IngatlanCom\ApiClient\ApiClient($apiUrl, $pool); 20 | 21 | /* 22 | * bejelentkezés - ha megvan a token a pool-ban, akkor nem hív be az API-n. 23 | */ 24 | try { 25 | $apiClient->login('username', 'password'); 26 | } catch (\Exception $e) { 27 | echo $e->getMessage() . "\n"; 28 | die(); 29 | } 30 | 31 | /* 32 | * törli az ingatlan.com rendszeréből azokat a hirdetéseket, amik az iroda hirdetései között már nem szerepelnek 33 | */ 34 | $adIds = array_keys($testAds); 35 | $apiClient->syncAds($adIds); 36 | 37 | /* 38 | * Összes hirdetés fetöltése/frissítése 39 | */ 40 | foreach ($testAds as $ownId => $ad) { 41 | try { 42 | /* 43 | * hirdetés feltöltése/frissítése 44 | */ 45 | $icomAd = $apiClient->putAd($ad); 46 | 47 | /* 48 | * összehasonlíthatjuk, mely értékeket javította ki az API 49 | */ 50 | $diff = array_diff_assoc($icomAd, $ad); 51 | echo "$ownId API-tól visszakapott értékek különbségei:\n"; 52 | foreach ($diff as $key => $val) { 53 | if (isset($ad[$key])) { 54 | echo str_pad($key, 20, ' ') . "$ad[$key]\t" . var_export($val, true) . "\n"; 55 | } 56 | } 57 | 58 | /* 59 | * hirdetés képeinek frissítése, hibák listázása 60 | */ 61 | $photoSync = $apiClient->syncPhotos($ownId, $testPhotos[$ownId], false, $icomAd['photos'], true); 62 | $errors = $photoSync->getErrors(); 63 | if (count($errors) > 0) { 64 | echo "Hibák történtek a képfeltöltés során:\n"; 65 | foreach ($errors as $photoOwnId => $error) { 66 | echo $photoOwnId . ' : ' . $error['errorMessage'] . "\n"; 67 | } 68 | } elseif (count($testPhotos[$ownId]) > 0) { 69 | echo "Fotók rendben feltöltve:\n"; 70 | var_dump($photoSync->getPhotos()); 71 | } 72 | } catch (\GuzzleHttp\Exception\RequestException $e) { 73 | print_r($e->getResponse()->getBody()->getContents()); 74 | } catch (\IngatlanCom\ApiClient\Exception\JSendException $e) { 75 | print_r($e->getJSendResponse()); 76 | } catch (\Exception $e) { 77 | echo $ownId . ' ' . $e->getMessage() . "\n"; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/ApiClient/ApiClient.php: -------------------------------------------------------------------------------- 1 | stashPool = $stashPool; 87 | } else { 88 | $this->stashPool = new Pool(new Ephemeral()); 89 | } 90 | $clientFactoryService = null != $clientFactoryService ? $clientFactoryService : new ClientFactoryService(); 91 | $this->client = $clientFactoryService->getClient($apiUrl); 92 | } 93 | 94 | /** 95 | * Bejelentkezés 96 | * 97 | * @param string $username 98 | * @param string $password 99 | * @throws NotAuthenticatedException 100 | * @throws JWTTokenException 101 | */ 102 | public function login($username, $password) 103 | { 104 | $this->username = $username; 105 | $this->password = $password; 106 | $this->getToken(); 107 | } 108 | 109 | /** 110 | * Authentikációs token lekérése a cache-ből, vagy a szervertől. 111 | * 112 | * @return string JWT token 113 | * @throws NotAuthenticatedException 114 | * @throws JWTTokenException 115 | */ 116 | private function getToken() 117 | { 118 | $item = $this->stashPool->getItem($this->getTokenCacheKey()); 119 | if (!$item->isMiss()) { 120 | return $item->get(); 121 | } 122 | 123 | $token = $this->callLogin(); 124 | 125 | // calculate token lifetime 126 | $ttl = $this->getTokenTTL($token); 127 | 128 | /** @var Item $item */ 129 | $item = $this->stashPool->getItem($this->getTokenCacheKey()); 130 | // cache token 131 | $item->set($token)->setTTL($ttl)->save(); 132 | 133 | return $token; 134 | } 135 | 136 | /** 137 | * Visszaadja a token kulcsát a poolban 138 | * 139 | * @return string 140 | */ 141 | private function getTokenCacheKey() 142 | { 143 | return $this->username . 'Token'; 144 | } 145 | 146 | /** 147 | * Kiszedi az auth token érvényességét 148 | * 149 | * @param string $token 150 | * @return mixed 151 | * @throws JWTTokenException 152 | */ 153 | private function getTokenTTL($token) 154 | { 155 | $tokenArr = explode('.', $token); 156 | if (count($tokenArr) > 1) { 157 | $data = base64_decode($tokenArr[1]); 158 | $tokenData = json_decode($data); 159 | if ($tokenData) { 160 | return $tokenData->exp - $tokenData->iat - $tokenData->clock_skew - 60; 161 | } 162 | } 163 | throw new JWTTokenException(sprintf("Invalid token: %s", $token)); 164 | } 165 | 166 | /** 167 | * API bejelentkezés meghívása 168 | * 169 | * @return string JWT token 170 | * @throws NotAuthenticatedException 171 | */ 172 | private function callLogin() 173 | { 174 | try { 175 | $result = $this->sendRequest('POST', '/auth/login', json_encode(array( 176 | 'username' => $this->username, 177 | 'password' => $this->password 178 | ))); 179 | } catch (JSendFailException $e) { 180 | throw new NotAuthenticatedException('Login failed', 0, $e); 181 | } 182 | 183 | return $result['token']; 184 | } 185 | 186 | /** 187 | * Hirdetés lekérése 188 | * 189 | * @param string $adOwnId hirdetés saját azonosítója 190 | * @return array hirdetés adatai 191 | */ 192 | public function getAd($adOwnId) 193 | { 194 | $result = $this->sendRequest('GET', '/ads/' . $adOwnId); 195 | 196 | return $result['ad']; 197 | } 198 | 199 | /** 200 | * Hirdetés feladása/módosítása 201 | * 202 | * @param array $ad hirdetésadatok 203 | * @return array a feladott hirdetés adatai 204 | * @throws JSendFailException 205 | */ 206 | public function putAd(array $ad) 207 | { 208 | $result = $this->sendRequest('PUT', '/ads/' . $ad['ownId'], json_encode($ad)); 209 | 210 | return $result['ad']; 211 | } 212 | 213 | /** 214 | * Hirdetés törlése 215 | * 216 | * @param string $adOwnId hirdetés saját azonosítója 217 | * @return array törölt hirdetés adatai 218 | */ 219 | public function deleteAd($adOwnId) 220 | { 221 | $result = $this->sendRequest('DELETE', '/ads/' . $adOwnId); 222 | 223 | return $result['ad']; 224 | } 225 | 226 | /** 227 | * Visszaadja az iroda összes hirdetésének azonosítóit (ingatlan.com-os és saját azonosítót) 228 | * 229 | * @return array hirdetés ID-k 230 | */ 231 | public function getAdIds() 232 | { 233 | $data = $this->sendRequest('GET', '/ads/ids'); 234 | 235 | return $data['ids']; 236 | } 237 | 238 | /** 239 | * Hirdetés fotóinak lekérése 240 | * 241 | * @param string $adOwnId hirdetés saját azonosítója 242 | * @return array fotók adatai 243 | */ 244 | public function getPhotos($adOwnId) 245 | { 246 | $photos = $this->sendRequest('GET', '/ads/' . $adOwnId . '/photos'); 247 | 248 | return $photos['photos']; 249 | } 250 | 251 | /** 252 | * Fotó feltöltés Request létrehozása 253 | * 254 | * @param string $adOwnId hirdetés saját azonosítója 255 | * @param array $photoData fotó adatok 256 | * @return RequestInterface 257 | * @throws InvalidValueException 258 | */ 259 | private function createPhotoPutRequest($adOwnId, array $photoData) 260 | { 261 | $photoOwnId = $photoData['ownId']; 262 | 263 | if (isset($photoData['labelId']) && !PhotoLabelEnum::validate($photoData['labelId'])) { 264 | throw new InvalidValueException(sprintf("A labelId értéke érvénytelen a %s fotónál: %s", $photoOwnId, $photoData['labelId'])); 265 | } 266 | if (isset($photoData['title']) && $photoData['title'] != '') { 267 | $encoding = mb_detect_encoding($photoData['title'], ['UTF-8', 'ISO-8859-2'], true); 268 | if ($encoding == 'ISO-8859-2') { 269 | $photoData['title'] = mb_convert_encoding($photoData['title'], 'UTF-8', 'ISO-8859-2'); 270 | } else if ($encoding === false) { 271 | throw new InvalidValueException(sprintf("A title értéke nem UTF-8 karakterkódolással van megadva a %s fotónál: %s", $photoOwnId, $photoData['title'])); 272 | } 273 | } 274 | 275 | 276 | unset($photoData['ownId']); 277 | unset($photoData['location']); 278 | if (isset($photoData['imageData'])) { 279 | $photoData['imageData'] = base64_encode($photoData['imageData']); 280 | } 281 | 282 | $request = $this->getRequest('PUT', '/ads/' . $adOwnId . '/photos/' . $photoOwnId, json_encode($photoData)); 283 | 284 | return $request; 285 | } 286 | 287 | /** 288 | * Fotó feltöltése/módosítása 289 | * 290 | * @param string $adOwnId hirdetés saját azonosítója 291 | * @param array $photoData fotó adatok 292 | * @return array feltöltött fotó adatok 293 | * @throws JSendFailException 294 | */ 295 | public function putPhoto($adOwnId, array $photoData) 296 | { 297 | $request = $this->createPhotoPutRequest($adOwnId, $photoData); 298 | try { 299 | $response = $this->client->send($request); 300 | } catch (BadResponseException $e) { 301 | $response = $e->getResponse(); 302 | } 303 | 304 | $response = $this->parseResponse($response); 305 | 306 | return $response['photo']; 307 | } 308 | 309 | /** 310 | * Hirdetés fotóinak feltöltése, több szálon 311 | * 312 | * @param string $adOwnId hirdetés saját azonosítója 313 | * @param array $photosByOwnId hirdetés képeinek adatai, saját azonosító szerint indexelve 314 | * @return array 315 | */ 316 | public function putPhotosMulti($adOwnId, array $photosByOwnId) 317 | { 318 | $requests = array(); 319 | foreach ($photosByOwnId as $photoOwnId => $photo) { 320 | $requests[$photoOwnId] = $this->createPhotoPutRequest($adOwnId, $photo); 321 | } 322 | 323 | return $this->sendMultiRequest($requests); 324 | } 325 | 326 | /** 327 | * Fotó törlése 328 | * 329 | * @param string $adOwnId hirdetés saját azonosítója 330 | * @param string $photoId fotó saját azonosítója 331 | * @return bool sikeres törlés 332 | */ 333 | public function deletePhoto($adOwnId, $photoId) 334 | { 335 | $this->sendRequest('DELETE', '/ads/' . $adOwnId . '/photos/' . $photoId); 336 | 337 | return true; 338 | } 339 | 340 | /** 341 | * Hirdetés fotóinak törlése, több szálon 342 | * 343 | * @param string $adOwnId hirdetés saját azonosítója 344 | * @param array $photosByOwnId hirdetés képeinek adatai, saját azonosító szerint indexelve 345 | * @return array 346 | */ 347 | public function deletePhotosMulti($adOwnId, array $photosByOwnId) 348 | { 349 | $requests = array(); 350 | foreach (array_keys($photosByOwnId) as $photoOwnIdToDelete) { 351 | $requests[$photoOwnIdToDelete] = $this->getRequest('DELETE', '/ads/' . $adOwnId . '/photos/' . $photoOwnIdToDelete); 352 | } 353 | 354 | return $this->sendMultiRequest($requests); 355 | } 356 | 357 | /** 358 | * Iroda ingatlan.com-on lévő hirdetéseit szinkronba hozza az iroda saját rendszerében lévő hirdetésekkel 359 | * (kitörli az ingatlan.com-ról azokat a hirdetéseket, amik már nincsenek meg az iroda saját rendszerében) 360 | * 361 | * @param array $adIds az iroda saját rendszerében lévő összes hirdetés ID-ja 362 | * @return array a törölt hirdetések ID-i 363 | * @throws \Exception 364 | */ 365 | public function syncAds(array $adIds) 366 | { 367 | try { 368 | $ids = $this->getAdIds(); 369 | } catch (\Exception $e) { 370 | throw new \Exception('A hirdetés ID-k lekérése nem sikerült', 0, $e); 371 | } 372 | 373 | $ownIds = array_reduce( 374 | $ids, 375 | function ($carry, $item) { 376 | if ($item['ownId'] && 0 == $item['statusId']) { 377 | $carry[] = $item['ownId']; 378 | } 379 | return $carry; 380 | }, 381 | array() 382 | ); 383 | 384 | $idsToDelete = array_diff($ownIds, $adIds); 385 | foreach ($idsToDelete as $id) { 386 | $this->deleteAd($id); 387 | } 388 | 389 | return $idsToDelete; 390 | } 391 | 392 | /** 393 | * Hirdetés képeinek sorrendezése 394 | * 395 | * @param string $adOwnId hirdetés saját azonosítója 396 | * @param array $photoOwnIds fotók saját azonosítója, kívánt sorrendben 397 | * @return array hirdetés fotói 398 | */ 399 | public function putPhotoOrder($adOwnId, $photoOwnIds) 400 | { 401 | $photos = $this->sendRequest('PUT', '/ads/' . $adOwnId . '/photoOrder', 402 | json_encode(array('order' => $photoOwnIds))); 403 | 404 | return $photos['photos']; 405 | } 406 | 407 | /** 408 | * Guzzle Request előállítása az API-nak megfelelő headerekkel 409 | * 410 | * @param string $method HTTP method 411 | * @param string $endpoint path 412 | * @param string|null $body content 413 | * @return RequestInterface 414 | * @throws NotAuthenticatedException 415 | * @throws JWTTokenException 416 | */ 417 | private function getRequest($method, $endpoint, $body = null) 418 | { 419 | $headers['Accept'] = 'application/json'; 420 | 421 | if ($body) { 422 | $headers['Content-type'] = 'application/json'; 423 | } 424 | 425 | if ('/auth/login' != $endpoint) { 426 | $headers['Authorization'] = 'Bearer ' . $this->getToken(); 427 | } 428 | 429 | return new Request($method, '/v' . self::APIVERSION . $endpoint, $headers, $body); 430 | } 431 | 432 | /** 433 | * Request legyártás, elküldés, válasz feldolgozás 434 | * 435 | * @param string $method HTTP method 436 | * @param string $endpoint path 437 | * @param string|null $body content 438 | * @return array 439 | * @throws InvalidJSendException if JSend does not conform to spec 440 | * @throws ServerErrorException 441 | * @throws JSendFailException 442 | */ 443 | private function sendRequest($method, $endpoint, $body = null) 444 | { 445 | $request = $this->getRequest($method, $endpoint, $body); 446 | 447 | try { 448 | $response = $this->client->send($request); 449 | } catch (BadResponseException $e) { 450 | $response = $e->getResponse(); 451 | } 452 | 453 | return $this->parseResponse($response); 454 | } 455 | 456 | /** 457 | * Request-ek párhuzamos elküldése 458 | * 459 | * @param RequestInterface[] $requests 460 | * @return array 461 | */ 462 | private function sendMultiRequest($requests) 463 | { 464 | $results = []; 465 | 466 | if (count($requests)) { 467 | $promises = []; 468 | foreach ($requests as $photoOwnId => $request) { 469 | $promises[$photoOwnId] = $this->client->sendAsync($request); 470 | } 471 | Promise\each_limit( 472 | $promises, 473 | self::NUMBER_OF_MAX_PARALLEL_REQUESTS, 474 | function ($value, $idx) use (&$results) { 475 | $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; 476 | }, 477 | function ($reason, $idx) use (&$results) { 478 | $results[$idx] = ['state' => PromiseInterface::REJECTED, 'value' => $reason]; 479 | } 480 | )->wait(); 481 | } 482 | 483 | return $results; 484 | } 485 | 486 | /** 487 | * API válasz feldolgozása 488 | * 489 | * @param Response $response 490 | * @return array 491 | * @throws InvalidJSendException 492 | * @throws ServerErrorException 493 | * @throws JSendFailException 494 | */ 495 | public function parseResponse(Response $response) 496 | { 497 | try { 498 | $jsendResponse = JSendResponse::decode((string)$response->getBody()); 499 | } catch (\UnexpectedValueException $exception) { 500 | $match = preg_match("/\\d+ .*<\\/title>/", (string)$response->getBody(), $message); 501 | $jsendResponse = JSendResponse::error( 502 | $match ? strip_tags($message[0]) : "server connection error", 503 | $match ? $response->getStatusCode() : 503 504 | ); 505 | } 506 | 507 | if ($jsendResponse->isFail()) { 508 | throw new JSendFailException('Call failed', 0, null, $jsendResponse); 509 | } 510 | 511 | if ($jsendResponse->isError()) { 512 | throw new ServerErrorException($jsendResponse->getErrorMessage(), 0, null, $jsendResponse); 513 | } 514 | 515 | return $jsendResponse->getData(); 516 | } 517 | 518 | /** 519 | * A teljes szinkronizálási folyamat 520 | * 521 | * @param string $adOwnId hirdetés saját azonosító 522 | * @param array $photos iroda rendszerében levő fotók adatai 523 | * @param bool $forceImageDataUpdate akkor is töltsük le a fotót az iroda rendszeréből, ha már fel van töltve adott azonosítóval 524 | * @param array|null $uploadedPhotos ingatlan.com rendszerében levő fotók adatai 525 | * @param bool $paralellDownload párhuzamos fotóletöltés az iroda szerveréről 526 | * @return PhotoSyncResult 527 | * @throws TransferException 528 | */ 529 | public function syncPhotos( 530 | $adOwnId, 531 | array $photos, 532 | $forceImageDataUpdate = false, 533 | array $uploadedPhotos = null, 534 | $paralellDownload = false 535 | ) { 536 | $this->photoPutQueue = []; 537 | $this->photoSortQueue = []; 538 | $errors = []; 539 | 540 | if (null === $uploadedPhotos) { 541 | $uploadedPhotos = $this->getPhotos($adOwnId); 542 | } 543 | 544 | $localPhotosByOwnId = $this->mapArrayByField($photos, 'ownId'); 545 | $uploadedPhotosByOwnId = $this->mapArrayByField($uploadedPhotos, 'ownId'); 546 | 547 | //delete 548 | $photosToDelete = array_diff_key($uploadedPhotosByOwnId, $localPhotosByOwnId); 549 | $deleteResults = $this->deletePhotosMulti($adOwnId, $photosToDelete); 550 | $errors['photoDelete'] = $this->parseMultiTransferErrors($deleteResults, $photosToDelete); 551 | 552 | //fetch image data, diff with uploaded 553 | $errors['photoFetch'] = $this->buildPhotoQueues($localPhotosByOwnId, $uploadedPhotosByOwnId, $forceImageDataUpdate, $paralellDownload); 554 | 555 | //put 556 | $putResults = $this->putPhotosMulti($adOwnId, $this->photoPutQueue); 557 | $errors['photoPut'] = $this->parseMultiTransferErrors($putResults, $this->photoPutQueue); 558 | 559 | //fix order 560 | $photos = $this->syncPhotosPutOrder($adOwnId, 561 | array_merge(array_diff_key($this->photoSortQueue, $errors['photoPut']), $errors['photoDelete'])); 562 | 563 | return new PhotoSyncResult($photos, $errors); 564 | } 565 | 566 | /** 567 | * Tömbből asszociatív tömböt készít valamely mező alapján 568 | * 569 | * @param array $array tömb 570 | * @param string $field mező 571 | * @return array 572 | */ 573 | private function mapArrayByField(array $array, $field) 574 | { 575 | $result = array(); 576 | foreach ($array as $el) { 577 | //TODO duplicate key-re exception 578 | $result[$el[$field]] = $el; 579 | } 580 | return $result; 581 | } 582 | 583 | /** 584 | * fotó feltöltési és rendezési sorok létrehozása 585 | * 586 | * @param array $localPhotosByOwnId A feltölteni kívánt fotók tömbje 587 | * @param array $uploadedPhotosByOwnId Az ingatlan.com szerverére feltöltött fotók tömbje 588 | * @param bool $forceImageDataUpdate akkor is töltsük le a fotót az iroda rendszeréből, ha már fel van töltve adott azonosítóval 589 | * @param bool $paralellDownload párhuzamos fotóletöltés az iroda szerveréről 590 | * @return array 591 | */ 592 | private function buildPhotoQueues($localPhotosByOwnId, $uploadedPhotosByOwnId, $forceImageDataUpdate, $paralellDownload) 593 | { 594 | $downloadQueue = array(); 595 | 596 | foreach ($localPhotosByOwnId as $ownId => $photoData) { 597 | //ha feltoltendo, mert nincs feltolve sajatid alapjan, vagy update szukseges 598 | if (!array_key_exists($ownId, $uploadedPhotosByOwnId) || $forceImageDataUpdate) { 599 | $downloadQueue[$ownId] = $photoData; 600 | } else { 601 | if ($this->arePhotosDifferent($uploadedPhotosByOwnId[$ownId], $photoData)) { 602 | $this->photoPutQueue[$ownId] = $photoData; 603 | } 604 | $this->photoSortQueue[$ownId] = $photoData; 605 | } 606 | } 607 | 608 | return $this->downloadPhotosToQueues($localPhotosByOwnId, $uploadedPhotosByOwnId, $downloadQueue, $paralellDownload); 609 | } 610 | 611 | /** 612 | * Fotók letöltése az iroda szerveréről, ellenőrzés, hogy szükséges-e a betöltés 613 | * az ingatlan.com rendszerébe 614 | * 615 | * @param array $localPhotosByOwnId A feltölteni kívánt fotók tömbje 616 | * @param array $uploadedPhotosByOwnId Az ingatlan.com szerverére feltöltött fotók tömbje 617 | * @param array $photosByOwnId hirdetés képeinek adatai, saját azonosító szerint indexelve 618 | * @param bool $paralellDownload párhuzamos fotóletöltés az iroda szerveréről 619 | * @return array 620 | */ 621 | private function downloadPhotosToQueues($localPhotosByOwnId, $uploadedPhotosByOwnId, array $photosByOwnId, $paralellDownload) 622 | { 623 | $photoResizeService = new PhotoResizeService(); 624 | $photoFetchErrors = []; 625 | $imageDatas = $photoResizeService->getResizedPhotosData($photosByOwnId, $paralellDownload); 626 | 627 | foreach ($imageDatas as $ownId => $imageData) { 628 | $photoData = $localPhotosByOwnId[$ownId]; 629 | if ($imageData instanceof \Exception) { 630 | $photoData['exception'] = $imageData; 631 | $photoData['errorMessage'] = $imageData->getMessage(); 632 | $photoFetchErrors[$ownId] = $photoData; 633 | } else { 634 | //md5 check, full upload 635 | $needToPutPhoto = $this->needToPutPhoto($uploadedPhotosByOwnId, $photoData, $imageData); 636 | if ($needToPutPhoto != self::PUT_NOTNEEDED) { 637 | if (self::PUT_WITH_IMAGE_DATA == $needToPutPhoto) { 638 | $photoData['imageData'] = $imageData; 639 | } 640 | $this->photoPutQueue[$ownId] = $photoData; 641 | } 642 | 643 | $this->photoSortQueue[$ownId] = $photoData; 644 | } 645 | } 646 | 647 | return $photoFetchErrors; 648 | } 649 | 650 | /** 651 | * Feltöltés szükségességének ellenőrzése 652 | * MD5 hash és képadatok alapján 653 | * 654 | * @param array $photoData fotó adatok 655 | * @param string $imageData fotó bináris formátumban 656 | * @return int feltöltés típusa 657 | */ 658 | private function needToPutPhoto($uploadedPhotosByOwnId, $photoData, $imageData) 659 | { 660 | $ownId = $photoData['ownId']; 661 | 662 | //ha nincs feltoltve, vagy md5 nem egyezik 663 | if (!array_key_exists($ownId, $uploadedPhotosByOwnId) 664 | || md5($imageData) != $uploadedPhotosByOwnId[$ownId]['md5Hash'] 665 | ) { 666 | return self::PUT_WITH_IMAGE_DATA; 667 | } elseif (array_key_exists($ownId, $uploadedPhotosByOwnId)) { 668 | if ($this->arePhotosDifferent($uploadedPhotosByOwnId[$ownId], $photoData)) { 669 | return self::PUT_WITHOUT_IMAGE_DATA; 670 | } 671 | } 672 | 673 | return self::PUT_NOTNEEDED; 674 | } 675 | 676 | /** 677 | * Fotó adatok különbségének vizsgálata 678 | * 679 | * @param array $photo1 fotó adatok 680 | * @param array $photo2 fotó adatok 681 | * @return bool 682 | */ 683 | private function arePhotosDifferent(array $photo1, array $photo2) 684 | { 685 | isset($photo1['labelId']) ?: $photo1['labelId'] = null; 686 | isset($photo2['labelId']) ?: $photo2['labelId'] = null; 687 | isset($photo1['title']) ?: $photo1['title'] = null; 688 | isset($photo2['title']) ?: $photo2['title'] = null; 689 | if ( 690 | $photo1['title'] != $photo2['title'] || 691 | $photo1['labelId'] != $photo2['labelId'] 692 | ) { 693 | return true; 694 | } 695 | 696 | return false; 697 | } 698 | 699 | /** 700 | * Megnézi, hogy a párhuzamos kérések között volt-e, ami sikertelen 701 | * 702 | * @param array $results A requestek eredményei 703 | * @param array $photosByOwnId 704 | * @return array 705 | */ 706 | private function parseMultiTransferErrors(array $results, array $photosByOwnId) 707 | { 708 | $errors = []; 709 | foreach ($results as $index => $result) { 710 | if ($result['state'] == PromiseInterface::REJECTED && $result['value'] instanceof \Exception) { 711 | /** @var \Exception $exception */ 712 | $exception = $result['value']; 713 | 714 | $errorPhoto = $photosByOwnId[$index]; 715 | $errorPhoto['exception'] = $exception; 716 | if ($exception instanceof RequestException) { 717 | try { 718 | if ($exception->getResponse()) { 719 | $error = $this->parseResponse($exception->getResponse()); 720 | } else { 721 | $error = $exception->getMessage(); 722 | } 723 | } catch (ServerErrorException $jse) { 724 | $error = 'Server error: ' . $jse->getJSendResponse()->getErrorMessage(); 725 | } catch (JSendFailException $jse) { 726 | $error = $jse->getJSendResponse()->getData(); 727 | $error = isset($error['message']) ? $error['message'] : $error; 728 | } catch (\UnexpectedValueException $uve) { 729 | $error = "JSON decode error"; 730 | } 731 | $errorPhoto['errorMessage'] = $error; 732 | } else { 733 | $errorPhoto['errorMessage'] = $exception->getMessage(); 734 | } 735 | $errors[$errorPhoto['ownId']] = $errorPhoto; 736 | } 737 | } 738 | return $errors; 739 | } 740 | 741 | /** 742 | * Fotók sorrdendezése 743 | * 744 | * @param string $adOwnId hirdetés saját azonosító 745 | * @param array $photosByOwnId hirdetés képeinek adatai, saját azonosító szerint indexelve 746 | * @return array hirdetés fotói 747 | */ 748 | private function syncPhotosPutOrder($adOwnId, array $photosByOwnId) 749 | { 750 | isset($a['order']) ?: $a['order'] = null; 751 | isset($b['order']) ?: $b['order'] = null; 752 | usort($photosByOwnId, function ($a, $b) { 753 | if ($a['order'] == $b['order']) { 754 | return 0; 755 | } 756 | 757 | return $a['order'] < $b['order'] ? -1 : 1; 758 | }); 759 | 760 | $order = array_map(function ($photo) { 761 | return $photo['ownId']; 762 | }, $photosByOwnId); 763 | 764 | return $this->putPhotoOrder($adOwnId, $order); 765 | } 766 | 767 | /** 768 | * @return bool 769 | */ 770 | public function checkApiStatus() 771 | { 772 | $headers = ['Accept' => 'application/json']; 773 | $request = new Request('GET', "/status/", $headers); 774 | 775 | try { 776 | $response = $this->client->send($request); 777 | $this->parseResponse($response); 778 | } catch (TransferException $e) { 779 | return false; 780 | } catch (ServerErrorException $e) { 781 | return false; 782 | } 783 | 784 | return true; 785 | } 786 | } 787 | -------------------------------------------------------------------------------- /lib/ApiClient/Composer/ScriptHandler.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace IngatlanCom\ApiClient\Composer; 3 | 4 | use IngatlanCom\ApiClient\Exception\MissingRequirementsException; 5 | 6 | class ScriptHandler 7 | { 8 | /** 9 | * Ellenőrzi, hogy az "imagick", vagy a "gd" bővítmény telepítve van e. 10 | * 11 | * @throws MissingRequirementsException 12 | */ 13 | public static function checkRequirements() 14 | { 15 | if (!extension_loaded('imagick') && !extension_loaded('gd')) { 16 | throw new MissingRequirementsException( 17 | 'Az Api kliens használatához kérjük telepítse az "imagick", vagy a "gd" PHP bővítmények valamelyikét!' 18 | ); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/ApiClient/Enum/PhotoLabelEnum.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace IngatlanCom\ApiClient\Enum; 3 | 4 | /** 5 | * A lehetséges fotó címkéket tartalmazó osztály 6 | */ 7 | class PhotoLabelEnum 8 | { 9 | /** alaprajz */ 10 | const ALAPRAJZ = 1; 11 | /** térkép */ 12 | const TERKEP = 2; 13 | /** erkély */ 14 | const ERKELY = 3; 15 | /** terasz */ 16 | const TERASZ = 4; 17 | /** tetőterasz */ 18 | const TETOTERASZ = 5; 19 | /** kilátás */ 20 | const KILATAS = 6; 21 | /** kert */ 22 | const KERT = 7; 23 | /** kívülről */ 24 | const KIVULROL = 8; 25 | /** bejárat */ 26 | const BEJARAT = 9; 27 | /** parkoló */ 28 | const PARKOLO = 10; 29 | /** környék */ 30 | const KORNYEK = 11; 31 | /** lépcsőház */ 32 | const LEPCSOHAZ = 12; 33 | /** tetőtér */ 34 | const TETOTER = 13; 35 | /** padlás */ 36 | const PADLAS = 14; 37 | /** pince */ 38 | const PINCE = 15; 39 | /** konyha */ 40 | const KONYHA = 16; 41 | /** hall */ 42 | const HALL = 17; 43 | /** nappali */ 44 | const NAPPALI = 18; 45 | /** előtér */ 46 | const ELOTER = 19; 47 | /** folyosó */ 48 | const FOLYOSO = 20; 49 | /** wc */ 50 | const WC = 21; 51 | /** fürdőszoba */ 52 | const FURDOSZOBA = 22; 53 | /** étkező */ 54 | const ETKEZO = 23; 55 | /** szuterén */ 56 | const SZUTEREN = 24; 57 | /** hálószoba */ 58 | const HALOSZOBA = 25; 59 | /** gardrób */ 60 | const GARDROB = 26; 61 | /** garázs */ 62 | const GARAZS = 27; 63 | /** bejárat */ 64 | const BEJARAT2 = 28; 65 | /** kamra */ 66 | const KAMRA = 29; 67 | /** tároló */ 68 | const TAROLO = 30; 69 | /** egyéb helyiség */ 70 | const EGYEB_HELYISEG = 31; 71 | 72 | /** @var array $valueCache */ 73 | public static $valueCache = []; 74 | 75 | /** 76 | * @param int $value 77 | * @return bool 78 | */ 79 | public static function validate($value) 80 | { 81 | if (empty(self::$valueCache)) { 82 | $class = get_called_class(); 83 | $ref = new \ReflectionClass($class); 84 | self::$valueCache = $ref->getConstants(); 85 | } 86 | 87 | if ($value === null || in_array($value, self::$valueCache)) { 88 | return true; 89 | } else { 90 | return false; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/ApiClient/Exception/InvalidValueException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace IngatlanCom\ApiClient\Exception; 3 | 4 | class InvalidValueException extends \Exception 5 | { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /lib/ApiClient/Exception/JSendException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Exception; 4 | 5 | use JSend\JSendResponse; 6 | 7 | abstract class JSendException extends \Exception 8 | { 9 | /** 10 | * @var JSendResponse 11 | */ 12 | protected $response; 13 | 14 | /** 15 | * JSendException constructor. 16 | * 17 | * @param string $message 18 | * @param int $code 19 | * @param \Exception|null $previous 20 | * @param JSendResponse $response 21 | */ 22 | public function __construct($message = "", $code = 0, \Exception $previous = null, JSendResponse $response = null) 23 | { 24 | parent::__construct($message, $code, $previous); 25 | $this->response = $response; 26 | } 27 | 28 | /** 29 | * @return JSendResponse 30 | */ 31 | public function getJSendResponse() 32 | { 33 | return $this->response; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/ApiClient/Exception/JSendFailException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Exception; 4 | 5 | class JSendFailException extends JSendException 6 | { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /lib/ApiClient/Exception/JWTTokenException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace IngatlanCom\ApiClient\Exception; 3 | 4 | class JWTTokenException extends \Exception 5 | { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /lib/ApiClient/Exception/MissingRequirementsException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace IngatlanCom\ApiClient\Exception; 3 | 4 | class MissingRequirementsException extends \RuntimeException 5 | { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /lib/ApiClient/Exception/NotAuthenticatedException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Exception; 4 | 5 | class NotAuthenticatedException extends \Exception 6 | { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /lib/ApiClient/Exception/ServerErrorException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Exception; 4 | 5 | class ServerErrorException extends JSendException 6 | { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /lib/ApiClient/PhotoSyncResult.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace IngatlanCom\ApiClient; 3 | 4 | /** 5 | * A fotók szinkronizálásának eredménye (ApiClient::syncPhotos() hívásakor kapjuk vissza) 6 | */ 7 | class PhotoSyncResult 8 | { 9 | /** 10 | * @var $photos 11 | */ 12 | private $photos = []; 13 | 14 | /** 15 | * @var $errors 16 | */ 17 | private $errors = []; 18 | 19 | /** 20 | * PhotoSyncResult constructor. 21 | * @param array $photos 22 | * @param array $errors 23 | */ 24 | public function __construct(array $photos, array $errors) 25 | { 26 | $this->photos = $photos; 27 | $this->errors = $errors; 28 | } 29 | 30 | /** 31 | * @return array 32 | */ 33 | public function getPhotos() 34 | { 35 | return $this->photos; 36 | } 37 | 38 | /** 39 | * @param string $arrayName 40 | * @return array 41 | */ 42 | private function getErrorsFromArray($arrayName) 43 | { 44 | return isset($this->errors[$arrayName]) ? $this->errors[$arrayName] : []; 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getFetchPhotoErrors() 51 | { 52 | return $this->getErrorsFromArray('photoFetch'); 53 | } 54 | 55 | /** 56 | * A fotók letöltésének hibaüzenetei own id indexű tömbben 57 | * 58 | * @return array 59 | */ 60 | public function getFetchPhotoErrorMessages() 61 | { 62 | return array_column($this->getFetchPhotoErrors(), 'errorMessage', 'ownId'); 63 | } 64 | 65 | /** 66 | * @return array 67 | */ 68 | public function getDeletePhotoErrors() 69 | { 70 | return $this->getErrorsFromArray('photoDelete'); 71 | } 72 | 73 | /** 74 | * A fotók törlésének hibaüzenetei own id indexű tömbben 75 | * 76 | * @return array 77 | */ 78 | public function getDeletePhotoErrorMessages() 79 | { 80 | return array_column($this->getDeletePhotoErrors(), 'errorMessage', 'ownId'); 81 | } 82 | 83 | /** 84 | * @return array 85 | */ 86 | public function getPutPhotoErrors() 87 | { 88 | return $this->getErrorsFromArray('photoPut'); 89 | } 90 | 91 | /** 92 | * A fotók feltöltésének hibaüzenetei own id indexű tömbben 93 | * 94 | * @return array 95 | */ 96 | public function getPutPhotoErrorMessages() 97 | { 98 | return array_column($this->getPutPhotoErrors(), 'errorMessage', 'ownId'); 99 | } 100 | 101 | /** 102 | * @return array 103 | */ 104 | public function getErrors() 105 | { 106 | return array_merge( 107 | $this->getDeletePhotoErrors(), 108 | $this->getFetchPhotoErrors(), 109 | $this->getPutPhotoErrors() 110 | ); 111 | } 112 | 113 | /** 114 | * A szinkronizálás közben történt hibák own id indexű tömbben 115 | * 116 | * @return array 117 | */ 118 | public function getErrorMessages() 119 | { 120 | return $this->getFetchPhotoErrorMessages() + $this->getDeletePhotoErrorMessages() + $this->getPutPhotoErrorMessages(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/ApiClient/Service/ClientFactoryService.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Service; 4 | 5 | use GuzzleHttp\Client; 6 | use IngatlanCom\ApiClient\ApiClient; 7 | 8 | /** 9 | * Class ClientFactoryService 10 | * 11 | * Guzzle Client factory 12 | * 13 | * @package IngatlanCom\ApiClient\Service 14 | */ 15 | class ClientFactoryService 16 | { 17 | /** 18 | * @param string $baseUrl 19 | * @param null $config 20 | * @return Client 21 | */ 22 | public function getClient($baseUrl = '', $config = null) 23 | { 24 | $config['base_uri'] = $baseUrl; 25 | $config['headers'] = ['X-Icom-Client-Version' => ApiClient::CLIENT_VERSION]; 26 | 27 | return new Client($config); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/ApiClient/Service/Image/ImageException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Service\Image; 4 | 5 | /** 6 | * Class ImageException 7 | * 8 | * Kép kivétel 9 | * 10 | * @package IngatlanCom\ApiClient\Service\Image 11 | */ 12 | class ImageException extends \Exception 13 | { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /lib/ApiClient/Service/Image/ImageGD.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Service\Image; 4 | 5 | /** 6 | * Class ImageGD 7 | * 8 | * GD kép wrapper 9 | * 10 | * @package IngatlanCom\ApiClient\Service\Image 11 | */ 12 | class ImageGD implements ImageInterface 13 | { 14 | /** 15 | * GD image 16 | * 17 | * @var resource 18 | */ 19 | protected $img; 20 | 21 | /** 22 | * Konstruktor 23 | * 24 | * @param resource $img GD image 25 | */ 26 | public function __construct($img) 27 | { 28 | $this->img = $img; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public static function createFromBytes($imageBytes) 35 | { 36 | $img = imagecreatefromstring($imageBytes); 37 | if (false === $img) { 38 | throw new \Exception('Hibás kép!'); 39 | } 40 | 41 | return new static($img); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function getWidth() 48 | { 49 | return imagesx($this->img); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function getHeight() 56 | { 57 | return imagesy($this->img); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function getPixelColor($x, $y) 64 | { 65 | $rgb = imagecolorat($this->img, $x, $y); 66 | $color = array( 67 | 'r' => ($rgb >> 16) & 0xFF, 68 | 'g' => ($rgb >> 8) & 0xFF, 69 | 'b' => $rgb & 0xFF 70 | ); 71 | 72 | return $color; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function createResizedMaximizedImage($maxWidth, $maxHeight) 79 | { 80 | $width = $this->getWidth(); 81 | $height = $this->getHeight(); 82 | $newWidth = $width; 83 | $newHeight = $height; 84 | if ($newHeight > $maxHeight) { 85 | $newWidth = ($maxHeight / $newHeight) * $newWidth; 86 | $newHeight = $maxHeight; 87 | } 88 | if ($newWidth > $maxWidth) { 89 | $newHeight = ($maxWidth / $newWidth) * $newHeight; 90 | $newWidth = $maxWidth; 91 | } 92 | 93 | $img = imagecreatetruecolor($newWidth, $newHeight); 94 | imagecopyresampled($img, $this->img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); 95 | 96 | return new static($img); 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function getJpegBytes() 103 | { 104 | ob_start(); 105 | imagejpeg($this->img); 106 | 107 | $imageData = ob_get_contents(); 108 | ob_end_clean(); 109 | 110 | return $imageData; 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function __destruct() 117 | { 118 | if ($this->img) { 119 | imagedestroy($this->img); 120 | } 121 | 122 | $this->img = null; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/ApiClient/Service/Image/ImageImagick.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Service\Image; 4 | 5 | use Imagick; 6 | 7 | /** 8 | * Class ImageImagick 9 | * 10 | * ImageMagick kép wrapper 11 | * 12 | * @package IngatlanCom\ApiClient\Service\Image 13 | */ 14 | class ImageImagick implements ImageInterface 15 | { 16 | /** 17 | * ImageMagick image 18 | * 19 | * @var Imagick 20 | */ 21 | protected $img; 22 | 23 | /** 24 | * Konstruktor 25 | * 26 | * @param Imagick $img ImageMagick image 27 | */ 28 | public function __construct(Imagick $img) 29 | { 30 | $this->img = $img; 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public static function createFromBytes($imageBytes) 37 | { 38 | try { 39 | $img = new \Imagick(); 40 | $img->readImageBlob($imageBytes); 41 | } catch (\Exception $we) { 42 | throw new ImageException('Hibás kép!'); 43 | } 44 | 45 | 46 | return new static($img); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getWidth() 53 | { 54 | return $this->img->getImageWidth(); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function getHeight() 61 | { 62 | return $this->img->getImageHeight(); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function getPixelColor($x, $y) 69 | { 70 | $colorObject = $this->img->getImagePixelColor($x, $y); 71 | 72 | return $colorObject->getColor(); 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function createResizedMaximizedImage($maxWidth, $maxHeight) 79 | { 80 | $img = clone $this->img; 81 | $img->resizeImage($maxWidth, $maxHeight, \Imagick::FILTER_LANCZOS, 1, true); 82 | 83 | return new static($img); 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function getJpegBytes() 90 | { 91 | return $this->img->getImageBlob(); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function __destruct() 98 | { 99 | if ($this->img) { 100 | $this->img->destroy(); 101 | } 102 | 103 | $this->img = null; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/ApiClient/Service/Image/ImageInterface.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | namespace IngatlanCom\ApiClient\Service\Image; 4 | 5 | /** 6 | * Interface ImageInterface 7 | * 8 | * Kép absztrakció interface 9 | * 10 | * @package IngatlanCom\ApiClient\Service\Image 11 | */ 12 | interface ImageInterface 13 | { 14 | /** 15 | * Kép betöltése adatokból 16 | * 17 | * @param string $imageBytes 18 | * @return ImageInterface 19 | * @throws ImageException 20 | */ 21 | public static function createFromBytes($imageBytes); 22 | 23 | /** 24 | * Szélesség 25 | * 26 | * @return mixed 27 | */ 28 | public function getWidth(); 29 | 30 | /** 31 | * Magasság 32 | * 33 | * @return mixed 34 | */ 35 | public function getHeight(); 36 | 37 | /** 38 | * Pixel színe 39 | * 40 | * @param int $x 41 | * @param int $y 42 | * @return array kulcsok: r, g, b 43 | */ 44 | public function getPixelColor($x, $y); 45 | 46 | /** 47 | * Átméretezett kép elkészítése 48 | * 49 | * @param int $maxWidth 50 | * @param int $maxHeight 51 | * @return ImageInterface 52 | */ 53 | public function createResizedMaximizedImage($maxWidth, $maxHeight); 54 | 55 | /** 56 | * Jpeg reprezentáció 57 | * 58 | * @return string jpeg byteok 59 | */ 60 | public function getJpegBytes(); 61 | 62 | /** 63 | * Memória felszabadítás 64 | */ 65 | public function __destruct(); 66 | } 67 | -------------------------------------------------------------------------------- /lib/ApiClient/Service/PhotoResizeService.php: -------------------------------------------------------------------------------- 1 | <?php 2 | namespace IngatlanCom\ApiClient\Service; 3 | 4 | use GuzzleHttp\Client; 5 | use GuzzleHttp\Exception\TransferException; 6 | use GuzzleHttp\Promise\PromiseInterface; 7 | use GuzzleHttp\Psr7\Request; 8 | use IngatlanCom\ApiClient\Service\Image\ImageException; 9 | use IngatlanCom\ApiClient\Service\Image\ImageGD; 10 | use IngatlanCom\ApiClient\Service\Image\ImageImagick; 11 | use IngatlanCom\ApiClient\Service\Image\ImageInterface; 12 | use Psr\Http\Message\RequestInterface; 13 | 14 | /** 15 | * Képátméretezés közös részei, pl. file műveletek, file letöltés 16 | * 17 | * @package IngatlanCom\ApiClient\Service 18 | */ 19 | class PhotoResizeService 20 | { 21 | /** 22 | * GD használata képméretezéshez 23 | */ 24 | const LIB_GD = 1; 25 | 26 | /** 27 | * ImageMagick használata képméretezéshez 28 | */ 29 | const LIB_IMAGICK = 2; 30 | 31 | protected $minWidth = 800; 32 | protected $minHeight = 600; 33 | 34 | protected $maxWidth = 1920; 35 | protected $maxHeight = 1080; 36 | 37 | /** 38 | * @var Client 39 | */ 40 | private $client; 41 | 42 | /** 43 | * @var integer PhotoResizeService::LIB_GD vagy PhotoResizeService::LIB_IMAGICK osztálykonstansok 44 | */ 45 | protected $imageLibrary; 46 | 47 | /** 48 | * Konstruktor 49 | * 50 | * @param integer $imageLibrary PhotoResizeService::LIB_GD vagy default:PhotoResizeService::LIB_IMAGICK osztálykonstansok 51 | * @param ClientFactoryService $clientFactoryService Guzzle kliens factory 52 | */ 53 | public function __construct($imageLibrary = null, ClientFactoryService $clientFactoryService = null) 54 | { 55 | if (null === $imageLibrary) { 56 | $imageLibrary = extension_loaded('imagick') ? static::LIB_IMAGICK : static::LIB_GD; 57 | } 58 | 59 | $this->imageLibrary = $imageLibrary; 60 | 61 | $clientFactoryService = null != $clientFactoryService ? $clientFactoryService : new ClientFactoryService(); 62 | $this->client = $clientFactoryService->getClient(); 63 | } 64 | 65 | /** 66 | * Fotó átméretezés 67 | * 68 | * @param string $path kép elérési útvonal 69 | * @return string kép byteok 70 | * @throws \Exception 71 | */ 72 | public function getResizedPhotoData($path) 73 | { 74 | if ('http' == substr(strtolower($path), 0, 4)) { 75 | $response = $this->client->request('GET', $path); 76 | $contents = $response->getBody(); 77 | } else { 78 | $contents = file_get_contents($path); 79 | if (false === $contents) { 80 | throw new ImageException('Photo not found: ' . $path); 81 | } 82 | } 83 | 84 | $fileData = $this->resizePhoto($contents); 85 | 86 | return $fileData; 87 | } 88 | 89 | /** 90 | * Képek letöltése, átméretezése 91 | * 92 | * @param array $photosByOwnId hirdetés képeinek adatai, saját azonosító szerint indexelve 93 | * @param bool $paralellDownload párhuzamos fotóletöltés az iroda szerveréről 94 | * @return array fotó byte-ok|Exception-ok fotó saját azonosító szerint 95 | */ 96 | public function getResizedPhotosData(array $photosByOwnId, $paralellDownload = false) 97 | { 98 | $results = array(); 99 | $requests = array(); 100 | 101 | foreach ($photosByOwnId as $ownId => $photo) { 102 | $path = $photo['location']; 103 | 104 | if ($paralellDownload && 'http' == substr(strtolower($path), 0, 4)) { 105 | $requests[$ownId] = new Request('GET', $path); 106 | } else { 107 | try { 108 | $results[$ownId] = $this->getResizedPhotoData($path); 109 | } catch (\Exception $e) { 110 | $results[$ownId] = $e; 111 | } 112 | } 113 | } 114 | 115 | $results = $results + $this->getResizedPhotosDataByRequests($requests); 116 | 117 | return $results; 118 | } 119 | 120 | /** 121 | * Párhuzamos képletöltés 122 | * 123 | * @param RequestInterface[] $requests 124 | * @return array fotó byte-ok|Exception-ok fotó saját azonosító szerint 125 | */ 126 | private function getResizedPhotosDataByRequests($requests) 127 | { 128 | $results = []; 129 | $promises = []; 130 | 131 | if (count($requests)) { 132 | try { 133 | foreach ($requests as $key => $request) { 134 | $promises[$key] = $this->client->sendAsync($request); 135 | } 136 | } catch (TransferException $e) { 137 | } 138 | 139 | \GuzzleHttp\Promise\each( 140 | $promises, 141 | function ($value, $idx) use (&$results) { 142 | $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; 143 | }, 144 | function ($reason, $idx) use (&$results) { 145 | $results[$idx] = ['state' => PromiseInterface::REJECTED, 'value' => $reason]; 146 | } 147 | )->wait(); 148 | } 149 | 150 | foreach ($results as $ownId => $response) { 151 | if ($response['state'] == PromiseInterface::REJECTED) { 152 | $results[$ownId] = $response['value']; 153 | } else { 154 | try { 155 | $results[$ownId] = $this->resizePhoto($response['value']->getBody()); 156 | } catch (\Exception $e) { 157 | $results[$ownId] = $e; 158 | } 159 | } 160 | } 161 | 162 | return $results; 163 | } 164 | 165 | /** 166 | * Szürkeárnyalatos-e a kép (pl. alaprajz "fekete-fehér-e"?) 167 | * 168 | * @param ImageInterface $img 169 | * @param float $tolerancePercentage default 95 (%) 170 | * @return boolean 171 | */ 172 | private function isGrayScale(ImageInterface $img, $tolerancePercentage = 95.0) 173 | { 174 | $width = $img->getWidth(); 175 | $height = $img->getHeight(); 176 | $grayPixelCount = 0; 177 | for ($x = 0; $x < $width; $x++) { 178 | for ($y = 0; $y < $height; $y++) { 179 | $color = $img->getPixelColor($x, $y); 180 | if ($color['r'] == $color['g'] && $color['r'] == $color['b']) { 181 | $grayPixelCount++; 182 | } 183 | } 184 | } 185 | 186 | // Legalább $tolerance százalékban szürke-e? 187 | return $grayPixelCount > $width * $height * $tolerancePercentage / 100; 188 | } 189 | 190 | /** 191 | * Méretvalidációk 192 | * 193 | * @param ImageInterface $img 194 | * @throws ImageException 195 | * @return array $img, $width, $height 196 | */ 197 | private function validate($img) 198 | { 199 | $width = $img->getWidth(); 200 | $height = $img->getHeight(); 201 | if ($width < $this->minWidth && $height < $this->minHeight) { 202 | if (450 == $width && 450 == $height) { 203 | if (!$this->isGrayScale($img)) { 204 | $msg = '450x450 pixel méretű alaprajzokat kérjük, a http://alaprajz.ingatlan.com oldalon készítsen!'; 205 | throw new ImageException($msg); 206 | } 207 | } else { 208 | throw new ImageException('Ön 800x600 pixelnél kisebb képet próbált meg feltölteni. Kérjük, hogy töltsön fel legalább 800x600 pixel felbontású képet.'); 209 | } 210 | } 211 | 212 | return array($img, $width, $height); 213 | } 214 | 215 | /** 216 | * Kép betöltése 217 | * 218 | * @param $imageData 219 | * @return \IngatlanCom\ApiClient\Service\Image\ImageInterface 220 | */ 221 | private function loadImage($imageData) 222 | { 223 | if (static::LIB_IMAGICK == $this->imageLibrary) { 224 | $img = ImageImagick::createFromBytes($imageData); 225 | } else { 226 | $img = ImageGD::createFromBytes($imageData); 227 | } 228 | 229 | $this->validate($img); 230 | 231 | return $img; 232 | } 233 | 234 | /** 235 | * Kép átméretezése 236 | * 237 | * @param string $imageData forrás kép byteok 238 | * @return string átméretezett kép byteok 239 | */ 240 | private function resizePhoto($imageData) 241 | { 242 | $img = $this->loadImage($imageData); 243 | $width = $img->getWidth(); 244 | $height = $img->getHeight(); 245 | 246 | if ($width > $this->maxWidth || $height > $this->maxHeight) { 247 | $resizedImg = $img->createResizedMaximizedImage($this->maxWidth, $this->maxHeight); 248 | $img = $resizedImg; 249 | } 250 | 251 | $imageData = $img->getJpegBytes(); 252 | 253 | return $imageData; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <phpunit bootstrap="tests/bootstrap.php" colors="true"> 3 | <testsuites> 4 | <testsuite name="lib"> 5 | <directory suffix="Test.php">tests</directory> 6 | </testsuite> 7 | </testsuites> 8 | </phpunit> 9 | -------------------------------------------------------------------------------- /tests/ApiClientTest.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | use GuzzleHttp\Promise\PromiseInterface; 4 | use IngatlanCom\ApiClient\ApiClient; 5 | 6 | /** 7 | * ApiClient tesztek 8 | */ 9 | class ApiClientTest extends \PHPUnit_Framework_TestCase 10 | { 11 | /** 12 | * @param array $mocks 13 | * @return ApiClient 14 | */ 15 | public function getClient(array $mocks) 16 | { 17 | return new ApiClient('/', null, new ClientFactoryMockService('responses', $mocks)); 18 | } 19 | 20 | public function testLoginSuccess() 21 | { 22 | $client = $this->getClient( 23 | [ 24 | ['statusCode' => 200, 'fileName' => 'loginSuccess'], 25 | ] 26 | ); 27 | $client->login('lolka', 'bolka'); 28 | } 29 | 30 | /** 31 | * @expectedException IngatlanCom\ApiClient\Exception\NotAuthenticatedException 32 | */ 33 | public function testLoginFail() 34 | { 35 | $client = $this->getClient( 36 | [ 37 | ['statusCode' => 401, 'fileName' => 'loginFail'], 38 | ] 39 | ); 40 | $client->login('lolka', 'bolka'); 41 | } 42 | 43 | public function testPutAdSuccess() 44 | { 45 | $client = $this->getClient( 46 | [ 47 | ['statusCode' => 200, 'fileName' => 'loginSuccess'], 48 | ['statusCode' => 200, 'fileName' => 'putAdSuccess'], 49 | ] 50 | ); 51 | $client->login('lolka', 'bolka'); 52 | $client->putAd(array('ownId' => 'i12345')); 53 | } 54 | 55 | public function testPutAdFail() 56 | { 57 | $client = $this->getClient( 58 | [ 59 | ['statusCode' => 200, 'fileName' => 'loginSuccess'], 60 | ['statusCode' => 400, 'fileName' => 'putAdFail'], 61 | ] 62 | ); 63 | $client->login('lolka', 'bolka'); 64 | 65 | try { 66 | $client->putAd(['ownId' => 'i12345']); 67 | } catch (\IngatlanCom\ApiClient\Exception\JSendFailException $e) { 68 | $this->assertArrayHasKey('listingType', $e->getJSendResponse()->getData()); 69 | } 70 | } 71 | 72 | public function testSyncAds() 73 | { 74 | $client = $this->getClient( 75 | [ 76 | ['statusCode' => 200, 'fileName' => 'loginSuccess'], 77 | ['statusCode' => 200, 'fileName' => 'getAdIdsSuccess'], 78 | ['statusCode' => 200, 'fileName' => 'deleteAdSuccess'], 79 | ['statusCode' => 200, 'fileName' => 'deleteAdSuccess'], 80 | ] 81 | ); 82 | $client->login('lolka', 'bolka'); 83 | 84 | $deleted = $client->syncAds(['ad2', 'ad4']); 85 | 86 | $this->assertEquals(['ad1', 'ad3'], array_values($deleted)); 87 | } 88 | 89 | public function testPutPhotosMultiSuccess() 90 | { 91 | $client = $this->getClient( 92 | [ 93 | ['statusCode' => 200, 'fileName' => 'loginSuccess'], 94 | ['statusCode' => 200, 'fileName' => 'putPhotoSuccess'], 95 | ['statusCode' => 200, 'fileName' => 'putPhotoSuccess'], 96 | ] 97 | ); 98 | $client->login('lolka', 'bolka'); 99 | 100 | $result = $client->putPhotosMulti( 101 | 'i12345', 102 | array( 103 | 'p1' => array( 104 | 'ownId' => 'p1', 105 | ), 106 | 'p2' => array( 107 | 'ownId' => 'p2', 108 | ), 109 | ) 110 | ); 111 | 112 | $this->assertEquals(PromiseInterface::FULFILLED, $result['p1']['state']); 113 | $this->assertEquals(PromiseInterface::FULFILLED, $result['p2']['state']); 114 | } 115 | 116 | public function testPutPhotosMultiFail() 117 | { 118 | $client = $this->getClient( 119 | [ 120 | ['statusCode' => 200, 'fileName' => 'loginSuccess'], 121 | ['statusCode' => 200, 'fileName' => 'putPhotoSuccess'], 122 | ['statusCode' => 400, 'fileName' => 'putPhotoFail'], 123 | ] 124 | ); 125 | $client->login('lolka', 'bolka'); 126 | 127 | $result = $client->putPhotosMulti( 128 | 'i12345', 129 | array( 130 | 'p1' => array( 131 | 'ownId' => 'p1', 132 | ), 133 | 'p2' => array( 134 | 'ownId' => 'p2', 135 | ), 136 | ) 137 | ); 138 | 139 | $this->assertEquals(PromiseInterface::FULFILLED, $result['p1']['state']); 140 | $this->assertEquals(PromiseInterface::REJECTED, $result['p2']['state']); 141 | } 142 | 143 | public function testSyncPhotos() 144 | { 145 | $client = $this->getClient( 146 | [ 147 | ['statusCode' => 200, 'fileName' => 'loginSuccess'], 148 | ['statusCode' => 200, 'fileName' => 'getPhotosSuccess'], 149 | ['statusCode' => 200, 'fileName' => 'deletePhotoSuccess'], //1 150 | ['statusCode' => 200, 'fileName' => 'deletePhotoSuccess'], //4 151 | ['statusCode' => 200, 'fileName' => 'putPhotoSuccess'], //5 152 | ['statusCode' => 200, 'fileName' => 'getPhotosSuccess'], //putPhotoOrderSuccess ugyanolyan, 153 | ] 154 | ); 155 | $client->login('lolka', 'bolka'); 156 | 157 | $result = $client->syncPhotos( 158 | 'ad1', 159 | array( 160 | array( 161 | 'ownId' => 'p2', 162 | 'title' => 'photo2', 163 | 'labelId' => null, 164 | 'order' => 1, 165 | ), 166 | array( 167 | 'ownId' => 'p3', 168 | 'title' => 'photo3', 169 | 'labelId' => null, 170 | 'order' => 2, 171 | ), 172 | array( 173 | 'ownId' => 'p5', 174 | 'title' => 'photo5', 175 | 'labelId' => null, 176 | 'location' => __DIR__ . '/mock/photos/1.jpg', 177 | 'order' => 3, 178 | ), 179 | array( 180 | 'ownId' => 'p6', 181 | 'title' => 'photo6', 182 | 'labelId' => null, 183 | 'location' => __DIR__ . '/mock/photos/2.jpg', 184 | 'order' => 4, 185 | ), 186 | ) 187 | ); 188 | 189 | //photos/2.jpg nincs 190 | $this->assertCount(0, $result->getDeletePhotoErrors()); 191 | $this->assertCount(1, $result->getFetchPhotoErrors()); 192 | $this->assertCount(0, $result->getPutPhotoErrors()); 193 | } 194 | 195 | public function testCheckApiStatus() 196 | { 197 | $client = $this->getClient( 198 | [ 199 | ['statusCode' => 200, 'fileName' => 'checkApiStatus'] 200 | ] 201 | ); 202 | $isOk = $client->checkApiStatus(); 203 | $this->assertTrue($isOk); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /tests/PhotoResizeServiceGDTest.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | use IngatlanCom\ApiClient\Service\PhotoResizeService; 4 | 5 | /** 6 | * Photo Resize tests with GD 7 | */ 8 | class PhotoResizeServiceGDTest extends PhotoResizeServiceTestAbstract 9 | { 10 | /** 11 | * @var integer PhotoResizeService::LIB_GB, PhotoResizeService::LIB_IMAGICK 12 | */ 13 | protected $imageLibrary = PhotoResizeService::LIB_GD; 14 | } 15 | -------------------------------------------------------------------------------- /tests/PhotoResizeServiceImagickTest.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | use IngatlanCom\ApiClient\Service\PhotoResizeService; 4 | 5 | /** 6 | * Photo Resize tests with Imagick 7 | */ 8 | class PhotoResizeServiceImagickTest extends PhotoResizeServiceTestAbstract 9 | { 10 | /** 11 | * @var integer PhotoResizeService::LIB_GB, PhotoResizeService::LIB_IMAGICK 12 | */ 13 | protected $imageLibrary = PhotoResizeService::LIB_IMAGICK; 14 | } 15 | -------------------------------------------------------------------------------- /tests/PhotoResizeServiceParalellDownloadTest.php: -------------------------------------------------------------------------------- 1 | <?php 2 | use IngatlanCom\ApiClient\Service\PhotoResizeService; 3 | 4 | /** 5 | * Parhuzamos kepletoltes teszt 6 | */ 7 | class PhotoResizeServiceParalellDownloadTest extends \PHPUnit_Framework_TestCase 8 | { 9 | public function testGetResizedPhotos() 10 | { 11 | $service = $this->getPhotoResizeService( 12 | [ 13 | ['statusCode' => 200, 'fileName' => '1.jpg'], 14 | ['statusCode' => 200, 'fileName' => 'toosmall.png'], 15 | ['statusCode' => 404], 16 | ] 17 | ); 18 | 19 | $photos = array( 20 | 1 => array('location' => 'http://1.jpg'), 21 | 2 => array('location' => 'http://toosmall.png'), 22 | 3 => array('location' => 'http://notfound.jpg'), 23 | 4 => array('location' => __DIR__ . '/mock/photos/1.jpg'), 24 | ); 25 | 26 | $res = $service->getResizedPhotosData($photos, true); 27 | 28 | $this->assertTrue(is_string($res[1])); 29 | $this->assertTrue(is_string($res[4])); 30 | $this->assertTrue($res[2] instanceof \IngatlanCom\ApiClient\Service\Image\ImageException); 31 | $this->assertTrue($res[3] instanceof \GuzzleHttp\Exception\ClientException); 32 | 33 | $sizes1 = getimagesizefromstring($res[1]); 34 | $sizes4 = getimagesizefromstring($res[4]); 35 | $this->assertEquals($sizes1, $sizes4); 36 | } 37 | 38 | /** 39 | * @param array $mocks 40 | * @return PhotoResizeService 41 | */ 42 | private function getPhotoResizeService(array $mocks) 43 | { 44 | return new PhotoResizeService(null, new ClientFactoryMockService('photos', $mocks)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/PhotoResizeServiceTestAbstract.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | use IngatlanCom\ApiClient\Service\PhotoResizeService; 4 | 5 | /** 6 | * Képátméretezés tesztek 7 | */ 8 | abstract class PhotoResizeServiceTestAbstract extends \PHPUnit_Framework_TestCase 9 | { 10 | /** 11 | * @var PhotoResizeService 12 | */ 13 | private $service; 14 | 15 | /** 16 | * @var integer PhotoResizeService::LIB_GB, PhotoResizeService::LIB_IMAGICK 17 | */ 18 | protected $imageLibrary = PhotoResizeService::LIB_IMAGICK; 19 | 20 | public function setUp() 21 | { 22 | if (PhotoResizeService::LIB_IMAGICK == $this->imageLibrary && !extension_loaded('imagick')) { 23 | $this->markTestSkipped( 24 | 'The imagick extension is not available.' 25 | ); 26 | } 27 | 28 | if (PhotoResizeService::LIB_GD == $this->imageLibrary && !extension_loaded('gd')) { 29 | $this->markTestSkipped( 30 | 'The gd extension is not available.' 31 | ); 32 | } 33 | 34 | $this->service = new PhotoResizeService($this->imageLibrary); 35 | } 36 | 37 | public function tearDown() 38 | { 39 | $this->service = null; 40 | } 41 | 42 | public function testResize() 43 | { 44 | $this->service->getResizedPhotoData(__DIR__ . '/mock/photos/1.jpg'); 45 | } 46 | 47 | /** 48 | * @expectedException \Exception 49 | */ 50 | public function testResizeTooSmallNotGroundPlan() 51 | { 52 | $this->service->getResizedPhotoData(__DIR__ . '/mock/photos/toosmall.png'); 53 | } 54 | 55 | public function testResizeTooSmall450x450ButGroundPlan() 56 | { 57 | $this->service->getResizedPhotoData(__DIR__ . '/mock/photos/groundplan450.jpg'); 58 | } 59 | 60 | /** 61 | * @expectedException \Exception 62 | */ 63 | public function testResizeTooSmall450x450NotGroundPlan() 64 | { 65 | $this->service->getResizedPhotoData(__DIR__ . '/mock/photos/notgroundplan450.jpg'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | <?php 2 | require dirname(__DIR__) . '/vendor/autoload.php'; 3 | require dirname(__DIR__) . '/tests/mock/ClientFactoryMockService.php'; 4 | require dirname(__DIR__) . '/tests/PhotoResizeServiceTestAbstract.php'; 5 | 6 | if (!function_exists('getimagesizefromstring')) { 7 | function getimagesizefromstring($string_data) 8 | { 9 | $uri = 'data://application/octet-stream;base64,' . base64_encode($string_data); 10 | return getimagesize($uri); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/mock/ClientFactoryMockService.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | use GuzzleHttp\Client; 4 | use GuzzleHttp\Handler\MockHandler; 5 | use GuzzleHttp\HandlerStack; 6 | use GuzzleHttp\Psr7\Response; 7 | use IngatlanCom\ApiClient\Service\ClientFactoryService; 8 | 9 | class ClientFactoryMockService extends ClientFactoryService 10 | { 11 | /** 12 | * @var Client 13 | */ 14 | private $client; 15 | 16 | /** 17 | * @var HandlerStack 18 | */ 19 | private $handler; 20 | 21 | /** 22 | * ClientFactoryMockService constructor. 23 | * @param $mockDirectory 24 | * @param array $mocks 25 | */ 26 | public function __construct($mockDirectory, array $mocks) 27 | { 28 | $responses = []; 29 | foreach ($mocks as $mockFile) { 30 | $body = isset($mockFile['fileName']) 31 | ? file_get_contents(__DIR__ . "/$mockDirectory/" . $mockFile['fileName']) 32 | : null; 33 | 34 | $responses[] = new Response($mockFile['statusCode'], [], $body); 35 | } 36 | 37 | $mock = new MockHandler($responses); 38 | $this->handler = HandlerStack::create($mock); 39 | } 40 | 41 | /** 42 | * @param string $baseUrl 43 | * @param null $config 44 | * @return Client 45 | */ 46 | public function getClient($baseUrl = '', $config = null) 47 | { 48 | if (!$this->client) { 49 | $config['handler'] = $this->handler; 50 | $this->client = parent::getClient($baseUrl, $config); 51 | } 52 | 53 | return $this->client; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/mock/photos/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingatlancom/api-client/f16d40bd502f32b9e392cc9b99f062c31fb54e02/tests/mock/photos/1.jpg -------------------------------------------------------------------------------- /tests/mock/photos/groundplan450.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingatlancom/api-client/f16d40bd502f32b9e392cc9b99f062c31fb54e02/tests/mock/photos/groundplan450.jpg -------------------------------------------------------------------------------- /tests/mock/photos/notgroundplan450.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingatlancom/api-client/f16d40bd502f32b9e392cc9b99f062c31fb54e02/tests/mock/photos/notgroundplan450.jpg -------------------------------------------------------------------------------- /tests/mock/photos/toosmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingatlancom/api-client/f16d40bd502f32b9e392cc9b99f062c31fb54e02/tests/mock/photos/toosmall.png -------------------------------------------------------------------------------- /tests/mock/responses/checkApiStatus: -------------------------------------------------------------------------------- 1 | {"status":"success","data":null} -------------------------------------------------------------------------------- /tests/mock/responses/deleteAdSuccess: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "data": { 4 | "ad": { 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/mock/responses/deletePhotoSuccess: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "data": { 4 | "photo": { 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/mock/responses/getAdIdsSuccess: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "data": { 4 | "ids": [ 5 | { 6 | "id": "1", 7 | "ownId": "ad1", 8 | "statusId": 0 9 | }, 10 | { 11 | "id": "2", 12 | "ownId": "ad2", 13 | "statusId": 0 14 | }, 15 | { 16 | "id": "3", 17 | "ownId": "ad3", 18 | "statusId": 0 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /tests/mock/responses/getPhotosSuccess: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "data": { 4 | "photos": [ 5 | { 6 | "order": 1, 7 | "ownId": "p1", 8 | "md5Hash": null, 9 | "title": "photo1", 10 | "labelId": null 11 | }, 12 | { 13 | "order": 2, 14 | "ownId": "p2", 15 | "md5Hash": null, 16 | "title": "photo2", 17 | "labelId": null 18 | }, 19 | { 20 | "order": 3, 21 | "ownId": "p3", 22 | "md5Hash": null, 23 | "title": "photo3", 24 | "labelId": null 25 | }, 26 | { 27 | "order": 4, 28 | "ownId": "p4", 29 | "md5Hash": null, 30 | "title": "photo4", 31 | "labelId": null 32 | } 33 | ] 34 | } 35 | } -------------------------------------------------------------------------------- /tests/mock/responses/loginFail: -------------------------------------------------------------------------------- 1 | { 2 | "status": "fail", 3 | "data": { 4 | "message": "Hibás bejelentkezési adatok" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/mock/responses/loginSuccess: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "data": { 4 | "token": "eyJhbGciOiJSUzI1NiJ9.eyJyb2xlcyI6WyJST0xFX0FQSSJdLCJ1c2VybmFtZSI6ImludGVyZmFjZTExMTYiLCJpYXQiOjE0OTMyODA3NTUsImNsb2NrX3NrZXciOjMwMCwiZXhwIjoxNDkzMjg5Njk1fQ.goLTfEZsr86UZn43PlRBt1FVZF4j1tW-vVHYm5VDx-pDFSz8tgjUtEnOYIPuXXhlmV4_7CZwx80MUk1JSw-N6QwleZtDO4SynJ607U6ovQTNXQDQrIPpZueOIo6ZRlbbgGMljS_Vwt7FFMPi7bjEXS39eCwEclTS_hgOZ2-oQL3LG1KNSU8P9qxuUYZ5RSGWt_dCwf7tRkGvRiY2Duw0EAXIMV3EYMfk9EshJtEm2Zfno27Pn5Cy4-AJ6WHlt_eMertsn9wQp4HGYPIYBMIkxC-3M4-s8IIzDX9nIt_r0NXnTo9E3ZGG82tIAfpUWASFvku-gxRikS76ShvkYXmXOp9fp0wU1ShI_j29VyjZeUkrVC2C_93_PNKomVJKhyrHEJiGjotIEnOGUfZfLyYm_ZId1sElFXOcBAI9rBrkdXhMALFzhCli-6hcbvNrxfLWZKfqAhL9862Hcf9RPElp-7o9VsmLBPfxyNOzxVjsZx7wxkuTlxOW1W79bv-3EmUHAyA9EtkOCeEoOAsyHaxmWhElTYIfHV4ffYAuB6fxkuGD59cxUAnzuJ_-79pYYyD3nDv8rRG77z3HBiXQwaQYHSdFGc8AaB-H0_2QV-88dHWvtidZblhBnqi6gBdXYjMca5uoco0lCgRpPyhqeM36I4BA5X7aAkoiCWhZubuy23g" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/mock/responses/putAdFail: -------------------------------------------------------------------------------- 1 | { 2 | "status": "fail", 3 | "data": { 4 | "listingType": "Válasszon a listából" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/mock/responses/putAdSuccess: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "data": { 4 | "ad": { 5 | "id": "21334998", 6 | "agentId": 0, 7 | "ownId": "130362", 8 | "realEstateAgencyOwnId": "GDN-130362", 9 | "projectId": "21334998", 10 | "listingType": 1, 11 | "isRentRight": false, 12 | "isStreetHidden": true, 13 | "isStreetNumberHidden": true, 14 | "agenciesAccepted": 0, 15 | "priceHuf": 18500000, 16 | "priceEur": 59690, 17 | "priceType": 1, 18 | "parkingPrice": 0, 19 | "city": "Budapest", 20 | "zone": null, 21 | "street": "Gyöngyösi utca", 22 | "district": 13, 23 | "utilityCosts": null, 24 | "petsAllowed": null, 25 | "smokingAllowed": null, 26 | "propertyType": 1, 27 | "propertySubtype": 2, 28 | "areaSize": 58, 29 | "areaSizeLiving": null, 30 | "lotSize": 0, 31 | "buildingFloorCount": 4, 32 | "floor": 5, 33 | "elevatorType": 2, 34 | "cellarType": 0, 35 | "atticType": 0, 36 | "furnitureType": 0, 37 | "houseNumber": "", 38 | "gardenSize": 0, 39 | "balconySize": 3, 40 | "electricityType": 0, 41 | "heatingType": 4, 42 | "description": "XIII, kerületben, Angyalföld közkedvelt csendes-parkosított övezetében, Gyöngyösi utcában, pár perc sétára az M3-as metrótól, és a Duna Plazától, 80-as években épült, 4.emeletes rendezett panelházban, 58 nm-es, 1+2 fél szobás, erkélyes, ablakos konyhás, étkezős, kiváló elosztású, külön nyíló szobás, napos-világos fekvésű \r\n(D-NY), alacsony fenntartású, 2. emeleti, csendes parkra néző, jó állapotú lakás, kulturált lakóközösségű házban eladó!\r\nAz ingatlant 2013-ban felújították, az ablakokat thermora cserélték!\r\nA parkolás közvetlen a ház előtt megoldott.\r\nHa csendre, nyugalomra vágyik, de mégis biztonságos, központi helyen élni, ez az Ön lakása!\r\nTovábbi részletekért hívjon bizalommal. (hétvégén is)", 43 | "descriptionEn": "", 44 | "descriptionDe": "", 45 | "statusId": 0, 46 | "comfortLevel": 3, 47 | "energyPerformanceCert": 0, 48 | "drainageType": 0, 49 | "waterType": 0, 50 | "gasType": 0, 51 | "roomCount": 1, 52 | "roomCountOffice": null, 53 | "smallRoomCount": 2, 54 | "parkingType": 0, 55 | "parkingPriceType": 0, 56 | "parkingPlaceCount": 0, 57 | "conditionType": 5, 58 | "viewType": 4, 59 | "innerHeight": 0, 60 | "accessibility": 2, 61 | "hasMachines": null, 62 | "panelprogram": 0, 63 | "gardenAccess": 0, 64 | "bathroomWithoutToilet": 0, 65 | "orientation": 6, 66 | "airConditioner": 0, 67 | "officeCategory": null, 68 | "photos": [ 69 | { 70 | "order": 1, 71 | "ownId": "7f53d2121c2bc4307953326e38f8964c", 72 | "md5Hash": null, 73 | "title": null, 74 | "labelId": null 75 | }, 76 | { 77 | "order": 2, 78 | "ownId": "6e2be3d1486605f6323f76ba763b8f59", 79 | "md5Hash": null, 80 | "title": null, 81 | "labelId": null 82 | }, 83 | { 84 | "order": 3, 85 | "ownId": "65b6fcf42c2454b67c813ee6c1f7ad4e", 86 | "md5Hash": null, 87 | "title": null, 88 | "labelId": null 89 | }, 90 | { 91 | "order": 4, 92 | "ownId": "7a65d09d614a190985da71f3dbbb8256", 93 | "md5Hash": null, 94 | "title": null, 95 | "labelId": null 96 | } 97 | ] 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /tests/mock/responses/putPhotoFail: -------------------------------------------------------------------------------- 1 | { 2 | "status": "fail", 3 | "data": { 4 | "order": "This value is not valid." 5 | } 6 | } -------------------------------------------------------------------------------- /tests/mock/responses/putPhotoSuccess: -------------------------------------------------------------------------------- 1 | { 2 | "status": "success", 3 | "data": { 4 | "photo": { 5 | "order": 1, 6 | "ownId": "pX", 7 | "md5Hash": null, 8 | "title": null, 9 | "labelId": null 10 | } 11 | } 12 | } 13 | --------------------------------------------------------------------------------