]> 91.132.146.200 Git - bibliotheca-php.git/commitdiff
remove imdb scraper and replace it with metacritic for movies for now
authorBanana <mail@bananas-playground.net>
Sun, 30 Aug 2026 13:56:56 +0000 (15:56 +0200)
committerBanana <mail@bananas-playground.net>
Sun, 30 Aug 2026 13:56:56 +0000 (15:56 +0200)
Signed-off-by: Banana <mail@bananas-playground.net>
18 files changed:
CHANGELOG
documentation/tool-imdbweb.md [deleted file]
documentation/tool-metacritic.md [new file with mode: 0644]
upgrade/from-version-1.8.1.md [new file with mode: 0644]
upgrade/from-version-1.8.md
webclient/config/config-imdbweb.php.default [deleted file]
webclient/config/config-metacritic.php.default [new file with mode: 0644]
webclient/config/config.php.default
webclient/i18n/deu.ini
webclient/i18n/eng.ini
webclient/lib/imdbweb.class.php [deleted file]
webclient/lib/metacritic.class.php [new file with mode: 0644]
webclient/view/98/tool/tool-imdbweb.html [deleted file]
webclient/view/98/tool/tool-metacriticMovie.html [new file with mode: 0644]
webclient/view/default/tool/tool-imdbweb.html [deleted file]
webclient/view/default/tool/tool-imdbweb.php [deleted file]
webclient/view/default/tool/tool-metacriticMovie.html [new file with mode: 0644]
webclient/view/default/tool/tool-metacriticMovie.php [new file with mode: 0644]

index 3caf2584eccf7a0442373f85eeaf4a1afa004aa9..331303ce17af55814df1471d7c16845eae46c366 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,8 @@
-x.x.x - Terraniux Underground
+1.9.0 - Terraniux Underground 2026-08-30
+    * Config Update: See upgrade/from-version-1.8.1.md
+    * DB Update (non breaking): See upgrade/from-version-1.8.1.md
+    * Removed IMDB web parser since IMDB does not like cli tools anymore.
+    * Added: Metacritic as a replacement for IMDB. See upgrade/from-version-1.8.1.md
 
 1.8.1 - Harobed Village 2026-01-06
     * Config Update: See upgrade/from-version-1.8.md
diff --git a/documentation/tool-imdbweb.md b/documentation/tool-imdbweb.md
deleted file mode 100644 (file)
index 0b8358a..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-# IMDB web parser tool
-
-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.
-
-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
-easier since not every field the tool provides are needed.
-Follow the comments in the file for more details.
-
-An option to make the target fields automatically selected by default to given values is also available
-in the config file.
-
-# Setup
-
-Rename the `config-imdbweb.php.default` to `config-imdbweb.php` in the config folder.
-Follow the comments to update the settings. Add the tool to your collection.
diff --git a/documentation/tool-metacritic.md b/documentation/tool-metacritic.md
new file mode 100644 (file)
index 0000000..2779a3b
--- /dev/null
@@ -0,0 +1,18 @@
+# Metacritic web parser tool
+
+Since IMDB does not like terminal clients, Metacritic it is for now.
+
+There is no search for now.
+Search manuall and then copy the part after `movie/` from the URL and use this for the data fetch.
+
+Which fields are available for you to select, are configured in the `config.metacritic.php` file to make it
+easier since not every field the tool provides are needed.
+Follow the comments in the file for more details.
+
+An option to make the target fields automatically selected by default to given values is also available
+in the config file.
+
+# Setup
+
+Rename the `config-metacritic.php.default` to `config-metacritic.php` in the config folder.
+Follow the comments to update the settings. Add the tool to your collection.
diff --git a/upgrade/from-version-1.8.1.md b/upgrade/from-version-1.8.1.md
new file mode 100644 (file)
index 0000000..f913205
--- /dev/null
@@ -0,0 +1,37 @@
+# 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#_tool` SET `name` = 'Metacritic Movie', `action` = 'metacriticMovie' WHERE `bib_tool`.`id` = 1;
+```
+
+```
+INSERT INTO `#REPLACEME#_sys_fields` (`id`, `identifier`, `displayname`, `type`, `searchtype`, `createstring`, `inputValidation`, `value`, `apiinfo`, `created`, `modified`, `modificationuser`, `owner`, `group`, `rights`) VALUES (NULL, 'metacritic', 'sysfield.metacritic', 'text', 'entrySingleText', '`metacritic` varchar(4) NULL DEFAULT NULL', '', '', 'string 4', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, '1', '1', 'rw-r--r--');  
+```
+
+# Remove not needed folders and files
+
+The following files and direcotries should be deleted.
+
+```
+config/config-imdbweb.php
+config/config-imdbweb.php.default
+lib/imdbweb.class.php
+systemout/imdb
+view/default/tool-imdbweb.html
+view/default/tool-imdbweb.php
+view/98/tool-imdbweb.html
+view/98/tool-imdbweb.php
+```
+
+# Tool changes
+
+Since IMDB scraping is not working anymore, Metracritic does it for now. 
+It replaces IMDB and needs only the initial setup. See the tool documentation for that.
index f7743379cb3133d1158332071934b91b545dc8f4..3d6d9543aef3c4f17587315115bcc88e0eb8b2e5 100644 (file)
@@ -6,7 +6,7 @@ Updated BROWSER_AGENT string. See default config file for its new value.
 
 Run each line against your bibliotheca DB.
 
-Replace #REPLACEME# with your table prefix. Default is bib
+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';
diff --git a/webclient/config/config-imdbweb.php.default b/webclient/config/config-imdbweb.php.default
deleted file mode 100644 (file)
index 1692bda..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-/**
- * Bibliotheca
- *
- * 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
- *  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
- */
-
-/**
- * Options for the imdb grabber
- *
- * 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.
- *             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_ATTRIBUTES = array(
-       'runtime', 'rating', 'genres', 'title', 'originalTitle', 'year', 'plot', 'synopses', 'directors',
-       'writers', 'cast', 'coverImage'
-);
-const TOOL_IMDBWEB_FIELDS_TO = array(
-       'cast' => 'actors', 'synopses' => 'content', 'directors' => 'directors', 'genres' => 'genres',
-       'plot' => 'description', 'rating' => 'imdbrating', 'runtime' => 'runtime', 'originalTitle' => 'title',
-       'writers' => 'writers', 'year' => 'year', 'title' => 'localizedTitle', 'coverImage' => 'coverimage'
-);
-
-// reuse existing constants but define it to the tool
-const TOOL_IMDBWEB_BROWSER_AGENT = BROWSER_AGENT;
-const TOOL_IMDBWEB_BROWSER_ACCEPT = BROWSER_ACCEPT;
-const TOOL_IMDBWEB_BROWSER_ACCEPT_LANG = BROWSER_ACCEPT_LANG;
-const TOOL_IMDBWEB_CACHE_STORAGE = PATH_SYSTEMOUT.'/imdb';
diff --git a/webclient/config/config-metacritic.php.default b/webclient/config/config-metacritic.php.default
new file mode 100644 (file)
index 0000000..9ee3c6d
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Bibliotheca
+ *
+ * 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
+ *  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
+ */
+
+/**
+ * Options for the metacritic grabber
+ *
+ * TOOL_METACRITIC_ATTRIBUTES_MOVIE is an array to define which fields from Metracitic are displayed for selection
+ *             an empty array() shows all.
+ *             'description', 'datePublished', 'ratingValue', 'genre', 'name', 'actor', 'duration', 'director
+ *
+ * TOOL_METACRITIC_FIELDS_TO_MOVIE is a array to define which attribute (see TOOL_METACRITIC_ATTRIBUTES_MOVIE) 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_METACRITIC_BROWSER_AGENT A current browser agent string. See default config file.
+ * TOOL_METACRITIC_BROWSER_ACCEPT Browser accept request string. See default config file.
+ * TOOL_METACRITIC_BROWSER_ACCEPT_LANG Defines in which language the content returns
+ * TOOL_METACRITIC_CACHE_STORAGE Cache storage path
+ */
+const TOOL_METACRITIC_ATTRIBUTES_MOVIE = array(
+    'description', 'datePublished', 'ratingValue', 'genre', 'name', 'actor', 'duration', 'director'
+);
+const TOOL_METACRITIC_FIELDS_TO_MOVIE = array(
+    'actor' => 'actors', 'director' => 'directors', 'genre' => 'genres',
+    'description' => 'description', 'ratingValue' => 'metacritic', 'duration' => 'runtime',
+    'name' => 'title', 'datePublished' => 'year'
+);
+
+// reuse existing constants but define it to the tool
+const TOOL_METACRITIC_BROWSER_AGENT = BROWSER_AGENT;
+const TOOL_METACRITIC_BROWSER_ACCEPT = BROWSER_ACCEPT;
+const TOOL_METACRITIC_BROWSER_ACCEPT_LANG = BROWSER_ACCEPT_LANG;
+const TOOL_METACRITIC_CACHE_STORAGE = PATH_SYSTEMOUT.'/metacritic';
index 81c20cfce0b874fdb346b1b3f4a555477cf7fa56..c7d9b5c1a47346712a5cfa42f96a22222bfce1c5 100644 (file)
@@ -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:146.0) Gecko/20100101 Firefox/146.0';
+const BROWSER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:154.0) Gecko/20100101 Firefox/154.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 784ad3691e5f7a868df53e9dffc3cc8141c95b7c..873960e857b201e8f30ccf233642946e7582b76b 100644 (file)
@@ -258,6 +258,7 @@ sysfield.imdbrating = "IMDB Wertung"
 sysfield.isbn = "ISBN"
 sysfield.languages = "Sprachen"
 sysfield.localizedTitle = "Lokalisierter Titel"
+sysfield.metacritic = "Metascore"
 sysfield.missingtemplate = "Fehlende Feldvorlage:"
 sysfield.modified = "Bearbeitet"
 sysfield.os = "Betriebssystem und Version"
@@ -276,23 +277,26 @@ sysfield.year = "Jahr"
 
 tags.headline.tagsfor = "Tags für:"
 
+tool.headline.using = "Verwendung von %s in Sammlung: %s"
+tool.limitations = "Einschränkungen"
+tool.limitations.override = "Daten werden überschrieben."
+tool.message.missingFiles = "Benötige Tooldateien können nicht gefunden werden."
+
 tool.gameinfo = "Dies ist eine Sammlung von Webseiten, die unterschiedliche Informationen über Spiele anbieten."
 tool.googlebooks.save = reuse.global.save
 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."
-tool.message.missingFiles = "Benötige Tooldateien können nicht gefunden werden."
+
+tool.metacritic.fetch = "Datenbezug"
+tool.metacritic.id = "URL Teil"
+tool.metacritic.posters = "Film Poster"
+tool.metacritic.save = reuse.global.save
+tool.metacritic.search = reuse.global.search
+tool.metacritic.search.hint = "Suche funktioniert mit dem Teil der URL nach /movie/. Auf Metacritic manuell suchen und dann diesen Teils aus der URL nehmen."
+tool.metacritic.search.missingid = "Metacritic Suchinformation fehlen."
+tool.metacritic.select.none = reuse.global.none
+
 tool.music.input.albumTitle = "Album/Titel"
 tool.music.input.artist = "Künstler"
 tool.music.limitations.override = "Daten werden überschrieben; keine Platzhaltersuche oder Teilwörter."
index 1e43e49674d2be9380a40fedd954537e33015878..9bc4aa880f3f4021325e2afcd37b3e0792352433 100644 (file)
@@ -258,6 +258,7 @@ sysfield.imdbrating = "IMDB rating"
 sysfield.isbn = "ISBN"
 sysfield.languages = "Languages"
 sysfield.localizedTitle = "Localized title"
+sysfield.metacritic = "Metascore"
 sysfield.missingtemplate = "Missing template for:"
 sysfield.modified = "Modified"
 sysfield.os = "Operating system and version"
@@ -276,23 +277,26 @@ sysfield.year = "Year"
 
 tags.headline.tagsfor = "Tags for:"
 
+tool.headline.using = "Using %s with collection: %s"
+tool.limitations = "Limitations"
+tool.limitations.override = "Data will be overwritten"
+tool.message.missingFiles = "Required tool files can not be found."
+
 tool.gameinfo = "This is a collection of websites which provide different kind of information about games."
 tool.googlebooks.save = reuse.global.save
 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"
-tool.message.missingFiles = "Required tool files can not be found."
+
+tool.metacritic.fetch = "Fetch"
+tool.metacritic.id = "Url part"
+tool.metacritic.posters = "Movie posters"
+tool.metacritic.save = reuse.global.save
+tool.metacritic.search = reuse.global.search
+tool.metacritic.search.hint = "Seach works with the url part from Metacritic URL after /movie/."
+tool.metacritic.search.missingid = "Metacritic search result information missing."
+tool.metacritic.select.none = reuse.global.none
+
 tool.music.input.albumTitle = "Album/Title"
 tool.music.input.artist = "Artist"
 tool.music.limitations.override = "Data will be overwritten; No wildcardsearch or partial words."
diff --git a/webclient/lib/imdbweb.class.php b/webclient/lib/imdbweb.class.php
deleted file mode 100644 (file)
index 71b8700..0000000
+++ /dev/null
@@ -1,413 +0,0 @@
-<?php
-/**
- * Bibliotheca
- *
- * 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
- *  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/metacritic.class.php b/webclient/lib/metacritic.class.php
new file mode 100644 (file)
index 0000000..dcc18e1
--- /dev/null
@@ -0,0 +1,318 @@
+<?php
+/**
+ * Bibliotheca
+ *
+ * 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
+ *  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 Metacritic
+ *
+ * Retrive the HTML from a metacritic URL and parse the json+ld information
+ */
+
+class Metacritic {
+    /**
+     * Set this to true if you run into problems.
+     */
+    private bool $_DEBUG = false;
+
+    /**
+    * @var string The user agent used to make curl calls
+     */
+    private string $_BROWSER_AGENT = '';
+
+    /**
+    * @var string The user agent lang used to make curl calls
+    */
+    private string $_BROWSER_LANG = '';
+
+    /**
+    * @var string The user agent accept used to make curl calls
+    */
+    private string $_BROWSER_ACCEPT = '';
+
+    /**
+    * @var string The directory to store cache files
+    */
+    private string $_CACHE_DIR = "";
+
+    /**
+    * @var array The fields to return
+    */
+    private array $_attributes;
+
+    private string $_BASE_URL_MOVIES = 'https://www.metacritic.com/movie/%s/';
+
+    /**
+    * @var int Maximum cache time.
+    */
+    private int $_CACHE_TIME = 1440;
+
+    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'];
+        }
+    }
+
+    public function fetch(string $urlPart): array {
+        $ret = array();
+
+        if (empty($urlPart)) return $ret;
+        $url = sprintf($this->_BASE_URL_MOVIES, $urlPart);
+        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;
+    }
+
+    /**
+     * 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.metacritic.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;
+                }
+            } else {
+                Summoner::sysLog('[WARN] ' . __METHOD__ . ' non 200 return status ' . Summoner::cleanForLog($http_code));
+            }
+        }
+        curl_close($ch);
+
+        if ($this->_DEBUG) {
+            Summoner::sysLog('[DEBUG] ' . __METHOD__ . ' headers ' . Summoner::cleanForLog($_headers));
+        }
+
+        return $ret;
+    }
+
+    /**
+     * The html from the curl call should contain application/ld+json
+     * 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('~type="application/ld\+json".*?>([^<]+)</script>~s', $data, $matches);
+        if(!empty($m)) {
+            $ret = $matches[1];
+        } elseif($this->_DEBUG) {
+            Summoner::sysLog("[DEBUG] ".__METHOD__." no script with type application/ld+json 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["name"])) {
+            foreach($this->_attributes as $att) {
+                $_m = "_get_".$att;
+                if(method_exists($this, $_m)) {
+                    $ret[$att] = $this->$_m($jsonData);
+                } elseif (isset($jsonData[$att])) {
+                    $ret[$att] = $jsonData[$att];
+                } else {
+                    $ret[$att] = "";
+                }
+            }
+        }
+
+        return $ret;
+    }
+
+    private function _get_ratingValue(array $data): string {
+        $t = $this->_retrieveDataFromArray(array("aggregateRating","ratingValue"),$data);
+        return $t;
+    }
+
+    private function _get_genre(array $data): array {
+        $ret = array();
+        foreach($data['genre'] as $g) {
+            $ret[] = $g;
+        }
+        return $ret;
+    }
+
+    private function _get_actor(array $data): array {
+        $ret = array();
+        foreach($data['actor'] as $e) {
+            $ret[] = $e['name'];
+        }
+        return $ret;
+    }
+
+    private function _get_director(array $data): array {
+        $ret = array();
+        foreach($data['director'] as $e) {
+            $ret[] = $e['name'];
+        }
+        return $ret;
+    }
+
+    private function _get_datePublished(array $data): string {
+        $ret = '';
+        if(isset($data['datePublished'])) {
+            $time = strtotime($data['datePublished']);
+            $ret = date('Y', $time);
+        }
+        return $ret;
+    }
+
+
+
+    /**
+     * The duration something like this: PT2M11S
+     * It is https://iso8601.com/#duration but wrong?
+     * The above example is from a movie which is 2h and 11m
+     * and NOT 2m 11s
+     *
+     * @param array $data
+     * @return array
+     */
+    private function _get_duration(array $data): string {
+        $ret = '';
+        if(isset($data['duration'])) {
+            try {
+                $interval = new DateInterval($data['duration']);
+                $ret = $interval->format("%i")*60;
+                $ret = $ret+$interval->format("%s"); // not very cool...
+            } catch (Exception $e) {
+                Summoner::sysLog("[DEBUG] ".__METHOD__." can not duration ".$data['duration']);
+            }
+        }
+        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/view/98/tool/tool-imdbweb.html b/webclient/view/98/tool/tool-imdbweb.html
deleted file mode 100644 (file)
index 4f9876d..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<p><b><?php echo $I18n->t('tool.limitations'); ?></b></p>
-<p><?php echo $I18n->t('tool.limitations.override'); ?></p>
-<p>
-       <?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">
-       <div class="field-row-stacked">
-               <label for="search">Search</label>
-               <input id="search" type="text" autocomplete="off"
-                          name="fdata[search]"
-                          value="<?php echo $TemplateData['editEntry']['title'] ?? ''; ?>"
-               />
-       </div>
-       <div class="field-row-stacked">
-               <input value="<?php echo $I18n->t('tool.imdb.fetch'); ?>" type="submit" name="submitFormSearch">
-       </div>
-</form>
-
-<?php if($TemplateData['showMatchingForm']) { ?>
-<form class="maxSizeForm" method="post" enctype="multipart/form-data">
-       <input type="hidden" name="fdata[imdbId]" value="<?php echo $TemplateData['movieImdbId']; ?>">
-       <?php
-       if(!empty($TemplateData['movieData'])) {
-               foreach($TemplateData['movieData'] as $k=>$v) {
-       ?>
-
-       <div class="field-row-stacked">
-               <p>
-                       <b><?php echo $k; ?></b><br />
-                       <?php echo toolMethod_DisplayValues($v); ?>
-               </p>
-               <select name="fdata[into][<?php echo $k; ?>]">
-                       <option value=""><?php echo $I18n->t('tool.imdb.select.none'); ?></option>
-                       <?php echo toolMethod_GetTargetSelection($TemplateData['saveToSelection'],$k); ?>
-               </select>
-       </div>
-
-       <?php
-               }
-       }
-       ?>
-
-       <div class="field-row-stacked">
-               <input value="<?php echo $I18n->t('tool.imdb.save'); ?>" type="submit" name="submitFormSave" />
-       </div>
-
-</form>
-<?php } ?>
diff --git a/webclient/view/98/tool/tool-metacriticMovie.html b/webclient/view/98/tool/tool-metacriticMovie.html
new file mode 100644 (file)
index 0000000..e944bc1
--- /dev/null
@@ -0,0 +1,53 @@
+<p><b><?php echo $I18n->t('tool.limitations'); ?></b></p>
+<p><?php echo $I18n->t('tool.limitations.override'); ?></p>
+<p>
+       <?php echo $I18n->t('tool.metacritic.search.hint'); ?>
+
+</p>
+<p>
+       <?php echo $I18n->t('tool.metacritic.posters'); ?> <a href="https://www.impawards.com/" target=_blank>impawards</a>
+</p>
+
+<form class="maxSizeForm" method="post" enctype="multipart/form-data">
+       <div class="field-row-stacked">
+               <label for="search">Search</label>
+               <input id="search" type="text" autocomplete="off"
+                      name="fdata[search]"
+                      value=""
+               />
+       </div>
+       <div class="field-row-stacked">
+               <input value="<?php echo $I18n->t('tool.metacritic.fetch'); ?>" type="submit" name="submitFormSearch">
+       </div>
+</form>
+
+<?php if($TemplateData['showMatchingForm']) { ?>
+<form class="maxSizeForm" method="post" enctype="multipart/form-data">
+       <input type="hidden" name="fdata[urlpart]" value="<?php echo $TemplateData['urlpart']; ?>">
+       <?php
+       if(!empty($TemplateData['movieData'])) {
+               foreach($TemplateData['movieData'] as $k=>$v) {
+       ?>
+
+       <div class="field-row-stacked">
+               <p>
+                       <b><?php echo $k; ?></b><br />
+                       <?php echo toolMethod_DisplayValues($v); ?>
+               </p>
+               <select name="fdata[into][<?php echo $k; ?>]">
+                       <option value=""><?php echo $I18n->t('tool.metacritic.select.none'); ?></option>
+                       <?php echo toolMethod_GetTargetSelection($TemplateData['saveToSelection'],$k); ?>
+               </select>
+       </div>
+
+       <?php
+               }
+       }
+       ?>
+
+       <div class="field-row-stacked">
+               <input value="<?php echo $I18n->t('tool.metacritic.save'); ?>" type="submit" name="submitFormSave" />
+       </div>
+
+</form>
+<?php } ?>
diff --git a/webclient/view/default/tool/tool-imdbweb.html b/webclient/view/default/tool/tool-imdbweb.html
deleted file mode 100644 (file)
index 585bf47..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<p><b><?php echo $I18n->t('tool.limitations'); ?></b></p>
-<p><?php echo $I18n->t('tool.limitations.override'); ?></p>
-<p>
-       <?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="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.imdbid'); ?></label>
-               <div class="uk-form-controls">
-                       <input class="uk-input" id="search" type="text" autocomplete="off"
-                                       placeholder="tt123456790"
-                                  name="fdata[search]"
-                                  value="<?php echo $TemplateData['editEntry']['title'] ?? ''; ?>"
-                       >
-               </div>
-               <div class="uk-margin">
-                       <button class="uk-button uk-button-primary" type="submit" name="submitFormSearch">
-                               <?php echo $I18n->t('tool.imdb.fetch'); ?>
-                       </button>
-               </div>
-       </div>
-</form>
-
-<?php if($TemplateData['showMatchingForm']) { ?>
-<form class="uk-form-horizontal uk-margin-small uk-grid-small" method="post" enctype="multipart/form-data" uk-grid>
-       <input type="hidden" name="fdata[imdbId]" value="<?php echo $TemplateData['movieImdbId']; ?>">
-       <?php
-       if(!empty($TemplateData['movieData'])) {
-               foreach($TemplateData['movieData'] as $k=>$v) {
-       ?>
-
-       <div class="uk-width-1-2@s uk-overflow-hidden">
-               <p>
-                       <b><?php echo $k; ?></b><br />
-                       <?php echo toolMethod_DisplayValues($v); ?>
-               </p>
-       </div>
-       <div class="uk-width-1-2@s">
-               <select class="uk-select" name="fdata[into][<?php echo $k; ?>]">
-                       <option value=""><?php echo $I18n->t('tool.imdb.select.none'); ?></option>
-                       <?php echo toolMethod_GetTargetSelection($TemplateData['saveToSelection'],$k); ?>
-               </select>
-       </div>
-
-       <?php
-               }
-       }
-       ?>
-
-       <div class="uk-margin">
-               <button class="uk-button uk-button-primary" type="submit" name="submitFormSave">
-                       <?php echo $I18n->t('tool.imdb.save'); ?>
-               </button>
-       </div>
-
-</form>
-<?php } ?>
diff --git a/webclient/view/default/tool/tool-imdbweb.php b/webclient/view/default/tool/tool-imdbweb.php
deleted file mode 100644 (file)
index e920711..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-<?php
-/**
- * Bibliotheca
- *
- * Copyright 2018-2023 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
- */
-
-/**
- * this is the special file for a tool.
- * Requirements and more information come from the main tool.php file
- */
-
-/*
-require_once 'lib/imdbwebparser.class.php';
-if(file_exists(PATH_ABSOLUTE.'/config/config-imdbweb.php')) {
-       require_once 'config/config-imdbweb.php';
-}
-
-$IMDB = new IMDB(array(
-       'sSearchFor' => TOOL_IMDBWEB_SEARCH,
-       'showFields' => TOOL_IMDBWEB_FIELDS,
-       'storage' => PATH_SYSTEMOUT,
-       'browserAgent' => TOOL_IMDBWEB_BROWSER_AGENT,
-       'browserLang' => TOOL_IMDBWEB_BROWSER_ACCEPT_LANG,
-       'browserAccept' => TOOL_IMDBWEB_BROWSER_ACCEPT,
-       'debug' => false
-));
-*/
-
-require_once 'lib/imdbweb.class.php';
-if(file_exists(PATH_ABSOLUTE.'/config/config-imdbweb.php')) {
-    require_once 'config/config-imdbweb.php';
-}
-$IMDB = new IMDBWEB(array(
-    'storage' => TOOL_IMDBWEB_CACHE_STORAGE,
-    'browserAgent' => TOOL_IMDBWEB_BROWSER_AGENT,
-    'browserLang' => TOOL_IMDBWEB_BROWSER_ACCEPT_LANG,
-    'browserAccept' => TOOL_IMDBWEB_BROWSER_ACCEPT,
-    'attributes' => TOOL_IMDBWEB_ATTRIBUTES,
-    'debug' => DEBUG
-));
-
-
-$TemplateData['movieData'] = array();
-$TemplateData['saveToSelection'] = '';
-$TemplateData['showMatchingForm'] = false;
-
-// prepare fields to save into selection
-// create one time and then reuse it
-$collectionFields = $ManangeCollectionsFields->getExistingFields(false, true);
-if(!empty($collectionFields)) {
-       foreach ($collectionFields as $k=>$v) {
-               $TemplateData['saveToSelection'] .= "<option value='".$k."' sel_".$v['identifier'].">".$I18n->t($v['displayname'])."</option>\n";
-       }
-}
-
-if(isset($_POST['submitFormSearch'])) {
-       $fdata = $_POST['fdata'];
-       if (!empty($fdata)) {
-           // this is the imdb id
-               $search = trim($fdata['search']);
-               $search = Summoner::validate($search, "nospace") ? $search : false;
-
-               if (!empty($search)) {
-            $mData = $IMDB->search($search);
-
-                       if (!empty($mData)) {
-                               $TemplateData['movieData'] = $mData;
-                               $TemplateData['movieImdbId'] = $search;
-                               $TemplateData['showMatchingForm'] = true;
-                       } else {
-                               $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound');
-                               $TemplateData['message']['status'] = "error";
-                       }
-               }
-               else {
-                       $TemplateData['message']['content'] = $I18n->t('global.message.invalidSearchTerm');
-                       $TemplateData['message']['status'] = "error";
-               }
-       }
-}
-
-if(isset($_POST['submitFormSave'])) {
-       $fdata = $_POST['fdata'];
-       if (!empty($fdata)) {
-               $_imdbId = $fdata['imdbId'];
-               $_imdbId = Summoner::validate($_imdbId,'nospace') ? $_imdbId : false;
-
-               if(!empty($_imdbId)) {
-                   // 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 (!empty($mData)) {
-                               $TemplateData['movieImdbId'] = $_imdbId;
-
-                               // build data array based on submit
-                               // see creation log for structure
-                               $_data = array();
-                               foreach($fdata['into'] as $k=>$v) {
-                                       if(!empty($v)) {
-                                               $_t = $mData[$k];
-
-                                               // multiple selections format for field type lookup_multiple
-                                               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;
-                            }
-                                               }
-                                       }
-                               }
-
-                               $_r = $Tools->getDefaultCreationInfo();
-                               if(!empty($TemplateData['editEntry'])) {
-                                       // update existing one
-                                       $do = $Manageentry->create($_data,
-                                               $_r['id'],
-                                               $_r['group'],
-                                               $_r['rights'],
-                                               $TemplateData['editEntry']['id']
-                                       );
-                                       $TemplateData['message']['content'] = $I18n->t('global.message.dataSaved');
-                               }
-                               else {
-                                       // create into loaded collection
-                                       $do = $Manageentry->create($_data,
-                                               $_r['id'],
-                                               $_r['group'],
-                                               $_r['rights']
-                                       );
-                                       $TemplateData['message']['content'] = $I18n->t('global.message.dataSaved')." <a href='index.php?p=manageentry&collection=".$collection['id']."&id=".$do."'>".$I18n->t('global.view')."</a>";
-                               }
-
-                               if(!empty($do)) {
-                                       $TemplateData['message']['status'] = "success";
-                               }
-                               else {
-                                       $TemplateData['message']['content'] = $I18n->t('global.message.couldNotBeSaved');
-                                       $TemplateData['message']['status'] = "error";
-                               }
-                       } else {
-                               $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound');
-                               $TemplateData['message']['status'] = "error";
-                       }
-               }
-               else {
-                       $TemplateData['message']['content'] = $I18n->t('tool.imdb.search.missingid');
-                       $TemplateData['message']['status'] = "error";
-               }
-       }
-}
-
-
-/**
- * Helper function. Takes the prebuild options for the target selection field and search for a matching key.
- * Since the optionString is prebuild, avoiding looping over and over again, the selection needs to be done
- * by search and replace.
- * Checks if TOOL_IMDBWEB_FIELDS_TO is defined and a matching key=>value pair is available
- *
- * @param string $optionString
- * @param string $imdbKey
- * @return string
- */
-function toolMethod_GetTargetSelection(string $optionString, string $imdbKey): string {
-       if(defined('TOOL_IMDBWEB_FIELDS_TO') & !empty($imdbKey)) {
-               if(isset(TOOL_IMDBWEB_FIELDS_TO[$imdbKey])) {
-                       $_k = "sel_".TOOL_IMDBWEB_FIELDS_TO[$imdbKey];
-                       $optionString = str_replace($_k,'selected="selected"',$optionString);
-               }
-       }
-
-       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;
-}
diff --git a/webclient/view/default/tool/tool-metacriticMovie.html b/webclient/view/default/tool/tool-metacriticMovie.html
new file mode 100644 (file)
index 0000000..c58db14
--- /dev/null
@@ -0,0 +1,62 @@
+<p><b><?php echo $I18n->t('tool.limitations'); ?></b></p>
+<p><?php echo $I18n->t('tool.limitations.override'); ?></p>
+<p>
+       <?php echo $I18n->t('tool.metacritic.search.hint'); ?>
+
+</p>
+<p>
+       <?php echo $I18n->t('tool.metacritic.posters'); ?> <a href="https://www.impawards.com/" target=_blank>impawards</a>
+</p>
+
+<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.metacritic.id'); ?></label>
+               <div class="uk-form-controls">
+                       <input class="uk-input" id="search" type="text" autocomplete="off"
+                              placeholder="zootopia-2"
+                              name="fdata[search]"
+                              value=""
+                       >
+               </div>
+               <div class="uk-margin">
+                       <button class="uk-button uk-button-primary" type="submit" name="submitFormSearch">
+                               <?php echo $I18n->t('tool.metacritic.fetch'); ?>
+                       </button>
+               </div>
+       </div>
+</form>
+
+<?php if($TemplateData['showMatchingForm']) { ?>
+<form class="uk-form-horizontal uk-margin-small uk-grid-small" method="post" enctype="multipart/form-data" uk-grid>
+       <input type="hidden" name="fdata[urlpart]" value="<?php echo $TemplateData['urlpart']; ?>">
+       <?php
+       if(!empty($TemplateData['movieData'])) {
+               foreach($TemplateData['movieData'] as $k=>$v) {
+       ?>
+
+       <div class="uk-width-1-2@s uk-overflow-hidden">
+               <p>
+                       <b><?php echo $k; ?></b><br />
+                       <?php echo toolMethod_DisplayValues($v); ?>
+               </p>
+       </div>
+       <div class="uk-width-1-2@s">
+               <select class="uk-select" name="fdata[into][<?php echo $k; ?>]">
+                       <option value=""><?php echo $I18n->t('tool.metacritic.select.none'); ?></option>
+                       <?php echo toolMethod_GetTargetSelection($TemplateData['saveToSelection'],$k); ?>
+               </select>
+       </div>
+
+       <?php
+               }
+       }
+       ?>
+
+       <div class="uk-margin">
+               <button class="uk-button uk-button-primary" type="submit" name="submitFormSave">
+                       <?php echo $I18n->t('tool.metacritic.save'); ?>
+               </button>
+       </div>
+
+</form>
+<?php } ?>
diff --git a/webclient/view/default/tool/tool-metacriticMovie.php b/webclient/view/default/tool/tool-metacriticMovie.php
new file mode 100644 (file)
index 0000000..393f7ca
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * Bibliotheca
+ *
+ * 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
+ *  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
+ */
+
+/**
+ * this is the special file for a tool.
+ * Requirements and more information come from the main tool.php file
+ */
+
+require_once 'lib/metacritic.class.php';
+if(file_exists(PATH_ABSOLUTE.'/config/config-metacritic.php')) {
+    require_once 'config/config-metacritic.php';
+}
+$METACRITIC = new Metacritic(array(
+    'storage' => TOOL_METACRITIC_CACHE_STORAGE,
+    'browserAgent' => TOOL_METACRITIC_BROWSER_AGENT,
+    'browserLang' => TOOL_METACRITIC_BROWSER_ACCEPT_LANG,
+    'browserAccept' => TOOL_METACRITIC_BROWSER_ACCEPT,
+    'attributes' => TOOL_METACRITIC_ATTRIBUTES_MOVIE,
+    'debug' => DEBUG,
+    'type' => 'movie'
+));
+
+$TemplateData['movieData'] = array();
+$TemplateData['saveToSelection'] = '';
+$TemplateData['showMatchingForm'] = false;
+
+// prepare fields to save into selection
+// create one time and then reuse it
+$collectionFields = $ManangeCollectionsFields->getExistingFields(false, true);
+if(!empty($collectionFields)) {
+    foreach ($collectionFields as $k=>$v) {
+        $TemplateData['saveToSelection'] .= "<option value='".$k."' sel_".$v['identifier'].">".$I18n->t($v['displayname'])."</option>\n";
+    }
+}
+
+if(isset($_POST['submitFormSearch'])) {
+    $fdata = $_POST['fdata'];
+    if (!empty($fdata)) {
+        $search = trim($fdata['search']);
+        $search = Summoner::validate($search, "nospaceP") ? $search : false;
+
+        if (!empty($search)) {
+            $mData = $METACRITIC->fetch($search);
+
+            if (!empty($mData)) {
+                $TemplateData['movieData'] = $mData;
+                $TemplateData['urlpart'] = $search;
+                $TemplateData['showMatchingForm'] = true;
+            } else {
+                $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound');
+                $TemplateData['message']['status'] = "error";
+            }
+        }
+        else {
+            $TemplateData['message']['content'] = $I18n->t('global.message.invalidSearchTerm');
+            $TemplateData['message']['status'] = "error";
+        }
+    }
+}
+
+if(isset($_POST['submitFormSave'])) {
+    $fdata = $_POST['fdata'];
+    if (!empty($fdata)) {
+        $_urlpart = $fdata['urlpart'];
+        $_urlpart = Summoner::validate($_urlpart,'nospaceP') ? $_urlpart : false;
+
+        if(!empty($_urlpart)) {
+            // 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 = $METACRITIC->fetch($_urlpart);
+
+            if (!empty($mData)) {
+                $TemplateData['urlpart'] = $_urlpart;
+
+                // build data array based on submit
+                // see creation log for structure
+                $_data = array();
+                foreach($fdata['into'] as $k=>$v) {
+                    if(!empty($v)) {
+                        $_t = $mData[$k];
+
+                        // multiple selections format for field type lookup_multiple
+                        if(is_array($_t)) {
+                            $_t = implode(",", $_t);
+                        }
+
+                        if(isset($collectionFields[$v])) {
+                            $_data[$v] = $collectionFields[$v];
+                            $_data[$v]['valueToSave'] = $_t;
+                        }
+                    }
+                }
+
+                $_r = $Tools->getDefaultCreationInfo();
+                if(!empty($TemplateData['editEntry'])) {
+                    // update existing one
+                    $do = $Manageentry->create($_data,
+                        $_r['id'],
+                        $_r['group'],
+                        $_r['rights'],
+                        $TemplateData['editEntry']['id']
+                    );
+                    $TemplateData['message']['content'] = $I18n->t('global.message.dataSaved');
+                }
+                else {
+                    // create into loaded collection
+                    $do = $Manageentry->create($_data,
+                        $_r['id'],
+                        $_r['group'],
+                        $_r['rights']
+                    );
+                    $TemplateData['message']['content'] = $I18n->t('global.message.dataSaved')." <a href='index.php?p=manageentry&collection=".$collection['id']."&id=".$do."'>".$I18n->t('global.view')."</a>";
+                }
+
+                if(!empty($do)) {
+                    $TemplateData['message']['status'] = "success";
+                }
+                else {
+                    $TemplateData['message']['content'] = $I18n->t('global.message.couldNotBeSaved');
+                    $TemplateData['message']['status'] = "error";
+                }
+            } else {
+                $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound');
+                $TemplateData['message']['status'] = "error";
+            }
+        }
+        else {
+            $TemplateData['message']['content'] = $I18n->t('tool.imdb.search.missingid');
+            $TemplateData['message']['status'] = "error";
+        }
+    }
+}
+
+
+/**
+ * Helper function. Takes the prebuild options for the target selection field and search for a matching key.
+ * Since the optionString is prebuild, avoiding looping over and over again, the selection needs to be done
+ * by search and replace.
+ * Checks if TOOL_METACRITIC_FIELDS_TO_MOVIE is defined and a matching key=>value pair is available
+ *
+ * @param string $optionString
+ * @param string $key
+ * @return string
+ */
+function toolMethod_GetTargetSelection(string $optionString, string $key): string {
+    if(defined('TOOL_METACRITIC_FIELDS_TO_MOVIE') & !empty($key)) {
+        if(isset(TOOL_METACRITIC_FIELDS_TO_MOVIE[$key])) {
+            $_k = "sel_".TOOL_METACRITIC_FIELDS_TO_MOVIE[$key];
+            $optionString = str_replace($_k,'selected="selected"',$optionString);
+        }
+    }
+
+    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;
+}