See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [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 * This file contains all global functions to do with manipulating portfolios. 19 * 20 * Everything else that is logically namespaced by class is in its own file 21 * in lib/portfolio/ directory. 22 * 23 * Major Contributors 24 * - Penny Leach <penny@catalyst.net.nz> 25 * 26 * @package core_portfolio 27 * @category portfolio 28 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 */ 31 32 defined('MOODLE_INTERNAL') || die(); 33 34 // require some of the sublibraries first. 35 // this is not an exhaustive list, the others are pulled in as they're needed 36 // so we don't have to always include everything unnecessarily for performance 37 38 // very lightweight list of constants. always needed and no further dependencies 39 require_once($CFG->libdir . '/portfolio/constants.php'); 40 // a couple of exception deinitions. always needed and no further dependencies 41 require_once($CFG->libdir . '/portfolio/exceptions.php'); // exception classes used by portfolio code 42 // The base class for the caller classes. We always need this because we're either drawing a button, 43 // in which case the button needs to know the calling class definition, which requires the base class, 44 // or we're exporting, in which case we need the caller class anyway. 45 require_once($CFG->libdir . '/portfolio/caller.php'); 46 47 // the other dependencies are included on demand: 48 // libdir/portfolio/formats.php - the classes for the export formats 49 // libdir/portfolio/forms.php - all portfolio form classes (requires formslib) 50 // libdir/portfolio/plugin.php - the base class for the export plugins 51 // libdir/portfolio/exporter.php - the exporter class 52 53 54 /** 55 * Use this to add a portfolio button or icon or form to a page. 56 * 57 * These class methods do not check permissions. the caller must check permissions first. 58 * Later, during the export process, the caller class is instantiated and the check_permissions method is called 59 * If you are exporting a single file, you should always call set_format_by_file($file) 60 * This class can be used like this: 61 * <code> 62 * $button = new portfolio_add_button(); 63 * $button->set_callback_options('name_of_caller_class', array('id' => 6), 'yourcomponent'); eg. mod_forum 64 * $button->render(PORTFOLIO_ADD_FULL_FORM, get_string('addeverythingtoportfolio', 'yourcomponent')); 65 * </code> 66 * or like this: 67 * <code> 68 * $button = new portfolio_add_button(array('callbackclass' => 'name_of_caller_class', 'callbackargs' => array('id' => 6), 'callbackcomponent' => 'yourcomponent')); eg. mod_forum 69 * $somehtml .= $button->to_html(PORTFOLIO_ADD_TEXT_LINK); 70 * </code> 71 * 72 * @package core_portfolio 73 * @category portfolio 74 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 75 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 76 */ 77 class portfolio_add_button { 78 79 /** @var string the name of the callback functions */ 80 private $callbackclass; 81 82 /** @var array can be an array of arguments to pass back to the callback functions (passed by reference)*/ 83 private $callbackargs; 84 85 /** @var string caller file */ 86 private $callbackcomponent; 87 88 /** @var array array of more specific formats (eg based on mime detection) */ 89 private $formats; 90 91 /** @var array array of portfolio instances */ 92 private $instances; 93 94 /** @var stored_file for single-file exports */ 95 private $file; 96 97 /** @var string for writing specific types of files*/ 98 private $intendedmimetype; 99 100 /** 101 * Constructor. Either pass the options here or set them using the helper methods. 102 * Generally the code will be clearer if you use the helper methods. 103 * 104 * @param array $options keyed array of options: 105 * key 'callbackclass': name of the caller class (eg forum_portfolio_caller') 106 * key 'callbackargs': the array of callback arguments your caller class wants passed to it in the constructor 107 * key 'callbackcomponent': the file containing the class definition of your caller class. 108 * See set_callback_options for more information on these three. 109 * key 'formats': an array of PORTFOLIO_FORMATS this caller will support 110 * See set_formats or set_format_by_file for more information on this. 111 */ 112 public function __construct($options=null) { 113 global $SESSION, $CFG; 114 115 if (empty($CFG->enableportfolios)) { 116 debugging('Building portfolio add button while portfolios is disabled. This code can be optimised.', DEBUG_DEVELOPER); 117 } 118 119 $cache = cache::make('core', 'portfolio_add_button_portfolio_instances'); 120 $instances = $cache->get('instances'); 121 if ($instances === false) { 122 $instances = portfolio_instances(); 123 $cache->set('instances', $instances); 124 } 125 126 $this->instances = $instances; 127 if (empty($options)) { 128 return true; 129 } 130 $constructoroptions = array('callbackclass', 'callbackargs', 'callbackcomponent'); 131 foreach ((array)$options as $key => $value) { 132 if (!in_array($key, $constructoroptions)) { 133 throw new portfolio_button_exception('invalidbuttonproperty', 'portfolio', $key); 134 } 135 } 136 137 $this->set_callback_options($options['callbackclass'], $options['callbackargs'], $options['callbackcomponent']); 138 } 139 140 /** 141 * Function to set the callback options 142 * 143 * @param string $class Name of the class containing the callback functions 144 * activity components should ALWAYS use their name_portfolio_caller 145 * other locations must use something unique 146 * @param array $argarray This can be an array or hash of arguments to pass 147 * back to the callback functions (passed by reference) 148 * these MUST be primatives to be added as hidden form fields. 149 * and the values get cleaned to PARAM_ALPHAEXT or PARAM_FLOAT or PARAM_PATH 150 * @param string $component This is the name of the component in Moodle, eg 'mod_forum' 151 */ 152 public function set_callback_options($class, array $argarray, $component) { 153 global $CFG; 154 155 // Require the base class first before any other files. 156 require_once($CFG->libdir . '/portfolio/caller.php'); 157 158 // Include any potential callback files and check for errors. 159 portfolio_include_callback_file($component, $class); 160 161 // This will throw exceptions but should not actually do anything other than verify callbackargs. 162 $test = new $class($argarray); 163 unset($test); 164 165 $this->callbackcomponent = $component; 166 $this->callbackclass = $class; 167 $this->callbackargs = $argarray; 168 } 169 170 /** 171 * Sets the available export formats for this content. 172 * This function will also poll the static function in the caller class 173 * and make sure we're not overriding a format that has nothing to do with mimetypes. 174 * Eg: if you pass IMAGE here but the caller can export LEAP2A it will keep LEAP2A as well. 175 * @see portfolio_most_specific_formats for more information 176 * @see portfolio_format_from_mimetype 177 * 178 * @param array $formats if the calling code knows better than the static method on the calling class (base_supported_formats). 179 * Eg: if it's going to be a single file, or if you know it's HTML, you can pass it here instead. 180 * This is almost always the case so it should be use all the times 181 * portfolio_format_from_mimetype for how to get the appropriate formats to pass here for uploaded files. 182 * or just call set_format_by_file instead 183 */ 184 public function set_formats($formats=null) { 185 if (is_string($formats)) { 186 $formats = array($formats); 187 } 188 if (empty($formats)) { 189 $formats = array(); 190 } 191 if (empty($this->callbackclass)) { 192 throw new portfolio_button_exception('noclassbeforeformats', 'portfolio'); 193 } 194 $callerformats = call_user_func(array($this->callbackclass, 'base_supported_formats')); 195 $this->formats = portfolio_most_specific_formats($formats, $callerformats); 196 } 197 198 /** 199 * Reset formats to the default, 200 * which is usually what base_supported_formats returns 201 */ 202 public function reset_formats() { 203 $this->set_formats(); 204 } 205 206 207 /** 208 * If we already know we have exactly one file, 209 * bypass set_formats and just pass the file 210 * so we can detect the formats by mimetype. 211 * 212 * @param stored_file $file file to set the format from 213 * @param array $extraformats any additional formats other than by mimetype 214 * eg leap2a etc 215 */ 216 public function set_format_by_file(stored_file $file, $extraformats=null) { 217 $this->file = $file; 218 $fileformat = portfolio_format_from_mimetype($file->get_mimetype()); 219 if (is_string($extraformats)) { 220 $extraformats = array($extraformats); 221 } else if (!is_array($extraformats)) { 222 $extraformats = array(); 223 } 224 $this->set_formats(array_merge(array($fileformat), $extraformats)); 225 } 226 227 /** 228 * Correllary this is use to set_format_by_file, but it is also used when there is no stored_file and 229 * when we're writing out a new type of file (like csv or pdf) 230 * 231 * @param string $extn the file extension we intend to generate 232 * @param array $extraformats any additional formats other than by mimetype 233 * eg leap2a etc 234 */ 235 public function set_format_by_intended_file($extn, $extraformats=null) { 236 $mimetype = mimeinfo('type', 'something. ' . $extn); 237 $fileformat = portfolio_format_from_mimetype($mimetype); 238 $this->intendedmimetype = $fileformat; 239 if (is_string($extraformats)) { 240 $extraformats = array($extraformats); 241 } else if (!is_array($extraformats)) { 242 $extraformats = array(); 243 } 244 $this->set_formats(array_merge(array($fileformat), $extraformats)); 245 } 246 247 /** 248 * Echo the form/button/icon/text link to the page 249 * 250 * @param int $format format to display the button or form or icon or link. 251 * See constants PORTFOLIO_ADD_XXX for more info. 252 * optional, defaults to PORTFOLIO_ADD_FULL_FORM 253 * @param string $addstr string to use for the button or icon alt text or link text. 254 * this is whole string, not key. optional, defaults to 'Export to portfolio'; 255 */ 256 public function render($format=null, $addstr=null) { 257 echo $this->to_html($format, $addstr); 258 } 259 260 /** 261 * Returns the form/button/icon/text link as html 262 * 263 * @param int $format format to display the button or form or icon or link. 264 * See constants PORTFOLIO_ADD_XXX for more info. 265 * Optional, defaults to PORTFOLIO_ADD_FULL_FORM 266 * @param string $addstr string to use for the button or icon alt text or link text. 267 * This is whole string, not key. optional, defaults to 'Add to portfolio'; 268 * @return void|string|moodle_url 269 */ 270 public function to_html($format=null, $addstr=null) { 271 global $CFG, $COURSE, $OUTPUT, $USER; 272 if (!$this->is_renderable()) { 273 return; 274 } 275 if (empty($this->callbackclass) || empty($this->callbackcomponent)) { 276 throw new portfolio_button_exception('mustsetcallbackoptions', 'portfolio'); 277 } 278 if (empty($this->formats)) { 279 // use the caller defaults 280 $this->set_formats(); 281 } 282 $url = new moodle_url('/portfolio/add.php'); 283 foreach ($this->callbackargs as $key => $value) { 284 if (!empty($value) && !is_string($value) && !is_numeric($value)) { 285 $a = new stdClass(); 286 $a->key = $key; 287 $a->value = print_r($value, true); 288 debugging(get_string('nonprimative', 'portfolio', $a)); 289 return; 290 } 291 $url->param('ca_' . $key, $value); 292 } 293 $url->param('sesskey', sesskey()); 294 $url->param('callbackcomponent', $this->callbackcomponent); 295 $url->param('callbackclass', $this->callbackclass); 296 $url->param('course', (!empty($COURSE)) ? $COURSE->id : 0); 297 $url->param('callerformats', implode(',', $this->formats)); 298 $mimetype = null; 299 if ($this->file instanceof stored_file) { 300 $mimetype = $this->file->get_mimetype(); 301 } else if ($this->intendedmimetype) { 302 $mimetype = $this->intendedmimetype; 303 } 304 $selectoutput = ''; 305 if (count($this->instances) == 1) { 306 $tmp = array_values($this->instances); 307 $instance = $tmp[0]; 308 309 $formats = portfolio_supported_formats_intersect($this->formats, $instance->supported_formats()); 310 if (count($formats) == 0) { 311 // bail. no common formats. 312 //debugging(get_string('nocommonformats', 'portfolio', (object)array('location' => $this->callbackclass, 'formats' => implode(',', $this->formats)))); 313 return; 314 } 315 if ($error = portfolio_instance_sanity_check($instance)) { 316 // bail, plugin is misconfigured 317 //debugging(get_string('instancemisconfigured', 'portfolio', get_string($error[$instance->get('id')], 'portfolio_' . $instance->get('plugin')))); 318 return; 319 } 320 if (!$instance->allows_multiple_exports() && $already = portfolio_existing_exports($USER->id, $instance->get('plugin'))) { 321 //debugging(get_string('singleinstancenomultiallowed', 'portfolio')); 322 return; 323 } 324 if ($mimetype&& !$instance->file_mime_check($mimetype)) { 325 // bail, we have a specific file or mimetype and this plugin doesn't support it 326 //debugging(get_string('mimecheckfail', 'portfolio', (object)array('plugin' => $instance->get('plugin'), 'mimetype' => $mimetype))); 327 return; 328 } 329 $url->param('instance', $instance->get('id')); 330 } 331 else { 332 if (!$selectoutput = portfolio_instance_select($this->instances, $this->formats, $this->callbackclass, $mimetype, 'instance', true)) { 333 return; 334 } 335 } 336 // If we just want a moodle_url to redirect to, do it now. 337 if ($format == PORTFOLIO_ADD_MOODLE_URL) { 338 return $url; 339 } 340 341 // if we just want a url to redirect to, do it now 342 if ($format == PORTFOLIO_ADD_FAKE_URL) { 343 return $url->out(false); 344 } 345 346 if (empty($addstr)) { 347 $addstr = get_string('addtoportfolio', 'portfolio'); 348 } 349 if (empty($format)) { 350 $format = PORTFOLIO_ADD_FULL_FORM; 351 } 352 353 $formoutput = '<form method="post" action="' . $CFG->wwwroot . '/portfolio/add.php" id="portfolio-add-button">' . "\n"; 354 $formoutput .= html_writer::input_hidden_params($url); 355 $linkoutput = ''; 356 357 switch ($format) { 358 case PORTFOLIO_ADD_FULL_FORM: 359 $formoutput .= $selectoutput; 360 $formoutput .= "\n" . '<input type="submit" class="btn btn-secondary" value="' . $addstr .'" />'; 361 $formoutput .= "\n" . '</form>'; 362 break; 363 case PORTFOLIO_ADD_ICON_FORM: 364 $formoutput .= $selectoutput; 365 $formoutput .= "\n" . '<button class="portfolio-add-icon">' . $OUTPUT->pix_icon('t/portfolioadd', $addstr) . '</button>'; 366 $formoutput .= "\n" . '</form>'; 367 break; 368 case PORTFOLIO_ADD_ICON_LINK: 369 $linkoutput = $OUTPUT->action_icon($url, new pix_icon('t/portfolioadd', $addstr, '', 370 array('class' => 'portfolio-add-icon smallicon'))); 371 break; 372 case PORTFOLIO_ADD_TEXT_LINK: 373 $linkoutput = html_writer::link($url, $addstr, array('class' => 'portfolio-add-link', 374 'title' => $addstr)); 375 break; 376 default: 377 debugging(get_string('invalidaddformat', 'portfolio', $format)); 378 } 379 $output = (in_array($format, array(PORTFOLIO_ADD_FULL_FORM, PORTFOLIO_ADD_ICON_FORM)) ? $formoutput : $linkoutput); 380 return $output; 381 } 382 383 /** 384 * Perform some internal checks. 385 * These are not errors, just situations 386 * where it's not appropriate to add the button 387 * 388 * @return bool 389 */ 390 private function is_renderable() { 391 global $CFG; 392 if (empty($CFG->enableportfolios)) { 393 return false; 394 } 395 if (defined('PORTFOLIO_INTERNAL')) { 396 // something somewhere has detected a risk of this being called during inside the preparation 397 // eg forum_print_attachments 398 return false; 399 } 400 if (empty($this->instances) || count($this->instances) == 0) { 401 return false; 402 } 403 return true; 404 } 405 406 /** 407 * Getter for $format property 408 * 409 * @return array 410 */ 411 public function get_formats() { 412 return $this->formats; 413 } 414 415 /** 416 * Getter for $callbackargs property 417 * 418 * @return array 419 */ 420 public function get_callbackargs() { 421 return $this->callbackargs; 422 } 423 424 /** 425 * Getter for $callbackcomponent property 426 * 427 * @return string 428 */ 429 public function get_callbackcomponent() { 430 return $this->callbackcomponent; 431 } 432 433 /** 434 * Getter for $callbackclass property 435 * 436 * @return string 437 */ 438 public function get_callbackclass() { 439 return $this->callbackclass; 440 } 441 } 442 443 /** 444 * Returns a drop menu with a list of available instances. 445 * 446 * @param array $instances array of portfolio plugin instance objects - the instances to put in the menu 447 * @param array $callerformats array of PORTFOLIO_FORMAT_XXX constants - the formats the caller supports (this is used to filter plugins) 448 * @param string $callbackclass the callback class name - used for debugging only for when there are no common formats 449 * @param string $mimetype if we already know we have exactly one file, or are going to write one, pass it here to do mime filtering. 450 * @param string $selectname the name of the select element. Optional, defaults to instance. 451 * @param bool $return whether to print or return the output. Optional, defaults to print. 452 * @param bool $returnarray if returning, whether to return the HTML or the array of options. Optional, defaults to HTML. 453 * @return void|array|string the html, from <select> to </select> inclusive. 454 */ 455 function portfolio_instance_select($instances, $callerformats, $callbackclass, $mimetype=null, $selectname='instance', $return=false, $returnarray=false) { 456 global $CFG, $USER; 457 458 if (empty($CFG->enableportfolios)) { 459 return; 460 } 461 462 $insane = portfolio_instance_sanity_check(); 463 $pinsane = portfolio_plugin_sanity_check(); 464 465 $count = 0; 466 $selectoutput = "\n" . '<label class="accesshide" for="instanceid">' . get_string('plugin', 'portfolio') . '</label>'; 467 $selectoutput .= "\n" . '<select id="instanceid" name="' . $selectname . '" class="custom-select">' . "\n"; 468 $existingexports = portfolio_existing_exports_by_plugin($USER->id); 469 foreach ($instances as $instance) { 470 $formats = portfolio_supported_formats_intersect($callerformats, $instance->supported_formats()); 471 if (count($formats) == 0) { 472 // bail. no common formats. 473 continue; 474 } 475 if (array_key_exists($instance->get('id'), $insane)) { 476 // bail, plugin is misconfigured 477 //debugging(get_string('instanceismisconfigured', 'portfolio', get_string($insane[$instance->get('id')], 'portfolio_' . $instance->get('plugin')))); 478 continue; 479 } else if (array_key_exists($instance->get('plugin'), $pinsane)) { 480 // bail, plugin is misconfigured 481 //debugging(get_string('pluginismisconfigured', 'portfolio', get_string($pinsane[$instance->get('plugin')], 'portfolio_' . $instance->get('plugin')))); 482 continue; 483 } 484 if (!$instance->allows_multiple_exports() && in_array($instance->get('plugin'), $existingexports)) { 485 // bail, already exporting something with this plugin and it doesn't support multiple exports 486 continue; 487 } 488 if ($mimetype && !$instance->file_mime_check($mimetype)) { 489 //debugging(get_string('mimecheckfail', 'portfolio', (object)array('plugin' => $instance->get('plugin'), 'mimetype' => $mimetype()))); 490 // bail, we have a specific file and this plugin doesn't support it 491 continue; 492 } 493 $count++; 494 $selectoutput .= "\n" . '<option value="' . $instance->get('id') . '">' . $instance->get('name') . '</option>' . "\n"; 495 $options[$instance->get('id')] = $instance->get('name'); 496 } 497 if (empty($count)) { 498 // bail. no common formats. 499 //debugging(get_string('nocommonformats', 'portfolio', (object)array('location' => $callbackclass, 'formats' => implode(',', $callerformats)))); 500 return; 501 } 502 $selectoutput .= "\n" . "</select>\n"; 503 if (!empty($returnarray)) { 504 return $options; 505 } 506 if (!empty($return)) { 507 return $selectoutput; 508 } 509 echo $selectoutput; 510 } 511 512 /** 513 * Return all portfolio instances 514 * 515 * @todo MDL-15768 - check capabilities here 516 * @param bool $visibleonly Don't include hidden instances. Defaults to true and will be overridden to true if the next parameter is true 517 * @param bool $useronly Check the visibility preferences and permissions of the logged in user. Defaults to true. 518 * @return array of portfolio instances (full objects, not just database records) 519 */ 520 function portfolio_instances($visibleonly=true, $useronly=true) { 521 522 global $DB, $USER; 523 524 $values = array(); 525 $sql = 'SELECT * FROM {portfolio_instance}'; 526 527 if ($visibleonly || $useronly) { 528 $values[] = 1; 529 $sql .= ' WHERE visible = ?'; 530 } 531 if ($useronly) { 532 $sql .= ' AND id NOT IN ( 533 SELECT instance FROM {portfolio_instance_user} 534 WHERE userid = ? AND name = ? AND ' . $DB->sql_compare_text('value') . ' = ? 535 )'; 536 $values = array_merge($values, array($USER->id, 'visible', 0)); 537 } 538 $sql .= ' ORDER BY name'; 539 540 $instances = array(); 541 foreach ($DB->get_records_sql($sql, $values) as $instance) { 542 $instances[$instance->id] = portfolio_instance($instance->id, $instance); 543 } 544 return $instances; 545 } 546 547 /** 548 * Return whether there are visible instances in portfolio. 549 * 550 * @return bool true when there are some visible instances. 551 */ 552 function portfolio_has_visible_instances() { 553 global $DB; 554 return $DB->record_exists('portfolio_instance', array('visible' => 1)); 555 } 556 557 /** 558 * Supported formats currently in use. 559 * Canonical place for a list of all formats 560 * that portfolio plugins and callers 561 * can use for exporting content 562 * 563 * @return array keyed array of all the available export formats (constant => classname) 564 */ 565 function portfolio_supported_formats() { 566 return array( 567 PORTFOLIO_FORMAT_FILE => 'portfolio_format_file', 568 PORTFOLIO_FORMAT_IMAGE => 'portfolio_format_image', 569 PORTFOLIO_FORMAT_RICHHTML => 'portfolio_format_richhtml', 570 PORTFOLIO_FORMAT_PLAINHTML => 'portfolio_format_plainhtml', 571 PORTFOLIO_FORMAT_TEXT => 'portfolio_format_text', 572 PORTFOLIO_FORMAT_VIDEO => 'portfolio_format_video', 573 PORTFOLIO_FORMAT_PDF => 'portfolio_format_pdf', 574 PORTFOLIO_FORMAT_DOCUMENT => 'portfolio_format_document', 575 PORTFOLIO_FORMAT_SPREADSHEET => 'portfolio_format_spreadsheet', 576 PORTFOLIO_FORMAT_PRESENTATION => 'portfolio_format_presentation', 577 /*PORTFOLIO_FORMAT_MBKP, */ // later 578 PORTFOLIO_FORMAT_LEAP2A => 'portfolio_format_leap2a', 579 PORTFOLIO_FORMAT_RICH => 'portfolio_format_rich', 580 ); 581 } 582 583 /** 584 * Deduce export format from file mimetype 585 * This function returns the revelant portfolio export format 586 * which is used to determine which portfolio plugins can be used 587 * for exporting this content 588 * according to the given mime type 589 * this only works when exporting exactly <b>one</b> file, or generating a new one 590 * (like a pdf or csv export) 591 * 592 * @param string $mimetype (usually $file->get_mimetype()) 593 * @return string the format constant (see PORTFOLIO_FORMAT_XXX constants) 594 */ 595 function portfolio_format_from_mimetype($mimetype) { 596 global $CFG; 597 static $alreadymatched; 598 if (empty($alreadymatched)) { 599 $alreadymatched = array(); 600 } 601 if (array_key_exists($mimetype, $alreadymatched)) { 602 return $alreadymatched[$mimetype]; 603 } 604 $allformats = portfolio_supported_formats(); 605 require_once($CFG->libdir . '/portfolio/formats.php'); 606 foreach ($allformats as $format => $classname) { 607 $supportedmimetypes = call_user_func(array($classname, 'mimetypes')); 608 if (!is_array($supportedmimetypes)) { 609 debugging("one of the portfolio format classes, $classname, said it supported something funny for mimetypes, should have been array..."); 610 debugging(print_r($supportedmimetypes, true)); 611 continue; 612 } 613 if (in_array($mimetype, $supportedmimetypes)) { 614 $alreadymatched[$mimetype] = $format; 615 return $format; 616 } 617 } 618 return PORTFOLIO_FORMAT_FILE; // base case for files... 619 } 620 621 /** 622 * Intersection of plugin formats and caller formats. 623 * Walks both the caller formats and portfolio plugin formats 624 * and looks for matches (walking the hierarchy as well) 625 * and returns the intersection 626 * 627 * @param array $callerformats formats the caller supports 628 * @param array $pluginformats formats the portfolio plugin supports 629 * @return array 630 */ 631 function portfolio_supported_formats_intersect($callerformats, $pluginformats) { 632 global $CFG; 633 $allformats = portfolio_supported_formats(); 634 $intersection = array(); 635 foreach ($callerformats as $cf) { 636 if (!array_key_exists($cf, $allformats)) { 637 if (!portfolio_format_is_abstract($cf)) { 638 debugging(get_string('invalidformat', 'portfolio', $cf)); 639 } 640 continue; 641 } 642 require_once($CFG->libdir . '/portfolio/formats.php'); 643 $cfobj = new $allformats[$cf](); 644 foreach ($pluginformats as $p => $pf) { 645 if (!array_key_exists($pf, $allformats)) { 646 if (!portfolio_format_is_abstract($pf)) { 647 debugging(get_string('invalidformat', 'portfolio', $pf)); 648 } 649 unset($pluginformats[$p]); // to avoid the same warning over and over 650 continue; 651 } 652 if ($cfobj instanceof $allformats[$pf]) { 653 $intersection[] = $cf; 654 } 655 } 656 } 657 return $intersection; 658 } 659 660 /** 661 * Tiny helper to figure out whether a portfolio format is abstract 662 * 663 * @param string $format the format to test 664 * @return bool 665 */ 666 function portfolio_format_is_abstract($format) { 667 if (class_exists($format)) { 668 $class = $format; 669 } else if (class_exists('portfolio_format_' . $format)) { 670 $class = 'portfolio_format_' . $format; 671 } else { 672 $allformats = portfolio_supported_formats(); 673 if (array_key_exists($format, $allformats)) { 674 $class = $allformats[$format]; 675 } 676 } 677 if (empty($class)) { 678 return true; // it may as well be, we can't instantiate it :) 679 } 680 $rc = new ReflectionClass($class); 681 return $rc->isAbstract(); 682 } 683 684 /** 685 * Return the combination of the two arrays of formats with duplicates in terms of specificity removed 686 * and also removes conflicting formats. 687 * Use case: a module is exporting a single file, so the general formats would be FILE and MBKP 688 * while the specific formats would be the specific subclass of FILE based on mime (say IMAGE) 689 * and this function would return IMAGE and MBKP 690 * 691 * @param array $specificformats array of more specific formats (eg based on mime detection) 692 * @param array $generalformats array of more general formats (usually more supported) 693 * @return array merged formats with dups removed 694 */ 695 function portfolio_most_specific_formats($specificformats, $generalformats) { 696 global $CFG; 697 $allformats = portfolio_supported_formats(); 698 if (empty($specificformats)) { 699 return $generalformats; 700 } else if (empty($generalformats)) { 701 return $specificformats; 702 } 703 $removedformats = array(); 704 foreach ($specificformats as $k => $f) { 705 // look for something less specific and remove it, ie outside of the inheritance tree of the current formats. 706 if (!array_key_exists($f, $allformats)) { 707 if (!portfolio_format_is_abstract($f)) { 708 throw new portfolio_button_exception('invalidformat', 'portfolio', $f); 709 } 710 } 711 if (in_array($f, $removedformats)) { 712 // already been removed from the general list 713 //debugging("skipping $f because it was already removed"); 714 unset($specificformats[$k]); 715 } 716 require_once($CFG->libdir . '/portfolio/formats.php'); 717 $fobj = new $allformats[$f]; 718 foreach ($generalformats as $key => $cf) { 719 if (in_array($cf, $removedformats)) { 720 //debugging("skipping $cf because it was already removed"); 721 continue; 722 } 723 $cfclass = $allformats[$cf]; 724 $cfobj = new $allformats[$cf]; 725 if ($fobj instanceof $cfclass && $cfclass != get_class($fobj)) { 726 //debugging("unsetting $key $cf because it's not specific enough ($f is better)"); 727 unset($generalformats[$key]); 728 $removedformats[] = $cf; 729 continue; 730 } 731 // check for conflicts 732 if ($fobj->conflicts($cf)) { 733 //debugging("unsetting $key $cf because it conflicts with $f"); 734 unset($generalformats[$key]); 735 $removedformats[] = $cf; 736 continue; 737 } 738 if ($cfobj->conflicts($f)) { 739 //debugging("unsetting $key $cf because it reverse-conflicts with $f"); 740 $removedformats[] = $cf; 741 unset($generalformats[$key]); 742 continue; 743 } 744 } 745 //debugging('inside loop'); 746 //print_object($generalformats); 747 } 748 749 //debugging('final formats'); 750 $finalformats = array_unique(array_merge(array_values($specificformats), array_values($generalformats))); 751 //print_object($finalformats); 752 return $finalformats; 753 } 754 755 /** 756 * Helper function to return a format object from the constant 757 * 758 * @param string $name the constant PORTFOLIO_FORMAT_XXX 759 * @return portfolio_format 760 */ 761 function portfolio_format_object($name) { 762 global $CFG; 763 require_once($CFG->libdir . '/portfolio/formats.php'); 764 $formats = portfolio_supported_formats(); 765 return new $formats[$name]; 766 } 767 768 /** 769 * Helper function to return an instance of a plugin (with config loaded) 770 * 771 * @param int $instanceid id of instance 772 * @param object $record database row that corresponds to this instance 773 * this is passed to avoid unnecessary lookups 774 * Optional, and the record will be retrieved if null. 775 * @return object of portfolio_plugin_XXX 776 */ 777 function portfolio_instance($instanceid, $record=null) { 778 global $DB, $CFG; 779 780 if ($record) { 781 $instance = $record; 782 } else { 783 if (!$instance = $DB->get_record('portfolio_instance', array('id' => $instanceid))) { 784 throw new portfolio_exception('invalidinstance', 'portfolio'); 785 } 786 } 787 require_once($CFG->libdir . '/portfolio/plugin.php'); 788 require_once($CFG->dirroot . '/portfolio/'. $instance->plugin . '/lib.php'); 789 $classname = 'portfolio_plugin_' . $instance->plugin; 790 return new $classname($instanceid, $instance); 791 } 792 793 /** 794 * Helper function to call a static function on a portfolio plugin class. 795 * This will figure out the classname and require the right file and call the function. 796 * You can send a variable number of arguments to this function after the first two 797 * and they will be passed on to the function you wish to call. 798 * 799 * @param string $plugin name of plugin 800 * @param string $function function to call 801 * @return mixed 802 */ 803 function portfolio_static_function($plugin, $function) { 804 global $CFG; 805 806 $pname = null; 807 if (is_object($plugin) || is_array($plugin)) { 808 $plugin = (object)$plugin; 809 $pname = $plugin->name; 810 } else { 811 $pname = $plugin; 812 } 813 814 $args = func_get_args(); 815 if (count($args) <= 2) { 816 $args = array(); 817 } 818 else { 819 array_shift($args); 820 array_shift($args); 821 } 822 823 require_once($CFG->libdir . '/portfolio/plugin.php'); 824 require_once($CFG->dirroot . '/portfolio/' . $plugin . '/lib.php'); 825 return call_user_func_array(array('portfolio_plugin_' . $plugin, $function), $args); 826 } 827 828 /** 829 * Helper function to check all the plugins for sanity and set any insane ones to invisible. 830 * 831 * @param array $plugins array of supported plugin types 832 * @return array array of insane instances (keys= id, values = reasons (keys for plugin lang) 833 */ 834 function portfolio_plugin_sanity_check($plugins=null) { 835 global $DB; 836 if (is_string($plugins)) { 837 $plugins = array($plugins); 838 } else if (empty($plugins)) { 839 $plugins = core_component::get_plugin_list('portfolio'); 840 $plugins = array_keys($plugins); 841 } 842 843 $insane = array(); 844 foreach ($plugins as $plugin) { 845 if ($result = portfolio_static_function($plugin, 'plugin_sanity_check')) { 846 $insane[$plugin] = $result; 847 } 848 } 849 if (empty($insane)) { 850 return array(); 851 } 852 list($where, $params) = $DB->get_in_or_equal(array_keys($insane)); 853 $where = ' plugin ' . $where; 854 $DB->set_field_select('portfolio_instance', 'visible', 0, $where, $params); 855 return $insane; 856 } 857 858 /** 859 * Helper function to check all the instances for sanity and set any insane ones to invisible. 860 * 861 * @param array $instances array of plugin instances 862 * @return array array of insane instances (keys= id, values = reasons (keys for plugin lang) 863 */ 864 function portfolio_instance_sanity_check($instances=null) { 865 global $DB; 866 if (empty($instances)) { 867 $instances = portfolio_instances(false); 868 } else if (!is_array($instances)) { 869 $instances = array($instances); 870 } 871 872 $insane = array(); 873 foreach ($instances as $instance) { 874 if (is_object($instance) && !($instance instanceof portfolio_plugin_base)) { 875 $instance = portfolio_instance($instance->id, $instance); 876 } else if (is_numeric($instance)) { 877 $instance = portfolio_instance($instance); 878 } 879 if (!($instance instanceof portfolio_plugin_base)) { 880 debugging('something weird passed to portfolio_instance_sanity_check, not subclass or id'); 881 continue; 882 } 883 if ($result = $instance->instance_sanity_check()) { 884 $insane[$instance->get('id')] = $result; 885 } 886 } 887 if (empty($insane)) { 888 return array(); 889 } 890 list ($where, $params) = $DB->get_in_or_equal(array_keys($insane)); 891 $where = ' id ' . $where; 892 $DB->set_field_select('portfolio_instance', 'visible', 0, $where, $params); 893 portfolio_insane_notify_admins($insane, true); 894 return $insane; 895 } 896 897 /** 898 * Helper function to display a table of plugins (or instances) and reasons for disabling 899 * 900 * @param array $insane array of portfolio plugin 901 * @param array $instances if reporting instances rather than whole plugins, pass the array (key = id, value = object) here 902 * @param bool $return option to deliver the report in html format or print it out directly to the page. 903 * @return void|string of portfolio report in html table format 904 */ 905 function portfolio_report_insane($insane, $instances=false, $return=false) { 906 global $OUTPUT; 907 if (empty($insane)) { 908 return; 909 } 910 911 static $pluginstr; 912 if (empty($pluginstr)) { 913 $pluginstr = get_string('plugin', 'portfolio'); 914 } 915 if ($instances) { 916 $headerstr = get_string('someinstancesdisabled', 'portfolio'); 917 } else { 918 $headerstr = get_string('somepluginsdisabled', 'portfolio'); 919 } 920 921 $output = $OUTPUT->notification($headerstr, 'notifyproblem'); 922 $table = new html_table(); 923 $table->head = array($pluginstr, ''); 924 $table->data = array(); 925 foreach ($insane as $plugin => $reason) { 926 if ($instances) { 927 $instance = $instances[$plugin]; 928 $plugin = $instance->get('plugin'); 929 $name = $instance->get('name'); 930 } else { 931 $name = $plugin; 932 } 933 $table->data[] = array($name, get_string($reason, 'portfolio_' . $plugin)); 934 } 935 $output .= html_writer::table($table); 936 $output .= '<br /><br /><br />'; 937 938 if ($return) { 939 return $output; 940 } 941 echo $output; 942 } 943 944 /** 945 * Helper function to rethrow a caught portfolio_exception as an export exception. 946 * Used because when a portfolio_export exception is thrown the export is cancelled 947 * throws portfolio_export_exceptiog 948 * 949 * @param portfolio_exporter $exporter current exporter object 950 * @param object $exception exception to rethrow 951 */ 952 function portfolio_export_rethrow_exception($exporter, $exception) { 953 throw new portfolio_export_exception($exporter, $exception->errorcode, $exception->module, $exception->link, $exception->a); 954 } 955 956 /** 957 * Try and determine expected_time for purely file based exports 958 * or exports that might include large file attachments. 959 * 960 * @param stored_file|array $totest - either an array of stored_file objects or a single stored_file object 961 * @return string PORTFOLIO_TIME_XXX 962 */ 963 function portfolio_expected_time_file($totest) { 964 global $CFG; 965 if ($totest instanceof stored_file) { 966 $totest = array($totest); 967 } 968 $size = 0; 969 foreach ($totest as $file) { 970 if (!($file instanceof stored_file)) { 971 debugging('something weird passed to portfolio_expected_time_file - not stored_file object'); 972 debugging(print_r($file, true)); 973 continue; 974 } 975 $size += $file->get_filesize(); 976 } 977 978 $fileinfo = portfolio_filesize_info(); 979 980 $moderate = $high = 0; // avoid warnings 981 982 foreach (array('moderate', 'high') as $setting) { 983 $settingname = 'portfolio_' . $setting . '_filesize_threshold'; 984 if (empty($CFG->{$settingname}) || !array_key_exists($CFG->{$settingname}, $fileinfo['options'])) { 985 debugging("weird or unset admin value for $settingname, using default instead"); 986 $$setting = $fileinfo[$setting]; 987 } else { 988 $$setting = $CFG->{$settingname}; 989 } 990 } 991 992 if ($size < $moderate) { 993 return PORTFOLIO_TIME_LOW; 994 } else if ($size < $high) { 995 return PORTFOLIO_TIME_MODERATE; 996 } 997 return PORTFOLIO_TIME_HIGH; 998 } 999 1000 1001 /** 1002 * The default filesizes and threshold information for file based transfers. 1003 * This shouldn't need to be used outside the admin pages and the portfolio code 1004 * 1005 * @return array 1006 */ 1007 function portfolio_filesize_info() { 1008 $filesizes = array(); 1009 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152, 5242880, 10485760, 20971520, 52428800); 1010 foreach ($sizelist as $size) { 1011 $filesizes[$size] = display_size($size, 0); 1012 } 1013 return array( 1014 'options' => $filesizes, 1015 'moderate' => 1048576, 1016 'high' => 5242880, 1017 ); 1018 } 1019 1020 /** 1021 * Try and determine expected_time for purely database based exports 1022 * or exports that might include large parts of a database. 1023 * 1024 * @param int $recordcount number of records trying to export 1025 * @return string PORTFOLIO_TIME_XXX 1026 */ 1027 function portfolio_expected_time_db($recordcount) { 1028 global $CFG; 1029 1030 if (empty($CFG->portfolio_moderate_db_threshold)) { 1031 set_config('portfolio_moderate_db_threshold', 20); 1032 } 1033 if (empty($CFG->portfolio_high_db_threshold)) { 1034 set_config('portfolio_high_db_threshold', 50); 1035 } 1036 if ($recordcount < $CFG->portfolio_moderate_db_threshold) { 1037 return PORTFOLIO_TIME_LOW; 1038 } else if ($recordcount < $CFG->portfolio_high_db_threshold) { 1039 return PORTFOLIO_TIME_MODERATE; 1040 } 1041 return PORTFOLIO_TIME_HIGH; 1042 } 1043 1044 /** 1045 * Function to send portfolio report to admins 1046 * 1047 * @param array $insane array of insane plugins 1048 * @param array $instances (optional) if reporting instances rather than whole plugins 1049 */ 1050 function portfolio_insane_notify_admins($insane, $instances=false) { 1051 1052 global $CFG; 1053 1054 if (defined('ADMIN_EDITING_PORTFOLIO')) { 1055 return true; 1056 } 1057 1058 $admins = get_admins(); 1059 1060 if (empty($admins)) { 1061 return; 1062 } 1063 if ($instances) { 1064 $instances = portfolio_instances(false, false); 1065 } 1066 1067 $site = get_site(); 1068 1069 $a = new StdClass; 1070 $a->sitename = format_string($site->fullname, true, array('context' => context_course::instance(SITEID))); 1071 $a->fixurl = "$CFG->wwwroot/$CFG->admin/settings.php?section=manageportfolios"; 1072 $a->htmllist = portfolio_report_insane($insane, $instances, true); 1073 $a->textlist = ''; 1074 1075 foreach ($insane as $k => $reason) { 1076 if ($instances) { 1077 $a->textlist = $instances[$k]->get('name') . ': ' . $reason . "\n"; 1078 } else { 1079 $a->textlist = $k . ': ' . $reason . "\n"; 1080 } 1081 } 1082 1083 $subject = get_string('insanesubject', 'portfolio'); 1084 $plainbody = get_string('insanebody', 'portfolio', $a); 1085 $htmlbody = get_string('insanebodyhtml', 'portfolio', $a); 1086 $smallbody = get_string('insanebodysmall', 'portfolio', $a); 1087 1088 foreach ($admins as $admin) { 1089 $eventdata = new \core\message\message(); 1090 $eventdata->courseid = SITEID; 1091 $eventdata->modulename = 'portfolio'; 1092 $eventdata->component = 'portfolio'; 1093 $eventdata->name = 'notices'; 1094 $eventdata->userfrom = get_admin(); 1095 $eventdata->userto = $admin; 1096 $eventdata->subject = $subject; 1097 $eventdata->fullmessage = $plainbody; 1098 $eventdata->fullmessageformat = FORMAT_PLAIN; 1099 $eventdata->fullmessagehtml = $htmlbody; 1100 $eventdata->smallmessage = $smallbody; 1101 message_send($eventdata); 1102 } 1103 } 1104 1105 /** 1106 * Setup page export 1107 * 1108 * @param moodle_page $PAGE global variable from page object 1109 * @param portfolio_caller_base $caller plugin type caller 1110 */ 1111 function portfolio_export_pagesetup($PAGE, $caller) { 1112 // set up the context so that build_navigation works nice 1113 $caller->set_context($PAGE); 1114 1115 list($extranav, $cm) = $caller->get_navigation(); 1116 1117 // and now we know the course for sure and maybe the cm, call require_login with it 1118 require_login($PAGE->course, false, $cm); 1119 $PAGE->activityheader->set_attrs([ 1120 'description' => '', 1121 'hidecompletion' => true 1122 ]); 1123 foreach ($extranav as $navitem) { 1124 $PAGE->navbar->add($navitem['name']); 1125 } 1126 $PAGE->navbar->add(get_string('exporting', 'portfolio')); 1127 } 1128 1129 /** 1130 * Get export type id 1131 * 1132 * @param string $type plugin type 1133 * @param int $userid the user to check for 1134 * @return mixed|bool 1135 */ 1136 function portfolio_export_type_to_id($type, $userid) { 1137 global $DB; 1138 $sql = 'SELECT t.id FROM {portfolio_tempdata} t JOIN {portfolio_instance} i ON t.instance = i.id WHERE t.userid = ? AND i.plugin = ?'; 1139 return $DB->get_field_sql($sql, array($userid, $type)); 1140 } 1141 1142 /** 1143 * Return a list of current exports for the given user. 1144 * This will not go through and call rewaken_object, because it's heavy. 1145 * It's really just used to figure out what exports are currently happening. 1146 * This is useful for plugins that don't support multiple exports per session 1147 * 1148 * @param int $userid the user to check for 1149 * @param string $type (optional) the portfolio plugin to filter by 1150 * @return array 1151 */ 1152 function portfolio_existing_exports($userid, $type=null) { 1153 global $DB; 1154 $sql = 'SELECT t.*,t.instance,i.plugin,i.name FROM {portfolio_tempdata} t JOIN {portfolio_instance} i ON t.instance = i.id WHERE t.userid = ? '; 1155 $values = array($userid); 1156 if ($type) { 1157 $sql .= ' AND i.plugin = ?'; 1158 $values[] = $type; 1159 } 1160 return $DB->get_records_sql($sql, $values); 1161 } 1162 1163 /** 1164 * Return an array of existing exports by type for a given user. 1165 * This is much more lightweight than existing_exports because it only returns the types, rather than the whole serialised data 1166 * so can be used for checking availability of multiple plugins at the same time. 1167 * @see existing_exports 1168 * 1169 * @param int $userid the user to check for 1170 * @return array 1171 */ 1172 function portfolio_existing_exports_by_plugin($userid) { 1173 global $DB; 1174 $sql = 'SELECT t.id,i.plugin FROM {portfolio_tempdata} t JOIN {portfolio_instance} i ON t.instance = i.id WHERE t.userid = ? '; 1175 $values = array($userid); 1176 return $DB->get_records_sql_menu($sql, $values); 1177 } 1178 1179 /** 1180 * Return default common options for {@link format_text()} when preparing a content to be exported. 1181 * It is important not to apply filters and not to clean the HTML in format_text() 1182 * 1183 * @return stdClass 1184 */ 1185 function portfolio_format_text_options() { 1186 1187 $options = new stdClass(); 1188 $options->para = false; 1189 $options->newlines = true; 1190 $options->filter = false; 1191 $options->noclean = true; 1192 $options->overflowdiv = false; 1193 1194 return $options; 1195 } 1196 1197 /** 1198 * callback function from {@link portfolio_rewrite_pluginfile_urls} 1199 * looks through preg_replace matches and replaces content with whatever the active portfolio export format says 1200 * 1201 * @param int $contextid module context id 1202 * @param string $component module name (eg:mod_assignment) 1203 * @param string $filearea normal file_area arguments 1204 * @param int $itemid component item id 1205 * @param portfolio_format $format exporter format type 1206 * @param array $options extra options to pass through to the file_output function in the format (optional) 1207 * @param array $matches internal matching 1208 * @return object|array|string 1209 */ 1210 function portfolio_rewrite_pluginfile_url_callback($contextid, $component, $filearea, $itemid, $format, $options, $matches) { 1211 $matches = $matches[0]; // No internal matching. 1212 1213 // Loads the HTML. 1214 $dom = new DomDocument(); 1215 if (!$dom->loadHTML($matches)) { 1216 return $matches; 1217 } 1218 1219 // Navigates to the node. 1220 $xpath = new DOMXPath($dom); 1221 $nodes = $xpath->query('/html/body/child::*'); 1222 if (empty($nodes) || count($nodes) > 1) { 1223 // Unexpected sequence, none or too many nodes. 1224 return $matches; 1225 } 1226 $dom = $nodes->item(0); 1227 1228 $attributes = array(); 1229 foreach ($dom->attributes as $attr => $node) { 1230 $attributes[$attr] = $node->value; 1231 } 1232 // now figure out the file 1233 $fs = get_file_storage(); 1234 $key = 'href'; 1235 if (!array_key_exists('href', $attributes) && array_key_exists('src', $attributes)) { 1236 $key = 'src'; 1237 } 1238 if (!array_key_exists($key, $attributes)) { 1239 debugging('Couldn\'t find an attribute to use that contains @@PLUGINFILE@@ in portfolio_rewrite_pluginfile'); 1240 return $matches; 1241 } 1242 $filename = substr($attributes[$key], strpos($attributes[$key], '@@PLUGINFILE@@') + strlen('@@PLUGINFILE@@')); 1243 $filepath = '/'; 1244 if (strpos($filename, '/') !== 0) { 1245 $bits = explode('/', $filename); 1246 $filename = array_pop($bits); 1247 $filepath = implode('/', $bits); 1248 } 1249 if (!$file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, urldecode($filename))) { 1250 debugging("Couldn't find a file from the embedded path info context $contextid component $component filearea $filearea itemid $itemid filepath $filepath name $filename"); 1251 return $matches; 1252 } 1253 if (empty($options)) { 1254 $options = array(); 1255 } 1256 $options['attributes'] = $attributes; 1257 return $format->file_output($file, $options); 1258 } 1259 1260 /** 1261 * Function to require any potential callback files, throwing exceptions 1262 * if an issue occurs. 1263 * 1264 * @param string $component This is the name of the component in Moodle, eg 'mod_forum' 1265 * @param string $class Name of the class containing the callback functions 1266 * activity components should ALWAYS use their name_portfolio_caller 1267 * other locations must use something unique 1268 */ 1269 function portfolio_include_callback_file($component, $class = null) { 1270 global $CFG; 1271 require_once($CFG->libdir . '/adminlib.php'); 1272 1273 // It's possible that they are passing a file path rather than passing a component. 1274 // We want to try and convert this to a component name, eg. mod_forum. 1275 $pos = strrpos($component, '/'); 1276 if ($pos !== false) { 1277 // Get rid of the first slash (if it exists). 1278 $component = ltrim($component, '/'); 1279 // Get a list of valid plugin types. 1280 $plugintypes = core_component::get_plugin_types(); 1281 // Assume it is not valid for now. 1282 $isvalid = false; 1283 // Go through the plugin types. 1284 foreach ($plugintypes as $type => $path) { 1285 // Getting the path relative to the dirroot. 1286 $path = preg_replace('|^' . preg_quote($CFG->dirroot, '|') . '/|', '', $path); 1287 if (strrpos($component, $path) === 0) { 1288 // Found the plugin type. 1289 $isvalid = true; 1290 $plugintype = $type; 1291 $pluginpath = $path; 1292 } 1293 } 1294 // Throw exception if not a valid component. 1295 if (!$isvalid) { 1296 throw new coding_exception('Somehow a non-valid plugin path was passed, could be a hackz0r attempt, exiting.'); 1297 } 1298 // Remove the file name. 1299 $component = trim(substr($component, 0, $pos), '/'); 1300 // Replace the path with the type. 1301 $component = str_replace($pluginpath, $plugintype, $component); 1302 // Ok, replace '/' with '_'. 1303 $component = str_replace('/', '_', $component); 1304 // Place a debug message saying the third parameter should be changed. 1305 debugging('The third parameter sent to the function set_callback_options should be the component name, not a file path, please update this.', DEBUG_DEVELOPER); 1306 } 1307 1308 // Check that it is a valid component. 1309 if (!get_component_version($component)) { 1310 throw new portfolio_button_exception('nocallbackcomponent', 'portfolio', '', $component); 1311 } 1312 1313 // Obtain the component's location. 1314 if (!$componentloc = core_component::get_component_directory($component)) { 1315 throw new portfolio_button_exception('nocallbackcomponent', 'portfolio', '', $component); 1316 } 1317 1318 // Check if the component contains the necessary file for the portfolio plugin. 1319 // These are locallib.php, portfoliolib.php and portfolio_callback.php. 1320 $filefound = false; 1321 if (file_exists($componentloc . '/locallib.php')) { 1322 $filefound = true; 1323 require_once($componentloc . '/locallib.php'); 1324 } 1325 if (file_exists($componentloc . '/portfoliolib.php')) { 1326 $filefound = true; 1327 debugging('Please standardise your plugin by renaming your portfolio callback file to locallib.php, or if that file already exists moving the portfolio functionality there.', DEBUG_DEVELOPER); 1328 require_once ($componentloc . '/portfoliolib.php'); 1329 } 1330 if (file_exists($componentloc . '/portfolio_callback.php')) { 1331 $filefound = true; 1332 debugging('Please standardise your plugin by renaming your portfolio callback file to locallib.php, or if that file already exists moving the portfolio functionality there.', DEBUG_DEVELOPER); 1333 require_once($componentloc . '/portfolio_callback.php'); 1334 } 1335 1336 // Ensure that we found a file we can use, if not throw an exception. 1337 if (!$filefound) { 1338 throw new portfolio_button_exception('nocallbackfile', 'portfolio', '', $component); 1339 } 1340 1341 if (!is_null($class)) { 1342 // If class is specified, check it exists and extends portfolio_caller_base. 1343 if (!class_exists($class) || !is_subclass_of($class, 'portfolio_caller_base')) { 1344 throw new portfolio_button_exception('nocallbackclass', 'portfolio', '', $class); 1345 } 1346 } 1347 } 1348 1349 /** 1350 * Go through all the @@PLUGINFILE@@ matches in some text, 1351 * extract the file information and pass it back to the portfolio export format 1352 * to regenerate the html to output 1353 * 1354 * @param string $text the text to search through 1355 * @param int $contextid normal file_area arguments 1356 * @param string $component module name 1357 * @param string $filearea normal file_area arguments 1358 * @param int $itemid normal file_area arguments 1359 * @param portfolio_format $format the portfolio export format 1360 * @param array $options additional options to be included in the plugin file url (optional) 1361 * @return mixed 1362 */ 1363 function portfolio_rewrite_pluginfile_urls($text, $contextid, $component, $filearea, $itemid, $format, $options=null) { 1364 $patterns = array( 1365 '(<(a|A)[^<]*?href="@@PLUGINFILE@@/[^>]*?>.*?</(a|A)>)', 1366 '(<(img|IMG)\s[^<]*?src="@@PLUGINFILE@@/[^>]*?/?>)', 1367 ); 1368 $pattern = '~' . implode('|', $patterns) . '~'; 1369 $callback = partial('portfolio_rewrite_pluginfile_url_callback', $contextid, $component, $filearea, $itemid, $format, $options); 1370 return preg_replace_callback($pattern, $callback, $text); 1371 } 1372 // this function has to go last, because the regexp screws up syntax highlighting in some editors
title
Description
Body
title
Description
Body
title
Description
Body
title
Body