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

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