]> 91.132.146.200 Git - bibliotheca-php.git/commitdiff
fixing #1 with an alternative implementation
authorBanana <mail@bananas-playground.net>
Sun, 4 Jan 2026 11:59:16 +0000 (12:59 +0100)
committerBanana <mail@bananas-playground.net>
Sun, 4 Jan 2026 11:59:16 +0000 (12:59 +0100)
Signed-off-by: Banana <mail@bananas-playground.net>
26 files changed:
CHANGELOG
README.md
TODO
USES
VERSION
documentation/first-steps.md
documentation/setup/bibliotheca.sql
documentation/tool-imdbweb.md
sources/imdb.class.php.txt
upgrade/from-version-1.8.md [new file with mode: 0644]
webclient/config/config-imdbweb.php.default
webclient/config/config.php.default
webclient/i18n/deu.ini
webclient/i18n/eng.ini
webclient/index.php
webclient/lib/googlebookparser.class.php
webclient/lib/i18n.class.php
webclient/lib/imdbweb.class.php [new file with mode: 0644]
webclient/lib/imdbwebparser.class.php [deleted file]
webclient/setup/bibliotheca.sql.default
webclient/systemout/.gitignore
webclient/view/98/dashboard/dashboard.html
webclient/view/98/tool/tool-imdbweb.html
webclient/view/compact/dashboard/dashboard.html
webclient/view/default/tool/tool-imdbweb.html
webclient/view/default/tool/tool-imdbweb.php

index ab154d4b4802b10c8ddbf9b44ac23e12d6635923..6ca713a2009875a56c34ce8c5d8d1bed1c7434be 100644 (file)
--- 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
index 99e43088a4752cd62310a7365c798af2b294e960..a260ce7dabfddda7a07c215fb616ff0f0dcb1f1d 100644 (file)
--- 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 78e05111280fdbae29e4b11402d87b292cf0803e..a35ff318a69543e13a84f71a39028d6334ade995 100644 (file)
--- 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 08368beffe4ff1059d440c0ef29f807fa43a2c95..1194220e59b03219c6c71f19d91f5e21d1b34371 100644 (file)
--- 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 b892a4a1cd47df08b1c29fd1fc28e083a862b052..5992a386ed1af62fdb35f1bb71369b905415ff19 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.x - Harobed Village
+1.8.1 - Harobed Village
index 00613652fa90dff420064b2de0cd17824076ca51..d634b24e54e764cc81ba4aea7928462210f9df5c 100644 (file)
@@ -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.
 
index 370dee490bc79cc7e56234674d4e5b2f19ca0eff..f2a53681b37d205c9d01bd411d2c5c64e1dafc06 100644 (file)
@@ -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--'),
index 2a9f0cd339264f0cc36c4129029d9b465bfeb7ae..0b8358ac6138de360f1d8a4b91b24d5d20ace415 100644 (file)
@@ -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
index f19db269377fa48d90654da63835f3c7fe5ef19e..93a2a9bb5e171f7011ea85b0509fd860af1b3558 100644 (file)
@@ -13,7 +13,7 @@
  * @author  Fabian Beiner <fb@fabianbeiner.de>
  * @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</a>.*span\sclass.*>(.*)</div>~Uis';
     const IMDB_BUDGET        = '~budget</span>.*<span.*>\s*(.*)(?:\s*\(estimated\))\s*</span>~Ui';
     const IMDB_CAST          = '~<\/div><a class="ipc-lockup-overlay ipc-focusable" href="\/name\/([^\/]*)\/.*href.*>(.*)<\/a>~Ui';
-    const IMDB_CAST_IMAGE    = '~(loadlate="(.*)"[^>]*><\/a>\s+<\/td>\s+)?<td[^>]*itemprop="actor"[^>]*>\s*<a\s*href="\/name\/([^/]*)\/\?[^"]*"[^>]*>\s*<span.+>(.+)<\/span+~Uis';
+    const IMDB_CAST_IMAGE = '~<img\s+alt="([^"]+)"\s+class="ipc-image"\s+[^>]*src="([^"]+)"[^>]*srcset="([^"]+)"~is';
     const IMDB_CERTIFICATION = '~\?certificates=.*ref_=ttrv_stry">(.+)(?:</span></li></ul></div>|<a\sclass[^>]+\/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 (file)
index 0000000..f774337
--- /dev/null
@@ -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\
+```
index 030e5c2af53888c6f0bc16c50562db3a39ec9967..1692bda78016e23138b8f3bb49406fcef0d0d4da 100644 (file)
@@ -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
  */
 
 /**
  * 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';
index dfc2ee5e5a1b2b71b4cfbda40eb42ae23769bdf8..439827b49ff779f992280093c74ff7873bd1d747 100644 (file)
@@ -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';
index b706deefa677b80d8f3b1e4f2a23c1778f72c884..784ad3691e5f7a868df53e9dffc3cc8141c95b7c 100644 (file)
 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."
index df4888190d1e1cd8dbc1fb5d66749c4a58bc5da9..1e43e49674d2be9380a40fedd954537e33015878 100644 (file)
@@ -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"
index cf251510d1cde6ee27bd8d362056490605c2a65e..53a92fd2ff7553ac8e4772c7a8c9a0d6a036ae93 100644 (file)
@@ -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');
index 485821919336a188a70a6ed6e2dff4e90a3a42a2..79e6843a3c402a69da9ce4ba65e6a17567c9c2be 100644 (file)
@@ -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;
        }
-
 }
index 842446d2897a91ea4366162caa6cd908967b8677..0fc2372ba6da8a89d7fe1415136b768795c590aa 100644 (file)
@@ -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 (file)
index 0000000..117264e
--- /dev/null
@@ -0,0 +1,414 @@
+<?php
+/**
+ * Bibliotheca
+ *
+ * Copyright 2018-2025 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
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  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
+ */
+
+/**
+ * Class ImdbWeb
+ *
+ * Based on the idea of  https://github.com/FabianBeiner/PHP-IMDB-Grabber/
+ * But since the web srcaping does not work anymore, here is an own implementation
+ *
+ * Main and important action is at _processData
+ */
+class IMDBWEB
+{
+    /**
+     * Set this to true if you run into problems.
+     */
+    private bool $_DEBUG = false;
+
+    /**
+     * @var string The user agent used to make curl calls
+     */
+    private mixed $_BROWSER_AGENT = '';
+
+    /**
+     * @var string The user agent lang used to make curl calls
+     */
+    private mixed $_BROWSER_LANG = '';
+
+    /**
+     * @var string The user agent accept used to make curl calls
+     */
+    private mixed $_BROWSER_ACCEPT = '';
+
+    /**
+     * This is used to get the base informations
+     *
+     * A good start for further URLs is: https://www.imdb.com/title/%s/reference/
+     * @var string The imdb URL to get the HTML from
+     */
+    private string $_FULLCREDITS_URL = 'https://www.imdb.com/title/%s/fullcredits/';
+
+    /**
+     * This is used to get more text
+     * Not implemented yet.
+     *
+     * A good start for further URLs is: https://www.imdb.com/title/%s/reference/
+     * @var string The imdb URL to get the HTML from
+     */
+    private string $_REFERENCE_URL = "https://www.imdb.com/title/%s/reference/";
+
+    /**
+     * @var string The directory to store cache files
+     */
+    private string $_CACHE_DIR = "";
+
+    /**
+     * @var int Maximum cache time.
+     */
+    private int $_CACHE_TIME = 1440;
+
+    /**
+     * @var array The fields to return
+     */
+    private array $_attributes;
+
+    /**
+     * ImdbWeb constructor
+     *
+     * @param array $options
+     */
+    public function __construct(array $options) {
+        if (isset($options['debug']) && !empty($options['debug'])) {
+            $this->_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__".*?>([^<]+)</script>~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 (file)
index fb93a71..0000000
+++ /dev/null
@@ -1,1850 +0,0 @@
-<?php
-/**
- * PHP IMDb.com Grabber
- *
- * This PHP library enables you to scrape data from IMDB.com.
- *
- *
- * If you want to thank me for this library, please buy me something at Amazon
- * (https://www.amazon.de/hz/wishlist/ls/8840JITISN9L/) or use
- * https://www.paypal.me/FabianBeiner. Thank you!
- *
- * @author  Fabian Beiner <fb@fabianbeiner.de>
- * @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.*<span[^>]+>(.*)</span>~Ui';
-    const IMDB_ASPECT_RATIO  = '~aspect ratio.*<span.*>(.*)</span>~Uis';
-    const IMDB_AWARDS        = '~Awards</a>.*span\sclass.*>(.*)</div>~Uis';
-    const IMDB_BUDGET        = '~budget</span>.*<span.*>\s*(.*)(?:\s*\(estimated\))\s*</span>~Ui';
-    const IMDB_CAST          = '~<\/div><a class="ipc-lockup-overlay ipc-focusable" href="\/name\/([^\/]*)\/.*href.*>(.*)<\/a>~Ui';
-    const IMDB_CAST_IMAGE    = '~(loadlate="(.*)"[^>]*><\/a>\s+<\/td>\s+)?<td[^>]*itemprop="actor"[^>]*>\s*<a\s*href="\/name\/([^/]*)\/\?[^"]*"[^>]*>\s*<span.+>(.+)<\/span+~Uis';
-    const IMDB_CERTIFICATION = '~\?certificates=.*ref_=ttrv_stry">(.+)(?:</span></li></ul></div>|<a\sclass[^>]+\/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]*?(.*)</section>~Ui';
-    const IMDB_COMPANY       = '~href="/company/(co\d+)/\?ref_=ttrv_cmpy_\d">([^<svg].*?)</a><div~';
-    const IMDB_COUNTRY       = '~country_of_origin=(.*)&amp;ref_=ttrv_dt_cnt">(.*)<\/a~Ui';
-    const IMDB_CREATOR       = '~>\s*(?:Creator|Creators|Producer|Producers).*<ul[^>]*>(.+)</ul>~Uxsi';
-    const IMDB_DISTRIBUTOR   = '@<span\sid="distribution".*<ul\sclass[^>]+>(.*)</section>@Uis';
-    const IMDB_DISTRIBUTORS  = '@<li.*\/company\/(.*)\/[^>]+>(.*)<.*\((.*),\s*([\d-]+?).*\((.*)\).*</li>@Uis';
-    const IMDB_DIRECTOR      = '~id="director".*<ul[^>]*>(.+)</section>~Uxsi';
-    const IMDB_GENRE         = '~genres=([a-zA-Z_-]*)&amp;.*<span class="ipc-chip__text">([a-zA-Z_ -]*)<\/span><\/a~Ui';
-    const IMDB_GROSS         = '~pl-zebra-list__label">Cumulative Worldwide Gross<\/td>\s*<td>\s*(.*?)\s*<\/td>~i';
-    const IMDB_ID            = '~((?:tt\d+)|(?:itle\?\d+))~';
-    const IMDB_LANGUAGE      = '~<a href="\/language\/(\w+)">(.*)<\/a>~Ui';
-    const IMDB_LOCATION      = '~href="/search/title/\?locations=(.*)&amp.*">(.*)<\/a>~Ui';
-    const IMDB_LOCATIONS     = '~href="(?<url>\/search\/title\/\?locations=[^>]*)">\s?(?<location>.*)\s?<\/a><p(.*)>\((?<specification>.*)\)<\/p>~Ui';
-    const IMDB_MPAA          = '~<li class="ipl-inline-list__item">(?:\s+)(TV-Y|TV-Y7|TV-Y7-FV|TV-G|TV-PG|TV-14|TV-MA|TV-MA-L|TV-MA-S|TV-MA-V|G|PG|PG-13|R|NC-17|NR|UR|M|X)(?:\s+)<\/li>~Ui';
-    const IMDB_MUSIC         = '~id="composer">.*<ul\sclass[^>]+>(.*)</section>~Uxsi';
-    const IMDB_NAME          = '~href="/name/(.+)/?(?:\?[^"]*)?"[^>]*>(.+)</a>~Ui';
-    const IMDB_MOVIE_DESC    = '~<section class="titlereference-section-overview">\s+<div>\s+(.*)\s*?</div>\s+<hr>\s+<div class="titlereference-overview-section">~Ui';
-    const IMDB_SERIES_DESC   = '~<div>\s+(?:.*?</a>\s+</span>\s+</div>\s+<hr>\s+<div>\s+)(.*)\s+</div>\s+<hr>\s+<div class="titlereference-overview-section">~Ui';
-    const IMDB_SERIESEP_DESC = '~All Episodes(?:.*?)</li>\s+(?:.*?)?</ul>\s+</span>\s+<hr>\s+</div>\s+<div>\s+(.*?)\s+</div>\s+<hr>~';
-    const IMDB_NOT_FOUND_ADV = '~"results-section-empty-results-msg"~Ui';
-    const IMDB_NOT_FOUND_DES = 'Know what this is about';
-    const IMDB_NOT_FOUND_ORG = '~<h1 class="findHeader">No results found for ~Ui';
-    const IMDB_PLOT          = '~data-testid="plot-l".*>(.*)<\/span>~Ui';
-    const IMDB_PLOT_KEYWORDS = '~explore=keywords.*<span class="ipc-chip__text">(.*)<\/span>~Ui';
-    const IMDB_POSTER        = '~<meta property="og:image" content="(.*)"\/>~Ui';
-    const IMDB_RATING        = '~"ratingsSummary":{"aggregateRating":(.*),.*}~Ui';
-    const IMDB_RATING_COUNT  = '~"ratingsSummary":{.*"voteCount":(\d+),.*}~Ui';
-    const IMDB_RELEASE_DATE  = '~\/title\/tt\d+\/releaseinfo\/\?ref_=ttrv_ov_rdat">(.*)</a>~Ui';
-    const IMDB_RUNTIME       = '~id="runtime".*<ul[^>]+>(.*)</ul>~Ui';
-    const IMDB_SEARCH_ADV    = '~<a href="/title/(tt\d+).*?ipc-title-link-wrapper~i';
-    const IMDB_SEARCH_ORG    = '~find-title-result">(?:.*?)alt="(.*?)"(?:.*?)href="\/title\/(tt\d{6,})\/(?:.*?)">(.*?)<\/a>~';
-    const IMDB_SEASONS       = '~episodes/\?season=[^>]+>(\d+)<~Ui';
-    const IMDB_SOUND_MIX     = '~/search/title/\?sound_mixes.*ref_=spec_2">(.*)</a>~Ui';
-    const IMDB_TAGLINE       = '~"taglines":{"edges":\[{"node":{"text":"(.*)","__typename":"Tagline"}~Ui';
-    const IMDB_TITLE         = '~<title>(.*)\s*\(.*\)\s*-\sReference\s*view \s*-\s*IMDb</title>~Ui';
-    const IMDB_TITLE_EP      = '~titlereference-watch-ribbon"(?:.*)itemprop="name">(.*?)\s+<span\sclass="titlereference-title-year">~Ui';
-    const IMDB_TITLE_ORIG    = '~hero__pageTitle.*hero__primary-text">(.*)</span>~Ui';
-    const IMDB_TOP250        = '~href="/chart/top(?:tv)?".class(?:.*?)#([0-9]{1,})</a>~Ui';
-    const IMDB_TRAILER       = '~href="/title/(?:tt\d+)/videoplayer/(vi[0-9]*)"~Ui';
-    const IMDB_TYPE          = '~"titleType":.*"text":"(.*)",~Ui';
-    const IMDB_URL           = '~https?://(?:.*\.|.*)imdb.com/(?:t|T)itle(?:\?|/)(..\d+)~i';
-    const IMDB_USER_REVIEW   = '~href="/title/tt\d+/reviews/\?ref_=ttrv_ov_ururv">(.*)</a>~Ui';
-    const IMDB_VOTES         = '~"ratingsSummary":{.*"voteCount":(\d+),.*}~Ui';
-    const IMDB_WRITER        = '~>\s*(?:Writer|Writers).*<ul[^>]*>(.+)</ul>~Uxsi';
-    const IMDB_YEAR          = '~<title>.*\s*\((?:[^()]+ )?(\d{4}(?:–\d{4})?)\)\s*-\sReference\s*view \s*-\s*IMDb</title>~iU';
-
-       /**
-        * @var string The string returned, if nothing is found.
-        */
-       public string $sNotFound = 'n/A';
-
-       /**
-        * @var string The ID of the movie.
-        */
-       public string $iId = '';
-
-       /**
-        * @var bool Is the content ready?
-        */
-       public bool $isReady = false;
-
-       /**
-        * @var string Char that separates multiple entries.
-        */
-       public string $sSeparator = ' / ';
-
-       /**
-        * @var string The URL to the movie.
-        */
-       public string $sUrl = '';
-
-       /**
-        * @var bool Return responses enclosed in array
-        */
-       public bool $bArrayOutput = false;
-
-       /**
-        * @var int Maximum cache time.
-        */
-       private int $iCache = 1440;
-
-       /**
-        * @var string The root of the script.
-        */
-       private string $sRoot = '';
-
-       /**
-        * @var string Holds the source.
-        */
-       private string $sSource = '';
-
-       /**
-        * @var string What to search for?
-        */
-       private mixed $sSearchFor = 'all';
-
-       /**
-        * @var array The fields to return at getAll
-        */
-       private array $_showFields;
-
-       /**
-        * IMDB constructor. Can now set some options
-        *
-        * @param $options array with the following options
-        *      int iCache Custom cache time in minutes.
-        *      string sSearchFor What type to search for?
-        *      string storage Where to store data. Absolute path
-        *      boolean debug Show debug messages or not
-        */
-       public function __construct(array $options) {
-
-               if(isset($options['debug']) && !empty($options['debug'])) {
-                       $this->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 '<pre><b>Running:</b> fetchUrl("' . $sSearch . '")</pre>';
-               }
-
-               // 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 '<pre><b>Using redirect:</b> ' . basename($sRedirectFile) . '</pre>';
-                               }
-                               $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 '<pre><b>Using cache:</b> ' . basename($sCacheFile) . '</pre>';
-                                       }
-                                       $this->sSource = file_get_contents($sCacheFile);
-                                       $this->isReady = true;
-
-                                       return true;
-                               }
-                       }
-               }
-
-               // Run cURL on the URL.
-               if ($this->IMDB_DEBUG) {
-                       echo '<pre><b>Running cURL:</b> ' . $this->sUrl . '</pre>';
-               }
-
-               $aCurlInfo = $this->runCurl($this->sUrl);
-               $sSource = isset($aCurlInfo['contents']) ? $aCurlInfo['contents'] : false;
-
-               if (false === $sSource) {
-                       if ($this->IMDB_DEBUG) {
-                               echo '<pre><b>cURL error:</b> ' . var_dump($aCurlInfo) . '</pre>';
-                       }
-
-                       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 '<pre><b>New redirect saved:</b> ' . basename($sRedirectFile) . ' => ' . $sUrl . '</pre>';
-                       }
-                       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 '<pre><b>Movie not found:</b> ' . $sSearch . '</pre>';
-                       }
-
-                       return false;
-               }
-
-               $this->sSource = str_replace(
-                       [
-                               "\n",
-                               "\r\n",
-                               "\r",
-                       ],
-                       '',
-                       $sSource
-               );
-               $this->isReady = true;
-
-               // Save cache.
-               if (false === $bSearch) {
-                       if ($this->IMDB_DEBUG) {
-                               echo '<pre><b>Cache created:</b> ' . basename($sCacheFile) . '</pre>';
-                       }
-                       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 '<pre><b>cURL error:</b> ' . var_dump($aCurlInfo) . '</pre>';
-                                       }
-
-                                       return false;
-                               }
-
-                               $aReturned = $this->matchRegex($sSource, "~<td>(.*?)<\/td>\s+<td>(.*?)<\/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[] = '<a href="https://www.imdb.com/name/' . $this->cleanString(
-                                                       $aMatch[1][$i]
-                                               ) . '/"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a>';
-                               }
-
-                               $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[] = '<a href="https://www.imdb.com/name/' . $this->cleanString(
-                                                       $aMatch[1][$i]
-                                               ) . '/"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a> 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 '<a href="https://www.imdb.com/company/' . $this->cleanString(
-                                               $aMatch[1][0]
-                                       ) . '/"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                               $aMatch[2][0]
-                                       ) . '</a>';
-                       }
-               }
-
-               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[] = '<a href="https://www.imdb.com/country/' . trim(
-                                                       $aMatch[1][$i]
-                                               ) . '/"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a>';
-                               }
-
-                               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[] = '<a href="https://www.imdb.com/name/' . $this->cleanString(
-                                                       $aMatch[1][$i]
-                                               ) . '/"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a>';
-                               }
-
-                               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[] = '<a href="https://www.imdb.com/name/' . $this->cleanString(
-                                                       $aMatch[1][$i]
-                                               ) . '/"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a>';
-                               }
-
-                               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[] = '<a href="https://www.imdb.com/search/title?genres=' . $this->cleanString(
-                                                       $aMatch[1][$i]
-                                               ) . '"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a>';
-                               }
-
-                               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[] = '<a href="https://www.imdb.com/language/' . $this->cleanString(
-                                                       $aMatch[1][$i]
-                                               ) . '"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a>';
-                               }
-
-                               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[] = '<a href="https://www.imdb.com/search/title?locations=' . $this->cleanString(
-                                                       $aMatch[1][$i]
-                                               ) . '"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a>';
-                               }
-
-                               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 '<pre><b>cURL error:</b> ' . var_dump($aCurlInfo) . '</pre>';
-                                       }
-
-                                       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 '<pre><b>cURL error:</b> ' . var_dump($aCurlInfo) . '</pre>';
-                                       }
-
-                    return $this->sNotFound;
-                               }
-
-                               $aReturned = $this->matchRegex(
-                                       $sSource,
-                    '~<a class="ipc-metadata-list-item__label[^>]*>([^<]+)</a>.*?<span class="ipc-metadata-list-item__list-content-item"[^>]*>([^<]+)</span>~s'
-                               );
-
-                               if ($aReturned) {
-                                       $aReturn = [];
-                                       foreach ($aReturned[1] as $i => $strName) {
-                                               if (strpos($strName, '(') === false) {
-                                                       $aReturn[] = [
-                                                               'country'     => $this->cleanString($strName),
-                                                               'releasedate' => $this->cleanString($aReturned[2][$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 runtime of the movie or $sNotFound.
-        */
-       public function getRuntime()
-       {
-               if (true === $this->isReady) {
-                       $sMatch = $this->matchRegex($this->sSource, self::IMDB_RUNTIME, "1");
-                       if (false !== $sMatch) {
-                               return $this->cleanString($sMatch);
-                       }
-               }
-
-               return $this->sNotFound;
-       }
-
-       /**
-        * @return string A list with the seasons or $sNotFound.
-        */
-       public function getSeasons()
-       {
-               if (true === $this->isReady) {
-                       $sMatch = $this->getSeasonsAsUrl();
-                       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 seasons or $sNotFound.
-        */
-       public function getSeasonsAsUrl($sTarget = '')
-       {
-               if (true === $this->isReady) {
-                       $aMatch  = $this->matchRegex($this->sSource, self::IMDB_SEASONS);
-                       $aReturn = [];
-                       if (count($aMatch[1])) {
-                               foreach (range(1, max($aMatch[1])) as $i => $sName) {
-                                       $aReturn[] = '<a href="https://www.imdb.com/title/tt' . $this->iId . '/episodes?season=' . $sName . '"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $sName . '</a>';
-                               }
-
-                               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[] = '<a href="https://www.imdb.com/name/' . $this->cleanString(
-                                                       $aMatch[1][$i]
-                                               ) . '/"' . ($sTarget ? ' target="' . $sTarget . '"' : '') . '>' . $this->cleanString(
-                                                       $sName
-                                               ) . '</a>';
-                               }
-
-                               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 &raquo;',
-                       'Full synopsis &raquo;',
-                       'Add summary &raquo;',
-                       'Add synopsis &raquo;',
-                       'See more &raquo;',
-                       'See why on IMDbPro.',
-                       "\n",
-                       "\r",
-               ];
-               $aReplace = [
-                       '',
-                       '',
-                       '',
-                       '',
-                       '',
-                       '',
-                       '',
-                       '',
-               ];
-               $sInput   = str_replace('</li>', ' | ', $sInput);
-               $sInput   = strip_tags($sInput);
-               $sInput   = str_replace('&nbsp;', ' ', $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 '<pre><b>cURL returned wrong HTTP code “' . $aCurlInfo['http_code'] . '” for “' . $aCurlInfo['url'] . '”, aborting.</b></pre>';
-                       }
-
-                       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;
-       }
-}
index 370dee490bc79cc7e56234674d4e5b2f19ca0eff..f2a53681b37d205c9d01bd411d2c5c64e1dafc06 100644 (file)
@@ -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--'),
index 0f482c88e2a5debbad2bd19339eb904c987156ea..296a8ab41953fe355ab389bc7d49f86cff0b23f5 100644 (file)
@@ -1,4 +1,3 @@
-*.log
-cache
-cast
-posters
+*
+!.gitignore
+!session
index 169e5be318dcfd6cf4b0396fc28d260a33dcc5fd..d86e7c6efc4575f005619177a3c4f65c3e07f406 100644 (file)
@@ -41,7 +41,7 @@
                                                <?php if(isset($entry['fields']['coverimage'])) { ?>
                                                <a href="index.php?p=entry&collection=<?php echo $k ?>&id=<?php echo $entryK; ?>">
                                                        <img src="<?php echo PATH_WEB_STORAGE.'/'.$k.'/'.$entryK.'/'.$entry['fields']['coverimage']['value']; ?>"
-                                                            alt="<?php echo $entry['fields']['coverimage']['displayname']; ?>"
+                                                            alt="<?php echo $I18n->t($entry['fields']['coverimage']['displayname']); ?>"
                                                        width="50px">
                                                </a>
                                                <?php } ?>
index e78dc9dc2e00298bb3bc8f706e3f9354621b2663..4f9876d3bc276dbe8b072e4c9b7da0a02b8b9bb5 100644 (file)
@@ -1,6 +1,11 @@
+<p><b><?php echo $I18n->t('tool.limitations'); ?></b></p>
+<p><?php echo $I18n->t('tool.limitations.override'); ?></p>
 <p>
-       <b>Limitations</b><br />
-       Data will be overwritten
+       <?php echo $I18n->t('tool.imdb.search.hint'); ?>
+
+</p>
+<p>
+       <?php echo $I18n->t('tool.imdb.posters'); ?> <a href="http://www.impawards.com/" target=_blank>impawards</a
 </p>
 
 <form class="maxSizeForm" method="post" enctype="multipart/form-data">
@@ -12,7 +17,7 @@
                />
        </div>
        <div class="field-row-stacked">
-               <input value="Search" type="submit" name="submitFormSearch">
+               <input value="<?php echo $I18n->t('tool.imdb.fetch'); ?>" type="submit" name="submitFormSearch">
        </div>
 </form>
 
 
        <div class="field-row-stacked">
                <p>
-                       <b><?php echo $v['name']; ?></b><br />
-                       <?php echo $v['value']; ?>
+                       <b><?php echo $k; ?></b><br />
+                       <?php echo toolMethod_DisplayValues($v); ?>
                </p>
                <select name="fdata[into][<?php echo $k; ?>]">
-                       <option value="">None</option>
+                       <option value=""><?php echo $I18n->t('tool.imdb.select.none'); ?></option>
                        <?php echo toolMethod_GetTargetSelection($TemplateData['saveToSelection'],$k); ?>
                </select>
        </div>
@@ -41,7 +46,7 @@
        ?>
 
        <div class="field-row-stacked">
-               <input value="Save" type="submit" name="submitFormSave" />
+               <input value="<?php echo $I18n->t('tool.imdb.save'); ?>" type="submit" name="submitFormSave" />
        </div>
 
 </form>
index dc1c9c8d1beae9e7a2275fc02ed0f4c45a4d2971..60c38903d5dfc8fa004231e5dba6101fb597f8b9 100644 (file)
@@ -28,7 +28,7 @@
                                                <?php if(isset($entry['fields']['coverimage'])) { ?>
                                                <a href="index.php?p=entry&collection=<?php echo $k ?>&id=<?php echo $entryK; ?>">
                                                <img src="<?php echo PATH_WEB_STORAGE.'/'.$k.'/'.$entryK.'/'.$entry['fields']['coverimage']['value']; ?>"
-                                                        alt="<?php echo $entry['fields']['coverimage']['displayname']; ?>" uk-cover>
+                                                        alt="<?php echo $I18n->t($entry['fields']['coverimage']['displayname']); ?>" uk-cover>
                                                <canvas width="400" height="200"></canvas>
                                                </a>
                                                <?php } ?>
index 97d9b3dd289f640fb1b3b514ae403603afe98761..585bf4703db9b0c128a78355880d191fbc01c2bf 100644 (file)
@@ -10,7 +10,7 @@
 
 <form class="uk-form-horizontal uk-margin-small" method="post" enctype="multipart/form-data">
        <div class="uk-margin">
-               <label class="uk-form-label" for="search"><?php echo $I18n->t('tool.imdb.search'); ?></label>
+               <label class="uk-form-label" for="search"><?php echo $I18n->t('tool.imdb.imdbid'); ?></label>
                <div class="uk-form-controls">
                        <input class="uk-input" id="search" type="text" autocomplete="off"
                                        placeholder="tt123456790"
@@ -20,7 +20,7 @@
                </div>
                <div class="uk-margin">
                        <button class="uk-button uk-button-primary" type="submit" name="submitFormSearch">
-                               <?php echo $I18n->t('tool.imdb.search'); ?>
+                               <?php echo $I18n->t('tool.imdb.fetch'); ?>
                        </button>
                </div>
        </div>
@@ -36,8 +36,8 @@
 
        <div class="uk-width-1-2@s uk-overflow-hidden">
                <p>
-                       <b><?php echo $v['name']; ?></b><br />
-                       <?php echo $v['value']; ?>
+                       <b><?php echo $k; ?></b><br />
+                       <?php echo toolMethod_DisplayValues($v); ?>
                </p>
        </div>
        <div class="uk-width-1-2@s">
index ccb08950516c80b93d74f76e21705a3bfbea47e1..e9207113398f55ad9f3c59ff94227badc699562e 100644 (file)
@@ -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;
+}