add.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * This file is the main controller to do with the portfolio export wizard.
  18. *
  19. * @package core_portfolio
  20. * @copyright 2008 Penny Leach <penny@catalyst.net.nz>,
  21. * Martin Dougiamas <http://dougiamas.com>
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL
  23. */
  24. require_once(__DIR__ . '/../config.php');
  25. if (empty($CFG->enableportfolios)) {
  26. print_error('disabled', 'portfolio');
  27. }
  28. require_once($CFG->libdir . '/portfoliolib.php');
  29. require_once($CFG->libdir . '/portfolio/exporter.php');
  30. require_once($CFG->libdir . '/portfolio/caller.php');
  31. require_once($CFG->libdir . '/portfolio/plugin.php');
  32. $dataid = optional_param('id', 0, PARAM_INT); // The ID of partially completed export, corresponds to a record in portfolio_tempdata.
  33. $type = optional_param('type', null, PARAM_SAFEDIR); // If we're returning from an external system (postcontrol) for a single-export only plugin.
  34. $cancel = optional_param('cancel', 0, PARAM_RAW); // User has cancelled the request.
  35. $cancelsure = optional_param('cancelsure', 0, PARAM_BOOL); // Make sure they confirm first.
  36. $logreturn = optional_param('logreturn', 0, PARAM_BOOL); // When cancelling, we can also come from the log page, rather than the caller.
  37. $instanceid = optional_param('instance', 0, PARAM_INT); // The instance of configured portfolio plugin.
  38. $courseid = optional_param('course', 0, PARAM_INT); // The courseid the data being exported belongs to (caller object should provide this later).
  39. $stage = optional_param('stage', PORTFOLIO_STAGE_CONFIG, PARAM_INT); // Stage of the export we're at (stored in the exporter).
  40. $postcontrol = optional_param('postcontrol', 0, PARAM_INT); // When returning from some bounce to an external system, this gets passed.
  41. $callbackcomponent = optional_param('callbackcomponent', null, PARAM_PATH); // Callback component eg mod_forum - the component of the exporting content.
  42. $callbackclass = optional_param('callbackclass', null, PARAM_ALPHAEXT); // Callback class eg forum_portfolio_caller - the class to handle the exporting content.
  43. $callerformats = optional_param('callerformats', null, PARAM_TAGLIST); // Comma separated list of formats the specific place exporting content supports.
  44. require_login(); // this is selectively called again with $course later when we know for sure which one we're in.
  45. $PAGE->set_context(context_system::instance());
  46. $PAGE->set_url('/portfolio/add.php', array('id' => $dataid, 'sesskey' => sesskey()));
  47. $PAGE->set_pagelayout('admin');
  48. $exporter = null;
  49. if ($postcontrol && $type && !$dataid) {
  50. // we're returning from an external system that can't construct dynamic return urls
  51. // this is a special "one export of this type only per session" case
  52. if (portfolio_static_function($type, 'allows_multiple_exports')) {
  53. throw new portfolio_exception('multiplesingleresume', 'portfolio');
  54. }
  55. if (!$dataid = portfolio_export_type_to_id($type, $USER->id)) {
  56. throw new portfolio_exception('invalidtempid', 'portfolio');
  57. }
  58. } else {
  59. // we can't do this in the above case, because we're redirecting straight back from an external system
  60. // this is not really ideal, but since we're in a "staged" wizard, the session key is checked in other stages.
  61. require_sesskey(); // pretty much everything in this page is a write that could be hijacked, so just do this at the top here
  62. }
  63. // if we have a dataid, it means we're in the middle of an export,
  64. // so rewaken it and continue.
  65. if (!empty($dataid)) {
  66. try {
  67. $exporter = portfolio_exporter::rewaken_object($dataid);
  68. } catch (portfolio_exception $e) {
  69. // this can happen in some cases, a cancel request is sent when something is already broken
  70. // so process it elegantly and move on.
  71. if ($cancel) {
  72. if ($logreturn) {
  73. redirect($CFG->wwwroot . '/user/portfoliologs.php');
  74. }
  75. redirect($CFG->wwwroot);
  76. } else {
  77. throw $e;
  78. }
  79. }
  80. // we have to wake it up first before we can cancel it
  81. // so temporary directories etc get cleaned up.
  82. if ($cancel) {
  83. if ($cancelsure) {
  84. $exporter->cancel_request($logreturn);
  85. } else {
  86. portfolio_export_pagesetup($PAGE, $exporter->get('caller'));
  87. $exporter->print_header(get_string('confirmcancel', 'portfolio'));
  88. echo $OUTPUT->box_start();
  89. $yesbutton = new single_button(new moodle_url('/portfolio/add.php', array('id' => $dataid, 'cancel' => 1, 'cancelsure' => 1, 'logreturn' => $logreturn)), get_string('yes'));
  90. if ($logreturn) {
  91. $nobutton = new single_button(new moodle_url('/user/portfoliologs.php'), get_string('no'));
  92. } else {
  93. $nobutton = new single_button(new moodle_url('/portfolio/add.php', array('id' => $dataid)), get_string('no'));
  94. }
  95. echo $OUTPUT->confirm(get_string('confirmcancel', 'portfolio'), $yesbutton, $nobutton);
  96. echo $OUTPUT->box_end();
  97. echo $OUTPUT->footer();
  98. exit;
  99. }
  100. }
  101. // verify we still belong to the correct user and permissions are still ok
  102. $exporter->verify_rewaken();
  103. // if we don't have an instanceid in the exporter
  104. // it means we've just posted from the 'choose portfolio instance' page
  105. // so process that and start up the portfolio plugin
  106. if (!$exporter->get('instance')) {
  107. if ($instanceid) {
  108. try {
  109. $instance = portfolio_instance($instanceid);
  110. } catch (portfolio_exception $e) {
  111. portfolio_export_rethrow_exception($exporter, $e);
  112. }
  113. // this technically shouldn't happen but make sure anyway
  114. if ($broken = portfolio_instance_sanity_check($instance)) {
  115. throw new portfolio_export_exception($exporter, $broken[$instance->get('id')], 'portfolio_' . $instance->get('plugin'));
  116. }
  117. // now we're all set up, ready to go
  118. $instance->set('user', $USER);
  119. $exporter->set('instance', $instance);
  120. $exporter->save();
  121. }
  122. }
  123. portfolio_export_pagesetup($PAGE, $exporter->get('caller')); // this calls require_login($course) if it can..
  124. // completely new request, look to see what information we've been passed and set up the exporter object.
  125. } else {
  126. // you cannot get here with no information for us, we must at least have the caller.
  127. if (empty($_GET) && empty($_POST)) {
  128. portfolio_exporter::print_expired_export();
  129. }
  130. // we'e just posted here for the first time and have might the instance already
  131. if ($instanceid) {
  132. // this can throw exceptions but there's no point catching and rethrowing here
  133. // as the exporter isn't created yet.
  134. $instance = portfolio_instance($instanceid);
  135. if ($broken = portfolio_instance_sanity_check($instance)) {
  136. throw new portfolio_exception($broken[$instance->get('id')], 'portfolio_' . $instance->get('plugin'));
  137. }
  138. $instance->set('user', $USER);
  139. } else {
  140. $instance = null;
  141. }
  142. // we must be passed this from the caller, we cannot start a new export
  143. // without knowing information about what part of moodle we come from.
  144. if (empty($callbackcomponent) || empty($callbackclass)) {
  145. debugging('no callback file or class');
  146. portfolio_exporter::print_expired_export();
  147. }
  148. // so each place in moodle can pass callback args here
  149. // process the entire request looking for ca_*
  150. // be as lenient as possible while still being secure
  151. // so only accept certain parameter types.
  152. $callbackargs = array();
  153. foreach (array_keys(array_merge($_GET, $_POST)) as $key) {
  154. if (strpos($key, 'ca_') === 0) {
  155. if (!$value = optional_param($key, false, PARAM_ALPHAEXT)) {
  156. if (!$value = optional_param($key, false, PARAM_FLOAT)) {
  157. $value = optional_param($key, false, PARAM_PATH);
  158. }
  159. }
  160. // strip off ca_ for niceness
  161. $callbackargs[substr($key, 3)] = $value;
  162. }
  163. }
  164. // Ensure that we found a file we can use, if not throw an exception.
  165. portfolio_include_callback_file($callbackcomponent, $callbackclass);
  166. $caller = new $callbackclass($callbackargs);
  167. $caller->set('user', $USER);
  168. if ($formats = explode(',', $callerformats)) {
  169. $caller->set_formats_from_button($formats);
  170. }
  171. $caller->load_data();
  172. // this must check capabilities and either throw an exception or return false.
  173. if (!$caller->check_permissions()) {
  174. throw new portfolio_caller_exception('nopermissions', 'portfolio', $caller->get_return_url());
  175. }
  176. portfolio_export_pagesetup($PAGE, $caller); // this calls require_login($course) if it can..
  177. // finally! set up the exporter object with the portfolio instance, and caller information elements
  178. $exporter = new portfolio_exporter($instance, $caller, $callbackcomponent);
  179. // set the export-specific variables, and save.
  180. $exporter->set('user', $USER);
  181. $exporter->save();
  182. }
  183. if (!$exporter->get('instance')) {
  184. // we've just arrived but have no instance
  185. // in this case the exporter object and the caller object have been set up above
  186. // so just make a little form to select the portfolio plugin instance,
  187. // which is the last thing to do before starting the export.
  188. //
  189. // first check to make sure there is actually a point
  190. $options = portfolio_instance_select(
  191. portfolio_instances(),
  192. $exporter->get('caller')->supported_formats(),
  193. get_class($exporter->get('caller')),
  194. $exporter->get('caller')->get_mimetype(),
  195. 'instance',
  196. true,
  197. true
  198. );
  199. if (empty($options)) {
  200. throw new portfolio_export_exception($exporter, 'noavailableplugins', 'portfolio');
  201. } else if (count($options) == 1) {
  202. // no point displaying a form, just redirect.
  203. $optionskeys = array_keys($options);
  204. $instance = array_shift($optionskeys);
  205. redirect($CFG->wwwroot . '/portfolio/add.php?id= ' . $exporter->get('id') . '&instance=' . $instance . '&sesskey=' . sesskey());
  206. }
  207. // be very selective about not including this unless we really need to
  208. require_once($CFG->libdir . '/portfolio/forms.php');
  209. $mform = new portfolio_instance_select('', array('id' => $exporter->get('id'), 'caller' => $exporter->get('caller'), 'options' => $options));
  210. if ($mform->is_cancelled()) {
  211. $exporter->cancel_request();
  212. } else if ($fromform = $mform->get_data()){
  213. redirect($CFG->wwwroot . '/portfolio/add.php?instance=' . $fromform->instance . '&amp;id=' . $exporter->get('id'));
  214. exit;
  215. }
  216. else {
  217. $exporter->print_header(get_string('selectplugin', 'portfolio'));
  218. echo $OUTPUT->box_start();
  219. $mform->display();
  220. echo $OUTPUT->box_end();
  221. echo $OUTPUT->footer();
  222. exit;
  223. }
  224. }
  225. // if we haven't been passed &stage= grab it from the exporter.
  226. if (!$stage) {
  227. $stage = $exporter->get('stage');
  228. }
  229. // for places returning control to pass (rather than PORTFOLIO_STAGE_PACKAGE
  230. // which is unstable if they can't get to the constant (eg external system)
  231. $alreadystolen = false;
  232. if ($postcontrol) { // the magic request variable plugins must pass on returning here
  233. try {
  234. // allow it to read whatever gets sent back in the request
  235. // this is useful for plugins that redirect away and back again
  236. // adding a token to the end of the url, for example box.net
  237. $exporter->instance()->post_control($stage, array_merge($_GET, $_POST));
  238. } catch (portfolio_plugin_exception $e) {
  239. portfolio_export_rethrow_exception($exporter, $e);
  240. }
  241. $alreadystolen = true; // remember this so we don't get caught in a steal control loop!
  242. }
  243. // actually do the work now..
  244. $exporter->process_stage($stage, $alreadystolen);