-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
+++ /dev/null
-# 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.
--- /dev/null
+# 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.
--- /dev/null
+# 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.
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';
+++ /dev/null
-<?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';
--- /dev/null
+<?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';
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';
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"
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."
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"
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."
+++ /dev/null
-<?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;
- }
-}
--- /dev/null
+<?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;
+ }
+}
+++ /dev/null
-<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 } ?>
--- /dev/null
+<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 } ?>
+++ /dev/null
-<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 } ?>
+++ /dev/null
-<?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;
-}
--- /dev/null
+<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 } ?>
--- /dev/null
+<?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;
+}