upsc_quiz-1.0.x-dev/src/Controller/AdminController.php
src/Controller/AdminController.php
<?php
namespace Drupal\upsc_quiz\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
/**
* Controller for UPSC Quiz administrative functionality.
*/
class AdminController extends ControllerBase implements ContainerInjectionInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs an AdminController object.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('database')
);
}
/**
* Manage questions page.
*/
public function manageQuestions() {
$build = [];
// Add header with action buttons
$build['header'] = [
'#type' => 'container',
'#attributes' => ['class' => ['upsc-quiz-admin-header']],
];
$build['header']['title'] = [
'#markup' => '<h2>' . $this->t('Manage Quiz Questions') . '</h2>',
];
$build['header']['actions'] = [
'#type' => 'container',
'#attributes' => ['class' => ['upsc-quiz-admin-actions']],
];
$add_url = Url::fromRoute('upsc_quiz.admin_questions_add');
$build['header']['actions']['add_question'] = Link::fromTextAndUrl(
$this->t('Add New Question'),
$add_url
)->toRenderable();
$build['header']['actions']['add_question']['#attributes'] = [
'class' => ['button', 'button--primary']
];
$import_url = Url::fromRoute('upsc_quiz.admin_import');
$build['header']['actions']['import_questions'] = Link::fromTextAndUrl(
$this->t('Import Questions'),
$import_url
)->toRenderable();
$build['header']['actions']['import_questions']['#attributes'] = [
'class' => ['button', 'button--secondary']
];
// Questions table
$build['questions_table'] = [
'#type' => 'table',
'#header' => [
$this->t('ID'),
$this->t('Section'),
$this->t('Question'),
$this->t('Difficulty'),
$this->t('Status'),
$this->t('Created'),
$this->t('Actions'),
],
'#empty' => $this->t('No questions found.'),
'#attributes' => ['class' => ['upsc-quiz-questions-table']],
];
// Get questions from database
$query = $this->database->select('upsc_quiz_questions', 'q')
->fields('q', ['id', 'section', 'question', 'difficulty', 'status', 'created'])
->orderBy('created', 'DESC')
->range(0, 50); // Limit to 50 for performance
$results = $query->execute();
foreach ($results as $row) {
$edit_url = Url::fromRoute('upsc_quiz.admin_questions_edit', ['question_id' => $row->id]);
$delete_url = Url::fromRoute('upsc_quiz.admin_questions_delete', ['question_id' => $row->id]);
$build['questions_table'][] = [
'id' => ['#markup' => $row->id],
'section' => ['#markup' => ucfirst($row->section)],
'question' => [
'#markup' => substr($row->question, 0, 100) . (strlen($row->question) > 100 ? '...' : ''),
'#attributes' => ['class' => ['upsc-quiz-question-text']],
],
'difficulty' => [
'#markup' => $this->getDifficultyLabel($row->difficulty),
'#attributes' => ['class' => ['upsc-quiz-difficulty', 'difficulty-' . $row->difficulty]],
],
'status' => [
'#markup' => $row->status ? $this->t('Published') : $this->t('Unpublished'),
'#attributes' => ['class' => ['upsc-quiz-status', $row->status ? 'status-published' : 'status-unpublished']],
],
'created' => ['#markup' => date('Y-m-d H:i', $row->created)],
'actions' => [
'#type' => 'container',
'#attributes' => ['class' => ['upsc-quiz-actions']],
'edit' => Link::fromTextAndUrl($this->t('Edit'), $edit_url)->toRenderable(),
'delete' => Link::fromTextAndUrl($this->t('Delete'), $delete_url)->toRenderable(),
],
];
}
// Attach CSS
$build['#attached']['library'][] = 'upsc_quiz/quiz_admin';
return $build;
}
/**
* Analytics dashboard page.
*/
public function analytics() {
$build = [];
// Page title
$build['title'] = [
'#markup' => '<h2>' . $this->t('UPSC Quiz Analytics') . '</h2>',
];
// Summary statistics
$build['summary'] = [
'#type' => 'container',
'#attributes' => ['class' => ['upsc-quiz-analytics-summary']],
];
// Get total questions
$total_questions = $this->database->select('upsc_quiz_questions', 'q')
->condition('status', 1)
->countQuery()
->execute()
->fetchField();
$build['summary']['total_questions'] = [
'#type' => 'container',
'#attributes' => ['class' => ['analytics-stat']],
'label' => ['#markup' => '<div class="stat-label">' . $this->t('Total Questions') . '</div>'],
'value' => ['#markup' => '<div class="stat-value">' . $total_questions . '</div>'],
];
// Get total attempts
$total_attempts = $this->database->select('upsc_quiz_attempts', 'a')
->countQuery()
->execute()
->fetchField();
$build['summary']['total_attempts'] = [
'#type' => 'container',
'#attributes' => ['class' => ['analytics-stat']],
'label' => ['#markup' => '<div class="stat-label">' . $this->t('Total Attempts') . '</div>'],
'value' => ['#markup' => '<div class="stat-value">' . $total_attempts . '</div>'],
];
// Get average score
$avg_score_query = $this->database->select('upsc_quiz_attempts', 'a')
->addExpression('AVG(score)', 'avg_score');
$avg_score = $avg_score_query->execute()->fetchField();
$avg_score = $avg_score ? round($avg_score, 1) : 0;
$build['summary']['avg_score'] = [
'#type' => 'container',
'#attributes' => ['class' => ['analytics-stat']],
'label' => ['#markup' => '<div class="stat-label">' . $this->t('Average Score') . '</div>'],
'value' => ['#markup' => '<div class="stat-value">' . $avg_score . '%</div>'],
];
// Section-wise analytics
$build['section_analytics'] = [
'#type' => 'table',
'#header' => [
$this->t('Section'),
$this->t('Questions'),
$this->t('Attempts'),
$this->t('Avg Score'),
$this->t('Success Rate'),
],
'#attributes' => ['class' => ['upsc-quiz-analytics-table']],
];
$sections = ['polity', 'history', 'geography', 'economics', 'general_science', 'current_affairs'];
foreach ($sections as $section) {
// Questions count
$questions_count = $this->database->select('upsc_quiz_questions', 'q')
->condition('section', $section)
->condition('status', 1)
->countQuery()
->execute()
->fetchField();
// Attempts count
$attempts_count = $this->database->select('upsc_quiz_attempts', 'a')
->condition('section', $section)
->countQuery()
->execute()
->fetchField();
// Average score
$section_avg_query = $this->database->select('upsc_quiz_attempts', 'a')
->condition('section', $section)
->addExpression('AVG(score)', 'avg_score');
$section_avg = $section_avg_query->execute()->fetchField();
$section_avg = $section_avg ? round($section_avg, 1) : 0;
// Success rate (attempts with score >= 60%)
$success_count = $this->database->select('upsc_quiz_attempts', 'a')
->condition('section', $section)
->condition('score', 60, '>=')
->countQuery()
->execute()
->fetchField();
->countQuery()
->execute()
->fetchField();
$success_rate = $attempts_count > 0 ? round(($success_count / $attempts_count) * 100, 1) : 0;
$build['section_analytics'][] = [
'section' => ['#markup' => ucfirst(str_replace('_', ' ', $section))],
'questions' => ['#markup' => $questions_count],
'attempts' => ['#markup' => $attempts_count],
'avg_score' => ['#markup' => $section_avg . '%'],
'success_rate' => ['#markup' => $success_rate . '%'],
];
}
// Recent attempts table
$build['recent_attempts'] = [
'#type' => 'table',
'#header' => [
$this->t('User'),
$this->t('Section'),
$this->t('Score'),
$this->t('Time Taken'),
$this->t('Completed'),
],
'#empty' => $this->t('No recent attempts found.'),
'#attributes' => ['class' => ['upsc-quiz-recent-attempts']],
];
$recent_attempts = $this->database->select('upsc_quiz_attempts', 'a')
->fields('a', ['uid', 'section', 'score', 'time_taken', 'finished'])
->orderBy('finished', 'DESC')
->range(0, 20)
->execute();
foreach ($recent_attempts as $attempt) {
$user_name = $attempt->uid ? $this->entityTypeManager()->getStorage('user')->load($attempt->uid)->getDisplayName() : $this->t('Anonymous');
$build['recent_attempts'][] = [
'user' => ['#markup' => $user_name],
'section' => ['#markup' => ucfirst($attempt->section)],
'score' => ['#markup' => $attempt->score . '%'],
'time_taken' => ['#markup' => $this->formatTime($attempt->time_taken)],
'completed' => ['#markup' => date('Y-m-d H:i', $attempt->finished)],
];
}
// Attach CSS
$build['#attached']['library'][] = 'upsc_quiz/quiz_admin';
return $build;
}
/**
* Export quiz data.
*/
public function exportData() {
$format = \Drupal::request()->query->get('format', 'csv');
if ($format === 'csv') {
return $this->exportCsv();
}
// Default to questions export
return $this->exportQuestionsCsv();
}
/**
* Export questions as CSV.
*/
private function exportQuestionsCsv() {
$questions = $this->database->select('upsc_quiz_questions', 'q')
->fields('q')
->condition('status', 1)
->orderBy('section')
->orderBy('weight')
->execute();
$csv_data = "ID,Section,Question,Option A,Option B,Option C,Option D,Correct Answer,Explanation,Difficulty,Created\n";
foreach ($questions as $question) {
$csv_data .= sprintf(
"%d,%s,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",%s,\"%s\",%d,%s\n",
$question->id,
$question->section,
$this->escapeCsv($question->question),
$this->escapeCsv($question->option_a),
$this->escapeCsv($question->option_b),
$this->escapeCsv($question->option_c),
$this->escapeCsv($question->option_d),
$question->correct_answer,
$this->escapeCsv($question->explanation),
$question->difficulty,
date('Y-m-d H:i:s', $question->created)
);
}
$response = new Response($csv_data);
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename="upsc_quiz_questions_' . date('Y-m-d') . '.csv"');
return $response;
}
/**
* Export results as CSV.
*/
private function exportCsv() {
$attempts = $this->database->select('upsc_quiz_attempts', 'a')
->fields('a')
->orderBy('finished', 'DESC')
->execute();
$csv_data = "ID,User ID,Section,Score,Total Questions,Correct Answers,Time Taken,Completed,Started,Finished\n";
foreach ($attempts as $attempt) {
$csv_data .= sprintf(
"%d,%d,%s,%d,%d,%d,%d,%d,%s,%s\n",
$attempt->id,
$attempt->uid,
$attempt->section,
$attempt->score,
$attempt->total_questions,
$attempt->correct_answers,
$attempt->time_taken,
$attempt->completed,
date('Y-m-d H:i:s', $attempt->started),
date('Y-m-d H:i:s', $attempt->finished)
);
}
$response = new Response($csv_data);
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename="upsc_quiz_results_' . date('Y-m-d') . '.csv"');
return $response;
}
/**
* Get difficulty label.
*/
private function getDifficultyLabel($level) {
$labels = [
1 => $this->t('Easy'),
2 => $this->t('Medium'),
3 => $this->t('Hard'),
4 => $this->t('Very Hard'),
5 => $this->t('Expert'),
];
return isset($labels[$level]) ? $labels[$level] : $this->t('Unknown');
}
/**
* Format time in human readable format.
*/
private function formatTime($seconds) {
$minutes = floor($seconds / 60);
$remaining_seconds = $seconds % 60;
if ($minutes > 0) {
return $this->t('@minutesm @secondss', [
'@minutes' => $minutes,
'@seconds' => $remaining_seconds
]);
}
return $this->t('@secondss', ['@seconds' => $remaining_seconds]);
}
/**
* Escape CSV data.
*/
private function escapeCsv($data) {
// Remove newlines and escape quotes
$data = str_replace(["\r\n", "\n", "\r"], ' ', $data);
$data = str_replace('"', '""', $data);
return $data;
}
}