├── README.md
├── pix
├── icon.png
└── icon.svg
├── version.php
├── db
├── upgrade.php
├── access.php
└── install.xml
├── classes
└── form
│ └── upload_form.php
├── index.php
├── lang
├── en
│ └── courseworktopics.php
└── ru
│ └── courseworktopics.php
├── preview.php
├── mod_form.php
├── view.php
└── lib.php
/README.md:
--------------------------------------------------------------------------------
1 | .
2 |
--------------------------------------------------------------------------------
/pix/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KEV0143/Moodle_PluginPoll/HEAD/pix/icon.png
--------------------------------------------------------------------------------
/pix/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/version.php:
--------------------------------------------------------------------------------
1 | component = 'mod_courseworktopics';
5 | $plugin->version = 2025090815;
6 | $plugin->requires = 2024051300;
7 | $plugin->maturity = MATURITY_STABLE;
8 | $plugin->release = '1.8.3';
9 |
--------------------------------------------------------------------------------
/db/upgrade.php:
--------------------------------------------------------------------------------
1 | get_manager();
8 |
9 | if ($oldversion < 2025090806) {
10 | $table = new xmldb_table('courseworktopics');
11 | $field = new xmldb_field('choiceid', XMLDB_TYPE_INTEGER, '10', null, null, null, 0, 'introformat');
12 | if (!$dbman->field_exists($table, $field)) {
13 | $dbman->add_field($table, $field);
14 | }
15 | upgrade_mod_savepoint(true, 2025090806, 'courseworktopics');
16 | }
17 |
18 | return true;
19 | }
20 |
--------------------------------------------------------------------------------
/classes/form/upload_form.php:
--------------------------------------------------------------------------------
1 | libdir.'/formslib.php');
5 |
6 | class courseworktopics_upload_form extends moodleform {
7 | public function definition() {
8 | $mform = $this->_form;
9 |
10 | $mform->addElement('header', 'uploadheader', get_string('uploadfile', 'courseworktopics'));
11 |
12 | $mform->addElement('filepicker', 'topicsfile', get_string('file'), null,
13 | array('accepted_types' => '.txt'));
14 | $mform->addRule('topicsfile', null, 'required');
15 |
16 | $mform->addElement('submit', 'submitbutton', get_string('upload'));
17 | }
18 | }
--------------------------------------------------------------------------------
/db/access.php:
--------------------------------------------------------------------------------
1 | [
6 | 'riskbitmask' => RISK_XSS,
7 | 'captype' => 'write',
8 | 'contextlevel' => CONTEXT_COURSE,
9 | 'archetypes' => [
10 | 'editingteacher' => CAP_ALLOW,
11 | 'manager' => CAP_ALLOW
12 | ],
13 | 'clonepermissionsfrom' => 'moodle/course:manageactivities'
14 | ],
15 | 'mod/courseworktopics:view' => [
16 | 'captype' => 'read',
17 | 'contextlevel' => CONTEXT_MODULE,
18 | 'archetypes' => [
19 | 'teacher' => CAP_ALLOW,
20 | 'editingteacher' => CAP_ALLOW,
21 | 'manager' => CAP_ALLOW,
22 | 'student' => CAP_ALLOW
23 | ]
24 | ],
25 | ];
26 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | set_url(new moodle_url('/mod/courseworktopics/index.php',['id'=>$id]));
7 | $PAGE->set_title(get_string('modulenameplural','courseworktopics'));
8 | $PAGE->set_heading(format_string($course->fullname));
9 | echo $OUTPUT->header();
10 | echo $OUTPUT->heading(get_string('modulenameplural','courseworktopics'));
11 | $instances = get_all_instances_in_course('courseworktopics',$course);
12 | if(empty($instances)){
13 | notice(get_string('thereareno','moodle',get_string('modulenameplural','courseworktopics')),
14 | new moodle_url('/course/view.php',['id'=>$course->id]));
15 | }
16 | $table=new html_table();
17 | $table->head=[get_string('name'), get_string('viewhelp','courseworktopics')];
18 | foreach($instances as $inst){
19 | $url=new moodle_url('/mod/courseworktopics/view.php',['id'=>$inst->coursemodule]);
20 | $link=html_writer::link($url,format_string($inst->name));
21 | $table->data[]=[$link, get_string('viewhelp','courseworktopics')];
22 | }
23 | echo html_writer::table($table);
24 | echo $OUTPUT->footer();
25 |
--------------------------------------------------------------------------------
/db/install.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/lang/en/courseworktopics.php:
--------------------------------------------------------------------------------
1 | id);
9 | $PAGE->set_url(new moodle_url('/mod/courseworktopics/preview.php', ['draftid' => $draftid]));
10 | $PAGE->set_context($context);
11 | $PAGE->set_pagelayout('popup');
12 | $PAGE->set_title(get_string('previewtitle','courseworktopics'));
13 | $PAGE->set_heading(get_string('previewtitle','courseworktopics'));
14 |
15 | echo $OUTPUT->header();
16 | echo $OUTPUT->heading(get_string('previewtitle','courseworktopics'));
17 |
18 | $fs = get_file_storage();
19 | $files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'filename', false);
20 | if (empty($files)) {
21 | echo $OUTPUT->notification(get_string('nofile','courseworktopics'), 'notifyproblem');
22 | echo $OUTPUT->footer();
23 | exit;
24 | }
25 | $file = reset($files);
26 |
27 | $filename = core_text::strtolower($file->get_filename());
28 | $ext = pathinfo($filename, PATHINFO_EXTENSION);
29 |
30 | if ($ext === 'xlsx' && class_exists('PhpOffice\PhpSpreadsheet\IOFactory')) {
31 | $tmp = make_temp_directory('courseworktopics').'/preview.xlsx';
32 | $file->copy_content_to($tmp);
33 |
34 | $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
35 | $reader->setReadDataOnly(true);
36 | $ss = $reader->load($tmp);
37 | $sheet = $ss->getActiveSheet();
38 |
39 | $rows = [];
40 | foreach ($sheet->getRowIterator() as $row) {
41 | $cells = [];
42 | $ci = $row->getCellIterator();
43 | $ci->setIterateOnlyExistingCells(false);
44 | foreach ($ci as $cell) {
45 | $cells[] = trim((string)$cell->getValue());
46 | }
47 | // обрезаем хвостовые пустые
48 | while (!empty($cells) && end($cells) === '') { array_pop($cells); }
49 | if (!empty($cells)) { $rows[] = $cells; }
50 | }
51 | @unlink($tmp);
52 | } else {
53 | echo $OUTPUT->notification(get_string('xlsxonly','courseworktopics'), 'notifyproblem');
54 | echo $OUTPUT->footer();
55 | exit;
56 | }
57 |
58 | require_once($CFG->dirroot.'/mod/courseworktopics/lib.php');
59 | $options = courseworktopics_parse_rows($rows);
60 |
61 | if (empty($options)) {
62 | echo $OUTPUT->notification(get_string('notopics','courseworktopics'), 'notifyproblem');
63 | } else {
64 | $table = new html_table();
65 | $table->head = [get_string('name'), get_string('slots','courseworktopics')];
66 | foreach ($options as $o) {
67 | $table->data[] = [s($o['text']), (int)$o['limit']];
68 | }
69 | echo html_writer::table($table);
70 | }
71 |
72 | echo html_writer::tag('p', get_string('previewhint','courseworktopics'));
73 | echo $OUTPUT->footer();
74 |
--------------------------------------------------------------------------------
/mod_form.php:
--------------------------------------------------------------------------------
1 | dirroot.'/course/moodleform_mod.php');
5 |
6 | class mod_courseworktopics_mod_form extends moodleform_mod {
7 | public function definition() {
8 | global $PAGE;
9 | $mform = $this->_form;
10 |
11 | $mform->addElement('header','general',get_string('general','form'));
12 | $mform->addElement('text','name',get_string('name'),['size'=>'64']);
13 | $mform->setType('name', PARAM_TEXT);
14 | $mform->addRule('name', null, 'required', null, 'client');
15 |
16 | $this->standard_intro_elements(get_string('moduleintro'));
17 | $this->standard_coursemodule_elements();
18 |
19 | $mform->addElement('header','choicesettings', get_string('choicesettings','courseworktopics'));
20 |
21 | $mform->addElement('advcheckbox', 'showavailable', get_string('showavailable','courseworktopics'));
22 | $mform->setDefault('showavailable', 1);
23 | $mform->addHelpButton('showavailable','showavailable','courseworktopics');
24 |
25 | $mform->addElement('advcheckbox', 'allowupdate', get_string('allowupdate','courseworktopics'));
26 | $mform->setDefault('allowupdate', 1);
27 | $mform->addHelpButton('allowupdate','allowupdate','courseworktopics');
28 |
29 | $mform->addElement('date_time_selector', 'timeopen', get_string('timeopenlbl','courseworktopics'), array('optional'=>true));
30 | $mform->addElement('date_time_selector', 'timeclose', get_string('timecloselbl','courseworktopics'), array('optional'=>true));
31 |
32 | $mform->addElement('header','importhdr',get_string('importhdr','courseworktopics'));
33 | $mform->addHelpButton('importhdr','importhdr','courseworktopics');
34 |
35 | $mform->addElement('filepicker','topicsfile',get_string('topicsfile','courseworktopics'), null,
36 | ['accepted_types'=>['.xlsx']]);
37 | $mform->addRule('topicsfile', null, 'required', null, 'client');
38 | $mform->addHelpButton('topicsfile','topicsfile','courseworktopics');
39 |
40 | $previewhtml = html_writer::tag('button', get_string('previewbtn','courseworktopics'),
41 | ['type'=>'button','id'=>'cwtopics_preview','class'=>'btn btn-secondary']);
42 | $mform->addElement('static','previewbtn','', $previewhtml);
43 |
44 | $pickfilemsg = json_encode(get_string('topicsfile','courseworktopics'), JSON_UNESCAPED_UNICODE);
45 | $js = <<requires->js_init_code($js);
57 |
58 | $this->add_action_buttons();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/view.php:
--------------------------------------------------------------------------------
1 | get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
7 | $instance = $DB->get_record('courseworktopics', ['id' => $cm->instance], '*', MUST_EXIST);
8 |
9 | require_login($course, true, $cm);
10 | $context = context_module::instance($cm->id);
11 |
12 | $PAGE->set_url(new moodle_url('/mod/courseworktopics/view.php', ['id' => $cm->id]));
13 | $PAGE->set_title(format_string($instance->name));
14 | $PAGE->set_heading(format_string($course->fullname));
15 | $PAGE->set_context($context);
16 |
17 | echo $OUTPUT->header();
18 | echo $OUTPUT->heading(format_string($instance->name));
19 |
20 | if (!empty($instance->choiceid)) {
21 | $choicecmid = (int)$instance->choiceid;
22 | $choiceurl = new moodle_url('/mod/choice/view.php', ['id' => $choicecmid]);
23 |
24 | $choicecontext = context_module::instance($choicecmid);
25 | if (has_capability('mod/choice:readresponses', $choicecontext)) {
26 | $answer = $DB->get_record('choice_answers', ['choiceid' => $choicecmid, 'userid' => $USER->id]);
27 | if ($answer) {
28 | // Получим текст варианта.
29 | $opt = $DB->get_record('choice_options', ['id' => $answer->optionid], 'text');
30 | if ($opt) {
31 | echo $OUTPUT->box(get_string('yourchoice_admin','courseworktopics', format_string($opt->text)), 'generalbox');
32 | }
33 | } else {
34 | echo $OUTPUT->notification(get_string('nochosen_admin','courseworktopics'), 'info');
35 | }
36 | echo $OUTPUT->single_button($choiceurl, get_string('gotochoice','courseworktopics'), 'get');
37 | } else {
38 | redirect($choiceurl);
39 | }
40 |
41 | echo $OUTPUT->footer();
42 | exit;
43 | }
44 |
45 | echo $OUTPUT->box(format_module_intro('courseworktopics', $instance, $cm->id), 'generalbox mod_introbox');
46 |
47 | $fs = get_file_storage();
48 | $files = $fs->get_area_files($context->id, 'mod_courseworktopics', 'topicsfile', 0, 'filename', false);
49 |
50 | if (!empty($files)) {
51 | $file = reset($files);
52 | require_once($CFG->dirroot.'/mod/courseworktopics/lib.php');
53 | list($rows, $fmt) = courseworktopics_read_uploaded_file($file);
54 | $options = courseworktopics_parse_rows($rows);
55 |
56 | if (!empty($options)) {
57 | $table = new html_table();
58 | $table->head = [get_string('name'), get_string('slots','courseworktopics')];
59 | foreach ($options as $o) {
60 | $table->data[] = [s($o['text']), (int)$o['limit']];
61 | }
62 | echo html_writer::table($table);
63 | } else {
64 | echo $OUTPUT->notification(get_string('notopics','courseworktopics'), 'notifyproblem');
65 | }
66 | } else {
67 | echo $OUTPUT->notification(get_string('nofile','courseworktopics'), 'notifyproblem');
68 | }
69 |
70 | echo $OUTPUT->footer();
71 |
--------------------------------------------------------------------------------
/lib.php:
--------------------------------------------------------------------------------
1 | dirroot.'/course/modlib.php');
5 | if (file_exists($CFG->dirroot.'/vendor/autoload.php')) {
6 | require_once($CFG->dirroot.'/vendor/autoload.php');
7 | }
8 |
9 | function courseworktopics_add_instance($data, $mform = null) {
10 | global $DB, $CFG;
11 |
12 | $data->timecreated = time();
13 | $data->timemodified = $data->timecreated;
14 | $id = $DB->insert_record('courseworktopics', $data);
15 |
16 | $cmid = $data->coursemodule;
17 | $context = context_module::instance($cmid);
18 |
19 | $draftid = file_get_submitted_draft_itemid('topicsfile');
20 | file_save_draft_area_files($draftid, $context->id, 'mod_courseworktopics', 'topicsfile', 0, [
21 | 'subdirs' => 0, 'maxfiles' => 1, 'accepted_types' => ['.txt','.csv','.xlsx']
22 | ]);
23 |
24 | $fs = get_file_storage();
25 | $files = $fs->get_area_files($context->id, 'mod_courseworktopics', 'topicsfile', 0, 'filename', false);
26 | if (empty($files)) {
27 | throw new moodle_exception('nofile', 'courseworktopics');
28 | }
29 | $file = reset($files);
30 |
31 | list($rows, $fmt) = courseworktopics_read_uploaded_file($file);
32 | $options = courseworktopics_parse_rows($rows);
33 |
34 | if (empty($options)) {
35 | foreach ($rows as $r) {
36 | $line = is_array($r) ? trim(implode(' ', $r)) : trim((string)$r);
37 | if ($line === '') { continue; }
38 | $lower = core_text::strtolower($line);
39 | if ((strpos($lower,'тема')!==false && (strpos($lower,'мест')!==false||strpos($lower,'кол-во')!==false))
40 | ||(strpos($lower,'topic')!==false && strpos($lower,'slot')!==false)) { continue; }
41 | $options[] = ['text' => preg_replace('/[\s;,:()\-\d]+$/u','',$line),'limit'=>1];
42 | }
43 | }
44 |
45 | require_once($CFG->dirroot.'/mod/choice/lib.php');
46 | require_once($CFG->dirroot.'/course/lib.php');
47 |
48 | $course = $DB->get_record('course', ['id' => $data->course], '*', MUST_EXIST);
49 | $sectionnum = isset($data->section) ? (int)$data->section : 0;
50 |
51 | $choice = (object)[
52 | 'course' => $course->id,
53 | 'name' => $data->name,
54 | 'intro' => get_string('introdefault','courseworktopics'),
55 | 'introformat' => FORMAT_HTML,
56 | 'timeopen' => 0,
57 | 'timeclose' => !empty($data->timeclose) ? (int)$data->timeclose : 0,
58 | 'allowmultiple' => 0,
59 | 'showresults' => CHOICE_SHOWRESULTS_NOT,
60 | 'allowupdate' => !empty($data->allowupdate) ? 1 : 0,
61 | 'limitanswers' => 1,
62 | 'display' => CHOICE_DISPLAY_VERTICAL,
63 | 'showunanswered' => 0,
64 | 'showavailable' => !empty($data->showavailable) ? 1 : 0,
65 | 'includeinactive' => 0,
66 | 'completionsubmit'=> 1
67 | ];
68 |
69 | foreach ($options as $idx=>$opt) {
70 | $choice->option[$idx] = $opt['text'];
71 | $choice->limit[$idx] = $opt['limit'];
72 | $choice->optionid[$idx] = 0;
73 | $choice->limitid[$idx] = 0;
74 | }
75 |
76 | $fromform = (object)[
77 | 'course' => $course->id,
78 | 'module' => $DB->get_field('modules', 'id', ['name' => 'choice']),
79 | 'modulename' => 'choice',
80 | 'section' => $sectionnum,
81 | 'visible' => 1,
82 | 'groupmode' => 0,
83 | 'groupingid' => 0,
84 | 'completion' => 0,
85 | 'completionview'=> 0,
86 | 'completionexpected' => 0,
87 | 'showdescription' => 0,
88 | 'intro' => $choice->intro,
89 | 'introformat' => $choice->introformat,
90 | ];
91 |
92 | $fromform->introeditor = [
93 | 'text' => $choice->intro,
94 | 'format' => FORMAT_HTML,
95 | 'itemid' => 0
96 | ];
97 |
98 | foreach ((array)$choice as $k => $v) {
99 | $fromform->$k = $v;
100 | }
101 |
102 | $created = create_module($fromform);
103 | if (is_object($created)) {
104 | if (!empty($created->coursemodule)) {
105 | $cmid = (int)$created->coursemodule;
106 | } else if (!empty($created->cmid)) {
107 | $cmid = (int)$created->cmid;
108 | } else if (!empty($created->id)) {
109 | $cmid = (int)$created->id;
110 | } else {
111 | $cmid = 0;
112 | }
113 | } else {
114 | $cmid = (int)$created;
115 | }
116 | $DB->set_field('courseworktopics', 'choiceid', $cmid, ['id' => $id]);
117 |
118 | return $id;
119 | }
120 |
121 | function courseworktopics_delete_instance($id) {
122 | global $DB;
123 |
124 | if (!$instance = $DB->get_record('courseworktopics', ['id' => $id])) {
125 | return false;
126 | }
127 |
128 | if ($cm = get_coursemodule_from_instance('courseworktopics', $id)) {
129 | $context = context_module::instance($cm->id);
130 | $fs = get_file_storage();
131 | $fs->delete_area_files($context->id, 'mod_courseworktopics');
132 | }
133 |
134 | $DB->delete_records('courseworktopics', ['id' => $id]);
135 | return true;
136 | }
137 |
138 | function courseworktopics_read_uploaded_file($file): array {
139 | $filename = core_text::strtolower($file->get_filename());
140 | $ext = pathinfo($filename, PATHINFO_EXTENSION);
141 |
142 | if ($ext === 'xlsx' && class_exists('PhpOffice\PhpSpreadsheet\IOFactory')){
143 | $tmp=make_temp_directory('courseworktopics').'/import.xlsx';
144 | $file->copy_content_to($tmp);
145 | $reader=\PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
146 | $reader->setReadDataOnly(true);
147 | $ss=$reader->load($tmp);
148 | $sheet=$ss->getActiveSheet();
149 | $rows=[];
150 | foreach($sheet->getRowIterator() as $row){
151 | $cells=[];
152 | $ci=$row->getCellIterator();
153 | $ci->setIterateOnlyExistingCells(false);
154 | foreach($ci as $cell){ $cells[]=trim((string)$cell->getValue()); }
155 | while(!empty($cells) && end($cells)===''){ array_pop($cells); }
156 | if(!empty($cells)){$rows[]=$cells;}
157 | }
158 | @unlink($tmp);
159 | return [$rows,'xlsx'];
160 | }
161 | if ($ext === 'xlsx'){
162 | throw new moodle_exception('missingdep','courseworktopics','', 'PhpSpreadsheet (composer: phpoffice/phpspreadsheet)');
163 | }
164 | throw new moodle_exception('invalidfiletype', 'error', '', $ext);
165 | }
166 |
167 | function courseworktopics_parse_rows(array $rows): array {
168 | $out=[];
169 | foreach($rows as $r){
170 | if(is_array($r)){
171 | $cells=array_map(function($v){return trim((string)$v);},$r);
172 | if(count($cells)>=2){ $topicRaw=$cells[0]; $slotsRaw=$cells[1]; }
173 | else{ $topicRaw=$cells[0]??''; $slotsRaw=''; }
174 | } else {
175 | $line=trim((string)$r);
176 | if($line===''){continue;}
177 | $lower=core_text::strtolower($line);
178 | if((strpos($lower,'тема')!==false && (strpos($lower,'мест')!==false||strpos($lower,'кол-во')!==false))
179 | ||(strpos($lower,'topic')!==false && strpos($lower,'slot')!==false)){continue;}
180 | if(preg_match('/^(.*?)[\s;,:\-\t|()]*?(\d+)\s*(?:мест(?:а|о)?|slots?|шт)?\s*$/ui',$line,$m)){
181 | $topicRaw=trim($m[1]); $slotsRaw=$m[2];
182 | } else {
183 | $topicRaw=$line; $slotsRaw='';
184 | }
185 | }
186 | $topic=preg_replace('/^[\s\d\)\.\-]+/u','',(string)$topicRaw);
187 | $topic=preg_replace('/[\s;,:()\-\d]+$/u','',$topic);
188 | if($topic===''){continue;}
189 | $slots=null;
190 | if($slotsRaw!==''){
191 | if(preg_match('/(\d+)/u',(string)$slotsRaw,$mm)){$slots=(int)$mm[1];}
192 | } else {
193 | if(preg_match('/(\d+)\s*(?:мест(?:а|о)?|slots?|шт)?\s*$/ui',(string)$topicRaw,$mm)){
194 | $slots=(int)$mm[1];
195 | $topic=preg_replace('/[\s;,:()\-\d]+$/u','',(string)$topicRaw);
196 | }
197 | }
198 | if(!$slots||$slots<1){$slots=1;}
199 | $out[]=['text'=>$topic,'limit'=>$slots];
200 | }
201 | return $out;
202 | }
203 |
--------------------------------------------------------------------------------