if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/cas to auth_cas.
- $DB->set_field('config_plugins', 'plugin', 'auth_cas', array('plugin' => 'auth/cas'));
+ upgrade_fix_config_auth_plugin_names('cas');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'cas');
}
if ($oldversion < 2017032800) {
// Convert info in config plugins from auth/db to auth_db
- $DB->set_field('config_plugins', 'plugin', 'auth_db', array('plugin' => 'auth/db'));
+ upgrade_fix_config_auth_plugin_names('db');
upgrade_plugin_savepoint(true, 2017032800, 'auth', 'db');
}
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/email to auth_email.
- $DB->set_field('config_plugins', 'plugin', 'auth_email', array('plugin' => 'auth/email'));
+ upgrade_fix_config_auth_plugin_names('email');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'email');
}
return true;
}
-
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/fc to auth_fc.
- $DB->set_field('config_plugins', 'plugin', 'auth_fc', array('plugin' => 'auth/fc'));
+ upgrade_fix_config_auth_plugin_names('fc');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'fc');
}
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/imap to auth_imap.
- $DB->set_field('config_plugins', 'plugin', 'auth_imap', array('plugin' => 'auth/imap'));
+ upgrade_fix_config_auth_plugin_names('imap');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'imap');
}
return true;
}
-
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/ldap to auth_ldap.
- $DB->set_field('config_plugins', 'plugin', 'auth_ldap', array('plugin' => 'auth/ldap'));
+ upgrade_fix_config_auth_plugin_names('ldap');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'ldap');
}
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/manual to auth_manual.
- $DB->set_field('config_plugins', 'plugin', 'auth_manual', array('plugin' => 'auth/manual'));
+ upgrade_fix_config_auth_plugin_names('manual');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'manual');
}
// Put any upgrade step following this.
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/mnet to auth_mnet.
- $DB->set_field('config_plugins', 'plugin', 'auth_mnet', array('plugin' => 'auth/mnet'));
+ upgrade_fix_config_auth_plugin_names('mnet');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'mnet');
}
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/nntp to auth_nntp.
- $DB->set_field('config_plugins', 'plugin', 'auth_nntp', array('plugin' => 'auth/nntp'));
+ upgrade_fix_config_auth_plugin_names('nntp');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'nntp');
}
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/none to auth_none.
- $DB->set_field('config_plugins', 'plugin', 'auth_none', array('plugin' => 'auth/none'));
+ upgrade_fix_config_auth_plugin_names('none');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'none');
}
return true;
}
-
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/pam to auth_pam.
- $DB->set_field('config_plugins', 'plugin', 'auth_pam', array('plugin' => 'auth/pam'));
+ upgrade_fix_config_auth_plugin_names('pam');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'pam');
}
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/pop3 to auth_pop3.
- $DB->set_field('config_plugins', 'plugin', 'auth_pop3', array('plugin' => 'auth/pop3'));
+ upgrade_fix_config_auth_plugin_names('pop3');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'pop3');
}
if ($oldversion < 2017020700) {
// Convert info in config plugins from auth/shibboleth to auth_shibboleth.
- $DB->set_field('config_plugins', 'plugin', 'auth_shibboleth', array('plugin' => 'auth/shibboleth'));
+ upgrade_fix_config_auth_plugin_names('shibboleth');
upgrade_plugin_savepoint(true, 2017020700, 'auth', 'shibboleth');
}
* Authentication plugins have been migrated to use the admin settings API.
Plugins should use a settings.php file to manage configurations rather than using the config.html files.
+ See how the helper function upgrade_fix_config_auth_plugin_names() can be used to convert the legacy settings to the
+ new ones.
* The function 'print_auth_lock_options' has been replaced by 'display_auth_lock_options' which uses the admin settings API.
See auth_manual as an exmple of how it can be used. More information can be found in MDL-12689.
* The list of supported identity providers (SSO IdP) returned by the 'loginpage_idp_list' method (used to render the
$external = new stdClass();
-// Check that this id exists.
-if (!empty($id) && !$DB->record_exists('blog_external', array('id' => $id))) {
- print_error('wrongexternalid', 'blog');
-} else if (!empty($id)) {
- $external = $DB->get_record('blog_external', array('id' => $id));
+// Retrieve the external blog record.
+if (!empty($id)) {
+ if (!$external = $DB->get_record('blog_external', array('id' => $id, 'userid' => $USER->id))) {
+ print_error('wrongexternalid', 'blog');
+ }
$external->autotags = core_tag_tag::get_item_tags_array('core', 'blog_external', $id);
}
}
// Append Search info.
- if (!empty($search)) {
+ if (!empty($search) && has_capability('moodle/blog:search', $sitecontext)) {
$headers['filters']['search'] = $search;
$blogurl->param('search', $search);
$PAGE->navbar->add(get_string('searchterm', 'blog', $search), $blogurl->out());
$string['security_question'] = 'Security question';
$string['selfregistration'] = 'Self registration';
$string['selfregistration_help'] = 'If an authentication plugin, such as email-based self-registration, is selected, then it enables potential users to register themselves and create accounts. This results in the possibility of spammers creating accounts in order to use forum posts, blog entries etc. for spam. To avoid this risk, self-registration should be disabled or limited by <em>Allowed email domains</em> setting.';
+$string['settingmigrationmismatch'] = 'Values mismatch detected while correcting the plugin setting names! The authentication plugin \'{$a->plugin}\' had the setting \'{$a->setting}\' configured to \'{$a->legacy}\' under the legacy name and to \'{$a->current}\' under the current name. The latter value has been set as the valid one but you should check and confirm that it is expected.';
$string['sha1'] = 'SHA-1 hash';
$string['showguestlogin'] = 'You can hide or show the guest login button on the login page.';
$string['stdchangepassword'] = 'Use standard page for changing password';
$string['viewsiteentries'] = 'View all entries';
$string['viewuserentries'] = 'View all entries by {$a}';
$string['worldblogs'] = 'The world can read entries set to be world-accessible';
+$string['wrongexternalid'] = 'Wrong external blog id';
$string['wrongpostid'] = 'Wrong blog post id';
$string['page-blog-edit'] = 'Blog editing pages';
$string['page-blog-index'] = 'Blog listing pages';
return $savemessage->id;
}
- $processors = get_message_processors(true);
-
$failed = false;
foreach ($processorlist as $procname) {
// Let new messaging class add custom content based on the processor.
$proceventdata = ($eventdata instanceof message) ? $eventdata->get_eventobject_for_processor($procname) : $eventdata;
- if (!$processors[$procname]->object->send_message($proceventdata)) {
+ $stdproc = new \stdClass();
+ $stdproc->name = $procname;
+ $processor = \core_message\api::get_processed_processor_object($stdproc);
+ if (!$processor->object->send_message($proceventdata)) {
debugging('Error calling message processor ' . $procname);
$failed = true;
// Previously the $messageid = false here was overridden
require_once($CFG->libdir . '/filelib.php');
use moodle_url;
+use moodle_exception;
use curl;
use stdClass;
return false;
}
- if (isset($r->refresh_token)) {
- $systemaccount->set('refreshtoken', $r->refresh_token);
- $systemaccount->update();
- $this->refreshtoken = $r->refresh_token;
- }
-
// Store the token an expiry time.
$accesstoken = new stdClass;
$accesstoken->token = $r->access_token;
// Expires 10 seconds before actual expiry.
$accesstoken->expires = (time() + ($r->expires_in - 10));
}
- if (isset($r->scope)) {
- $accesstoken->scope = $r->scope;
- } else {
- $accesstoken->scope = $this->scope;
- }
+ $accesstoken->scope = $this->scope;
// Also add the scopes.
$this->store_token($accesstoken);
+ if (isset($r->refresh_token)) {
+ $userinfo = $this->get_userinfo();
+
+ if ($userinfo['email'] == $systemaccount->get('email')) {
+ $systemaccount->set('refreshtoken', $r->refresh_token);
+ $systemaccount->update();
+ $this->refreshtoken = $r->refresh_token;
+ } else {
+ throw new moodle_exception('Attempt to store refresh token for non-system user.');
+ }
+ }
+
return true;
}
'core:e/visual_aid' => 'fa-universal-access',
'core:e/visual_blocks' => 'fa-audio-description',
'theme:fp/add_file' => 'fa-file-o',
- 'theme:fp/alias' => 'fa-link',
+ 'theme:fp/alias' => 'fa-share',
+ 'theme:fp/alias_sm' => 'fa-share',
'theme:fp/check' => 'fa-check',
'theme:fp/create_folder' => 'fa-folder-o',
'theme:fp/cross' => 'fa-remove',
}
}
- // Fetch enabled processors
- $processors = get_message_processors(true);
+ // Fetch enabled processors.
+ // If we are dealing with a message some processors may want to handle it regardless of user and site settings.
+ if (empty($savemessage->notification)) {
+ $processors = array_filter(get_message_processors(false), function($processor) {
+ if ($processor->object->force_process_messages()) {
+ return true;
+ }
+
+ return ($processor->enabled && $processor->configured);
+ });
+ } else {
+ $processors = get_message_processors(true);
+ }
// Preset variables
$processorlist = array();
}
// Populate the list of processors we will be using
- if ($permitted == 'forced' && $userisconfigured) {
+ if (empty($savemessage->notification) && $processor->object->force_process_messages()) {
+ $processorlist[] = $processor->name;
+ } else if ($permitted == 'forced' && $userisconfigured) {
// An admin is forcing users to use this message processor. Use this processor unconditionally.
$processorlist[] = $processor->name;
} else if ($permitted == 'permitted' && $userisconfigured && !$eventdata->userto->emailstop) {
/** @var stdClass $accesstoken access token object */
private $accesstoken = null;
/** @var string $refreshtoken refresh token string */
- private $refreshtoken = '';
+ protected $refreshtoken = '';
/** @var string $mocknextresponse string */
private $mocknextresponse = '';
/** @var array $upgradedcodes list of upgraded codes in this request */
$emails = $sink->get_messages();
$this->assertCount(1, $emails);
$email = reset($emails);
- $recordexists = $DB->record_exists('message_read', array('id' => $messageid));
+ $recordexists = $DB->record_exists('message', array('id' => $messageid));
$this->assertSame(true, $recordexists);
$this->assertSame($user1->email, $email->from);
$this->assertSame($user2->email, $email->to);
$emails = $sink->get_messages();
$this->assertCount(1, $emails);
$email = reset($emails);
- $recordexists = $DB->record_exists('message_read', array('id' => $messageid));
+ $recordexists = $DB->record_exists('message', array('id' => $messageid));
$this->assertSame(true, $recordexists);
$this->assertSame($user1->email, $email->from);
$this->assertSame($user2->email, $email->to);
$eventsink = $this->redirectEvents();
+ // Will always use the pop-up processor.
set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'none', $user2);
$message = new \core\message\message();
$this->assertInstanceOf('\core\event\message_viewed', $events[1]);
$eventsink->clear();
+ // Will always use the pop-up processor.
set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
$message = new \core\message\message();
$eventsink->clear();
$user2->emailstop = '0';
+ // Will always use the pop-up processor.
set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
$message = new \core\message\message();
$emails = $sink->get_messages();
$this->assertCount(1, $emails);
$email = reset($emails);
- $savedmessage = $DB->get_record('message_read', array('id' => $messageid), '*', MUST_EXIST);
+ $savedmessage = $DB->get_record('message', array('id' => $messageid), '*', MUST_EXIST);
$this->assertSame($user1->email, $email->from);
$this->assertSame($user2->email, $email->to);
$this->assertSame($message->subject, $email->subject);
$this->assertNotEmpty($email->header);
$this->assertNotEmpty($email->body);
$sink->clear();
- $this->assertFalse($DB->record_exists('message', array()));
+ $this->assertFalse($DB->record_exists('message_read', array()));
$DB->delete_records('message_read', array());
$events = $eventsink->get_events();
- $this->assertCount(2, $events);
+ $this->assertCount(1, $events);
$this->assertInstanceOf('\core\event\message_sent', $events[0]);
- $this->assertInstanceOf('\core\event\message_viewed', $events[1]);
$eventsink->clear();
set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email,popup', $user2);
}
$transaction->allow_commit();
+ // Will always use the pop-up processor.
set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'none', $user2);
$message = new \core\message\message();
$this->assertFalse($DB->record_exists('message_read', array()));
$DB->delete_records('message', array());
$events = $eventsink->get_events();
- $this->assertCount(1, $events);
- $this->assertInstanceOf('\core\event\message_sent', $events[0]);
+ $this->assertCount(0, $events);
$eventsink->clear();
$transaction->allow_commit();
$events = $eventsink->get_events();
- $this->assertCount(0, $events);
+ $this->assertCount(1, $events);
+ $this->assertInstanceOf('\core\event\message_sent', $events[0]);
+ // Will always use the pop-up processor.
set_user_preference('message_provider_moodle_instantmessage_loggedoff', 'email', $user2);
$message = new \core\message\message();
$sink->clear();
$this->assertFalse($DB->record_exists('message_read', array()));
$events = $eventsink->get_events();
- $this->assertCount(0, $events);
+ $this->assertCount(1, $events);
+ $this->assertInstanceOf('\core\event\message_sent', $events[0]);
$transaction->allow_commit();
$events = $eventsink->get_events();
$this->assertCount(2, $events);
- $this->assertInstanceOf('\core\event\message_sent', $events[0]);
- $this->assertInstanceOf('\core\event\message_viewed', $events[1]);
+ $this->assertInstanceOf('\core\event\message_sent', $events[1]);
$eventsink->clear();
$transaction = $DB->start_delegated_transaction();
message_send($message);
message_send($message);
- $this->assertCount(2, $DB->get_records('message'));
- $this->assertCount(1, $DB->get_records('message_read'));
+ $this->assertCount(3, $DB->get_records('message'));
+ $this->assertFalse($DB->record_exists('message_read', array()));
$events = $eventsink->get_events();
$this->assertCount(0, $events);
$transaction->allow_commit();
$events = $eventsink->get_events();
- $this->assertCount(4, $events);
+ $this->assertCount(2, $events);
$this->assertInstanceOf('\core\event\message_sent', $events[0]);
- $this->assertInstanceOf('\core\event\message_viewed', $events[1]);
- $this->assertInstanceOf('\core\event\message_sent', $events[2]);
- $this->assertInstanceOf('\core\event\message_viewed', $events[3]);
+ $this->assertInstanceOf('\core\event\message_sent', $events[1]);
$eventsink->clear();
$DB->delete_records('message', array());
$DB->delete_records('message_read', array());
$this->assertCount(0, $DB->get_records('message'));
$this->assertCount(0, $DB->get_records('message_read'));
message_send($message);
- $this->assertCount(0, $DB->get_records('message'));
- $this->assertCount(1, $DB->get_records('message_read'));
+ $this->assertCount(1, $DB->get_records('message'));
+ $this->assertCount(0, $DB->get_records('message_read'));
$events = $eventsink->get_events();
- $this->assertCount(2, $events);
+ $this->assertCount(1, $events);
+ $this->assertInstanceOf('\core\event\message_sent', $events[0]);
$sink->clear();
$DB->delete_records('message_read', array());
}
$this->assertEquals(count($blockinstances), $DB->count_records('block_positions', ['subpage' => $page1->id, 'pagetype' => 'my-index', 'contextid' => $context1->id]));
$this->assertEquals(0, $DB->count_records('block_positions', ['subpage' => $page2->id, 'pagetype' => 'my-index']));
}
+
+ /**
+ * Test the conversion of auth plugin settings names.
+ */
+ public function test_upgrade_fix_config_auth_plugin_names() {
+ $this->resetAfterTest();
+
+ // Let the plugin auth_foo use legacy format only.
+ set_config('name1', 'val1', 'auth/foo');
+ set_config('name2', 'val2', 'auth/foo');
+
+ // Let the plugin auth_bar use new format only.
+ set_config('name1', 'val1', 'auth_bar');
+ set_config('name2', 'val2', 'auth_bar');
+
+ // Let the plugin auth_baz use a mix of legacy and new format, with no conflicts.
+ set_config('name1', 'val1', 'auth_baz');
+ set_config('name1', 'val1', 'auth/baz');
+ set_config('name2', 'val2', 'auth/baz');
+ set_config('name3', 'val3', 'auth_baz');
+
+ // Let the plugin auth_qux use a mix of legacy and new format, with conflicts.
+ set_config('name1', 'val1', 'auth_qux');
+ set_config('name1', 'val2', 'auth/qux');
+
+ // Execute the migration.
+ upgrade_fix_config_auth_plugin_names('foo');
+ upgrade_fix_config_auth_plugin_names('bar');
+ upgrade_fix_config_auth_plugin_names('baz');
+ upgrade_fix_config_auth_plugin_names('qux');
+
+ // Assert that legacy settings are gone and no new were introduced.
+ $this->assertEmpty((array) get_config('auth/foo'));
+ $this->assertEmpty((array) get_config('auth/bar'));
+ $this->assertEmpty((array) get_config('auth/baz'));
+ $this->assertEmpty((array) get_config('auth/qux'));
+
+ // Assert values were simply kept where there was no conflict.
+ $this->assertSame('val1', get_config('auth_foo', 'name1'));
+ $this->assertSame('val2', get_config('auth_foo', 'name2'));
+
+ $this->assertSame('val1', get_config('auth_bar', 'name1'));
+ $this->assertSame('val2', get_config('auth_bar', 'name2'));
+
+ $this->assertSame('val1', get_config('auth_baz', 'name1'));
+ $this->assertSame('val2', get_config('auth_baz', 'name2'));
+ $this->assertSame('val3', get_config('auth_baz', 'name3'));
+
+ // Assert the new format took precedence in case of conflict.
+ $this->assertSame('val1', get_config('auth_qux', 'name1'));
+ }
}
return null;
}
+
+/**
+ * Fix how auth plugins are called in the 'config_plugins' table.
+ *
+ * For legacy reasons, the auth plugins did not always use their frankenstyle
+ * component name in the 'plugin' column of the 'config_plugins' table. This is
+ * a helper function to correctly migrate the legacy settings into the expected
+ * and consistent way.
+ *
+ * @param string $plugin the auth plugin name such as 'cas', 'manual' or 'mnet'
+ */
+function upgrade_fix_config_auth_plugin_names($plugin) {
+ global $CFG, $DB, $OUTPUT;
+
+ $legacy = (array) get_config('auth/'.$plugin);
+ $current = (array) get_config('auth_'.$plugin);
+
+ // I don't want to rely on array_merge() and friends here just in case
+ // there was some crazy setting with a numerical name.
+
+ if ($legacy) {
+ $new = $legacy;
+ } else {
+ $new = [];
+ }
+
+ if ($current) {
+ foreach ($current as $name => $value) {
+ if (isset($legacy[$name]) && ($legacy[$name] !== $value)) {
+ // No need to pollute the output during unit tests.
+ if (!empty($CFG->upgraderunning)) {
+ $message = get_string('settingmigrationmismatch', 'core_auth', [
+ 'plugin' => 'auth_'.$plugin,
+ 'setting' => s($name),
+ 'legacy' => s($legacy[$name]),
+ 'current' => s($value),
+ ]);
+ echo $OUTPUT->notification($message, \core\output\notification::NOTIFY_ERROR);
+
+ upgrade_log(UPGRADE_LOG_NOTICE, 'auth_'.$plugin, 'Setting values mismatch detected',
+ 'SETTING: '.$name. ' LEGACY: '.$legacy[$name].' CURRENT: '.$value);
+ }
+ }
+
+ $new[$name] = $value;
+ }
+ }
+
+ foreach ($new as $name => $value) {
+ set_config($name, $value, 'auth_'.$plugin);
+ unset_config($name, 'auth/'.$plugin);
+ }
+}
public function has_message_preferences() {
return true;
}
+
+ /**
+ * Determines if this processor should process a message regardless of user preferences or site settings.
+ *
+ * @return bool
+ */
+ public function force_process_messages() {
+ return false;
+ }
}
$DB->update_record('message_popup', $record);
}
}
+
+ /**
+ * Determines if this processor should process a message regardless of user preferences or site settings.
+ *
+ * @return bool
+ */
+ public function force_process_messages() {
+ global $CFG;
+
+ return !empty($CFG->messaging);
+ }
}
* @param string $filearea
* @param array $args
* @param bool $forcedownload
+ * @param array $options - List of options affecting file serving.
* @return bool false if file not found, does not return if found - just send the file
*/
function assignfeedback_editpdf_pluginfile($course,
context $context,
$filearea,
$args,
- $forcedownload) {
+ $forcedownload,
+ array $options=array()) {
global $USER, $DB, $CFG;
if ($context->contextlevel == CONTEXT_MODULE) {
return false;
}
// Download MUST be forced - security!
- send_stored_file($file, 0, 0, true);// Check if we want to retrieve the stamps.
+ send_stored_file($file, 0, 0, true, $options);// Check if we want to retrieve the stamps.
}
}
* @param string $filearea
* @param array $args
* @param bool $forcedownload
+ * @param array $options - List of options affecting file serving.
* @return bool false if file not found, does not return if found - just send the file
*/
function assignfeedback_file_pluginfile($course,
context $context,
$filearea,
$args,
- $forcedownload) {
+ $forcedownload,
+ array $options=array()) {
global $USER, $DB;
if ($context->contextlevel != CONTEXT_MODULE) {
return false;
}
// Download MUST be forced - security!
- send_stored_file($file, 0, 0, true);
+ send_stored_file($file, 0, 0, true, $options);
}
* @param string $filearea
* @param array $args
* @param bool $forcedownload
+ * @param array $options - List of options affecting file serving.
* @return bool false if file not found, does not return if found - just send the file
*/
function assignsubmission_file_pluginfile($course,
context $context,
$filearea,
$args,
- $forcedownload) {
+ $forcedownload,
+ array $options=array()) {
global $DB, $CFG;
if ($context->contextlevel != CONTEXT_MODULE) {
}
// Download MUST be forced - security!
- send_stored_file($file, 0, 0, true);
+ send_stored_file($file, 0, 0, true, $options);
}
* @param string $filearea
* @param array $args
* @param bool $forcedownload
+ * @param array $options - List of options affecting file serving.
* @return bool false if file not found, does not return if found - just send the file
*/
-function assignsubmission_onlinetext_pluginfile($course, $cm, context $context, $filearea, $args, $forcedownload) {
+function assignsubmission_onlinetext_pluginfile($course,
+ $cm,
+ context $context,
+ $filearea,
+ $args,
+ $forcedownload,
+ array $options=array()) {
global $DB, $CFG;
if ($context->contextlevel != CONTEXT_MODULE) {
}
// Download MUST be forced - security!
- send_stored_file($file, 0, 0, true);
+ send_stored_file($file, 0, 0, true, $options);
}
This files describes API changes in the assign code.
=== 3.3 ===
+* All pluginfile file serving functions now pass through the options to send_stored_file() (all assignment plugins should do
+ the same).
* Fixed calendar event types for overridden due dates from 'close' to 'due'.
* Removed calendar event type of 'open', since mod_assign only has the 'due' event type. No point in creating an override event
for an event type that does not exist.
$users = choice_get_response_data($choice, $cm, $groupmode, $onlyactive);
+ $extrafields = get_extra_user_fields($context);
+
if ($download == "ods" && has_capability('mod/choice:downloadresponses', $context)) {
require_once("$CFG->libdir/odslib.class.php");
$myxls = $workbook->add_worksheet($strresponses);
/// Print names of all the fields
- $myxls->write_string(0,0,get_string("lastname"));
- $myxls->write_string(0,1,get_string("firstname"));
- $myxls->write_string(0,2,get_string("idnumber"));
- $myxls->write_string(0,3,get_string("group"));
- $myxls->write_string(0,4,get_string("choice","choice"));
+ $i = 0;
+ $myxls->write_string(0, $i++, get_string("lastname"));
+ $myxls->write_string(0, $i++, get_string("firstname"));
- /// generate the data for the body of the spreadsheet
- $i=0;
- $row=1;
+ // Add headers for extra user fields.
+ foreach ($extrafields as $field) {
+ $myxls->write_string(0, $i++, get_user_field_name($field));
+ }
+
+ $myxls->write_string(0, $i++, get_string("group"));
+ $myxls->write_string(0, $i++, get_string("choice", "choice"));
+
+ // Generate the data for the body of the spreadsheet.
+ $row = 1;
if ($users) {
foreach ($users as $option => $userid) {
$option_text = choice_get_option_text($choice, $option);
- foreach($userid as $user) {
- $myxls->write_string($row,0,$user->lastname);
- $myxls->write_string($row,1,$user->firstname);
- $studentid=(!empty($user->idnumber) ? $user->idnumber : " ");
- $myxls->write_string($row,2,$studentid);
+ foreach ($userid as $user) {
+ $i = 0;
+ $myxls->write_string($row, $i++, $user->lastname);
+ $myxls->write_string($row, $i++, $user->firstname);
+ foreach ($extrafields as $field) {
+ $myxls->write_string($row, $i++, $user->$field);
+ }
$ug2 = '';
if ($usergrps = groups_get_all_groups($course->id, $user->id)) {
foreach ($usergrps as $ug) {
- $ug2 = $ug2. $ug->name;
+ $ug2 = $ug2 . $ug->name;
}
}
- $myxls->write_string($row,3,$ug2);
+ $myxls->write_string($row, $i++, $ug2);
if (isset($option_text)) {
- $myxls->write_string($row,4,format_string($option_text,true));
+ $myxls->write_string($row, $i++, format_string($option_text, true));
}
$row++;
- $pos=4;
}
}
}
$myxls = $workbook->add_worksheet($strresponses);
/// Print names of all the fields
- $myxls->write_string(0,0,get_string("lastname"));
- $myxls->write_string(0,1,get_string("firstname"));
- $myxls->write_string(0,2,get_string("idnumber"));
- $myxls->write_string(0,3,get_string("group"));
- $myxls->write_string(0,4,get_string("choice","choice"));
+ $i = 0;
+ $myxls->write_string(0, $i++, get_string("lastname"));
+ $myxls->write_string(0, $i++, get_string("firstname"));
+ // Add headers for extra user fields.
+ foreach ($extrafields as $field) {
+ $myxls->write_string(0, $i++, get_user_field_name($field));
+ }
- /// generate the data for the body of the spreadsheet
- $i=0;
- $row=1;
+ $myxls->write_string(0, $i++, get_string("group"));
+ $myxls->write_string(0, $i++, get_string("choice", "choice"));
+
+ // Generate the data for the body of the spreadsheet.
+ $row = 1;
if ($users) {
foreach ($users as $option => $userid) {
+ $i = 0;
$option_text = choice_get_option_text($choice, $option);
foreach($userid as $user) {
- $myxls->write_string($row,0,$user->lastname);
- $myxls->write_string($row,1,$user->firstname);
- $studentid=(!empty($user->idnumber) ? $user->idnumber : " ");
- $myxls->write_string($row,2,$studentid);
+ $i = 0;
+ $myxls->write_string($row, $i++, $user->lastname);
+ $myxls->write_string($row, $i++, $user->firstname);
+ foreach ($extrafields as $field) {
+ $myxls->write_string($row, $i++, $user->$field);
+ }
$ug2 = '';
if ($usergrps = groups_get_all_groups($course->id, $user->id)) {
foreach ($usergrps as $ug) {
- $ug2 = $ug2. $ug->name;
+ $ug2 = $ug2 . $ug->name;
}
}
- $myxls->write_string($row,3,$ug2);
+ $myxls->write_string($row, $i++, $ug2);
if (isset($option_text)) {
- $myxls->write_string($row,4,format_string($option_text,true));
+ $myxls->write_string($row, $i++, format_string($option_text, true));
}
$row++;
}
}
- $pos=4;
}
/// Close the workbook
$workbook->close();
/// Print names of all the fields
- echo get_string("lastname")."\t".get_string("firstname") . "\t". get_string("idnumber") . "\t";
+ echo get_string("lastname") . "\t" . get_string("firstname") . "\t";
+
+ // Add headers for extra user fields.
+ foreach ($extrafields as $field) {
+ echo get_user_field_name($field) . "\t";
+ }
+
echo get_string("group"). "\t";
echo get_string("choice","choice"). "\n";
foreach ($users as $option => $userid) {
$option_text = choice_get_option_text($choice, $option);
foreach($userid as $user) {
- echo $user->lastname;
- echo "\t".$user->firstname;
- $studentid = " ";
- if (!empty($user->idnumber)) {
- $studentid = $user->idnumber;
+ echo $user->lastname . "\t";
+ echo $user->firstname . "\t";
+ foreach ($extrafields as $field) {
+ echo $user->$field . "\t";
}
- echo "\t". $studentid."\t";
$ug2 = '';
if ($usergrps = groups_get_all_groups($course->id, $user->id)) {
foreach ($usergrps as $ug) {
namespace repository_onedrive;
use \core\task\scheduled_task;
+use DateTime;
+use DateInterval;
+use repository_exception;
+use \core\oauth2\rest_exception;
defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot . '/repository/lib.php');
+
/**
* Simple task to delete temporary permission records.
* @package repository_onedrive
$expires->sub(new DateInterval("P7D"));
$timestamp = $expires->getTimestamp();
- $issuerid = get_config('repository_onedrive', 'issuerid');
- $issuer = \core\oauth2\api::get_issuer_by_id($issuerid);
+ $issuerid = get_config('onedrive', 'issuerid');
+ $issuer = \core\oauth2\api::get_issuer($issuerid);
// Add the current user as an OAuth writer.
$systemauth = \core\oauth2\api::get_system_oauth_client($issuer);
$details = 'Cannot connect as system user';
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
}
- $systemservice = new repository_onedrive\rest($systemauth);
+ $systemservice = new \repository_onedrive\rest($systemauth);
foreach ($accessrecords as $access) {
if ($access->get('timemodified') < $timestamp) {
$params = ['permissionid' => $access->get('permissionid'), 'itemid' => $access->get('itemid')];
- $systemservice->call('delete_permission', $params);
+ try {
+ $systemservice->call('delete_permission', $params);
+ } catch (rest_exception $re) {
+ // We log and give up here or we will always fail for eternity.
+ mtrace('Failed to remove access from file: ' . $access->get('itemid'));
+ }
$access->delete();
}
}
],
'response' => 'json'
],
- 'list_permissions' => [
- 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}/permissions',
- 'method' => 'get',
+ 'create_permission' => [
+ 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}/invite',
+ 'method' => 'post',
'args' => [
- '$select' => PARAM_RAW,
- '$expand' => PARAM_RAW,
- 'fileid' => PARAM_RAW,
- '$skip' => PARAM_INT,
- '$skipToken' => PARAM_RAW,
- '$count' => PARAM_INT
+ 'fileid' => PARAM_RAW
],
'response' => 'json'
],
- 'create_permission' => [
- 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}/invite',
+ 'create_upload' => [
+ 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{parentid}:/{filename}:/createUploadSession',
'method' => 'post',
'args' => [
- 'fileid' => PARAM_RAW
+ 'parentid' => PARAM_RAW,
+ 'filename' => PARAM_RAW
],
'response' => 'json'
],
],
'response' => 'json'
],
- 'get_drive' => [
- 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive',
- 'method' => 'get',
- 'args' => [],
- 'response' => 'json'
- ],
'delete_file_by_path' => [
'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/root:/{fullpath}',
'method' => 'delete',
],
'response' => 'json'
],
- 'copy_share' => [
- 'endpoint' => 'https://graph.microsoft.com/v1.0/shares/{sharetoken}/root/copy',
- 'method' => 'post',
- 'args' => [
- 'sharetoken' => PARAM_RAW,
- ],
- 'response' => 'json'
- ],
'delete_permission' => [
'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}/permissions/{permissionid}',
'method' => 'delete',
$string['internal'] = 'Internal (files stored in Moodle)';
$string['issuer_help'] = 'Select the OAuth 2 service that is configured to talk to the OneDrive API. If the services does not exist yet, you might need to create it.';
$string['issuer'] = 'OAuth 2 service';
+$string['mysitenotfound'] = 'You have never logged into OneDrive before. You must login to OneDrive at least once it before it can be used with Moodle.';
$string['oauth2serviceslink'] = '<a href="{$a}" title="Link to OAuth Services configuration">OAuth 2 Services Configuration</a>';
$string['owner'] = 'Owned by: {$a}';
$string['pluginname'] = 'Microsoft OneDrive';
} catch (Exception $e) {
if ($e->getCode() == 403 && strpos($e->getMessage(), 'Access Not Configured') !== false) {
throw new repository_exception('servicenotenabled', 'repository_onedrive');
- } else {
- throw $e;
+ } else if (strpos($e->getMessage(), 'mysite not found') !== false) {
+ throw new repository_exception('mysitenotfound', 'repository_onedrive');
}
}
if ($this->disabled) {
throw new repository_exception('cannotdownload', 'repository');
}
+ $sourceinfo = json_decode($reference);
+
+ $client = null;
+ if (!empty($sourceinfo->usesystem)) {
+ $client = \core\oauth2\api::get_system_oauth_client($this->issuer);
+ } else {
+ $client = $this->get_user_oauth_client();
+ }
- $client = $this->get_user_oauth_client();
$base = 'https://graph.microsoft.com/v1.0/';
- $sourceinfo = json_decode($reference);
$sourceurl = new moodle_url($base . 'me/drive/items/' . $sourceinfo->id . '/content');
$source = $sourceurl->out(false);
$storedfile->get_filepath(),
$storedfile->get_filename());
- if (empty($options['offline']) && !empty($info) && $info->is_writable()) {
+ if (empty($options['offline']) && !empty($info) && $info->is_writable() && !empty($source->usesystem)) {
// Add the current user as an OAuth writer.
$systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
}
}
- /**
- * List the permissions on a file.
- *
- * @param \repository_onedrive\rest $client Authenticated client.
- * @param string $fileid The id of the file.
- * @return array
- */
- protected function list_file_permissions(\repository_onedrive\rest $client, $fileid) {
- $fields = "id,roles,link,grantedTo";
- return $client->call('list_permissions', ['fileid' => $fileid, '$select' => $fields]);
- }
-
/**
* See if a folder exists within a folder
*
return true;
}
-
- /**
- * Get a file summary by full path.
- *
- * @param \repository_onedrive\rest $client Authenticated client.
- * @param string $fullpath
- * @return stdClass
- */
- protected function get_file_summary_by_path(\repository_onedrive\rest $client, $fullpath) {
- $fields = "folder,id,lastModifiedDateTime,name,size,webUrl,createdByUser";
- $response = $client->call('get_file_by_path', ['fullpath' => $fullpath, '$select' => $fields]);
- if (empty($response->id)) {
- $details = 'Cannot get file summary:' . $fullpath;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return $response;
- }
-
/**
* Create a folder within a folder
*
return $response;
}
- /**
- * Get the id of this users root drive.
- *
- * @param \repository_onedrive\rest $client Authenticated client.
- *
- * @return string id
- */
- protected function get_root_drive_id(\repository_onedrive\rest $client) {
- $response = $client->call('get_drive', []);
-
- if (empty($response->id)) {
- $details = 'Cannot get driveid';
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return $response->id;
- }
-
/**
* Add a writer to the permissions on the file (temporary).
*
return true;
}
- /**
- * Add a writer to the permissions on the file.
- *
- * @param \repository_onedrive\rest $client Authenticated client.
- * @param string $fileid The file we are updating.
- * @param string $useremail The user email of the writer account to add.
- * @return boolean
- */
- protected function add_writer_to_file(\repository_onedrive\rest $client, $fileid, $useremail) {
- $updateeditor = [
- 'recipients' => [ [ 'email' => $useremail ] ],
- 'roles' => ['write'],
- 'requireSignIn' => true,
- 'sendInvitation' => false
- ];
- $params = [ 'fileid' => $fileid ];
- $response = $client->call('create_permission', $params, json_encode($updateeditor));
- if (empty($response->value)) {
- $details = 'Cannot add user ' . $useremail . ' as a writer for document: ' . $fileid;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return true;
- }
-
/**
* Allow anyone with the link to read the file.
*
$details = 'Cannot update link sharing for the document: ' . $fileid;
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
}
- return true;
+ return $response->link->webUrl;
}
/**
- * Copy a shared file to a new folder.
+ * Given a filename, use the core_filetypes registered types to guess a mimetype.
*
- * @param \repository_onedrive\rest $client Authenticated client.
- * @param string $sharetoken The share we are querying.
- * @param string $newdrive Id of the drive to copy to.
- * @param string $parentid Id of the folder to copy to.
- * @return stdClass
+ * If no mimetype is known, return 'application/unknown';
+ *
+ * @param string $filename
+ * @return string $mimetype
*/
- protected function copy_share(\repository_onedrive\rest $client, $sharetoken, $newdrive, $parentid) {
- $folder = [
- 'parentReference' => ['id' => $parentid, 'driveId' => $newdrive]
- ];
- $params = ['sharetoken' => $sharetoken];
- $response = $client->call('copy_share', $params, json_encode($folder));
- return true;
+ protected function get_mimetype_from_filename($filename) {
+ $mimetype = 'application/unknown';
+ $types = core_filetypes::get_types();
+ $fileextension = '.bin';
+ if (strpos($filename, '.') !== false) {
+ $fileextension = substr($filename, strrpos($filename, '.') + 1);
+ }
+
+ if (isset($types[$fileextension])) {
+ $mimetype = $types[$fileextension]['type'];
+ }
+ return $mimetype;
}
/**
- * From MS docs - to get a share token from a url, do this:
- * Reference: https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/shares_get
- * To access a sharing URL using the shares API, the URL needs to be transformed into a sharing token.
- * To transform a URL into a sharing token:
- * Base64 encode the sharing URL.
- * Convert the base64 encoded data to unpadded base64url format by:
- * Trim trailing = characeters from the string.
- * Replace unsafe URL characters with an equivelent character; replace / with _ and + with -.
- * Append u! to the beginning of the string.
+ * Upload a file to onedrive.
*
- * @param string $shareurl
- * @return string The sharing token
+ * @param \repository_onedrive\rest $service Authenticated client.
+ * @param \curl $curl Curl client to perform the put operation.
+ * @param string $filepath The local path to the file to upload
+ * @param string $mimetype The new mimetype
+ * @param string $parentid The folder to put it.
+ * @param string $filename The name of the new file
+ * @return string $fileid
*/
- protected function get_share_token($shareurl) {
- return 'u!' . str_replace(['/', '+'], ['_', '-'], rtrim(base64_encode($shareurl), '='));
+ protected function upload_file(\repository_onedrive\rest $service, \curl $curl, $filepath, $mimetype, $parentid, $filename) {
+ // Start an upload session.
+ // Docs https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/item_createuploadsession link.
+
+ $params = ['parentid' => $parentid, 'filename' => urlencode($filename)];
+ $behaviour = [ 'item' => [ "@microsoft.graph.conflictBehavior" => "rename" ] ];
+ $created = $service->call('create_upload', $params, json_encode($behaviour));
+ if (empty($created->uploadUrl)) {
+ $details = 'Cannot begin upload session:' . $parentid;
+ throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
+ }
+
+ $options = ['file' => $filepath];
+ $curl->setHeader('Content-type: ' . $mimetype);
+ $size = filesize($filepath);
+ $curl->setHeader('Content-Range: bytes 0-' . ($size - 1) . '/' . $size);
+ $response = $curl->put($created->uploadUrl, $options);
+ if ($curl->errno == 0) {
+ $response = json_decode($response);
+ }
+
+ if (empty($response->id)) {
+ $details = 'File not created';
+ throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
+ }
+
+ return $response->id;
}
+
/**
* Called when a file is selected as a "link".
* Invoked at MOODLE/repository/repository_ajax.php
$details = 'Cannot connect as system user';
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
}
- $systemuserinfo = $systemauth->get_userinfo();
- $systemuseremail = $systemuserinfo['email'];
$source = json_decode($reference);
$details = 'Cannot connect as current user';
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
}
- $userinfo = $userauth->get_userinfo();
- $useremail = $userinfo['email'];
- $userservice = new repository_onedrive\rest($userauth);
$systemservice = new repository_onedrive\rest($systemauth);
- // Get the list of existing permissions so we can see if the owner is already the system account,
- // and whether we need to update the link sharing options.
- $permissions = $this->list_file_permissions($userservice, $source->id);
-
- $readshareupdaterequired = true;
- $ownerupdaterequired = true;
- foreach ($permissions->value as $permission) {
- if (!empty($permission->link)) {
- if ($permission->link->scope == 'anonymous' &&
- $permission->link->type == 'view') {
- $shareurl = $permission->link->webUrl;
- $readshareupdaterequired = false;
- break;
- }
- }
- }
+ // Download the file.
+ $tmpfilename = clean_param($source->id, PARAM_PATH);
+ $temppath = make_request_directory() . $tmpfilename;
- // Add Moodle as writer.
- $this->add_writer_to_file($userservice, $source->id, $systemuseremail);
+ $options = ['filepath' => $temppath, 'timeout' => 60, 'followlocation' => true, 'maxredirs' => 5];
+ $base = 'https://graph.microsoft.com/v1.0/';
+ $sourceurl = new moodle_url($base . 'me/drive/items/' . $source->id . '/content');
+ $sourceurl = $sourceurl->out(false);
+
+ $result = $userauth->download_one($sourceurl, null, $options);
+
+ if (!$result) {
+ throw new repository_exception('cannotdownload', 'repository');
+ }
// Now copy it to a sensible folder.
$contextlist = array_reverse($context->get_parent_contexts(true));
}
}
- // Get the users drive id.
- $newdrive = $this->get_root_drive_id($systemservice);
-
- if ($readshareupdaterequired) {
- $response = $this->set_file_sharing_anyone_with_link_can_read($userservice, $source->id);
- $shareurl = $response->value->webUrl;
- }
-
- // Turn the share url into a sharing token.
- $sharetoken = $this->get_share_token($shareurl);
-
// Delete any existing file at this path.
- $path = $fullpath . '/' . $source->name;
+ $path = $fullpath . '/' . urlencode(clean_param($source->name, PARAM_PATH));
$this->delete_file_by_path($systemservice, $path);
- // Copy the file to the moodle account.
- // Note this method (copying via a share link) is the only way to copy a file in
- // office 365 from one user to another.
- $this->copy_share($systemservice, $sharetoken, $newdrive, $parentid);
+ // Upload the file.
+ $safefilename = clean_param($source->name, PARAM_PATH);
+ $mimetype = $this->get_mimetype_from_filename($safefilename);
+ // We cannot send authorization headers in the upload or personal microsoft accounts will fail (what a joke!).
+ $curl = new \curl();
+ $fileid = $this->upload_file($systemservice, $curl, $temppath, $mimetype, $parentid, $safefilename);
+
+ // Read with link.
+ $link = $this->set_file_sharing_anyone_with_link_can_read($systemservice, $fileid);
- $summary = $this->get_file_summary_by_path($systemservice, $path);
+ $summary = $this->get_file_summary($systemservice, $fileid);
// Update the details in the file reference before it is saved.
$source->id = $summary->id;
- $source->link = $summary->webUrl;
+ $source->link = $link;
+ $source->usesystem = true;
$reference = json_encode($source);
return get_string('unknownsource', 'repository');
}
$source = json_decode($reference);
+ if (empty($source->usesystem)) {
+ return '';
+ }
$systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
if ($systemauth === false) {
-<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMid meet"><path d="M1520 1216q0-40-28-68l-208-208q-28-28-68-28-42 0-72 32 3 3 19 18.5t21.5 21.5 15 19 13 25.5 3.5 27.5q0 40-28 68t-68 28q-15 0-27.5-3.5t-25.5-13-19-15-21.5-21.5-18.5-19q-33 31-33 73 0 40 28 68l206 207q27 27 68 27 40 0 68-26l147-146q28-28 28-67zm-703-705q0-40-28-68l-206-207q-28-28-68-28-39 0-68 27l-147 146q-28 28-28 67 0 40 28 68l208 208q27 27 68 27 42 0 72-31-3-3-19-18.5t-21.5-21.5-15-19-13-25.5-3.5-27.5q0-40 28-68t68-28q15 0 27.5 3.5t25.5 13 19 15 21.5 21.5 18.5 19q33-31 33-73zm895 705q0 120-85 203l-147 146q-83 83-203 83-121 0-204-85l-206-207q-83-83-83-203 0-123 88-209l-88-88q-86 88-208 88-120 0-204-84l-208-208q-84-84-84-204t85-203l147-146q83-83 203-83 121 0 204 85l206 207q83 83 83 203 0 123-88 209l88 88q86-88 208-88 120 0 204 84l208 208q84 84 84 204z"/></svg>
\ No newline at end of file
+<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1792 640q0 26-19 45l-512 512q-19 19-45 19t-45-19-19-45v-256h-224q-98 0-175.5 6t-154 21.5-133 42.5-105.5 69.5-80 101-48.5 138.5-17.5 181q0 55 5 123 0 6 2.5 23.5t2.5 26.5q0 15-8.5 25t-23.5 10q-16 0-28-17-7-9-13-22t-13.5-30-10.5-24q-127-285-127-451 0-199 53-333 162-403 875-403h224v-256q0-26 19-45t45-19 45 19l512 512q19 19 19 45z"/></svg>
\ No newline at end of file
--- /dev/null
+<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1792 640q0 26-19 45l-512 512q-19 19-45 19t-45-19-19-45v-256h-224q-98 0-175.5 6t-154 21.5-133 42.5-105.5 69.5-80 101-48.5 138.5-17.5 181q0 55 5 123 0 6 2.5 23.5t2.5 26.5q0 15-8.5 25t-23.5 10q-16 0-28-17-7-9-13-22t-13.5-30-10.5-24q-127-285-127-451 0-199 53-333 162-403 875-403h224v-256q0-26 19-45t45-19 45 19l512 512q19 19 19 45z"/></svg>
\ No newline at end of file
*/
/*rtl:ignore*/
background-position: bottom right;
+ background-size: 16px 16px;
}
.fp-iconview .fp-file.fp-isreference .fp-reficons2 {
*/
/*rtl:ignore*/
background-position: bottom left;
+ background-size: 16px 16px;
}
.filemanager .fp-iconview .fp-file.fp-originalmissing .fp-thumbnail img {
position: absolute;
top: 8px;
left: 17px;
+ background-size: 16px 16px;
}
.filemanager .fp-filename-icon.fp-isreference .fp-reficons2 {
position: absolute;
top: 9px;
left: -6px;
+ background-size: 16px 16px;
}
// Folder Context Menu (File Manager only)
.filemanager .fp-contextmenu {
&.iconsize-big {
width: $icon-big-width;
height: $icon-big-height;
+ font-size: $icon-big-height;
}
}