├── 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 | --------------------------------------------------------------------------------