Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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