closedquestion-8.x-3.x-dev/src/Controller/ClosedQuestionController.php
src/Controller/ClosedQuestionController.php
<?php
namespace Drupal\closedquestion\Controller;
use Drupal\closedquestion\Entity\ClosedQuestionInterface;
use Drupal\closedquestion\Question\CqQuestionSequence;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Class ClosedQuestionController.
*
* @package Drupal\closedquestion\Controller
*/
class ClosedQuestionController extends ControllerBase {
/**
* Database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $databaseConnection;
/**
* ClosedQuestionController constructor.
*
* @param \Drupal\Core\Database\Connection $database
* Database connection.
*/
public function __construct(Connection $database) {
$this->databaseConnection = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('database')
);
}
/**
* Displays a view with scoreboard.
*
* @param \Drupal\closedquestion\Entity\ClosedQuestionInterface $closedquestion
* A question to display scores for.
*
* @return array
* Render array.
*/
public function scoreboard(ClosedQuestionInterface $closedquestion) {
return [
'#type' => 'container',
'#cache' => [
'tags' => [
'closedquestion_scoreboard:' . $closedquestion->id(),
],
'contexts' => [
'url.path',
],
],
'heading' => [
'#type' => 'html_tag',
'#tag' => 'h2',
'#value' => t('Users that answered question %question', array('%question' => $closedquestion->label())),
],
'view' => views_embed_view('closed_question_scoreboard', 'default', $closedquestion->id()),
];
}
/**
* Generates age title for the tabs.
*
* @param \Drupal\closedquestion\Entity\ClosedQuestionInterface $closedquestion
* A closed question being viewed.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* Page title.
*/
public function tabTitle(ClosedQuestionInterface $closedquestion) {
return $closedquestion->label();
}
/**
* Displays answers list.
*
* @param \Drupal\closedquestion\Entity\ClosedQuestionInterface $closedquestion
* A question to display answers for.
* @param bool $asData
* If method should return data instead of HTML.
*
* @return array|string
* HTML | data array.
*/
public function answers(ClosedQuestionInterface $closedquestion, $asData = FALSE) {
/* summary table */
// Set the table headers.
$summaryTableHeaders = array(
array('data' => t('User'), 'field' => 'name'),
array('data' => t('Correct?'), 'field' => 'correct'),
);
$summaryTableHeadersKeys = array_keys($summaryTableHeaders[0]);
$langcode = $this->languageManager()->getCurrentLanguage()->getId();
// Get results from db.
$query = $this->databaseConnection->select('cq__field_data', 'cq');
$query->leftJoin('cq_answer', 'cqa', 'cqa.qid = cq.id');
$query->join('users_field_data', 'u', 'cqa.uid = u.uid');
$query->condition('cq.id', $closedquestion->id());
$query->condition('cq.langcode', $langcode);
$query->condition('u.langcode', $langcode);
$query->addExpression('SUM(IF(correct_past>0, 1, 0))', 'total_correct');
$query->addField('u', 'uid');
$query->addField('u', 'name');
$query->addField('cq', 'id');
$query->addField('cq', 'title');
$query->addField('cqa', 'answer__value');
$query->addField('cqa', 'correct_past');
$query->addField('cqa', 'correct');
$query->addExpression("IF(correct=1, 'yes', 'no')", 'correct');
$query->addExpression("DATE_FORMAT(FROM_UNIXTIME(cqa.changed), '%Y-%m-%d, %H:%i')", 'timestamp');
$query = $query->extend('Drupal\Core\Database\Query\TableSortExtender');
$query->groupBy('u.uid');
$query->groupBy('cqa.id');
$query->orderByHeader($summaryTableHeaders);
$result = $query->execute();
// Put db results in table.
$summaryTableData = array();
$summaryTableHeadersResultsAdded = FALSE;
while ($row = $result->fetchAssoc()) {
$summaryTableRowData = array();
$summaryTableRowData[0] = $row['name'];
$summaryTableRowData[1] = $row['correct'];
/* add answer */
$answerAsArray = unserialize($row['answer__value']);
if (is_array($answerAsArray)) {
ksort($answerAsArray);
// Sequence questions require special handling.
if ($closedquestion->question instanceof CqQuestionSequence) {
$answerValueCombined = '';
foreach ($answerAsArray as $key => $answerValue) {
if (is_numeric($key)) {
$answerValueCombined .= $answerValue['sa'];
}
}
if ($summaryTableHeadersResultsAdded === FALSE) {
$summaryTableHeaders[] = array('data' => t('Answer'), 'field' => 'answer__value');
$summaryTableHeadersResultsAdded = TRUE;
}
$summaryTableRowData[count($summaryTableHeaders) - 1] = $answerValueCombined;
}
else {
foreach ($answerAsArray as $key => $answerValue) {
$columnIndex = array_search($key, $summaryTableHeadersKeys);
if ($columnIndex === FALSE) {
$columnIndex = count($summaryTableHeaders);
$summaryTableHeaders[] = array(
'data' => t('@key', ['@key' => $key]),
'field' => 'answer__value',
);
$summaryTableHeadersKeys[] = $key;
}
$summaryTableRowData[$columnIndex] = Xss::filterAdmin($answerValue);
}
}
}
else {
if ($summaryTableHeadersResultsAdded === FALSE) {
$summaryTableHeaders[] = array('data' => t('Answer'), 'field' => 'answer__value');
$summaryTableHeadersResultsAdded = TRUE;
}
$summaryTableRowData[count($summaryTableHeaders) - 1] = $answerAsArray;
}
$summaryTableRowData[] = $row['timestamp'];
$summaryTableData[] = $summaryTableRowData;
}
$summaryTableHeaders[] = array('data' => t('Timestamp'), 'field' => 'changed');
$tableData = array(
'header' => $summaryTableHeaders,
'rows' => $summaryTableData,
);
// Theme table.
if ($asData === FALSE) {
$retval = [
'#type' => 'container',
'#cache' => [
'tags' => [
'closedquestion_answers:' . $closedquestion->id(),
],
'contexts' => [
'url.path',
],
],
'heading' => [
'#type' => 'html_tag',
'#tag' => 'h2',
'#value' => t('User answers'),
],
'results' => [
'#type' => 'table',
'#prefix' => '<div style="overflow: auto;">',
'#suffix' => '</div>',
'#header' => $summaryTableHeaders,
'#rows' => $summaryTableData,
],
'csv' => [
'#type' => 'link',
'#title' => t('Export to CSV'),
'#url' => Url::fromRoute(
'closedquestion.answers.csv',
['closedquestion' => $closedquestion->id()]
),
],
];
}
else {
$retval = $tableData;
}
return $retval;
}
/**
* Returns a CSV file with answers.
*
* @param \Drupal\closedquestion\Entity\ClosedQuestionInterface $closedquestion
* A question to generate report for.
*
* @return \Symfony\Component\HttpFoundation\Response
* Response with exported CSV file.
*/
public function answersCsv(ClosedQuestionInterface $closedquestion) {
$csvExport = '';
$csvExportAsArray = array();
$answers = $this->answers($closedquestion, TRUE);
/* convert answers data to csv string */
if (is_array($answers['header']) && is_array($answers['rows'])) {
foreach ($answers['header'] as $columnData) {
$csvExportAsArray[] = '"' . addslashes($columnData['data']) . '"';
}
$csvExport .= implode(';', $csvExportAsArray) . "\n";
foreach ($answers['rows'] as &$rowData) {
array_walk($rowData, function (&$value) {
$value = '"' . addslashes($value) . '"';
});
$csvExport .= implode(';', $rowData) . "\n";
}
}
/* output */
$filename = 'cq_' . $closedquestion->id() . '_answers_' . date('YmdHis') . '.csv';
$response = new Response();
$response->headers->add([
'Content-type' => 'text/csv',
'Content-Disposition' => "attachment; filename=$filename",
'Pragma' => 'no-cache',
'Expires' => '0',
]);
$response->setContent($csvExport);
return $response;
}
/**
* Displays user's history of answering the question.
*
* @param \Drupal\closedquestion\Entity\ClosedQuestionInterface $closedquestion
* A question to display log for.
* @param \Drupal\Core\Session\AccountInterface $user
* A user to display log for.
*
* @return array
* Render array.
*/
public function userLog(ClosedQuestionInterface $closedquestion, AccountInterface $user) {
return [
'#type' => 'container',
'#cache' => [
'tags' => [
'closedquestion_user_log:' . $closedquestion->id(),
],
'contexts' => [
'url.path',
],
],
'heading' => [
'#type' => 'html_tag',
'#tag' => 'h2',
'#value' => t('The log of how user %name answered question %question', array('%name' => $user->getAccountName(), '%question' => $closedquestion->label())),
],
'view' => views_embed_view('closed_question_user_log', 'default', $closedquestion->id(), $user->id()),
];
}
/**
* Returns text representation of a question.
*
* @param \Drupal\closedquestion\Entity\ClosedQuestionInterface $closedquestion
* Closed question entity.
*
* @return array
* Text representation of the question.
*/
public function text(ClosedQuestionInterface $closedquestion) {
$retval = $closedquestion->question->getAllText();
$retval['#cache'] = [
'tags' => [
'closedquestion_text:' . $closedquestion->id(),
],
'contexts' => [
'url.path',
],
];
return $retval;
}
/**
* Returns file as stream.
*
* @param \Drupal\file\FileInterface $file
* Id of a file to return.
*
* @return \Symfony\Component\HttpFoundation\Response|null
* File response if available.
*/
public function getFile(FileInterface $file) {
$uri = \Drupal::service('file_system')->realpath($file->getFileUri());
if (file_exists($uri)) {
$mimetype = mime_content_type($uri);
$response = new Response();
$response->headers->add([
'Content-Description' => 'File Transfer',
'Content-Type' => $mimetype != '' ? $mimetype : 'application/octet-stream',
]);
$response->setContent(file_get_contents($uri));
return $response;
}
return NULL;
}
}
