Browse Source

Merge pull request #8 from biotorrents/development

Merge recent updates from development into production
pjc09h 4 years ago
parent
commit
572dce17e9
No account linked to committer's email address
100 changed files with 7418 additions and 5768 deletions
  1. 33
    6
      .gitignore
  2. 0
    0
      api.php
  3. 10
    10
      classes/artists.class.php
  4. 5
    16
      classes/autoload.php
  5. 2
    2
      classes/badges.class.php
  6. 1
    1
      classes/bookmarks.class.php
  7. 0
    63
      classes/charts.class.php
  8. 4
    4
      classes/collages.class.php
  9. 713
    429
      classes/config.template.php
  10. 111
    58
      classes/donations.class.php
  11. 2
    2
      classes/donationsview.class.php
  12. 80
    29
      classes/env.class.php
  13. 52
    2
      classes/json.class.php
  14. 0
    38
      classes/loginwatch.class.php
  15. 42
    14
      classes/mysql.class.php
  16. 83
    66
      classes/permissions_form.php
  17. 19
    60
      classes/script_start.php
  18. 41
    13
      classes/security.class.php
  19. 0
    17
      classes/slaves.class.php
  20. 55
    38
      classes/text.class.php
  21. 1
    12
      classes/tools.class.php
  22. 191
    422
      classes/torrent_form.class.php
  23. 248
    93
      classes/torrents.class.php
  24. 277
    0
      classes/twig.class.php
  25. 185
    93
      classes/userrank.class.php
  26. 1
    7
      classes/users.class.php
  27. 17
    4
      classes/util.php
  28. 29
    53
      classes/validate.class.php
  29. 0
    1995
      classes/vendor/Parsedown.php
  30. 0
    687
      classes/vendor/ParsedownExtra.php
  31. 0
    411
      classes/vendor/TwitterAPIExchange.php
  32. 2
    2
      classes/wiki.class.php
  33. 54
    0
      composer.json
  34. 4065
    0
      composer.lock
  35. 0
    4
      delete.php
  36. 1
    1
      design/privatefooter.php
  37. 7
    17
      design/privateheader.php
  38. 2
    1
      design/publicfooter.php
  39. 4
    7
      design/publicheader.php
  40. 1
    1
      design/views/generic/reply/quickreply.php
  41. 1
    1
      design/views/generic/reply/staffpm.php
  42. 59
    18
      feeds.php
  43. 197
    97
      gazelle.sql
  44. 86
    48
      image.php
  45. 1
    1
      manifest.php
  46. 97
    30
      readme.md
  47. 1
    1
      rules.php
  48. 0
    60
      sections/ajax/autofill/anime.php
  49. 0
    234
      sections/ajax/autofill/jav.php
  50. 0
    106
      sections/ajax/autofill/manga.php
  51. 0
    292
      sections/ajax/index.php
  52. 0
    101
      sections/ajax/torrentgroup.php
  53. 0
    0
      sections/api/announcements.php
  54. 17
    15
      sections/api/artist.php
  55. 32
    0
      sections/api/autofill/doi.php
  56. 0
    0
      sections/api/better/index.php
  57. 0
    0
      sections/api/better/single.php
  58. 0
    0
      sections/api/better/transcode.php
  59. 0
    0
      sections/api/bookmarks/artists.php
  60. 0
    0
      sections/api/bookmarks/index.php
  61. 0
    0
      sections/api/bookmarks/torrents.php
  62. 0
    0
      sections/api/browse.php
  63. 0
    0
      sections/api/clear_user_notification.php
  64. 0
    0
      sections/api/collage.php
  65. 0
    0
      sections/api/community_stats.php
  66. 0
    0
      sections/api/forum/forum.php
  67. 0
    0
      sections/api/forum/index.php
  68. 0
    0
      sections/api/forum/main.php
  69. 1
    1
      sections/api/forum/thread.php
  70. 0
    0
      sections/api/get_friends.php
  71. 0
    0
      sections/api/get_user_notifications.php
  72. 0
    0
      sections/api/inbox/inbox.php
  73. 0
    0
      sections/api/inbox/index.php
  74. 0
    0
      sections/api/inbox/viewconv.php
  75. 288
    0
      sections/api/index.php
  76. 0
    0
      sections/api/info.php
  77. 1
    1
      sections/api/loadavg.php
  78. 0
    0
      sections/api/news_ajax.php
  79. 0
    0
      sections/api/notifications.php
  80. 0
    0
      sections/api/ontology.php
  81. 0
    0
      sections/api/preview.php
  82. 0
    0
      sections/api/raw_bbcode.php
  83. 0
    0
      sections/api/request.php
  84. 0
    0
      sections/api/requests.php
  85. 51
    24
      sections/api/send_recommendation.php
  86. 5
    3
      sections/api/stats.php
  87. 0
    0
      sections/api/subscriptions.php
  88. 0
    0
      sections/api/tcomments.php
  89. 0
    0
      sections/api/top10/index.php
  90. 0
    0
      sections/api/top10/tags.php
  91. 23
    17
      sections/api/top10/torrents.php
  92. 0
    0
      sections/api/top10/users.php
  93. 166
    0
      sections/api/torrents/group.php
  94. 16
    16
      sections/api/torrents/torrent.php
  95. 1
    1
      sections/api/torrents/torrentgroupalbumart.php
  96. 0
    0
      sections/api/user.php
  97. 37
    23
      sections/api/user_recents.php
  98. 0
    0
      sections/api/userhistory/index.php
  99. 0
    0
      sections/api/userhistory/post_history.php
  100. 0
    0
      sections/api/usersearch.php

+ 33
- 6
.gitignore View File

@@ -1,9 +1,36 @@
1
-classes/config.php
2
-_packages/**/.git/**
1
+# https://github.com/OPSnet/Gazelle/blob/master/.gitignore
3 2
 
4
-static/styles/*.css
5
-static/styles/assets/fonts/**
6
-static/common/badges/**
3
+# backup files
4
+*.bak
5
+*.sw*
6
+*~
7 7
 
8
+# home folder
8 9
 .DS_Store
9
-*.sw*
10
+.bash_history
11
+.cache/
12
+.config/
13
+.histfile
14
+.lesshst
15
+.local/
16
+.npm-global/
17
+.npm/
18
+.npmrc
19
+.php_history
20
+.pki/
21
+.profile
22
+.wget-hsts
23
+.zsh*
24
+__MACOSX/
25
+
26
+# app files
27
+/cache
28
+/classes/config.php
29
+/node_modules
30
+/vendor
31
+
32
+# heavy assets
33
+/static/common/badges/**
34
+/static/styles/**/*.css
35
+/static/styles/assets/fonts/**
36
+fonts.tgz

ajax.php → api.php View File


+ 10
- 10
classes/artists.class.php View File

@@ -47,7 +47,7 @@ class Artists
47 47
             }
48 48
 
49 49
             $QueryID = G::$DB->get_query_id();
50
-            G::$DB->query("
50
+            G::$DB->prepared_query("
51 51
             SELECT
52 52
               ta.`GroupID`,
53 53
               ta.`ArtistID`,
@@ -169,7 +169,7 @@ class Artists
169 169
     public static function delete_artist($ArtistID)
170 170
     {
171 171
         $QueryID = G::$DB->get_query_id();
172
-        G::$DB->query("
172
+        G::$DB->prepared_query("
173 173
         SELECT
174 174
           `NAME`
175 175
         FROM
@@ -180,7 +180,7 @@ class Artists
180 180
         list($Name) = G::$DB->next_record(MYSQLI_NUM, false);
181 181
 
182 182
         // Delete requests
183
-        G::$DB->query("
183
+        G::$DB->prepared_query("
184 184
         SELECT
185 185
           `RequestID`
186 186
         FROM
@@ -192,7 +192,7 @@ class Artists
192 192
         $Requests = G::$DB->to_array();
193 193
         foreach ($Requests as $Request) {
194 194
             list($RequestID) = $Request;
195
-            G::$DB->query("
195
+            G::$DB->prepared_query("
196 196
             DELETE
197 197
             FROM
198 198
               `requests`
@@ -200,7 +200,7 @@ class Artists
200 200
               `ID` = '$RequestID'
201 201
             ");
202 202
 
203
-            G::$DB->query("
203
+            G::$DB->prepared_query("
204 204
             DELETE
205 205
             FROM
206 206
               `requests_votes`
@@ -208,7 +208,7 @@ class Artists
208 208
               `RequestID` = '$RequestID'
209 209
             ");
210 210
 
211
-            G::$DB->query("
211
+            G::$DB->prepared_query("
212 212
             DELETE
213 213
             FROM
214 214
               `requests_tags`
@@ -216,7 +216,7 @@ class Artists
216 216
               `RequestID` = '$RequestID'
217 217
             ");
218 218
 
219
-            G::$DB->query("
219
+            G::$DB->prepared_query("
220 220
             DELETE
221 221
             FROM
222 222
               `requests_artists`
@@ -226,7 +226,7 @@ class Artists
226 226
         }
227 227
 
228 228
         // Delete artist
229
-        G::$DB->query("
229
+        G::$DB->prepared_query("
230 230
         DELETE
231 231
         FROM
232 232
           `artists_group`
@@ -236,7 +236,7 @@ class Artists
236 236
         G::$Cache->decrement('stats_artist_count');
237 237
 
238 238
         // Delete wiki revisions
239
-        G::$DB->query("
239
+        G::$DB->prepared_query("
240 240
         DELETE
241 241
         FROM
242 242
           `wiki_artists`
@@ -245,7 +245,7 @@ class Artists
245 245
         ");
246 246
 
247 247
         // Delete tags
248
-        G::$DB->query("
248
+        G::$DB->prepared_query("
249 249
         DELETE
250 250
         FROM
251 251
           `artists_tags`

+ 5
- 16
classes/autoload.php View File

@@ -11,8 +11,10 @@ declare(strict_types=1);
11 11
  * @see https://www.php.net/manual/en/language.oop5.autoload.php
12 12
  */
13 13
 spl_autoload_register(function ($ClassName) {
14
-    $FilePath = SERVER_ROOT . '/classes/' . strtolower($ClassName) . '.class.php';
15
-    #$FilePath = $_SERVER['DOCUMENT_ROOT'] . '/classes/' . strtolower($ClassName) . '.class.php';
14
+  $ENV = ENV::go();
15
+
16
+  $classname = strtolower($ClassName);
17
+    $FilePath = "$ENV->SERVER_ROOT/classes/$classname.class.php";
16 18
 
17 19
     if (!file_exists($FilePath)) {
18 20
         // todo: Rename the following classes to conform with the code guidelines
@@ -43,24 +45,11 @@ spl_autoload_register(function ($ClassName) {
43 45
           $FileName = 'env.class';
44 46
           break;
45 47
 
46
-        case 'Parsedown':
47
-          $FileName = 'vendor/Parsedown';
48
-          break;
49
-
50
-        case 'ParsedownExtra':
51
-          $FileName = 'vendor/ParsedownExtra';
52
-          break;
53
-
54
-        case 'TwitterAPIExchange':
55
-          $FileName = 'vendor/TwitterAPIExchange';
56
-          break;
57
-
58 48
         default:
59 49
           error("Couldn't import class $ClassName");
60 50
     }
61 51
 
62
-        $FilePath = SERVER_ROOT . "/classes/$FileName.php";
63
-        #$FilePath = $_SERVER['DOCUMENT_ROOT'] . "/classes/$FileName.php";
52
+        $FilePath = "$ENV->SERVER_ROOT/classes/$FileName.php";
64 53
     }
65 54
 
66 55
     require_once $FilePath;

+ 2
- 2
classes/badges.class.php View File

@@ -28,7 +28,7 @@ class Badges
28 28
             return false;
29 29
         } else {
30 30
             $QueryID = G::$DB->get_query_id();
31
-            G::$DB->query("
31
+            G::$DB->prepared_query("
32 32
             INSERT INTO `users_badges`(`UserID`, `BadgeID`)
33 33
             VALUES($UserID, $BadgeID)
34 34
             ");
@@ -126,7 +126,7 @@ class Badges
126 126
     {
127 127
         $QueryID = G::$DB->get_query_id();
128 128
 
129
-        G::$DB->query("
129
+        G::$DB->prepared_query("
130 130
         SELECT
131 131
           `ID`,
132 132
           `Icon`,

+ 1
- 1
classes/bookmarks.class.php View File

@@ -97,7 +97,7 @@ class Bookmarks
97 97
             list($Table, $Col) = self::bookmark_schema($Type);
98 98
             $QueryID = G::$DB->get_query_id();
99 99
 
100
-            G::$DB->query("
100
+            G::$DB->prepared_query("
101 101
             SELECT `$Col`
102 102
             FROM `$Table`
103 103
               WHERE UserID = '$UserID'");

+ 0
- 63
classes/charts.class.php View File

@@ -168,66 +168,3 @@ class PIE_CHART extends GOOGLE_CHARTS
168 168
         $this->URL .= "&chl=".implode('|', $Labels).'&chd=e:'.implode('', $Data);
169 169
     }
170 170
 }
171
-
172
-/*
173
-class LOG_BAR_GRAPH extends GOOGLE_CHARTS
174
-{
175
-    // todo: Finish
176
-    public function __construct($Base, $Width, $Height, $Options = [])
177
-    {
178
-        parent::__construct('lc', $Width, $Height, $Options);
179
-    }
180
-
181
-    public function color($Color)
182
-    {
183
-        $this->URL .= '&chco='.$Color.'&chm=B,'.$Color.'50,0,0,0';
184
-    }
185
-
186
-    public function generate()
187
-    {
188
-        $Max = max($this->Data);
189
-        $Min = ((isset($this->Options['Break'])) ? $Min = min($this->Data) : 0);
190
-        $Data = [];
191
-
192
-        foreach ($this->Data as $Value) {
193
-            $Data[] = $this->encode((($Value - $Min) / ($Max - $Min)) * 4095);
194
-        }
195
-        $this->URL .= "&chxt=y,x&chxs=0,h&chxl=1:|".implode('|', $this->Labels).'&chxr=0,'.$Min.','.($Max-$Min).'&chd=e:'.implode('', $Data);
196
-    }
197
-}
198
-*/
199
-
200
-/*
201
-class POLL_GRAPH extends GOOGLE_CHARTS
202
-{
203
-    public function __construct()
204
-    {
205
-        $this->URL .= '?cht=bhg';
206
-    }
207
-
208
-    public function add($Label, $Data)
209
-    {
210
-        if ($Label !== false) {
211
-            $this->Labels[] = Format::cut_string($Label, 35);
212
-        }
213
-        $this->Data[] = $Data;
214
-    }
215
-
216
-    public function generate()
217
-    {
218
-        $Count = count($this->Data);
219
-        $Height = (30 * $Count) + 20;
220
-        $Max = max($this->Data);
221
-        $Sum = array_sum($this->Data);
222
-        $Increment = ($Max / $Sum) * 25; // * 100% / 4divisions
223
-        $Data = [];
224
-        $Labels = [];
225
-
226
-        foreach ($this->Data as $Key => $Value) {
227
-            $Data[] = $this->encode(($Value / $Max) * 4095);
228
-            $Labels[] = '@t'.str_replace(array(' ', ','), array('+', '\,'), $this->Labels[$Key]).',000000,1,'.round((($Key + 1) / $Count) - (12 / $Height), 2).':0,12';
229
-        }
230
-        $this->URL .= "&chbh=25,0,5&chs=214x$Height&chl=0%|".round($Increment, 1)."%|".round($Increment * 2, 1)."%|".round($Increment * 3, 1)."%|".round($Increment * 4, 1)."%&chm=".implode('|', $Labels).'&chd=e:'.implode('', $Data);
231
-    }
232
-}
233
-*/

+ 4
- 4
classes/collages.class.php View File

@@ -6,7 +6,7 @@ class Collages
6 6
     public static function increase_subscriptions($CollageID)
7 7
     {
8 8
         $QueryID = G::$DB->get_query_id();
9
-        G::$DB->query("
9
+        G::$DB->prepared_query("
10 10
         UPDATE
11 11
           `collages`
12 12
         SET
@@ -20,7 +20,7 @@ class Collages
20 20
     public static function decrease_subscriptions($CollageID)
21 21
     {
22 22
         $QueryID = G::$DB->get_query_id();
23
-        G::$DB->query("
23
+        G::$DB->prepared_query("
24 24
         UPDATE
25 25
           `collages`
26 26
         SET
@@ -37,7 +37,7 @@ class Collages
37 37
 
38 38
     public static function create_personal_collage()
39 39
     {
40
-        G::$DB->query("
40
+        G::$DB->prepared_query("
41 41
         SELECT
42 42
           COUNT(`ID`)
43 43
         FROM
@@ -57,7 +57,7 @@ class Collages
57 57
         $NameStr = db_string(G::$LoggedUser['Username']."'s personal collage".($CollageCount > 0 ? ' no. '.($CollageCount + 1) : ''));
58 58
         $Description = db_string('Personal collage for '.G::$LoggedUser['Username'].'. The first 5 albums will appear on his or her [url='.site_url().'user.php?id= '.G::$LoggedUser['ID'].']profile[/url].');
59 59
 
60
-        G::$DB->query("
60
+        G::$DB->prepared_query("
61 61
         INSERT INTO `collages`(
62 62
           `Name`,
63 63
           `Description`,

classes/config.template.php
File diff suppressed because it is too large
View File


+ 111
- 58
classes/donations.class.php View File

@@ -1,13 +1,14 @@
1 1
 <?php
2 2
 declare(strict_types=1);
3 3
 
4
-define('BTC_API_URL', 'https://api.bitcoinaverage.com/ticker/global/EUR/');
5
-define('USD_API_URL', 'http://www.google.com/ig/calculator?hl=en&q=1USD=?EUR');
6
-
7 4
 class Donations
8 5
 {
9 6
     private static $IsSchedule = false;
10 7
 
8
+
9
+    /**
10
+     * regular_donate
11
+     */
11 12
     public static function regular_donate($UserID, $DonationAmount, $Source, $Reason, $Currency = 'USD')
12 13
     {
13 14
         self::donate(
@@ -23,6 +24,10 @@ class Donations
23 24
         );
24 25
     }
25 26
 
27
+
28
+    /**
29
+     * donate
30
+     */
26 31
     public static function donate($UserID, $Args)
27 32
     {
28 33
         $UserID = (int) $UserID;
@@ -220,6 +225,10 @@ class Donations
220 225
         G::$DB->set_query_id($QueryID);
221 226
     }
222 227
 
228
+
229
+    /**
230
+     * calculate_special_rank
231
+     */
223 232
     private static function calculate_special_rank($UserID)
224 233
     {
225 234
         $UserID = (int) $UserID;
@@ -272,6 +281,10 @@ class Donations
272 281
         G::$DB->set_query_id($QueryID);
273 282
     }
274 283
 
284
+
285
+    /**
286
+     * expire_ranks
287
+     */
275 288
     public static function expire_ranks()
276 289
     {
277 290
         $QueryID = G::$DB->get_query_id();
@@ -309,11 +322,19 @@ class Donations
309 322
         G::$DB->set_query_id($QueryID);
310 323
     }
311 324
 
325
+
326
+    /**
327
+     * calculate_rank
328
+     */
312 329
     private static function calculate_rank($Amount)
313 330
     {
314 331
         return floor($Amount / 5);
315 332
     }
316 333
 
334
+
335
+    /**
336
+     * update_rank
337
+     */
317 338
     public static function update_rank($UserID, $Rank, $TotalRank, $Reason)
318 339
     {
319 340
         $Rank = (int) $Rank;
@@ -332,6 +353,10 @@ class Donations
332 353
         );
333 354
     }
334 355
 
356
+
357
+    /**
358
+     * hide_stats
359
+     */
335 360
     public static function hide_stats($UserID)
336 361
     {
337 362
         $QueryID = G::$DB->get_query_id();
@@ -345,6 +370,10 @@ class Donations
345 370
         G::$DB->set_query_id($QueryID);
346 371
     }
347 372
 
373
+
374
+    /**
375
+     * show_stats
376
+     */
348 377
     public static function show_stats($UserID)
349 378
     {
350 379
         $QueryID = G::$DB->get_query_id();
@@ -358,6 +387,10 @@ class Donations
358 387
         G::$DB->set_query_id($QueryID);
359 388
     }
360 389
 
390
+
391
+    /**
392
+     * is_visible
393
+     */
361 394
     public static function is_visible($UserID)
362 395
     {
363 396
         $QueryID = G::$DB->get_query_id();
@@ -375,12 +408,17 @@ class Donations
375 408
         return $HasResults;
376 409
     }
377 410
 
411
+
412
+    /**
413
+     * has_donor_forum
414
+     */
378 415
     public static function has_donor_forum($UserID)
379 416
     {
380 417
         $ENV = ENV::go();
381 418
         return self::get_rank($UserID) >= $ENV->DONOR_FORUM_RANK || self::get_special_rank($UserID) >= MAX_SPECIAL_RANK;
382 419
     }
383 420
 
421
+
384 422
     /**
385 423
      * Put all the common donor info in the same cache key to save some cache calls
386 424
      */
@@ -452,26 +490,46 @@ class Donations
452 490
         return $DonorInfo;
453 491
     }
454 492
 
493
+
494
+    /**
495
+     * get_rank
496
+     */
455 497
     public static function get_rank($UserID)
456 498
     {
457 499
         return self::get_donor_info($UserID)['Rank'];
458 500
     }
459 501
 
502
+
503
+    /**
504
+     * get_special_rank
505
+     */
460 506
     public static function get_special_rank($UserID)
461 507
     {
462 508
         return self::get_donor_info($UserID)['SRank'];
463 509
     }
464 510
 
511
+
512
+    /**
513
+     * get_total_rank
514
+     */
465 515
     public static function get_total_rank($UserID)
466 516
     {
467 517
         return self::get_donor_info($UserID)['TotRank'];
468 518
     }
469 519
 
520
+
521
+    /**
522
+     * get_donation_time
523
+     */
470 524
     public static function get_donation_time($UserID)
471 525
     {
472 526
         return self::get_donor_info($UserID)['Time'];
473 527
     }
474 528
 
529
+
530
+    /**
531
+     * get_personal_collages
532
+     */
475 533
     public static function get_personal_collages($UserID)
476 534
     {
477 535
         $DonorInfo = self::get_donor_info($UserID);
@@ -483,30 +541,10 @@ class Donations
483 541
         return $Collages;
484 542
     }
485 543
 
486
-    public static function get_titles($UserID)
487
-    {
488
-        $Results = G::$Cache->get_value("donor_title_$UserID");
489
-        if ($Results === false) {
490
-            $QueryID = G::$DB->get_query_id();
491
-
492
-            G::$DB->query("
493
-            SELECT
494
-              `Prefix`,
495
-              `Suffix`,
496
-              `UseComma`
497
-            FROM
498
-              `donor_forum_usernames`
499
-            WHERE
500
-              `UserID` = '$UserID'
501
-            ");
502
-
503
-            $Results = G::$DB->next_record();
504
-            G::$DB->set_query_id($QueryID);
505
-            G::$Cache->cache_value("donor_title_$UserID", $Results, 0);
506
-        }
507
-        return $Results;
508
-    }
509 544
 
545
+    /**
546
+     * get_enabled_rewards
547
+     */
510 548
     public static function get_enabled_rewards($UserID)
511 549
     {
512 550
         $Rewards = [];
@@ -553,11 +591,19 @@ class Donations
553 591
         return $Rewards;
554 592
     }
555 593
 
594
+
595
+    /**
596
+     * get_rewards
597
+     */
556 598
     public static function get_rewards($UserID)
557 599
     {
558 600
         return self::get_donor_info($UserID)['Rewards'];
559 601
     }
560 602
 
603
+
604
+    /**
605
+     * get_profile_rewards
606
+     */
561 607
     public static function get_profile_rewards($UserID)
562 608
     {
563 609
         $Results = G::$Cache->get_value("donor_profile_rewards_$UserID");
@@ -587,6 +633,10 @@ class Donations
587 633
         return $Results;
588 634
     }
589 635
 
636
+
637
+    /**
638
+     * add_profile_info_reward
639
+     */
590 640
     private static function add_profile_info_reward($Counter, &$Insert, &$Values, &$Update)
591 641
     {
592 642
         if (isset($_POST["profile_title_" . $Counter]) && isset($_POST["profile_info_" . $Counter])) {
@@ -603,6 +653,10 @@ class Donations
603 653
         }
604 654
     }
605 655
 
656
+
657
+    /**
658
+     * update_rewards
659
+     */
606 660
     public static function update_rewards($UserID)
607 661
     {
608 662
         $Rank = self::get_rank($UserID);
@@ -663,7 +717,6 @@ class Donations
663 717
                 $Values[] = "'$CustomIcon'";
664 718
                 $Update[] = "CustomIcon = '$CustomIcon'";
665 719
             }
666
-            self::update_titles($UserID, $_POST['donor_title_prefix'], $_POST['donor_title_suffix'], $_POST['donor_title_comma']);
667 720
             $Counter++;
668 721
         }
669 722
 
@@ -705,37 +758,10 @@ class Donations
705 758
         G::$Cache->delete_value("donor_info_$UserID");
706 759
     }
707 760
 
708
-    public static function update_titles($UserID, $Prefix, $Suffix, $UseComma)
709
-    {
710
-        $QueryID = G::$DB->get_query_id();
711
-        $Prefix = trim(db_string($Prefix));
712
-        $Suffix = trim(db_string($Suffix));
713
-        $UseComma = empty($UseComma);
714
-
715
-        G::$DB->query("
716
-        INSERT INTO `donor_forum_usernames`(
717
-          `UserID`,
718
-          `Prefix`,
719
-          `Suffix`,
720
-          `UseComma`
721
-        )
722
-        VALUES(
723
-          '$UserID',
724
-          '$Prefix',
725
-          '$Suffix',
726
-          '$UseComma'
727
-        )
728
-        ON DUPLICATE KEY
729
-        UPDATE
730
-          `Prefix` = '$Prefix',
731
-          `Suffix` = '$Suffix',
732
-          `UseComma` = '$UseComma'
733
-        ");
734
-
735
-        G::$Cache->delete_value("donor_title_$UserID");
736
-        G::$DB->set_query_id($QueryID);
737
-    }
738 761
 
762
+    /**
763
+     * get_donation_history
764
+     */
739 765
     public static function get_donation_history($UserID)
740 766
     {
741 767
         $UserID = (int) $UserID;
@@ -743,7 +769,6 @@ class Donations
743 769
             error(404);
744 770
         }
745 771
 
746
-        # todo: Investigate Rank in donations table
747 772
         $QueryID = G::$DB->get_query_id();
748 773
         G::$DB->query("
749 774
         SELECT
@@ -770,6 +795,10 @@ class Donations
770 795
         return $DonationHistory;
771 796
     }
772 797
 
798
+
799
+    /**
800
+     * get_rank_expiration
801
+     */
773 802
     public static function get_rank_expiration($UserID)
774 803
     {
775 804
         $DonorInfo = self::get_donor_info($UserID);
@@ -789,6 +818,10 @@ class Donations
789 818
         return $Return;
790 819
     }
791 820
 
821
+
822
+    /**
823
+     * get_leaderboard_position
824
+     */
792 825
     public static function get_leaderboard_position($UserID)
793 826
     {
794 827
         $UserID = (int) $UserID;
@@ -823,11 +856,19 @@ class Donations
823 856
         return $Position;
824 857
     }
825 858
 
859
+
860
+    /**
861
+     * is_donor
862
+     */
826 863
     public static function is_donor($UserID)
827 864
     {
828 865
         return self::get_rank($UserID) > 0;
829 866
     }
830 867
 
868
+
869
+    /**
870
+     * currency_exchange
871
+     */
831 872
     public static function currency_exchange($Amount, $Currency)
832 873
     {
833 874
         if (!self::is_valid_currency($Currency)) {
@@ -849,11 +890,19 @@ class Donations
849 890
         return round($Amount, 2);
850 891
     }
851 892
 
893
+
894
+    /**
895
+     * is_valid_currency
896
+     */
852 897
     public static function is_valid_currency($Currency)
853 898
     {
854 899
         return $Currency === 'EUR' || $Currency === 'BTC' || $Currency === 'USD';
855 900
     }
856 901
 
902
+
903
+    /**
904
+     * btc_to_euro
905
+     */
857 906
     public static function btc_to_euro($Amount)
858 907
     {
859 908
         $Rate = G::$Cache->get_value('btc_rate');
@@ -863,6 +912,10 @@ class Donations
863 912
         return $Rate * $Amount;
864 913
     }
865 914
 
915
+
916
+    /**
917
+     * usd_to_euro
918
+     */
866 919
     public static function usd_to_euro($Amount)
867 920
     {
868 921
         $Rate = G::$Cache->get_value('usd_rate');

+ 2
- 2
classes/donationsview.class.php View File

@@ -6,7 +6,7 @@ class DonationsView
6 6
     public static function render_mod_donations($UserID)
7 7
     {
8 8
         ?>
9
-<table class="layout box" id="donation_box">
9
+<table class="box skeleton-fix" id="donation_box">
10 10
   <tr class="colhead">
11 11
     <td colspan="2">
12 12
       Donor System (add points)
@@ -34,7 +34,7 @@ class DonationsView
34 34
   </tr>
35 35
 </table>
36 36
 
37
-<table class="layout box" id="donor_points_box">
37
+<table class="box skeleton-fix" id="donor_points_box">
38 38
   <tr class="colhead">
39 39
     <td colspan="3" class="tooltip"
40 40
       title='Use this tool only when manually correcting values. If crediting donations normally, use the "Donor System (add points)" tool'>

+ 80
- 29
classes/env.class.php View File

@@ -29,7 +29,7 @@ class ENV
29 29
 
30 30
 
31 31
     /**
32
-     * __functions()
32
+     * __functions
33 33
      */
34 34
 
35 35
     # Prevents outside construction
@@ -43,10 +43,13 @@ class ENV
43 43
     # Prevents multiple instances
44 44
     public function __clone()
45 45
     {
46
-        return trigger_error(
47
-            'clone() not allowed',
48
-            E_USER_ERROR
49
-        );
46
+        return error('clone() not allowed.');
47
+    }
48
+
49
+    # Prevents unserializing
50
+    public function __wakeup()
51
+    {
52
+        return error('wakeup() not allowed.');
50 53
     }
51 54
 
52 55
     # $this->key returns public->key
@@ -103,8 +106,39 @@ class ENV
103 106
     }
104 107
 
105 108
 
109
+    /**
110
+     * convert
111
+     *
112
+     * Take a mixed input and returns a RecursiveArrayObject.
113
+     * This function is the sausage grinder, so to speak.
114
+     */
115
+    public function convert($obj)
116
+    {
117
+        switch (gettype($obj)) {
118
+            case 'string':
119
+                $out = json_decode($obj, true);
120
+                return (json_last_error() === JSON_ERROR_NONE)
121
+                    ? new RecursiveArrayObject($out)
122
+                    : error('json_last_error_msg(): ' . json_last_error_msg());
123
+                break;
124
+            
125
+            case 'array':
126
+            case 'object':
127
+                return new RecursiveArrayObject($obj);
128
+            
129
+            default:
130
+                return error('$ENV->convert() expects a JSON string, array, or object.');
131
+                break;
132
+        }
133
+    }
134
+
135
+
106 136
     /**
107 137
      * toArray
138
+     *
139
+     * Takes an object and returns an array.
140
+     * @param object|string $obj Thing to turn into an array
141
+     * @return $new New recursive array with $obj contents
108 142
      * @see https://ben.lobaugh.net/blog/567/php-recursively-convert-an-object-to-an-array
109 143
      */
110 144
     public function toArray($obj)
@@ -127,41 +161,54 @@ class ENV
127 161
 
128 162
 
129 163
     /**
130
-     * fromJson
164
+     * dedupe
131 165
      *
132
-     * @param string $JSON Valid JavaScript object string
133
-     * @return RecursiveArrayObject Not stdClass as in json_decode()
166
+     * Takes a collection (usually an array) of various jumbled $ENV slices.
167
+     * Returns a once-deduplicated RecursiveArrayObject with original nesting intact.
168
+     * Simple and handy if you need to populate a form with arbitrary collections of metadata.
134 169
      */
135
-
136
-    public function fromJson($str)
170
+    public function dedupe($obj)
137 171
     {
138
-        if (!is_string($str) || is_empty($str)) {
139
-            error('$ENV->fromJson() expects a string.');
172
+        if (is_object($obj)) {
173
+            $obj = (array) $obj;
140 174
         }
141 175
 
142
-        # Decode to array and construct RAO
143
-        return $RAO = new RecursiveArrayObject(
144
-            json_decode($str, true)
176
+        return new RecursiveArrayObject(
177
+            array_unique($this->toArray($obj))
145 178
         );
146 179
     }
147 180
 
148 181
 
149 182
     /**
150
-     * dedupe
183
+     * flatten
151 184
      *
152
-     * Takes a collection (usually an array) of various jumbled $ENV slices.
153
-     * Returns a once-deduplicated RecursiveArrayObject with original nesting intact.
154
-     * Simple and handy if you need to populate a form with arbitrary collections of metadata.
185
+     * Takes an $ENV node or array of them
186
+     * and flattens out the multi-dimensionality.
187
+     * It returns a flat array with keys intact.
155 188
      */
156
-    public function dedupe($obj)
189
+    public function flatten($arr, int $lvl = null)
157 190
     {
158
-        if (is_object($obj)) {
159
-            $obj = (array) $obj;
191
+        if (!is_array($arr) && !is_object($arr)) {
192
+            return error('$ENV->flatten() expects an array or object, got ' . gettype($arr));
160 193
         }
161 194
 
162
-        return $RAO = new RecursiveArrayObject(
163
-            array_unique($this->toArray($obj))
164
-        );
195
+        $new = array();
196
+
197
+        foreach ($arr as $k => $v) {
198
+            /*
199
+             if (is_object($v)) {
200
+                $v = $this->toArray($v);
201
+            }
202
+            */
203
+    
204
+            if (is_array($v)) {
205
+                $new = array_merge($new, $this->flatten($v));
206
+            } else {
207
+                $new[$k] = $v;
208
+            }
209
+        }
210
+
211
+        return $new;
165 212
     }
166 213
 
167 214
 
@@ -172,7 +219,7 @@ class ENV
172 219
      * Maps a callback (or default) to an object.
173 220
      *
174 221
      * Example output:
175
-     * $Hashes = $ENV->map('md5', $ENV->CATS->SEQ);
222
+     * $Hashes = $ENV->map('md5', $ENV->CATS->{6});
176 223
      *
177 224
      * var_dump($Hashes);
178 225
      * object(RecursiveArrayObject)#324 (1) {
@@ -197,10 +244,10 @@ class ENV
197 244
      * string(32) "52963afccc006d2bce3c890ad9e8f73a"
198 245
      *
199 246
      * @param string $fn Callback function
200
-     * @param object $obj Object to operate on
247
+     * @param object|string $obj Object or property to operate on
201 248
      * @return object $RAO Mapped RecursiveArrayObject
202 249
      */
203
-    public function map($fn = '', $obj = null)
250
+    public function map(string $fn = '', $obj = null)
204 251
     {
205 252
         # Set a default function if desired
206 253
         if (empty($fn) && !is_object($fn)) {
@@ -227,7 +274,7 @@ class ENV
227 274
 
228 275
         # Map the sanitized function name
229 276
         # to a mapped array conversion
230
-        return $RAO = new RecursiveArrayObject(
277
+        return new RecursiveArrayObject(
231 278
             array_map(
232 279
                 $fn,
233 280
                 array_map(
@@ -259,6 +306,7 @@ class RecursiveArrayObject extends \ArrayObject
259 306
         return $this;
260 307
     }
261 308
 
309
+
262 310
     /**
263 311
      * __set
264 312
      */
@@ -271,6 +319,7 @@ class RecursiveArrayObject extends \ArrayObject
271 319
         }
272 320
     }
273 321
 
322
+
274 323
     /**
275 324
      * __get
276 325
      */
@@ -285,6 +334,7 @@ class RecursiveArrayObject extends \ArrayObject
285 334
         }
286 335
     }
287 336
 
337
+
288 338
     /**
289 339
      * __isset
290 340
      */
@@ -293,6 +343,7 @@ class RecursiveArrayObject extends \ArrayObject
293 343
         return array_key_exists($name, $this);
294 344
     }
295 345
 
346
+    
296 347
     /**
297 348
      * __unset
298 349
      */

+ 52
- 2
classes/json.class.php View File

@@ -4,8 +4,6 @@ declare(strict_types = 1);
4 4
 /**
5 5
  * Adapted from
6 6
  * https://github.com/OPSnet/Gazelle/blob/master/app/Json.php
7
- *
8
- * Unused as of 2020-12-12
9 7
  */
10 8
 
11 9
 abstract class Json
@@ -14,6 +12,7 @@ abstract class Json
14 12
     protected $source;
15 13
     protected $mode;
16 14
 
15
+
17 16
     /**
18 17
      * __construct
19 18
      */
@@ -25,6 +24,7 @@ abstract class Json
25 24
         $this->version = 1;
26 25
     }
27 26
 
27
+
28 28
     /**
29 29
      * The payload of a valid JSON response, implemented in the child class.
30 30
      * @return array Payload to be passed to json_encode()
@@ -32,6 +32,7 @@ abstract class Json
32 32
      */
33 33
     abstract public function payload(): ?array;
34 34
 
35
+
35 36
     /**
36 37
      * Configure JSON printing (any of the json_encode  JSON_* constants)
37 38
      *
@@ -43,6 +44,7 @@ abstract class Json
43 44
         return $this;
44 45
     }
45 46
 
47
+
46 48
     /**
47 49
      * set the version of the Json payload. Increment the
48 50
      * value when there is significant change in the payload.
@@ -56,6 +58,7 @@ abstract class Json
56 58
         return $this;
57 59
     }
58 60
 
61
+
59 62
     /**
60 63
      * General failure routine for when bad things happen.
61 64
      *
@@ -77,6 +80,7 @@ abstract class Json
77 80
         );
78 81
     }
79 82
 
83
+
80 84
     /**
81 85
      * emit
82 86
      */
@@ -99,6 +103,7 @@ abstract class Json
99 103
         );
100 104
     }
101 105
 
106
+
102 107
     /**
103 108
      * debug
104 109
      */
@@ -116,6 +121,7 @@ abstract class Json
116 121
         ];
117 122
     }
118 123
 
124
+
119 125
     /**
120 126
      * info
121 127
      */
@@ -128,4 +134,48 @@ abstract class Json
128 134
             ]
129 135
         ];
130 136
     }
137
+
138
+
139
+    /**
140
+     * fetch
141
+     *
142
+     * Get resources over the API to populate Gazelle display.
143
+     * Instead of copy-pasting the same SQL queries in many places.
144
+     *
145
+     * Takes a query string, e.g., "action=torrentgroup&id=1."
146
+     * Requires an API key for the user ID 0 (minor database surgery).
147
+     */
148
+    public function fetch($Action, $Params = [])
149
+    {
150
+        $ENV = ENV::go();
151
+
152
+        $Token = $ENV->getPriv('SELF_API');
153
+        $Params = implode('&', $Params);
154
+
155
+        $ch = curl_init();
156
+
157
+        # todo: Make this use localhost and not HTTPS
158
+        curl_setopt($ch, CURLOPT_URL, "https://$ENV->SITE_DOMAIN/api.php?action=$Action&$Params");
159
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
160
+
161
+        # https://docs.biotorrents.de
162
+        curl_setopt(
163
+            $ch,
164
+            CURLOPT_HTTPHEADER,
165
+            [
166
+                'Accept: application/json',
167
+                "Authorization: Bearer $Token",
168
+            ]
169
+        );
170
+
171
+        $Data = curl_exec($ch);
172
+        curl_close($ch);
173
+
174
+        # Error out on bad query
175
+        if (!$Data) {
176
+            return error();
177
+        } else {
178
+            return json_decode($Data);
179
+        }
180
+    }
131 181
 }

+ 0
- 38
classes/loginwatch.class.php View File

@@ -68,44 +68,6 @@ class LoginWatch
68 68
         return ($this->watchId = G::$DB->inserted_id());
69 69
     }
70 70
 
71
-    /**
72
-     * Record another failure attempt on this watch. If the user has not
73
-     * logged in recently from this IP address then subsequent logins
74
-     * will be blocked for increasingly longer times, otherwise 1 minute.
75
-     *
76
-     * @param int $userId The ID of the user
77
-     * @param string $ipaddr The IP the user is coming from
78
-     * @param string $capture The username captured on the form
79
-     * @return int 1 if the watch was updated
80
-     */
81
-    public function increment(int $userId, string $ipaddr, ?string $capture): int
82
-    {
83
-        $seen = G::$DB->query("
84
-        SELECT
85
-          1
86
-        FROM
87
-          `users_history_ips`
88
-        WHERE
89
-          (
90
-            `EndTime` IS NULL
91
-            OR `EndTime` > NOW() - INTERVAL 1 WEEK
92
-          )
93
-          AND `UserID` = '$userId'
94
-          AND `IP` = '$ipaddr'
95
-        ");
96
-
97
-        $delay = $seen ? 60 : LOGIN_ATTEMPT_BACKOFF[min($this->nrAttempts(), count(LOGIN_ATTEMPT_BACKOFF)-1)];
98
-        G::$DB->prepare_query("
99
-            UPDATE `login_attempts` SET
100
-                `Attempts` = `Attempts` + 1,
101
-                `LastAttempt` = now(),
102
-                `BannedUntil` = now() + INTERVAL '$delay' SECOND,
103
-                `UserID` = '$userId',
104
-                `Capture` ='$capture' 
105
-            WHERE `ID` = '$this->watchId' 
106
-            ");
107
-        return G::$DB->affected_rows();
108
-    }
109 71
 
110 72
     /**
111 73
      * Ban subsequent attempts to login from this watched IP address for 6 hours

+ 42
- 14
classes/mysql.class.php View File

@@ -22,7 +22,7 @@ $DB = NEW DB_MYSQL;
22 22
 
23 23
 * Making a query
24 24
 
25
-$DB->query("
25
+$DB->prepared_query("
26 26
   SELECT *
27 27
   FROM table...");
28 28
 
@@ -92,14 +92,14 @@ set_query_id($ResultSet)
92 92
   This class can only hold one result set at a time. Using set_query_id allows
93 93
   you to set the result set that the class is using to the result set in
94 94
   $ResultSet. This result set should have been obtained earlier by using
95
-  $DB->query().
95
+  $DB->prepared_query().
96 96
 
97 97
   Example:
98 98
 
99
-  $FoodRS = $DB->query("
99
+  $FoodRS = $DB->prepared_query("
100 100
       SELECT *
101 101
       FROM food");
102
-  $DB->query("
102
+  $DB->prepared_query("
103 103
     SELECT *
104 104
     FROM drink");
105 105
   $Drinks = $DB->next_record();
@@ -111,14 +111,11 @@ set_query_id($ResultSet)
111 111
 -------------------------------------------------------------------------------------
112 112
 *///---------------------------------------------------------------------------------
113 113
 
114
-if (!extension_loaded('mysqli')) {
115
-    error('Mysqli Extension not loaded.');
116
-}
117
-
118 114
 
119 115
 /**
120 116
  * db_string
121
- * Handles escaping
117
+ *
118
+ * Handles escaping.
122 119
  */
123 120
 function db_string($String, $DisableWildcards = false)
124 121
 {
@@ -269,6 +266,7 @@ class DB_MYSQL
269 266
                 $this->Database,
270 267
                 $this->Port,
271 268
                 $this->Socket,
269
+                # Needed for self-signed certs
272 270
                 MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT
273 271
             );
274 272
 
@@ -283,13 +281,31 @@ class DB_MYSQL
283 281
 
284 282
 
285 283
     /**
286
-     * prepare_query
284
+     * Prepare and execute a prepared query returning the result set.
285
+     *
286
+     * Utility function that wraps DB_MYSQL::prepare and DB_MYSQL::execute
287
+     * as most times, the query is going to be one-off and this will save
288
+     * on keystrokes. If you do plan to be executing a prepared query
289
+     * multiple times with different bound parameters, you'll want to call
290
+     * the two functions separately instead of this function.
291
+     *
292
+     * @param $Query
293
+     * @param mixed ...$Parameters
294
+     * @return bool|mysqli_result
287 295
      */
288
-    public function prepare_query($Query, &...$BindVars)
296
+    public function prepared_query($Query, ...$Parameters) {
297
+        $this->prepare($Query);
298
+        return $this->execute(...$Parameters);
299
+    }
300
+
301
+    /**
302
+     * prepare
303
+     */
304
+    public function prepare($Query, &...$BindVars)
289 305
     {
290 306
         $this->connect();
291
-
292 307
         $this->StatementID = mysqli_prepare($this->LinkID, $Query);
308
+
293 309
         if (!empty($BindVars)) {
294 310
             $Types = '';
295 311
             $TypeMap = ['string'=>'s', 'double'=>'d', 'integer'=>'i', 'boolean'=>'i'];
@@ -304,11 +320,17 @@ class DB_MYSQL
304 320
         return $this->StatementID;
305 321
     }
306 322
 
323
+    # Compatibility function for the old name
324
+    public function prepare_query($Query, &...$BindVars)
325
+    {
326
+        return $this->prepare($Query, $BindVars);
327
+    }
328
+
307 329
 
308 330
     /**
309
-     * exec_prepared_query
331
+     * execute
310 332
      */
311
-    public function exec_prepared_query()
333
+    public function execute()
312 334
     {
313 335
         $QueryStartTime = microtime(true);
314 336
         mysqli_stmt_execute($this->StatementID);
@@ -318,6 +340,12 @@ class DB_MYSQL
318 340
         $this->Time += $QueryRunTime;
319 341
     }
320 342
 
343
+    # Compatibility function for the old name
344
+    public function exec_prepared_query()
345
+    {
346
+        return $this->execute();
347
+    }
348
+
321 349
 
322 350
     /**
323 351
      * Runs a raw query assuming pre-sanitized input. However, attempting to self sanitize (such

+ 83
- 66
classes/permissions_form.php View File

@@ -1,12 +1,13 @@
1 1
 <?php
2 2
 declare(strict_types=1);
3 3
 
4
-/********************************************************************************
5
- ************ Permissions form ********************** user.php and tools.php ****
6
- ********************************************************************************
7
- ** This function is used to create both the class permissions form, and the   **
8
- ** user custom permissions form.                                              **
9
- ********************************************************************************/
4
+/**
5
+ * Permissions form
6
+ * user.php and tools.php
7
+ *
8
+ * This function is used to create both the class permissions form,
9
+ * and the user custom permissions form.
10
+ */
10 11
 
11 12
 $PermissionsArray = array(
12 13
   'site_leech' => 'Can leech (Does this work?).',
@@ -38,7 +39,6 @@ $PermissionsArray = array(
38 39
   'site_recommend_own' => 'Can recommend own torrents.',
39 40
   'site_manage_recommendations' => 'Recommendations management access.',
40 41
   'site_delete_tag' => 'Can delete tags.',
41
-  'site_disable_ip_history' => 'Disable IP history.',
42 42
   'zip_downloader' => 'Download multiple torrents at once.',
43 43
   'site_debug' => 'Developer access.',
44 44
   'site_proxy_images' => 'Image proxy & anti-canary.',
@@ -112,16 +112,17 @@ $PermissionsArray = array(
112 112
 
113 113
 function permissions_form()
114 114
 {
115
-    ?>
116
-<div class="permissions">
117
-  <div class="permission_container">
118
-    <table>
119
-      <tr class="colhead">
120
-        <td>Site</td>
121
-      </tr>
122
-      <tr>
123
-        <td>
124
-          <?php
115
+    echo <<<HTML
116
+    <div class="permission_container">
117
+      <table>
118
+        <tr class="colhead">
119
+          <th>Site</th>
120
+        </tr>
121
+        
122
+        <tr>
123
+          <td>
124
+HTML;
125
+
125 126
     display_perm('site_leech', 'Can leech.');
126 127
     display_perm('site_upload', 'Can upload.');
127 128
     display_perm('site_vote', 'Can vote on requests.');
@@ -152,7 +153,6 @@ function permissions_form()
152 153
     display_perm('site_recommend_own', 'Can add own torrents to recommendations list.');
153 154
     display_perm('site_manage_recommendations', 'Can edit recommendations list.');
154 155
     display_perm('site_delete_tag', 'Can delete tags.');
155
-    display_perm('site_disable_ip_history', 'Disable IP history.');
156 156
     display_perm('zip_downloader', 'Download multiple torrents at once.');
157 157
     display_perm('site_debug', 'View site debug tables.');
158 158
     display_perm('site_proxy_images', 'Proxy images through the server.');
@@ -161,20 +161,24 @@ function permissions_form()
161 161
     display_perm('site_forums_double_post', 'Can double post in the forums.');
162 162
     display_perm('project_team', 'Part of the project team.');
163 163
     display_perm('site_tag_aliases_read', 'Can view the list of tag aliases.');
164
-    display_perm('site_ratio_watch_immunity', 'Immune from being put on ratio watch.'); ?>
165
-        </td>
166
-      </tr>
167
-    </table>
168
-  </div>
164
+    display_perm('site_ratio_watch_immunity', 'Immune from being put on ratio watch.');
165
+
166
+    echo <<<HTML
167
+          </td>
168
+        </tr>
169
+      </table>
170
+    </div>
171
+    
172
+    <div class="permission_container">
173
+      <table>
174
+        <tr class="colhead">
175
+          <th>Users</th>
176
+        </tr>
177
+        
178
+        <tr>
179
+          <td>
180
+HTML;
169 181
 
170
-  <div class="permission_container">
171
-    <table>
172
-      <tr class="colhead">
173
-        <td>Users</td>
174
-      </tr>
175
-      <tr>
176
-        <td>
177
-          <?php
178 182
     display_perm('users_edit_usernames', 'Can edit usernames.');
179 183
     display_perm('users_edit_ratio', 'Can edit anyone\'s upload/download amounts.');
180 184
     display_perm('users_edit_own_ratio', 'Can edit own upload/download amounts.');
@@ -206,21 +210,27 @@ function permissions_form()
206 210
     display_perm('users_override_paranoia', 'Can override paranoia');
207 211
     display_perm('users_make_invisible', 'Can make users invisible');
208 212
     display_perm('users_logout', 'Can log users out');
209
-    display_perm('users_mod', 'Can access basic moderator tools (Admin comment)'); ?>
210
-          * Everything is only applicable to users with the same or lower class level
211
-        </td>
212
-      </tr>
213
-    </table>
214
-  </div>
213
+    display_perm('users_mod', 'Can access basic moderator tools (Admin comment)');
214
+
215
+    echo <<<HTML
216
+            <strong class="important_text">
217
+              Everything is only applicable to users with the same or lower class level
218
+            </strong>
219
+          </td>
220
+        </tr>
221
+      </table>
222
+    </div>
223
+    
224
+    <div class="permission_container">
225
+      <table>
226
+        <tr class="colhead">
227
+          <th>Torrents</th>
228
+        </tr>
229
+        
230
+        <tr>
231
+          <td>
232
+HTML;
215 233
 
216
-  <div class="permission_container">
217
-    <table>
218
-      <tr class="colhead">
219
-        <td>Torrents</td>
220
-      </tr>
221
-      <tr>
222
-        <td>
223
-          <?php
224 234
     display_perm('torrents_edit', 'Can edit any torrent');
225 235
     display_perm('torrents_delete', 'Can delete torrents');
226 236
     display_perm('torrents_delete_fast', 'Can delete more than 3 torrents at a time.');
@@ -232,20 +242,24 @@ function permissions_form()
232 242
     display_perm('artist_edit_vanityhouse', 'Can mark artists as part of Vanity House.');
233 243
     display_perm('torrents_fix_ghosts', 'Can fix ghost groups on artist pages.');
234 244
     display_perm('screenshots_add', 'Can add screenshots to any torrent and delete their own screenshots.');
235
-    display_perm('screenshots_delete', 'Can delete any screenshot from any torrent.'); ?>
236
-        </td>
237
-      </tr>
238
-    </table>
239
-  </div>
245
+    display_perm('screenshots_delete', 'Can delete any screenshot from any torrent.');
246
+
247
+    echo <<<HTML
248
+          </td>
249
+        </tr>
250
+      </table>
251
+    </div>
252
+    
253
+    <div class="permission_container">
254
+      <table>
255
+        <tr class="colhead">
256
+          <th>Administrative</th>
257
+        </tr>
258
+        
259
+        <tr>
260
+          <td>
261
+HTML;
240 262
 
241
-  <div class="permission_container">
242
-    <table>
243
-      <tr class="colhead">
244
-        <td>Administrative</td>
245
-      </tr>
246
-      <tr>
247
-        <td>
248
-          <?php
249 263
     display_perm('admin_manage_news', 'Can manage site news');
250 264
     display_perm('admin_manage_blog', 'Can manage the site blog');
251 265
     display_perm('admin_manage_polls', 'Can manage polls');
@@ -261,13 +275,16 @@ function permissions_form()
261 275
     display_perm('admin_manage_permissions', 'Can edit permission classes/user permissions.');
262 276
     display_perm('admin_schedule', 'Can run the site schedule.');
263 277
     display_perm('admin_login_watch', 'Can manage login watch.');
264
-    display_perm('admin_manage_wiki', 'Can manage wiki access.'); ?>
265
-        </td>
266
-      </tr>
267
-    </table>
268
-  </div>
278
+    display_perm('admin_manage_wiki', 'Can manage wiki access.');
269 279
 
270
-  <div class="submit_container"><input type="submit" name="submit" value="Save Permission Class" /></div>
271
-</div>
272
-<?php
280
+    echo <<<HTML
281
+          </td>
282
+        </tr>
283
+      </table>
284
+    </div>
285
+    
286
+    <div class="submit_container">
287
+      <input type="submit" name="submit" class ="button-primary" value="Save Permission Class" />
288
+    </div>
289
+HTML;
273 290
 }

+ 19
- 60
classes/script_start.php View File

@@ -1,11 +1,10 @@
1 1
 <?php
2 2
 #declare(strict_types=1);
3 3
 
4
-# https://www.php.net/manual/en/language.oop5.autoload.php
4
+# Initialize
5 5
 require_once 'config.php';
6 6
 require_once 'security.class.php';
7 7
 
8
-# Initialize
9 8
 $ENV = ENV::go();
10 9
 $Security = new Security();
11 10
 $Security->SetupPitfalls();
@@ -57,12 +56,12 @@ if (!defined('PHP_WINDOWS_VERSION_MAJOR')) {
57 56
 }
58 57
 ob_start(); // Start a buffer, mainly in case there is a mysql error
59 58
 
60
-require SERVER_ROOT.'/classes/debug.class.php'; // Require the debug class
61
-require SERVER_ROOT.'/classes/mysql.class.php'; // Require the database wrapper
62
-require SERVER_ROOT.'/classes/cache.class.php'; // Require the caching class
63
-require SERVER_ROOT.'/classes/time.class.php'; // Require the time class
64
-require SERVER_ROOT.'/classes/paranoia.class.php'; // Require the paranoia check_paranoia function
65
-require SERVER_ROOT.'/classes/util.php';
59
+require_once SERVER_ROOT.'/classes/debug.class.php'; // Require the debug class
60
+require_once SERVER_ROOT.'/classes/mysql.class.php'; // Require the database wrapper
61
+require_once SERVER_ROOT.'/classes/cache.class.php'; // Require the caching class
62
+require_once SERVER_ROOT.'/classes/time.class.php'; // Require the time class
63
+require_once SERVER_ROOT.'/classes/paranoia.class.php'; // Require the paranoia check_paranoia function
64
+require_once SERVER_ROOT.'/classes/util.php';
66 65
 
67 66
 $Debug = new DEBUG;
68 67
 $Debug->handle_errors();
@@ -72,7 +71,8 @@ $DB = new DB_MYSQL;
72 71
 $Cache = new Cache($ENV->getPriv('MEMCACHED_SERVERS'));
73 72
 
74 73
 // Autoload classes.
75
-require SERVER_ROOT.'/classes/autoload.php';
74
+require_once SERVER_ROOT.'/vendor/autoload.php';
75
+#require_once SERVER_ROOT.'/classes/autoload.php';
76 76
 
77 77
 // Note: G::initialize is called twice.
78 78
 // This is necessary as the code inbetween (initialization of $LoggedUser) makes use of G::$DB and G::$Cache.
@@ -225,7 +225,7 @@ if (isset($_COOKIE['session']) && isset($_COOKIE['userid'])) {
225 225
 
226 226
     $UserSessions = $Cache->get_value("users_sessions_$UserID");
227 227
     if (!is_array($UserSessions)) {
228
-        $DB->query(
228
+        $DB->prepared_query(
229 229
             "
230 230
         SELECT
231 231
           SessionID,
@@ -250,7 +250,7 @@ if (isset($_COOKIE['session']) && isset($_COOKIE['userid'])) {
250 250
     // Check if user is enabled
251 251
     $Enabled = $Cache->get_value('enabled_'.$LoggedUser['ID']);
252 252
     if ($Enabled === false) {
253
-        $DB->query("
253
+        $DB->prepared_query("
254 254
         SELECT Enabled
255 255
           FROM users_main
256 256
           WHERE ID = '$LoggedUser[ID]'");
@@ -267,7 +267,7 @@ if (isset($_COOKIE['session']) && isset($_COOKIE['userid'])) {
267 267
     // Up/Down stats
268 268
     $UserStats = $Cache->get_value('user_stats_'.$LoggedUser['ID']);
269 269
     if (!is_array($UserStats)) {
270
-        $DB->query("
270
+        $DB->prepared_query("
271 271
         SELECT Uploaded AS BytesUploaded, Downloaded AS BytesDownloaded, RequiredRatio
272 272
         FROM users_main
273 273
           WHERE ID = '$LoggedUser[ID]'");
@@ -319,14 +319,9 @@ if (isset($_COOKIE['session']) && isset($_COOKIE['userid'])) {
319 319
     // Change necessary triggers in external components
320 320
     $Cache->CanClear = check_perms('admin_clear_cache');
321 321
 
322
-    // Because we <3 our staff
323
-    if (check_perms('site_disable_ip_history')) {
324
-        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
325
-    }
326
-
327 322
     // Update LastUpdate every 10 minutes
328 323
     if (strtotime($UserSessions[$SessionID]['LastUpdate']) + 600 < time()) {
329
-        $DB->query("
324
+        $DB->prepared_query("
330 325
         UPDATE users_main
331 326
         SET LastAccess = NOW()
332 327
         WHERE ID = '$LoggedUser[ID]'
@@ -348,7 +343,7 @@ if (isset($_COOKIE['session']) && isset($_COOKIE['userid'])) {
348 343
         WHERE UserID = '$LoggedUser[ID]'
349 344
         AND SessionID = '".db_string($SessionID)."'";
350 345
 
351
-        $DB->query($SessionQuery);
346
+        $DB->prepared_query($SessionQuery);
352 347
         $Cache->begin_transaction("users_sessions_$UserID");
353 348
         $Cache->delete_row($SessionID);
354 349
 
@@ -367,7 +362,7 @@ if (isset($_COOKIE['session']) && isset($_COOKIE['userid'])) {
367 362
     if (isset($LoggedUser['Permissions']['site_torrents_notify'])) {
368 363
         $LoggedUser['Notify'] = $Cache->get_value('notify_filters_'.$LoggedUser['ID']);
369 364
         if (!is_array($LoggedUser['Notify'])) {
370
-            $DB->query("
365
+            $DB->prepared_query("
371 366
             SELECT ID, Label
372 367
             FROM users_notify_filters
373 368
               WHERE UserID = '$LoggedUser[ID]'");
@@ -383,39 +378,13 @@ if (isset($_COOKIE['session']) && isset($_COOKIE['userid'])) {
383 378
     }
384 379
 
385 380
     // IP changed
386
-    if (apcu_exists('DBKEY') && Crypto::decrypt($LoggedUser['IP']) != $_SERVER['REMOTE_ADDR'] && !check_perms('site_disable_ip_history')) {
381
+    if (apcu_exists('DBKEY') && Crypto::decrypt($LoggedUser['IP']) != $_SERVER['REMOTE_ADDR']) {
387 382
         if (Tools::site_ban_ip($_SERVER['REMOTE_ADDR'])) {
388 383
             error('Your IP address has been banned.');
389 384
         }
390 385
 
391 386
         $CurIP = db_string($LoggedUser['IP']);
392 387
         $NewIP = db_string($_SERVER['REMOTE_ADDR']);
393
-        $DB->query("
394
-        SELECT IP
395
-        FROM users_history_ips
396
-          WHERE EndTime IS NULL
397
-          AND UserID = '$LoggedUser[ID]'");
398
-
399
-        while (list($EncIP) = $DB->next_record()) {
400
-            if (Crypto::decrypt($EncIP) == $CurIP) {
401
-                $CurIP = $EncIP;
402
-                // CurIP is now the encrypted IP that was already in the database (for matching)
403
-                break;
404
-            }
405
-        }
406
-
407
-        $DB->query("
408
-        UPDATE users_history_ips
409
-        SET EndTime = NOW()
410
-          WHERE EndTime IS NULL
411
-          AND UserID = '$LoggedUser[ID]'
412
-          AND IP = '$CurIP'");
413
-
414
-        $DB->query("
415
-        INSERT IGNORE INTO users_history_ips
416
-          (UserID, IP, StartTime)
417
-        VALUES
418
-          ('$LoggedUser[ID]', '".Crypto::encrypt($NewIP)."', NOW())");
419 388
 
420 389
         $Cache->begin_transaction('user_info_heavy_'.$LoggedUser['ID']);
421 390
         $Cache->update_row(false, array('IP' => Crypto::encrypt($_SERVER['REMOTE_ADDR'])));
@@ -425,7 +394,7 @@ if (isset($_COOKIE['session']) && isset($_COOKIE['userid'])) {
425 394
     // Get stylesheets
426 395
     $Stylesheets = $Cache->get_value('stylesheets');
427 396
     if (!is_array($Stylesheets)) {
428
-        $DB->query('
397
+        $DB->prepared_query('
429 398
         SELECT
430 399
           ID,
431 400
           LOWER(REPLACE(Name, " ", "_")) AS Name,
@@ -460,7 +429,7 @@ function logout()
460 429
     setcookie('keeplogged', '', time() - 60 * 60 * 24 * 365, '/', '', false);
461 430
 
462 431
     if ($SessionID) {
463
-        G::$DB->query("
432
+        G::$DB->prepared_query("
464 433
         DELETE FROM users_sessions
465 434
           WHERE UserID = '" . G::$LoggedUser['ID'] . "'
466 435
           AND SessionID = '".db_string($SessionID)."'");
@@ -482,7 +451,7 @@ function logout_all_sessions()
482 451
 {
483 452
     $UserID = G::$LoggedUser['ID'];
484 453
 
485
-    G::$DB->query("
454
+    G::$DB->prepared_query("
486 455
     DELETE FROM users_sessions
487 456
       WHERE UserID = '$UserID'");
488 457
 
@@ -547,16 +516,6 @@ if (isset(G::$LoggedUser['LockedAccount']) && !in_array($Document, $AllowedPages
547 516
 
548 517
 $Debug->set_flag('completed module execution');
549 518
 
550
-/* Required in the absence of session_start() for providing that pages will change
551
-upon hit rather than being browser cached for changing content.
552
-
553
-Old versions of Internet Explorer choke when downloading binary files over HTTPS with disabled cache.
554
-Define the following constant in files that handle file downloads */
555
-if (!defined('SKIP_NO_CACHE_HEADERS')) {
556
-    header('Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0');
557
-    header('Pragma: no-cache');
558
-}
559
-
560 519
 // Flush to user
561 520
 ob_end_flush();
562 521
 

+ 41
- 13
classes/security.class.php View File

@@ -6,50 +6,77 @@ declare(strict_types = 1);
6 6
  *
7 7
  * Designed to hold common authentication functions from various sources:
8 8
  *  - classes/script_start.php
9
+ *  - "Quick SQL injection check"
9 10
  */
10 11
 
11 12
 class Security
12 13
 {
13 14
     /**
14
-     * Check ID
15
+     * Check integer
15 16
      *
16 17
      * Makes sure a number ID is valid,
17 18
      * e.g., a page ID requested by GET.
18 19
      */
19
-    public function CheckID($ID)
20
+    public function checkInt(...$IDs)
20 21
     {
21
-        # Temporary failsafe
22
-        # (int) 'dingus' = 0
23
-        # (int) 3.14 = 3
24
-        $ID = (int) $ID;
25
-
26
-        if (!is_int($ID) || $ID < 1) {
27
-            error(400);
22
+        foreach ($IDs as $ID) {
23
+            if (!ID || !is_int($ID) || $ID < 1) {
24
+                return "Expected an integer > 1, got $ID in Security::checkInt.";
25
+                #error("Expected an integer > 1, got $ID in Security::checkInt.");
26
+            }
28 27
         }
29 28
 
30 29
         return;
31 30
     }
32 31
 
32
+
33 33
     /**
34 34
      * Setup pitfalls
35 35
      *
36 36
      * A series of quick sanity checks during app init.
37 37
      * Previously in classes/script_start.php.
38 38
      */
39
-    public function SetupPitfalls()
39
+    public function setupPitfalls()
40 40
     {
41
+        $ENV = ENV::go();
42
+
43
+        # Bad PHP version
44
+        if (version_compare(PHP_VERSION, $ENV->PHP_MIN, '<')) {
45
+            error("Gazelle requires PHP > $ENV->PHP_MIN.");
46
+        }
47
+
41 48
         # short_open_tag
42 49
         if (!ini_get('short_open_tag')) {
43
-            error('short_open_tag != On in php.ini');
50
+            error('short_open_tag != On in php.ini.');
44 51
         }
45 52
 
46 53
         # apcu
47 54
         if (!extension_loaded('apcu')) {
48
-            error('APCu extension not loaded');
55
+            error('APCu extension php-apcu not loaded.');
56
+        }
57
+
58
+        # mbstring
59
+        if (!extension_loaded('mbstring')) {
60
+            error('Multibyte string extension php-mbstring not loaded.');
61
+        }
62
+
63
+        # memcache
64
+        if (!extension_loaded('memcache')) {
65
+            error('memcached extension php-memcache not loaded.');
66
+        }
67
+
68
+        # mysqli
69
+        if (!extension_loaded('mysqli')) {
70
+            error('mysqli extension php-mysql not loaded.');
71
+        }
72
+        
73
+        # blake3
74
+        if ($ENV->FEATURE_BIOPHP && !extension_loaded('blake3')) {
75
+            error('Please install and enable the php-blake3 extension.');
49 76
         }
50 77
 
51 78
         # Deal with dumbasses
52
-        if (isset($_REQUEST['info_hash']) && isset($_REQUEST['peer_id'])) {
79
+        if (isset($_REQUEST['info_hash']) || isset($_REQUEST['peer_id'])) {
53 80
             error(
54 81
                 'd14:failure reason40:Invalid .torrent, try downloading again.e',
55 82
                 $NoHTML = true,
@@ -60,6 +87,7 @@ class Security
60 87
         return;
61 88
     }
62 89
 
90
+
63 91
     /**
64 92
      * UserID checks
65 93
      *

+ 0
- 17
classes/slaves.class.php View File

@@ -1,17 +0,0 @@
1
-<?php
2
-#declare(strict_types=1);
3
-
4
-class Slaves
5
-{
6
-    public static function get_level($SlaveID)
7
-    {
8
-        G::$DB->query("
9
-        SELECT u.Uploaded, u.Downloaded, u.BonusPoints, COUNT(t.UserID)
10
-        FROM users_main AS u
11
-          LEFT JOIN torrents AS t ON u.ID=t.UserID
12
-          WHERE u.ID = $SlaveID");
13
-          
14
-        list($Upload, $Download, $Points, $Uploads) = G::$DB->next_record();
15
-        return intval(((($Uploads**0.35)*1.5)+1) * max(($Upload+($Points*1000000)-$Download)/(1024**3), 1));
16
-    }
17
-};

+ 55
- 38
classes/text.class.php View File

@@ -14,8 +14,6 @@ class Text
14 14
       's' => 0,
15 15
       '*' => 0,
16 16
       '#' => 0,
17
-      #'ch' => 0,
18
-      #'uch' => 0,
19 17
       'artist' => 0,
20 18
       'user' => 0,
21 19
       'n' => 0,
@@ -128,7 +126,41 @@ class Text
128 126
      */
129 127
     public static $TOC = false;
130 128
 
129
+
130
+    /**
131
+     * Fix the links
132
+     * 
133
+     * Make it so that internal links are in the form "/section?p=foo"
134
+     * and that external links are secure and look like Wikipedia.
135
+     * Takes an already-parsed input, to hit Markdown and BBcode.
136
+     */
137
+    public function fix_links($Parsed) {
138
+            # Replace links to $ENV->SITE_DOMAIN
139
+            $Parsed = preg_replace(
140
+                "/<a href=\"$ENV->RESOURCE_REGEX($ENV->SITE_DOMAIN|$ENV->OLD_SITE_DOMAIN)\//",
141
+                '<a href="/',
142
+                $Parsed
143
+            );
144
+                
145
+            # Replace external links and add Wikipedia-style CSS class
146
+            $RelTags = 'external nofollow noopener noreferrer';
147
+
148
+            $Parsed = preg_replace(
149
+                '/<a href="https?:\/\//',
150
+                '<a class="external" rel="'.$RelTags.'" target="_blank" href="https://',
151
+                $Parsed
152
+            );
153
+
154
+            $Parsed = preg_replace(
155
+                '/<a href="ftps?:\/\//',
156
+                '<a class="external" rel="'.$RelTags.'" target="_blank" href="ftps://',
157
+                $Parsed
158
+            );
159
+
160
+            return $Parsed;       
161
+    }
131 162
     
163
+
132 164
     /**
133 165
      * Output BBCode as XHTML
134 166
      *
@@ -152,8 +184,18 @@ class Text
152 184
         )) {
153 185
             $Parsedown = new ParsedownExtra();
154 186
             $Parsedown->setSafeMode(true);
187
+
188
+            # Prepare clean escapes
155 189
             $Str = html_entity_decode($Str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
156 190
 
191
+            # Parse early and post-process
192
+            $Parsed = $Parsedown->text($Str);
193
+            
194
+            # Replace links to $ENV->SITE_DOMAIN
195
+            $Parsed = self::fix_links($Parsed);
196
+
197
+            return $Parsed;
198
+
157 199
             # Markdown ToC not happening yet
158 200
             # Shouldn't parse_toc() output HTML
159 201
             /*
@@ -174,8 +216,6 @@ class Text
174 216
             }
175 217
             */
176 218
 
177
-            return $P = $Parsedown->text($Str);
178
-
179 219
         /*
180 220
         return $P =
181 221
             ((self::$TOC && $OutputTOC)
@@ -183,37 +223,24 @@ class Text
183 223
                 : null)
184 224
             . $Parsedown->text($Str);
185 225
         */
186
-        } else {
226
+        }
227
+        
228
+        /**
229
+         * BBcode formatting
230
+         */
231
+        else {
187 232
             global $Debug;
188 233
             $Debug->set_flag('BBCode start');
189 234
 
190 235
             self::$Headlines = [];
191 236
             $Str = display_str($Str);
192 237
 
193
-            # Checkboxes: broken and stupid
194
-            /*
195
-            $Str = preg_replace('/\[\\[(ch|uch)]\]/i', '', $Str);
196
-            $Str = preg_replace('/\[ch\]/i', '[ch][/ch]', $Str);
197
-            $Str = preg_replace('/\[uch\]/i', '[uch][/uch]', $Str);
198
-            */
199
-
200 238
             // Inline links
201 239
             $URLPrefix = '(\[url\]|\[url\=|\[img\=|\[img\])';
202 240
             $Str = preg_replace('/'.$URLPrefix.'\s+/i', '$1', $Str);
203 241
             $Str = preg_replace('/(?<!'.$URLPrefix.')http(s)?:\/\//i', '$1[inlineurl]http$2://', $Str);
204 242
             $Str = preg_replace('/\[embed\]\[inlineurl\]/', '[embed]', $Str);
205 243
 
206
-            // For anonym.to and archive.org links, remove any [inlineurl] in the middle of the link
207
-            /*
208
-            $Str = preg_replace_callback(
209
-                '/(?<=\[inlineurl\]|'.$URLPrefix.')(\S*\[inlineurl\]\S*)/m',
210
-                function ($matches) {
211
-                    return str_replace("[inlineurl]", "", $matches[0]);
212
-                },
213
-                $Str
214
-            );
215
-            */
216
-
217 244
             if (self::$TOC) {
218 245
                 $Str = preg_replace('/(\={5})([^=].*)\1/i', '[headline=4]$2[/headline]', $Str);
219 246
                 $Str = preg_replace('/(\={4})([^=].*)\1/i', '[headline=3]$2[/headline]', $Str);
@@ -233,6 +260,9 @@ class Text
233 260
                 $HTML = self::parse_toc($Min) . $HTML;
234 261
             }
235 262
 
263
+            # Rewrite the URLs
264
+            $HTML = self::fix_links($HTML);
265
+
236 266
             $Debug->set_flag('BBCode end');
237 267
             return $HTML;
238 268
         }
@@ -871,20 +901,6 @@ class Text
871 901
                   break;
872 902
 
873 903
 
874
-                /*
875
-                case 'ch':
876
-                  $Str .= '<input type="checkbox" checked="checked" disabled="disabled">';
877
-                  break;
878
-                */
879
-
880
-
881
-                /*
882
-                case 'uch':
883
-                  $Str .= '<input type="checkbox" disabled="disabled">';
884
-                  break;
885
-                */
886
-
887
-
888 904
                 case 'list':
889 905
                   $Str .= "<$Block[ListType] class=\"postlist\">";
890 906
                   foreach ($Block['Val'] as $Line) {
@@ -1051,7 +1067,8 @@ class Text
1051 1067
                       if ($LocalURL) {
1052 1068
                           $Str .= '<a href="'.$LocalURL.'">'.substr($LocalURL, 1).'</a>';
1053 1069
                       } else {
1054
-                          $Str .= '<a rel="noreferrer" target="_blank" href="'.$Block['Attr'].'">'.$Block['Attr'].'</a>';
1070
+                          $Str .= '<a href="'.$Block['Attr'].'">'.$Block['Attr'].'</a>';
1071
+                          #$Str .= '<a rel="noreferrer" target="_blank" href="'.$Block['Attr'].'">'.$Block['Attr'].'</a>';
1055 1072
                       }
1056 1073
                   }
1057 1074
                   break;

+ 1
- 12
classes/tools.class.php View File

@@ -122,18 +122,7 @@ class Tools
122 122
         return trim($Output[4]);
123 123
     }
124 124
 
125
-    /**
126
-     * Format an IP address with links to IP history.
127
-     *
128
-     * @param string IP
129
-     * @return string The HTML
130
-     */
131
-    public static function display_ip($IP)
132
-    {
133
-        return $Line = '<a href="user.php?action=search&amp;ip_history=on&amp;ip='.display_str($IP).'&amp;matchtype=strict" title="Search" class="brackets tooltip">S</a>';
134
-    }
135
-
136
-
125
+    
137 126
     /**
138 127
      * Disable an array of users.
139 128
      *

+ 191
- 422
classes/torrent_form.class.php View File

@@ -14,16 +14,6 @@ class TorrentForm
14 14
      * recursively copying parts to arrays in place as needed.
15 15
      */
16 16
     
17
-    # Platforms
18
-    # See classes/config.php
19
-    public $SeqPlatforms = [];
20
-    public $GraphPlatforms = [];
21
-    public $ImgPlatforms = [];
22
-    public $DocPlatforms = [];
23
-    public $RawPlatforms = [];
24
-    #public $Media = [];
25
-    #public $MediaManga = [];
26
-
27 17
     # Formats
28 18
     # See classes/config.php
29 19
     public $SeqFormats = [];
@@ -36,22 +26,8 @@ class TorrentForm
36 26
     public $BinDocFormats = [];
37 27
     public $CpuGenFormats = [];
38 28
     public $PlainFormats = [];
39
-    #public $Containers = [];
40
-    #public $ContainersGames = [];
41
-    #public $ContainersProt = [];
42
-    #public $ContainersExtra = [];
43
-
44
-    # Misc
45
-    public $Codecs = [];
46
-    public $Archives = [];
47 29
     public $Resolutions = [];
48 30
 
49
-    # Deprecated
50
-    #public $Formats = [];
51
-    #public $Versions = [];
52
-    #public $Bitrates = [];
53
-    #public $Platform = [];
54
-
55 31
     # Gazelle
56 32
     public $NewTorrent = false;
57 33
     public $Torrent = [];
@@ -63,7 +39,8 @@ class TorrentForm
63 39
     public function __construct($Torrent = false, $Error = false, $NewTorrent = true)
64 40
     {
65 41
         # See classes/config.php
66
-        global $UploadForm, $Categories, $TorrentID, $SeqPlatforms, $GraphPlatforms, $ImgPlatforms, $DocPlatforms, $RawPlatforms, $SeqFormats, $ProtFormats, $GraphXmlFormats, $GraphTxtFormats, $ImgFormats, $MapVectorFormats, $MapRasterFormats, $BinDocFormats, $CpuGenFormats, $PlainFormats, $Codecs, $Archives, $Resolutions;
42
+        global $UploadForm, $Categories, $TorrentID, $SeqFormats, $ProtFormats, $GraphXmlFormats, $GraphTxtFormats, $ImgFormats, $MapVectorFormats, $MapRasterFormats, $BinDocFormats, $CpuGenFormats, $PlainFormats, $Resolutions;
43
+        #global $UploadForm, $Categories, $TorrentID, $SeqPlatforms, $GraphPlatforms, $ImgPlatforms, $DocPlatforms, $RawPlatforms, $SeqFormats, $ProtFormats, $GraphXmlFormats, $GraphTxtFormats, $ImgFormats, $MapVectorFormats, $MapRasterFormats, $BinDocFormats, $CpuGenFormats, $PlainFormats, $Codecs, $Archives, $Resolutions;
67 44
         #global $UploadForm, $Categories, $Formats, $Bitrates, $Media, $MediaManga, $TorrentID, $Containers, $ContainersGames, $Codecs, $Resolutions, $Platform, $Archives, $ArchivesManga;
68 45
 
69 46
         # Gazelle
@@ -75,14 +52,6 @@ class TorrentForm
75 52
         $this->Categories = $Categories;
76 53
         $this->TorrentID = $TorrentID;
77 54
 
78
-        # Platforms
79
-        # See classes/config.php
80
-        $this->SeqPlatforms = $SeqPlatforms;
81
-        $this->GraphPlatforms = $GraphPlatforms;
82
-        $this->ImgPlatforms = $ImgPlatforms;
83
-        $this->DocPlatforms = $DocPlatforms;
84
-        $this->RawPlatforms = $RawPlatforms;
85
-       
86 55
         # Formats
87 56
         # See classes/config.php
88 57
         $this->SeqFormats = $SeqFormats;
@@ -95,10 +64,6 @@ class TorrentForm
95 64
         $this->BinDocFormats = $BinDocFormats;
96 65
         $this->CpuGenFormats = $CpuGenFormats;
97 66
         $this->PlainFormats = $PlainFormats;
98
-        
99
-        # Misc
100
-        $this->Codecs = $Codecs;
101
-        $this->Archives = $Archives;
102 67
         $this->Resolutions = $Resolutions;
103 68
 
104 69
         # Quick constructor test
@@ -110,127 +75,91 @@ class TorrentForm
110 75
 
111 76
 
112 77
     /**
113
-     * ========================
114
-     * = New functional class =
115
-     * ========================
116
-     *
117
-     * Contains functions that output discreet torrent form fields.
118
-     * Useful for <?= echoing in skeleton tables in the sections.
119
-     */
78
+     * ====================
79
+     * = Twig-based class =
80
+     * ====================
81
+    */
120 82
 
121 83
 
122 84
     /**
123
-     * Upload notice
85
+     * render
124 86
      *
125
-     * Broken into multiple NewTorrent tests for sanity.
126
-     * Each if statement should contain one discreet content block.
87
+     * TorrentForm Twig wrapper.
88
+     * Hopefully more pleasant.
127 89
      */
128
-    public function uploadNotice()
90
+    public function render()
129 91
     {
130
-        if ($this->NewTorrent) {
131
-            $HTML = <<<HTML
132
-            <aside class="upload_notice">
133
-              <p>
134
-                Please consult the
135
-                <a href="/rules.php?p=upload">Upload Rules</a>
136
-                and the
137
-                <a href="/wiki.php?action=article&name=categories">Categories Wiki</a>
138
-                to help fill out the upload form correctly.
139
-              </p>
140
-
141
-              <p>
142
-                The site adds the Announce and Source automatically.
143
-                Just download and seed the new torrent after uploading it.
144
-
145
-              <!--
146
-              <strong>
147
-                If you never have before, be sure to read this list of
148
-                <a href="wiki.php?action=article&name=uploadingpitfalls">uploading pitfalls</a>.
149
-              </strong>
150
-              -->
151
-              </p>
152
-            </aside>
153
-HTML;
154
-        } # fi NewTorrent
155
-        return $HTML;
156
-    }
157
-
92
+        $ENV = ENV::go();
93
+        $Twig  = Twig::go();
158 94
 
159
-    /**
160
-     * Announce URLs
161
-     *
162
-     * Announce URLs displayed on the form.
163
-     * They're added to torrents in torrentsdl.class.php.
164
-     * Bio Gazelle supports tiered swarms, T1 private and T2 public.
165
-     */
166
-    public function announceSource()
167
-    {
95
+        /**
96
+         * Upload notice
97
+         */
168 98
         if ($this->NewTorrent) {
169
-            $HTML = '<aside class="announce_source">';
99
+            echo $Twig->render('torrent_form/notice.html');
100
+        }
170 101
 
102
+        /**
103
+         * Announce and source
104
+         */
105
+        if ($this->NewTorrent) {
171 106
             $Announces = ANNOUNCE_URLS[0];
172 107
             #$Announces = call_user_func_array('array_merge', ANNOUNCE_URLS);
108
+
173 109
             $TorrentPass = G::$LoggedUser['torrent_pass'];
110
+            $TorrentSource = Users::get_upload_sources()[0];
174 111
 
175
-            foreach ($Announces as $Announce) {
176
-                $HTML .= <<<HTML
177
-                <p>
178
-                  <strong>Announce</strong>
179
-                  <input type="text"
180
-                    value="$Announce/$TorrentPass/announce"
181
-                    size="60" readonly="readonly"
182
-                    onclick="this.select();" />
183
-                </p>
184
-HTML;
185
-            }
112
+            echo $Twig->render(
113
+                'torrent_form/announce_source.html',
114
+                [
115
+                  'announces' => $Announces,
116
+                  'torrent_pass' => $TorrentPass,
117
+                  'torrent_source' => $TorrentSource,
118
+                ]
119
+            );
120
+        }
186 121
 
187
-            /**
188
-             * Source (randomize infohash)
189
-             */
190
-            $TorrentSource = Users::get_upload_sources()[0];
191
-            $HTML .= <<<HTML
192
-            <p>
193
-              <strong>Source</strong>
194
-              <input type="text"
195
-                value="$TorrentSource"
196
-                size="30" readonly="readonly"
197
-                onclick="this.select();" />
198
-            </p>
122
+        /**
123
+         * Errors
124
+         * (Twig unjustified)
125
+         */
126
+        if ($this->Error) {
127
+            echo <<<HTML
128
+              <aside class="upload_error">
129
+                <p>$this->Error</p>
130
+              </aside>
199 131
 HTML;
132
+        }
200 133
 
201
-            $HTML .= '</aside>';
202
-        } # fi NewTorrent
203
-        return $HTML;
204
-    }
134
+        /**
135
+         * head
136
+         * IMPORTANT!
137
+         */
138
+        echo $this->head();
205 139
 
140
+        /**
141
+         * upload_form
142
+         * Where the fields are.
143
+         */
144
+        echo $this->upload_form();
206 145
 
207
-    /**
208
-     * Display torrent upload errors
209
-     */
210
-    public function error()
211
-    {
212
-        if ($this->NewTorrent) {
213
-            if ($this->Error) {
214
-                echo <<<HTML
215
-                <aside class="upload_error">
216
-                  <p>$this->Error</p>
217
-                </aside>
218
-HTML;
219
-            }
220
-        } # fi NewTorrent
221
-    }
146
+        /**
147
+         * foot
148
+         */
149
+        echo $this->foot();
150
+    } # End render()
222 151
 
223 152
 
224 153
     /**
225 154
      * head
226 155
      *
227
-     * Everything until the catalogue number field.
228
-     * Server-side torrent scrubbing admonishment.
156
+     * Everything up to the main form tag open:
157
+     * <div id="dynamic_form">
158
+     * Kept as an HTML function because it's simpler.
229 159
      */
230
-    public function head()
160
+    private function head()
231 161
     {
232 162
         $ENV = ENV::go();
233
-
234 163
         G::$DB->query(
235 164
             "
236 165
         SELECT
@@ -288,19 +217,13 @@ HTML;
288 217
          * Start printing the torrent form
289 218
          */
290 219
         $HTML .= '<table class="torrent_form">';
291
-        return $HTML;
292
-    }
293
-
294 220
 
295
-    /**
296
-     * New torrent options: file
297
-     */
298
-    public function basicInfo()
299
-    {
300
-        $ENV = ENV::go();
301
-      
221
+        /**
222
+         * New torrent options:
223
+         * file and category
224
+         */
302 225
         if ($this->NewTorrent) {
303
-            $HTML =  '<h2 class="header">Basic Info</h2>';
226
+            $HTML .=  '<h2 class="header">Basic Info</h2>';
304 227
             $HTML .= <<<HTML
305 228
             <tr>
306 229
               <td>
@@ -319,14 +242,8 @@ HTML;
319 242
               </td>
320 243
             </tr>
321 244
 HTML;
322
-        } # fi NewTorrent
323 245
 
324
-        /**
325
-         * New torrent options: category
326
-         */
327
-        if ($this->NewTorrent) {
328 246
             $DisabledFlag = ($this->DisabledFlag) ? ' disabled="disabled"' : '';
329
-
330 247
             $HTML .= <<<HTML
331 248
               <tr>
332 249
                 <td>
@@ -375,7 +292,7 @@ HTML;
375 292
      *
376 293
      * Make the endmatter.
377 294
      */
378
-    public function foot()
295
+    private function foot()
379 296
     {
380 297
         $Torrent = $this->Torrent;
381 298
         echo '<table class="torrent_form>';
@@ -471,8 +388,8 @@ HTML;
471 388
 
472 389
         echo <<<HTML
473 390
               <tr>
474
-                <td>
475
-                  <input id="post" type="submit" value="$Value" />
391
+                <td class="center">
392
+                  <input id="post" type="submit" value="$Value" class="button-primary" />
476 393
                 </td>
477 394
               </tr>
478 395
             </table> <!-- torrent_form -->
@@ -486,206 +403,75 @@ HTML;
486 403
      * upload_form
487 404
      *
488 405
      * Finally the "real" upload form.
489
-     * Contains all the field you'd expect.
406
+     * Contains all the fields you'd expect.
490 407
      *
491 408
      * This is currently one enormous function.
492 409
      * It has sub-functions, variables, and everything.
493 410
      * It continues to the end of the class.
494 411
      */
495
-    public function upload_form()
412
+    private function upload_form()
496 413
     {
497 414
         $ENV = ENV::go();
415
+        $Twig = Twig::go();
498 416
 
499 417
         $QueryID = G::$DB->get_query_id();
500 418
         $Torrent = $this->Torrent;
501 419
 
502
-        # Moved to their own functions
503
-        #echo $this->head();
504
-        #echo $this->basicInfo();
505
-
506 420
         # Start printing the form
507 421
         echo '<h2 class="header">Torrent Form</h2>';
508
-        echo '<table class="torrent_form">';
422
+        echo '<table class="torrent_form skeleton-fix">';
509 423
 
510 424
         
511 425
         /**
512 426
          * Accession Number
513
-         *
514
-         * The headings below refer to a new generic input schema.
515
-         * The HTML labels and various user-visible text should come from $ENV.
516
-         *
517
-         * RecursiveArrayObject->toArray() returns arrays from, e.g., $ENV->A->B->C.
518
-         * This makes it easy to get and program with any subset of config objects.
519 427
          */
520 428
         $CatalogueNumber = display_str($Torrent['CatalogueNumber']);
521 429
         $Disabled = $this->Disabled;
522
-
523
-        # DOI
524
-        echo <<<HTML
525
-        <tr id="javdb_tr">
526
-          <td>
527
-            <label for="catalogue">
528
-              Accession Number
529
-            </label>
530
-          </td>
531
-
532
-          <td>
533
-            <input type="text"
534
-              id="catalogue" name="catalogue" size="30"
535
-              placeholder="RefSeq and UniProt preferred"
536
-              value="$CatalogueNumber" />
537
-
538
-            <input type="button" autofill="jav" value="Autofill"
539
-              style="pointer-events: none; opacity: 0.5;">
540
-            </input>
541
-          </td>
542
-        </tr>
543
-HTML;
544
-
545
-        # RefSeq
546
-        $DisabledFlagInput = (!$this->DisabledFlag)
547
-            ? '<input type="button" autofill="anime" value="Autofill" />'
548
-            : null;
549
-
550
-        echo <<<HTML
551
-        <tr id="anidb_tr" class="hidden">
552
-          <td>
553
-            <label for="anidb">
554
-              AniDB Autofill (optional)
555
-            </label>
556
-          </td>
557
-          
558
-          <td>
559
-            <input type="text" id="anidb" size="10" $Disabled />
560
-            $DisabledFlagInput
561
-          </td>
562
-        </tr>
563
-HTML;
564 430
         
565
-        # UniProt
566
-        $DisabledFlagInput = (!$this->DisabledFlag)
567
-            ? '<input type="button" autofill="anime" value="Autofill" />'
568
-            : null;
569
-
570
-        echo <<<HTML
571
-        <tr id="anidb_tr" class="hidden">
572
-          <td>
573
-            <label for="douj">
574
-              e-hentai URL (optional)
575
-            </label>
576
-          </td>
577
-          
578
-          <td>
579
-            <input type="text" id="douj" size="10" $Disabled />
580
-            $DisabledFlagInput
581
-          </td>
582
-        </tr>
583
-HTML;
431
+        echo $Twig->render(
432
+            'torrent_form/identifier.html',
433
+            [
434
+                'db' => $ENV->DB->identifier,
435
+                'identifier' => $CatalogueNumber,
436
+            ]
437
+        );
584 438
 
585 439
 
586 440
         /**
587
-         * Semantic Version
441
+         * Version
588 442
          */
589 443
         
590 444
         $Version = display_str($Torrent['Version']);
591
-        echo <<<HTML
592
-        <tr id="audio_tr">
593
-          <td>
594
-            <label for="version">
595
-              Version
596
-            </label>
597
-          </td>
598 445
 
599
-          <td>
600
-            <input type="text"
601
-              id="version" name="version"
602
-              size="12" pattern="\d+\.*\d*\.*\d*"
603
-              placeholder="Start with 0.1.0"
604
-              value="$Version" />
605
-            
606
-            <p>
607
-              Please see
608
-              <a href="https://semver.org target=" _blank">Semantic Versioning</a>
609
-            </p>
610
-          </td>
611
-        </tr>
612
-HTML;
446
+        echo $Twig->render(
447
+            'torrent_form/version.html',
448
+            [
449
+              'db' => $ENV->DB->version,
450
+              'version' => $Version,
451
+          ]
452
+        );
613 453
 
614 454
 
615 455
         /**
616
-         * Title fields
617
-         *
618
-         * Gazelle has three title fields available, regrettably hardcoded.
619
-         * Ideally we could rank them in importance in the site ontology,
620
-         * then update one config file to apply custom metadata across the board.
456
+         * Title Fields
621 457
          */
622 458
 
623 459
         # New torrent upload
624 460
         if ($this->NewTorrent) {
625
-            $Disabled = $this->Disabled;
626
-
627
-
628
-            /**
629
-             * Title 1
630
-             */
631 461
             $Title1 = display_str($Torrent['Title']);
632
-            echo <<<HTML
633
-              <tr id="title_tr">
634
-                <td>
635
-                  <label for="title" class="required">
636
-                    Torrent Title
637
-                  </label>
638
-                </td>
639
-              
640
-                <td>
641
-                  <input type="text" id="title" name="title" size="60"
642
-                    placeholder="Definition line, e.g., Alcohol dehydrogenase ADH1"
643
-                    value="$Title1" $Disabled />
644
-                </td>
645
-              </tr>
646
-HTML;
647
-
648
-
649
-            /**
650
-             * Title 2
651
-             */
652 462
             $Title2 = display_str($Torrent['Title2']);
653
-            echo <<<HTML
654
-              <tr id="title_rj_tr">
655
-                <td>
656
-                  <label for="title_rj">
657
-                  Organism
658
-                  </label>
659
-                </td>
660
-              
661
-              <td>
662
-                <input type="text" id="title_rj" name="title_rj" size="60"
663
-                  placeholder="Organism line binomial, e.g., Saccharomyces cerevisiae"
664
-                  value="$Title2" $Disabled />
665
-              </td>
666
-            </tr>
667
-HTML;
668
-
669
-
670
-            /**
671
-             * Title 3
672
-             */
673 463
             $Title3 = display_str($Torrent['TitleJP']);
674
-            echo <<<HTML
675
-            <tr id="title_jp_tr">
676
-              <td>
677
-                <label for="title_jp">
678
-                Strain/Variety
679
-                </label>
680
-              </td>
681
-              
682
-              <td>
683
-                <input type="text" id="title_jp" name="title_jp" size="60"
684
-                  placeholder="Organism line if any, e.g., S288C"
685
-                  value="$Title3" $Disabled />
686
-              </td>
687
-            </tr>
688
-HTML;
464
+            #$Disabled = $this->Disabled;
465
+
466
+            echo $Twig->render(
467
+                'torrent_form/titles.html',
468
+                [
469
+                  'db' => $ENV->DB,
470
+                  'title' => $Title1,
471
+                  'subject' => $Title2,
472
+                  'object' => $Title3,
473
+                ]
474
+            );
689 475
         } # fi NewTorrent
690 476
         
691 477
         
@@ -748,29 +534,18 @@ HTML;
748 534
 
749 535
 
750 536
         /**
751
-         * Affiliation
752
-         *
753
-         * The company, studio, lab, etc., that did the work.
754
-         * todo: Add creator affiliation and pick a predetermined one
755
-         * (in our case, last author's institution).
537
+         * Workgroup
756 538
          */
757 539
         if ($this->NewTorrent) {
758 540
             $Affiliation = display_str($Torrent['Studio']);
759
-            echo <<<HTML
760
-            <tr id="studio_tr">
761
-              <td>
762
-                <label for="studio" class="required">
763
-                  Department/Lab
764
-                </label>
765
-              </td>
766
-              
767
-              <td>
768
-                <input type="text" id="studio" name="studio" size="60"
769
-                  placeholder="Last author's institution, e.g., Lawrence Berkeley Laboratory"
770
-                  value="$Affiliation" $Disabled />
771
-              </td>
772
-            </tr>
773
-HTML;
541
+
542
+            echo $Twig->render(
543
+                'torrent_form/workgroup.html',
544
+                [
545
+                  'db' => $ENV->DB->workgroup,
546
+                  'workgroup' => $Affiliation,
547
+                ]
548
+            );
774 549
         }
775 550
 
776 551
 
@@ -782,22 +557,14 @@ HTML;
782 557
          */
783 558
         if ($this->NewTorrent) {
784 559
             $TorrentLocation = display_str($Torrent['Series']);
785
-            echo <<<HTML
786
-            <tr id="series_tr">
787
-              <td>
788
-                <label for="series">
789
-                  Location
790
-                </label>
791
-              </td>
792
-            
793
-              <td>
794
-                <input type="text" id="series" name="series" size="60"
795
-                  placeholder="Physical location, e.g., Berkeley, CA 94720"
796
-                  value="$TorrentLocation" $Disabled />
797
-              </td>
798
-            </tr>
799
-HTML;
800
-        } # fi NewTorrent
560
+            echo $Twig->render(
561
+                'torrent_form/location.html',
562
+                [
563
+                  'db' => $ENV->DB->location,
564
+                  'location' => $TorrentLocation,
565
+                ]
566
+            );
567
+        }
801 568
 
802 569
 
803 570
         /**
@@ -811,21 +578,14 @@ HTML;
811 578
          * Year
812 579
          */
813 580
         $TorrentYear = display_str($Torrent['Year']);
814
-        echo <<<HTML
815
-        <tr id="year_tr">
816
-          <td>
817
-            <label for="year" class="required">
818
-              Year
819
-            </label>
820
-          </td>
821
-          
822
-          <td>
823
-            <input type="text" id="year" name="year"
824
-              maxlength="4" size="15" placeholder="Publication year"
825
-              value="$TorrentYear" />
826
-          </td>
827
-        </tr>
828
-HTML;
581
+
582
+        echo $Twig->render(
583
+            'torrent_form/year.html',
584
+            [
585
+            'db' => $ENV->DB->year,
586
+            'year' => $TorrentYear,
587
+          ]
588
+        );
829 589
 
830 590
 
831 591
         /**
@@ -850,14 +610,14 @@ HTML;
850 610
               <option>---</option>
851 611
 HTML;
852 612
 
853
-        foreach ($this->Codecs as $Codec) {
854
-            echo "<option value='$Codec'";
613
+        foreach ($ENV->META->Licenses as $License) {
614
+            echo "<option value='$License'";
855 615
 
856
-            if ($Codec === ($Torrent['Codec'] ?? false)) {
616
+            if ($License === ($Torrent['Codec'] ?? false)) {
857 617
                 echo " selected";
858 618
             }
859 619
             
860
-            echo ">$Codec</option>\n";
620
+            echo ">$License</option>\n";
861 621
         }
862 622
 
863 623
         echo <<<HTML
@@ -939,7 +699,7 @@ HTML;
939 699
                 $trID = 'media_tr',
940 700
                 $Label = 'Platform',
941 701
                 $Torrent = $Torrent,
942
-                $Media = $this->SeqPlatforms
702
+                $Media = $ENV->CATS->{1}->Platforms
943 703
             );
944 704
             
945 705
 
@@ -950,7 +710,7 @@ HTML;
950 710
                 $trID = 'media_graphs_tr',
951 711
                 $Label = 'Platform',
952 712
                 $Torrent = $Torrent,
953
-                $Media = array_merge($this->GraphPlatforms, $this->SeqPlatforms)
713
+                $Media = $ENV->CATS->{2}->Platforms
954 714
             );
955 715
             
956 716
 
@@ -961,18 +721,18 @@ HTML;
961 721
                 $trID = 'media_scalars_vectors_tr',
962 722
                 $Label = 'Platform',
963 723
                 $Torrent = $Torrent,
964
-                $Media = array_merge($this->GraphPlatforms, $this->ImgPlatforms)
724
+                $Media = $ENV->CATS->{5}->Platforms
965 725
             );
966 726
 
967 727
 
968 728
             /**
969
-             * Platform: Scalars/Vectors
729
+             * Platform: Images
970 730
              */
971 731
             mediaSelect(
972 732
                 $trID = 'media_images_tr',
973 733
                 $Label = 'Platform',
974 734
                 $Torrent = $Torrent,
975
-                $Media = $this->ImgPlatforms
735
+                $Media = $ENV->CATS->{8}->Platforms
976 736
             );
977 737
 
978 738
 
@@ -983,7 +743,7 @@ HTML;
983 743
                 $trID = 'media_documents_tr',
984 744
                 $Label = 'Platform',
985 745
                 $Torrent = $Torrent,
986
-                $Media = $this->DocPlatforms
746
+                $Media = $ENV->CATS->{11}->Platforms
987 747
             );
988 748
 
989 749
 
@@ -994,7 +754,7 @@ HTML;
994 754
                 $trID = 'media_machine_data_tr',
995 755
                 $Label = 'Platform',
996 756
                 $Torrent = $Torrent,
997
-                $Media = $this->RawPlatforms
757
+                $Media = $ENV->CATS->{12}->Platforms
998 758
             );
999 759
         } # fi NewTorrent
1000 760
         else {
@@ -1014,6 +774,7 @@ HTML;
1014 774
          */
1015 775
         function formatSelect($trID = '', $Label = '', $Torrent = [], $FileTypes = [])
1016 776
         {
777
+            #var_dump($FileTypes);
1017 778
             echo <<<HTML
1018 779
             <tr id="$trID">
1019 780
               <td>
@@ -1021,21 +782,24 @@ HTML;
1021 782
                   $Label
1022 783
                 <label>
1023 784
               </td>
1024
-              
785
+
1025 786
               <td>
1026 787
                 <select id="container" name="container">
1027 788
                   <option value="Autofill">Autofill</option>
1028 789
 HTML;
1029 790
 
1030
-            foreach ($FileTypes as $Type => $Extensions) {
1031
-                echo "<option value='$Type'";
791
+            foreach ($FileTypes as $FileType) {
792
+                foreach ($FileType as $Type => $Extensions) {
793
+                    echo "<option value='$Type'";
1032 794
 
1033
-                if ($Type === ($Torrent['Container'] ?? false)) {
1034
-                    echo ' selected';
1035
-                }
795
+                    if ($Type === ($Torrent['Container'] ?? false)) {
796
+                        echo ' selected';
797
+                    }
1036 798
 
1037
-                echo ">$Type</option>\n";
799
+                    echo ">$Type</option>\n";
800
+                }
1038 801
             }
802
+        
1039 803
 
1040 804
             echo <<<HTML
1041 805
                 </select>
@@ -1060,7 +824,7 @@ HTML;
1060 824
             $trID = 'container_tr',
1061 825
             $Label = 'Format',
1062 826
             $Torrent = $Torrent,
1063
-            $FileTypes = array_merge($this->SeqFormats, $this->ProtFormats, $this->PlainFormats)
827
+            $FileTypes = $ENV->CATS->{1}->Formats
1064 828
         );
1065 829
         
1066 830
 
@@ -1071,7 +835,8 @@ HTML;
1071 835
             $trID = 'container_graphs_tr',
1072 836
             $Label = 'Format',
1073 837
             $Torrent = $Torrent,
1074
-            $FileTypes = array_merge($this->GraphXmlFormats, $this->GraphTxtFormats, $this->SeqFormats, $this->ProtFormats, $this->PlainFormats)
838
+            #$FileTypes = array_column($ENV->META, $Formats)
839
+            $FileTypes = $ENV->CATS->{2}->Formats
1075 840
         );
1076 841
 
1077 842
 
@@ -1082,7 +847,8 @@ HTML;
1082 847
             $trID = 'container_scalars_vectors_tr',
1083 848
             $Label = 'Format',
1084 849
             $Torrent = $Torrent,
1085
-            $FileTypes = array_merge($this->ImgFormats, $this->SeqFormats, $this->ProtFormats, $this->PlainFormats)
850
+            #$FileTypes = $ENV->flatten($ENV->CATS->{5}->Formats)
851
+            $FileTypes = $ENV->CATS->{5}->Formats
1086 852
         );
1087 853
 
1088 854
 
@@ -1093,7 +859,8 @@ HTML;
1093 859
             $trID = 'container_images_tr',
1094 860
             $Label = 'Format',
1095 861
             $Torrent = $Torrent,
1096
-            $FileTypes = array_merge($this->ImgFormats, $this->PlainFormats)
862
+            #$FileTypes = array_merge($this->ImgFormats)
863
+            $FileTypes = $ENV->CATS->{8}->Formats
1097 864
         );
1098 865
 
1099 866
 
@@ -1104,7 +871,8 @@ HTML;
1104 871
             $trID = 'container_spatial_tr',
1105 872
             $Label = 'Format',
1106 873
             $Torrent = $Torrent,
1107
-            $FileTypes = array_merge($this->MapVectorFormats, $this->MapRasterFormats, $this->ImgFormats, $this->PlainFormats)
874
+            #$FileTypes = array_merge($this->MapVectorFormats, $this->MapRasterFormats, $this->ImgFormats, $this->PlainFormats)
875
+            $FileTypes = $ENV->CATS->{9}->Formats
1108 876
         );
1109 877
 
1110 878
 
@@ -1115,7 +883,8 @@ HTML;
1115 883
             $trID = 'container_documents_tr',
1116 884
             $Label = 'Format',
1117 885
             $Torrent = $Torrent,
1118
-            $FileTypes = array_merge($this->BinDocFormats, $this->CpuGenFormats, $this->PlainFormats)
886
+            #$FileTypes = array_merge($this->BinDocFormats, $this->CpuGenFormats, $this->PlainFormats)
887
+            $FileTypes = $ENV->CATS->{11}->Formats
1119 888
         );
1120 889
 
1121 890
 
@@ -1126,7 +895,8 @@ HTML;
1126 895
             $trID = 'archive_tr',
1127 896
             $Label = 'Archive',
1128 897
             $Torrent = $Torrent,
1129
-            $FileTypes = $this->Archives
898
+            # $ENV->Archives nests -1 deep
899
+            $FileTypes = [$ENV->META->Formats->Archives]
1130 900
         );
1131 901
 
1132 902
 
@@ -1264,47 +1034,31 @@ HTML;
1264 1034
             $TorrentImage = display_str($Torrent['Image']);
1265 1035
             $Disabled = $this->Disabled;
1266 1036
 
1267
-            echo <<<HTML
1268
-            <tr id="cover_tr">
1269
-            <td>
1270
-              <label for="image">
1271
-                Picture
1272
-              </label>
1273
-            </td>
1274
-            
1275
-            <td>
1276
-              <input type="text" id="image" name="image" size="60"
1277
-                placeholder="A meaningful picture, e.g., the specimen or a thumbnail"
1278
-                value="$TorrentImage" $Disabled? />
1279
-            </td>
1280
-          </tr>
1281
-HTML;
1037
+            echo $Twig->render(
1038
+                'torrent_form/picture.html',
1039
+                [
1040
+                    'db' => $ENV->DB->picture,
1041
+                    'picture' => $TorrentImage,
1042
+                ]
1043
+            );
1282 1044
         }
1283 1045
 
1284 1046
 
1285 1047
         /**
1286 1048
          * Mirrors
1287 1049
          *
1288
-         * This should be in the `torrents` table not `torrents_group`.
1050
+         * This should be in the `torrents` table not `torrents_group.`
1289 1051
          * The intended use is for web seeds, Dat mirrors, etc.
1290 1052
          */
1291 1053
         if (!$this->DisabledFlag && $this->NewTorrent) {
1292 1054
             $TorrentMirrors = display_str($Torrent['Mirrors']);
1293
-            echo <<<HTML
1294
-            <tr id="mirrors_tr">
1295
-              <td>
1296
-                <label for="mirrors">
1297
-                  Mirrors
1298
-                </label>
1299
-              </td>
1300
-              
1301
-              <td>
1302
-                <!-- Needs to be all on one line -->
1303
-                <textarea rows="2" name="mirrors" id="mirrors"
1304
-                  placeholder="Up to two FTP/HTTP addresses that either point directly to a file, or for multi-file torrents, to the enclosing folder">$TorrentMirrors</textarea>
1305
-              </td>
1306
-            </tr>
1307
-HTML;
1055
+            echo $Twig->render(
1056
+                'torrent_form/mirrors.html',
1057
+                [
1058
+                  'db' => $ENV->DB->mirrors,
1059
+                  'mirrors' => $TorrentMirrors,
1060
+              ]
1061
+            );
1308 1062
         }
1309 1063
 
1310 1064
 
@@ -1337,6 +1091,22 @@ HTML;
1337 1091
         }
1338 1092
 
1339 1093
 
1094
+        /**
1095
+         * Seqhash
1096
+         */
1097
+
1098
+        if ($ENV->FEATURE_BIOPHP && !$this->DisabledFlag && $this->NewTorrent) {
1099
+            $TorrentSeqhash = display_str($Torrent['Seqhash']);
1100
+            echo $Twig->render(
1101
+                'torrent_form/seqhash.html',
1102
+                [
1103
+                    'db' => $ENV->DB->seqhash,
1104
+                    'seqhash' => $TorrentSeqhash,
1105
+                ]
1106
+            );
1107
+        }
1108
+
1109
+
1340 1110
         /**
1341 1111
          * Torrent group description
1342 1112
          *
@@ -1455,7 +1225,6 @@ HTML;
1455 1225
         echo '</table>';
1456 1226
 
1457 1227
         # Drink a stiff one
1458
-        $this->foot();
1459 1228
         G::$DB->set_query_id($QueryID);
1460 1229
     } # End upload_form()
1461 1230
 } # End TorrentForm()

+ 248
- 93
classes/torrents.class.php View File

@@ -60,54 +60,98 @@ class Torrents
60 60
                 unset($GroupIDs[$i], $Found[$GroupID], $NotFound[$GroupID]);
61 61
                 continue;
62 62
             }
63
+
63 64
             $Data = G::$Cache->get_value($Key . $GroupID, true);
64 65
             if (!empty($Data) && is_array($Data) && $Data['ver'] === Cache::GROUP_VERSION) {
65 66
                 unset($NotFound[$GroupID]);
66 67
                 $Found[$GroupID] = $Data['d'];
67 68
             }
68 69
         }
70
+
69 71
         // Make sure there's something in $GroupIDs, otherwise the SQL will break
70 72
         if (count($GroupIDs) === 0) {
71 73
             return [];
72 74
         }
73 75
 
74
-        /*
75
-        Changing any of these attributes returned will cause very large, very dramatic site-wide chaos.
76
-        Do not change what is returned or the order thereof without updating:
77
-          torrents, artists, collages, bookmarks, better, the front page,
78
-        and anywhere else the get_groups function is used.
79
-        Update self::array_group(), too
80
-        */
76
+        /**
77
+         * Changing any of these attributes returned will cause very large, very dramatic site-wide chaos.
78
+         * Do not change what is returned or the order thereof without updating:
79
+         * torrents, artists, collages, bookmarks, better, the front page,
80
+         * and anywhere else the get_groups function is used.
81
+         * Update self::array_group(), too.
82
+         */
81 83
 
82 84
         if (count($NotFound) > 0) {
83 85
             $IDs = implode(',', array_keys($NotFound));
84 86
             $NotFound = [];
85 87
             $QueryID = G::$DB->get_query_id();
86
-            G::$DB->query("
88
+
89
+            G::$DB->prepare_query("
87 90
             SELECT
88
-              ID, Name, Title2, NameJP, Year, CatalogueNumber, Studio, Series, TagList, WikiImage, CategoryID
89
-            FROM torrents_group
90
-              WHERE ID IN ($IDs)");
91
+              `id`,
92
+              `title`,
93
+              `subject`,
94
+              `object`,
95
+              `year`,
96
+              `identifier`,
97
+              `workgroup`,
98
+              `location`,
99
+              `tag_list`,
100
+              `picture`,
101
+              `category_id`
102
+            FROM
103
+              `torrents_group`
104
+            WHERE
105
+              `id` IN($IDs)
106
+            ");
107
+            G::$DB->exec_prepared_query();
91 108
 
92 109
             while ($Group = G::$DB->next_record(MYSQLI_ASSOC, true)) {
93
-                $NotFound[$Group['ID']] = $Group;
94
-                $NotFound[$Group['ID']]['Torrents'] = [];
95
-                $NotFound[$Group['ID']]['Artists'] = [];
110
+                $NotFound[$Group['id']] = $Group;
111
+                $NotFound[$Group['id']]['Torrents'] = [];
112
+                $NotFound[$Group['id']]['Artists'] = [];
96 113
             }
97 114
             G::$DB->set_query_id($QueryID);
98 115
 
99 116
             if ($Torrents) {
100 117
                 $QueryID = G::$DB->get_query_id();
118
+
101 119
                 G::$DB->query("
102
-                  SELECT
103
-                  ID, GroupID, Media, Container, Codec, Resolution, Version,
104
-                    Censored, Archive, FileCount, FreeTorrent,
105
-                    Size, Leechers, Seeders, Snatched, Time, f.ExpiryTime, ID AS HasFile,
106
-                    FreeLeechType, hex(info_hash) as info_hash
107
-                  FROM torrents
108
-                    LEFT JOIN shop_freeleeches AS f ON f.TorrentID=ID
109
-                    WHERE GroupID IN ($IDs)
110
-                  ORDER BY GroupID, Media, Container, Codec, ID");
120
+                SELECT
121
+                  `ID`,
122
+                  `GroupID`,
123
+                  `Media`,
124
+                  `Container`,
125
+                  `Codec`,
126
+                  `Resolution`,
127
+                  `Version`,
128
+                  `Censored`,
129
+                  `Archive`,
130
+                  `FileCount`,
131
+                  `FreeTorrent`,
132
+                  `Size`,
133
+                  `Leechers`,
134
+                  `Seeders`,
135
+                  `Snatched`,
136
+                  `Time`,
137
+                  f.`ExpiryTime`,
138
+                  `ID` AS `HasFile`,
139
+                  `FreeLeechType`,
140
+                  HEX(`info_hash`) AS `info_hash`
141
+                FROM
142
+                  `torrents`
143
+                LEFT JOIN `shop_freeleeches` AS f
144
+                ON
145
+                  f.`TorrentID` = `ID`
146
+                WHERE
147
+                  `GroupID` IN($IDs)
148
+                ORDER BY
149
+                  `GroupID`,
150
+                  `Media`,
151
+                  `Container`,
152
+                  `Codec`,
153
+                  `ID`
154
+                ");
111 155
                   
112 156
                 while ($Torrent = G::$DB->next_record(MYSQLI_ASSOC, true)) {
113 157
                     $NotFound[$Torrent['GroupID']]['Torrents'][$Torrent['ID']] = $Torrent;
@@ -138,6 +182,7 @@ class Torrents
138 182
                 }
139 183
                 $Found[$GroupID]['Artists'] = $Data;
140 184
             }
185
+
141 186
             // Fetch all user specific torrent properties
142 187
             if ($Torrents) {
143 188
                 foreach ($Found as &$Group) {
@@ -166,18 +211,18 @@ class Torrents
166 211
     public static function array_group(array &$Group)
167 212
     {
168 213
         return array(
169
-          'GroupID' => $Group['ID'],
170
-          'GroupName' => $Group['Name'],
171
-          'GroupTitle2' => $Group['Title2'],
172
-          'GroupNameJP' => $Group['NameJP'],
173
-          'GroupYear' => $Group['Year'],
174
-          'GroupCategoryID' => $Group['CategoryID'],
175
-          'GroupCatalogueNumber' => $Group['CatalogueNumber'],
176
-          'GroupStudio' => $Group['Studio'],
177
-          'GroupSeries' => $Group['Series'],
214
+          'id' => $Group['id'],
215
+          'title' => $Group['title'],
216
+          'subject' => $Group['subject'],
217
+          'object' => $Group['object'],
218
+          'published' => $Group['published'],
219
+          'category_id' => $Group['category_id'],
220
+          'identifier' => $Group['identifier'],
221
+          'workgroup' => $Group['workgroup'],
222
+          'location' => $Group['location'],
178 223
           'GroupFlags' => ($Group['Flags'] ?? ''),
179
-          'TagList' => $Group['TagList'],
180
-          'WikiImage' => $Group['WikiImage'],
224
+          'tag_list' => $Group['tag_list'],
225
+          'picture' => $Group['picture'],
181 226
           'Torrents' => $Group['Torrents'],
182 227
           'Artists' => $Group['Artists']
183 228
         );
@@ -231,18 +276,24 @@ class Torrents
231 276
     {
232 277
         global $Time;
233 278
         $QueryID = G::$DB->get_query_id();
234
-        G::$DB->query(
235
-            "
236
-        INSERT INTO group_log
237
-          (GroupID, TorrentID, UserID, Info, Time, Hidden)
238
-        VALUES
239
-          (?, ?, ?, ?, NOW(), ?)",
240
-            $GroupID,
241
-            $TorrentID,
242
-            $UserID,
243
-            $Message,
244
-            $Hidden
245
-        );
279
+        G::$DB->query("
280
+        INSERT INTO `group_log`(
281
+          `GroupID`,
282
+          `TorrentID`,
283
+          `UserID`,
284
+          `Info`,
285
+          `Time`,
286
+          `Hidden`
287
+        )
288
+        VALUES(
289
+          '$GroupID',
290
+          '$TorrentID',
291
+          '$UserID',
292
+          '$Message',
293
+          NOW(),
294
+          '$Hidden'
295
+        )
296
+        ");
246 297
         G::$DB->set_query_id($QueryID);
247 298
     }
248 299
 
@@ -371,12 +422,19 @@ class Torrents
371 422
 
372 423
         Misc::write_log("Group $GroupID automatically deleted (No torrents have this group).");
373 424
 
374
-        G::$DB->query("
375
-        SELECT CategoryID
376
-        FROM torrents_group
377
-          WHERE ID = ?", $GroupID);
425
+        G::$DB->prepare_query("
426
+        SELECT
427
+          `category_id`
428
+        FROM
429
+          `torrents_group`
430
+        WHERE
431
+          `id` = '$GroupID'
432
+        ");
433
+        G::$DB->exec_prepared_query();
378 434
         list($Category) = G::$DB->next_record();
379
-        if ($Category == 1) {
435
+
436
+        # todo: Check strict equality here
437
+        if ($Category === 1) {
380 438
             G::$Cache->decrement('stats_album_count');
381 439
         }
382 440
         G::$Cache->decrement('stats_group_count');
@@ -459,18 +517,41 @@ class Torrents
459 517
         // Comments
460 518
         Comments::delete_page('torrents', $GroupID);
461 519
 
462
-        G::$DB->query("
463
-        DELETE FROM torrents_group
464
-          WHERE ID = ?", $GroupID);
465
-        G::$DB->query("
466
-        DELETE FROM torrents_tags
467
-          WHERE GroupID = ?", $GroupID);
468
-        G::$DB->query("
469
-        DELETE FROM bookmarks_torrents
470
-          WHERE GroupID = ?", $GroupID);
471
-        G::$DB->query("
472
-        DELETE FROM wiki_torrents
473
-          WHERE PageID = ?", $GroupID);
520
+        G::$DB->prepare_query("
521
+        DELETE
522
+        FROM
523
+          `torrents_group`
524
+        WHERE
525
+          `id` = '$GroupID'
526
+        ");
527
+        G::$DB->exec_prepared_query();
528
+
529
+        G::$DB->prepare_query("
530
+        DELETE
531
+        FROM
532
+          `torrents_tags`
533
+        WHERE
534
+          `GroupID` = '$GroupID'
535
+        ");
536
+        G::$DB->exec_prepared_query();
537
+
538
+        G::$DB->prepare_query("
539
+        DELETE
540
+        FROM
541
+          `bookmarks_torrents`
542
+        WHERE
543
+          `GroupID` = '$GroupID'
544
+        ");
545
+        G::$DB->exec_prepared_query();
546
+
547
+        G::$DB->prepare_query("
548
+        DELETE
549
+        FROM
550
+          `wiki_torrents`
551
+        WHERE
552
+          `PageID` = '$GroupID'
553
+        ");
554
+        G::$DB->exec_prepared_query();
474 555
 
475 556
         G::$Cache->delete_value("torrents_details_$GroupID");
476 557
         G::$Cache->delete_value("torrent_group_$GroupID");
@@ -483,55 +564,129 @@ class Torrents
483 564
      *
484 565
      * @param int $GroupID
485 566
      */
486
-    public static function update_hash($GroupID)
567
+    public static function update_hash(int $GroupID)
487 568
     {
488 569
         $QueryID = G::$DB->get_query_id();
489 570
 
490
-        G::$DB->query("
491
-        UPDATE torrents_group
492
-        SET TagList = (
493
-          SELECT REPLACE(GROUP_CONCAT(tags.Name SEPARATOR ' '), '.', '_')
494
-          FROM torrents_tags AS t
495
-            INNER JOIN tags ON tags.ID = t.TagID
496
-          WHERE t.GroupID = ?
497
-          GROUP BY t.GroupID
571
+        G::$DB->prepare_query("
572
+        UPDATE
573
+          `torrents_group`
574
+        SET
575
+          `tag_list` =(
576
+          SELECT
577
+          REPLACE
578
+            (
579
+              GROUP_CONCAT(tags.Name SEPARATOR ' '),
580
+              '.',
581
+              '_'
582
+            )
583
+          FROM
584
+            `torrents_tags` AS t
585
+          INNER JOIN `tags` ON tags.`ID` = t.`TagID`
586
+          WHERE
587
+            t.`GroupID` = '$GroupID'
588
+          GROUP BY
589
+            t.`GroupID`
498 590
         )
499
-          WHERE ID = ?", $GroupID, $GroupID);
591
+        WHERE
592
+          `ID` = '$GroupID'
593
+        ");
594
+        G::$DB->exec_prepared_query();
500 595
 
501 596
         // Fetch album artists
502
-        G::$DB->query("
503
-        SELECT GROUP_CONCAT(ag.Name separator ' ')
504
-        FROM torrents_artists AS ta
505
-          JOIN artists_group AS ag ON ag.ArtistID = ta.ArtistID
506
-          WHERE ta.GroupID = ?
507
-        GROUP BY ta.GroupID", $GroupID);
597
+        G::$DB->prepare_query("
598
+        SELECT GROUP_CONCAT(ag.`Name` separator ' ')
599
+        FROM `torrents_artists` AS `ta`
600
+          JOIN `artists_group` AS ag ON ag.`ArtistID` = ta.`ArtistID`
601
+          WHERE ta.`GroupID` = '$GroupID'
602
+        GROUP BY ta.`GroupID`
603
+        ");
604
+        G::$DB->exec_prepared_query();
605
+
508 606
         if (G::$DB->has_results()) {
509 607
             list($ArtistName) = G::$DB->next_record(MYSQLI_NUM, false);
510 608
         } else {
511 609
             $ArtistName = '';
512 610
         }
513 611
 
514
-        G::$DB->query("
515
-        REPLACE INTO sphinx_delta
516
-          (ID, GroupID, GroupName, GroupTitle2, GroupNameJP, TagList, Year, CatalogueNumber, CategoryID, Time,
517
-          Size, Snatched, Seeders, Leechers, Censored, Studio, Series,
518
-          FreeTorrent, Media, Container, Codec, Resolution, Version, Description,
519
-          FileList, ArtistName)
612
+        G::$DB->prepare_query("
613
+        REPLACE
614
+        INTO sphinx_delta(
615
+          `ID`,
616
+          `GroupID`,
617
+          `GroupName`,
618
+          `GroupTitle2`,
619
+          `GroupNameJP`,
620
+          `TagList`,
621
+          `Year`,
622
+          `CatalogueNumber`,
623
+          `CategoryID`,
624
+          `Time`,
625
+          `Size`,
626
+          `Snatched`,
627
+          `Seeders`,
628
+          `Leechers`,
629
+          `Censored`,
630
+          `Studio`,
631
+          `Series`,
632
+          `FreeTorrent`,
633
+          `Media`,
634
+          `Container`,
635
+          `Codec`,
636
+          `Resolution`,
637
+          `Version`,
638
+          `Description`,
639
+          `FileList`,
640
+          `ArtistName`
641
+        )
520 642
         SELECT
521
-          t.ID, g.ID, Name, Title2, NameJP, TagList, Year, CatalogueNumber, CategoryID, UNIX_TIMESTAMP(t.Time),
522
-          Size, Snatched, Seeders, Leechers, Censored, Studio, Series,
523
-          CAST(FreeTorrent AS CHAR), Media, Container, Codec, Resolution, Version, Description,
524
-          REPLACE(REPLACE(FileList, '_', ' '), '/', ' ') AS FileList, ?
525
-        FROM torrents AS t
526
-          JOIN torrents_group AS g ON g.ID = t.GroupID
527
-          WHERE g.ID = ?", $ArtistName, $GroupID);
643
+          t.`ID`,
644
+          g.`id`,
645
+          g.`title`,
646
+          g.`subject`,
647
+          g.`object`,
648
+          g.`tag_list`,
649
+          g.`year`,
650
+          g.`identifier`,
651
+          g.`category_id`,
652
+          UNIX_TIMESTAMP(t.`Time`),
653
+          t.`Size`,
654
+          t.`Snatched`,
655
+          t.`Seeders`,
656
+          t.`Leechers`,
657
+          t.`Censored`,
658
+          g.`workgroup`,
659
+          g.`location`,
660
+          CAST(`FreeTorrent` AS CHAR),
661
+          t.`Media`,
662
+          t.`Container`,
663
+          t.`Codec`,
664
+          t.`Resolution`,
665
+          t.`Version`,
666
+          t.`Description`,
667
+        REPLACE
668
+          (
669
+        REPLACE
670
+          (`FileList`, '_', ' '),
671
+          '/',
672
+          ' '
673
+        ) AS FileList,
674
+        '$ArtistName'
675
+        FROM
676
+          `torrents` AS t
677
+        JOIN `torrents_group` AS g
678
+        ON
679
+          g.`id` = t.`GroupID`
680
+        WHERE
681
+          g.`id` = '$GroupID'
682
+        ");
683
+        G::$DB->exec_prepared_query();
528 684
 
529 685
         G::$Cache->delete_value("torrents_details_$GroupID");
530 686
         G::$Cache->delete_value("torrent_group_$GroupID");
531 687
         G::$Cache->delete_value("torrent_group_light_$GroupID");
532 688
 
533 689
         $ArtistInfo = Artists::get_artist($GroupID);
534
-
535 690
         G::$Cache->delete_value("groups_artists_$GroupID");
536 691
         G::$DB->set_query_id($QueryID);
537 692
     }

+ 277
- 0
classes/twig.class.php View File

@@ -0,0 +1,277 @@
1
+<?php
2
+declare(strict_types=1);
3
+
4
+/**
5
+ * Twig class
6
+ *
7
+ * Converted to a singleton class like $ENV.
8
+ * One instance should only ever exist,
9
+ * because of its separate disk cache system.
10
+ *
11
+ * Based on OPS's useful rule set:
12
+ * https://github.com/OPSnet/Gazelle/blob/master/app/Util/Twig.php
13
+ */
14
+
15
+class Twig
16
+{
17
+    /**
18
+     * __functions
19
+     */
20
+
21
+    private static $Twig = null;
22
+
23
+    private function __construct()
24
+    {
25
+        return;
26
+    }
27
+
28
+    public function __clone()
29
+    {
30
+        return trigger_error(
31
+            'clone() not allowed',
32
+            E_USER_ERROR
33
+        );
34
+    }
35
+
36
+    public function __wakeup()
37
+    {
38
+        return trigger_error(
39
+            'wakeup() not allowed',
40
+            E_USER_ERROR
41
+        );
42
+    }
43
+
44
+
45
+    /**
46
+     * go
47
+     */
48
+    public static function go()
49
+    {
50
+        return (self::$Twig === null)
51
+            ? self::$Twig = Twig::factory()
52
+            : self::$Twig;
53
+    }
54
+
55
+
56
+    /**
57
+     * factory
58
+     */
59
+
60
+    private static function factory(): \Twig\Environment
61
+    {
62
+        $ENV = ENV::go();
63
+
64
+        # https://twig.symfony.com/doc/3.x/api.html
65
+        $Twig = new \Twig\Environment(
66
+            new \Twig\Loader\FilesystemLoader("$ENV->SERVER_ROOT/templates"),
67
+            [
68
+                'auto_reload' => true,
69
+                'autoescape' => 'name',
70
+                'cache' => "$ENV->WEB_ROOT/cache/twig",
71
+                'debug' => $ENV->DEV,
72
+                'strict_variables' => true,
73
+        ]
74
+        );
75
+
76
+        $Twig->addFilter(new \Twig\TwigFilter(
77
+            'article',
78
+            function ($word) {
79
+                return preg_match('/^[aeiou]/i', $word) ? 'an' : 'a';
80
+            }
81
+        ));
82
+
83
+        $Twig->addFilter(new \Twig\TwigFilter(
84
+            'b64',
85
+            function (string $binary) {
86
+                return base64_encode($binary);
87
+            }
88
+        ));
89
+
90
+        $Twig->addFilter(new \Twig\TwigFilter(
91
+            'bb_format',
92
+            function ($text) {
93
+                return new \Twig\Markup(\Text::full_format($text), 'UTF-8');
94
+            }
95
+        ));
96
+
97
+        $Twig->addFilter(new \Twig\TwigFilter(
98
+            'checked',
99
+            function ($isChecked) {
100
+                return $isChecked ? ' checked="checked"' : '';
101
+            }
102
+        ));
103
+
104
+        $Twig->addFilter(new \Twig\TwigFilter(
105
+            'image',
106
+            function ($i) {
107
+                return new \Twig\Markup(\ImageTools::process($i, true), 'UTF-8');
108
+            }
109
+        ));
110
+
111
+        $Twig->addFilter(new \Twig\TwigFilter(
112
+            'ipaddr',
113
+            function ($ipaddr) {
114
+                return new \Twig\Markup(\Tools::display_ip($ipaddr), 'UTF-8');
115
+            }
116
+        ));
117
+
118
+        $Twig->addFilter(new \Twig\TwigFilter(
119
+            'octet_size',
120
+            function ($size, array $option = []) {
121
+                return \Format::get_size($size, empty($option) ? 2 : $option[0]);
122
+            },
123
+            ['is_variadic' => true]
124
+        ));
125
+
126
+        $Twig->addFilter(new \Twig\TwigFilter(
127
+            'plural',
128
+            function ($number) {
129
+                return plural($number);
130
+            }
131
+        ));
132
+
133
+        $Twig->addFilter(new \Twig\TwigFilter(
134
+            'selected',
135
+            function ($isSelected) {
136
+                return $isSelected ? ' selected="selected"' : '';
137
+            }
138
+        ));
139
+
140
+        $Twig->addFilter(new \Twig\TwigFilter(
141
+            'shorten',
142
+            function (string $text, int $length) {
143
+                return shortenString($text, $length);
144
+            }
145
+        ));
146
+
147
+        $Twig->addFilter(new \Twig\TwigFilter(
148
+            'time_diff',
149
+            function ($time) {
150
+                return new \Twig\Markup(time_diff($time), 'UTF-8');
151
+            }
152
+        ));
153
+
154
+        $Twig->addFilter(new \Twig\TwigFilter(
155
+            'ucfirst',
156
+            function ($text) {
157
+                return ucfirst($text);
158
+            }
159
+        ));
160
+
161
+        $Twig->addFilter(new \Twig\TwigFilter(
162
+            'ucfirstall',
163
+            function ($text) {
164
+                return implode(' ', array_map(function ($w) {
165
+                    return ucfirst($w);
166
+                }, explode(' ', $text)));
167
+            }
168
+        ));
169
+
170
+        $Twig->addFilter(new \Twig\TwigFilter(
171
+            'user_url',
172
+            function ($userId) {
173
+                return new \Twig\Markup(\Users::format_username($userId, false, false, false), 'UTF-8');
174
+            }
175
+        ));
176
+
177
+        $Twig->addFilter(new \Twig\TwigFilter(
178
+            'user_full',
179
+            function ($userId) {
180
+                return new \Twig\Markup(\Users::format_username($userId, true, true, true, true), 'UTF-8');
181
+            }
182
+        ));
183
+
184
+        $Twig->addFunction(new \Twig\TwigFunction('donor_icon', function ($icon, $userId) {
185
+            return new \Twig\Markup(
186
+                \ImageTools::process($icon, false, 'donoricon', $userId),
187
+                'UTF-8'
188
+            );
189
+        }));
190
+
191
+        $Twig->addFunction(new \Twig\TwigFunction('privilege', function ($default, $config, $key) {
192
+            return new \Twig\Markup(
193
+                (
194
+                    $default
195
+                    ? sprintf(
196
+                        '<input id="%s" type="checkbox" disabled="disabled"%s />&nbsp;',
197
+                        "default_$key",
198
+                        (isset($default[$key]) && $default[$key] ? ' checked="checked"' : '')
199
+                    )
200
+                    : ''
201
+                )
202
+                . sprintf(
203
+                    '<input type="checkbox" name="%s" id="%s" value="1"%s />&nbsp;<label title="%s" for="%s">%s</label><br />',
204
+                    "perm_$key",
205
+                    $key,
206
+                    (empty($config[$key]) ? '' : ' checked="checked"'),
207
+                    $key,
208
+                    $key,
209
+                    \Permissions::list()[$key] ?? "!unknown($key)!"
210
+                ),
211
+                'UTF-8'
212
+            );
213
+        }));
214
+
215
+        $Twig->addFunction(new \Twig\TwigFunction('ratio', function ($up, $down) {
216
+            return new \Twig\Markup(
217
+                \Format::get_ratio_html($up, $down),
218
+                'UTF-8'
219
+            );
220
+        }));
221
+
222
+        $Twig->addFunction(new \Twig\TwigFunction('resolveCountryIpv4', function ($addr) {
223
+            return new \Twig\Markup(
224
+                (function ($ip) {
225
+                    static $cache = [];
226
+                    if (!isset($cache[$ip])) {
227
+                        $Class = strtr($ip, '.', '-');
228
+                        $cache[$ip] = '<span class="cc_'.$Class.'">Resolving CC...'
229
+                            . '<script type="text/javascript">'
230
+                                . '$(document).ready(function() {'
231
+                                    . '$.get(\'tools.php?action=get_cc&ip='.$ip.'\', function(cc) {'
232
+                                        . '$(\'.cc_'.$Class.'\').html(cc);'
233
+                                    . '});'
234
+                                . '});'
235
+                            . '</script></span>';
236
+                    }
237
+                    return $cache[$ip];
238
+                })($addr),
239
+                'UTF-8'
240
+            );
241
+        }));
242
+
243
+        $Twig->addFunction(new \Twig\TwigFunction('resolveIpv4', function ($addr) {
244
+            return new \Twig\Markup(
245
+                (function ($ip) {
246
+                    if (!$ip) {
247
+                        $ip = '127.0.0.1';
248
+                    }
249
+                    static $cache = [];
250
+                    if (!isset($cache[$ip])) {
251
+                        $class = strtr($ip, '.', '-');
252
+                        $cache[$ip] = '<span class="host_' . $class
253
+                            . '">Resolving host' . "\xE2\x80\xA6" . '<script type="text/javascript">$(document).ready(function() {'
254
+                            .  "\$.get('tools.php?action=get_host&ip=$ip', function(host) {\$('.host_$class').html(host)})})</script></span>";
255
+                    }
256
+                    return $cache[$ip];
257
+                })($addr),
258
+                'UTF-8'
259
+            );
260
+        }));
261
+
262
+        $Twig->addFunction(new \Twig\TwigFunction('shorten', function ($text, $length) {
263
+            return new \Twig\Markup(
264
+                shortenString($text, $length),
265
+                'UTF-8'
266
+            );
267
+        }));
268
+
269
+        $Twig->addTest(
270
+            new \Twig\TwigTest('numeric', function ($value) {
271
+                return is_numeric($value);
272
+            })
273
+        );
274
+
275
+        return $Twig;
276
+    }
277
+}

+ 185
- 93
classes/userrank.class.php View File

@@ -1,133 +1,216 @@
1 1
 <?php
2
-#declare(strict_types=1);
2
+declare(strict_types=1);
3 3
 
4 4
 class UserRank
5 5
 {
6
-    const PREFIX = 'percentiles_'; // Prefix for memcache keys, to make life easier
6
+    # Prefix for memcache keys, to make life easier
7
+    const PREFIX = 'percentiles_';
7 8
 
8
-    // Returns a 101 row array (101 percentiles - 0 - 100), with the minimum value for that percentile as the value for each row
9
-    // BTW - ingenious
9
+
10
+    /**
11
+     * Returns a 101 row array (101 percentiles: 0-100),
12
+     * with the minimum value for that percentile as the value for each row.
13
+     *
14
+     * BTW - ingenious
15
+     */
10 16
     private static function build_table($MemKey, $Query)
11 17
     {
12 18
         $QueryID = G::$DB->get_query_id();
13 19
 
14
-        G::$DB->query("
15
-        DROP TEMPORARY TABLE IF EXISTS temp_stats");
20
+        G::$DB->prepare_query("
21
+        DROP TEMPORARY TABLE IF EXISTS
22
+          `temp_stats`
23
+        ");
24
+        G::$DB->exec_prepared_query();
16 25
 
17
-        G::$DB->query("
18
-        CREATE TEMPORARY TABLE temp_stats (
19
-          ID int(10) NOT NULL PRIMARY KEY AUTO_INCREMENT,
20
-          Val bigint(20) NOT NULL
21
-        );");
26
+        G::$DB->prepare_query("
27
+        CREATE TEMPORARY TABLE `temp_stats`(
28
+          `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
29
+          `value` BIGINT NOT NULL
30
+        );
31
+        ");
32
+        G::$DB->exec_prepared_query();
22 33
 
23
-        G::$DB->query("
24
-        INSERT INTO temp_stats (Val) ".
25
-        $Query);
34
+        G::$DB->prepare_query("
35
+        INSERT INTO `temp_stats`(`value`) "
36
+        . $Query
37
+        );
38
+        G::$DB->exec_prepared_query();
26 39
 
27
-        G::$DB->query("
28
-        SELECT COUNT(ID)
29
-        FROM temp_stats");
40
+        G::$DB->prepare_query("
41
+        SELECT
42
+          COUNT(`id`)
43
+        FROM
44
+          `temp_stats`
45
+        ");
46
+        G::$DB->exec_prepared_query();
30 47
         list($UserCount) = G::$DB->next_record();
31 48
 
49
+        $UserCount = (int) $UserCount;
32 50
         G::$DB->query("
33
-        SELECT MIN(Val)
34
-        FROM temp_stats
35
-          GROUP BY CEIL(ID / (".(int)$UserCount." / 100));");
51
+        SELECT
52
+          MIN(`value`)
53
+        FROM
54
+          `temp_stats`
55
+        GROUP BY
56
+          CEIL(`id` /($UserCount / 100));
57
+        ");
58
+        G::$DB->exec_prepared_query();
36 59
 
37 60
         $Table = G::$DB->to_array();
38 61
         G::$DB->set_query_id($QueryID);
39 62
 
40
-        // Give a little variation to the cache length, so all the tables don't expire at the same time
63
+        # Give a little variation to the cache length, so all the tables don't expire at the same time
41 64
         G::$Cache->cache_value($MemKey, $Table, 3600 * 24 * rand(800, 1000) * 0.001);
42 65
 
43 66
         return $Table;
44 67
     }
45 68
 
69
+
70
+    /**
71
+     * table_query
72
+     */
46 73
     private static function table_query($TableName)
47 74
     {
48 75
         switch ($TableName) {
49
-        case 'uploaded':
50
-          $Query =  "
51
-          SELECT Uploaded
52
-          FROM users_main
53
-          WHERE Enabled = '1'
54
-            AND Uploaded > 0
55
-          ORDER BY Uploaded;";
56
-          break;
57
-
58
-        case 'downloaded':
59
-          $Query =  "
60
-          SELECT Downloaded
61
-          FROM users_main
62
-          WHERE Enabled = '1'
63
-            AND Downloaded > 0
64
-          ORDER BY Downloaded;";
65
-          break;
66
-
67
-        case 'uploads':
68
-          $Query = "
69
-          SELECT COUNT(t.ID) AS Uploads
70
-          FROM users_main AS um
71
-            JOIN torrents AS t ON t.UserID = um.ID
72
-          WHERE um.Enabled = '1'
73
-          GROUP BY um.ID
74
-          ORDER BY Uploads;";
75
-          break;
76
-
77
-        case 'requests':
78
-          $Query = "
79
-          SELECT COUNT(r.ID) AS Requests
80
-          FROM users_main AS um
81
-            JOIN requests AS r ON r.FillerID = um.ID
82
-          WHERE um.Enabled = '1'
83
-          GROUP BY um.ID
84
-          ORDER BY Requests;";
85
-          break;
86
-
87
-        case 'posts':
88
-          $Query = "
89
-          SELECT COUNT(p.ID) AS Posts
90
-          FROM users_main AS um
91
-            JOIN forums_posts AS p ON p.AuthorID = um.ID
92
-          WHERE um.Enabled = '1'
93
-          GROUP BY um.ID
94
-          ORDER BY Posts;";
95
-          break;
96
-
97
-        case 'bounty':
98
-          $Query = "
99
-          SELECT SUM(rv.Bounty) AS Bounty
100
-          FROM users_main AS um
101
-            JOIN requests_votes AS rv ON rv.UserID = um.ID
102
-          WHERE um.Enabled = '1' " .
103
-          "GROUP BY um.ID
104
-          ORDER BY Bounty;";
105
-          break;
106
-
107
-        case 'artists':
108
-          $Query = "
109
-          SELECT COUNT(ta.ArtistID) AS Artists
110
-          FROM torrents_artists AS ta
111
-            JOIN torrents_group AS tg ON tg.ID = ta.GroupID
112
-            JOIN torrents AS t ON t.GroupID = tg.ID
113
-          WHERE t.UserID != ta.UserID
114
-          GROUP BY tg.ID
115
-          ORDER BY Artists ASC";
116
-          break;
76
+            case 'uploaded':
77
+            $Query =  "
78
+            SELECT
79
+              `Uploaded`
80
+            FROM
81
+              `users_main`
82
+            WHERE
83
+              `Enabled` = '1' AND `Uploaded` > 0
84
+            ORDER BY
85
+              `Uploaded`;
86
+            ";
87
+            break;
88
+
89
+            case 'downloaded':
90
+            $Query =  "
91
+            SELECT
92
+              `Downloaded`
93
+            FROM
94
+              `users_main`
95
+            WHERE
96
+              `Enabled` = '1' AND `Downloaded` > 0
97
+            ORDER BY
98
+              `Downloaded`;
99
+            ";
100
+            break;
101
+
102
+            case 'uploads':
103
+            $Query = "
104
+            SELECT
105
+              COUNT(t.`ID`) AS `Uploads`
106
+            FROM
107
+              `users_main` AS um
108
+            JOIN `torrents` AS t
109
+            ON
110
+              t.`UserID` = um.`ID`
111
+            WHERE
112
+              um.`Enabled` = '1'
113
+            GROUP BY
114
+              um.`ID`
115
+            ORDER BY
116
+              `Uploads`;
117
+            ";
118
+            break;
119
+
120
+            case 'requests':
121
+            $Query = "
122
+            SELECT
123
+              COUNT(r.`ID`) AS `Requests`
124
+            FROM
125
+              `users_main` AS um
126
+            JOIN `requests` AS r
127
+            ON
128
+              r.`FillerID` = um.`ID`
129
+            WHERE
130
+              um.`Enabled` = '1'
131
+            GROUP BY
132
+              um.`ID`
133
+            ORDER BY
134
+              `Requests`;
135
+            ";
136
+            break;
137
+
138
+            case 'posts':
139
+            $Query = "
140
+            SELECT
141
+              COUNT(p.`ID`) AS `Posts`
142
+            FROM
143
+              `users_main` AS um
144
+            JOIN `forums_posts` AS p
145
+            ON
146
+              p.`AuthorID` = um.`ID`
147
+            WHERE
148
+              um.`Enabled` = '1'
149
+            GROUP BY
150
+              um.`ID`
151
+            ORDER BY
152
+              `Posts`;
153
+            ";
154
+            break;
155
+
156
+            case 'bounty':
157
+            $Query = "
158
+            SELECT
159
+              SUM(rv.`Bounty`) AS `Bounty`
160
+            FROM
161
+              `users_main` AS um
162
+            JOIN `requests_votes` AS rv
163
+            ON
164
+              rv.`UserID` = um.`ID`
165
+            WHERE
166
+              um.`Enabled` = '1'
167
+            GROUP BY
168
+              um.`ID`
169
+            ORDER BY
170
+              `Bounty`;
171
+            ";
172
+            break;
173
+
174
+            case 'artists':
175
+            $Query = "
176
+            SELECT
177
+              COUNT(ta.`ArtistID`) AS `Artists`
178
+            FROM
179
+              `torrents_artists` AS ta
180
+            JOIN `torrents_group` AS tg
181
+            ON
182
+              tg.`id` = ta.`GroupID`
183
+            JOIN `torrents` AS t
184
+            ON
185
+              t.`GroupID` = tg.`id`
186
+            WHERE
187
+              t.`UserID` != ta.`UserID`
188
+            GROUP BY
189
+              tg.`id`
190
+            ORDER BY
191
+              `Artists` ASC
192
+            ";
193
+            break;
117 194
         }
195
+
118 196
         return $Query;
119 197
     }
120 198
 
199
+
200
+    /**
201
+     * get_rank
202
+     */
121 203
     public static function get_rank($TableName, $Value)
122 204
     {
123
-        if ($Value == 0) {
205
+        if ($Value === 0) {
124 206
             return 0;
125 207
         }
126 208
 
127 209
         $Table = G::$Cache->get_value(self::PREFIX.$TableName);
128 210
         if (!$Table) {
129
-            //Cache lock!
211
+            # Cache lock!
130 212
             $Lock = G::$Cache->get_value(self::PREFIX.$TableName.'_lock');
213
+
131 214
             if ($Lock) {
132 215
                 return false;
133 216
             } else {
@@ -140,17 +223,25 @@ class UserRank
140 223
         $LastPercentile = 0;
141 224
         foreach ($Table as $Row) {
142 225
             list($CurValue) = $Row;
226
+
143 227
             if ($CurValue >= $Value) {
144 228
                 return $LastPercentile;
145 229
             }
230
+
146 231
             $LastPercentile++;
147 232
         }
148
-        return 100; // 100th percentile
233
+
234
+        # 100th percentile
235
+        return 100;
149 236
     }
150 237
 
238
+
239
+    /**
240
+     * overall_score
241
+     */
151 242
     public static function overall_score($Uploaded, $Downloaded, $Uploads, $Requests, $Posts, $Bounty, $Artists, $Ratio)
152 243
     {
153
-        // We can do this all in 1 line, but it's easier to read this way
244
+        # We can do this all in 1 line, but it's easier to read this way
154 245
         if ($Ratio > 1) {
155 246
             $Ratio = 1;
156 247
         }
@@ -169,6 +260,7 @@ class UserRank
169 260
         $TotalScore += $Artists;
170 261
         $TotalScore /= (15 + 8 + 25 + 2 + 1 + 1 + 1);
171 262
         $TotalScore *= $Ratio;
263
+        
172 264
         return $TotalScore;
173 265
     }
174 266
 }

+ 1
- 7
classes/users.class.php View File

@@ -457,10 +457,9 @@ class Users
457 457
      * @param boolean $IsEnabled
458 458
      * @param boolean $Class whether or not to show the class
459 459
      * @param boolean $Title whether or not to show the title
460
-     * @param boolean $IsDonorForum for displaying donor forum honorific prefixes and suffixes
461 460
      * @return HTML formatted username
462 461
      */
463
-    public static function format_username($UserID, $Badges = false, $IsWarned = true, $IsEnabled = true, $Class = false, $Title = false, $IsDonorForum = false)
462
+    public static function format_username($UserID, $Badges = false, $IsWarned = true, $IsEnabled = true, $Class = false, $Title = false)
464 463
     {
465 464
         global $Classes;
466 465
 
@@ -490,11 +489,6 @@ class Users
490 489
         # Show donor icon?
491 490
         $ShowDonorIcon = (!in_array('hide_donor_heart', $Paranoia) || $OverrideParanoia);
492 491
 
493
-        if ($IsDonorForum) {
494
-            list($Prefix, $Suffix, $HasComma) = Donations::get_titles($UserID);
495
-            $Username = "$Prefix $Username" . ($HasComma ? ', ' : ' ') . "$Suffix ";
496
-        }
497
-
498 492
         if ($Title) {
499 493
             $Str .= "<strong><a href='user.php?id=$UserID'>$Username</a></strong>";
500 494
         } else {

+ 17
- 4
classes/util.php View File

@@ -383,14 +383,14 @@ function error($Error = 1, $NoHTML = false, $Log = false, $Debug = true) # , $JS
383 383
      * Append $Log
384 384
      * Formerly in sections/error/index.php
385 385
      */
386
-    if ($Log ?? false) {
386
+    if ($Log) {
387 387
         $Message .= " <a href='log.php?search=$Title'>Search Log</a>";
388 388
     }
389 389
 
390 390
     /**
391 391
      * Append $Debug
392 392
      */
393
-    if ($Debug ?? false) {
393
+    if ($Debug) {
394 394
         $DateTime = strftime('%c', $_SERVER['REQUEST_TIME']);
395 395
         $BackTrace = debug_string_backtrace();
396 396
 
@@ -508,7 +508,12 @@ function json_print($Status, $Message)
508 508
         $response = ['status' => $Status, 'response' => []];
509 509
     }
510 510
 
511
-    print(json_encode(add_json_info($response)));
511
+    print(
512
+        json_encode(
513
+            add_json_info($response),
514
+            JSON_UNESCAPED_SLASHES
515
+        )
516
+    );
512 517
 }
513 518
 
514 519
 /**
@@ -516,7 +521,15 @@ function json_print($Status, $Message)
516 521
  */
517 522
 function json_error($Code)
518 523
 {
519
-    echo json_encode(add_json_info(['status' => 'failure', 'error' => $Code, 'response' => []]));
524
+    echo json_encode(
525
+        add_json_info(
526
+            [
527
+                'status' => 'failure',
528
+                'error' => $Code,
529
+                'response' => []
530
+            ]
531
+        )
532
+    );
520 533
     die();
521 534
 }
522 535
 

+ 29
- 53
classes/validate.class.php View File

@@ -46,6 +46,34 @@
46 46
 
47 47
 class Validate
48 48
 {
49
+    /**
50
+     * mirrors
51
+     *
52
+     * @param string $String The raw textarea, e.g., from torrent_form.class.php
53
+     * @param string $Regex  The regex to test against, e.g., from $ENV
54
+     * @return array An array of unique values that match the regex
55
+     */
56
+    public function textarea2array(string $String, string $Regex)
57
+    {
58
+        $ENV = ENV::go();
59
+
60
+        $String = array_map(
61
+            'trim',
62
+            explode(
63
+                "\n",
64
+                $String
65
+            )
66
+        );
67
+        
68
+        return array_unique(
69
+            preg_grep(
70
+                "/^$Regex$/i",
71
+                $String
72
+            )
73
+        );
74
+    }
75
+
76
+    
49 77
     /**
50 78
      * title
51 79
      *
@@ -190,7 +218,7 @@ class Validate
190 218
      * Extension Parser
191 219
      *
192 220
      * Takes an associative array of file types and extension, e.g.,
193
-     * $Archives = [
221
+     * $ENV->META->Archives = [
194 222
      *   '7z'     => ['7z'],
195 223
      *   'bzip2'  => ['bz2', 'bzip2'],
196 224
      *   'gzip'   => ['gz', 'gzip', 'tgz', 'tpz'],
@@ -245,58 +273,6 @@ class Validate
245 273
     }
246 274
 
247 275
 
248
-    /**
249
-     * Make input
250
-     * https://www.w3schools.com/html/html_form_input_types.asp
251
-     *
252
-     * Generate a secure HTML input element.
253
-     * Takes $Type, $Name (also used for id), and $Classes.
254
-     * Outputs a hardened input with the requested attributes.
255
-     *
256
-     * todo: See if this could work, one ugly switch for all forms in sections/
257
-     */
258
-
259
-    public function MakeInput($Type = 'text', $Name = '', $Classes = [])
260
-    {
261
-        if (!is_str($Type) || !is_str($Name)) {
262
-            error('$Type and $Name must be strings');
263
-        }
264
-
265
-        # Intentionally double quoted PHP constructing $HTML
266
-        # <input type='text' id='title_jp' name='title_jp' class='one two'
267
-        $HTML  = "";
268
-        $HTML .= "<input type='$Type' id='$Name' name='$Name'";
269
-        $HTML .= (!empty($Classes)) ? " class='" . implode(' ', $Classes) . "'" : "";
270
-
271
-        /*
272
-        'button'
273
-        'checkbox'
274
-        'color'
275
-        'date'
276
-        'datetime-local'
277
-        'email'
278
-        'file'
279
-        'hidden'
280
-        'image'
281
-        'month'
282
-        'number'
283
-        'password'
284
-        'radio'
285
-        'range'
286
-        'reset'
287
-        'search'
288
-        'submit'
289
-        'tel'
290
-        'text'
291
-        'time'
292
-        'url'
293
-        'week'
294
-        */
295
-                
296
-        return;
297
-    }
298
-    
299
-
300 276
     /**
301 277
      * Legacy class
302 278
      */

+ 0
- 1995
classes/vendor/Parsedown.php
File diff suppressed because it is too large
View File


+ 0
- 687
classes/vendor/ParsedownExtra.php View File

@@ -1,687 +0,0 @@
1
-<?php
2
-#declare(strict_types=1);
3
-
4
-#
5
-#
6
-# Parsedown Extra
7
-# https://github.com/erusev/parsedown-extra
8
-#
9
-# (c) Emanuil Rusev
10
-# http://erusev.com
11
-#
12
-# For the full license information, view the LICENSE file that was distributed
13
-# with this source code.
14
-#
15
-#
16
-
17
-class ParsedownExtra extends Parsedown
18
-{
19
-    # ~
20
-
21
-    const version = '0.8.0';
22
-
23
-    # ~
24
-
25
-    function __construct()
26
-    {
27
-        if (version_compare(parent::version, '1.7.1') < 0)
28
-        {
29
-            throw new Exception('ParsedownExtra requires a later version of Parsedown');
30
-        }
31
-
32
-        $this->BlockTypes[':'] []= 'DefinitionList';
33
-        $this->BlockTypes['*'] []= 'Abbreviation';
34
-
35
-        # identify footnote definitions before reference definitions
36
-        array_unshift($this->BlockTypes['['], 'Footnote');
37
-
38
-        # identify footnote markers before before links
39
-        array_unshift($this->InlineTypes['['], 'FootnoteMarker');
40
-    }
41
-
42
-    #
43
-    # ~
44
-
45
-    function text($text)
46
-    {
47
-        $Elements = $this->textElements($text);
48
-
49
-        # convert to markup
50
-        $markup = $this->elements($Elements);
51
-
52
-        # trim line breaks
53
-        $markup = trim($markup, "\n");
54
-
55
-        # merge consecutive dl elements
56
-
57
-        $markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
58
-
59
-        # add footnotes
60
-
61
-        if (isset($this->DefinitionData['Footnote']))
62
-        {
63
-            $Element = $this->buildFootnoteElement();
64
-
65
-            $markup .= "\n" . $this->element($Element);
66
-        }
67
-
68
-        return $markup;
69
-    }
70
-
71
-    #
72
-    # Blocks
73
-    #
74
-
75
-    #
76
-    # Abbreviation
77
-
78
-    protected function blockAbbreviation($Line)
79
-    {
80
-        if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
81
-        {
82
-            $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
83
-
84
-            $Block = array(
85
-                'hidden' => true,
86
-            );
87
-
88
-            return $Block;
89
-        }
90
-    }
91
-
92
-    #
93
-    # Footnote
94
-
95
-    protected function blockFootnote($Line)
96
-    {
97
-        if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
98
-        {
99
-            $Block = array(
100
-                'label' => $matches[1],
101
-                'text' => $matches[2],
102
-                'hidden' => true,
103
-            );
104
-
105
-            return $Block;
106
-        }
107
-    }
108
-
109
-    protected function blockFootnoteContinue($Line, $Block)
110
-    {
111
-        if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
112
-        {
113
-            return;
114
-        }
115
-
116
-        if (isset($Block['interrupted']))
117
-        {
118
-            if ($Line['indent'] >= 4)
119
-            {
120
-                $Block['text'] .= "\n\n" . $Line['text'];
121
-
122
-                return $Block;
123
-            }
124
-        }
125
-        else
126
-        {
127
-            $Block['text'] .= "\n" . $Line['text'];
128
-
129
-            return $Block;
130
-        }
131
-    }
132
-
133
-    protected function blockFootnoteComplete($Block)
134
-    {
135
-        $this->DefinitionData['Footnote'][$Block['label']] = array(
136
-            'text' => $Block['text'],
137
-            'count' => null,
138
-            'number' => null,
139
-        );
140
-
141
-        return $Block;
142
-    }
143
-
144
-    #
145
-    # Definition List
146
-
147
-    protected function blockDefinitionList($Line, $Block)
148
-    {
149
-        if ( ! isset($Block) or $Block['type'] !== 'Paragraph')
150
-        {
151
-            return;
152
-        }
153
-
154
-        $Element = array(
155
-            'name' => 'dl',
156
-            'elements' => array(),
157
-        );
158
-
159
-        $terms = explode("\n", $Block['element']['handler']['argument']);
160
-
161
-        foreach ($terms as $term)
162
-        {
163
-            $Element['elements'] []= array(
164
-                'name' => 'dt',
165
-                'handler' => array(
166
-                    'function' => 'lineElements',
167
-                    'argument' => $term,
168
-                    'destination' => 'elements'
169
-                ),
170
-            );
171
-        }
172
-
173
-        $Block['element'] = $Element;
174
-
175
-        $Block = $this->addDdElement($Line, $Block);
176
-
177
-        return $Block;
178
-    }
179
-
180
-    protected function blockDefinitionListContinue($Line, array $Block)
181
-    {
182
-        if ($Line['text'][0] === ':')
183
-        {
184
-            $Block = $this->addDdElement($Line, $Block);
185
-
186
-            return $Block;
187
-        }
188
-        else
189
-        {
190
-            if (isset($Block['interrupted']) and $Line['indent'] === 0)
191
-            {
192
-                return;
193
-            }
194
-
195
-            if (isset($Block['interrupted']))
196
-            {
197
-                $Block['dd']['handler']['function'] = 'textElements';
198
-                $Block['dd']['handler']['argument'] .= "\n\n";
199
-
200
-                $Block['dd']['handler']['destination'] = 'elements';
201
-
202
-                unset($Block['interrupted']);
203
-            }
204
-
205
-            $text = substr($Line['body'], min($Line['indent'], 4));
206
-
207
-            $Block['dd']['handler']['argument'] .= "\n" . $text;
208
-
209
-            return $Block;
210
-        }
211
-    }
212
-
213
-    #
214
-    # Header
215
-
216
-    protected function blockHeader($Line)
217
-    {
218
-        $Block = parent::blockHeader($Line);
219
-
220
-        if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
221
-        {
222
-            $attributeString = $matches[1][0];
223
-
224
-            $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
225
-
226
-            $Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
227
-        }
228
-
229
-        return $Block;
230
-    }
231
-
232
-    #
233
-    # Markup
234
-
235
-    protected function blockMarkup($Line)
236
-    {
237
-        if ($this->markupEscaped or $this->safeMode)
238
-        {
239
-            return;
240
-        }
241
-
242
-        if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
243
-        {
244
-            $element = strtolower($matches[1]);
245
-
246
-            if (in_array($element, $this->textLevelElements))
247
-            {
248
-                return;
249
-            }
250
-
251
-            $Block = array(
252
-                'name' => $matches[1],
253
-                'depth' => 0,
254
-                'element' => array(
255
-                    'rawHtml' => $Line['text'],
256
-                    'autobreak' => true,
257
-                ),
258
-            );
259
-
260
-            $length = strlen($matches[0]);
261
-            $remainder = substr($Line['text'], $length);
262
-
263
-            if (trim($remainder) === '')
264
-            {
265
-                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
266
-                {
267
-                    $Block['closed'] = true;
268
-                    $Block['void'] = true;
269
-                }
270
-            }
271
-            else
272
-            {
273
-                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
274
-                {
275
-                    return;
276
-                }
277
-                if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
278
-                {
279
-                    $Block['closed'] = true;
280
-                }
281
-            }
282
-
283
-            return $Block;
284
-        }
285
-    }
286
-
287
-    protected function blockMarkupContinue($Line, array $Block)
288
-    {
289
-        if (isset($Block['closed']))
290
-        {
291
-            return;
292
-        }
293
-
294
-        if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
295
-        {
296
-            $Block['depth'] ++;
297
-        }
298
-
299
-        if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
300
-        {
301
-            if ($Block['depth'] > 0)
302
-            {
303
-                $Block['depth'] --;
304
-            }
305
-            else
306
-            {
307
-                $Block['closed'] = true;
308
-            }
309
-        }
310
-
311
-        if (isset($Block['interrupted']))
312
-        {
313
-            $Block['element']['rawHtml'] .= "\n";
314
-            unset($Block['interrupted']);
315
-        }
316
-
317
-        $Block['element']['rawHtml'] .= "\n".$Line['body'];
318
-
319
-        return $Block;
320
-    }
321
-
322
-    protected function blockMarkupComplete($Block)
323
-    {
324
-        if ( ! isset($Block['void']))
325
-        {
326
-            $Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']);
327
-        }
328
-
329
-        return $Block;
330
-    }
331
-
332
-    #
333
-    # Setext
334
-
335
-    protected function blockSetextHeader($Line, array $Block = null)
336
-    {
337
-        $Block = parent::blockSetextHeader($Line, $Block);
338
-
339
-        if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
340
-        {
341
-            $attributeString = $matches[1][0];
342
-
343
-            $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
344
-
345
-            $Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
346
-        }
347
-
348
-        return $Block;
349
-    }
350
-
351
-    #
352
-    # Inline Elements
353
-    #
354
-
355
-    #
356
-    # Footnote Marker
357
-
358
-    protected function inlineFootnoteMarker($Excerpt)
359
-    {
360
-        if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
361
-        {
362
-            $name = $matches[1];
363
-
364
-            if ( ! isset($this->DefinitionData['Footnote'][$name]))
365
-            {
366
-                return;
367
-            }
368
-
369
-            $this->DefinitionData['Footnote'][$name]['count'] ++;
370
-
371
-            if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
372
-            {
373
-                $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # &raquo; &
374
-            }
375
-
376
-            $Element = array(
377
-                'name' => 'sup',
378
-                'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
379
-                'element' => array(
380
-                    'name' => 'a',
381
-                    'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
382
-                    'text' => $this->DefinitionData['Footnote'][$name]['number'],
383
-                ),
384
-            );
385
-
386
-            return array(
387
-                'extent' => strlen($matches[0]),
388
-                'element' => $Element,
389
-            );
390
-        }
391
-    }
392
-
393
-    private $footnoteCount = 0;
394
-
395
-    #
396
-    # Link
397
-
398
-    protected function inlineLink($Excerpt)
399
-    {
400
-        $Link = parent::inlineLink($Excerpt);
401
-
402
-        $remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : '';
403
-
404
-        if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
405
-        {
406
-            $Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
407
-
408
-            $Link['extent'] += strlen($matches[0]);
409
-        }
410
-
411
-        return $Link;
412
-    }
413
-
414
-    #
415
-    # ~
416
-    #
417
-
418
-    private $currentAbreviation;
419
-    private $currentMeaning;
420
-
421
-    protected function insertAbreviation(array $Element)
422
-    {
423
-        if (isset($Element['text']))
424
-        {
425
-            $Element['elements'] = self::pregReplaceElements(
426
-                '/\b'.preg_quote($this->currentAbreviation, '/').'\b/',
427
-                array(
428
-                    array(
429
-                        'name' => 'abbr',
430
-                        'attributes' => array(
431
-                            'title' => $this->currentMeaning,
432
-                        ),
433
-                        'text' => $this->currentAbreviation,
434
-                    )
435
-                ),
436
-                $Element['text']
437
-            );
438
-
439
-            unset($Element['text']);
440
-        }
441
-
442
-        return $Element;
443
-    }
444
-
445
-    protected function inlineText($text)
446
-    {
447
-        $Inline = parent::inlineText($text);
448
-
449
-        if (isset($this->DefinitionData['Abbreviation']))
450
-        {
451
-            foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
452
-            {
453
-                $this->currentAbreviation = $abbreviation;
454
-                $this->currentMeaning = $meaning;
455
-
456
-                $Inline['element'] = $this->elementApplyRecursiveDepthFirst(
457
-                    array($this, 'insertAbreviation'),
458
-                    $Inline['element']
459
-                );
460
-            }
461
-        }
462
-
463
-        return $Inline;
464
-    }
465
-
466
-    #
467
-    # Util Methods
468
-    #
469
-
470
-    protected function addDdElement(array $Line, array $Block)
471
-    {
472
-        $text = substr($Line['text'], 1);
473
-        $text = trim($text);
474
-
475
-        unset($Block['dd']);
476
-
477
-        $Block['dd'] = array(
478
-            'name' => 'dd',
479
-            'handler' => array(
480
-                'function' => 'lineElements',
481
-                'argument' => $text,
482
-                'destination' => 'elements'
483
-            ),
484
-        );
485
-
486
-        if (isset($Block['interrupted']))
487
-        {
488
-            $Block['dd']['handler']['function'] = 'textElements';
489
-
490
-            unset($Block['interrupted']);
491
-        }
492
-
493
-        $Block['element']['elements'] []= & $Block['dd'];
494
-
495
-        return $Block;
496
-    }
497
-
498
-    protected function buildFootnoteElement()
499
-    {
500
-        $Element = array(
501
-            'name' => 'div',
502
-            'attributes' => array('class' => 'footnotes'),
503
-            'elements' => array(
504
-                array('name' => 'hr'),
505
-                array(
506
-                    'name' => 'ol',
507
-                    'elements' => array(),
508
-                ),
509
-            ),
510
-        );
511
-
512
-        uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
513
-
514
-        foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
515
-        {
516
-            if ( ! isset($DefinitionData['number']))
517
-            {
518
-                continue;
519
-            }
520
-
521
-            $text = $DefinitionData['text'];
522
-
523
-            $textElements = parent::textElements($text);
524
-
525
-            $numbers = range(1, $DefinitionData['count']);
526
-
527
-            $backLinkElements = array();
528
-
529
-            foreach ($numbers as $number)
530
-            {
531
-                $backLinkElements[] = array('text' => ' ');
532
-                $backLinkElements[] = array(
533
-                    'name' => 'a',
534
-                    'attributes' => array(
535
-                        'href' => "#fnref$number:$definitionId",
536
-                        'rev' => 'footnote',
537
-                        'class' => 'footnote-backref',
538
-                    ),
539
-                    'rawHtml' => '&#8617;',
540
-                    'allowRawHtmlInSafeMode' => true,
541
-                    'autobreak' => false,
542
-                );
543
-            }
544
-
545
-            unset($backLinkElements[0]);
546
-
547
-            $n = count($textElements) -1;
548
-
549
-            if ($textElements[$n]['name'] === 'p')
550
-            {
551
-                $backLinkElements = array_merge(
552
-                    array(
553
-                        array(
554
-                            'rawHtml' => '&#160;',
555
-                            'allowRawHtmlInSafeMode' => true,
556
-                        ),
557
-                    ),
558
-                    $backLinkElements
559
-                );
560
-
561
-                unset($textElements[$n]['name']);
562
-
563
-                $textElements[$n] = array(
564
-                    'name' => 'p',
565
-                    'elements' => array_merge(
566
-                        array($textElements[$n]),
567
-                        $backLinkElements
568
-                    ),
569
-                );
570
-            }
571
-            else
572
-            {
573
-                $textElements[] = array(
574
-                    'name' => 'p',
575
-                    'elements' => $backLinkElements
576
-                );
577
-            }
578
-
579
-            $Element['elements'][1]['elements'] []= array(
580
-                'name' => 'li',
581
-                'attributes' => array('id' => 'fn:'.$definitionId),
582
-                'elements' => array_merge(
583
-                    $textElements
584
-                ),
585
-            );
586
-        }
587
-
588
-        return $Element;
589
-    }
590
-
591
-    # ~
592
-
593
-    protected function parseAttributeData($attributeString)
594
-    {
595
-        $Data = array();
596
-
597
-        $attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
598
-
599
-        foreach ($attributes as $attribute)
600
-        {
601
-            if ($attribute[0] === '#')
602
-            {
603
-                $Data['id'] = substr($attribute, 1);
604
-            }
605
-            else # "."
606
-            {
607
-                $classes []= substr($attribute, 1);
608
-            }
609
-        }
610
-
611
-        if (isset($classes))
612
-        {
613
-            $Data['class'] = implode(' ', $classes);
614
-        }
615
-
616
-        return $Data;
617
-    }
618
-
619
-    # ~
620
-
621
-    protected function processTag($elementMarkup) # recursive
622
-    {
623
-        # http://stackoverflow.com/q/1148928/200145
624
-        libxml_use_internal_errors(true);
625
-
626
-        $DOMDocument = new DOMDocument;
627
-
628
-        # http://stackoverflow.com/q/11309194/200145
629
-        $elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
630
-
631
-        # http://stackoverflow.com/q/4879946/200145
632
-        $DOMDocument->loadHTML($elementMarkup);
633
-        $DOMDocument->removeChild($DOMDocument->doctype);
634
-        $DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
635
-
636
-        $elementText = '';
637
-
638
-        if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
639
-        {
640
-            foreach ($DOMDocument->documentElement->childNodes as $Node)
641
-            {
642
-                $elementText .= $DOMDocument->saveHTML($Node);
643
-            }
644
-
645
-            $DOMDocument->documentElement->removeAttribute('markdown');
646
-
647
-            $elementText = "\n".$this->text($elementText)."\n";
648
-        }
649
-        else
650
-        {
651
-            foreach ($DOMDocument->documentElement->childNodes as $Node)
652
-            {
653
-                $nodeMarkup = $DOMDocument->saveHTML($Node);
654
-
655
-                if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
656
-                {
657
-                    $elementText .= $this->processTag($nodeMarkup);
658
-                }
659
-                else
660
-                {
661
-                    $elementText .= $nodeMarkup;
662
-                }
663
-            }
664
-        }
665
-
666
-        # because we don't want for markup to get encoded
667
-        $DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
668
-
669
-        $markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
670
-        $markup = str_replace('placeholder\x1A', $elementText, $markup);
671
-
672
-        return $markup;
673
-    }
674
-
675
-    # ~
676
-
677
-    protected function sortFootnotes($A, $B) # callback
678
-    {
679
-        return $A['number'] - $B['number'];
680
-    }
681
-
682
-    #
683
-    # Fields
684
-    #
685
-
686
-    protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
687
-}

+ 0
- 411
classes/vendor/TwitterAPIExchange.php View File

@@ -1,411 +0,0 @@
1
-<?php
2
-#declare(strict_types=1);
3
-
4
-/**
5
- * Twitter-API-PHP : Simple PHP wrapper for the v1.1 API
6
- *
7
- * PHP version 5.3.10
8
- *
9
- * @category Awesomeness
10
- * @package  Twitter-API-PHP
11
- * @author   James Mallison <me@j7mbo.co.uk>
12
- * @license  MIT License
13
- * @version  1.0.4
14
- * @link     http://github.com/j7mbo/twitter-api-php
15
- */
16
-class TwitterAPIExchange
17
-{
18
-    /**
19
-     * @var string
20
-     */
21
-    private $oauth_access_token;
22
-
23
-    /**
24
-     * @var string
25
-     */
26
-    private $oauth_access_token_secret;
27
-
28
-    /**
29
-     * @var string
30
-     */
31
-    private $consumer_key;
32
-
33
-    /**
34
-     * @var string
35
-     */
36
-    private $consumer_secret;
37
-
38
-    /**
39
-     * @var array
40
-     */
41
-    private $postfields;
42
-
43
-    /**
44
-     * @var string
45
-     */
46
-    private $getfield;
47
-
48
-    /**
49
-     * @var mixed
50
-     */
51
-    protected $oauth;
52
-
53
-    /**
54
-     * @var string
55
-     */
56
-    public $url;
57
-
58
-    /**
59
-     * @var string
60
-     */
61
-    public $requestMethod;
62
-
63
-    /**
64
-     * The HTTP status code from the previous request
65
-     *
66
-     * @var int
67
-     */
68
-    protected $httpStatusCode;
69
-
70
-    /**
71
-     * Create the API access object. Requires an array of settings::
72
-     * oauth access token, oauth access token secret, consumer key, consumer secret
73
-     * These are all available by creating your own application on dev.twitter.com
74
-     * Requires the cURL library
75
-     *
76
-     * @throws \RuntimeException When cURL isn't loaded
77
-     * @throws \InvalidArgumentException When incomplete settings parameters are provided
78
-     *
79
-     * @param array $settings
80
-     */
81
-    public function __construct(array $settings)
82
-    {
83
-        if (!function_exists('curl_init'))
84
-        {
85
-            throw new RuntimeException('TwitterAPIExchange requires cURL extension to be loaded, see: http://curl.haxx.se/docs/install.html');
86
-        }
87
-
88
-        if (!isset($settings['oauth_access_token'])
89
-            || !isset($settings['oauth_access_token_secret'])
90
-            || !isset($settings['consumer_key'])
91
-            || !isset($settings['consumer_secret']))
92
-        {
93
-            throw new InvalidArgumentException('Incomplete settings passed to TwitterAPIExchange');
94
-        }
95
-
96
-        $this->oauth_access_token = $settings['oauth_access_token'];
97
-        $this->oauth_access_token_secret = $settings['oauth_access_token_secret'];
98
-        $this->consumer_key = $settings['consumer_key'];
99
-        $this->consumer_secret = $settings['consumer_secret'];
100
-    }
101
-
102
-    /**
103
-     * Set postfields array, example: array('screen_name' => 'J7mbo')
104
-     *
105
-     * @param array $array Array of parameters to send to API
106
-     *
107
-     * @throws \Exception When you are trying to set both get and post fields
108
-     *
109
-     * @return TwitterAPIExchange Instance of self for method chaining
110
-     */
111
-    public function setPostfields(array $array)
112
-    {
113
-        if (!is_null($this->getGetfield()))
114
-        {
115
-            throw new Exception('You can only choose get OR post fields (post fields include put).');
116
-        }
117
-
118
-        if (isset($array['status']) && substr($array['status'], 0, 1) === '@')
119
-        {
120
-            $array['status'] = sprintf("\0%s", $array['status']);
121
-        }
122
-
123
-        foreach ($array as $key => &$value)
124
-        {
125
-            if (is_bool($value))
126
-            {
127
-                $value = ($value === true) ? 'true' : 'false';
128
-            }
129
-        }
130
-
131
-        $this->postfields = $array;
132
-
133
-        // rebuild oAuth
134
-        if (isset($this->oauth['oauth_signature']))
135
-        {
136
-            $this->buildOauth($this->url, $this->requestMethod);
137
-        }
138
-
139
-        return $this;
140
-    }
141
-
142
-    /**
143
-     * Set getfield string, example: '?screen_name=J7mbo'
144
-     *
145
-     * @param string $string Get key and value pairs as string
146
-     *
147
-     * @throws \Exception
148
-     *
149
-     * @return \TwitterAPIExchange Instance of self for method chaining
150
-     */
151
-    public function setGetfield($string)
152
-    {
153
-        if (!is_null($this->getPostfields()))
154
-        {
155
-            throw new Exception('You can only choose get OR post / post fields.');
156
-        }
157
-
158
-        $getfields = preg_replace('/^\?/', '', explode('&', $string));
159
-        $params = array();
160
-
161
-        foreach ($getfields as $field)
162
-        {
163
-            if ($field !== '')
164
-            {
165
-                list($key, $value) = explode('=', $field);
166
-                $params[$key] = $value;
167
-            }
168
-        }
169
-
170
-        $this->getfield = '?' . http_build_query($params, '', '&');
171
-
172
-        return $this;
173
-    }
174
-
175
-    /**
176
-     * Get getfield string (simple getter)
177
-     *
178
-     * @return string $this->getfields
179
-     */
180
-    public function getGetfield()
181
-    {
182
-        return $this->getfield;
183
-    }
184
-
185
-    /**
186
-     * Get postfields array (simple getter)
187
-     *
188
-     * @return array $this->postfields
189
-     */
190
-    public function getPostfields()
191
-    {
192
-        return $this->postfields;
193
-    }
194
-
195
-    /**
196
-     * Build the Oauth object using params set in construct and additionals
197
-     * passed to this method. For v1.1, see: https://dev.twitter.com/docs/api/1.1
198
-     *
199
-     * @param string $url           The API url to use. Example: https://api.twitter.com/1.1/search/tweets.json
200
-     * @param string $requestMethod Either POST or GET
201
-     *
202
-     * @throws \Exception
203
-     *
204
-     * @return \TwitterAPIExchange Instance of self for method chaining
205
-     */
206
-    public function buildOauth($url, $requestMethod)
207
-    {
208
-        if (!in_array(strtolower($requestMethod), array('post', 'get', 'put', 'delete')))
209
-        {
210
-            throw new Exception('Request method must be either POST, GET or PUT or DELETE');
211
-        }
212
-
213
-        $consumer_key              = $this->consumer_key;
214
-        $consumer_secret           = $this->consumer_secret;
215
-        $oauth_access_token        = $this->oauth_access_token;
216
-        $oauth_access_token_secret = $this->oauth_access_token_secret;
217
-
218
-        $oauth = array(
219
-            'oauth_consumer_key' => $consumer_key,
220
-            'oauth_nonce' => time(),
221
-            'oauth_signature_method' => 'HMAC-SHA1',
222
-            'oauth_token' => $oauth_access_token,
223
-            'oauth_timestamp' => time(),
224
-            'oauth_version' => '1.0'
225
-        );
226
-
227
-        $getfield = $this->getGetfield();
228
-
229
-        if (!is_null($getfield))
230
-        {
231
-            $getfields = str_replace('?', '', explode('&', $getfield));
232
-
233
-            foreach ($getfields as $g)
234
-            {
235
-                $split = explode('=', $g);
236
-
237
-                /** In case a null is passed through **/
238
-                if (isset($split[1]))
239
-                {
240
-                    $oauth[$split[0]] = urldecode($split[1]);
241
-                }
242
-            }
243
-        }
244
-
245
-        $postfields = $this->getPostfields();
246
-
247
-        if (!is_null($postfields)) {
248
-            foreach ($postfields as $key => $value) {
249
-                $oauth[$key] = $value;
250
-            }
251
-        }
252
-
253
-        $base_info = $this->buildBaseString($url, $requestMethod, $oauth);
254
-        $composite_key = rawurlencode($consumer_secret) . '&' . rawurlencode($oauth_access_token_secret);
255
-        $oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
256
-        $oauth['oauth_signature'] = $oauth_signature;
257
-
258
-        $this->url           = $url;
259
-        $this->requestMethod = $requestMethod;
260
-        $this->oauth         = $oauth;
261
-
262
-        return $this;
263
-    }
264
-
265
-    /**
266
-     * Perform the actual data retrieval from the API
267
-     *
268
-     * @param boolean $return      If true, returns data. This is left in for backward compatibility reasons
269
-     * @param array   $curlOptions Additional Curl options for this request
270
-     *
271
-     * @throws \Exception
272
-     *
273
-     * @return string json If $return param is true, returns json data.
274
-     */
275
-    public function performRequest($return = true, $curlOptions = array())
276
-    {
277
-        if (!is_bool($return))
278
-        {
279
-            throw new Exception('performRequest parameter must be true or false');
280
-        }
281
-
282
-        $header =  array($this->buildAuthorizationHeader($this->oauth), 'Expect:');
283
-
284
-        $getfield = $this->getGetfield();
285
-        $postfields = $this->getPostfields();
286
-
287
-        if (in_array(strtolower($this->requestMethod), array('put', 'delete')))
288
-        {
289
-            $curlOptions[CURLOPT_CUSTOMREQUEST] = $this->requestMethod;
290
-        }
291
-
292
-        $options = $curlOptions + array(
293
-            CURLOPT_HTTPHEADER => $header,
294
-            CURLOPT_HEADER => false,
295
-            CURLOPT_URL => $this->url,
296
-            CURLOPT_RETURNTRANSFER => true,
297
-            CURLOPT_TIMEOUT => 10,
298
-        );
299
-
300
-        if (!is_null($postfields))
301
-        {
302
-            $options[CURLOPT_POSTFIELDS] = http_build_query($postfields, '', '&');
303
-        }
304
-        else
305
-        {
306
-            if ($getfield !== '')
307
-            {
308
-                $options[CURLOPT_URL] .= $getfield;
309
-            }
310
-        }
311
-
312
-        $feed = curl_init();
313
-        curl_setopt_array($feed, $options);
314
-        $json = curl_exec($feed);
315
-
316
-        $this->httpStatusCode = curl_getinfo($feed, CURLINFO_HTTP_CODE);
317
-
318
-        if (($error = curl_error($feed)) !== '')
319
-        {
320
-            curl_close($feed);
321
-
322
-            throw new \Exception($error);
323
-        }
324
-
325
-        curl_close($feed);
326
-
327
-        return $json;
328
-    }
329
-
330
-    /**
331
-     * Private method to generate the base string used by cURL
332
-     *
333
-     * @param string $baseURI
334
-     * @param string $method
335
-     * @param array  $params
336
-     *
337
-     * @return string Built base string
338
-     */
339
-    private function buildBaseString($baseURI, $method, $params)
340
-    {
341
-        $return = array();
342
-        ksort($params);
343
-
344
-        foreach($params as $key => $value)
345
-        {
346
-            $return[] = rawurlencode($key) . '=' . rawurlencode($value);
347
-        }
348
-
349
-        return $method . "&" . rawurlencode($baseURI) . '&' . rawurlencode(implode('&', $return));
350
-    }
351
-
352
-    /**
353
-     * Private method to generate authorization header used by cURL
354
-     *
355
-     * @param array $oauth Array of oauth data generated by buildOauth()
356
-     *
357
-     * @return string $return Header used by cURL for request
358
-     */
359
-    private function buildAuthorizationHeader(array $oauth)
360
-    {
361
-        $return = 'Authorization: OAuth ';
362
-        $values = array();
363
-
364
-        foreach($oauth as $key => $value)
365
-        {
366
-            if (in_array($key, array('oauth_consumer_key', 'oauth_nonce', 'oauth_signature',
367
-                'oauth_signature_method', 'oauth_timestamp', 'oauth_token', 'oauth_version'))) {
368
-                $values[] = "$key=\"" . rawurlencode($value) . "\"";
369
-            }
370
-        }
371
-
372
-        $return .= implode(', ', $values);
373
-        return $return;
374
-    }
375
-
376
-    /**
377
-     * Helper method to perform our request
378
-     *
379
-     * @param string $url
380
-     * @param string $method
381
-     * @param string $data
382
-     * @param array  $curlOptions
383
-     *
384
-     * @throws \Exception
385
-     *
386
-     * @return string The json response from the server
387
-     */
388
-    public function request($url, $method = 'get', $data = null, $curlOptions = array())
389
-    {
390
-        if (strtolower($method) === 'get')
391
-        {
392
-            $this->setGetfield($data);
393
-        }
394
-        else
395
-        {
396
-            $this->setPostfields($data);
397
-        }
398
-
399
-        return $this->buildOauth($url, $method)->performRequest(true, $curlOptions);
400
-    }
401
-
402
-    /**
403
-     * Get the HTTP status code for the previous request
404
-     *
405
-     * @return integer
406
-     */
407
-    public function getHttpStatusCode()
408
-    {
409
-        return $this->httpStatusCode;
410
-    }
411
-}

+ 2
- 2
classes/wiki.class.php View File

@@ -22,7 +22,7 @@ class Wiki
22 22
         $Aliases = G::$Cache->get_value('wiki_aliases');
23 23
         if (!$Aliases) {
24 24
             $QueryID = G::$DB->get_query_id();
25
-            G::$DB->query("
25
+            G::$DB->prepared_query("
26 26
             SELECT Alias, ArticleID
27 27
             FROM wiki_aliases");
28 28
             $Aliases = G::$DB->to_pair('Alias', 'ArticleID');
@@ -67,7 +67,7 @@ class Wiki
67 67
         $Contents = G::$Cache->get_value('wiki_article_'.$ArticleID);
68 68
         if (!$Contents) {
69 69
             $QueryID = G::$DB->get_query_id();
70
-            G::$DB->query("
70
+            G::$DB->prepared_query("
71 71
             SELECT
72 72
               w.Revision,
73 73
               w.Title,

+ 54
- 0
composer.json View File

@@ -0,0 +1,54 @@
1
+{
2
+  "name": "biotorrents/gazelle",
3
+  "description": "Web framework for private BitTorrent trackers using Ocelot",
4
+  "type": "project",
5
+  "license": "Unlicense",
6
+  "authors": [
7
+    {
8
+      "name": "What.cd"
9
+    },
10
+    {
11
+      "name": "Oppaitime"
12
+    },
13
+    {
14
+      "name": "BioTorrents.de"
15
+    }
16
+  ],
17
+  "autoload": {
18
+    "classmap": [
19
+      "classes/"
20
+    ],
21
+    "files": [
22
+      "classes/autoload.php"
23
+    ]
24
+  },
25
+  "config": {
26
+    "sort-packages": true
27
+  },
28
+  "require": {
29
+    "php": ">=7.4",
30
+    "ext-apcu": "*",
31
+    "ext-curl": "*",
32
+    "ext-json": "*",
33
+    "ext-mbstring": "*",
34
+    "ext-memcache": "*",
35
+    "ext-mysqli": "*",
36
+    "biotorrents/biophp": "dev-master",
37
+    "erusev/parsedown": "^1.8.0-beta-7",
38
+    "erusev/parsedown-extra": "^0.8.1",
39
+    "j7mbo/twitter-api-php": "^1.0.6",
40
+    "orpheusnet/bencode-torrent": "^1.1.1",
41
+    "robmorgan/phinx": "^0.12.7",
42
+    "twig/twig": "^3.3.2"
43
+  },
44
+  "require-dev": {
45
+    "d11wtq/boris": "^1.0.10",
46
+    "phpstan/phpstan": "^0.12.92",
47
+    "phpunit/phpunit": "^9.5.6",
48
+    "squizlabs/php_codesniffer": "^3.6.0"
49
+  },
50
+  "scripts": {
51
+    "phpstan": "phpstan analyse",
52
+    "test": "phpunit"
53
+  }
54
+}

+ 4065
- 0
composer.lock
File diff suppressed because it is too large
View File


+ 0
- 4
delete.php View File

@@ -1,4 +0,0 @@
1
-<?php
2
-declare(strict_types=1);
3
-
4
-require_once 'classes/script_start.php';

+ 1
- 1
design/privatefooter.php View File

@@ -6,7 +6,7 @@ $Sep = '&emsp;';
6 6
 
7 7
 # End <div#content>, begin <footer>
8 8
 # This needs to be <main>, in each page
9
-echo $HTML = '</div></main><footer class="halfwide">';
9
+echo $HTML = '</div></main><footer>';
10 10
 
11 11
 # Disclaimer
12 12
 #if (!empty($Options['disclaimer'])) {

+ 7
- 17
design/privateheader.php View File

@@ -49,6 +49,8 @@ $Styles = array_filter(
49 49
     array_merge(
50 50
         [
51 51
           'vendor/jquery-ui.min',
52
+          'vendor/normalize',
53
+          'vendor/skeleton',
52 54
           #'assets/fonts/fa/css/all.min',
53 55
           'global'
54 56
         ],
@@ -250,8 +252,7 @@ if ($NotificationsManager->is_skipped(NotificationsManager::SUBSCRIPTIONS)) {
250 252
           <li id="nav_irc" <?=
251 253
             Format::add_class($PageID, ['chat'], 'active', true)?>>
252 254
             <a href="https://join.slack.com/t/biotorrents/shared_invite/<?=$ENV->SLACK_INVITE?>"
253
-              target="_blank">Slack
254
-              <img src="/static/common/symbols/external.png" style="height: 0.75em; vertical-align: center;" /></a>
255
+              target="_blank">Slack</a>
255 256
           </li>
256 257
 
257 258
           <li id="nav_top10" <?=
@@ -332,11 +333,13 @@ if (isset(G::$LoggedUser['SearchType']) && G::$LoggedUser['SearchType']) { // Ad
332 333
             size="17">
333 334
         </form>
334 335
 
336
+        <!--
335 337
         <form class="search_form" name="artists" action="artist.php" method="get">
336
-          <input id="artistsearch" <?=Users::has_autocomplete_enabled('search')?>
338
+          <input id="artistsearch" <?=null#Users::has_autocomplete_enabled('search')?>
337 339
           aria-label="Search authors" accesskey="a" spellcheck="false" autocomplete="off" placeholder="Authors"
338 340
           type="text" name="artistname" size="17">
339 341
         </form>
342
+          -->
340 343
 
341 344
         <form class="search_form" name="requests" action="requests.php" method="get">
342 345
           <input id="requestssearch" aria-label="Search requests" spellcheck="false" autocomplete="off"
@@ -607,19 +610,6 @@ if (check_perms('admin_reports')) {
607 610
     }
608 611
 }
609 612
 
610
-if (check_perms('users_mod')) {
611
-    $NumDeleteRequests = G::$Cache->get_value('num_deletion_requests');
612
-    if ($NumDeleteRequests === false) {
613
-        G::$DB->query("SELECT COUNT(*) FROM deletion_requests");
614
-        list($NumDeleteRequests) = G::$DB->next_record();
615
-        G::$Cache->cache_value('num_deletion_requests', $NumDeleteRequests);
616
-    }
617
-
618
-    if ($NumDeleteRequests > 0) {
619
-        $ModBar[] = '<a href="tools.php?action=expunge_requests">' . $NumDeleteRequests . " Expunge request".($NumDeleteRequests > 1 ? 's' : '')."</a>";
620
-    }
621
-}
622
-
623 613
 if (check_perms('users_mod') && FEATURE_EMAIL_REENABLE) {
624 614
     $NumEnableRequests = G::$Cache->get_value(AutoEnable::CACHE_KEY_NAME);
625 615
     if ($NumEnableRequests === false) {
@@ -636,7 +626,7 @@ if (check_perms('users_mod') && FEATURE_EMAIL_REENABLE) {
636 626
 if (!empty($Alerts) || !empty($ModBar)) { ?>
637 627
     <div id="alerts">
638 628
       <?php foreach ($Alerts as $Alert) { ?>
639
-      <div class="alertbar">
629
+      <div class="alertbar warning">
640 630
         <?=$Alert?>
641 631
       </div>
642 632
       <?php

+ 2
- 1
design/publicfooter.php View File

@@ -7,9 +7,10 @@ echo <<<HTML
7 7
 </main>
8 8
 
9 9
 <footer>
10
-  <a href="https://github.com/biotorrents/gazelle" target="_blank">GitHub</a>
11 10
   <a href="/legal.php?p=privacy">Privacy</a>
12 11
   <a href="/legal.php?p=dmca">DMCA</a>
12
+  <a class="external" href="https://github.com/biotorrents" target="_blank">GitHub</a>
13
+  <a class="external" href="https://patreon.com/biotorrents" target="_blank">Patreon</a>
13 14
 </footer>
14 15
 
15 16
 <script src="$ENV->STATIC_SERVER/functions/vendor/instantpage.js" type="module"></script>

+ 4
- 7
design/publicheader.php View File

@@ -41,7 +41,7 @@ foreach ($Scripts as $Script) {
41 41
 }
42 42
 
43 43
 # Load CSS
44
-$Styles = ['global', 'public'];
44
+$Styles = ['vendor/normalize', 'vendor/skeleton', 'global', 'public'];
45 45
 foreach ($Styles as $Style) {
46 46
     echo View::pushAsset(
47 47
         "$ENV->STATIC_SERVER/styles/$Style.css",
@@ -52,7 +52,7 @@ foreach ($Styles as $Style) {
52 52
 # Fonts
53 53
 echo View::pushAsset(
54 54
 # Only Noto Sans available on public pages
55
-"$ENV->STATIC_SERVER/styles/assets/fonts/noto/woff2/NotoSans-SemiCondensed.woff2",
55
+"$ENV->STATIC_SERVER/styles/assets/fonts/noto/NotoSans-SemiCondensed.woff2",
56 56
     'font'
57 57
 );
58 58
 
@@ -68,12 +68,9 @@ if ($ENV->OPEN_REGISTRATION) {
68 68
     echo '<a href="register.php">Register</a>';
69 69
 }
70 70
 
71
-$Email = $ENV->HELP->Email;
72
-$Subject = $ENV->HELP->Subject;
73
-$Body = $ENV->HELP->Body;
74
-echo "<a href='mailto:$Email?subject=$Subject&body=$Body'>Support</a>";
75
-
76 71
 echo <<<HTML
72
+    <a href="/legal.php?p=about">About</a>
73
+    <a class="external" href="https://docs.torrents.bio" target="_blank">Docs</a>
77 74
   </header>
78 75
 
79 76
 <main>

+ 1
- 1
design/views/generic/reply/quickreply.php View File

@@ -200,7 +200,7 @@ if (!isset($InputTitle)) {
200 200
           class="hidden button_preview_<?=$ReplyText->getID()?>"
201 201
           tabindex="1" />
202 202
 
203
-        <input type="submit" value="Post" id="submit_button" tabindex="1" />
203
+        <input type="submit" class="button-primary" value="Post" id="submit_button" tabindex="1" />
204 204
       </div>
205 205
     </form>
206 206
   </div>

+ 1
- 1
design/views/generic/reply/staffpm.php View File

@@ -32,7 +32,7 @@ $TextPrev = new TEXTAREA_PREVIEW(
32 32
 
33 33
     <input type="button" value="Preview"
34 34
       class="hidden button_preview_<?=$TextPrev->getID()?>" />
35
-    <input type="submit" value="Send message" />
35
+    <input type="submit" class="button-primary" value="Send message" />
36 36
     <input type="button" value="Hide" data-toggle-target="#compose" />
37 37
   </form>
38 38
 </div>

+ 59
- 18
feeds.php View File

@@ -1,42 +1,58 @@
1 1
 <?php
2 2
 declare(strict_types = 1);
3 3
 
4
-/*-- Feed Start Class ----------------------------------*/
5
-/*------------------------------------------------------*/
6
-/* Simplified version of script_start, used for the     */
7
-/* sitewide RSS system.                                 */
8
-/*------------------------------------------------------*/
9
-/********************************************************/
10
-
11
-// Let's prevent people from clearing feeds
4
+/**
5
+ * Feed start class
6
+ *
7
+ * Simplified version of script_start,
8
+ * used for the sitewide RSS system.
9
+ */
10
+
11
+# Let's prevent people from clearing feeds
12 12
 if (isset($_GET['clearcache'])) {
13 13
     unset($_GET['clearcache']);
14 14
 }
15 15
 
16 16
 require_once 'classes/config.php';
17
-require_once SERVER_ROOT.'/classes/misc.class.php';
18
-require_once SERVER_ROOT.'/classes/cache.class.php';
19
-require_once SERVER_ROOT.'/classes/feed.class.php';
20
-
21 17
 $ENV = ENV::go();
22
-$Cache = new Cache($ENV->getPriv('MEMCACHED_SERVERS')); // Load the caching class
23
-$Feed = new Feed; // Load the time class
24 18
 
19
+require_once "$ENV->SERVER_ROOT/classes/misc.class.php";
20
+require_once "$ENV->SERVER_ROOT/classes/cache.class.php";
21
+require_once "$ENV->SERVER_ROOT/classes/feed.class.php";
22
+
23
+# Load the caching class
24
+$Cache = new Cache($ENV->getPriv('MEMCACHED_SERVERS'));
25
+
26
+# Load the time class
27
+$Feed = new Feed;
28
+
29
+
30
+/**
31
+ * check_perms
32
+ */
25 33
 function check_perms()
26 34
 {
27 35
     return false;
28 36
 }
29 37
 
38
+
39
+/**
40
+ * is_number
41
+ */
30 42
 function is_number($Str)
31 43
 {
32 44
     if ($Str < 0) {
33 45
         return false;
34 46
     }
35 47
 
36
-    // We're converting input to an int, then string, and comparing to the original
48
+    # We're converting input to an int, then string, and comparing to the original
37 49
     return ($Str === strval(intval($Str)));
38 50
 }
39 51
 
52
+
53
+/**
54
+ * display_str
55
+ */
40 56
 function display_str($Str)
41 57
 {
42 58
     if ($Str !== '') {
@@ -62,21 +78,29 @@ function display_str($Str)
62 78
 
63 79
         $Str = str_replace($Replace, $With, $Str);
64 80
     }
81
+
65 82
     return $Str;
66 83
 }
67 84
 
85
+
86
+/**
87
+ * make_utf8
88
+ */
68 89
 function make_utf8($Str)
69 90
 {
70 91
     if ($Str !== '') {
71 92
         if (is_utf8($Str)) {
72 93
             $Encoding = 'UTF-8';
73 94
         }
95
+
74 96
         if (empty($Encoding)) {
75 97
             $Encoding = mb_detect_encoding($Str, 'UTF-8, ISO-8859-1');
76 98
         }
99
+
77 100
         if (empty($Encoding)) {
78 101
             $Encoding = 'ISO-8859-1';
79 102
         }
103
+
80 104
         if ($Encoding === 'UTF-8') {
81 105
             return $Str;
82 106
         } else {
@@ -85,6 +109,10 @@ function make_utf8($Str)
85 109
     }
86 110
 }
87 111
 
112
+
113
+/**
114
+ * is_utf8
115
+ */
88 116
 function is_utf8($Str)
89 117
 {
90 118
     return preg_match(
@@ -102,6 +130,10 @@ function is_utf8($Str)
102 130
     );
103 131
 }
104 132
 
133
+
134
+/**
135
+ * display_array
136
+ */
105 137
 function display_array($Array, $Escape = [])
106 138
 {
107 139
     foreach ($Array as $Key => $Val) {
@@ -109,20 +141,29 @@ function display_array($Array, $Escape = [])
109 141
             $Array[$Key] = display_str($Val);
110 142
         }
111 143
     }
144
+
112 145
     return $Array;
113 146
 }
114 147
 
148
+
115 149
 /**
116
- * Print the site's URL including the appropriate URI scheme, including the trailing slash
150
+ * site_url
151
+ *
152
+ * Print the site's URL and appropriate URI scheme,
153
+ * including the trailing slash.
117 154
  */
118 155
 function site_url()
119 156
 {
120
-    return 'https://' . SITE_DOMAIN . '/';
157
+    $ENV = ENV::go();
158
+    return "https://$ENV->SITE_DOMAIN/";
121 159
 }
122 160
 
161
+
162
+# Set the headers
123 163
 header('Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0');
124 164
 header('Pragma:');
125 165
 header('Expires: '.date('D, d M Y H:i:s', time() + (2 * 60 * 60)).' GMT');
126 166
 header('Last-Modified: '.date('D, d M Y H:i:s').' GMT');
127 167
 
128
-require_once SERVER_ROOT.'/sections/feeds/index.php';
168
+# Load the feeds section
169
+require_once "$ENV->SERVER_ROOT/sections/feeds/index.php";

+ 197
- 97
gazelle.sql
File diff suppressed because it is too large
View File


+ 86
- 48
image.php View File

@@ -1,7 +1,7 @@
1 1
 <?php
2 2
 declare(strict_types=1);
3 3
 
4
-// Functions and headers needed by the image proxy
4
+# Functions and headers needed by the image proxy
5 5
 error_reporting(E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR);
6 6
 
7 7
 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
@@ -9,108 +9,146 @@ if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
9 9
     error();
10 10
 }
11 11
 
12
-header('Expires: '.date('D, d-M-Y H:i:s \U\T\C', time() + 3600 * 24 * 120)); // 120 days
12
+header('Expires: '.date('D, d-M-Y H:i:s \U\T\C', time() + 3600 * 24 * 120)); # 120 days
13 13
 header('Last-Modified: '.date('D, d-M-Y H:i:s \U\T\C', time()));
14 14
 
15 15
 if (!extension_loaded('gd')) {
16
-    error('nogd');
16
+    error('Please install and enable the GD Graphics Library.');
17 17
 }
18 18
 
19
+
20
+/**
21
+ * img_error
22
+ */
19 23
 function img_error($Type)
20 24
 {
25
+    $ENV = ENV::go();
26
+
21 27
     header('Content-type: image/gif');
22
-    error(file_get_contents(SERVER_ROOT.'/sections/image/err_imgs/'.$Type.'.png'));
28
+    error(file_get_contents("$ENV->SERVER_ROOT/sections/image/err_imgs/$Type.png"));
23 29
 }
24 30
 
31
+
32
+/**
33
+ * invisible
34
+ */
25 35
 function invisible($Image)
26 36
 {
27 37
     $Count = imagecolorstotal($Image);
28 38
     if ($Count === 0) {
29 39
         return false;
30 40
     }
41
+
31 42
     $TotalAlpha = 0;
32 43
     for ($i = 0; $i < $Count; ++$i) {
33 44
         $Color = imagecolorsforindex($Image, $i);
34 45
         $TotalAlpha += $Color['alpha'];
35 46
     }
47
+
36 48
     return (($TotalAlpha / $Count) === 127);
37 49
 }
38 50
 
51
+
52
+/**
53
+ * verysmall
54
+ */
39 55
 function verysmall($Image)
40 56
 {
41 57
     return ((imagesx($Image) * imagesy($Image)) < 25);
42 58
 }
43 59
 
60
+
61
+/**
62
+ * image_type
63
+ */
44 64
 function image_type($Data)
45 65
 {
46 66
     if (!strncmp($Data, 'GIF', 3)) {
47 67
         return 'gif';
48 68
     }
69
+
49 70
     if (!strncmp($Data, pack('H*', '89504E47'), 4)) {
50 71
         return 'png';
51 72
     }
73
+
52 74
     if (!strncmp($Data, pack('H*', 'FFD8'), 2)) {
53 75
         return 'jpeg';
54 76
     }
77
+
55 78
     if (!strncmp($Data, 'BM', 2)) {
56 79
         return 'bmp';
57 80
     }
81
+
58 82
     if (!strncmp($Data, 'II', 2) || !strncmp($Data, 'MM', 2)) {
59 83
         return 'tiff';
60 84
     }
85
+
61 86
     if (!substr_compare($Data, 'webm', 31, 4)) {
62 87
         return 'webm';
63 88
     }
64 89
 }
65 90
 
91
+
92
+/**
93
+ * image_height
94
+ */
66 95
 function image_height($Type, $Data)
67 96
 {
68
-    $Length = strlen($Data);
69 97
     global $URL, $_GET;
98
+    $Length = strlen($Data);
70 99
 
71 100
     switch ($Type) {
72
-    case 'jpeg':
73
-      // See http://www.obrador.com/essentialjpeg/headerinfo.htm
74
-      $i = 4;
75
-      $Data = (substr($Data, $i));
76
-      $Block = unpack('nLength', $Data);
77
-      $Data = substr($Data, $Block['Length']);
78
-      $i += $Block['Length'];
79
-      $Str []= 'Started 4, + '.$Block['Length'];
80
-
81
-      while ($Data !== '') { // Iterate through the blocks until we find the start of frame marker (FFC0)
82
-        $Block = unpack('CBlock/CType/nLength', $Data); // Get info about the block
83
-        if ($Block['Block'] !== '255') { // We should be at the start of a new block
84
-          break;
85
-        }
86
-
87
-          if ($Block['Type'] !== '192') { // C0
88
-          $Data = substr($Data, $Block['Length'] + 2); // Next block
89
-          $Str []= 'Started $i, + '.($Block['Length'] + 2);
90
-              $i += ($Block['Length'] + 2);
91
-          } else { // We're at the FFC0 block
92
-          $Data = substr($Data, 5); // Skip FF C0 Length(2) precision(1)
93
-          $i += 5;
94
-              $Height = unpack('nHeight', $Data);
95
-              return $Height['Height'];
96
-          }
97
-      }
98
-      break;
99
-
100
-    case 'gif':
101
-      $Data = substr($Data, 8);
102
-      $Height = unpack('vHeight', $Data);
103
-      return $Height['Height'];
104
-
105
-    case 'png':
106
-      $Data = substr($Data, 20);
107
-      $Height = unpack('NHeight', $Data);
108
-      return $Height['Height'];
109
-      
110
-    default:
111
-      return 0;
112
-  }
101
+        case 'jpeg':
102
+            # https://www.geocities.ws/crestwoodsdd/JPEG.htm
103
+            $i = 4;
104
+            $Data = (substr($Data, $i));
105
+            $Block = unpack('nLength', $Data);
106
+            $Data = substr($Data, $Block['Length']);
107
+            $i += $Block['Length'];
108
+            $Str []= 'Started 4, + '.$Block['Length'];
109
+
110
+            # Iterate through the blocks until we find the start of frame marker (FFC0)
111
+            while ($Data !== '') {
112
+                # Get info about the block
113
+                $Block = unpack('CBlock/CType/nLength', $Data);
114
+
115
+                # We should be at the start of a new block
116
+                if ($Block['Block'] !== '255') {
117
+                    break;
118
+                }
119
+
120
+                if ($Block['Type'] !== '192') { # C0
121
+                    $Data = substr($Data, $Block['Length'] + 2); # Next block
122
+                    $Str []= 'Started $i, + '.($Block['Length'] + 2);
123
+                    $i += ($Block['Length'] + 2);
124
+                }
125
+                
126
+                # We're at the FFC0 block
127
+                else {
128
+                    # Skip FF C0 Length(2) precision(1)
129
+                    $Data = substr($Data, 5);
130
+                    $i += 5;
131
+                    $Height = unpack('nHeight', $Data);
132
+                    return $Height['Height'];
133
+                }
134
+            }
135
+            break;
136
+            
137
+        case 'gif':
138
+            $Data = substr($Data, 8);
139
+            $Height = unpack('vHeight', $Data);
140
+            return $Height['Height'];
141
+        
142
+        case 'png':
143
+            $Data = substr($Data, 20);
144
+            $Height = unpack('NHeight', $Data);
145
+            return $Height['Height'];
146
+            
147
+        default:
148
+            return 0;
149
+    }
113 150
 }
114 151
 
115
-define('SKIP_NO_CACHE_HEADERS', 1);
116
-require_once 'classes/script_start.php'; // script_start.php contains all we need and includes sections/image/index.php
152
+
153
+# script_start.php contains all we need and includes sections/image/index.php
154
+require_once 'classes/script_start.php';

+ 1
- 1
manifest.php View File

@@ -31,7 +31,7 @@ JSON;
31 31
 
32 32
     # Print header and $manifest for remote addresses
33 33
     # Return JSON for localhost (API manifest endpoint):
34
-    #   ajax.php?action=manifest
34
+    #   api.php?action=manifest
35 35
     if ($_SERVER['REMOTE_ADDR'] !== "127.0.0.1") {
36 36
         header('Content-type: application/json; charset=utf-8');
37 37
         echo $manifest;

+ 97
- 30
readme.md View File

@@ -1,71 +1,138 @@
1
-This is BioTorrents.de's version of Gazelle
1
+# BioTorrents.de Gazelle
2 2
 
3
-Below are some lists of differences between this version of Gazelle and What.cd's. Please note that these lists are far from complete.
3
+This software is twice removed from the original
4
+[What.cd Gazelle](https://github.com/WhatCD/Gazelle).
5
+It's based on the security hardened PHP7 fork
6
+[Oppaitime Gazelle](https://git.oppaiti.me/Oppaitime/Gazelle).
7
+It shares several features with
8
+[Orpheus Gazelle](https://github.com/OPSnet/Gazelle).
9
+The goal is to organize a functional database with pleasant interfaces,
10
+and render insightful views using data from robust external sources.
4 11
 
5
-## Major Changes
12
+# Changelog: OT → Bio
6 13
 
7
-#### Integrated Database Encryption
14
+## Bearer token authorization
8 15
 
9
-Using a database key [provided by staff](sections/tools/misc/database_key.php) and only ever stored as a hash in memory (via APCu), the [integrated database encryption](classes/crypto.class.php) is used to encrypt sensitive user data like IP addresses, emails, and private messages regardless of the underlying system gazelle is running on.
16
+[API Docs](https://docs.biotorrents.de).
17
+API tokens can be generated in the
18
+[user security settings](sections/user/token.php)
19
+and used with the JSON API.
10 20
 
11
-The rest of gazelle must be aware that some of the data it fetches from the DB is encrypted, and must have a fallback if that data is unavailable (the key is not in memory). You will see plenty of `if (!apcu_exists('DBKEY')) {` in this codebase.
21
+## Good typography
22
+
23
+BioTorrents.de supports an array of
24
+[unobtrusive fonts](static/styles/assets/scss/fonts.scss)
25
+with the appropriate bold/italic glyphs and monospace.
26
+These options are available to every theme.
27
+Font Awesome 5 is also universally available.
28
+[Download the fonts](https://torrents.bio/fonts.tgz).
29
+
30
+## Markdown support
31
+
32
+[SimpleMDE markdown editor](https://simplemde.com)
33
+with extended custom editor interface.
34
+All the Markdown Extra features supported by
35
+[Parsedown Extra](https://github.com/erusev/parsedown-extra)
36
+are documented and the useful ones exposed in the editor interface.
37
+Support for the default Gazelle recursive regex BBcode parser.
38
+
39
+## $ENV recursive singleton
40
+
41
+[The site configuration](classes/config.template.php)
42
+is being migrated to a format govered by the
43
+[ENV special class](classes/env.class.php)
44
+for modified recursive ArrayObjects.
45
+
46
+## Twig template system
47
+
48
+Similar to ENV, the
49
+[Twig interface](classes/twig.class.php)
50
+operates as a singleton because it's an external module with its own cache.
51
+Twig provides a security benefit by escaping rendered output,
52
+and a secondary benefit of clarifying the PHP running the site sections.
53
+Several custom filters are available from OPS.
54
+
55
+## Active data minimization
56
+
57
+BioTorrents.de has
58
+[real lawyer-vetted policies](templates/legal).
59
+In the process of matching the tech to the legal word,
60
+we dropped support for a number of compromising features:
12 61
 
13
-#### Authorized Login Locations
62
+- Bitcoin, PayPal, and currency exchange API and system calls;
63
+- Bitcoin addresses, user donation history, and similar metadata; and
64
+- IP address and geolocation, email address, passphrase, and passkey history.
14 65
 
15
-Whenever a login occurs from a location (determined by ASN) that hasn't logged into that account before, an email is sent to the account owner requesting that they authorize that location before the login will go through.
66
+Besides that, BioTorrents has several passive developments in progress:
16 67
 
17
-This prevents most attacks that would be otherwise successful, as it requires an attacker to access the site from the same locations the actual user uses to login.
68
+- prepare all queries with parameterized statements;
69
+- declare strict mode at the top of every PHP and JS file;
70
+- check strict equality and strong typing, including function arguments;
71
+- run all files through generic formatters such as PHP-CS-Fixer; and
72
+- move all external libraries to uncomplicated package management.
18 73
 
19
-#### Two-Factor Authentication
74
+## Minor changes
75
+
76
+- Database crypto bumped up to AES-256
77
+- Good subresource integrity support
78
+- Configurable HTTP status code errors
79
+- Integrated diceware passphrase generator
80
+- TLS database connections
81
+- Semantic HTML5 themes (WIP)
82
+
83
+# Changelog: WCD → OT
84
+
85
+## Integrated Database Encryption
86
+
87
+Using a database key [provided by staff](sections/tools/misc/database_key.php) and only ever stored as a hash in memory (via APCu), the [integrated database encryption](classes/crypto.class.php) is used to encrypt sensitive user data like IP addresses, emails, and private messages regardless of the underlying system gazelle is running on.
88
+
89
+The rest of gazelle must be aware that some of the data it fetches from the DB is encrypted, and must have a fallback if that data is unavailable (the key is not in memory). You will see plenty of `if (!apcu_exists('DBKEY')) {` in this codebase.
90
+
91
+## Two-Factor Authentication
20 92
 
21 93
 Despite our other (less intrusive) methods of protecting user accounts being more than sufficient for virtually all feasible attacks, we also ship optional 2FA should users feel the need to enable it.
22 94
 
23
-#### Universal 2nd Factor
95
+## Universal 2nd Factor
24 96
 
25 97
 Support for physical U2F tokens has also been added as an optional alternative to normal 2FA. U2F allows users to protect their account with something less likely to be lost or erased than 2FA keys stored on a phone.
26 98
 
27
-#### Unique Infohashes
99
+## Unique Infohashes
28 100
 
29 101
 Upon upload, torrent files are modified to contain a "source" field in the info dict containing the concatination of the site name and some generated junk data (unique per-torrent). This prevents infohash collisions with torrents cross-seeded from other sites in the same client, and also helps protect against some not particularly likely peer-leaking attacks.
30 102
 
31
-#### Expunge Requests
32
-
33
-Users are able to view the data kept on them and [issue requests for the deletion of old information](sections/delete) to staff through a simple interface.
34
-
35
-#### Resource Proxying
103
+## Resource Proxying
36 104
 
37 105
 All external resources that may appear on a page are fetched and served by the server running gazelle. This prevents the leak of user information to third parties hosting content that has been included on a page through an image tag or similar.
38 106
 
39
-#### Scheduler
107
+## Scheduler
40 108
 
41 109
 The [scheduler](sections/schedule) has been broken up into more manageable parts and has additional selective runtime features for manual execution.
42 110
 
43
-#### Bonus Points
111
+## Bonus Points
44 112
 
45 113
 Like most gazelle forks, we've added a [bonus point system](sections/schedule/hourly/bonus_points.php) and [store](sections/store).
46 114
 
47
-#### Modern password hashing
115
+## Modern password hashing
48 116
 
49 117
 We use modern PHP password hashing features that automatically rehash your password when a better hashing algorithm is made available and employ prehashing to allow you to use a secure password of any length. Original gazelle would effectively truncate your password after around 72 characters (if the tracker even allowed you to use a password that long). This codebase does not have the same problem, and allows passwords of virtually unlimited length (over 30,000 characters by default) that remain useful after a few tens of characters.
50 118
 
51 119
 ## Minor Changes
52 120
 
53
-* When a torrent is trumped, the new torrent is made freeleech to users who snatched the old torrent for a few days.
54
-* Sends headers to tell cloudflare to use HTTP/2 Server Push for most resources.
55
-* ~~BTN-style magnet link support.~~
56
-* Support for optional per-user stylesheet additions and tweaks
57
-* This codebase expects to run over https only.
121
+- When a torrent is trumped, the new torrent is made freeleech to users who snatched the old torrent for a few days.
122
+- Sends headers to tell cloudflare to use HTTP/2 Server Push for most resources.
123
+- Support for optional per-user stylesheet additions and tweaks
124
+- This codebase expects to run over https only.
58 125
 
59
-## Mascot
126
+# Mascot
60 127
 
61
-<img align="left" alt="Gracie Gazelle" src="https://git.oppaiti.me/Oppaitime/Gazelle/raw/branch/master/static/common/mascot.png">
128
+![Gracie Gazelle](static/common/mascot.png)
62 129
 
63 130
 **Gracie Gazelle**
64 131
 
65 132
 Gracie is a veteran pirate of the Digital Ocean. On land, predators form companies to hunt down prey. But in the lawless water, prey attack the predators' transports. Gracies steals resources from the rich and shares them with the poor and isolated people. Her great eyesight sees through the darkest corners of the Internet for her next target. Her charisma attracts countless salty goats to join her fleet. She proudly puts the forbidden share symbols on her hat and belt, and is now one of the most wanted women in the world.
66 133
 
67
-<small>High resolution downloads [here](https://git.oppaiti.me/Oppaitime/Gazelle/issues/34#issuecomment-99)</small>
134
+High resolution downloads [here](https://git.oppaiti.me/Oppaitime/Gazelle/issues/34#issuecomment-99)
68 135
 
69
-<small>Character design and bio by Tyson Tan, who offers mascot design services for free and open source software, free of charge, under a free license.</small>
136
+Character design and bio by Tyson Tan, who offers mascot design services for free and open source software, free of charge, under a free license.
70 137
 
71
-<small>Contact: [tysontan.com](https://tysontan.com) / <tysontan@mail.com></small>
138
+Contact: [tysontan.com](https://tysontan.com) / <tysontan@mail.com>

+ 1
- 1
rules.php View File

@@ -1,4 +1,4 @@
1 1
 <?php
2 2
 declare(strict_types=1);
3 3
 
4
-require 'classes/script_start.php';
4
+require_once 'classes/script_start.php';

+ 0
- 60
sections/ajax/autofill/anime.php View File

@@ -1,60 +0,0 @@
1
-<?php
2
-#declare(strict_types=1);
3
-
4
-if (empty($_GET['aid'])) {
5
-  json_die();
6
-}
7
-
8
-$aid = $_GET['aid'];
9
-
10
-if ($Cache->get_value('anime_fill_json_'.$aid)) {
11
-  json_die("success", $Cache->get_value('anime_fill_json_'.$aid));
12
-} else {
13
-
14
-  $anidb_url = 'http://api.anidb.net:9001/httpapi?request=anime&client='.API_KEYS['ANIDB'].'&clientver=1&protover=1&aid='.$aid;
15
-
16
-  $crl = curl_init();
17
-  curl_setopt($crl, CURLOPT_URL, $anidb_url);
18
-  curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1);
19
-  curl_setopt($crl, CURLOPT_CONNEECTTIMEOUT, 5);
20
-  $ret = curl_exec($crl);
21
-  curl_close($curl);
22
-
23
-  $anidb_xml = new SimpleXMLElement(zlib_decode($ret));
24
-
25
-  if ($anidb_xml->xpath('/error')) {
26
-    json_die("failure", $anidb_xml->xpath('/error')[0]."");
27
-  }
28
-
29
-  $title = $anidb_xml->xpath('//titles/title[@xml:lang = "en" and @type = "official"]')[0].'';
30
-  $title = (empty($title))?$anidb_xml->xpath('//titles/title[@xml:lang = "en"]')[0].'':$title;
31
-  $title = (empty($title))?$anidb_xml->xpath('//titles/title[@type = "main"]')[0].'':$title;
32
-
33
-  $title_rj = $anidb_xml->xpath('//titles/title[@xml:lang = "x-jat" and @type = "official"]')[0].'';
34
-  $title_rj = (empty($title_rj))?$anidb_xml->xpath('//titles/title[@xml:lang = "x-jat"]')[0].'':$title_rj;
35
-
36
-  $title_jp = $anidb_xml->xpath('//titles/title[@xml:lang = "ja" and @type = "official"]')[0].'';
37
-  $title_jp = (empty($title_jp))?$anidb_xml->xpath('//titles/title[@xml:lang = "ja"]')[0].'':$title_jp;
38
-
39
-  $artist = $anidb_xml->xpath('//creators/name[@type = "Animation Work"]')[0].'';
40
-
41
-  $year = substr($anidb_xml->startdate, 0, 4);
42
-
43
-  $desc = preg_replace('/http:\/\/anidb.net\S+ \[(.*?)\]/', '$1', ($anidb_xml->description).'');
44
-
45
-  $json_str = array(
46
-    'id' => $aid,
47
-    'title' => $title,
48
-    'title_rj' => $title_rj,
49
-    'title_jp' => $title_jp,
50
-    'artist' => $artist,
51
-    'year' => $year,
52
-    'description' => $desc
53
-  );
54
-
55
-  $Cache->cache_value('anime_fill_json_'.$aid, $json_str, 86400);
56
-
57
-  json_die("success", $json_str);
58
-}
59
-
60
-?>

+ 0
- 234
sections/ajax/autofill/jav.php View File

@@ -1,234 +0,0 @@
1
-<?php
2
-#declare(strict_types=1);
3
-
4
-# Headers, cache, etc.
5
-$debug = false;
6
-
7
-if (empty($_GET['cn'])) {
8
-  json_die();
9
-}
10
-
11
-$cn = strtoupper($_GET['cn']);
12
-
13
-if (!strpos($cn, '-')) {
14
-  preg_match('/\d/', $cn, $m, PREG_OFFSET_CAPTURE);
15
-  if ($m) { $cn = substr_replace($cn, '-', $m[0][1], 0); }
16
-}
17
-
18
-if (!$debug && $Cache->get_value('jav_fill_json_'.$cn)) {
19
-  json_die('success', $Cache->get_value('jav_fill_json_'.$cn));
20
-} else {
21
-
22
-  # Query the API
23
-  # todo: Validate to change $db
24
-
25
-/* todo
26
- * switch $category:
27
- *   case 'DNA' || 'RNA':
28
- *     if $number = refseq_regex:
29
- *        $db = 'refseq';
30
- *     break;
31
- *   case 'Protein':
32
- *     if $number = uniprot_regex:
33
- *        $db = 'uniprot';
34
- *     break;
35
- *   default:
36
- *     error 'invalid number';
37
- *     break;
38
- */
39
-$id = 'NM_001183340.1';
40
-
41
-# Assemble the esearch URL
42
-$base = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/';
43
-$url = $base . "esummary.fcgi?db=$db&id=$id&version=2.0";
44
-
45
-# Post the esearch URL
46
-$output = file_get_contents($url);
47
-
48
-# Parse WebEnv and QueryKey
49
-#$web = $1 if ($output =~ /<WebEnv>(\S+)<\/WebEnv>/);
50
-#$key = $1 if ($output =~ /<QueryKey>(\d+)<\/QueryKey>/);
51
-
52
-### Include this code for ESearch-ESummary
53
-# Assemble the esummary URL
54
-$url = $base . "esummary.fcgi?db=$db&query_key=$key&WebEnv=$web";
55
-
56
-# Post the esummary URL
57
-$docsums = file_get_contents($url);
58
-echo "$docsums";
59
-
60
-### Include this code for ESearch-EFetch
61
-# Assemble the efetch URL
62
-$url = $base . "efetch.fcgi?db=$db&query_key=$key&WebEnv=$web";
63
-$url .= "&rettype=abstract&retmode=text";
64
-
65
-# Post the efetch URL
66
-$data = file_get_contents($url);
67
-echo "$data";
68
-
69
-  /*
70
-  $jlib_jp_url = ('http://www.javlibrary.com/ja/vl_searchbyid.php?keyword='.$cn);
71
-  $jlib_en_url = ('http://www.javlibrary.com/en/vl_searchbyid.php?keyword='.$cn);
72
-  $jdb_url     = ('http://javdatabase.com/movies/'.$cn.'/');
73
-
74
-  $jlib_page_jp = file_get_contents($jlib_jp_url);
75
-  $jlib_page_en = file_get_contents($jlib_en_url);
76
-  $jdb_page     = file_get_contents($jdb_url);
77
-
78
-  if ($jlib_page_en) {
79
-    $jlib_dom_en = new DOMDocument();
80
-    $jlib_dom_en->loadHTML($jlib_page_en);
81
-    $jlib_en = new DOMXPath($jlib_dom_en);
82
-
83
-    // Check if we're still on the search page and fix it if so
84
-    if($jlib_en->query("//a[starts-with(@title, \"$cn\")]")->item(0)) {
85
-      $href = substr($jlib_en->query("//a[starts-with(@title, \"$cn\")]")->item(0)->getAttribute('href'),1);
86
-      $jlib_page_en = file_get_contents('http://www.javlibrary.com/en/'.$href);
87
-      $jlib_page_jp = file_get_contents('http://www.javlibrary.com/ja/'.$href);
88
-      $jlib_dom_en->loadHTML($jlib_page_en);
89
-      $jlib_en = new DOMXPath($jlib_dom_en);
90
-      // If the provided CN was so bad that search provided a different match, die
91
-      if(strtoupper($jlib_en->query('//*[@id="video_id"]/table/tr/td[2]')->item(0)->nodeValue) != $cn) {
92
-        json_die('failure', 'Movie not found');
93
-      }
94
-    }
95
-  }
96
-  if ($jlib_page_jp) {
97
-    $jlib_dom_jp = new DOMDocument();
98
-    $jlib_dom_jp->loadHTML($jlib_page_jp);
99
-    $jlib_jp = new DOMXPath($jlib_dom_jp);
100
-  }
101
-  if ($jdb_page) {
102
-    $jdb_dom = new DOMDocument();
103
-    $jdb_dom->loadHTML($jdb_page);
104
-    $jdb = new DOMXPath($jdb_dom);
105
-  }
106
-
107
-  list($idols, $genres, $screens, $title, $title_jp, $year, $studio, $label, $desc, $image) = array([],[],[],'','','','','','','');
108
-
109
-  if (!$jdb_page && !$jlib_page_jp && !$jlib_page_en) {
110
-    json_die('failure', 'Movie not found');
111
-  }
112
-
113
-  $degraded = false;
114
-
115
-  if ($jlib_page_jp && $jlib_jp->query('//*[@id="video_title"]')['length']) {
116
-    $title_jp = $jlib_jp->query('//*[@id="video_title"]/h3/a')->item(0)->nodeValue;
117
-    $title_jp = substr($title_jp, strlen($cn) + 1);
118
-  } else {
119
-    $degraded = true;
120
-  }
121
-  if ($jlib_page_en && $jlib_en->query('//*[@id="video_title"]')['length']) {
122
-    $title = $jlib_en->query('//*[@id="video_title"]/h3/a')->item(0)->nodeValue;
123
-    $title = substr($title, strlen($cn) + 1);
124
-    $idols = [];
125
-    foreach ($jlib_en->query('//*[starts-with(@id, "cast")]/span[1]/a') as $idol) {
126
-      $idols[] = $idol->nodeValue;
127
-    }
128
-    $year = $jlib_en->query('//*[@id="video_date"]/table/tr/td[2]')->item(0)->nodeValue;
129
-    $year = explode('-', $year)[0];
130
-    $studio = $jlib_en->query('//*[starts-with(@id, "maker")]/a')->item(0)->nodeValue;
131
-    $label = $jlib_en->query('//*[starts-with(@id, "label")]/a')->item(0)->nodeValue;
132
-    $image = $jlib_en->query('//*[@id="video_jacket_img"]')->item(0)->getAttribute('src');
133
-    $comments = "";
134
-    foreach ($jlib_en->query('//*[@class="comment"]//*[@class="t"]//textarea') as $comment) {
135
-      $comments .= ($comment->nodeValue).' ';
136
-    }
137
-    preg_match_all("/\[img\b[^\]]*\]([^\[]*?)\[\/img\](?!\[\/url)/is", $comments, $screens_t);
138
-    if (isset($screens_t[1])) {
139
-      $screens = $screens_t[1];
140
-      function f($s) { return !(preg_match('/(rapidgator)|(uploaded)|(javsecret)|(\.gif)|(google)|(thumb)|(imgur)|(fileboom)|(openload)/', $s)); }
141
-      $screens = array_values(array_filter($screens, f));
142
-    }
143
-    if (preg_match('/http:\/\/imagetwist.com\/\S*jpg.html/', $comments, $twist)) {
144
-      $twist_t = file_get_contents($twist[0]);
145
-      $twist = new DOMDocument();
146
-      $twist->loadHTML($twist_t);
147
-      $twist = new DOMXPath($twist);
148
-      if ($twist->query('//img[@class="pic"]')->item(0)) {
149
-        $screens[] =  $twist->query('//img[@class="pic"]')->item(0)->getAttribute('src');
150
-      }
151
-    }
152
-    $desc = '';
153
-    $genres = [];
154
-    foreach ($jlib_en->query('//*[starts-with(@id, "genre")]/a') as $genre) {
155
-      $genres[] =  str_replace(' ', '.', strtolower($genre->nodeValue));
156
-    }
157
-  } else {
158
-    $degraded = true;
159
-  }
160
-  if ($jdb_page) {
161
-    if (!$title) {
162
-      $title = trim(substr($jdb->query("//h1[contains(@class, 'entry-title')]")[0]->nodeValue, strlen($cn) + 3));
163
-    }
164
-    if (!$studio) {
165
-      $studio = $jdb->query("//b[contains(., 'Studio:')]")[0]->nextSibling->nodeValue;
166
-    }
167
-    if (!$label) {
168
-      $label = $jdb->query("//b[contains(., 'Label:')]")[0]->nextSibling->nodeValue;
169
-    }
170
-    if (!$idols) {
171
-      $idols_raw = $jdb->query("//b[contains(., 'Idol(s): ')]")[0]->nextSibling;
172
-
173
-      for ($i = 0; $i < 10; $i++) {
174
-        if ($idols_raw->tagName == "a") {
175
-          $idol_name = $idols_raw->nodeValue;
176
-          $idol_lower = strtolower(str_replace(' ', '-', $idol_name));
177
-          // ensure it's actually an idol name
178
-          if (strpos($idols_raw->attributes->item(0)->nodeValue, '.com/idols/' . $idol_lower) !== false) {
179
-            $idols[] = $idols_raw->nodeValue;
180
-          }
181
-        }
182
-        $idols_raw = $idols_raw->nextSibling;
183
-      }
184
-    }
185
-    if (!$year) {
186
-      $year = substr($jdb->query("//b[contains(., 'Release Date:')]")[0]->nextSibling->nodeValue, 1, 4);
187
-    }
188
-    if (!$image) {
189
-      $image = $jdb->query("//img[contains(@alt, ' download or stream.')]")->item(0)->getAttribute('src');
190
-    }
191
-    if (substr($image, 0, 2) == '//') {
192
-      $image = 'https:'.$image;
193
-    }
194
-    if (!$desc) {
195
-      // Shit neither of the sites have descriptions
196
-      $desc = '';
197
-    }
198
-  }
199
-
200
-  if (!($title || $idols || $year || $studio || $label || $genres)) {
201
-    json_die('failure', 'Movie not found');
202
-  }
203
-
204
-  // Only show "genres" we have tags for
205
-  if (!$Cache->get_value('genre_tags')) {
206
-    $DB->query('
207
-      SELECT Name
208
-      FROM tags
209
-      WHERE TagType = \'genre\'
210
-      ORDER BY Name');
211
-    $Cache->cache_value('genre_tags', $DB->collect('Name'), 3600 * 6);
212
-  }
213
-  $genres = array_values(array_intersect(array_values($Cache->get_value('genre_tags')), str_replace('_','.',array_values(Tags::remove_aliases(array('include' => str_replace('.','_',$genres)))['include']))));
214
-
215
-  $json = array(
216
-    'cn'          => $cn,
217
-    'title'       => ($title ? $title : ''),
218
-    'title_jp'    => ($title_jp ? $title_jp : ''),
219
-    'idols'       => ($idols ? $idols : []),
220
-    'year'        => ($year ? $year : ''),
221
-    'studio'      => ($studio ? $studio : ''),
222
-    'label'       => ($label ? $label : ''),
223
-    'image'       => ($image ? $image : ''),
224
-    'description' => ($desc ? $desc : ''),
225
-    'tags'        => ($genres ? $genres : []),
226
-    'screens'     => ($screens ? $screens : []),
227
-    'degraded'    => $degraded
228
-  );
229
-
230
-  $Cache->cache_value('jav_fill_json_'.$cn, $json, 86400);
231
-
232
-  json_die('success', $json);
233
-*/
234
-}

+ 0
- 106
sections/ajax/autofill/manga.php View File

@@ -1,106 +0,0 @@
1
-<?php
2
-#declare(strict_types=1);
3
-
4
-if (empty($_GET['url'])) {
5
-    json_die();
6
-}
7
-
8
-$url = str_replace('exhentai', 'e-hentai', $_GET['url']);
9
-
10
-$matches = [];
11
-preg_match('/^https?:\/\/g?\.?e.hentai\.org\/g\/(\d+)\/([\w\d]+)\/?$/', $url, $matches);
12
-
13
-$gid = $matches[1] ?? '';
14
-$token = $matches[2] ?? '';
15
-
16
-if (empty($gid) || empty($token)) {
17
-    json_die("failure", "Invalid URL");
18
-}
19
-
20
-if ($Cache->get_value('manga_fill_json_'.$gid)) {
21
-    json_die("success", $Cache->get_value('manga_fill_json_'.$gid));
22
-} else {
23
-    $data = json_encode(["method" => "gdata", "gidlist" => [[$gid, $token]], "namespace" => 1]);
24
-    $curl = curl_init('http://api.e-hentai.org/api.php');
25
-    curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
26
-    curl_setopt($curl, CURLOPT_TIMEOUT, 10);
27
-    curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
28
-    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
29
-    curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Content-Length: '.strlen($data)]);
30
-
31
-    $json = curl_exec($curl);
32
-
33
-    if (empty($json)) {
34
-        json_die("failure", "Could not get page");
35
-    }
36
-
37
-    $json = json_decode($json, true)["gmetadata"][0];
38
-
39
-    $artists = [];
40
-    $tags = [];
41
-    $lang = null;
42
-    $circle = null;
43
-    $censored = true;
44
-    foreach ($json["tags"] as $tag) {
45
-        if (strpos($tag, ':') !== false) {
46
-            list($namespace, $tag) = explode(':', $tag);
47
-        } else {
48
-            $namespace = '';
49
-        }
50
-
51
-        if ($namespace == "artist") {
52
-            array_push($artists, ucwords($tag));
53
-        } elseif ($namespace == "group") {
54
-            $circle = empty($circle) ? ucfirst($tag) : $circle;
55
-        } elseif ($tag == "uncensored") {
56
-            $censored = false;
57
-        } else {
58
-            if ($namespace) {
59
-                $tag = $tag.':'.$namespace;
60
-            }
61
-            array_push($tags, str_replace(' ', '.', $tag));
62
-        }
63
-    }
64
-
65
-    // get the cover for ants
66
-    $cover = $json['thumb'];
67
-    // and let's see if we can replace it with something better
68
-    $gallery_page = file_get_contents($url);
69
-    $re = '/'.preg_quote('-0px 0 no-repeat"><a href="').'(.*)'.preg_quote('"><img alt="01"').'/';
70
-    preg_match($re, $gallery_page, $galmatch);
71
-    // were we able to find the first page of the gallery?
72
-    if ($galmatch[1]) {
73
-        $image_page = file_get_contents($galmatch[1]);
74
-        $re = '/'.preg_quote('"><img id="img" src="').'([^<]*)'.preg_quote('" style=').'/';
75
-        preg_match($re, $image_page, $imgmatch);
76
-        // were we able to find the image url?
77
-        if ($imgmatch[1]) {
78
-            $cover = $imgmatch[1];
79
-        }
80
-    }
81
-
82
-    $title = html_entity_decode($json['title'], ENT_QUOTES);
83
-    $title = preg_replace("/\(([^()]*+|(?R))*\)/", "", $title);
84
-    $title = trim(preg_replace("/\[([^\[\]]*+|(?R))*\]/", "", $title));
85
-    $title_jp = html_entity_decode($json['title_jpn'], ENT_QUOTES);
86
-    $title_jp = preg_replace("/\(([^()]*+|(?R))*\)/", "", $title_jp);
87
-    $title_jp = trim(preg_replace("/\[([^\[\]]*+|(?R))*\]/", "", $title_jp));
88
-
89
-    $json_str = [
90
-    'id'          => $gid,
91
-    'title'       => $title,
92
-    'title_jp'    => $title_jp,
93
-    'artists'     => $artists,
94
-    'circle'      => $circle,
95
-    'censored'    => $censored,
96
-    'year'        => null,
97
-    'tags'        => $tags,
98
-    'lang'        => $lang ?? 'Japanese',
99
-    'description' => '',
100
-    'cover'       => $cover
101
-  ];
102
-
103
-    $Cache->cache_value('manga_fill_json_'.$gid, $json_str, 86400);
104
-
105
-    json_die("success", $json_str);
106
-}

+ 0
- 292
sections/ajax/index.php View File

@@ -1,292 +0,0 @@
1
-<?php
2
-declare(strict_types = 1);
3
-
4
-/**
5
- * AJAX Switch Center
6
- *
7
- * This page acts as an AJAX "switch" - it's called by scripts, and it includes the required pages.
8
- * The required page is determined by $_GET['action'].
9
- */
10
-
11
-# $_POST login cookie
12
-if (!isset($FullToken)) {
13
-    enforce_login();
14
-}
15
-
16
-/*
17
-# I wish...
18
-else {
19
-  authorize(true);
20
-}
21
-*/
22
-
23
-
24
-/**
25
- * These users aren't rate limited.
26
- * This array should contain user IDs.
27
- */
28
-
29
-# Get people with Donor permissions
30
-$Donors = $DB->query("
31
-SELECT
32
-  `ID`
33
-FROM
34
-  `users_main`
35
-WHERE
36
-  `PermissionID` = 20
37
-");
38
-
39
-# Add Donors to $UserExceptions or define manually
40
-if ($DB->record_count()) {
41
-    $UserExceptions = array_unique($DB->collect('ID'));
42
-} else {
43
-    $UserExceptions = array(
44
-      # 1, 2, 3, etc.
45
-    );
46
-}
47
-
48
-# System and admin fix
49
-array_push($UserExceptions, 0, 1);
50
-
51
-
52
-/**
53
- * AJAX_LIMIT = array($x, $y) = $x requests every $y seconds,
54
- * e.g., array(5, 10) = 5 requests every 10 seconds.
55
- */
56
-$AJAX_LIMIT = array(1, 6);
57
-$UserID = $LoggedUser['ID'];
58
-
59
-# Set proper headers for JSON output
60
-# https://github.com/OPSnet/Gazelle/blob/master/sections/ajax/index.php
61
-if (!empty($_SERVER['CONTENT_TYPE']) && substr($_SERVER['CONTENT_TYPE'], 0, 16) === 'application/json') {
62
-    $_POST = json_decode(file_get_contents('php://input'), true);
63
-}
64
-header('Content-Type: application/json; charset=utf-8');
65
-
66
-//  Enforce rate limiting everywhere except info.php
67
-if (!in_array($UserID, $UserExceptions) && isset($_GET['action'])) {
68
-    if (!$UserRequests = $Cache->get_value("ajax_requests_$UserID")) {
69
-        $UserRequests = 0;
70
-        $Cache->cache_value("ajax_requests_$UserID", '0', $AJAX_LIMIT[1]);
71
-    }
72
-
73
-    if ($UserRequests > $AJAX_LIMIT[0]) {
74
-        json_die('failure', 'rate limit exceeded');
75
-    } else {
76
-        $Cache->increment_value("ajax_requests_$UserID");
77
-    }
78
-}
79
-
80
-
81
-/**
82
- * Actions
83
- */
84
-switch ($_GET['action']) {
85
-  /**
86
-   * Torrents
87
-   */
88
-  case 'torrent':
89
-    require 'torrent.php';
90
-    break;
91
-
92
-  case 'torrentgroup':
93
-    require 'torrentgroup.php';
94
-    break;
95
-
96
-  // So the album art script can function without breaking the rate limit
97
-  case 'torrentgroupalbumart':
98
-    require SERVER_ROOT.'/sections/ajax/torrentgroupalbumart.php';
99
-    break;
100
-
101
-  case 'browse':
102
-    require SERVER_ROOT.'/sections/ajax/browse.php';
103
-    break;
104
-  
105
-   case 'tcomments':
106
-    require SERVER_ROOT.'/sections/ajax/tcomments.php';
107
-    break;
108
-
109
-  /**
110
-   * Features
111
-   */
112
-  case 'collage':
113
-    require SERVER_ROOT.'/sections/ajax/collage.php';
114
-    break;
115
-  
116
-  case 'artist':
117
-    require SERVER_ROOT.'/sections/ajax/artist.php';
118
-    break;
119
-
120
-  case 'request':
121
-    require SERVER_ROOT.'/sections/ajax/request.php';
122
-    break;
123
-
124
-  case 'requests':
125
-    require SERVER_ROOT.'/sections/ajax/requests.php';
126
-    break;
127
-
128
-  case 'top10':
129
-    require SERVER_ROOT.'/sections/ajax/top10/index.php';
130
-    break;
131
-
132
-  /**
133
-   * Users
134
-   */
135
-  case 'user':
136
-    require SERVER_ROOT.'/sections/ajax/user.php';
137
-    break;
138
-
139
-  case 'usersearch':
140
-    require SERVER_ROOT.'/sections/ajax/usersearch.php';
141
-    break;
142
-  
143
-  case 'community_stats':
144
-    require SERVER_ROOT.'/sections/ajax/community_stats.php';
145
-    break;
146
-
147
-  case 'user_recents':
148
-    require SERVER_ROOT.'/sections/ajax/user_recents.php';
149
-    break;
150
-
151
-  case 'userhistory':
152
-    require SERVER_ROOT.'/sections/ajax/userhistory/index.php';
153
-    break;
154
-
155
-  /**
156
-   * Account
157
-   */
158
-  case 'inbox':
159
-    require SERVER_ROOT.'/sections/ajax/inbox/index.php';
160
-    break;
161
-
162
-  case 'bookmarks':
163
-    require SERVER_ROOT.'/sections/ajax/bookmarks/index.php';
164
-    break;
165
-
166
-  case 'notifications':
167
-    require SERVER_ROOT.'/sections/ajax/notifications.php';
168
-    break;
169
-
170
-  case 'get_user_notifications':
171
-    require SERVER_ROOT.'/sections/ajax/get_user_notifications.php';
172
-    break;
173
-
174
-  case 'clear_user_notification':
175
-    require SERVER_ROOT.'/sections/ajax/clear_user_notification.php';
176
-    break;
177
-
178
-  /**
179
-   * Forums
180
-   */
181
-  case 'forum':
182
-    require SERVER_ROOT.'/sections/ajax/forum/index.php';
183
-    break;
184
-
185
-
186
-  case 'subscriptions':
187
-    require SERVER_ROOT.'/sections/ajax/subscriptions.php';
188
-    break;
189
-
190
-  case 'raw_bbcode':
191
-    require SERVER_ROOT.'/sections/ajax/raw_bbcode.php';
192
-    break;
193
-
194
-  /**
195
-   * Meta
196
-   */
197
-  case 'index':
198
-    require SERVER_ROOT.'/sections/ajax/info.php';
199
-    break;
200
-
201
-  case 'manifest':
202
-    require SERVER_ROOT.'/manifest.php';
203
-    json_die('success', manifest());
204
-    break;
205
-
206
-  case 'stats':
207
-    require SERVER_ROOT.'/sections/ajax/stats.php';
208
-    break;
209
-
210
-  case 'loadavg':
211
-    require SERVER_ROOT.'/sections/ajax/loadavg.php';
212
-    break;
213
-
214
-  case 'announcements':
215
-    require SERVER_ROOT.'/sections/ajax/announcements.php';
216
-    break;
217
-
218
-  case 'wiki':
219
-    require SERVER_ROOT.'/sections/ajax/wiki.php';
220
-    break;
221
-  
222
-  case 'ontology':
223
-    require SERVER_ROOT.'/sections/ajax/ontology.php';
224
-    break;
225
-  
226
-  /**
227
-   * Under construction
228
-   */
229
-  case 'preview':
230
-    require 'preview.php';
231
-    break;
232
-
233
-  case 'better':
234
-    require SERVER_ROOT.'/sections/ajax/better/index.php';
235
-    break;
236
-
237
-  case 'get_friends':
238
-    require SERVER_ROOT.'/sections/ajax/get_friends.php';
239
-    break;
240
-
241
-  case 'news_ajax':
242
-    require SERVER_ROOT.'/sections/ajax/news_ajax.php';
243
-    break;
244
-
245
-  case 'send_recommendation':
246
-    require SERVER_ROOT.'/sections/ajax/send_recommendation.php';
247
-    break;
248
-
249
-  /*
250
-  case 'similar_artists':
251
-    require SERVER_ROOT.'/sections/ajax/similar_artists.php';
252
-    break;
253
-  */
254
-
255
-  /*
256
-  case 'votefavorite':
257
-    require SERVER_ROOT.'/sections/ajax/takevote.php';
258
-    break;
259
-  */
260
-
261
-  /*
262
-  case 'torrent_info':
263
-    require 'torrent_info.php';
264
-    break;
265
-  */
266
-
267
-  /*
268
-  case 'checkprivate':
269
-    include 'checkprivate.php';
270
-    break;
271
-  */
272
-
273
-  case 'autofill':
274
-    /*
275
-    if ($_GET['cat'] === 'anime') {
276
-        require SERVER_ROOT.'/sections/ajax/autofill/anime.php';
277
-    }
278
-
279
-    if ($_GET['cat'] === 'jav') {
280
-        require SERVER_ROOT.'/sections/ajax/autofill/jav.php';
281
-    }
282
-
283
-    if ($_GET['cat'] === 'manga') {
284
-        require SERVER_ROOT.'/sections/ajax/autofill/manga.php';
285
-    }
286
-    */
287
-    break;
288
-
289
-  default:
290
-    // If they're screwing around with the query string
291
-    json_die('failure');
292
-}

+ 0
- 101
sections/ajax/torrentgroup.php View File

@@ -1,101 +0,0 @@
1
-<?php
2
-#declare(strict_types=1);
3
-
4
-require SERVER_ROOT.'/sections/torrents/functions.php';
5
-
6
-$GroupID = (int) $_GET['id'];
7
-$TorrentHash = (string) $_GET['hash'];
8
-
9
-if ($GroupID && $TorrentHash) {
10
-    json_die('failure', 'bad parameters');
11
-}
12
-
13
-if ($TorrentHash) {
14
-    if (!is_valid_torrenthash($TorrentHash)) {
15
-        json_die('failure', 'bad hash parameter');
16
-    } else {
17
-        $GroupID = (int) torrenthash_to_groupid($TorrentHash);
18
-        if (!$GroupID) {
19
-            json_die('failure', 'bad hash parameter');
20
-        }
21
-    }
22
-}
23
-
24
-if ($GroupID <= 0) {
25
-    json_die('failure', 'bad id parameter');
26
-}
27
-
28
-$TorrentCache = get_group_info($GroupID, true, 0, true, true);
29
-if (!$TorrentCache) {
30
-    json_die('failure', 'bad id parameter');
31
-}
32
-
33
-list($TorrentDetails, $TorrentList) = $TorrentCache;
34
-$Artists = Artists::get_artist($GroupID);
35
-
36
-if ($TorrentDetails['CategoryID'] === 0) {
37
-    $CategoryName = 'Unknown';
38
-} else {
39
-    $CategoryName = $Categories[$TorrentDetails['CategoryID'] - 1];
40
-}
41
-
42
-$TagList = explode('|', $TorrentDetails['GROUP_CONCAT(DISTINCT tags.Name SEPARATOR \'|\')']);
43
-
44
-$JsonTorrentDetails = [
45
-  'description'  => Text::full_format($TorrentDetails['WikiBody']),
46
-  'picture'      => $TorrentDetails['WikiImage'],
47
-  'id'           => (int) $TorrentDetails['ID'],
48
-  'name'         => $TorrentDetails['Name'],
49
-  'organism'     => $TorrentDetails['Title2'],
50
-  'strain'       => $TorrentDetails['NameJP'],
51
-  'authors'      => $Artists,
52
-  'year'         => (int) $TorrentDetails['Year'],
53
-  'accession'    => $TorrentDetails['CatalogueNumber'],
54
-  'categoryId'   => (int) $TorrentDetails['CategoryID'],
55
-  'categoryName' => $CategoryName,
56
-  'time'         => $TorrentDetails['Time'],
57
-  'isBookmarked' => Bookmarks::has_bookmarked('torrent', $GroupID),
58
-  'tags'         => $TagList
59
-];
60
-
61
-$JsonTorrentList = [];
62
-foreach ($TorrentList as $Torrent) {
63
-    // Convert file list back to the old format
64
-    $FileList = explode("\n", $Torrent['FileList']);
65
-    foreach ($FileList as &$File) {
66
-        $File = Torrents::filelist_old_format($File);
67
-    }
68
-
69
-    unset($File);
70
-    $FileList = implode('|||', $FileList);
71
-    $Userinfo = Users::user_info($Torrent['UserID']);
72
-
73
-    $Reports = Torrents::get_reports($Torrent['ID']);
74
-    $Torrent['Reported'] = count($Reports) > 0;
75
-
76
-    $JsonTorrentList[] = [
77
-      'id'          => (int) $Torrent['ID'],
78
-      'infoHash'    => $Torrent['InfoHash'],
79
-      'platform'    => $Torrent['Media'],
80
-      'format'      => $Torrent['Container'],
81
-      'license'     => $Torrent['Codec'],
82
-      'scope'       => $Torrent['Resolution'],
83
-      'annotated'   => (bool) $Torrent['Censored'],
84
-      'archive'     => $Torrent['Archive'],
85
-      'fileCount'   => (int) $Torrent['FileCount'],
86
-      'size'        => (int) $Torrent['Size'],
87
-      'seeders'     => (int) $Torrent['Seeders'],
88
-      'leechers'    => (int) $Torrent['Leechers'],
89
-      'snatched'    => (int) $Torrent['Snatched'],
90
-      'freeTorrent' => ($Torrent['FreeTorrent'] == 1),
91
-      'reported'    => (bool) $Torrent['Reported'],
92
-      'time'        => $Torrent['Time'],
93
-      'description' => $Torrent['Description'],
94
-      'fileList'    => $FileList,
95
-      'filePath'    => $Torrent['FilePath'],
96
-      'userId'      => (int) ($Torrent['Anonymous'] ? 0 : $Torrent['UserID']),
97
-      'username'    => ($Torrent['Anonymous'] ? 'Anonymous' : $Userinfo['Username'])
98
-    ];
99
-}
100
-
101
-json_die('success', ['group' => $JsonTorrentDetails, 'torrents' => $JsonTorrentList]);

sections/ajax/announcements.php → sections/api/announcements.php View File


sections/ajax/artist.php → sections/api/artist.php View File

@@ -134,22 +134,24 @@ if (empty($LoggedUser['DisableRequests'])) {
134 134
 $NumRequests = count($Requests);
135 135
 
136 136
 if (($Importances = $Cache->get_value("artist_groups_$ArtistID")) === false) {
137
-    /*
138 137
     $DB->query("
139
-      SELECT
140
-        DISTINCTROW ta.GroupID, ta.Importance, tg.VanityHouse, tg.Year
141
-      FROM torrents_artists AS ta
142
-        JOIN torrents_group AS tg ON tg.ID = ta.GroupID
143
-      WHERE ta.ArtistID = '$ArtistID'
144
-      ORDER BY tg.Year DESC, tg.Name DESC");
145
-    */
146
-    $DB->query("
147
-    SELECT
148
-      DISTINCTROW ta.GroupID, ta.Importance, tg.Year
149
-    FROM torrents_artists AS ta
150
-      JOIN torrents_group AS tg ON tg.ID = ta.GroupID
151
-    WHERE ta.ArtistID = '$ArtistID'
152
-    ORDER BY tg.Year DESC, tg.Name DESC");
138
+    SELECT DISTINCTROW
139
+      ta.`GroupID`,
140
+      ta.`Importance`,
141
+      tg.`year`
142
+    FROM
143
+      `torrents_artists` AS ta
144
+    JOIN `torrents_group` AS tg
145
+    ON
146
+      tg.`id` = ta.`GroupID`
147
+    WHERE
148
+      ta.`ArtistID` = '$ArtistID'
149
+    ORDER BY
150
+      tg.`year`,
151
+      tg.`Name`
152
+    DESC
153
+    ");
154
+    
153 155
     $GroupIDs = $DB->collect('GroupID');
154 156
     $Importances = $DB->to_array(false, MYSQLI_BOTH, false);
155 157
     $Cache->cache_value("artist_groups_$ArtistID", $Importances, 0);

+ 32
- 0
sections/api/autofill/doi.php View File

@@ -0,0 +1,32 @@
1
+<?php
2
+declare(strict_types=1);
3
+
4
+$ENV = ENV::go();
5
+
6
+if (!$_GET['doi']) {
7
+    json_error('expected doi param');
8
+} elseif (!preg_match("/$ENV->DOI_REGEX/", strtoupper($_GET['doi']))) {
9
+    json_error('expected valid doi');
10
+} else {
11
+    $DOI = $_GET['doi'];
12
+}
13
+
14
+# https://weichie.com/blog/curl-api-calls-with-php/
15
+$curl = curl_init();
16
+curl_setopt($curl, CURLOPT_URL, "$ENV->SS/$DOI");
17
+curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
18
+curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
19
+$output = curl_exec($curl);
20
+curl_close($curl);
21
+
22
+# I don't like this nested json_*code() business
23
+# It's slow and unnecesary since SS already outputs JSON
24
+# todo: At least cache the response, then refactor
25
+print
26
+    json_encode(
27
+        [
28
+            'status' => 'success',
29
+            'response' => json_decode($output, true),
30
+        ],
31
+        JSON_UNESCAPED_SLASHES
32
+    );

sections/ajax/better/index.php → sections/api/better/index.php View File


sections/ajax/better/single.php → sections/api/better/single.php View File


sections/ajax/better/transcode.php → sections/api/better/transcode.php View File


sections/ajax/bookmarks/artists.php → sections/api/bookmarks/artists.php View File


sections/ajax/bookmarks/index.php → sections/api/bookmarks/index.php View File


sections/ajax/bookmarks/torrents.php → sections/api/bookmarks/torrents.php View File


sections/ajax/browse.php → sections/api/browse.php View File


sections/ajax/clear_user_notification.php → sections/api/clear_user_notification.php View File


sections/ajax/collage.php → sections/api/collage.php View File


sections/ajax/community_stats.php → sections/api/community_stats.php View File


sections/ajax/forum/forum.php → sections/api/forum/forum.php View File


sections/ajax/forum/index.php → sections/api/forum/index.php View File


sections/ajax/forum/main.php → sections/api/forum/main.php View File


sections/ajax/forum/thread.php → sections/api/forum/thread.php View File

@@ -28,7 +28,7 @@ if (!isset($_GET['threadid']) || !is_number($_GET['threadid'])) {
28 28
 
29 29
         if ($ThreadID) {
30 30
             // Redirect postid to threadid when necessary
31
-            header("Location: ajax.php?action=forum&type=viewthread&threadid=$ThreadID&postid=$_GET[postid]");
31
+            header("Location: api.php?action=forum&type=viewthread&threadid=$ThreadID&postid=$_GET[postid]");
32 32
             error();
33 33
         } else {
34 34
             echo json_encode(array('status' => 'failure'));

sections/ajax/get_friends.php → sections/api/get_friends.php View File


sections/ajax/get_user_notifications.php → sections/api/get_user_notifications.php View File


sections/ajax/inbox/inbox.php → sections/api/inbox/inbox.php View File


sections/ajax/inbox/index.php → sections/api/inbox/index.php View File


sections/ajax/inbox/viewconv.php → sections/api/inbox/viewconv.php View File


+ 288
- 0
sections/api/index.php View File

@@ -0,0 +1,288 @@
1
+<?php
2
+declare(strict_types = 1);
3
+
4
+/**
5
+ * AJAX switch center
6
+ *
7
+ * This page acts as an AJAX "switch" - it's called by scripts, and it includes the required pages.
8
+ * The required page is determined by $_GET['action'].
9
+ */
10
+
11
+$ENV = ENV::go();
12
+
13
+# $_POST login cookie
14
+if (!isset($FullToken)) {
15
+    enforce_login();
16
+}
17
+
18
+
19
+/**
20
+ * These users aren't rate limited.
21
+ * This array should contain user IDs.
22
+ */
23
+
24
+# Get people with Donor permissions
25
+$Donors = $DB->query("
26
+SELECT
27
+  `ID`
28
+FROM
29
+  `users_main`
30
+WHERE
31
+  `PermissionID` = 20
32
+");
33
+
34
+# Add Donors to $UserExceptions or define manually
35
+if ($DB->record_count()) {
36
+    $UserExceptions = array_unique($DB->collect('ID'));
37
+} else {
38
+    $UserExceptions = array(
39
+      # 1, 2, 3, etc.
40
+    );
41
+}
42
+
43
+# System and admin fix
44
+array_push($UserExceptions, 0, 1);
45
+
46
+
47
+/**
48
+ * $AjaxLimit = array($x, $y) = $x requests every $y seconds,
49
+ * e.g., array(5, 10) = 5 requests every 10 seconds.
50
+ */
51
+$AjaxLimit = array(1, 6);
52
+$UserID = $LoggedUser['ID'];
53
+
54
+# Set proper headers for JSON output
55
+# https://github.com/OPSnet/Gazelle/blob/master/sections/api/index.php
56
+if (!empty($_SERVER['CONTENT_TYPE']) && substr($_SERVER['CONTENT_TYPE'], 0, 16) === 'application/json') {
57
+    $_POST = json_decode(file_get_contents('php://input'), true);
58
+}
59
+header('Content-Type: application/json; charset=utf-8');
60
+
61
+# Enforce rate limiting everywhere
62
+if (!in_array($UserID, $UserExceptions) && isset($_GET['action'])) {
63
+    if (!$UserRequests = $Cache->get_value("ajax_requests_$UserID")) {
64
+        $UserRequests = 0;
65
+        $Cache->cache_value("ajax_requests_$UserID", '0', $AjaxLimit[1]);
66
+    }
67
+
68
+    if ($UserRequests > $AjaxLimit[0]) {
69
+        json_die('failure', 'rate limit exceeded');
70
+    } else {
71
+        $Cache->increment_value("ajax_requests_$UserID");
72
+    }
73
+}
74
+
75
+
76
+/**
77
+ * Actions
78
+ */
79
+
80
+switch ($_GET['action']) {
81
+    /**
82
+     * Torrents
83
+     */
84
+    case 'torrent':
85
+      require_once "$ENV->SERVER_ROOT/sections/api/torrents/torrent.php";
86
+      break;
87
+
88
+    case 'group':
89
+      require_once "$ENV->SERVER_ROOT/sections/api/torrents/group.php";
90
+      break;
91
+
92
+  // So the album art script can function without breaking the rate limit
93
+  case 'torrentgroupalbumart':
94
+    require_once "$ENV->SERVER_ROOT/sections/api/torrentgroupalbumart.php";
95
+    break;
96
+
97
+  case 'browse':
98
+    require_once "$ENV->SERVER_ROOT/sections/api/browse.php";
99
+    break;
100
+  
101
+   case 'tcomments':
102
+    require_once "$ENV->SERVER_ROOT/sections/api/tcomments.php";
103
+    break;
104
+
105
+  /**
106
+   * Features
107
+   */
108
+  case 'collage':
109
+    require_once "$ENV->SERVER_ROOT/sections/api/collage.php";
110
+    break;
111
+  
112
+  case 'artist':
113
+    require_once "$ENV->SERVER_ROOT/sections/api/artist.php";
114
+    break;
115
+
116
+  case 'request':
117
+    require_once "$ENV->SERVER_ROOT/sections/api/request.php";
118
+    break;
119
+
120
+  case 'requests':
121
+    require_once "$ENV->SERVER_ROOT/sections/api/requests.php";
122
+    break;
123
+
124
+  case 'top10':
125
+    require_once "$ENV->SERVER_ROOT/sections/api/top10/index.php";
126
+    break;
127
+
128
+  /**
129
+   * Users
130
+   */
131
+  case 'user':
132
+    require_once "$ENV->SERVER_ROOT/sections/api/user.php";
133
+    break;
134
+
135
+  case 'usersearch':
136
+    require_once "$ENV->SERVER_ROOT/sections/api/usersearch.php";
137
+    break;
138
+  
139
+  case 'community_stats':
140
+    require_once "$ENV->SERVER_ROOT/sections/api/community_stats.php";
141
+    break;
142
+
143
+  case 'user_recents':
144
+    require_once "$ENV->SERVER_ROOT/sections/api/user_recents.php";
145
+    break;
146
+
147
+  case 'userhistory':
148
+    require_once "$ENV->SERVER_ROOT/sections/api/userhistory/index.php";
149
+    break;
150
+
151
+  /**
152
+   * Account
153
+   */
154
+  case 'inbox':
155
+    require_once "$ENV->SERVER_ROOT/sections/api/inbox/index.php";
156
+    break;
157
+
158
+  case 'bookmarks':
159
+    require_once "$ENV->SERVER_ROOT/sections/api/bookmarks/index.php";
160
+    break;
161
+
162
+  case 'notifications':
163
+    require_once "$ENV->SERVER_ROOT/sections/api/notifications.php";
164
+    break;
165
+
166
+  case 'get_user_notifications':
167
+    require_once "$ENV->SERVER_ROOT/sections/api/get_user_notifications.php";
168
+    break;
169
+
170
+  case 'clear_user_notification':
171
+    require_once "$ENV->SERVER_ROOT/sections/api/clear_user_notification.php";
172
+    break;
173
+
174
+  /**
175
+   * Forums
176
+   */
177
+  case 'forum':
178
+    require_once "$ENV->SERVER_ROOT/sections/api/forum/index.php";
179
+    break;
180
+
181
+  case 'subscriptions':
182
+    require_once "$ENV->SERVER_ROOT/sections/api/subscriptions.php";
183
+    break;
184
+
185
+  case 'raw_bbcode':
186
+    require_once "$ENV->SERVER_ROOT/sections/api/raw_bbcode.php";
187
+    break;
188
+
189
+  /**
190
+   * Meta
191
+   */
192
+  case 'index':
193
+    require_once "$ENV->SERVER_ROOT/sections/api/info.php";
194
+    break;
195
+
196
+  case 'manifest':
197
+    require_once "$ENV->SERVER_ROOT/manifest.php";
198
+    json_die('success', manifest());
199
+    break;
200
+
201
+  case 'stats':
202
+    require_once "$ENV->SERVER_ROOT/sections/api/stats.php";
203
+    break;
204
+
205
+  case 'loadavg':
206
+    require_once "$ENV->SERVER_ROOT/sections/api/loadavg.php";
207
+    break;
208
+
209
+  case 'announcements':
210
+    require_once "$ENV->SERVER_ROOT/sections/api/announcements.php";
211
+    break;
212
+
213
+  case 'wiki':
214
+    require_once "$ENV->SERVER_ROOT/sections/api/wiki.php";
215
+    break;
216
+  
217
+  case 'ontology':
218
+    require_once "$ENV->SERVER_ROOT/sections/api/ontology.php";
219
+    break;
220
+  
221
+  /**
222
+   * Under construction
223
+   */
224
+  case 'preview':
225
+    require_once "$ENV->SERVER_ROOT/sections/api/preview.php";
226
+    break;
227
+
228
+  case 'better':
229
+    require_once "$ENV->SERVER_ROOT/sections/api/better/index.php";
230
+    break;
231
+
232
+  case 'get_friends':
233
+    require_once "$ENV->SERVER_ROOT/sections/api/get_friends.php";
234
+    break;
235
+
236
+  case 'news_ajax':
237
+    require_once "$ENV->SERVER_ROOT/sections/api/news_ajax.php";
238
+    break;
239
+
240
+  case 'send_recommendation':
241
+    require_once "$ENV->SERVER_ROOT/sections/api/send_recommendation.php";
242
+    break;
243
+
244
+  /*
245
+  case 'similar_artists':
246
+    require_once "$ENV->SERVER_ROOT/sections/api/similar_artists.php";
247
+    break;
248
+  */
249
+
250
+  /*
251
+  case 'votefavorite':
252
+    require_once "$ENV->SERVER_ROOT/sections/api/takevote.php";
253
+    break;
254
+  */
255
+
256
+  /*
257
+  case 'torrent_info':
258
+    require_once "$ENV->SERVER_ROOT/sections/api/torrent_info.php";
259
+    break;
260
+  */
261
+
262
+  /*
263
+  case 'checkprivate':
264
+    include "$ENV->SERVER_ROOT/sections/api/checkprivate.php";
265
+    break;
266
+  */
267
+
268
+  case 'autofill':
269
+    require_once "$ENV->SERVER_ROOT/sections/api/autofill/doi.php";
270
+    /*
271
+    if ($_GET['cat'] === 'anime') {
272
+        require_once "$ENV->SERVER_ROOT/sections/api/autofill/anime.php";
273
+    }
274
+
275
+    if ($_GET['cat'] === 'jav') {
276
+        require_once "$ENV->SERVER_ROOT/sections/api/autofill/jav.php";
277
+    }
278
+
279
+    if ($_GET['cat'] === 'manga') {
280
+        require_once "$ENV->SERVER_ROOT/sections/api/autofill/manga.php";
281
+    }
282
+    */
283
+    break;
284
+
285
+  default:
286
+    // If they're screwing around with the query string
287
+    json_die('failure');
288
+}

sections/ajax/info.php → sections/api/info.php View File


sections/ajax/loadavg.php → sections/api/loadavg.php View File

@@ -1,5 +1,5 @@
1 1
 <?php
2
-#declare(strict_types=1);
2
+declare(strict_types=1);
3 3
 
4 4
 #authorize();
5 5
 

sections/ajax/news_ajax.php → sections/api/news_ajax.php View File


sections/ajax/notifications.php → sections/api/notifications.php View File


sections/ajax/ontology.php → sections/api/ontology.php View File


sections/ajax/preview.php → sections/api/preview.php View File


sections/ajax/raw_bbcode.php → sections/api/raw_bbcode.php View File


sections/ajax/request.php → sections/api/request.php View File


sections/ajax/requests.php → sections/api/requests.php View File


sections/ajax/send_recommendation.php → sections/api/send_recommendation.php View File

@@ -1,25 +1,33 @@
1 1
 <?php
2
-#declare(strict_types=1);
2
+declare(strict_types=1);
3 3
 
4
-$FriendID = (int)$_POST['friend'];
4
+$FriendID = (int) $_POST['friend'];
5 5
 $Type = $_POST['type'];
6
-$ID = (int)$_POST['id'];
6
+$ID = (int) $_POST['id'];
7 7
 $Note = $_POST['note'];
8 8
 
9 9
 if (empty($FriendID) || empty($Type) || empty($ID)) {
10 10
     echo json_encode(array('status' => 'error', 'response' => 'Error.'));
11 11
     error();
12 12
 }
13
+
13 14
 // Make sure the recipient is on your friends list and not some random dude.
14
-$DB->query("
15
-  SELECT f.FriendID, u.Username
16
-  FROM friends AS f
17
-    RIGHT JOIN users_enable_recommendations AS r
18
-      ON r.ID = f.FriendID AND r.Enable = 1
19
-    RIGHT JOIN users_main AS u
20
-      ON u.ID = f.FriendID
21
-  WHERE f.UserID = '$LoggedUser[ID]'
22
-    AND f.FriendID = '$FriendID'");
15
+$DB->prepare_query("
16
+SELECT
17
+  f.`FriendID`,
18
+  u.`Username`
19
+FROM
20
+  `friends` AS f
21
+RIGHT JOIN `users_enable_recommendations` AS r
22
+ON
23
+  r.`ID` = f.`FriendID` AND r.`Enable` = 1
24
+RIGHT JOIN `users_main` AS u
25
+ON
26
+  u.`ID` = f.`FriendID`
27
+WHERE
28
+  f.`UserID` = '$LoggedUser[ID]' AND f.`FriendID` = '$FriendID'
29
+");
30
+$DB->exec_prepared_query();
23 31
 
24 32
 if (!$DB->has_results()) {
25 33
     echo json_encode(array('status' => 'error', 'response' => 'Not on friend list.'));
@@ -32,32 +40,51 @@ $Link = '';
32 40
 // https://en.wikipedia.org/wiki/English_articles#Distinction_between_a_and_an
33 41
 $Article = 'a';
34 42
 switch ($Type) {
35
-  case 'torrent':
43
+    case 'torrent':
36 44
     $Link = "torrents.php?id=$ID";
37 45
     $DB->query("
38
-      SELECT Name
39
-      FROM torrents_group
40
-      WHERE ID = '$ID'");
46
+    SELECT
47
+      `title`
48
+    FROM
49
+      `torrents_group`
50
+    WHERE
51
+      `id` = '$ID'
52
+    ");
41 53
     break;
42
-  case 'artist':
54
+
55
+    case 'artist':
43 56
     $Article = 'an';
44 57
     $Link = "artist.php?id=$ID";
45 58
     $DB->query("
46
-      SELECT Name
47
-      FROM artists_group
48
-      WHERE ArtistID = '$ID'");
59
+    SELECT
60
+      `Name`
61
+    FROM
62
+      `artists_group`
63
+    WHERE
64
+      `ArtistID` = '$ID'
65
+    ");
49 66
     break;
50
-  case 'collage':
67
+
68
+    case 'collage':
51 69
     $Link = "collages.php?id=$ID";
52 70
     $DB->query("
53
-      SELECT Name
54
-      FROM collages
55
-      WHERE ID = '$ID'");
71
+    SELECT
72
+      `Name`
73
+    FROM
74
+      `collages`
75
+    WHERE
76
+      `ID` = '$ID'
77
+    ");
78
+    break;
79
+
80
+    default:
56 81
     break;
57 82
 }
83
+
58 84
 list($Name) = $DB->next_record();
59 85
 $Subject = $LoggedUser['Username'] . " recommended you $Article $Type!";
60 86
 $Body = $LoggedUser['Username'] . " recommended you the $Type [url=".site_url()."$Link]$Name".'[/url].';
87
+
61 88
 if (!empty($Note)) {
62 89
     $Body = "$Body\n\n$Note";
63 90
 }

sections/ajax/stats.php → sections/api/stats.php View File

@@ -1,5 +1,5 @@
1 1
 <?php
2
-#declare(strict_types=1);
2
+declare(strict_types=1);
3 3
 
4 4
 // Begin user stats
5 5
 if (($UserCount = $Cache->get_value('stats_user_count')) === false) {
@@ -67,12 +67,13 @@ if (($TorrentCount = $Cache->get_value('stats_torrent_count')) === false) {
67 67
 if (($AlbumCount = $Cache->get_value('stats_album_count')) === false) {
68 68
     $DB->query("
69 69
     SELECT
70
-      COUNT(`ID`)
70
+      COUNT(`id`)
71 71
     FROM
72 72
       `torrents_group`
73 73
     WHERE
74
-      `CategoryID` = '1'
74
+      `category_id` = '1'
75 75
     ");
76
+
76 77
     list($AlbumCount) = $DB->next_record();
77 78
     $Cache->cache_value('stats_album_count', $AlbumCount, 604830); // staggered 1 week cache
78 79
 }
@@ -84,6 +85,7 @@ if (($ArtistCount = $Cache->get_value('stats_artist_count')) === false) {
84 85
     FROM
85 86
       `artists_group`
86 87
     ");
88
+    
87 89
     list($ArtistCount) = $DB->next_record();
88 90
     $Cache->cache_value('stats_artist_count', $ArtistCount, 604860); // staggered 1 week cache
89 91
 }

sections/ajax/subscriptions.php → sections/api/subscriptions.php View File


sections/ajax/tcomments.php → sections/api/tcomments.php View File


sections/ajax/top10/index.php → sections/api/top10/index.php View File


sections/ajax/top10/tags.php → sections/api/top10/tags.php View File


sections/ajax/top10/torrents.php → sections/api/top10/torrents.php View File

@@ -18,23 +18,29 @@ $Limit = isset($_GET['limit']) ? intval($_GET['limit']) : 10;
18 18
 $Limit = in_array($Limit, array(10, 100, 250)) ? $Limit : 10;
19 19
 
20 20
 $WhereSum = (empty($Where)) ? '' : md5($Where);
21
-$BaseQuery = '
22
-  SELECT
23
-    t.ID,
24
-    g.ID,
25
-    g.Name,
26
-    g.CategoryID,
27
-    g.WikiImage,
28
-    g.TagList,
29
-    t.Media,
30
-    g.Year,
31
-    t.Snatched,
32
-    t.Seeders,
33
-    t.Leechers,
34
-    ((t.Size * t.Snatched) + (t.Size * 0.5 * t.Leechers)) AS Data,
35
-    t.Size
36
-  FROM torrents AS t
37
-    LEFT JOIN torrents_group AS g ON g.ID = t.GroupID';
21
+$BaseQuery = "
22
+SELECT
23
+  t.`ID`,
24
+  g.`id`,
25
+  g.`title`,
26
+  g.`category_id`,
27
+  g.`picture`,
28
+  g.`tag_list`,
29
+  t.`Media`,
30
+  g.`year`,
31
+  t.`Snatched`,
32
+  t.`Seeders`,
33
+  t.`Leechers`,
34
+  (
35
+    (t.`Size` * t.`Snatched`) +(t.`Size` * 0.5 * t.`Leechers`)
36
+  ) AS `Data`,
37
+  t.`Size`
38
+FROM
39
+  `torrents` AS t
40
+LEFT JOIN `torrents_group` AS g
41
+ON
42
+  g.`id` = t.`GroupID`
43
+";
38 44
 
39 45
 $OuterResults = [];
40 46
 

sections/ajax/top10/users.php → sections/api/top10/users.php View File


+ 166
- 0
sections/api/torrents/group.php View File

@@ -0,0 +1,166 @@
1
+<?php
2
+#declare(strict_types=1);
3
+
4
+$ENV = ENV::go();
5
+require_once "$ENV->SERVER_ROOT/sections/torrents/functions.php";
6
+
7
+# Either id or hash
8
+$GroupID = (int) $_GET['id'];
9
+$TorrentHash = (string) $_GET['hash'];
10
+
11
+# Error if both supplied
12
+if ($GroupID && $TorrentHash) {
13
+    json_die('failure', 'bad parameters');
14
+}
15
+
16
+# Get id from hash
17
+if ($TorrentHash) {
18
+    if (!is_valid_torrenthash($TorrentHash)) {
19
+        json_die('failure', 'bad hash parameter');
20
+    } else {
21
+        $GroupID = (int) torrenthash_to_groupid($TorrentHash);
22
+        if (!$GroupID) {
23
+            json_die('failure', 'bad hash parameter');
24
+        }
25
+    }
26
+}
27
+
28
+# Error if bad id
29
+if ($GroupID <= 0) {
30
+    json_die('failure', 'bad id parameter');
31
+}
32
+
33
+$TorrentCache = get_group_info($GroupID, true, 0, true, true);
34
+if (!$TorrentCache) {
35
+    json_die('failure', 'bad id parameter');
36
+}
37
+
38
+# Get torrent details (group, torrents, artists)
39
+list($TorrentDetails, $TorrentList) = $TorrentCache;
40
+$Artists = Artists::get_artist($GroupID);
41
+
42
+# Get category name if possible
43
+if ($TorrentDetails['category_id'] === 0) {
44
+    $CategoryName = 'Unknown';
45
+} else {
46
+    $CategoryName = $Categories[$TorrentDetails['category_id'] - 1];
47
+}
48
+
49
+# Get tag list (name and id)
50
+$TagIDs = explode('|', $TorrentDetails["GROUP_CONCAT(DISTINCT tags.`ID` SEPARATOR '|')"]);
51
+$TagNames= explode('|', $TorrentDetails["GROUP_CONCAT(DISTINCT tags.`Name` SEPARATOR '|')"]);
52
+
53
+$TagList = [];
54
+foreach ($TagIDs as $Key => $ID) {
55
+    array_push(
56
+        $TagList,
57
+        [
58
+            'id'   => $ID,
59
+            'name' => $TagNames[$Key],
60
+        ]
61
+    );
62
+}
63
+
64
+# Get citation list (doi and id)
65
+# todo: Update DB schema
66
+$Citations = [];
67
+foreach ($TorrentDetails['Screenshots'] as $Citation) {
68
+    array_push(
69
+        $Citations,
70
+        [
71
+            'id'        => $Citation['ID'],
72
+            'doi'       => $Citation['URI'],
73
+           #'timestamp' => $Citation['Time'],
74
+        ]
75
+    );
76
+}
77
+
78
+# Torrent group response
79
+# todo: Add seeding, leeching, snatched
80
+$JsonTorrentDetails = [
81
+    'id'            => (int) $TorrentDetails['id'],
82
+    'identifier'    => $TorrentDetails['identifier'],
83
+
84
+    'categoryId'   => (int) $TorrentDetails['category_id'],
85
+    'categoryName' => $CategoryName,
86
+
87
+    'title'         => $TorrentDetails['title'],
88
+    'subject'       => $TorrentDetails['subject'],
89
+    'object'        => $TorrentDetails['object'],
90
+
91
+    'authors'       => $Artists,
92
+    'year'          => (int) $TorrentDetails['year'],
93
+    'workgroup'     => $TorrentDetails['workgroup'],
94
+    'location'      => $TorrentDetails['location'],
95
+
96
+    'citations'     => $Citations,
97
+    'mirrors'       => ($TorrentDetails['Mirrors']) ?: false,
98
+  
99
+    'description'   => $TorrentDetails['description'],
100
+   #'description'   => Text::full_format($TorrentDetails['description']),
101
+    'picture'       => $TorrentDetails['picture'],
102
+    'tagList'      => $TagList,
103
+
104
+    'bookmarked'    => Bookmarks::has_bookmarked('torrent', $GroupID),
105
+    'timestamp'     => $TorrentDetails['timestamp'],
106
+];
107
+
108
+# Torrents in group
109
+$JsonTorrentList = [];
110
+foreach ($TorrentList as $Torrent) {
111
+    # Convert file list back to the old format
112
+    $FileList = explode("\n", $Torrent['FileList']);
113
+
114
+    foreach ($FileList as &$File) {
115
+        $File = Torrents::filelist_old_format($File);
116
+    }
117
+
118
+    # todo: Make a nested object
119
+    # todo: Limit to 100 files
120
+    unset($File);
121
+    $FileList = implode('|||', $FileList);
122
+    $Userinfo = Users::user_info($Torrent['UserID']);
123
+
124
+    $Reports = Torrents::get_reports($Torrent['ID']);
125
+    $Torrent['Reported'] = count($Reports) > 0;
126
+
127
+    # Torrent details response
128
+    # todo: Update DB schema
129
+    $JsonTorrentList[] = [
130
+        'id'           => (int) $Torrent['ID'],
131
+        'infoHash'    => $Torrent['InfoHash'],
132
+        'description'  => $Torrent['Description'],
133
+
134
+        'platform'     => $Torrent['Media'],
135
+        'format'       => $Torrent['Container'],
136
+        'scope'        => $Torrent['Resolution'],
137
+        'annotated'    => (bool) $Torrent['Censored'],
138
+        'license'      => $Torrent['Codec'],
139
+
140
+        'size'         => (int) $Torrent['Size'],
141
+        'archive'      => $Torrent['Archive'],
142
+        'fileCount'   => (int) $Torrent['FileCount'],
143
+        'filePath'    => $Torrent['FilePath'],
144
+        'fileList'    => $FileList,
145
+
146
+        'seeders'      => (int) $Torrent['Seeders'],
147
+        'leechers'     => (int) $Torrent['Leechers'],
148
+        'snatched'     => (int) $Torrent['Snatched'],
149
+        'freeTorrent' => ($Torrent['FreeTorrent'] === 1),
150
+
151
+        'reported'     => (bool) $Torrent['Reported'],
152
+        'time'         => $Torrent['Time'],
153
+
154
+        'userId'      => (int) ($Torrent['Anonymous'] ? 0 : $Torrent['UserID']),
155
+        'username'     => ($Torrent['Anonymous'] ? 'Anonymous' : $Userinfo['Username']),
156
+    ];
157
+}
158
+
159
+# Print response
160
+json_die(
161
+    'success',
162
+    [
163
+        'group' => $JsonTorrentDetails,
164
+        'torrents' => $JsonTorrentList,
165
+    ]
166
+);

sections/ajax/torrent.php → sections/api/torrents/torrent.php View File

@@ -1,7 +1,7 @@
1 1
 <?php
2 2
 #declare(strict_types=1);
3 3
 
4
-require SERVER_ROOT.'/sections/torrents/functions.php';
4
+require_once SERVER_ROOT.'/sections/torrents/functions.php';
5 5
 
6 6
 $TorrentID = (int) $_GET['id'];
7 7
 $TorrentHash = (string) $_GET['hash'];
@@ -38,29 +38,29 @@ if (!isset($TorrentList[$TorrentID])) {
38 38
 $GroupID = $TorrentDetails['ID'];
39 39
 $Artists = Artists::get_artist($GroupID);
40 40
 
41
-if ($TorrentDetails['CategoryID'] === 0) {
41
+if ($TorrentDetails['category_id'] === 0) {
42 42
     $CategoryName = 'Unknown';
43 43
 } else {
44
-    $CategoryName = $Categories[$TorrentDetails['CategoryID'] - 1];
44
+    $CategoryName = $Categories[$TorrentDetails['category_id'] - 1];
45 45
 }
46 46
 
47 47
 $TagList = explode('|', $TorrentDetails['GROUP_CONCAT(DISTINCT tags.Name SEPARATOR \'|\')']);
48 48
 
49 49
 $JsonTorrentDetails = [
50
-  'description'  => Text::full_format($TorrentDetails['WikiBody']),
51
-  'picture'      => $TorrentDetails['WikiImage'],
52
-  'id'           => (int) $TorrentDetails['ID'],
53
-  'name'         => $TorrentDetails['Name'],
54
-  'organism'     => $TorrentDetails['Title2'],
55
-  'strain'       => $TorrentDetails['NameJP'],
50
+  'description'  => Text::full_format($TorrentDetails['description']),
51
+  'picture'      => $TorrentDetails['picture'],
52
+  'id'           => (int) $TorrentDetails['id'],
53
+  'title'         => $TorrentDetails['title'],
54
+  'subject'     => $TorrentDetails['subject'],
55
+  'object'       => $TorrentDetails['object'],
56 56
   'authors'      => $Artists,
57
-  'year'         => (int) $TorrentDetails['Year'],
58
-  'accession'    => $TorrentDetails['CatalogueNumber'],
59
-  'categoryId'   => (int) $TorrentDetails['CategoryID'],
60
-  'categoryName' => $CategoryName,
61
-  'time'         => $TorrentDetails['Time'],
62
-  'isBookmarked' => Bookmarks::has_bookmarked('torrent', $GroupID),
63
-  'tags'         => $TagList
57
+  'year'         => (int) $TorrentDetails['published'],
58
+  'identifier'    => $TorrentDetails['identifier'],
59
+  'categoryId'   => (int) $TorrentDetails['category_id'],
60
+  'icategoryName' => $CategoryName,
61
+  'timestamp'         => $TorrentDetails['timestamp'],
62
+  'bookmarked' => Bookmarks::has_bookmarked('torrent', $GroupID),
63
+  'tagList'         => $TagList
64 64
 ];
65 65
 
66 66
 $Torrent = $TorrentList[$TorrentID];

sections/ajax/torrentgroupalbumart.php → sections/api/torrents/torrentgroupalbumart.php View File

@@ -1,7 +1,7 @@
1 1
 <?php
2 2
 declare(strict_types=1);
3 3
 
4
-require SERVER_ROOT.'/sections/torrents/functions.php';
4
+require_once SERVER_ROOT.'/sections/torrents/functions.php';
5 5
 
6 6
 $GroupID = (int) $_GET['id'];
7 7
 if ($GroupID === 0) {

sections/ajax/user.php → sections/api/user.php View File


sections/ajax/user_recents.php → sections/api/user_recents.php View File

@@ -17,18 +17,26 @@ $Results = [];
17 17
 if (check_paranoia_here('snatched')) {
18 18
     $DB->query("
19 19
     SELECT
20
-      g.ID,
21
-      g.Name,
22
-      g.WikiImage
23
-    FROM xbt_snatched AS s
24
-      INNER JOIN torrents AS t ON t.ID = s.fid
25
-      INNER JOIN torrents_group AS g ON t.GroupID = g.ID
26
-    WHERE s.uid = '$UserID'
27
-      AND g.CategoryID = '1'
28
-      AND g.WikiImage != ''
29
-    GROUP BY g.ID
30
-    ORDER BY s.tstamp DESC
31
-    LIMIT $Limit");
20
+      g.`id`,
21
+      g.`title`,
22
+      g.`picture`
23
+    FROM
24
+      `xbt_snatched` AS s
25
+    INNER JOIN `torrents` AS t
26
+    ON
27
+      t.`ID` = s.`fid`
28
+    INNER JOIN `torrents_group` AS g
29
+    ON
30
+      t.`GroupID` = g.`id`
31
+    WHERE
32
+      s.`uid` = '$UserID' AND g.`category_id` = '1' AND g.`picture` != ''
33
+    GROUP BY
34
+      g.`id`
35
+    ORDER BY
36
+      s.`tstamp`
37
+    DESC
38
+    LIMIT $Limit
39
+    ");
32 40
 
33 41
     $RecentSnatches = $DB->to_array(false, MYSQLI_ASSOC);
34 42
     $Artists = Artists::get_artists($DB->collect('ID'));
@@ -45,17 +53,23 @@ if (check_paranoia_here('snatched')) {
45 53
 if (check_paranoia_here('uploads')) {
46 54
     $DB->query("
47 55
     SELECT
48
-      g.ID,
49
-      g.Name,
50
-      g.WikiImage
51
-    FROM torrents_group AS g
52
-      INNER JOIN torrents AS t ON t.GroupID = g.ID
53
-    WHERE t.UserID = '$UserID'
54
-      AND g.CategoryID = '1'
55
-      AND g.WikiImage != ''
56
-    GROUP BY g.ID
57
-    ORDER BY t.Time DESC
58
-    LIMIT $Limit");
56
+      g.`id`,
57
+      g.`title`,
58
+      g.`picture`
59
+    FROM
60
+      `torrents_group` AS g
61
+    INNER JOIN `torrents` AS t
62
+    ON
63
+      t.`GroupID` = g.`id`
64
+    WHERE
65
+      t.`UserID` = '$UserID' AND g.`category_id` = '1' AND g.`picture` != ''
66
+    GROUP BY
67
+      g.`id`
68
+    ORDER BY
69
+      t.`Time`
70
+    DESC
71
+    LIMIT $Limit
72
+    ");
59 73
 
60 74
     $RecentUploads = $DB->to_array(false, MYSQLI_ASSOC);
61 75
     $Artists = Artists::get_artists($DB->collect('ID'));

sections/ajax/userhistory/index.php → sections/api/userhistory/index.php View File


sections/ajax/userhistory/post_history.php → sections/api/userhistory/post_history.php View File


sections/ajax/usersearch.php → sections/api/usersearch.php View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save