diff --git a/main/exercice/exercice.php b/main/exercice/exercice.php index b3ce7661bc..37b797e2ab 100644 --- a/main/exercice/exercice.php +++ b/main/exercice/exercice.php @@ -698,6 +698,13 @@ if (!empty($exercise_list)) { }*/ } + if ($app['security']->isGranted('ROLE_SESSION_MANAGER')) { + $actions .= Display::url( + Display::return_icon('admin_star.png', get_lang('Distribution'), '', ICON_SIZE_SMALL), + $app['url_generator']->generate('exercise_distribution.controller:indexAction', array('exerciseId' => $exercise_id)) + ); + } + // Number of questions /* $random_label = null; diff --git a/main/exercice/question.class.php b/main/exercice/question.class.php index 6192b3fbf4..9c1643ebc9 100644 --- a/main/exercice/question.class.php +++ b/main/exercice/question.class.php @@ -137,7 +137,7 @@ abstract class Question * @param int $course_id * @param Exercise * - * @return boolean - true if question exists, otherwise false + * @return Question */ public static function read($id, $course_id = null, Exercise $exercise = null) { diff --git a/main/exercice/testcategory.class.php b/main/exercice/testcategory.class.php index fa2af193ea..f095d93626 100644 --- a/main/exercice/testcategory.class.php +++ b/main/exercice/testcategory.class.php @@ -722,6 +722,39 @@ class Testcategory return $newCategoryList; } + /** + * Returns an array of question ids for each category + * $categories[1][30] = 10, array with category id = 1 and question_id = 10 + * A question has "n" categories + * @param int exercise + * @param array check question list + * @param string order by + * @return array + */ + static function getQuestionsByCategory($categoryId) { + $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); + $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); + $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY); + $categoryTable = Database::get_course_table(TABLE_QUIZ_CATEGORY); + $categoryId = intval($categoryId); + + $sql = "SELECT DISTINCT qrc.question_id, qrc.category_id + FROM $TBL_QUESTION_REL_CATEGORY qrc INNER JOIN $TBL_EXERCICE_QUESTION eq + ON (eq.question_id = qrc.question_id) + INNER JOIN $categoryTable c + ON (c.iid = qrc.category_id) + INNER JOIN $tableQuestion q + ON (q.iid = qrc.question_id ) + WHERE qrc.category_id = $categoryId "; + + $res = Database::query($sql); + $newCategoryList = array(); + while ($data = Database::fetch_array($res)) { + $newCategoryList[] = $data['question_id']; + } + return $newCategoryList; + } + /** * Return an array of X elements of an array * @param array $array diff --git a/main/inc/Entity/CQuizDistribution.php b/main/inc/Entity/CQuizDistribution.php index 522168f7ce..e2d645e862 100644 --- a/main/inc/Entity/CQuizDistribution.php +++ b/main/inc/Entity/CQuizDistribution.php @@ -23,6 +23,14 @@ class CQuizDistribution */ private $id; + /** + * @var string + * + * @ORM\Column(name="title", type="string", length=250, precision=0, scale=0, nullable=true, unique=false) + */ + private $title; + + /** * @var integer * @@ -58,6 +66,32 @@ class CQuizDistribution */ private $lastGenerationDate; + /** + * @ORM\OneToMany(targetEntity="CQuizDistributionQuestions", mappedBy="distribution", cascade={"persist", "remove"} ) + */ + private $questions; + + public function __construct() + { + $this->lastGenerationDate = new \DateTime(); + $this->questions = new ArrayCollection(); + } + + public function getQuestions() + { + return $this->questions; + } + + public function setQuestions($questions) + { + $this->questions = $questions; + return $this; + } + + public function setQuestion(CQuizDistributionQuestions $questions) + { + $this->questions->add($questions); + } /** * Get id @@ -82,6 +116,29 @@ class CQuizDistribution return $this; } + /** + * Set title + * + * @param string $title + * @return CQuizDistribution + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * Get title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + /** * Get exerciseId * diff --git a/main/inc/Entity/CQuizDistributionQuestions.php b/main/inc/Entity/CQuizDistributionQuestions.php index 97ea1eeae2..09e5650f17 100644 --- a/main/inc/Entity/CQuizDistributionQuestions.php +++ b/main/inc/Entity/CQuizDistributionQuestions.php @@ -45,6 +45,17 @@ class CQuizDistributionQuestions */ private $questionId; + /** + * @ORM\ManyToOne(targetEntity="CQuizDistribution") + * @ORM\JoinColumn(name="quiz_distribution_id", referencedColumnName="id", nullable=true) + */ + private $distribution; + + + public function setDistribution($distribution) + { + $this->distribution = $distribution; + } /** * Get id diff --git a/main/inc/routes.php b/main/inc/routes.php index 99be98f73a..41468ca7d1 100644 --- a/main/inc/routes.php +++ b/main/inc/routes.php @@ -668,5 +668,7 @@ if ($alreadyInstalled) { $app->mount('/admin/director', new ChamiloLMS\Provider\ReflectionControllerProvider('branch_director.controller')); $app->mount('/admin/jury_president', new ChamiloLMS\Provider\ReflectionControllerProvider('jury_president.controller')); $app->mount('/admin/jury_member', new ChamiloLMS\Provider\ReflectionControllerProvider('jury_member.controller')); + + $app->mount('/admin/question_manager/exercise_distribution', new ChamiloLMS\Provider\ReflectionControllerProvider('exercise_distribution.controller')); } diff --git a/main/inc/services.php b/main/inc/services.php index 4cd9e8bd88..be4817a471 100644 --- a/main/inc/services.php +++ b/main/inc/services.php @@ -762,4 +762,8 @@ $app['jury_member.controller'] = $app->share( } ); - +$app['exercise_distribution.controller'] = $app->share( + function () use ($app) { + return new ChamiloLMS\Controller\Admin\QuestionManager\ExerciseDistributionController($app); + } +); diff --git a/main/install/1.10.0/update.sql b/main/install/1.10.0/update.sql index 29ece0c83a..ad9305a43f 100644 --- a/main/install/1.10.0/update.sql +++ b/main/install/1.10.0/update.sql @@ -55,10 +55,11 @@ RENAME TABLE branch_sync_log TO branch_transaction_log; UPDATE settings_current SET selected_value = 'minedu' WHERE variable = 'template'; UPDATE settings_current SET selected_value = 'digedd' WHERE variable = 'stylesheets'; - +DROP TABLE c_quiz_distribution; CREATE TABLE c_quiz_distribution ( id int unsigned not null primary key AUTO_INCREMENT, exercise_id int unsigned not null, + title varchar(255) not null, -- the list of questions id that the student will have to go through for this form, split by "," -- (as in track_e_exercices - this will avoid 60 more queries to the next table once the exam is taking place) data_tracking text not null default '', diff --git a/main/template/minedu/admin/questionmanager/exercise_distribution/add.tpl b/main/template/minedu/admin/questionmanager/exercise_distribution/add.tpl new file mode 100644 index 0000000000..24b40d4488 --- /dev/null +++ b/main/template/minedu/admin/questionmanager/exercise_distribution/add.tpl @@ -0,0 +1,11 @@ +{% extends app.template_style ~ "/layout/layout_1_col.tpl" %} +{% block content %} + + + {{ 'List' |trans }} + +
+
+ {{ form_widget(form) }} +
+{% endblock %} diff --git a/main/template/minedu/admin/questionmanager/exercise_distribution/edit.tpl b/main/template/minedu/admin/questionmanager/exercise_distribution/edit.tpl new file mode 100644 index 0000000000..17e1e1c99c --- /dev/null +++ b/main/template/minedu/admin/questionmanager/exercise_distribution/edit.tpl @@ -0,0 +1,11 @@ +{% extends app.template_style ~ "/layout/layout_1_col.tpl" %} +{% block content %} + + + {{ 'List' |trans }} + +
+
+ {{ form_widget(form) }} +
+{% endblock %} diff --git a/main/template/minedu/admin/questionmanager/exercise_distribution/list.tpl b/main/template/minedu/admin/questionmanager/exercise_distribution/list.tpl new file mode 100644 index 0000000000..e46d8e8a31 --- /dev/null +++ b/main/template/minedu/admin/questionmanager/exercise_distribution/list.tpl @@ -0,0 +1,31 @@ +{% extends app.template_style ~ "/layout/layout_1_col.tpl" %} +{% block content %} + + {{ 'Add' |trans }} + + + {% for item in items %} + + + + + + {% endfor %} +
+ + {{ item.title }} + + + {{ item.active }} + + {{ 'Edit' |trans }} + {{ 'Visible' |trans }} + {{ 'Apply distribution' |trans }} + + {{ 'Delete' |trans }} +
+{% endblock %} diff --git a/main/template/minedu/admin/questionmanager/exercise_distribution/read.tpl b/main/template/minedu/admin/questionmanager/exercise_distribution/read.tpl new file mode 100644 index 0000000000..6527886742 --- /dev/null +++ b/main/template/minedu/admin/questionmanager/exercise_distribution/read.tpl @@ -0,0 +1,31 @@ +{% extends app.template_style ~ "/layout/layout_1_col.tpl" %} +{% block content %} + + List + + +

#{{ item.id }} {{ item.branchName }}

+ + {% if item.getUsers %} +
+

{{ 'Users' | trans }}

+ + Add users + {% for branchUsers in item.getUsers %} +
  • + {{ branchUsers.user.getCompleteName }} - {{ branchUsers.role.name }} + + Remove + +
  • + {% endfor %} + {% endif %} +
    + +{% endblock %} diff --git a/src/ChamiloLMS/Controller/Admin/QuestionManager/ExerciseDistributionController.php b/src/ChamiloLMS/Controller/Admin/QuestionManager/ExerciseDistributionController.php new file mode 100644 index 0000000000..79a19e2f7f --- /dev/null +++ b/src/ChamiloLMS/Controller/Admin/QuestionManager/ExerciseDistributionController.php @@ -0,0 +1,343 @@ + + */ +class ExerciseDistributionController extends CommonController +{ + public $exerciseId = null; + + /** + * @Route("/{exerciseId}/distribution/list") + * @Method({"GET"}) + */ + public function indexAction($exerciseId) + { + $criteria = array('exerciseId' => $exerciseId); + $items = $this->getRepository()->findBy($criteria); + + $template = $this->get('template'); + $template->assign('exerciseId', $exerciseId); + $template->assign('items', $items); + $template->assign('links', $this->generateLinks()); + $response = $template->render_template($this->getTemplatePath().'list.tpl'); + return new Response($response, 200, array()); + } + + /** + * + * @Route("/{exerciseId}/distribution/{id}", requirements={"id" = "\d+"}) + * @Method({"GET"}) + */ + public function readAction($id) + { + return parent::readAction($id); + } + + /** + * + * @Route("/{exerciseId}/distribution/{id}/toggle_visibility", requirements={"id" = "\d+"}) + * @Method({"GET"}) + */ + public function toogleVisibilityAction($exerciseId, $id) + { + $criteria = array('exerciseId' => $exerciseId, 'id' => $id); + /** @var Entity\CQuizDistribution $distribution */ + $distribution = $this->getRepository()->findOneBy($criteria); + + $distribution->setActive(!$distribution->getActive()); + + $this->getManager()->persist($distribution); + $this->getManager()->flush(); + + $this->get('session')->getFlashBag()->add('success', "Visibility changed"); + $url = $this->createUrl('list_link'); + return $this->redirect($url); + } + + /** + * @Route("/{exerciseId}/distribution/{id}/apply") + * @Method({"GET"}) + */ + public function applyDistributionAction($exerciseId, $id) + { + $em = $this->getManager(); + $criteria = array('exerciseId' => $exerciseId); + $distributionRelSession = $em->getRepository('Entity\CQuizDistributionRelSession')->findOneBy($criteria); + + if ($distributionRelSession) { + $em->remove($distributionRelSession); + $em->flush(); + } + + $distributionRelSession = new Entity\CQuizDistributionRelSession(); + /*$distributionRelSession->setCId($this->getCourse()->getId()); + $distributionRelSession->setSessionId($this->getSession()->getId());*/ + + $distributionRelSession->setCId(api_get_course_int_id()); + $distributionRelSession->setSessionId(api_get_session_id()); + + $distributionRelSession->setQuizDistributionId($id); + $distributionRelSession->setExerciseId($exerciseId); + $em->persist($distributionRelSession); + $em->flush(); + + $this->get('session')->getFlashBag()->add('success', "Distribution applied"); + $url = $this->createUrl('list_link'); + return $this->redirect($url); + } + + /** + * @Route("/{exerciseId}/distribution/add") + * @Method({"GET"}) + */ + public function addDistributionAction($exerciseId) + { + $template = $this->get('template'); + $em = $this->getManager(); + $this->exerciseId = $exerciseId; + $template->assign('exerciseId', $exerciseId); + + $request = $this->getRequest(); + $distribution = $this->getDefaultEntity(); + $form = $this->createForm($this->getFormType(), $distribution); + + $form->handleRequest($request); + + if ($form->isValid()) { + + $exercise = new \Exercise(); + $exercise->read($exerciseId); + $questionList = $exercise->getQuestionList(); + $exercise->get_categories_in_exercise(); + + /** @var Entity\CQuizDistribution $distribution */ + $distribution = $form->getData(); + + $distribution->setDataTracking(implode(',', $questionList)); + $distribution->setAuthorUserId($this->getUser()->getUserId()); + + $em->persist($distribution); + // Registering quiz distribution + quiz distribution questions + if ($distribution) { + foreach ($questionList as $questionId) { + $distributionQuestion = new Entity\CQuizDistributionQuestions(); + + $questionObj = \Question::read($questionId); + $categories = $questionObj->get_categories_from_question(); + if (!empty($categories)) { + $categoryId = current($categories); + $distributionQuestion->setCategoryId($categoryId); + } + $distributionQuestion->setQuestionId($questionId); + $distributionQuestion->setDistribution($distribution); + $em->persist($distributionQuestion); + } + } + + // Checking from all distributions + $em->flush(); + + $categoriesInExercise = $exercise->get_categories_in_exercise(); + + $questionsPerCategory = array(); + foreach ($categoriesInExercise as $categoryInfo) { + $categoryId = $categoryInfo['category_id']; + $questions = \Testcategory::getQuestionsByCategory($categoryId); + $questionsPerCategory[$categoryId] = $questions; + } + + $criteria = array('quizDistributionId' => $distribution->getId()); + $currentDistributionQuestions = $this->getManager()->getRepository('Entity\CQuizDistributionQuestions')->findBy($criteria); + + /** @var Entity\CQuizDistributionQuestions $question */ + foreach ($currentDistributionQuestions as $question) { + $criteria = array('categoryId' => $question->getCategoryId(), 'questionId' => $question->getQuestionId()); + $result = $this->getManager()->getRepository('Entity\CQuizDistributionQuestions')->findBy($criteria); + + // doubles found ! + if (count($result) > 1) { + + // Question list of this category + $questionList = $questionsPerCategory[$question->getCategoryId()]; + + // Checking if there are questions that are not added yet + $qb = $this->getManager()->getRepository('Entity\CQuizDistributionQuestions')->createQueryBuilder('e'); + $qb->where('e.categoryId = :categoryId') + ->andWhere($qb->expr()->notIn('e.questionId', $questionList)) + ->setParameters(array('categoryId' => $question->getCategoryId())); + + $result = $qb->getQuery()->getArrayResult(); + // Found some questions + if (count($result) > 0) { + shuffle($result); + $selected = current($result); + } else { + // Nothing found take one question + shuffle($questionList); + $selected = current($questionList); + } + // $selected contains the new question id + if (!empty($selected)) { + //remove the old and create a new one + $newQuestionDistribution = $question; + $em->remove($question); + $newQuestionDistribution->setQuestionId($selected); + $em->persist($newQuestionDistribution); + $em->flush(); + } + } + } + + $currentDistributionQuestions = $this->getManager()->getRepository('Entity\CQuizDistributionQuestions')->findBy($criteria); + $questionList = array(); + foreach($currentDistributionQuestions as $question) { + $questionList[] = $question->getQuestionId(); + } + + // Rebuild question list + $distribution->setDataTracking(implode(',', $questionList)); + $em->persist($distribution); + $em->flush(); + + $this->get('session')->getFlashBag()->add('success', "Added"); + $url = $this->createUrl('list_link'); + return $this->redirect($url); + } + + $template = $this->get('template'); + $template->assign('links', $this->generateLinks()); + $template->assign('form', $form->createView()); + $response = $template->render_template($this->getTemplatePath().'add.tpl'); + return new Response($response, 200, array()); + } + + + /** + * + * @Route("/{exerciseId}/distribution/{id}/edit", requirements={"id" = "\d+"}) + * @Method({"GET"}) + */ + public function editDistributionAction($exerciseId, $id) + { + $repo = $this->getRepository(); + $request = $this->getRequest(); + $item = $repo->findOneById($id); + + $this->exerciseId = $exerciseId; + $template = $this->get('template'); + $template->assign('exerciseId', $exerciseId); + $template->assign('id', $id); + + if ($item) { + + $form = $this->createForm($this->getFormType(), $item); + + $form->handleRequest($request); + + if ($form->isValid()) { + $data = $form->getData(); + $this->updateAction($data); + $this->get('session')->getFlashBag()->add('success', "Updated"); + $url = $this->createUrl('list_link'); + return $this->redirect($url); + } + + $template->assign('item', $item); + $template->assign('form', $form->createView()); + $template->assign('links', $this->generateLinks()); + $response = $template->render_template($this->getTemplatePath().'edit.tpl'); + return new Response($response, 200, array()); + } else { + return $this->createNotFoundException(); + } + } + + /** + * + * @Route("/{exerciseId}/distribution/{id}/delete", requirements={"id" = "\d+"}) + * @Method({"GET"}) + */ + public function deleteDistributionAction($exerciseId, $id) + { + $this->exerciseId = $exerciseId; + $template = $this->get('template'); + $template->assign('exerciseId', $exerciseId); + + $result = $this->removeEntity($id); + if ($result) { + $url = $this->createUrl('list_link'); + $this->get('session')->getFlashBag()->add('success', "Deleted"); + + return $this->redirect($url); + } + } + + + + + + protected function getExtraParameters() + { + return array('exerciseId'); + } + + protected function getControllerAlias() + { + return 'exercise_distribution.controller'; + } + + /** + * {@inheritdoc} + */ + protected function getTemplatePath() + { + return 'admin/questionmanager/exercise_distribution/'; + } + + /** + * @return \Entity\Repository\JuryRepository + */ + protected function getRepository() + { + return $this->get('orm.em')->getRepository('Entity\CQuizDistribution'); + } + + /** + * {@inheritdoc} + */ + protected function getNewEntity() + { + return new Entity\CQuizDistribution(); + } + + protected function getDefaultEntity() + { + $dist = new Entity\CQuizDistribution(); + $dist ->setExerciseId($this->exerciseId); + return $dist; + } + + /** + * {@inheritdoc} + */ + protected function getFormType() + { + return new CQuizDistributionType(); + } +} diff --git a/src/ChamiloLMS/Controller/BaseController.php b/src/ChamiloLMS/Controller/BaseController.php index fe7d9a7321..19a1079701 100644 --- a/src/ChamiloLMS/Controller/BaseController.php +++ b/src/ChamiloLMS/Controller/BaseController.php @@ -141,6 +141,21 @@ abstract class BaseController extends FlintController if (!empty($session)) { $parameters['id_session'] = $session->getId(); } + + $extraParams = $this->getExtraParameters(); + + if (!empty($extraParams)) { + $request = $this->getRequest(); + $dynamicParams = array(); + foreach ($extraParams as $param) { + $value = $request->get($param); + if (!empty($value)) { + $dynamicParams[$param] = $value; + } + } + $parameters = array_merge($parameters, $dynamicParams); + } + if (isset($links) && is_array($links) && isset($links[$label])) { $url = $this->generateUrl($links[$label], $parameters); return $url; @@ -148,6 +163,11 @@ abstract class BaseController extends FlintController return $url = $this->generateUrl($links['list_link']); } + protected function addParameters() + { + return array(); + } + /** * @see Symfony\Component\Routing\RouterInterface::generate() */ @@ -191,16 +211,14 @@ abstract class BaseController extends FlintController $request = $this->getRequest(); $form = $this->createForm($this->getFormType(), $this->getDefaultEntity()); - if ($request->getMethod() == 'POST') { - $form->bind($request); + $form->handleRequest($request); - if ($form->isValid()) { - $item = $form->getData(); - $this->createAction($item); - $this->get('session')->getFlashBag()->add('success', "Added"); - $url = $this->createUrl('list_link'); - return $this->redirect($url); - } + if ($form->isValid()) { + $item = $form->getData(); + $this->createAction($item); + $this->get('session')->getFlashBag()->add('success', "Added"); + $url = $this->createUrl('list_link'); + return $this->redirect($url); } $template = $this->get('template'); @@ -237,15 +255,14 @@ abstract class BaseController extends FlintController $form = $this->createForm($this->getFormType(), $item); - if ($request->getMethod() == 'POST') { - $form->bind($this->getRequest()); - if ($form->isValid()) { - $data = $form->getData(); - $this->updateAction($data); - $this->get('session')->getFlashBag()->add('success', "Updated"); - $url = $this->createUrl('list_link'); - return $this->redirect($url); - } + $form->handleRequest($request); + + if ($form->isValid()) { + $data = $form->getData(); + $this->updateAction($data); + $this->get('session')->getFlashBag()->add('success', "Updated"); + $url = $this->createUrl('list_link'); + return $this->redirect($url); } $template = $this->get('template'); diff --git a/src/ChamiloLMS/Form/CQuizDistributionType.php b/src/ChamiloLMS/Form/CQuizDistributionType.php new file mode 100644 index 0000000000..50892a722c --- /dev/null +++ b/src/ChamiloLMS/Form/CQuizDistributionType.php @@ -0,0 +1,33 @@ +add('title', 'text'); + $builder->add('active', 'checkbox', array('required' => false)); + $builder->add('exerciseId', 'hidden'); + $builder->add('submit', 'submit'); + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults( + array( + 'data_class' => 'Entity\CQuizDistribution' + ) + ); + } + + public function getName() + { + return 'quiz_distribution'; + } +}