questlab/questtypes/submit/SubmitQuesttypeModel.inc

464 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* The Legend of Z
*
* @author Oliver Hanraths <oliver.hanraths@uni-duesseldorf.de>
* @copyright 2014 Heinrich-Heine-Universität Düsseldorf
* @license http://www.gnu.org/licenses/gpl.html
* @link https://bitbucket.org/coderkun/the-legend-of-z
*/
namespace hhu\z\questtypes;
/**
* Model of the SubmitQuesttypeAgent for a submit task.
*
* @author Oliver Hanraths <oliver.hanraths@uni-duesseldorf.de>
*/
class SubmitQuesttypeModel extends \hhu\z\models\QuesttypeModel
{
/**
* Minimum similarity value for two submissions
*
* @var float
*/
const SIMILARITY_MIN = 0.8;
/**
* Supported mimetypes
*
* @var array
*/
const mimetypes = array('application/pdf');
/**
* Required models
*
* @var array
*/
public $models = array('uploads');
/**
* Copy a Quest
*
* @param int $userId ID of creating user
* @param int $sourceQuestId ID of Quest to copy from
* @param int $targetQuestId ID of Quest to copy to
* @param int $seminaryMediaIds Mapping of SeminaryMedia-IDs from source Seminary to targetSeminary
*/
public function copyQuest($userId, $sourceQuestId, $targetQuestId, $seminaryMediaIds)
{
}
/**
* Delete a Quest.
*
* @param int $questId ID of Quest to delete
*/
public function deleteQuest($questId)
{
$this->db->query('DELETE FROM questtypes_submit_characters WHERE quest_id = ?', 'i', $questId);
}
/**
* Save Characters submitted upload.
*
* @param int $seminaryId ID of Seminary
* @param int $questId ID of Quest
* @param int $userId ID of user
* @param int $characterId ID of Character
* @param array $file Submitted upload
* @param string $filename Name of submitted file
*/
public function setCharacterSubmission($seminaryId, $questId, $userId, $characterId, $file, $filename)
{
// Save file on harddrive
$uploadId = $this->Uploads->uploadSeminaryFile($userId, $seminaryId, $file['name'], $filename, $file['tmp_name'], $file['type']);
if($uploadId === false) {
return false;
}
// Create database record
$this->db->query(
'INSERT INTO questtypes_submit_characters '.
'(quest_id, character_id, upload_id) '.
'VALUES '.
'(?, ?, ?) ',
'iii',
$questId, $characterId, $uploadId
);
// Index submission for similarity calculation
$this->addDocument(
$this->db->getInsertId(),
ROOT.DS.\nre\configs\AppConfig::$dirs['seminaryuploads'].DS.$filename
);
return true;
}
/**
* Add a comment for the answer of a Character.
*
* @param int $userId ID of user that comments
* @param int $submissionId ID of Character answer to comment
* @param string $comment Comment text
*/
public function addCommentForCharacterAnswer($userId, $submissionId, $comment)
{
$this->db->query(
'INSERT INTO questtypes_submit_characters_comments '.
'(created_user_id, questtypes_submit_character_id, comment) '.
'VALUES '.
'(?, ?, ?)',
'iis',
$userId,
$submissionId,
$comment
);
}
/**
* Get all uploads submitted by Character.
*
* @param int $questId ID of Quest
* @param int $characterId ID of Character
* @return array Text submitted by Character or NULL
*/
public function getCharacterSubmissions($questId, $characterId)
{
return $this->db->query(
'SELECT id, created, upload_id '.
'FROM questtypes_submit_characters '.
'WHERE quest_id = ? AND character_id = ? '.
'ORDER BY created ASC',
'ii',
$questId, $characterId
);
}
/**
* Get allowed mimetypes for uploading a file.
*
* @param int $seminaryId ID of Seminary
* @return array Allowed mimetypes
*/
public function getAllowedMimetypes($seminaryId)
{
$mimetypes = array();
foreach(\nre\configs\AppConfig::$mimetypes['questtypes'] as $mimetype) {
if(in_array($mimetype['mimetype'], self::mimetypes)) {
$mimetypes[] = $mimetype;
}
}
return $mimetypes;
}
/**
* Get all comments for a Character submission.
*
* @param int $characterSubmissionId ID of Character submission
* @return array Comments for this submission
*/
public function getCharacterSubmissionComments($characterSubmissionId)
{
return $this->db->query(
'SELECT id, created, created_user_id, comment '.
'FROM questtypes_submit_characters_comments '.
'WHERE questtypes_submit_character_id = ?',
'i',
$characterSubmissionId
);
}
/**
* Get similar submissions for a Character submission.
*
* @param int $seminaryId ID of Seminary
* @param int $questId ID of Quest
* @param int $characterId ID of Character
* @param int $submissionId ID of submission
* @return array List of submissions
*/
public function getSimilarSubmissions($seminaryId, $questId, $characterId, $submissionId)
{
// List of submissions with high similarity
$similarSubmissions = array();
// Get IDFs
$idf_N = $this->getIDF_total($seminaryId);
$idf_n = $this->getIDF_docs($seminaryId);
// Get stored TFs of submission
$tfsA = $this->getTFs($submissionId);
// Get submissions of same task
$submissions = $this->getSubmissionsForQuest(
$questId,
$characterId,
$submissionId
);
// Iterate through submissions of same task
foreach($submissions as &$submission)
{
// Check if similarity has already be calculated
if(is_null($submission['similarity']))
{
// Get stored TFs of submissions to compare to
$tfsB = $this->getTFs($submission['id']);
// Calculate similarity
$submission['similarity'] = \hhu\z\lib\Similarity::compare(
$tfsA,
$tfsB,
$idf_N,
$idf_n
);
// Save similarity
$this->setSimilarity(
$submissionId,
$submission['id'],
$submission['similarity']
);
}
// Add high simnilarities to list
if($submission['similarity'] >= self::SIMILARITY_MIN) {
$similarSubmissions[] = $submission;
}
}
return $similarSubmissions;
}
/**
* Index a submission as document.
*
* @param int $submissionId ID of submission
* @param string $filename Full file path of document to read
*/
private function addDocument($submissionId, $filename)
{
// Read document
$document = \hhu\z\lib\Similarity::readDocument($filename);
if($document === false) {
return false;
}
// Split document into terms
$terms = \hhu\z\lib\Similarity::splitNgrams($document);
// Update global values
$this->addTerms($submissionId, $terms);
}
/**
* Add terms to the corpus, stored in database.
*
* @param int $submissionId ID of submission
* @param array $terms List of (non-unique) terms
*/
private function addTerms($submissionId, $terms)
{
// Calculate IDF: n (n_term)
$uniqueTerms = array();
foreach($terms as &$term)
{
if(!in_array($term, $uniqueTerms))
{
// Add term to database
$this->db->query(
'INSERT IGNORE INTO questtypes_submit_terms '.
'(term) '.
'VALUES '.
'(?)',
's',
$term
);
$uniqueTerms[] = $term;
}
// Link term to submission
$this->db->query(
'INSERT INTO questtypes_submit_submissions_terms '.
'(submission_id, term_id, tf) '.
'SELECT ?, questtypes_submit_terms.id, 1 '.
'FROM questtypes_submit_terms '.
'WHERE term = ? '.
'ON DUPLICATE KEY UPDATE '.
'tf = tf + 1',
'is',
$submissionId,
$term
);
}
}
/**
* Get all submissions for a Quest including similarity values to the
* given submission, excluding the submissions of the given Character.
*
* @param int $questId ID of Quest
* @param int $characterId ID of Character to exclude submissions of
* @param int $submissionId ID of submission to get similarity values for
* @return array List of submissions
*/
private function getSubmissionsForQuest($questId, $characterId, $submissionId)
{
return $this->db->query(
'SELECT questtypes_submit_characters.id, questtypes_submit_characters.created, questtypes_submit_characters.quest_id, character_id, upload_id, questtypes_submit_similarities.similarity '.
'FROM questtypes_submit_characters '.
'LEFT JOIN questtypes_submit_similarities ON questtypes_submit_similarities.submission_id1 = ? AND questtypes_submit_similarities.submission_id2 = questtypes_submit_characters.id '.
'WHERE quest_id = ? AND character_id != ?',
'iii',
$submissionId,
$questId, $characterId
);
}
/**
* Get Term Frequency (TF) values for a submission.
*
* @param int $submissionId ID of submission
* @return array Associative array with term as key and frequency as value
*/
private function getTFs($submissionId)
{
// Read terms
$terms = $this->db->query(
'SELECT term, tf '.
'FROM questtypes_submit_submissions_terms '.
'INNER JOIN questtypes_submit_terms ON questtypes_submit_terms.id = questtypes_submit_submissions_terms.term_id '.
'WHERE submission_id = ?',
'i',
$submissionId
);
// Convert to TFs
$tfs = array();
foreach($terms as &$term) {
$tfs[$term['term']] = $term['tf'];
}
// Return TFs
return $tfs;
}
/**
* Get total count of submissions for a Seminary.
*
* @param int $seminaryId ID of Seminary
* @return int Total count of submissions
*/
private function getIDF_total($seminaryId)
{
$data = $this->db->query(
'SELECT count(questtypes_submit_characters.id) as c '.
'FROM charactertypes '.
'INNER JOIN characters ON characters.charactertype_id = charactertypes.id '.
'INNER JOIN questtypes_submit_characters ON questtypes_submit_characters.character_id = characters.id '.
'WHERE charactertypes.seminary_id = ?',
'i',
$seminaryId
);
if(!empty($data)) {
return $data[0]['c'];
}
return 0;
}
/**
* Get count of submissions each term is in for a Seminary.
*
* @param int $seminaryId ID of Seminary
* @return array Associatve array wtih terms as keys and counts as values
*/
private function getIDF_docs($seminaryId)
{
$terms = $this->db->query(
'SELECT questtypes_submit_terms.term, count(*) AS c '.
'FROM charactertypes '.
'INNER JOIN characters ON characters.charactertype_id = charactertypes.id '.
'INNER JOIN questtypes_submit_characters ON questtypes_submit_characters.character_id = characters.id '.
'INNER JOIN questtypes_submit_submissions_terms ON questtypes_submit_submissions_terms.submission_id = questtypes_submit_characters.id '.
'INNER JOIN questtypes_submit_terms ON questtypes_submit_terms.id = questtypes_submit_submissions_terms.term_id '.
'WHERE charactertypes.seminary_id = ? '.
'GROUP BY questtypes_submit_terms.term',
'i',
$seminaryId
);
$idfs = array();
foreach($terms as &$term) {
$idfs[$term['term']] = $term['c'];
}
return $idfs;
}
/**
* Save the similarity of two submissions.
*
* @param int $submissionId1 ID of submission
* @param int $submissionId2 ID of submission
* @param float $similarity Similarity of both submissions
*/
private function setSimilarity($submissionId1, $submissionId2, $similarity)
{
$this->db->query(
'INSERT INTO questtypes_submit_similarities '.
'(submission_id1, submission_id2, similarity) '.
'VALUES '.
'(?, ?, ?) '.
'ON DUPLICATE KEY UPDATE '.
'similarity = ?',
'iidd',
$submissionId1, $submissionId2, $similarity,
$similarity
);
$this->db->query(
'INSERT INTO questtypes_submit_similarities '.
'(submission_id1, submission_id2, similarity) '.
'VALUES '.
'(?, ?, ?) '.
'ON DUPLICATE KEY UPDATE '.
'similarity = ?',
'iidd',
$submissionId2, $submissionId1, $similarity,
$similarity
);
}
}
?>