diff --git a/app/BonusUploadReward.php b/app/BonusUploadReward.php
index 54ffca02c..366d55f88 100644
--- a/app/BonusUploadReward.php
+++ b/app/BonusUploadReward.php
@@ -5,6 +5,23 @@ declare(strict_types=1);
namespace Gazelle;
class BonusUploadReward extends Base {
+ public function boost(User $user): int {
+ if (
+ !BONUS_UPLOAD_BOOST_ACTIVE
+ ||
+ $user->classLevel() > BONUS_UPLOAD_BOOST_MAX_LEVEL
+ ) {
+ return 0;
+ }
+ $index = $user->ordinal()->value('bonus-upload-boost');
+ if (!isset(BONUS_UPLOAD_BOOST[$index])) {
+ return 0;
+ }
+ $boost = BONUS_UPLOAD_BOOST[$index];
+ $user->ordinal()->set('bonus-upload-boost', $index + 1);
+ return $boost;
+ }
+
public function reward(Torrent $torrent): int {
$categoryId = $torrent->group()->categoryId();
if ($torrent->isPerfectFlac()) {
diff --git a/app/User/Ordinal.php b/app/User/Ordinal.php
index ae8ec8dde..bf7a85912 100644
--- a/app/User/Ordinal.php
+++ b/app/User/Ordinal.php
@@ -13,7 +13,7 @@ namespace Gazelle\User;
*/
class Ordinal extends \Gazelle\BaseUser {
- final protected const CACHE_KEY = 'u_ord_%s';
+ final protected const CACHE_KEY = 'u_ord2_%s';
protected array $info;
diff --git a/lib/config.php b/lib/config.php
index 4e8507ace..16e061857 100644
--- a/lib/config.php
+++ b/lib/config.php
@@ -736,9 +736,19 @@ defined('BONUS_POOL_TAX_STAFF') or define('BONUS_POOL_TAX_STAFF', 0.5);
// Pricing of tokens to other scales up at every interval of tokens received.
defined('BONUS_OTHER_TOKEN_INTERVAL') or define('BONUS_OTHER_TOKEN_INTERVAL', 100);
-// At each interval, prices are raised by SCALE percent. Set to 0 to disable scaling.
+
+// At each interval, prices are raised by SCALE percent. Set to 0 to enable flat pricing.
defined('BONUS_OTHER_TOKEN_SCALE') or define('BONUS_OTHER_TOKEN_SCALE', 20);
+// Do initial uploads get a boost?
+defined('BONUS_UPLOAD_BOOST_ACTIVE') or define('BONUS_UPLOAD_BOOST_ACTIVE', true);
+
+// What are the (and how many) rewards for upload boosts
+defined('BONUS_UPLOAD_BOOST') or define('BONUS_UPLOAD_BOOST', [5000, 4000, 3000, 2000, 1000]);
+
+// What is the highest userclass level that benefits from boosts? (200 == Power User)
+defined('BONUS_UPLOAD_BOOST_MAX_LEVEL') or define('BONUS_UPLOAD_BOOST_MAX_LEVEL', 200);
+
// ------------------------------------------------------------------------
// Pagination
diff --git a/misc/my-migrations/20251002073716_bonus_upload_boost.php b/misc/my-migrations/20251002073716_bonus_upload_boost.php
new file mode 100644
index 000000000..6515aed7b
--- /dev/null
+++ b/misc/my-migrations/20251002073716_bonus_upload_boost.php
@@ -0,0 +1,23 @@
+table('user_ordinal')
+ ->insert([[
+ 'name' => 'bonus-upload-boost',
+ 'description' => 'How many upload boosts has this user earned',
+ 'default_value' => 0,
+ ]])
+ ->save();
+ }
+
+ public function down(): void {
+ $this->query("
+ delete from user_ordinal where name = 'bonus-upload-boost';
+ ");
+ }
+}
diff --git a/misc/phpstan.neon b/misc/phpstan.neon
index 9f03ba61d..e95bfe7da 100644
--- a/misc/phpstan.neon
+++ b/misc/phpstan.neon
@@ -71,6 +71,7 @@ parameters:
- AJAX
- BITCOIN_DONATION_XYZPUB
- BLOCK_TOR
+ - BONUS_UPLOAD_BOOST_ACTIVE
- DISABLE_IRC
- DISABLE_TRACKER
- DEBUG
diff --git a/sections/upload/upload_handle.php b/sections/upload/upload_handle.php
index a7f372abd..831f4de8c 100644
--- a/sections/upload/upload_handle.php
+++ b/sections/upload/upload_handle.php
@@ -616,7 +616,10 @@ $Debug->mark('upload: database committed');
$tracker = new Tracker();
$uploadReward = new BonusUploadReward();
-$bonusTotal = 0;
+$bonusTotal = $uploadReward->boost($Viewer);
+if ($bonusTotal > 0) {
+ $Viewer->addStaffNote("$bonusTotal points boost for {$torrent->publicLocation()}")->modify();
+}
$folderCheck = [];
foreach ($upload['new'] as $t) {
$t->flush()->unlockUpload();
diff --git a/templates/user/header.twig b/templates/user/header.twig
index 3932e0fef..e46cbc7e1 100644
--- a/templates/user/header.twig
+++ b/templates/user/header.twig
@@ -156,28 +156,28 @@ None
{% endif %}
-{% set visible = user.propertyVisible(preview_user, 'uploaded') %}
+{%- set visible = user.propertyVisible(preview_user, 'uploaded') %}
{% if visible %}
Uploaded: {{ user.uploadedSize|octet_size }}
{% endif %}
-{% set visible = user.propertyVisible(preview_user, 'downloaded') %}
+{%- set visible = user.propertyVisible(preview_user, 'downloaded') %}
{% if visible %}
Downloaded: {{ user.downloadedSize|octet_size }}
{% endif %}
-{% set visible = min(user.propertyVisible(preview_user, 'downloaded'), user.propertyVisible(preview_user, 'uploaded')) %}
+{%- set visible = min(user.propertyVisible(preview_user, 'downloaded'), user.propertyVisible(preview_user, 'uploaded')) %}
{% if visible %}
{% set buffer = user.buffer[1] %}
Buffer: {{ buffer|octet_size }}
{% endif %}
-{% set visible = user.propertyVisible(preview_user, 'ratio') %}
+{%- set visible = user.propertyVisible(preview_user, 'ratio') %}
{% if visible %}
Ratio: {{ ratio(user.uploadedSize, user.downloadedSize) }}
{% endif %}
-{% if own_profile or viewer.permitted('users_mod') %}
+{%- if own_profile or viewer.permitted('users_mod') %}
{% set recovered = user.recoveryFinalSize %}
{% if recovered %}
Recovered: {{ recovered|octet_size }}
@@ -186,24 +186,34 @@ None
{% endif %}
{% endif %}
-{% set visible = user.propertyVisible(preview_user, 'requiredratio') %}
+{%- set visible = user.propertyVisible(preview_user, 'requiredratio') %}
{% if visible %}
{% set required = user.buffer[0] %}
Required Ratio: {{ user.requiredRatio|number_format(2) }}
Required Class Ratio: {{ required|number_format(2) }}
{% endif %}
-{% set visible = user.propertyVisible(preview_user, 'requiredratio') %}
+{%- set visible = user.propertyVisible(preview_user, 'requiredratio') %}
{% if visible %}
{% set size = user.seedingSize %}
Seeding Size: {{ size|octet_size }}
{% endif %}
-{% set visible = user.propertyVisible(preview_user, 'bonuspoints') %}
+{%- set visible = user.propertyVisible(preview_user, 'bonuspoints') %}
{% if visible %}
{% if viewer.permitted('admin_bp_history') %}
Bonus Points: {{ user.bonusPointsTotal|number_format }}
History
+{% if constant('BONUS_UPLOAD_BOOST_ACTIVE') %}
+{% set boost = user.ordinal.value('bonus-upload-boost') %}
+{% set total = 0 %}
+{% if boost %}
+{% for n in range(1, boost) %}
+{% set total = total + constant('BONUS_UPLOAD_BOOST')[n - 1] %}
+{% endfor %}
+{% endif %}
+ Upload boosts: {{ boost }}{% if total %} ({{ total|number_format }} points){% endif %}
+{% endif %}
Points Per Hour: {{ bonus.hourlyRate|number_format(2) }}
{% elseif own_profile %}
Bonus Points: {{ user.bonusPointsTotal|number_format }}
@@ -215,12 +225,12 @@ None
{% endif %}
{% endif %}
-{% if own_profile or viewer.permitted('users_mod') %}
+{%- if own_profile or viewer.permitted('users_mod') %}
Tokens: {{ user.tokenCount|number_format }}
{% endif %}
-{% if user.isWarned and (own_profile or viewer.permitted('users_mod')) %}
+{%- if user.isWarned and (own_profile or viewer.permitted('users_mod')) %}
Warning expires in: {{ user.warningExpiry|time_diff }}
{% endif %}
diff --git a/templates/user/sidebar.twig b/templates/user/sidebar.twig
index e2fa4a4c4..9e50a3ecd 100644
--- a/templates/user/sidebar.twig
+++ b/templates/user/sidebar.twig
@@ -3,27 +3,26 @@
- Class: {{ user.userclassName }}
{% for id, name in user.privilege.secondaryClassList %}
- {% if loop.first %}
+{% if loop.first %}
-
- {% endif %}
- {% if id != constant('DONOR') or user.propertyVisible(viewer, 'hide_donor_heart') %}
+{% endif %}
+{% if id != constant('DONOR') or user.propertyVisible(viewer, 'hide_donor_heart') %}
- {{ name }}
- {% endif %}
- {% if loop.last %}
+{% endif %}
+{% if loop.last %}
- {% endif %}
+{% endif %}
{% endfor %}
-{% set own_profile = user.id == viewer.id %}
+{%- set own_profile = user.id == viewer.id %}
- Paranoia level: {{ user.paranoiaLabel }}
-
-{%- if own_profile or viewer.permitted('users_view_email') %}
+{% if own_profile or viewer.permitted('users_view_email') %}
- Email: {{ user.email }}
- {% if viewer.permitted('users_view_email') %}
+{% if viewer.permitted('users_view_email') %}
S
- {%- endif -%}
+{% endif %}
{% endif %}
@@ -35,7 +34,7 @@
- Passkey: View
{% endif %}
-{% if viewer.permitted('users_view_invites') %}
+{%- if viewer.permitted('users_view_invites') %}
- Invited by:
{% if user.referral %}
self from {{ user.referral }}.
@@ -61,9 +60,9 @@
{%- if viewer.permitted('users_view_invites') or (own_profile and user.canPurchaseInvite) %}
- Invites: {% if user.disableInvites %}X{% else %}{{ user.unusedInviteTotal|number_format }}{% endif %}
({{ user.invite.pendingTotal|number_format }} in use)
- {% set total = user.stats.invitedTotal %}
+{% set total = user.stats.invitedTotal %}
- Invited: {{ total|number_format }}
- {% if total %} View{% endif %}
+{% if total %} View{% endif -%}
{% endif %}
diff --git a/tests/phpunit/BonusTest.php b/tests/phpunit/BonusTest.php
index 5a77f470a..e6b374d91 100644
--- a/tests/phpunit/BonusTest.php
+++ b/tests/phpunit/BonusTest.php
@@ -699,6 +699,21 @@ class BonusTest extends TestCase {
);
}
+ public function testUploadBoost(): void {
+ $this->userList[] = $user = Helper::makeUser('bonusboost.' . randomString(6), 'bonus');
+ $reward = new BonusUploadReward();
+
+ $user->setField('PermissionID', ELITE)->modify();
+ $this->assertEquals(0, $reward->boost($user), 'bonus-boost-elite');
+ $user->setField('PermissionID', MEMBER)->modify();
+ $this->assertEquals(BONUS_UPLOAD_BOOST[0], $reward->boost($user), 'bonus-boost-01');
+ $this->assertEquals(BONUS_UPLOAD_BOOST[1], $reward->boost($user), 'bonus-boost-02');
+ $this->assertEquals(BONUS_UPLOAD_BOOST[2], $reward->boost($user), 'bonus-boost-03');
+ $this->assertEquals(BONUS_UPLOAD_BOOST[3], $reward->boost($user), 'bonus-boost-04');
+ $this->assertEquals(BONUS_UPLOAD_BOOST[4], $reward->boost($user), 'bonus-boost-05');
+ $this->assertEquals(0, $reward->boost($user), 'bonus-boost-06');
+ }
+
public function testStats(): void {
$eco = new Stats\Economic();
$eco->flush();