Files
Gazelle-Porn/classes/torrents.class.php
T
2022-06-08 11:46:36 +00:00

1914 lines
71 KiB
PHP

<?
use Gazelle\Torrent\EditionType;
use Gazelle\Torrent\EditionInfo;
class Torrents {
const FILELIST_DELIM = 0xF7; // Hex for &divide; Must be the same as phrase_boundary in sphinx.conf!
const SNATCHED_UPDATE_INTERVAL = 3600; // How often we want to update users' snatch lists
const SNATCHED_UPDATE_AFTERDL = 300; // How long after a torrent download we want to update a user's snatch lists
public static function get_thumb_counts($GroupID) {
G::$DB->query("
select tt.id,
count(tb.fromuserid) count,
(
select count(1)
from thumb where
itemid = tt.id
and fromuserid=" . G::$LoggedUser['ID'] . "
and type = 'torrent'
) 'on'
from torrents as tt
left join thumb as tb
on tt.id = tb.itemid
and tb.type = 'torrent'
WHERE tt.groupid = $GroupID
group by tt.id");
$ThumbCounts = G::$DB->to_array('id');
return $ThumbCounts;
}
public static function get_bonus_sended($GroupID) {
G::$DB->query("
SELECT `ID`,
sum(bonus) Count,
(select group_concat(bonus)
from torrents_send_bonus where torrentid=t.id
and FromUserID = " . G::$LoggedUser['ID'] . "
) Sended
FROM `torrents` as t
left join torrents_send_bonus as tsb
on t.id = tsb.torrentid
where t.groupid = $GroupID
group by id");
$BonusSended = G::$DB->to_array('ID');
return $BonusSended;
}
public static function get_group($GroupID, $Return = true, $RevisionID = 0, $PersonalProperties = true, $ApiCall = false) {
$Cache = G::$Cache;
$DB = G::$DB;
if (!$RevisionID) {
$TorrentCache = $Cache->get_value("torrents_details_$GroupID");
}
if ($RevisionID || empty($TorrentCache)) {
// Fetch the group details
$SQL = 'SELECT ';
if (!$RevisionID) {
$SQL .= '
g.WikiBody,
g.WikiImage,
g.IMDBID,
g.IMDBRating,
g.Duration,
g.ReleaseDate,
g.Region,
g.Language,
g.RTRating,
g.DoubanRating,
g.IMDBVote,
g.DoubanVote,
g.DoubanID,
g.RTTitle,';
} else {
$SQL .= '
w.Body as WikiBody,
w.Image as WikiImage,
w.IMDBID,
w.IMDBRating,
w.Duration,
w.ReleaseDate,
w.Region,
w.Language,
w.RTRating,
w.DoubanRating,
g.IMDBVote,
g.DoubanVote,
g.DoubanID,
g.RTTitle,';
}
$SQL .= "
g.ID,
g.Name,
g.Year,
g.ReleaseType,
g.CategoryID,
g.Time,
GROUP_CONCAT(DISTINCT tags.Name ORDER BY `TagID` SEPARATOR '|') as TorrentTags,
GROUP_CONCAT(DISTINCT tags.ID SEPARATOR '|') as TorrentTagIDs,
GROUP_CONCAT(tt.UserID SEPARATOR '|') as TorrentTagUserIDs,
GROUP_CONCAT(tt.PositiveVotes SEPARATOR '|') as TagPositiveVotes,
GROUP_CONCAT(tt.NegativeVotes SEPARATOR '|') as TagNegativeVotes,
g.SubName
FROM torrents_group AS g
LEFT JOIN torrents_tags AS tt ON tt.GroupID = g.ID
LEFT JOIN tags ON tags.ID = tt.TagID";
if ($RevisionID) {
$SQL .= "
LEFT JOIN wiki_torrents AS w ON w.PageID = '" . db_string($GroupID) . "'
AND w.RevisionID = '" . db_string($RevisionID) . "' ";
}
$SQL .= "
WHERE g.ID = '" . db_string($GroupID) . "'
GROUP BY NULL";
$DB->query($SQL);
$TorrentGroup = $DB->next_record(MYSQLI_ASSOC, ['Name', 'SubName', 'WikiBody']);
// Fetch the individual torrents
$DB->query("
SELECT
t.ID,
t.GroupID,
t.RemasterYear,
t.RemasterTitle,
t.RemasterCustomTitle,
t.Scene,
t.Jinzhuan,
t.Diy,
t.Buy,
t.Allow,
t.FileCount,
t.Size,
t.Seeders,
t.Leechers,
t.Snatched,
t.FreeTorrent,
t.Time,
t.Checked,
t.NotMainMovie,
t.Source,
t.Codec,
t.Container,
t.Resolution,
t.Processing,
t.ChineseDubbed,
t.SpecialSub,
t.Subtitles,
group_concat(sub.languages separator '|') as ExternalSubtitles,
group_concat(sub.id separator '|') as ExternalSubtitleIDs,
t.Makers,
t.Description,
t.MediaInfo,
t.Note,
t.SubtitleType,
t.FileList,
t.FilePath,
t.UserID,
t.last_action,
HEX(t.info_hash) AS InfoHash,
tbf.TorrentID AS BadFolders,
tfi.TorrentID AS BadFiles,
tns.TorrentID AS NoSub,
ths.TorrentID AS HardSub,
tct.CustomTrumpable as CustomTrumpable,
t.LastReseedRequest,
t.ID AS HasFile,
fttd.EndTime as FreeEndTime,
t.Slot,
t.IsExtraSlot,
rt.ID as ReportID
FROM torrents AS t
LEFT JOIN torrents_bad_folders AS tbf ON tbf.TorrentID = t.ID
LEFT JOIN torrents_bad_files AS tfi ON tfi.TorrentID = t.ID
LEFT JOIN torrents_no_sub AS tns ON tns.TorrentID = t.ID
LEFT JOIN torrents_hard_sub AS ths ON ths.TorrentID = t.ID
LEFT JOIN torrents_custom_trumpable AS tct ON tct.TorrentID = t.ID
LEFT JOIN freetorrents_timed as fttd on fttd.TorrentID = t.id
LEFT JOIN subtitles as sub on sub.torrent_id = t.id
LEFT JOIN reportsv2 as rt on rt.TorrentID = t.id AND rt.Status != 'Resolved'
WHERE t.GroupID = '" . db_string($GroupID) . "'
GROUP BY t.ID
ORDER BY t.ID");
$TorrentList = $DB->to_array('ID', MYSQLI_ASSOC, ['MediaInfo', 'Description']);
uasort($TorrentList, 'Torrents::sort_torrent');
if (count($TorrentList) === 0 && $ApiCall == false) {
header('Location: log.php?search=' . (empty($_GET['torrentid']) ? "Group+$GroupID" : "Torrent+$_GET[torrentid]"));
die();
} elseif (count($TorrentList) === 0 && $ApiCall == true) {
return null;
}
if (in_array(0, $DB->collect('Seeders'))) {
$CacheTime = 600;
} else {
$CacheTime = 3600;
}
$TorrentGroup['Torrents'] = $TorrentList;
// Store it all in cache
if (!$RevisionID) {
$Cache->cache_value("torrents_details_$GroupID", $TorrentGroup, $CacheTime);
}
} else { // If we're reading from cache
$TorrentGroup = $TorrentCache;
$TorrentList = &$TorrentGroup['Torrents'];
}
if ($PersonalProperties) {
// Fetch all user specific torrent and group properties
$TorrentGroup['Flags'] = array('IsSnatched' => false);
foreach ($TorrentList as &$Torrent) {
Torrents::torrent_properties($Torrent, $TorrentGroup['Flags']);
}
}
if ($Return) {
return $TorrentGroup;
}
}
public static function get_groups($GroupIDs, $Return = true, $GetArtists = true, $Torrents = true) {
$Found = $NotFound = array_fill_keys($GroupIDs, false);
$Key = $Torrents ? 'torrent_group_' : 'torrent_group_light_';
foreach ($GroupIDs as $i => $GroupID) {
if (!is_number($GroupID)) {
unset($GroupIDs[$i], $Found[$GroupID], $NotFound[$GroupID]);
continue;
}
$Data = G::$Cache->get_value($Key . $GroupID, true);
if (!empty($Data) && is_array($Data) && $Data['ver'] == CACHE::GROUP_VERSION) {
unset($NotFound[$GroupID]);
$Found[$GroupID] = $Data['d'];
}
}
// Make sure there's something in $GroupIDs, otherwise the SQL will break
if (count($GroupIDs) === 0) {
return array();
}
/*
Changing any of these attributes returned will cause very large, very dramatic site-wide chaos.
Do not change what is returned or the order thereof without updating:
torrents, artists, collages, bookmarks, better, the front page,
and anywhere else the get_groups function is used.
Update self::array_group(), too
*/
if (count($NotFound) > 0) {
$IDs = implode(',', array_keys($NotFound));
$NotFound = array();
$QueryID = G::$DB->get_query_id();
G::$DB->query("
SELECT
ID, CategoryID
FROM torrents_group
WHERE ID IN ($IDs)");
$DiffCategoryGroups = G::$DB->to_array();
$MovieWayIDs = [];
foreach ($DiffCategoryGroups as $Record) {
$MovieWayIDs[] = $Record['ID'];
}
$MovieWayIDs = implode(',', $MovieWayIDs);
if ($MovieWayIDs) {
G::$DB->query("
SELECT
ID, Name, Year, TagList, ReleaseType, WikiImage, CategoryID, SubName, IMDBID, TrailerLink, IMDBRating, DoubanRating, RTRating, DoubanVote, IMDBVote, DoubanID, RTTitle, Region
FROM torrents_group
WHERE ID IN ($MovieWayIDs)");
while ($Group = G::$DB->next_record(MYSQLI_ASSOC, ['SubName', 'Name'])) {
$NotFound[$Group['ID']] = $Group;
$NotFound[$Group['ID']]['Torrents'] = array();
$NotFound[$Group['ID']]['Artists'] = array();
}
}
G::$DB->set_query_id($QueryID);
if ($Torrents) {
$QueryID = G::$DB->get_query_id();
if ($MovieWayIDs) {
G::$DB->query("
SELECT
t.ID,
t.GroupID,
t.Processing,
t.RemasterYear,
t.RemasterTitle,
t.RemasterCustomTitle,
t.Scene,
t.Jinzhuan,
t.Diy,
t.Buy,
t.Allow,
t.FileCount,
t.FreeTorrent,
t.Size,
t.Leechers,
t.Seeders,
t.Snatched,
t.Time,
t.ID AS HasFile,
t.Checked,
t.NotMainMovie,
t.Source,
t.Codec,
t.Container,
t.Resolution,
t.Subtitles,
t.ChineseDubbed,
t.SpecialSub,
t.Makers,
t.last_action,
tbf.TorrentID AS BadFolders,
tfi.TorrentID AS BadFiles,
tct.CustomTrumpable as CustomTrumpable,
fttd.EndTime as FreeEndTime,
tns.TorrentID AS NoSub,
ths.TorrentID AS HardSub,
group_concat(sub.languages separator '|') as ExternalSubtitles,
group_concat(sub.id separator '|') as ExternalSubtitleIDs,
t.Slot,
rt.ID as ReportID,
t.FilePath,
t.MediaInfo
FROM torrents t
LEFT JOIN torrents_bad_folders AS tbf ON tbf.TorrentID = t.ID
LEFT JOIN torrents_bad_files AS tfi ON tfi.TorrentID = t.ID
LEFT JOIN torrents_custom_trumpable AS tct ON tct.TorrentID = t.ID
LEFT JOIN freetorrents_timed as fttd on fttd.TorrentID = t.id
LEFT JOIN torrents_no_sub AS tns ON tns.TorrentID = t.ID
LEFT JOIN torrents_hard_sub AS ths ON ths.TorrentID = t.ID
LEFT JOIN subtitles as sub on sub.torrent_id = t.id
LEFT JOIN reportsv2 as rt on rt.TorrentID = t.id AND rt.Status != 'Resolved'
WHERE t.GroupID IN ($IDs)
GROUP BY t.ID ORDER BY t.ID");
while ($Torrent = G::$DB->next_record(MYSQLI_ASSOC, ['MediaInfo'])) {
$NotFound[$Torrent['GroupID']]['Torrents'][$Torrent['ID']] = $Torrent;
}
}
G::$DB->set_query_id($QueryID);
}
foreach ($NotFound as $GroupID => &$GroupInfo) {
uasort($GroupInfo['Torrents'], 'Torrents::sort_torrent');
G::$Cache->cache_value($Key . $GroupID, array('ver' => CACHE::GROUP_VERSION, 'd' => $GroupInfo), 0);
}
$Found = $NotFound + $Found;
}
// Filter out orphans (elements that are == false)
$Found = array_filter($Found);
if ($GetArtists) {
$Artists = Artists::get_artists($GroupIDs);
} else {
$Artists = array();
}
if ($Return) { // If we're interested in the data, and not just caching it
foreach ($Artists as $GroupID => $Data) {
if (!isset($Found[$GroupID])) {
continue;
}
$Found[$GroupID]['Artists'] = $Data;
}
// Fetch all user specific torrent properties
if ($Torrents) {
foreach ($Found as &$Group) {
$Group['Flags'] = array('IsSnatched' => false);
if (!empty($Group['Torrents'])) {
foreach ($Group['Torrents'] as &$Torrent) {
self::torrent_properties($Torrent, $Group['Flags']);
}
}
}
}
uasort($Found, function ($a, $b) use ($GroupIDs) {
return (array_search($a['ID'], $GroupIDs) < array_search($b['ID'], $GroupIDs)) ? -1 : 1;
});
return $Found;
}
}
/**
* Returns a reconfigured array from a Torrent Group
*
* Use this with extract() instead of the volatile list($GroupID, ...)
* Then use the variables $GroupID, $GroupName, etc
*
* @example extract(Torrents::array_group($SomeGroup));
* @param array $Group torrent group
* @return array Re-key'd array
*/
public static function array_group(array &$Group) {
return array(
'GroupID' => $Group['ID'],
'GroupName' => $Group['Name'],
'GroupYear' => $Group['Year'],
'GroupSubName' => $Group['SubName'],
'GroupCategoryID' => $Group['CategoryID'],
'GroupFlags' => isset($Group['Flags']) ? $Group['Flags'] : array('IsSnatched' => false),
'TagList' => $Group['TagList'],
'ReleaseType' => $Group['ReleaseType'],
'WikiImage' => $Group['WikiImage'],
'Torrents' => isset($Group['Torrents']) ? $Group['Torrents'] : array(),
'Artists' => $Group['Artists'],
'Artists' => $Group['Artists']
);
}
private static function getval($str, $key) {
if (preg_match("/^\s*$key\s*:\s*(.+)$/mi", $str, $match)) {
return $match[1];
} else {
return false;
}
}
public static function parse_file_name($Torrent) {
$MediaInfo = json_decode($Torrent['MediaInfo'])[0];
$CM = self::getval($MediaInfo, 'Complete name');
if ($CM) {
$slash = strrpos($CM, '\\');
if (!$slash) {
$slash = strrpos($CM, '/');
}
$CM = ltrim(substr($CM, $slash), '\\');
$CM = ltrim($CM, '/');
return $CM;
}
$CM = self::getval($MediaInfo, 'Disc Title');
if ($CM) {
return $CM;
}
$CM = self::getval($MediaInfo, "Disc Label");
if ($CM) {
return $CM;
}
return "";
}
public static function parse_container($Container) {
global $Containers;
if (!in_array($Container, $Containers)) {
return 'Other';
}
return $Container;
}
public static function parse_codec($Codec) {
global $Codecs;
if (!in_array($Codec, $Codecs)) {
return 'Other';
}
return $Codec;
}
public static function parse_resolution($Resolution) {
global $Resolutions;
if (!in_array($Resolution, $Resolutions)) {
return 'Other';
}
return $Resolution;
}
public static function parse_source($Source) {
global $Sources;
if (!in_array($Source, $Sources)) {
return 'Other';
}
return $Source;
}
/**
* Supplements a torrent array with information that only concerns certain users and therefore cannot be cached
*
* @param array $Torrent torrent array preferably in the form used by Torrents::get_groups() or get_group_info()
* @param int $TorrentID
*/
public static function torrent_properties(&$Torrent, &$Flags) {
$Torrent['PersonalFL'] = self::has_token($Torrent['ID']);
if ($Torrent['IsSnatched'] = self::has_snatched($Torrent['ID'])) {
$Flags['IsSnatched'] = true;
}
}
/*
* Write to the group log.
*
* @param int $GroupID
* @param int $TorrentID
* @param int $UserID
* @param string $Message
* @param boolean $Hidden Currently does fuck all. TODO: Fix that.
*/
public static function write_group_log($GroupID, $TorrentID, $UserID, $Message, $Hidden) {
$QueryID = G::$DB->get_query_id();
G::$DB->query("
INSERT INTO group_log
(GroupID, TorrentID, UserID, Info, Time, Hidden)
VALUES
($GroupID, $TorrentID, $UserID, '" . db_string($Message) . "', '" . sqltime() . "', $Hidden)");
G::$DB->set_query_id($QueryID);
}
/**
* Delete a torrent.
*
* @param int $ID The ID of the torrent to delete.
* @param int $GroupID Set it if you have it handy, to save a query. Otherwise, it will be found.
* @param string $OcelotReason The deletion reason for ocelot to report to users.
*/
public static function delete_torrent($ID, $GroupID = 0, $OcelotReason = -1) {
$QueryID = G::$DB->get_query_id();
if (!$GroupID) {
G::$DB->query("
SELECT GroupID, UserID
FROM torrents
WHERE ID = '$ID'");
list($GroupID, $UserID) = G::$DB->next_record();
}
if (empty($UserID)) {
G::$DB->query("
SELECT UserID
FROM torrents
WHERE ID = '$ID'");
list($UserID) = G::$DB->next_record();
}
$RecentUploads = G::$Cache->get_value("recent_uploads_$UserID");
if (is_array($RecentUploads)) {
foreach ($RecentUploads as $Key => $Recent) {
if ($Recent['ID'] == $GroupID) {
G::$Cache->delete_value("recent_uploads_$UserID");
}
}
}
G::$DB->query("
SELECT info_hash
FROM torrents
WHERE ID = $ID");
list($InfoHash) = G::$DB->next_record(MYSQLI_BOTH, false);
G::$DB->query("
DELETE FROM torrents
WHERE ID = $ID");
Tracker::update_tracker('delete_torrent', array('info_hash' => rawurlencode($InfoHash), 'id' => $ID, 'reason' => $OcelotReason));
G::$Cache->decrement('stats_torrent_count');
G::$DB->query("
SELECT COUNT(ID)
FROM torrents
WHERE GroupID = '$GroupID'");
list($Count) = G::$DB->next_record();
if ($Count == 0) {
Torrents::delete_group($GroupID);
} else {
Torrents::update_hash($GroupID);
}
// Torrent notifications
G::$DB->query("
SELECT UserID
FROM users_notify_torrents
WHERE TorrentID = '$ID'");
while (list($UserID) = G::$DB->next_record()) {
G::$Cache->delete_value("notifications_new_$UserID");
}
G::$DB->query("
DELETE FROM users_notify_torrents
WHERE TorrentID = '$ID'");
G::$DB->query("
UPDATE reportsv2
SET
Status = 'Resolved',
LastChangeTime = '" . sqltime() . "',
ModComment = 'Report already dealt with (torrent deleted)'
WHERE TorrentID = $ID
AND Status != 'Resolved'");
$Reports = G::$DB->affected_rows();
if ($Reports) {
G::$Cache->decrement('num_torrent_reportsv2', $Reports);
}
G::$DB->query("
DELETE FROM torrents_files
WHERE TorrentID = '$ID'");
G::$DB->query("
DELETE FROM torrents_bad_folders
WHERE TorrentID = $ID");
G::$DB->query("
DELETE FROM torrents_bad_files
WHERE TorrentID = $ID");
// Tells Sphinx that the group is removed
G::$DB->query("
REPLACE INTO sphinx_delta (ID, Time)
VALUES ($ID, UNIX_TIMESTAMP())");
G::$Cache->delete_value("torrent_download_$ID");
G::$Cache->delete_value("torrent_group_$GroupID");
G::$Cache->delete_value("torrents_details_$GroupID");
G::$DB->set_query_id($QueryID);
}
public static function send_pm($TorrentID, $UploaderID, $Name, $Log, $TrumpID = 0, $PMUploader = false) {
global $DB;
$Variable = ['TorrentID' => $TorrentID, 'SiteURL' => site_url(false), 'Name' => $Name, 'Log' => $Log, 'TrumpID' => $TrumpID];
// Uploader
if ($PMUploader) {
$Variable['Action'] = 'Uploaded';
Misc::send_pm_with_tpl($UploaderID, 'torrent_delete', $Variable);
}
$PMedUsers = [$UploaderID];
// Seeders
$Extra = implode(',', array_fill(0, count($PMedUsers), '?'));
$DB->prepared_query("
SELECT DISTINCT(xfu.uid)
FROM
xbt_files_users AS xfu
JOIN users_info AS ui ON xfu.uid = ui.UserID
WHERE xfu.fid = ?
AND ui.NotifyOnDeleteSeeding='1'
AND xfu.uid NOT IN ({$Extra})", $TorrentID, ...$PMedUsers);
$UserIDs = $DB->collect('uid');
foreach ($UserIDs as $UserID) {
$Variable['Action'] = 'Seeding';
Misc::send_pm_with_tpl($UserID, 'torrent_delete', $Variable);
}
$PMedUsers = array_merge($PMedUsers, $UserIDs);
// Snatchers
$Extra = implode(',', array_fill(0, count($PMedUsers), '?'));
$DB->prepared_query("
SELECT DISTINCT(xs.uid)
FROM xbt_snatched AS xs JOIN users_info AS ui ON xs.uid = ui.UserID
WHERE xs.fid=? AND ui.NotifyOnDeleteSnatched='1' AND xs.uid NOT IN ({$Extra})", $TorrentID, ...$PMedUsers);
$UserIDs = $DB->collect('uid');
foreach ($UserIDs as $UserID) {
$Variable['Action'] = 'Snatched';
Misc::send_pm_with_tpl($UserID, 'torrent_delete', $Variable);
}
$PMedUsers = array_merge($PMedUsers, $UserIDs);
// Downloaders
$Extra = implode(',', array_fill(0, count($PMedUsers), '?'));
$DB->prepared_query("
SELECT DISTINCT(ud.UserID)
FROM users_downloads AS ud JOIN users_info AS ui ON ud.UserID = ui.UserID
WHERE ud.TorrentID=? AND ui.NotifyOnDeleteDownloaded='1' AND ud.UserID NOT IN ({$Extra})", $TorrentID, ...$PMedUsers);
$UserIDs = $DB->collect('UserID');
foreach ($UserIDs as $UserID) {
$Variable['Action'] = 'Downloaded';
Misc::send_pm_with_tpl($UserID, 'torrent_delete', $Variable);
}
}
/**
* Delete a group, called after all of its torrents have been deleted.
* IMPORTANT: Never call this unless you're certain the group is no longer used by any torrents
*
* @param int $GroupID
*/
public static function delete_group($GroupID) {
$QueryID = G::$DB->get_query_id();
Misc::write_log("Group $GroupID automatically deleted (No torrents have this group).");
G::$DB->query("
SELECT CategoryID
FROM torrents_group
WHERE ID = '$GroupID'");
list($Category) = G::$DB->next_record();
if ($Category == 1) {
G::$Cache->decrement('stats_album_count');
}
if ($Category == 2) {
G::$Cache->decrement('stats_drama_count');
}
G::$Cache->decrement('stats_group_count');
// Collages
G::$DB->query("
SELECT CollageID
FROM collages_torrents
WHERE GroupID = '$GroupID'");
if (G::$DB->has_results()) {
$CollageIDs = G::$DB->collect('CollageID');
G::$DB->query("
UPDATE collages
SET NumTorrents = NumTorrents - 1
WHERE ID IN (" . implode(', ', $CollageIDs) . ')');
G::$DB->query("
DELETE FROM collages_torrents
WHERE GroupID = '$GroupID'");
foreach ($CollageIDs as $CollageID) {
G::$Cache->delete_value("collage_$CollageID");
}
G::$Cache->delete_value("torrent_collages_$GroupID");
}
// Artists
// Collect the artist IDs and then wipe the torrents_artist entry
G::$DB->query("
SELECT ArtistID
FROM torrents_artists
WHERE GroupID = $GroupID");
$Artists = G::$DB->collect('ArtistID');
G::$DB->query("
DELETE FROM torrents_artists
WHERE GroupID = '$GroupID'");
foreach ($Artists as $ArtistID) {
if (empty($ArtistID)) {
continue;
}
// Get a count of how many groups or requests use the artist ID
G::$DB->query("
SELECT COUNT(ag.ArtistID)
FROM artists_group AS ag
LEFT JOIN requests_artists AS ra ON ag.ArtistID = ra.ArtistID
WHERE ra.ArtistID IS NOT NULL
AND ag.ArtistID = '$ArtistID'");
list($ReqCount) = G::$DB->next_record();
G::$DB->query("
SELECT COUNT(ag.ArtistID)
FROM artists_group AS ag
LEFT JOIN torrents_artists AS ta ON ag.ArtistID = ta.ArtistID
WHERE ta.ArtistID IS NOT NULL
AND ag.ArtistID = '$ArtistID'");
list($GroupCount) = G::$DB->next_record();
if (($ReqCount + $GroupCount) == 0) {
//The only group to use this artist
Artists::delete_artist($ArtistID);
} else {
//Not the only group, still need to clear cache
G::$Cache->delete_value("artist_groups_$ArtistID");
}
}
// Requests
G::$DB->query("
SELECT ID
FROM requests
WHERE GroupID = '$GroupID'");
$Requests = G::$DB->collect('ID');
G::$DB->query("
UPDATE requests
SET GroupID = NULL
WHERE GroupID = '$GroupID'");
foreach ($Requests as $RequestID) {
G::$Cache->delete_value("request_$RequestID");
}
// comments
Comments::delete_page('torrents', $GroupID);
G::$DB->query("
DELETE FROM torrents_group
WHERE ID = '$GroupID'");
G::$DB->query("
DELETE FROM torrents_tags
WHERE GroupID = '$GroupID'");
G::$DB->query("
DELETE FROM torrents_tags_votes
WHERE GroupID = '$GroupID'");
G::$DB->query("
DELETE FROM bookmarks_torrents
WHERE GroupID = '$GroupID'");
G::$DB->query("
DELETE FROM wiki_torrents
WHERE PageID = '$GroupID'");
G::$Cache->delete_value("torrents_details_$GroupID");
G::$Cache->delete_value("torrent_group_$GroupID");
G::$Cache->delete_value("groups_artists_$GroupID");
G::$DB->set_query_id($QueryID);
}
/**
* Update the cache and sphinx delta index to keep everything up-to-date.
*
* @param int $GroupID
*/
public static function update_hash($GroupID) {
$QueryID = G::$DB->get_query_id();
G::$DB->query("
UPDATE torrents_group
SET TagList = (
SELECT REPLACE(GROUP_CONCAT(tags.Name SEPARATOR ' '), '.', '_')
FROM torrents_tags AS t
INNER JOIN tags ON tags.ID = t.TagID
WHERE t.GroupID = '$GroupID'
GROUP BY t.GroupID
)
WHERE ID = '$GroupID'");
// Fetch album vote score
G::$DB->query("
SELECT Score
FROM torrents_votes
WHERE GroupID = $GroupID");
if (G::$DB->has_results()) {
list($VoteScore) = G::$DB->next_record();
} else {
$VoteScore = 0;
}
// Fetch album artists
G::$DB->query("
SELECT GROUP_CONCAT(aa.Name separator ' ')
FROM torrents_artists AS ta
JOIN artists_alias AS aa ON aa.AliasID = ta.AliasID
WHERE ta.GroupID = $GroupID
AND ta.Importance IN ('1', '4', '5', '6')
GROUP BY ta.GroupID");
if (G::$DB->has_results()) {
list($ArtistName) = G::$DB->next_record(MYSQLI_NUM, false);
} else {
$ArtistName = '';
}
G::$DB->query("
REPLACE INTO sphinx_delta
(ID, GroupID, GroupName, TagList, Year, CategoryID, Time, ReleaseType, Size, Snatched, Seeders, Leechers, Scene, Jinzhuan, Allow,
FreeTorrent,Description, FileList, VoteScore, ArtistName,
IMDBRating, DoubanRating, Region, Language, IMDBID, Resolution, Container, Source, Codec, SubTitles,
Diy, Buy, ChineseDubbed, SpecialSub)
SELECT
t.ID, g.ID, CONCAT_WS(' ', g.Name, g.SubName), TagList, Year, CategoryID, UNIX_TIMESTAMP(t.Time), ReleaseType,
Size, Snatched, Seeders,
Leechers, CAST(Scene AS CHAR), CAST(Jinzhuan AS CHAR), CAST(Allow AS CHAR),
CAST(FreeTorrent AS CHAR),Description,
REPLACE(REPLACE(FileList, '_', ' '), '/', ' ') AS FileList, $VoteScore, '" . db_string($ArtistName) . "',
IMDBRating, DoubanRating, Region, Language, IMDBID, Resolution, Container, Source, Codec, Subtitles,
Diy, Buy, ChineseDubbed, SpecialSub
FROM torrents AS t
JOIN torrents_group AS g ON g.ID = t.GroupID
WHERE g.ID = $GroupID");
G::$Cache->delete_value("torrents_details_$GroupID");
G::$Cache->delete_value("torrent_group_$GroupID");
G::$Cache->delete_value("torrent_group_light_$GroupID");
$ArtistInfo = Artists::get_artist($GroupID);
foreach ($ArtistInfo as $Importances => $Importance) {
foreach ($Importance as $Artist) {
G::$Cache->delete_value('artist_groups_' . $Artist['id']); //Needed for at least freeleech change, if not others.
}
}
G::$Cache->delete_value("groups_artists_$GroupID");
G::$DB->set_query_id($QueryID);
}
public static function update_slots($TorrentIDs, $TorrentSlots, $GroupID) {
$SQLArray = [];
G::$DB->query("SELECT ID, Slot, IsExtraSlot FROM torrents WHERE ID in (" . implode(',', $TorrentIDs) . ")");
$TorrentOldSlots = G::$DB->to_array('ID', MYSQLI_ASSOC);
$TorrentNewSlots = array_combine($TorrentIDs, $TorrentSlots);
$Messages = [];
foreach ($TorrentNewSlots as $TorrentID => $Slot) {
$torrentSlot = explode('*', $Slot)[0];
$isExtraSlot = false;
if (strpos($Slot, '*')) {
$isExtraSlot = true;
}
$oldExtraSlot = false;
if (!empty($TorrentOldSlots[$TorrentID]['IsExtraSlot'])) {
$oldExtraSlot = true;
}
if ($TorrentOldSlots[$TorrentID]['Slot'] == $torrentSlot && $oldExtraSlot == $isExtraSlot) {
continue;
}
$SQLArray[] = " ($TorrentID, $torrentSlot, '$isExtraSlot') ";
$Messages[] = [$TorrentID, $TorrentOldSlots[$TorrentID]['Slot'], $torrentSlot];
}
if (count($SQLArray) == 0) {
return;
}
$SQL = implode(',', $SQLArray);
$SQL = "
insert into torrents(ID, Slot, IsExtraSlot) VALUES $SQL on duplicate key update Slot=values(Slot), IsExtraSlot=values(IsExtraSlot);";
G::$DB->query($SQL);
foreach ($Messages as $Message) {
$TorrentID = $Message[0];
$TorrentOldSlot = $Message[1];
$TorrentNewSlot = $Message[2];
Misc::write_log("Torrent $TorrentID was edited by " . G::$LoggedUser['Username'] . " (Slot: $TorrentOldSlot -> $TorrentNewSlot)");
}
G::$Cache->delete_value("torrents_details_$GroupID");
G::$Cache->delete_value("torrent_group_$GroupID");
}
/**
* Regenerate a torrent's file list from its meta data,
* update the database record and clear relevant cache keys
*
* @param int $TorrentID
*/
public static function regenerate_filelist($TorrentID) {
$QueryID = G::$DB->get_query_id();
G::$DB->query("
SELECT tg.ID,
tf.File
FROM torrents_files AS tf
JOIN torrents AS t ON t.ID = tf.TorrentID
JOIN torrents_group AS tg ON tg.ID = t.GroupID
WHERE tf.TorrentID = $TorrentID");
if (G::$DB->has_results()) {
list($GroupID, $Contents) = G::$DB->next_record(MYSQLI_NUM, false);
if (Misc::is_new_torrent($Contents)) {
$Tor = new BencodeTorrent($Contents);
$FilePath = (isset($Tor->Dec['info']['files']) ? Format::make_utf8($Tor->get_name()) : '');
} else {
$Tor = new TORRENT(unserialize(base64_decode($Contents)), true);
$FilePath = (isset($Tor->Val['info']->Val['files']) ? Format::make_utf8($Tor->get_name()) : '');
}
list($TotalSize, $FileList) = $Tor->file_list();
foreach ($FileList as $File) {
$TmpFileList[] = self::filelist_format_file($File);
}
$FileString = implode("\n", $TmpFileList);
G::$DB->query("
UPDATE torrents
SET Size = $TotalSize, FilePath = '" . db_string($FilePath) . "', FileList = '" . db_string($FileString) . "'
WHERE ID = $TorrentID");
G::$Cache->delete_value("torrents_details_$GroupID");
}
G::$DB->set_query_id($QueryID);
}
/**
* Return UTF-8 encoded string to use as file delimiter in torrent file lists
*/
public static function filelist_delim() {
static $FilelistDelimUTF8;
if (isset($FilelistDelimUTF8)) {
return $FilelistDelimUTF8;
}
return $FilelistDelimUTF8 = utf8_encode(chr(self::FILELIST_DELIM));
}
/**
* Create a string that contains file info in a format that's easy to use for Sphinx
*
* @param array $File (File size, File name)
* @return string with the format .EXT sSIZEs NAME DELIMITER
*/
public static function filelist_format_file($File) {
list($Size, $Name) = $File;
$Name = Format::make_utf8(strtr($Name, "\n\r\t", ' '));
$ExtPos = strrpos($Name, '.');
// Should not be $ExtPos !== false. Extensionless files that start with a . should not get extensions
$Ext = ($ExtPos ? trim(substr($Name, $ExtPos + 1)) : '');
return sprintf("%s s%ds %s %s", ".$Ext", $Size, $Name, self::filelist_delim());
}
/**
* Create a string that contains file info in the old format for the API
*
* @param string $File string with the format .EXT sSIZEs NAME DELIMITER
* @return string with the format NAME{{{SIZE}}}
*/
public static function filelist_old_format($File) {
$File = self::filelist_get_file($File);
return $File['name'] . '{{{' . $File['size'] . '}}}';
}
/**
* Translate a formatted file info string into a more useful array structure
*
* @param string $File string with the format .EXT sSIZEs NAME DELIMITER
* @return file info array with the keys 'ext', 'size' and 'name'
*/
public static function filelist_get_file($File) {
// Need this hack because filelists are always display_str()ed
$DelimLen = strlen(display_str(self::filelist_delim())) + 1;
list($FileExt, $Size, $Name) = explode(' ', $File, 3);
if ($Spaces = strspn($Name, ' ')) {
$Name = str_replace(' ', '&nbsp;', substr($Name, 0, $Spaces)) . substr($Name, $Spaces);
}
return array(
'ext' => $FileExt,
'size' => substr($Size, 1, -1),
'name' => substr($Name, 0, -$DelimLen)
);
}
public static function display_edition_info($EditionInfo) {
$t = explode(' / ', $EditionInfo);
$t = array_map(
function ($item) {
return EditionInfo::text(trim($item));
},
$t
);
return implode(' / ', $t);
}
public static function group_name($Group, $Link = true) {
global $LoggedUser;
$lang = Lang::getUserLang($LoggedUser['ID']);
$GroupName = !empty($Group['SubName']) && $lang == Lang::CHS ? $Group['SubName'] : $Group['Name'];
$Year = $Group['Year'];
$Ret = $GroupName . ' (' . $Year . ')';
if ($Link) {
$GroupID = $Group['ID'];
$Ret = "<a href='torrents.php?id=$GroupID'>$Ret</a>";
}
return $Ret;
}
public static function torrent_name($Torrent, $WithLink = true, $WithMedia = true, $WithSize = true) {
$Size = Format::get_size($Torrent['Size']);
$Info = self::torrent_media_info($Torrent);
$Group = $Torrent['Group'];
$GroupName = !empty($Group['SubName']) ? $Group['SubName'] : $Group['Name'];
$Year = $Group['Year'];
$Ret = $GroupName . ' (' . $Year . ') - ' . implode(' / ', $Info) . ($WithSize ? ' - ' . $Size : '');
if ($WithLink) {
$TorrentID = $Torrent['ID'];
$Ret = "<a href='torrents.php?torrentid=$TorrentID#$TorrentID'>$Ret</a>";
}
return $Ret;
}
public static function parse_group_snatched($GroupInfo) {
if (isset($GroupInfo['Flags']) && isset($GroupInfo['Flags']['IsSnatched']) && $GroupInfo['Flags']['IsSnatched']) {
return true;
}
return false;
}
private static function torrent_media_info($Data, $Style = false) {
$Info = array();
if (!empty($Data['Codec'])) {
if ($Style) {
$Info[] = "<span class='codec'>" . $Data['Codec'] . "</span>";
} else {
$Info[] = $Data['Codec'];
}
}
if (!empty($Data['Source'])) {
if ($Style) {
$Info[] = "<span class='source'>" . $Data['Source'] . "</span>";
} else {
$Info[] = $Data['Source'];
}
}
if (!empty($Data['Resolution'])) {
if ($Style) {
$Info[] = "<span class='resolution'>" . $Data['Resolution'] . "</span>";
} else {
$Info[] = $Data['Resolution'];
}
}
if (!empty($Data['Container'])) {
if ($Style) {
$Info[] = "<span class='container'>" . $Data['Container'] . "</span>";
} else {
$Info[] = $Data['Container'];
}
}
if (!empty($Data['Processing']) && $Data['Processing'] != 'Encode' && $Data['Processing'] != '---') {
if ($Style) {
$Info[] = "<span class='processing'>" . $Data['Processing'] . "</span>";
} else {
$Info[] = $Data['Processing'];
}
}
return $Info;
}
public static function torrent_simple_view($Group, $Torrent, $Link = true, $Option = ['Self' => true, 'Class' => '']) {
$GroupID = $Group['ID'];
$TorrentID = $Torrent['ID'];
$TorrentInfo = Torrents::torrent_info($Torrent, true, $Option);
$Class = $Option['Class'];
if ($Link) {
$TorrentInfo = "<a class='$Class' href='torrents.php?id=$GroupID&amp;torrentid=$TorrentID#torrent$TorrentID'>" . $TorrentInfo . "</a>";
}
return Torrents::group_name($Group, $Link) . "&nbsp;&nbsp;&raquo;&nbsp;" . $TorrentInfo;
}
/**
* Format the information about a torrent.
* @param array $Data an array a subset of the following keys:
* Codec, Scene, Jinzhuan, RemasterYear etc
* RemasterTitle, FreeTorrent, PersonalFL
* @param boolean $ShowMedia if false, Media key will be omitted
* @return string
*/
public static function torrent_info($Data, $ShowMedia = true, $Option = ['Self' => true]) {
$Info = array();
if ($ShowMedia) {
$Info = self::torrent_media_info($Data, true);
}
$RemasterYearInfo = '';
if (!empty($Data['RemasterYear'])) {
$RemasterYearInfo = " <sup class='remaster_year'><b>" . $Data['RemasterYear'] . "</b></sup>";
}
$EditionInfo = array();
$t = [];
if (!empty($Data['RemasterTitle'])) {
$EditionInfo = explode(' / ', $Data['RemasterTitle']);
$remaster_labels = EditionInfo::allEditionKey(EditionType::Remaster);
$t = array_filter(
$remaster_labels,
function ($label) use ($EditionInfo) {
return in_array($label, $EditionInfo);
}
);
$EditionInfo = array_map(
function ($label) use ($t, $RemasterYearInfo) {
$title = EditionInfo::text($label);
if (in_array($label, $t)) {
$title = $title . $RemasterYearInfo;
}
return "<span class='remaster_$label'>" . $title . "</span>";
},
array_intersect($EditionInfo, $EditionInfo)
);
}
if (count($EditionInfo)) {
$Info[] = implode(' / ', $EditionInfo);
}
if (!empty($Data['RemasterCustomTitle'])) {
$CustomTitle = "<span class='remaster_custom_title'>" . $Data['RemasterCustomTitle'] . "</span>";
if (!count($t)) {
$CustomTitle .= $RemasterYearInfo;
}
$Info[] = $CustomTitle;
}
if (!empty($Data['Scene'])) {
$Info[] = 'Scene';
}
if (!empty($Data['Subtitles'])) {
$Subtitles = explode(',', $Data['Subtitles']);
if (in_array('chinese_simplified', $Subtitles)) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'chi'), 'tl_chi');
} else if (in_array('chinese_traditional', $Subtitles)) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'chi'), 'tl_chi');
}
}
if ($Data['ChineseDubbed']) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'cn_dub'), 'tl_diy');
}
if ($Data['SpecialSub']) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'se_sub'), 'tl_diy');
}
if ($Data['Buy'] == '1' && $Data['Diy'] == '0') {
$Info[] = Format::torrent_label(Lang::get('torrents', 'buy'), 'tl_diy');
}
if ($Data['Diy'] == '1') {
$Info[] = Format::torrent_label(Lang::get('torrents', 'diy'), 'tl_diy');
}
if ($Data['Jinzhuan'] == '1' && $Data['Allow'] == '0') {
$Info[] = Format::torrent_label(Lang::get('torrents', 'jinzhuan'), 'tl_diy');
}
if ($Data['Allow'] == '1') {
$Info[] = Format::torrent_label(Lang::get('torrents', 'allow'), 'tl_diy');
}
if (
(!empty($Data['BadFiles'])) ||
(!empty($Data['BadFolders'])) ||
(!empty($Data['NoSub'])) ||
(!empty($Data['HardSub'])) ||
(!empty($Data['CustomTrumpable'])) ||
self::is_torrent_dead($Data)
) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'trump'), 'tl_trumpable');
}
if (self::global_freeleech()) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'fld'), 'tl_free torrent_discount free');
} else if (isset($Data['FreeTorrent'])) {
if ($Data['FreeTorrent'] == '1') {
$Info[] = Format::torrent_label(Lang::get('torrents', 'fld'), 'tl_free torrent_discount free', ($Data['FreeEndTime'] ? Lang::get('torrents', 'free_left', false, time_diff($Data['FreeEndTime'], 2, false)) : ""));
} else if ($Data['FreeTorrent'] == '2') {
$Info[] = Format::torrent_label('Neutral Leech!', 'torrent_discount neutral');
} else if ($Data['FreeTorrent'] == '11') {
$Info[] = Format::torrent_label('-25%', 'torrent_discount one_fourth_off', ($Data['FreeEndTime'] ? Lang::get('torrents', 'free_left', false, time_diff($Data['FreeEndTime'], 2, false)) : ""));
} else if ($Data['FreeTorrent'] == '12') {
$Info[] = Format::torrent_label('-50%', 'torrent_discount two_fourth_off', ($Data['FreeEndTime'] ? Lang::get('torrents', 'free_left', false, time_diff($Data['FreeEndTime'], 2, false)) : ""));
} else if ($Data['FreeTorrent'] == '13') {
$Info[] = Format::torrent_label('-75%', 'torrent_discount three_fourth_off', ($Data['FreeEndTime'] ? Lang::get('torrents', 'free_left', false, time_diff($Data['FreeEndTime'], 2, false)) : ""));
}
}
if ($Option['Self']) {
if (!empty($Data['ReportID']) > 0) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'reported'), 'tl_reported tips-reported');
}
if (!empty($Data['PersonalFL'])) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'pfl'), 'tl_free');
}
if (!empty($Data['IsSnatched'])) {
$Info[] = Format::torrent_label(Lang::get('torrents', 'snatched'));
}
}
return implode(' / ', $Info);
}
/**
* Will freeleech / neutral leech / normalise a set of torrents
*
* @param array $TorrentIDs An array of torrent IDs to iterate over
* @param int $FreeNeutral 0 = normal, 1 = fl, 2 = nl
* @param int $FreeLeechType 0 = Unknown, 1 = Staff picks, 2 = Perma-FL (Toolbox, etc.)
*/
public static function freeleech_torrents($TorrentIDs, $FreeNeutral = 1, $FreeLeechType = 0, $Schedule = false) {
if (!is_array($TorrentIDs)) {
$TorrentIDs = array($TorrentIDs);
}
$QueryID = G::$DB->get_query_id();
G::$DB->query("
UPDATE torrents
SET FreeTorrent = '$FreeNeutral', FreeLeechType = '$FreeLeechType'
WHERE ID IN (" . implode(', ', $TorrentIDs) . ')');
G::$DB->query('
SELECT ID, GroupID, info_hash
FROM torrents
WHERE ID IN (' . implode(', ', $TorrentIDs) . ')
ORDER BY GroupID ASC');
$Torrents = G::$DB->to_array(false, MYSQLI_NUM, false);
$GroupIDs = G::$DB->collect('GroupID');
G::$DB->set_query_id($QueryID);
foreach ($Torrents as $Torrent) {
list($TorrentID, $GroupID, $InfoHash) = $Torrent;
Tracker::update_tracker('update_torrent', array('info_hash' => rawurlencode($InfoHash), 'freetorrent' => $FreeNeutral));
G::$Cache->delete_value("torrent_download_$TorrentID");
Misc::write_log(($Schedule ? "Schedule" : G::$LoggedUser['Username']) . " marked torrent $TorrentID freeleech $FreeNeutral type $FreeLeechType!");
Torrents::write_group_log($GroupID, $TorrentID, $Schedule ? 0 : G::$LoggedUser['ID'], "marked as freeleech $FreeNeutral type $FreeLeechType!", 0);
}
foreach ($GroupIDs as $GroupID) {
Torrents::update_hash($GroupID);
}
}
/**
* Convenience function to allow for passing groups to Torrents::freeleech_torrents()
*
* @param array $GroupIDs the groups in question
* @param int $FreeNeutral see Torrents::freeleech_torrents()
* @param int $FreeLeechType see Torrents::freeleech_torrents()
*/
public static function freeleech_groups($GroupIDs, $FreeNeutral = 1, $FreeLeechType = 0) {
$QueryID = G::$DB->get_query_id();
if (!is_array($GroupIDs)) {
$GroupIDs = array($GroupIDs);
}
G::$DB->query('
SELECT ID
FROM torrents
WHERE GroupID IN (' . implode(', ', $GroupIDs) . ')');
if (G::$DB->has_results()) {
$TorrentIDs = G::$DB->collect('ID');
Torrents::freeleech_torrents($TorrentIDs, $FreeNeutral, $FreeLeechType);
}
G::$DB->set_query_id($QueryID);
}
/**
* Check if the logged in user has an active freeleech token
*
* @param int $TorrentID
* @return true if an active token exists
*/
public static function has_token($TorrentID) {
if (empty(G::$LoggedUser)) {
return false;
}
static $TokenTorrents;
$UserID = G::$LoggedUser['ID'];
if (!isset($TokenTorrents)) {
$TokenTorrents = G::$Cache->get_value("users_tokens_$UserID");
if ($TokenTorrents === false) {
$QueryID = G::$DB->get_query_id();
G::$DB->query("
SELECT TorrentID
FROM users_freeleeches
WHERE UserID = $UserID
AND Expired = 0");
$TokenTorrents = array_fill_keys(G::$DB->collect('TorrentID', false), true);
G::$DB->set_query_id($QueryID);
G::$Cache->cache_value("users_tokens_$UserID", $TokenTorrents);
}
}
return isset($TokenTorrents[$TorrentID]);
}
/**
* Check if the logged in user can use a freeleech token on this torrent
*
* @param int $Torrent
* @return boolen True if user is allowed to use a token
*/
public static function can_use_token($Torrent) {
if (empty(G::$LoggedUser)) {
return false;
}
return (G::$LoggedUser['FLTokens'] >= 0
&& !$Torrent['PersonalFL']
&& (in_array($Torrent['FreeTorrent'], ['11', '12', '13']) || empty($Torrent['FreeTorrent']))
&& G::$LoggedUser['CanLeech'] == '1');
}
/**
* Build snatchlists and check if a torrent has been snatched
* if a user has the 'ShowSnatched' option enabled
* @param int $TorrentID
* @return bool
*/
public static function has_snatched($TorrentID) {
if (empty(G::$LoggedUser) || empty(G::$LoggedUser['ShowSnatched'])) {
return false;
}
$UserID = G::$LoggedUser['ID'];
$Buckets = 64;
$LastBucket = $Buckets - 1;
$BucketID = $TorrentID & $LastBucket;
static $SnatchedTorrents = array(), $UpdateTime = array();
if (empty($SnatchedTorrents)) {
$SnatchedTorrents = array_fill(0, $Buckets, false);
$UpdateTime = G::$Cache->get_value("users_snatched_{$UserID}_time");
if ($UpdateTime === false) {
$UpdateTime = array(
'last' => 0,
'next' => 0
);
}
} elseif (isset($SnatchedTorrents[$BucketID][$TorrentID])) {
return true;
}
// Torrent was not found in the previously inspected snatch lists
$CurSnatchedTorrents = &$SnatchedTorrents[$BucketID];
if ($CurSnatchedTorrents === false) {
$CurTime = time();
// This bucket hasn't been checked before
$CurSnatchedTorrents = G::$Cache->get_value("users_snatched_{$UserID}_$BucketID", true);
if ($CurSnatchedTorrents === false || $CurTime > $UpdateTime['next']) {
$Updated = array();
$QueryID = G::$DB->get_query_id();
if ($CurSnatchedTorrents === false || $UpdateTime['last'] == 0) {
for ($i = 0; $i < $Buckets; $i++) {
$SnatchedTorrents[$i] = array();
}
// Not found in cache. Since we don't have a suitable index, it's faster to update everything
G::$DB->query("
SELECT fid
FROM xbt_snatched
WHERE uid = '$UserID'");
while (list($ID) = G::$DB->next_record(MYSQLI_NUM, false)) {
$SnatchedTorrents[$ID & $LastBucket][(int)$ID] = true;
}
$Updated = array_fill(0, $Buckets, true);
} elseif (isset($CurSnatchedTorrents[$TorrentID])) {
// Old cache, but torrent is snatched, so no need to update
return true;
} else {
// Old cache, check if torrent has been snatched recently
G::$DB->query("
SELECT fid
FROM xbt_snatched
WHERE uid = '$UserID'
AND tstamp >= $UpdateTime[last]");
while (list($ID) = G::$DB->next_record(MYSQLI_NUM, false)) {
$CurBucketID = $ID & $LastBucket;
if ($SnatchedTorrents[$CurBucketID] === false) {
$SnatchedTorrents[$CurBucketID] = G::$Cache->get_value("users_snatched_{$UserID}_$CurBucketID", true);
if ($SnatchedTorrents[$CurBucketID] === false) {
$SnatchedTorrents[$CurBucketID] = array();
}
}
$SnatchedTorrents[$CurBucketID][(int)$ID] = true;
$Updated[$CurBucketID] = true;
}
}
G::$DB->set_query_id($QueryID);
for ($i = 0; $i < $Buckets; $i++) {
if (isset($Updated[$i])) {
G::$Cache->cache_value("users_snatched_{$UserID}_$i", $SnatchedTorrents[$i], 0);
}
}
$UpdateTime['last'] = $CurTime;
$UpdateTime['next'] = $CurTime + self::SNATCHED_UPDATE_INTERVAL;
G::$Cache->cache_value("users_snatched_{$UserID}_time", $UpdateTime, 0);
}
}
return isset($CurSnatchedTorrents[$TorrentID]);
}
/**
* Change the schedule for when the next update to a user's cached snatch list should be performed.
* By default, the change will only be made if the new update would happen sooner than the current
* @param int $Time Seconds until the next update
* @param bool $Force Whether to accept changes that would push back the update
*/
public static function set_snatch_update_time($UserID, $Time, $Force = false) {
if (!$UpdateTime = G::$Cache->get_value("users_snatched_{$UserID}_time")) {
return;
}
$NextTime = time() + $Time;
if ($Force || $NextTime < $UpdateTime['next']) {
// Skip if the change would delay the next update
$UpdateTime['next'] = $NextTime;
G::$Cache->cache_value("users_snatched_{$UserID}_time", $UpdateTime, 0);
}
}
// Some constants for self::display_string's $Mode parameter
const DISPLAYSTRING_HTML = 1; // Whether or not to use HTML for the output (e.g. VH tooltip)
const DISPLAYSTRING_ARTISTS = 2; // Whether or not to display artists
const DISPLAYSTRING_YEAR = 4; // Whether or not to display the group's year
const DISPLAYSTRING_VH = 8; // Whether or not to display the VH flag
const DISPLAYSTRING_RELEASETYPE = 16; // Whether or not to display the release type
const DISPLAYSTRING_LINKED = 33; // Whether or not to link artists and the group
// The constant for linking is 32, but because linking only works with HTML, this constant is defined as 32|1 = 33, i.e. LINKED also includes HTML
// Keep this in mind when defining presets below!
// Presets to facilitate the use of $Mode
const DISPLAYSTRING_DEFAULT = 63; // HTML|ARTISTS|YEAR|VH|RELEASETYPE|LINKED = 63
const DISPLAYSTRING_SHORT = 6; // Very simple format, only artists and year, no linking (e.g. for forum thread titles)
public static function update_movie_artist_info($GroupID, $IMDBID, $Refresh = false) {
G::$DB->query("SELECT
ta.ArtistID,
wa.IMDBID,
ag.RevisionID as RevisionID,
wa.Image as Image,
wa.Birthday as Birthday,
wa.PlaceOfBirth as PlaceOfBirth,
wa.Body as Body
FROM
torrents_artists as ta
LEFT JOIN
artists_group as ag
ON
ag.ArtistID = ta.ArtistID
LEFT JOIN
wiki_artists as wa
ON
ag.RevisionID = wa.RevisionID
WHERE
GroupID=$GroupID
AND
wa.IMDBID <> ''");
$Artists = G::$DB->to_array(false, MYSQLI_ASSOC, false);
$IMDBIDs = [];
foreach ($Artists as $Artist) {
if (empty($Artist['Image'])) {
$IMDBIDs[] = $Artist['IMDBID'];
}
}
$ArtistInfos = MOVIE::get_artists_seq($IMDBIDs, $IMDBID, $Refresh);
foreach ($Artists as $Artist) {
$UpdateSQL = [];
$ArtistInfo = $ArtistInfos[$Artist['IMDBID']];
if (empty($Artist['Image']) && $ArtistInfo && $ArtistInfo['Image']) {
$UpdateSQL[] = "Image = '" . db_string($ArtistInfo['Image']) . "'";
}
if (empty($Artist['Body']) && $ArtistInfo && $ArtistInfo['Description']) {
$UpdateSQL[] = "Body = '" . db_string($ArtistInfo['Description']) . "'";
}
if (empty($Artist['PlaceOfBirth']) && $ArtistInfo && $ArtistInfo['PlaceOfBirth']) {
$UpdateSQL[] = "PlaceOfBirth = '" . db_string($ArtistInfo['PlaceOfBirth']) . "'";
}
if (empty($Artist['Birthday']) && $ArtistInfo && $ArtistInfo['Birthday']) {
$UpdateSQL[] = "Birthday = '" . db_string($ArtistInfo['Birthday']) . "'";
}
if (empty($UpdateSQL)) {
continue;
}
$SQL = '
Update wiki_artists set ' . implode(',', $UpdateSQL) .
' WHERE RevisionID = ' . $Artist['RevisionID'] . ' ';
G::$DB->query($SQL);
G::$Cache->delete_value('artist_' . $Artist['ArtistID']);
G::$Cache->delete_value('artist_groups_' . $Artist['ArtistID']);
G::$Cache->delete_value('groups_artists_' . $GroupID);
G::$Cache->delete_value("torrent_group_$GroupID");
G::$Cache->delete_value("torrents_details_$GroupID");
}
echo "Update group $GroupID artist info success.\n";
}
public static function update_movie_info($GroupID, $IMDBID, $DoubanID = null, $Force = true) {
if (empty($IMDBID)) {
return;
}
$OMDBData = MOVIE::get_omdb_data($IMDBID, $Force);
$UpdateSQL = [];
$UpdateSQL[] = "IMDBID = '" . $IMDBID . "'";
if ($OMDBData->imdbVotes && $OMDBData->imdbVotes != 'N/A') {
$UpdateSQL[] = "IMDBVote = " . str_replace(',', '', $OMDBData->imdbVotes);
}
if ($OMDBData->imdbRating && $OMDBData->imdbRating != 'N/A') {
$UpdateSQL[] = "IMDBRating = " . $OMDBData->imdbRating;
}
if ($OMDBData->Runtime && $OMDBData->Runtime != 'N/A') {
$UpdateSQL[] = "Duration = '" . $OMDBData->Runtime . "'";
}
if ($OMDBData->Released && $OMDBData->Released != 'N/A') {
$UpdateSQL[] = "ReleaseDate = '" . $OMDBData->Released . "'";
}
if ($OMDBData->Country && $OMDBData->Country != 'N/A') {
$UpdateSQL[] = "Region = '" . $OMDBData->Country . "'";
}
if ($OMDBData->Language && $OMDBData->Language != 'N/A') {
$UpdateSQL[] = "Language = '" . $OMDBData->Language . "'";
}
$RTRating = null;
foreach ($OMDBData->Ratings as $key => $value) {
if ($value->Source == "Rotten Tomatoes") {
$RTRating = $value->Value;
}
}
if ($RTRating) {
$UpdateSQL[] = "RTRating = '" . $RTRating . "'";
}
$DoubanData = null;
if ($DoubanID) {
$DoubanData = MOVIE::get_douban_data_by_doubanid($DoubanID, $Force);
} else {
$DoubanData = MOVIE::get_douban_data($IMDBID, $Force);
}
if ($DoubanData && $DoubanData->rating) {
$UpdateSQL[] = "DoubanRating = " . $DoubanData->rating;
}
if ($DoubanData && $DoubanData->votes) {
$UpdateSQL[] = "DoubanVote = " . $DoubanData->votes;
}
if ($DoubanData && $DoubanData->id) {
$UpdateSQL[] = "DoubanID = " . $DoubanData->id;
}
$SQL = '
Update torrents_group set ' . implode(',', $UpdateSQL) .
' WHERE ID = ' . $GroupID . ' ';
G::$DB->query($SQL);
G::$Cache->delete_value("torrent_group_$GroupID");
G::$Cache->delete_value("torrents_details_$GroupID");
}
//Used to get reports info on a unison cache in both browsing pages and torrent pages.
public static function get_reports($TorrentID) {
$Reports = G::$Cache->get_value("reports_torrent_$TorrentID");
if ($Reports === false) {
$QueryID = G::$DB->get_query_id();
G::$DB->query("
SELECT
ID,
ReporterID,
Type,
UserComment,
ReportedTime,
UploaderReply,
ReplyTime
FROM reportsv2
WHERE TorrentID = $TorrentID
AND Status != 'Resolved'");
$Reports = G::$DB->to_array(false, MYSQLI_ASSOC, false);
G::$DB->set_query_id($QueryID);
G::$Cache->cache_value("reports_torrent_$TorrentID", $Reports, 0);
}
if (!check_perms('admin_reports')) {
$Return = array();
foreach ($Reports as $Report) {
if ($Report['Type'] !== 'edited') {
$Return[] = $Report;
}
}
return $Return;
}
return $Reports;
}
public static function resolution_level($a) {
global $HighDefinition, $StandardDefinition, $UltraDefinition;
$Resolution = $a['Resolution'];
if ($a['NotMainMovie']) {
return SUBGROUP_Extra;
}
if (self::is_3d($a['RemasterTitle'])) {
return SUBGROUP_3D;
}
if (in_array($Resolution, $StandardDefinition)) {
return SUBGROUP_SD;
} else if (in_array($Resolution, $HighDefinition)) {
return SUBGROUP_HD;
} else if (in_array($Resolution, $UltraDefinition)) {
return SUBGROUP_UHD;
}
}
private static function resolution_value($a) {
$resolution_val = [
'480p' => 1,
'NTSC' => 2,
'576p' => 3,
'PAL' => 4,
'720p' => 5,
'1080i' => 6,
'1080p' => 7,
'2160p' => 8,
];
$value = 0;
if (!isset($resolution_val[$a['Resolution']])) {
list($width, $height) = explode('&times;', $a['Resolution']);
$value = 100000000 + $height * 10000 + $width;
} else {
$value = 200000000 + $resolution_val[$a['Resolution']];
}
return $value;
}
public static function codec_value($a) {
global $Codecs;
$v = array_search($a['Codec'], $Codecs);
if (!$v) {
$v = 2.5;
}
return $v;
}
public static function processing_value($a) {
if (empty($a['Processing']) || $a['Processing'] == '---') {
return 'Encode';
}
if (in_array($a['Processing'], ['BD25', 'BD66', 'BD50', 'BD100', 'DVD9', 'DVD5'])) {
return 'Untouched';
}
return $a['Processing'];
}
public static function get_processing_list($a) {
if ($a == 'Untouched') {
return ['BD25', 'BD66', 'BD50', 'BD100', 'DVD9', 'DVD5'];
}
return [$a];
}
private static function slot_value($a) {
$extra = 1;
if (empty($a['IsSlotExtra'])) {
$extra = 0;
}
return $a['Slot'] * 100 + $extra;
}
public static function sort_torrent($a, $b) {
global $Processings;
$LevelA = self::resolution_level($a);
$LevelB = self::resolution_level($b);
if ($LevelA != $LevelB) {
return $LevelA < $LevelB ? -1 : 1;
}
$ProcessingA = array_search(self::processing_value($a), $Processings);
$ProcessingB = array_search(self::processing_value($b), $Processings);
if ($ProcessingA != $ProcessingB) {
return $ProcessingA < $ProcessingB ? -1 : 1;
}
$ResA = self::resolution_value($a);
$ResB = self::resolution_value($b);
if ($ResA != $ResB) {
return $ResA < $ResB ? -1 : 1;
}
$SlotA = self::slot_value($a);
$SlotB = self::slot_value($b);
if ($SlotA != $SlotB) {
return $SlotA < $SlotB ? -1 : 1;
}
$CodecA = self::codec_value($a);
$CodecB = self::codec_value($b);
if ($CodecA != $CodecB) {
return $CodecA < $CodecB ? -1 : 1;
}
$SizeA = $a['Size'];
$SizeB = $b['Size'];
if ($SizeA != $SizeB) {
return $SizeA < $SizeB ? -1 : 1;
}
// 不应该走到这里
return 1;
}
public static function get_torrent_checked($TorrentID) {
$TorrentChecked = G::$Cache->get_value("torrent_checked_$TorrentID");
if ($TorrentChecked === false) {
G::$DB->query("select Checked from torrents where ID=$TorrentID");
list($TorrentChecked) = G::$DB->next_record();
G::$Cache->cache_value("torrent_checked_$TorrentID", $TorrentChecked);
}
return $TorrentChecked;
}
public static function get_new_edition_title($LastTorrent, $Torrent) {
$LastResolution = isset($LastTorrent['Resolution']) ? $LastTorrent['Resolution'] : '';
$LastRemasterTitle = isset($LastTorrent['RemasterTitle']) ? $LastTorrent['RemasterTitle'] : '';
$LastRemasterCustomTitle = isset($LastTorrent['RemasterCustomTitle']) ? $LastTorrent['RemasterCustomTitle'] : '';
$LastNotMain = isset($LastTorrent['NotMainMovie']) ? $LastTorrent['NotMainMovie'] : '';
$Resolution = isset($Torrent['Resolution']) ? $Torrent['Resolution'] : '';
$RemasterTitle = isset($Torrent['RemasterTitle']) ? $Torrent['RemasterTitle'] : '';
$RemasterCustomTitle = isset($Torrent['RemasterCustomTitle']) ? $Torrent['RemasterCustomTitle'] : '';
$NotMain = isset($Torrent['NotMainMovie']) ? $Torrent['NotMainMovie'] : '';
// TODO bad design
$lastEdition = self::get_edition($LastResolution, $LastRemasterTitle, $LastRemasterCustomTitle, $LastNotMain);
$nextEdition = self::get_edition($Resolution, $RemasterTitle, $RemasterCustomTitle, $NotMain);
if ($lastEdition != $nextEdition) {
return Lang::get('torrents', $nextEdition);
}
return false;
}
private static function is_3d($RemasterTitle) {
foreach (EditionInfo::allEditionKey(EditionType::ThreeD) as $value) {
if (strstr($RemasterTitle, $value)) {
return true;
}
}
return false;
}
private static function is_extra($RemasterTitle, $RemasterCustomTitle) {
if (strstr(strtolower($RemasterCustomTitle), "extra")) {
return true;
}
if (strstr($RemasterTitle, "extras")) {
return true;
}
return false;
}
public static function get_edition($Resolution, $RemasterTitle, $RemasterCustomTitle, $NotMain) {
global $HighDefinition, $StandardDefinition, $UltraDefinition;
if ($NotMain) {
return "extra_definition";
}
if (self::is_3d($RemasterTitle)) {
return "3d";
}
if (in_array($Resolution, $StandardDefinition)) {
return "group_standard_resolution";
} else if (in_array($Resolution, $HighDefinition)) {
return "group_high_resolution";
} else if (in_array($Resolution, $UltraDefinition)) {
return "group_ultra_high_resolution";
} else if (empty($Resolution)) {
return "";
}
return "group_standard_resolution";
}
public static function display_simple_group_name($GroupInfo, $TorrentID = null, $Style = true) {
$SubName = $GroupInfo['SubName'];
$GroupName = $GroupInfo['Name'];
$GroupYear = $GroupInfo['Year'];
$DisplayName = '';
if (!$Style) {
if ($SubName) {
$DisplayName .= " [" . $SubName . "] ";
}
$DisplayName .= $GroupName;
if ($GroupYear) {
$DisplayName .= " ($GroupYear)";
}
return $DisplayName;
}
$GroupID = $GroupInfo['ID'];
if ($SubName) {
$DisplayName .= " [<a href=\"torrents.php?searchstr=" . $SubName . "\">$SubName</a>] ";
}
$DisplayName .= "<a href=\"torrents.php?id=$GroupID&amp;torrentid=$TorrentID#torrent$TorrentID\" data-tooltip=\"" . Lang::get('global', 'view_torrent') . "\" dir=\"ltr\">$GroupName</a>";
if ($GroupYear) {
$DisplayName .= " ($GroupYear)";
}
return $DisplayName;
}
public static function is_torrent_dead($Torrent) {
return $Torrent['Seeders'] == 0 && !empty($Torrent['last_action']) && $Torrent['last_action'] != '0000-00-00 00:00:00' && $Torrent['last_action'] < time_minus(3600 * 24 * 28);
}
// New Torrent Name: 安全领域 / Dirty Rotten Scoundrels Year: 1988 Uploader: joey Tags: comedy,documentary Codec: x264 Source: Blu-ray Container: MKV Resolution: 720p Size: 1.1 GB Freeleech: Freeleech! Link: https://SITE_NAME/torrents.php?id=375
public static function build_irc_msg($UploaderName, $Torrent) {
$Freeleech = '';
switch ($Torrent['FreeTorrent']) {
case 1:
$Freeleech = 'Freeleech!';
break;
case 11:
$Freeleech = '25% off!';
break;
case 12:
$Freeleech = '50% off!';
break;
case 13:
$Freeleech = '75% off!';
break;
}
if (self::global_freeleech()) {
$Freeleech = 'Freeleech!';
}
$Link = site_url() . "torrents.php?torrentid={$Torrent['ID']}";
$Size = Format::get_size($Torrent['Size']);
return "\002New Torrent\002 \00303Name: " . $Torrent['SubName'] . ' / ' . $Torrent['Name'] . ' Year: ' . $Torrent['Year'] . ' Uploader: ' . $UploaderName . ' Tags: ' . $Torrent['TagList'] . "\003 \00312Codec: " . $Torrent['Codec'] . ' Source: ' . $Torrent['Source'] . ' Container: ' . $Torrent['Container'] . ' Resolution: ' . $Torrent['Resolution'] . ' Size: ' . $Size . ' Freeleech: ' . $Freeleech . "\003 \00304Link: " . $Link . "\003";
}
public static function torrentid_to_groupid($TorrentID) {
global $Cache, $DB;
$DB->query("
SELECT GroupID
FROM torrents
WHERE ID = '" . db_string($TorrentID) . "'");
list($GroupID) = $DB->next_record(MYSQLI_NUM);
if ($GroupID) {
return $GroupID;
}
return null;
}
public static function convert_torrent($Group, $TorrentID = 0) {
if ($TorrentID) {
$TorrentInfo = $Group['Torrents'][$TorrentID];
} else {
list(, $TorrentInfo) = each($Group['Torrents']);
}
$TorrentInfo['Group'] = $Group;
unset($Group['Torrents']);
return $TorrentInfo;
}
public static function get_torrent($TorrentID, $Return = true, $RevisionID = 0, $PersonalProperties = true, $ApiCall = false) {
$GroupID = self::torrentid_to_groupid($TorrentID);
$GroupInfo = Torrents::get_group($GroupID, $Return, $RevisionID, $PersonalProperties, $ApiCall);
if ($GroupInfo) {
foreach ($GroupInfo['Torrents'] as &$Torrent) {
if ($Torrent['ID'] == $TorrentID) {
$TorrentInfo = $GroupInfo['Torrents'][$TorrentID];
$TorrentInfo['Group'] = $GroupInfo;
unset($GroupInfo['Torrents']);
return $TorrentInfo;
}
}
return null;
} else {
if ($Return) {
return null;
}
}
}
public static function freeleech_option() {
return array(0 => "Normal", 1 => "Free", 2 => "Neutral", 11 => "-25%", 12 => "-50%", 13 => "-75%");
}
public static function global_freeleech() {
return GLOBAL_FREELEECH == true;
}
public static function torrent_freeleech($Torrent) {
return $Torrent['FreeTorrent'] == 1 || self::global_freeleech();
}
public static function torrent_freetype($Torrent) {
if (self::global_freeleech()) {
return 1;
}
return $Torrent['FreeTorrent'];
}
}