MDL-54945 workshop: Improve the portfolio API implementation
authorDavid Mudrák <david@moodle.com>
Tue, 11 Oct 2016 10:36:44 +0000 (12:36 +0200)
committerDavid Mudrák <david@moodle.com>
Wed, 12 Oct 2016 16:35:02 +0000 (18:35 +0200)
* Use a single capability 'mod/workshop:exportsubmissions' to control
  who is able to export a submission and all associated visible
  peer-assessments.
* The export capability is by default granted to teachers and managers
  as well.
* Simplify the integration of the export button into the submission
  page. There is now a single place where the export button appears - at
  the buttom of the submission page.
* Clean up the new strings introduced for this feature.
* Rewrite the portfolio caller class so that it better mimics the
  behaviour of the submission.php page. It must not be possible to get
  access to contents otherwise not available via the UI.
* Fix phpunit tests.
* Improve behat tests so that they can run in headless browser
  (performance).

During the development, some problems were detected in the underlying
portfolio API, especially with LEAP2A format and files collisions
handling. These may be eventually fixed in the future, should there be a
demand for it.

mod/workshop/classes/portfolio_caller.php
mod/workshop/db/access.php
mod/workshop/lang/en/workshop.php
mod/workshop/submission.php
mod/workshop/tests/behat/behat_mod_workshop.php
mod/workshop/tests/behat/export_assessment.feature [deleted file]
mod/workshop/tests/behat/export_submission.feature
mod/workshop/tests/portfolio_caller_test.php
mod/workshop/version.php

index c20ae18..a361b24 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Workshop portfolio caller class to integrate with portfolio API.
+ * Provides the {@link mod_workshop_portfolio_caller} class.
  *
  * @package   mod_workshop
+ * @category  portfolio
  * @copyright Loc Nguyen <ndloc1905@gmail.com>
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 defined('MOODLE_INTERNAL') || die();
 require_once($CFG->libdir . '/portfolio/caller.php');
 
-
 /**
- * Workshop portfolio caller subclass to handle export submission and assessment to portfolio.
+ * Workshop portfolio caller class to integrate with portfolio API.
  *
  * @package   mod_workshop
  * @copyright Loc Nguyen <ndloc1905@gmail.com>
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class mod_workshop_portfolio_caller extends portfolio_module_caller_base {
-    /**
-     * @var int ID of submission.
-     */
+
+    /** @var workshop The workshop instance where the export is happening. */
+    protected $workshop;
+
+    /** @var int ID if the exported submission, set via the constructor. */
     protected $submissionid;
-    /**
-     * @var int ID of assessment.
-     */
-    protected $assessmentid;
-    /**
-     * @var stdClass Course module.
-     */
-    protected $cm;
-    /**
-     * @var array Submission files list.
-     */
-    protected $submissionfiles = array();
-    /**
-     * @var array Assessment files list.
-     */
-    protected $assessmentfiles = array();
+
+    /** @var object The submission being exported. */
+    protected $submission;
+
+    /** @var array of objects List of assessments of the exported submission. */
+    protected $assessments = [];
 
     /**
-     * @var stdClass Submission object load from DB.
-     */
-    private $submission;
-    /**
-     * @var stdClass Assessment object.
-     */
-    private $assessment;
-    /**
-     * @var stdClass User object.
-     */
-    private $author;
-    /**
-     * @var boolean Show author permission.
+     * Explicit constructor to set the properties declared by the parent class.
+     *
+     * Firstly we call the parent's constructor to set the $this->id property
+     * from the passed argument. Then we populate the $this->cm so that the
+     * default parent class methods work well.
+     *
+     * @param array $callbackargs
      */
-    private $showauthor;
+    public function __construct($callbackargs) {
+
+        // Let the parent class set the $this->id property.
+        parent::__construct($callbackargs);
+        // Populate the $this->cm property.
+        $this->cm = get_coursemodule_from_id('workshop', $this->id, 0, false, MUST_EXIST);
+    }
 
     /**
-     * Return array of expected call back arguments
+     * Return array of expected callback arguments and whether they are required or not.
      *
-     * and whether they are required or not.
+     * The 'id' argument is supposed to be our course module id (cmid) - see
+     * the parent class' properties.
      *
-     * @return array
+     * @return array of (string)callbackname => (bool)required
      */
     public static function expected_callbackargs() {
-        return array(
-            'submissionid' => false,
-            'assessmentid' => false,
-        );
+        return [
+            'id' => true,
+            'submissionid' => true,
+        ];
     }
 
     /**
      * Load data required for the export.
-     *
-     * Load submission and assessment by submissionid, assessmentid.
-     *
-     * @return void
      */
     public function load_data() {
-        global $DB;
-
-        $this->submission = $DB->get_record('workshop_submissions', array('id' => $this->submissionid), '*', MUST_EXIST);
-        $this->cm = get_coursemodule_from_instance('workshop', $this->submission->workshopid, 0, false, MUST_EXIST);
-        $this->author = $DB->get_record('user', array('id' => $this->submission->authorid));
-
-        $workshoprecord = $DB->get_record('workshop', array('id' => $this->cm->instance), '*', MUST_EXIST);
-        $course = $DB->get_record('course', array('id' => $this->cm->course), '*', MUST_EXIST);
-        $this->workshop = new workshop($workshoprecord, $this->cm, $course);
-
-        // Load data for export assessment.
-        if ($this->submissionid && $this->assessmentid) {
-            $assessments    = $this->workshop->get_assessments_of_submission($this->submission->id);
-            $this->assessment = new stdClass();
-            $this->assessment = $assessments[$this->assessmentid];
-            $fs = get_file_storage();
-            $this->assessmentfiles = $fs->get_area_files($this->workshop->context->id, 'mod_workshop',
-                'overallfeedback_attachment', $this->assessment->id);
+        global $DB, $USER;
+
+        // Note that require_login() is normally called later as a part of
+        // portfolio_export_pagesetup() in the portfolio/add.php file. But we
+        // load various data depending of capabilities so it makes sense to
+        // call it explicitly here, too.
+        require_login($this->get('course'), false, $this->cm, false, true);
+
+        if (isguestuser()) {
+            throw new portfolio_caller_exception('guestsarenotallowed', 'core_error');
         }
-        // Load data for export submission.
-        if ($this->submissionid && !$this->assessmentid) {
-            $fs = get_file_storage();
-            $submissioncontentfiles = $fs->get_area_files($this->workshop->context->id,
-                'mod_workshop', 'submission_content', $this->submissionid);
-            $submissionattachmentfiles = $fs->get_area_files($this->workshop->context->id,
-                'mod_workshop', 'submission_attachment', $this->submissionid);
-            $submissioninstructionfiles = $fs->get_area_files($this->workshop->context->id,
-                'mod_workshop', 'instructauthors');
-            $this->submissionfiles = array_merge($submissioncontentfiles, $submissionattachmentfiles, $submissioninstructionfiles);
-
-            // Check anonymity of exported, show author or not.
-            $ispublished    = ($this->workshop->phase == workshop::PHASE_CLOSED
-                and $this->submission->published == 1
-                and has_capability('mod/workshop:viewpublishedsubmissions', $this->workshop->context));
-            $seenaspublished = false; // Is the submission seen as a published submission?
-
-            if ($ispublished) {
-                $seenaspublished = true;
-            }
-            if ($seenaspublished) {
-                $this->showauthor = has_capability('mod/workshop:viewauthorpublished', $this->workshop->context);
-            } else {
-                $this->showauthor = has_capability('mod/workshop:viewauthornames', $this->workshop->context);
+
+        $workshoprecord = $DB->get_record('workshop', ['id' => $this->cm->instance], '*', MUST_EXIST);
+        $this->workshop = new workshop($workshoprecord, $this->cm, $this->get('course'));
+
+        $this->submission = $this->workshop->get_submission_by_id($this->submissionid);
+
+        // Is the user exporting her/his own submission?
+        $ownsubmission = $this->submission->authorid == $USER->id;
+
+        // Does the user have permission to see all submissions (aka is it a teacher)?
+        $canviewallsubmissions = has_capability('mod/workshop:viewallsubmissions', $this->workshop->context);
+        $canviewallsubmissions = $canviewallsubmissions && $this->workshop->check_group_membership($this->submission->authorid);
+
+        // Is the user exporting a submission that she/he has peer-assessed?
+        $userassessment = $this->workshop->get_assessment_of_submission_by_user($this->submission->id, $USER->id);
+        if ($userassessment) {
+            $this->assessments[$userassessment->id] = $userassessment;
+            $isreviewer = true;
+        }
+
+        if (!$ownsubmission and !$canviewallsubmissions and !$isreviewer) {
+            throw new portfolio_caller_exception('nopermissions', 'core_error');
+        }
+
+        // Does the user have permission to see all assessments (aka is it a teacher)?
+        $canviewallassessments = has_capability('mod/workshop:viewallassessments', $this->workshop->context);
+
+        // Load other assessments eventually if the user can see them.
+        if ($canviewallassessments or ($ownsubmission and $this->workshop->assessments_available())) {
+            foreach ($this->workshop->get_assessments_of_submission($this->submission->id) as $assessment) {
+                if ($assessment->reviewerid == $USER->id) {
+                    // User's own assessment is already loaded.
+                    continue;
+                }
+                if (is_null($assessment->grade) and !$canviewallassessments) {
+                    // Students do not see peer-assessment that are not graded.
+                    continue;
+                }
+                $this->assessments[$assessment->id] = $assessment;
             }
         }
+
+        // Prepare embedded and attached files for the export.
+        $this->multifiles = [];
+
+        $this->add_area_files('submission_content', $this->submission->id);
+        $this->add_area_files('submission_attachment', $this->submission->id);
+
+        foreach ($this->assessments as $assessment) {
+            $this->add_area_files('overallfeedback_content', $assessment->id);
+            $this->add_area_files('overallfeedback_attachment', $assessment->id);
+        }
+
+        $this->add_area_files('instructauthors', 0);
+
+        // If there are no files to be exported, we can offer plain HTML file export.
+        if (empty($this->multifiles)) {
+            $this->add_format(PORTFOLIO_FORMAT_PLAINHTML);
+        }
     }
 
     /**
      * Prepare the package ready to be passed off to the portfolio plugin.
-     *
-     * @return void
      */
     public function prepare_package() {
-        // Set up the leap2a writer if we need it.
+
+        $canviewauthornames = has_capability('mod/workshop:viewauthornames', $this->workshop->context, $this->get('user'));
+
+        // Prepare the submission record for rendering.
+        $workshopsubmission = $this->workshop->prepare_submission($this->submission, $canviewauthornames);
+
+        // Set up the LEAP2A writer if we need it.
         $writingleap = false;
+
         if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
             $leapwriter = $this->exporter->get('format')->leap2a_writer();
             $writingleap = true;
         }
 
-        if ($this->submissionid && $this->assessmentid) {
-            $assessmenthtml = $this->prepare_assessment($this->assessment);
-            $content = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $assessmenthtml);
-            $name = 'assessment.html';
-            if ($writingleap) {
-                $this->prepare_assessment_leap2a($leapwriter, $this->assessment, $content);
-                $content = $leapwriter->to_xml();
-                $name = $this->exporter->get('format')->manifest_name();
-            }
+        // If writing to HTML file, accumulate the exported hypertext here.
+        $html = '';
+
+        // If writing LEAP2A, keep track of all entry ids so we can add a selection element.
+        $leapids = [];
+
+        $html .= $this->export_header($workshopsubmission);
+        $content = $this->export_content($workshopsubmission);
+        // Get rid of the JS relics left by moodleforms.
+        $content = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $content);
+        $html .= $content;
+
+        if ($writingleap) {
+            $leapids[] = $this->export_content_leap2a($leapwriter, $workshopsubmission, $content);
         }
-        if ($this->submissionid && !$this->assessmentid) {
-            $submissionhtml = $this->prepare_submission($this->submission);
-            $content = $submissionhtml;
-            $name = 'submission.html';
-            if ($writingleap) {
-                $this->prepare_submission_leap2a($leapwriter, $this->submission, $content);
-                $content = $leapwriter->to_xml();
-                $name = $this->exporter->get('format')->manifest_name();
-            }
+
+        // Export the files.
+        foreach ($this->multifiles as $file) {
+            $this->exporter->copy_existing_file($file);
+        }
+
+        if ($writingleap) {
+            // Add an extra LEAP2A selection entry. In Mahara, this maps to a journal.
+            $selection = new portfolio_format_leap2a_entry('workshop'.$this->workshop->id,
+                get_string('pluginname', 'mod_workshop').': '.s($this->workshop->name), 'selection');
+            $leapwriter->add_entry($selection);
+            $leapwriter->make_selection($selection, $leapids, 'Grouping');
+            $leapxml = $leapwriter->to_xml();
+            $name = $this->exporter->get('format')->manifest_name();
+            $this->exporter->write_new_file($leapxml, $name, true);
+
+        } else {
+            $this->exporter->write_new_file($html, 'submission.html', true);
+        }
+    }
+
+    /**
+     * Helper method to add all files from the given location to $this->multifiles
+     *
+     * @param string $filearea
+     * @param int $itemid
+     */
+    protected function add_area_files($filearea, $itemid) {
+
+        $fs = get_file_storage();
+        $areafiles = $fs->get_area_files($this->workshop->context->id, 'mod_workshop', $filearea, $itemid, null, false);
+        if ($areafiles) {
+            $this->multifiles = array_merge($this->multifiles, array_values($areafiles));
         }
-        $manifest = ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH);
-        $this->get('exporter')->write_new_file($content, $name, $manifest);
     }
 
     /**
-     * Helper function to output submission for export.
+     * Render the header of the exported content.
      *
-     * This is cut down version of workshop submission renderer function.
+     * This is mainly used for the HTML output format. In case of LEAP2A
+     * export, this is not used as the information is stored in metadata and
+     * displayed as a part of the journal and entry title in Mahara.
      *
-     * @param object $submission
-     * @return string
+     * @param workshop_submission $workshopsubmission
+     * @return string HTML
      */
-    private function prepare_submission($submission) {
-        global $CFG;
+    protected function export_header(workshop_submission $workshopsubmission) {
+
         $output = '';
-        $output .= '<meta charset="UTF-8">';
-        $output .= html_writer::tag("h2", $this->workshop->name);
-
-        // Write submission content.
-        $output .= html_writer::start_tag('div', array('class' => 'submissionheader', 'style' => 'background-color: #ddd;'));
-        $output .= html_writer::tag("h3", format_string($submission->title));
-        if ($this->showauthor) {
-            $output .= html_writer::tag('div', get_string('byfullnamewithoutlink', 'workshop', fullname($this->author)));
+        $output .= html_writer::tag('h2', get_string('pluginname', 'mod_workshop').': '.s($this->workshop->name));
+        $output .= html_writer::tag('h3', s($workshopsubmission->title));
+
+        $created = get_string('userdatecreated', 'workshop', userdate($workshopsubmission->timecreated));
+        $created = html_writer::tag('span', $created);
+
+        if ($workshopsubmission->timemodified > $workshopsubmission->timecreated) {
+            $modified = get_string('userdatemodified', 'workshop', userdate($workshopsubmission->timemodified));
+            $modified = ' | ' . html_writer::tag('span', $modified);
+        } else {
+            $modified = '';
         }
-        $created = get_string('userdatecreated', 'workshop', userdate($submission->timecreated));
-        $output .= html_writer::tag("div", $created, array('style' => 'font-size:x-small'));
 
-        if ($submission->timemodified > $submission->timecreated) {
-            $modified = get_string('userdatemodified', 'workshop', userdate($submission->timemodified));
-            $output .= html_writer::tag("div", $modified, array('style' => 'font-size:x-small'));
+        $output .= html_writer::div($created.$modified);
+        $output .= html_writer::empty_tag('br');
+
+        return $output;
+    }
+
+    /**
+     * Render the content of the submission.
+     *
+     * @param workshop_submission $workshopsubmission
+     * @return string
+     */
+    protected function export_content(workshop_submission $workshopsubmission) {
+
+        $output = '';
+
+        if (!$workshopsubmission->is_anonymous()) {
+            $author = username_load_fields_from_object((object)[], $workshopsubmission, 'author');
+            $output .= html_writer::div(get_string('byfullnamewithoutlink', 'mod_workshop', fullname($author)));
         }
-        $output .= html_writer::end_tag('div');
-        $format = $this->get('exporter')->get('format');
-        $content = portfolio_rewrite_pluginfile_urls($submission->content, $this->workshop->context->id, 'mod_workshop',
-            'submission_content', $submission->id, $format);
-        $content = format_text($content, $submission->contentformat);
-        $output .= html_writer::tag("div", $content);
-        if ($this->submissionfiles) {
-            $outputfiles = '';
-            foreach ($this->submissionfiles as $file) {
-                if ($file->is_directory()) {
-                    continue;
-                }
 
-                $filename = $file->get_filename();
-                $filearea = $file->get_filearea();
-                $type = $file->get_mimetype();
-                $linkhtml = $format->file_output($file);
+        $content = $this->format_exported_text($workshopsubmission->content, $workshopsubmission->contentformat);
+        $content = portfolio_rewrite_pluginfile_urls($content, $this->workshop->context->id, 'mod_workshop',
+            'submission_content', $workshopsubmission->id, $this->exporter->get('format'));
+        $output .= html_writer::div($content);
 
-                if ($filearea == "submission_attachment") {
-                    $outputfiles .= html_writer::tag('li', $linkhtml, array('class' => $type));
-                }
+        $output .= $this->export_files_list('submission_attachment');
 
-                if ($filename) {
-                    $this->get('exporter')->copy_existing_file($file);
-                }
+        $strategy = $this->workshop->grading_strategy_instance();
 
-                if (!empty($CFG->enableplagiarism)) {
-                    require_once($CFG->libdir . '/plagiarismlib.php');
-                    $outputfiles .= plagiarism_get_links(array('userid' => $file->get_userid(),
-                        'file' => $file,
-                        'cmid' => $this->cm->id,
-                        'course' => $this->cm->course));
-                }
+        $canviewauthornames = has_capability('mod/workshop:viewauthornames', $this->workshop->context, $this->get('user'));
+        $canviewreviewernames = has_capability('mod/workshop:viewreviewernames', $this->workshop->context, $this->get('user'));
+
+        foreach ($this->assessments as $assessment) {
+            $mform = $strategy->get_assessment_form(null, 'assessment', $assessment, false);
+            $options = [
+                'showreviewer' => $canviewreviewernames,
+                'showauthor' => $canviewauthornames,
+                'showform' => true,
+                'showweight' => true,
+            ];
+            if ($assessment->reviewerid == $this->get('user')->id) {
+                $options['showreviewer'] = true;
             }
-            $output .= $outputfiles;
+
+            $workshopassessment = $this->workshop->prepare_assessment($assessment, $mform, $options);
+
+            if ($assessment->reviewerid == $this->get('user')->id) {
+                $workshopassessment->title = get_string('assessmentbyyourself', 'mod_workshop');
+            } else {
+                $workshopassessment->title = get_string('assessment', 'mod_workshop');
+            }
+
+            $output .= html_writer::empty_tag('hr');
+            $output .= $this->export_assessment($workshopassessment);
         }
 
-        // Write submission instruction.
         if (trim($this->workshop->instructauthors)) {
-            $output .= html_writer::tag("h3", get_string('instructauthors', 'workshop'));
-            $format = $this->get('exporter')->get('format');
-            $instructauthorscontent = portfolio_rewrite_pluginfile_urls($this->workshop->instructauthors,
-                $this->workshop->context->id, 'mod_workshop', 'instructauthors', 0, $format);
-            $output .= $instructauthorscontent;
+            $output .= html_writer::tag('h3', get_string('instructauthors', 'mod_workshop'));
+            $content = $this->format_exported_text($this->workshop->instructauthors, $this->workshop->instructauthorsformat);
+            $content = portfolio_rewrite_pluginfile_urls($content, $this->workshop->context->id, 'mod_workshop',
+                'instructauthors', 0, $this->exporter->get('format'));
+            $output .= $content;
         }
 
         return $output;
     }
 
     /**
-     * Helper function to add a leap2a entry element that corresponds to a submission,
-     * including any attachments.
-     *
-     * The entry/ies are added directly to the leapwriter, which is passed by ref.
+     * Render the content of an assessment.
      *
-     * @param portfolio_format_leap2a_writer $leapwriter writer object to add entries to
-     * @param object $submission the stdclass object representing the database record
-     * @param string $submissionhtml the content of the submission
-     *
-     * @return int id of new entry
+     * @param workshop_assessment $assessment
+     * @return string HTML
      */
-    private function prepare_submission_leap2a(portfolio_format_leap2a_writer $leapwriter, $submission, $submissionhtml) {
-        global $DB;
-        $entry = new portfolio_format_leap2a_entry('workshopsubmission' . $submission->id,  $submission->title,
-            'resource', $submissionhtml);
-        $entry->published = $submission->timecreated;
-        $entry->updated = $submission->timemodified;
-        $entry->author = $DB->get_record('user', array('id' => $submission->authorid), 'id,firstname,lastname,email');
-
-        if (is_array($this->submissionfiles) && array_key_exists($submission->id, $this->submissionfiles)
-            && is_array($this->submissionfiles[$submission->id])) {
-            $leapwriter->link_files($entry, $this->submissionfiles[$submission->id], 'workshopsubmission'
-                . $submission->id . 'attachment');
-        }
-        $entry->add_category('web', 'resource_type');
-        $leapwriter->add_entry($entry);
-        return $entry->id;
-    }
+    protected function export_assessment(workshop_assessment $assessment) {
 
-    /**
-     * Helper function to output assessment for export.
-     *
-     * This is a cut down function of workshop assessment renderer function.
-     *
-     * @param object $assessment the stdclass object representing the database record
-     * @return string
-     */
-    private function prepare_assessment($assessment) {
-        global $PAGE;
-        global $CFG;
-        global $USER;
-        $strategy       = $this->workshop->grading_strategy_instance();
-        $mform      = $strategy->get_assessment_form($PAGE->url, 'assessment', $assessment, false);
-        $userassessment = $this->workshop->get_assessment_of_submission_by_user($assessment->submissionid, $USER->id);
-        $isreviewer     = !empty($userassessment);
-        if ($isreviewer) {
-            $showreviewer = true;
+        $output = '';
+
+        if (empty($assessment->title)) {
+            $title = get_string('assessment', 'workshop');
         } else {
-            $showreviewer   = has_capability('mod/workshop:viewreviewernames', $this->workshop->context);
+            $title = s($assessment->title);
         }
 
-        $fs = get_file_storage();
-        $options    = array(
-            'showreviewer'  => $showreviewer,
-            'showauthor'    => $this->showauthor,
-            'showform'      => !is_null($assessment->grade),
-            'showweight'    => true,
-        );
-        $displayassessment = $this->workshop->prepare_assessment($assessment, $mform, $options);
+        $output .= html_writer::tag('h3', $title);
 
-        $output = '';
-        $output .= '<meta charset="UTF-8">';
+        if ($assessment->reviewer) {
+            $output .= html_writer::div(get_string('byfullnamewithoutlink', 'mod_workshop', fullname($assessment->reviewer)));
+            $output .= html_writer::empty_tag('br');
+        }
 
-        // Start write assessment content.
-        $anonymous = is_null($displayassessment->reviewer);
+        if ($this->workshop->overallfeedbackmode) {
+            if ($assessment->feedbackauthorattachment or trim($assessment->feedbackauthor) !== '') {
+                $output .= html_writer::tag('h3', get_string('overallfeedback', 'mod_workshop'));
+                $content = $this->format_exported_text($assessment->feedbackauthor, $assessment->feedbackauthorformat);
+                $content = portfolio_rewrite_pluginfile_urls($content, $this->workshop->context->id, 'mod_workshop',
+                    'overallfeedback_content', $assessment->id , $this->exporter->get('format'));
+                $output .= $content;
 
-        $output .= html_writer::start_tag('div', array('class' => 'assessmentheader', 'style' => 'background-color: #ddd;'));
+                $output .= $this->export_files_list('overallfeedback_attachment');
+            }
+        }
 
-        if (!empty($displayassessment->title)) {
-            $title = s($displayassessment->title);
-        } else {
-            $title = get_string('assessmentbyyourself', 'workshop');
+        if ($assessment->form) {
+            $output .= $assessment->form->render();
         }
 
-        $output .= html_writer::tag("div", $title);
+        return $output;
+    }
 
-        if (!$anonymous) {
-            $reviewer   = $displayassessment->reviewer;
-            $output .= html_writer::tag("div", get_string('byfullnamewithoutlink', 'workshop', fullname($reviewer)));
-        }
+    /**
+     * Export the files in the given file area in a list.
+     *
+     * @param string $filearea
+     * @return string HTML
+     */
+    protected function export_files_list($filearea) {
 
-        if (is_null($displayassessment->realgrade)) {
-            $output .= html_writer::tag("div", get_string('notassessed', 'workshop'));
-        } else {
-            $a              = new stdClass();
-            $a->max         = $displayassessment->maxgrade;
-            $a->received    = $displayassessment->realgrade;
-            $output .= html_writer::tag("div", get_string('gradeinfo', 'workshop', $a));
+        $output = '';
+        $files = [];
 
-            if (!is_null($displayassessment->weight) and $displayassessment->weight != 1) {
-                $output .= html_writer::tag("div", get_string('weightinfo', 'workshop', $displayassessment->weight));
+        foreach ($this->multifiles as $file) {
+            if ($file->is_directory()) {
+                continue;
             }
-        }
-        $output .= html_writer::end_tag('div');
-
-        $dir = $this->get('exporter')->get('format')->get_file_directory();
-
-        if (!is_null($displayassessment->form)) {
-            $output .= html_writer::tag("div", get_string('assessmentform', 'workshop'));
-            $contentform = self::moodleform($displayassessment->form);
-            $contentform = $this->portfolio_caller_rewrite_pluginfile_urls($contentform, $dir);
-            $output .= html_writer::tag("div", $contentform);
-            if (!$displayassessment->form->is_editable()) {
-                $content = $displayassessment->get_overall_feedback_content();
-                $content = $this->portfolio_caller_rewrite_pluginfile_urls($content, $dir);
-                if ($content != '') {
-                    $output .= html_writer::tag("div", get_string('overallfeedback', 'workshop'));
-                    $output .= html_writer::tag("div", $content);
-                }
-                // Export overall feedback files.
-                $overallfeedbackfiles = $fs->get_area_files($this->workshop->context->id,
-                    'mod_workshop', 'overallfeedback_content', $assessment->id);
-                foreach ($overallfeedbackfiles as $file) {
-                    $this->get('exporter')->copy_existing_file($file);
-                }
+            if ($file->get_filearea() !== $filearea) {
+                continue;
             }
-
-            // Export description files.
-            $assessmentdescriptionfiles = $fs->get_area_files($this->workshop->context->id,
-                'workshopform_' . $this->workshop->strategy, 'description');
-            foreach ($assessmentdescriptionfiles as $file) {
-                $this->get('exporter')->copy_existing_file($file);
+            if ($file->is_valid_image()) {
+                // Not optimal but looks better than original images.
+                $files[] = html_writer::tag('li', $this->exporter->get('format')->file_output($file,
+                    ['attributes' => ['style' => 'max-height:24px; max-width:24px']]).' '.s($file->get_filename()));
+            } else {
+                $files[] = html_writer::tag('li', $this->exporter->get('format')->file_output($file));
             }
         }
 
-        if ($this->assessmentfiles) {
-            $outputfiles = '';
-            $format = $this->get('exporter')->get('format');
-            foreach ($this->assessmentfiles as $file) {
-                if ($file->is_directory()) {
-                    continue;
-                }
-                $type       = $file->get_mimetype();
-                $linkhtml   = $format->file_output($file);
-                $outputfiles .= html_writer::tag('li', $linkhtml, array('class' => $type));
-
-                $this->get('exporter')->copy_existing_file($file);
-
-                if (!empty($CFG->enableplagiarism)) {
-                    require_once($CFG->libdir.'/plagiarismlib.php');
-                    $outputfiles .= plagiarism_get_links(array('userid' => $file->get_userid(),
-                        'file' => $file,
-                        'cmid' => $this->cm->id,
-                        'course' => $this->cm->course));
-                }
-            }
-            $output .= $outputfiles;
+        if ($files) {
+            $output .= html_writer::tag('ul', implode('', $files));
         }
 
         return $output;
     }
 
-
     /**
-     * Helper function to add a leap2a entry element that corresponds to a assessment,
-     * including any attachments.
+     * Helper function to call {@link format_text()} on exported text.
      *
-     * The entry/ies are added directly to the leapwriter, which is passed by ref.
+     * We need to call {@link format_text()} to convert the text into HTML, but
+     * we have to keep the original @@PLUGINFILE@@ placeholder there without a
+     * warning so that {@link portfolio_rewrite_pluginfile_urls()} can do its work.
      *
-     * @param portfolio_format_leap2a_writer $leapwriter writer object to add entries to
-     * @param object $assessment the stdclass object representing the database record
-     * @param string $assessmenthtml the content of the assessment
+     * @param string $text
+     * @param int $format
+     * @return string HTML
+     */
+    protected function format_exported_text($text, $format) {
+
+        $text = str_replace('@@PLUGINFILE@@', '@@ORIGINALPLUGINFILE@@', $text);
+        $html = format_text($text, $format, portfolio_format_text_options());
+        $html = str_replace('@@ORIGINALPLUGINFILE@@', '@@PLUGINFILE@@', $html);
+
+        return $html;
+    }
+
+    /**
+     * Add a LEAP2A entry element that corresponds to a submission including attachments.
      *
+     * @param portfolio_format_leap2a_writer $leapwriter Writer object to add entries to.
+     * @param workshop_submission $workshopsubmission
+     * @param string $html The exported HTML content of the submission
      * @return int id of new entry
      */
-    private function prepare_assessment_leap2a(portfolio_format_leap2a_writer $leapwriter, $assessment, $assessmenthtml) {
-        global $DB;
-        $entry = new portfolio_format_leap2a_entry('workshopassessment' . $assessment->id,
-            $assessment->title, 'resource', $assessmenthtml);
-        $entry->published = $assessment->timecreated;
-        $entry->updated = $assessment->timemodified;
-        $entry->author = $DB->get_record('user', array('id' => $assessment->reviewerid), 'id,firstname,lastname,email');
-
-        if (is_array($this->assessmentfiles) && array_key_exists($assessment->id, $this->assessmentfiles)
-            && is_array($this->assessmentfiles[$assessment->id])) {
-            $leapwriter->link_files($entry, $this->assessmentfiles[$assessment->id], 'workshopassessment'
-                . $assessment->id . 'attachment');
-        }
+    protected function export_content_leap2a(portfolio_format_leap2a_writer $leapwriter,
+            workshop_submission $workshopsubmission, $html) {
+
+        $entry = new portfolio_format_leap2a_entry('workshopsubmission'.$workshopsubmission->id,  s($workshopsubmission->title),
+            'resource', $html);
+        $entry->published = $workshopsubmission->timecreated;
+        $entry->updated = $workshopsubmission->timemodified;
+        $entry->author = (object)[
+            'id' => $workshopsubmission->authorid,
+            'email' => $workshopsubmission->authoremail
+        ];
+        username_load_fields_from_object($entry->author, $workshopsubmission);
+
+        $leapwriter->link_files($entry, $this->multifiles);
         $entry->add_category('web', 'resource_type');
         $leapwriter->add_entry($entry);
+
         return $entry->id;
     }
 
     /**
-     * Return url for redirecting user when cancel or go back.
+     * Return URL for redirecting the user back to where the export started.
      *
      * @return string
      */
     public function get_return_url() {
-        $returnurl = new moodle_url('/mod/workshop/submission.php',
-            array('cmid' => $this->cm->id));
+
+        $returnurl = new moodle_url('/mod/workshop/submission.php', ['cmid' => $this->cm->id, 'id' => $this->submissionid]);
         return $returnurl->out();
     }
 
@@ -455,53 +459,52 @@ class mod_workshop_portfolio_caller extends portfolio_module_caller_base {
      * @return array
      */
     public function get_navigation() {
-        $link = new moodle_url('/mod/workshop/submission.php',
-            array('cmid' => $this->cm->id));
-        $navlinks = array();
-        $navlinks[] = array(
-            'name' => format_string($this->submission->title),
-            'link' => $link->out(),
-            'type' => 'title'
-        );
-        return array($navlinks, $this->cm);
+
+        $navlinks = [
+            ['name' => s($this->submission->title)],
+        ];
+
+        return [$navlinks, $this->cm];
     }
 
     /**
      * How long might we expect this export to take.
      *
-     * @return constant one of PORTFOLIO_TIME_XX
+     * @return string such as PORTFOLIO_TIME_LOW
      */
     public function expected_time() {
-        return PORTFOLIO_TIME_LOW;
+        return $this->expected_time_file();
     }
 
     /**
      * Make sure that the current user is allowed to do the export.
      *
-     * @uses CONTEXT_MODULE
      * @return boolean
      */
     public function check_permissions() {
-        $context = context_module::instance($this->cm->id);
-        if ($this->submissionid && $this->assessmentid) {
-            return has_capability('mod/workshop:exportownsubmission', $context)
-                && has_capability('mod/workshop:exportownsubmissionassessment', $context);
-        }
-        return has_capability('mod/workshop:exportownsubmission', $context);
+        return has_capability('mod/workshop:exportsubmissions', context_module::instance($this->cm->id));
     }
 
     /**
-     * Return the sha1 of this content.
+     * Return the SHA1 hash of the exported content.
      *
      * @return string
      */
     public function get_sha1() {
-        if ($this->submissionid && $this->assessmentid) {
-            return sha1($this->assessment->id . ',' . $this->assessment->title . ',' . $this->assessment->timecreated);
+
+        $identifier = 'submission:'.$this->submission->id.'@'.$this->submission->timemodified;
+
+        if ($this->assessments) {
+            $ids = array_keys($this->assessments);
+            sort($ids);
+            $identifier .= '/assessments:'.implode(',', $ids);
         }
-        if ($this->submissionid && !$this->assessmentid) {
-            return sha1($this->submission->id . ',' . $this->submission->title . ',' . $this->submission->timecreated);
+
+        if ($this->multifiles) {
+            $identifier .= '/files:'.$this->get_sha1_file();
         }
+
+        return sha1($identifier);
     }
 
     /**
@@ -510,62 +513,21 @@ class mod_workshop_portfolio_caller extends portfolio_module_caller_base {
      * @return string
      */
     public static function display_name() {
-        return get_string('modulename', 'workshop');
+        return get_string('pluginname', 'mod_workshop');
     }
 
     /**
-     * What formats this function *generally* supports.
+     * What export formats the workshop generally supports.
+     *
+     * If there are no files embedded/attached, the plain HTML format is added
+     * in {@link self::load_data()}.
      *
      * @return array
      */
     public static function base_supported_formats() {
-        return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_LEAP2A);
-    }
-
-    /**
-     * Helper method dealing with the fact we can not just fetch the output of moodleforms.
-     *
-     * @param moodleform $mform
-     * @return string HTML
-     */
-    protected static function moodleform(moodleform $mform) {
-        ob_start();
-        $mform->display();
-        $o = ob_get_contents();
-        ob_end_clean();
-        return $o;
-    }
-
-    /**
-     * Helper method dealing with the fact we can not just fetch the output of moodleforms.
-     *
-     * @param string $text HTML string to rewrite url
-     * @param string $dir rewrite file directory
-     * @return string HTML
-     */
-    public static function portfolio_caller_rewrite_pluginfile_urls($text, $dir) {
-        $doc = new DOMDocument();
-        $doc->loadHTML($text);
-
-        $imagetags = $doc->getElementsByTagName('img');
-        foreach ($imagetags as $tag) {
-            $src = $tag->getAttribute('src');
-            if (strpos($src, 'pluginfile.php') !== false) {
-                $rewriteurl = $dir . basename($src);
-                $text = str_replace($src, $rewriteurl, $text);
-            }
-        }
-
-        $atags = $doc->getElementsByTagName('a');
-        foreach ($atags as $atag) {
-            $href = $atag->getAttribute('href');
-            if (strpos($href, 'pluginfile.php') !== false) {
-                $rewriteurl = $dir . basename($href);
-                $text = str_replace($href, $rewriteurl, $text);
-            }
-        }
-
-        return $text;
+        return [
+            PORTFOLIO_FORMAT_RICHHTML,
+            PORTFOLIO_FORMAT_LEAP2A,
+        ];
     }
 }
-
index c93e3d4..9e51ff8 100644 (file)
@@ -245,21 +245,16 @@ $capabilities = array(
         )
     ),
 
-    // Ability to export to portfolio of submission only.
-    'mod/workshop:exportownsubmission' => array(
-        'captype' => 'write',
-        'contextlevel' => CONTEXT_MODULE,
-        'archetypes' => array(
-            'student' => CAP_ALLOW
-        )
-    ),
-
-    // Ability to export to portfolio of submission's assessment.
-    'mod/workshop:exportownsubmissionassessment' => array(
-        'captype' => 'write',
+    // Ability to export submissions to a portfolio. Applies to all submissions
+    // the user has access to.
+    'mod/workshop:exportsubmissions' => array(
+        'captype' => 'read',
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
-            'student' => CAP_ALLOW
+            'manager' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'student' => CAP_ALLOW,
         )
     ),
 );
index 4aa252d..17b4dee 100644 (file)
@@ -144,8 +144,7 @@ $string['examplesbeforesubmission'] = 'Examples must be assessed before own subm
 $string['examplesmode'] = 'Mode of examples assessment';
 $string['examplesubmissions'] = 'Example submissions';
 $string['examplesvoluntary'] = 'Assessment of example submission is voluntary';
-$string['exportsubmission'] = 'Export submission to portfolio';
-$string['exportsubmissionassessment'] = 'Export assessment to portfolio';
+$string['exportsubmission'] = 'Export this page';
 $string['feedbackauthor'] = 'Feedback for the author';
 $string['feedbackauthorattachment'] = 'Attachment';
 $string['feedbackby'] = 'Feedback by {$a}';
@@ -334,8 +333,7 @@ $string['withoutsubmission'] = 'Reviewer without own submission';
 $string['workshop:addinstance'] = 'Add a new workshop';
 $string['workshop:allocate'] = 'Allocate submissions for review';
 $string['workshop:editdimensions'] = 'Edit assessment forms';
-$string['workshop:exportownsubmission'] = 'Export own submission only';
-$string['workshop:exportownsubmissionassessment'] = 'Export own assessment in submission';
+$string['workshop:exportsubmissions'] = 'Export submissions';
 $string['workshop:deletesubmissions'] = 'Delete submissions';
 $string['workshop:ignoredeadlines'] = 'Ignore time restrictions';
 $string['workshop:manageexamples'] = 'Manage example submissions';
index 366bef8..6e9860b 100644 (file)
@@ -86,12 +86,6 @@ $canallocate    = has_capability('mod/workshop:allocate', $workshop->context);
 $canpublish     = has_capability('mod/workshop:publishsubmissions', $workshop->context);
 $canoverride    = (($workshop->phase == workshop::PHASE_EVALUATION) and has_capability('mod/workshop:overridegrades', $workshop->context));
 $candeleteall   = has_capability('mod/workshop:deletesubmissions', $workshop->context);
-$canexportownsubmission = !empty($CFG->enableportfolios)
-    && (has_capability('mod/workshop:exportownsubmission', $workshop->context)) && $ownsubmission && !empty($submission->id);
-$canexportownsubmissionassessment = !empty($CFG->enableportfolios)
-    && (has_capability('mod/workshop:exportownsubmission', $workshop->context))
-    && (has_capability('mod/workshop:exportownsubmissionassessment', $workshop->context))
-    && $ownsubmission && !empty($submission->id);
 $userassessment = $workshop->get_assessment_of_submission_by_user($submission->id, $USER->id);
 $isreviewer     = !empty($userassessment);
 $editable       = ($cansubmit and $ownsubmission);
@@ -131,11 +125,6 @@ if (!$candeleteall and $ownsubmission and $editable) {
     }
 }
 
-// Load portfolio lib if user has capability to export.
-if ($canexportownsubmission || $canexportownsubmissionassessment) {
-    require_once($CFG->libdir.'/portfoliolib.php');
-}
-
 if ($submission->id and $delete and $confirm and $deletable) {
     require_sesskey();
     $workshop->delete_submission($submission);
@@ -363,6 +352,7 @@ if ($submission->id) {
 
 // If not at removal confirmation screen, some action buttons can be displayed.
 if (!$delete) {
+    // Display create/edit button.
     if ($editable) {
         if ($submission->id) {
             $btnurl = new moodle_url($PAGE->url, array('edit' => 'on', 'id' => $submission->id));
@@ -374,32 +364,17 @@ if (!$delete) {
         echo $output->single_button($btnurl, $btntxt, 'get');
     }
 
+    // Display delete button.
     if ($submission->id and $deletable) {
         $url = new moodle_url($PAGE->url, array('delete' => 1));
         echo $output->single_button($url, get_string('deletesubmission', 'workshop'), 'get');
     }
 
+    // Display assess button.
     if ($submission->id and !$edit and !$isreviewer and $canallocate and $workshop->assessing_allowed($USER->id)) {
         $url = new moodle_url($PAGE->url, array('assess' => 1));
         echo $output->single_button($url, get_string('assess', 'workshop'), 'post');
     }
-
-    // Add portfolio export button for submission.
-    if ($canexportownsubmission) {
-        $button = new portfolio_add_button();
-        $button->set_callback_options('mod_workshop_portfolio_caller', array('submissionid' => $submission->id), 'mod_workshop');
-        $fs = get_file_storage();
-        $files = array_merge($fs->get_area_files($workshop->context->id, 'mod_workshop', 'submission_attachment', $submission->id)
-            , $fs->get_area_files($workshop->context->id, 'mod_workshop', 'submission_content', $submission->id));
-        if ($files) {
-            $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
-        } else {
-            $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
-        }
-        echo html_writer::start_tag('div', array('class' => 'singlebutton'));
-        echo $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportsubmission', 'workshop'));
-        echo html_writer::end_tag('div');
-    }
 }
 
 if (($workshop->phase == workshop::PHASE_CLOSED) and ($ownsubmission or $canviewall)) {
@@ -441,31 +416,9 @@ if ($isreviewer) {
             echo $output->render(new workshop_feedback_reviewer($userassessment));
         }
     }
-
-    // Add portfolio export button for assessment.
-    if ($canexportownsubmissionassessment) {
-        $button = new portfolio_add_button();
-        $button->set_callback_options('mod_workshop_portfolio_caller', array('submissionid' => $submission->id,
-            'assessmentid' => $assessment->id), 'mod_workshop');
-        $fs = get_file_storage();
-        $files = array_merge($fs->get_area_files($workshop->context->id, 'mod_workshop'
-            , 'overallfeedback_attachment', $assessment->id)
-            , $fs->get_area_files($workshop->context->id, 'workshopform_' . $workshop->strategy, 'description')
-            , $fs->get_area_files($workshop->context->id, 'mod_workshop', 'overallfeedback', $assessment->id));
-
-        if ($files) {
-            $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
-        } else {
-            $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
-        }
-        echo html_writer::start_tag('div', array('class' => 'singlebutton'));
-        echo $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportsubmissionassessment', 'workshop'));
-        echo html_writer::end_tag('div');
-    }
 }
 
-if (has_capability('mod/workshop:viewallassessments', $workshop->context)
-    or ($ownsubmission and $workshop->assessments_available())) {
+if (has_capability('mod/workshop:viewallassessments', $workshop->context) or ($ownsubmission and $workshop->assessments_available())) {
     // other assessments
     $strategy       = $workshop->grading_strategy_instance();
     $assessments    = $workshop->get_assessments_of_submission($submission->id);
@@ -497,27 +450,6 @@ if (has_capability('mod/workshop:viewallassessments', $workshop->context)
                 echo $output->render(new workshop_feedback_reviewer($assessment));
             }
         }
-
-        // Add portfolio export button for assessment.
-        if ($canexportownsubmissionassessment) {
-            $button = new portfolio_add_button();
-            $button->set_callback_options('mod_workshop_portfolio_caller', array('submissionid' => $submission->id,
-                'assessmentid' => $assessment->id), 'mod_workshop');
-            $fs = get_file_storage();
-            $files = array_merge($fs->get_area_files($workshop->context->id, 'mod_workshop'
-                , 'overallfeedback_attachment', $assessment->id)
-                , $fs->get_area_files($workshop->context->id, 'workshopform_' . $workshop->strategy, 'description')
-                , $fs->get_area_files($workshop->context->id, 'mod_workshop', 'overallfeedback', $assessment->id));
-
-            if ($files) {
-                $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
-            } else {
-                $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
-            }
-            echo html_writer::start_tag('div', array('class' => 'singlebutton'));
-            echo $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportsubmissionassessment', 'workshop'));
-            echo html_writer::end_tag('div');
-        }
     }
 }
 
@@ -526,4 +458,24 @@ if (!$edit and $canoverride) {
     $feedbackform->display();
 }
 
+// If portfolios are enabled and we are not on the edit/removal confirmation screen, display a button to export this page.
+// The export is not offered if the submission is seen as a published one (it has no relation to the current user.
+if (!empty($CFG->enableportfolios)) {
+    if (!$delete and !$edit and !$seenaspublished and $submission->id and ($ownsubmission or $canviewall or $isreviewer)) {
+        if (has_capability('mod/workshop:exportsubmissions', $workshop->context)) {
+            require_once($CFG->libdir.'/portfoliolib.php');
+
+            $button = new portfolio_add_button();
+            $button->set_callback_options('mod_workshop_portfolio_caller', array(
+                'id' => $workshop->cm->id,
+                'submissionid' => $submission->id,
+            ), 'mod_workshop');
+            $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
+            echo html_writer::start_tag('div', array('class' => 'singlebutton'));
+            echo $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportsubmission', 'workshop'));
+            echo html_writer::end_tag('div');
+        }
+    }
+}
+
 echo $output->footer();
index 2d15912..1c9224a 100644 (file)
@@ -169,9 +169,18 @@ class behat_mod_workshop extends behat_base {
      * @param string $value
      */
     public function i_set_portfolio_instance_to($portfolioinstance, $value) {
-        $xpath = "//table[contains(@class, 'generaltable')]//tr//td[contains(text(), '"
-            . $portfolioinstance . "')]/following-sibling::td//select";
-        $select = $this->find('xpath', $xpath);
+
+        $rowxpath = "//table[contains(@class, 'generaltable')]//tr//td[contains(text(), '"
+            . $portfolioinstance . "')]/following-sibling::td";
+
+        $selectxpath = $rowxpath.'//select';
+        $select = $this->find('xpath', $selectxpath);
         $select->selectOption($value);
+
+        if (!$this->running_javascript()) {
+            $this->execute('behat_general::i_click_on_in_the',
+                array(get_string('go'), "button", $rowxpath, "xpath_element")
+            );
+        }
     }
 }
diff --git a/mod/workshop/tests/behat/export_assessment.feature b/mod/workshop/tests/behat/export_assessment.feature
deleted file mode 100644 (file)
index 487a71c..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-@mod @mod_workshop
-@javascript
-
-Feature: Workshop submission's assessment export to portfolio
-  In order to be able to reuse my assessment content in my submission
-  As a student
-  I need to be able to export my own submission's assessment from other people
-
-  Background:
-    Given the following "users" exist:
-      | username | firstname | lastname | email                 |
-      | student1 | Sam1      | Student1 | student1@example.com  |
-      | student2 | Sam2      | Student2 | student2@example.com  |
-      | teacher1 | Terry1    | Teacher1 | teacher1@example.com  |
-    And the following "courses" exist:
-      | fullname  | shortname |
-      | Course1   | c2        |
-    And the following "course enrolments" exist:
-      | user     | course | role           |
-      | student1 | c2     | student        |
-      | student2 | c2     | student        |
-      | teacher1 | c2     | editingteacher |
-    And the following "activities" exist:
-      | activity | name         | intro                     | course | idnumber  |
-      | workshop | TestWorkshop | Test workshop description | c2     | workshop1 |
-    # Admin enable portfolio plugin
-    And I log in as "admin"
-    And I expand "Site administration" node
-    And I follow "Advanced features"
-    And I set the following administration settings values:
-      | Enable portfolios | 1 |
-    And I expand "Plugins" node
-    And I expand "Portfolios" node
-    And I follow "Manage portfolios"
-    And I set portfolio instance "File download" to "Enabled and visible"
-    And I click on "Save" "button"
-    And I log out
-    # Teacher sets up assessment form and changes the phase to submission.
-    And I log in as "teacher1"
-    And I follow "Course1"
-    And I edit assessment form in workshop "TestWorkshop" as:"
-      | id_description__idx_0_editor | Aspect1 |
-      | id_description__idx_1_editor | Aspect2 |
-      | id_description__idx_2_editor |         |
-    And I change phase in workshop "TestWorkshop" to "Submission phase"
-    And I log out
-    # Student1 submits.
-    And I log in as "student1"
-    And I follow "Course1"
-    And I follow "TestWorkshop"
-    And I add a submission in workshop "TestWorkshop" as:"
-      | Title              | Submission1  |
-      | Submission content | Some content |
-    And I log out
-    # Student2 submits.
-    And I log in as "student2"
-    And I follow "Course1"
-    And I add a submission in workshop "TestWorkshop" as:"
-      | Title              | Submission2  |
-      | Submission content | Some content |
-    And I log out
-    # teacher1 allocates reviewers and changes the phase to assessment
-    And I log in as "teacher1"
-    And I follow "Course1"
-    And I follow "TestWorkshop"
-    And I allocate submissions in workshop "TestWorkshop" as:"
-      | Participant   | Reviewer      |
-      | Sam1 Student1 | Sam2 Student2 |
-      | Sam2 Student2 | Sam1 Student1 |
-    And I follow "TestWorkshop"
-    And I change phase in workshop "TestWorkshop" to "Assessment phase"
-    And I log out
-    # student1 assesses work of student2
-    And I log in as "student1"
-    And I follow "Course1"
-    And I follow "TestWorkshop"
-    And I assess submission "Sam2" in workshop "TestWorkshop" as:"
-      | grade__idx_0            | 5 / 10            |
-      | peercomment__idx_0      | You can do better |
-      | grade__idx_1            | 10 / 10           |
-      | peercomment__idx_1      | Amazing           |
-      | Feedback for the author | Good work         |
-    And I log out
-    # student2 assesses work of student1
-    And I log in as "student2"
-    And I follow "Course1"
-    And I follow "TestWorkshop"
-    And I assess submission "Sam1" in workshop "TestWorkshop" as:"
-      | grade__idx_0            | 6 / 10     |
-      | peercomment__idx_0      |            |
-      | grade__idx_1            | 7 / 10     |
-      | peercomment__idx_1      |            |
-      | Feedback for the author | Keep it up |
-    And I log out
-
-  Scenario: Hide export to portfolio button of assessment if admin disable portfolio feature
-    Given I log in as "admin"
-    And I expand "Site administration" node
-    And I follow "Advanced features"
-    And I set the following administration settings values:
-      | Enable portfolios | 0 |
-    And I log out
-    When I log in as "student1"
-    And I follow "Course1"
-    And I follow "TestWorkshop"
-    And I follow "My submission"
-    Then I should see "Submission1"
-    Then "Export assessment to portfolio" "button" should not exist
-    And I log out
-
-  Scenario: Students can export to portfolio their own submission's assessments
-    Given I log in as "teacher1"
-    And I follow "Course1"
-    And I change phase in workshop "TestWorkshop" to "Closed"
-    And I log out
-    When I log in as "student1"
-    And I follow "Course1"
-    And I follow "TestWorkshop"
-    And I follow "My submission"
-    Then I should see "Submission1"
-    Then "Export assessment to portfolio" "button" should exist
-    And I click on "Export assessment to portfolio" "button"
-    Then I should see "Available export formats"
-    And I click on "Next" "button"
-    Then I should see "Summary of your export"
-    And I click on "Continue" "button"
-    Then I should see "Return to where you were"
-    And I log out
-
-  Scenario: Students can not export to portfolio the assessment content when viewing submission of other people
-    Given I log in as "teacher1"
-    And I follow "Course1"
-    And I change phase in workshop "TestWorkshop" to "Closed"
-    And I log out
-    When I log in as "student2"
-    And I follow "Course1"
-    And I follow "TestWorkshop"
-    And I follow "Submission1"
-    Then I should see "Assessment"
-    Then "Export assessment to portfolio" "button" should not exist
-    And I log out
index b6352b5..88a3037 100644 (file)
@@ -1,9 +1,8 @@
 @mod @mod_workshop
-@javascript
-Feature: Workshop submission export to portfolio
-  In order to be able to reuse my submission content
-  As a student
-  I need to be able to export my submission
+Feature: Exporting workshop submissions and assessments to a portfolio
+  In order to archive my workshop contribution in a personal storage
+  As a student or as a teacher
+  I need to be able to export a workshop submission and its associated assessments
 
   Background:
     Given the following "users" exist:
@@ -22,12 +21,11 @@ Feature: Workshop submission export to portfolio
     And the following "activities" exist:
       | activity | name         | intro                     | course | idnumber  |
       | workshop | TestWorkshop | Test workshop description | c1     | workshop1 |
-    # Admin enable portfolio plugin
+    # Admin needs to enable portfolio API and set a portfolio instance first.
     And I log in as "admin"
+    And the following config values are set as admin:
+      | enableportfolios | 1 |
     And I expand "Site administration" node
-    And I follow "Advanced features"
-    And I set the following administration settings values:
-      | Enable portfolios | 1 |
     And I expand "Plugins" node
     And I expand "Portfolios" node
     And I follow "Manage portfolios"
@@ -40,7 +38,6 @@ Feature: Workshop submission export to portfolio
     And I edit assessment form in workshop "TestWorkshop" as:"
       | id_description__idx_0_editor | Aspect1 |
       | id_description__idx_1_editor | Aspect2 |
-      | id_description__idx_2_editor |         |
     And I change phase in workshop "TestWorkshop" to "Submission phase"
     And I log out
     # Student1 submits.
@@ -58,12 +55,12 @@ Feature: Workshop submission export to portfolio
       | Title              | Submission2  |
       | Submission content | Some content |
     And I log out
-     # teacher1 allocates reviewers and changes the phase to assessment
+     # Teacher allocates reviewers and changes the phase to assessment.
     And I log in as "teacher1"
     And I follow "Course1"
     And I follow "TestWorkshop"
     And I should see "to allocate: 2"
-    Then I should see "Workshop submissions report"
+    And I should see "Workshop submissions report"
     And I should see "Submitted (2) / not submitted (0)"
     And I should see "Submission1" in the "Sam1 Student1" "table_row"
     And I should see "Submission2" in the "Sam2 Student2" "table_row"
@@ -76,45 +73,42 @@ Feature: Workshop submission export to portfolio
     And I change phase in workshop "TestWorkshop" to "Assessment phase"
     And I log out
 
-
-  Scenario: Hide export to portfolio button if admin disable portfolio feature
-    Given I log in as "admin"
-    And I expand "Site administration" node
-    And I follow "Advanced features"
-    And I set the following administration settings values:
-      | Enable portfolios | 0 |
-    And I log out
-    When I log in as "student1"
+  Scenario: Students can export their own submission to a portfolio.
+    Given I log in as "student1"
     And I follow "Course1"
     And I follow "TestWorkshop"
-    And I follow "My submission"
+    When I follow "My submission"
     Then I should see "Submission1"
-    Then "Export submission to portfolio" "button" should not exist
+    And "Export this page" "button" should exist
+    And I click on "Export this page" "button"
+    And I should see "Available export formats"
+    And I click on "Next" "button"
+    And I should see "Summary of your export"
+    And I click on "Continue" "button"
+    And I should see "Return to where you were"
     And I log out
 
-  Scenario: Students can export to portfolio their own submission
+  Scenario: Students can export submission they have peer-assessed.
     Given I log in as "student1"
     And I follow "Course1"
     And I follow "TestWorkshop"
-    When I follow "My submission"
-    Then I should see "Submission1"
-    Then "Export submission to portfolio" "button" should exist
-    And I click on "Export submission to portfolio" "button"
+    And I should see "Submission2"
+    And I follow "Submission2"
+    And "Export this page" "button" should exist
+    When I click on "Export this page" "button"
     Then I should see "Available export formats"
     And I click on "Next" "button"
-    Then I should see "Summary of your export"
+    And I should see "Summary of your export"
     And I click on "Continue" "button"
-    Then I should see "Return to where you were"
+    And I should see "Return to where you were"
     And I log out
 
-  Scenario: Students can not export to portfolio when viewing submission of other people
-    Given I log in as "teacher1"
-    And I follow "Course1"
-    And I change phase in workshop "TestWorkshop" to "Closed"
-    And I log out
-    When I log in as "student2"
+  Scenario: If the portfolio API is disabled, the portfolio export button is not displayed.
+    Given the following config values are set as admin:
+      | enableportfolios | 0 |
+    When I log in as "student1"
     And I follow "Course1"
     And I follow "TestWorkshop"
-    And I follow "Submission1"
-    Then "Export submission to portfolio" "button" should not exist
-    And I log out
+    And I follow "My submission"
+    Then I should see "Submission1"
+    And "Export this page" "button" should not exist
index cee2d1f..f93805e 100644 (file)
@@ -51,7 +51,7 @@ class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
         parent::setUp();
         $this->setAdminUser();
         $course = $this->getDataGenerator()->create_course();
-        $workshop = $this->getDataGenerator()->create_module('workshop', array('course' => $course));
+        $workshop = $this->getDataGenerator()->create_module('workshop', ['course' => $course]);
         $this->cm = get_coursemodule_from_instance('workshop', $workshop->id, $course->id, false, MUST_EXIST);
         $this->workshop = new testable_workshop($workshop, $this->cm, $course);
     }
@@ -66,11 +66,9 @@ class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
     }
 
     /**
-     * Test function load_data()
-     * Case 1: User exports the assessment of his/her own submission.
-     * Assert that this function can load the correct assessment.
+     * Test the method mod_workshop_portfolio_caller::load_data()
      */
-    public function test_load_data_for_own_submissionassessment() {
+    public function test_load_data() {
         $this->resetAfterTest(true);
 
         $student1 = $this->getDataGenerator()->create_user();
@@ -81,45 +79,20 @@ class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
         $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
         $asid1 = $workshopgenerator->create_assessment($subid1, $student2->id);
 
-        $portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1, 'assessmentid' => $asid1));
+        $portfoliocaller = new mod_workshop_portfolio_caller(['id' => $this->workshop->cm->id, 'submissionid' => $subid1]);
+        $portfoliocaller->set_formats_from_button([]);
         $portfoliocaller->load_data();
 
         $reflector = new ReflectionObject($portfoliocaller);
-        $assessment = $reflector->getProperty('assessment');
-        $assessment->setAccessible(true);
-        $result = $assessment->getValue($portfoliocaller);
+        $propertysubmission = $reflector->getProperty('submission');
+        $propertysubmission->setAccessible(true);
+        $submission = $propertysubmission->getValue($portfoliocaller);
 
-        $this->assertEquals($asid1, $result->id);
+        $this->assertEquals($subid1, $submission->id);
     }
 
     /**
-     * Test function load_data()
-     * Case 2: User exports his/her own submission.
-     * Assert that this function can load the correct submission.
-     */
-    public function test_load_data_for_own_submission() {
-        $this->resetAfterTest(true);
-
-        $student1 = $this->getDataGenerator()->create_user();
-        $this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
-        $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
-        $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
-
-        $portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
-        $portfoliocaller->load_data();
-
-        $reflector = new ReflectionObject($portfoliocaller);
-        $submission = $reflector->getProperty('submission');
-        $submission->setAccessible(true);
-
-        $result = $submission->getValue($portfoliocaller);
-
-        $this->assertEquals($subid1, $result->id);
-    }
-
-    /**
-     * Test function get_return_url()
-     * Assert that this function can return the correct url.
+     * Test the method mod_workshop_portfolio_caller::get_return_url()
      */
     public function test_get_return_url() {
         $this->resetAfterTest(true);
@@ -129,20 +102,17 @@ class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
         $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
         $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
 
-        $portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
-
-        $reflector = new ReflectionObject($portfoliocaller);
-        $cm = $reflector->getProperty('cm');
-        $cm->setAccessible(true);
-        $cm->setValue($portfoliocaller, $this->cm);
+        $portfoliocaller = new mod_workshop_portfolio_caller(['id' => $this->workshop->cm->id, 'submissionid' => $subid1]);
+        $portfoliocaller->set_formats_from_button([]);
+        $portfoliocaller->load_data();
 
-        $expected = 'http://www.example.com/moodle/mod/workshop/submission.php?cmid='.$this->cm->id;
-        $this->assertEquals($expected, $portfoliocaller->get_return_url());
+        $expected = new moodle_url('/mod/workshop/submission.php', ['cmid' => $this->workshop->cm->id, 'id' => $subid1]);
+        $actual = new moodle_url($portfoliocaller->get_return_url());
+        $this->assertTrue($expected->compare($actual));
     }
 
     /**
-     * Test function get_navigation()
-     * Assert that this function can return the navigation array.
+     * Test the method mod_workshop_portfolio_caller::get_navigation()
      */
     public function test_get_navigation() {
         $this->resetAfterTest(true);
@@ -152,22 +122,15 @@ class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
         $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
         $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
 
-        $portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
+        $portfoliocaller = new mod_workshop_portfolio_caller(['id' => $this->workshop->cm->id, 'submissionid' => $subid1]);
+        $portfoliocaller->set_formats_from_button([]);
         $portfoliocaller->load_data();
 
-        $reflector = new ReflectionObject($portfoliocaller);
-        $cm = $reflector->getProperty('cm');
-        $cm->setAccessible(true);
-        $cm->setValue($portfoliocaller, $this->cm);
-
         $this->assertTrue(is_array($portfoliocaller->get_navigation()));
     }
 
     /**
-     * Test function check_permissions()
-     * Case 1: User exports assessment.
-     * Assert that this function can return a boolean value
-     * to indicate that the user has capability to export the assessment.
+     * Test the method mod_workshop_portfolio_caller::check_permissions()
      */
     public function test_check_permissions_exportownsubmissionassessment() {
         global $DB;
@@ -184,77 +147,19 @@ class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
         $asid1 = $workshopgenerator->create_assessment($subid1, $student2->id);
         $this->setUser($student1);
 
-        $portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1, 'assessmentid' => $asid1));
-
-        $reflector = new ReflectionObject($portfoliocaller);
-        $cm = $reflector->getProperty('cm');
-        $cm->setAccessible(true);
-        $cm->setValue($portfoliocaller, $this->cm);
-
-        // Case 1: If user has capabilities exportownsubmission prevented and exportownsubmissionassessment prevented
-        // then check_permissions should return false.
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_PREVENT);
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmissionassessment', CAP_PREVENT);
-        $this->assertFalse($portfoliocaller->check_permissions());
-
-        // Case 2: If user has capabilities exportownsubmission allowed and exportownsubmissionassessment prevented
-        // then check_permissions should return false.
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_ALLOW);
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmissionassessment', CAP_PREVENT);
-        $this->assertFalse($portfoliocaller->check_permissions());
+        $portfoliocaller = new mod_workshop_portfolio_caller(['id' => $this->workshop->cm->id, 'submissionid' => $subid1]);
 
-        // Case 3: If user has capabilities exportownsubmission prevented and exportownsubmissionassessment allowed
-        // then check_permissions should return false.
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_PREVENT);
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmissionassessment', CAP_ALLOW);
+        role_change_permission($roleids['student'], $context, 'mod/workshop:exportsubmissions', CAP_PREVENT);
         $this->assertFalse($portfoliocaller->check_permissions());
 
-        // Case 4: If user has capabilities exportownsubmission allowed and exportownsubmissionassessment allowed
-        // then check_permissions should return true.
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_ALLOW);
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmissionassessment', CAP_ALLOW);
-        $this->assertTrue($portfoliocaller->check_permissions());
-    }
-
-    /**
-     * Test function check_permissions()
-     * Case 2: User exports submission.
-     * Assert that this function can return a boolean value
-     * to indicate that the user has capability to export submission.
-     */
-    public function test_check_permissions_exportownsubmission() {
-        global $DB;
-        $this->resetAfterTest(true);
-
-        $context = context_module::instance($this->cm->id);
-        $student1 = $this->getDataGenerator()->create_user();
-        $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
-        $this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id, $roleids['student']);
-        $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
-        $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
-        $this->setUser($student1);
-
-        $portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
-        $reflector = new ReflectionObject($portfoliocaller);
-        $cm = $reflector->getProperty('cm');
-        $cm->setAccessible(true);
-        $cm->setValue($portfoliocaller, $this->cm);
-
-        // Case 1: If user has capability to export submission then check_permissions should return true.
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_ALLOW);
+        role_change_permission($roleids['student'], $context, 'mod/workshop:exportsubmissions', CAP_ALLOW);
         $this->assertTrue($portfoliocaller->check_permissions());
-
-        // Case 2: If user doesn't have capability to export submission then check_permissions should return false.
-        role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_PREVENT);
-        $this->assertFalse($portfoliocaller->check_permissions());
     }
 
     /**
-     * Test function get_sha1()
-     * Case 1: User exports the assessment of his/her own submission.
-     * Assert that this function can return a hash string.
+     * Test the method mod_workshop_portfolio_caller::get_sha1()
      */
-    public function test_get_sha1_assessment() {
+    public function test_get_sha1() {
         $this->resetAfterTest(true);
 
         $student1 = $this->getDataGenerator()->create_user();
@@ -265,28 +170,8 @@ class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
         $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
         $asid1 = $workshopgenerator->create_assessment($subid1, $student2->id);
 
-        $portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1, 'assessmentid' => $asid1));
-        $portfoliocaller->load_data();
-
-        $this->assertTrue(is_string($portfoliocaller->get_sha1()));
-    }
-
-    /**
-     * Test function get_sha1()
-     * Case 2: User exports his/her own submission.
-     * Assert that this function can return a hash string.
-     */
-    public function test_get_sha1_submission() {
-        $this->resetAfterTest(true);
-
-        $student1 = $this->getDataGenerator()->create_user();
-        $student2 = $this->getDataGenerator()->create_user();
-        $this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
-        $this->getDataGenerator()->enrol_user($student2->id, $this->workshop->course->id);
-        $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
-        $subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
-
-        $portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
+        $portfoliocaller = new mod_workshop_portfolio_caller(['id' => $this->workshop->cm->id, 'submissionid' => $subid1]);
+        $portfoliocaller->set_formats_from_button([]);
         $portfoliocaller->load_data();
 
         $this->assertTrue(is_string($portfoliocaller->get_sha1()));
@@ -298,7 +183,8 @@ class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
      */
     public function test_display_name() {
         $this->resetAfterTest(true);
-        $this->assertEquals('Workshop', mod_workshop_portfolio_caller::display_name());
-    }
 
+        $name = mod_workshop_portfolio_caller::display_name();
+        $this->assertEquals(get_string('pluginname', 'mod_workshop'), $name);
+    }
 }
index efe92de..5d03388 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2016062901;        // The current module version (YYYYMMDDXX)
+$plugin->version   = 2016100600;        // The current module version (YYYYMMDDXX)
 $plugin->requires  = 2016051900;        // Requires this Moodle version.
 $plugin->component = 'mod_workshop';
 $plugin->cron      = 60;                // Give as a chance every minute.