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

+ 5
- 16
classes/autoload.php View File

11
  * @see https://www.php.net/manual/en/language.oop5.autoload.php
11
  * @see https://www.php.net/manual/en/language.oop5.autoload.php
12
  */
12
  */
13
 spl_autoload_register(function ($ClassName) {
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
     if (!file_exists($FilePath)) {
19
     if (!file_exists($FilePath)) {
18
         // todo: Rename the following classes to conform with the code guidelines
20
         // todo: Rename the following classes to conform with the code guidelines
43
           $FileName = 'env.class';
45
           $FileName = 'env.class';
44
           break;
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
         default:
48
         default:
59
           error("Couldn't import class $ClassName");
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
     require_once $FilePath;
55
     require_once $FilePath;

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

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

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

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

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

168
         $this->URL .= "&chl=".implode('|', $Labels).'&chd=e:'.implode('', $Data);
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
     public static function increase_subscriptions($CollageID)
6
     public static function increase_subscriptions($CollageID)
7
     {
7
     {
8
         $QueryID = G::$DB->get_query_id();
8
         $QueryID = G::$DB->get_query_id();
9
-        G::$DB->query("
9
+        G::$DB->prepared_query("
10
         UPDATE
10
         UPDATE
11
           `collages`
11
           `collages`
12
         SET
12
         SET
20
     public static function decrease_subscriptions($CollageID)
20
     public static function decrease_subscriptions($CollageID)
21
     {
21
     {
22
         $QueryID = G::$DB->get_query_id();
22
         $QueryID = G::$DB->get_query_id();
23
-        G::$DB->query("
23
+        G::$DB->prepared_query("
24
         UPDATE
24
         UPDATE
25
           `collages`
25
           `collages`
26
         SET
26
         SET
37
 
37
 
38
     public static function create_personal_collage()
38
     public static function create_personal_collage()
39
     {
39
     {
40
-        G::$DB->query("
40
+        G::$DB->prepared_query("
41
         SELECT
41
         SELECT
42
           COUNT(`ID`)
42
           COUNT(`ID`)
43
         FROM
43
         FROM
57
         $NameStr = db_string(G::$LoggedUser['Username']."'s personal collage".($CollageCount > 0 ? ' no. '.($CollageCount + 1) : ''));
57
         $NameStr = db_string(G::$LoggedUser['Username']."'s personal collage".($CollageCount > 0 ? ' no. '.($CollageCount + 1) : ''));
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].');
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
         INSERT INTO `collages`(
61
         INSERT INTO `collages`(
62
           `Name`,
62
           `Name`,
63
           `Description`,
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
 <?php
1
 <?php
2
 declare(strict_types=1);
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
 class Donations
4
 class Donations
8
 {
5
 {
9
     private static $IsSchedule = false;
6
     private static $IsSchedule = false;
10
 
7
 
8
+
9
+    /**
10
+     * regular_donate
11
+     */
11
     public static function regular_donate($UserID, $DonationAmount, $Source, $Reason, $Currency = 'USD')
12
     public static function regular_donate($UserID, $DonationAmount, $Source, $Reason, $Currency = 'USD')
12
     {
13
     {
13
         self::donate(
14
         self::donate(
23
         );
24
         );
24
     }
25
     }
25
 
26
 
27
+
28
+    /**
29
+     * donate
30
+     */
26
     public static function donate($UserID, $Args)
31
     public static function donate($UserID, $Args)
27
     {
32
     {
28
         $UserID = (int) $UserID;
33
         $UserID = (int) $UserID;
220
         G::$DB->set_query_id($QueryID);
225
         G::$DB->set_query_id($QueryID);
221
     }
226
     }
222
 
227
 
228
+
229
+    /**
230
+     * calculate_special_rank
231
+     */
223
     private static function calculate_special_rank($UserID)
232
     private static function calculate_special_rank($UserID)
224
     {
233
     {
225
         $UserID = (int) $UserID;
234
         $UserID = (int) $UserID;
272
         G::$DB->set_query_id($QueryID);
281
         G::$DB->set_query_id($QueryID);
273
     }
282
     }
274
 
283
 
284
+
285
+    /**
286
+     * expire_ranks
287
+     */
275
     public static function expire_ranks()
288
     public static function expire_ranks()
276
     {
289
     {
277
         $QueryID = G::$DB->get_query_id();
290
         $QueryID = G::$DB->get_query_id();
309
         G::$DB->set_query_id($QueryID);
322
         G::$DB->set_query_id($QueryID);
310
     }
323
     }
311
 
324
 
325
+
326
+    /**
327
+     * calculate_rank
328
+     */
312
     private static function calculate_rank($Amount)
329
     private static function calculate_rank($Amount)
313
     {
330
     {
314
         return floor($Amount / 5);
331
         return floor($Amount / 5);
315
     }
332
     }
316
 
333
 
334
+
335
+    /**
336
+     * update_rank
337
+     */
317
     public static function update_rank($UserID, $Rank, $TotalRank, $Reason)
338
     public static function update_rank($UserID, $Rank, $TotalRank, $Reason)
318
     {
339
     {
319
         $Rank = (int) $Rank;
340
         $Rank = (int) $Rank;
332
         );
353
         );
333
     }
354
     }
334
 
355
 
356
+
357
+    /**
358
+     * hide_stats
359
+     */
335
     public static function hide_stats($UserID)
360
     public static function hide_stats($UserID)
336
     {
361
     {
337
         $QueryID = G::$DB->get_query_id();
362
         $QueryID = G::$DB->get_query_id();
345
         G::$DB->set_query_id($QueryID);
370
         G::$DB->set_query_id($QueryID);
346
     }
371
     }
347
 
372
 
373
+
374
+    /**
375
+     * show_stats
376
+     */
348
     public static function show_stats($UserID)
377
     public static function show_stats($UserID)
349
     {
378
     {
350
         $QueryID = G::$DB->get_query_id();
379
         $QueryID = G::$DB->get_query_id();
358
         G::$DB->set_query_id($QueryID);
387
         G::$DB->set_query_id($QueryID);
359
     }
388
     }
360
 
389
 
390
+
391
+    /**
392
+     * is_visible
393
+     */
361
     public static function is_visible($UserID)
394
     public static function is_visible($UserID)
362
     {
395
     {
363
         $QueryID = G::$DB->get_query_id();
396
         $QueryID = G::$DB->get_query_id();
375
         return $HasResults;
408
         return $HasResults;
376
     }
409
     }
377
 
410
 
411
+
412
+    /**
413
+     * has_donor_forum
414
+     */
378
     public static function has_donor_forum($UserID)
415
     public static function has_donor_forum($UserID)
379
     {
416
     {
380
         $ENV = ENV::go();
417
         $ENV = ENV::go();
381
         return self::get_rank($UserID) >= $ENV->DONOR_FORUM_RANK || self::get_special_rank($UserID) >= MAX_SPECIAL_RANK;
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
      * Put all the common donor info in the same cache key to save some cache calls
423
      * Put all the common donor info in the same cache key to save some cache calls
386
      */
424
      */
452
         return $DonorInfo;
490
         return $DonorInfo;
453
     }
491
     }
454
 
492
 
493
+
494
+    /**
495
+     * get_rank
496
+     */
455
     public static function get_rank($UserID)
497
     public static function get_rank($UserID)
456
     {
498
     {
457
         return self::get_donor_info($UserID)['Rank'];
499
         return self::get_donor_info($UserID)['Rank'];
458
     }
500
     }
459
 
501
 
502
+
503
+    /**
504
+     * get_special_rank
505
+     */
460
     public static function get_special_rank($UserID)
506
     public static function get_special_rank($UserID)
461
     {
507
     {
462
         return self::get_donor_info($UserID)['SRank'];
508
         return self::get_donor_info($UserID)['SRank'];
463
     }
509
     }
464
 
510
 
511
+
512
+    /**
513
+     * get_total_rank
514
+     */
465
     public static function get_total_rank($UserID)
515
     public static function get_total_rank($UserID)
466
     {
516
     {
467
         return self::get_donor_info($UserID)['TotRank'];
517
         return self::get_donor_info($UserID)['TotRank'];
468
     }
518
     }
469
 
519
 
520
+
521
+    /**
522
+     * get_donation_time
523
+     */
470
     public static function get_donation_time($UserID)
524
     public static function get_donation_time($UserID)
471
     {
525
     {
472
         return self::get_donor_info($UserID)['Time'];
526
         return self::get_donor_info($UserID)['Time'];
473
     }
527
     }
474
 
528
 
529
+
530
+    /**
531
+     * get_personal_collages
532
+     */
475
     public static function get_personal_collages($UserID)
533
     public static function get_personal_collages($UserID)
476
     {
534
     {
477
         $DonorInfo = self::get_donor_info($UserID);
535
         $DonorInfo = self::get_donor_info($UserID);
483
         return $Collages;
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
     public static function get_enabled_rewards($UserID)
548
     public static function get_enabled_rewards($UserID)
511
     {
549
     {
512
         $Rewards = [];
550
         $Rewards = [];
553
         return $Rewards;
591
         return $Rewards;
554
     }
592
     }
555
 
593
 
594
+
595
+    /**
596
+     * get_rewards
597
+     */
556
     public static function get_rewards($UserID)
598
     public static function get_rewards($UserID)
557
     {
599
     {
558
         return self::get_donor_info($UserID)['Rewards'];
600
         return self::get_donor_info($UserID)['Rewards'];
559
     }
601
     }
560
 
602
 
603
+
604
+    /**
605
+     * get_profile_rewards
606
+     */
561
     public static function get_profile_rewards($UserID)
607
     public static function get_profile_rewards($UserID)
562
     {
608
     {
563
         $Results = G::$Cache->get_value("donor_profile_rewards_$UserID");
609
         $Results = G::$Cache->get_value("donor_profile_rewards_$UserID");
587
         return $Results;
633
         return $Results;
588
     }
634
     }
589
 
635
 
636
+
637
+    /**
638
+     * add_profile_info_reward
639
+     */
590
     private static function add_profile_info_reward($Counter, &$Insert, &$Values, &$Update)
640
     private static function add_profile_info_reward($Counter, &$Insert, &$Values, &$Update)
591
     {
641
     {
592
         if (isset($_POST["profile_title_" . $Counter]) && isset($_POST["profile_info_" . $Counter])) {
642
         if (isset($_POST["profile_title_" . $Counter]) && isset($_POST["profile_info_" . $Counter])) {
603
         }
653
         }
604
     }
654
     }
605
 
655
 
656
+
657
+    /**
658
+     * update_rewards
659
+     */
606
     public static function update_rewards($UserID)
660
     public static function update_rewards($UserID)
607
     {
661
     {
608
         $Rank = self::get_rank($UserID);
662
         $Rank = self::get_rank($UserID);
663
                 $Values[] = "'$CustomIcon'";
717
                 $Values[] = "'$CustomIcon'";
664
                 $Update[] = "CustomIcon = '$CustomIcon'";
718
                 $Update[] = "CustomIcon = '$CustomIcon'";
665
             }
719
             }
666
-            self::update_titles($UserID, $_POST['donor_title_prefix'], $_POST['donor_title_suffix'], $_POST['donor_title_comma']);
667
             $Counter++;
720
             $Counter++;
668
         }
721
         }
669
 
722
 
705
         G::$Cache->delete_value("donor_info_$UserID");
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
     public static function get_donation_history($UserID)
765
     public static function get_donation_history($UserID)
740
     {
766
     {
741
         $UserID = (int) $UserID;
767
         $UserID = (int) $UserID;
743
             error(404);
769
             error(404);
744
         }
770
         }
745
 
771
 
746
-        # todo: Investigate Rank in donations table
747
         $QueryID = G::$DB->get_query_id();
772
         $QueryID = G::$DB->get_query_id();
748
         G::$DB->query("
773
         G::$DB->query("
749
         SELECT
774
         SELECT
770
         return $DonationHistory;
795
         return $DonationHistory;
771
     }
796
     }
772
 
797
 
798
+
799
+    /**
800
+     * get_rank_expiration
801
+     */
773
     public static function get_rank_expiration($UserID)
802
     public static function get_rank_expiration($UserID)
774
     {
803
     {
775
         $DonorInfo = self::get_donor_info($UserID);
804
         $DonorInfo = self::get_donor_info($UserID);
789
         return $Return;
818
         return $Return;
790
     }
819
     }
791
 
820
 
821
+
822
+    /**
823
+     * get_leaderboard_position
824
+     */
792
     public static function get_leaderboard_position($UserID)
825
     public static function get_leaderboard_position($UserID)
793
     {
826
     {
794
         $UserID = (int) $UserID;
827
         $UserID = (int) $UserID;
823
         return $Position;
856
         return $Position;
824
     }
857
     }
825
 
858
 
859
+
860
+    /**
861
+     * is_donor
862
+     */
826
     public static function is_donor($UserID)
863
     public static function is_donor($UserID)
827
     {
864
     {
828
         return self::get_rank($UserID) > 0;
865
         return self::get_rank($UserID) > 0;
829
     }
866
     }
830
 
867
 
868
+
869
+    /**
870
+     * currency_exchange
871
+     */
831
     public static function currency_exchange($Amount, $Currency)
872
     public static function currency_exchange($Amount, $Currency)
832
     {
873
     {
833
         if (!self::is_valid_currency($Currency)) {
874
         if (!self::is_valid_currency($Currency)) {
849
         return round($Amount, 2);
890
         return round($Amount, 2);
850
     }
891
     }
851
 
892
 
893
+
894
+    /**
895
+     * is_valid_currency
896
+     */
852
     public static function is_valid_currency($Currency)
897
     public static function is_valid_currency($Currency)
853
     {
898
     {
854
         return $Currency === 'EUR' || $Currency === 'BTC' || $Currency === 'USD';
899
         return $Currency === 'EUR' || $Currency === 'BTC' || $Currency === 'USD';
855
     }
900
     }
856
 
901
 
902
+
903
+    /**
904
+     * btc_to_euro
905
+     */
857
     public static function btc_to_euro($Amount)
906
     public static function btc_to_euro($Amount)
858
     {
907
     {
859
         $Rate = G::$Cache->get_value('btc_rate');
908
         $Rate = G::$Cache->get_value('btc_rate');
863
         return $Rate * $Amount;
912
         return $Rate * $Amount;
864
     }
913
     }
865
 
914
 
915
+
916
+    /**
917
+     * usd_to_euro
918
+     */
866
     public static function usd_to_euro($Amount)
919
     public static function usd_to_euro($Amount)
867
     {
920
     {
868
         $Rate = G::$Cache->get_value('usd_rate');
921
         $Rate = G::$Cache->get_value('usd_rate');

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

6
     public static function render_mod_donations($UserID)
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
   <tr class="colhead">
10
   <tr class="colhead">
11
     <td colspan="2">
11
     <td colspan="2">
12
       Donor System (add points)
12
       Donor System (add points)
34
   </tr>
34
   </tr>
35
 </table>
35
 </table>
36
 
36
 
37
-<table class="layout box" id="donor_points_box">
37
+<table class="box skeleton-fix" id="donor_points_box">
38
   <tr class="colhead">
38
   <tr class="colhead">
39
     <td colspan="3" class="tooltip"
39
     <td colspan="3" class="tooltip"
40
       title='Use this tool only when manually correcting values. If crediting donations normally, use the "Donor System (add points)" tool'>
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
 
29
 
30
 
30
 
31
     /**
31
     /**
32
-     * __functions()
32
+     * __functions
33
      */
33
      */
34
 
34
 
35
     # Prevents outside construction
35
     # Prevents outside construction
43
     # Prevents multiple instances
43
     # Prevents multiple instances
44
     public function __clone()
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
     # $this->key returns public->key
55
     # $this->key returns public->key
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
      * toArray
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
      * @see https://ben.lobaugh.net/blog/567/php-recursively-convert-an-object-to-an-array
142
      * @see https://ben.lobaugh.net/blog/567/php-recursively-convert-an-object-to-an-array
109
      */
143
      */
110
     public function toArray($obj)
144
     public function toArray($obj)
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
      * Maps a callback (or default) to an object.
219
      * Maps a callback (or default) to an object.
173
      *
220
      *
174
      * Example output:
221
      * Example output:
175
-     * $Hashes = $ENV->map('md5', $ENV->CATS->SEQ);
222
+     * $Hashes = $ENV->map('md5', $ENV->CATS->{6});
176
      *
223
      *
177
      * var_dump($Hashes);
224
      * var_dump($Hashes);
178
      * object(RecursiveArrayObject)#324 (1) {
225
      * object(RecursiveArrayObject)#324 (1) {
197
      * string(32) "52963afccc006d2bce3c890ad9e8f73a"
244
      * string(32) "52963afccc006d2bce3c890ad9e8f73a"
198
      *
245
      *
199
      * @param string $fn Callback function
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
      * @return object $RAO Mapped RecursiveArrayObject
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
         # Set a default function if desired
252
         # Set a default function if desired
206
         if (empty($fn) && !is_object($fn)) {
253
         if (empty($fn) && !is_object($fn)) {
227
 
274
 
228
         # Map the sanitized function name
275
         # Map the sanitized function name
229
         # to a mapped array conversion
276
         # to a mapped array conversion
230
-        return $RAO = new RecursiveArrayObject(
277
+        return new RecursiveArrayObject(
231
             array_map(
278
             array_map(
232
                 $fn,
279
                 $fn,
233
                 array_map(
280
                 array_map(
259
         return $this;
306
         return $this;
260
     }
307
     }
261
 
308
 
309
+
262
     /**
310
     /**
263
      * __set
311
      * __set
264
      */
312
      */
271
         }
319
         }
272
     }
320
     }
273
 
321
 
322
+
274
     /**
323
     /**
275
      * __get
324
      * __get
276
      */
325
      */
285
         }
334
         }
286
     }
335
     }
287
 
336
 
337
+
288
     /**
338
     /**
289
      * __isset
339
      * __isset
290
      */
340
      */
293
         return array_key_exists($name, $this);
343
         return array_key_exists($name, $this);
294
     }
344
     }
295
 
345
 
346
+    
296
     /**
347
     /**
297
      * __unset
348
      * __unset
298
      */
349
      */

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

4
 /**
4
 /**
5
  * Adapted from
5
  * Adapted from
6
  * https://github.com/OPSnet/Gazelle/blob/master/app/Json.php
6
  * https://github.com/OPSnet/Gazelle/blob/master/app/Json.php
7
- *
8
- * Unused as of 2020-12-12
9
  */
7
  */
10
 
8
 
11
 abstract class Json
9
 abstract class Json
14
     protected $source;
12
     protected $source;
15
     protected $mode;
13
     protected $mode;
16
 
14
 
15
+
17
     /**
16
     /**
18
      * __construct
17
      * __construct
19
      */
18
      */
25
         $this->version = 1;
24
         $this->version = 1;
26
     }
25
     }
27
 
26
 
27
+
28
     /**
28
     /**
29
      * The payload of a valid JSON response, implemented in the child class.
29
      * The payload of a valid JSON response, implemented in the child class.
30
      * @return array Payload to be passed to json_encode()
30
      * @return array Payload to be passed to json_encode()
32
      */
32
      */
33
     abstract public function payload(): ?array;
33
     abstract public function payload(): ?array;
34
 
34
 
35
+
35
     /**
36
     /**
36
      * Configure JSON printing (any of the json_encode  JSON_* constants)
37
      * Configure JSON printing (any of the json_encode  JSON_* constants)
37
      *
38
      *
43
         return $this;
44
         return $this;
44
     }
45
     }
45
 
46
 
47
+
46
     /**
48
     /**
47
      * set the version of the Json payload. Increment the
49
      * set the version of the Json payload. Increment the
48
      * value when there is significant change in the payload.
50
      * value when there is significant change in the payload.
56
         return $this;
58
         return $this;
57
     }
59
     }
58
 
60
 
61
+
59
     /**
62
     /**
60
      * General failure routine for when bad things happen.
63
      * General failure routine for when bad things happen.
61
      *
64
      *
77
         );
80
         );
78
     }
81
     }
79
 
82
 
83
+
80
     /**
84
     /**
81
      * emit
85
      * emit
82
      */
86
      */
99
         );
103
         );
100
     }
104
     }
101
 
105
 
106
+
102
     /**
107
     /**
103
      * debug
108
      * debug
104
      */
109
      */
116
         ];
121
         ];
117
     }
122
     }
118
 
123
 
124
+
119
     /**
125
     /**
120
      * info
126
      * info
121
      */
127
      */
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
         return ($this->watchId = G::$DB->inserted_id());
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
      * Ban subsequent attempts to login from this watched IP address for 6 hours
73
      * Ban subsequent attempts to login from this watched IP address for 6 hours

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

22
 
22
 
23
 * Making a query
23
 * Making a query
24
 
24
 
25
-$DB->query("
25
+$DB->prepared_query("
26
   SELECT *
26
   SELECT *
27
   FROM table...");
27
   FROM table...");
28
 
28
 
92
   This class can only hold one result set at a time. Using set_query_id allows
92
   This class can only hold one result set at a time. Using set_query_id allows
93
   you to set the result set that the class is using to the result set in
93
   you to set the result set that the class is using to the result set in
94
   $ResultSet. This result set should have been obtained earlier by using
94
   $ResultSet. This result set should have been obtained earlier by using
95
-  $DB->query().
95
+  $DB->prepared_query().
96
 
96
 
97
   Example:
97
   Example:
98
 
98
 
99
-  $FoodRS = $DB->query("
99
+  $FoodRS = $DB->prepared_query("
100
       SELECT *
100
       SELECT *
101
       FROM food");
101
       FROM food");
102
-  $DB->query("
102
+  $DB->prepared_query("
103
     SELECT *
103
     SELECT *
104
     FROM drink");
104
     FROM drink");
105
   $Drinks = $DB->next_record();
105
   $Drinks = $DB->next_record();
111
 -------------------------------------------------------------------------------------
111
 -------------------------------------------------------------------------------------
112
 *///---------------------------------------------------------------------------------
112
 *///---------------------------------------------------------------------------------
113
 
113
 
114
-if (!extension_loaded('mysqli')) {
115
-    error('Mysqli Extension not loaded.');
116
-}
117
-
118
 
114
 
119
 /**
115
 /**
120
  * db_string
116
  * db_string
121
- * Handles escaping
117
+ *
118
+ * Handles escaping.
122
  */
119
  */
123
 function db_string($String, $DisableWildcards = false)
120
 function db_string($String, $DisableWildcards = false)
124
 {
121
 {
269
                 $this->Database,
266
                 $this->Database,
270
                 $this->Port,
267
                 $this->Port,
271
                 $this->Socket,
268
                 $this->Socket,
269
+                # Needed for self-signed certs
272
                 MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT
270
                 MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT
273
             );
271
             );
274
 
272
 
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
         $this->connect();
306
         $this->connect();
291
-
292
         $this->StatementID = mysqli_prepare($this->LinkID, $Query);
307
         $this->StatementID = mysqli_prepare($this->LinkID, $Query);
308
+
293
         if (!empty($BindVars)) {
309
         if (!empty($BindVars)) {
294
             $Types = '';
310
             $Types = '';
295
             $TypeMap = ['string'=>'s', 'double'=>'d', 'integer'=>'i', 'boolean'=>'i'];
311
             $TypeMap = ['string'=>'s', 'double'=>'d', 'integer'=>'i', 'boolean'=>'i'];
304
         return $this->StatementID;
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
         $QueryStartTime = microtime(true);
335
         $QueryStartTime = microtime(true);
314
         mysqli_stmt_execute($this->StatementID);
336
         mysqli_stmt_execute($this->StatementID);
318
         $this->Time += $QueryRunTime;
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
      * Runs a raw query assuming pre-sanitized input. However, attempting to self sanitize (such
351
      * Runs a raw query assuming pre-sanitized input. However, attempting to self sanitize (such

+ 83
- 66
classes/permissions_form.php View File

1
 <?php
1
 <?php
2
 declare(strict_types=1);
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
 $PermissionsArray = array(
12
 $PermissionsArray = array(
12
   'site_leech' => 'Can leech (Does this work?).',
13
   'site_leech' => 'Can leech (Does this work?).',
38
   'site_recommend_own' => 'Can recommend own torrents.',
39
   'site_recommend_own' => 'Can recommend own torrents.',
39
   'site_manage_recommendations' => 'Recommendations management access.',
40
   'site_manage_recommendations' => 'Recommendations management access.',
40
   'site_delete_tag' => 'Can delete tags.',
41
   'site_delete_tag' => 'Can delete tags.',
41
-  'site_disable_ip_history' => 'Disable IP history.',
42
   'zip_downloader' => 'Download multiple torrents at once.',
42
   'zip_downloader' => 'Download multiple torrents at once.',
43
   'site_debug' => 'Developer access.',
43
   'site_debug' => 'Developer access.',
44
   'site_proxy_images' => 'Image proxy & anti-canary.',
44
   'site_proxy_images' => 'Image proxy & anti-canary.',
112
 
112
 
113
 function permissions_form()
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
     display_perm('site_leech', 'Can leech.');
126
     display_perm('site_leech', 'Can leech.');
126
     display_perm('site_upload', 'Can upload.');
127
     display_perm('site_upload', 'Can upload.');
127
     display_perm('site_vote', 'Can vote on requests.');
128
     display_perm('site_vote', 'Can vote on requests.');
152
     display_perm('site_recommend_own', 'Can add own torrents to recommendations list.');
153
     display_perm('site_recommend_own', 'Can add own torrents to recommendations list.');
153
     display_perm('site_manage_recommendations', 'Can edit recommendations list.');
154
     display_perm('site_manage_recommendations', 'Can edit recommendations list.');
154
     display_perm('site_delete_tag', 'Can delete tags.');
155
     display_perm('site_delete_tag', 'Can delete tags.');
155
-    display_perm('site_disable_ip_history', 'Disable IP history.');
156
     display_perm('zip_downloader', 'Download multiple torrents at once.');
156
     display_perm('zip_downloader', 'Download multiple torrents at once.');
157
     display_perm('site_debug', 'View site debug tables.');
157
     display_perm('site_debug', 'View site debug tables.');
158
     display_perm('site_proxy_images', 'Proxy images through the server.');
158
     display_perm('site_proxy_images', 'Proxy images through the server.');
161
     display_perm('site_forums_double_post', 'Can double post in the forums.');
161
     display_perm('site_forums_double_post', 'Can double post in the forums.');
162
     display_perm('project_team', 'Part of the project team.');
162
     display_perm('project_team', 'Part of the project team.');
163
     display_perm('site_tag_aliases_read', 'Can view the list of tag aliases.');
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
     display_perm('users_edit_usernames', 'Can edit usernames.');
182
     display_perm('users_edit_usernames', 'Can edit usernames.');
179
     display_perm('users_edit_ratio', 'Can edit anyone\'s upload/download amounts.');
183
     display_perm('users_edit_ratio', 'Can edit anyone\'s upload/download amounts.');
180
     display_perm('users_edit_own_ratio', 'Can edit own upload/download amounts.');
184
     display_perm('users_edit_own_ratio', 'Can edit own upload/download amounts.');
206
     display_perm('users_override_paranoia', 'Can override paranoia');
210
     display_perm('users_override_paranoia', 'Can override paranoia');
207
     display_perm('users_make_invisible', 'Can make users invisible');
211
     display_perm('users_make_invisible', 'Can make users invisible');
208
     display_perm('users_logout', 'Can log users out');
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
     display_perm('torrents_edit', 'Can edit any torrent');
234
     display_perm('torrents_edit', 'Can edit any torrent');
225
     display_perm('torrents_delete', 'Can delete torrents');
235
     display_perm('torrents_delete', 'Can delete torrents');
226
     display_perm('torrents_delete_fast', 'Can delete more than 3 torrents at a time.');
236
     display_perm('torrents_delete_fast', 'Can delete more than 3 torrents at a time.');
232
     display_perm('artist_edit_vanityhouse', 'Can mark artists as part of Vanity House.');
242
     display_perm('artist_edit_vanityhouse', 'Can mark artists as part of Vanity House.');
233
     display_perm('torrents_fix_ghosts', 'Can fix ghost groups on artist pages.');
243
     display_perm('torrents_fix_ghosts', 'Can fix ghost groups on artist pages.');
234
     display_perm('screenshots_add', 'Can add screenshots to any torrent and delete their own screenshots.');
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
     display_perm('admin_manage_news', 'Can manage site news');
263
     display_perm('admin_manage_news', 'Can manage site news');
250
     display_perm('admin_manage_blog', 'Can manage the site blog');
264
     display_perm('admin_manage_blog', 'Can manage the site blog');
251
     display_perm('admin_manage_polls', 'Can manage polls');
265
     display_perm('admin_manage_polls', 'Can manage polls');
261
     display_perm('admin_manage_permissions', 'Can edit permission classes/user permissions.');
275
     display_perm('admin_manage_permissions', 'Can edit permission classes/user permissions.');
262
     display_perm('admin_schedule', 'Can run the site schedule.');
276
     display_perm('admin_schedule', 'Can run the site schedule.');
263
     display_perm('admin_login_watch', 'Can manage login watch.');
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
 <?php
1
 <?php
2
 #declare(strict_types=1);
2
 #declare(strict_types=1);
3
 
3
 
4
-# https://www.php.net/manual/en/language.oop5.autoload.php
4
+# Initialize
5
 require_once 'config.php';
5
 require_once 'config.php';
6
 require_once 'security.class.php';
6
 require_once 'security.class.php';
7
 
7
 
8
-# Initialize
9
 $ENV = ENV::go();
8
 $ENV = ENV::go();
10
 $Security = new Security();
9
 $Security = new Security();
11
 $Security->SetupPitfalls();
10
 $Security->SetupPitfalls();
57
 }
56
 }
58
 ob_start(); // Start a buffer, mainly in case there is a mysql error
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
 $Debug = new DEBUG;
66
 $Debug = new DEBUG;
68
 $Debug->handle_errors();
67
 $Debug->handle_errors();
72
 $Cache = new Cache($ENV->getPriv('MEMCACHED_SERVERS'));
71
 $Cache = new Cache($ENV->getPriv('MEMCACHED_SERVERS'));
73
 
72
 
74
 // Autoload classes.
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
 // Note: G::initialize is called twice.
77
 // Note: G::initialize is called twice.
78
 // This is necessary as the code inbetween (initialization of $LoggedUser) makes use of G::$DB and G::$Cache.
78
 // This is necessary as the code inbetween (initialization of $LoggedUser) makes use of G::$DB and G::$Cache.
225
 
225
 
226
     $UserSessions = $Cache->get_value("users_sessions_$UserID");
226
     $UserSessions = $Cache->get_value("users_sessions_$UserID");
227
     if (!is_array($UserSessions)) {
227
     if (!is_array($UserSessions)) {
228
-        $DB->query(
228
+        $DB->prepared_query(
229
             "
229
             "
230
         SELECT
230
         SELECT
231
           SessionID,
231
           SessionID,
250
     // Check if user is enabled
250
     // Check if user is enabled
251
     $Enabled = $Cache->get_value('enabled_'.$LoggedUser['ID']);
251
     $Enabled = $Cache->get_value('enabled_'.$LoggedUser['ID']);
252
     if ($Enabled === false) {
252
     if ($Enabled === false) {
253
-        $DB->query("
253
+        $DB->prepared_query("
254
         SELECT Enabled
254
         SELECT Enabled
255
           FROM users_main
255
           FROM users_main
256
           WHERE ID = '$LoggedUser[ID]'");
256
           WHERE ID = '$LoggedUser[ID]'");
267
     // Up/Down stats
267
     // Up/Down stats
268
     $UserStats = $Cache->get_value('user_stats_'.$LoggedUser['ID']);
268
     $UserStats = $Cache->get_value('user_stats_'.$LoggedUser['ID']);
269
     if (!is_array($UserStats)) {
269
     if (!is_array($UserStats)) {
270
-        $DB->query("
270
+        $DB->prepared_query("
271
         SELECT Uploaded AS BytesUploaded, Downloaded AS BytesDownloaded, RequiredRatio
271
         SELECT Uploaded AS BytesUploaded, Downloaded AS BytesDownloaded, RequiredRatio
272
         FROM users_main
272
         FROM users_main
273
           WHERE ID = '$LoggedUser[ID]'");
273
           WHERE ID = '$LoggedUser[ID]'");
319
     // Change necessary triggers in external components
319
     // Change necessary triggers in external components
320
     $Cache->CanClear = check_perms('admin_clear_cache');
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
     // Update LastUpdate every 10 minutes
322
     // Update LastUpdate every 10 minutes
328
     if (strtotime($UserSessions[$SessionID]['LastUpdate']) + 600 < time()) {
323
     if (strtotime($UserSessions[$SessionID]['LastUpdate']) + 600 < time()) {
329
-        $DB->query("
324
+        $DB->prepared_query("
330
         UPDATE users_main
325
         UPDATE users_main
331
         SET LastAccess = NOW()
326
         SET LastAccess = NOW()
332
         WHERE ID = '$LoggedUser[ID]'
327
         WHERE ID = '$LoggedUser[ID]'
348
         WHERE UserID = '$LoggedUser[ID]'
343
         WHERE UserID = '$LoggedUser[ID]'
349
         AND SessionID = '".db_string($SessionID)."'";
344
         AND SessionID = '".db_string($SessionID)."'";
350
 
345
 
351
-        $DB->query($SessionQuery);
346
+        $DB->prepared_query($SessionQuery);
352
         $Cache->begin_transaction("users_sessions_$UserID");
347
         $Cache->begin_transaction("users_sessions_$UserID");
353
         $Cache->delete_row($SessionID);
348
         $Cache->delete_row($SessionID);
354
 
349
 
367
     if (isset($LoggedUser['Permissions']['site_torrents_notify'])) {
362
     if (isset($LoggedUser['Permissions']['site_torrents_notify'])) {
368
         $LoggedUser['Notify'] = $Cache->get_value('notify_filters_'.$LoggedUser['ID']);
363
         $LoggedUser['Notify'] = $Cache->get_value('notify_filters_'.$LoggedUser['ID']);
369
         if (!is_array($LoggedUser['Notify'])) {
364
         if (!is_array($LoggedUser['Notify'])) {
370
-            $DB->query("
365
+            $DB->prepared_query("
371
             SELECT ID, Label
366
             SELECT ID, Label
372
             FROM users_notify_filters
367
             FROM users_notify_filters
373
               WHERE UserID = '$LoggedUser[ID]'");
368
               WHERE UserID = '$LoggedUser[ID]'");
383
     }
378
     }
384
 
379
 
385
     // IP changed
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
         if (Tools::site_ban_ip($_SERVER['REMOTE_ADDR'])) {
382
         if (Tools::site_ban_ip($_SERVER['REMOTE_ADDR'])) {
388
             error('Your IP address has been banned.');
383
             error('Your IP address has been banned.');
389
         }
384
         }
390
 
385
 
391
         $CurIP = db_string($LoggedUser['IP']);
386
         $CurIP = db_string($LoggedUser['IP']);
392
         $NewIP = db_string($_SERVER['REMOTE_ADDR']);
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
         $Cache->begin_transaction('user_info_heavy_'.$LoggedUser['ID']);
389
         $Cache->begin_transaction('user_info_heavy_'.$LoggedUser['ID']);
421
         $Cache->update_row(false, array('IP' => Crypto::encrypt($_SERVER['REMOTE_ADDR'])));
390
         $Cache->update_row(false, array('IP' => Crypto::encrypt($_SERVER['REMOTE_ADDR'])));
425
     // Get stylesheets
394
     // Get stylesheets
426
     $Stylesheets = $Cache->get_value('stylesheets');
395
     $Stylesheets = $Cache->get_value('stylesheets');
427
     if (!is_array($Stylesheets)) {
396
     if (!is_array($Stylesheets)) {
428
-        $DB->query('
397
+        $DB->prepared_query('
429
         SELECT
398
         SELECT
430
           ID,
399
           ID,
431
           LOWER(REPLACE(Name, " ", "_")) AS Name,
400
           LOWER(REPLACE(Name, " ", "_")) AS Name,
460
     setcookie('keeplogged', '', time() - 60 * 60 * 24 * 365, '/', '', false);
429
     setcookie('keeplogged', '', time() - 60 * 60 * 24 * 365, '/', '', false);
461
 
430
 
462
     if ($SessionID) {
431
     if ($SessionID) {
463
-        G::$DB->query("
432
+        G::$DB->prepared_query("
464
         DELETE FROM users_sessions
433
         DELETE FROM users_sessions
465
           WHERE UserID = '" . G::$LoggedUser['ID'] . "'
434
           WHERE UserID = '" . G::$LoggedUser['ID'] . "'
466
           AND SessionID = '".db_string($SessionID)."'");
435
           AND SessionID = '".db_string($SessionID)."'");
482
 {
451
 {
483
     $UserID = G::$LoggedUser['ID'];
452
     $UserID = G::$LoggedUser['ID'];
484
 
453
 
485
-    G::$DB->query("
454
+    G::$DB->prepared_query("
486
     DELETE FROM users_sessions
455
     DELETE FROM users_sessions
487
       WHERE UserID = '$UserID'");
456
       WHERE UserID = '$UserID'");
488
 
457
 
547
 
516
 
548
 $Debug->set_flag('completed module execution');
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
 // Flush to user
519
 // Flush to user
561
 ob_end_flush();
520
 ob_end_flush();
562
 
521
 

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

6
  *
6
  *
7
  * Designed to hold common authentication functions from various sources:
7
  * Designed to hold common authentication functions from various sources:
8
  *  - classes/script_start.php
8
  *  - classes/script_start.php
9
+ *  - "Quick SQL injection check"
9
  */
10
  */
10
 
11
 
11
 class Security
12
 class Security
12
 {
13
 {
13
     /**
14
     /**
14
-     * Check ID
15
+     * Check integer
15
      *
16
      *
16
      * Makes sure a number ID is valid,
17
      * Makes sure a number ID is valid,
17
      * e.g., a page ID requested by GET.
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
         return;
29
         return;
31
     }
30
     }
32
 
31
 
32
+
33
     /**
33
     /**
34
      * Setup pitfalls
34
      * Setup pitfalls
35
      *
35
      *
36
      * A series of quick sanity checks during app init.
36
      * A series of quick sanity checks during app init.
37
      * Previously in classes/script_start.php.
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
         # short_open_tag
48
         # short_open_tag
42
         if (!ini_get('short_open_tag')) {
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
         # apcu
53
         # apcu
47
         if (!extension_loaded('apcu')) {
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
         # Deal with dumbasses
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
             error(
80
             error(
54
                 'd14:failure reason40:Invalid .torrent, try downloading again.e',
81
                 'd14:failure reason40:Invalid .torrent, try downloading again.e',
55
                 $NoHTML = true,
82
                 $NoHTML = true,
60
         return;
87
         return;
61
     }
88
     }
62
 
89
 
90
+
63
     /**
91
     /**
64
      * UserID checks
92
      * UserID checks
65
      *
93
      *

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

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
       's' => 0,
14
       's' => 0,
15
       '*' => 0,
15
       '*' => 0,
16
       '#' => 0,
16
       '#' => 0,
17
-      #'ch' => 0,
18
-      #'uch' => 0,
19
       'artist' => 0,
17
       'artist' => 0,
20
       'user' => 0,
18
       'user' => 0,
21
       'n' => 0,
19
       'n' => 0,
128
      */
126
      */
129
     public static $TOC = false;
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
      * Output BBCode as XHTML
165
      * Output BBCode as XHTML
134
      *
166
      *
152
         )) {
184
         )) {
153
             $Parsedown = new ParsedownExtra();
185
             $Parsedown = new ParsedownExtra();
154
             $Parsedown->setSafeMode(true);
186
             $Parsedown->setSafeMode(true);
187
+
188
+            # Prepare clean escapes
155
             $Str = html_entity_decode($Str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
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
             # Markdown ToC not happening yet
199
             # Markdown ToC not happening yet
158
             # Shouldn't parse_toc() output HTML
200
             # Shouldn't parse_toc() output HTML
159
             /*
201
             /*
174
             }
216
             }
175
             */
217
             */
176
 
218
 
177
-            return $P = $Parsedown->text($Str);
178
-
179
         /*
219
         /*
180
         return $P =
220
         return $P =
181
             ((self::$TOC && $OutputTOC)
221
             ((self::$TOC && $OutputTOC)
183
                 : null)
223
                 : null)
184
             . $Parsedown->text($Str);
224
             . $Parsedown->text($Str);
185
         */
225
         */
186
-        } else {
226
+        }
227
+        
228
+        /**
229
+         * BBcode formatting
230
+         */
231
+        else {
187
             global $Debug;
232
             global $Debug;
188
             $Debug->set_flag('BBCode start');
233
             $Debug->set_flag('BBCode start');
189
 
234
 
190
             self::$Headlines = [];
235
             self::$Headlines = [];
191
             $Str = display_str($Str);
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
             // Inline links
238
             // Inline links
201
             $URLPrefix = '(\[url\]|\[url\=|\[img\=|\[img\])';
239
             $URLPrefix = '(\[url\]|\[url\=|\[img\=|\[img\])';
202
             $Str = preg_replace('/'.$URLPrefix.'\s+/i', '$1', $Str);
240
             $Str = preg_replace('/'.$URLPrefix.'\s+/i', '$1', $Str);
203
             $Str = preg_replace('/(?<!'.$URLPrefix.')http(s)?:\/\//i', '$1[inlineurl]http$2://', $Str);
241
             $Str = preg_replace('/(?<!'.$URLPrefix.')http(s)?:\/\//i', '$1[inlineurl]http$2://', $Str);
204
             $Str = preg_replace('/\[embed\]\[inlineurl\]/', '[embed]', $Str);
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
             if (self::$TOC) {
244
             if (self::$TOC) {
218
                 $Str = preg_replace('/(\={5})([^=].*)\1/i', '[headline=4]$2[/headline]', $Str);
245
                 $Str = preg_replace('/(\={5})([^=].*)\1/i', '[headline=4]$2[/headline]', $Str);
219
                 $Str = preg_replace('/(\={4})([^=].*)\1/i', '[headline=3]$2[/headline]', $Str);
246
                 $Str = preg_replace('/(\={4})([^=].*)\1/i', '[headline=3]$2[/headline]', $Str);
233
                 $HTML = self::parse_toc($Min) . $HTML;
260
                 $HTML = self::parse_toc($Min) . $HTML;
234
             }
261
             }
235
 
262
 
263
+            # Rewrite the URLs
264
+            $HTML = self::fix_links($HTML);
265
+
236
             $Debug->set_flag('BBCode end');
266
             $Debug->set_flag('BBCode end');
237
             return $HTML;
267
             return $HTML;
238
         }
268
         }
871
                   break;
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
                 case 'list':
904
                 case 'list':
889
                   $Str .= "<$Block[ListType] class=\"postlist\">";
905
                   $Str .= "<$Block[ListType] class=\"postlist\">";
890
                   foreach ($Block['Val'] as $Line) {
906
                   foreach ($Block['Val'] as $Line) {
1051
                       if ($LocalURL) {
1067
                       if ($LocalURL) {
1052
                           $Str .= '<a href="'.$LocalURL.'">'.substr($LocalURL, 1).'</a>';
1068
                           $Str .= '<a href="'.$LocalURL.'">'.substr($LocalURL, 1).'</a>';
1053
                       } else {
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
                   break;
1074
                   break;

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

122
         return trim($Output[4]);
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
      * Disable an array of users.
127
      * Disable an array of users.
139
      *
128
      *

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

14
      * recursively copying parts to arrays in place as needed.
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
     # Formats
17
     # Formats
28
     # See classes/config.php
18
     # See classes/config.php
29
     public $SeqFormats = [];
19
     public $SeqFormats = [];
36
     public $BinDocFormats = [];
26
     public $BinDocFormats = [];
37
     public $CpuGenFormats = [];
27
     public $CpuGenFormats = [];
38
     public $PlainFormats = [];
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
     public $Resolutions = [];
29
     public $Resolutions = [];
48
 
30
 
49
-    # Deprecated
50
-    #public $Formats = [];
51
-    #public $Versions = [];
52
-    #public $Bitrates = [];
53
-    #public $Platform = [];
54
-
55
     # Gazelle
31
     # Gazelle
56
     public $NewTorrent = false;
32
     public $NewTorrent = false;
57
     public $Torrent = [];
33
     public $Torrent = [];
63
     public function __construct($Torrent = false, $Error = false, $NewTorrent = true)
39
     public function __construct($Torrent = false, $Error = false, $NewTorrent = true)
64
     {
40
     {
65
         # See classes/config.php
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
         #global $UploadForm, $Categories, $Formats, $Bitrates, $Media, $MediaManga, $TorrentID, $Containers, $ContainersGames, $Codecs, $Resolutions, $Platform, $Archives, $ArchivesManga;
44
         #global $UploadForm, $Categories, $Formats, $Bitrates, $Media, $MediaManga, $TorrentID, $Containers, $ContainersGames, $Codecs, $Resolutions, $Platform, $Archives, $ArchivesManga;
68
 
45
 
69
         # Gazelle
46
         # Gazelle
75
         $this->Categories = $Categories;
52
         $this->Categories = $Categories;
76
         $this->TorrentID = $TorrentID;
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
         # Formats
55
         # Formats
87
         # See classes/config.php
56
         # See classes/config.php
88
         $this->SeqFormats = $SeqFormats;
57
         $this->SeqFormats = $SeqFormats;
95
         $this->BinDocFormats = $BinDocFormats;
64
         $this->BinDocFormats = $BinDocFormats;
96
         $this->CpuGenFormats = $CpuGenFormats;
65
         $this->CpuGenFormats = $CpuGenFormats;
97
         $this->PlainFormats = $PlainFormats;
66
         $this->PlainFormats = $PlainFormats;
98
-        
99
-        # Misc
100
-        $this->Codecs = $Codecs;
101
-        $this->Archives = $Archives;
102
         $this->Resolutions = $Resolutions;
67
         $this->Resolutions = $Resolutions;
103
 
68
 
104
         # Quick constructor test
69
         # Quick constructor test
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
         if ($this->NewTorrent) {
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
             $Announces = ANNOUNCE_URLS[0];
106
             $Announces = ANNOUNCE_URLS[0];
172
             #$Announces = call_user_func_array('array_merge', ANNOUNCE_URLS);
107
             #$Announces = call_user_func_array('array_merge', ANNOUNCE_URLS);
108
+
173
             $TorrentPass = G::$LoggedUser['torrent_pass'];
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
 HTML;
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
      * head
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
         $ENV = ENV::go();
162
         $ENV = ENV::go();
233
-
234
         G::$DB->query(
163
         G::$DB->query(
235
             "
164
             "
236
         SELECT
165
         SELECT
288
          * Start printing the torrent form
217
          * Start printing the torrent form
289
          */
218
          */
290
         $HTML .= '<table class="torrent_form">';
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
         if ($this->NewTorrent) {
225
         if ($this->NewTorrent) {
303
-            $HTML =  '<h2 class="header">Basic Info</h2>';
226
+            $HTML .=  '<h2 class="header">Basic Info</h2>';
304
             $HTML .= <<<HTML
227
             $HTML .= <<<HTML
305
             <tr>
228
             <tr>
306
               <td>
229
               <td>
319
               </td>
242
               </td>
320
             </tr>
243
             </tr>
321
 HTML;
244
 HTML;
322
-        } # fi NewTorrent
323
 
245
 
324
-        /**
325
-         * New torrent options: category
326
-         */
327
-        if ($this->NewTorrent) {
328
             $DisabledFlag = ($this->DisabledFlag) ? ' disabled="disabled"' : '';
246
             $DisabledFlag = ($this->DisabledFlag) ? ' disabled="disabled"' : '';
329
-
330
             $HTML .= <<<HTML
247
             $HTML .= <<<HTML
331
               <tr>
248
               <tr>
332
                 <td>
249
                 <td>
375
      *
292
      *
376
      * Make the endmatter.
293
      * Make the endmatter.
377
      */
294
      */
378
-    public function foot()
295
+    private function foot()
379
     {
296
     {
380
         $Torrent = $this->Torrent;
297
         $Torrent = $this->Torrent;
381
         echo '<table class="torrent_form>';
298
         echo '<table class="torrent_form>';
471
 
388
 
472
         echo <<<HTML
389
         echo <<<HTML
473
               <tr>
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
                 </td>
393
                 </td>
477
               </tr>
394
               </tr>
478
             </table> <!-- torrent_form -->
395
             </table> <!-- torrent_form -->
486
      * upload_form
403
      * upload_form
487
      *
404
      *
488
      * Finally the "real" upload form.
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
      * This is currently one enormous function.
408
      * This is currently one enormous function.
492
      * It has sub-functions, variables, and everything.
409
      * It has sub-functions, variables, and everything.
493
      * It continues to the end of the class.
410
      * It continues to the end of the class.
494
      */
411
      */
495
-    public function upload_form()
412
+    private function upload_form()
496
     {
413
     {
497
         $ENV = ENV::go();
414
         $ENV = ENV::go();
415
+        $Twig = Twig::go();
498
 
416
 
499
         $QueryID = G::$DB->get_query_id();
417
         $QueryID = G::$DB->get_query_id();
500
         $Torrent = $this->Torrent;
418
         $Torrent = $this->Torrent;
501
 
419
 
502
-        # Moved to their own functions
503
-        #echo $this->head();
504
-        #echo $this->basicInfo();
505
-
506
         # Start printing the form
420
         # Start printing the form
507
         echo '<h2 class="header">Torrent Form</h2>';
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
          * Accession Number
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
         $CatalogueNumber = display_str($Torrent['CatalogueNumber']);
428
         $CatalogueNumber = display_str($Torrent['CatalogueNumber']);
521
         $Disabled = $this->Disabled;
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
         $Version = display_str($Torrent['Version']);
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
         # New torrent upload
459
         # New torrent upload
624
         if ($this->NewTorrent) {
460
         if ($this->NewTorrent) {
625
-            $Disabled = $this->Disabled;
626
-
627
-
628
-            /**
629
-             * Title 1
630
-             */
631
             $Title1 = display_str($Torrent['Title']);
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
             $Title2 = display_str($Torrent['Title2']);
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
             $Title3 = display_str($Torrent['TitleJP']);
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
         } # fi NewTorrent
475
         } # fi NewTorrent
690
         
476
         
691
         
477
         
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
         if ($this->NewTorrent) {
539
         if ($this->NewTorrent) {
758
             $Affiliation = display_str($Torrent['Studio']);
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
          */
557
          */
783
         if ($this->NewTorrent) {
558
         if ($this->NewTorrent) {
784
             $TorrentLocation = display_str($Torrent['Series']);
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
          * Year
578
          * Year
812
          */
579
          */
813
         $TorrentYear = display_str($Torrent['Year']);
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
               <option>---</option>
610
               <option>---</option>
851
 HTML;
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
                 echo " selected";
617
                 echo " selected";
858
             }
618
             }
859
             
619
             
860
-            echo ">$Codec</option>\n";
620
+            echo ">$License</option>\n";
861
         }
621
         }
862
 
622
 
863
         echo <<<HTML
623
         echo <<<HTML
939
                 $trID = 'media_tr',
699
                 $trID = 'media_tr',
940
                 $Label = 'Platform',
700
                 $Label = 'Platform',
941
                 $Torrent = $Torrent,
701
                 $Torrent = $Torrent,
942
-                $Media = $this->SeqPlatforms
702
+                $Media = $ENV->CATS->{1}->Platforms
943
             );
703
             );
944
             
704
             
945
 
705
 
950
                 $trID = 'media_graphs_tr',
710
                 $trID = 'media_graphs_tr',
951
                 $Label = 'Platform',
711
                 $Label = 'Platform',
952
                 $Torrent = $Torrent,
712
                 $Torrent = $Torrent,
953
-                $Media = array_merge($this->GraphPlatforms, $this->SeqPlatforms)
713
+                $Media = $ENV->CATS->{2}->Platforms
954
             );
714
             );
955
             
715
             
956
 
716
 
961
                 $trID = 'media_scalars_vectors_tr',
721
                 $trID = 'media_scalars_vectors_tr',
962
                 $Label = 'Platform',
722
                 $Label = 'Platform',
963
                 $Torrent = $Torrent,
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
             mediaSelect(
731
             mediaSelect(
972
                 $trID = 'media_images_tr',
732
                 $trID = 'media_images_tr',
973
                 $Label = 'Platform',
733
                 $Label = 'Platform',
974
                 $Torrent = $Torrent,
734
                 $Torrent = $Torrent,
975
-                $Media = $this->ImgPlatforms
735
+                $Media = $ENV->CATS->{8}->Platforms
976
             );
736
             );
977
 
737
 
978
 
738
 
983
                 $trID = 'media_documents_tr',
743
                 $trID = 'media_documents_tr',
984
                 $Label = 'Platform',
744
                 $Label = 'Platform',
985
                 $Torrent = $Torrent,
745
                 $Torrent = $Torrent,
986
-                $Media = $this->DocPlatforms
746
+                $Media = $ENV->CATS->{11}->Platforms
987
             );
747
             );
988
 
748
 
989
 
749
 
994
                 $trID = 'media_machine_data_tr',
754
                 $trID = 'media_machine_data_tr',
995
                 $Label = 'Platform',
755
                 $Label = 'Platform',
996
                 $Torrent = $Torrent,
756
                 $Torrent = $Torrent,
997
-                $Media = $this->RawPlatforms
757
+                $Media = $ENV->CATS->{12}->Platforms
998
             );
758
             );
999
         } # fi NewTorrent
759
         } # fi NewTorrent
1000
         else {
760
         else {
1014
          */
774
          */
1015
         function formatSelect($trID = '', $Label = '', $Torrent = [], $FileTypes = [])
775
         function formatSelect($trID = '', $Label = '', $Torrent = [], $FileTypes = [])
1016
         {
776
         {
777
+            #var_dump($FileTypes);
1017
             echo <<<HTML
778
             echo <<<HTML
1018
             <tr id="$trID">
779
             <tr id="$trID">
1019
               <td>
780
               <td>
1021
                   $Label
782
                   $Label
1022
                 <label>
783
                 <label>
1023
               </td>
784
               </td>
1024
-              
785
+
1025
               <td>
786
               <td>
1026
                 <select id="container" name="container">
787
                 <select id="container" name="container">
1027
                   <option value="Autofill">Autofill</option>
788
                   <option value="Autofill">Autofill</option>
1028
 HTML;
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
             echo <<<HTML
804
             echo <<<HTML
1041
                 </select>
805
                 </select>
1060
             $trID = 'container_tr',
824
             $trID = 'container_tr',
1061
             $Label = 'Format',
825
             $Label = 'Format',
1062
             $Torrent = $Torrent,
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
             $trID = 'container_graphs_tr',
835
             $trID = 'container_graphs_tr',
1072
             $Label = 'Format',
836
             $Label = 'Format',
1073
             $Torrent = $Torrent,
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
             $trID = 'container_scalars_vectors_tr',
847
             $trID = 'container_scalars_vectors_tr',
1083
             $Label = 'Format',
848
             $Label = 'Format',
1084
             $Torrent = $Torrent,
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
             $trID = 'container_images_tr',
859
             $trID = 'container_images_tr',
1094
             $Label = 'Format',
860
             $Label = 'Format',
1095
             $Torrent = $Torrent,
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
             $trID = 'container_spatial_tr',
871
             $trID = 'container_spatial_tr',
1105
             $Label = 'Format',
872
             $Label = 'Format',
1106
             $Torrent = $Torrent,
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
             $trID = 'container_documents_tr',
883
             $trID = 'container_documents_tr',
1116
             $Label = 'Format',
884
             $Label = 'Format',
1117
             $Torrent = $Torrent,
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
             $trID = 'archive_tr',
895
             $trID = 'archive_tr',
1127
             $Label = 'Archive',
896
             $Label = 'Archive',
1128
             $Torrent = $Torrent,
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
             $TorrentImage = display_str($Torrent['Image']);
1034
             $TorrentImage = display_str($Torrent['Image']);
1265
             $Disabled = $this->Disabled;
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
          * Mirrors
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
          * The intended use is for web seeds, Dat mirrors, etc.
1051
          * The intended use is for web seeds, Dat mirrors, etc.
1290
          */
1052
          */
1291
         if (!$this->DisabledFlag && $this->NewTorrent) {
1053
         if (!$this->DisabledFlag && $this->NewTorrent) {
1292
             $TorrentMirrors = display_str($Torrent['Mirrors']);
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
         }
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
          * Torrent group description
1111
          * Torrent group description
1342
          *
1112
          *
1455
         echo '</table>';
1225
         echo '</table>';
1456
 
1226
 
1457
         # Drink a stiff one
1227
         # Drink a stiff one
1458
-        $this->foot();
1459
         G::$DB->set_query_id($QueryID);
1228
         G::$DB->set_query_id($QueryID);
1460
     } # End upload_form()
1229
     } # End upload_form()
1461
 } # End TorrentForm()
1230
 } # End TorrentForm()

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

60
                 unset($GroupIDs[$i], $Found[$GroupID], $NotFound[$GroupID]);
60
                 unset($GroupIDs[$i], $Found[$GroupID], $NotFound[$GroupID]);
61
                 continue;
61
                 continue;
62
             }
62
             }
63
+
63
             $Data = G::$Cache->get_value($Key . $GroupID, true);
64
             $Data = G::$Cache->get_value($Key . $GroupID, true);
64
             if (!empty($Data) && is_array($Data) && $Data['ver'] === Cache::GROUP_VERSION) {
65
             if (!empty($Data) && is_array($Data) && $Data['ver'] === Cache::GROUP_VERSION) {
65
                 unset($NotFound[$GroupID]);
66
                 unset($NotFound[$GroupID]);
66
                 $Found[$GroupID] = $Data['d'];
67
                 $Found[$GroupID] = $Data['d'];
67
             }
68
             }
68
         }
69
         }
70
+
69
         // Make sure there's something in $GroupIDs, otherwise the SQL will break
71
         // Make sure there's something in $GroupIDs, otherwise the SQL will break
70
         if (count($GroupIDs) === 0) {
72
         if (count($GroupIDs) === 0) {
71
             return [];
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
         if (count($NotFound) > 0) {
84
         if (count($NotFound) > 0) {
83
             $IDs = implode(',', array_keys($NotFound));
85
             $IDs = implode(',', array_keys($NotFound));
84
             $NotFound = [];
86
             $NotFound = [];
85
             $QueryID = G::$DB->get_query_id();
87
             $QueryID = G::$DB->get_query_id();
86
-            G::$DB->query("
88
+
89
+            G::$DB->prepare_query("
87
             SELECT
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
             while ($Group = G::$DB->next_record(MYSQLI_ASSOC, true)) {
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
             G::$DB->set_query_id($QueryID);
114
             G::$DB->set_query_id($QueryID);
98
 
115
 
99
             if ($Torrents) {
116
             if ($Torrents) {
100
                 $QueryID = G::$DB->get_query_id();
117
                 $QueryID = G::$DB->get_query_id();
118
+
101
                 G::$DB->query("
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
                 while ($Torrent = G::$DB->next_record(MYSQLI_ASSOC, true)) {
156
                 while ($Torrent = G::$DB->next_record(MYSQLI_ASSOC, true)) {
113
                     $NotFound[$Torrent['GroupID']]['Torrents'][$Torrent['ID']] = $Torrent;
157
                     $NotFound[$Torrent['GroupID']]['Torrents'][$Torrent['ID']] = $Torrent;
138
                 }
182
                 }
139
                 $Found[$GroupID]['Artists'] = $Data;
183
                 $Found[$GroupID]['Artists'] = $Data;
140
             }
184
             }
185
+
141
             // Fetch all user specific torrent properties
186
             // Fetch all user specific torrent properties
142
             if ($Torrents) {
187
             if ($Torrents) {
143
                 foreach ($Found as &$Group) {
188
                 foreach ($Found as &$Group) {
166
     public static function array_group(array &$Group)
211
     public static function array_group(array &$Group)
167
     {
212
     {
168
         return array(
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
           'GroupFlags' => ($Group['Flags'] ?? ''),
223
           'GroupFlags' => ($Group['Flags'] ?? ''),
179
-          'TagList' => $Group['TagList'],
180
-          'WikiImage' => $Group['WikiImage'],
224
+          'tag_list' => $Group['tag_list'],
225
+          'picture' => $Group['picture'],
181
           'Torrents' => $Group['Torrents'],
226
           'Torrents' => $Group['Torrents'],
182
           'Artists' => $Group['Artists']
227
           'Artists' => $Group['Artists']
183
         );
228
         );
231
     {
276
     {
232
         global $Time;
277
         global $Time;
233
         $QueryID = G::$DB->get_query_id();
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
         G::$DB->set_query_id($QueryID);
297
         G::$DB->set_query_id($QueryID);
247
     }
298
     }
248
 
299
 
371
 
422
 
372
         Misc::write_log("Group $GroupID automatically deleted (No torrents have this group).");
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
         list($Category) = G::$DB->next_record();
434
         list($Category) = G::$DB->next_record();
379
-        if ($Category == 1) {
435
+
436
+        # todo: Check strict equality here
437
+        if ($Category === 1) {
380
             G::$Cache->decrement('stats_album_count');
438
             G::$Cache->decrement('stats_album_count');
381
         }
439
         }
382
         G::$Cache->decrement('stats_group_count');
440
         G::$Cache->decrement('stats_group_count');
459
         // Comments
517
         // Comments
460
         Comments::delete_page('torrents', $GroupID);
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
         G::$Cache->delete_value("torrents_details_$GroupID");
556
         G::$Cache->delete_value("torrents_details_$GroupID");
476
         G::$Cache->delete_value("torrent_group_$GroupID");
557
         G::$Cache->delete_value("torrent_group_$GroupID");
483
      *
564
      *
484
      * @param int $GroupID
565
      * @param int $GroupID
485
      */
566
      */
486
-    public static function update_hash($GroupID)
567
+    public static function update_hash(int $GroupID)
487
     {
568
     {
488
         $QueryID = G::$DB->get_query_id();
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
         // Fetch album artists
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
         if (G::$DB->has_results()) {
606
         if (G::$DB->has_results()) {
509
             list($ArtistName) = G::$DB->next_record(MYSQLI_NUM, false);
607
             list($ArtistName) = G::$DB->next_record(MYSQLI_NUM, false);
510
         } else {
608
         } else {
511
             $ArtistName = '';
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
         SELECT
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
         G::$Cache->delete_value("torrents_details_$GroupID");
685
         G::$Cache->delete_value("torrents_details_$GroupID");
530
         G::$Cache->delete_value("torrent_group_$GroupID");
686
         G::$Cache->delete_value("torrent_group_$GroupID");
531
         G::$Cache->delete_value("torrent_group_light_$GroupID");
687
         G::$Cache->delete_value("torrent_group_light_$GroupID");
532
 
688
 
533
         $ArtistInfo = Artists::get_artist($GroupID);
689
         $ArtistInfo = Artists::get_artist($GroupID);
534
-
535
         G::$Cache->delete_value("groups_artists_$GroupID");
690
         G::$Cache->delete_value("groups_artists_$GroupID");
536
         G::$DB->set_query_id($QueryID);
691
         G::$DB->set_query_id($QueryID);
537
     }
692
     }

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

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
 <?php
1
 <?php
2
-#declare(strict_types=1);
2
+declare(strict_types=1);
3
 
3
 
4
 class UserRank
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
     private static function build_table($MemKey, $Query)
16
     private static function build_table($MemKey, $Query)
11
     {
17
     {
12
         $QueryID = G::$DB->get_query_id();
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
         list($UserCount) = G::$DB->next_record();
47
         list($UserCount) = G::$DB->next_record();
31
 
48
 
49
+        $UserCount = (int) $UserCount;
32
         G::$DB->query("
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
         $Table = G::$DB->to_array();
60
         $Table = G::$DB->to_array();
38
         G::$DB->set_query_id($QueryID);
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
         G::$Cache->cache_value($MemKey, $Table, 3600 * 24 * rand(800, 1000) * 0.001);
64
         G::$Cache->cache_value($MemKey, $Table, 3600 * 24 * rand(800, 1000) * 0.001);
42
 
65
 
43
         return $Table;
66
         return $Table;
44
     }
67
     }
45
 
68
 
69
+
70
+    /**
71
+     * table_query
72
+     */
46
     private static function table_query($TableName)
73
     private static function table_query($TableName)
47
     {
74
     {
48
         switch ($TableName) {
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
         return $Query;
196
         return $Query;
119
     }
197
     }
120
 
198
 
199
+
200
+    /**
201
+     * get_rank
202
+     */
121
     public static function get_rank($TableName, $Value)
203
     public static function get_rank($TableName, $Value)
122
     {
204
     {
123
-        if ($Value == 0) {
205
+        if ($Value === 0) {
124
             return 0;
206
             return 0;
125
         }
207
         }
126
 
208
 
127
         $Table = G::$Cache->get_value(self::PREFIX.$TableName);
209
         $Table = G::$Cache->get_value(self::PREFIX.$TableName);
128
         if (!$Table) {
210
         if (!$Table) {
129
-            //Cache lock!
211
+            # Cache lock!
130
             $Lock = G::$Cache->get_value(self::PREFIX.$TableName.'_lock');
212
             $Lock = G::$Cache->get_value(self::PREFIX.$TableName.'_lock');
213
+
131
             if ($Lock) {
214
             if ($Lock) {
132
                 return false;
215
                 return false;
133
             } else {
216
             } else {
140
         $LastPercentile = 0;
223
         $LastPercentile = 0;
141
         foreach ($Table as $Row) {
224
         foreach ($Table as $Row) {
142
             list($CurValue) = $Row;
225
             list($CurValue) = $Row;
226
+
143
             if ($CurValue >= $Value) {
227
             if ($CurValue >= $Value) {
144
                 return $LastPercentile;
228
                 return $LastPercentile;
145
             }
229
             }
230
+
146
             $LastPercentile++;
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
     public static function overall_score($Uploaded, $Downloaded, $Uploads, $Requests, $Posts, $Bounty, $Artists, $Ratio)
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
         if ($Ratio > 1) {
245
         if ($Ratio > 1) {
155
             $Ratio = 1;
246
             $Ratio = 1;
156
         }
247
         }
169
         $TotalScore += $Artists;
260
         $TotalScore += $Artists;
170
         $TotalScore /= (15 + 8 + 25 + 2 + 1 + 1 + 1);
261
         $TotalScore /= (15 + 8 + 25 + 2 + 1 + 1 + 1);
171
         $TotalScore *= $Ratio;
262
         $TotalScore *= $Ratio;
263
+        
172
         return $TotalScore;
264
         return $TotalScore;
173
     }
265
     }
174
 }
266
 }

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

457
      * @param boolean $IsEnabled
457
      * @param boolean $IsEnabled
458
      * @param boolean $Class whether or not to show the class
458
      * @param boolean $Class whether or not to show the class
459
      * @param boolean $Title whether or not to show the title
459
      * @param boolean $Title whether or not to show the title
460
-     * @param boolean $IsDonorForum for displaying donor forum honorific prefixes and suffixes
461
      * @return HTML formatted username
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
         global $Classes;
464
         global $Classes;
466
 
465
 
490
         # Show donor icon?
489
         # Show donor icon?
491
         $ShowDonorIcon = (!in_array('hide_donor_heart', $Paranoia) || $OverrideParanoia);
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
         if ($Title) {
492
         if ($Title) {
499
             $Str .= "<strong><a href='user.php?id=$UserID'>$Username</a></strong>";
493
             $Str .= "<strong><a href='user.php?id=$UserID'>$Username</a></strong>";
500
         } else {
494
         } else {

+ 17
- 4
classes/util.php View File

383
      * Append $Log
383
      * Append $Log
384
      * Formerly in sections/error/index.php
384
      * Formerly in sections/error/index.php
385
      */
385
      */
386
-    if ($Log ?? false) {
386
+    if ($Log) {
387
         $Message .= " <a href='log.php?search=$Title'>Search Log</a>";
387
         $Message .= " <a href='log.php?search=$Title'>Search Log</a>";
388
     }
388
     }
389
 
389
 
390
     /**
390
     /**
391
      * Append $Debug
391
      * Append $Debug
392
      */
392
      */
393
-    if ($Debug ?? false) {
393
+    if ($Debug) {
394
         $DateTime = strftime('%c', $_SERVER['REQUEST_TIME']);
394
         $DateTime = strftime('%c', $_SERVER['REQUEST_TIME']);
395
         $BackTrace = debug_string_backtrace();
395
         $BackTrace = debug_string_backtrace();
396
 
396
 
508
         $response = ['status' => $Status, 'response' => []];
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
  */
521
  */
517
 function json_error($Code)
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
     die();
533
     die();
521
 }
534
 }
522
 
535
 

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

46
 
46
 
47
 class Validate
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
      * title
78
      * title
51
      *
79
      *
190
      * Extension Parser
218
      * Extension Parser
191
      *
219
      *
192
      * Takes an associative array of file types and extension, e.g.,
220
      * Takes an associative array of file types and extension, e.g.,
193
-     * $Archives = [
221
+     * $ENV->META->Archives = [
194
      *   '7z'     => ['7z'],
222
      *   '7z'     => ['7z'],
195
      *   'bzip2'  => ['bz2', 'bzip2'],
223
      *   'bzip2'  => ['bz2', 'bzip2'],
196
      *   'gzip'   => ['gz', 'gzip', 'tgz', 'tpz'],
224
      *   'gzip'   => ['gz', 'gzip', 'tgz', 'tpz'],
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
      * Legacy class
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
-<?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
-<?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
         $Aliases = G::$Cache->get_value('wiki_aliases');
22
         $Aliases = G::$Cache->get_value('wiki_aliases');
23
         if (!$Aliases) {
23
         if (!$Aliases) {
24
             $QueryID = G::$DB->get_query_id();
24
             $QueryID = G::$DB->get_query_id();
25
-            G::$DB->query("
25
+            G::$DB->prepared_query("
26
             SELECT Alias, ArticleID
26
             SELECT Alias, ArticleID
27
             FROM wiki_aliases");
27
             FROM wiki_aliases");
28
             $Aliases = G::$DB->to_pair('Alias', 'ArticleID');
28
             $Aliases = G::$DB->to_pair('Alias', 'ArticleID');
67
         $Contents = G::$Cache->get_value('wiki_article_'.$ArticleID);
67
         $Contents = G::$Cache->get_value('wiki_article_'.$ArticleID);
68
         if (!$Contents) {
68
         if (!$Contents) {
69
             $QueryID = G::$DB->get_query_id();
69
             $QueryID = G::$DB->get_query_id();
70
-            G::$DB->query("
70
+            G::$DB->prepared_query("
71
             SELECT
71
             SELECT
72
               w.Revision,
72
               w.Revision,
73
               w.Title,
73
               w.Title,

+ 54
- 0
composer.json View File

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
-<?php
2
-declare(strict_types=1);
3
-
4
-require_once 'classes/script_start.php';

+ 1
- 1
design/privatefooter.php View File

6
 
6
 
7
 # End <div#content>, begin <footer>
7
 # End <div#content>, begin <footer>
8
 # This needs to be <main>, in each page
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
 # Disclaimer
11
 # Disclaimer
12
 #if (!empty($Options['disclaimer'])) {
12
 #if (!empty($Options['disclaimer'])) {

+ 7
- 17
design/privateheader.php View File

49
     array_merge(
49
     array_merge(
50
         [
50
         [
51
           'vendor/jquery-ui.min',
51
           'vendor/jquery-ui.min',
52
+          'vendor/normalize',
53
+          'vendor/skeleton',
52
           #'assets/fonts/fa/css/all.min',
54
           #'assets/fonts/fa/css/all.min',
53
           'global'
55
           'global'
54
         ],
56
         ],
250
           <li id="nav_irc" <?=
252
           <li id="nav_irc" <?=
251
             Format::add_class($PageID, ['chat'], 'active', true)?>>
253
             Format::add_class($PageID, ['chat'], 'active', true)?>>
252
             <a href="https://join.slack.com/t/biotorrents/shared_invite/<?=$ENV->SLACK_INVITE?>"
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
           </li>
256
           </li>
256
 
257
 
257
           <li id="nav_top10" <?=
258
           <li id="nav_top10" <?=
332
             size="17">
333
             size="17">
333
         </form>
334
         </form>
334
 
335
 
336
+        <!--
335
         <form class="search_form" name="artists" action="artist.php" method="get">
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
           aria-label="Search authors" accesskey="a" spellcheck="false" autocomplete="off" placeholder="Authors"
339
           aria-label="Search authors" accesskey="a" spellcheck="false" autocomplete="off" placeholder="Authors"
338
           type="text" name="artistname" size="17">
340
           type="text" name="artistname" size="17">
339
         </form>
341
         </form>
342
+          -->
340
 
343
 
341
         <form class="search_form" name="requests" action="requests.php" method="get">
344
         <form class="search_form" name="requests" action="requests.php" method="get">
342
           <input id="requestssearch" aria-label="Search requests" spellcheck="false" autocomplete="off"
345
           <input id="requestssearch" aria-label="Search requests" spellcheck="false" autocomplete="off"
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
 if (check_perms('users_mod') && FEATURE_EMAIL_REENABLE) {
613
 if (check_perms('users_mod') && FEATURE_EMAIL_REENABLE) {
624
     $NumEnableRequests = G::$Cache->get_value(AutoEnable::CACHE_KEY_NAME);
614
     $NumEnableRequests = G::$Cache->get_value(AutoEnable::CACHE_KEY_NAME);
625
     if ($NumEnableRequests === false) {
615
     if ($NumEnableRequests === false) {
636
 if (!empty($Alerts) || !empty($ModBar)) { ?>
626
 if (!empty($Alerts) || !empty($ModBar)) { ?>
637
     <div id="alerts">
627
     <div id="alerts">
638
       <?php foreach ($Alerts as $Alert) { ?>
628
       <?php foreach ($Alerts as $Alert) { ?>
639
-      <div class="alertbar">
629
+      <div class="alertbar warning">
640
         <?=$Alert?>
630
         <?=$Alert?>
641
       </div>
631
       </div>
642
       <?php
632
       <?php

+ 2
- 1
design/publicfooter.php View File

7
 </main>
7
 </main>
8
 
8
 
9
 <footer>
9
 <footer>
10
-  <a href="https://github.com/biotorrents/gazelle" target="_blank">GitHub</a>
11
   <a href="/legal.php?p=privacy">Privacy</a>
10
   <a href="/legal.php?p=privacy">Privacy</a>
12
   <a href="/legal.php?p=dmca">DMCA</a>
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
 </footer>
14
 </footer>
14
 
15
 
15
 <script src="$ENV->STATIC_SERVER/functions/vendor/instantpage.js" type="module"></script>
16
 <script src="$ENV->STATIC_SERVER/functions/vendor/instantpage.js" type="module"></script>

+ 4
- 7
design/publicheader.php View File

41
 }
41
 }
42
 
42
 
43
 # Load CSS
43
 # Load CSS
44
-$Styles = ['global', 'public'];
44
+$Styles = ['vendor/normalize', 'vendor/skeleton', 'global', 'public'];
45
 foreach ($Styles as $Style) {
45
 foreach ($Styles as $Style) {
46
     echo View::pushAsset(
46
     echo View::pushAsset(
47
         "$ENV->STATIC_SERVER/styles/$Style.css",
47
         "$ENV->STATIC_SERVER/styles/$Style.css",
52
 # Fonts
52
 # Fonts
53
 echo View::pushAsset(
53
 echo View::pushAsset(
54
 # Only Noto Sans available on public pages
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
     'font'
56
     'font'
57
 );
57
 );
58
 
58
 
68
     echo '<a href="register.php">Register</a>';
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
 echo <<<HTML
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
   </header>
74
   </header>
78
 
75
 
79
 <main>
76
 <main>

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

200
           class="hidden button_preview_<?=$ReplyText->getID()?>"
200
           class="hidden button_preview_<?=$ReplyText->getID()?>"
201
           tabindex="1" />
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
       </div>
204
       </div>
205
     </form>
205
     </form>
206
   </div>
206
   </div>

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

32
 
32
 
33
     <input type="button" value="Preview"
33
     <input type="button" value="Preview"
34
       class="hidden button_preview_<?=$TextPrev->getID()?>" />
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
     <input type="button" value="Hide" data-toggle-target="#compose" />
36
     <input type="button" value="Hide" data-toggle-target="#compose" />
37
   </form>
37
   </form>
38
 </div>
38
 </div>

+ 59
- 18
feeds.php View File

1
 <?php
1
 <?php
2
 declare(strict_types = 1);
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
 if (isset($_GET['clearcache'])) {
12
 if (isset($_GET['clearcache'])) {
13
     unset($_GET['clearcache']);
13
     unset($_GET['clearcache']);
14
 }
14
 }
15
 
15
 
16
 require_once 'classes/config.php';
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
 $ENV = ENV::go();
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
 function check_perms()
33
 function check_perms()
26
 {
34
 {
27
     return false;
35
     return false;
28
 }
36
 }
29
 
37
 
38
+
39
+/**
40
+ * is_number
41
+ */
30
 function is_number($Str)
42
 function is_number($Str)
31
 {
43
 {
32
     if ($Str < 0) {
44
     if ($Str < 0) {
33
         return false;
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
     return ($Str === strval(intval($Str)));
49
     return ($Str === strval(intval($Str)));
38
 }
50
 }
39
 
51
 
52
+
53
+/**
54
+ * display_str
55
+ */
40
 function display_str($Str)
56
 function display_str($Str)
41
 {
57
 {
42
     if ($Str !== '') {
58
     if ($Str !== '') {
62
 
78
 
63
         $Str = str_replace($Replace, $With, $Str);
79
         $Str = str_replace($Replace, $With, $Str);
64
     }
80
     }
81
+
65
     return $Str;
82
     return $Str;
66
 }
83
 }
67
 
84
 
85
+
86
+/**
87
+ * make_utf8
88
+ */
68
 function make_utf8($Str)
89
 function make_utf8($Str)
69
 {
90
 {
70
     if ($Str !== '') {
91
     if ($Str !== '') {
71
         if (is_utf8($Str)) {
92
         if (is_utf8($Str)) {
72
             $Encoding = 'UTF-8';
93
             $Encoding = 'UTF-8';
73
         }
94
         }
95
+
74
         if (empty($Encoding)) {
96
         if (empty($Encoding)) {
75
             $Encoding = mb_detect_encoding($Str, 'UTF-8, ISO-8859-1');
97
             $Encoding = mb_detect_encoding($Str, 'UTF-8, ISO-8859-1');
76
         }
98
         }
99
+
77
         if (empty($Encoding)) {
100
         if (empty($Encoding)) {
78
             $Encoding = 'ISO-8859-1';
101
             $Encoding = 'ISO-8859-1';
79
         }
102
         }
103
+
80
         if ($Encoding === 'UTF-8') {
104
         if ($Encoding === 'UTF-8') {
81
             return $Str;
105
             return $Str;
82
         } else {
106
         } else {
85
     }
109
     }
86
 }
110
 }
87
 
111
 
112
+
113
+/**
114
+ * is_utf8
115
+ */
88
 function is_utf8($Str)
116
 function is_utf8($Str)
89
 {
117
 {
90
     return preg_match(
118
     return preg_match(
102
     );
130
     );
103
 }
131
 }
104
 
132
 
133
+
134
+/**
135
+ * display_array
136
+ */
105
 function display_array($Array, $Escape = [])
137
 function display_array($Array, $Escape = [])
106
 {
138
 {
107
     foreach ($Array as $Key => $Val) {
139
     foreach ($Array as $Key => $Val) {
109
             $Array[$Key] = display_str($Val);
141
             $Array[$Key] = display_str($Val);
110
         }
142
         }
111
     }
143
     }
144
+
112
     return $Array;
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
 function site_url()
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
 header('Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0');
163
 header('Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0');
124
 header('Pragma:');
164
 header('Pragma:');
125
 header('Expires: '.date('D, d M Y H:i:s', time() + (2 * 60 * 60)).' GMT');
165
 header('Expires: '.date('D, d M Y H:i:s', time() + (2 * 60 * 60)).' GMT');
126
 header('Last-Modified: '.date('D, d M Y H:i:s').' GMT');
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
 <?php
1
 <?php
2
 declare(strict_types=1);
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
 error_reporting(E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR);
5
 error_reporting(E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR);
6
 
6
 
7
 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
7
 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
9
     error();
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
 header('Last-Modified: '.date('D, d-M-Y H:i:s \U\T\C', time()));
13
 header('Last-Modified: '.date('D, d-M-Y H:i:s \U\T\C', time()));
14
 
14
 
15
 if (!extension_loaded('gd')) {
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
 function img_error($Type)
23
 function img_error($Type)
20
 {
24
 {
25
+    $ENV = ENV::go();
26
+
21
     header('Content-type: image/gif');
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
 function invisible($Image)
35
 function invisible($Image)
26
 {
36
 {
27
     $Count = imagecolorstotal($Image);
37
     $Count = imagecolorstotal($Image);
28
     if ($Count === 0) {
38
     if ($Count === 0) {
29
         return false;
39
         return false;
30
     }
40
     }
41
+
31
     $TotalAlpha = 0;
42
     $TotalAlpha = 0;
32
     for ($i = 0; $i < $Count; ++$i) {
43
     for ($i = 0; $i < $Count; ++$i) {
33
         $Color = imagecolorsforindex($Image, $i);
44
         $Color = imagecolorsforindex($Image, $i);
34
         $TotalAlpha += $Color['alpha'];
45
         $TotalAlpha += $Color['alpha'];
35
     }
46
     }
47
+
36
     return (($TotalAlpha / $Count) === 127);
48
     return (($TotalAlpha / $Count) === 127);
37
 }
49
 }
38
 
50
 
51
+
52
+/**
53
+ * verysmall
54
+ */
39
 function verysmall($Image)
55
 function verysmall($Image)
40
 {
56
 {
41
     return ((imagesx($Image) * imagesy($Image)) < 25);
57
     return ((imagesx($Image) * imagesy($Image)) < 25);
42
 }
58
 }
43
 
59
 
60
+
61
+/**
62
+ * image_type
63
+ */
44
 function image_type($Data)
64
 function image_type($Data)
45
 {
65
 {
46
     if (!strncmp($Data, 'GIF', 3)) {
66
     if (!strncmp($Data, 'GIF', 3)) {
47
         return 'gif';
67
         return 'gif';
48
     }
68
     }
69
+
49
     if (!strncmp($Data, pack('H*', '89504E47'), 4)) {
70
     if (!strncmp($Data, pack('H*', '89504E47'), 4)) {
50
         return 'png';
71
         return 'png';
51
     }
72
     }
73
+
52
     if (!strncmp($Data, pack('H*', 'FFD8'), 2)) {
74
     if (!strncmp($Data, pack('H*', 'FFD8'), 2)) {
53
         return 'jpeg';
75
         return 'jpeg';
54
     }
76
     }
77
+
55
     if (!strncmp($Data, 'BM', 2)) {
78
     if (!strncmp($Data, 'BM', 2)) {
56
         return 'bmp';
79
         return 'bmp';
57
     }
80
     }
81
+
58
     if (!strncmp($Data, 'II', 2) || !strncmp($Data, 'MM', 2)) {
82
     if (!strncmp($Data, 'II', 2) || !strncmp($Data, 'MM', 2)) {
59
         return 'tiff';
83
         return 'tiff';
60
     }
84
     }
85
+
61
     if (!substr_compare($Data, 'webm', 31, 4)) {
86
     if (!substr_compare($Data, 'webm', 31, 4)) {
62
         return 'webm';
87
         return 'webm';
63
     }
88
     }
64
 }
89
 }
65
 
90
 
91
+
92
+/**
93
+ * image_height
94
+ */
66
 function image_height($Type, $Data)
95
 function image_height($Type, $Data)
67
 {
96
 {
68
-    $Length = strlen($Data);
69
     global $URL, $_GET;
97
     global $URL, $_GET;
98
+    $Length = strlen($Data);
70
 
99
 
71
     switch ($Type) {
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
 
31
 
32
     # Print header and $manifest for remote addresses
32
     # Print header and $manifest for remote addresses
33
     # Return JSON for localhost (API manifest endpoint):
33
     # Return JSON for localhost (API manifest endpoint):
34
-    #   ajax.php?action=manifest
34
+    #   api.php?action=manifest
35
     if ($_SERVER['REMOTE_ADDR'] !== "127.0.0.1") {
35
     if ($_SERVER['REMOTE_ADDR'] !== "127.0.0.1") {
36
         header('Content-type: application/json; charset=utf-8');
36
         header('Content-type: application/json; charset=utf-8');
37
         echo $manifest;
37
         echo $manifest;

+ 97
- 30
readme.md View File

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
 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.
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
 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.
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
 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.
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
 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.
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
 The [scheduler](sections/schedule) has been broken up into more manageable parts and has additional selective runtime features for manual execution.
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
 Like most gazelle forks, we've added a [bonus point system](sections/schedule/hourly/bonus_points.php) and [store](sections/store).
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
 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.
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
 ## Minor Changes
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
 **Gracie Gazelle**
130
 **Gracie Gazelle**
64
 
131
 
65
 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.
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
 <?php
1
 <?php
2
 declare(strict_types=1);
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
-<?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
-<?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
-<?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
-<?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
-<?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
 $NumRequests = count($Requests);
134
 $NumRequests = count($Requests);
135
 
135
 
136
 if (($Importances = $Cache->get_value("artist_groups_$ArtistID")) === false) {
136
 if (($Importances = $Cache->get_value("artist_groups_$ArtistID")) === false) {
137
-    /*
138
     $DB->query("
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
     $GroupIDs = $DB->collect('GroupID');
155
     $GroupIDs = $DB->collect('GroupID');
154
     $Importances = $DB->to_array(false, MYSQLI_BOTH, false);
156
     $Importances = $DB->to_array(false, MYSQLI_BOTH, false);
155
     $Cache->cache_value("artist_groups_$ArtistID", $Importances, 0);
157
     $Cache->cache_value("artist_groups_$ArtistID", $Importances, 0);

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

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
 
28
 
29
         if ($ThreadID) {
29
         if ($ThreadID) {
30
             // Redirect postid to threadid when necessary
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
             error();
32
             error();
33
         } else {
33
         } else {
34
             echo json_encode(array('status' => 'failure'));
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

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
 <?php
1
 <?php
2
-#declare(strict_types=1);
2
+declare(strict_types=1);
3
 
3
 
4
 #authorize();
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
 <?php
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
 $Type = $_POST['type'];
5
 $Type = $_POST['type'];
6
-$ID = (int)$_POST['id'];
6
+$ID = (int) $_POST['id'];
7
 $Note = $_POST['note'];
7
 $Note = $_POST['note'];
8
 
8
 
9
 if (empty($FriendID) || empty($Type) || empty($ID)) {
9
 if (empty($FriendID) || empty($Type) || empty($ID)) {
10
     echo json_encode(array('status' => 'error', 'response' => 'Error.'));
10
     echo json_encode(array('status' => 'error', 'response' => 'Error.'));
11
     error();
11
     error();
12
 }
12
 }
13
+
13
 // Make sure the recipient is on your friends list and not some random dude.
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
 if (!$DB->has_results()) {
32
 if (!$DB->has_results()) {
25
     echo json_encode(array('status' => 'error', 'response' => 'Not on friend list.'));
33
     echo json_encode(array('status' => 'error', 'response' => 'Not on friend list.'));
32
 // https://en.wikipedia.org/wiki/English_articles#Distinction_between_a_and_an
40
 // https://en.wikipedia.org/wiki/English_articles#Distinction_between_a_and_an
33
 $Article = 'a';
41
 $Article = 'a';
34
 switch ($Type) {
42
 switch ($Type) {
35
-  case 'torrent':
43
+    case 'torrent':
36
     $Link = "torrents.php?id=$ID";
44
     $Link = "torrents.php?id=$ID";
37
     $DB->query("
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
     break;
53
     break;
42
-  case 'artist':
54
+
55
+    case 'artist':
43
     $Article = 'an';
56
     $Article = 'an';
44
     $Link = "artist.php?id=$ID";
57
     $Link = "artist.php?id=$ID";
45
     $DB->query("
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
     break;
66
     break;
50
-  case 'collage':
67
+
68
+    case 'collage':
51
     $Link = "collages.php?id=$ID";
69
     $Link = "collages.php?id=$ID";
52
     $DB->query("
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
     break;
81
     break;
57
 }
82
 }
83
+
58
 list($Name) = $DB->next_record();
84
 list($Name) = $DB->next_record();
59
 $Subject = $LoggedUser['Username'] . " recommended you $Article $Type!";
85
 $Subject = $LoggedUser['Username'] . " recommended you $Article $Type!";
60
 $Body = $LoggedUser['Username'] . " recommended you the $Type [url=".site_url()."$Link]$Name".'[/url].';
86
 $Body = $LoggedUser['Username'] . " recommended you the $Type [url=".site_url()."$Link]$Name".'[/url].';
87
+
61
 if (!empty($Note)) {
88
 if (!empty($Note)) {
62
     $Body = "$Body\n\n$Note";
89
     $Body = "$Body\n\n$Note";
63
 }
90
 }

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

1
 <?php
1
 <?php
2
-#declare(strict_types=1);
2
+declare(strict_types=1);
3
 
3
 
4
 // Begin user stats
4
 // Begin user stats
5
 if (($UserCount = $Cache->get_value('stats_user_count')) === false) {
5
 if (($UserCount = $Cache->get_value('stats_user_count')) === false) {
67
 if (($AlbumCount = $Cache->get_value('stats_album_count')) === false) {
67
 if (($AlbumCount = $Cache->get_value('stats_album_count')) === false) {
68
     $DB->query("
68
     $DB->query("
69
     SELECT
69
     SELECT
70
-      COUNT(`ID`)
70
+      COUNT(`id`)
71
     FROM
71
     FROM
72
       `torrents_group`
72
       `torrents_group`
73
     WHERE
73
     WHERE
74
-      `CategoryID` = '1'
74
+      `category_id` = '1'
75
     ");
75
     ");
76
+
76
     list($AlbumCount) = $DB->next_record();
77
     list($AlbumCount) = $DB->next_record();
77
     $Cache->cache_value('stats_album_count', $AlbumCount, 604830); // staggered 1 week cache
78
     $Cache->cache_value('stats_album_count', $AlbumCount, 604830); // staggered 1 week cache
78
 }
79
 }
84
     FROM
85
     FROM
85
       `artists_group`
86
       `artists_group`
86
     ");
87
     ");
88
+    
87
     list($ArtistCount) = $DB->next_record();
89
     list($ArtistCount) = $DB->next_record();
88
     $Cache->cache_value('stats_artist_count', $ArtistCount, 604860); // staggered 1 week cache
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
 $Limit = in_array($Limit, array(10, 100, 250)) ? $Limit : 10;
18
 $Limit = in_array($Limit, array(10, 100, 250)) ? $Limit : 10;
19
 
19
 
20
 $WhereSum = (empty($Where)) ? '' : md5($Where);
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
 $OuterResults = [];
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

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
 <?php
1
 <?php
2
 #declare(strict_types=1);
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
 $TorrentID = (int) $_GET['id'];
6
 $TorrentID = (int) $_GET['id'];
7
 $TorrentHash = (string) $_GET['hash'];
7
 $TorrentHash = (string) $_GET['hash'];
38
 $GroupID = $TorrentDetails['ID'];
38
 $GroupID = $TorrentDetails['ID'];
39
 $Artists = Artists::get_artist($GroupID);
39
 $Artists = Artists::get_artist($GroupID);
40
 
40
 
41
-if ($TorrentDetails['CategoryID'] === 0) {
41
+if ($TorrentDetails['category_id'] === 0) {
42
     $CategoryName = 'Unknown';
42
     $CategoryName = 'Unknown';
43
 } else {
43
 } else {
44
-    $CategoryName = $Categories[$TorrentDetails['CategoryID'] - 1];
44
+    $CategoryName = $Categories[$TorrentDetails['category_id'] - 1];
45
 }
45
 }
46
 
46
 
47
 $TagList = explode('|', $TorrentDetails['GROUP_CONCAT(DISTINCT tags.Name SEPARATOR \'|\')']);
47
 $TagList = explode('|', $TorrentDetails['GROUP_CONCAT(DISTINCT tags.Name SEPARATOR \'|\')']);
48
 
48
 
49
 $JsonTorrentDetails = [
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
   'authors'      => $Artists,
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
 $Torrent = $TorrentList[$TorrentID];
66
 $Torrent = $TorrentList[$TorrentID];

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

1
 <?php
1
 <?php
2
 declare(strict_types=1);
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
 $GroupID = (int) $_GET['id'];
6
 $GroupID = (int) $_GET['id'];
7
 if ($GroupID === 0) {
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
 if (check_paranoia_here('snatched')) {
17
 if (check_paranoia_here('snatched')) {
18
     $DB->query("
18
     $DB->query("
19
     SELECT
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
     $RecentSnatches = $DB->to_array(false, MYSQLI_ASSOC);
41
     $RecentSnatches = $DB->to_array(false, MYSQLI_ASSOC);
34
     $Artists = Artists::get_artists($DB->collect('ID'));
42
     $Artists = Artists::get_artists($DB->collect('ID'));
45
 if (check_paranoia_here('uploads')) {
53
 if (check_paranoia_here('uploads')) {
46
     $DB->query("
54
     $DB->query("
47
     SELECT
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
     $RecentUploads = $DB->to_array(false, MYSQLI_ASSOC);
74
     $RecentUploads = $DB->to_array(false, MYSQLI_ASSOC);
61
     $Artists = Artists::get_artists($DB->collect('ID'));
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