Skip to content

Commit

Permalink
Add an invite source toolbox to track recruitments
Browse files Browse the repository at this point in the history
  • Loading branch information
Spine authored and itismadness committed Aug 15, 2021
1 parent 3e5ae4a commit 2a5c839
Show file tree
Hide file tree
Showing 24 changed files with 681 additions and 110 deletions.
174 changes: 174 additions & 0 deletions app/Manager/InviteSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

namespace Gazelle\Manager;

class InviteSource extends \Gazelle\Base {

public function create(string $name): int {
$this->db->prepared_query("
INSERT INTO invite_source (name) VALUES (?)
", $name
);
return $this->db->inserted_id();
}

public function createPendingInviteSource(int $inviteSourceId, string $inviteKey): int {
$this->db->prepared_query("
INSERT INTO invite_source_pending
(invite_source_id, invite_key)
VALUES (?, ?)
", $inviteSourceId, $inviteKey
);
return $this->db->affected_rows();
}

public function resolveInviteSource(string $inviteKey, int $userId): int {
$inviteSourceId = $this->db->scalar("
SELECT invite_source_id
FROM invite_source_pending
WHERE invite_key = ?
", $inviteKey
);
if (!$inviteSourceId) {
return 0;
}
$this->db->prepared_query("
DELETE FROM invite_source_pending WHERE invite_key = ?
", $inviteKey
);
$this->db->prepared_query("
INSERT INTO user_has_invite_source
(user_id, invite_source_id)
VALUES (?, ?)
", $userId, $inviteSourceId
);
return $this->db->affected_rows();
}

public function findSourceNameByUserId(int $userId): ?string {
return $this->db->scalar("
SELECT i.name
FROM invite_source i
INNER JOIN user_has_invite_source uhis USING (invite_source_id)
WHERE uhis.user_id = ?
", $userId
);
}

public function remove(int $id): int {
$this->db->prepared_query("
DELETE FROM invite_source WHERE invite_source_id = ?
", $id
);
return $this->db->affected_rows();
}

public function listByUse(): array {
$this->db->prepared_query("
SELECT i.invite_source_id,
i.name,
count(DISTINCT ihis.user_id) AS inviter_total,
count(DISTINCT uhis.user_id) AS user_total
FROM invite_source i
LEFT JOIN inviter_has_invite_source ihis USING (invite_source_id)
LEFT JOIN user_has_invite_source uhis USING (invite_source_id)
GROUP BY i.invite_source_id, i.name
ORDER BY i.name
");
return $this->db->to_array(false, MYSQLI_ASSOC, false);
}

public function summaryByInviter(): array {
$this->db->prepared_query("
SELECT ihis.user_id,
group_concat(i.name ORDER BY i.name SEPARATOR ', ') as name_list
FROM inviter_has_invite_source ihis
INNER JOIN invite_source i USING (invite_source_id)
INNER JOIN users_main um ON (um.ID = ihis.user_id)
GROUP BY ihis.user_id
ORDER BY um.username
");
return $this->db->to_array(false, MYSQLI_ASSOC, false);
}

public function inviterConfiguration(int $userId): array {
$this->db->prepared_query("
SELECT i.invite_source_id,
i.name,
ihis.invite_source_id IS NOT NULL AS active
FROM invite_source i
LEFT JOIN inviter_has_invite_source ihis ON (i.invite_source_id = ihis.invite_source_id AND ihis.user_id = ?)
ORDER BY i.name
", $userId
);
return $this->db->to_array(false, MYSQLI_ASSOC, false);
}

public function inviterConfigurationActive(int $userId): array {
$this->db->prepared_query("
SELECT i.invite_source_id,
i.name,
ihis.invite_source_id IS NOT NULL AS active
FROM invite_source i
INNER JOIN inviter_has_invite_source ihis ON (i.invite_source_id = ihis.invite_source_id AND ihis.user_id = ?)
ORDER BY i.name
", $userId
);
return $this->db->to_array(false, MYSQLI_ASSOC, false);
}

public function modifyInviterConfiguration(int $userId, array $ids): int {
$this->db->begin_transaction();
$this->db->prepared_query("
DELETE FROM inviter_has_invite_source WHERE user_id = ?
", $userId
);
$userAndSourceId = [];
foreach ($ids as $sourceId) {
$userAndSourceId[] = $userId;
$userAndSourceId[] = $sourceId;
}
$this->db->prepared_query("
INSERT INTO inviter_has_invite_source (user_id, invite_source_id)
VALUES " . placeholders($ids, '(?, ?)'), ...$userAndSourceId
);
$this->db->commit();
return $this->db->affected_rows();
}

public function userSource(int $userId) {
$this->db->prepared_query("
SELECT ui.UserID AS user_id,
uhis.invite_source_id,
i.name
FROM users_info ui
LEFT JOIN user_has_invite_source uhis ON (uhis.user_id = ui.UserID)
LEFT JOIN invite_source i USING (invite_source_id)
WHERE ui.inviter = ?
", $userId
);
return $this->db->to_array('user_id', MYSQLI_ASSOC, false);
}

public function modifyUserSource(int $userId, array $ids): int {
$userAndSourceId = [];
foreach ($ids as $inviteeId => $sourceId) {
$userAndSourceId[] = $inviteeId;
$userAndSourceId[] = $sourceId;
}
$this->db->begin_transaction();
$this->db->prepared_query("
DELETE uhis
FROM user_has_invite_source uhis
INNER JOIN users_info ui ON (ui.UserID = uhis.user_id)
WHERE ui.Inviter = ?
", $userId
);
$this->db->prepared_query("
INSERT INTO user_has_invite_source (user_id, invite_source_id)
VALUES " . placeholders($ids, '(?, ?)'), ...$userAndSourceId
);
$this->db->commit();
return $this->db->affected_rows();
}
}
13 changes: 10 additions & 3 deletions app/Schedule/Tasks/ExpireInvites.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@ class ExpireInvites extends \Gazelle\Schedule\Task
{
public function run()
{
$userQuery = $this->db->prepared_query("SELECT InviterID FROM invites WHERE Expires < now()");
$this->db->begin_transaction();
$this->db->prepared_query("SELECT InviterID FROM invites WHERE Expires < now()");
$users = $this->db->collect('InviterID', false);

$this->db->prepared_query("DELETE FROM invites WHERE Expires < now()");
$this->db->prepared_query("
DELETE isp FROM invite_source_pending isp
LEFT JOIN invites i ON (i.InviteKey = isp.invite_key)
WHERE i.InviteKey IS NULL
");

$this->db->set_query_id($userQuery);
$users = $this->db->collect('InviterID', false);
foreach ($users as $user) {
$this->db->prepared_query("UPDATE users_main SET Invites = Invites + 1 WHERE ID = ?", $user);
$this->cache->deleteMulti(["u_$user", "user_info_heavy_$user"]);
$this->debug("Expired invite from user $user", $user);
$this->processed++;
}
$this->db->commit();
}
}
9 changes: 4 additions & 5 deletions app/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,8 @@ public function isWarned(): bool { return !is_null($this->warningExpiry());
public function isStaff(): bool { return $this->info()['isStaff']; }
public function isDonor(): bool { return isset($this->info()['secondary_class'][DONOR]) || $this->isStaff(); }
public function isFLS(): bool { return isset($this->info()['secondary_class'][FLS_TEAM]); }
public function isInterviewer(): bool { return isset($this->info()['secondary_class'][INTERVIEWER]); }
public function isRecruiter(): bool { return isset($this->info()['secondary_class'][RECRUITER]); }
public function isStaffPMReader(): bool { return $this->isFLS() || $this->isStaff(); }

public function warningExpiry(): ?string {
Expand Down Expand Up @@ -2436,17 +2438,14 @@ public function canInvite(): bool {
}

/**
* Checks whether a user is allowed to purchase an invite. User classes up to Elite are capped,
* Checks whether a user is allowed to purchase an invite. Lower classes are capped,
* users above this class will always return true.
*
* @param integer $minClass Minimum class level necessary to purchase invites
* @return boolean false if insufficient funds, otherwise true
*/
public function canPurchaseInvite(): bool {
if ($this->info()['DisableInvites']) {
return false;
}
return $this->info()['effective_class'] >= MIN_INVITE_CLASS;
return !$this->disableInvites() && $this->effectiveClass() >= MIN_INVITE_CLASS;
}

/**
Expand Down
1 change: 1 addition & 0 deletions app/UserCreator.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public function create() {
);

if ($inviterId) {
(new \Gazelle\Manager\InviteSource)->resolveInviteSource($this->inviteKey, $this->id);
$this->db->prepared_query("
DELETE FROM invites WHERE InviteKey = ?
", $this->inviteKey
Expand Down
34 changes: 17 additions & 17 deletions classes/config.template.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,24 +162,24 @@
define('FEATURE_EMAIL_REENABLE', true);
}

// User class IDs needed for automatic promotions. Found in the 'permissions' table
// Name of class Class ID (NOT level)
define('ADMIN', '1');
define('USER', '2');
define('MEMBER', '3');
define('POWER', '4');
define('ELITE', '5');
define('VIP', '6');
define('TORRENT_MASTER','7');
define('MOD', '11');
define('SYSOP', '15');
define('ARTIST', '19');
define('DONOR', '20');
define('FLS_TEAM', '23');
define('POWER_TM', '22');
define('ELITE_TM', '23');
define('FORUM_MOD', '28');
define('ULTIMATE_TM', '48');
define('USER', 2);
define('MEMBER', 3);
define('POWER', 4);
define('ELITE', 5);
define('TORRENT_MASTER', 7);
define('POWER_TM', 22);
define('ELITE_TM', 23);
define('ULTIMATE_TM', 48);
define('FORUM_MOD', 28);
define('MOD', 11);
define('SYSOP', 15);

define('DONOR', 20);
define('FLS_TEAM', 23);
define('INTERVIEWER', 30);
define('RECRUITER', 41);
define('VIP', 6);

// Locked account constant
define('STAFF_LOCKED', 1);
Expand Down
1 change: 1 addition & 0 deletions classes/permissions.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public static function list() {
'admin_manage_polls' => 'Can manage polls',
'admin_manage_forums' => 'Can manage forums (add/edit/delete)',
'admin_manage_fls' => 'Can manage First Line Support (FLS) crew',
'admin_manage_invite_source' => 'Can manage invite sources',
'admin_manage_user_fls' => 'Can manage user FL tokens',
'admin_manage_applicants' => 'Can manage job roles and user applications',
'admin_manage_referrals' => 'Can manage referrals',
Expand Down
49 changes: 49 additions & 0 deletions db/migrations/20210616100351_invite_source.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class InviteSource extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('invite_source', ['id' => false, 'primary_key' => ['invite_source_id']])
->addColumn('invite_source_id', 'integer', ['limit' => 10, 'signed' => false, 'identity' => true])
->addColumn('name', 'string', ['limit' => 20, 'encoding' => 'ascii'])
->addIndex(['name'], ['unique' => true, 'name' => 'is_name_uidx'])
->create();

$this->table('invite_source_pending', ['id' => false, 'primary_key' => ['invite_key']])
->addColumn('user_id', 'integer', ['limit' => 10, 'signed' => false])
->addColumn('invite_source_id', 'integer', ['limit' => 10, 'signed' => false])
->addColumn('invite_key', 'string', ['limit' => 32])
->addForeignKey('user_id', 'users_main', 'ID', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->addForeignKey('invite_source_id', 'invite_source', 'invite_source_id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->create();

$this->table('user_has_invite_source', ['id' => false, 'primary_key' => ['user_id']])
->addColumn('user_id', 'integer', ['limit' => 10, 'signed' => false])
->addColumn('invite_source_id', 'integer', ['limit' => 10, 'signed' => false])
->addForeignKey('user_id', 'users_main', 'ID', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->addForeignKey('invite_source_id', 'invite_source', 'invite_source_id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->create();

$this->table('inviter_has_invite_source', ['id' => false, 'primary_key' => ['user_id', 'invite_source_id']])
->addColumn('user_id', 'integer', ['limit' => 10, 'signed' => false])
->addColumn('invite_source_id', 'integer', ['limit' => 10, 'signed' => false])
->addForeignKey('user_id', 'users_main', 'ID', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->addForeignKey('invite_source_id', 'invite_source', 'invite_source_id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->create();
}
}
12 changes: 12 additions & 0 deletions db/seeds/InviteSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php


use Phinx\Seed\AbstractSeed;

class InviteSource extends AbstractSeed
{
public function run()
{
$this->table('invite_source')->insert(['name' => 'Personal'])->save();
}
}
Loading

0 comments on commit 2a5c839

Please sign in to comment.