572 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			572 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
require_once "vendor/autoload.php";
 | 
						|
 | 
						|
require_once "AutoStoring.php";
 | 
						|
 | 
						|
require_once "Utils.php";
 | 
						|
 | 
						|
class Game extends AutoStoring
 | 
						|
{
 | 
						|
    public const DEFAULT_GAME_PROPERTIES = [
 | 
						|
        "forward_only" => false, // player may traverse back and forth between tasks
 | 
						|
        "time_limit" => 0, // no time limit; otherwise, this field indicates time limit in seconds
 | 
						|
        "repeatable" => false // this test can be taken multiple times
 | 
						|
    ];
 | 
						|
 | 
						|
    public const CURRENT_GAME_VERSION = 2; // MUST BE INCREMENTED!!
 | 
						|
 | 
						|
    // --------
 | 
						|
    private int $id; // Game's ID
 | 
						|
    private string $name; // Game's name
 | 
						|
    private string $owner; // Game's owner
 | 
						|
    private array $contributors; // Contributors to the game
 | 
						|
    private string $description; // Game's description
 | 
						|
    private bool $gameFileIsPresent; // Indicates if game CSV is in place
 | 
						|
    private array $properties; // Collection of several game properties
 | 
						|
    private bool $public; // Is this game publicly available?
 | 
						|
    private string $publicId; // Public-accessible ID
 | 
						|
    private int $VERSION; // Game representation version (used during updates)
 | 
						|
    private GameMgr $gameMgr; // Game manager managing this instance
 | 
						|
    private bool $tasksLoaded; // Indicates if tasks have been fetched
 | 
						|
    private array $tasks; // Tasks
 | 
						|
 | 
						|
    // -------
 | 
						|
 | 
						|
    static public function genPublicId(): string
 | 
						|
    {
 | 
						|
        return uniqid("p");
 | 
						|
    }
 | 
						|
 | 
						|
    // -------
 | 
						|
 | 
						|
    static private function patchUpGameData(array &$a): void
 | 
						|
    {
 | 
						|
        $version = $a["version"] ?? 0;
 | 
						|
        if ($version < 2) { // update to game version 2
 | 
						|
            if (!key_exists("public_id", $a)) {
 | 
						|
                $a["public"] = false;
 | 
						|
                $a["public_id"] = self::genPublicId();
 | 
						|
            }
 | 
						|
 | 
						|
            $a["version"] = 2;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Store modifications.
 | 
						|
    public function storeMods(): void
 | 
						|
    {
 | 
						|
        $this->gameMgr->updateGame($this);
 | 
						|
    }
 | 
						|
 | 
						|
    // Commit modifications.
 | 
						|
    function commitMods(): void
 | 
						|
    {
 | 
						|
        //$this->patchUpGameDate();
 | 
						|
        parent::commitMods();
 | 
						|
    }
 | 
						|
 | 
						|
    // Load game tasks.
 | 
						|
    public function loadTasks(): void
 | 
						|
    {
 | 
						|
        if ($this->isGameFileIsPresent() && !$this->tasksLoaded) { // load if file is present
 | 
						|
            $this->tasks = TaskFactory::constructFromCollection(json_decode(file_get_contents($this->getGameFile()), true));
 | 
						|
            foreach ($this->tasks as &$ch) {
 | 
						|
                $ch->setTemplate(true);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Save tasks.
 | 
						|
    public function saveTasks(): void
 | 
						|
    {
 | 
						|
        file_put_contents($this->getGameFile(), json_encode($this->tasks)); // store tasks in JSON-format
 | 
						|
    }
 | 
						|
 | 
						|
    // -------
 | 
						|
 | 
						|
    function __construct(GameMgr &$gameMgr, string $name, string $description = "", int $id = -1, string $owner = "",
 | 
						|
                         array   $contributors = [], bool $gameFileIsPresent = false, array $properties = [],
 | 
						|
                         bool    $public = false, string $publicId = "", int $version = 2)
 | 
						|
    {
 | 
						|
        parent::__construct();
 | 
						|
 | 
						|
        $this->tasksLoaded = false;
 | 
						|
 | 
						|
        $this->gameMgr = $gameMgr;
 | 
						|
        $this->id = $id;
 | 
						|
        $this->name = $name;
 | 
						|
        $this->description = $description;
 | 
						|
        $this->owner = $owner;
 | 
						|
        $this->contributors = $contributors;
 | 
						|
        $this->gameFileIsPresent = $gameFileIsPresent;
 | 
						|
        $this->properties = $properties;
 | 
						|
        $this->public = $public;
 | 
						|
        $this->publicId = $publicId;
 | 
						|
        $this->VERSION = $version;
 | 
						|
        $this->tasks = [];
 | 
						|
    }
 | 
						|
 | 
						|
    // Create game from array representation.
 | 
						|
    static function fromArray(GameMgr &$gameMgr, array $a): Game
 | 
						|
    {
 | 
						|
        $id = $a["_id"] ?? -1;
 | 
						|
        self::patchUpGameData($a);
 | 
						|
        return new Game($gameMgr, $a["name"], $a["description"], $id, $a["owner"], $a["contributors"],
 | 
						|
            $a["game_file_present"], $a["properties"], $a["public"], $a["public_id"], $a["version"]);
 | 
						|
    }
 | 
						|
 | 
						|
    const OMIT_ADVANCED_FIELDS = ["contributors", "game_file_is_present", "properties", "public", "public_id", "version"];
 | 
						|
 | 
						|
    // Convert game to array representation.
 | 
						|
    function toArray(array $omit = []): array
 | 
						|
    {
 | 
						|
        $a = [
 | 
						|
            "_id" => $this->id,
 | 
						|
            "name" => $this->name,
 | 
						|
            "description" => $this->description,
 | 
						|
            "owner" => $this->owner,
 | 
						|
            "contributors" => $this->contributors,
 | 
						|
            "game_file_present" => $this->gameFileIsPresent,
 | 
						|
            "properties" => $this->properties,
 | 
						|
            "public" => $this->public,
 | 
						|
            "public_id" => $this->publicId,
 | 
						|
            "version" => $this->VERSION,
 | 
						|
        ];
 | 
						|
 | 
						|
        foreach ($omit as $field) {
 | 
						|
            unset($a[$field]);
 | 
						|
        }
 | 
						|
 | 
						|
        return $a;
 | 
						|
    }
 | 
						|
 | 
						|
    // Export tasks to a CSV file. TODO: ez csak a feleletválasztóshoz lesz jó
 | 
						|
    function exportTasksToCSV(&$f): void
 | 
						|
    {
 | 
						|
        // load tasks
 | 
						|
        $this->loadTasks();
 | 
						|
 | 
						|
        // populate CSV file
 | 
						|
        foreach ($this->tasks as $ch) {
 | 
						|
            if ($ch->getType() === "singlechoice") {
 | 
						|
                $csvline = [
 | 
						|
                    $ch["question"],
 | 
						|
                    $ch["image_url"],
 | 
						|
                ];
 | 
						|
                $csvline = array_merge($csvline, $ch["answers"]);
 | 
						|
                fputcsv($f, $csvline);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Get game directory NAME with path. Does not check if the game directory exists or not.
 | 
						|
    function getGameDir(): string
 | 
						|
    {
 | 
						|
        return self::getGameDirById($this->getId());
 | 
						|
    }
 | 
						|
 | 
						|
    function getAccompanyingFilePath(string $file_name): string {
 | 
						|
        return $this->getGameDir() . DIRECTORY_SEPARATOR . $file_name;
 | 
						|
    }
 | 
						|
 | 
						|
    static function getGameDirById(int $id): string {
 | 
						|
        return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $id;
 | 
						|
    }
 | 
						|
 | 
						|
    // Get game file NAME with path. Does not check whether the game file is in place or not.
 | 
						|
    function getGameFile(): string
 | 
						|
    {
 | 
						|
        return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $this->getId() . DIRECTORY_SEPARATOR . GAME_FILE;
 | 
						|
    }
 | 
						|
 | 
						|
    // Is the given user the owner of the game?
 | 
						|
    function isUserOwner(string $nickname): bool
 | 
						|
    {
 | 
						|
        return $this->owner === $nickname;
 | 
						|
    }
 | 
						|
 | 
						|
    // Is the given user a contributor of the game?
 | 
						|
    function isUserContributor(string $nickname): bool
 | 
						|
    {
 | 
						|
        return in_array($nickname, $this->contributors);
 | 
						|
    }
 | 
						|
 | 
						|
    // Is user contributor or owner?
 | 
						|
    function isUserContributorOrOwner(string $nickname): bool
 | 
						|
    {
 | 
						|
        return $this->isUserContributor($nickname) || $this->isUserOwner($nickname);
 | 
						|
    }
 | 
						|
 | 
						|
    const CSV_ENCODINGS = ["UTF-8", "Windows-1252"];
 | 
						|
 | 
						|
    // Import tasks from a CSV table. TODO: ez csak a feleletválasztós betöltésére lesz jó
 | 
						|
    function importTasksFromCSV(string $csv_path): array
 | 
						|
    {
 | 
						|
        // convert text encoding into UTF-8
 | 
						|
        $data = file_get_contents($csv_path);
 | 
						|
        $encoding = "UNKNOWN";
 | 
						|
        foreach (self::CSV_ENCODINGS as $enc) { // detect encoding
 | 
						|
            if (mb_check_encoding($data, $enc)) {
 | 
						|
                $encoding = $enc;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if ($encoding !== "UNKNOWN") { // if encoding has been detected successfully
 | 
						|
            $data = mb_convert_encoding($data, "UTF-8", $encoding);
 | 
						|
            file_put_contents($csv_path, $data);
 | 
						|
        }
 | 
						|
 | 
						|
        // clear tasks
 | 
						|
        $this->tasks = [];
 | 
						|
 | 
						|
        // load filled CSV file
 | 
						|
        $f = fopen($csv_path, "r");
 | 
						|
        if (!$f) { // failed to open file
 | 
						|
            return ["n" => 0, "encoding" => $encoding];
 | 
						|
        }
 | 
						|
        while ($csvline = fgetcsv($f)) {
 | 
						|
            // skip empty lines
 | 
						|
            if (trim(implode("", $csvline)) === "") {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            if (count($csvline) >= 3) {
 | 
						|
                // construct task record
 | 
						|
                $a = [
 | 
						|
                    "question" => trim($csvline[0]),
 | 
						|
                    "image_data" => trim($csvline[1]),
 | 
						|
                    "correct_answer" => 0,
 | 
						|
                    "answers" => array_filter(array_slice($csvline, 2), function ($v) {
 | 
						|
                        return trim($v) !== "";
 | 
						|
                    })
 | 
						|
                ];
 | 
						|
 | 
						|
                // if an image is attached to the task, then give a random name to the image
 | 
						|
                if ($a["image_data"] !== "") {
 | 
						|
                    $a["image_data"] = $this->obfuscateAttachedImage($a["image_data"]);
 | 
						|
                    $a["image_type"] = "url";
 | 
						|
                }
 | 
						|
 | 
						|
                // store the task
 | 
						|
                $this->tasks[] = new SingleChoiceTask($a);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        fclose($f);
 | 
						|
 | 
						|
        // save tasks
 | 
						|
        $this->saveTasks();
 | 
						|
 | 
						|
        // update game with game file present
 | 
						|
        $this->gameFileIsPresent = true;
 | 
						|
 | 
						|
        // store modifications
 | 
						|
        $this->commitMods();
 | 
						|
 | 
						|
        return ["n" => count($this->tasks), "encoding" => $encoding];
 | 
						|
    }
 | 
						|
 | 
						|
    private static function getTableVersion(string &$cA1): string
 | 
						|
    {
 | 
						|
        if (str_starts_with($cA1, "#:V")) {
 | 
						|
            return trim(substr($cA1, 3));
 | 
						|
        } else {
 | 
						|
            return "1";
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private function obfuscateAttachedImage(string $old_img_name): string
 | 
						|
    {
 | 
						|
        $ext = pathinfo($old_img_name, PATHINFO_EXTENSION);
 | 
						|
        $ext = ($ext !== "") ? ("." . $ext) : $ext;
 | 
						|
        $new_img_name = uniqid("img_", true) . $ext;
 | 
						|
 | 
						|
        // rename the actual file
 | 
						|
        $old_img_path = $this->getGameDir() . DIRECTORY_SEPARATOR . $old_img_name;
 | 
						|
        $new_img_path = $this->getGameDir() . DIRECTORY_SEPARATOR . $new_img_name;
 | 
						|
        rename($old_img_path, $new_img_path);
 | 
						|
 | 
						|
        return $new_img_name;
 | 
						|
    }
 | 
						|
 | 
						|
    private function importTasksFromTableV1(array &$table): array
 | 
						|
    {
 | 
						|
        $n = count($table);
 | 
						|
        for ($i = 1; $i < $n; $i++) {
 | 
						|
            $row = &$table[$i]; // fetch row
 | 
						|
            $a = [ // create initializing array
 | 
						|
                "question" => trim($row[0]),
 | 
						|
                "image_data" => trim($row[1]),
 | 
						|
                "correct_answer" => 0,
 | 
						|
                "answers" => array_filter(array_slice($row, 2), function ($v) {
 | 
						|
                    return trim($v ?? "") !== "";
 | 
						|
                })
 | 
						|
            ];
 | 
						|
 | 
						|
            // obfuscate image filename
 | 
						|
            if ($a["image_data"] !== "") {
 | 
						|
                $a["image_data"] = $this->obfuscateAttachedImage($a["image_data"]);
 | 
						|
                $a["image_type"] = "url";
 | 
						|
            }
 | 
						|
 | 
						|
            // create the task
 | 
						|
            $this->tasks[] = new SingleChoiceTask($a);
 | 
						|
        }
 | 
						|
 | 
						|
        return ["n" => $n, "encoding" => "automatikusan konvertált"];
 | 
						|
    }
 | 
						|
 | 
						|
    private static function getTableColumnIndices(array &$header): array
 | 
						|
    {
 | 
						|
        $columns = [];
 | 
						|
        for ($i = 1; $i < count($header); $i++) { // skip the first column as it is metadata
 | 
						|
            $label = $header[$i];
 | 
						|
            if (($label ?? "") !== "") {
 | 
						|
                $columns[$label] = $i;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return $columns;
 | 
						|
    }
 | 
						|
 | 
						|
    private static function getFirstUnlabeledColumn(array &$header): int
 | 
						|
    {
 | 
						|
        for ($i = 0; $i < count($header); $i++) {
 | 
						|
            if (trim($header[$i] ?? "") === "") {
 | 
						|
                return $i;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
 | 
						|
    private static function explodeFlags(string $fs): array
 | 
						|
    {
 | 
						|
        $flags = explode(",", trim($fs));
 | 
						|
        return array_filter($flags, fn($v) => trim($v) !== "");
 | 
						|
    }
 | 
						|
 | 
						|
    private function importTasksFromTableV2(array &$table): array
 | 
						|
    {
 | 
						|
        $result = ["n" => 0, "encoding" => "automatikusan konvertált"]; // prepare result
 | 
						|
        $n = count($table); // get number of entries (including header)
 | 
						|
        if ($n === 0) { // cannot import an empty table
 | 
						|
            return $result;
 | 
						|
        }
 | 
						|
        $header = &$table[0]; // extract header
 | 
						|
 | 
						|
        $fuc = Game::getFirstUnlabeledColumn($header); // get first unlabeled column
 | 
						|
        if ($fuc === -1) { // if there's no data, then it is impossible to create the tasks
 | 
						|
            return $result;
 | 
						|
        }
 | 
						|
 | 
						|
        $columns = Game::getTableColumnIndices($header); // fetch column names
 | 
						|
 | 
						|
        // start iterating over tasks
 | 
						|
        $count = 0;
 | 
						|
        for ($i = 1; $i < $n; $i++) {
 | 
						|
            $row = &$table[$i]; // fetch row
 | 
						|
 | 
						|
            $flags = Game::explodeFlags($row[0] ?? ""); // get flags
 | 
						|
            if (in_array("hidden", $flags)) { // skip hidden tasks
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            // count in this task
 | 
						|
            $count++;
 | 
						|
 | 
						|
            // prepare a function that looks up the fields referenced by their labels
 | 
						|
            $select_fn = function (array $cols) use (&$row, &$columns) {
 | 
						|
                for ($i = 0; $i < count($cols); $i++) {
 | 
						|
                    if (isset($columns[$cols[$i]])) {
 | 
						|
                        return trim($row[$columns[$cols[$i]]]);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                return "";
 | 
						|
            };
 | 
						|
 | 
						|
            // prepare a function that extracts all unlabeled fields
 | 
						|
            $extract_unlabeled_fn = fn() => array_filter(array_slice($row, $fuc), function ($v) {
 | 
						|
                return trim($v ?? "") !== "";
 | 
						|
            });
 | 
						|
 | 
						|
            // fetch generic fields
 | 
						|
            $a = [
 | 
						|
                "flags" => $flags,
 | 
						|
                "type" => strtolower($select_fn(["Típus", "Type"])),
 | 
						|
                "image_data" => $select_fn(["Kép", "Image"]),
 | 
						|
                "question" => $select_fn(["Kérdés", "Question"]),
 | 
						|
                "lua_script" => $select_fn(["Lua"]),
 | 
						|
                "lua_params" => Utils::str2kv($select_fn(["LuaParam"])),
 | 
						|
            ];
 | 
						|
 | 
						|
            // convert into
 | 
						|
            switch ($a["type"]) {
 | 
						|
                case "singlechoice":
 | 
						|
                    $a["answers"] = $extract_unlabeled_fn();
 | 
						|
                    $a["correct_answer"] = 0;
 | 
						|
                    break;
 | 
						|
                case "openended":
 | 
						|
                    $a["correct_answer"] = $extract_unlabeled_fn();
 | 
						|
                    break;
 | 
						|
                case "numberconversion":
 | 
						|
                    $a["instruction"] = $row[$fuc];
 | 
						|
                    break;
 | 
						|
                case "truthtable":
 | 
						|
                case "logicfunction":
 | 
						|
                    $a["input_variables"] = Utils::str2a($row[$fuc]);
 | 
						|
                    $a["output_variable"] = $row[$fuc + 1];
 | 
						|
                    $a["expression"] = $row[$fuc + 2];
 | 
						|
                    break;
 | 
						|
                case "verilog":
 | 
						|
                    $a["test_bench_fn"] = $row[$fuc];
 | 
						|
                    $a["correct_answer"] = file_get_contents($this->getAccompanyingFilePath($row[$fuc + 1]));
 | 
						|
                    if (isset($row[$fuc + 2])) {
 | 
						|
                        $a["player_answer"] = file_get_contents($this->getAccompanyingFilePath($row[$fuc + 2]));
 | 
						|
                    } else {
 | 
						|
                        $a["player_answer"] = "";
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
 | 
						|
            // obfuscate image filename
 | 
						|
            if ($a["image_data"] !== "") {
 | 
						|
                if (in_array("codeimage", $flags)) {
 | 
						|
                    $a["image_type"] = "code";
 | 
						|
                } else {
 | 
						|
                    $a["image_data"] = $this->obfuscateAttachedImage($a["image_data"]);
 | 
						|
                    $a["image_type"] = "url";
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // generate the task
 | 
						|
            $this->tasks[] = TaskFactory::fromArray($a, $this);
 | 
						|
 | 
						|
            // assign scoring strategy
 | 
						|
            $sct = $select_fn(["Pontozás", "Scoring"]);
 | 
						|
            if ($sct !== "") {
 | 
						|
                $sct_fields = Utils::str2kv($sct);
 | 
						|
                foreach ($sct_fields as $key => $value) {
 | 
						|
                    $sct_fields[$key] = $value;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $result["n"] = $count;
 | 
						|
        return $result;
 | 
						|
    }
 | 
						|
 | 
						|
    public function importTasksFromTable(array &$table): array
 | 
						|
    {
 | 
						|
        // clear tasks
 | 
						|
        $this->tasks = [];
 | 
						|
 | 
						|
        // get table version
 | 
						|
        $vs = Game::getTableVersion($table[0][0]);
 | 
						|
 | 
						|
        // continue processing based on table version
 | 
						|
        $result = ["n" => 0, "encoding" => "ismeretlen"];
 | 
						|
        switch ($vs) {
 | 
						|
            case "1":
 | 
						|
                $result = $this->importTasksFromTableV1($table);
 | 
						|
                break;
 | 
						|
            case "2":
 | 
						|
                $result = $this->importTasksFromTableV2($table);
 | 
						|
                break;
 | 
						|
        }
 | 
						|
 | 
						|
        // if the number of imported tasks is not zero, then it was a successful import
 | 
						|
        $this->gameFileIsPresent = false; // assume no game file present
 | 
						|
        if ($result["n"] > 0) {
 | 
						|
            $this->saveTasks();             // save tasks
 | 
						|
            $this->gameFileIsPresent = true;             // update game with game file present
 | 
						|
            $this->commitMods(); // store modifications
 | 
						|
        }
 | 
						|
 | 
						|
        return $result;
 | 
						|
    }
 | 
						|
 | 
						|
    // ---------
 | 
						|
 | 
						|
    public function getName(): string
 | 
						|
    {
 | 
						|
        return $this->name;
 | 
						|
    }
 | 
						|
 | 
						|
    public function setName(string $name): void
 | 
						|
    {
 | 
						|
        $this->name = $name;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getOwner(): string
 | 
						|
    {
 | 
						|
        return $this->owner;
 | 
						|
    }
 | 
						|
 | 
						|
    public function setOwner(string $owner): void
 | 
						|
    {
 | 
						|
        $this->owner = $owner;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getContributors(): array
 | 
						|
    {
 | 
						|
        return $this->contributors;
 | 
						|
    }
 | 
						|
 | 
						|
    public function setContributors(array $contributors): void
 | 
						|
    {
 | 
						|
        $this->contributors = $contributors;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getDescription(): string
 | 
						|
    {
 | 
						|
        return $this->description;
 | 
						|
    }
 | 
						|
 | 
						|
    public function setDescription(string $description): void
 | 
						|
    {
 | 
						|
        $this->description = $description;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getId(): int
 | 
						|
    {
 | 
						|
        return $this->id;
 | 
						|
    }
 | 
						|
 | 
						|
    public function isGameFileIsPresent(): bool
 | 
						|
    {
 | 
						|
        return $this->gameFileIsPresent;
 | 
						|
    }
 | 
						|
 | 
						|
    function setProperties(array $properties): void
 | 
						|
    {
 | 
						|
        $this->properties = $properties;
 | 
						|
        $this->commitMods();
 | 
						|
    }
 | 
						|
 | 
						|
    public function& getProperties(): array
 | 
						|
    {
 | 
						|
        return $this->properties;
 | 
						|
    }
 | 
						|
 | 
						|
    public function isPublic(): bool
 | 
						|
    {
 | 
						|
        return $this->public;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getPublicId(): string
 | 
						|
    {
 | 
						|
        return $this->publicId;
 | 
						|
    }
 | 
						|
 | 
						|
    public function setPublic(bool $public): void
 | 
						|
    {
 | 
						|
        $this->public = $public;
 | 
						|
        $this->commitMods();
 | 
						|
    }
 | 
						|
 | 
						|
    public function getTasks(): array
 | 
						|
    {
 | 
						|
        $this->loadTasks();
 | 
						|
        return $this->tasks;
 | 
						|
    }
 | 
						|
} |