diff --git a/console.php b/console.php index 99e1cf3360..903b8fec1d 100755 --- a/console.php +++ b/console.php @@ -76,6 +76,11 @@ $cli->addCommands( // Chamilo commands. new ChamiloLMS\Command\Template\AsseticDumpCommand(), + new ChamiloLMS\Command\Transaction\ImportToSystemCommand(), + new ChamiloLMS\Command\Transaction\ReceiveCommand(), + new ChamiloLMS\Command\Transaction\ProcessReceivedEnvelopesCommand(), + new ChamiloLMS\Command\Transaction\SendCommand(), + new ChamiloLMS\Command\Transaction\MineduSendCommand(), new ChamiloLMS\Command\Translation\ExportLanguagesCommand(), // Chash commands. diff --git a/main/exercice/exercise.class.php b/main/exercice/exercise.class.php index 4bc6eec5cf..2bdfa1ba23 100644 --- a/main/exercice/exercise.class.php +++ b/main/exercice/exercise.class.php @@ -148,8 +148,8 @@ class Exercise } $this->course_id = $course_info['real_id']; $this->course = $course_info; - $this->fastEdition = api_get_course_setting('allow_fast_exercise_edition') == 1 ? true : false; - $this->emailAlert = api_get_course_setting('email_alert_manager_on_new_quiz') == 1 ? true : false; + $this->fastEdition = api_get_course_setting('allow_fast_exercise_edition', $course_info['code']) == 1 ? true : false; + $this->emailAlert = api_get_course_setting('email_alert_manager_on_new_quiz', $course_info['code']) == 1 ? true : false; $this->hideQuestionTitle = 0; } @@ -5324,12 +5324,12 @@ class Exercise * * @return array */ - private function setMediaList($questionList) + private function setMediaList($questionList, $course_id) { $mediaList= array(); if (!empty($questionList)) { foreach ($questionList as $questionId) { - $objQuestionTmp = Question::read($questionId); + $objQuestionTmp = Question::read($questionId, $course_id); // If a media question exists if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) { @@ -5496,7 +5496,7 @@ class Exercise } } - $this->setMediaList($questionList); + $this->setMediaList($questionList, $this->course_id); $this->questionList = $this->transformQuestionListWithMedias($questionList, false); $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true); diff --git a/main/exercice/fill_blanks.class.php b/main/exercice/fill_blanks.class.php index fb55e0d88e..bd6de73917 100644 --- a/main/exercice/fill_blanks.class.php +++ b/main/exercice/fill_blanks.class.php @@ -30,9 +30,9 @@ class FillBlanks extends Question /** * Constructor */ - public function FillBlanks() + public function FillBlanks($course_code = null) { - parent::question(); + parent::question($course_code); $this->type = FILL_IN_BLANKS; $this->isContent = $this->getIsContent(); } diff --git a/main/exercice/freeanswer.class.php b/main/exercice/freeanswer.class.php index 7976ebecb4..8f620894b6 100644 --- a/main/exercice/freeanswer.class.php +++ b/main/exercice/freeanswer.class.php @@ -24,9 +24,9 @@ class FreeAnswer extends Question /** * Constructor */ - public function __construct() + public function __construct($course_code = null) { - parent::question(); + parent::question($course_code); $this->type = FREE_ANSWER; $this->isContent = $this->getIsContent(); } diff --git a/main/exercice/global_multiple_answer.class.php b/main/exercice/global_multiple_answer.class.php index 611c677e7a..7ee1cc44a9 100644 --- a/main/exercice/global_multiple_answer.class.php +++ b/main/exercice/global_multiple_answer.class.php @@ -10,9 +10,9 @@ class GlobalMultipleAnswer extends Question /** * */ - public function GlobalMultipleAnswer() + public function GlobalMultipleAnswer($course_code = null) { - parent::question(); + parent::question($course_code); $this->type = GLOBAL_MULTIPLE_ANSWER; $this->isContent = $this->getIsContent(); } diff --git a/main/exercice/hotspot.class.php b/main/exercice/hotspot.class.php index 860885c3e3..ba75de8032 100644 --- a/main/exercice/hotspot.class.php +++ b/main/exercice/hotspot.class.php @@ -24,8 +24,8 @@ class HotSpot extends Question { static $typePicture = 'hotspot.gif'; static $explanationLangVar = 'HotSpot'; - function HotSpot() { - parent::question(); + function HotSpot($course_code = null) { + parent::question($course_code); $this -> type = HOT_SPOT; } diff --git a/main/exercice/matching.class.php b/main/exercice/matching.class.php index 5b7d07a5ed..a89ee1ec54 100644 --- a/main/exercice/matching.class.php +++ b/main/exercice/matching.class.php @@ -25,9 +25,9 @@ class Matching extends Question /** * Constructor */ - function Matching() + function Matching($course_code = null) { - parent::question(); + parent::question($course_code); $this->type = MATCHING; $this->isContent = $this->getIsContent(); } diff --git a/main/exercice/media_question.class.php b/main/exercice/media_question.class.php index 25d1fa5f53..b92dc180db 100644 --- a/main/exercice/media_question.class.php +++ b/main/exercice/media_question.class.php @@ -6,9 +6,9 @@ class MediaQuestion extends Question static $typePicture = 'media-question.png'; static $explanationLangVar = 'MediaQuestion'; - public function __construct() + public function __construct($course_code = null) { - parent::question(); + parent::question($course_code); $this->type = MEDIA_QUESTION; } diff --git a/main/exercice/multiple_answer.class.php b/main/exercice/multiple_answer.class.php index 7a80f89d86..2e50926a94 100644 --- a/main/exercice/multiple_answer.class.php +++ b/main/exercice/multiple_answer.class.php @@ -26,9 +26,9 @@ class MultipleAnswer extends Question /** * Constructor */ - public function MultipleAnswer() + public function MultipleAnswer($course_code = null) { - parent::question(); + parent::question($course_code); $this->type = MULTIPLE_ANSWER; $this->isContent = $this->getIsContent(); } diff --git a/main/exercice/multiple_answer_combination.class.php b/main/exercice/multiple_answer_combination.class.php index 353566fb80..5d774eecbb 100644 --- a/main/exercice/multiple_answer_combination.class.php +++ b/main/exercice/multiple_answer_combination.class.php @@ -25,9 +25,9 @@ class MultipleAnswerCombination extends Question /** * Constructor */ - public function MultipleAnswerCombination() + public function MultipleAnswerCombination($course_code = null) { - parent::question(); + parent::question($course_code); $this->type = MULTIPLE_ANSWER_COMBINATION; $this->isContent = $this->getIsContent(); } diff --git a/main/exercice/multiple_answer_true_false.class.php b/main/exercice/multiple_answer_true_false.class.php index 90cbde4963..c37eb5b78c 100644 --- a/main/exercice/multiple_answer_true_false.class.php +++ b/main/exercice/multiple_answer_true_false.class.php @@ -25,9 +25,9 @@ class MultipleAnswerTrueFalse extends Question /** * Constructor */ - public function MultipleAnswerTrueFalse() + public function MultipleAnswerTrueFalse($course_code = null) { - parent::question(); + parent::question($course_code); $this->type = MULTIPLE_ANSWER_TRUE_FALSE; $this->isContent = $this->getIsContent(); $this->options = array(1 => get_lang('True'), 2 => get_lang('False'), 3 => get_lang('DoubtScore')); diff --git a/main/exercice/oral_expression.class.php b/main/exercice/oral_expression.class.php index 4c4737feac..efd1befe98 100644 --- a/main/exercice/oral_expression.class.php +++ b/main/exercice/oral_expression.class.php @@ -23,8 +23,8 @@ class OralExpression extends Question /** * Constructor */ - function OralExpression(){ - parent::question(); + function OralExpression($course_code = null){ + parent::question($course_code); $this -> type = ORAL_EXPRESSION; $this -> isContent = $this-> getIsContent(); } diff --git a/main/exercice/question.class.php b/main/exercice/question.class.php index 585e60fee2..7ee506323a 100644 --- a/main/exercice/question.class.php +++ b/main/exercice/question.class.php @@ -73,7 +73,7 @@ abstract class Question * * @author - Olivier Brouckaert */ - public function Question() + public function Question($course_code = null) { $this->id = 0; $this->question = ''; @@ -86,7 +86,7 @@ abstract class Question //with an special hotspot: final_overlap, final_missing, final_excess $this->extra = ''; $this->exerciseList = array(); - $this->course = api_get_course_info(); + $this->course = api_get_course_info($course_code); $this->category_list = array(); $this->parent_id = 0; $this->editionMode = 'normal'; @@ -165,7 +165,7 @@ abstract class Question // if the question has been found if ($object = Database::fetch_object($result)) { - $objQuestion = Question::getInstance($object->type); + $objQuestion = Question::getInstance($object->type, null, $course_info['code']); if (!empty($objQuestion)) { $objQuestion->id = $id; @@ -1329,14 +1329,14 @@ abstract class Question * @param \Exercise * @return \Question an instance of a Question subclass (or of Questionc class by default) */ - public static function getInstance($type, Exercise $exercise = null) + public static function getInstance($type, Exercise $exercise = null, $course_code = null) { if (!is_null($type)) { list($file_name, $class_name) = self::get_question_type($type); if (!empty($file_name)) { include_once $file_name; if (class_exists($class_name)) { - $obj = new $class_name(); + $obj = new $class_name($course_code); $obj->exercise = $exercise; return $obj; } else { diff --git a/main/exercice/unique_answer.class.php b/main/exercice/unique_answer.class.php index 7313e1c412..23c44ef37d 100644 --- a/main/exercice/unique_answer.class.php +++ b/main/exercice/unique_answer.class.php @@ -29,10 +29,10 @@ class UniqueAnswer extends Question /** * Constructor */ - public function UniqueAnswer() + public function UniqueAnswer($course_code = null) { //this is highly important - parent::question(); + parent::question($course_code); $this->type = UNIQUE_ANSWER; $this->isContent = $this->getIsContent(); } diff --git a/main/exercice/unique_answer_no_option.class.php b/main/exercice/unique_answer_no_option.class.php index 932f3eb5e7..247fd20a58 100644 --- a/main/exercice/unique_answer_no_option.class.php +++ b/main/exercice/unique_answer_no_option.class.php @@ -30,9 +30,9 @@ class UniqueAnswerNoOption extends Question /** * Constructor */ - function UniqueAnswerNoOption(){ + function UniqueAnswerNoOption($course_code = null){ //this is highly important - parent::question(); + parent::question($course_code); $this -> type = UNIQUE_ANSWER_NO_OPTION; $this -> isContent = $this-> getIsContent(); } diff --git a/main/inc/lib/api.lib.php b/main/inc/lib/api.lib.php index 63f67e7e20..d9124ea367 100644 --- a/main/inc/lib/api.lib.php +++ b/main/inc/lib/api.lib.php @@ -1128,6 +1128,10 @@ function api_is_self_registration_allowed() { * @return integer the id of the current user, 0 if is empty */ function api_get_user_id() { + if (PHP_SAPI == 'cli') { + // Do not try to call session on CLI. + return 0; + } $userInfo = Session::read('_user'); if ($userInfo && isset($userInfo['user_id'])) { return $userInfo['user_id']; @@ -2154,6 +2158,16 @@ function api_get_session_condition($session_id, $and = true, $with_base_content * @author Bart Mollet */ function api_get_setting($variable, $key = null) { + if (PHP_SAPI == 'cli') { + // Do not use session on CLI. + // @todo Support key. + $variable_data = api_get_settings_params_simple(array("variable = '?'" => $variable)); + if (isset($variable_data['selected_value'])) { + return $variable_data['selected_value']; + } + return ''; + } + $_setting = Session::read('_setting'); if ($variable == 'header_extra_content') { $filename = api_get_path(SYS_PATH).api_get_home_path().'header_extra_content.txt'; diff --git a/main/inc/lib/database.constants.inc.php b/main/inc/lib/database.constants.inc.php index fbf0992371..b802429bee 100644 --- a/main/inc/lib/database.constants.inc.php +++ b/main/inc/lib/database.constants.inc.php @@ -347,6 +347,7 @@ define('TABLE_GRADE_MODEL', 'grade_model'); define('TABLE_GRADE_MODEL_COMPONENTS', 'grade_components'); // Transaction related tables +define('TABLE_BRANCH_REL_SESSION', 'branch_rel_session'); define('TABLE_BRANCH_SYNC', 'branch_sync'); define('TABLE_BRANCH_TRANSACTION', 'branch_transaction'); define('TABLE_BRANCH_TRANSACTION_STATUS', 'branch_transaction_status'); diff --git a/src/ChamiloLMS/Command/Transaction/ImportToSystemCommand.php b/src/ChamiloLMS/Command/Transaction/ImportToSystemCommand.php new file mode 100644 index 0000000000..82591a6f49 --- /dev/null +++ b/src/ChamiloLMS/Command/Transaction/ImportToSystemCommand.php @@ -0,0 +1,35 @@ +setName('tx:import-to-system') + ->setDescription('Imports transactions on the transaction table to the local system.') + ->addOption('limit', null, InputOption::VALUE_OPTIONAL, 'The maximum number of transactions to import into the system in this operation.', 10); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $tc = new TransactionLogController(); + $limit = (int) $input->getOption('limit'); + if ($limit <= 0) { + $limit = 10; + } + $imported_ids = $tc->importPendingToSystem($limit); + $output->writeln(sprintf('Imported correctly (%d) transactions to the system.', count($imported_ids['success']))); + if (!empty($imported_ids['fail'])) { + $output->writeln(sprintf('The transactions identified by the following ids failed to be imported: (%s)', implode(', ', $imported_ids['fail']))); + return 1; + } + } +} diff --git a/src/ChamiloLMS/Command/Transaction/MineduSendCommand.php b/src/ChamiloLMS/Command/Transaction/MineduSendCommand.php new file mode 100644 index 0000000000..04baa588bc --- /dev/null +++ b/src/ChamiloLMS/Command/Transaction/MineduSendCommand.php @@ -0,0 +1,56 @@ +setName('minedu:send') + ->setDescription('Sends data using tx:send, using custom minedu logic to convert turn numbers into a course/session pair.') + ->addArgument('turn', InputArgument::REQUIRED, 'The turn to be used.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + // This is just wrong, but was requested. Used to pass a session_id to + // be used on the MineduAuthHttpsPostSend plugin. + global $session_id; + + $turn = $input->getArgument('turn'); + $branch_rel_session_table = Database::get_main_table(TABLE_BRANCH_REL_SESSION); + $results = Database::select('session_id', $branch_rel_session_table, array('where'=> array('display_order = ?' => array($turn)))); + if (empty($results)) { + $output->writeln(sprintf('Failed to retrive a session id for the given turn "%s".', $turn)); + return 100; + } + $row = array_shift($results); + $session_id = $row['session_id']; + $command = $this->getApplication()->find('tx:send'); + $arguments = array( + 'command' => 'tx:send', + '--session' => $session_id, + ); + $input = new ArrayInput($arguments); + $return_code = $command->run($input, $output); + if ($return_code !== 0) { + $output->writeln('Failed trying to send the turn information.'); + return $return_code; + } + } +} diff --git a/src/ChamiloLMS/Command/Transaction/ProcessReceivedEnvelopesCommand.php b/src/ChamiloLMS/Command/Transaction/ProcessReceivedEnvelopesCommand.php new file mode 100644 index 0000000000..49a0f26940 --- /dev/null +++ b/src/ChamiloLMS/Command/Transaction/ProcessReceivedEnvelopesCommand.php @@ -0,0 +1,35 @@ +setName('tx:process-received-envelopes') + ->setDescription('Process already received envelopes to extract its transactions to the transactions table.') + ->addOption('limit', null, InputOption::VALUE_OPTIONAL, 'The maximum number of received envelopes to process in this operation.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $tc = new TransactionLogController(); + $limit = (int) $input->getOption('limit'); + if ($limit <= 0) { + $limit = 10; + } + $imported_transaction_ids_by_envelope = $tc->importPendingEnvelopes($limit); + $total_transactions = 0; + foreach ($imported_transaction_ids_by_envelope as $envelope_id => $imported_transaction_ids) { + $total_transactions += count($imported_transaction_ids); + } + $output->writeln(sprintf('Imported correctly (%d) transactions from (%d) envelopes to the transactions table.', $total_transactions, count($imported_transaction_ids_by_envelope))); + } +} diff --git a/src/ChamiloLMS/Command/Transaction/ReceiveCommand.php b/src/ChamiloLMS/Command/Transaction/ReceiveCommand.php new file mode 100644 index 0000000000..443726c0a6 --- /dev/null +++ b/src/ChamiloLMS/Command/Transaction/ReceiveCommand.php @@ -0,0 +1,31 @@ +setName('tx:receive') + ->setDescription('Runs local branch associated receive plugin processing envelope reception.') + ->addOption('limit', null, InputOption::VALUE_OPTIONAL, 'The maximum number of envelopes to receive in this operation.', 1); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $tc = new TransactionLogController(); + $limit = (int) $input->getOption('limit'); + if ($limit <= 0) { + $limit = 1; + } + $envelopes = $tc->receiveEnvelopeData($limit); + $output->writeln(sprintf('Correctly received (%d) envelopes now added to the queue.', count($envelopes))); + } +} diff --git a/src/ChamiloLMS/Command/Transaction/SendCommand.php b/src/ChamiloLMS/Command/Transaction/SendCommand.php new file mode 100644 index 0000000000..d48b936c3d --- /dev/null +++ b/src/ChamiloLMS/Command/Transaction/SendCommand.php @@ -0,0 +1,105 @@ +local_branch = $app['orm.em']->getRepository('Entity\BranchSync')->getLocalBranch(); + } + + /** + * @todo Let accept multiple values on options. Notice Database::select() + * does not allow multiple values. + */ + protected function configure() + { + $this + ->setName('tx:send') + ->setDescription('Selects a subset of transactions, wraps and send them using local branch configuration.') + ->addOption('course', null, InputOption::VALUE_OPTIONAL, 'A course ID to select transactions.', '') + ->addOption('session', null, InputOption::VALUE_OPTIONAL, 'A session ID to select transactions.', ''); + } + + /** + * Convert input options into TransactionLogController::load() conditions. + * + * @todo Validate arguments? + */ + protected function getSelectConditions(InputInterface $input) + { + $conditions = array( + 'branch_id' => $this->local_branch->getId(), + 'status_id' => TransactionLog::STATUS_LOCAL, + ); + // Course. + $course_string = $input->getOption('course'); + if (strlen($course_string) > 0) { + $conditions['c_id'] = $course_string; + } + // Session. + $session_string = $input->getOption('session'); + if (strlen($session_string) > 0) { + $conditions['session_id'] = $session_string; + } + return $conditions; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $tc = new TransactionLogController(); + $transactions = $tc->load($this->getSelectConditions($input)); + if (empty($transactions)) { + $output->writeln('No transactions to be exported'); + return 1; + } + + // Export. + $export_result = $tc->exportTransactions($transactions); + if (!empty($export_result['fail'])) { + $fail_data = print_r($export_result['fail'], 1); + $output->writeln('Failed exporting some transactions:'); + $output->writeln("$fail_data"); + return 2; + } + + // Create an evelope and wrap it. + $wrapper = TransactionLogController::createPlugin('wrapper', $this->local_branch->getPluginEnvelope(), $this->local_branch->getPluginData('wrapper')); + $envelope_data = array('transactions' => $transactions, 'origin_branch_id' => $this->local_branch->getId()); + $envelope = new Envelope($wrapper, $envelope_data); + try { + $envelope->wrap(); + } + catch (Exception $e) { + $output->writeln(sprintf('Failed wrapping the envelope: %s.', $e->getMessage())); + return 3; + } + + // Finally send it. + $success = $tc->sendEnvelope($envelope); + if ($success !== TRUE) { + $output->writeln('There was a problem while sending the envelope.'); + return 4; + } + $output->writeln('Envelope sent!'); + } +} diff --git a/src/ChamiloLMS/Transaction/Plugin/AuthHttpsPostSend.php b/src/ChamiloLMS/Transaction/Plugin/AuthHttpsPostSend.php index 42d62b11a2..0260e92ac9 100644 --- a/src/ChamiloLMS/Transaction/Plugin/AuthHttpsPostSend.php +++ b/src/ChamiloLMS/Transaction/Plugin/AuthHttpsPostSend.php @@ -71,7 +71,7 @@ class AuthHttpsPostSend implements SendPluginInterface throw new SendException('auth_https_post: Cannot retrieve blob from envelope.'); } - if (!$blob_file = $this->getTemporaryFile('blob_file')) { + if (!$blob_file = $this->getTemporaryFileToSend('blob_file', $envelope)) { throw new SendException(sprintf('auth_https_post: Unable to create correctly the temporary blob file on "%s".', $blob_file)); } if (file_put_contents($blob_file, $blob) === false) { @@ -112,7 +112,7 @@ class AuthHttpsPostSend implements SendPluginInterface * @see SslSignedJsonWrapper. * @todo Unify on a common base plugin parent. */ - protected function getTemporaryFile($name) { + protected function getTemporaryFileToSend($name, Envelope $envelope) { static $tmp_directory; if (!isset($tmp_directory)) { $tmp_directory = api_get_path(SYS_DATA_PATH) . 'transaction_tmp_files'; diff --git a/src/ChamiloLMS/Transaction/Plugin/MineduAuthHttpsPostSend.php b/src/ChamiloLMS/Transaction/Plugin/MineduAuthHttpsPostSend.php new file mode 100644 index 0000000000..d181d838ef --- /dev/null +++ b/src/ChamiloLMS/Transaction/Plugin/MineduAuthHttpsPostSend.php @@ -0,0 +1,45 @@ +getOriginBranchId(), $session_id, $stamp); + return parent::getTemporaryFileToSend($filename, $envelope); + } +} diff --git a/src/ChamiloLMS/Transaction/TransactionLogController.php b/src/ChamiloLMS/Transaction/TransactionLogController.php index 4c6c1be973..96ebff0821 100644 --- a/src/ChamiloLMS/Transaction/TransactionLogController.php +++ b/src/ChamiloLMS/Transaction/TransactionLogController.php @@ -166,6 +166,10 @@ class TransactionLogController * * @param integer $limit * The maximum allowed envelopes to process. 0 means unlimited. + * + * @return array + * An array keyed by received envelope id containing an array transaction + * ids added based on that received envelope. */ public static function importPendingEnvelopes($limit = 0) { $table = Database::get_main_table(TABLE_RECEIVED_ENVELOPES); @@ -177,6 +181,7 @@ class TransactionLogController else { $sql = sprintf('SELECT * FROM %s WHERE status = %d LIMIT %d', $table, Envelope::RECEIVED_TO_BE_IMPORTED, $limit); } + $added_transaction_ids_per_envelope = array(); $result = Database::query($sql); while ($row = $result->fetch()) { try { @@ -187,7 +192,8 @@ class TransactionLogController $envelope = new Envelope($wrapper_plugin, $envelope_data); $envelope->unwrap(); $transactions = $envelope->getTransactions(); - $this->importToLog($transactions); + $added_transaction_ids = $this->importToLog($transactions); + $added_transaction_ids_per_envelope[$row['id']] = $added_transaction_ids; Database::update($table, array('status' => Envelope::RECEIVED_IMPORTED), array('id = ?' => $row['id'])); } catch (Exception $exception) { @@ -196,6 +202,8 @@ class TransactionLogController continue; } } + + return $added_transaction_ids_per_envelope; } @@ -420,6 +428,7 @@ class TransactionLogController 'send' => array( 'none' => 'NoneSendPlugin', 'auth_https_post' => 'AuthHttpsPostSend', + 'minedu_auth_https_post' => 'MineduAuthHttpsPostSend', ), 'receive' => array( 'none' => 'NoneReceivePlugin',