BioTorrents.de’s version of Gazelle
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

upload_handle.php 34KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  1. <?php
  2. //****************************************************************************//
  3. //--------------- Take upload ------------------------------------------------//
  4. // This pages handles the backend of the torrent upload function. It checks //
  5. // the data, and if it all validates, it builds the torrent file, then writes //
  6. // the data to the database and the torrent to the disk. //
  7. //****************************************************************************//
  8. // Maximum allowed size for uploaded files
  9. // https://php.net/upload-max-filesize
  10. ini_set('upload_max_filesize', 2097152); // 2 MiB
  11. # Allow many uncompressed files,
  12. # e.g., http://academictorrents.com/details/5a447ff50062194bd58dd11c0fedead59e6d873c/tech
  13. ini_set('max_file_uploads', 10000);
  14. define('MAX_FILENAME_LENGTH', 255);
  15. include(SERVER_ROOT.'/classes/validate.class.php');
  16. include(SERVER_ROOT.'/classes/feed.class.php');
  17. include(SERVER_ROOT.'/sections/torrents/functions.php');
  18. include(SERVER_ROOT.'/classes/file_checker.class.php');
  19. enforce_login();
  20. authorize();
  21. $Validate = new Validate;
  22. $Feed = new Feed;
  23. //*****************************************************************************//
  24. //--------------- Set $Properties array ---------------------------------------//
  25. // This is used if the form doesn't validate, and when the time comes to enter //
  26. // it into the database.
  27. // todo: Do something about this mess
  28. //****************************************************************************//
  29. $Properties = [];
  30. $Type = $Categories[(int)$_POST['type']];
  31. $TypeID = $_POST['type'] + 1;
  32. $Properties['CategoryID'] = $TypeID;
  33. $Properties['CategoryName'] = $Type;
  34. # todo: Associate containers with categories beforehand
  35. # It may have to happen structurally in config.php, e.g.,
  36. # $Categories = [
  37. # 'GazelleName' => [$Name, $Icon, $Description, $Containers],
  38. # ...
  39. # ];
  40. $Properties['FileTypes'] = [
  41. 'DNA' => $Containers,
  42. 'RNA' => $Containers,
  43. 'Proteins' => $ContainersProt,
  44. 'Imaging' => $ContainersGames,
  45. 'Extras' => $ContainersExtra
  46. ];
  47. $Properties['ArchiveTypes'] = [
  48. 'DNA' => $Archives,
  49. 'RNA' => $Archives,
  50. 'Proteins' => $Archives,
  51. 'Imaging' => $Archives,
  52. 'Extras' => $Archives
  53. ];
  54. $Properties['Title'] = $_POST['title'];
  55. $Properties['TitleRJ'] = $_POST['title_rj'];
  56. $Properties['TitleJP'] = $_POST['title_jp'];
  57. $Properties['Year'] = $_POST['year'];
  58. $Properties['Studio'] = isset($_POST['studio']) ? $_POST['studio'] : '';
  59. $Properties['Series'] = isset($_POST['series']) ? $_POST['series'] : '';
  60. $Properties['CatalogueNumber'] = isset($_POST['catalogue']) ? $_POST['catalogue'] : '';
  61. $Properties['Pages'] = isset($_POST['pages']) ? $_POST['pages'] : 0;
  62. $Properties['Container'] = isset($_POST['container']) ? $_POST['container'] : '';
  63. $Properties['Media'] = $_POST['media'];
  64. $Properties['Codec'] = isset($_POST['codec']) ? $_POST['codec'] : '';
  65. if (!($_POST['resolution'] ?? false)) {
  66. $_POST['resolution'] = $_POST['ressel'] ?? '';
  67. }
  68. $Properties['Resolution'] = $_POST['resolution'] ?? '';
  69. $Properties['AudioFormat'] = $_POST['audioformat'] ?? '';
  70. $Properties['Subbing'] = 'nil';
  71. $Properties['Language'] = 'nil';
  72. $Properties['Subber'] = isset($_POST['subber']) ? $_POST['subber'] : '';
  73. $Properties['DLsiteID'] = (isset($_POST['dlsiteid'])) ? $_POST['dlsiteid'] : '';
  74. $Properties['Censored'] = (isset($_POST['censored'])) ? '1' : '0';
  75. $Properties['Anonymous'] = (isset($_POST['anonymous'])) ? '1' : '0';
  76. $Properties['Archive'] = (isset($_POST['archive']) && $_POST['archive'] !== '---') ? $_POST['archive'] : '';
  77. if (isset($_POST['library_image'])) {
  78. $Properties['LibraryImage'] = $_POST['library_image'];
  79. }
  80. if (isset($_POST['tags'])) {
  81. $Properties['TagList'] = implode(',', array_unique(explode(',', str_replace(' ', '', $_POST['tags']))));
  82. }
  83. if (isset($_POST['image'])) {
  84. $Properties['Image'] = $_POST['image'];
  85. }
  86. if (isset($_POST['release'])) {
  87. $Properties['ReleaseGroup'] = $_POST['release'];
  88. }
  89. $Properties['GroupDescription'] = trim($_POST['album_desc']);
  90. $Properties['TorrentDescription'] = $_POST['release_desc'];
  91. $Properties['MediaInfo'] = 'nil';
  92. $Properties['Screenshots'] = isset($_POST['screenshots']) ? $_POST['screenshots'] : '';
  93. $Properties['Mirrors'] = isset($_POST['mirrors']) ? $_POST['mirrors'] : '';
  94. if ($_POST['album_desc']) {
  95. $Properties['GroupDescription'] = trim($_POST['album_desc']);
  96. } elseif ($_POST['desc']) {
  97. $Properties['GroupDescription'] = trim($_POST['desc']);
  98. $Properties['MediaInfo'] = 'nil';
  99. }
  100. if (isset($_POST['groupid'])) {
  101. $Properties['GroupID'] = $_POST['groupid'];
  102. }
  103. if (isset($Properties['GroupID'])) {
  104. $Properties['Artists'] = Artists::get_artist($Properties['GroupID']);
  105. }
  106. # todo: Make this more generalized
  107. if ($Type === 'Movies' || $Type === 'Manga' || $Type === 'Anime' || $Type === 'Games') {
  108. if (empty($_POST['idols'])) {
  109. $Err = "You didn't enter any artists/idols";
  110. } else {
  111. $Artists = $_POST['idols'];
  112. }
  113. } else {
  114. if (!empty($_POST['idols'])) {
  115. $Artists = $_POST['idols'];
  116. }
  117. }
  118. if (!empty($_POST['requestid'])) {
  119. $RequestID = $_POST['requestid'];
  120. $Properties['RequestID'] = $RequestID;
  121. }
  122. //******************************************************************************//
  123. //--------------- Validate data in upload form ---------------------------------//
  124. # torrents_group.CategoryID
  125. $Validate->SetFields(
  126. 'type',
  127. '1',
  128. 'inarray',
  129. 'Please select a valid type.',
  130. array('inarray' => array_keys($Categories))
  131. );
  132. # todo: Remove the switch statement
  133. switch ($Type) {
  134. /*
  135. case 'Imaging':
  136. if (!isset($_POST['groupid']) || !$_POST['groupid']) {
  137. # torrents.Media
  138. $Validate->SetFields(
  139. 'media',
  140. '1',
  141. 'inarray',
  142. 'Please select a valid platform.',
  143. array('inarray' => array_merge($Media, $MediaManga, $Platform))
  144. );
  145. # torrents.Container
  146. $Validate->SetFields(
  147. 'container',
  148. '1',
  149. 'inarray',
  150. 'Please select a valid format.',
  151. array('inarray' => array_merge($Containers, $ContainersGames))
  152. );
  153. }
  154. break;
  155. */
  156. default:
  157. if (!isset($_POST['groupid']) || !$_POST['groupid']) {
  158. # torrents_group.CatalogueNumber
  159. $Validate->SetFields(
  160. 'catalogue',
  161. '0',
  162. 'string',
  163. 'Accession Number must be between 0 and 50 characters.',
  164. array('maxlength' => 50, 'minlength' => 0)
  165. );
  166. # torrents.AudioFormat
  167. $Validate->SetFields(
  168. 'audioformat',
  169. '0',
  170. 'string',
  171. 'Version must be between 0 and 10 characters.',
  172. array('maxlength' => 10, 'minlength' => 0)
  173. );
  174. # torrents_group.Name
  175. $Validate->SetFields(
  176. 'title',
  177. '1',
  178. 'string',
  179. 'Torrent Title must be between 10 and 255 characters.',
  180. array('maxlength' => 255, 'minlength' => 10)
  181. );
  182. # torrents_group.NameRJ
  183. $Validate->SetFields(
  184. 'title_rj',
  185. '0',
  186. 'string',
  187. 'Organism must be between 0 and 255 characters.',
  188. array('maxlength' => 255, 'minlength' => 0)
  189. );
  190. # torrents_group.NameJP
  191. $Validate->SetFields(
  192. 'title_jp',
  193. '0',
  194. 'string',
  195. 'Strain/Variety must be between 0 and 255 characters.',
  196. array('maxlength' => 255, 'minlength' => 0)
  197. );
  198. # torrents_group.Studio
  199. $Validate->SetFields(
  200. 'studio',
  201. '1',
  202. 'string',
  203. 'Department/Lab must be between 10 and 100 characters.',
  204. array('maxlength' => 100, 'minlength' => 10)
  205. );
  206. # torrents_group.Series
  207. $Validate->SetFields(
  208. 'series',
  209. '0',
  210. 'string',
  211. 'Location must be between 0 and 100 characters.',
  212. array('maxlength' => 100, 'minlength' => 0)
  213. );
  214. /* todo: Fix the year validation
  215. # torrents_group.Year
  216. $Validate->SetFields(
  217. 'year',
  218. '1',
  219. 'number',
  220. 'The year of the original release must be entered.',
  221. array('maxlength' => 4, 'minlength' => 4)
  222. );
  223. */
  224. # torrents.Media
  225. $Validate->SetFields(
  226. 'media',
  227. '1',
  228. 'inarray',
  229. 'Please select a valid platform.',
  230. array('inarray' => array_merge($Media, $MediaManga))
  231. );
  232. /*
  233. # torrents.Container
  234. $Validate->SetFields(
  235. 'container',
  236. '1',
  237. 'inarray',
  238. 'Please select a valid format.',
  239. array('inarray' => array_merge($Containers, $ContainersGames))
  240. );
  241. */
  242. # torrents_group.TagList
  243. $Validate->SetFields(
  244. 'tags',
  245. '1',
  246. 'string',
  247. 'You must enter at least five tags. Maximum length is 500 characters.',
  248. array('maxlength' => 500, 'minlength' => 10)
  249. );
  250. # torrents_group.WikiImage
  251. $Validate->SetFields(
  252. 'image',
  253. '0',
  254. 'link',
  255. 'The image URL you entered was invalid.',
  256. array('maxlength' => 255, 'minlength' => 10) # x.yz/a.bc
  257. );
  258. }
  259. # torrents_group.WikiBody
  260. $Validate->SetFields(
  261. 'album_desc',
  262. '1',
  263. 'string',
  264. 'The description must be between 100 and 65535 characters.',
  265. array('maxlength' => 65535, 'minlength' => 100)
  266. );
  267. /* todo: Fix the Group ID validation
  268. # torrents_group.ID
  269. $Validate->SetFields(
  270. 'groupid',
  271. '0',
  272. 'number',
  273. 'Group ID was not numeric.'
  274. );
  275. */
  276. }
  277. $Err = $Validate->ValidateForm($_POST); // Validate the form
  278. # todo: Move all this validation code to the Validate class
  279. if (count(explode(',', $Properties['TagList'])) < 5) {
  280. $Err = 'You must enter at least 5 tags.';
  281. }
  282. if (!(isset($_POST['title']) || isset($_POST['title_rj']) || isset($_POST['title_jp']))) {
  283. $Err = 'You must enter at least one title.';
  284. }
  285. $File = $_FILES['file_input']; // This is our torrent file
  286. $TorrentName = $File['tmp_name'];
  287. if (!is_uploaded_file($TorrentName) || !filesize($TorrentName)) {
  288. $Err = 'No torrent file uploaded, or file is empty.';
  289. } elseif (substr(strtolower($File['name']), strlen($File['name']) - strlen('.torrent')) !== '.torrent') {
  290. $Err = "You seem to have put something other than a torrent file into the upload field. (".$File['name'].").";
  291. }
  292. // Multiple artists!
  293. $LogName = '';
  294. if (empty($Properties['GroupID']) && empty($ArtistForm)) {
  295. $ArtistNames = [];
  296. $ArtistForm = [];
  297. for ($i = 0; $i < count($Artists); $i++) {
  298. if (trim($Artists[$i]) !== '') {
  299. if (!in_array($Artists[$i], $ArtistNames)) {
  300. $ArtistForm[$i] = array('name' => Artists::normalise_artist_name($Artists[$i]));
  301. array_push($ArtistNames, $ArtistForm[$i]['name']);
  302. }
  303. }
  304. }
  305. $LogName .= Artists::display_artists($ArtistForm, false, true, false);
  306. } elseif (empty($ArtistForm)) {
  307. $DB->query("
  308. SELECT ta.ArtistID, ag.Name
  309. FROM torrents_artists AS ta
  310. JOIN artists_group AS ag ON ta.ArtistID = ag.ArtistID
  311. WHERE ta.GroupID = ?
  312. ORDER BY ag.Name ASC", $Properties['GroupID']);
  313. $ArtistForm = [];
  314. while (list($ArtistID, $ArtistName) = $DB->next_record(MYSQLI_BOTH, false)) {
  315. array_push($ArtistForm, array('id' => $ArtistID, 'name' => display_str($ArtistName)));
  316. array_push($ArtistsUnescaped, array('name' => $ArtistName));
  317. }
  318. $LogName .= Artists::display_artists($ArtistsUnescaped, false, true, false);
  319. }
  320. if ($Err) { // Show the upload form, with the data the user entered
  321. $UploadForm = $Type;
  322. include(SERVER_ROOT.'/sections/upload/upload.php');
  323. die();
  324. }
  325. ImageTools::blacklisted($Properties['Image']);
  326. //******************************************************************************//
  327. //--------------- Make variables ready for database input ----------------------//
  328. // Prepared SQL statements do this for us, so there is nothing to do here anymore
  329. $T = $Properties;
  330. //******************************************************************************//
  331. //--------------- Generate torrent file ----------------------------------------//
  332. $Tor = new BencodeTorrent($TorrentName, true);
  333. $PublicTorrent = $Tor->make_private(); // The torrent is now private
  334. $UnsourcedTorrent = $Tor->make_sourced(); // The torrent now has the source field set
  335. $InfoHash = pack('H*', $Tor->info_hash());
  336. if (isset($Tor->Dec['encrypted_files'])) {
  337. $Err = 'This torrent contains an encrypted file list which is not supported here.';
  338. }
  339. // File list and size
  340. list($TotalSize, $FileList) = $Tor->file_list();
  341. $NumFiles = count($FileList);
  342. $TmpFileList = [];
  343. $TooLongPaths = [];
  344. $DirName = (isset($Tor->Dec['info']['files']) ? Format::make_utf8($Tor->get_name()) : '');
  345. check_name($DirName); // Check the folder name against the blacklist
  346. foreach ($FileList as $File) {
  347. list($Size, $Name) = $File;
  348. // Check file name and extension against blacklist/whitelist
  349. check_file($Type, $Name);
  350. // Make sure the filename is not too long
  351. if (mb_strlen($Name, 'UTF-8') + mb_strlen($DirName, 'UTF-8') + 1 > MAX_FILENAME_LENGTH) {
  352. $TooLongPaths[] = "$DirName/$Name";
  353. }
  354. // Add file info to array
  355. $TmpFileList[] = Torrents::filelist_format_file($File);
  356. }
  357. if (count($TooLongPaths) > 0) {
  358. $Names = implode(' <br />', $TooLongPaths);
  359. $Err = "The torrent contained one or more files with too long a name:<br /> $Names";
  360. }
  361. $FilePath = $DirName;
  362. $FileString = implode("\n", $TmpFileList);
  363. $Debug->set_flag('upload: torrent decoded');
  364. if (!empty($Err)) { // Show the upload form, with the data the user entered
  365. $UploadForm = $Type;
  366. include(SERVER_ROOT.'/sections/upload/upload.php');
  367. die();
  368. }
  369. //******************************************************************************//
  370. //--------------- Autofill format and archive ----------------------------------//
  371. if ($T['Container'] === 'Autofill') {
  372. # torrents.Container
  373. $T['Container'] = $Validate->ParseExtensions(
  374. # $FileList
  375. $Tor->file_list(),
  376. # $Category
  377. $T['CategoryName'],
  378. # $FileTypes
  379. $T['FileTypes'],
  380. );
  381. }
  382. if ($T['Archive'] === 'Autofill') {
  383. # torrents.Archive
  384. $T['Archive'] = $Validate->ParseExtensions(
  385. # $FileList
  386. $Tor->file_list(),
  387. # $Category
  388. $T['CategoryName'],
  389. # $FileTypes
  390. $T['ArchiveTypes'],
  391. );
  392. }
  393. //******************************************************************************//
  394. //--------------- Start database stuff -----------------------------------------//
  395. $Body = $T['GroupDescription'];
  396. // Trickery
  397. if (!preg_match('/^'.IMAGE_REGEX.'$/i', $T['Image'])) {
  398. $T['Image'] = '';
  399. }
  400. // Does it belong in a group?
  401. if ($T['GroupID']) {
  402. $DB->query("
  403. SELECT
  404. ID,
  405. WikiImage,
  406. WikiBody,
  407. RevisionID,
  408. Name,
  409. Year,
  410. TagList
  411. FROM torrents_group
  412. WHERE id = ?", $T['GroupID']);
  413. if ($DB->has_results()) {
  414. // Don't escape tg.Name. It's written directly to the log table
  415. list($GroupID, $WikiImage, $WikiBody, $RevisionID, $T['Title'], $T['Year'], $T['TagList']) = $DB->next_record(MYSQLI_NUM, array(4));
  416. $T['TagList'] = str_replace(array(' ', '.', '_'), array(', ', '.', '.'), $T['TagList']);
  417. if (!$T['Image'] && $WikiImage) {
  418. $T['Image'] = $WikiImage;
  419. }
  420. if (strlen($WikiBody) > strlen($Body)) {
  421. $Body = $WikiBody;
  422. if (!$T['Image'] || $T['Image'] === $WikiImage) {
  423. $NoRevision = true;
  424. }
  425. }
  426. $T['Artist'] = Artists::display_artists(Artists::get_artist($GroupID), false, false);
  427. }
  428. }
  429. if (!isset($GroupID) || !$GroupID) {
  430. foreach ($ArtistForm as $Num => $Artist) {
  431. // The album hasn't been uploaded. Try to get the artist IDs
  432. $DB->query("
  433. SELECT
  434. ArtistID,
  435. Name
  436. FROM artists_group
  437. WHERE Name = ?", $Artist['name']);
  438. if ($DB->has_results()) {
  439. while (list($ArtistID, $Name) = $DB->next_record(MYSQLI_NUM, false)) {
  440. if (!strcasecmp($Artist['name'], $Name)) {
  441. $ArtistForm[$Num] = ['id' => $ArtistID, 'name' => $Name];
  442. break;
  443. }
  444. }
  445. }
  446. }
  447. }
  448. // Needs to be here as it isn't set for add format until now
  449. $LogName .= $T['Title'];
  450. // For notifications. Take note now whether it's a new group
  451. $IsNewGroup = !isset($GroupID) || !$GroupID;
  452. //----- Start inserts
  453. if ((!isset($GroupID) || !$GroupID)) {
  454. // Array to store which artists we have added already, to prevent adding an artist twice
  455. $ArtistsAdded = [];
  456. foreach ($ArtistForm as $Num => $Artist) {
  457. if (!isset($Artist['id']) || !$Artist['id']) {
  458. if (isset($ArtistsAdded[strtolower($Artist['name'])])) {
  459. $ArtistForm[$Num] = $ArtistsAdded[strtolower($Artist['name'])];
  460. } else {
  461. // Create artist
  462. $DB->query("
  463. INSERT INTO artists_group (Name)
  464. VALUES ( ? )", $Artist['name']);
  465. $ArtistID = $DB->inserted_id();
  466. $Cache->increment('stats_artist_count');
  467. $ArtistForm[$Num] = array('id' => $ArtistID, 'name' => $Artist['name']);
  468. $ArtistsAdded[strtolower($Artist['name'])] = $ArtistForm[$Num];
  469. }
  470. }
  471. }
  472. unset($ArtistsAdded);
  473. }
  474. if (!isset($GroupID) || !$GroupID) {
  475. // Create torrent group
  476. $DB->query(
  477. "
  478. INSERT INTO torrents_group
  479. (CategoryID, Name, NameRJ, NameJP, Year,
  480. Series, Studio, CatalogueNumber, Pages, Time,
  481. WikiBody, WikiImage, DLsiteID)
  482. VALUES
  483. ( ?, ?, ?, ?, ?,
  484. ?, ?, ?, ?, NOW(),
  485. ?, ?, ? )",
  486. $TypeID,
  487. $T['Title'],
  488. $T['TitleRJ'],
  489. $T['TitleJP'],
  490. $T['Year'],
  491. $T['Series'],
  492. $T['Studio'],
  493. $T['CatalogueNumber'],
  494. $T['Pages'],
  495. $Body,
  496. $T['Image'],
  497. $T['DLsiteID']
  498. );
  499. $GroupID = $DB->inserted_id();
  500. foreach ($ArtistForm as $Num => $Artist) {
  501. $DB->query("
  502. INSERT IGNORE INTO torrents_artists (GroupID, ArtistID, UserID)
  503. VALUES ( ?, ?, ? )", $GroupID, $Artist['id'], $LoggedUser['ID']);
  504. $Cache->increment('stats_album_count');
  505. $Cache->delete_value('artist_groups_'.$Artist['id']);
  506. }
  507. $Cache->increment('stats_group_count');
  508. // Add screenshots
  509. // todo: Clear DB_MYSQL::exec_prepared_query() errors
  510. $Screenshots = explode("\n", $T['Screenshots']);
  511. $Screenshots = array_map('trim', $Screenshots);
  512. $Screenshots = array_filter($Screenshots, function ($s) {
  513. return preg_match('/^'.DOI_REGEX.'$/i', $s);
  514. });
  515. $Screenshots = array_unique($Screenshots);
  516. $Screenshots = array_slice($Screenshots, 0, 10);
  517. # Add optional web seeds similar to screenshots
  518. # Support an arbitrary and limited number of sources
  519. $Mirrors = explode("\n", $T['Mirrors']);
  520. $Mirrors = array_map('trim', $Mirrors);
  521. $Mirrors = array_filter($Mirrors, function ($s) {
  522. return preg_match('/^'.URL_REGEX.'$/i', $s);
  523. });
  524. $Mirrors = array_unique($Mirrors);
  525. $Mirrors = array_slice($Mirrors, 0, 2);
  526. # Downgrade TLS on resource URIs
  527. # Required for BEP 19 compatibility
  528. $Mirrors = str_ireplace('tps://', 'tp://', $Mirrors);
  529. # Perform the DB inserts here
  530. # Screenshots (Publications)
  531. if (!empty($Screenshots)) {
  532. $Screenshot = '';
  533. $DB->prepare_query("
  534. INSERT INTO torrents_screenshots
  535. (GroupID, UserID, Time, Image)
  536. VALUES (?, ?, NOW(), ?)", $GroupID, $LoggedUser['ID'], $Screenshot);
  537. foreach ($Screenshots as $Screenshot) {
  538. $DB->exec_prepared_query();
  539. }
  540. }
  541. # Mirrors
  542. if (!empty($Mirrors)) {
  543. $Mirror = '';
  544. $DB->prepare_query("
  545. INSERT INTO torrents_mirrors
  546. (GroupID, UserID, Time, Resource)
  547. VALUES (?, ?, NOW(), ?)", $GroupID, $LoggedUser['ID'], $Mirror);
  548. foreach ($Mirrors as $Mirror) {
  549. $DB->exec_prepared_query();
  550. }
  551. }
  552. # Main if/else
  553. } else {
  554. $DB->query("
  555. UPDATE torrents_group
  556. SET Time = NOW()
  557. WHERE ID = ?", $GroupID);
  558. $Cache->delete_value("torrent_group_$GroupID");
  559. $Cache->delete_value("torrents_details_$GroupID");
  560. $Cache->delete_value("detail_files_$GroupID");
  561. }
  562. // Description
  563. if (!isset($NoRevision) || !$NoRevision) {
  564. $DB->query("
  565. INSERT INTO wiki_torrents
  566. (PageID, Body, UserID, Summary, Time, Image)
  567. VALUES
  568. ( ?, ?, ?, 'Uploaded new torrent', NOW(), ? )", $GroupID, $T['GroupDescription'], $LoggedUser['ID'], $T['Image']);
  569. $RevisionID = $DB->inserted_id();
  570. // Revision ID
  571. $DB->query("
  572. UPDATE torrents_group
  573. SET RevisionID = ?
  574. WHERE ID = ?", $RevisionID, $GroupID);
  575. }
  576. // Tags
  577. $Tags = explode(',', $T['TagList']);
  578. if (!$T['GroupID']) {
  579. foreach ($Tags as $Tag) {
  580. $Tag = Misc::sanitize_tag($Tag);
  581. if (!empty($Tag)) {
  582. $Tag = Misc::get_alias_tag($Tag);
  583. $DB->query("
  584. INSERT INTO tags
  585. (Name, UserID)
  586. VALUES
  587. ( ?, ? )
  588. ON DUPLICATE KEY UPDATE
  589. Uses = Uses + 1;", $Tag, $LoggedUser['ID']);
  590. $TagID = $DB->inserted_id();
  591. $DB->query("
  592. INSERT INTO torrents_tags
  593. (TagID, GroupID, UserID)
  594. VALUES
  595. ( ?, ?, ? )
  596. ON DUPLICATE KEY UPDATE TagID=TagID", $TagID, $GroupID, $LoggedUser['ID']);
  597. }
  598. }
  599. }
  600. // Use this section to control freeleeches
  601. $T['FreeTorrent'] = '0';
  602. $T['FreeLeechType'] = '0';
  603. $DB->query("
  604. SELECT Name, First, Second
  605. FROM misc
  606. WHERE Second = 'freeleech'");
  607. if ($DB->has_results()) {
  608. $FreeLeechTags = $DB->to_array('Name');
  609. foreach ($FreeLeechTags as $Tag => $Exp) {
  610. if ($Tag === 'global' || in_array($Tag, $Tags)) {
  611. $T['FreeTorrent'] = '1';
  612. $T['FreeLeechType'] = '3';
  613. break;
  614. }
  615. }
  616. }
  617. // Torrents over a size in bytes are neutral leech
  618. // Download doesn't count, upload does
  619. if (($TotalSize > 10737418240)) { # 10 GiB
  620. $T['FreeTorrent'] = '2';
  621. $T['FreeLeechType'] = '2';
  622. }
  623. // Torrent
  624. $DB->query(
  625. "
  626. INSERT INTO torrents
  627. (GroupID, UserID, Media, Container, Codec, Resolution,
  628. AudioFormat, Subbing, Language, Subber, Censored,
  629. Anonymous, Archive, info_hash, FileCount, FileList, FilePath, Size, Time,
  630. Description, MediaInfo, FreeTorrent, FreeLeechType)
  631. VALUES
  632. ( ?, ?, ?, ?, ?, ?,
  633. ?, ?, ?, ?, ?,
  634. ?, ?, ?, ?, ?, ?, ?, NOW(),
  635. ?, ?, ?, ? )",
  636. $GroupID,
  637. $LoggedUser['ID'],
  638. $T['Media'],
  639. $T['Container'],
  640. $T['Codec'],
  641. $T['Resolution'],
  642. $T['AudioFormat'],
  643. $T['Subbing'],
  644. $T['Language'],
  645. $T['Subber'],
  646. $T['Censored'],
  647. $T['Anonymous'],
  648. $T['Archive'],
  649. $InfoHash,
  650. $NumFiles,
  651. $FileString,
  652. $FilePath,
  653. $TotalSize,
  654. $T['TorrentDescription'],
  655. $T['MediaInfo'],
  656. $T['FreeTorrent'],
  657. $T['FreeLeechType']
  658. );
  659. $TorrentID = $DB->inserted_id();
  660. $Cache->increment('stats_torrent_count');
  661. $Tor->Dec['comment'] = 'https://'.SITE_DOMAIN.'/torrents.php?torrentid='.$TorrentID;
  662. Tracker::update_tracker('add_torrent', [
  663. 'id' => $TorrentID,
  664. 'info_hash' => rawurlencode($InfoHash),
  665. 'freetorrent' => $T['FreeTorrent']
  666. ]);
  667. $Debug->set_flag('upload: ocelot updated');
  668. // Prevent deletion of this torrent until the rest of the upload process is done
  669. // (expire the key after 10 minutes to prevent locking it for too long in case there's a fatal error below)
  670. $Cache->cache_value("torrent_{$TorrentID}_lock", true, 600);
  671. // Give BP if necessary
  672. // todo: Repurpose this
  673. if (($Type === "Movies" || $Type === "Anime") && ($T['Container'] === 'ISO' || $T['Container'] === 'M2TS' || $T['Container'] === 'VOB IFO')) {
  674. $BPAmt = (int) 2*($TotalSize / (1024*1024*1024))*1000;
  675. $DB->query("
  676. UPDATE users_main
  677. SET BonusPoints = BonusPoints + ?
  678. WHERE ID = ?", $BPAmt, $LoggedUser['ID']);
  679. $DB->query("
  680. UPDATE users_info
  681. SET AdminComment = CONCAT(NOW(), ' - Received $BPAmt ".BONUS_POINTS." for uploading a torrent $TorrentID\n\n', AdminComment)
  682. WHERE UserID = ?", $LoggedUser['ID']);
  683. $Cache->delete_value('user_info_heavy_'.$LoggedUser['ID']);
  684. $Cache->delete_value('user_stats_'.$LoggedUser['ID']);
  685. }
  686. // Add to shop freeleeches if necessary
  687. if ($T['FreeLeechType'] === 3) {
  688. // Figure out which duration to use
  689. $Expiry = 0;
  690. foreach ($FreeLeechTags as $Tag => $Exp) {
  691. if ($Tag === 'global' || in_array($Tag, $Tags)) {
  692. if (((int) $FreeLeechTags[$Tag]['First']) > $Expiry) {
  693. $Expiry = (int) $FreeLeechTags[$Tag]['First'];
  694. }
  695. }
  696. }
  697. if ($Expiry > 0) {
  698. $DB->query("
  699. INSERT INTO shop_freeleeches
  700. (TorrentID, ExpiryTime)
  701. VALUES
  702. (" . $TorrentID . ", FROM_UNIXTIME(" . $Expiry . "))
  703. ON DUPLICATE KEY UPDATE
  704. ExpiryTime = FROM_UNIXTIME(UNIX_TIMESTAMP(ExpiryTime) + ($Expiry - FROM_UNIXTIME(NOW())))");
  705. } else {
  706. Torrents::freeleech_torrents($TorrentID, 0, 0);
  707. }
  708. }
  709. //******************************************************************************//
  710. //--------------- Write torrent file -------------------------------------------//
  711. file_put_contents(TORRENT_STORE.$TorrentID.'.torrent', $Tor->encode());
  712. Misc::write_log("Torrent $TorrentID ($LogName) (".number_format($TotalSize / (1024 * 1024), 2).' MB) was uploaded by ' . $LoggedUser['Username']);
  713. Torrents::write_group_log($GroupID, $TorrentID, $LoggedUser['ID'], 'uploaded ('.number_format($TotalSize / (1024 * 1024), 2).' MB)', 0);
  714. Torrents::update_hash($GroupID);
  715. $Debug->set_flag('upload: sphinx updated');
  716. //******************************************************************************//
  717. //---------------------- Recent Uploads ----------------------------------------//
  718. if (trim($T['Image']) !== '') {
  719. $RecentUploads = $Cache->get_value("recent_uploads_$UserID");
  720. if (is_array($RecentUploads)) {
  721. do {
  722. foreach ($RecentUploads as $Item) {
  723. if ($Item['ID'] === $GroupID) {
  724. break 2;
  725. }
  726. }
  727. // Only reached if no matching GroupIDs in the cache already.
  728. if (count($RecentUploads) === 5) {
  729. array_pop($RecentUploads);
  730. }
  731. array_unshift($RecentUploads, array(
  732. 'ID' => $GroupID,
  733. 'Name' => trim($T['Title']),
  734. 'Artist' => Artists::display_artists($ArtistForm, false, true),
  735. 'WikiImage' => trim($T['Image'])));
  736. $Cache->cache_value("recent_uploads_$UserID", $RecentUploads, 0);
  737. } while (0);
  738. }
  739. }
  740. //******************************************************************************//
  741. //------------------------------- Post-processing ------------------------------//
  742. /* Because tracker updates and notifications can be slow, we're
  743. * redirecting the user to the destination page and flushing the buffers
  744. * to make it seem like the PHP process is working in the background.
  745. */
  746. if ($PublicTorrent) {
  747. View::show_header('Warning'); ?>
  748. <h1>Warning</h1>
  749. <p>
  750. <strong>Your torrent has been uploaded but you must re-download your torrent file from
  751. <a
  752. href="torrents.php?id=<?=$GroupID?>&torrentid=<?=$TorrentID?>">here</a>
  753. because the site modified it to make it private.</strong>
  754. </p>
  755. <?php
  756. View::show_footer();
  757. } elseif ($UnsourcedTorrent) {
  758. View::show_header('Warning'); ?>
  759. <h1>Warning</h1>
  760. <p>
  761. <strong>Your torrent has been uploaded but you must re-download your torrent file from
  762. <a
  763. href="torrents.php?id=<?=$GroupID?>&torrentid=<?=$TorrentID?>">here</a>
  764. because the site modified it to add a source flag.</strong>
  765. </p>
  766. <?php
  767. View::show_footer();
  768. } elseif ($RequestID) {
  769. header("Location: requests.php?action=takefill&requestid=$RequestID&torrentid=$TorrentID&auth=".$LoggedUser['AuthKey']);
  770. } else {
  771. header("Location: torrents.php?id=$GroupID&torrentid=$TorrentID");
  772. }
  773. if (function_exists('fastcgi_finish_request')) {
  774. fastcgi_finish_request();
  775. } else {
  776. ignore_user_abort(true);
  777. ob_flush();
  778. flush();
  779. ob_start(); // So we don't keep sending data to the client
  780. }
  781. //******************************************************************************//
  782. //--------------------------- IRC announce and feeds ---------------------------//
  783. $Announce = '';
  784. $Announce .= Artists::display_artists($ArtistForm, false);
  785. $Announce .= substr(trim(empty($T['Title']) ? (empty($T['TitleRJ']) ? $T['TitleJP'] : $T['TitleRJ']) : $T['Title']), 0, 100);
  786. $Announce .= ' ';
  787. if ($Type !== 'Other') {
  788. $Announce .= '['.Torrents::torrent_info($T, false, false, false).']';
  789. }
  790. $Title = '['.$T['CategoryName'].'] '.$Announce;
  791. $Announce = "$Title - ".site_url()."torrents.php?id=$GroupID / ".site_url()."torrents.php?action=download&id=$TorrentID";
  792. $Announce .= ' - '.trim($T['TagList']);
  793. // ENT_QUOTES is needed to decode single quotes/apostrophes
  794. send_irc('PRIVMSG '.BOT_ANNOUNCE_CHAN.' '.html_entity_decode($Announce, ENT_QUOTES));
  795. $Debug->set_flag('upload: announced on irc');
  796. // Manage notifications
  797. // For RSS
  798. $Item = $Feed->item($Title, Text::strip_bbcode($Body), 'torrents.php?action=download&amp;authkey=[[AUTHKEY]]&amp;torrent_pass=[[PASSKEY]]&amp;id='.$TorrentID, $Properties['Anonymous'] ? 'Anonymous' : $LoggedUser['Username'], 'torrents.php?id='.$GroupID, trim($T['TagList']));
  799. // Notifications
  800. $SQL = "
  801. SELECT unf.ID, unf.UserID, torrent_pass
  802. FROM users_notify_filters AS unf
  803. JOIN users_main AS um ON um.ID = unf.UserID
  804. WHERE um.Enabled = '1'";
  805. if (empty($ArtistsUnescaped)) {
  806. $ArtistsUnescaped = $ArtistForm;
  807. }
  808. if (!empty($ArtistsUnescaped)) {
  809. $ArtistNameList = [];
  810. $GuestArtistNameList = [];
  811. foreach ($ArtistsUnescaped as $Importance => $Artist) {
  812. $ArtistNameList[] = "Artists LIKE '%|".db_string(str_replace('\\', '\\\\', $Artist['name']), true)."|%'";
  813. }
  814. // Don't add notification if >2 main artists or if tracked artist isn't a main artist
  815. /*
  816. if (count($ArtistNameList) > 2 || $Artist['name'] === 'Various Artists') {
  817. $SQL .= " AND (ExcludeVA = '0' AND (";
  818. $SQL .= implode(' OR ', array_merge($ArtistNameList, $GuestArtistNameList));
  819. $SQL .= " OR Artists = '')) AND (";
  820. } else {
  821. */
  822. $SQL .= " AND (";
  823. if (!empty($GuestArtistNameList)) {
  824. $SQL .= "(ExcludeVA = '0' AND (";
  825. $SQL .= implode(' OR ', $GuestArtistNameList);
  826. $SQL .= ')) OR ';
  827. }
  828. if (count($ArtistNameList) > 0) {
  829. $SQL .= implode(' OR ', $ArtistNameList);
  830. $SQL .= " OR ";
  831. }
  832. $SQL .= "Artists = '') AND (";
  833. #}
  834. } else {
  835. $SQL .= "AND (Artists = '') AND (";
  836. }
  837. reset($Tags);
  838. $TagSQL = [];
  839. $NotTagSQL = [];
  840. foreach ($Tags as $Tag) {
  841. $TagSQL[] = " Tags LIKE '%|".db_string(trim($Tag))."|%' ";
  842. $NotTagSQL[] = " NotTags LIKE '%|".db_string(trim($Tag))."|%' ";
  843. }
  844. $TagSQL[] = "Tags = ''";
  845. $SQL .= implode(' OR ', $TagSQL);
  846. $SQL .= ") AND !(".implode(' OR ', $NotTagSQL).')';
  847. $SQL .= " AND (Categories LIKE '%|".db_string(trim($Type))."|%' OR Categories = '') ";
  848. if ($T['ReleaseType']) {
  849. $SQL .= " AND (ReleaseTypes LIKE '%|".db_string(trim($ReleaseTypes[$T['ReleaseType']]))."|%' OR ReleaseTypes = '') ";
  850. } else {
  851. $SQL .= " AND (ReleaseTypes = '') ";
  852. }
  853. /*
  854. Notify based on the following:
  855. 1. The torrent must match the formatbitrate filter on the notification
  856. 2. If they set NewGroupsOnly to 1, it must also be the first torrent in the group to match the formatbitrate filter on the notification
  857. */
  858. if ($T['Format']) {
  859. $SQL .= " AND (Formats LIKE '%|".db_string(trim($T['Format']))."|%' OR Formats = '') ";
  860. } else {
  861. $SQL .= " AND (Formats = '') ";
  862. }
  863. if ($_POST['bitrate']) {
  864. $SQL .= " AND (Encodings LIKE '%|".db_string(trim($_POST['bitrate']))."|%' OR Encodings = '') ";
  865. } else {
  866. $SQL .= " AND (Encodings = '') ";
  867. }
  868. if ($T['Media']) {
  869. $SQL .= " AND (Media LIKE '%|".db_string(trim($T['Media']))."|%' OR Media = '') ";
  870. } else {
  871. $SQL .= " AND (Media = '') ";
  872. }
  873. // Either they aren't using NewGroupsOnly
  874. $SQL .= "AND ((NewGroupsOnly = '0' ";
  875. // Or this is the first torrent in the group to match the formatbitrate filter
  876. $SQL .= ") OR ( NewGroupsOnly = '1' ";
  877. $SQL .= '))';
  878. if ($T['Year']) {
  879. $SQL .= " AND (('".db_string(trim($T['Year']))."' BETWEEN FromYear AND ToYear)
  880. OR (FromYear = 0 AND ToYear = 0)) ";
  881. } else {
  882. $SQL .= " AND (FromYear = 0 AND ToYear = 0) ";
  883. }
  884. $SQL .= " AND UserID !== '".$LoggedUser['ID']."' ";
  885. $DB->query("
  886. SELECT Paranoia
  887. FROM users_main
  888. WHERE ID = $LoggedUser[ID]");
  889. list($Paranoia) = $DB->next_record();
  890. $Paranoia = unserialize($Paranoia);
  891. if (!is_array($Paranoia)) {
  892. $Paranoia = [];
  893. }
  894. if (!in_array('notifications', $Paranoia)) {
  895. $SQL .= " AND (Users LIKE '%|".$LoggedUser['ID']."|%' OR Users = '') ";
  896. }
  897. $SQL .= " AND UserID !== '".$LoggedUser['ID']."' ";
  898. $DB->query($SQL);
  899. $Debug->set_flag('upload: notification query finished');
  900. if ($DB->has_results()) {
  901. $UserArray = $DB->to_array('UserID');
  902. $FilterArray = $DB->to_array('ID');
  903. $InsertSQL = '
  904. INSERT IGNORE INTO users_notify_torrents (UserID, GroupID, TorrentID, FilterID)
  905. VALUES ';
  906. $Rows = [];
  907. foreach ($UserArray as $User) {
  908. list($FilterID, $UserID, $Passkey) = $User;
  909. $Rows[] = "('$UserID', '$GroupID', '$TorrentID', '$FilterID')";
  910. $Feed->populate("torrents_notify_$Passkey", $Item);
  911. $Cache->delete_value("notifications_new_$UserID");
  912. }
  913. $InsertSQL .= implode(',', $Rows);
  914. $DB->query($InsertSQL);
  915. $Debug->set_flag('upload: notification inserts finished');
  916. foreach ($FilterArray as $Filter) {
  917. list($FilterID, $UserID, $Passkey) = $Filter;
  918. $Feed->populate("torrents_notify_{$FilterID}_$Passkey", $Item);
  919. }
  920. }
  921. // RSS for bookmarks
  922. $DB->query("
  923. SELECT u.ID, u.torrent_pass
  924. FROM users_main AS u
  925. JOIN bookmarks_torrents AS b ON b.UserID = u.ID
  926. WHERE b.GroupID = $GroupID");
  927. while (list($UserID, $Passkey) = $DB->next_record()) {
  928. $Feed->populate("torrents_bookmarks_t_$Passkey", $Item);
  929. }
  930. $Feed->populate('torrents_all', $Item);
  931. $Feed->populate('torrents_'.strtolower($Type), $Item);
  932. $Debug->set_flag('upload: notifications handled');
  933. // Clear cache
  934. $Cache->delete_value("torrents_details_$GroupID");
  935. $Cache->delete_value("contest_scores");
  936. // Allow deletion of this torrent now
  937. $Cache->delete_value("torrent_{$TorrentID}_lock");