Compare commits

...

6 Commits

  1. 24
      .htaccess
  2. 44
      documentation/changelog.html
  3. 1
      main/auth/external_login/facebook.inc.php
  4. 38
      main/auth/external_login/functions.inc.php
  5. 4
      main/exercise/export/aiken/aiken_import.inc.php
  6. 2
      main/inc/lib/Compilatio.php
  7. 2
      main/inc/lib/api.lib.php
  8. 3
      main/inc/lib/formvalidator/Element/HtmlEditor.php
  9. 7
      main/inc/lib/glossary.lib.php
  10. 2
      main/lp/LpAiHelper.php
  11. 4
      main/lp/learnpath.class.php
  12. 1
      main/session/resume_session.php
  13. 2
      main/template/default/exercise/submit.js.tpl
  14. 130
      plugin/ai_helper/AiHelperPlugin.php
  15. 12
      plugin/ai_helper/src/deepseek/DeepSeek.php
  16. 2
      plugin/ai_helper/src/deepseek/DeepSeekUrl.php
  17. 3
      plugin/ai_helper/tool/answers.php
  18. 1
      plugin/ai_helper/tool/learnpath.php
  19. 46
      plugin/azure_active_directory/src/AzureActiveDirectory.php
  20. 6
      plugin/azure_active_directory/src/Entity/AzureSyncState.php

@ -34,24 +34,22 @@ RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_ur
# Rewrite everything in the document folder of a course to the download script # Rewrite everything in the document folder of a course to the download script
# Except certificate resources, which might need to be accessible publicly to all # Except certificate resources, which might need to be accessible publicly to all
RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L] RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L]
# Note : since version 2.4.38-3 of Apache a security fix had a side effect that made redirection with space not to work. # Note : since version 2.4.38-3 of Apache a security fix had a side effect that broke redirections with spaces.
# To fix this issue we did not have a common syntaxis but it work with one of those 2 options : # To fix this issue we did not have a common syntaxis but it work with one of those 2 options :
# changing at the end of the following line [QSA,L] for [QSA,L,B=\x20?] or for "[QSA,L,B= ?,BNP]" (with the quotes) # changing at the end of the following line [QSA,L] for [QSA,L,B=\x20?] or for "[QSA,L,B= ?,BNP]" (with the quotes)
RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 [QSA,L] # We have opted to use the latter by default. If you are encountering "Not found" issues when entering course homepages,
# you might want to try either of the other 2 forms.
RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 "[QSA,L,B= ?,BNP]"
# Optimize load of custom per-course icons in courses (avoid download_uploaded_files.php) # Optimize load of custom per-course icons in courses (avoid download_uploaded_files.php)
RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L] RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L]
# Course upload files # Course upload files
# Note : since version 2.4.38-3 of Apache a security fix had a side effect that made redirection with space not to work. # See note on line 37
# To fix this issue we did not have a common syntaxis but it work with one of those 2 options : RewriteRule ^courses/([^/]+)/upload/([^/]+)/(.*)$ main/document/download_uploaded_files.php?code=$1&type=$2&file=$3 "[QSA,L,B= ?,BNP]"
# changing at the end of the following line [QSA,L] for [QSA,L,B=\x20?] or for "[QSA,L,B= ?,BNP]" (with the quotes)
RewriteRule ^courses/([^/]+)/upload/([^/]+)/(.*)$ main/document/download_uploaded_files.php?code=$1&type=$2&file=$3 [QSA,L]
# Rewrite everything in the work folder # Rewrite everything in the work folder
# Note : since version 2.4.38-3 of Apache a security fix had a side effect that made redirection with space not to work. # See note on line 37
# To fix this issue we did not have a common syntaxis but it work with one of those 2 options : RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir=$1 "[QSA,L,B= ?,BNP]"
# changing at the end of the following line [QSA,L] for [QSA,L,B=\x20?] or for "[QSA,L,B= ?,BNP]" (with the quotes)
RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir=$1 [QSA,L]
RewriteRule ^courses/([^/]+)/course-pic85x85.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_source [QSA,L] RewriteRule ^courses/([^/]+)/course-pic85x85.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_source [QSA,L]
RewriteRule ^courses/([^/]+)/course-pic.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_large_source [QSA,L] RewriteRule ^courses/([^/]+)/course-pic.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_large_source [QSA,L]
@ -87,10 +85,8 @@ RewriteRule ^service/(\d{1,})$ plugin/buycourses/src/service_information.php?ser
RewriteRule ^lti/os$ plugin/ims_lti/outcome_service.php [L] RewriteRule ^lti/os$ plugin/ims_lti/outcome_service.php [L]
# Deny direct access to user my files # Deny direct access to user my files
# Note : since version 2.4.38-3 of Apache a security fix had a side effect that made redirection with space not to work. # See note on line 37
# To fix this issue we did not have a common syntaxis but it work with one of those 2 options : RewriteRule ^app/upload/users/([^/]+)/([^/]+)/my_files/(.*)$ main/social/download_my_files.php?user_id=$2&file=$3 "[QSA,L,B= ?,BNP]"
# changing at the end of the following line [QSA,L] for [QSA,L,B=\x20?] or for "[QSA,L,B= ?,BNP]" (with the quotes)
RewriteRule ^app/upload/users/([^/]+)/([^/]+)/my_files/(.*)$ main/social/download_my_files.php?user_id=$2&file=$3 [QSA,L]
# Deny access # Deny access
RewriteRule ^(tests|.git) - [F,L,NC] RewriteRule ^(tests|.git) - [F,L,NC]

@ -110,6 +110,50 @@
</table> </table>
<div class="version" aria-label="1.11.30">
<a id="1.11.30"></a>
<h1>Chamilo 1.11.30 - ?, /02/2025</h1>
<h3>Release notes - summary</h3>
<p>Chamilo 1.11.30 is a patch release on top of 1.11.28.</p>
<h3>Release name</h3>
<p><a href="https://en.wikipedia.org/wiki/">?</a> is ...</p>
<h3>Security fixes</h3>
<ul aria-live="off">
</ul>
<h3>Important note</h3>
<p>Chamilo 1.11.30 comes with subtle changes in the root .htaccess file which could affect your system (for example by triggering "Not Found" errors on course homepages) if you use Apache &lt; 2.4.38-3. Please check line 37 of /.htaccess for more info.</p>
<h3>Notable new Features</h3>
<h4>For end-users, teachers and Chamilo admins</h4>
These features are immediately available to users through the web interface.<br />
<ul aria-live="off">
</ul>
<h4>For developers and sysadmins</h4>
Although most features here will be used by teachers or Chamilo admins, they require sysadmin privileges to enable them on the server.
<ul aria-live="off">
</ul>
<h3>Improvements (minor features) and debug</h3>
In reverse chronological order...
<ul aria-live="off">
</ul>
<h3>Stylesheets and theming</h3>
<ul aria-live="off">
<li>No notable style change</li>
</ul>
<h3>Web services</h3>
<ul aria-live="off">
<li>No notable change</li>
</ul>
<h3>Removals</h3>
<ul aria-live="off">
<li>No notable removal</li>
</ul>
<h3>Known issues</h3>
<ul aria-live="off">
<li>No notable known issue</li>
</ul>
</div>
<div class="version" aria-label="1.11.28"> <div class="version" aria-label="1.11.28">
<a id="1.11.28"></a> <a id="1.11.28"></a>
<h1>Chamilo 1.11.28 - Alcatraz, 21/10/2024</h1> <h1>Chamilo 1.11.28 - Alcatraz, 21/10/2024</h1>

@ -171,7 +171,6 @@ function facebookConnect()
/** /**
* Get facebook login url for the platform. * Get facebook login url for the platform.
* *
* @return string
* @throws FacebookSDKException * @throws FacebookSDKException
*/ */
function facebookGetLoginUrl(): string function facebookGetLoginUrl(): string

@ -166,25 +166,25 @@ function external_add_user($u)
* new_user array. * new_user array.
* *
* @param array $new_user associative array with the value to upgrade * @param array $new_user associative array with the value to upgrade
* WARNING user_id key is MANDATORY * WARNING user_id key is MANDATORY
* Possible keys are : * Possible keys are :
* - firstname * - firstname
* - lastname * - lastname
* - username * - username
* - auth_source * - auth_source
* - email * - email
* - status * - status
* - official_code * - official_code
* - phone * - phone
* - picture_uri * - picture_uri
* - expiration_date * - expiration_date
* - active * - active
* - creator_id * - creator_id
* - hr_dept_id * - hr_dept_id
* - extra : array of custom fields * - extra : array of custom fields
* - language * - language
* - courses : string of all courses code separated by '|' * - courses : string of all courses code separated by '|'
* - admin : boolean * - admin : boolean
* *
* @author ndiechburg <noel@cblue.be> * @author ndiechburg <noel@cblue.be>
* */ * */

@ -70,7 +70,7 @@ function generateAikenForm()
if ($hasSingleApi) { if ($hasSingleApi) {
$apiName = $availableApis[$configuredApi] ?? $configuredApi; $apiName = $availableApis[$configuredApi] ?? $configuredApi;
$form->addHtml('<div style="margin-bottom: 10px; font-size: 14px; color: #555;">' $form->addHtml('<div style="margin-bottom: 10px; font-size: 14px; color: #555;">'
. sprintf(get_lang('UsingAIProviderX'), '<strong>'.htmlspecialchars($apiName).'</strong>').'</div>'); .sprintf(get_lang('UsingAIProviderX'), '<strong>'.htmlspecialchars($apiName).'</strong>').'</div>');
} }
$form->addElement('text', 'quiz_name', get_lang('QuestionsTopic')); $form->addElement('text', 'quiz_name', get_lang('QuestionsTopic'));
@ -105,7 +105,7 @@ function generateAikenForm()
var quizName = $("[name=\'quiz_name\']").val(); var quizName = $("[name=\'quiz_name\']").val();
var nroQ = parseInt($("[name=\'nro_questions\']").val()); var nroQ = parseInt($("[name=\'nro_questions\']").val());
var qType = $("[name=\'question_type\']").val();' var qType = $("[name=\'question_type\']").val();'
. (!$hasSingleApi ? 'var provider = $("[name=\'ai_provider\']").val();' : 'var provider = "' . $configuredApi . '";') . .(!$hasSingleApi ? 'var provider = $("[name=\'ai_provider\']").val();' : 'var provider = "'.$configuredApi.'";').
'var valid = (quizName != \'\' && nroQ > 0); 'var valid = (quizName != \'\' && nroQ > 0);
if (valid) { if (valid) {
btnGenerate.attr("disabled", true); btnGenerate.attr("disabled", true);

@ -208,7 +208,7 @@ class Compilatio
if (isset($dataDocument['analyses'][$anasim]['state'])) { if (isset($dataDocument['analyses'][$anasim]['state'])) {
$documentInfo['analysis_status'] = $dataDocument['analyses'][$anasim]['state']; $documentInfo['analysis_status'] = $dataDocument['analyses'][$anasim]['state'];
} }
if (isset($dataDocument['light_reports'][$anasim]['scores']['global_score_percent'])) { if (isset($dataDocument['light_reports'][$anasim]['scores']['global_score_percent'])) {
$documentInfo['report_percent'] = $dataDocument['light_reports'][$anasim]['scores']['global_score_percent']; $documentInfo['report_percent'] = $dataDocument['light_reports'][$anasim]['scores']['global_score_percent'];
} }

@ -4033,7 +4033,7 @@ function api_not_allowed(
// Check if a custom file (login.tpl) exists for custompages included overrides // Check if a custom file (login.tpl) exists for custompages included overrides
if ((!isset($user_id) || api_is_anonymous()) && CustomPages::enabled()) { if ((!isset($user_id) || api_is_anonymous()) && CustomPages::enabled()) {
$customLoginTemplate = Template::findTemplateFilePath('custompage/login.tpl'); $customLoginTemplate = Template::findTemplateFilePath('custompage/login.tpl');
if (file_exists(api_get_path(SYS_TEMPLATE_PATH) . $customLoginTemplate)) { if (file_exists(api_get_path(SYS_TEMPLATE_PATH).$customLoginTemplate)) {
if (empty($_SESSION['request_uri'])) { if (empty($_SESSION['request_uri'])) {
$_SESSION['request_uri'] = $_SERVER['REQUEST_URI']; $_SESSION['request_uri'] = $_SERVER['REQUEST_URI'];
} }

@ -112,9 +112,6 @@ class HtmlEditor extends HTML_QuickForm_textarea
return $result; return $result;
} }
/**
* @return string|null
*/
public function getValue(): ?string public function getValue(): ?string
{ {
return RemoveOnAttributes::filter($this->_value); return RemoveOnAttributes::filter($this->_value);

@ -132,12 +132,11 @@ class GlossaryManager
* This functions stores the glossary in the database. * This functions stores the glossary in the database.
* *
* @param array $values Array of title + description (name => $title, description => $comment) * @param array $values Array of title + description (name => $title, description => $comment)
* @param bool $showMessage
*
* @return bool|int Term id on success, false on failure
* *
* @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\OptimisticLockException
*
* @return bool|int Term id on success, false on failure
*/ */
public static function save_glossary(array $values, bool $showMessage = true) public static function save_glossary(array $values, bool $showMessage = true)
{ {
@ -170,7 +169,7 @@ class GlossaryManager
->setDescription($values['description'] ?? "") ->setDescription($values['description'] ?? "")
->setDisplayOrder($max_glossary_item + 1) ->setDisplayOrder($max_glossary_item + 1)
->setSessionId($session_id); ->setSessionId($session_id);
;
Database::getManager()->persist($glossary); Database::getManager()->persist($glossary);
Database::getManager()->flush(); Database::getManager()->flush();

@ -74,7 +74,7 @@ class LpAiHelper
var wordsCount = parseInt($("[name=\'words_count\']").val()); var wordsCount = parseInt($("[name=\'words_count\']").val());
var addTests = $("#add-lp-quiz").is(":checked"); var addTests = $("#add-lp-quiz").is(":checked");
var nroQuestions = parseInt($("[name=\'nro_questions\']").val()); var nroQuestions = parseInt($("[name=\'nro_questions\']").val());
var provider = "' . $configuredApi . '"; var provider = "'.$configuredApi.'";
if (lpName && nroItems > 0 && wordsCount > 0) { if (lpName && nroItems > 0 && wordsCount > 0) {
if (addTests && (nroQuestions <= 0 || nroQuestions > 5)) { if (addTests && (nroQuestions <= 0 || nroQuestions > 5)) {

@ -4860,12 +4860,8 @@ class learnpath
/** /**
* Check if the learnpath category is visible for a user. * Check if the learnpath category is visible for a user.
* *
* @param CLpCategory|null $category
* @param User $user
* @param int $courseId * @param int $courseId
* @param int $sessionId * @param int $sessionId
*
* @return bool
*/ */
public static function categoryIsVisibleForStudent( public static function categoryIsVisibleForStudent(
?CLpCategory $category, ?CLpCategory $category,

@ -370,6 +370,7 @@ if (!empty($userList)) {
$valueA = strtotime($a['registered_at']); $valueA = strtotime($a['registered_at']);
$valueB = strtotime($b['registered_at']); $valueB = strtotime($b['registered_at']);
} }
return $sortOrder === SORT_ASC ? $valueA <=> $valueB : $valueB <=> $valueA; return $sortOrder === SORT_ASC ? $valueA <=> $valueB : $valueB <=> $valueA;
}); });

@ -61,12 +61,14 @@ var DraggableAnswer = {
DraggableAnswer.trash.droppable({ DraggableAnswer.trash.droppable({
accept: ".exercise-draggable-answer > li.touch-items", accept: ".exercise-draggable-answer > li.touch-items",
tolerance: "pointer",
drop: function (e, ui) { drop: function (e, ui) {
DraggableAnswer.deleteItem(ui.draggable, $(this)); DraggableAnswer.deleteItem(ui.draggable, $(this));
} }
}); });
DraggableAnswer.gallery.droppable({ DraggableAnswer.gallery.droppable({
tolerance: "pointer",
drop: function (e, ui) { drop: function (e, ui) {
DraggableAnswer.recycleItem(ui.draggable, $(this)); DraggableAnswer.recycleItem(ui.draggable, $(this));
} }

@ -3,7 +3,8 @@
use Chamilo\PluginBundle\Entity\AiHelper\Requests; use Chamilo\PluginBundle\Entity\AiHelper\Requests;
use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\Tools\SchemaTool;
require_once __DIR__ . '/src/deepseek/DeepSeek.php';
require_once __DIR__.'/src/deepseek/DeepSeek.php';
/** /**
* Description of AiHelperPlugin. * Description of AiHelperPlugin.
* *
@ -112,6 +113,7 @@ class AiHelperPlugin extends Plugin
if (isset($result['error'])) { if (isset($result['error'])) {
$errorMessage = $result['error']['message'] ?? 'Unknown error'; $errorMessage = $result['error']['message'] ?? 'Unknown error';
error_log("OpenAI Error: $errorMessage"); error_log("OpenAI Error: $errorMessage");
return [ return [
'error' => true, 'error' => true,
'message' => $errorMessage, 'message' => $errorMessage,
@ -132,11 +134,10 @@ class AiHelperPlugin extends Plugin
} }
return $resultText ?: 'No response generated.'; return $resultText ?: 'No response generated.';
} catch (Exception $e) { } catch (Exception $e) {
return [ return [
'error' => true, 'error' => true,
'message' => 'An error occurred while connecting to OpenAI: ' . $e->getMessage(), 'message' => 'An error occurred while connecting to OpenAI: '.$e->getMessage(),
]; ];
} }
} }
@ -179,8 +180,9 @@ class AiHelperPlugin extends Plugin
$response = curl_exec($ch); $response = curl_exec($ch);
if ($response === false) { if ($response === false) {
error_log('cURL error: ' . curl_error($ch)); error_log('cURL error: '.curl_error($ch));
curl_close($ch); curl_close($ch);
return ['error' => true, 'message' => 'Request to AI provider failed.']; return ['error' => true, 'message' => 'Request to AI provider failed.'];
} }
@ -211,12 +213,14 @@ class AiHelperPlugin extends Plugin
/** /**
* Generate questions based on the selected AI provider. * Generate questions based on the selected AI provider.
* *
* @param int $nQ Number of questions * @param int $nQ Number of questions
* @param string $lang Language for the questions * @param string $lang Language for the questions
* @param string $topic Topic of the questions * @param string $topic Topic of the questions
* @param string $questionType Type of questions (e.g., 'multiple_choice') * @param string $questionType Type of questions (e.g., 'multiple_choice')
* @return string Questions generated in Aiken format *
* @throws Exception If an error occurs * @throws Exception If an error occurs
*
* @return string Questions generated in Aiken format
*/ */
public function generateQuestions(int $nQ, string $lang, string $topic, string $questionType = 'multiple_choice'): string public function generateQuestions(int $nQ, string $lang, string $topic, string $questionType = 'multiple_choice'): string
{ {
@ -232,61 +236,6 @@ class AiHelperPlugin extends Plugin
} }
} }
/**
* Generate questions using OpenAI.
*/
private function generateOpenAiQuestions(int $nQ, string $lang, string $topic, string $questionType): string
{
$prompt = sprintf(
'Generate %d "%s" questions in Aiken format in the %s language about "%s", making sure there is a \'ANSWER\' line for each question. \'ANSWER\' lines must only mention the letter of the correct answer, not the full answer text and not a parenthesis. The line starting with \'ANSWER\' must not be separated from the last possible answer by a blank line. Each answer starts with an uppercase letter, a dot, one space and the answer text without quotes. Include an \'ANSWER_EXPLANATION\' line after the \'ANSWER\' line for each question. The terms between single quotes above must not be translated. There must be a blank line between each question.',
$nQ,
$questionType,
$lang,
$topic
);
$result = $this->openAiGetCompletionText($prompt, 'quiz');
if (isset($result['error']) && true === $result['error']) {
throw new Exception($result['message']);
}
return $result;
}
/**
* Generate questions using DeepSeek.
*/
private function generateDeepSeekQuestions(int $nQ, string $lang, string $topic, string $questionType): string
{
$apiKey = $this->get('api_key');
$prompt = sprintf(
'Generate %d "%s" questions in Aiken format in the %s language about "%s", making sure there is a \'ANSWER\' line for each question. \'ANSWER\' lines must only mention the letter of the correct answer, not the full answer text and not a parenthesis. The line starting with \'ANSWER\' must not be separated from the last possible answer by a blank line. Each answer starts with an uppercase letter, a dot, one space and the answer text without quotes. Include an \'ANSWER_EXPLANATION\' line after the \'ANSWER\' line for each question. The terms between single quotes above must not be translated. There must be a blank line between each question.',
$nQ,
$questionType,
$lang,
$topic
);
$payload = [
'model' => 'deepseek-chat',
'messages' => [
[
'role' => 'system',
'content' => 'You are a helpful assistant that generates Aiken format questions.',
],
[
'role' => 'user',
'content' => $prompt,
],
],
'stream' => false,
];
$deepSeek = new DeepSeek($apiKey);
$response = $deepSeek->generateQuestions($payload);
return $response;
}
/** /**
* Validates tokens limit of a user per current month. * Validates tokens limit of a user per current month.
*/ */
@ -414,4 +363,59 @@ class AiHelperPlugin extends Plugin
] ]
); );
} }
/**
* Generate questions using OpenAI.
*/
private function generateOpenAiQuestions(int $nQ, string $lang, string $topic, string $questionType): string
{
$prompt = sprintf(
'Generate %d "%s" questions in Aiken format in the %s language about "%s", making sure there is a \'ANSWER\' line for each question. \'ANSWER\' lines must only mention the letter of the correct answer, not the full answer text and not a parenthesis. The line starting with \'ANSWER\' must not be separated from the last possible answer by a blank line. Each answer starts with an uppercase letter, a dot, one space and the answer text without quotes. Include an \'ANSWER_EXPLANATION\' line after the \'ANSWER\' line for each question. The terms between single quotes above must not be translated. There must be a blank line between each question.',
$nQ,
$questionType,
$lang,
$topic
);
$result = $this->openAiGetCompletionText($prompt, 'quiz');
if (isset($result['error']) && true === $result['error']) {
throw new Exception($result['message']);
}
return $result;
}
/**
* Generate questions using DeepSeek.
*/
private function generateDeepSeekQuestions(int $nQ, string $lang, string $topic, string $questionType): string
{
$apiKey = $this->get('api_key');
$prompt = sprintf(
'Generate %d "%s" questions in Aiken format in the %s language about "%s", making sure there is a \'ANSWER\' line for each question. \'ANSWER\' lines must only mention the letter of the correct answer, not the full answer text and not a parenthesis. The line starting with \'ANSWER\' must not be separated from the last possible answer by a blank line. Each answer starts with an uppercase letter, a dot, one space and the answer text without quotes. Include an \'ANSWER_EXPLANATION\' line after the \'ANSWER\' line for each question. The terms between single quotes above must not be translated. There must be a blank line between each question.',
$nQ,
$questionType,
$lang,
$topic
);
$payload = [
'model' => 'deepseek-chat',
'messages' => [
[
'role' => 'system',
'content' => 'You are a helpful assistant that generates Aiken format questions.',
],
[
'role' => 'user',
'content' => $prompt,
],
],
'stream' => false,
];
$deepSeek = new DeepSeek($apiKey);
$response = $deepSeek->generateQuestions($payload);
return $response;
}
} }

@ -21,8 +21,10 @@ class DeepSeek
* Generate questions using the DeepSeek API. * Generate questions using the DeepSeek API.
* *
* @param array $payload Data to send to the API * @param array $payload Data to send to the API
* @return string Decoded response from the API *
* @throws Exception If an error occurs during the request * @throws Exception If an error occurs during the request
*
* @return string Decoded response from the API
*/ */
public function generateQuestions(array $payload): string public function generateQuestions(array $payload): string
{ {
@ -51,11 +53,13 @@ class DeepSeek
/** /**
* Send a request to the DeepSeek API. * Send a request to the DeepSeek API.
* *
* @param string $url Endpoint to send the request to * @param string $url Endpoint to send the request to
* @param string $method HTTP method (e.g., GET, POST) * @param string $method HTTP method (e.g., GET, POST)
* @param array $data Data to send as JSON * @param array $data Data to send as JSON
* @return string Raw response from the API *
* @throws Exception If a cURL error occurs * @throws Exception If a cURL error occurs
*
* @return string Raw response from the API
*/ */
private function sendRequest(string $url, string $method, array $data = []): string private function sendRequest(string $url, string $method, array $data = []): string
{ {

@ -12,6 +12,6 @@ class DeepSeekUrl
*/ */
public static function completionsUrl(): string public static function completionsUrl(): string
{ {
return self::BASE_URL . '/completions'; return self::BASE_URL.'/completions';
} }
} }

@ -22,10 +22,9 @@ try {
'text' => trim($resultText), 'text' => trim($resultText),
]); ]);
} catch (Exception $e) { } catch (Exception $e) {
error_log("Error: " . $e->getMessage()); error_log("Error: ".$e->getMessage());
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'text' => $e->getMessage(), 'text' => $e->getMessage(),
]); ]);
} }

@ -22,7 +22,6 @@ if (!in_array($apiName, array_keys($apiList))) {
exit; exit;
} }
$courseLanguage = (string) $_REQUEST['language']; $courseLanguage = (string) $_REQUEST['language'];
$chaptersCount = (int) $_REQUEST['nro_items']; $chaptersCount = (int) $_REQUEST['nro_items'];
$topic = (string) $_REQUEST['lp_name']; $topic = (string) $_REQUEST['lp_name'];

@ -384,6 +384,29 @@ class AzureActiveDirectory extends Plugin
]; ];
} }
public function getSyncState(string $title): ?AzureSyncState
{
$stateRepo = Database::getManager()->getRepository(AzureSyncState::class);
return $stateRepo->findOneBy(['title' => $title]);
}
public function saveSyncState(string $title, $value)
{
$state = $this->getSyncState($title);
if (!$state) {
$state = new AzureSyncState();
$state->setTitle($title);
Database::getManager()->persist($state);
}
$state->setValue($value);
Database::getManager()->flush();
}
/** /**
* @throws Exception * @throws Exception
*/ */
@ -425,27 +448,4 @@ class AzureActiveDirectory extends Plugin
$extra, $extra,
]; ];
} }
public function getSyncState(string $title): ?AzureSyncState
{
$stateRepo = Database::getManager()->getRepository(AzureSyncState::class);
return $stateRepo->findOneBy(['title' => $title]);
}
public function saveSyncState(string $title, $value)
{
$state = $this->getSyncState($title);
if (!$state) {
$state = new AzureSyncState();
$state->setTitle($title);
Database::getManager()->persist($state);
}
$state->setValue($value);
Database::getManager()->flush();
}
} }

@ -21,8 +21,6 @@ class AzureSyncState
public const USERGROUPS_DATALINK = 'usergroups_datalink'; public const USERGROUPS_DATALINK = 'usergroups_datalink';
/** /**
* @var int
*
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\Id() * @ORM\Id()
* @ORM\GeneratedValue() * @ORM\GeneratedValue()
@ -30,15 +28,11 @@ class AzureSyncState
private int $id = 0; private int $id = 0;
/** /**
* @var string
*
* @ORM\Column(name="title", type="string") * @ORM\Column(name="title", type="string")
*/ */
private string $title; private string $title;
/** /**
* @var string
*
* @ORM\Column(name="value", type="text") * @ORM\Column(name="value", type="text")
*/ */
private string $value; private string $value;

Loading…
Cancel
Save