mirror of
https://github.com/moodle/moodle.git
synced 2025-04-14 13:02:07 +02:00
MDL-72031 atto_recordrtc: separate out max_time for audio/video files
This commit is contained in:
parent
036800d99d
commit
af67582dbe
lib/editor/atto/plugins/recordrtc
db
lang/en
lib.phpsettings.phpversion.phpyui
build
moodle-atto_recordrtc-button
moodle-atto_recordrtc-button-debug.jsmoodle-atto_recordrtc-button-min.jsmoodle-atto_recordrtc-button.js
moodle-atto_recordrtc-recording
src
48
lib/editor/atto/plugins/recordrtc/db/upgrade.php
Normal file
48
lib/editor/atto/plugins/recordrtc/db/upgrade.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Atto text editor recordrtc upgrade script.
|
||||
*
|
||||
* @package atto_recordrtc
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Upgrade code for the recordrtc atto text editor.
|
||||
*
|
||||
* @param int $oldversion the version we are upgrading from.
|
||||
* @return bool
|
||||
*/
|
||||
function xmldb_atto_recordrtc_upgrade($oldversion) {
|
||||
global $CFG;
|
||||
|
||||
// Change settings from timelimit to audiotimelimit and videotimelimit.
|
||||
require_once($CFG->dirroot . '/lib/editor/atto/plugins/recordrtc/lib.php');
|
||||
if ($oldversion < 2021073000) {
|
||||
$timelimit = get_config('atto_recordrtc', 'timelimit');
|
||||
if ($timelimit != DEFAULT_TIME_LIMIT) {
|
||||
set_config('audiotimelimit', $timelimit, 'atto_recordrtc');
|
||||
set_config('videotimelimit', $timelimit, 'atto_recordrtc');
|
||||
}
|
||||
// Recordrtc savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2021073000, 'atto', 'recordrtc');
|
||||
}
|
||||
return true;
|
||||
}
|
@ -66,8 +66,11 @@ $string['recordrtc:recordvideo'] = 'Record video directly into the text editor';
|
||||
$string['settings'] = 'RecordRTC settings';
|
||||
$string['startrecording'] = 'Start recording';
|
||||
$string['stoprecording'] = 'Stop recording';
|
||||
$string['timelimit'] = 'Time limit in seconds';
|
||||
$string['timelimit_desc'] = 'Maximum recording length allowed for the audio/video clips';
|
||||
$string['audiotimelimit'] = 'Audio time limit in seconds';
|
||||
$string['audiotimelimit_desc'] = 'Maximum recording length allowed for the audio clips';
|
||||
$string['videotimelimit'] = 'Video time limit in seconds';
|
||||
$string['videotimelimit_desc'] = 'Maximum recording length allowed for the video clips';
|
||||
$string['timelimitwarning'] = 'You must enter a number that is greater than 0.';
|
||||
$string['uploadaborted'] = 'Upload aborted:';
|
||||
$string['uploadfailed'] = 'Upload failed:';
|
||||
$string['uploadfailed404'] = 'Upload failed: file too large';
|
||||
@ -75,3 +78,7 @@ $string['uploadprogress'] = 'completed';
|
||||
$string['videobitrate'] = 'Video bitrate';
|
||||
$string['videobitrate_desc'] = 'Quality of video recording (larger number means higher quality)';
|
||||
$string['videortc'] = 'Record video';
|
||||
|
||||
// Deprecated since Moodle 4.0.
|
||||
$string['timelimit'] = 'Time limit in seconds';
|
||||
$string['timelimit_desc'] = 'Maximum recording length allowed for the audio/video clips';
|
||||
|
2
lib/editor/atto/plugins/recordrtc/lang/en/deprecated.txt
Normal file
2
lib/editor/atto/plugins/recordrtc/lang/en/deprecated.txt
Normal file
@ -0,0 +1,2 @@
|
||||
timelimit,atto_recordrtc
|
||||
timelimit_desc,atto_recordrtc
|
@ -26,6 +26,11 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Default maximum recording length allowed for the audio/video clips.
|
||||
*/
|
||||
define('DEFAULT_TIME_LIMIT', 120);
|
||||
|
||||
/**
|
||||
* Set params for this plugin.
|
||||
*
|
||||
@ -43,7 +48,8 @@ function atto_recordrtc_params_for_js($elementid, $options, $fpoptions) {
|
||||
$allowedtypes = get_config('atto_recordrtc', 'allowedtypes');
|
||||
$audiobitrate = get_config('atto_recordrtc', 'audiobitrate');
|
||||
$videobitrate = get_config('atto_recordrtc', 'videobitrate');
|
||||
$timelimit = get_config('atto_recordrtc', 'timelimit');
|
||||
$audiotimelimit = get_config('atto_recordrtc', 'audiotimelimit');
|
||||
$videotimelimit = get_config('atto_recordrtc', 'videotimelimit');
|
||||
|
||||
// Update $allowedtypes to account for capabilities.
|
||||
$audioallowed = $allowedtypes === 'audio' || $allowedtypes === 'both';
|
||||
@ -71,7 +77,9 @@ function atto_recordrtc_params_for_js($elementid, $options, $fpoptions) {
|
||||
'allowedtypes' => $allowedtypes,
|
||||
'audiobitrate' => $audiobitrate,
|
||||
'videobitrate' => $videobitrate,
|
||||
'timelimit' => $timelimit,
|
||||
'audiotimelimit' => $audiotimelimit,
|
||||
'videotimelimit' => $videotimelimit,
|
||||
'defaulttimelimit' => DEFAULT_TIME_LIMIT,
|
||||
'audiortcicon' => $audiortcicon,
|
||||
'videortcicon' => $videortcicon,
|
||||
'maxrecsize' => $maxrecsize
|
||||
|
@ -26,6 +26,9 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
// Needed for constants.
|
||||
require_once($CFG->dirroot . '/lib/editor/atto/plugins/recordrtc/lib.php');
|
||||
|
||||
$ADMIN->add('editoratto', new admin_category('atto_recordrtc', new lang_string('pluginname', 'atto_recordrtc')));
|
||||
|
||||
if ($ADMIN->fulltree) {
|
||||
@ -55,10 +58,29 @@ if ($ADMIN->fulltree) {
|
||||
$setting = new admin_setting_configtext('atto_recordrtc/videobitrate', $name, $desc, $default, PARAM_INT, 8);
|
||||
$settings->add($setting);
|
||||
|
||||
// Recording time limit.
|
||||
$name = get_string('timelimit', 'atto_recordrtc');
|
||||
$desc = get_string('timelimit_desc', 'atto_recordrtc');
|
||||
$default = '120';
|
||||
$setting = new admin_setting_configtext('atto_recordrtc/timelimit', $name, $desc, $default, PARAM_INT, 8);
|
||||
// Audio recording time limit.
|
||||
$name = get_string('audiotimelimit', 'atto_recordrtc');
|
||||
$desc = get_string('audiotimelimit_desc', 'atto_recordrtc');
|
||||
// Validate audiotimelimit greater than 0.
|
||||
$setting = new admin_setting_configduration('atto_recordrtc/audiotimelimit', $name, $desc, DEFAULT_TIME_LIMIT);
|
||||
$setting->set_validate_function(function(int $value): string {
|
||||
if ($value <= 0) {
|
||||
return get_string('timelimitwarning', 'atto_recordrtc');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
$settings->add($setting);
|
||||
|
||||
// Video recording time limit.
|
||||
$name = get_string('videotimelimit', 'atto_recordrtc');
|
||||
$desc = get_string('videotimelimit_desc', 'atto_recordrtc');
|
||||
// Validate videotimelimit greater than 0.
|
||||
$setting = new admin_setting_configduration('atto_recordrtc/videotimelimit', $name, $desc, DEFAULT_TIME_LIMIT);
|
||||
$setting->set_validate_function(function(int $value): string {
|
||||
if ($value <= 0) {
|
||||
return get_string('timelimitwarning', 'atto_recordrtc');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
$settings->add($setting);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2021052500;
|
||||
$plugin->version = 2021073000;
|
||||
$plugin->requires = 2021052500;
|
||||
$plugin->component = 'atto_recordrtc';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
|
@ -329,12 +329,32 @@ Y.namespace('M.atto_recordrtc').Button = Y.Base.create('button', Y.M.editor_atto
|
||||
},
|
||||
|
||||
/**
|
||||
* The timelimit to use when generating this recordrtc.
|
||||
* The audiotimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute timelimit
|
||||
* @attribute audiotimelimit
|
||||
* @type String
|
||||
*/
|
||||
timelimit: {
|
||||
audiotimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
/**
|
||||
* The videotimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute videotimelimit
|
||||
* @type String
|
||||
*/
|
||||
videotimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
/**
|
||||
* The defaulttimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute defaulttimelimit
|
||||
* @type String
|
||||
*/
|
||||
defaulttimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_recordrtc-button",function(s,t){var n="atto_recordrtc";s.namespace("M.atto_recordrtc").Button=s.Base.create("button",s.M.editor_atto.EditorPlugin,[],{_lang:"en",initializer:function(){var t,e,i;if(this.get("host").canShowFilepicker("media")){if(e=!1,"both"!==(t=this.get("allowedtypes"))&&"audio"!==t||(this._addButton("audio",this._audio),e=!0),"both"!==t&&"video"!==t||(this._addButton("video",this._video),e=!0),!e)return;(i=this.getDialogue({width:1e3,focusAfterHide:null})).after("visibleChange",function(){var t=!i.get("visible"),e=M.atto_recordrtc.commonmodule;t&&(window.clearInterval(e.countdownTicker),e.mediaRecorder&&"inactive"!==e.mediaRecorder.state&&e.mediaRecorder.stop(),e.stream&&e.stream.getTracks().forEach(function(t){"ended"!==t.readyState&&t.stop()}),this.getDialogue().set("bodyContent",""))},this),i.on("click",function(){this.centered()}),window.require(["core/adapter"],function(t){window.adapter=t})}},_addButton:function(t,e){this.addButton({buttonName:t,icon:this.get(t+"rtcicon"),iconComponent:n,callback:e,title:t+"rtc",tags:t+"rtc",tagMatchRequiresAll:!1})},_audio:function(){var t=this.getDialogue({focusAfterHide:"audio"});t.set("headerContent",M.util.get_string("audiortc","atto_recordrtc")),t.set("bodyContent",this._createContent("audio")),t.show(),M.atto_recordrtc.audiomodule.init(this)},_video:function(){var t=this.getDialogue({focusAfterHide:"video"});t.set("headerContent",M.util.get_string("videortc","atto_recordrtc")),t.set("bodyContent",this._createContent("video")),t.show(),M.atto_recordrtc.videomodule.init(this)},_createContent:function(t){var e="audio"===t,i="row",o="col-",r="alert-danger",d="btn btn-lg btn-outline-danger btn-block",a=s.Handlebars.compile('<div class="{{PLUGINNAME}} container-fluid"><div class="{{bs_row}} hide"><div class="{{bs_col}}12"><div id="alert-danger" class="alert {{bs_al_dang}}"><strong>{{insecurealert_title}}</strong> {{insecurealert}}</div></div></div><div class="{{bs_row}} hide">{{#if isAudio}}<div class="{{bs_col}}1"></div><div class="{{bs_col}}10"><audio id="player"></audio></div><div class="{{bs_col}}1"></div>{{else}}<div class="{{bs_col}}12"><video id="player"></video></div>{{/if}}</div><div class="{{bs_row}}"><div class="{{bs_col}}1"></div><div class="{{bs_col}}10"><button id="start-stop" class="{{bs_ss_btn}}">{{startrecording}}</button></div><div class="{{bs_col}}1"></div></div><div class="{{bs_row}} hide"><div class="{{bs_col}}3"></div><div class="{{bs_col}}6"><button id="upload" class="btn btn-primary btn-block">{{attachrecording}}</button></div><div class="{{bs_col}}3"></div></div></div>')({PLUGINNAME:n,isAudio:e,bs_row:i,bs_col:o,bs_al_dang:r,bs_ss_btn:d,insecurealert_title:M.util.get_string("insecurealert_title","atto_recordrtc"),insecurealert:M.util.get_string("insecurealert","atto_recordrtc"),startrecording:M.util.get_string("startrecording","atto_recordrtc"),attachrecording:M.util.get_string("attachrecording","atto_recordrtc")});return a},closeDialogue:function(t){t.getDialogue().hide({focusAfterHide:null}),t.editor.focus()},setLink:function(t,e){t.getDialogue().hide({focusAfterHide:null}),t.editor.focus(),t.get("host").insertContentAtFocusPoint(e),t.markUpdated()}},{ATTRS:{contextid:{value:null},sesskey:{value:null},allowedtypes:{value:null},audiobitrate:{value:null},videobitrate:{value:null},timelimit:{value:null},audiortcicon:{value:null},videortcicon:{value:null},maxrecsize:{value:null}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin","moodle-atto_recordrtc-recording"]});
|
||||
YUI.add("moodle-atto_recordrtc-button",function(s,t){var n="atto_recordrtc";s.namespace("M.atto_recordrtc").Button=s.Base.create("button",s.M.editor_atto.EditorPlugin,[],{_lang:"en",initializer:function(){var t,e,i;if(this.get("host").canShowFilepicker("media")){if(e=!1,"both"!==(t=this.get("allowedtypes"))&&"audio"!==t||(this._addButton("audio",this._audio),e=!0),"both"!==t&&"video"!==t||(this._addButton("video",this._video),e=!0),!e)return;(i=this.getDialogue({width:1e3,focusAfterHide:null})).after("visibleChange",function(){var t=!i.get("visible"),e=M.atto_recordrtc.commonmodule;t&&(window.clearInterval(e.countdownTicker),e.mediaRecorder&&"inactive"!==e.mediaRecorder.state&&e.mediaRecorder.stop(),e.stream&&e.stream.getTracks().forEach(function(t){"ended"!==t.readyState&&t.stop()}),this.getDialogue().set("bodyContent",""))},this),i.on("click",function(){this.centered()}),window.require(["core/adapter"],function(t){window.adapter=t})}},_addButton:function(t,e){this.addButton({buttonName:t,icon:this.get(t+"rtcicon"),iconComponent:n,callback:e,title:t+"rtc",tags:t+"rtc",tagMatchRequiresAll:!1})},_audio:function(){var t=this.getDialogue({focusAfterHide:"audio"});t.set("headerContent",M.util.get_string("audiortc","atto_recordrtc")),t.set("bodyContent",this._createContent("audio")),t.show(),M.atto_recordrtc.audiomodule.init(this)},_video:function(){var t=this.getDialogue({focusAfterHide:"video"});t.set("headerContent",M.util.get_string("videortc","atto_recordrtc")),t.set("bodyContent",this._createContent("video")),t.show(),M.atto_recordrtc.videomodule.init(this)},_createContent:function(t){var e="audio"===t,i="row",o="col-",r="alert-danger",a="btn btn-lg btn-outline-danger btn-block",d=s.Handlebars.compile('<div class="{{PLUGINNAME}} container-fluid"><div class="{{bs_row}} hide"><div class="{{bs_col}}12"><div id="alert-danger" class="alert {{bs_al_dang}}"><strong>{{insecurealert_title}}</strong> {{insecurealert}}</div></div></div><div class="{{bs_row}} hide">{{#if isAudio}}<div class="{{bs_col}}1"></div><div class="{{bs_col}}10"><audio id="player"></audio></div><div class="{{bs_col}}1"></div>{{else}}<div class="{{bs_col}}12"><video id="player"></video></div>{{/if}}</div><div class="{{bs_row}}"><div class="{{bs_col}}1"></div><div class="{{bs_col}}10"><button id="start-stop" class="{{bs_ss_btn}}">{{startrecording}}</button></div><div class="{{bs_col}}1"></div></div><div class="{{bs_row}} hide"><div class="{{bs_col}}3"></div><div class="{{bs_col}}6"><button id="upload" class="btn btn-primary btn-block">{{attachrecording}}</button></div><div class="{{bs_col}}3"></div></div></div>')({PLUGINNAME:n,isAudio:e,bs_row:i,bs_col:o,bs_al_dang:r,bs_ss_btn:a,insecurealert_title:M.util.get_string("insecurealert_title","atto_recordrtc"),insecurealert:M.util.get_string("insecurealert","atto_recordrtc"),startrecording:M.util.get_string("startrecording","atto_recordrtc"),attachrecording:M.util.get_string("attachrecording","atto_recordrtc")});return d},closeDialogue:function(t){t.getDialogue().hide({focusAfterHide:null}),t.editor.focus()},setLink:function(t,e){t.getDialogue().hide({focusAfterHide:null}),t.editor.focus(),t.get("host").insertContentAtFocusPoint(e),t.markUpdated()}},{ATTRS:{contextid:{value:null},sesskey:{value:null},allowedtypes:{value:null},audiobitrate:{value:null},videobitrate:{value:null},audiotimelimit:{value:null},videotimelimit:{value:null},defaulttimelimit:{value:null},audiortcicon:{value:null},videortcicon:{value:null},maxrecsize:{value:null}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin","moodle-atto_recordrtc-recording"]});
|
26
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button.js
vendored
26
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button.js
vendored
@ -329,12 +329,32 @@ Y.namespace('M.atto_recordrtc').Button = Y.Base.create('button', Y.M.editor_atto
|
||||
},
|
||||
|
||||
/**
|
||||
* The timelimit to use when generating this recordrtc.
|
||||
* The audiotimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute timelimit
|
||||
* @attribute audiotimelimit
|
||||
* @type String
|
||||
*/
|
||||
timelimit: {
|
||||
audiotimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
/**
|
||||
* The videotimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute videotimelimit
|
||||
* @type String
|
||||
*/
|
||||
videotimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
/**
|
||||
* The defaulttimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute defaulttimelimit
|
||||
* @type String
|
||||
*/
|
||||
defaulttimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
|
@ -158,7 +158,14 @@ M.atto_recordrtc.commonmodule = {
|
||||
cm.player.set('muted', true);
|
||||
|
||||
// Set recording timer to the time specified in the settings.
|
||||
cm.countdownSeconds = cm.editorScope.get('timelimit');
|
||||
if (type === 'audio') {
|
||||
cm.countdownSeconds = cm.editorScope.get('audiotimelimit');
|
||||
} else if (type === 'video') {
|
||||
cm.countdownSeconds = cm.editorScope.get('videotimelimit');
|
||||
} else {
|
||||
// Default timer.
|
||||
cm.countdownSeconds = cm.editorScope.get('defaulttimelimit');
|
||||
}
|
||||
cm.countdownSeconds++;
|
||||
var timerText = M.util.get_string('stoprecording', 'atto_recordrtc');
|
||||
timerText += ' (<span id="minutes"></span>:<span id="seconds"></span>)';
|
||||
|
File diff suppressed because one or more lines are too long
@ -158,7 +158,14 @@ M.atto_recordrtc.commonmodule = {
|
||||
cm.player.set('muted', true);
|
||||
|
||||
// Set recording timer to the time specified in the settings.
|
||||
cm.countdownSeconds = cm.editorScope.get('timelimit');
|
||||
if (type === 'audio') {
|
||||
cm.countdownSeconds = cm.editorScope.get('audiotimelimit');
|
||||
} else if (type === 'video') {
|
||||
cm.countdownSeconds = cm.editorScope.get('videotimelimit');
|
||||
} else {
|
||||
// Default timer.
|
||||
cm.countdownSeconds = cm.editorScope.get('defaulttimelimit');
|
||||
}
|
||||
cm.countdownSeconds++;
|
||||
var timerText = M.util.get_string('stoprecording', 'atto_recordrtc');
|
||||
timerText += ' (<span id="minutes"></span>:<span id="seconds"></span>)';
|
||||
|
@ -327,12 +327,32 @@ Y.namespace('M.atto_recordrtc').Button = Y.Base.create('button', Y.M.editor_atto
|
||||
},
|
||||
|
||||
/**
|
||||
* The timelimit to use when generating this recordrtc.
|
||||
* The audiotimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute timelimit
|
||||
* @attribute audiotimelimit
|
||||
* @type String
|
||||
*/
|
||||
timelimit: {
|
||||
audiotimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
/**
|
||||
* The videotimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute videotimelimit
|
||||
* @type String
|
||||
*/
|
||||
videotimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
/**
|
||||
* The defaulttimelimit to use when generating this recordrtc.
|
||||
*
|
||||
* @attribute defaulttimelimit
|
||||
* @type String
|
||||
*/
|
||||
defaulttimelimit: {
|
||||
value: null
|
||||
},
|
||||
|
||||
|
@ -156,7 +156,14 @@ M.atto_recordrtc.commonmodule = {
|
||||
cm.player.set('muted', true);
|
||||
|
||||
// Set recording timer to the time specified in the settings.
|
||||
cm.countdownSeconds = cm.editorScope.get('timelimit');
|
||||
if (type === 'audio') {
|
||||
cm.countdownSeconds = cm.editorScope.get('audiotimelimit');
|
||||
} else if (type === 'video') {
|
||||
cm.countdownSeconds = cm.editorScope.get('videotimelimit');
|
||||
} else {
|
||||
// Default timer.
|
||||
cm.countdownSeconds = cm.editorScope.get('defaulttimelimit');
|
||||
}
|
||||
cm.countdownSeconds++;
|
||||
var timerText = M.util.get_string('stoprecording', 'atto_recordrtc');
|
||||
timerText += ' (<span id="minutes"></span>:<span id="seconds"></span>)';
|
||||
|
Loading…
x
Reference in New Issue
Block a user