Coder Social home page Coder Social logo

laravel-quiz's Introduction

Laravel Quiz

Laravel Quiz

Latest Version on Packagist Total Downloads GitHub Actions

With this package, you can easily get quiz functionality into your Laravel project.

Features

  • Add Topics to Questions, Quizzes and to other Topics
  • Supported Question Types: Multiple Choice, Single Choice, and Fill In The Blank
  • Add your own Question Types and define your own methods to handle them
  • Flexible Negative Marking Settings
  • Flexible Quiz with most of the useful settings (Ex: Total marks, Pass marks, Negative Marking, Duration, Valid between date, Description etc)
  • Any type of User of your application can be a Participant of a Quiz
  • Any type of User, and any number of Users of your application can be Authors (different roles) For a Quiz
  • Generate Random Quizzes (In progress)

Installation

You can install the package via Composer:

composer require harishdurga/laravel-quiz
  • Laravel Version: 9.X
  • PHP Version: 8.X

Usage

Class Diagram

LaravelQuiz

Publish Vendor Files (config, migrations, seeder)

php artisan vendor:publish --provider="Harishdurga\LaravelQuiz\LaravelQuizServiceProvider"

If you are updating the package, you may need to run the above command to publish the vendor files. But please take a backup of the config file. Also, run the migration command to add new columns to the existing tables.

Create Topic

$computer_science = Topic::create([
    'name' => 'Computer Science',
    'slug' => 'computer-science',
]);

Create Sub Topics

$algorithms = Topic::create([
    'name' => 'Algorithms',
    'slug' => 'algorithms'
]);
$computer_science->children()->save($algorithms);

Question Types

A seeder class QuestionTypeSeeder will be published into the database/seeders folder. Run the following command to seed question types.

php artisan db:seed --class=\\Harishdurga\\LaravelQuiz\\Database\\Seeders\\QuestionTypeSeeder

Currently, this package is configured to only handle the following types of questions

  • multiple_choice_single_answer
  • multiple_choice_multiple_answer
  • fill_the_blank

Create a QuestionType:

QuestionType::create(['name'=>'select_all']);

User-Defined Methods To Evaluate The Answer For Each Question Type

Though this package provides three question types you can easily change the method that is used to evaluate the answer. You can do this by updating the get_score_for_question_type property in config file.

'get_score_for_question_type' => [
    1 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::get_score_for_type_1_question',
    2 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::get_score_for_type_2_question',
    3 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::get_score_for_type_3_question',
    4 => 'Your custom method'
]

But your method needs to have the following signature

/**
 * @param QuizAttemptAnswer[] $quizQuestionAnswers All the answers of the quiz question
 */
public static function get_score_for_type_3_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array $quizQuestionAnswers, $data = null): float
{
    // Your logic here
}

If you need to pass any data to your method then you can pass it as the last $data parameter. When you call the calculate_score() method of QuizAttempt then you can pass the data as the parameter.

Create Question

$question_one = Question::create([
    'name' => 'What is an algorithm?',
    'question_type_id' => 1,
    'is_active' => true,
    'media_url' => 'url',
    'media_type' => 'image'
]);

Fetch Questions Of A Question Type

$question_type->questions

Fetch only questions with an option (valid question)

Question::hasOptions()->get()

Attach Topics To Question

$question->topics()->attach([$computer_science->id, $algorithms->id]);

Question Option

$question_two_option_one = QuestionOption::create([
            'question_id' => $question_two->id,
            'name' => 'array',
            'is_correct' => true,
            'media_type'=>'image',
            'media_url'=>'media url'
        ]);

Fetch Options Of A Question

$question->options

Create Quiz

$quiz = Quiz::create([
    'name' => 'Computer Science Quiz',
    'description' => 'Test your knowledge of computer science',
    'slug' => 'computer-science-quiz',
    'time_between_attempts' => 0, //Time in seconds between each attempt
    'total_marks' => 10,
    'pass_marks' => 6,
    'max_attempts' => 1,
    'is_published' => 1,
    'valid_from' => now(),
    'valid_upto' => now()->addDay(5),
    'media_url'=>'',
    'media_type'=>'',
    'negative_marking_settings'=>[
        'enable_negative_marks' => true,
        'negative_marking_type' => 'fixed',
        'negative_mark_value' => 0,
    ]
]);

Attach Topics To A Quiz

$quiz->topics()->attach([$topic_one->id, $topic_two->id]);

Topicable

Topics can be attached to a quiz or a question. Questions can exist outside of the quiz context. For example, you can create a question bank which you can filter based on the topics if attached.

Negative Marking Settings

By default negative marking is enabled for backward compatibility. You can disable it by setting the enable_negative_marks to false. Two types of negative marking are supported(negative_marking_type). fixed and percentage. Negative marking value defined at question level will be given precedence over the value defined at quiz level. If you want to set the negative marking value at quiz level, set the negative_mark_value to the value you want to set. If you want to set the negative marking value at question level, set the negative_marks of QuizQuestion to your desired value. No need to give a negative number instead the negative marks or percentage should be given in positive.

Adding An Author(s) To A Quiz

$admin = Author::create(
            ['name' => "John Doe"]
        );
$quiz = Quiz::factory()->make()->create([
            'name' => 'Sample Quiz',
            'slug' => 'sample-quiz'
        ]);
QuizAuthor::create([
            'quiz_id' => $quiz->id,
            'author_id' => $admin->id,
            'author_type' => get_class($admin),
            'author_role' => 'admin',
        ]);
$quiz->quizAuthors->first()->author; //Original User

Add CanAuthorQuiz trait to your model and you can get all the quizzes associated by calling the quizzes relation. You can give any author role you want and implement ACL as per your use case.

Add Question To Quiz

$quiz_question =  QuizQuestion::create([
    'quiz_id' => $quiz->id,
    'question_id' => $question->id,
    'marks' => 3,
    'order' => 1,
    'negative_marks'=>1,
    'is_optional'=>false
]);

Fetch Quiz Questions

$quiz->questions

Attempt The Quiz

$quiz_attempt = QuizAttempt::create([
    'quiz_id' => $quiz->id,
    'participant_id' => $participant->id,
    'participant_type' => get_class($participant)
]);

Get the Quiz Attempt Participant

MorphTo relation.

$quiz_attempt->participant

Answer Quiz Attempt

QuizAttemptAnswer::create(
    [
        'quiz_attempt_id' => $quiz_attempt->id,
        'quiz_question_id' => $quiz_question->id,
        'question_option_id' => $question_option->id,
    ]
);

A QuizAttemptAnswer belongs to QuizAttempt,QuizQuestion and QuestionOption

Get Quiz Attempt Score

$quiz_attempt->calculate_score()

In case of no answer found for a quiz question which is not optional, a negative score will be applied if any.

Get Correct Option Of A Question

$question->correct_options

Return a collection of QuestionOption.

public function correct_options(): Collection
{
    return $this->options()->where('is_correct', 1)->get();
}

Please refer to unit and features tests for more understanding.

Validate A Quiz Question

Instead of getting total score for the quiz attempt, you can use QuizAttempt model's validate() method. This method will return an array with a QuizQuestion model's id as the key for the assoc array that will be returned. Example:

$quizAttempt->validate($quizQuestion->id); //For a particular question
$quizAttempt->validate(); //For all the questions in the quiz attempt
$quizAttempt->validate($quizQuestion->id,$data); //$data can any type
[
  1 => [
    'score' => 10,
    'is_correct' => true,
    'correct_answer' => ['One','Five','Seven'],
    'user_answer' => ['Five','One','Seven']
  ],
  2 => [
    'score' => 0,
    'is_correct' => false,
    'correct_answer' => 'Hello There',
    'user_answer' => 'Hello World'
  ]
]

To be able to render the user answer and correct answer for different types of question types other than the 3 types supported by the package, a new config option has been added.

'render_answers_responses'    => [
        1  => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType1Answers',
        2  => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType2Answers',
        3  => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType3Answers',
    ]

By keeping the question type id as the key, you can put the path to your custom function to handle the question type. This custom method will be called from inside the validate() method by passing the QuizQuestion object as the argument for your custom method as defined in the config. Example:

public static function renderQuestionType1Answers(QuizQuestion $quizQuestion,QuizAttempt $quizAttempt,mixed $data=null)
    {
        /**
         * @var Question $actualQuestion
         */
        $actualQuestion = $quizQuestion->question;
        $answers = $quizQuestion->answers->where('quiz_attempt_id', $quizAttempt->id);
        $questionOptions = $actualQuestion->options;
        $correctAnswer = $actualQuestion->correct_options()->first()?->option;
        $givenAnswer = $answers->first()?->question_option_id;
        foreach ($questionOptions as $questionOption) {
            if ($questionOption->id == $givenAnswer) {
                $givenAnswer = $questionOption->option;
                break;
            }
        }
        return [$correctAnswer, $givenAnswer];
    }

As shown in the example your customer method should return an array with two elements the first one being the correct answer and the second element being the user's answer for the question. And whatever the $data you send to the validate() will be sent to these custom methods so that you can send additional data for rendering the answers.

Testing

composer test

Changelog

Please see CHANGELOG for more information what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security-related issues, please email [email protected] instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Laravel Package Boilerplate

This package was generated using the Laravel Package Boilerplate.

laravel-quiz's People

Contributors

ades4827 avatar eskayamadeus avatar harishdurga avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

laravel-quiz's Issues

Undefined array key 0

Hi @harishdurga,

I checked your newest release it works fine with the first attempt. In the second I receive the following error: "Undefined array key 0".

Bildschirm­foto 2023-02-10 um 11 14 44

All the best
Alex

ERM model

I checked the ERM and I found some differences:
173225090-8dc96205-1a08-4ed2-8954-1a53bccc7359

Completely missing seems to be the "QuestionTopic" since I did't find how to connect a "Topic" and a "Question". There is also no migration existing for that. What is the need of "Topic"?

Another question I have is the table "topicables" which is not part or the ERM and seems not be used.

Extended validate function

In addition to the ticket #28 it would be good to have the id's of the given and the correct/wrong answers.

What I would like to archive is to show the user something like that:
Bildschirmfoto 2023-10-19 um 20 30 21
Source: https://www.daypo.com/c-cpi-14.html#test

I am using this function:
$this->quiz_attempt->validate($quiz_question_id)[$quiz_question_id]

But that function does not provide me enough information to be able to show it in the way above.

Multiple choice:
Bildschirmfoto 2023-10-19 um 20 22 50

Single choice:
Bildschirmfoto 2023-10-19 um 20 22 18

$question->options returns null.

When I accessed the options values based on the question model, it returned null.

Here is the code snippet that accesses the options

`public function showQuizQuestions($quizId)
{
try {
$quiz = Quiz::findOrFail($quizId);
// Fetch questions associated with the quiz
$questions = $quiz->questions;
$questionsList = [];

        foreach ($questions as $question) {
            // Get the question details
            $questionDetails = Question::findOrFail($question->question_id);
            // Get the options for the question
            $options = $questionDetails->options; 

            // Build the question metadata array
            $questionMetadata = [
                'question_metadata' => $question,
                'question_text' => $questionDetails,
                'options' => $options,
            ];

            // Add the question metadata to the questions list
            $questionsList[] = $questionMetadata;
        }

        // Return the quiz details
        return response()->json(['message' => 'Quiz found with attached questions', 'data' => $questionsList], 200);

    } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
        // Return an error response if the quiz does not exist
        return response()->json(['error' => 'No topic quiz not found.'], 404);
    }
}`

As a workaround, I had to use:
$options = QuestionOption::where('question_id', $questionDetails->id)->get();

Topicable namespace

The namespace for the Topicable model is currently 'namespace App\Models;but should benamespace Harishdurga\LaravelQuiz\Models;` like the others in the package so it could be accessed elsewhere.

For example, you cannot run Topicable::truncate();

QuestionTypeSeeder error

Hi,

I found that the migration for the QuestionTypeSeeder throws an error.
Bildschirmfoto 2022-07-22 um 14 46 32

From my knowledge the create() supports only one record. The insert() supports many records but does not automatically update the timestamps. One option would be:

$questionTypes = [
            [
                'name' => 'multiple_choice_single_answer',
            ],
            [
                'name' => 'multiple_choice_multiple_answer',
            ],
            [
                'name' => 'fill_the_blank',
            ]
        ];

        foreach ($questionTypes as $questionType) {
            QuestionType::create($questionType);
        };

All the best
Alex

laravel 10 illuminate/support error

i dont know much but when i try to install i get this below error

Your requirements could not be resolved to an installable set of packages.

Problem 1
- Root composer.json requires harishdurga/laravel-quiz ^1.2 -> satisfiable by harishdurga/laravel-quiz[v1.2.0, v1.2.1].
- harishdurga/laravel-quiz[v1.2.0, ..., v1.2.1] require illuminate/support ^9.0 -> found illuminate/support[v9.0.0, ..., v9.52.5] but these were not loaded, likely because it conflicts with another require.

You can also try re-running composer require with an explicit version constraint, e.g. "composer require harishdurga/laravel-quiz:*" to figure out if any version is installable, or "composer require harishdurga/laravel-quiz:^2.1" if you know which you need.

Installation failed, reverting ./composer.json and ./composer.lock to their original content.

Adding Created By Information To Different Models

Adding created by information to models like Topic, Quiz, Question, QuizQuestion, etc. so that when there are multiple users creating content we can know who's the author or creator for a given Topic, Question, Quiz, etc. and implement rules and features in the actual application.

Topic slug unique with soft delete

I think the "string('slug')->unique()" for the model Topic is misleading in combination wit soft delete. If a topic "Test" is created and deleted it can never be added anymore. In my opinion the id should be the unique key and the validation should handle it.

Create Validation:
'slug' => 'string|required|unique:topics,slug,NULL,id,deleted_at,NULL'

Update Validation:
'slug' => 'string|required|unique:topics,slug,' . $topic->id . ',id,deleted_at,NULL',

What do you think?

How topicables relate to Questions

When topics are attached to a question, the topic_ids are created in the topicables table.

However, I do not see any relation between the topics and the questions.

How is topicables table related to the questions table?

Quiz question soft delete

I found that in the quiz_questions a unique constraint is set on quiz_id and question_id.

2021_05_22_053359_create_quizzes_table.php

// Quiz Questions Table
Schema::create($this->tableNames['quiz_questions'], function (Blueprint $table) {
    $table->id();
    $table->foreignId('quiz_id')->nullable()->constrained($this->tableNames['quizzes'])->cascadeOnDelete();
    $table->foreignId('question_id')->nullable()->constrained($this->tableNames['questions'])->cascadeOnDelete();
    $table->unsignedFloat('marks')->default(0); //0 means no marks
    $table->unsignedFloat('negative_marks')->default(0); //0 means no negative marks in case of wrong answer
    $table->boolean('is_optional')->default(false); //0 means not optional, 1 means optional
    $table->unsignedInteger('order')->default(0);
    $table->timestamps();
    $table->softDeletes();
    $table->unique(['quiz_id', 'question_id']);
});

When a question is deleted by mistake it cannot be added anymore. I guess it would make sense to remove the unique constraint and handle this also with the validation. What do you think?

QuizAttempt check if question is answered correctly

Is it currently not possible to check after a question is answered if it's correct, right?

The leering effect is a lot better if the user knows on which question they made a mistake, instead of just telling them the points they made at the end.

I built myself a validation which seems to work (for single/multiple choice) but I guess there would a better way. Since the model already has the validation, could it provide a function to validate it?

Something like:
$quizAttempt->validate($quiz_question->id)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.