diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..1c08f96 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/php +{ + "name": "PHP", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/php:1-8.2-bullseye", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Configure tool-specific properties. + // "customizations": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [ + 8080 + ], + "customizations": { + "vscode": { + "extensions": [ + "rexshi.phpdoc-comment-vscode-plugin" + ] + } + } + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html" + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/docs/looper.png b/docs/looper_erd.png similarity index 100% rename from docs/looper.png rename to docs/looper_erd.png diff --git a/docs/looper_mcd.jpg b/docs/looper_mcd.jpg new file mode 100644 index 0000000..cda7abc Binary files /dev/null and b/docs/looper_mcd.jpg differ diff --git a/docs/looper_mld.jpg b/docs/looper_mld.jpg new file mode 100644 index 0000000..d0328c6 Binary files /dev/null and b/docs/looper_mld.jpg differ diff --git a/public/index.php b/public/index.php index 3150de9..bc2bbb3 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,11 @@ getId() . '/fields'); } + /** + * This method deletes an exercise identified by `$id`. It only allows deletion if the exercise + * is in the `Building` or `Closed` state. + * + * @param int $id The ID of the exercise to be deleted. + * @return void + */ public function deleteExercise(int $id) { $exercise = new Exercise($id); @@ -25,6 +48,12 @@ public function deleteExercise(int $id) header('Location: /exercises'); } + /** + * This method changes the state of an exercise identified by `$id`. + * + * @param int $id The ID of the exercise whose state is to be changed. + * @return void + */ public function changeStateOfExercise(int $id) { if (!isset($_GET['exercise']['status'])) { diff --git a/src/controllers/field_controller.php b/src/controllers/field_controller.php index 460ae4a..c20dd2f 100644 --- a/src/controllers/field_controller.php +++ b/src/controllers/field_controller.php @@ -1,9 +1,27 @@ getId() . '/fulfillments/' . $fulfillment->getId() . '/edit'); } + /** + * Edit a fulfillment + * This method handles the editing of an existing fulfillment, identified by `$fulfillment_id`, + * for a specific exercise, identified by `$exercise_id`. + * + * @param int $exercise_id The ID of the exercise associated with the fulfillment. + * @param int $fulfillment_id The ID of the fulfillment that is being edited. + * @return void This function updates the fulfillment and redirects, without returning a value. + */ public function editFulfillment(int $exercise_id, int $fulfillment_id) { if (!isset($_POST['fulfillment']['answers_attributes'])) { diff --git a/src/controllers/navigation.php b/src/controllers/navigation.php index 4e0709a..3f77884 100644 --- a/src/controllers/navigation.php +++ b/src/controllers/navigation.php @@ -1,25 +1,59 @@ db = new PDO('pgsql:host=' . $host . ';port=' . $port . ';dbname=' . $dbname, $postgres_user, $postgres_password); } + /** + * select (all select, returning at the end of an insert) basicly all that is returning somethings + * + * @param string $squery the Sql query (default: '') + * @param array $args (default: []) + * @return array[array] the result of the query + */ public function select(string $squery, array $args = []) { if ($args) { @@ -22,6 +34,13 @@ public function select(string $squery, array $args = []) return $statement->fetchAll(); } + /** + * modify (insert, update) basicly all that isn't returning anything + * + * @param string $squery The Sql query (default: '') + * @param array $args (default: []) + * @return void + */ public function modify(string $squery, array $args = []) { if ($args) { diff --git a/src/models/databases_connectors/postgresql/postgresql_access.php b/src/models/databases_connectors/postgresql/postgresql_access.php index b537f18..41b28dd 100644 --- a/src/models/databases_connectors/postgresql/postgresql_access.php +++ b/src/models/databases_connectors/postgresql/postgresql_access.php @@ -1,12 +1,32 @@ postgresql = new Postgresql($host, $port, $dbname, $postgres_user, $postgres_password); diff --git a/src/models/exceptions/looper_exception.php b/src/models/exceptions/looper_exception.php index 66040bf..960c24a 100644 --- a/src/models/exceptions/looper_exception.php +++ b/src/models/exceptions/looper_exception.php @@ -1,10 +1,32 @@ httpErrorMessage = $httpErrorMessage; } + /** + * Get the HTTP return code. + * + * This method returns the HTTP status code that represents the type of error encountered. + * + * @return int The HTTP error return code. + */ public function getReturnCode(): int { return $this->httpReturnCode; } + /** + * Get the HTTP error message. + * + * This method returns the HTTP error message, which can provide a more detailed explanation + * of the error that occurred, suitable for logging or debugging. + * + * @return string The HTTP error message. + */ public function getErrorMessage(): string { return $this->httpErrorMessage; diff --git a/src/models/exercise.php b/src/models/exercise.php index efb22c3..30ae0b9 100644 --- a/src/models/exercise.php +++ b/src/models/exercise.php @@ -1,9 +1,18 @@ database_access = (new DatabasesChoose())->getDatabase(); @@ -26,22 +45,43 @@ public function __construct(int $id) $this->id = $id; } - public static function create($title): self + /** + * This is a static method to create an exercise + * + * @param string $title the title of the exercise + * @return self the created exercise + */ + public static function create(string $title): self { $database_access = (new DatabasesChoose())->getDatabase(); return new self($database_access->createExercise($title)); } + /** + * Get the id of the exercise + * + * @return int the id of the exercise + */ public function getId() { return $this->id; } + /** + * Get the title of the exercise + * + * @return string the title of the exercise + */ public function getTitle() { return $this->database_access->getExerciseTitle($this->id); } + /** + * get exercise list of field in exercise + * + * @return array[Field] the list of field in exercise + */ public function getFields(): array { $array_field = []; @@ -51,6 +91,14 @@ public function getFields(): array return $array_field; } + /** + * Create a field in the exercise + * + * @param string $label the label of the field + * @param Kind $kind the kind of the field + * @throws ExerciseNotInBuildingStatus if the exercise is not in building status + * @return Field the created field + */ public function createField(string $label, Kind $kind): Field { if ($this->getStatus() != Status::Building) { @@ -59,21 +107,44 @@ public function createField(string $label, Kind $kind): Field return new Field($this->database_access->createField($this->id, $label, $kind->value)); } + /** + * Is field in exercise ? + * + * @param Field $field the field to check + * @return bool true if the field is in the exercise, false otherwise + */ public function isFieldInExercise(Field $field): bool { return $this->database_access->isFieldInExercise($this->id, $field->getId()); } + /** + * Check if a fulfillment is part of the exercise. + * + * @param Fulfillment $fulfillment The fulfillment object to check. + * @return bool Returns true if the fulfillment is part of the exercise, false otherwise. + */ public function isFulfillmentInExercise(Fulfillment $fulfillment): bool { return $this->database_access->isFulfillmentInExercise($this->id, $fulfillment->getId()); } + /** + * This method deletes the exercise + * + * @return void the deleted exercise + */ public function delete() { $this->database_access->deleteExercise($this->id); } + /** + * Get all static exercises by status (if status is null, get all exercises) + * + * @param Status $status the status of the exercise default null + * @return array[Exercise] the list of exercises by status + */ public static function getExercises(Status $status = null) { $database_access = (new DatabasesChoose())->getDatabase(); @@ -93,21 +164,43 @@ public static function getExercises(Status $status = null) return $exercises; } + /** + * get status of an exercise (Building, Answering, Closed) + * + * @return Status the status of the exercise (Building, Answering, Closed) + */ public function getStatus(): Status { return Status::from($this->database_access->getExerciseStatus($this->id)); } + /** + * Set the exercise as a status (Building, Answering, Closed) + * + * @param Status $status the status to set the exercise as (Building, Answering, Closed) + * @return void + */ public function setExerciseAs(Status $status) { $this->database_access->setExerciseStatus($this->id, $status->value); } + /** + * get number of field in exercise + * + * @return int the number of field in exercise + */ public function getFieldsCount(): int { return $this->database_access->getFieldsCount($this->id); } + /** + * create a fulfillment in exercise when the exercise is in answering status + * + * @throws ExerciseNotInAnsweringStatus if the exercise is not in answering status + * @return Fulfillment the created fulfillment + */ public function createFulfillment(): Fulfillment { if ($this->getStatus() != Status::Answering) { @@ -116,7 +209,12 @@ public function createFulfillment(): Fulfillment return new Fulfillment($this->database_access->createFulfillment($this->id)); } - public function getFulfillments() + /** + * Get a list of fulfillments in exercise (All fulfillments in exercise) + * + * @return array[Fulfillment] the list of fulfillments in exercise + */ + public function getFulfillments(): array { $fulfillments = []; foreach ($this->database_access->getFulfillments($this->id) as $field) { @@ -126,8 +224,19 @@ public function getFulfillments() } } +/** + * This exception is thrown when an exercise is not found + */ class ExerciseNotFoundException extends LooperException { + /** + * This is the constructor of the exception + * + * @param string $message the message of the exception + * @param int $code the code of the exception + * @param Exception|null $previous the previous exception + * @return void + */ public function __construct($message = 'The exercise does not exist', $code = 0, Exception $previous = null) { // Make sure everything is assigned properly @@ -135,16 +244,38 @@ public function __construct($message = 'The exercise does not exist', $code = 0, } } +/** + * This exception is thrown when an exercise is not in building status + */ class ExerciseNotInBuildingStatus extends LooperException { + /** + * This is the constructor of the exception + * + * @param string $message the message of the exception + * @param int $code the code of the exception + * @param Exception|null $previous the previous exception + * @return void + */ public function __construct($message = 'The Exercise is not in building status', $code = 0, Exception|null $previous = null) { parent::__construct(400, 'The Exercise is not in building status', $message, $code, $previous); } } +/** + * This exception is thrown when an exercise is not in answering status + */ class ExerciseNotInAnsweringStatus extends LooperException { + /** + * This is the constructor of the exception + * + * @param string $message the message of the exception + * @param int $code the code of the exception + * @param Exception|null $previous the previous exception + * @return void + */ public function __construct($message = 'The Exercise is not in answering status', $code = 0, Exception|null $previous = null) { parent::__construct(400, 'The Exercise is not in answering status', $message, $code, $previous); diff --git a/src/models/field.php b/src/models/field.php index aef72f2..a7b5e83 100644 --- a/src/models/field.php +++ b/src/models/field.php @@ -1,8 +1,17 @@ id = $id; @@ -26,16 +45,31 @@ public function __construct(int $id) } } + /** + * Returns the ID of a field. + * + * @return int The unique identifier of the object. + */ public function getId(): int { return $this->id; } + /** + * Get the label of the field + * + * @return string the label of the field + */ public function getLabel(): string { return $this->database_access->getFieldLabel($this->id); } + /** + * Get the kind of the field + * + * @return Kind the kind of the field + */ public function getKind(): Kind { switch ($this->database_access->getFieldKind($this->id)) { @@ -48,6 +82,12 @@ public function getKind(): Kind } } + /** + * Set the label of the field + * + * @param string $label the new label of the field + * @return void + */ public function setLabel(string $label): void { if ($this->getExercise()->getStatus() != Status::Building) { @@ -56,6 +96,12 @@ public function setLabel(string $label): void $this->database_access->setFieldLabel($this->id, $label); } + /** + * Set the kind of the field + * + * @param Kind $kind the new kind of the field + * @return void + */ public function setKind(Kind $kind): void { if ($this->getExercise()->getStatus() != Status::Building) { @@ -64,7 +110,12 @@ public function setKind(Kind $kind): void $this->database_access->setFieldKind($this->id, $kind->value); } - public function delete() + /** + * Delete the field + * + * @return void + */ + public function delete(): void { if ($this->getExercise()->getStatus() != Status::Building) { throw new ExerciseNotInBuildingStatus(); @@ -72,14 +123,30 @@ public function delete() $this->database_access->deleteField($this->id); } - public function getExercise() + /** + * Get the exercise of the field + * + * @return Exercise + */ + public function getExercise(): Exercise { return new Exercise($this->database_access->getExerciseByFieldId($this->id)); } } +/** + * This exception is thrown when a field is not found + */ class FieldNotFoundException extends LooperException { + /** + * The constructor of the FieldNotFoundException class + * + * @param string $message the message of the exception default 'The field does not exist' + * @param int $code the code of the exception default 0 + * @param Exception $previous the previous exception default null + * @return void + */ public function __construct($message = 'The field does not exist', $code = 0, Exception $previous = null) { parent::__construct(404, 'Field not found', $message, $code, $previous); diff --git a/src/models/fulfillment.php b/src/models/fulfillment.php index df69440..8905725 100644 --- a/src/models/fulfillment.php +++ b/src/models/fulfillment.php @@ -1,15 +1,32 @@ id = $id; @@ -21,16 +38,33 @@ public function __construct(int $id) } } + /** + * Get the id of the fulfillment + * + * @return int the id of the fulfillment + */ public function getId() { return $this->id; } + /** + * get fulfillment creation date + * + * @return int the timestamp of the creation date + */ public function getTimestamp() { return $this->database_access->getFulfillmentTimestamp($this->id); } + /** + * create fulfillment fields data + * + * @param Field $field the field to create + * @param string $body the body of the field + * @return FulfillmentField the created field + */ public function createFields(Field $field, string $body) { if ($this->getExercise()->getStatus() != Status::Answering) { @@ -42,6 +76,11 @@ public function createFields(Field $field, string $body) return new FulfillmentField($field->getId(), $this->id); } + /** + * get a fields of the fulfillment + * + * @return array[FulfillmentField $field] the fields of the fulfillment + */ public function getFields() { $fulfillment = []; @@ -53,14 +92,30 @@ public function getFields() return $fulfillment; } + /** + * get exercise of the fulfillment + * + * @return Exercise the exercise of the fulfillment + */ public function getExercise() { return new Exercise($this->database_access->getExerciseByFulfillmentId($this->id)); } } +/** + * This exception is thrown when the fulfillment does not exist + */ class FulfillmentNotFoundException extends LooperException { + /** + * constructor of the exception + * + * @param string $message the message of the exception Default: 'The fulfillment does not exist' + * @param int $code the code of the exception Default: 0 + * @param Exception $previous the previous exception Default: null + * @return void + */ public function __construct($message = 'The fulfillment does not exist', $code = 0, Exception $previous = null) { parent::__construct(404, 'fulfillment not found', $message, $code, $previous); diff --git a/src/models/fulfillment_field.php b/src/models/fulfillment_field.php index c50e879..0f9c037 100644 --- a/src/models/fulfillment_field.php +++ b/src/models/fulfillment_field.php @@ -1,11 +1,29 @@ fulfillment_id; } + /** + * Get the body of the fulfillment + * + * @return string the body of the fulfillment + */ public function getBody() { return $this->database_access->getFulfillmentBody(parent::getId(), $this->fulfillment_id); } + /** + * Set the body of the fulfillment + * + * @param string $body the body of the fulfillment + * @return void + */ public function setBody(string $body) { if ($this->getExercise()->getStatus() != Status::Answering) { diff --git a/src/models/router.php b/src/models/router.php index a0a417e..efa2279 100644 --- a/src/models/router.php +++ b/src/models/router.php @@ -1,5 +1,14 @@ controller_entry as $class => $routes) { @@ -64,6 +80,7 @@ public function run($request_method, $request_uri) private function callRoute($request_method, $request_uri, $class, $routes) { + // Iterate on all the route and check if it's the same route then launch the right controller on this route foreach ($routes[$request_method] as $route => $method) { $same_route = $this->isSameRoute($route, request_uri: $request_uri); diff --git a/src/views/create_an_exercise.php b/src/views/create_an_exercise.php index 2582529..060bb4b 100644 --- a/src/views/create_an_exercise.php +++ b/src/views/create_an_exercise.php @@ -1,4 +1,9 @@ getKind(); diff --git a/src/views/errors/error.php b/src/views/errors/error.php index 22f8463..c94e0e8 100644 --- a/src/views/errors/error.php +++ b/src/views/errors/error.php @@ -1,4 +1,10 @@ getTitle()?> for="getId()?>">getLabel()?> getBody() : '' ?> getKind()->value == 0): ?> - - diff --git a/src/views/take_an_exercise.php b/src/views/take_an_exercise.php index 32b9eeb..9b7276f 100644 --- a/src/views/take_an_exercise.php +++ b/src/views/take_an_exercise.php @@ -1,4 +1,10 @@ +