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();