Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

   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  
  18  /**
  19   * This file contains the class definition for the mahara portfolio plugin
  20   *
  21   * @since Moodle 2.0
  22   * @package moodlecore
  23   * @subpackage portfolio
  24   * @copyright 2009 Penny Leach
  25   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  
  28  
  29  define('PORTFOLIO_MAHARA_ERR_NETWORKING_OFF', 'err_networkingoff');
  30  define('PORTFOLIO_MAHARA_ERR_NOHOSTS', 'err_nomnethosts');
  31  define('PORTFOLIO_MAHARA_ERR_INVALIDHOST', 'err_invalidhost');
  32  define('PORTFOLIO_MAHARA_ERR_NOMNETAUTH', 'err_nomnetauth');
  33  
  34  require_once($CFG->libdir . '/portfoliolib.php');
  35  require_once($CFG->libdir . '/portfolio/plugin.php');
  36  require_once($CFG->libdir . '/portfolio/exporter.php');
  37  require_once($CFG->dirroot . '/mnet/lib.php');
  38  
  39  define('PORTFOLIO_MAHARA_QUEUE', PORTFOLIO_TIME_HIGH);
  40  define('PORTFOLIO_MAHARA_IMMEDIATE', PORTFOLIO_TIME_MODERATE);
  41  
  42  class portfolio_plugin_mahara extends portfolio_plugin_pull_base {
  43  
  44      private $hosts; // used in the admin config form
  45      private $mnethost; // privately set during export from the admin config value (mnethostid)
  46      private $hostrecord; // the host record that corresponds to the peer
  47      private $token; // during-transfer token
  48      private $sendtype; // whatever mahara has said it can handle (immediate or queued)
  49      private $filesmanifest; // manifest of files to send to mahara (set during prepare_package and sent later)
  50      private $totalsize; // total size of all included files added together
  51      private $continueurl; // if we've been sent back a specific url to continue to (eg folder id)
  52  
  53      /** @var mnet_environment the equivalent of old $MNET global. */
  54      public $mnet;
  55  
  56      protected function init() {
  57          $this->mnet = get_mnet_environment();
  58      }
  59  
  60      public function __wakeup() {
  61          $this->mnet = get_mnet_environment();
  62      }
  63  
  64      public static function get_name() {
  65          return get_string('pluginname', 'portfolio_mahara');
  66      }
  67  
  68      public static function get_allowed_config() {
  69          return array('mnethostid', 'enableleap2a');
  70      }
  71  
  72      public function supported_formats() {
  73          if ($this->get_config('enableleap2a')) {
  74              return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
  75          }
  76          return array(PORTFOLIO_FORMAT_FILE);
  77      }
  78  
  79      public function expected_time($callertime) {
  80          if ($this->sendtype == PORTFOLIO_MAHARA_QUEUE) {
  81              return PORTFOLIO_TIME_FORCEQUEUE;
  82          }
  83          return $callertime;
  84      }
  85  
  86      public static function has_admin_config() {
  87          return true;
  88      }
  89  
  90      public static function admin_config_form(&$mform) {
  91          $strrequired = get_string('required');
  92          $hosts = self::get_mnet_hosts(); // this is called by sanity check but it's ok because it's cached
  93          foreach ($hosts as $host) {
  94              $hosts[$host->id] = $host->name;
  95          }
  96          $mform->addElement('select', 'mnethostid', get_string('mnethost', 'portfolio_mahara'), $hosts);
  97          $mform->addRule('mnethostid', $strrequired, 'required', null, 'client');
  98          $mform->setType('mnethostid', PARAM_INT);
  99          $mform->addElement('selectyesno', 'enableleap2a', get_string('enableleap2a', 'portfolio_mahara'));
 100          $mform->setType('enableleap2a', PARAM_BOOL);
 101      }
 102  
 103      public function instance_sanity_check() {
 104          // make sure the host record exists since we don't have referential integrity
 105          if (!is_enabled_auth('mnet')) {
 106              return PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
 107          }
 108          try {
 109              $this->ensure_mnethost();
 110          }
 111          catch (portfolio_exception $e) {
 112              return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
 113          }
 114          // make sure we have the right services
 115          $hosts = $this->get_mnet_hosts();
 116          if (!array_key_exists($this->get_config('mnethostid'), $hosts)) {
 117              return PORTFOLIO_MAHARA_ERR_INVALIDHOST;
 118          }
 119          return 0;
 120      }
 121  
 122      public static function plugin_sanity_check() {
 123          global $CFG, $DB;
 124          $errorcode = 0;
 125          if (!isset($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode != 'strict') {
 126              $errorcode =  PORTFOLIO_MAHARA_ERR_NETWORKING_OFF;
 127          }
 128          if (!is_enabled_auth('mnet')) {
 129              $errorcode = PORTFOLIO_MAHARA_ERR_NOMNETAUTH;
 130          }
 131          if (!self::get_mnet_hosts()) {
 132              $errorcode =  PORTFOLIO_MAHARA_ERR_NOHOSTS;
 133          }
 134          return $errorcode;
 135      }
 136  
 137      private static function get_mnet_hosts() {
 138          global $DB, $CFG;
 139          static $hosts;
 140          if (isset($hosts)) {
 141              return $hosts;
 142          }
 143          $hosts = $DB->get_records_sql('  SELECT
 144                                      h.id,
 145                                      h.wwwroot,
 146                                      h.ip_address,
 147                                      h.name,
 148                                      h.public_key,
 149                                      h.public_key_expires,
 150                                      h.transport,
 151                                      h.portno,
 152                                      h.last_connect_time,
 153                                      h.last_log_id,
 154                                      h.applicationid,
 155                                      a.name as app_name,
 156                                      a.display_name as app_display_name,
 157                                      a.xmlrpc_server_url
 158                                  FROM {mnet_host} h
 159                                      JOIN {mnet_application} a ON h.applicationid=a.id
 160                                      JOIN {mnet_host2service} hs1 ON hs1.hostid = h.id
 161                                      JOIN {mnet_service} s1 ON hs1.serviceid = s1.id
 162                                      JOIN {mnet_host2service} hs2 ON hs2.hostid = h.id
 163                                      JOIN {mnet_service} s2 ON hs2.serviceid = s2.id
 164                                      JOIN {mnet_host2service} hs3 ON hs3.hostid = h.id
 165                                      JOIN {mnet_service} s3 ON hs3.serviceid = s3.id
 166                                  WHERE
 167                                      h.id <> ? AND
 168                                      h.deleted = 0 AND
 169                                      a.name = ? AND
 170                                      s1.name = ? AND hs1.publish = ? AND
 171                                      s2.name = ? AND hs2.subscribe = ? AND
 172                                      s3.name = ? AND hs3.subscribe = ? AND
 173                                      s3.name = ? AND hs3.publish = ?',
 174                          array($CFG->mnet_localhost_id, 'mahara', 'sso_idp', 1, 'sso_sp', 1, 'pf', 1, 'pf', 1));
 175          return $hosts;
 176      }
 177  
 178      public function prepare_package() {
 179          $files = $this->exporter->get_tempfiles();
 180          $this->totalsize = 0;
 181          foreach ($files as $f) {
 182              $this->filesmanifest[$f->get_contenthash()] = array(
 183                  'filename' => $f->get_filename(),
 184                  'sha1'     => $f->get_contenthash(),
 185                  'size'     => $f->get_filesize(),
 186              );
 187              $this->totalsize += $f->get_filesize();
 188          }
 189  
 190          $this->set('file', $this->exporter->zip_tempfiles());  // this will throw a file_exception which the exporter catches separately.
 191      }
 192  
 193      public function send_package() {
 194          global $CFG;
 195          // send the 'content_ready' request to mahara
 196          require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
 197          $client = new mnet_xmlrpc_client();
 198          $client->set_method('portfolio/mahara/lib.php/send_content_ready');
 199          $client->add_param($this->token);
 200          $client->add_param($this->get('user')->username);
 201          $client->add_param($this->resolve_format());
 202          $client->add_param(array(
 203              'filesmanifest' => $this->filesmanifest,
 204              'zipfilesha1'   => $this->get('file')->get_contenthash(),
 205              'zipfilesize'   => $this->get('file')->get_filesize(),
 206              'totalsize'     => $this->totalsize,
 207          ));
 208          $client->add_param($this->get_export_config('wait'));
 209          $this->ensure_mnethost();
 210          if (!$client->send($this->mnethost)) {
 211              foreach ($client->error as $errormessage) {
 212                  list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
 213                  $message .= "ERROR $code:<br/>$errormessage<br/>";
 214              }
 215              throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
 216          }
 217          // we should get back...  an ok and a status
 218          // either we've been waiting a while and mahara has fetched the file or has queued it.
 219          $response = (object)$client->response;
 220          if (!$response->status) {
 221              throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara');
 222          }
 223          if ($response->type =='queued') {
 224              $this->exporter->set_forcequeue();
 225          }
 226          if (isset($response->querystring)) {
 227              $this->continueurl = $response->querystring;
 228          }
 229          // if we're not queuing the logging might have already happened
 230          $this->exporter->update_log_url($this->get_static_continue_url());
 231      }
 232  
 233      public function get_static_continue_url() {
 234          $remoteurl = '';
 235          if ($this->resolve_format() == 'file') {
 236              $remoteurl = '/artefact/file/'; // we hopefully get the files that were imported highlighted
 237          }
 238          if (isset($this->continueurl)) {
 239              $remoteurl .= $this->continueurl;
 240          }
 241          return $remoteurl;
 242      }
 243  
 244      public function resolve_static_continue_url($remoteurl) {
 245          global $CFG;
 246          $this->ensure_mnethost();
 247          $u = new moodle_url('/auth/mnet/jump.php', array('hostid' => $this->get_config('mnethostid'), 'wantsurl' => $remoteurl));
 248          return $u->out();
 249      }
 250  
 251      public function get_interactive_continue_url() {
 252          return $this->resolve_static_continue_url($this->get_static_continue_url());
 253      }
 254  
 255      public function steal_control($stage) {
 256          if ($stage != PORTFOLIO_STAGE_CONFIG) {
 257              return false;
 258          }
 259          global $CFG;
 260          return $CFG->wwwroot . '/portfolio/mahara/preconfig.php?id=' . $this->exporter->get('id');
 261      }
 262  
 263      public function verify_file_request_params($params) {
 264          return false;
 265          // the data comes from an xmlrpc request,
 266          // not a request to file.php
 267      }
 268  
 269      /**
 270      * sends the 'content_intent' ping to mahara
 271      * if all goes well, this will set the 'token' and 'sendtype' member variables.
 272      */
 273      public function send_intent() {
 274          global $CFG, $DB;
 275          require_once($CFG->dirroot . '/mnet/xmlrpc/client.php');
 276          $client = new mnet_xmlrpc_client();
 277          $client->set_method('portfolio/mahara/lib.php/send_content_intent');
 278          $client->add_param($this->get('user')->username);
 279          $this->ensure_mnethost();
 280          if (!$client->send($this->mnethost)) {
 281              foreach ($client->error as $errormessage) {
 282                  list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
 283                  $message .= "ERROR $code:<br/>$errormessage<br/>";
 284              }
 285              throw new portfolio_export_exception($this->get('exporter'), 'failedtoping', 'portfolio_mahara', '', $message);
 286          }
 287          // we should get back... the send type and a shared token
 288          $response = (object)$client->response;
 289          if (empty($response->sendtype) || empty($response->token)) {
 290              throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
 291          }
 292          switch ($response->sendtype) {
 293              case 'immediate':
 294                  $this->sendtype = PORTFOLIO_MAHARA_IMMEDIATE;
 295                  break;
 296              case 'queue':
 297                  $this->sendtype = PORTFOLIO_MAHARA_QUEUE;
 298                  break;
 299              case 'none':
 300              default:
 301                  throw new portfolio_export_exception($this->get('exporter'), 'senddisallowed', 'portfolio_mahara');
 302          }
 303          $this->token = $response->token;
 304          $this->get('exporter')->save();
 305          // put the entry in the mahara queue table now too
 306          $q = new stdClass;
 307          $q->token = $this->token;
 308          $q->transferid = $this->get('exporter')->get('id');
 309          $DB->insert_record('portfolio_mahara_queue', $q);
 310      }
 311  
 312      private function ensure_mnethost() {
 313          if (!empty($this->hostrecord) && !empty($this->mnethost)) {
 314              return;
 315          }
 316          global $DB;
 317          if (!$this->hostrecord = $DB->get_record('mnet_host', array('id' => $this->get_config('mnethostid')))) {
 318              throw new portfolio_plugin_exception(PORTFOLIO_MAHARA_ERR_INVALIDHOST, 'portfolio_mahara');
 319          }
 320          $this->mnethost = new mnet_peer();
 321          $this->mnethost->set_wwwroot($this->hostrecord->wwwroot);
 322      }
 323  
 324      /**
 325      * xmlrpc (mnet) function to get the file.
 326      * reads in the file and returns it base_64 encoded
 327      * so that it can be enrypted by mnet.
 328      *
 329      * @param string $token the token recieved previously during send_content_intent
 330      */
 331      public static function fetch_file($token) {
 332          global $DB;
 333          $remoteclient = get_mnet_remote_client();
 334          try {
 335              if (!$transferid = $DB->get_field('portfolio_mahara_queue', 'transferid', array('token' => $token))) {
 336                  throw new mnet_server_exception(8009, 'mnet_notoken', 'portfolio_mahara');
 337              }
 338              $exporter = portfolio_exporter::rewaken_object($transferid);
 339          } catch (portfolio_exception $e) {
 340              throw new mnet_server_exception(8010, 'mnet_noid', 'portfolio_mahara');
 341          }
 342          if ($exporter->get('instance')->get_config('mnethostid') != $remoteclient->id) {
 343              throw new mnet_server_exception(8011, 'mnet_wronghost', 'portfolio_mahara');
 344          }
 345          global $CFG;
 346          try {
 347              $i = $exporter->get('instance');
 348              $f = $i->get('file');
 349              if (empty($f) || !($f instanceof stored_file)) {
 350                  throw new mnet_server_exception(8012, 'mnet_nofile', 'portfolio_mahara');
 351              }
 352              try {
 353                  $c = $f->get_content();
 354              } catch (file_exception $e) {
 355                  throw new mnet_server_exception(8013, 'mnet_nofilecontents', 'portfolio_mahara', $e->getMessage());
 356              }
 357              $contents = base64_encode($c);
 358          } catch (Exception $e) {
 359              throw new mnet_server_exception(8013, 'mnet_nofile', 'portfolio_mahara');
 360          }
 361          $exporter->log_transfer();
 362          $exporter->process_stage_cleanup(true);
 363          return $contents;
 364      }
 365  
 366      public function cleanup() {
 367          global $DB;
 368          $DB->delete_records('portfolio_mahara_queue', array('transferid' => $this->get('exporter')->get('id'), 'token' => $this->token));
 369      }
 370  
 371  
 372      /**
 373       * internal helper function, that converts between the format constant,
 374       * which might be too specific (eg 'image') and the class in our *supported* list
 375       * which might be higher up the format hierarchy tree (eg 'file')
 376       */
 377      private function resolve_format() {
 378          global $CFG;
 379          $thisformat = $this->get_export_config('format');
 380          $allformats = portfolio_supported_formats();
 381          require_once($CFG->libdir . '/portfolio/formats.php');
 382          $thisobj = new $allformats[$thisformat];
 383          foreach ($this->supported_formats() as $f) {
 384              $class = $allformats[$f];
 385              if ($thisobj instanceof $class) {
 386                  return $f;
 387              }
 388          }
 389      }
 390  }
 391  
 392