phpstan level 0 error free - fixes for kyu subpage - move mams into participo framework - remove legacy `lib/db.php` usage - add attributer admin function - add newsposter - fixing apiKey creation
427 lines
14 KiB
PHP
427 lines
14 KiB
PHP
<?php
|
|
require_once "bootstrap.php";
|
|
|
|
// Libs
|
|
require_once "participoLib/user.php";
|
|
|
|
participo::init($CONFIG["cwsvJudo"], $SECRETS["cwsvJudo"]);
|
|
|
|
$pmelo = new PmElo();
|
|
|
|
class PmElo
|
|
{
|
|
/** initialize the pmelo session */
|
|
function __construct()
|
|
{
|
|
// starting/continuing a session
|
|
session_start();
|
|
|
|
// create 'pmelo' section in the sessions data
|
|
if (!array_key_exists("pmelo", $_SESSION)) {
|
|
$_SESSION["pmelo"] = [];
|
|
}
|
|
// - data storage for id-s of chosen fighters
|
|
if (!array_key_exists("fighterIds", $_SESSION["pmelo"])) {
|
|
$_SESSION["pmelo"]["fighterIds"] = [];
|
|
}
|
|
|
|
// load/recreate ranking data
|
|
if (!is_file(self::$pmeloJson)) {
|
|
$this->log["info"][] =
|
|
"Couldn't find `" . self::$pmeloJson . "`. Create a new one!";
|
|
file_put_contents(self::$pmeloJson, json_encode([]));
|
|
}
|
|
|
|
// init members
|
|
$this->trainees = self::getTrainees();
|
|
$this->rankings = $this->createRankings();
|
|
$this->fighters = [];
|
|
foreach ($_SESSION["pmelo"]["fighterIds"] as $id) {
|
|
$this->fighters[$id] = $this->trainees[$id];
|
|
}
|
|
|
|
// @todo Can be split. Some actions need the members to be already filled, some could be done earlier.
|
|
// check for action in post data
|
|
if (array_key_exists("pmelo", $_POST)) {
|
|
// setting the current fighters
|
|
// - meaning: fighter list is automatically updated by posted fighters
|
|
if (array_key_exists("fighterIds", $_POST["pmelo"])) {
|
|
$_SESSION["pmelo"]["fighterIds"] =
|
|
$_POST["pmelo"]["fighterIds"];
|
|
|
|
header("Location: pmelo");
|
|
}
|
|
|
|
if (array_key_exists("promote", $_POST["pmelo"])) {
|
|
$promote = $_POST["pmelo"]["promote"];
|
|
$this->promote(
|
|
$promote["promoteeId"],
|
|
$promote["promoteeRank"],
|
|
$promote["degradeeId"],
|
|
$promote["degradeeRank"],
|
|
);
|
|
|
|
header("Location: pmelo");
|
|
}
|
|
}
|
|
|
|
// save the updated ranking
|
|
// @todo should be in destructor
|
|
self::saveRankings($this->rankings);
|
|
}
|
|
|
|
////
|
|
// html code injection
|
|
///
|
|
|
|
/** Echo the fighterInputForm */
|
|
public function htmlFighterInputForm()
|
|
{
|
|
echo $this->getFighterInputForm($this->trainees);
|
|
}
|
|
|
|
/** Echo the fighterTable */
|
|
public function htmlFighterTable()
|
|
{
|
|
echo $this->getFighterTable($this->fighters, $this->rankings);
|
|
}
|
|
|
|
/** Echo logOutput */
|
|
public function htmlLogOutput()
|
|
{
|
|
echo $this->getLogOutput($this->log);
|
|
}
|
|
|
|
////
|
|
// saving and loading
|
|
////
|
|
|
|
/** load all active trainees into a id=>User assoc array */
|
|
private static function getTrainees()
|
|
{
|
|
$traineeList = array_map(
|
|
"User::fromDbArray",
|
|
User::dbSelectWithAttribute(4),
|
|
);
|
|
$traineeAssocArray = [];
|
|
foreach ($traineeList as $trainee) {
|
|
$traineeAssocArray[$trainee->getId()] = $trainee;
|
|
}
|
|
return $traineeAssocArray;
|
|
}
|
|
|
|
/** load ranking from json file */
|
|
private static function loadRankings()
|
|
{
|
|
return json_decode(file_get_contents(self::$pmeloJson));
|
|
}
|
|
|
|
/** save a ranking to json file */
|
|
private static function saveRankings(array $rankings)
|
|
{
|
|
file_put_contents(self::$pmeloJson, json_encode($rankings));
|
|
}
|
|
|
|
////
|
|
// member functions
|
|
////
|
|
|
|
/** create the ranking for the current session
|
|
*
|
|
* - load saved ranking from file
|
|
* - remove old trainees
|
|
* - add new trainees
|
|
*/
|
|
private function createRankings()
|
|
{
|
|
// load the last state of the ranking
|
|
$loadedRanking = self::loadRankings();
|
|
|
|
// check if the ranked trainees still are in training
|
|
$cleanRanking = [];
|
|
// - trainees that aren't currently in the ranking for later inserting
|
|
$toBeInsertedTrainees = $this->trainees;
|
|
foreach ($loadedRanking as $id) {
|
|
if (array_key_exists($id, $this->trainees)) {
|
|
// userId is still in training, so append it to the cleaned up ranking
|
|
$cleanRanking[] = $id;
|
|
// trainees already in the ranking we won't have to insert manual
|
|
unset($toBeInsertedTrainees[$id]);
|
|
}
|
|
}
|
|
|
|
// get the ids of the to be inserted trainees sorted by the birthday
|
|
$newTraineesIds = array_map(function ($u) {
|
|
return $u->getId();
|
|
}, $toBeInsertedTrainees);
|
|
usort($newTraineesIds, function ($lhs, $rhs) {
|
|
return $this->trainees[$lhs]->getStrBirthday() <
|
|
$this->trainees[$rhs]->getStrBirthday()
|
|
? -1
|
|
: ($this->trainees[$lhs]->getStrBirthday() >
|
|
$this->trainees[$rhs]->getStrBirthday()
|
|
? 1
|
|
: 0);
|
|
});
|
|
|
|
// now we do a mergesort (step)
|
|
$updatedRanking = [];
|
|
$idxNewTrainees = count($newTraineesIds) - 1;
|
|
$idxRanking = count($cleanRanking) - 1;
|
|
// - while we can merge, we merge
|
|
while ($idxRanking >= 0 && $idxNewTrainees >= 0) {
|
|
if (
|
|
$this->trainees[$cleanRanking[$idxRanking]]->getDateOfBirth() >
|
|
$this->trainees[
|
|
$newTraineesIds[$idxNewTrainees]
|
|
]->getDateOfBirth()
|
|
) {
|
|
array_unshift($updatedRanking, $cleanRanking[$idxRanking]);
|
|
$idxRanking -= 1;
|
|
} else {
|
|
array_unshift(
|
|
$updatedRanking,
|
|
$newTraineesIds[$idxNewTrainees],
|
|
);
|
|
$idxNewTrainees -= 1;
|
|
}
|
|
}
|
|
while ($idxRanking >= 0) {
|
|
array_unshift($updatedRanking, $cleanRanking[$idxRanking]);
|
|
$idxRanking -= 1;
|
|
}
|
|
while ($idxNewTrainees >= 0) {
|
|
array_unshift($updatedRanking, $newTraineesIds[$idxNewTrainees]);
|
|
$idxNewTrainees -= 1;
|
|
}
|
|
return $updatedRanking;
|
|
}
|
|
|
|
/** promote a fighter
|
|
*
|
|
* Promotion means two fighter switch places
|
|
*
|
|
* $promoteeId (int>0) id to promote
|
|
* $promopteeRank (int>0) current rank of the one to be promoted
|
|
* $degradeeId (int>0) id to demote
|
|
* $degradeeRank (int>0) new rank of the promotee
|
|
*/
|
|
private function promote(
|
|
int $promoteeId,
|
|
int $promoteeRank,
|
|
int $degradeeId,
|
|
int $degradeeRank,
|
|
) {
|
|
// input sanitation
|
|
filterId($promoteeId);
|
|
filterId($promoteeRank);
|
|
filterId($degradeeId);
|
|
filterId($degradeeRank);
|
|
// check if both id are in the current fighter list
|
|
if (
|
|
array_key_exists($promoteeId, $this->fighters) &&
|
|
array_key_exists($degradeeId, $this->fighters)
|
|
) {
|
|
// check their current rank
|
|
if (
|
|
$this->rankings[$promoteeRank] == $promoteeId &&
|
|
$this->rankings[$degradeeRank] == $degradeeId
|
|
) {
|
|
// @todo check if they are rank neighbors in the fighter list
|
|
if (true) {
|
|
// do the actual switch
|
|
$this->rankings[$promoteeRank] = $degradeeId;
|
|
$this->rankings[$degradeeRank] = $promoteeId;
|
|
}
|
|
}
|
|
}
|
|
// save the updated ranking
|
|
$this->saveRankings($this->rankings);
|
|
}
|
|
|
|
////
|
|
// helper for output generation
|
|
////
|
|
|
|
/** Assemble the Input form for the fighter selection
|
|
*
|
|
* $trainees (array(participo::user)) list of trainees that can be selected for fighting
|
|
*
|
|
* return fighterInputForm as html code
|
|
*/
|
|
private static function getFighterInputForm(array $trainees)
|
|
{
|
|
$style = [
|
|
"classes" => [
|
|
"input-field" => "input-field col s12",
|
|
],
|
|
];
|
|
|
|
$retHtml =
|
|
'<form id="pmelo-form-fighters" action="pmelo" method="post">' .
|
|
// selects have to be wrapped into an .input-field, limitation of materializeCss
|
|
"<div class=" .
|
|
$style["classes"]["input-field"] .
|
|
">" .
|
|
'<select name="pmelo[fighterIds][]" id="pmelo-form-fighters-select" multiple>' .
|
|
'<option value="" disabled selected>Kämpfer auswählen</option>';
|
|
foreach ($trainees as $trainee) {
|
|
$retHtml .=
|
|
'<option value="' .
|
|
$trainee->getId() .
|
|
'">' .
|
|
$trainee->getFirstname() .
|
|
" " .
|
|
$trainee->getName() .
|
|
"</option>";
|
|
}
|
|
$retHtml .=
|
|
"</select>" .
|
|
'<label for="pmelo-form-fighters-select">' .
|
|
"Kämpfer auswählen" .
|
|
"</label>" .
|
|
'<input type="submit" value="Liste erstellen">' .
|
|
"</div>" .
|
|
"</form>";
|
|
return $retHtml;
|
|
}
|
|
|
|
/** Assemble the ranking table of the fighters
|
|
*
|
|
* $fighters (array(id=>participo::User)) list of fighters in the active round
|
|
* $ranking (array(rank=>id)) ranking of the current selected fighters
|
|
*
|
|
* return fighter ranking table as html code
|
|
*/
|
|
private static function getFighterTable(array $fighters, array $rankings)
|
|
{
|
|
$retHtml =
|
|
"<table>" .
|
|
"<thead><tr><th>Platz</th><th>Name</th><th>Aufstieg</th></tr></thead>" .
|
|
"<tbody>";
|
|
if (!empty($fighters)) {
|
|
// cache the predecessor in the ranking for the promoting form
|
|
$lastOne = null;
|
|
foreach ($rankings as $rank => $id) {
|
|
if (array_key_exists($id, $fighters)) {
|
|
$fighter = $fighters[$id];
|
|
$retHtml .=
|
|
"<tr>" .
|
|
"<td>" .
|
|
$rank .
|
|
"</td>" .
|
|
"<td>" .
|
|
join(" ", [
|
|
$fighter->getFirstName(),
|
|
$fighter->getName(),
|
|
]) .
|
|
"</td>";
|
|
if (!is_null($lastOne)) {
|
|
$retHtml .=
|
|
"<td>" .
|
|
self::getPromoteButton(
|
|
$fighter->getId(),
|
|
$rank,
|
|
$lastOne["id"],
|
|
$lastOne["rank"],
|
|
) .
|
|
"</td>";
|
|
}
|
|
$retHtml .= "</tr>";
|
|
$lastOne = ["id" => $fighter->getId(), "rank" => $rank];
|
|
}
|
|
}
|
|
}
|
|
$retHtml .= "</tbody>" . "</table>";
|
|
|
|
return $retHtml;
|
|
}
|
|
|
|
/** Assemble a Promote button
|
|
*
|
|
* $promoteeId (int>0) id to promote
|
|
* $promoteeRank (int>0) current rank of the promotee
|
|
* $degradeeId (int>0) id to demote
|
|
* $degradeeRank (int>0) rank of the to be demoted
|
|
*/
|
|
private static function getPromoteButton(
|
|
int $promoteeId,
|
|
int $promoteeRank,
|
|
int $degradeeId,
|
|
int $degradeeRank,
|
|
) {
|
|
return "<form action=\"pmelo\" method=\"post\">" .
|
|
"<input type= \"hidden\" name=\"pmelo[promote][promoteeId]\" value=\"" .
|
|
$promoteeId .
|
|
"\" />" .
|
|
"<input type= \"hidden\" name=\"pmelo[promote][promoteeRank]\" value=\"" .
|
|
$promoteeRank .
|
|
"\" />" .
|
|
"<input type= \"hidden\" name=\"pmelo[promote][degradeeId]\" value=\"" .
|
|
$degradeeId .
|
|
"\" />" .
|
|
"<input type= \"hidden\" name=\"pmelo[promote][degradeeRank]\" value=\"" .
|
|
$degradeeRank .
|
|
"\">" .
|
|
"<input class=\"btn\" type=\"submit\" value=\"^\">" .
|
|
"</form>";
|
|
}
|
|
|
|
/** Assemble the log Output
|
|
*
|
|
* $log (array logLevel=>array(messages)) lists of log messages per logLevel
|
|
*
|
|
* return logOutput as html code
|
|
*/
|
|
private static function getLogOutput(array $log)
|
|
{
|
|
$retHtml =
|
|
'<ul class="collection with-header">' .
|
|
'<li class="collection-header"><h3>Logs</h3></li>';
|
|
foreach ($log as $logLevel => $messages) {
|
|
if (!empty($messages)) {
|
|
$retHtml .=
|
|
'<li class="collection-item">' .
|
|
'<ul class="collection with-header">' .
|
|
'<li class="collection-header">' .
|
|
$logLevel .
|
|
"</li>";
|
|
foreach ($messages as $message) {
|
|
$retHtml .= "<li>" . $message . "</li>";
|
|
}
|
|
$retHtml .= "</ul>" . "</li>";
|
|
}
|
|
}
|
|
|
|
return $retHtml;
|
|
}
|
|
|
|
/** simple logger to a logging buffer */
|
|
private function var_log(
|
|
mixed $variable,
|
|
?string $prefix = null,
|
|
string $logChannel = "info",
|
|
): void {
|
|
if (!in_array($logChannel, ["info", "warning", "error"])) {
|
|
$logChannel = "info";
|
|
}
|
|
$prefix = !empty($prefix) ? "{$prefix}: " : "";
|
|
$this->log[$logChannel][] = $prefix . var_export($variable, true);
|
|
}
|
|
|
|
////
|
|
// member variables
|
|
////
|
|
|
|
/** current chosen trainees for fighting [id=>participo::User] */
|
|
public $fighters = null;
|
|
/** current trainees available for fighting as [id=>participo::User] */
|
|
public $trainees = null;
|
|
/** the current ranking as [rank=>id] */
|
|
public $rankings = null;
|
|
/** a place to save log messages for later output */
|
|
public $log = ["error" => [], "warning" => [], "info" => []];
|
|
|
|
static $pmeloJson = "./pmeloRanking.json";
|
|
}
|