false, 'Logger' => null, 'ReleaseImage' => null, 'Settings' => null, 'TMDb' => null, ]; $options += $defaults; $this->pdo = ($options['Settings'] instanceof Settings ? $options['Settings'] : new Settings()); $this->releaseImage = ($options['ReleaseImage'] instanceof \ReleaseImage ? $options['ReleaseImage'] : new \ReleaseImage($this->pdo)); $this->lookuplanguage = ($this->pdo->getSetting('lookuplanguage') != '') ? (string)$this->pdo->getSetting('lookuplanguage') : 'en'; $this->fanartapikey = $this->pdo->getSetting('fanarttvkey'); $this->imdburl = ($this->pdo->getSetting('imdburl') == 0 ? false : true); $this->movieqty = ($this->pdo->getSetting('maximdbprocessed') != '') ? $this->pdo->getSetting('maximdbprocessed') : 100; $this->searchEngines = true; $this->showPasswords = Releases::showPasswords($this->pdo); $this->debug = NN_DEBUG; $this->echooutput = ($options['Echo'] && NN_ECHOCLI && $this->pdo->cli); $this->imgSavePath = NN_COVERS . 'movies' . DS; $this->service = ''; if (NN_DEBUG || NN_LOGGING) { $this->debug = true; try { $this->debugging = new \Logger(); } catch (\LoggerException $error) { $this->_debug = false; } } } /** * Get info for a IMDB id. * * @param int $imdbId * * @return array|bool */ public function getMovieInfo($imdbId) { return $this->pdo->queryOneRow(sprintf("SELECT * FROM movieinfo WHERE imdbid = %d", $imdbId)); } /** * Get info for multiple IMDB id's. * * @param array $imdbIDs * * @return array */ public function getMovieInfoMultiImdb($imdbIDs) { return $this->pdo->query( sprintf(" SELECT DISTINCT movieinfo.*, releases.imdbid AS relimdb FROM movieinfo LEFT OUTER JOIN releases ON releases.imdbid = movieinfo.imdbid WHERE movieinfo.imdbid IN (%s)", str_replace( ',,', ',', str_replace( ['(,', ' ,', ', )', ',)'], '', implode(',', $imdbIDs) ) ) ), true, NN_CACHE_EXPIRY_MEDIUM ); } /** * Get movies for movie-list admin page. * * @param int $start * @param int $num * * @return array */ public function getRange($start, $num) { return $this->pdo->query( sprintf(' SELECT * FROM movieinfo ORDER BY createddate DESC %s', ($start === false ? '' : ' LIMIT ' . $num . ' OFFSET ' . $start) ) ); } /** * Get count of movies for movie-list admin page. * * @return int */ public function getCount() { $res = $this->pdo->queryOneRow('SELECT COUNT(id) AS num FROM movieinfo'); return ($res === false ? 0 : $res['num']); } /** * Get count of movies for movies browse page. * * @param $cat * @param $maxAge * @param array $excludedCats * * @return int */ public function getMovieCount($cat, $maxAge = -1, $excludedCats = []) { $catsrch = ''; if (count($cat) > 0 && $cat[0] != -1) { $catsrch = (new \Category(['Settings' => $this->pdo]))->getCategorySearch($cat); } $res = $this->pdo->queryOneRow( sprintf(" SELECT COUNT(DISTINCT r.imdbid) AS num FROM releases r INNER JOIN movieinfo m ON m.imdbid = r.imdbid WHERE r.nzbstatus = 1 AND r.imdbid != '0000000' AND m.cover = 1 AND m.title != '' AND r.passwordstatus %s AND %s %s %s %s ", $this->showPasswords, $this->getBrowseBy(), $catsrch, ($maxAge > 0 ? 'AND r.postdate > NOW() - INTERVAL ' . $maxAge . ' DAY' : ''), (count($excludedCats) > 0 ? ' AND r.categoryid NOT IN (' . implode(',', $excludedCats) . ')' : '') ) ); return ($res === false ? 0 : $res['num']); } /** * Get movie releases with covers for movie browse page. * * @param $cat * @param $start * @param $num * @param $orderBy * @param $maxAge * @param array $excludedCats * * @return bool|PDOStatement */ public function getMovieRange($cat, $start, $num, $orderBy, $maxAge = -1, $excludedCats = []) { $catsrch = ''; if (count($cat) > 0 && $cat[0] != -1) { $catsrch = (new \Category(['Settings' => $this->pdo]))->getCategorySearch($cat); } $order = $this->getMovieOrder($orderBy); $sql = sprintf(" SELECT GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id, GROUP_CONCAT(r.rarinnerfilecount ORDER BY r.postdate DESC SEPARATOR ',') as grp_rarinnerfilecount, GROUP_CONCAT(r.haspreview ORDER BY r.postdate DESC SEPARATOR ',') AS grp_haspreview, GROUP_CONCAT(r.passwordstatus ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_password, GROUP_CONCAT(r.guid ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_guid, GROUP_CONCAT(rn.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_nfoid, GROUP_CONCAT(groups.name ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grpname, GROUP_CONCAT(r.searchname ORDER BY r.postdate DESC SEPARATOR '#') AS grp_release_name, GROUP_CONCAT(r.postdate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_postdate, GROUP_CONCAT(r.size ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_size, GROUP_CONCAT(r.totalpart ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_totalparts, GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_comments, GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grabs, GROUP_CONCAT(r.failed ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_failed, m.*, groups.name AS group_name, rn.id as nfoid FROM releases r LEFT OUTER JOIN groups ON groups.id = r.groupid LEFT OUTER JOIN releasenfo rn ON rn.releaseid = r.id INNER JOIN movieinfo m ON m.imdbid = r.imdbid WHERE r.nzbstatus = 1 AND r.imdbid != '0000000' AND m.title != '' AND r.passwordstatus %s AND %s %s %s %s GROUP BY m.imdbid ORDER BY %s %s %s", $this->showPasswords, $this->getBrowseBy(), $catsrch, ($maxAge > 0 ? 'AND r.postdate > NOW() - INTERVAL ' . $maxAge . 'DAY ' : '' ), (count($excludedCats) > 0 ? ' AND r.categoryid NOT IN (' . implode(',', $excludedCats) . ')' : ''), $order[0], $order[1], ($start === false ? '' : ' LIMIT ' . $num . ' OFFSET ' . $start) ); return $this->pdo->query($sql, true, NN_CACHE_EXPIRY_MEDIUM); } /** * Get the order type the user requested on the movies page. * * @param $orderBy * * @return array */ protected function getMovieOrder($orderBy) { $orderArr = explode('_', (($orderBy == '') ? 'MAX(r.postdate)' : $orderBy)); switch ($orderArr[0]) { case 'title': $orderField = 'm.title'; break; case 'year': $orderField = 'm.year'; break; case 'rating': $orderField = 'm.rating'; break; case 'posted': default: $orderField = 'MAX(r.postdate)'; break; } return [$orderField, ((isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1])) ? $orderArr[1] : 'desc')]; } /** * Order types for movies page. * * @return array */ public function getMovieOrdering() { return ['title_asc', 'title_desc', 'year_asc', 'year_desc', 'rating_asc', 'rating_desc']; } /** * @return string */ protected function getBrowseBy() { $browseBy = ' '; $browseByArr = ['title', 'director', 'actors', 'genre', 'rating', 'year', 'imdb']; foreach ($browseByArr as $bb) { if (isset($_REQUEST[$bb]) && !empty($_REQUEST[$bb])) { $bbv = stripslashes($_REQUEST[$bb]); if ($bb === 'rating') { $bbv .= '.'; } if ($bb === 'imdb') { $browseBy .= 'm.' . $bb . 'id = ' . $bbv . ' AND '; } else { $browseBy .= 'm.' . $bb . ' ' . $this->pdo->likeString($bbv, true, true) . ' AND '; } } } return $browseBy; } /** * @var null|TraktTv */ public $traktTv = null; /** * Get trailer using IMDB Id. * * @param int $imdbID * * @return bool|string */ public function getTrailer($imdbID) { if (!is_numeric($imdbID)) { return false; } $trailer = $this->pdo->queryOneRow("SELECT trailer FROM movieinfo WHERE imdbid = $imdbID and trailer != ''"); if ($trailer) { return $trailer['trailer']; } if (is_null($this->traktTv)) { $this->traktTv = new TraktTv(['Settings' => $this->pdo]); } $data = $this->traktTv->movieSummary('tt' . $imdbID, 'full,images'); if ($data) { $this->parseTraktTv($data); if (isset($data['trailer']) && !empty($data['trailer'])) { return $data['trailer']; } } $trailer = Utility::imdb_trailers($imdbID); if ($trailer) { $this->pdo->queryExec( 'UPDATE movieinfo SET trailer = ' . $this->pdo->escapeString($trailer) . ' WHERE imdbid = ' . $imdbID ); return $trailer; } return false; } /** * Parse trakt info, insert into DB. * * @param array $data * * @return mixed|void */ public function parseTraktTv(&$data) { if (!isset($data['ids']['imdb']) || empty($data['ids']['imdb'])) { return false; } if (isset($data['trailer']) && !empty($data['trailer'])) { $data['trailer'] = str_ireplace( 'http://', 'https://', str_ireplace('watch?v=', 'embed/', $data['trailer']) ); return $data['trailer']; } $cover = 0; if (is_file($this->imgSavePath . $data['ids']['imdb'] . '-cover.jpg')) { $cover = 1; } else { $link = $this->checkTraktValue($data['images']['poster']['thumb']); if ($link) { $cover = $this->releaseImage->saveImage($data['ids']['imdb'] . '-cover', $link, $this->imgSavePath); } } $this->update([ 'genres' => $this->checkTraktValue($data['genres']), 'imdbid' => $this->checkTraktValue(str_ireplace('tt', '', $data['ids']['imdb'])), 'language' => $this->checkTraktValue($data['language']), 'plot' => $this->checkTraktValue($data['overview']), 'rating' => round($this->checkTraktValue($data['rating']), 1), 'tagline' => $this->checkTraktValue($data['tagline']), 'title' => $this->checkTraktValue($data['title']), 'tmdbid' => $this->checkTraktValue($data['ids']['tmdb']), 'trailer' => $this->checkTraktValue($data['trailer']), 'cover' => $cover, 'year' => $this->checkTraktValue($data['year']) ]); } /** * Checks if the value is set and not empty, returns it, else empty string. * * @param mixed $value * * @return string */ private function checkTraktValue($value) { if (is_array($value)) { $temp = ''; foreach($value as $val) { if (!is_array($val) && !is_object($val)) { $temp .= (string)$val; } } $value = $temp; } return (isset($value) && !empty($value) ? $value : ''); } /** * Create click-able links to IMDB actors/genres/directors/etc.. * * @param $data * @param $field * * @return string */ public function makeFieldLinks($data, $field) { if (!isset($data[$field]) || $data[$field] == '') { return ''; } $tmpArr = explode(', ', $data[$field]); $newArr = []; $i = 0; foreach ($tmpArr as $ta) { if (trim($ta) == '') { continue; } if ($i > 5) { break; } //only use first 6 $newArr[] = '' . $ta . ''; $i++; } return implode(', ', $newArr); } /** * Get array of column keys, for inserting / updating. * @return array */ public function getColumnKeys() { return [ 'actors','backdrop','cover','director','genre','imdbid','language', 'plot','rating','tagline','title','tmdbid', 'trailer','type','year', 'traktid' ]; } /** * Update movie on movie-edit page. * * @param array $values Array of keys/values to update. See $validKeys * @return int|bool */ public function update(array $values) { if (!count($values)) { return false; } $validKeys = $this->getColumnKeys(); $query = [ '0' => 'INSERT INTO movieinfo (updateddate, createddate, ', '1' => ' VALUES (NOW(), NOW(), ', '2' => 'ON DUPLICATE KEY UPDATE updateddate = NOW(), ' ]; $found = 0; foreach ($values as $key => $value) { if (in_array($key, $validKeys) && !empty($value)) { $found++; $query[0] .= "$key, "; if (in_array($key, ['genre', 'language'])) { $value = substr($value, 0, 64); } $value = $this->pdo->escapeString($value); $query[1] .= "$value, "; $query[2] .= "$key = $value, "; } } if (!$found) { return false; } foreach ($query as $key => $value) { $query[$key] = rtrim($value, ', '); } return $this->pdo->queryInsert($query[0] . ') ' . $query[1] . ') ' . $query[2]); } /** * Check if a variable is set and not a empty string. * * @param $variable * * @return string */ protected function checkVariable(&$variable) { if (isset($variable) && $variable != '') { return true; } return false; } /** * Returns a tmdb, imdb or trakt variable, the one that is set. Empty string if both not set. * * @param string $variable1 * @param string $variable2 * @param string $variable3 * * @return string */ protected function setTmdbImdbTraktVar(&$variable1, &$variable2, &$variable3) { if ($this->checkVariable($variable1)) { return $variable1; } elseif ($this->checkVariable($variable2)) { return $variable2; } elseif ($this->checkVariable($variable3)) { return $variable3; } return ''; } /** * Fetch IMDB/TMDB/TRAKT info for the movie. * * @param $imdbId * * @return bool */ public function updateMovieInfo($imdbId) { if ($this->echooutput && $this->service !== '') { $this->pdo->log->doEcho($this->pdo->log->primary("Fetching IMDB info from TMDB and/or Trakt using IMDB id: " . $imdbId)); } // Check TMDB for IMDB info. $tmdb = $this->fetchTMDBProperties($imdbId); // Check IMDB for movie info. $imdb = $this->fetchIMDBProperties($imdbId); // Check TRAKT for movie info $trakt = $this->fetchTraktTVProperties($imdbId); if (!$imdb && !$tmdb && !$trakt) { return false; } // Check FanArt.tv for cover and background images. $fanart = $this->fetchFanartTVProperties($imdbId); $mov = []; $mov['cover'] = $mov['backdrop'] = $mov['banner'] = $movieID = 0; $mov['type'] = $mov['director'] = $mov['actors'] = $mov['language'] = ''; $mov['imdbid'] = $imdbId; $mov['tmdbid'] = (!isset($tmdb['tmdbid']) || $tmdb['tmdbid'] == '') ? 0 : $tmdb['tmdbid']; $mov['traktid'] = $trakt['id']; // Prefer Fanart.tv cover over TRAKT, TRAKT over TMDB and TMDB over IMDB. if ($this->checkVariable($fanart['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId . '-cover', $fanart['cover'], $this->imgSavePath); } else if ($this->checkVariable($trakt['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId . '-cover', $trakt['cover'], $this->imgSavePath); } else if ($this->checkVariable($tmdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId . '-cover', $tmdb['cover'], $this->imgSavePath); } else if ($this->checkVariable($imdb['cover'])) { $mov['cover'] = $this->releaseImage->saveImage($imdbId . '-cover', $imdb['cover'], $this->imgSavePath); } // Backdrops. if ($this->checkVariable($fanart['backdrop'])) { $mov['backdrop'] = $this->releaseImage->saveImage($imdbId . '-backdrop', $fanart['backdrop'], $this->imgSavePath, 1920, 1024); } else if ($this->checkVariable($tmdb['backdrop'])) { $mov['backdrop'] = $this->releaseImage->saveImage($imdbId . '-backdrop', $tmdb['backdrop'], $this->imgSavePath, 1920, 1024); } // Banner if ($this->checkVariable($fanart['banner'])) { $mov['banner'] = $this->releaseImage->saveImage($imdbId . '-banner', $fanart['banner'], $this->imgSavePath); } $mov['title'] = $this->setTmdbImdbTraktVar($imdb['title'] , $tmdb['title'], $trakt['title']); $mov['rating'] = $this->setTmdbImdbTraktVar($imdb['rating'] , $tmdb['rating'], $trakt['rating']); $mov['plot'] = $this->setTmdbImdbTraktVar($imdb['plot'] , $tmdb['plot'], $trakt['overview']); $mov['tagline'] = $this->setTmdbImdbTraktVar($imdb['tagline'], $tmdb['tagline'], $trakt['tagline']); $mov['year'] = $this->setTmdbImdbTraktVar($imdb['year'] , $tmdb['year'], $trakt['year']); $mov['genre'] = $this->setTmdbImdbTraktVar($imdb['genre'] , $tmdb['genre'], $trakt['genres']); if ($this->checkVariable($imdb['type'])) { $mov['type'] = $imdb['type']; } if ($this->checkVariable($imdb['director'])) { $mov['director'] = (is_array($imdb['director'])) ? implode(', ', array_unique($imdb['director'])) : $imdb['director']; } if ($this->checkVariable($imdb['actors'])) { $mov['actors'] = (is_array($imdb['actors'])) ? implode(', ', array_unique($imdb['actors'])) : $imdb['actors']; } if ($this->checkVariable($imdb['language'])) { $mov['language'] = (is_array($imdb['language'])) ? implode(', ', array_unique($imdb['language'])) : $imdb['language']; } if (is_array($mov['genre'])) { $mov['genre'] = implode(', ', array_unique($mov['genre'])); } if (is_array($mov['type'])) { $mov['type'] = implode(', ', array_unique($mov['type'])); } $mov['title'] = html_entity_decode($mov['title'] , ENT_QUOTES, 'UTF-8'); $mov['title'] = str_replace(['/', '\\'], '', $mov['title']); $movieID = $this->update([ 'actors' => html_entity_decode($mov['actors'] , ENT_QUOTES, 'UTF-8'), 'backdrop' => $mov['backdrop'], 'cover' => $mov['cover'], 'director' => html_entity_decode($mov['director'], ENT_QUOTES, 'UTF-8'), 'genre' => html_entity_decode($mov['genre'] , ENT_QUOTES, 'UTF-8'), 'imdbid' => $mov['imdbid'], 'language' => html_entity_decode($mov['language'], ENT_QUOTES, 'UTF-8'), 'plot' => html_entity_decode(preg_replace('/\s+See full summary ยป/', ' ', $mov['plot']), ENT_QUOTES, 'UTF-8'), 'rating' => round($mov['rating'], 1), 'tagline' => html_entity_decode($mov['tagline'] , ENT_QUOTES, 'UTF-8'), 'title' => $mov['title'], 'tmdbid' => $mov['tmdbid'], 'type' => html_entity_decode(ucwords(preg_replace('/[\.\_]/', ' ', $mov['type'])), ENT_QUOTES, 'UTF-8'), 'year' => $mov['year'], 'traktid' => $mov['traktid'] ]); if ($this->echooutput && $this->service !== '') { $this->pdo->log->doEcho( $this->pdo->log->headerOver(($movieID !== 0 ? 'Added/updated movie: ' : 'Nothing to update for movie: ')) . $this->pdo->log->primary($mov['title'] . ' (' . $mov['year'] . ') - ' . $mov['imdbid'] ) ); } return ($movieID === 0 ? false : true); } /** * Fetch FanArt.tv backdrop / cover / title. * * @param $imdbId * * @return bool|array */ protected function fetchFanartTVProperties($imdbId) { if ($this->fanartapikey != '') { $buffer = Utility::getUrl(['url' => 'https://webservice.fanart.tv/v3/movies/' . 'tt' . $imdbId . '?api_key=' . $this->fanartapikey , 'verifycert' => false]); if ($buffer !== false) { $art = json_decode($buffer, true); if (isset($art['status']) && $art['status'] === 'error') { return false; } $ret = []; if ($this->checkVariable($art['moviebackground'][0]['url'])) { $ret['backdrop'] = $art['moviebackground'][0]['url']; } else if ($this->checkVariable($art['moviethumb'][0]['url'])) { $ret['backdrop'] = $art['moviethumb'][0]['url']; } if ($this->checkVariable($art['movieposter'][0]['url'])) { $ret['cover'] = $art['movieposter'][0]['url']; } if ($this->checkVariable($art['moviebanner'][0]['url'])) { $ret['banner'] = $art['moviebanner'][0]['url']; } if (isset($ret['backdrop']) && isset($ret['cover'])) { $ret['title'] = $imdbId; if (isset($art['name'])) { $ret['title'] = $art['name']; } if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->alternateOver("Fanart Found ") . $this->pdo->log->headerOver($ret['title']), true); } return $ret; } } } return false; } /** * Fetch info for IMDB id from TMDB. * * @param $imdbId * @param bool $text * * @return array|bool */ public function fetchTMDBProperties($imdbId, $text = false) { $lookupId = ($text === false ? 'tt' . $imdbId : $imdbId); $tmdb = new TMDB($this->pdo->getSetting('tmdbkey')); try { $tmdbLookup = $tmdb->getMovie($lookupId); } catch (exception $e) { return false; } /*$status = $tmdbLookup->get('status_code'); if (!$status || (isset($status) && $status !== 1)) { return false; }*/ $ret = []; $ret['title'] = $tmdbLookup->get('title'); if ($this->currentTitle !== '') { // Check the similarity. similar_text($this->currentTitle, $ret['title'], $percent); if ($percent < 40) { if ($this->debug) { $this->debugging->log( get_class(), __FUNCTION__, 'Found (' . $ret['title'] . ') from TMDB, but it\'s only ' . $percent . '% similar to (' . $this->currentTitle . ')', \Logger::LOG_INFO ); } return false; } } $ret['tmdbid'] = $tmdbLookup->getID(); $ImdbID = str_replace('tt', '', $tmdbLookup->get('imdb_id')); $ret['imdb_id'] = $ImdbID; $vote = $tmdbLookup->getVoteAverage(); if (isset($vote)) { $ret['rating'] = ($vote == 0) ? '' : $vote; } $overview = $tmdbLookup->get('overview'); if (isset($overview)) { $ret['plot'] = $overview; } $tagline = $tmdbLookup->getTagline(); if (isset($tagline)) { $ret['tagline'] = $tagline; } $released = $tmdbLookup->get('release_date'); if (isset($released)) { $ret['year'] = date("Y", strtotime($released)); } $genresa = $tmdbLookup->get('genres'); if (isset($genresa) && sizeof($genresa) > 0) { $genres = []; foreach ($genresa as $genre) { $genres[] = $genre['name']; } $ret['genre'] = $genres; } $posterp = $tmdbLookup->getPoster(); if (isset($posterp) && sizeof($posterp > 0)) { $ret['cover'] = "http://image.tmdb.org/t/p/w185" . $posterp; } $backdrop = $tmdbLookup->get('backdrop_path'); if (isset($backdrop) && sizeof($backdrop) > 0) { $ret['backdrop'] = "http://image.tmdb.org/t/p/original" . $backdrop; } if ($this->echooutput) { $this->pdo->log->doEcho($this->pdo->log->primaryOver("TMDb Found ") . $this->pdo->log->headerOver($ret['title']), true); } return $ret; } /** * @param $imdbId * * @return array|bool */ protected function fetchIMDBProperties($imdbId) { $imdb_regex = [ 'title' => '/
\s*?(.*?)\s*?<\/p>/i', 'rating' => '/"ratingValue">([\d.]+)<\/span>/i', 'year' => '/