Compare commits

...

21 Commits

Author SHA1 Message Date
Jane Adelmann faaca3e8ac
v3.14.7 Add compapability with bulk upload plugin 2024-03-05 12:56:36 +02:00
Jane Adelmann 6208bf4ff3
Add Privacy API implementation. Version 3.14.6 2023-11-20 19:50:29 +02:00
Jane Adelmann aa44efdf0b
v 3.14.5 add new option and fix quiz 2023-10-12 16:37:14 +03:00
Jane Adelmann 04a22a1d3d
Ignore letter case in email 2023-07-04 15:31:09 +03:00
Jane Adelmann 830b77e6f7
Fix for database schema 2023-06-23 16:39:20 +03:00
Jane Adelmann dd15b4983d
Add option to enable or disable ai-check 2023-06-08 12:17:51 +03:00
Jane Adelmann 8c3f615f7d
Add AI Score 2023-06-07 19:18:30 +03:00
Jane Adelmann 45634d8a3e
Add Support for 4.1, 4.2 2023-06-06 19:04:59 +03:00
Jane Adelmann a46f7043e5
Version v3.13.5 2023-06-06 11:53:05 +03:00
Llewelyn Williams 87f8af6563
Update lib.php "Support student custom roles" (#29)
Rollback 8862dc0
2023-06-06 11:44:56 +03:00
Jane Adelmann 8862dc050a
Support student custom roles v3.13.4 2023-05-01 17:09:59 +03:00
Jane Adelmann 99b58f8e27
Use long syntax for arrays, remove comma in last argument 2022-11-16 16:31:42 +02:00
Jane Adelmann 987a4b8674
Fix for backup and restore api 2022-11-07 17:05:50 +02:00
Jane Adelmann 0803af58ea
User auto registration 2022-11-04 17:56:48 +02:00
Jane Adelmann e198855544
Add case insensitive for user email 2022-11-04 17:21:10 +02:00
Jane Adelmann 28a2132625
Fix default value for activity option 2022-10-31 15:19:51 +02:00
Jane Adelmann c511214aed
increase API timeout 2022-10-31 13:00:16 +02:00
Jane Adelmann c0a0533b10
Add Backup API and Restore API implementation. 2022-10-28 14:20:12 +03:00
Jane Adelmann 74a67bce5f
Merge PR #28. Version 3.11.3 2022-10-07 11:02:23 +03:00
PlagiarismCheck fb0b80b495
Merge pull request #28 from leonstr/master
27 Fix The 'name' value must be set in other
2022-10-07 10:48:55 +03:00
Leon Stringer 1d568254bf 27 Fix The 'name' value must be set in other
With this plugin's "Enable plugin" setting set to No, editing an
activity's settings resulted in the error "Coding error detected, it must
be fixed by a programmer: The 'name' value must be set in other.".
2022-10-06 21:01:32 +01:00
13 changed files with 488 additions and 75 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
.idea
.vscode

View File

@ -0,0 +1,83 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
class backup_plagiarism_pchkorg_plugin extends backup_plagiarism_plugin {
/**
* define_module_plugin_structure
*
* @return mixed
*/
public function define_module_plugin_structure() {
// Define the virtual plugin element without conditions as the global class checks already.
$plugin = $this->get_plugin_element();
// Create one standard named plugin element (the visible container).
$pluginwrapper = new backup_nested_element($this->get_recommended_name());
// Connect the visible container ASAP.
$plugin->add_child($pluginwrapper);
$configs = new backup_nested_element('pchkorg_activities_configs');
$config = new backup_nested_element('pchkorg_activities_config', array('id'), array('name', 'value'));
$pluginwrapper->add_child($configs);
$configs->add_child($config);
$config->set_source_table('plagiarism_pchkorg_config', array('cm' => backup::VAR_PARENTID));
// Now information about files to module.
$files = new backup_nested_element('pchkorg_files');
$file = new backup_nested_element('pchkorg_file', array('id'), array(
'cm', 'fileid', 'userid',
'state', 'score', 'created_at',
'textid', 'reportid', 'signature',
'attempt', 'itemid'
));
$pluginwrapper->add_child($files);
$files->add_child($file);
// To know if we are including userinfo.
$userinfo = $this->get_setting_value('userinfo');
if ($userinfo) {
$file->set_source_table('plagiarism_pchkorg_files', array('cm' => backup::VAR_PARENTID));
}
return $plugin;
}
/**
* define_course_plugin_structure
*
* @return mixed
*/
public function define_course_plugin_structure() {
$plugin = $this->get_plugin_element();
$pluginwrapper = new backup_nested_element($this->get_recommended_name());
$plugin->add_child($pluginwrapper);
$configs = new backup_nested_element('pchkorg_configs');
$config = new backup_nested_element('pchkorg_config', array('id'), array('plugin', 'name', 'value'));
$pluginwrapper->add_child($configs);
$configs->add_child($config);
$config->set_source_table('config_plugins', array(
'name' => backup::VAR_PARENTID, 'plugin' => backup_helper::is_sqlparam('plagiarism'),
));
return $plugin;
}
}

View File

@ -0,0 +1,74 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
class restore_plagiarism_pchkorg_plugin extends restore_plagiarism_plugin {
public function process_pchkorg_config($data) {
$data = (object) $data;
set_config($this->task->get_courseid(), $data->value, $data->plugin);
}
public function process_pchkorgconfigmod($data) {
global $DB;
$data = (object) $data;
$data->cm = $this->task->get_moduleid();
$DB->insert_record('plagiarism_pchkorg_config', $data);
}
public function process_pchkorgfiles($data) {
global $DB;
$data = (object) $data;
$data->cm = $this->task->get_moduleid();
$data->userid = $this->get_mappingid('user', $data->userid);
$DB->insert_record('plagiarism_pchkorg_files', $data);
}
protected function define_course_plugin_structure() {
$paths = array();
$elename = 'pchkorg_config';
$elepath = $this->get_pathfor('/pchkorg_configs/pchkorg_config');
$paths[] = new restore_path_element($elename, $elepath);
return $paths; // And we return the interesting paths.
}
protected function define_module_plugin_structure() {
$paths = array();
$elename = 'pchkorgconfigmod';
$elepath = $this->get_pathfor('/pchkorg_activities_configs/pchkorg_activities_config');
$paths[] = new restore_path_element($elename, $elepath);
$elename = 'pchkorgfiles';
$elepath = $this->get_pathfor('/pchkorg_file/pchkorg_file');
$paths[] = new restore_path_element($elename, $elepath);
return $paths;
}
}

View File

@ -177,13 +177,10 @@ class plagiarism_pchkorg_api_provider {
$content,
$mime,
$filename,
$filters,
$filters
),
array(
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_FOLLOWLOCATION' => true,
'CURLOPT_SSL_VERIFYHOST' => false,
'CURLOPT_SSL_VERIFYPEER' => false,
'CURLOPT_HTTPHEADER' => array(
'X-API-TOKEN: ' . $this->generate_api_token(),
'Content-Type: multipart/form-data; boundary=' . $boundary
@ -299,13 +296,10 @@ class plagiarism_pchkorg_api_provider {
$content,
$mime,
$filename,
$filters,
$filters
),
array(
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_FOLLOWLOCATION' => true,
'CURLOPT_SSL_VERIFYHOST' => false,
'CURLOPT_SSL_VERIFYPEER' => false,
'CURLOPT_POST' => true,
'CURLOPT_HTTPHEADER' => array(
'X-API-TOKEN: ' . $this->generate_api_token(),
@ -347,9 +341,6 @@ class plagiarism_pchkorg_api_provider {
'',
array(
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_FOLLOWLOCATION' => true,
'CURLOPT_SSL_VERIFYHOST' => false,
'CURLOPT_SSL_VERIFYPEER' => false,
'CURLOPT_POST' => true,
'CURLOPT_HTTPHEADER' => array(
'X-API-TOKEN: ' . $token,
@ -455,7 +446,7 @@ class plagiarism_pchkorg_api_provider {
*/
public function user_email_to_hash($email) {
// We don't send raw user email to the service.
return hash('sha256', $this->token . $email);
return hash('sha256', $this->token . strtolower($email));
}
/**
@ -489,11 +480,8 @@ class plagiarism_pchkorg_api_provider {
'hash' => $this->user_email_to_hash($email)
), array(
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_FOLLOWLOCATION' => true,
'CURLOPT_SSL_VERIFYHOST' => false,
'CURLOPT_SSL_VERIFYPEER' => false,
// The maximum number of seconds to allow cURL functions to execute.
'CURLOPT_TIMEOUT' => 2
'CURLOPT_TIMEOUT' => 8
));
if ($json = json_decode($response)) {
@ -506,6 +494,82 @@ class plagiarism_pchkorg_api_provider {
return $resultmap[$email];
}
/**
* Check that user belongs to group when it is group account.
* And Receive auto_registration_option.
*
* @param string $email
* @return object
*/
public function get_group_member_response($email = '') {
if (!$this->is_group_token()) {
$result = new \stdClass;
$result->is_member = true;
$result->is_auto_registration_enabled = false;
return $result;
}
static $resultmap = array();
if (!array_key_exists($email, $resultmap)) {
//default result. For case when we can not receive response.
$result = new \stdClass;
$result->is_member = false;
$result->is_auto_registration_enabled = false;
$resultmap[$email] = $result;
$curl = new curl();
$response = $curl->post($this->endpoint . '/lms/moodle/is-group-member/', array(
'token' => $this->token,
'hash' => $this->user_email_to_hash($email)
), array(
'CURLOPT_RETURNTRANSFER' => true,
// The maximum number of seconds to allow cURL functions to execute.
'CURLOPT_TIMEOUT' => 8
));
if ($json = json_decode($response)) {
$result->is_member = $json->is_member;
$result->is_auto_registration_enabled = $json->is_auto_registration_enabled;
$resultmap[$email] = $result;
}
}
return $resultmap[$email];
}
/**
* Auto registration is enabled for this university,
* so we registrate a user and user can check submissions.
*
* @param $name
* @param $email
* @param $role
*
* @return bool
*/
public function auto_registrate_member($name, $email, $role) {
$curl = new curl();
$response = $curl->post($this->endpoint . '/lms/moodle/auto-registration/', array(
'token' => $this->token,
'name' => $name,
'email' => $email,
'role' => $role,
), array(
'CURLOPT_RETURNTRANSFER' => true,
// The maximum number of seconds to allow cURL functions to execute.
'CURLOPT_TIMEOUT' => 8
));
if ($json = json_decode($response)) {
return $json->success;
}
return false;
}
/**
* Check status of document.
* If document has been checked, state is 5.
@ -519,12 +583,10 @@ class plagiarism_pchkorg_api_provider {
// It uses different auth.
return $this->group_check_text($textid);
}
$curl = new curl();
$response = $curl->get($this->endpoint . '/api/v1/text/' . $textid, array(), array(
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_FOLLOWLOCATION' => true,
'CURLOPT_SSL_VERIFYHOST' => false,
'CURLOPT_SSL_VERIFYPEER' => false,
'CURLOPT_POST' => false,
'CURLOPT_HTTPHEADER' => array(
'X-API-TOKEN: ' . $this->generate_api_token(),
@ -533,7 +595,12 @@ class plagiarism_pchkorg_api_provider {
));
if ($json = json_decode($response)) {
if (isset($json->data) && 5 == $json->data->state) {
return $json->data->report;
$result = new stdClass;
$result->id = $json->data->report->id;
$result->percent = $json->data->report->percent;
$result->percent_ai = $json->data->ai_report->processed_percent;
return $result;
}
}
@ -553,9 +620,6 @@ class plagiarism_pchkorg_api_provider {
'token' => $this->token
), array(
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_FOLLOWLOCATION' => true,
'CURLOPT_SSL_VERIFYHOST' => false,
'CURLOPT_SSL_VERIFYPEER' => false,
'CURLOPT_POST' => false,
'CURLOPT_HTTPHEADER' => array(
'Content-Type: application/x-www-form-urlencoded'
@ -563,7 +627,12 @@ class plagiarism_pchkorg_api_provider {
));
if ($json = json_decode($response)) {
if (isset($json->data) && 5 == $json->data->state) {
return $json->data->report;
$result = new stdClass;
$result->id = $json->data->report->id;
$result->percent = $json->data->report->percent;
$result->percent_ai = $json->data->ai_report->processed_percent;
return $result;
}
}
@ -589,7 +658,7 @@ class plagiarism_pchkorg_api_provider {
global $USER;
if ($this->is_group_token()) {
return $this->token . '::' . hash('sha256', $this->token . $USER->email);
return $this->token . '::' . hash('sha256', $this->token . strtolower($USER->email));
}
return $this->token;

View File

@ -26,6 +26,16 @@ namespace plagiarism_pchkorg\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\userlist;
use core_privacy\local\request\approved_userlist;
if (interface_exists('\core_privacy\local\request\userlist')) {
interface my_userlist extends \core_privacy\local\request\userlist{}
} else {
interface my_userlist {};
}
/**
* Class provider
@ -33,8 +43,10 @@ use core_privacy\local\metadata\collection;
* @package plagiarism_pchkorg\privacy
*/
class provider implements
my_userlist,
\core_privacy\local\metadata\provider,
\core_privacy\local\request\plugin\provider {
\core_privacy\local\request\plugin\provider,
\core_privacy\local\request\core_userlist_provider {
// This trait must be included.
use \core_privacy\local\legacy_polyfill;
@ -59,6 +71,7 @@ class provider implements
'userid' => 'privacy:metadata:plagiarism_pchkorg_files:userid',
'state' => 'privacy:metadata:plagiarism_pchkorg_files:state',
'score' => 'privacy:metadata:plagiarism_pchkorg_files:score',
'scoreai' => 'privacy:metadata:plagiarism_pchkorg_files:scoreai',
'created_at' => 'privacy:metadata:plagiarism_pchkorg_files:created_at',
'textid' => 'privacy:metadata:plagiarism_pchkorg_files:textid',
'reportid' => 'privacy:metadata:plagiarism_pchkorg_files:reportid',
@ -91,4 +104,80 @@ class provider implements
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function _get_contexts_for_userid(int $userid) : contextlist {
$contextlist = new contextlist();
$sql = "SELECT DISTINCT cm FROM {plagiarism_pchkorg_files} WHERE userid = :userid";
$params = [
'userid' => $userid
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_module) {
return;
}
$params = [
'cm' => $context->instanceid,
];
$sql = "SELECT DISTINCT userid FROM {plagiarism_pchkorg_files} WHERE cm = :cm";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function _export_user_data(approved_contextlist $contextlist) {
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function _export_user_preferences(int $userid) {
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function _delete_data_for_all_users_in_context(\context $context) {
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function _delete_data_for_user(approved_contextlist $contextlist) {
}
}

View File

@ -12,6 +12,7 @@
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="state" TYPE="int" LENGTH="3" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="score" TYPE="float" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="scoreai" TYPE="number" LENGTH="4" NOTNULL="false" SEQUENCE="false" DECIMALS="2"/>
<FIELD NAME="created_at" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="textid" TYPE="int" LENGTH="11" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="reportid" TYPE="int" LENGTH="11" NOTNULL="false" SEQUENCE="false"/>

View File

@ -27,7 +27,7 @@ $tasks = array(
array(
'classname' => 'plagiarism_pchkorg\task\update_reports',
'blocking' => 0,
'minute' => '*/2',
'minute' => '*',
'hour' => '*',
'day' => '*',
'dayofweek' => '*',
@ -36,7 +36,7 @@ $tasks = array(
array(
'classname' => 'plagiarism_pchkorg\task\send_submissions',
'blocking' => 0,
'minute' => '*/2',
'minute' => '*',
'hour' => '*',
'day' => '*',
'dayofweek' => '*',

View File

@ -57,5 +57,16 @@ function xmldb_plagiarism_pchkorg_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2021072801, 'plagiarism', 'pchkorg');
}
if ($oldversion < 2023060713) {
$table = new xmldb_table('plagiarism_pchkorg_files');
$field1 = new xmldb_field('scoreai', XMLDB_TYPE_NUMBER, '4,2', XMLDB_UNSIGNED, null, null, null, null);
$field1->setComment('AI score');
if (!$dbman->field_exists($table, $field1)) {
$dbman->add_field($table, $field1);
}
}
return true;
}

View File

@ -86,6 +86,14 @@ class plagiarism_pchkorg_setup_form extends moodleform {
array(get_string('no'), get_string('yes'))
);
$mform->addElement(
'select',
'pchkorg_enabled_by_default',
get_string('pchkorg:enabledbydefault', 'plagiarism_pchkorg'),
array(get_string('no'), get_string('yes'))
);
$mform->setDefault('pchkorg_enabled_by_default', '1');
$this->add_action_buttons(true);
}

View File

@ -58,6 +58,7 @@ $string['pchkorg_debug_status_error'] = 'Some error for this file';
$string['pchkorg_debug_student_not_allowed_see_widget'] = 'Students can not see a similarity score';
$string['pchkorg_student_can_see_widget'] = 'Students can see a similarity score';
$string['pchkorg_student_can_see_report'] = 'Students can access a similarity report';
$string['pchkorg_check_ai'] = 'Enable AI Detector';
$string['pchkorg_disclosure'] = 'Submission will be sent to <a target="_blank" href="https://plagiarismcheck.org/">PlagiarismCheck.org</a> for check.
<br />
By submitting assignment I agree with <a target="_blank" href="https://plagiarismcheck.org/terms-of-service/">Terms &amp; Conditions</a>
@ -69,6 +70,7 @@ $string['privacy:metadata:plagiarism_pchkorg_files:fileid'] = 'Identity of a sub
$string['privacy:metadata:plagiarism_pchkorg_files:userid'] = 'Identity of user who submit file';
$string['privacy:metadata:plagiarism_pchkorg_files:state'] = 'Status of a document. For example: queued, sent, checked.';
$string['privacy:metadata:plagiarism_pchkorg_files:score'] = 'Originality score';
$string['privacy:metadata:plagiarism_pchkorg_files:scoreai'] = 'Chat GPT score';
$string['privacy:metadata:plagiarism_pchkorg_files:created_at'] = 'Date and time when document was saved.';
$string['privacy:metadata:plagiarism_pchkorg_files:textid'] = 'Identity of originality check';
$string['privacy:metadata:plagiarism_pchkorg_files:reportid'] = 'Identity of originality report';
@ -88,8 +90,11 @@ $string['sendqueuedsubmissions'] = '';
$string['updatereportscores'] = '';
$string['pchkorg_label_title'] = 'PlagiarismCheck.org ID: %s; Similarity Score: %s%%';
$string['pchkorg_label_result'] = 'ID: %s Similarity: %s%%';
$string['pchkorg_label_title_ai'] = 'PlagiarismCheck.org ID: %s; Similarity Score: %s%% AI: %s%%';
$string['pchkorg_label_result_ai'] = 'ID: %s Similarity: %s%% AI: %s%%';
$string['pchkorg_label_sent'] = 'ID: %s Sent';
$string['pchkorg_label_queued'] = 'In queue';
$string['pchkorg:enable'] = 'Allow to enable/disable PlagiarismCheck.org inside an activity';
$string['pchkorg:viewsimilarity'] = 'Allow to view similarity value from PlagiarismCheck.org';
$string['pchkorg:changeminpercentfilter'] = 'Allow changing "Exclude sources below X% similarity"';
$string['pchkorg:enabledbydefault'] = 'Enable PlagiarismCheck in Activities by default';

156
lib.php
View File

@ -66,7 +66,7 @@ function plagiarism_pchkorg_coursemodule_standard_elements($formwrapper, $mform)
$defaultcmid = null;
$cm = optional_param('update', $defaultcmid, PARAM_INT);
$minpercent = $pchkorgconfigmodel->get_system_config('pchkorg_min_percent');
$exportedvalues = $mform->exportValues([]);
$exportedvalues = $mform->exportValues(array());
if (!is_array($exportedvalues)) {
$exportedvalues = array();
}
@ -97,7 +97,13 @@ function plagiarism_pchkorg_coursemodule_standard_elements($formwrapper, $mform)
if (null === $cm) {
if (!isset($exportedvalues['pchkorg_module_use'])
|| is_null($exportedvalues['pchkorg_module_use'])) {
$mform->setDefault('pchkorg_module_use', '1');
$enabledbydefault = $pchkorgconfigmodel->get_system_config('pchkorg_enabled_by_default');
if ('1' === $enabledbydefault || null === $enabledbydefault) {
$mform->setDefault('pchkorg_module_use', '1');
}
if ('0' === $enabledbydefault) {
$mform->setDefault('pchkorg_module_use', '0');
}
}
} else {
$records = $DB->get_records('plagiarism_pchkorg_config', array(
@ -173,7 +179,6 @@ function plagiarism_pchkorg_coursemodule_standard_elements($formwrapper, $mform)
array(get_string('no'), get_string('yes'))
);
$mform->addElement(
'select',
'pchkorg_student_can_see_widget',
@ -187,6 +192,14 @@ function plagiarism_pchkorg_coursemodule_standard_elements($formwrapper, $mform)
get_string('pchkorg_student_can_see_report', 'plagiarism_pchkorg'),
array(get_string('no'), get_string('yes'))
);
$mform->addElement(
'select',
'pchkorg_check_ai',
get_string('pchkorg_check_ai', 'plagiarism_pchkorg'),
array(get_string('no'), get_string('yes'))
);
$mform->setDefault('pchkorg_check_ai', 1);
}
}
@ -198,7 +211,7 @@ function plagiarism_pchkorg_coursemodule_edit_post_actions($data, $course)
$config = $pchkorgconfigmodel->get_system_config('pchkorg_use');
if ('1' != $config) {
return;
return $data;
}
$fields = array(
@ -208,7 +221,8 @@ function plagiarism_pchkorg_coursemodule_edit_post_actions($data, $course)
'pchkorg_include_referenced',
'pchkorg_exclude_self_plagiarism',
'pchkorg_student_can_see_widget',
'pchkorg_student_can_see_report'
'pchkorg_student_can_see_report',
'pchkorg_check_ai'
);
$records = $DB->get_records('plagiarism_pchkorg_config', array(
@ -276,7 +290,11 @@ class plagiarism_plugin_pchkorg extends plagiarism_plugin {
$isdebugenabled = $pchkorgconfigmodel->get_system_config('pchkorg_enable_debug') === '1';
$apiprovider = new plagiarism_pchkorg_api_provider($apitoken);
$cmid = $linkarray['cmid'];
$cmid = null;
if (array_key_exists('cmid', $linkarray)) {
$cmid = $linkarray['cmid'];
}
if (array_key_exists('file', $linkarray)) {
$file = $linkarray['file'];
} else {
@ -324,7 +342,7 @@ class plagiarism_plugin_pchkorg extends plagiarism_plugin {
);
}
$roleDatas = get_user_roles($context, $USER->id, true);
$roles = [];
$roles = array();
foreach ($roleDatas as $rolesData) {
$roles[] = strtolower($rolesData->shortname);
}
@ -361,11 +379,13 @@ class plagiarism_plugin_pchkorg extends plagiarism_plugin {
);
}
$isreportallowed = !$isstudent || $pchkorgconfigmodel->show_report_for_student($cmid) === true;
$isreportallowed = !$isstudent
|| $pchkorgconfigmodel->show_report_for_student($cmid) === true
|| $pchkorgconfigmodel->show_report_for_student($cmid) === null;
// Only for some type of account, method will call a remote HTTP API.
// The API will be called only once, because result is static.
// Also, there is timeout 2 seconds for response.
// Also, there is timeout 8 seconds for response.
// Even if service will be unavailable, method will try call API only once.
// Also, we don't use raw user email.
if (!$apiprovider->is_group_member($USER->email)) {
@ -418,24 +438,46 @@ class plagiarism_plugin_pchkorg extends plagiarism_plugin {
$action = $apiprovider->get_report_action($filerecord->textid);
$reporttoken = $apiprovider->generate_api_token();
$score = $filerecord->score;
$title = sprintf(get_string('pchkorg_label_title', 'plagiarism_pchkorg'),
$filerecord->textid,
$score);
$label = sprintf(get_string('pchkorg_label_result', 'plagiarism_pchkorg'), $filerecord->textid, $score);
$isaienabled = '1' === $pchkorgconfigmodel->get_filter_for_module($cmid, 'pchkorg_check_ai');
if (isset($filerecord->scoreai) && $isaienabled) {
$title = sprintf(
get_string('pchkorg_label_title_ai', 'plagiarism_pchkorg'),
$filerecord->textid,
$score,
$filerecord->scoreai
);
$label = sprintf(
get_string('pchkorg_label_result_ai', 'plagiarism_pchkorg'),
$filerecord->textid,
$score,
$filerecord->scoreai
);
} else {
$title = sprintf(
get_string('pchkorg_label_title', 'plagiarism_pchkorg'),
$filerecord->textid,
$score
);
$label = sprintf(
get_string('pchkorg_label_result', 'plagiarism_pchkorg'),
$filerecord->textid,
$score
);
}
if ($score < 30) {
$color = '#63ec80a1';
$color = '#63EC80';
} else if (30 < $score && $score < 60) {
$color = '#f7b011';
$color = '#F7B011';
} else {
$color = '#f04343';
$color = '#F04343';
}
$jsdata = array(
'id' => $filerecord->id,
'title' => $title,
'action' => $action,
'token' => $reporttoken,
'image' => $imgsrc,
'label' => $label,
'color' => $color,
'isreportallowed' => $isreportallowed,
@ -488,20 +530,20 @@ require(['jquery'], function ($) {
a.setAttribute('href', '#');
a.setAttribute('title', data.title);
a.setAttribute('data-id', data.id);
a.style.padding = '5px 3px';
a.style.fontFamily = 'Roboto';
a.style.fontStyle = 'normal';
a.style.fontWeight = '400';
a.style.fontSize = '16px';
a.style.textAlign = 'center';
a.style.padding = '4px 16px';
a.style.textDecoration = 'none';
a.style.backgroundColor = data.color;
a.style.color = 'black';
a.style.cursor = 'pointer';
a.style.borderRadius = '3px 3px 3px 3px';
a.style.borderRadius = '4px 4px 4px 4px';
a.style.margin = '4px';
a.style.display = 'inline-block';
var label = document.createTextNode(data.label);
var img = document.createElement('img');
img.setAttribute('alt', 'PlagiarismCheck.org');
img.setAttribute('src', data.image);
img.setAttribute('width', '20');
a.appendChild(img);
a.appendChild(label);
span.appendChild(a);
break;
@ -678,6 +720,17 @@ display: inline-block;"
public function event_handler($eventdata) {
global $USER, $DB;
// Whitelist of supported events, ignore other.
$issupportedevent = in_array($eventdata['eventtype'], array(
"forum_attachment",
"quiz_submitted",
"assessable_submitted",
"content_uploaded"
));
if (!$issupportedevent) {
return true;
}
$modulename = $eventdata['other']['modulename'];
$allowedmodules = array('assign', 'mod_assign');
// We support only assign module so just ignore all other.
@ -704,7 +757,7 @@ display: inline-block;"
$cmid = $eventdata['contextinstanceid'];
// Remove the event if the course module no longer exists.
$cm = get_coursemodule_from_id($eventdata['other']['modulename'], $cmid);
$context = context_module::instance($cm->id);
if (!$cm) {
return true;
}
@ -717,25 +770,39 @@ display: inline-block;"
// Only for some type of account, method will call a remote HTTP API.
// The API will be called only once, because result is static.
// Also, there is timeout 2 seconds for response.
// Also, there is timeout 8 seconds for response.
// Even if service is unavailable, method will try call only once.
// Also, we don't use raw users email.
if (!$apiprovider->is_group_member($USER->email)) {
return true;
$ismemberresponse = $apiprovider->get_group_member_response($USER->email);
if (!$ismemberresponse->is_member) {
if ($ismemberresponse->is_auto_registration_enabled) {
$name = $USER->firstname . ' ' . $USER->lastname;
$roleDatas = get_user_roles($context, $USER->id, true);
$roles = array();
foreach ($roleDatas as $rolesData) {
$roles[] = strtolower($rolesData->shortname);
}
// Moodle has multiple roles in courses.
$isstudent = !in_array('teacher', $roles)
&& !in_array('editingteacher', $roles)
&& !in_array('managerteacher', $roles);
$isregistered = $apiprovider->auto_registrate_member($name, $USER->email, $isstudent ? 3 : 2);
if (!$isregistered) {
return true;
}
} else {
return true;
}
}
// Set the author and submitter.
$submitter = $eventdata['userid'];
$author = (!empty($eventdata['relateduserid'])) ? $eventdata['relateduserid'] : $eventdata['userid'];
// Related user ID will be NULL if an instructor submits on behalf of a student who is in a group.
// To get around this, we get the group ID, get the group members and set the author as the first student in the group.
if ((empty($eventdata['relateduserid'])) && ($eventdata['other']['modulename'] == 'assign')
&& has_capability('mod/assign:editothersubmission', context_module::instance($cm->id), $submitter)) {
&& has_capability('mod/assign:editothersubmission', $context, $submitter)) {
$moodlesubmission = $DB->get_record('assign_submission', array('id' => $eventdata['objectid']), 'id, groupid');
if (!empty($moodlesubmission->groupid)) {
$author = $this->get_first_group_author($cm->course, $moodlesubmission->groupid);
}
}
if ($eventdata['other']['modulename'] === 'forum'
@ -905,7 +972,7 @@ display: inline-block;"
$moodlesubmission = $DB->get_record('assign_submission', array('id' => $eventdata['objectid']), 'id');
$moodletextsubmission = $DB->get_record('assignsubmission_onlinetext',
array('submission' => $moodlesubmission->id), 'onlinetext');
array('submission' => $eventdata['objectid']), 'onlinetext');
if ($moodletextsubmission) {
$eventdata['other']['content'] = $moodletextsubmission->onlinetext;
@ -913,8 +980,8 @@ display: inline-block;"
$filesconditions = array(
'component' => 'assignsubmission_file',
'itemid' => $moodlesubmission->id,
'userid' => $author
'itemid' => $eventdata['objectid'],
'userid' => $eventdata['userid']
);
$moodlefiles = $DB->get_records('files', $filesconditions);
@ -1067,7 +1134,7 @@ display: inline-block;"
} else {
$minpercent = $systemminpercent;
}
$filters = [
$filters = array(
'include_references' => $pchkorgconfigmodel->get_filter_for_module(
$cm->id,
'pchkorg_include_referenced'
@ -1080,7 +1147,7 @@ display: inline-block;"
$cm->id,
'pchkorg_exclude_self_plagiarism'
),
];
);
if ($minpercent) {
$filters['source_min_percent'] = $minpercent;
}
@ -1098,11 +1165,13 @@ display: inline-block;"
if ($cm->modname === 'quiz') {
if ($filedb->fileid === null) {
$questionanswers = $DB->get_records_sql(
"SELECT {question_attempts}.responsesummary "
." FROM {question_attempts} "
." INNER JOIN {question} on {question}.id = {question_attempts}.questionid "
." WHERE {question_attempts}.questionusageid = ? AND {question}.qtype = 'essay' ", array(
." INNER JOIN {question} on {question}.id = {question_attempts}.questionid "
." INNER JOIN {quiz_attempts} on {quiz_attempts}.uniqueid = {question_attempts}.questionusageid "
." WHERE {quiz_attempts}.id= ? AND {question}.qtype = 'essay' ", array(
$filedb->itemid
)
);
@ -1121,7 +1190,7 @@ display: inline-block;"
html_to_text($content, 75, false),
'plain/text',
sprintf('%s-quiz.txt', $filedb->itemid),
$filters,
$filters
);
break;
}
@ -1190,7 +1259,7 @@ display: inline-block;"
html_to_text($content, 75, false),
'plain/text',
sprintf('%s-quiz.txt', $filedb->itemid),
$filters,
$filters
);
}
}
@ -1248,7 +1317,7 @@ display: inline-block;"
html_to_text($content, 75, false),
'plain/text',
sprintf('%s-submussion.txt', $moodletextsubmission->id),
$filters,
$filters
);
}
} else {
@ -1338,6 +1407,7 @@ display: inline-block;"
$filedbnew->state = 5;
$filedbnew->reportid = $report->id;
$filedbnew->score = $report->percent;
$filedbnew->scoreai = $report->percent_ai;
$DB->update_record('plagiarism_pchkorg_files', $filedbnew);
}

View File

@ -55,7 +55,10 @@ if (($data = $mform->get_data()) && confirm_sesskey()) {
foreach ($data as $field => $value) {
if (strpos($field, 'pchkorg') === 0) {
set_config($field, $value, 'plagiarism');
if ('pchkorg_use' === $field) {
set_config('enabled', $value, 'plagiarism_pchkorg');
}
set_config($field, $value, 'plagiarism_pchkorg');
$pchkorgconfigmodel->set_system_config($field, $value);
}
}

View File

@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die();
if (!isset($plugin)) {
$plugin = new stdClass();
}
$plugin->version = 2022092718;
$plugin->version = 2024030512;
$plugin->requires = 2020061501; // Requires Moodle 3.9 .
$plugin->release = 'v3.11.2';
$plugin->release = 'v3.14.7';
$plugin->component = 'plagiarism_pchkorg';
$plugin->maturity = MATURITY_STABLE;
$plugin->dependencies = array(