From 3c3b8a86d4593e424d3631da4bb4c26d5d13c3e3 Mon Sep 17 00:00:00 2001 From: Banana Date: Sun, 4 Jan 2026 12:59:16 +0100 Subject: [PATCH] fixing #1 with an alternative implementation Signed-off-by: Banana --- CHANGELOG | 7 +- README.md | 1 - TODO | 2 + USES | 1 - VERSION | 2 +- documentation/first-steps.md | 12 +- documentation/setup/bibliotheca.sql | 2 +- documentation/tool-imdbweb.md | 4 +- sources/imdb.class.php.txt | 107 +- upgrade/from-version-1.8.md | 23 + webclient/config/config-imdbweb.php.default | 32 +- webclient/config/config.php.default | 4 +- webclient/i18n/deu.ini | 7 +- webclient/i18n/eng.ini | 3 + webclient/index.php | 4 +- webclient/lib/googlebookparser.class.php | 3 +- webclient/lib/i18n.class.php | 4 +- webclient/lib/imdbweb.class.php | 414 ++++ webclient/lib/imdbwebparser.class.php | 1850 ----------------- webclient/setup/bibliotheca.sql.default | 2 +- webclient/systemout/.gitignore | 7 +- webclient/view/98/dashboard/dashboard.html | 2 +- webclient/view/98/tool/tool-imdbweb.html | 19 +- .../view/compact/dashboard/dashboard.html | 2 +- webclient/view/default/tool/tool-imdbweb.html | 8 +- webclient/view/default/tool/tool-imdbweb.php | 62 +- 26 files changed, 625 insertions(+), 1959 deletions(-) create mode 100644 upgrade/from-version-1.8.md create mode 100644 webclient/lib/imdbweb.class.php delete mode 100644 webclient/lib/imdbwebparser.class.php diff --git a/CHANGELOG b/CHANGELOG index ab154d4..6ca713a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,9 @@ -1.x - Harobed Village +1.8.1 - Harobed Village + * Config Update: See upgrade/from-version-1.8.md + * DB Update (non breaking): See upgrade/from-version-1.8.md + * Fixed: GH issue #1. IMDB web parser not working. Switched to own implementation. + * Updated some translation keys and values + * Updated USES and README file. 1.8 - Dark Arena 2025-08-30 * Config change: See upgrade/from-version-1.7.md diff --git a/README.md b/README.md index 99e4308..a260ce7 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ See Contributing document: CONTRIBUTING.md # Uses -+ https://github.com/FabianBeiner/PHP-IMDB-Grabber with some modifications + https://getuikit.com/ + https://jdan.github.io/98.css/ + https://sortablejs.github.io/Sortable/ diff --git a/TODO b/TODO index 78e0511..a35ff31 100644 --- a/TODO +++ b/TODO @@ -3,6 +3,8 @@ * change multiple-attachment to a field which tells it is used for a image gallery * Export of an entry, collection or everything. Stored on disk. * Import of the export +* cache and session cleanup of the systemout folder +* Update to date PHP syntax * update JS and remove deprecations * Definition of fields in "card view" diff --git a/USES b/USES index 08368be..1194220 100644 --- a/USES +++ b/USES @@ -1,4 +1,3 @@ -https://github.com/FabianBeiner/PHP-IMDB-Grabber with some modifications https://getuikit.com/ https://jdan.github.io/98.css/ https://sortablejs.github.io/Sortable/ diff --git a/VERSION b/VERSION index b892a4a..5992a38 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.x - Harobed Village +1.8.1 - Harobed Village diff --git a/documentation/first-steps.md b/documentation/first-steps.md index 0061365..d634b24 100644 --- a/documentation/first-steps.md +++ b/documentation/first-steps.md @@ -3,33 +3,33 @@ ## Change your password After your installation and successfull login with the default user change your password. -This is done in the manage -> profile screen. +This is done in the `Manage` -> `Profile` screen. ## Create your own user There is no need to use the admin account for your daily doings. -To create a new user go to the manage -> user screen and fill the form. +To create a new user go to the `Manage` -> `User` screen and fill the form. More information about users and rights can be found in usermanagement-and-rights.md ## Create your first collection -Choose manage -> collections to add your new collection. First make sure to fill the required fields +Choose `Manage` -> `Collections` to add your new collection. First make sure to fill the required fields and save it. Adding fields and choosing default fields will only work after the creation of collection. ## Add fields to your collection -Choose manage -> collections and select the newly created collection from the right. +Choose `Manage` -> `Collections` and select the newly created collection from the right. Click on the list icon (right from the pen icon) Choose from the right which fields should be available in your collection by tragging them to the left. Make sure that at least title, coverimage and description is available. Those are the basic fields which are used to display entries. On the left you can change the order of the fields. This order will used in the detail- and editview. -See fields.txt to read more about the fields and their features. +See fields.md to read more about the fields and their features. After saving you are good to go. ## Add an entry to your collection -Choose manage -> add. You will be presented a collection selection. Choose your newly created collection. +Choose `Manage` -> `Add`. You will be presented a collection selection. Choose your newly created collection. The following screen should match your selected fields and their order. Input the required information and save. diff --git a/documentation/setup/bibliotheca.sql b/documentation/setup/bibliotheca.sql index 370dee4..f2a5368 100644 --- a/documentation/setup/bibliotheca.sql +++ b/documentation/setup/bibliotheca.sql @@ -157,7 +157,7 @@ INSERT INTO `#REPLACEME#_sys_fields` (`id`, `identifier`, `displayname`, `type`, (21, 'genres', 'sysfield.genres', 'lookupmultiple', 'tag', NULL, '', NULL, 'string 64', '2020-07-26 07:18:55', '2024-04-20 07:52:44', NULL, 1, 1, 'rw-r--r--'), (22, 'languages', 'sysfield.languages', 'lookupmultiple', 'tag', NULL, '', NULL, 'string 64', '2020-07-26 07:20:45', '2024-04-20 07:53:17', NULL, 1, 1, 'rw-r--r--'), (23, 'runtime', 'sysfield.runtime', 'number', 'entrySingleNum', '`runtime` int(10) NULL, ADD INDEX (`runtime`)', '', NULL, 'int 10', '2020-07-26 07:22:24', '2024-04-20 07:53:40', NULL, 1, 1, 'rw-r--r--'), -(24, 'imdbrating', 'sysfield.imdbrating', 'text', 'entrySingleText', '`imdbrating` varchar(128) NULL DEFAULT NULL', '', NULL, 'string 128', '2020-12-27 10:00:33', '2024-04-20 07:54:05', 0, 1, 1, 'rw-r--r--'), +(24, 'imdbrating', 'sysfield.imdbrating', 'text', 'entrySingleText', '`imdbrating` varchar(4) NULL DEFAULT NULL', '', NULL, 'string 4', '2020-12-27 10:00:33', '2024-04-20 07:54:05', 0, 1, 1, 'rw-r--r--'), (25, 'viewcount', 'sysfield.viewcount', 'number', 'entrySingleNum', '`viewcount` int(10) NULL, ADD INDEX (`viewcount`)', '', NULL, 'int 10', '2020-12-27 10:41:10', '2024-04-20 07:54:29', 0, 1, 1, 'rw-r--r--'), (26, 'writers', 'sysfield.writers', 'lookupmultiple', 'tag', NULL, 'allowSpace', NULL, 'string 64', '2021-01-05 09:47:20', '2024-04-20 07:54:53', NULL, 1, 1, 'rw-r--r--'), (27, 'localizedTitle', 'sysfield.localizedTitle', 'text', 'entryText', '`localizedTitle` varchar(128) NULL DEFAULT NULL, ADD FULLTEXT (`localizedTitle`)', '', NULL, 'string 128', '2021-04-25 19:33:31', '2024-04-20 07:55:22', 0, 1, 1, 'rw-r--r--'), diff --git a/documentation/tool-imdbweb.md b/documentation/tool-imdbweb.md index 2a9f0cd..0b8358a 100644 --- a/documentation/tool-imdbweb.md +++ b/documentation/tool-imdbweb.md @@ -3,9 +3,7 @@ It provides easy searching for a movie entry within https://www.imdb.com. Creating a new one with the selected fields or updating an existing one. -It uses: https://github.com/FabianBeiner/PHP-IMDB-Grabber with some modifications - -You can search for words or the exact imdb id, which is in tt* format of an entry url. +You can search the exact imdb id, which is in tt* format of an entry url. It searches for movies only. Which fields are available for you to select, are configured in the config.imdbweb.php file to make it diff --git a/sources/imdb.class.php.txt b/sources/imdb.class.php.txt index f19db26..93a2a9b 100644 --- a/sources/imdb.class.php.txt +++ b/sources/imdb.class.php.txt @@ -13,7 +13,7 @@ * @author Fabian Beiner * @license https://opensource.org/licenses/MIT The MIT License * @link https://github.com/FabianBeiner/PHP-IMDB-Grabber/ GitHub Repository - * @version 6.2.6 + * @version 6.2.7 */ class IMDB { @@ -58,7 +58,7 @@ class IMDB const IMDB_AWARDS = '~Awards.*span\sclass.*>(.*)~Uis'; const IMDB_BUDGET = '~budget.*\s*(.*)(?:\s*\(estimated\))\s*~Ui'; const IMDB_CAST = '~<\/div>]*><\/a>\s+<\/td>\s+)?]*itemprop="actor"[^>]*>\s*]*>\s*(.+)<\/span+~Uis'; + const IMDB_CAST_IMAGE = '~]*src="([^"]+)"[^>]*srcset="([^"]+)"~is'; const IMDB_CERTIFICATION = '~\?certificates=.*ref_=ttrv_stry">(.+)(?:|]+\/parentalguide\/[^>]+>)~Ui'; const IMDB_CHAR = '~\/characters\/nm\d+\/.*>(.*)<\/a>~Ui'; const IMDB_COLOR = '~href="/search/title/\?colors(?:.*)">(.*)<\/a>~Ui'; @@ -228,6 +228,7 @@ class IMDB private function fetchUrl($sSearch, $orgSearch = false, $exactSearch = false) { $sSearch = trim($sSearch); + $sRedirectFile = null; // Try to find a valid URL. $sId = IMDBHelper::matchRegex($sSearch, self::IMDB_ID, 1); @@ -826,59 +827,101 @@ class IMDB * * @return array Array with cast name as key, and image as value. */ - public function getCastImages($iLimit = 0, $bMore = true, $sSize = 'small', $bDownload = false) - { + public function getCastImages($iLimit = 0, $bMore = true, $sSize = 'small', $bDownload = false) { if (true === $this->isReady) { $aMatch = IMDBHelper::matchRegex($this->sSource, self::IMDB_CAST_IMAGE); $aReturn = []; - if (count($aMatch[4])) { - foreach ($aMatch[4] as $i => $sName) { + + if (count($aMatch[1])) { + foreach ($aMatch[1] as $i => $sName) { if (0 !== $iLimit && $i >= $iLimit) { break; } - $sMatch = $aMatch[2][$i]; - if ('big' === strtolower($sSize) && false !== strstr($aMatch[2][$i], '@._')) { - $sMatch = substr($aMatch[2][$i], 0, strpos($aMatch[2][$i], '@._')) . '@.jpg'; - } elseif ('mid' === strtolower($sSize) && false !== strstr($aMatch[2][$i], '@._')) { - $sMatch = substr($aMatch[2][$i], 0, strpos($aMatch[2][$i], '@._')) . '@._V1_UX214_AL_.jpg'; + // Parse srcset to get different image sizes + $sSrcset = $aMatch[3][$i]; + $aSrcsetParts = explode(',', $sSrcset); + + $sMatch = $aMatch[2][$i]; // default to src + + // Extract image URLs from srcset based on size preference + if ('big' === strtolower($sSize)) { + // Get the largest image (280w) + foreach ($aSrcsetParts as $sPart) { + if (strpos($sPart, '280w') !== false) { + preg_match('~(https?://[^\s]+)~', $sPart, $aUrl); + if ( ! empty($aUrl[1])) { + $sMatch = $aUrl[1]; + } + } + } + } + elseif ('mid' === strtolower($sSize)) { + // Get the medium image (210w) + foreach ($aSrcsetParts as $sPart) { + if (strpos($sPart, '210w') !== false) { + preg_match('~(https?://[^\s]+)~', $sPart, $aUrl); + if ( ! empty($aUrl[1])) { + $sMatch = $aUrl[1]; + } + } + } + } + else { + // Small - use the 140w image or default src + foreach ($aSrcsetParts as $sPart) { + if (strpos($sPart, '140w') !== false) { + preg_match('~(https?://[^\s]+)~', $sPart, $aUrl); + if ( ! empty($aUrl[1])) { + $sMatch = $aUrl[1]; + } + } + } } if (false === $bDownload) { $sMatch = IMDBHelper::cleanString($sMatch); - } else { - $sLocal = IMDBHelper::saveImageCast($sMatch, $aMatch[3][$i]); + } + else { + // Create a sanitized filename using hash + $sFileHash = md5($sMatch); + $sExtension = '.jpg'; // IMDB images are typically JPG + + // Sanitize actor name for use in filename (optional, can use hash only) + $sSafeName = preg_replace('/[^a-z0-9_-]/i', '_', $sName); + $sSafeName = substr($sSafeName, 0, 50); // Limit length + + // Option 1: Use hash only + $sFilename = $sFileHash . $sExtension; + + // Option 2: Use sanitized name + hash (uncomment to use this instead) + // $sFilename = $sSafeName . '_' . substr($sFileHash, 0, 8) . $sExtension; + + $sLocal = IMDBHelper::saveImageCast($sMatch, $sFilename); + if (file_exists(dirname(__FILE__) . '/' . $sLocal)) { $sMatch = $sLocal; - } else { - //the 'big' image isn't available, try the 'mid' one (vice versa) - if ('big' === strtolower($sSize) && false !== strstr($aMatch[2][$i], '@._')) { - //trying the 'mid' one - $sMatch = substr( - $aMatch[2][$i], - 0, - strpos($aMatch[2][$i], '@._') - ) . '@._V1_UX214_AL_.jpg'; - } else { - //trying the 'big' one - $sMatch = substr($aMatch[2][$i], 0, strpos($aMatch[2][$i], '@._')) . '@.jpg'; - } + } + else { + // Fallback to default src if download fails + $sFileHash = md5($aMatch[2][$i]); + $sFilename = $sFileHash . $sExtension; - $sLocal = IMDBHelper::saveImageCast($sMatch, $aMatch[3][$i]); + $sLocal = IMDBHelper::saveImageCast($aMatch[2][$i], $sFilename); if (file_exists(dirname(__FILE__) . '/' . $sLocal)) { $sMatch = $sLocal; - } else { + } + else { $sMatch = IMDBHelper::cleanString($aMatch[2][$i]); } } } - $aReturn[IMDBHelper::cleanString($aMatch[4][$i])] = $sMatch; + $aReturn[IMDBHelper::cleanString($sName)] = $sMatch; } - $bMore = (0 !== $iLimit && $bMore && (count($aMatch[4]) > $iLimit) ? '…' : ''); - - $bHaveMore = ($bMore && (count($aMatch[4]) > $iLimit)); + $bMore = (0 !== $iLimit && $bMore && (count($aMatch[1]) > $iLimit) ? '…' : ''); + $bHaveMore = ($bMore && (count($aMatch[1]) > $iLimit)); $aReturn = array_replace( $aReturn, diff --git a/upgrade/from-version-1.8.md b/upgrade/from-version-1.8.md new file mode 100644 index 0000000..f774337 --- /dev/null +++ b/upgrade/from-version-1.8.md @@ -0,0 +1,23 @@ +# Config change + +Updated BROWSER_AGENT string. See default config file for its new value. + +# DB changes. + +Run each line against your bibliotheca DB. + +Replace #REPLACEME# with your table prefix. Default is bib + +``` +UPDATE `#REPLACEME#_sys_fields` SET `createstring` = '`imdbrating` varchar(4) NULL DEFAULT NULL', `apiinfo` = 'string 4' WHERE `#REPLACEME#_sys_fields`.`identifier` = 'imdbrating'; +``` + +# Remove not needed folders and files + +If in `PATH_SYSTEMOUT` following folders are present, you can delete them. + +``` +cache\ +cast\ +posters\ +``` diff --git a/webclient/config/config-imdbweb.php.default b/webclient/config/config-imdbweb.php.default index 030e5c2..1692bda 100644 --- a/webclient/config/config-imdbweb.php.default +++ b/webclient/config/config-imdbweb.php.default @@ -2,7 +2,7 @@ /** * Bibliotheca * - * Copyright 2018-2024 Johannes Keßler + * Copyright 2018-2026 Johannes Keßler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +15,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0. + * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0 */ /** @@ -24,33 +24,27 @@ * TOOL_IMDBWEB_SEARCH can be 'movie = 'tv = 'episode = 'game = 'all'. Default is movie * TOOL_IMDBWEB_FIELDS is an array to define which fields from IMDB are displayed for selection * an empty array() shows all. - * getAka, getAkas, getAspectRatio, getAwards, getBudget, getCast, getCastAndCharacter, CastAndCharacterAsUrl, - * getCastAsUrl, getCertification, getColor, getCompany, getCompanyAsUrl, getCountry, getCountryAsUrl, - * getCreator, getCreatorAsUrl, getDescription, getDirector, getDirectorAsUrl, getGenre, getGenreAsUrl, - * getGross, getLanguage, getLanguageAsUrl, getLocation, getLocationAsUrl, getLocations, getMpaa, getPlot, - * getPlotKeywords, getPoster, getRating, getRatingCount, getReleaseDate, getReleaseDates, getRuntime, - * getSeasons, getSeasonsAsUrl, getSoundMix, getTagline, getTitle, getTrailerAsUrl, getUrl, getUserReview, - * getVotes, getWriter, getWriterAsUrl, getYear + * runtime, rating, genres, title, originalTitle, year, plot, synopses, + * directors, writers, cast * TOOL_IMDBWEB_FIELDS_TO is a array to define which imdbwebfield (see TOOL_IMDBWEB_FIELDS) should be saved into * a bibliotheca field. Those or the fields a collection can have. Use the identifier of a field. * Depends on your settings so make sure everything is setup first. Leave it commented if not needed. * nameFromService => bibFieldName * TOOL_IMDBWEB_BROWSERSTRING a current browser agent string. Should be updated from time to time. See default config file. * TOOL_IMDBWEB_BROWSER_ACCEPT_LANG should define in which language the content returns -*/ -const TOOL_IMDBWEB_SEARCH = 'movie'; -const TOOL_IMDBWEB_FIELDS = array( - 'getCast', 'getDescription', 'getDirector', 'getGenre', 'getPlot', 'getRating', 'getRuntime', 'getTitle', - 'getWriter', 'getYear' + */ +const TOOL_IMDBWEB_ATTRIBUTES = array( + 'runtime', 'rating', 'genres', 'title', 'originalTitle', 'year', 'plot', 'synopses', 'directors', + 'writers', 'cast', 'coverImage' ); -/* const TOOL_IMDBWEB_FIELDS_TO = array( - 'getCast' => 'actors', 'getDescription' => 'description', 'getDirector' => 'directors', 'getGenre' => 'genres', - 'getPlot' => 'content', 'getRating' => 'imdbrating', 'getRuntime' => 'runtime', 'getTitle' => 'title', - 'getWriter' => 'writers', 'getYear' => 'year' + 'cast' => 'actors', 'synopses' => 'content', 'directors' => 'directors', 'genres' => 'genres', + 'plot' => 'description', 'rating' => 'imdbrating', 'runtime' => 'runtime', 'originalTitle' => 'title', + 'writers' => 'writers', 'year' => 'year', 'title' => 'localizedTitle', 'coverImage' => 'coverimage' ); -*/ +// reuse existing constants but define it to the tool const TOOL_IMDBWEB_BROWSER_AGENT = BROWSER_AGENT; const TOOL_IMDBWEB_BROWSER_ACCEPT = BROWSER_ACCEPT; const TOOL_IMDBWEB_BROWSER_ACCEPT_LANG = BROWSER_ACCEPT_LANG; +const TOOL_IMDBWEB_CACHE_STORAGE = PATH_SYSTEMOUT.'/imdb'; diff --git a/webclient/config/config.php.default b/webclient/config/config.php.default index dfc2ee5..439827b 100644 --- a/webclient/config/config.php.default +++ b/webclient/config/config.php.default @@ -2,7 +2,7 @@ /** * Bibliotheca * - * Copyright 2018-2024 Johannes Keßler + * Copyright 2018-2026 Johannes Keßler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -75,6 +75,6 @@ const ANON_GROUP_ID = '3'; const RESULTS_PER_PAGE = 24; # CURL browser settings -const BROWSER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0'; +const BROWSER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:146.0) Gecko/20100101 Firefox/146.0'; const BROWSER_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'; const BROWSER_ACCEPT_LANG = 'en-US,en;q=0.5'; diff --git a/webclient/i18n/deu.ini b/webclient/i18n/deu.ini index b706dee..784ad36 100644 --- a/webclient/i18n/deu.ini +++ b/webclient/i18n/deu.ini @@ -23,14 +23,14 @@ global.add = "Hinzufügen" global.ascending = "Aufsteigend" global.clear = "Leeren" -global.clickremove = "Anwählen zum Entfernen" +global.clickremove = "Anwählen zum Löschen" global.close = "Schließen" global.collection = "Sammlung" global.collection.select.notice = "Zuerst eine Sammlung wählen" global.collections = "Sammlungen" global.created = "Erstellt" global.default = "Standard" -global.delete = "Entfernen" +global.delete = "Löschen" global.descending = "Absteigend" global.description = "Beschreibung" global.edit = "Bearbeiten" @@ -282,10 +282,13 @@ tool.googlebooks.search = reuse.global.search tool.googlebooks.searchIsbn = "ISBN Suche" tool.googlebooks.select = reuse.global.none tool.headline.using = "Verwendung von %s in Sammlung: %s" +tool.imdb.fetch = "Datenbezug" +tool.imdb.imdbid = "IMDB ID" tool.imdb.posters = "Film Poster" tool.imdb.save = reuse.global.save tool.imdb.search = reuse.global.search tool.imdb.search.hint = "Suche funktioniert mit der IMDB Film ID. Auf IMDB manuell suchen und davon die ID (tt*) aus der URL nehmen." +tool.imdb.search.missingid = "IMDB Suchinformation fehlen." tool.imdb.select.none = reuse.global.none tool.limitations = "Einschränkungen" tool.limitations.override = "Daten werden überschrieben." diff --git a/webclient/i18n/eng.ini b/webclient/i18n/eng.ini index df48881..1e43e49 100644 --- a/webclient/i18n/eng.ini +++ b/webclient/i18n/eng.ini @@ -282,10 +282,13 @@ tool.googlebooks.search = reuse.global.search tool.googlebooks.searchIsbn = "Search ISBN" tool.googlebooks.select = reuse.global.none tool.headline.using = "Using %s with collection: %s" +tool.imdb.fetch = "Fetch" +tool.imdb.imdbid = "IMDB ID" tool.imdb.posters = "Movie posters" tool.imdb.save = reuse.global.save tool.imdb.search = reuse.global.search tool.imdb.search.hint = "Seach works with the imdb movie ID (the tt* in the url) after searching manually on imdb itself." +tool.imdb.search.missingid = "IMDB search result information missing." tool.imdb.select.none = reuse.global.none tool.limitations = "Limitations" tool.limitations.override = "Data will be overwritten" diff --git a/webclient/index.php b/webclient/index.php index cf25151..53a92fd 100644 --- a/webclient/index.php +++ b/webclient/index.php @@ -2,7 +2,7 @@ /** * Bibliotheca * - * Copyright 2018-2025 Johannes Keßler + * Copyright 2018-2026 Johannes Keßler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,7 @@ require_once './config/config.php'; -const BIB_VERSION = '1.x - Harobed Village'; +const BIB_VERSION = '1.8.1 - Harobed Village'; mb_http_output('UTF-8'); mb_internal_encoding('UTF-8'); diff --git a/webclient/lib/googlebookparser.class.php b/webclient/lib/googlebookparser.class.php index 4858219..79e6843 100644 --- a/webclient/lib/googlebookparser.class.php +++ b/webclient/lib/googlebookparser.class.php @@ -2,7 +2,7 @@ /** * Bibliotheca * - * Copyright 2018-2023 Johannes Keßler + * Copyright 2018-2026 Johannes Keßler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -226,5 +226,4 @@ class GoogleBooks { return $ret; } - } diff --git a/webclient/lib/i18n.class.php b/webclient/lib/i18n.class.php index 842446d..0fc2372 100644 --- a/webclient/lib/i18n.class.php +++ b/webclient/lib/i18n.class.php @@ -2,7 +2,7 @@ /** * Bibliotheca * - * Copyright 2018-2024 Johannes Keßler + * Copyright 2018-2026 Johannes Keßler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,7 +15,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0. + * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0 */ class I18n { diff --git a/webclient/lib/imdbweb.class.php b/webclient/lib/imdbweb.class.php new file mode 100644 index 0000000..117264e --- /dev/null +++ b/webclient/lib/imdbweb.class.php @@ -0,0 +1,414 @@ +_DEBUG = true; + } + + $this->_CACHE_DIR = $options['storage']; + if (!is_writable($this->_CACHE_DIR) && !mkdir($this->_CACHE_DIR)) { + Summoner::sysLog("[ERROR] Missing directory or write access: ".$this->_CACHE_DIR); + } + + $this->_BROWSER_AGENT = $options['browserAgent']; + $this->_BROWSER_LANG = $options['browserLang']; + $this->_BROWSER_ACCEPT = $options['browserAccept']; + + $this->_attributes = array(); + if (isset($options['attributes']) && !empty($options['attributes'])) { + $this->_attributes = $options['attributes']; + } + } + + /** + * Not really a search. Since this does only really work with a given imdb ID an exact + * URL is called and processed. + * Decides if the data will called of cache is used. + * Does return the data based on $this->_attributes + * + * @param string $search + * @return array + */ + public function search(string $search): array { + $ret = array(); + + if (empty($search)) return $ret; + $url = sprintf($this->_FULLCREDITS_URL, $search); + if ($this->_DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." using url : " . $url); + + // Does a cache of this movie exist? + $_cacheFile = $this->_CACHE_DIR.'/'.md5($url).'.cache'; + $_htmlData = ""; + if (is_readable($_cacheFile)) { + $_timeDiff = round(abs(time() - filemtime($_cacheFile)) / 60); + if (($_timeDiff < $this->_CACHE_TIME) && !$this->_DEBUG) { + $_htmlData = file_get_contents($_cacheFile); + } else { + $_htmlData = $this->_curlCall($url); + if (!empty($_htmlData)) { + file_put_contents($_cacheFile, $_htmlData); + } + } + } else { + $_htmlData = $this->_curlCall($url); + if (!empty($_htmlData)) { + file_put_contents($_cacheFile, $_htmlData); + } + } + + if(!empty($_htmlData)) { + $jsonString = $this->_extractData($_htmlData); + if(!empty($jsonString)) { + $ret = $this->_processData($jsonString); + } + } + + return $ret; + } + + /** + * Retrieve the title cover from the given URL and return a tmp file + * + * @param string $url + * @return string + */ + public function downloadCover(string $url): string { + $ret = ""; + + $_tmpFile = tempnam(sys_get_temp_dir(), "bibliotheca-"); + $fh = fopen($_tmpFile,"w+"); + if($this->_DEBUG) { + Summoner::sysLog('[DEBUG] '.__METHOD__.' url '.Summoner::cleanForLog($url)); + } + + if($fh !== false) { + + // modified curl call for fetching an image + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_FILE, $fh); + + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 3); + curl_setopt($ch, CURLOPT_USERAGENT, $this->_BROWSER_AGENT); + + curl_exec($ch); + curl_close($ch); + + $ret = $_tmpFile; + } + fclose($fh); + + return $ret; + } + + /** + * The html from $this->_REFERENCE_URL should contain a script with the id __NEXT_DATA__ + * and it holds a json object with all the information we currently need. + * + * @param string $data + * @return string + */ + private function _extractData(string $data): string { + $ret = ""; + + $m = preg_match('~id="__NEXT_DATA__".*?>([^<]+)~s', $data, $matches); + if(!empty($m)) { + $ret = $matches[1]; + } elseif($this->_DEBUG) { + Summoner::sysLog("[DEBUG] ".__METHOD__." no script with id __NEXT_DATA__ found."); + } + //Summoner::sysLog("[DEBUG] ".__METHOD__." extracted json data.".Summoner::cleanForLog($ret)); + return $ret; + } + + /** + * Take the given jsonString and extract the wanted information defined by $this->_attributes + * + * @param string $jsonString + * @return array + */ + private function _processData(string $jsonString): array { + $ret = array(); + + $jsonData = json_decode($jsonString, true); + if(!empty(json_last_error())) { + Summoner::sysLog("[DEBUG] ".__METHOD__." can not decode json. ".json_last_error_msg()); + return $ret; + } + if(isset($jsonData["props"]["pageProps"]["contentData"]["data"]["title"])) { + foreach($this->_attributes as $att) { + $_m = "_get_".$att; + if(method_exists($this, $_m)) { + $ret[$att] = $this->$_m($jsonData["props"]["pageProps"]["contentData"]["data"]["title"]); + } else { + $ret[$att] = ""; + } + } + } + + return $ret; + } + + private function _get_runtime(array $data): string { + $t = $this->_retrieveDataFromArray(array("runtime","seconds"),$data); + return floor($t / 60); + } + + private function _get_rating(array $data): string { + return $this->_retrieveDataFromArray(array("ratingsSummary","aggregateRating"), $data); + } + + private function _get_title(array $data): string { + return $this->_retrieveDataFromArray(array("titleText","text"), $data); + } + + private function _get_originalTitle(array $data): string { + return $this->_retrieveDataFromArray(array("originalTitleText","text"), $data); + } + + private function _get_year(array $data): string { + return $this->_retrieveDataFromArray(array("releaseYear","year"), $data); + } + + private function _get_plot(array $data): string { + return $this->_retrieveDataFromArray(array("plot","plotText","plainText"),$data); + } + + private function _get_synopses(array $data): string { + return $this->_retrieveDataFromArray(array("synopses","edges",0,"node","plotText","plaidHtml"), $data); + } + + private function _get_coverImage(array $data): string { + return $this->_retrieveDataFromArray(array("primaryImage","url"), $data); + } + + private function _get_genres(array $data): array { + $ret = array(); + foreach($data["titleGenres"]["genres"] as $g) { + $ret[] = $g["genre"]["text"]; + } + return $ret; + } + + /** + * Looks like there is a unique id for director(s) + * amzn1.imdb.concept.name_credit_category.ace5cb4c-8708-4238-9542-04641e7c8171 + * + * @param array $data + * @return array + */ + private function _get_directors(array $data): array { + return $this->_resolveCreditGroupings($data, "amzn1.imdb.concept.name_credit_category.ace5cb4c-8708-4238-9542-04641e7c8171"); + } + + /** + * Looks like there is a unique id for writer(s) + * amzn1.imdb.concept.name_credit_category.c84ecaff-add5-4f2e-81db-102a41881fe3 + * + * @param array $data + * @return array + */ + private function _get_writers(array $data): array { + return $this->_resolveCreditGroupings($data, "amzn1.imdb.concept.name_credit_category.c84ecaff-add5-4f2e-81db-102a41881fe3"); + } + + /** + * Looks like there is a unique id for the cast + * amzn1.imdb.concept.name_credit_group.7caf7d16-5db9-4f4f-8864-d4c6e711c686 + * + * @param array $data + * @return array + */ + private function _get_cast(array $data): array { + return $this->_resolveCreditGroupings($data, "amzn1.imdb.concept.name_credit_group.7caf7d16-5db9-4f4f-8864-d4c6e711c686"); + } + + /** + * Extract the information from the creditGroupings array. + * + * @param array $data + * @param string $gId + * @return array + */ + private function _resolveCreditGroupings(array $data, string $gId): array { + $ret = array(); + foreach($data["creditGroupings"]["edges"] as $edge) { + $_gid = $this->_retrieveDataFromArray(array("node", "grouping", "groupingId"), $edge); + if($_gid == $gId) { + foreach($edge["node"]["credits"]["edges"] as $e) { + $ret[] = $this->_retrieveDataFromArray(array("node", "name", "nameText", "text"), $e); + } + break; + } + } + return $ret; + } + + /** + * execute a curl call to the given $url + * + * @param string $url The request url + * @return string + */ + private function _curlCall(string $url): string { + $ret = ''; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 2); + curl_setopt($ch, CURLOPT_USERAGENT, $this->_BROWSER_AGENT); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Accept: ' . $this->_BROWSER_ACCEPT, + 'Accept-Charset: utf-8, iso-8859-1;q=0.5', + 'Accept-Language: ' . $this->_BROWSER_LANG) + ); + curl_setopt($ch, CURLOPT_REFERER, 'https://www.imdb.com'); + + if ($this->_DEBUG) { + $_headers = array(); + curl_setopt($ch, CURLOPT_VERBOSE, true); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, + function ($curl, $header) use (&$_headers) { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) { // ignore invalid headers + return $len; + } + $_headers[strtolower(trim($header[0]))][] = trim($header[1]); + return $len; + } + ); + } + + $do = curl_exec($ch); + if (!curl_errno($ch)) { + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($http_code == 200) { + if (is_string($do) === true) { + $ret = $do; + } + } + } + curl_close($ch); + + if ($this->_DEBUG) { + Summoner::sysLog('[DEBUG] ' . __METHOD__ . ' headers ' . Summoner::cleanForLog($_headers)); + } + + return $ret; + } + + /** + * Accessing a nested array can fail if a nested key is not present. + * Checks every given key, nested, for availablity + * + * @param array $toGet + * @param array $data + * @return mixed + */ + private function _retrieveDataFromArray(array $toGet, array $data): mixed { + $ret = ""; + + foreach($toGet as $key) { + if(isset($data[$key])) { + $data = $data[$key]; + $ret = $data; + } else { + $ret = ""; // reset otherwise the last found will be returned + } + } + + return $ret; + } +} diff --git a/webclient/lib/imdbwebparser.class.php b/webclient/lib/imdbwebparser.class.php deleted file mode 100644 index fb93a71..0000000 --- a/webclient/lib/imdbwebparser.class.php +++ /dev/null @@ -1,1850 +0,0 @@ - - * @license https://opensource.org/licenses/MIT The MIT License - * @link https://github.com/FabianBeiner/PHP-IMDB-Grabber/ GitHub Repository - * @version 6.2.6 - * - * - * Functionality is the same but modified heavily to remove the does-not-make-sense static helper - * which was not static since it depended on the IMDB class. Also some could not be extended or overwritten - * - */ -class IMDB -{ - /** - * Set this to true if you run into problems. - */ - private bool $IMDB_DEBUG = true; - - /** - * @var string Set the preferred language for the User Agent. - */ - private string $IMDB_BROWSER_LANG; - - /** - * Set this to true if you want to start with normal search and - * if you get no result, it will use the advanced method - */ - const IMDB_SEARCH_ORIGINAL = true; - - /** - * Set this to true if you want to search for exact titles - * it falls back to false if theres no result - */ - const IMDB_EXACT_SEARCH = true; - - /** - * Set the sensitivity for search results in percentage. - */ - const IMDB_SENSITIVITY = 85; - - /** - * @var string The accept string for curl call - */ - private string $IMDB_BROWSER_ACCEPT; - - /** - * @var string The user-agent string fpr curl call - */ - private string $IMDB_BROWSER_AGENT; - - /** - * Define the timeout for cURL requests. - */ - private int $IMDB_TIMEOUT = 15; - - /** - * These are the regular expressions used to extract the data. - * If you don’t know what you’re doing, you shouldn’t touch them. - */ - const IMDB_AKA = '~=ttrv_dt_aka.*]+>(.*)~Ui'; - const IMDB_ASPECT_RATIO = '~aspect ratio.*(.*)~Uis'; - const IMDB_AWARDS = '~Awards.*span\sclass.*>(.*)~Uis'; - const IMDB_BUDGET = '~budget.*\s*(.*)(?:\s*\(estimated\))\s*~Ui'; - const IMDB_CAST = '~<\/div>]*><\/a>\s+<\/td>\s+)?]*itemprop="actor"[^>]*>\s*]*>\s*(.+)<\/span+~Uis'; - const IMDB_CERTIFICATION = '~\?certificates=.*ref_=ttrv_stry">(.+)(?:|]+\/parentalguide\/[^>]+>)~Ui'; - const IMDB_CHAR = '~\/characters\/nm\d+\/.*>(.*)<\/a>~Ui'; - const IMDB_COLOR = '~href="/search/title/\?colors(?:.*)">(.*)<\/a>~Ui'; - const IMDB_COMPANIES = '~id="production">[\w\s]*?(.*)~Ui'; - const IMDB_COMPANY = '~href="/company/(co\d+)/\?ref_=ttrv_cmpy_\d">([^IMDB_DEBUG = true; - } - - if(isset($options['iCache']) && !empty($options['iCache'])) $this->iCache = (int) $options['iCache']; - - $this->sRoot = dirname(__FILE__); - if(isset($options['storage']) && !empty($options['storage'])) { - $this->sRoot = $options['storage']; - } - - if(isset($options['sSearchFor']) && !empty($options['sSearchFor'])) { - if (in_array( - $options['sSearchFor'], - [ - 'movie', - 'tv', - 'episode', - 'game', - 'documentary', - 'all', - ] - )) { - $this->sSearchFor = $options['sSearchFor']; - } - } - - $this->IMDB_BROWSER_AGENT = $options['browserAgent']; - $this->IMDB_BROWSER_LANG = $options['browserLang']; - $this->IMDB_BROWSER_ACCEPT = $options['browserAccept']; - - $this->_showFields = array(); - if(isset($options['showFields']) && !empty($options['showFields'])) { - $this->_showFields = $options['showFields']; - } - } - - - /** - * @param string $sSearch - * @throws Exception - */ - public function search(string $sSearch): void { - - $sSearch = trim($sSearch); - if(empty($sSearch)) { - throw new Exception('Missing search term'); - } - - if ( ! is_writable($this->sRoot . '/posters') && ! mkdir($this->sRoot . '/posters')) { - throw new Exception('The directory “' . $this->sRoot . '/posters” isn’t writable.'); - } - if ( ! is_writable($this->sRoot . '/cache') && ! mkdir($this->sRoot . '/cache')) { - throw new Exception('The directory “' . $this->sRoot . '/cache” isn’t writable.'); - } - if ( ! is_writable($this->sRoot . '/cast') && ! mkdir($this->sRoot . '/cast')) { - throw new Exception('The directory “' . $this->sRoot . '/cast” isn’t writable.'); - } - - if ( ! function_exists('curl_init')) { - throw new Exception('You need to enable the PHP cURL extension.'); - } - - $this->fetchUrl($sSearch); - } - - /** - * @param string $sSearch IMDb URL or movie title to search for. - * - * @return bool True on success, false on failure. - */ - private function fetchUrl(string $sSearch): bool { - - if ($this->IMDB_DEBUG) { - echo '
Running: fetchUrl("' . $sSearch . '")
'; - } - - // Try to find a valid URL. - $sId = $this->matchRegex($sSearch, self::IMDB_ID, "1"); - if (false !== $sId) { - $this->iId = preg_replace('~[\D]~', '', $sId); - $this->sUrl = 'https://www.imdb.com/title/tt' . $this->iId . '/reference'; - $bSearch = false; - } else { - switch (strtolower($this->sSearchFor)) { - case 'movie': - $sParameters = '&s=tt&ttype=ft'; - break; - case 'tv': - $sParameters = '&s=tt&ttype=tv'; - break; - case 'episode': - $sParameters = '&s=tt&ttype=ep'; - break; - case 'game': - $sParameters = '&s=tt&ttype=vg'; - break; - default: - $sParameters = '&s=tt'; - } - - $this->sUrl = 'https://www.imdb.com/find/?q=' . rawurlencode(str_replace(' ', '+', $sSearch)) . $sParameters; - $bSearch = true; - - // Was this search already performed and cached? - $sRedirectFile = $this->sRoot . '/cache/' . sha1($this->sUrl) . '.redir'; - if (is_readable($sRedirectFile)) { - if ($this->IMDB_DEBUG) { - echo '
Using redirect: ' . basename($sRedirectFile) . '
'; - } - $sRedirect = file_get_contents($sRedirectFile); - $this->sUrl = trim($sRedirect); - $this->iId = preg_replace('~[\D]~', '', $this->matchRegex($sRedirect, self::IMDB_ID, "1")); - $bSearch = false; - } - } - - // Does a cache of this movie exist? - if(!empty($this->iId)) { - $sCacheFile = $this->sRoot . '/cache/' . sha1($this->iId) . '.cache'; - if (is_readable($sCacheFile)) { - $iDiff = round(abs(time() - filemtime($sCacheFile)) / 60); - if ($iDiff < $this->iCache) { - if ($this->IMDB_DEBUG) { - echo '
Using cache: ' . basename($sCacheFile) . '
'; - } - $this->sSource = file_get_contents($sCacheFile); - $this->isReady = true; - - return true; - } - } - } - - // Run cURL on the URL. - if ($this->IMDB_DEBUG) { - echo '
Running cURL: ' . $this->sUrl . '
'; - } - - $aCurlInfo = $this->runCurl($this->sUrl); - $sSource = isset($aCurlInfo['contents']) ? $aCurlInfo['contents'] : false; - - if (false === $sSource) { - if ($this->IMDB_DEBUG) { - echo '
cURL error: ' . var_dump($aCurlInfo) . '
'; - } - - return false; - } - - // Was the movie found? - $sMatch = $this->matchRegex($sSource, self::IMDB_SEARCH_ADV, "1"); - if (false !== $sMatch) { - $sUrl = 'https://www.imdb.com/title/' . $sMatch . '/reference'; - if ($this->IMDB_DEBUG) { - echo '
New redirect saved: ' . basename($sRedirectFile) . ' => ' . $sUrl . '
'; - } - file_put_contents($sRedirectFile, $sUrl); - $this->sSource = ''; - $this->fetchUrl($sUrl); - - return true; - } - $sMatch = $this->matchRegex($sSource, self::IMDB_NOT_FOUND_ADV, "0"); - if (false !== $sMatch) { - if ($this->IMDB_DEBUG) { - echo '
Movie not found: ' . $sSearch . '
'; - } - - return false; - } - - $this->sSource = str_replace( - [ - "\n", - "\r\n", - "\r", - ], - '', - $sSource - ); - $this->isReady = true; - - // Save cache. - if (false === $bSearch) { - if ($this->IMDB_DEBUG) { - echo '
Cache created: ' . basename($sCacheFile) . '
'; - } - file_put_contents($sCacheFile, $this->sSource); - } - - return true; - } - - /** - * @return array All data. - */ - public function getAll(): array { - $aData = []; - foreach (get_class_methods(__CLASS__) as $method) { - if (substr($method, 0, 3) === 'get' && $method !== 'getAll' && $method !== 'getCastImages') { - if(!empty($this->_showFields) && !in_array($method,$this->_showFields)) continue; - $aData[$method] = [ - 'name' => ltrim($method, 'get'), - 'value' => $this->{$method}(), - ]; - } - } - array_multisort($aData); - - return $aData; - } - - /** - * @return string “Also Known As” or $sNotFound. - */ - public function getAka(): string { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_AKA, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * Returns all local names - * - * @return string All local names. - */ - public function getAkas() - { - if (true === $this->isReady) { - // Does a cache of this movie exist? - $sCacheFile = $this->sRoot . '/cache/' . sha1($this->iId) . '_akas.cache'; - $bUseCache = false; - - if (is_readable($sCacheFile)) { - $iDiff = round(abs(time() - filemtime($sCacheFile)) / 60); - if ($iDiff < $this->iCache || false) { - $bUseCache = true; - } - } - - if ($bUseCache) { - $aRawReturn = file_get_contents($sCacheFile); - $aReturn = unserialize($aRawReturn); - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } else { - $fullAkas = sprintf('https://www.imdb.com/title/tt%s/releaseinfo/', $this->iId); - $aCurlInfo = $this->runCurl($fullAkas); - $sSource = $aCurlInfo['contents']; - - if (false === $sSource) { - if ($this->IMDB_DEBUG) { - echo '
cURL error: ' . var_dump($aCurlInfo) . '
'; - } - - return false; - } - - $aReturned = $this->matchRegex($sSource, "~(.*?)<\/td>\s+(.*?)<\/td>~"); - - if ($aReturned) { - $aReturn = []; - foreach ($aReturned[1] as $i => $strName) { - if (strpos($strName, '(') === false) { - $aReturn[] = [ - 'title' => $this->cleanString($aReturned[2][$i]), - 'country' => $this->cleanString($strName), - ]; - } - } - - file_put_contents($sCacheFile, serialize($aReturn)); - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string “Aspect Ratio” or $sNotFound. - */ - public function getAspectRatio() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_ASPECT_RATIO, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string The awards of the movie or $sNotFound - */ - public function getAwards() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_AWARDS, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param int $iLimit How many cast members should be returned? - * @param bool $bMore Add … if there are more cast members than printed. - * @param string $sTarget Add a target to the links? - * - * @return string A list with linked cast members or $sNotFound. - */ - public function getCastAsUrl($iLimit = 0, $bMore = true, $sTarget = '') - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_CAST); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - if (0 !== $iLimit && $i >= $iLimit) { - break; - } - $aReturn[] = '
' . $this->cleanString( - $sName - ) . ''; - } - - $bHaveMore = ($bMore && (count($aMatch[2]) > $iLimit)); - - return $this->arrayOutput( - $this->bArrayOutput, - $this->sSeparator, - $this->sNotFound, - $aReturn, - $bHaveMore - ); - } - } - - return $this->sNotFound; - } - - /** - * @param int $iLimit How many cast members should be returned? - * @param bool $bMore Add … if there are more cast members than printed. - * - * @return string A list with cast members or $sNotFound. - */ - public function getCast($iLimit = 0, $bMore = true) - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_CAST); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - if (0 !== $iLimit && $i >= $iLimit) { - break; - } - $aReturn[] = $this->cleanString($sName); - } - - $bMore = (0 !== $iLimit && $bMore && (count($aMatch[2]) > $iLimit) ? '…' : ''); - - $bHaveMore = ($bMore && (count($aMatch[2]) > $iLimit)); - - return $this->arrayOutput( - $this->bArrayOutput, - $this->sSeparator, - $this->sNotFound, - $aReturn, - $bHaveMore - ); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @param int $iLimit How many cast images should be returned? - * @param bool $bMore Add … if there are more cast members than printed. - * @param string $sSize small, mid or big cast images - * @param bool $bDownload Return URL or Download - * - * @return array Array with cast name as key, and image as value. - */ - public function getCastImages($iLimit = 0, $bMore = true, $sSize = 'small', $bDownload = false) - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_CAST_IMAGE); - $aReturn = []; - if (count($aMatch[4])) { - foreach ($aMatch[4] as $i => $sName) { - if (0 !== $iLimit && $i >= $iLimit) { - break; - } - $sMatch = $aMatch[2][$i]; - - if ('big' === strtolower($sSize) && false !== strstr($aMatch[2][$i], '@._')) { - $sMatch = substr($aMatch[2][$i], 0, strpos($aMatch[2][$i], '@._')) . '@.jpg'; - } elseif ('mid' === strtolower($sSize) && false !== strstr($aMatch[2][$i], '@._')) { - $sMatch = substr($aMatch[2][$i], 0, strpos($aMatch[2][$i], '@._')) . '@._V1_UX214_AL_.jpg'; - } - - if (false === $bDownload) { - $sMatch = $this->cleanString($sMatch); - } else { - $sLocal = $this->saveImageCast($sMatch, $aMatch[3][$i]); - if (file_exists(dirname(__FILE__) . '/' . $sLocal)) { - $sMatch = $sLocal; - } else { - //the 'big' image isn't available, try the 'mid' one (vice versa) - if ('big' === strtolower($sSize) && false !== strstr($aMatch[2][$i], '@._')) { - //trying the 'mid' one - $sMatch = substr( - $aMatch[2][$i], - 0, - strpos($aMatch[2][$i], '@._') - ) . '@._V1_UX214_AL_.jpg'; - } else { - //trying the 'big' one - $sMatch = substr($aMatch[2][$i], 0, strpos($aMatch[2][$i], '@._')) . '@.jpg'; - } - - $sLocal = $this->saveImageCast($sMatch, $aMatch[3][$i]); - if (file_exists(dirname(__FILE__) . '/' . $sLocal)) { - $sMatch = $sLocal; - } else { - $sMatch = $this->cleanString($aMatch[2][$i]); - } - } - } - - $aReturn[$this->cleanString($aMatch[4][$i])] = $sMatch; - } - - $bMore = (0 !== $iLimit && $bMore && (count($aMatch[4]) > $iLimit) ? '…' : ''); - - $bHaveMore = ($bMore && (count($aMatch[4]) > $iLimit)); - - $aReturn = array_replace( - $aReturn, - array_fill_keys( - array_keys($aReturn, $this->sNotFound), - 'cast/not-found.jpg' - ) - ); - - return $aReturn; - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @param int $iLimit How many cast members should be returned? - * @param bool $bMore Add … if there are more cast members than - * printed. - * @param string $sTarget Add a target to the links? - * - * @return string A list with linked cast members and their character or - * $sNotFound. - */ - public function getCastAndCharacterAsUrl($iLimit = 0, $bMore = true, $sTarget = '') - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_CAST); - $aMatchChar = $this->matchRegex($this->sSource, self::IMDB_CHAR); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - if (0 !== $iLimit && $i >= $iLimit) { - break; - } - $aReturn[] = '' . $this->cleanString( - $sName - ) . ' as ' . $this->cleanString($aMatchChar[1][$i]); - } - - $bHaveMore = ($bMore && (count($aMatch[2]) > $iLimit)); - - return $this->arrayOutput( - $this->bArrayOutput, - $this->sSeparator, - $this->sNotFound, - $aReturn, - $bHaveMore - ); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @param int $iLimit How many cast members should be returned? - * @param bool $bMore Add … if there are more cast members than printed. - * - * @return string A list with cast members and their character or - * $sNotFound. - */ - public function getCastAndCharacter($iLimit = 0, $bMore = true) - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_CAST); - $aMatchChar = $this->matchRegex($this->sSource, self::IMDB_CHAR); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - if (0 !== $iLimit && $i >= $iLimit) { - break; - } - $aReturn[] = $this->cleanString($sName) . ' as ' . $this->cleanString($aMatchChar[1][$i]); - } - - $bHaveMore = ($bMore && (count($aMatch[2]) > $iLimit)); - - return $this->arrayOutput( - $this->bArrayOutput, - $this->sSeparator, - $this->sNotFound, - $aReturn, - $bHaveMore - ); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string The certification of the movie or $sNotFound. - */ - public function getCertification() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_CERTIFICATION, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string Color or $sNotFound. - */ - public function getColor() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_COLOR, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string The company producing the movie or $sNotFound. - */ - public function getCompany() - { - if (true === $this->isReady) { - $sMatch = $this->getCompanyAsUrl(); - if ($this->sNotFound !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sTarget Add a target to the links? - * - * @return string The linked company producing the movie or $sNotFound. - */ - public function getCompanyAsUrl($sTarget = '') - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_COMPANY); - if (isset($aMatch[2][0])) { - return '' . $this->cleanString( - $aMatch[2][0] - ) . ''; - } - } - - return $this->sNotFound; - } - - /** - * @return string A list with countries or $sNotFound. - */ - public function getCountry() - { - if (true === $this->isReady) { - $sMatch = $this->getCountryAsUrl(); - if ($this->sNotFound !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sTarget Add a target to the links? - * - * @return string A list with linked countries or $sNotFound. - */ - public function getCountryAsUrl($sTarget = '') - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_COUNTRY); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - $aReturn[] = '' . $this->cleanString( - $sName - ) . ''; - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string A list with the creators or $sNotFound. - */ - public function getCreator() - { - if (true === $this->isReady) { - $sMatch = $this->getCreatorAsUrl(); - if ($this->sNotFound !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sTarget Add a target to the links? - * - * @return string A list with the linked creators or $sNotFound. - */ - public function getCreatorAsUrl($sTarget = '') - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_CREATOR, "1"); - $aMatch = $this->matchRegex($sMatch, self::IMDB_NAME); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - $aReturn[] = '' . $this->cleanString( - $sName - ) . ''; - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string The description of the movie or $sNotFound. - */ - public function getDescription() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_MOVIE_DESC, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string A list with the directors or $sNotFound. - */ - public function getDirector() - { - if (true === $this->isReady) { - $sMatch = $this->getDirectorAsUrl(); - if ($this->sNotFound !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sTarget Add a target to the links? - * - * @return string A list with the linked directors or $sNotFound. - */ - public function getDirectorAsUrl($sTarget = '') - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_DIRECTOR, "1"); - $aMatch = $this->matchRegex($sMatch, self::IMDB_NAME); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - $aReturn[] = '' . $this->cleanString( - $sName - ) . ''; - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string A list with the genres or $sNotFound. - */ - public function getGenre() - { - if (true === $this->isReady) { - $sMatch = $this->getGenreAsUrl(); - if ($this->sNotFound !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sTarget Add a target to the links? - * - * @return string A list with the linked genres or $sNotFound. - */ - public function getGenreAsUrl($sTarget = '') - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_GENRE); - $aReturn = []; - if (count($aMatch[2])) { - foreach (array_unique($aMatch[2]) as $i => $sName) { - $aReturn[] = '' . $this->cleanString( - $sName - ) . ''; - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string cumulative worldwide gross or $sNotFound. - */ - public function getGross() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_GROSS, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string A list with the languages or $sNotFound. - */ - public function getLanguage() - { - if (true === $this->isReady) { - $sMatch = $this->getLanguageAsUrl(); - if ($this->sNotFound !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sTarget Add a target to the links? - * - * @return string A list with the linked languages or $sNotFound. - */ - public function getLanguageAsUrl($sTarget = '') - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_LANGUAGE); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - $aReturn[] = '' . $this->cleanString( - $sName - ) . ''; - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string A list with the location or $sNotFound. - */ - public function getLocation() - { - if (true === $this->isReady) { - $sMatch = $this->getLocationAsUrl(); - if ($this->sNotFound !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sTarget Add a target to the links? - * - * @return string A list with the linked location or $sNotFound. - */ - public function getLocationAsUrl($sTarget = '') - { - if (true === $this->isReady) { - $aMatch = $this->matchRegex($this->sSource, self::IMDB_LOCATION); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - $aReturn[] = '' . $this->cleanString( - $sName - ) . ''; - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * Returns all locations - * - * @return string location - * @return string specification - */ - public function getLocations() - { - if (true === $this->isReady) { - // Does a cache of this movie exist? - $sCacheFile = $this->sRoot . '/cache/' . sha1($this->iId) . '_locations.cache'; - $bUseCache = false; - - if (is_readable($sCacheFile)) { - $iDiff = round(abs(time() - filemtime($sCacheFile)) / 60); - if ($iDiff < $this->iCache || false) { - $bUseCache = true; - } - } - - if ($bUseCache) { - $aRawReturn = file_get_contents($sCacheFile); - $aReturn = unserialize($aRawReturn); - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } else { - $fullLocations = sprintf('https://www.imdb.com/title/tt%s/locations', $this->iId); - $aCurlInfo = $this->runCurl($fullLocations); - $sSource = $aCurlInfo['contents']; - - if (false === $sSource) { - if ($this->IMDB_DEBUG) { - echo '
cURL error: ' . var_dump($aCurlInfo) . '
'; - } - - return false; - } - - $aReturned = $this->matchRegex($sSource, self::IMDB_LOCATIONS); - - if ($aReturned) { - $aReturn = []; - foreach ($aReturned['url'] as $i => $strName) { - if (strpos($strName, '(') === false) { - $aReturn[] = [ - 'url' => IMDBHelper::cleanString($aReturned['url'][$i]), - 'location' => IMDBHelper::cleanString($aReturned['location'][$i]), - 'specification' => IMDBHelper::cleanString($aReturned['specification'][$i]), - ]; - } - } - - file_put_contents($sCacheFile, serialize($aReturn)); - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string The MPAA of the movie or $sNotFound. - */ - public function getMpaa() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_MPAA, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string A list with the plot keywords or $sNotFound. - */ - public function getPlotKeywords() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_PLOT_KEYWORDS, "1"); - if (false !== $sMatch) { - $aReturn = explode('|', $this->cleanString($sMatch)); - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @param int $iLimit The limit. - * - * @return string The plot of the movie or $sNotFound. - */ - public function getPlot($iLimit = 0) - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_PLOT, "1"); - if (false !== $sMatch) { - if ($iLimit !== 0) { - return $this->shortText($this->cleanString($sMatch), $iLimit); - } - - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sSize Small, big, xxs, xs, s poster? - * @param bool $bDownload Return URL to the poster or download it? - * - * @return bool|string Path to the poster. - */ - public function getPoster($sSize = 'small', $bDownload = false) - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_POSTER, "1"); - if (false !== $sMatch) { - if ('big' === strtolower($sSize) && false !== strstr($sMatch, '@._')) { - $sMatch = substr($sMatch, 0, strpos($sMatch, '@._')) . '@.jpg'; - } - if ('xxs' === strtolower($sSize) && false !== strstr($sMatch, '@._')) { - $sMatch = substr($sMatch, 0, strpos($sMatch, '@._')) . '@._V1_UY67_CR0,0,45,67_AL_.jpg'; - } - if ('xs' === strtolower($sSize) && false !== strstr($sMatch, '@._')) { - $sMatch = substr($sMatch, 0, strpos($sMatch, '@._')) . '@._V1_UY113_CR0,0,76,113_AL_.jpg'; - } - if ('s' === strtolower($sSize) && false !== strstr($sMatch, '@._')) { - $sMatch = substr($sMatch, 0, strpos($sMatch, '@._')) . '@._V1_UX182_CR0,0,182,268_AL_.jpg'; - } - if (false === $bDownload) { - return $this->cleanString($sMatch); - } else { - $sLocal = $this->saveImage($sMatch, $this->iId); - if (file_exists(dirname(__FILE__) . '/' . $sLocal)) { - return $sLocal; - } else { - return $sMatch; - } - } - } - } - - return $this->sNotFound; - } - - /** - * @return string The rating of the movie or $sNotFound. - */ - public function getRating() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_RATING, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string The rating count of the movie or $sNotFound. - */ - public function getRatingCount() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_RATING_COUNT, "1"); - if (false !== $sMatch) { - return str_replace(',', '', $this->cleanString($sMatch)); - } - } - - return $this->sNotFound; - } - - /** - * Release date doesn't contain all the information we need to create a media and - * we need this function that checks if users can vote target media (if can, it's released). - * - * @return true If the media is released - */ - public function isReleased() - { - $strReturn = $this->getReleaseDate(); - if ($strReturn == $this->sNotFound || $strReturn == 'Not yet released') { - return false; - } - - return true; - } - - /** - * @return string The release date of the movie or $sNotFound. - */ - public function getReleaseDate() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_RELEASE_DATE, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * Returns all local names - * - * @return string country - * @return string release date - */ - public function getReleaseDates() - { - if (true === $this->isReady) { - // Does a cache of this movie exist? - $sCacheFile = $this->sRoot . '/cache/' . sha1($this->iId) . '_akas.cache'; - $bUseCache = false; - - if (is_readable($sCacheFile)) { - $iDiff = round(abs(time() - filemtime($sCacheFile)) / 60); - if ($iDiff < $this->iCache || false) { - $bUseCache = true; - } - } - - if ($bUseCache) { - $aRawReturn = file_get_contents($sCacheFile); - $aReturn = unserialize($aRawReturn); - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } else { - $fullAkas = sprintf('https://www.imdb.com/title/tt%s/releaseinfo/', $this->iId); - $aCurlInfo = $this->runCurl($fullAkas); - $sSource = $aCurlInfo['contents']; - - if (false === $sSource) { - if ($this->IMDB_DEBUG) { - echo '
cURL error: ' . var_dump($aCurlInfo) . '
'; - } - - return $this->sNotFound; - } - - $aReturned = $this->matchRegex( - $sSource, - '~' . $sName . ''; - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound); - } - - /** - * @return string The sound mix of the movie or $sNotFound. - */ - public function getSoundMix() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_SOUND_MIX, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string The tagline of the movie or $sNotFound. - */ - public function getTagline() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_TAGLINE, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param bool $bForceLocal Try to return the original name of the movie. - * - * @return string The title of the movie or $sNotFound. - */ - public function getTitle($bForceLocal = false) - { - if (true === $this->isReady) { - if (true === $bForceLocal) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_TITLE_ORIG, "1"); - if (false !== $sMatch && "" !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - $sMatch = $this->matchRegex($this->sSource, self::IMDB_TITLE, "1"); - $sMatch = preg_replace('~\(\d{4}\)$~Ui', '', $sMatch); - if (false !== $sMatch && "" !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param bool $bEmbed Link to player directly? - * - * @return string The URL to the trailer of the movie or $sNotFound. - */ - public function getTrailerAsUrl($bEmbed = false) - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_TRAILER, "1"); - if (false !== $sMatch) { - $sUrl = 'https://www.imdb.com/video/imdb/' . $sMatch . '/' . ($bEmbed ? 'player' : ''); - - return $this->cleanString($sUrl); - } - } - - return $this->sNotFound; - } - - /** - * @return string The IMDb URL. - */ - public function getUrl() - { - if (true === $this->isReady) { - return $this->cleanString(str_replace('reference', '', $this->sUrl)); - } - - return $this->sNotFound; - } - - /** - * @return string The user review of the movie or $sNotFound. - */ - public function getUserReview() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_USER_REVIEW, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string The votes of the movie or $sNotFound. - */ - public function getVotes() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_VOTES, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string A list with the writers or $sNotFound. - */ - public function getWriter() - { - if (true === $this->isReady) { - $sMatch = $this->getWriterAsUrl(); - if ($this->sNotFound !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @param string $sTarget Add a target to the links? - * - * @return string A list with the linked writers or $sNotFound. - */ - public function getWriterAsUrl($sTarget = '') - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_WRITER, "1"); - $aMatch = $this->matchRegex($sMatch, self::IMDB_NAME); - $aReturn = []; - if (count($aMatch[2])) { - foreach ($aMatch[2] as $i => $sName) { - $aReturn[] = '' . $this->cleanString( - $sName - ) . ''; - } - - return $this->arrayOutput($this->bArrayOutput, $this->sSeparator, $this->sNotFound, $aReturn); - } - } - - return $this->sNotFound; - } - - /** - * @return string The year of the movie or $sNotFound. - */ - public function getYear() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_YEAR, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - /** - * @return string The budget of the movie or $sNotFound. - */ - public function getBudget() - { - if (true === $this->isReady) { - $sMatch = $this->matchRegex($this->sSource, self::IMDB_BUDGET, "1"); - if (false !== $sMatch) { - return $this->cleanString($sMatch); - } - } - - return $this->sNotFound; - } - - - /** - * Regular expression helper. - * - * @param string $sContent The content to search in. - * @param string $sPattern The regular expression. - * @param string $iIndex The index to return. - * - * @return bool If no match was found. - * @return string If one match was found. - * @return array If more than one match was found. - */ - private function matchRegex(string $sContent, string $sPattern, string $iIndex = '') - { - preg_match_all($sPattern, $sContent, $aMatches); - if ($aMatches === false) { - return false; - } - if (is_numeric($iIndex)) { - if (isset($aMatches[$iIndex][0])) { - return $aMatches[$iIndex][0]; - } - return false; - } - - return $aMatches; - } - - /** - * Preferred output in responses with multiple elements - * - * @param bool $bArrayOutput Native array or string with separators. - * @param string $sSeparator String separator. - * @param string $sNotFound Not found text. - * @param array $aReturn Original input. - * @param bool $bHaveMore Have more elements indicator. - * - * @return string|array Multiple results separated by selected separator string, or enclosed into native array. - */ - private function arrayOutput($bArrayOutput, $sSeparator, $sNotFound, $aReturn = '', $bHaveMore = false) - { - if ($aReturn === null) { - return $bArrayOutput ? [] : $sNotFound; - } - - if ($bArrayOutput) { - return $bHaveMore ? [...$aReturn, '…'] : $aReturn; - } - - $processValue = fn(mixed $value): string => match (true) { - is_array($value) => empty(array_filter($value, fn($v) => $v !== '' && $v !== null)) - ? $sNotFound - : implode($sSeparator, array_map(fn($v) => $v ?: $sNotFound, $value)), - $value === '' || $value === null => $sNotFound, - default => (string)$value, - }; - - $result = implode($sSeparator, array_map($processValue, $aReturn)); - return $bHaveMore ? $result . '…' : $result; - } - - /** - * @param string $sInput Input (eg. HTML). - * - * @return string Cleaned string. - */ - private function cleanString($sInput) - { - $aSearch = [ - 'Full summary »', - 'Full synopsis »', - 'Add summary »', - 'Add synopsis »', - 'See more »', - 'See why on IMDbPro.', - "\n", - "\r", - ]; - $aReplace = [ - '', - '', - '', - '', - '', - '', - '', - '', - ]; - $sInput = str_replace('', ' | ', $sInput); - $sInput = strip_tags($sInput); - $sInput = str_replace(' ', ' ', $sInput); - $sInput = str_replace($aSearch, $aReplace, $sInput); - $sInput = html_entity_decode($sInput, ENT_QUOTES | ENT_HTML5); - $sInput = preg_replace('/\s+/', ' ', $sInput); - $sInput = trim($sInput); - $sInput = rtrim($sInput, ' |'); - - return ($sInput ? trim($sInput) : $this->sNotFound); - } - - /** - * @param string $sText The long text. - * @param int $iLength The maximum length of the text. - * - * @return string The shortened text. - */ - private function shortText($sText, $iLength = 100) - { - if (mb_strlen($sText) <= $iLength) { - return $sText; - } - - list($sShort) = explode("\n", wordwrap($sText, $iLength - 1)); - - if (substr($sShort, -1) !== '.') { - return $sShort . '…'; - } - - return $sShort; - } - - /** - * @param string $sUrl The URL to the image to download. - * @param int $iId The ID of the movie. - * - * @return string Local path. - */ - private function saveImage($sUrl, $iId) - { - if (preg_match('~title_addposter.jpg|imdb-share-logo.png~', $sUrl)) { - return 'posters/not-found.jpg'; - } - - $sFilename = $this->sRoot . '/posters/' . $iId . '.jpg'; - if (file_exists($sFilename)) { - return 'posters/' . $iId . '.jpg'; - } - - $aCurlInfo = $this->runCurl($sUrl, true); - $sData = $aCurlInfo['contents']; - if (false === $sData) { - return 'posters/not-found.jpg'; - } - - $oFile = fopen($sFilename, 'x'); - fwrite($oFile, $sData); - fclose($oFile); - - return 'posters/' . $iId . '.jpg'; - } - - /** - * @param string $sUrl The URL to fetch. - * @param bool $bDownload Download? - * - * @return bool|mixed Array on success, false on failure. - */ - private function runCurl($sUrl, $bDownload = false) - { - $oCurl = curl_init($sUrl); - curl_setopt_array( - $oCurl, - [ - CURLOPT_CONNECTTIMEOUT => $this->IMDB_TIMEOUT, - CURLOPT_ENCODING => '', - CURLOPT_FOLLOWLOCATION => 1, - CURLOPT_FRESH_CONNECT => 0, - CURLOPT_HEADER => ($bDownload ? false : true), - CURLOPT_HTTPHEADER => [ - 'Accept: '.$this->IMDB_BROWSER_ACCEPT, - 'Accept-Language: ' . $this->IMDB_BROWSER_LANG, - ], - CURLOPT_REFERER => 'https://www.imdb.com', - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_SSL_VERIFYHOST => 0, - CURLOPT_SSL_VERIFYPEER => 0, - CURLOPT_TIMEOUT => $this->IMDB_TIMEOUT, - CURLOPT_USERAGENT => $this->IMDB_BROWSER_AGENT, - CURLOPT_VERBOSE => 0 - ] - ); - $sOutput = curl_exec($oCurl); - $aCurlInfo = curl_getinfo($oCurl); - curl_close($oCurl); - $aCurlInfo['contents'] = $sOutput; - - if (200 !== $aCurlInfo['http_code']) { - if ($this->IMDB_DEBUG) { - echo '
cURL returned wrong HTTP code “' . $aCurlInfo['http_code'] . '” for “' . $aCurlInfo['url'] . '”, aborting.
'; - } - - return false; - } - - return $aCurlInfo; - } - - /** - * @param string $sUrl The URL to the image to download. - * @param int $cId The cast ID of the actor. - * - * @return string Local path. - */ - private function saveImageCast($sUrl, $cId) - { - if ( ! preg_match('~http~', $sUrl)) { - return 'cast/not-found.jpg'; - } - - $sFilename = $this->sRoot . '/cast/' . $cId . '.jpg'; - if (file_exists($sFilename)) { - return 'cast/' . $cId . '.jpg'; - } - - $aCurlInfo = $this->runCurl($sUrl, true); - $sData = $aCurlInfo['contents']; - if (false === $sData) { - return 'cast/not-found.jpg'; - } - - $oFile = fopen($sFilename, 'x'); - fwrite($oFile, $sData); - fclose($oFile); - - return 'cast/' . $cId . '.jpg'; - } - - /** - * Makes strings with $this->sSeparator as separator result in an array - * - * @param $string - * @return array|string - */ - public function slashStringAsArray($string) { - $ret = $string; - - if(strstr($string, $this->sSeparator)) { - $ret = array(); - $_t = explode($this->sSeparator, $string); - foreach ($_t as $v) { - $v = trim($v); - if(!empty($v)) { - $ret[] = $v; - } - } - } - - return $ret; - } -} diff --git a/webclient/setup/bibliotheca.sql.default b/webclient/setup/bibliotheca.sql.default index 370dee4..f2a5368 100644 --- a/webclient/setup/bibliotheca.sql.default +++ b/webclient/setup/bibliotheca.sql.default @@ -157,7 +157,7 @@ INSERT INTO `#REPLACEME#_sys_fields` (`id`, `identifier`, `displayname`, `type`, (21, 'genres', 'sysfield.genres', 'lookupmultiple', 'tag', NULL, '', NULL, 'string 64', '2020-07-26 07:18:55', '2024-04-20 07:52:44', NULL, 1, 1, 'rw-r--r--'), (22, 'languages', 'sysfield.languages', 'lookupmultiple', 'tag', NULL, '', NULL, 'string 64', '2020-07-26 07:20:45', '2024-04-20 07:53:17', NULL, 1, 1, 'rw-r--r--'), (23, 'runtime', 'sysfield.runtime', 'number', 'entrySingleNum', '`runtime` int(10) NULL, ADD INDEX (`runtime`)', '', NULL, 'int 10', '2020-07-26 07:22:24', '2024-04-20 07:53:40', NULL, 1, 1, 'rw-r--r--'), -(24, 'imdbrating', 'sysfield.imdbrating', 'text', 'entrySingleText', '`imdbrating` varchar(128) NULL DEFAULT NULL', '', NULL, 'string 128', '2020-12-27 10:00:33', '2024-04-20 07:54:05', 0, 1, 1, 'rw-r--r--'), +(24, 'imdbrating', 'sysfield.imdbrating', 'text', 'entrySingleText', '`imdbrating` varchar(4) NULL DEFAULT NULL', '', NULL, 'string 4', '2020-12-27 10:00:33', '2024-04-20 07:54:05', 0, 1, 1, 'rw-r--r--'), (25, 'viewcount', 'sysfield.viewcount', 'number', 'entrySingleNum', '`viewcount` int(10) NULL, ADD INDEX (`viewcount`)', '', NULL, 'int 10', '2020-12-27 10:41:10', '2024-04-20 07:54:29', 0, 1, 1, 'rw-r--r--'), (26, 'writers', 'sysfield.writers', 'lookupmultiple', 'tag', NULL, 'allowSpace', NULL, 'string 64', '2021-01-05 09:47:20', '2024-04-20 07:54:53', NULL, 1, 1, 'rw-r--r--'), (27, 'localizedTitle', 'sysfield.localizedTitle', 'text', 'entryText', '`localizedTitle` varchar(128) NULL DEFAULT NULL, ADD FULLTEXT (`localizedTitle`)', '', NULL, 'string 128', '2021-04-25 19:33:31', '2024-04-20 07:55:22', 0, 1, 1, 'rw-r--r--'), diff --git a/webclient/systemout/.gitignore b/webclient/systemout/.gitignore index 0f482c8..296a8ab 100644 --- a/webclient/systemout/.gitignore +++ b/webclient/systemout/.gitignore @@ -1,4 +1,3 @@ -*.log -cache -cast -posters +* +!.gitignore +!session diff --git a/webclient/view/98/dashboard/dashboard.html b/webclient/view/98/dashboard/dashboard.html index 169e5be..d86e7c6 100644 --- a/webclient/view/98/dashboard/dashboard.html +++ b/webclient/view/98/dashboard/dashboard.html @@ -41,7 +41,7 @@ <?php echo $entry['fields']['coverimage']['displayname']; ?> diff --git a/webclient/view/98/tool/tool-imdbweb.html b/webclient/view/98/tool/tool-imdbweb.html index e78dc9d..4f9876d 100644 --- a/webclient/view/98/tool/tool-imdbweb.html +++ b/webclient/view/98/tool/tool-imdbweb.html @@ -1,6 +1,11 @@ +

t('tool.limitations'); ?>

+

t('tool.limitations.override'); ?>

- Limitations
- Data will be overwritten + t('tool.imdb.search.hint'); ?> + +

+

+ t('tool.imdb.posters'); ?> impawards

@@ -12,7 +17,7 @@ />
- +
@@ -26,11 +31,11 @@

-
- +
+

@@ -41,7 +46,7 @@ ?>
- +
diff --git a/webclient/view/compact/dashboard/dashboard.html b/webclient/view/compact/dashboard/dashboard.html index dc1c9c8..60c3890 100644 --- a/webclient/view/compact/dashboard/dashboard.html +++ b/webclient/view/compact/dashboard/dashboard.html @@ -28,7 +28,7 @@ <?php echo $entry['fields']['coverimage']['displayname']; ?> + alt="t($entry['fields']['coverimage']['displayname']); ?>" uk-cover> diff --git a/webclient/view/default/tool/tool-imdbweb.html b/webclient/view/default/tool/tool-imdbweb.html index 97d9b3d..585bf47 100644 --- a/webclient/view/default/tool/tool-imdbweb.html +++ b/webclient/view/default/tool/tool-imdbweb.html @@ -10,7 +10,7 @@
- +
@@ -36,8 +36,8 @@

-
- +
+

diff --git a/webclient/view/default/tool/tool-imdbweb.php b/webclient/view/default/tool/tool-imdbweb.php index ccb0895..e920711 100644 --- a/webclient/view/default/tool/tool-imdbweb.php +++ b/webclient/view/default/tool/tool-imdbweb.php @@ -70,16 +70,16 @@ if(!empty($collectionFields)) { if(isset($_POST['submitFormSearch'])) { $fdata = $_POST['fdata']; if (!empty($fdata)) { + // this is the imdb id $search = trim($fdata['search']); - $search = Summoner::validate($search) ? $search : false; + $search = Summoner::validate($search, "nospace") ? $search : false; - if(!empty($search)) { + if (!empty($search)) { $mData = $IMDB->search($search); - var_dump($mData); if (!empty($mData)) { $TemplateData['movieData'] = $mData; - $TemplateData['movieImdbId'] = "tt"; // this is the IMDB id you can search for + $TemplateData['movieImdbId'] = $search; $TemplateData['showMatchingForm'] = true; } else { $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound'); @@ -100,32 +100,48 @@ if(isset($_POST['submitFormSave'])) { $_imdbId = Summoner::validate($_imdbId,'nospace') ? $_imdbId : false; if(!empty($_imdbId)) { - try { - $IMDB->search($_imdbId); - } - catch (Exception $e) { - if(DEBUG) Summoner::sysLog("[DEBUG] imdb search catch: ".$e->getMessage()); - } + // why search again? + // Cache is used, so not really a new search and the data stays the same without a roundtrip + // thriugh the requsts + $mData = $IMDB->search($_imdbId); - if ($IMDB->isReady) { + if (!empty($mData)) { $TemplateData['movieImdbId'] = $_imdbId; - $_movieData = $IMDB->getAll(); // build data array based on submit // see creation log for structure $_data = array(); foreach($fdata['into'] as $k=>$v) { if(!empty($v)) { - $_t = $IMDB->$k(); + $_t = $mData[$k]; // multiple selections format for field type lookup_multiple - if(strstr($_t, $IMDB->sSeparator)) { - $_t = str_replace($IMDB->sSeparator,",", $_t); + if(is_array($_t)) { + $_t = implode(",", $_t); } if(isset($collectionFields[$v])) { $_data[$v] = $collectionFields[$v]; $_data[$v]['valueToSave'] = $_t; + + // special case for cover image since there is no POST and FILES + if($k == "coverImage") { + $fieldData = array(); + + $_f = $IMDB->downloadCover($_t); + if($_f && is_file($_f)) { + $_e = UPLOAD_ERR_OK; + // build _FILES based on regular add form + $fieldData['name'][$_data[$v]['identifier']] = 'cover.jpg'; + $fieldData['type'][$_data[$v]['identifier']] = mime_content_type($_f); + $fieldData['size'][$_data[$v]['identifier']] = filesize($_f); + $fieldData['tmp_name'][$_data[$v]['identifier']] = $_f; + $fieldData['error'][$_data[$v]['identifier']] = UPLOAD_ERR_OK; + $fieldData['rebuildUpload'][$_data[$v]['identifier']] = true; + } + + $_data[$v]['uploadData'] = $fieldData; + } } } } @@ -164,7 +180,7 @@ if(isset($_POST['submitFormSave'])) { } } else { - $TemplateData['message']['content'] = "IMDB search result information lost."; + $TemplateData['message']['content'] = $I18n->t('tool.imdb.search.missingid'); $TemplateData['message']['status'] = "error"; } } @@ -191,3 +207,17 @@ function toolMethod_GetTargetSelection(string $optionString, string $imdbKey): s return $optionString; } + +/** + * Make the given mixed data displayable as a string + * + * @param mixed $value + * @return string + */ +function toolMethod_DisplayValues(mixed $value): string { + $ret = $value; + if(is_array($value)) { + $ret = implode(", ", $value); + } + return $ret; +} -- 2.39.5