限制繳交語言
===
Admin(管理者)才有權限去修改此內容,是為了確保系統的安全性與公平性
原先的系統設定中 ```Allow submit``` 一欄的設定值會決定整個系統競賽允許繳
交的語言,並不能針對單一個問題去做設定限制語言的繳交。

若要限制特定的問題只能使用特定的程式語言去繳交,需要進行修改。
在 problem table 中新增一個欄位 ```restriction_language```
這樣對於每個 problem 都可以限制其繳交的 language
若不去設定,則預設系統中```Allow_submit```的所有語言都可以繳交。
<br/>
新增完成後會如以下畫面

點進去問題則可以編輯,可以從系統設定```allow_submit```的語言中選擇所要限制繳交的語言,點擊save後儲存
此範例中設定```Hello World```此題目只能限定用```C/C++```來繳交

實際打開繳交畫面,只能選擇C/C++

程式碼
==
首先要對 problem table 新增 ```restriction_language```此欄位
在其對應的 Entity 中```Problem.php``` 新增 restriction_language
找到```webapp/src/Entity/Problem.php```檔案
```php=
//ivan
//新增限制語言,設定為json type
/**
* @var array
* @ORM\Column(type="json", name="restriction_languages",
* options={"comment"="JSON-encoded restrictions","default"="NULL"},
* nullable=true)
*
*/
private $restriction_languages;
```
在同一個檔案下方設定getter()與setter()
```php=
/*-------CCU-------*/
//新增限制語言
/**
* Set restrictions
*
* @param array $restriction_languages
*
* @return Problem
*/
public function setRestrictionLanguages($restriction_languages)
{
$this->restriction_languages = $restriction_languages;
return $this;
}
/**
* Get restrictions
*
* @return array
*/
public function getRestrictionLanguages()
{
return $this->restriction_languages;
}
public function setLanguages(array $languages)
{
$this->restriction_languages['languages'] = $languages;
return $this;
}
/**
* Get restriction contests
* @return int[]
*/
public function getLanguages()
{
return $this->restriction_languages['language'] ?? [];
}
```
在某一個entity設定新的欄位(內容)時,可以透過phpMyAdmin資料庫管理系統新增,或手動輸入程式碼,也可以使用 symfony 內建的```make:entity```方式來新增,會自動產生```getter()```與```setter()```
接著就是 admin 選擇```Allow_submit```的語言,將其設定至資料庫中
這裡利用了 symfony 內建的 Form component,為每個form設定固定的type以便於此表單可以重複利用
可以找到此檔案```webapp/src/Form/Type/ProblemType.php```
在其中的```bulidForm()```函式中新增以下內容:
```php=
//在code最上方新增以下symfony套件
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Language;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
/*-------CCU-------*/
//新增限制語言
$restriction_languages = $this->em->getRepository(Language::class)->findAll(); //取得現有的程式語言
$restriction_languagesChoices = [];
foreach ($restriction_languages as $restriction_language) {
if($restriction_language->getAllowSubmit() == 1){
$restriction_languagesChoices[$restriction_language->getName()] = $restriction_language->getName();// 如果現有的程式語言同時被設為目前設定允許提交的語言,把它作為選項存入
}
}
```
取得目前系統可以提交的語言後,要確定傳回的型態,可以在相同函式```buildForm```下方新增
```php=
//add restriction language
$builder->add('restrictionlanguages', ChoiceType::class, [
'multiple' => true, //可以多選
'required' => false, //此欄位並非必要
'label' => 'Restriction Languages', //顯示在form中的文字
'choices' => $restriction_languagesChoices, //可選的選項
]);
```
此時此表單更新建立完成,admin可以透過controller將此表單渲染要需要的地方
通常每個form會對應到名稱相同的controller與entity
- ```ProblemType.php``` - ```ProblemController.php``` - ```Problem.php``` 三者互相對應,controller會去對應的form中渲染相關資料
此時可以透過```webapp/src/Controller/Jury/ProblemController.php```來決定要頁面要輸入的資料,以及要渲染到哪個網頁
### 後端
在```indexAction()```之下
```php=
//在最上方新增此entity
use App\Entity\Language;
/*----CCU-----*/
// ivan
// query資料庫的內容,這裡要新增restriction_languages這個欄位
$problems = $this->em->createQueryBuilder()
->select('partial p.{probid,externalid,name,timelimit,memlimit,outputlimit,subtask,restriction_languages,task_point}', 'COUNT(tc.testcaseid) AS testdatacount')
->from(Problem::class, 'p')
->leftJoin('p.testcases', 'tc')
->orderBy('p.probid', 'ASC')
->groupBy('p.probid')
->getQuery()->getResult();
/*----CCU-----*/
//這裡呈現出ProblemType此form的資料
//要新增 'restriction_languages'此欄位內容
$table_fields = [
'probid' => ['title' => 'ID', 'sort' => true, 'default_sort' => true],
'name' => ['title' => 'name', 'sort' => true],
'num_contests' => ['title' => '# contests', 'sort' => true],
'timelimit' => ['title' => 'time limit', 'sort' => true],
'memlimit' => ['title' => 'memory limit', 'sophp namespace jsonrt' => true],
'outputlimit' => ['title' => 'output limit', 'sort' => true],
'num_testcases' => ['title' => '# test cases', 'sort' => true],
//在 problems 頁面新增的內容
'subtask' => ['title' => 'subtask', 'sort' => true],
'restriction_languages' => ['title' => 'restriction language', 'sort' => true],
'task_point' => ['title' => 'task point', 'sort'=>true],
];
/*----CCU-----*/
return $this->render('jury/problems.html.twig', $data);
// 表示此action會將$data內容渲染至jury/problems.html.twig此網頁
```
### 前端
通常而言一個controller的action會將資料傳遞到特定的某個頁面
symfony可以透過```createForm()```對對應的form type建立form
通常可以透過系統生成,不過若要讓form有不同的資料與呈現方式則要透過手動的方式來添加
對於此頁面```webapp/templates/jury/problem.html.twig```
由```ProblemController.php >> viewAction()```所渲染,並將```$data```傳入,其中```$data```包含problem各欄位的值,可透過```problem.```的方式來取得
利用 Twig block 的特性在此區段```{% block content %}```之中
要顯示出限制語言則需要新增以下code:
```twig=
{% block content %}
<h1>Problem {{ problem.name }}</h1>
<div class="row">
<div class="col-lg-4">
<table class="table table-sm table-striped">
<tr>
<th>ID</th>
<td>p{{ problem.probid }}</td>
</tr>
{% if showExternalId(problem) %}
<tr>
<th>External ID</th>
<td>{{ problem.externalid }}</td>
</tr>
{% endif %}
<tr>
<th>Name</th>
<td>{{ problem.name }}</td>
</tr>
<tr>
<th>Testcases</th>
<td>
{% if problem.testcases is empty %}
<em>no testcases</em>
{% else %}
{{ problem.testcases.count }}
{% endif %}
<a href="{{ path('jury_problem_testcases', {'probId': problem.probid}) }}">details / edit</a>
</td>
</tr>
<tr>
<th>Timelimit</th>
<td>{{ problem.timelimit }} sec</td>
</tr>
<tr>
<th>Memory limit</th>
<td>
{% if problem.memlimit == null %}
{{ defaultMemoryLimit }} kB (default)
{% else %}
{{ problem.memlimit }} kB
{% endif %}
</td>
</tr>
<tr>
<th>Output limit</th>
<td>
{% if problem.outputlimit == null %}
{{ defaultOutputLimit }} kB (default)
{% else %}
{{ problem.outputlimit }} kB
{% endif %}
</td>
</tr>
{% if problem.problemtextType is not empty %}
<tr>
<th>Problem text</th>
<td>
<a href="{{ path('jury_problem_text', {'probId': problem.probid}) }}">
<i title="view problem description"
class="fas fa-file-{{ problem.problemtextType }}"></i>
</a>
</td>
</tr>
{% endif %}
<tr>
<th>Run script</th>
<td class="filename">
{% if problem.runExecutable is not empty %}
<a href="{{ path('jury_executable', {'execId': problem.runExecutable.execid}) }}">{{ problem.runExecutable.execid }}</a>
{% else %}
<a href="{{ path('jury_executable', {'execId': defaultRunExecutable}) }}">{{ defaultRunExecutable }}</a>
(default)
{% endif %}
</td>
</tr>
{% if problem.combinedRunCompare %}
<tr>
<th>Compare script</th>
<td>Run script combines run and compare script.</td>
</tr>
{% else %}
<tr>
<th>Compare script</th>
<td class="filename">
{% if problem.compareExecutable is not empty %}
<a href="{{ path('jury_executable', {'execId': problem.compareExecutable.execid}) }}">{{ problem.compareExecutable.execid }}</a>
{% else %}
<a href="{{ path('jury_executable', {'execId': defaultCompareExecutable}) }}">{{ defaultCompareExecutable }}</a>
(default)
{% endif %}
</td>
</tr>
{% endif %}
{% if problem.specialCompareArgs is not empty %}
<tr>
<th>Compare script arguments</th>
<td class="filename">{{ problem.specialCompareArgs }}</td>
</tr>
{% endif %}
{# ccu #}
{# ivan #}
{#新增限制語言#}
{#利用problem.的方式將限制語言的值取出#}
<tr>
<th>Restrction language </th>
{% if problem.restrictionlanguages is empty %}
<td class="nodata">none</td>
{% else %}
<td>
{% for languages in problem.restrictionlanguages %}
{{ languages }}<br/>
{% endfor %}
</td>
{% endif %}
</tr>
</table>
</div>
</div>
```
另外在此頁面
```webapp/templates/jury/jury_macros.twig```
利用了twig macro tag,有助於重複使用模板片段。
```twig=
{% macro table(data, fields, num_actions, options) %}
<tbody>
{%- for row in data %}
<tr class="{{ row.cssclass|default('') }}"
{%- if row.style is defined %} style="{{ row.style }}"{% endif %}>
{%- for key,column in fields %}
{%- set item = attribute(row.data, key) %}
<td
class="{{ item.cssclass|default('') }}{% if key == "status" %} {{ item.value|statusClass() }}{% endif %}"
{%- if item.title is defined %} title="{{ item.title }}"{% endif %}
{%- if item.sortvalue is defined %} data-sort="{{ item.sortvalue }}"{% endif %}>
{%- if item.link is defined %}<a href="{{ item.link }}">
{%- elseif row.link is defined %}<a href="{{ row.link }}">{% endif %}
{% if key == "status" %}
{{- (item.value|default(item.default|default('')))|statusIcon()|raw -}}
{#ccu#}
{#ivan#}
{#新增內容 若key=restriction_languages其item.value 為 array 需將其中的值印出 #}
{% elseif key == "restriction_languages" %}
{%- for v in item.value -%}
{{- v -}} <br>
{%- endfor -%}
{#新增內容 若key=problems_group其item.value 為 array 需將其中的值印出 #}
{% elseif key == "problems_group" %}
{%- for k in item.value -%}
{{- k -}} <br>
{%- endfor -%}
{% else %}
{{- (item.value|default(item.default|default('')))|raw -}}
{% endif %}
{%- if item.link is defined or row.link is defined -%}</a>{% endif %}
{%- if item.link is defined or row.link is defined -%}</a>{% endif %}
</td>
{%- endfor %}
```
#### Ajax
為了能在繳交時直接獲取特定題目所限制的語言,利用JavaScripts ajax方式減低資源傳遞,並即時更新
```webapp/templates/team/partials/submit_scripts.html/twig```
```twig=
/*-------CCU-------*/
$(function () {
var $contests =$('#submit_problem_problem'); //取得目前題目的標籤
var $problem = $('#submit_problem_problem');
// 目前題目變化時,會引發change動作,針對此動作顯示其限定的語言
$contests.on('change', function () {
var $problems = $('#submit_problem_language');
var $form = $(this).closest('form');
var data = {};
//新增 ajax
data[$problem.attr('name')] = $problem.val();
console.log($problem.attr('name'));
console.log($problem.val());
$.ajax({
url: $form.attr('action'),
type: "POST",
data: data,
success: function (html) {
var $newProblems = $(html).find('#submit_problem_language');
$newProblems.closest('.form-group').find('.invalid-feedback').remove();
$('#submit_problem_language').closest('.form-group').replaceWith(
$newProblems.closest('.form-group')
);
$newProblems.select();
}
});
});
});
```