//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/* jshint node: true, browser: false */
/**
* @copyright 2014 Andrew Nicols
module.exports = function(grunt) {
var path = require('path'),
- fs = require('fs'),
tasks = {},
- cwd = process.env.PWD || process.cwd(),
- inAMD = path.basename(cwd) == 'amd';
+ cwd = process.env.PWD || process.cwd();
+
+ // Windows users can't run grunt in a subdirectory, so allow them to set
+ // the root by passing --root=path/to/dir.
+ if (grunt.option('root')) {
+ var root = grunt.option('root');
+ if (grunt.file.exists(__dirname, root)) {
+ cwd = path.join(__dirname, root);
+ grunt.log.ok('Setting root to '+cwd);
+ } else {
+ grunt.fail.fatal('Setting root to '+root+' failed - path does not exist');
+ }
+ }
+
+ var inAMD = path.basename(cwd) == 'amd';
+
+ // Globbing pattern for matching all AMD JS source files.
+ var amdSrc = [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js'];
+
+ /**
+ * Function to generate the destination for the uglify task
+ * (e.g. build/file.min.js). This function will be passed to
+ * the rename property of files array when building dynamically:
+ * http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
+ *
+ * @param {String} destPath the current destination
+ * @param {String} srcPath the matched src path
+ * @return {String} The rewritten destination path.
+ */
+ var uglify_rename = function (destPath, srcPath) {
+ destPath = srcPath.replace('src', 'build');
+ destPath = destPath.replace('.js', '.min.js');
+ destPath = path.resolve(cwd, destPath);
+ return destPath;
+ };
// Project configuration.
grunt.initConfig({
jshint: {
options: {jshintrc: '.jshintrc'},
- files: [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js']
+ amd: { src: amdSrc }
},
uglify: {
- dynamic_mappings: {
- files: grunt.file.expandMapping(
- ['**/src/*.js', '!**/node_modules/**'],
- '',
- {
- cwd: cwd,
- rename: function(destBase, destPath) {
- destPath = destPath.replace('src', 'build');
- destPath = destPath.replace('.js', '.min.js');
- destPath = path.resolve(cwd, destPath);
- return destPath;
- }
- }
- )
+ amd: {
+ files: [{
+ expand: true,
+ src: amdSrc,
+ rename: uglify_rename
+ }]
}
},
less: {
compress: true
}
}
+ },
+ watch: {
+ options: {
+ nospawn: true // We need not to spawn so config can be changed dynamically.
+ },
+ amd: {
+ files: ['**/amd/src/**/*.js'],
+ tasks: ['amd']
+ },
+ bootstrapbase: {
+ files: ["theme/bootstrapbase/less/**/*.less"],
+ tasks: ["less:bootstrapbase"]
+ },
+ yui: {
+ files: ['**/yui/src/**/*.js'],
+ tasks: ['shifter']
+ },
+ },
+ shifter: {
+ options: {
+ recursive: true,
+ paths: [cwd]
+ }
}
});
+ /**
+ * Shifter task. Is configured with a path to a specific file or a directory,
+ * in the case of a specific file it will work out the right module to be built.
+ *
+ * Note that this task runs the invidiaul shifter jobs async (becase it spawns
+ * so be careful to to call done().
+ */
tasks.shifter = function() {
- var exec = require('child_process').spawn,
+ var async = require('async'),
done = this.async(),
- args = [],
- options = {
- recursive: true,
- watch: false,
- walk: false,
- module: false
- },
- shifter;
+ options = grunt.config('shifter.options');
+ // Run the shifter processes one at a time to avoid confusing output.
+ async.eachSeries(options.paths, function (src, filedone) {
+ var args = [];
args.push( path.normalize(__dirname + '/node_modules/shifter/bin/shifter'));
+ // Always ignore the node_modules directory.
+ args.push('--excludes', 'node_modules');
+
// Determine the most appropriate options to run with based upon the current location.
- if (path.basename(cwd) === 'src') {
- // Detect whether we're in a src directory.
+ if (grunt.file.isMatch('**/yui/**/*.js', src)) {
+ // When passed a JS file, build our containing module (this happen with
+ // watch).
+ grunt.log.debug('Shifter passed a specific JS file');
+ src = path.dirname(path.dirname(src));
+ options.recursive = false;
+ } else if (grunt.file.isMatch('**/yui/src', src)) {
+ // When in a src directory --walk all modules.
grunt.log.debug('In a src directory');
args.push('--walk');
- options.walk = true;
- } else if (path.basename(path.dirname(cwd)) === 'src') {
- // Detect whether we're in a module directory.
+ options.recursive = false;
+ } else if (grunt.file.isMatch('**/yui/src/*', src)) {
+ // When in module, only build our module.
grunt.log.debug('In a module directory');
- options.module = true;
- }
-
- if (grunt.option('watch')) {
- if (!options.walk && !options.module) {
- grunt.fail.fatal('Unable to watch unless in a src or module directory');
- }
-
- // It is not advisable to run with recursivity and watch - this
- // leads to building the build directory in a race-like fashion.
- grunt.log.debug('Detected a watch - disabling recursivity');
options.recursive = false;
- args.push('--watch');
+ } else if (grunt.file.isMatch('**/yui/src/*/js', src)) {
+ // When in module src, only build our module.
+ grunt.log.debug('In a source directory');
+ src = path.dirname(src);
+ options.recursive = false;
}
- if (options.recursive) {
- args.push('--recursive');
+ if (grunt.option('watch')) {
+ grunt.fail.fatal('The --watch option has been removed, please use `grunt watch` instead');
}
- // Always ignore the node_modules directory.
- args.push('--excludes', 'node_modules');
-
// Add the stderr option if appropriate
if (grunt.option('verbose')) {
args.push('--lint-stderr');
var execShifter = function() {
- shifter = exec("node", args, {
- cwd: cwd,
- stdio: 'inherit',
- env: process.env
- });
-
- // Tidy up after exec.
- shifter.on('exit', function (code) {
+ grunt.log.ok("Running shifter on " + src);
+ grunt.util.spawn({
+ cmd: "node",
+ args: args,
+ opts: {cwd: src, stdio: 'inherit', env: process.env}
+ }, function (error, result, code) {
if (code) {
grunt.fail.fatal('Shifter failed with code: ' + code);
} else {
grunt.log.ok('Shifter build complete.');
- done();
+ filedone();
}
});
};
execShifter();
} else {
// Check that there are yui modules otherwise shifter ends with exit code 1.
- var found = false;
- var hasYuiModules = function(directory, callback) {
- fs.readdir(directory, function(err, files) {
- if (err) {
- return callback(err, null);
- }
-
- // If we already found a match there is no need to continue scanning.
- if (found === true) {
- return;
- }
-
- // We need to track the number of files to know when we return a result.
- var pending = files.length;
-
- // We first check files, so if there is a match we don't need further
- // async calls and we just return a true.
- for (var i = 0; i < files.length; i++) {
- if (files[i] === 'yui') {
- return callback(null, true);
- }
- }
-
- // Iterate through subdirs if there were no matches.
- files.forEach(function (file) {
-
- var p = path.join(directory, file);
- stat = fs.statSync(p);
- if (!stat.isDirectory()) {
- pending--;
- } else {
-
- // We defer the pending-1 until we scan the whole dir and subdirs.
- hasYuiModules(p, function(err, result) {
- if (err) {
- return callback(err);
- }
-
- if (result === true) {
- // Once we get a true we notify the caller.
- found = true;
- return callback(null, true);
- }
-
- pending--;
- if (pending === 0) {
- // Notify the caller that the whole dir has been scaned and there are no matches.
- return callback(null, false);
- }
- });
- }
-
- // No subdirs here, otherwise the return would be deferred until all subdirs are scanned.
- if (pending === 0) {
- return callback(null, false);
- }
- });
- });
- };
-
- hasYuiModules(cwd, function(err, result) {
- if (err) {
- grunt.fail.fatal(err.message);
- }
-
- if (result === true) {
- execShifter();
- } else {
- grunt.log.ok('No YUI modules to build.');
- done();
- }
- });
+ if (grunt.file.expand({cwd: src}, '**/yui/src/**/*.js').length > 0) {
+ args.push('--recursive');
+ execShifter();
+ } else {
+ grunt.log.ok('No YUI modules to build.');
+ filedone();
+ }
}
+ }, done);
};
tasks.startup = function() {
}
};
+ // On watch, we dynamically modify config to build only affected files. This
+ // method is slightly complicated to deal with multiple changed files at once (copied
+ // from the grunt-contrib-watch readme).
+ var changedFiles = Object.create(null);
+ var onChange = grunt.util._.debounce(function() {
+ var files = Object.keys(changedFiles);
+ grunt.config('jshint.amd.src', files);
+ grunt.config('uglify.amd.files', [{ expand: true, src: files, rename: uglify_rename }]);
+ grunt.config('shifter.options.paths', files);
+ changedFiles = Object.create(null);
+ }, 200);
+
+ grunt.event.on('watch', function(action, filepath) {
+ changedFiles[filepath] = action;
+ onChange();
+ });
// Register NPM tasks.
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-less');
+ grunt.loadNpmTasks('grunt-contrib-watch');
// Register JS tasks.
grunt.registerTask('shifter', 'Run Shifter against the current directory', tasks.shifter);
}
// Hack prefix to objectclass
- if (empty($this->config->objectclass)) {
- // Can't send empty filter
- $this->config->objectclass = '(objectClass=*)';
- } else if (stripos($this->config->objectclass, 'objectClass=') === 0) {
- // Value is 'objectClass=some-string-here', so just add ()
- // around the value (filter _must_ have them).
- $this->config->objectclass = '('.$this->config->objectclass.')';
- } else if (strpos($this->config->objectclass, '(') !== 0) {
- // Value is 'some-string-not-starting-with-left-parentheses',
- // which is assumed to be the objectClass matching value.
- // So build a valid filter with it.
- $this->config->objectclass = '(objectClass='.$this->config->objectclass.')';
- } else {
- // There is an additional possible value
- // '(some-string-here)', that can be used to specify any
- // valid filter string, to select subsets of users based
- // on any criteria. For example, we could select the users
- // whose objectClass is 'user' and have the
- // 'enabledMoodleUser' attribute, with something like:
- //
- // (&(objectClass=user)(enabledMoodleUser=1))
- //
- // In this particular case we don't need to do anything,
- // so leave $this->config->objectclass as is.
- }
+ $this->config->objectclass = ldap_normalise_objectclass($this->config->objectclass);
}
/**
$url = new moodle_url('/auth/mnet/jump.php', array('token'=>$token, 'idp'=>$remotewwwroot, 'wantsurl'=>$wantsurl));
if ($wantsremoteurl !== false) $url->param('remoteurl', $wantsremoteurl);
$PAGE->set_url($url);
+$PAGE->set_context(context_system::instance());
$site = get_site();
}
// Get HTML from logger.
- $loghtml = $logger->get_html();
+ if ($CFG->debugdisplay) {
+ $loghtml = $logger->get_html();
+ }
// Hide the progress display and first backup step bar (the 'finished' step will show next).
echo html_writer::end_div();
// Do actual restore.
$restore->execute();
// Get HTML from logger.
- $loghtml = $logger->get_html();
+ if ($CFG->debugdisplay) {
+ $loghtml = $logger->get_html();
+ }
// Hide this section because we are now going to make the page show 'finished'.
echo html_writer::end_div();
echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
When I follow "Test assignment"
And I click on "Edit settings" "link" in the "Administration" "block"
And I set the following fields to these values:
- | id_modgrade_type | None |
+ | id_grade_modgrade_type | None |
And I press "Save and return to course"
Then I should see "Error: the activity selected uses a grading method that is not supported by this block." in the "Activity results" "block"
| Assignment name | Test assignment |
| Description | Offline text |
| assignsubmission_file_enabled | 0 |
- | id_modgrade_type | Scale |
- | id_modgrade_scale | My Scale |
+ | id_grade_modgrade_type | Scale |
+ | id_grade_modgrade_scale | My Scale |
And I follow "Course 1"
And I navigate to "Grades" node in "Course administration"
And I turn editing mode on
| Assignment name | Test assignment |
| Description | Offline text |
| assignsubmission_file_enabled | 0 |
- | id_modgrade_type | Scale |
- | id_modgrade_scale | My Scale |
+ | id_grade_modgrade_type | Scale |
+ | id_grade_modgrade_scale | My Scale |
| Group mode | Separate groups |
And I follow "Course 1"
And I navigate to "Grades" node in "Course administration"
| Assignment name | Test assignment |
| Description | Offline text |
| assignsubmission_file_enabled | 0 |
- | id_modgrade_type | Scale |
- | id_modgrade_scale | My Scale |
+ | id_grade_modgrade_type | Scale |
+ | id_grade_modgrade_scale | My Scale |
And I follow "Course 1"
And I navigate to "Grades" node in "Course administration"
And I turn editing mode on
| Assignment name | Test assignment |
| Description | Offline text |
| assignsubmission_file_enabled | 0 |
- | id_modgrade_type | Scale |
- | id_modgrade_scale | My Scale |
+ | id_grade_modgrade_type | Scale |
+ | id_grade_modgrade_scale | My Scale |
| Group mode | Separate groups |
And I follow "Course 1"
And I navigate to "Grades" node in "Course administration"
$type = " AND (p.publishstate = 'site' or p.publishstate='public')";
}
- $sql = "SELECT t.id, t.tagtype, t.rawname, t.name, COUNT(DISTINCT ti.id) AS ct
+ $sql = "SELECT t.id, t.isstandard, t.rawname, t.name, COUNT(DISTINCT ti.id) AS ct
FROM {tag} t, {tag_instance} ti, {post} p, {blog_association} ba
WHERE t.id = ti.tagid AND p.id = ti.itemid
$type
}
$sql .= "
- GROUP BY t.id, t.tagtype, t.name, t.rawname
+ GROUP BY t.id, t.isstandard, t.name, t.rawname
ORDER BY ct DESC, t.name ASC";
if ($tags = $DB->get_records_sql($sql, null, 0, $this->config->numberoftags)) {
$size = 20 - ( (int)((($currenttag - 1) / $totaltags) * 20) );
}
- $tag->class = "$tag->tagtype s$size";
+ $tag->class = ($tag->isstandard ? "standardtag " : "") . "s$size";
$etags[] = $tag;
}
| fullname | shortname |
| Course 1 | c1 |
And the following "tags" exist:
- | name | tagtype |
- | Neverusedtag | official |
+ | name | isstandard |
+ | Neverusedtag | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | c1 | editingteacher |
if (!empty($config->glossary)) {
// Get glossary mapping and replace it in config
if ($glossarymap = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'glossary', $config->glossary)) {
- $config->glossary = $glossarymap->newitemid;
+ $mappedglossary = $DB->get_record('glossary', array('id' => $glossarymap->newitemid),
+ 'id,course,globalglossary', MUST_EXIST);
+ $config->glossary = $mappedglossary->id;
+ $config->courseid = $mappedglossary->course;
+ $config->globalglossary = $mappedglossary->globalglossary;
$configdata = base64_encode(serialize($config));
$DB->set_field('block_instances', 'configdata', $configdata, array('id' => $blockid));
+ } else {
+ // The block refers to a glossary not present in the backup file.
+ $DB->set_field('block_instances', 'configdata', '', array('id' => $blockid));
}
}
}
$this->config->numberoftags = 80;
}
- if (empty($this->config->tagtype)) {
- $this->config->tagtype = '';
+ if (empty($this->config->showstandard)) {
+ $this->config->showstandard = core_tag_tag::BOTH_STANDARD_AND_NOT;
}
if (empty($this->config->ctx)) {
// Get a list of tags.
$tagcloud = core_tag_collection::get_tag_cloud($this->config->tagcoll,
- $this->config->tagtype,
+ $this->config->showstandard == core_tag_tag::STANDARD_ONLY,
$this->config->numberoftags,
'name', '', $this->page->context->id, $this->config->ctx, $this->config->rec);
$this->content->text = $OUTPUT->render_from_template('core_tag/tagcloud', $tagcloud->export_for_template($OUTPUT));
$mform->setDefault('config_numberoftags', 80);
$defaults = array(
- 'official' => get_string('officialonly', 'block_tags'),
- '' => get_string('anytype', 'block_tags'));
- $mform->addElement('select', 'config_tagtype', get_string('defaultdisplay', 'block_tags'), $defaults);
- $mform->setDefault('config_tagtype', '');
+ core_tag_tag::STANDARD_ONLY => get_string('standardonly', 'block_tags'),
+ core_tag_tag::BOTH_STANDARD_AND_NOT => get_string('anytype', 'block_tags'));
+ $mform->addElement('select', 'config_showstandard', get_string('defaultdisplay', 'block_tags'), $defaults);
+ $mform->setDefault('config_showstandard', core_tag_tag::BOTH_STANDARD_AND_NOT);
$defaults = array(0 => context_system::instance()->get_context_name());
$parentcontext = context::instance_by_id($this->block->instance->parentcontextid);
$string['anytype'] = 'All';
$string['configtitle'] = 'Block title';
$string['disabledtags'] = 'Tags are disabled';
-$string['defaultdisplay'] = 'Tag type to display';
-$string['officialonly'] = 'Only official';
+$string['defaultdisplay'] = 'Display tags';
$string['pluginname'] = 'Tags';
$string['recursivecontext'] = 'Include child contexts';
$string['recursivecontext_help'] = 'If unchecked, tags of items in the context specified above will be displayed excluding underlying contexts, for example, you can search on course level only without searching inside course activities';
+$string['standardonly'] = 'Only standard';
$string['tagcollection'] = 'Tag collection';
$string['tagcollection_help'] = 'Select tag collection to display tags from. If you choose "Any" '
. 'the tags from all collections except for those marked with * will be displayed';
| fullname | shortname |
| Course 1 | c1 |
And the following "tags" exist:
- | name | tagtype |
- | Neverusedtag | official |
+ | name | isstandard |
+ | Neverusedtag | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | c1 | editingteacher |
// Create default tag.
$tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
- 'rawname' => 'Testtagname', 'tagtype' => 'official'));
+ 'rawname' => 'Testtagname', 'isstandard' => 1));
// Create default post.
$post = new stdClass();
| Course 1 | c1 |
| Course 2 | c2 |
And the following "tags" exist:
- | name | tagtype |
- | Neverusedtag | official |
+ | name | isstandard |
+ | Neverusedtag | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | c1 | editingteacher |
protected $enroltype = 'enrol_ldap';
protected $errorlogtag = '[ENROL LDAP] ';
+ /**
+ * The object class to use when finding users.
+ *
+ * @var string $userobjectclass
+ */
+ protected $userobjectclass;
+
/**
* Constructor for the plugin. In addition to calling the parent
* constructor, we define and 'fix' some settings depending on the
unset($ldap_usertypes);
$default = ldap_getdefaults();
- // Remove the objectclass default, as the values specified there are for
- // users, and we are dealing with groups here.
+
+ // The objectclass in the defaults is for a user.
+ // This will be required later, but enrol_ldap uses 'objectclass' for its group objectclass.
+ // Save the normalised user objectclass for later.
+ $this->userobjectclass = ldap_normalise_objectclass($default['objectclass'][$this->get_config('user_type')]);
+
+ // Remove the objectclass default, as the values specified there are for users, and we are dealing with groups here.
unset($default['objectclass']);
// Use defaults if values not given. Dont use this->get_config()
}
}
+ // Normalise the objectclass used for groups.
if (empty($this->config->objectclass)) {
- // Can't send empty filter. Fix it for now and future occasions
- $this->set_config('objectclass', '(objectClass=*)');
- } else if (stripos($this->config->objectclass, 'objectClass=') === 0) {
- // Value is 'objectClass=some-string-here', so just add ()
- // around the value (filter _must_ have them).
- // Fix it for now and future occasions
- $this->set_config('objectclass', '('.$this->config->objectclass.')');
- } else if (stripos($this->config->objectclass, '(') !== 0) {
- // Value is 'some-string-not-starting-with-left-parentheses',
- // which is assumed to be the objectClass matching value.
- // So build a valid filter with it.
- $this->set_config('objectclass', '(objectClass='.$this->config->objectclass.')');
+ // No objectclass set yet - set a default class.
+ $this->config->objectclass = ldap_normalise_objectclass(null, '*');
+ $this->set_config('objectclass', $this->config->objectclass);
} else {
- // There is an additional possible value
- // '(some-string-here)', that can be used to specify any
- // valid filter string, to select subsets of users based
- // on any criteria. For example, we could select the users
- // whose objectClass is 'user' and have the
- // 'enabledMoodleUser' attribute, with something like:
- //
- // (&(objectClass=user)(enabledMoodleUser=1))
- //
- // In this particular case we don't need to do anything,
- // so leave $this->config->objectclass as is.
+ $objectclass = ldap_normalise_objectclass($this->config->objectclass);
+ if ($objectclass !== $this->config->objectclass) {
+ // The objectclass was changed during normalisation.
+ // Save it in config, and update the local copy of config.
+ $this->set_config('objectclass', $objectclass);
+ $this->config->objectclass = $objectclass;
+ }
}
}
// as the idnumber does not match their dn and we get dn's from membership.
$memberidnumbers = array();
foreach ($ldapmembers as $ldapmember) {
- $result = ldap_read($this->ldapconnection, $ldapmember, '(objectClass=*)',
+ $result = ldap_read($this->ldapconnection, $ldapmember, $this->userobjectclass,
array($this->config->idnumber_attribute));
$entry = ldap_first_entry($this->ldapconnection, $result);
$values = ldap_get_values($this->ldapconnection, $entry, $this->config->idnumber_attribute);
require_once($CFG->libdir.'/ldaplib.php');
$ldap_contexts = explode(';', $this->get_config('user_contexts'));
- $ldap_defaults = ldap_getdefaults();
return ldap_find_userdn($this->ldapconnection, $userid, $ldap_contexts,
- '(objectClass='.$ldap_defaults['objectclass'][$this->get_config('user_type')].')',
+ $this->userobjectclass,
$this->get_config('idnumber_attribute'), $this->get_config('user_search_sub'));
}
}
}
}
+
+ /**
+ * Test that normalisation of the use objectclass is completed successfully.
+ *
+ * @dataProvider objectclass_fetch_provider
+ * @param string $usertype The supported user type
+ * @param string $expected The expected filter value
+ */
+ public function test_objectclass_fetch($usertype, $expected) {
+ $this->resetAfterTest();
+ // Set the user type - this must be performed before the plugin is instantiated.
+ set_config('user_type', $usertype, 'enrol_ldap');
+
+ // Fetch the plugin.
+ $instance = enrol_get_plugin('ldap');
+
+ // Use reflection to sneak a look at the plugin.
+ $rc = new ReflectionClass('enrol_ldap_plugin');
+ $rcp = $rc->getProperty('userobjectclass');
+ $rcp->setAccessible(true);
+
+ // Fetch the current userobjectclass value.
+ $value = $rcp->getValue($instance);
+ $this->assertEquals($expected, $value);
+ }
+
+ /**
+ * Data provider for the test_objectclass_fetch testcase.
+ *
+ * @return array of testcases.
+ */
+ public function objectclass_fetch_provider() {
+ return array(
+ // This is the list of values from ldap_getdefaults() normalised.
+ 'edir' => array(
+ 'edir',
+ '(objectClass=user)'
+ ),
+ 'rfc2307' => array(
+ 'rfc2307',
+ '(objectClass=posixaccount)'
+ ),
+ 'rfc2307bis' => array(
+ 'rfc2307bis',
+ '(objectClass=posixaccount)'
+ ),
+ 'samba' => array(
+ 'samba',
+ '(objectClass=sambasamaccount)'
+ ),
+ 'ad' => array(
+ 'ad',
+ '(samaccounttype=805306368)'
+ ),
+ 'default' => array(
+ 'default',
+ '(objectClass=*)'
+ ),
+ );
+ }
}
alertpanelid,
definition,
position;
- var self = this;
try {
data = Y.JSON.parse(content);
if (data.success){
// Register alertpanel for stacking.
alertpanelid = '#moodle-dialogue-' + alertpanel.get('COUNT');
- alertpanel.on('complete', this._deletealertpanel(self.alertpanels, alertpanelid));
+ alertpanel.on('complete', this._deletealertpanel, this, alertpanelid);
// We already have some windows opened, so set the right position...
if (!Y.Object.isEmpty(this.alertpanels)){
});
return lastPosition;
},
- _deletealertpanel : function(alertpanels, alertpanelid) {
- delete alertpanels[alertpanelid];
+ _deletealertpanel : function(ev, alertpanelid) {
+ delete this.alertpanels[alertpanelid];
}
}, {
NAME : AUTOLINKERNAME,
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package installer
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['parentlanguage'] = 'dk';
+$string['thislanguage'] = 'Dansk (kursus)';
tag:create,core_role
categoriesanditems,core_grades
taggedwith,core_tag
+officialtag,core_tag
+otags,core_tag
+othertags,core_tag
+tagtype,core_tag
+manageofficialtags,core_tag
+settypeofficial,core_tag
$string['blog:create'] = 'Create new blog entries';
$string['blog:manageentries'] = 'Edit and manage entries';
$string['blog:manageexternal'] = 'Edit and manage external blogs';
-$string['blog:manageofficialtags'] = 'Manage official tags';
-$string['blog:managepersonaltags'] = 'Manage personal tags';
$string['blog:search'] = 'Search blog entries';
$string['blog:view'] = 'View blog entries';
$string['blog:viewdrafts'] = 'View draft blog entries';
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-$string['added'] = 'Official tag(s) added';
-$string['addotags'] = 'Add official tags';
+$string['added'] = 'Standard tag(s) added';
+$string['addotags'] = 'Add standard tags';
$string['addtagcoll'] = 'Add tag collection';
$string['addtagtomyinterests'] = 'Add "{$a}" to my interests';
$string['alltagpages'] = 'All tag pages';
$string['backtoallitems'] = 'Back to all items tagged with "{$a}"';
+$string['changeshowstandard'] = 'Change showing standard tags in area {$a}';
$string['changessaved'] = 'Changes saved';
$string['changetagcoll'] = 'Change tag collection of area {$a}';
$string['collnameexplained'] = 'Leave the field empty to use the default value: {$a}';
$string['inalltagcoll'] = 'Everywhere';
$string['itemstaggedwith'] = '{$a->tagarea} tagged with "{$a->tag}"';
$string['lesstags'] = 'less...';
-$string['manageofficialtags'] = 'Manage official tags';
+$string['managestandardtags'] = 'Manage standard tags';
$string['managetags'] = 'Manage tags';
$string['managetagcolls'] = 'Manage tag collections';
$string['moretags'] = 'more...';
$string['notagsfound'] = 'No tags matching "{$a}" found';
$string['noresultsfor'] = 'No results for "{$a}"';
$string['nothingtoupdate'] = 'Nothing to update';
-$string['officialtag'] = 'Official';
-$string['otags'] = 'Official tags';
-$string['othertags'] = 'Other tags';
$string['owner'] = 'Owner';
$string['prevpage'] = 'Back';
$string['ptags'] = 'User defined tags (Comma separated)';
$string['responsiblewillbenotified'] = 'The person responsible will be notified';
$string['rssdesc'] = 'This RSS feed was automatically generated by Moodle and contains user generated tags for courses.';
$string['rsstitle'] = 'Course tags RSS feed for user: {$a}';
+$string['showstandard'] = 'Standard tags usage';
+$string['showstandard_help'] = 'This controls how area handles standard tags. When user edits the item the standard tags may be suggested or not, it is also possible to force area to only use standard tags and not allow user to type new ones.';
$string['search'] = 'Search';
$string['searchable'] = 'Searchable';
$string['searchable_help'] = 'Tags in this tag collection can be searched for on "Search tags" page. If unchecked, tags can still be accessed by clicking on them or via different search interfaces.';
$string['select'] = 'Select';
$string['selectcoll'] = 'Select tag collection';
$string['selecttag'] = 'Select tag {$a}';
-$string['settypedefault'] = 'Remove from official tags';
-$string['settypeofficial'] = 'Make official';
+$string['settypedefault'] = 'Remove from standard tags';
+$string['settypestandard'] = 'Make standard';
$string['showingfirsttags'] = 'Showing {$a} most popular tags';
+$string['standardforce'] = 'Force';
+$string['standardhide'] = 'Don\'t suggest';
+$string['standardsuggest'] = 'Suggest';
+$string['standardtag'] = 'Standard';
$string['suredeletecoll'] = 'Are you sure you want to delete tag collection "{$a}"?';
$string['tag'] = 'Tag';
$string['tagarea_blog_external'] = 'External blog posts';
$string['tagareaname'] = 'Name';
$string['tagareas'] = 'Tag areas';
$string['tagcollection'] = 'Tag collection';
+$string['tagcollection_help'] = 'Tag collections are sets of tags for different areas. For example, a collection of standard tags can be used to tag courses, with user interests and blog post tags kept in a separate collection. When a user clicks on a tag, the tag page displays only items with that tag in the same collection. Tags can be automatically added to a collection according to the area tagged or can be added manually as standard tags.';
$string['tagcollections'] = 'Tag collections';
$string['tagdescription'] = 'Tag description';
$string['tags'] = 'Tags';
$string['tagsaredisabled'] = 'Tags are disabled';
-$string['tagtype'] = 'Tag type';
$string['thingstaggedwith'] = '"{$a->name}" is used {$a->count} times';
$string['thingtaggedwith'] = '"{$a->name}" is used once';
$string['timemodified'] = 'Modified';
// Deprecated since 3.1 .
+$string['manageofficialtags'] = 'Manage official tags';
+$string['officialtag'] = 'Official';
+$string['otags'] = 'Official tags';
+$string['othertags'] = 'Other tags';
+$string['settypeofficial'] = 'Make official';
$string['taggedwith'] = 'tagged with "{$a}"';
+$string['tagtype'] = 'Tag type';
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * A way to call HTML fragments to be inserted as required via JavaScript.
+ *
+ * @module core/fragment
+ * @class fragment
+ * @package core
+ * @copyright 2016 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since 3.1
+ */
+define(['jquery', 'core/ajax', 'core/notification'], function($, ajax, notification) {
+
+ /**
+ * Loads an HTML fragment through a callback.
+ *
+ * @method loadFragment
+ * @param {string} component Component where callback is located.
+ * @param {string} callback Callback function name.
+ * @param {integer} contextid Context ID of the fragment.
+ * @param {object} params Parameters for the callback.
+ * @return {Promise} JQuery promise object resolved when the fragment has been loaded.
+ */
+ var loadFragment = function(component, callback, contextid, params) {
+ // Change params into required webservice format.
+ var formattedparams = [];
+ for (var index in params) {
+ formattedparams.push({name: index, value: params[index]});
+ }
+
+ // Ajax stuff.
+ var deferred = $.Deferred();
+
+ var promises = ajax.call([{
+ methodname: 'core_get_fragment',
+ args:{
+ component: component,
+ callback: callback,
+ contextid: contextid,
+ args: formattedparams
+ }
+ }], false);
+
+ promises[0].done(function(data) {
+ deferred.resolve(data);
+ }).fail(function(ex) {
+ deferred.reject(ex);
+ });
+ return deferred.promise();
+ };
+
+ /**
+ * Removes and cleans children of a node. This includes event handlers and listeners that may be
+ * attached to the nodes for both jquery and yui.
+ *
+ * @method recursiveCleanup
+ * @param {object} DOM node to be cleaned.
+ * @return {void}
+ */
+ var recursiveCleanup = function(node) {
+ node.children().each(function(index, el) {
+ var child = $(el);
+ recursiveCleanup(child);
+ });
+ var yuinode = new Y.Node(node);
+ node.empty();
+ node.remove();
+ yuinode.detachAll();
+ if (yuinode.get('childNodes')) {
+ yuinode.empty();
+ }
+ yuinode.remove(true);
+ };
+
+ return /** @alias module:core/fragment */{
+ /**
+ * Appends HTML and JavaScript fragments to specified nodes.
+ * Callbacks called by this AMD module are responsible for doing the appropriate security checks
+ * to access the information that is returned. This only does minimal validation on the context.
+ *
+ * @method fragmentAppend
+ * @param {string} component Component where callback is located.
+ * @param {string} callback Callback function name.
+ * @param {integer} contextid Context ID of the fragment.
+ * @param {object} params Parameters for the callback.
+ * @param {string} htmlnodeidentifier The 'class' or 'id' to attach the HTML.
+ * @param {string} javascriptnodeidentifier The 'class' or 'id' to attach the JavaScript.
+ * @return {void}
+ */
+ fragmentAppend: function(component, callback, contextid, params, htmlnodeidentifier, javascriptnodeidentifier) {
+ $.when(loadFragment(component, callback, contextid, params)).then(function(data) {
+ // Clean up previous code if found first.
+ recursiveCleanup($('#fragment-html'));
+ recursiveCleanup($('#fragment-scripts'));
+ // Attach new HTML and JavaScript.
+ $(htmlnodeidentifier).append('<div id="fragment-html">' + data.html + '</div>');
+ $(javascriptnodeidentifier).append('<div id="fragment-scripts">' + data.javascript + '</div>');
+
+ }).fail(function(ex) {
+ notification.exception(ex);
+ });
+ }
+ };
+});
};
// Click handler for changing tag type.
- $('.tag-management-table').delegate('.tagtype', 'click', function(e) {
+ $('.tag-management-table').delegate('.tagisstandard', 'click', function(e) {
e.preventDefault();
var target = $( this ),
tagid = target.attr('data-id'),
currentvalue = target.attr('data-value'),
- official = (currentvalue === "1") ? 0 : 1;
+ isstandard = (currentvalue === "1") ? 0 : 1;
var promises = ajax.call([{
methodname: 'core_tag_update_tags',
- args: { tags : [ { id : tagid , official : official } ] }
+ args: { tags : [ { id : tagid , isstandard : isstandard } ] }
}, {
methodname: 'core_tag_get_tags',
args: { tags : [ { id : tagid } ] }
$.when.apply($, promises)
.done( function(updateresult, data) {
if (updateresult.warnings[0] === undefined && data.tags[0] !== undefined) {
- templates.render('core_tag/tagtype', data.tags[0]).done(function(html) {
+ templates.render('core_tag/tagisstandard', data.tags[0]).done(function(html) {
update_modified(target);
var parent = target.parent();
target.replaceWith(html);
- parent.find('.tagtype').get(0).focus();
+ parent.find('.tagisstandard').get(0).focus();
});
}
});
/**
* The JS code to check that the page is ready.
*/
- const PAGE_READY_JS = '(M && M.util && M.util.pending_js && !Boolean(M.util.pending_js.length)) && (document.readyState === "complete")';
+ const PAGE_READY_JS = '(typeof M !== "undefined" && M.util && M.util.pending_js && !Boolean(M.util.pending_js.length)) && (document.readyState === "complete")';
/**
* Locates url, based on provided path.
if (isguestuser($user) or is_siteadmin($user)) {
continue;
}
+ if ($user->lastname !== '' and $user->firstname !== '' and $user->email !== '') {
+ // This can happen on MySQL - see MDL-52831.
+ continue;
+ }
delete_user($user);
mtrace(" Deleted not fully setup user $user->username ($user->id)");
}
<?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20160111" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20160202" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="itemtype" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
- <FIELD NAME="enabled" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
+ <FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="tagcollid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="callback" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="callbackfile" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="showstandard" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<FIELD NAME="tagcollid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="rawname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The raw, unnormalised name for the tag as entered by users"/>
- <FIELD NAME="tagtype" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="isstandard" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether this tag is standard"/>
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="flag" TYPE="int" LENGTH="4" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="a tag can be 'flagged' as inappropriate"/>
</KEYS>
<INDEXES>
<INDEX NAME="tagcollname" UNIQUE="true" FIELDS="tagcollid, name"/>
- <INDEX NAME="tagcolltype" UNIQUE="false" FIELDS="tagcollid, tagtype"/>
+ <INDEX NAME="tagcolltype" UNIQUE="false" FIELDS="tagcollid, isstandard"/>
</INDEXES>
</TABLE>
<TABLE NAME="tag_correlation" COMMENT="The rationale for the 'tag_correlation' table is performance. It works as a cache for a potentially heavy load query done at the 'tag_instance' table. So, the 'tag_correlation' table stores redundant information derived from the 'tag_instance' table">
'ajax' => true,
),
+ 'core_get_fragment' => array(
+ 'classname' => 'core_external',
+ 'methodname' => 'get_fragment',
+ 'classpath' => 'lib/external/externallib.php',
+ 'description' => 'Return a fragment for inclusion, such as a JavaScript page.',
+ 'type' => 'read',
+ 'ajax' => true,
+ ),
+
// === Calendar related functions ===
* any other tag areas to this collection nor move this tag area elsewhere
* - searchable (only if collection is specified) - wether the tag collection
* should be searchable on /tag/search.php
+ * - showstandard - default value for the "Standard tags" attribute of the area,
+ * this is only respected when new tag area is added and ignored during upgrade
* - customurl (only if collection is specified) - custom url to use instead of
* /tag/search.php to display information about one tag
* - callback - name of the function that returns items tagged with this tag,
'component' => 'core',
'callback' => 'user_get_tagged_users',
'callbackfile' => '/user/lib.php',
+ 'showstandard' => core_tag_tag::HIDE_STANDARD,
),
array(
'itemtype' => 'course', // Courses.
upgrade_main_savepoint(true, 2016011901.00);
}
+
+ if ($oldversion < 2016020200.00) {
+
+ // Define field isstandard to be added to tag.
+ $table = new xmldb_table('tag');
+ $field = new xmldb_field('isstandard', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'rawname');
+
+ // Conditionally launch add field isstandard.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Define index tagcolltype (not unique) to be dropped form tag.
+ $index = new xmldb_index('tagcolltype', XMLDB_INDEX_NOTUNIQUE, array('tagcollid', 'tagtype'));
+
+ // Conditionally launch drop index tagcolltype.
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+
+ // Define index tagcolltype (not unique) to be added to tag.
+ $index = new xmldb_index('tagcolltype', XMLDB_INDEX_NOTUNIQUE, array('tagcollid', 'isstandard'));
+
+ // Conditionally launch add index tagcolltype.
+ if (!$dbman->index_exists($table, $index)) {
+ $dbman->add_index($table, $index);
+ }
+
+ // Define field tagtype to be dropped from tag.
+ $field = new xmldb_field('tagtype');
+
+ // Conditionally launch drop field tagtype and update isstandard.
+ if ($dbman->field_exists($table, $field)) {
+ $DB->execute("UPDATE {tag} SET isstandard=(CASE WHEN (tagtype = ?) THEN 1 ELSE 0 END)", array('official'));
+ $dbman->drop_field($table, $field);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016020200.00);
+ }
+
+ if ($oldversion < 2016020201.00) {
+
+ // Define field showstandard to be added to tag_area.
+ $table = new xmldb_table('tag_area');
+ $field = new xmldb_field('showstandard', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'callbackfile');
+
+ // Conditionally launch add field showstandard.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // By default set user area to hide standard tags. 2 = core_tag_tag::HIDE_STANDARD (can not use constant here).
+ $DB->execute("UPDATE {tag_area} SET showstandard = ? WHERE itemtype = ? AND component = ?",
+ array(2, 'user', 'core'));
+
+ // Changing precision of field enabled on table tag_area to (1).
+ $table = new xmldb_table('tag_area');
+ $field = new xmldb_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'itemtype');
+
+ // Launch change of precision for field enabled.
+ $dbman->change_field_precision($table, $field);
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016020201.00);
+ }
+
return true;
}
// get tags from the db ordered by highest count first
$params = array();
- $sql = "SELECT id as tkey, name, id, tagtype, rawname, f.timemodified, flag, count
+ $sql = "SELECT id as tkey, name, id, isstandard, rawname, f.timemodified, flag, count
FROM {tag} t,
(SELECT tagid, MAX(timemodified) as timemodified, COUNT(id) as count
FROM {tag_instance}
$sql .= " GROUP BY tagid) f
WHERE t.id = f.tagid ";
if ($tagtype != '') {
- $sql .= "AND tagtype = :tagtype ";
- $params['tagtype'] = $tagtype;
+ $sql .= "AND isstandard = :isstandard ";
+ $params['isstandard'] = ($tagtype === 'official') ? 1 : 0;
}
$sql .= "ORDER BY count DESC, name ASC";
global $CFG, $DB;
// note that this selects all tags except for courses that are not visible
- $sql = "SELECT id, name, tagtype, rawname, f.timemodified, flag, count
+ $sql = "SELECT id, name, isstandard, rawname, f.timemodified, flag, count
FROM {tag} t,
(SELECT tagid, MAX(timemodified) as timemodified, COUNT(id) as count
FROM {tag_instance} WHERE tagid NOT IN
function tag_type_set($tagid, $type) {
debugging('Function tag_type_set() is deprecated and can be replaced with use core_tag_tag::get($tagid)->update().', DEBUG_DEVELOPER);
if ($tag = core_tag_tag::get($tagid, '*')) {
- return $tag->update(array('tagtype' => $type));
+ return $tag->update(array('isstandard' => ($type === 'official') ? 1 : 0));
}
return false;
}
function tag_get_tags($record_type, $record_id, $type=null, $userid=0) {
debugging('Method tag_get_tags() is deprecated and replaced with core_tag_tag::get_item_tags(). ' .
'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
- $official = ($type === 'official' ? true : (!empty($type) ? false : null));
- $tags = core_tag_tag::get_item_tags(null, $record_type, $record_id, $official, $userid);
+ $standardonly = ($type === 'official' ? core_tag_tag::STANDARD_ONLY :
+ (!empty($type) ? core_tag_tag::NOT_STANDARD_ONLY : core_tag_tag::BOTH_STANDARD_AND_NOT));
+ $tags = core_tag_tag::get_item_tags(null, $record_type, $record_id, $standardonly, $userid);
$rv = array();
foreach ($tags as $id => $t) {
$rv[$id] = $t->to_object();
function tag_get_tags_array($record_type, $record_id, $type=null) {
debugging('Method tag_get_tags_array() is deprecated and replaced with core_tag_tag::get_item_tags_array(). ' .
'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
- $official = ($type === 'official' ? true : (!empty($type) ? false : null));
- return core_tag_tag::get_item_tags_array('', $record_type, $record_id, $official);
+ $standardonly = ($type === 'official' ? core_tag_tag::STANDARD_ONLY :
+ (!empty($type) ? core_tag_tag::NOT_STANDARD_ONLY : core_tag_tag::BOTH_STANDARD_AND_NOT));
+ return core_tag_tag::get_item_tags_array('', $record_type, $record_id, $standardonly);
}
/**
debugging('Method tag_get_tags_csv() is deprecated. Instead you should use either ' .
'core_tag_tag::get_item_tags_array() or $OUTPUT->tag_list(core_tag_tag::get_item_tags()). ' .
'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
- $official = ($type === 'official' ? true : (!empty($type) ? false : null));
+ $standardonly = ($type === 'official' ? core_tag_tag::STANDARD_ONLY :
+ (!empty($type) ? core_tag_tag::NOT_STANDARD_ONLY : core_tag_tag::BOTH_STANDARD_AND_NOT));
if ($html != TAG_RETURN_TEXT) {
- return $OUTPUT->tag_list(core_tag_tag::get_item_tags('', $record_type, $record_id, $official), '');
+ return $OUTPUT->tag_list(core_tag_tag::get_item_tags('', $record_type, $record_id, $standardonly), '');
} else {
- return join(', ', core_tag_tag::get_item_tags_array('', $record_type, $record_id, $official, 0, false));
+ return join(', ', core_tag_tag::get_item_tags_array('', $record_type, $record_id, $standardonly, 0, false));
}
}
if (!is_array($tags)) {
$tags = array($tags);
}
- $objects = core_tag_tag::create_if_missing(core_tag_collection::get_default(), $tags, $type === 'official');
+ $objects = core_tag_tag::create_if_missing(core_tag_collection::get_default(), $tags,
+ $type === 'official');
// New function returns the tags in different format, for BC we keep the format that this function used to have.
$rv = array();
if (is_null($tagset)) {
// No tag set received, so fetch tags from database.
// Always add query by tagcollid even when it's not known to make use of the table index.
- $tagcloud = core_tag_collection::get_tag_cloud(0, '', $nr_of_tags, $sort);
+ $tagcloud = core_tag_collection::get_tag_cloud(0, false, $nr_of_tags, $sort);
} else {
$tagsincloud = $tagset;
$name = strtr(trim($name, "'"), '[]*/\?:', ' ');
// Shorten the title if necessary.
$name = core_text::substr($name, 0, 31);
+ // After the substr, we might now have a single quote on the end.
+ $name = trim($name, "'");
if ($name === '') {
// Name is required!
'string' => new external_value(PARAM_RAW, 'translated string'))
));
}
+
+ /**
+ * Returns description of get_fragment parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 3.1
+ */
+ public static function get_fragment_parameters() {
+ return new external_function_parameters(
+ array(
+ 'component' => new external_value(PARAM_COMPONENT, 'Component for the callback e.g. mod_assign'),
+ 'callback' => new external_value(PARAM_ALPHANUMEXT, 'Name of the callback to execute'),
+ 'contextid' => new external_value(PARAM_INT, 'Context ID that the fragment is from'),
+ 'args' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'name' => new external_value(PARAM_ALPHANUMEXT, 'param name'),
+ 'value' => new external_value(PARAM_RAW, 'param value')
+ )
+ ), 'args for the callback are optional', VALUE_OPTIONAL
+ )
+ )
+ );
+ }
+
+ /**
+ * Get a HTML fragment for inserting into something. Initial use is for inserting mforms into
+ * a page using AJAX.
+ * This web service is designed to be called only via AJAX and not directly.
+ * Callbacks that are called by this web service are responsible for doing the appropriate security checks
+ * to access the information returned. This only does minimal validation on the context.
+ *
+ * @param string $component Name of the component.
+ * @param string $callback Function callback name.
+ * @param int $contextid Context ID this fragment is in.
+ * @param array $args optional arguments for the callback.
+ * @return array HTML and JavaScript fragments for insertion into stuff.
+ * @since Moodle 3.1
+ */
+ public static function get_fragment($component, $callback, $contextid, $args = null) {
+ global $OUTPUT, $PAGE;
+
+ $params = self::validate_parameters(self::get_fragment_parameters(),
+ array(
+ 'component' => $component,
+ 'callback' => $callback,
+ 'contextid' => $contextid,
+ 'args' => $args
+ )
+ );
+
+ // Reformat arguments into something less unwieldy.
+ $arguments = array();
+ foreach ($params['args'] as $paramargument) {
+ $arguments[$paramargument['name']] = $paramargument['value'];
+ }
+
+ $context = context::instance_by_id($contextid);
+ self::validate_context($context);
+
+ // Hack alert: Forcing bootstrap_renderer to initiate moodle page.
+ $OUTPUT->header();
+
+ // Overwriting page_requirements_manager with the fragment one so only JS included from
+ // this point is returned to the user.
+ $PAGE->start_collecting_javascript_requirements();
+ $data = component_callback($params['component'], 'output_fragment_' . $params['callback'], $arguments);
+ $jsfooter = $PAGE->requires->get_end_code();
+ $output = array('html' => $data, 'javascript' => $jsfooter);
+ return $output;
+ }
+
+ /**
+ * Returns description of get_fragment() result value
+ *
+ * @return array
+ * @since Moodle 3.1
+ */
+ public static function get_fragment_returns() {
+ return new external_single_structure(
+ array(
+ 'html' => new external_value(PARAM_RAW, 'HTML fragment.'),
+ 'javascript' => new external_value(PARAM_RAW, 'JavaScript fragment')
+ )
+ );
+ }
}
send_file_not_found();
}
+ if ($context->get_course_context(false)) {
+ // If block is in course context, then check if user has capability to access course.
+ require_course_login($course);
+ } else if ($CFG->forcelogin) {
+ // If user is logged out, bp record will not be visible, even if the user would have access if logged in.
+ require_login();
+ }
+
$bprecord = $DB->get_record('block_positions', array('contextid' => $context->id, 'blockinstanceid' => $context->instanceid));
// User can't access file, if block is hidden or doesn't have block:view capability
if (($bprecord && !$bprecord->visible) || !has_capability('moodle/block:view', $context)) {
$langscale = get_string('modgradetypescale', 'grades');
$scaleselect = @MoodleQuickForm::createElement('select', 'modgrade_scale', $langscale, $scales, $attributes);
$scaleselect->setHiddenLabel = false;
- $scaleselect->_generateId();
- $scaleselectid = $scaleselect->getAttribute('id');
+ $scaleselectid = $this->generate_modgrade_subelement_id('modgrade_scale');
+ $scaleselect->updateAttributes(array('id' => $scaleselectid));
// Maximum grade textbox.
$langmaxgrade = get_string('modgrademaxgrade', 'grades');
$maxgrade = @MoodleQuickForm::createElement('text', 'modgrade_point', $langmaxgrade, array());
$maxgrade->setHiddenLabel = false;
- $maxgrade->_generateId();
- $maxgradeid = $maxgrade->getAttribute('id');
+ $maxgradeid = $this->generate_modgrade_subelement_id('modgrade_point');
+ $maxgrade->updateAttributes(array('id' => $maxgradeid));
// Grade type select box.
$gradetype = array(
$langtype = get_string('modgradetype', 'grades');
$typeselect = @MoodleQuickForm::createElement('select', 'modgrade_type', $langtype, $gradetype, $attributes, true);
$typeselect->setHiddenLabel = false;
- $typeselect->_generateId();
+ $typeselectid = $this->generate_modgrade_subelement_id('modgrade_type');
+ $typeselect->updateAttributes(array('id' => $typeselectid));
// Add elements.
return parent::onQuickFormEvent($event, $arg, $caller);
}
+ /**
+ * Generates the id attribute for the subelement of the modgrade group.
+ *
+ * Uses algorithm similar to what {@link HTML_QuickForm_element::_generateId()}
+ * does but takes the name of the wrapping modgrade group into account.
+ *
+ * @param string $subname the name of the HTML_QuickForm_element in this modgrade group
+ * @return string
+ */
+ protected function generate_modgrade_subelement_id($subname) {
+ $gid = str_replace(array('[', ']'), array('_', ''), $this->getName());
+ return clean_param('id_'.$gid.'_'.$subname, PARAM_ALPHANUMEXT);
+ }
}
/**
* Tag autocomplete field.
*
- * Contains HTML class for editing tags, both official and personal.
+ * Contains HTML class for editing tags, both standard and not.
*
* @package core_form
* @copyright 2009 Tim Hunt
/**
* Form field type for editing tags.
*
- * HTML class for editing tags, both official and personal.
+ * HTML class for editing tags, both standard and not.
*
* @package core_form
* @copyright 2009 Tim Hunt
/**
* Inidcates that the user should be the usual interface, with the official
* tags listed seprately, and a text box where they can type anything.
+ * @deprecated since 3.1
* @var int
*/
const DEFAULTUI = 'defaultui';
/**
* Indicates that the user should only be allowed to select official tags.
+ * @deprecated since 3.1
* @var int
*/
const ONLYOFFICIAL = 'onlyofficial';
/**
* Indicates that the user should just be given a text box to type in (they
* can still type official tags though.
+ * @deprecated since 3.1
* @var int
*/
const NOOFFICIAL = 'noofficial';
/**
- * @var boolean $showingofficial Official tags shown? (if not, then don't show link to manage official tags).
+ * @var boolean $showstandard Standard tags suggested? (if not, then don't show link to manage standard tags).
*/
- protected $showingofficial = false;
+ protected $showstandard = false;
/**
* Options passed when creating an element.
if (!empty($options)) {
// Only execute it when the element was created and $options has values set by user.
// In onQuickFormEvent() we make sure that $options is not empty even if developer left it empty.
- if (empty($options['display'])) {
- $options['display'] = self::DEFAULTUI;
+ $showstandard = core_tag_tag::BOTH_STANDARD_AND_NOT;
+ if (isset($options['showstandard'])) {
+ $showstandard = $options['showstandard'];
+ } else if (isset($options['display'])) {
+ debugging('Option "display" is deprecated, each tag area can be configured to show standard tags or not ' .
+ 'by admin or manager. If it is necessary for the developer to override it, please use "showstandard" option',
+ DEBUG_DEVELOPER);
+ if ($options['display'] === self::NOOFFICIAL) {
+ $showstandard = core_tag_tag::HIDE_STANDARD;
+ } else if ($options['display'] === self::ONLYOFFICIAL) {
+ $showstandard = core_tag_tag::STANDARD_ONLY;
+ }
+ } else if (!empty($options['component']) && !empty($options['itemtype'])) {
+ $showstandard = core_tag_area::get_showstandard($options['component'], $options['itemtype']);
}
- $this->tagsoptions = $options;
- $this->showingofficial = $options['display'] != self::NOOFFICIAL;
+ $this->tagsoptions = $options;
- if ($this->showingofficial) {
- $validoptions = $this->load_official_tags();
+ $this->showstandard = ($showstandard != core_tag_tag::HIDE_STANDARD);
+ if ($this->showstandard) {
+ $validoptions = $this->load_standard_tags();
}
// Option 'tags' allows us to type new tags.
- if ($options['display'] == self::ONLYOFFICIAL) {
- $attributes['tags'] = false;
- } else {
- $attributes['tags'] = true;
- }
+ $attributes['tags'] = ($showstandard != core_tag_tag::STANDARD_ONLY);
$attributes['multiple'] = 'multiple';
$attributes['placeholder'] = get_string('entertags', 'tag');
- $attributes['showsuggestions'] = $this->showingofficial;
+ $attributes['showsuggestions'] = $this->showstandard;
}
parent::__construct($elementName, $elementLabel, $validoptions, $attributes);
}
/**
- * Finds the tag collection to use for official tag selector
+ * Finds the tag collection to use for standard tag selector
*
* @return int
*/
global $OUTPUT;
$managelink = '';
- if (has_capability('moodle/tag:manage', context_system::instance()) && $this->showingofficial) {
+ if (has_capability('moodle/tag:manage', context_system::instance()) && $this->showstandard) {
$url = new moodle_url('/tag/manage.php', array('tc' => $this->get_tag_collection()));
- $managelink = ' ' . $OUTPUT->action_link($url, get_string('manageofficialtags', 'tag'));
+ $managelink = ' ' . $OUTPUT->action_link($url, get_string('managestandardtags', 'tag'));
}
return parent::toHTML() . $managelink;
}
/**
- * Internal function to load official tags
+ * Internal function to load standard tags
*/
- protected function load_official_tags() {
+ protected function load_standard_tags() {
global $CFG, $DB;
if (!$this->is_tagging_enabled()) {
return array();
}
$namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname';
$tags = $DB->get_records_menu('tag',
- array('tagtype' => 'official', 'tagcollid' => $this->get_tag_collection()),
+ array('isstandard' => 1, 'tagcollid' => $this->get_tag_collection()),
$namefield, 'id,' . $namefield);
return array_combine($tags, $tags);
}
return $ldap_user_dn;
}
+/**
+ * Normalise the supplied objectclass filter.
+ *
+ * This normalisation is a rudimentary attempt to format the objectclass filter correctly.
+ *
+ * @param string $objectclass The objectclass to normalise
+ * @param string $default The default objectclass value to use if no objectclass was supplied
+ * @return string The normalised objectclass.
+ */
+function ldap_normalise_objectclass($objectclass, $default = '*') {
+ if (empty($objectclass)) {
+ // Can't send empty filter.
+ $return = sprintf('(objectClass=%s)', $default);
+ } else if (stripos($objectclass, 'objectClass=') === 0) {
+ // Value is 'objectClass=some-string-here', so just add () around the value (filter _must_ have them).
+ $return = sprintf('(%s)', $objectclass);
+ } else if (stripos($objectclass, '(') !== 0) {
+ // Value is 'some-string-not-starting-with-left-parentheses', which is assumed to be the objectClass matching value.
+ // Build a valid filter using the value it.
+ $return = sprintf('(objectClass=%s)', $objectclass);
+ } else {
+ // There is an additional possible value '(some-string-here)', that can be used to specify any valid filter
+ // string, to select subsets of users based on any criteria.
+ //
+ // For example, we could select the users whose objectClass is 'user' and have the 'enabledMoodleUser'
+ // attribute, with something like:
+ //
+ // (&(objectClass=user)(enabledMoodleUser=1))
+ //
+ // In this particular case we don't need to do anything, so leave $this->config->objectclass as is.
+ $return = $objectclass;
+ }
+
+ return $return;
+}
+
/**
* Returns values like ldap_get_entries but is binary compatible and
* returns all attributes as array.
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Library functions to facilitate the use of JavaScript in Moodle.
+ *
+ * @copyright 2016 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package core
+ * @category output
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * This requirements manager captures the appropriate html for creating a fragment to
+ * be inserted elsewhere.
+ *
+ * @copyright 2016 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 3.1
+ * @package core
+ * @category output
+ */
+class fragment_requirements_manager extends page_requirements_manager {
+
+ /**
+ * Page fragment constructor.
+ */
+ public function __construct() {
+ parent::__construct();
+ // As this is a fragment the header should already be done.
+ $this->headdone = true;
+ }
+
+ /**
+ * Returns js code to load amd module loader, then insert inline script tags
+ * that contain require() calls using RequireJS.
+ *
+ * @return string
+ */
+ protected function get_amd_footercode() {
+ global $CFG;
+ $output = '';
+
+ // First include must be to a module with no dependencies, this prevents multiple requests.
+ $prefix = "require(['core/first'], function() {\n";
+ $suffix = "\n});";
+ $output .= html_writer::script($prefix . implode(";\n", $this->amdjscode) . $suffix);
+ return $output;
+ }
+
+
+ /**
+ * Generate any HTML that needs to go at the end of the page.
+ *
+ * @return string the HTML code to to at the end of the page.
+ */
+ public function get_end_code() {
+ global $CFG;
+
+ $output = '';
+
+ // Call amd init functions.
+ $output .= $this->get_amd_footercode();
+
+ // Add other requested modules.
+ $output .= $this->get_extra_modules_code();
+
+ // All the other linked scripts - there should be as few as possible.
+ if ($this->jsincludes['footer']) {
+ foreach ($this->jsincludes['footer'] as $url) {
+ $output .= html_writer::script('', $url);
+ }
+ }
+
+ if (!empty($this->stringsforjs)) {
+ // Add all needed strings.
+ $strings = array();
+ foreach ($this->stringsforjs as $component => $v) {
+ foreach ($v as $indentifier => $langstring) {
+ $strings[$component][$indentifier] = $langstring->out();
+ }
+ }
+ // Append don't overwrite.
+ $output .= html_writer::script('require(["jquery"], function($) {
+ M.str = $.extend(true, M.str, ' . json_encode($strings) . ');
+ });');
+ }
+
+ // Add variables.
+ if ($this->jsinitvariables['footer']) {
+ $js = '';
+ foreach ($this->jsinitvariables['footer'] as $data) {
+ list($var, $value) = $data;
+ $js .= js_writer::set_variable($var, $value, true);
+ }
+ $output .= html_writer::script($js);
+ }
+
+ $inyuijs = $this->get_javascript_code(false);
+ $ondomreadyjs = $this->get_javascript_code(true);
+ // See if this is still needed when we get to the ajax page.
+ $jsinit = $this->get_javascript_init_code();
+ $handlersjs = $this->get_event_handler_code();
+
+ // There is a global Y, make sure it is available in your scope.
+ $js = "(function() {{$inyuijs}{$ondomreadyjs}{$jsinit}{$handlersjs}})();";
+
+ $output .= html_writer::script($js);
+
+ return $output;
+ }
+}
return $this->_navbar->has_items();
}
+ /**
+ * Switches from the regular requirements manager to the fragment requirements manager to
+ * capture all necessary JavaScript to display a chunk of HTML such as an mform. This is for use
+ * by the get_fragment() web service and not for use elsewhere.
+ */
+ public function start_collecting_javascript_requirements() {
+ global $CFG;
+ require_once($CFG->libdir.'/outputfragmentrequirementslib.php');
+
+ // Check that the requirements manager has not already been switched.
+ if (get_class($this->_requires) == 'fragment_requirements_manager') {
+ throw new coding_exception('JavaScript collection has already been started.');
+ }
+ // The header needs to have been called to flush out the generic JavaScript for the page. This allows only
+ // JavaScript for the fragment to be collected. _wherethemewasinitialised is set when header() is called.
+ if (!empty($this->_wherethemewasinitialised)) {
+ // Change the current requirements manager over to the fragment manager to capture JS.
+ $this->_requires = new fragment_requirements_manager();
+ } else {
+ throw new coding_exception('$OUTPUT->header() needs to be called before collecting JavaScript requirements.');
+ }
+ }
+
/**
* Should the current user see this page in editing mode.
* That is, are they allowed to edit this page, and are they currently in
global $CFG;
$this->Version = 'Moodle '.$CFG->version; // mailer version
$this->CharSet = 'UTF-8';
+ // MDL-52637: Disable the automatic TLS encryption added in v5.2.10 (9da56fc1328a72aa124b35b738966315c41ef5c6).
+ $this->SMTPAutoTLS = false;
if (!empty($CFG->smtpauthtype)) {
$this->AuthType = $CFG->smtpauthtype;
* @return void
*/
public static function reset_all_data($detectchanges = false) {
- global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION;
+ global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION, $FULLME;
// Stop any message redirection.
self::stop_message_redirection();
$warnings[] = 'Warning: unexpected change of $COURSE';
}
+ if ($FULLME !== self::get_global_backup('FULLME')) {
+ $warnings[] = 'Warning: unexpected change of $FULLME';
+ }
+
if (setlocale(LC_TIME, 0) !== $localename) {
$warnings[] = 'Warning: unexpected change of locale';
}
$_SERVER = self::get_global_backup('_SERVER');
$CFG = self::get_global_backup('CFG');
$SITE = self::get_global_backup('SITE');
+ $FULLME = self::get_global_backup('FULLME');
$_GET = array();
$_POST = array();
$_FILES = array();
* @return void
*/
public static function bootstrap_init() {
- global $CFG, $SITE, $DB;
+ global $CFG, $SITE, $DB, $FULLME;
// backup the globals
self::$globals['_SERVER'] = $_SERVER;
self::$globals['CFG'] = clone($CFG);
self::$globals['SITE'] = clone($SITE);
self::$globals['DB'] = $DB;
+ self::$globals['FULLME'] = $FULLME;
// refresh data in all tables, clear caches, etc.
self::reset_all_data();
$record['name'] = core_text::strtolower($record['name']);
}
- if (!isset($record['tagtype'])) {
- $record['tagtype'] = 'default';
- }
-
if (!isset($record['tagcollid'])) {
$record['tagcollid'] = core_tag_collection::get_default();
}
* @AfterStep
*/
public function after_step(StepEvent $event) {
- global $CFG;
+ global $CFG, $DB;
// Save the page content if the step failed.
if (!empty($CFG->behat_faildump_path) &&
$event->getResult() === StepEvent::FAILED) {
$this->take_contentdump($event);
}
+
+ // Abort any open transactions to prevent subsequent tests hanging.
+ // This does the same as abort_all_db_transactions(), but doesn't call error_log() as we don't
+ // want to see a message in the behat output.
+ if ($event->hasException()) {
+ if ($DB && $DB->is_transaction_started()) {
+ $DB->force_transaction_rollback();
+ }
+ }
}
/**
* @AfterScenario @_switch_window
*/
public function after_scenario_switchwindow(ScenarioEvent $event) {
- $this->getSession()->restart();
+ for ($count = 0; $count < self::EXTENDED_TIMEOUT; $count) {
+ try {
+ $this->getSession()->restart();
+ break;
+ } catch (DriverException $e) {
+ // Wait for timeout and try again.
+ sleep(self::TIMEOUT);
+ }
+ }
+ // If session is not restarted above then it will try to start session before next scenario
+ // and if that fails then exception will be thrown.
}
/**
$data = $mform->get_data();
$this->assertSame($expectedvalues, (array) $data);
}
+
+ /**
+ * MDL-52873
+ */
+ public function test_multiple_modgrade_fields() {
+ $this->resetAfterTest(true);
+
+ $form = new formslib_multiple_modgrade_form();
+ ob_start();
+ $form->display();
+ $html = ob_get_clean();
+
+ $this->assertTag(array('id' => 'fgroup_id_grade1'), $html);
+ $this->assertTag(array('id' => 'id_grade1_modgrade_type'), $html);
+ $this->assertTag(array('id' => 'id_grade1_modgrade_point'), $html);
+ $this->assertTag(array('id' => 'id_grade1_modgrade_scale'), $html);
+
+ $this->assertTag(array('id' => 'fgroup_id_grade2'), $html);
+ $this->assertTag(array('id' => 'id_grade2_modgrade_type'), $html);
+ $this->assertTag(array('id' => 'id_grade2_modgrade_point'), $html);
+ $this->assertTag(array('id' => 'id_grade2_modgrade_scale'), $html);
+
+ $this->assertTag(array('id' => 'fgroup_id_grade_3'), $html);
+ $this->assertTag(array('id' => 'id_grade_3_modgrade_type'), $html);
+ $this->assertTag(array('id' => 'id_grade_3_modgrade_point'), $html);
+ $this->assertTag(array('id' => 'id_grade_3_modgrade_scale'), $html);
+ }
}
'repeatnamedgroup[repeatnamedgroupel2]' => array('type' => PARAM_INT)), 'repeatablenamedgroup', 'add', 0);
}
}
+
+/**
+ * Used to test that modgrade fields get unique id attributes.
+ */
+class formslib_multiple_modgrade_form extends moodleform {
+ public function definition() {
+ $mform = $this->_form;
+ $mform->addElement('modgrade', 'grade1', 'Grade 1');
+ $mform->addElement('modgrade', 'grade2', 'Grade 2');
+ $mform->addElement('modgrade', 'grade[3]', 'Grade 3');
+ }
+}
$this->assertSame($test['expected'], ldap_stripslashes($test['test']));
}
}
+
+ /**
+ * Tests for ldap_normalise_objectclass.
+ *
+ * @dataProvider ldap_normalise_objectclass_provider
+ * @param array $args Arguments passed to ldap_normalise_objectclass
+ * @param string $expected The expected objectclass filter
+ */
+ public function test_ldap_normalise_objectclass($args, $expected) {
+ $this->assertEquals($expected, call_user_func_array('ldap_normalise_objectclass', $args));
+ }
+
+ /**
+ * Data provider for the test_ldap_normalise_objectclass testcase.
+ *
+ * @return array of testcases.
+ */
+ public function ldap_normalise_objectclass_provider() {
+ return array(
+ 'Empty value' => array(
+ array(null),
+ '(objectClass=*)',
+ ),
+ 'Empty value with different default' => array(
+ array(null, 'lion'),
+ '(objectClass=lion)',
+ ),
+ 'Supplied unwrapped objectClass' => array(
+ array('objectClass=tiger'),
+ '(objectClass=tiger)',
+ ),
+ 'Supplied string value' => array(
+ array('leopard'),
+ '(objectClass=leopard)',
+ ),
+ 'Supplied complex' => array(
+ array('(&(objectClass=cheetah)(enabledMoodleUser=1))'),
+ '(&(objectClass=cheetah)(enabledMoodleUser=1))',
+ ),
+ );
+ }
}
function definition_after_data(){
$mform = $this->_form;
$mform->applyFilter('username', 'trim');
+
+ // Trim required name fields.
+ foreach (useredit_get_required_name_fields() as $field) {
+ $mform->applyFilter($field, 'trim');
+ }
}
function validation($data, $files) {
require_once(dirname(dirname(__FILE__)) . '/config.php');
+// Allow CORS requests.
+header('Access-Control-Allow-Origin: *');
+
$username = required_param('username', PARAM_USERNAME);
$password = required_param('password', PARAM_RAW);
$serviceshortname = required_param('service', PARAM_ALPHANUMEXT);
-// Allow CORS requests.
-header('Access-Control-Allow-Origin: *');
echo $OUTPUT->header();
if (!$CFG->enablewebservices) {
And "Delete" "link" should exist in the "#message_1" "css_element"
And "Delete" "link" should exist in the "#message_2" "css_element"
# Confirm that there is a confirmation box before deleting, and that when we cancel the messages remain.
- And I click on "#message_2" "css_element"
+ And I hover "#message_2" "css_element"
And I click on "Delete" "link" in the "#message_2" "css_element"
And I press "Cancel"
And I should see "Hey bud, what's happening?"
And I should see "Whoops, forgot to mention that I drank all your beers. Lol."
# Confirm we can delete a message and then can no longer see it.
- And I click on "#message_2" "css_element"
+ And I hover "#message_2" "css_element"
And I click on "Delete" "link" in the "#message_2" "css_element"
And I press "Delete"
And I should see "Hey bud, what's happening?"
And "Delete" "link" should exist in the "#message_3" "css_element"
And "Delete" "link" should exist in the "#message_4" "css_element"
# Now, delete one of the messages that User 1 sent and one by User 2.
- And I click on "#message_1" "css_element"
+ And I hover "#message_1" "css_element"
And I click on "Delete" "link" in the "#message_1" "css_element"
And I press "Delete"
- And I click on "#message_2" "css_element"
+ And I hover "#message_2" "css_element"
And I click on "Delete" "link" in the "#message_2" "css_element"
And I press "Delete"
# Confirm that the messages are no longer listed.
# Send a message from the admin to User 1
And I send "Hey there, this is the all-powerful administrator. Obey my commands." message to "User 1" user
# Check the admin is still able to delete messages.
- And I click on "#message_1" "css_element"
+ And I hover "#message_1" "css_element"
And I click on "Delete" "link" in the "#message_1" "css_element"
And I press "Delete"
And I should not see "Hey there, this is the all-powerful administrator. Obey my commands."
$selectcol .= '<input type="hidden"
name="grademodified_' . $row->userid . '"
value="' . $row->timemarked . '"/>';
+ $selectcol .= '<input type="hidden"
+ name="gradeattempt_' . $row->userid . '"
+ value="' . $row->attemptnumber . '"/>';
return $selectcol;
}
// Gets a list of possible users and look for values based upon that.
foreach ($participants as $userid => $unused) {
$modified = optional_param('grademodified_' . $userid, -1, PARAM_INT);
+ $attemptnumber = optional_param('gradeattempt_' . $userid, -1, PARAM_INT);
// Gather the userid, updated grade and last modified value.
$record = new stdClass();
$record->userid = $userid;
// This user was not in the grading table.
continue;
}
+ $record->attemptnumber = $attemptnumber;
$record->lastmodified = $modified;
$record->gradinginfo = grade_get_grades($this->get_course()->id,
'mod',
$params['assignid2'] = $this->get_instance()->id;
// Check them all for currency.
- $grademaxattempt = 'SELECT mxg.userid, MAX(mxg.attemptnumber) AS maxattempt
- FROM {assign_grades} mxg
- WHERE mxg.assignment = :assignid1 GROUP BY mxg.userid';
-
- $sql = 'SELECT u.id as userid, g.grade as grade, g.timemodified as lastmodified, uf.workflowstate, uf.allocatedmarker
- FROM {user} u
- LEFT JOIN ( ' . $grademaxattempt . ' ) gmx ON u.id = gmx.userid
- LEFT JOIN {assign_grades} g ON
- u.id = g.userid AND
- g.assignment = :assignid2 AND
- g.attemptnumber = gmx.maxattempt
- LEFT JOIN {assign_user_flags} uf ON uf.assignment = g.assignment AND uf.userid = g.userid
- WHERE u.id ' . $userids;
+ $grademaxattempt = 'SELECT s.userid, s.attemptnumber AS maxattempt
+ FROM {assign_submission} s
+ WHERE s.assignment = :assignid1 AND s.latest = 1';
+
+ $sql = 'SELECT u.id AS userid, g.grade AS grade, g.timemodified AS lastmodified,
+ uf.workflowstate, uf.allocatedmarker, gmx.maxattempt AS attemptnumber
+ FROM {user} u
+ LEFT JOIN ( ' . $grademaxattempt . ' ) gmx ON u.id = gmx.userid
+ LEFT JOIN {assign_grades} g ON
+ u.id = g.userid AND
+ g.assignment = :assignid2 AND
+ g.attemptnumber = gmx.maxattempt
+ LEFT JOIN {assign_user_flags} uf ON uf.assignment = g.assignment AND uf.userid = g.userid
+ WHERE u.id ' . $userids;
$currentgrades = $DB->get_recordset_sql($sql, $params);
$modifiedusers = array();
if ($this->grading_disabled($modified->userid)) {
continue;
}
- if ((int)$current->lastmodified > (int)$modified->lastmodified) {
+ $badmodified = (int)$current->lastmodified > (int)$modified->lastmodified;
+ $badattempt = (int)$current->attemptnumber != (int)$modified->attemptnumber;
+ if ($badmodified || $badattempt) {
// Error - record has been modified since viewing the page.
return get_string('errorrecordmodified', 'assign');
} else {
And I follow "View/grade all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
And I should see "I'm the student first submission"
+
+ @javascript @_alert
+ Scenario: Allow new attempt does not display incorrect error message on group submission
+ Given the following "courses" exist:
+ | fullname | shortname | category | groupmode |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
+ | student2 | Student | 2 | student2@example.com |
+ | student3 | Student | 3 | student3@example.com |
+ | student4 | Student | 4 | student4@example.com |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ | student3 | C1 | student |
+ | student4 | C1 | student |
+ And the following "groups" exist:
+ | name | course | idnumber |
+ | Group 1 | C1 | G1 |
+ | Group 2 | C1 | G2 |
+ And the following "group members" exist:
+ | user | group |
+ | student1 | G1 |
+ | student2 | G1 |
+ | student3 | G2 |
+ | student4 | G2 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add a "Assignment" to section "1" and I fill the form with:
+ | Assignment name | Test assignment name |
+ | Description | Test assignment description |
+ | assignsubmission_onlinetext_enabled | 1 |
+ | assignsubmission_file_enabled | 0 |
+ | Students submit in groups | Yes |
+ | Attempts reopened | Manually |
+ | Maximum attempts | 3 |
+ | Group mode | Separate groups |
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Test assignment name"
+ And I press "Add submission"
+ And I set the following fields to these values:
+ | Online text | I'm the student's first submission |
+ And I press "Save changes"
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I follow "Test assignment name"
+ When I follow "View/grade all submissions"
+ Then "Student 1" row "Status" column of "generaltable" table should contain "Submitted for grading"
+ And "Student 2" row "Status" column of "generaltable" table should contain "Submitted for grading"
+ And "Student 3" row "Status" column of "generaltable" table should contain "No submission"
+ And "Student 4" row "Status" column of "generaltable" table should contain "No submission"
+ And I click on "Quick grading" "checkbox"
+ And I click on "Student 1" "checkbox"
+ And I set the field "User grade" to "60.0"
+ And I press "Save all quick grading changes"
+ And I should see "The grade changes were saved"
+ And I press "Continue"
+ And I click on "Student 1" "checkbox"
+ And I set the following fields to these values:
+ | operation | Allow another attempt |
+ And I click on "Go" "button" confirming the dialogue
+ And I should not see "The grades were not saved because someone has modified one or more records more recently than when you loaded the page."
+# Behat tests for the group submission, should be uncommented once the MDL-48216 is fixed.
+# And I log out
+# And I log in as "student3"
+# And I follow "Course 1"
+# And I follow "Test assignment name"
+# #And I should see "This is attempt 1 ( 3 attempts allowed )."
+# And I press "Add submission"
+# And I set the following fields to these values:
+# | Online text | I'm the student's 3 group 2 first attempt |
+# And I press "Save changes"
+# And I log out
+# And I log in as "teacher1"
+# And I follow "Course 1"
+# And I follow "Test assignment name"
+# And I follow "View/grade all submissions"
+# And "Student 1" row "Status" column of "generaltable" table should contain "Reopened"
+# And "Student 2" row "Status" column of "generaltable" table should contain "Reopened"
+# And "Student 3" row "Status" column of "generaltable" table should contain "Submitted for grading"
+# And "Student 4" row "Status" column of "generaltable" table should contain "Submitted for grading"
+# And I click on "Grade Student 3" "link" in the "Student 3" "table_row"
+# And I set the following fields to these values:
+# | Allow another attempt | 1 |
+# And I press "Save changes"
+# And I log out
+# And I log in as "student4"
+# And I follow "Course 1"
+# And I follow "Test assignment name"
+# #And I should see "This is attempt 2 ( 3 attempts allowed )."
+# And I press "Add submission"
+# And I set the following fields to these values:
+# | Online text | I'm the student's 4 group 2 second attempt |
+# And I press "Save changes"
+# And I log out
+# And I log in as "teacher1"
+# And I follow "Course 1"
+# And I follow "Test assignment name"
+# And I follow "View/grade all submissions"
+# And I click on "Grade Student 4" "link" in the "Student 1" "table_row"
+ #And I should see "This is attempt 2 (3 attempts allowed)"
And I log in as "student2"
And I follow "Course 1"
And I follow "Test assignment name"
- And I click on ".mod-assign-history-link" "css_element"
And I should see "I'm the teacher first feedback" in the "Feedback comments" "table_row"
And I log out
When I log in as "teacher1"
And I follow "View/grade all submissions"
And I click on "Grade Student 2" "link" in the "Student 2" "table_row"
And I click on ".mod-assign-history-link" "css_element"
- And I follow "Edit the grade and feedback for attempt number 1"
And I set the following fields to these values:
| Grade | 50 |
| Feedback comments | I'm the teacher second feedback |
$data = array(
'grademodified_' . $this->students[0]->id => time(),
+ 'gradeattempt_' . $this->students[0]->id => '',
'quickgrade_' . $this->students[0]->id => '60.0',
'quickgrade_' . $this->students[0]->id . '_workflowstate' => 'inmarking'
);
// Test process_save_quick_grades.
$sink = $this->redirectEvents();
+ $grade = $assign->get_user_grade($this->students[0]->id, false);
$data = array(
'grademodified_' . $this->students[0]->id => time(),
+ 'gradeattempt_' . $this->students[0]->id => $grade->attemptnumber,
'quickgrade_' . $this->students[0]->id => '60.0'
);
$assign->testable_process_save_quick_grades($data);
$this->assertTrue(in_array($this->extrastudents[0]->id, $allgroupmembers));
$this->assertTrue(in_array($this->extrastudents[1]->id , $allgroupmembers));
}
-}
+ /**
+ * Test the quicksave grades processor
+ */
+ public function test_process_save_quick_grades() {
+ $this->editingteachers[0]->ignoresesskey = true;
+ $this->setUser($this->editingteachers[0]);
+
+ $assign = $this->create_instance(array('attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL));
+
+ // Initially grade the user.
+ $grade = $assign->get_user_grade($this->students[0]->id, false);
+ if (!$grade) {
+ $grade = new stdClass();
+ $grade->attemptnumber = '';
+ $grade->timemodified = '';
+ }
+ $data = array(
+ 'grademodified_' . $this->students[0]->id => $grade->timemodified,
+ 'gradeattempt_' . $this->students[0]->id => $grade->attemptnumber,
+ 'quickgrade_' . $this->students[0]->id => '60.0'
+ );
+ $result = $assign->testable_process_save_quick_grades($data);
+ $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result);
+ $grade = $assign->get_user_grade($this->students[0]->id, false);
+ $this->assertEquals('60.0', $grade->grade);
+
+ // Attempt to grade with a past attempts grade info.
+ $assign->testable_process_add_attempt($this->students[0]->id);
+ $data = array(
+ 'grademodified_' . $this->students[0]->id => $grade->timemodified,
+ 'gradeattempt_' . $this->students[0]->id => $grade->attemptnumber,
+ 'quickgrade_' . $this->students[0]->id => '50.0'
+ );
+ $result = $assign->testable_process_save_quick_grades($data);
+ $this->assertContains(get_string('errorrecordmodified', 'assign'), $result);
+ $grade = $assign->get_user_grade($this->students[0]->id, false);
+ $this->assertFalse($grade);
+
+ // Attempt to grade a the attempt.
+ $submission = $assign->get_user_submission($this->students[0]->id, false);
+ $data = array(
+ 'grademodified_' . $this->students[0]->id => '',
+ 'gradeattempt_' . $this->students[0]->id => $submission->attemptnumber,
+ 'quickgrade_' . $this->students[0]->id => '40.0'
+ );
+ $result = $assign->testable_process_save_quick_grades($data);
+ $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result);
+ $grade = $assign->get_user_grade($this->students[0]->id, false);
+ $this->assertEquals('40.0', $grade->grade);
+
+ // Catch grade update conflicts.
+ // Save old data for later.
+ $pastdata = $data;
+ // Update the grade the 'good' way.
+ $data = array(
+ 'grademodified_' . $this->students[0]->id => $grade->timemodified,
+ 'gradeattempt_' . $this->students[0]->id => $grade->attemptnumber,
+ 'quickgrade_' . $this->students[0]->id => '30.0'
+ );
+ $result = $assign->testable_process_save_quick_grades($data);
+ $this->assertContains(get_string('quickgradingchangessaved', 'assign'), $result);
+ $grade = $assign->get_user_grade($this->students[0]->id, false);
+ $this->assertEquals('30.0', $grade->grade);
+
+ // Now update using 'old' data. Should fail.
+ $result = $assign->testable_process_save_quick_grades($pastdata);
+ $this->assertContains(get_string('errorrecordmodified', 'assign'), $result);
+ $grade = $assign->get_user_grade($this->students[0]->id, false);
+ $this->assertEquals('30.0', $grade->grade);
+ }
+}
panel.set('aria-live', 'polite');
wrapper.addClass(CSS.LINK);
- wrapper.addClass(CSS.CLOSED);
+ if (COUNT == 1) {
+ wrapper.addClass(CSS.OPEN);
+ } else {
+ wrapper.addClass(CSS.CLOSED);
+ }
panel.addClass(CSS.PANEL);
- panel.hide();
+ if (COUNT == 1) {
+ panel.show();
+ } else {
+ panel.hide();
+ }
link = null;
} else {
link = this;
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The mod_choice answer deleted event.
+ *
+ * @package mod_choice
+ * @copyright 2016 Stephen Bourget
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_choice\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The mod_choice answer updated event class.
+ *
+ * @property-read array $other {
+ * Extra information about event.
+ *
+ * - int choiceid: id of choice.
+ * - int optionid: id of the option.
+ * }
+ *
+ * @package mod_choice
+ * @since Moodle 3.1
+ * @copyright 2016 Stephen Bourget
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class answer_deleted extends \core\event\base {
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user with id '$this->userid' has deleted the option with id '" . $this->other['optionid'] . "' for the
+ user with id '$this->relateduserid' from the choice activity with course module id '$this->contextinstanceid'.";
+ }
+
+ /**
+ * Return localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventanswerdeleted', 'mod_choice');
+ }
+
+ /**
+ * Get URL related to the action
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/mod/choice/view.php', array('id' => $this->contextinstanceid));
+ }
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'choice_answers';
+ $this->data['crud'] = 'd';
+ $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
+ }
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+
+ if (!isset($this->other['choiceid'])) {
+ throw new \coding_exception('The \'choiceid\' value must be set in other.');
+ }
+
+ if (!isset($this->other['optionid'])) {
+ throw new \coding_exception('The \'optionid\' value must be set in other.');
+ }
+ }
+
+ public static function get_objectid_mapping() {
+ return array('db' => 'choice_answers', 'restore' => \core\event\base::NOT_MAPPED);
+ }
+
+ public static function get_other_mapping() {
+ $othermapped = array();
+ $othermapped['choiceid'] = array('db' => 'choice', 'restore' => 'choice');
+ $othermapped['optionid'] = array('db' => 'choice_options', 'restore' => 'choice_option');
+
+ return $othermapped;
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The mod_choice report viewed event.
+ *
+ * @package mod_choice
+ * @copyright 2016 Stephen Bourget
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_choice\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The mod_choice report viewed event class.
+ *
+ * @property-read array $other {
+ * Extra information about the event.
+ *
+ * - string content: The content we are viewing.
+ * - string format: The report format
+ * - int choiced: The id of the choice
+ * }
+ *
+ * @package mod_choice
+ * @since Moodle 3.1
+ * @copyright 2016 Stephen Bourget
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class report_downloaded extends \core\event\base {
+
+ /**
+ * Init method.
+ */
+ protected function init() {
+ $this->data['crud'] = 'r';
+ $this->data['edulevel'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventreportdownloaded', 'mod_choice');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user with id '$this->userid' has downloaded the report in the '".$this->other['format']."' format for
+ the choice activity with course module id '$this->contextinstanceid'";
+ }
+
+ /**
+ * Returns relevant URL.
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/mod/choice/report.php', array('id' => $this->contextinstanceid));
+ }
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+
+ // Report format downloaded.
+ if (!isset($this->other['content'])) {
+ throw new \coding_exception('The \'content\' value must be set in other.');
+ }
+ // Report format downloaded.
+ if (!isset($this->other['format'])) {
+ throw new \coding_exception('The \'format\' value must be set in other.');
+ }
+ // ID of the choice activity.
+ if (!isset($this->other['choiceid'])) {
+ throw new \coding_exception('The \'choiceid\' value must be set in other.');
+ }
+ }
+
+ public static function get_objectid_mapping() {
+ return false;
+ }
+
+ public static function get_other_mapping() {
+ $othermapped = array();
+ $othermapped['choiceid'] = array('db' => 'choice', 'restore' => 'choice');
+
+ return $othermapped;
+ }
+}
$string['displaymode'] = 'Display mode for the options';
$string['displayvertical'] = 'Display vertically';
$string['eventanswercreated'] = 'Choice made';
+$string['eventanswerdeleted'] = 'Choice answer deleted';
$string['eventanswerupdated'] = 'Choice updated';
+$string['eventreportdownloaded'] = 'Choice report downloaded';
$string['eventreportviewed'] = 'Choice report viewed';
$string['expired'] = 'Sorry, this activity closed on {$a} and is no longer available';
$string['atleastoneoption'] = 'You need to provide at least one possible answer.';
* @return bool
*/
function choice_delete_responses($attemptids, $choice, $cm, $course) {
- global $DB, $CFG;
+ global $DB, $CFG, $USER;
require_once($CFG->libdir.'/completionlib.php');
if(!is_array($attemptids) || empty($attemptids)) {
}
}
+ $context = context_module::instance($cm->id);
$completion = new completion_info($course);
foreach($attemptids as $attemptid) {
if ($todelete = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'id' => $attemptid))) {
+ // Trigger the event answer deleted.
+ $eventdata = array();
+ $eventdata['objectid'] = $todelete->id;
+ $eventdata['context'] = $context;
+ $eventdata['userid'] = $USER->id;
+ $eventdata['courseid'] = $course->id;
+ $eventdata['relateduserid'] = $todelete->userid;
+ $eventdata['other'] = array();
+ $eventdata['other']['choiceid'] = $choice->id;
+ $eventdata['other']['optionid'] = $todelete->optionid;
+ $event = \mod_choice\event\answer_deleted::create($eventdata);
+ $event->add_record_snapshot('course', $course);
+ $event->add_record_snapshot('course_modules', $cm);
+ $event->add_record_snapshot('choice', $choice);
+ $event->add_record_snapshot('choice_answers', $todelete);
+ $event->trigger();
+
$DB->delete_records('choice_answers', array('choiceid' => $choice->id, 'id' => $attemptid));
- // Update completion state
- if ($completion->is_enabled($cm) && $choice->completionsubmit) {
- $completion->update_state($cm, COMPLETION_INCOMPLETE, $attemptid);
- }
}
}
+
+ // Update completion state.
+ if ($completion->is_enabled($cm) && $choice->completionsubmit) {
+ $completion->update_state($cm, COMPLETION_INCOMPLETE);
+ }
+
return true;
}
}
} else {
$groupmode = groups_get_activity_groupmode($cm);
+
+ // Trigger the report downloaded event.
+ $eventdata = array();
+ $eventdata['context'] = $context;
+ $eventdata['courseid'] = $course->id;
+ $eventdata['other']['content'] = 'choicereportcontentviewed';
+ $eventdata['other']['format'] = $download;
+ $eventdata['other']['choiceid'] = $choice->id;
+ $event = \mod_choice\event\report_downloaded::create($eventdata);
+ $event->trigger();
+
}
// Check if we want to include responses from inactive users.
$this->assertEventContextNotUsed($event);
}
+ /**
+ * Test to ensure that event data is being stored correctly.
+ */
+ public function test_answer_deleted() {
+ global $DB, $USER;
+ // Generate user data.
+ $user = $this->getDataGenerator()->create_user();
+
+ $optionids = array_keys($DB->get_records('choice_options', array('choiceid' => $this->choice->id)));
+
+ // Create the first answer.
+ choice_user_submit_response($optionids[2], $this->choice, $user->id, $this->course, $this->cm);
+ // Get the users response.
+ $answer = $DB->get_record('choice_answers', array('userid' => $user->id, 'choiceid' => $this->choice->id),
+ '*', $strictness = IGNORE_MULTIPLE);
+
+ // Redirect event.
+ $sink = $this->redirectEvents();
+ // Now delete the answer.
+ choice_delete_responses(array($answer->id), $this->choice, $this->cm, $this->course);
+
+ // Get our event event.
+ $events = $sink->get_events();
+ $event = reset($events);
+
+ // Data checking.
+ $this->assertInstanceOf('\mod_choice\event\answer_deleted', $event);
+ $this->assertEquals($USER->id, $event->userid);
+ $this->assertEquals($user->id, $event->relateduserid);
+ $this->assertEquals(context_module::instance($this->choice->cmid), $event->get_context());
+ $this->assertEquals($this->choice->id, $event->other['choiceid']);
+ $this->assertEquals($answer->optionid, $event->other['optionid']);
+ $this->assertEventContextNotUsed($event);
+ $sink->close();
+ }
+
/**
* Test to ensure that event data is being stored correctly.
*/
$sink->close();
}
+ /**
+ * Test to ensure that event data is being stored correctly.
+ */
+ public function test_report_downloaded() {
+ global $USER;
+
+ $this->resetAfterTest();
+
+ // Generate user data.
+ $this->setAdminUser();
+
+ $eventdata = array();
+ $eventdata['context'] = $this->context;
+ $eventdata['courseid'] = $this->course->id;
+ $eventdata['other']['content'] = 'choicereportcontentviewed';
+ $eventdata['other']['format'] = 'csv';
+ $eventdata['other']['choiceid'] = $this->choice->id;
+
+ // This is fired in a page view so we can't run this through a function.
+ $event = \mod_choice\event\report_downloaded::create($eventdata);
+
+ // Redirect event.
+ $sink = $this->redirectEvents();
+ $event->trigger();
+ $event = $sink->get_events();
+
+ // Data checking.
+ $this->assertCount(1, $event);
+ $this->assertInstanceOf('\mod_choice\event\report_downloaded', $event[0]);
+ $this->assertEquals($USER->id, $event[0]->userid);
+ $this->assertEquals(context_module::instance($this->choice->cmid), $event[0]->get_context());
+ $this->assertEquals('csv', $event[0]->other['format']);
+ $this->assertEquals($this->choice->id, $event[0]->other['choiceid']);
+ $this->assertEventContextNotUsed($event[0]);
+ $sink->close();
+ }
+
/**
* Test to ensure that event data is being stored correctly.
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2015111600; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2016020100; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2015111000; // Requires this Moodle version
$plugin->component = 'mod_choice'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;
and $choiceavailable) {
$answercount = $DB->count_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id));
if ($answercount > 0) {
- $DB->delete_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id));
-
- // Update completion state
- $completion = new completion_info($course);
- if ($completion->is_enabled($cm) && $choice->completionsubmit) {
- $completion->update_state($cm, COMPLETION_INCOMPLETE);
- }
+ $choiceanswers = $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id),
+ '', 'id');
+ $todelete = array_keys($choiceanswers);
+ choice_delete_responses($todelete, $choice, $cm, $course);
redirect("view.php?id=$cm->id");
}
}
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="display" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Display type of folder contents - on a separate page or inline"/>
<FIELD NAME="showexpanded" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="false" DEFAULT="1" SEQUENCE="false" COMMENT="1 = expanded, 0 = collapsed for sub-folders"/>
+ <FIELD NAME="showdownloadfolder" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="false" DEFAULT="1" SEQUENCE="false" COMMENT="1 = show download folder button"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
// Moodle v3.0.0 release upgrade line.
// Put any upgrade step following this.
+
+ // Add showdownloadfolder option.
+ if ($oldversion < 2016020201) {
+ $table = new xmldb_table('folder');
+ $field = new xmldb_field('showdownloadfolder', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'showexpanded');
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field, 'showdownloadfolder');
+ }
+
+ upgrade_mod_savepoint(true, 2016020201, 'folder');
+ }
+
return true;
}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Folder download
+ *
+ * @package mod_folder
+ * @copyright 2015 Andrew Hancox <andrewdchancox@googlemail.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . "/../../config.php");
+
+$id = required_param('id', PARAM_INT); // Course module ID.
+$cm = get_coursemodule_from_id('folder', $id, 0, true, MUST_EXIST);
+
+$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+require_course_login($course, true, $cm);
+$context = context_module::instance($cm->id);
+require_capability('mod/folder:view', $context);
+
+$folder = $DB->get_record('folder', array('id' => $cm->instance), '*', MUST_EXIST);
+
+$foldertree = new folder_tree($folder, $cm);
+$downloadable = folder_archive_available($folder, $foldertree);
+if (!$downloadable) {
+ print_error('cannotdownloaddir', 'repository');
+}
+
+// Completion.
+$completion = new completion_info($course);
+$completion->set_module_viewed($cm);
+
+$fs = get_file_storage();
+$file = $fs->get_file($context->id, 'mod_folder', 'content', 0, '/', '.');
+if (!$file) {
+ print_error('cannotdownloaddir', 'repository');
+}
+
+$zipper = get_file_packer('application/zip');
+$filename = clean_filename($folder->name . "-" . date("Ymd")) . ".zip";
+$temppath = make_request_directory() . $filename;
+
+if ($zipper->archive_to_pathname(array('/' => $file), $temppath)) {
+ send_temp_file($temppath, $filename);
+} else {
+ print_error('cannotdownloaddir', 'repository');
+}
$string['contentheader'] = 'Content';
$string['dnduploadmakefolder'] = 'Unzip files and create folder';
+$string['downloadfolder'] = 'Download folder';
$string['eventfolderupdated'] = 'Folder updated';
$string['folder:addinstance'] = 'Add a new folder';
$string['folder:managefiles'] = 'Manage files in folder module';
$string['displaypage'] = 'On a separate page';
$string['displayinline'] = 'Inline on a course page';
$string['noautocompletioninline'] = 'Automatic completion on viewing of activity can not be selected together with "Display inline" option';
+$string['showdownloadfolder'] = 'Show download folder button';
+$string['showdownloadfolder_help'] = 'If set to \'yes\', a button will be shown to allow users to download a zip archive containing all files.';
$string['showexpanded'] = 'Show subfolders expanded';
$string['showexpanded_help'] = 'If set to \'yes\', subfolders are shown expanded by default; otherwise they are shown collapsed.';
+$string['maxsizetodownload'] = 'Maximum folder download size (MB)';
+$string['maxsizetodownload_help'] = 'If set then users will not be able to downlod zip archives of folder where the total size is larger than this value.';
+
+
+
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
}
+
+/**
+ * Check if the folder can be zipped and downloaded.
+ * @param stdClass $folder
+ * @param folder_tree $foldertree
+ * @return bool True if the folder can be zipped and downloaded.
+ * @throws \dml_exception
+ */
+function folder_archive_available($folder, $foldertree) {
+ if (!$folder->showdownloadfolder) {
+ return false;
+ }
+
+ $size = folder_get_directory_size($foldertree->dir);
+ $maxsize = get_config('folder', 'maxsizetodownload') * 1024 * 1024;
+
+ if ($size == 0) {
+ return false;
+ }
+
+ if (!empty($maxsize) && $size > $maxsize) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Recursively measure the size of the files in a directory.
+ * @param array $directory
+ * @return int size of directory contents in bytes
+ */
+function folder_get_directory_size($directory) {
+ $size = 0;
+
+ foreach ($directory['files'] as $file) {
+ $size += $file->get_filesize();
+ }
+
+ foreach ($directory['subdirs'] as $subdirectory) {
+ $size += folder_get_directory_size($subdirectory);
+ }
+
+ return $size;
+}
\ No newline at end of file
$mform->addElement('advcheckbox', 'showexpanded', get_string('showexpanded', 'folder'));
$mform->addHelpButton('showexpanded', 'showexpanded', 'mod_folder');
$mform->setDefault('showexpanded', $config->showexpanded);
+
+ // Adding option to enable downloading archive of folder.
+ $mform->addElement('advcheckbox', 'showdownloadfolder', get_string('showdownloadfolder', 'folder'));
+ $mform->addHelpButton('showdownloadfolder', 'showdownloadfolder', 'mod_folder');
+ $mform->setDefault('showdownloadfolder', true);
//-------------------------------------------------------
$this->standard_coursemodule_elements();
'generalbox foldertree');
// Do not append the edit button on the course page.
- if ($folder->display != FOLDER_DISPLAY_INLINE && has_capability('mod/folder:managefiles', $context)) {
+ if ($folder->display != FOLDER_DISPLAY_INLINE) {
+ $containercontents = '';
+ $downloadable = folder_archive_available($folder, $foldertree);
+
+ if ($downloadable) {
+ $containercontents .= $this->output->single_button(
+ new moodle_url('/mod/folder/download_folder.php', array('id' => $cm->id)),
+ get_string('downloadfolder', 'folder')
+ );
+ }
+
+ if (has_capability('mod/folder:managefiles', $context)) {
+ $containercontents .= $this->output->single_button(
+ new moodle_url('/mod/folder/edit.php', array('id' => $cm->id)),
+ get_string('edit')
+ );
+ }
$output .= $this->output->container(
- $this->output->single_button(new moodle_url('/mod/folder/edit.php',
- array('id' => $cm->id)), get_string('edit')),
- 'mdl-align folder-edit-button');
+ $containercontents,
+ 'mdl-align folder-edit-button');
}
return $output;
}
if ($ADMIN->fulltree) {
//--- general settings -----------------------------------------------------------------------------------
$settings->add(new admin_setting_configcheckbox('folder/showexpanded',
- get_string('showexpanded', 'folder'),
- get_string('showexpanded_help', 'folder'), 1));
+ get_string('showexpanded', 'folder'),
+ get_string('showexpanded_help', 'folder'), 1));
+
+ $settings->add(new admin_setting_configtext('folder/maxsizetodownload',
+ get_string('maxsizetodownload', 'folder'),
+ get_string('maxsizetodownload_help', 'folder'), '', PARAM_INT));
}
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2015111600; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2016020201; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2015111000; // Requires this Moodle version
$plugin->component = 'mod_folder'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;
*/
$messageproviders = array (
+ // Ordinary single forum posts.
+ 'posts' => array(
+ ),
-/// Ordinary single forum posts
- 'posts' => array (
- )
-
+ // Forum digest messages.
+ 'digests' => array(
+ ),
);
-
-
-
$string['showsubscribers'] = 'Show/edit current subscribers';
$string['singleforum'] = 'A single simple discussion';
$string['smallmessage'] = '{$a->user} posted in {$a->forumname}';
+$string['smallmessagedigest'] = 'Forum digest containing {$a} messages';
$string['startedby'] = 'Started by';
$string['subject'] = 'Subject';
$string['subscribe'] = 'Subscribe to this forum';
$postsarray = $discussionposts[$discussionid];
sort($postsarray);
+ $sentcount = 0;
foreach ($postsarray as $postid) {
$post = $posts[$postid];
$userto->markposts[$post->id] = $post->id;
}
}
+ $sentcount++;
}
$footerlinks = array();
if ($canunsubscribe) {
$posthtml = '';
}
- $attachment = $attachname='';
- // Directly email forum digests rather than sending them via messaging, use the
- // site shortname as 'from name', the noreply address will be used by email_to_user.
- $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
+ $eventdata = new \core\message\message();
+ $eventdata->component = 'mod_forum';
+ $eventdata->name = 'digests';
+ $eventdata->userfrom = core_user::get_noreply_user();
+ $eventdata->userto = $userto;
+ $eventdata->subject = $postsubject;
+ $eventdata->fullmessage = $posttext;
+ $eventdata->fullmessageformat = FORMAT_PLAIN;
+ $eventdata->fullmessagehtml = $posthtml;
+ $eventdata->notification = 1;
+ $eventdata->smallmessage = get_string('smallmessagedigest', 'forum', $sentcount);
+ $mailresult = message_send($eventdata);
if (!$mailresult) {
mtrace("ERROR: mod/forum/cron.php: Could not send out digest mail to user $userto->id ".
{{/ showdiscussionname }}
</div>
-<table border="0" cellpadding="3" cellspacing="0" class="forumpost">
- <tr class="header">
- <td width="35" valign="top" class="picture left">
- {{{ authorpicture }}}
- </td>
- <td class="topic {{# firstpost }}starter{{/ firstpost }}">
- <div class="subject">
- {{{ subject }}}
- </div>
- <div class="author">
- {{# str }} bynameondate, forum, { "name": "<a target='_blank' href='{{{ authorlink }}}'>{{ authorfullname }}</a>", "date": "{{ postdate }}" } {{/ str }}
- </div>
- </td>
- </tr>
- <tr>
- <td class="left side" valign="top">
- {{# grouppicture }}
- {{{ grouppicture }}}
- {{/ grouppicture }}
- {{^ grouppicture }}
-
- {{/ grouppicture }}
- </td>
- <td class="content">
- {{# attachments }}
- <div class="attachments">
- {{{ attachments }}}
- </div>
- {{/ attachments }}
- {{{ message }}}
-
- <div class="commands">
- {{^ firstpost }}
- <a target="_blank" href="{{{ parentpostlink }}}">
- {{# str }} parent, forum {{/ str }}
- </a>
- {{# canreply }}
- |
- {{/ canreply }}
- {{/ firstpost }}
- {{# canreply }}
- <a target="_blank" href="{{{ replylink }}}">
- {{# str }} reply, forum {{/ str }}
- </a>
- {{/ canreply }}
- </div>
-
- <div class="link">
- <a target="_blank" href="{{{ permalink }}}">
- {{# str }} postincontext, forum {{/ str }}
- </a>
- </div>
- </td>
- </tr>
-</table>
+{{> mod_forum/forum_post_email_htmlemail_body }}
<hr />
<div class="mdl-align unsubscribelink">
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template mod_forum/forum_post_emaildigestfull_htmlemail_body
+
+ Template which defines the body component of a forum post for sending in a single-post HTML email.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * none
+
+ Context variables required for this template:
+ * courselink
+ * coursename
+ * forumindexlink
+ * forumviewlink
+ * forumname
+ * discussionlink
+ * discussionname
+ * showdiscussionname
+ * firstpost
+ * subject
+ * authorlink
+ * authorpicture
+ * authorfullname
+ * postdate
+ * grouppicture
+ * attachments
+ * message
+ * parentpostlink
+ * canreply
+ * replylink
+ * permalink
+ * unsubscribeforumlink
+ * unsubscribediscussionlink
+
+ Example context (json):
+ {
+ "courselink": "https://example.com/course/view.php?id=2",
+ "coursename": "Example course",
+ "forumindexlink": "https://example.com/mod/forum/index.php?id=2",
+ "forumviewlink": "https://example.com/mod/forum/view.php?f=2",
+ "forumname": "Lorem ipsum dolor",
+ "discussionlink": "https://example.com/mod/forum/discuss.php?d=70",
+ "discussionname": "Is Lorem ipsum Latin?",
+ "showdiscussionname": 1,
+ "firstpost": 1,
+ "subject": "Is Lorem ipsum Latin?",
+ "authorlink": "https://example.com/user/view.php?id=2&course=2",
+ "authorpicture": "<a href=\"https://example.com/user/view.php?id=2&course=6\"><img src=\"https://example.com/theme/image.php?theme=clean&component=core&image=u%2Ff2&svg=0\" alt=\"Picture of Admin User\" title=\"Picture of Admin User\" class=\"userpicture defaultuserpic\" width=\"35\" height=\"35\" /></a>",
+ "authorfullname": "Lucius Caecilius lucundus",
+ "postdate": "Sunday, 13 September 2015, 2:22 pm",
+ "grouppicture": "",
+ "attachments": "",
+ "message": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum et auctor libero. Quisque porta egestas neque, et bibendum libero dignissim at. Nulla facilisi. Morbi eget accumsan felis. Nunc et vulputate odio, vel venenatis nisl. Nunc maximus ipsum sed tincidunt mollis. Integer nunc erat, luctus sit amet arcu tincidunt, volutpat dignissim mi. Sed ut magna quam. Mauris accumsan porta turpis sed aliquam. Etiam at justo tristique, imperdiet augue quis, consectetur sapien. Ut nec erat malesuada sem suscipit lobortis. Vivamus posuere nibh eu ipsum porta fringilla. Sed vitae dapibus ipsum, ac condimentum enim. Sed dignissim ante at elit mollis, ac tempor lacus iaculis. Etiam nec lectus vitae nibh vulputate volutpat. Nulla quis tellus aliquam, commodo nisi et, dictum est.</p><p><br /></p>",
+ "parentpostlink": "",
+ "canreply": 1,
+ "replylink": "https://example.com/mod/forum/post.php?reply=2",
+ "permalink": "https://example.com/mod/forum/discuss.php?d=2#2",
+ "unsubscribeforumlink": "https://example.com/mod/forum/subscribe.php?id=2",
+ "unsubscribediscussionlink": "https://example.com/mod/discussion/subscribe.php?id=2&d=2"
+ }
+}}
+<table border="0" cellpadding="3" cellspacing="0" class="forumpost">
+ <tr class="header">
+ <td width="35" valign="top" class="picture left">
+ {{{ authorpicture }}}
+ </td>
+ <td class="topic {{# firstpost }}starter{{/ firstpost }}">
+ <div class="subject">
+ {{{ subject }}}
+ </div>
+ <div class="author">
+ {{# str }} bynameondate, forum, { "name": "<a target='_blank' href='{{{ authorlink }}}'>{{ authorfullname }}</a>", "date": "{{ postdate }}" } {{/ str }}
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="left side" valign="top">
+ {{# grouppicture }}
+ {{{ grouppicture }}}
+ {{/ grouppicture }}
+ {{^ grouppicture }}
+
+ {{/ grouppicture }}
+ </td>
+ <td class="content">
+ {{# attachments }}
+ <div class="attachments">
+ {{{ attachments }}}
+ </div>
+ {{/ attachments }}
+ {{{ message }}}
+
+ <div class="commands">
+ {{^ firstpost }}
+ <a target="_blank" href="{{{ parentpostlink }}}">
+ {{# str }} parent, forum {{/ str }}
+ </a>
+ {{# canreply }}
+ |
+ {{/ canreply }}
+ {{/ firstpost }}
+ {{# canreply }}
+ <a target="_blank" href="{{{ replylink }}}">
+ {{# str }} reply, forum {{/ str }}
+ </a>
+ {{/ canreply }}
+ </div>
+
+ <div class="link">
+ <a target="_blank" href="{{{ permalink }}}">
+ {{# str }} postincontext, forum {{/ str }}
+ </a>
+ </div>
+ </td>
+ </tr>
+</table>
"unsubscribediscussionlink": "https://example.com/mod/discussion/subscribe.php?id=2&d=2"
}
}}
-{{> mod_forum/forum_post_email_htmlemail }}
+{{> mod_forum/forum_post_email_htmlemail_body }}
* specified number of times.
*
* @param integer $expected The number of times that the post should have been sent
- * @return array An array of the messages caught by the message sink
+ * @param integer $individualcount The number of individual messages sent
+ * @param integer $digestcount The number of digest messages sent
*/
- protected function helper_run_cron_check_count($expected, $messagecount, $mailcount) {
+ protected function helper_run_cron_check_count($expected, $individualcount, $digestcount) {
if ($expected === 0) {
$this->expectOutputRegex('/(Email digests successfully sent to .* users.){0}/');
} else {
// Now check the results in the message sink.
$messages = $this->helper->messagesink->get_messages();
- // There should be the expected number of messages.
- $this->assertEquals($messagecount, count($messages));
- // Now check the results in the mail sink.
- $messages = $this->helper->mailsink->get_messages();
- // There should be the expected number of messages.
- $this->assertEquals($mailcount, count($messages));
+ $counts = (object) array('digest' => 0, 'individual' => 0);
+ foreach ($messages as $message) {
+ if (strpos($message->subject, 'forum digest') !== false) {
+ $counts->digest++;
+ } else {
+ $counts->individual++;
+ }
+ }
- return $messages;
+ $this->assertEquals($digestcount, $counts->digest);
+ $this->assertEquals($individualcount, $counts->individual);
}
public function test_set_maildigest() {
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2015120800; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2015120801; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2015111000; // Requires this Moodle version
$plugin->component = 'mod_forum'; // Full name of the plugin (used for diagnostics)
}
}
- echo "<p <p class=\"centerpara\">";
+ echo "<p class=\"centerpara\">";
echo $OUTPUT->user_picture($user, array('courseid'=>$course->id));
echo "</p>";
if ($showscales) {
// Print overall summary
- echo "<p <p class=\"centerpara\">>";
+ echo "<p class=\"centerpara\">";
survey_print_graph("id=$id&sid=$student&type=student.png");
echo "</p>";
$table = new html_table();
$table->head = array(get_string($question->text, "survey"));
$table->align = array ("left");
- $table->data[] = array(s($answer->answer1)); // no html here, just plain text
+ if (!empty($question->options) && $answer->answer1 > 0) {
+ $answers = explode(',', get_string($question->options, 'survey'));
+ if ($answer->answer1 <= count($answers)) {
+ $table->data[] = array(s($answers[$answer->answer1 - 1])); // No html here, just plain text.
+ } else {
+ $table->data[] = array(s($answer->answer1)); // No html here, just plain text.
+ }
+ } else {
+ $table->data[] = array(s($answer->answer1)); // No html here, just plain text.
+ }
echo html_writer::table($table);
echo $OUTPUT->spacer(array('height'=>30));
}
Then I should see "Cool" in the ".form-autocomplete-selection" "css_element"
And I press "Cancel"
- Scenario: Wiki page edition of official tags works as expected
+ Scenario: Wiki page edition of standard tags works as expected
Given I log in as "admin"
And I expand "Site administration" node
And I expand "Appearance" node
And I follow "Manage tags"
And I follow "Default collection"
And I set the field "otagsadd" to "OT1, OT2, OT3"
- And I press "Add official tags"
+ And I press "Add standard tags"
And I log out
And I log in as "student1"
And I follow "Course 1"
{
"name": "Moodle",
"dependencies": {
- "grunt": {
- "version": "0.4.5",
- "from": "grunt@0.4.5",
- "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz",
+ "abbrev": {
+ "version": "1.0.7",
+ "from": "abbrev@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
+ },
+ "align-text": {
+ "version": "0.1.3",
+ "from": "align-text@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz"
+ },
+ "amdefine": {
+ "version": "1.0.0",
+ "from": "amdefine@>=0.0.4",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
+ },
+ "ansi-color": {
+ "version": "0.2.1",
+ "from": "ansi-color@*",
+ "resolved": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz"
+ },
+ "ansi-regex": {
+ "version": "2.0.0",
+ "from": "ansi-regex@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
+ },
+ "ansi-styles": {
+ "version": "2.1.0",
+ "from": "ansi-styles@>=2.1.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz"
+ },
+ "argparse": {
+ "version": "0.1.16",
+ "from": "argparse@>=0.1.11 <0.2.0",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz",
+ "dependencies": {
+ "underscore.string": {
+ "version": "2.4.0",
+ "from": "underscore.string@>=2.4.0 <2.5.0",
+ "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz"
+ }
+ }
+ },
+ "asap": {
+ "version": "1.0.0",
+ "from": "asap@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz"
+ },
+ "asn1": {
+ "version": "0.2.3",
+ "from": "asn1@>=0.2.3 <0.3.0",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz"
+ },
+ "assert-plus": {
+ "version": "0.1.5",
+ "from": "assert-plus@>=0.1.5 <0.2.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
+ },
+ "async": {
+ "version": "1.5.2",
+ "from": "async@*",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
+ },
+ "aws-sign2": {
+ "version": "0.6.0",
+ "from": "aws-sign2@>=0.6.0 <0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
+ },
+ "balanced-match": {
+ "version": "0.3.0",
+ "from": "balanced-match@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz"
+ },
+ "bl": {
+ "version": "1.0.0",
+ "from": "bl@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz",
+ "dependencies": {
+ "readable-stream": {
+ "version": "2.0.5",
+ "from": "readable-stream@>=2.0.0 <2.1.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.5.tgz"
+ }
+ }
+ },
+ "boom": {
+ "version": "2.10.1",
+ "from": "boom@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
+ },
+ "brace-expansion": {
+ "version": "1.1.2",
+ "from": "brace-expansion@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.2.tgz"
+ },
+ "browserify-zlib": {
+ "version": "0.1.4",
+ "from": "browserify-zlib@>=0.1.4 <0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz"
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "from": "builtin-modules@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz"
+ },
+ "camelcase": {
+ "version": "2.0.1",
+ "from": "camelcase@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.0.1.tgz"
+ },
+ "camelcase-keys": {
+ "version": "2.0.0",
+ "from": "camelcase-keys@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.0.0.tgz"
+ },
+ "caseless": {
+ "version": "0.11.0",
+ "from": "caseless@>=0.11.0 <0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
+ },
+ "center-align": {
+ "version": "0.1.2",
+ "from": "center-align@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.2.tgz"
+ },
+ "chalk": {
+ "version": "1.1.1",
+ "from": "chalk@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
+ },
+ "cli": {
+ "version": "0.6.6",
+ "from": "cli@>=0.6.0 <0.7.0",
+ "resolved": "https://registry.npmjs.org/cli/-/cli-0.6.6.tgz",
"dependencies": {
- "async": {
- "version": "0.1.22",
- "from": "async@>=0.1.22 <0.2.0",
- "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz"
- },
- "coffee-script": {
- "version": "1.3.3",
- "from": "coffee-script@>=1.3.3 <1.4.0",
- "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz"
- },
- "colors": {
- "version": "0.6.2",
- "from": "colors@>=0.6.2 <0.7.0",
- "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz"
- },
- "dateformat": {
- "version": "1.0.2-1.2.3",
- "from": "dateformat@1.0.2-1.2.3",
- "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz"
- },
- "eventemitter2": {
- "version": "0.4.14",
- "from": "eventemitter2@>=0.4.13 <0.5.0",
- "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz"
- },
- "findup-sync": {
- "version": "0.1.3",
- "from": "findup-sync@>=0.1.2 <0.2.0",
- "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz",
- "dependencies": {
- "glob": {
- "version": "3.2.11",
- "from": "glob@>=3.2.9 <3.3.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
- "dependencies": {
- "inherits": {
- "version": "2.0.1",
- "from": "inherits@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
- },
- "minimatch": {
- "version": "0.3.0",
- "from": "minimatch@>=0.3.0 <0.4.0",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
- "dependencies": {
- "lru-cache": {
- "version": "2.7.3",
- "from": "lru-cache@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
- },
- "sigmund": {
- "version": "1.0.1",
- "from": "sigmund@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
- }
- }
- }
- }
- },
- "lodash": {
- "version": "2.4.2",
- "from": "lodash@>=2.4.1 <2.5.0",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz"
- }
- }
- },
"glob": {
- "version": "3.1.21",
- "from": "glob@>=3.1.21 <3.2.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
- "dependencies": {
- "graceful-fs": {
- "version": "1.2.3",
- "from": "graceful-fs@>=1.2.0 <1.3.0",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz"
- },
- "inherits": {
- "version": "1.0.2",
- "from": "inherits@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz"
- }
- }
- },
- "hooker": {
- "version": "0.2.3",
- "from": "hooker@>=0.2.3 <0.3.0",
- "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz"
- },
- "iconv-lite": {
- "version": "0.2.11",
- "from": "iconv-lite@>=0.2.11 <0.3.0",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz"
+ "version": "3.2.11",
+ "from": "glob@>=3.2.1 <3.3.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz"
},
"minimatch": {
- "version": "0.2.14",
- "from": "minimatch@>=0.2.12 <0.3.0",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
- "dependencies": {
- "lru-cache": {
- "version": "2.7.3",
- "from": "lru-cache@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
- },
- "sigmund": {
- "version": "1.0.1",
- "from": "sigmund@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
- }
- }
+ "version": "0.3.0",
+ "from": "minimatch@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz"
+ }
+ }
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "from": "cliui@>=2.1.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz"
+ },
+ "coffee-script": {
+ "version": "1.3.3",
+ "from": "coffee-script@>=1.3.3 <1.4.0",
+ "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz"
+ },
+ "colors": {
+ "version": "0.6.2",
+ "from": "colors@>=0.6.2 <0.7.0",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz"
+ },
+ "combined-stream": {
+ "version": "1.0.5",
+ "from": "combined-stream@>=1.0.5 <1.1.0",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz"
+ },
+ "commander": {
+ "version": "2.9.0",
+ "from": "commander@>=2.9.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz"
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "from": "concat-map@0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ },
+ "concat-stream": {
+ "version": "1.5.1",
+ "from": "concat-stream@>=1.4.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.1.tgz",
+ "dependencies": {
+ "readable-stream": {
+ "version": "2.0.5",
+ "from": "readable-stream@>=2.0.0 <2.1.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.5.tgz"
+ }
+ }
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "from": "console-browserify@>=1.1.0 <1.2.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz"
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "from": "core-util-is@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
+ },
+ "cpr": {
+ "version": "0.0.6",
+ "from": "cpr@>=0.0.6 <0.1.0",
+ "resolved": "https://registry.npmjs.org/cpr/-/cpr-0.0.6.tgz",
+ "dependencies": {
+ "graceful-fs": {
+ "version": "1.1.14",
+ "from": "graceful-fs@>=1.1.14 <1.2.0",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.1.14.tgz"
},
- "nopt": {
- "version": "1.0.10",
- "from": "nopt@>=1.0.10 <1.1.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
- "dependencies": {
- "abbrev": {
- "version": "1.0.7",
- "from": "abbrev@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
- }
- }
+ "mkdirp": {
+ "version": "0.3.5",
+ "from": "mkdirp@>=0.3.4 <0.4.0",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz"
},
"rimraf": {
- "version": "2.2.8",
- "from": "rimraf@>=2.2.8 <2.3.0",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
+ "version": "2.0.3",
+ "from": "rimraf@>=2.0.2 <2.1.0",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.0.3.tgz"
+ }
+ }
+ },
+ "cryptiles": {
+ "version": "2.0.5",
+ "from": "cryptiles@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
+ },
+ "csslint": {
+ "version": "0.10.0",
+ "from": "csslint@>=0.10.0 <0.11.0",
+ "resolved": "https://registry.npmjs.org/csslint/-/csslint-0.10.0.tgz"
+ },
+ "cssproc": {
+ "version": "0.0.7",
+ "from": "cssproc@>=0.0.1 <0.1.0",
+ "resolved": "https://registry.npmjs.org/cssproc/-/cssproc-0.0.7.tgz"
+ },
+ "dashdash": {
+ "version": "1.12.1",
+ "from": "dashdash@>=1.10.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.12.1.tgz"
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "from": "date-now@>=0.1.4 <0.2.0",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz"
+ },
+ "dateformat": {
+ "version": "1.0.2-1.2.3",
+ "from": "dateformat@1.0.2-1.2.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz"
+ },
+ "debug": {
+ "version": "0.7.4",
+ "from": "debug@>=0.7.0 <0.8.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
+ },
+ "decamelize": {
+ "version": "1.1.2",
+ "from": "decamelize@>=1.1.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.1.2.tgz"
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "from": "delayed-stream@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+ },
+ "dom-serializer": {
+ "version": "0.1.0",
+ "from": "dom-serializer@>=0.0.0 <1.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+ "dependencies": {
+ "domelementtype": {
+ "version": "1.1.3",
+ "from": "domelementtype@>=1.1.1 <1.2.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz"
+ },
+ "entities": {
+ "version": "1.1.1",
+ "from": "entities@>=1.1.1 <1.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz"
+ }
+ }
+ },
+ "domelementtype": {
+ "version": "1.3.0",
+ "from": "domelementtype@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz"
+ },
+ "domhandler": {
+ "version": "2.3.0",
+ "from": "domhandler@>=2.3.0 <2.4.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
+ },
+ "domutils": {
+ "version": "1.5.1",
+ "from": "domutils@>=1.5.0 <1.6.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz"
+ },
+ "ecc-jsbn": {
+ "version": "0.1.1",
+ "from": "ecc-jsbn@>=0.0.1 <1.0.0",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz"
+ },
+ "entities": {
+ "version": "1.0.0",
+ "from": "entities@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz"
+ },
+ "errno": {
+ "version": "0.1.4",
+ "from": "errno@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz"
+ },
+ "error-ex": {
+ "version": "1.3.0",
+ "from": "error-ex@>=1.2.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz"
+ },
+ "escape-string-regexp": {
+ "version": "1.0.4",
+ "from": "escape-string-regexp@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.4.tgz"
+ },
+ "escodegen": {
+ "version": "0.0.28",
+ "from": "escodegen@>=0.0.0 <0.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.28.tgz"
+ },
+ "esprima": {
+ "version": "1.0.4",
+ "from": "esprima@>=1.0.2 <1.1.0",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz"
+ },
+ "estraverse": {
+ "version": "1.3.2",
+ "from": "estraverse@>=1.3.0 <1.4.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.3.2.tgz"
+ },
+ "eventemitter2": {
+ "version": "0.4.14",
+ "from": "eventemitter2@>=0.4.13 <0.5.0",
+ "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz"
+ },
+ "exit": {
+ "version": "0.1.2",
+ "from": "exit@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
+ },
+ "extend": {
+ "version": "3.0.0",
+ "from": "extend@>=3.0.0 <3.1.0",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
+ },
+ "extsprintf": {
+ "version": "1.0.2",
+ "from": "extsprintf@1.0.2",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
+ },
+ "faye-websocket": {
+ "version": "0.4.4",
+ "from": "faye-websocket@>=0.4.3 <0.5.0",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.4.4.tgz"
+ },
+ "figures": {
+ "version": "1.4.0",
+ "from": "figures@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-1.4.0.tgz"
+ },
+ "fileset": {
+ "version": "0.1.8",
+ "from": "fileset@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz"
+ },
+ "find-up": {
+ "version": "1.1.0",
+ "from": "find-up@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.0.tgz"
+ },
+ "findup-sync": {
+ "version": "0.1.3",
+ "from": "findup-sync@>=0.1.2 <0.2.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz",
+ "dependencies": {
+ "glob": {
+ "version": "3.2.11",
+ "from": "glob@>=3.2.9 <3.3.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz"
},
"lodash": {
- "version": "0.9.2",
- "from": "lodash@>=0.9.2 <0.10.0",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz"
+ "version": "2.4.2",
+ "from": "lodash@>=2.4.1 <2.5.0",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz"
},
- "underscore.string": {
- "version": "2.2.1",
- "from": "underscore.string@>=2.2.1 <2.3.0",
- "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz"
+ "minimatch": {
+ "version": "0.3.0",
+ "from": "minimatch@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz"
+ }
+ }
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "from": "forever-agent@>=0.6.1 <0.7.0",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
+ },
+ "form-data": {
+ "version": "1.0.0-rc3",
+ "from": "form-data@>=1.0.0-rc3 <1.1.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz",
+ "dependencies": {
+ "async": {
+ "version": "1.5.2",
+ "from": "async@>=1.4.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
+ }
+ }
+ },
+ "gaze": {
+ "version": "0.5.2",
+ "from": "gaze@>=0.5.1 <0.6.0",
+ "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz"
+ },
+ "gear": {
+ "version": "0.8.18",
+ "from": "gear@>=0.8.0 <0.9.0",
+ "resolved": "https://registry.npmjs.org/gear/-/gear-0.8.18.tgz",
+ "dependencies": {
+ "async": {
+ "version": "0.2.10",
+ "from": "async@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
},
- "which": {
- "version": "1.0.9",
- "from": "which@>=1.0.5 <1.1.0",
- "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz"
+ "mkdirp": {
+ "version": "0.3.5",
+ "from": "mkdirp@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz"
+ }
+ }
+ },
+ "gear-lib": {
+ "version": "0.8.15",
+ "from": "gear-lib@>=0.8.0 <0.9.0",
+ "resolved": "https://registry.npmjs.org/gear-lib/-/gear-lib-0.8.15.tgz",
+ "dependencies": {
+ "async": {
+ "version": "0.2.10",
+ "from": "async@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
},
- "js-yaml": {
- "version": "2.0.5",
- "from": "js-yaml@>=2.0.5 <2.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz",
+ "glob": {
+ "version": "3.2.11",
+ "from": "glob@>=3.2.0 <3.3.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz"
+ },
+ "jshint": {
+ "version": "2.5.11",
+ "from": "jshint@>=2.5.0 <2.6.0",
+ "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.5.11.tgz",
"dependencies": {
- "argparse": {
- "version": "0.1.16",
- "from": "argparse@>=0.1.11 <0.2.0",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz",
- "dependencies": {
- "underscore": {
- "version": "1.7.0",
- "from": "underscore@>=1.7.0 <1.8.0",
- "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz"
- },
- "underscore.string": {
- "version": "2.4.0",
- "from": "underscore.string@>=2.4.0 <2.5.0",
- "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz"
- }
- }
- },
- "esprima": {
- "version": "1.0.4",
- "from": "esprima@>=1.0.2 <1.1.0",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz"
+ "minimatch": {
+ "version": "1.0.0",
+ "from": "minimatch@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz"
}
}
},
- "exit": {
- "version": "0.1.2",
- "from": "exit@>=0.1.1 <0.2.0",
- "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
+ "less": {
+ "version": "1.3.3",
+ "from": "less@>=1.3.0 <1.4.0",
+ "resolved": "https://registry.npmjs.org/less/-/less-1.3.3.tgz"
},
- "getobject": {
- "version": "0.1.0",
- "from": "getobject@>=0.1.0 <0.2.0",
- "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz"
+ "mime": {
+ "version": "1.2.11",
+ "from": "mime@>=1.2.0 <1.3.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
},
- "grunt-legacy-util": {
- "version": "0.2.0",
- "from": "grunt-legacy-util@>=0.2.0 <0.3.0",
- "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz"
+ "minimatch": {
+ "version": "0.3.0",
+ "from": "minimatch@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz"
},
- "grunt-legacy-log": {
- "version": "0.1.2",
- "from": "grunt-legacy-log@>=0.1.0 <0.2.0",
- "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.2.tgz",
- "dependencies": {
- "grunt-legacy-log-utils": {
- "version": "0.1.1",
- "from": "grunt-legacy-log-utils@>=0.1.1 <0.2.0",
- "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz"
- },
- "lodash": {
- "version": "2.4.2",
- "from": "lodash@>=2.4.1 <2.5.0",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz"
- },
- "underscore.string": {
- "version": "2.3.3",
- "from": "underscore.string@>=2.3.3 <2.4.0",
- "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz"
- }
- }
+ "uglify-js": {
+ "version": "1.3.5",
+ "from": "uglify-js@>=1.3.0 <1.4.0",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.3.5.tgz"
+ },
+ "underscore": {
+ "version": "1.6.0",
+ "from": "underscore@>=1.6.0 <1.7.0",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz"
}
}
},
- "grunt-contrib-jshint": {
- "version": "0.11.3",
- "from": "grunt-contrib-jshint@0.11.3",
- "resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-0.11.3.tgz",
+ "generate-function": {
+ "version": "2.0.0",
+ "from": "generate-function@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
+ },
+ "generate-object-property": {
+ "version": "1.2.0",
+ "from": "generate-object-property@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz"
+ },
+ "get-stdin": {
+ "version": "4.0.1",
+ "from": "get-stdin@>=4.0.1 <5.0.0",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
+ },
+ "getobject": {
+ "version": "0.1.0",
+ "from": "getobject@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz"
+ },
+ "glob": {
+ "version": "3.1.21",
+ "from": "glob@>=3.1.21 <3.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
"dependencies": {
- "hooker": {
- "version": "0.2.3",
- "from": "hooker@>=0.2.3 <0.3.0",
- "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz"
- },
- "jshint": {
- "version": "2.8.0",
- "from": "jshint@>=2.8.0 <2.9.0",
- "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.8.0.tgz",
- "dependencies": {
- "cli": {
- "version": "0.6.6",
- "from": "cli@>=0.6.0 <0.7.0",
- "resolved": "https://registry.npmjs.org/cli/-/cli-0.6.6.tgz",
- "dependencies": {
- "glob": {
- "version": "3.2.11",
- "from": "glob@>=3.2.1 <3.3.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
- "dependencies": {
- "inherits": {
- "version": "2.0.1",
- "from": "inherits@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
- },
- "minimatch": {
- "version": "0.3.0",
- "from": "minimatch@>=0.3.0 <0.4.0",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
- "dependencies": {
- "lru-cache": {
- "version": "2.7.3",
- "from": "lru-cache@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
- },
- "sigmund": {
- "version": "1.0.1",
- "from": "sigmund@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
- }
- }
- }
- }
- }
- }
- },
- "console-browserify": {
- "version": "1.1.0",
- "from": "console-browserify@>=1.1.0 <1.2.0",
- "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
- "dependencies": {
- "date-now": {
- "version": "0.1.4",
- "from": "date-now@>=0.1.4 <0.2.0",
- "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz"
- }
- }
- },
- "exit": {
- "version": "0.1.2",
- "from": "exit@>=0.1.0 <0.2.0",
- "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
- },
- "htmlparser2": {
- "version": "3.8.3",
- "from": "htmlparser2@>=3.8.0 <3.9.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
- "dependencies": {
- "domhandler": {
- "version": "2.3.0",
- "from": "domhandler@>=2.3.0 <2.4.0",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
- },
- "domutils": {
- "version": "1.5.1",
- "from": "domutils@>=1.5.0 <1.6.0",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
- "dependencies": {
- "dom-serializer": {
- "version": "0.1.0",
- "from": "dom-serializer@>=0.0.0 <1.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
- "dependencies": {
- "domelementtype": {
- "version": "1.1.3",
- "from": "domelementtype@>=1.1.1 <1.2.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz"
- },
- "entities": {
- "version": "1.1.1",
- "from": "entities@>=1.1.1 <1.2.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz"
- }
- }
- }
- }
- },
- "domelementtype": {
- "version": "1.3.0",
- "from": "domelementtype@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz"
- },
- "readable-stream": {
- "version": "1.1.13",
- "from": "readable-stream@>=1.1.0 <1.2.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
- "dependencies": {
- "core-util-is": {
- "version": "1.0.2",
- "from": "core-util-is@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
- },
- "isarray": {
- "version": "0.0.1",
- "from": "isarray@0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
- },
- "string_decoder": {
- "version": "0.10.31",
- "from": "string_decoder@>=0.10.0 <0.11.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
- },
- "inherits": {
- "version": "2.0.1",
- "from": "inherits@>=2.0.1 <2.1.0",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
- }
- }
- },
- "entities": {
- "version": "1.0.0",
- "from": "entities@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz"
- }
- }
- },
- "minimatch": {
- "version": "2.0.10",
- "from": "minimatch@>=2.0.0 <2.1.0",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
- "dependencies": {
- "brace-expansion": {
- "version": "1.1.2",
- "from": "brace-expansion@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.2.tgz",
- "dependencies": {
- "balanced-match": {
- "version": "0.3.0",
- "from": "balanced-match@>=0.3.0 <0.4.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz"
- },
- "concat-map": {
- "version": "0.0.1",
- "from": "concat-map@0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
- }
- }
- }
- }
- },
- "shelljs": {
- "version": "0.3.0",
- "from": "shelljs@>=0.3.0 <0.4.0",
- "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz"
- },
- "strip-json-comments": {
- "version": "1.0.4",
- "from": "strip-json-comments@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz"
- },
- "lodash": {
- "version": "3.7.0",
- "from": "lodash@>=3.7.0 <3.8.0",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.7.0.tgz"
- }
- }
+ "inherits": {
+ "version": "1.0.2",
+ "from": "inherits@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz"
}
}
},
+ "globule": {
+ "version": "0.1.0",
+ "from": "globule@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz",
+ "dependencies": {
+ "lodash": {
+ "version": "1.0.2",
+ "from": "lodash@>=1.0.1 <1.1.0",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz"
+ }
+ }
+ },
+ "graceful-fs": {
+ "version": "1.2.3",
+ "from": "graceful-fs@>=1.2.0 <1.3.0",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz"
+ },
+ "graceful-readlink": {
+ "version": "1.0.1",
+ "from": "graceful-readlink@>=1.0.0",
+ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
+ },
+ "grunt": {
+ "version": "0.4.5",
+ "from": "grunt@0.4.5",
+ "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz",
+ "dependencies": {
+ "async": {
+ "version": "0.1.22",
+ "from": "async@>=0.1.22 <0.2.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz"
+ }
+ }
+ },
+ "grunt-contrib-jshint": {
+ "version": "0.11.3",
+ "from": "grunt-contrib-jshint@0.11.3",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-0.11.3.tgz"
+ },
"grunt-contrib-less": {
"version": "1.1.0",
"from": "grunt-contrib-less@1.1.0",
"from": "async@>=0.9.0 <0.10.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
},
- "chalk": {
- "version": "1.1.1",
- "from": "chalk@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz",
- "dependencies": {
- "ansi-styles": {
- "version": "2.1.0",
- "from": "ansi-styles@>=2.1.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz"
- },
- "escape-string-regexp": {
- "version": "1.0.3",
- "from": "escape-string-regexp@>=1.0.2 <2.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
- },
- "has-ansi": {
- "version": "2.0.0",
- "from": "has-ansi@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
- "dependencies": {
- "ansi-regex": {
- "version": "2.0.0",
- "from": "ansi-regex@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
- }
- }
- },
- "strip-ansi": {
- "version": "3.0.0",
- "from": "strip-ansi@>=3.0.0 <4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz",
- "dependencies": {
- "ansi-regex": {
- "version": "2.0.0",
- "from": "ansi-regex@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
- }
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "from": "supports-color@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
- }
- }
- },
- "less": {
- "version": "2.5.3",
- "from": "less@>=2.5.0 <2.6.0",
- "resolved": "https://registry.npmjs.org/less/-/less-2.5.3.tgz",
- "dependencies": {
- "errno": {
- "version": "0.1.4",
- "from": "errno@>=0.1.1 <0.2.0",
- "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
- "dependencies": {
- "prr": {
- "version": "0.0.0",
- "from": "prr@>=0.0.0 <0.1.0",
- "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz"
- }
- }
- },
- "graceful-fs": {
- "version": "3.0.8",
- "from": "graceful-fs@>=3.0.5 <4.0.0",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz"
- },
- "image-size": {
- "version": "0.3.5",
- "from": "image-size@>=0.3.5 <0.4.0",
- "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.3.5.tgz"
- },
- "mime": {
- "version": "1.3.4",
- "from": "mime@>=1.2.11 <2.0.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz"
- },
- "mkdirp": {
- "version": "0.5.1",
- "from": "mkdirp@>=0.5.0 <0.6.0",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
- "dependencies": {
- "minimist": {
- "version": "0.0.8",
- "from": "minimist@0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
- }
- }
- },
- "promise": {
- "version": "6.1.0",
- "from": "promise@>=6.0.1 <7.0.0",
- "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz",
- "dependencies": {
- "asap": {
- "version": "1.0.0",
- "from": "asap@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz"
- }
- }
- },
- "request": {
- "version": "2.67.0",
- "from": "request@>=2.51.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/request/-/request-2.67.0.tgz",
- "dependencies": {
- "bl": {
- "version": "1.0.0",
- "from": "bl@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz",
- "dependencies": {
- "readable-stream": {
- "version": "2.0.4",
- "from": "readable-stream@>=2.0.0 <2.1.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz",
- "dependencies": {
- "core-util-is": {
- "version": "1.0.2",
- "from": "core-util-is@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
- },
- "inherits": {
- "version": "2.0.1",
- "from": "inherits@>=2.0.1 <2.1.0",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
- },
- "isarray": {
- "version": "0.0.1",
- "from": "isarray@0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
- },
- "process-nextick-args": {
- "version": "1.0.6",
- "from": "process-nextick-args@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz"
- },
- "string_decoder": {
- "version": "0.10.31",
- "from": "string_decoder@>=0.10.0 <0.11.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
- },
- "util-deprecate": {
- "version": "1.0.2",
- "from": "util-deprecate@>=1.0.1 <1.1.0",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
- }
- }
- }
- }
- },
- "caseless": {
- "version": "0.11.0",
- "from": "caseless@>=0.11.0 <0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
- },
- "extend": {
- "version": "3.0.0",
- "from": "extend@>=3.0.0 <3.1.0",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
- },
- "forever-agent": {
- "version": "0.6.1",
- "from": "forever-agent@>=0.6.1 <0.7.0",
- "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
- },
- "form-data": {
- "version": "1.0.0-rc3",
- "from": "form-data@>=1.0.0-rc3 <1.1.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz",
- "dependencies": {
- "async": {
- "version": "1.5.0",
- "from": "async@>=1.4.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
- }
- }
- },
- "json-stringify-safe": {
- "version": "5.0.1",
- "from": "json-stringify-safe@>=5.0.1 <5.1.0",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
- },
- "mime-types": {
- "version": "2.1.8",
- "from": "mime-types@>=2.1.7 <2.2.0",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.8.tgz",
- "dependencies": {
- "mime-db": {
- "version": "1.20.0",
- "from": "mime-db@>=1.20.0 <1.21.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.20.0.tgz"
- }
- }
- },
- "node-uuid": {
- "version": "1.4.7",
- "from": "node-uuid@>=1.4.7 <1.5.0",
- "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz"
- },
- "qs": {
- "version": "5.2.0",
- "from": "qs@>=5.2.0 <5.3.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz"
- },
- "tunnel-agent": {
- "version": "0.4.1",
- "from": "tunnel-agent@>=0.4.1 <0.5.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz"
- },
- "tough-cookie": {
- "version": "2.2.1",
- "from": "tough-cookie@>=2.2.0 <2.3.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.1.tgz"
- },
- "http-signature": {
- "version": "1.1.0",
- "from": "http-signature@>=1.1.0 <1.2.0",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.0.tgz",
- "dependencies": {
- "assert-plus": {
- "version": "0.1.5",
- "from": "assert-plus@>=0.1.5 <0.2.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
- },
- "jsprim": {
- "version": "1.2.2",
- "from": "jsprim@>=1.2.2 <2.0.0",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz",
- "dependencies": {
- "extsprintf": {
- "version": "1.0.2",
- "from": "extsprintf@1.0.2",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
- },
- "json-schema": {
- "version": "0.2.2",
- "from": "json-schema@0.2.2",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz"
- },
- "verror": {
- "version": "1.3.6",
- "from": "verror@1.3.6",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz"
- }
- }
- },
- "sshpk": {
- "version": "1.7.1",
- "from": "sshpk@>=1.7.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.1.tgz",
- "dependencies": {
- "asn1": {
- "version": "0.2.3",
- "from": "asn1@>=0.2.3 <0.3.0",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz"
- },
- "assert-plus": {
- "version": "0.2.0",
- "from": "assert-plus@>=0.2.0 <0.3.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz"
- },
- "dashdash": {
- "version": "1.10.1",
- "from": "dashdash@>=1.10.1 <2.0.0",
- "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.10.1.tgz",
- "dependencies": {
- "assert-plus": {
- "version": "0.1.5",
- "from": "assert-plus@>=0.1.0 <0.2.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
- }
- }
- },
- "jsbn": {
- "version": "0.1.0",
- "from": "jsbn@>=0.1.0 <0.2.0",
- "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz"
- },
- "tweetnacl": {
- "version": "0.13.2",
- "from": "tweetnacl@>=0.13.0 <1.0.0",
- "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.2.tgz"
- },
- "jodid25519": {
- "version": "1.0.2",
- "from": "jodid25519@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz"
- },
- "ecc-jsbn": {
- "version": "0.1.1",
- "from": "ecc-jsbn@>=0.0.1 <1.0.0",
- "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz"
- }
- }
- }
- }
- },
- "oauth-sign": {
- "version": "0.8.0",
- "from": "oauth-sign@>=0.8.0 <0.9.0",
- "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz"
- },
- "hawk": {
- "version": "3.1.2",
- "from": "hawk@>=3.1.0 <3.2.0",
- "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.2.tgz",
- "dependencies": {
- "hoek": {
- "version": "2.16.3",
- "from": "hoek@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
- },
- "boom": {
- "version": "2.10.1",
- "from": "boom@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
- },
- "cryptiles": {
- "version": "2.0.5",
- "from": "cryptiles@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
- },
- "sntp": {
- "version": "1.0.9",
- "from": "sntp@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
- }
- }
- },
- "aws-sign2": {
- "version": "0.6.0",
- "from": "aws-sign2@>=0.6.0 <0.7.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
- },
- "stringstream": {
- "version": "0.0.5",
- "from": "stringstream@>=0.0.4 <0.1.0",
- "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
- },
- "combined-stream": {
- "version": "1.0.5",
- "from": "combined-stream@>=1.0.5 <1.1.0",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
- "dependencies": {
- "delayed-stream": {
- "version": "1.0.0",
- "from": "delayed-stream@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
- }
- }
- },
- "isstream": {
- "version": "0.1.2",
- "from": "isstream@>=0.1.2 <0.2.0",
- "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
- },
- "is-typedarray": {
- "version": "1.0.0",
- "from": "is-typedarray@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
- },
- "har-validator": {
- "version": "2.0.3",
- "from": "har-validator@>=2.0.2 <2.1.0",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.3.tgz",
- "dependencies": {
- "commander": {
- "version": "2.9.0",
- "from": "commander@>=2.9.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
- "dependencies": {
- "graceful-readlink": {
- "version": "1.0.1",
- "from": "graceful-readlink@>=1.0.0",
- "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
- }
- }
- },
- "is-my-json-valid": {
- "version": "2.12.3",
- "from": "is-my-json-valid@>=2.12.3 <3.0.0",
- "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.3.tgz",
- "dependencies": {
- "generate-function": {
- "version": "2.0.0",
- "from": "generate-function@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
- },
- "generate-object-property": {
- "version": "1.2.0",
- "from": "generate-object-property@>=1.1.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
- "dependencies": {
- "is-property": {
- "version": "1.0.2",
- "from": "is-property@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
- }
- }
- },
- "jsonpointer": {
- "version": "2.0.0",
- "from": "jsonpointer@2.0.0",
- "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
- },
- "xtend": {
- "version": "4.0.1",
- "from": "xtend@>=4.0.0 <5.0.0",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
- }
- }
- },
- "pinkie-promise": {
- "version": "2.0.0",
- "from": "pinkie-promise@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz",
- "dependencies": {
- "pinkie": {
- "version": "2.0.1",
- "from": "pinkie@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.1.tgz"
- }
- }
- }
- }
- }
- }
- },
- "source-map": {
- "version": "0.4.4",
- "from": "source-map@>=0.4.2 <0.5.0",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
- "dependencies": {
- "amdefine": {
- "version": "1.0.0",
- "from": "amdefine@>=0.0.4",
- "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
- }
- }
- }
- }
- },
"lodash": {
"version": "3.10.1",
"from": "lodash@>=3.2.0 <4.0.0",
"from": "grunt-contrib-uglify@0.11.0",
"resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-0.11.0.tgz",
"dependencies": {
- "chalk": {
- "version": "1.1.1",
- "from": "chalk@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz",
- "dependencies": {
- "ansi-styles": {
- "version": "2.1.0",
- "from": "ansi-styles@>=2.1.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz"
- },
- "escape-string-regexp": {
- "version": "1.0.3",
- "from": "escape-string-regexp@>=1.0.2 <2.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
- },
- "has-ansi": {
- "version": "2.0.0",
- "from": "has-ansi@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
- "dependencies": {
- "ansi-regex": {
- "version": "2.0.0",
- "from": "ansi-regex@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
- }
- }
- },
- "strip-ansi": {
- "version": "3.0.0",
- "from": "strip-ansi@>=3.0.0 <4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz",
- "dependencies": {
- "ansi-regex": {
- "version": "2.0.0",
- "from": "ansi-regex@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
- }
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "from": "supports-color@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
- }
- }
- },
"lodash": {
"version": "3.10.1",
"from": "lodash@>=3.2.0 <4.0.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
+ }
+ }
+ },
+ "grunt-contrib-watch": {
+ "version": "0.6.1",
+ "from": "grunt-contrib-watch@>=0.6.1 <0.7.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-0.6.1.tgz",
+ "dependencies": {
+ "async": {
+ "version": "0.2.10",
+ "from": "async@>=0.2.9 <0.3.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
},
- "maxmin": {
- "version": "1.1.0",
- "from": "maxmin@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-1.1.0.tgz",
- "dependencies": {
- "figures": {
- "version": "1.4.0",
- "from": "figures@>=1.0.1 <2.0.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-1.4.0.tgz"
- },
- "gzip-size": {
- "version": "1.0.0",
- "from": "gzip-size@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz",
- "dependencies": {
- "concat-stream": {
- "version": "1.5.1",
- "from": "concat-stream@>=1.4.1 <2.0.0",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.1.tgz",
- "dependencies": {
- "inherits": {
- "version": "2.0.1",
- "from": "inherits@>=2.0.1 <2.1.0",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
- },
- "typedarray": {
- "version": "0.0.6",
- "from": "typedarray@>=0.0.5 <0.1.0",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
- },
- "readable-stream": {
- "version": "2.0.4",
- "from": "readable-stream@>=2.0.0 <2.1.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz",
- "dependencies": {
- "core-util-is": {
- "version": "1.0.2",
- "from": "core-util-is@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
- },
- "isarray": {
- "version": "0.0.1",
- "from": "isarray@0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
- },
- "process-nextick-args": {
- "version": "1.0.6",
- "from": "process-nextick-args@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz"
- },
- "string_decoder": {
- "version": "0.10.31",
- "from": "string_decoder@>=0.10.0 <0.11.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
- },
- "util-deprecate": {
- "version": "1.0.2",
- "from": "util-deprecate@>=1.0.1 <1.1.0",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
- }
- }
- }
- }
- },
- "browserify-zlib": {
- "version": "0.1.4",
- "from": "browserify-zlib@>=0.1.4 <0.2.0",
- "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
- "dependencies": {
- "pako": {
- "version": "0.2.8",
- "from": "pako@>=0.2.0 <0.3.0",
- "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.8.tgz"
- }
- }
- }
- }
- },
- "pretty-bytes": {
- "version": "1.0.4",
- "from": "pretty-bytes@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz",
- "dependencies": {
- "get-stdin": {
- "version": "4.0.1",
- "from": "get-stdin@>=4.0.1 <5.0.0",
- "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
- },
- "meow": {
- "version": "3.6.0",
- "from": "meow@>=3.1.0 <4.0.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-3.6.0.tgz",
- "dependencies": {
- "camelcase-keys": {
- "version": "2.0.0",
- "from": "camelcase-keys@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.0.0.tgz",
- "dependencies": {
- "camelcase": {
- "version": "2.0.1",
- "from": "camelcase@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.0.1.tgz"
- },
- "map-obj": {
- "version": "1.0.1",
- "from": "map-obj@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
- }
- }
- },
- "loud-rejection": {
- "version": "1.2.0",
- "from": "loud-rejection@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.2.0.tgz",
- "dependencies": {
- "signal-exit": {
- "version": "2.1.2",
- "from": "signal-exit@>=2.1.2 <3.0.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-2.1.2.tgz"
- }
- }
- },
- "minimist": {
- "version": "1.2.0",
- "from": "minimist@>=1.1.3 <2.0.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
- },
- "normalize-package-data": {
- "version": "2.3.5",
- "from": "normalize-package-data@>=2.3.4 <3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz",
- "dependencies": {
- "hosted-git-info": {
- "version": "2.1.4",
- "from": "hosted-git-info@>=2.1.4 <3.0.0",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz"
- },
- "is-builtin-module": {
- "version": "1.0.0",
- "from": "is-builtin-module@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
- "dependencies": {
- "builtin-modules": {
- "version": "1.1.0",
- "from": "builtin-modules@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz"
- }
- }
- },
- "semver": {
- "version": "5.1.0",
- "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz"
- },
- "validate-npm-package-license": {
- "version": "3.0.1",
- "from": "validate-npm-package-license@>=3.0.1 <4.0.0",
- "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
- "dependencies": {
- "spdx-correct": {
- "version": "1.0.2",
- "from": "spdx-correct@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
- "dependencies": {
- "spdx-license-ids": {
- "version": "1.1.0",
- "from": "spdx-license-ids@>=1.0.2 <2.0.0",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.1.0.tgz"
- }
- }
- },
- "spdx-expression-parse": {
- "version": "1.0.2",
- "from": "spdx-expression-parse@>=1.0.0 <1.1.0",
- "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz",
- "dependencies": {
- "spdx-exceptions": {
- "version": "1.0.4",
- "from": "spdx-exceptions@>=1.0.4 <2.0.0",
- "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz"
- },
- "spdx-license-ids": {
- "version": "1.1.0",
- "from": "spdx-license-ids@>=1.0.2 <2.0.0",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.1.0.tgz"
- }
- }
- }
- }
- }
- }
- },
- "object-assign": {
- "version": "4.0.1",
- "from": "object-assign@>=4.0.1 <5.0.0",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
- },
- "read-pkg-up": {
- "version": "1.0.1",
- "from": "read-pkg-up@>=1.0.1 <2.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
- "dependencies": {
- "find-up": {
- "version": "1.1.0",
- "from": "find-up@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.0.tgz",
- "dependencies": {
- "path-exists": {
- "version": "2.1.0",
- "from": "path-exists@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz"
- },
- "pinkie-promise": {
- "version": "2.0.0",
- "from": "pinkie-promise@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz",
- "dependencies": {
- "pinkie": {
- "version": "2.0.1",
- "from": "pinkie@>=2.0.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.1.tgz"
- }
- }
- }
- }
- },
- "read-pkg": {
- "version": "1.1.0",
- "from": "read-pkg@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
- "dependencies": {
- "load-json-file": {
- "version": "1.1.0",
- "from": "load-json-file@>=1.0.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
- "dependencies": {
- "graceful-fs": {
- "version": "4.1.2",
- "from": "graceful-fs@>=4.1.2 <5.0.0",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz"
- },
- "parse-json": {
- "version": "2.2.0",
- "from": "parse-json@>=2.2.0 <3.0.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
- "dependencies": {
- "error-ex": {
- "version": "1.3.0",
- "from": "error-ex@>=1.2.0 <2.0.0",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz",
- "dependencies": {
- "is-arrayish": {
- "version": "0.2.1",
- "from": "is-arrayish@>=0.2.1 <0.3.0",
- &nbs