Merge branch 'MDL-69270-master' of git://github.com/ferranrecio/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Mon, 31 Aug 2020 05:00:08 +0000 (13:00 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Mon, 31 Aug 2020 23:26:02 +0000 (07:26 +0800)
1  2 
contentbank/classes/contenttype.php
contentbank/tests/contenttype_test.php
contentbank/view.php
lang/en/contentbank.php

@@@ -40,18 -40,12 +40,18 @@@ use moodle_url
   */
  abstract class contenttype {
  
 -    /** Plugin implements uploading feature */
 +    /** @var string Constant representing whether the plugin implements uploading feature */
      const CAN_UPLOAD = 'upload';
  
 -    /** Plugin implements edition feature */
 +    /** @var string Constant representing whether the plugin implements edition feature */
      const CAN_EDIT = 'edit';
  
 +    /**
 +     * @var string Constant representing whether the plugin implements download feature
 +     * @since  Moodle 3.10
 +     */
 +    const CAN_DOWNLOAD = 'download';
 +
      /** @var \context This contenttype's context. **/
      protected $context = null;
  
          return $content;
      }
  
+     /**
+      * Replace a content using an uploaded file.
+      *
+      * @throws file_exception If file operations fail
+      * @throws dml_exception if the content creation fails
+      * @param stored_file $file the uploaded file
+      * @param content $content the original content record
+      * @return content Object with the updated content bank information.
+      */
+     public function replace_content(stored_file $file, content $content): content {
+         $content->import_file($file);
+         $content->update_content();
+         return $content;
+     }
      /**
       * Delete this content from the content_bank.
       * This method can be overwritten by the plugins if they need to delete specific information.
          return '';
      }
  
 +    /**
 +     * Returns the URL to download the content.
 +     *
 +     * @since  Moodle 3.10
 +     * @param  content $content The content to be downloaded.
 +     * @return string           URL with the content to download.
 +     */
 +    public function get_download_url(content $content): string {
 +        $downloadurl = '';
 +        $file = $content->get_file();
 +        if (!empty($file)) {
 +            $url = \moodle_url::make_pluginfile_url(
 +                $file->get_contextid(),
 +                $file->get_component(),
 +                $file->get_filearea(),
 +                $file->get_itemid(),
 +                $file->get_filepath(),
 +                $file->get_filename()
 +            );
 +            $downloadurl = $url->out(false);
 +        }
 +
 +        return $downloadurl;
 +    }
 +
      /**
       * Returns the HTML code to render the icon for content bank contents.
       *
          return true;
      }
  
 +    /**
 +     * Returns whether or not the user has permission to download the content.
 +     *
 +     * @since  Moodle 3.10
 +     * @param  content $content The content to be downloaded.
 +     * @return bool    True if the user can download the content. False otherwise.
 +     */
 +    final public function can_download(content $content): bool {
 +        if (!$this->is_feature_supported(self::CAN_DOWNLOAD)) {
 +            return false;
 +        }
 +
 +        if (!$this->can_access()) {
 +            return false;
 +        }
 +
 +        $hascapability = has_capability('moodle/contentbank:downloadcontent', $this->context);
 +        return $hascapability && $this->is_download_allowed($content);
 +    }
 +
 +    /**
 +     * Returns plugin allows downloading.
 +     *
 +     * @since  Moodle 3.10
 +     * @param  content $content The content to be downloaed.
 +     * @return bool    True if plugin allows downloading. False otherwise.
 +     */
 +    protected function is_download_allowed(content $content): bool {
 +        // Plugins can overwrite this function to add any check they need.
 +        return true;
 +    }
 +
      /**
       * Returns the plugin supports the feature.
       *
@@@ -290,6 -290,84 +290,84 @@@ class core_contenttype_contenttype_test
          $this->assertEquals(1, $DB->count_records('files', ['contenthash' => $dummyfile->get_contenthash()]));
      }
  
+     /**
+      * Tests for behaviour of replace_content() using a dummy file.
+      *
+      * @covers ::replace_content
+      */
+     public function test_replace_content(): void {
+         global $USER;
+         $this->resetAfterTest();
+         $this->setAdminUser();
+         $context = context_system::instance();
+         // Add some content to the content bank.
+         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+         $contents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $context);
+         $content = reset($contents);
+         $dummy = [
+             'contextid' => context_user::instance($USER->id)->id,
+             'component' => 'user',
+             'filearea' => 'draft',
+             'itemid' => 1,
+             'filepath' => '/',
+             'filename' => 'file.h5p',
+             'userid' => $USER->id,
+         ];
+         $fs = get_file_storage();
+         $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content');
+         $contenttype = new contenttype(context_system::instance());
+         $content = $contenttype->replace_content($dummyfile, $content);
+         $this->assertEquals('contenttype_testable', $content->get_content_type());
+         $this->assertInstanceOf('\\contenttype_testable\\content', $content);
+         $file = $content->get_file();
+         $this->assertEquals($dummyfile->get_userid(), $file->get_userid());
+         $this->assertEquals($dummyfile->get_contenthash(), $file->get_contenthash());
+         $this->assertEquals('contentbank', $file->get_component());
+         $this->assertEquals('public', $file->get_filearea());
+         $this->assertEquals('/', $file->get_filepath());
+     }
+     /**
+      * Tests for behaviour of replace_content() using an error file.
+      *
+      * @covers ::replace_content
+      */
+     public function test_replace_content_exception(): void {
+         global $USER;
+         $this->resetAfterTest();
+         $this->setAdminUser();
+         $context = context_system::instance();
+         // Add some content to the content bank.
+         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+         $contents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $context);
+         $content = reset($contents);
+         $dummy = [
+             'contextid' => context_user::instance($USER->id)->id,
+             'component' => 'user',
+             'filearea' => 'draft',
+             'itemid' => 1,
+             'filepath' => '/',
+             'filename' => 'error.txt',
+             'userid' => $USER->id,
+         ];
+         $fs = get_file_storage();
+         $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content');
+         $contenttype = new contenttype(context_system::instance());
+         $this->expectException(Exception::class);
+         $content = $contenttype->replace_content($dummyfile, $content);
+     }
      /**
       * Test the behaviour of can_delete().
       */
      /**
       * Helper function to setup 3 users (manager1, manager2 and user) and 4 contents (3 created by manager1 and 1 by user).
       */
 -    protected function contenttype_setup_scenario_data(): void {
 +    protected function contenttype_setup_scenario_data(string $contenttype = 'contenttype_testable'): void {
          global $DB;
          $systemcontext = context_system::instance();
  
          $this->managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
          $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager1->id);
          $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager2->id);
 +        $editingteacherrolerid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
          $this->user = $this->getDataGenerator()->create_user();
 +        $this->getDataGenerator()->role_assign($editingteacherrolerid, $this->user->id);
  
          // Add some content to the content bank.
          $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
 -        $this->contents[$this->manager1->id] = $generator->generate_contentbank_data(null, 3, $this->manager1->id);
 -        $this->contents[$this->user->id] = $generator->generate_contentbank_data(null, 1, $this->user->id);
 +        $this->contents[$this->manager1->id] = $generator->generate_contentbank_data($contenttype, 3, $this->manager1->id);
 +        $this->contents[$this->user->id] = $generator->generate_contentbank_data($contenttype, 1, $this->user->id);
  
 -        $this->contenttype = new \contenttype_testable\contenttype($systemcontext);
 +        $contenttypeclass = "\\$contenttype\\contenttype";
 +        $this->contenttype = new $contenttypeclass($systemcontext);
      }
  
      /**
          $this->assertFalse($contenttype->can_manage($contentbyteacher));
          $this->assertFalse($contenttype->can_manage($contentbyadmin));
      }
 +
 +    /**
 +     * Test the behaviour of can_download().
 +     *
 +     * @covers ::can_download
 +     */
 +    public function test_can_download() {
 +        global $DB;
 +
 +        $this->resetAfterTest();
 +        $this->contenttype_setup_scenario_data('contenttype_h5p');
 +
 +        $managercontent = array_shift($this->contents[$this->manager1->id]);
 +        $usercontent = array_shift($this->contents[$this->user->id]);
 +
 +        // Check the content has been created as expected.
 +        $records = $DB->count_records('contentbank_content');
 +        $this->assertEquals(4, $records);
 +
 +        // Check user can download content created by anybody.
 +        $this->setUser($this->user);
 +        $this->assertTrue($this->contenttype->can_download($usercontent));
 +        $this->assertTrue($this->contenttype->can_download($managercontent));
 +
 +        // Check manager can download all the content too.
 +        $this->setUser($this->manager1);
 +        $this->assertTrue($this->contenttype->can_download($managercontent));
 +        $this->assertTrue($this->contenttype->can_download($usercontent));
 +
 +        // Unassign capability to manager role and check she cannot download content anymore.
 +        unassign_capability('moodle/contentbank:downloadcontent', $this->managerroleid);
 +        $this->assertFalse($this->contenttype->can_download($managercontent));
 +        $this->assertFalse($this->contenttype->can_download($usercontent));
 +    }
 +
 +    /**
 +     * Tests get_download_url result.
 +     *
 +     * @covers ::get_download_url
 +     */
 +    public function test_get_download_url() {
 +        global $CFG;
 +
 +        $this->resetAfterTest();
 +        $this->setAdminUser();
 +        $systemcontext = context_system::instance();
 +
 +        // Add some content to the content bank.
 +        $filename = 'filltheblanks.h5p';
 +        $filepath = $CFG->dirroot . '/h5p/tests/fixtures/' . $filename;
 +        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
 +        $contents = $generator->generate_contentbank_data('contenttype_testable', 1, 0, $systemcontext, true, $filepath);
 +        $content = array_shift($contents);
 +
 +        // Check the URL is returned OK for a content with file.
 +        $contenttype = new contenttype($systemcontext);
 +        $url = $contenttype->get_download_url($content);
 +        $this->assertNotEmpty($url);
 +        $this->assertContains($filename, $url);
 +
 +        // Check the URL is empty when the content hasn't any file.
 +        $record = new stdClass();
 +        $content = $contenttype->create_content($record);
 +        $url = $contenttype->get_download_url($content);
 +        $this->assertEmpty($url);
 +    }
  }
diff --combined contentbank/view.php
@@@ -83,16 -83,16 +83,25 @@@ if ($contenttype->can_manage($content)
          false,
          $attributes
      ));
+     if ($contenttype->can_upload()) {
+         $actionmenu->add_secondary_action(new action_menu_link(
+             new moodle_url('/contentbank/upload.php', ['contextid' => $context->id, 'id' => $content->get_id()]),
+             new pix_icon('i/upload', get_string('upload')),
+             get_string('replacecontent', 'contentbank'),
+             false
+         ));
+     }
  }
 +if ($contenttype->can_download($content)) {
 +    // Add the download content item to the menu.
 +    $actionmenu->add_secondary_action(new action_menu_link(
 +        new moodle_url($contenttype->get_download_url($content)),
 +        new pix_icon('t/download', get_string('download')),
 +        get_string('download'),
 +        false
 +    ));
 +}
  if ($contenttype->can_delete($content)) {
      // Add the delete content item to the menu.
      $attributes = [
diff --combined lang/en/contentbank.php
@@@ -36,11 -36,12 +36,12 @@@ $string['contenttypenoedit'] = 'You ca
  $string['emptynamenotallowed'] = 'Empty name is not allowed';
  $string['eventcontentcreated'] = 'Content created';
  $string['eventcontentdeleted'] = 'Content deleted';
+ $string['eventcontentreplaced'] = 'Content replaced with file';
  $string['eventcontentupdated'] = 'Content updated';
  $string['eventcontentuploaded'] = 'Content uploaded';
  $string['eventcontentviewed'] = 'Content viewed';
  $string['errordeletingcontentfromcategory'] = 'Error deleting content from category {$a}.';
 -$string['errornofile'] = 'A compatible file is needed to create a content';
 +$string['errornofile'] = 'A compatible file is needed to create content.';
  $string['deletecontent'] = 'Delete content';
  $string['deletecontentconfirm'] = 'Are you sure you want to delete the content <em>\'{$a->name}\'</em> and all associated files? This action cannot be undone.';
  $string['displaydetails'] = 'Display content bank with file details';
@@@ -64,6 -65,7 +65,7 @@@ $string['privacy:metadata:userid'] = 'T
  $string['privacy:request:preference:set'] = 'The value of the setting \'{$a->name}\' was \'{$a->value}\'';
  $string['rename'] = 'Rename';
  $string['renamecontent'] = 'Rename content';
+ $string['replacecontent'] = 'Replace with file';
  $string['searchcontentbankbyname'] = 'Search for content by name';
  $string['size'] = 'Size';
  $string['timecreated'] = 'Time created';