mirror of
https://github.com/myvesta/vesta
synced 2025-07-16 10:03:23 -07:00
FileManger stuff
This commit is contained in:
parent
b6dd534b60
commit
52db853acf
11 changed files with 417 additions and 131 deletions
54
bin/v-check-fs-permission
Executable file
54
bin/v-check-fs-permission
Executable file
|
@ -0,0 +1,54 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# info: open file
|
||||||
|
# options: USER FILE
|
||||||
|
#
|
||||||
|
# The function opens/reads files on the file system
|
||||||
|
|
||||||
|
user=$1
|
||||||
|
src_file=$2
|
||||||
|
|
||||||
|
# Checking arguments
|
||||||
|
if [ -z "$src_file" ]; then
|
||||||
|
echo "Usage: USER FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Checking vesta user
|
||||||
|
if [ ! -e "$VESTA/data/users/$user" ]; then
|
||||||
|
echo "Error: vesta user $user doesn't exist"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Checking user homedir
|
||||||
|
homedir=$(grep "^$user:" /etc/passwd | cut -f 6 -d :)
|
||||||
|
if [ -z $homedir ]; then
|
||||||
|
echo "Error: user home directory doesn't exist"
|
||||||
|
exit 12
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Checking path
|
||||||
|
if [ ! -z "$src_file" ]; then
|
||||||
|
rpath=$(readlink -f "$src_file")
|
||||||
|
if [ -z "$(echo $rpath |egrep "^/tmp|^$homedir")" ]; then
|
||||||
|
echo "Error: invalid source path $src_file"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reading file
|
||||||
|
#sudo -u $user cat "$src_file" 2>/dev/null
|
||||||
|
#if [ $? -ne 0 ]; then
|
||||||
|
# echo "Error: file $src_file was not opened"
|
||||||
|
# exit 3
|
||||||
|
#fi
|
||||||
|
|
||||||
|
# Checking if file has readable permission
|
||||||
|
if [[ ! -r $src_file ]]
|
||||||
|
then
|
||||||
|
# echo "File is readable"
|
||||||
|
#else
|
||||||
|
echo "Cannot read file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Exiting
|
||||||
|
exit
|
53
web/css/styles.min.css
vendored
53
web/css/styles.min.css
vendored
|
@ -2787,3 +2787,56 @@ form#vstobjects.suspended {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 140px;
|
width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 25px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
margin-left: 50px;
|
||||||
|
}
|
||||||
|
.description ul{
|
||||||
|
margin-top: 15px;
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description li{
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description a {
|
||||||
|
line-height: 30px;
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #2c9491;
|
||||||
|
}
|
||||||
|
.description a.purchase {
|
||||||
|
color: #86A307;
|
||||||
|
background-color: #9fbf0c;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 7px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.description a.purchase:hover {
|
||||||
|
background-color: #c0e60f;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.description .licence {
|
||||||
|
padding: 20px 0;
|
||||||
|
color: #2c9491;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description .licence input {
|
||||||
|
margin-left: 17px;
|
||||||
|
width: 137px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description span {
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 45px;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,17 @@ session_start();
|
||||||
|
|
||||||
include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
|
include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
|
||||||
/*
|
/*
|
||||||
|
if (empty($panel)) {
|
||||||
|
$command = VESTA_CMD."v-list-user '".$user."' 'json'";
|
||||||
|
exec ($command, $output, $return_var);
|
||||||
|
if ( $return_var > 0 ) {
|
||||||
|
header("Location: /error/");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$panel = json_decode(implode('', $output), true);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/*
|
||||||
// Check user session
|
// Check user session
|
||||||
if ((!isset($_SESSION['user'])) && (!defined('NO_AUTH_REQUIRED'))) {
|
if ((!isset($_SESSION['user'])) && (!defined('NO_AUTH_REQUIRED'))) {
|
||||||
$_SESSION['request_uri'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['request_uri'] = $_SERVER['REQUEST_URI'];
|
||||||
|
@ -23,13 +34,15 @@ if ((!isset($_SESSION['user'])) && (!defined('NO_AUTH_REQUIRED'))) {
|
||||||
<script src="/js/cheef-editor/ace/mode-ruby.js"></script>
|
<script src="/js/cheef-editor/ace/mode-ruby.js"></script>
|
||||||
<script src="/js/cheef-editor/jquery-ace.min.js"></script>
|
<script src="/js/cheef-editor/jquery-ace.min.js"></script>
|
||||||
|
|
||||||
|
<div id="message" style="display:none; position: absoulte;background-color: green; color: white; padding: 10px;"></div>
|
||||||
|
<div id="error-message" style="display:none; position: absoulte;background-color: red; color: white; padding: 10px;"></div>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (!empty($_REQUEST['path'])) {
|
if (!empty($_REQUEST['path'])) {
|
||||||
$content = '';
|
$content = '';
|
||||||
$path = $_REQUEST['path'];
|
$path = $_REQUEST['path'];
|
||||||
if (is_readable($path)) {
|
if (is_readable($path)) {
|
||||||
|
|
||||||
$image = getimagesize($path) ? true : false;
|
$image = getimagesize($path) ? true : false;
|
||||||
|
|
||||||
if ($image) {
|
if ($image) {
|
||||||
|
@ -49,8 +62,16 @@ if ((!isset($_SESSION['user'])) && (!defined('NO_AUTH_REQUIRED'))) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = file_get_contents($path);
|
// $content = file_get_contents($path);
|
||||||
$content = $content;
|
// v-open-fs-file
|
||||||
|
|
||||||
|
exec (VESTA_CMD . "v-open-fs-file {$user} {$path}", $content, $return_var);
|
||||||
|
if ($return_var != 0) {
|
||||||
|
print 'Error while opening file'; // todo: handle this more styled
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = implode("\n", $content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -59,18 +80,78 @@ if ((!isset($_SESSION['user'])) && (!defined('NO_AUTH_REQUIRED'))) {
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<form method="post">
|
<form id="edit-file-form" method="post">
|
||||||
|
<!-- input id="do-backup" type="button" onClick="javascript:void(0);" name="save" value="backup (ctrl+F2)" class="backup" / -->
|
||||||
<input type="submit" name="save" value="Save" class="save" />
|
<input type="submit" name="save" value="Save" class="save" />
|
||||||
|
|
||||||
|
|
||||||
<textarea name="contents" class="editor" id="editor" rows="4" style="display:none;width: 100%; height: 100%;"><?php echo $content ?></textarea>
|
<textarea name="contents" class="editor" id="editor" rows="4" style="display:none;width: 100%; height: 100%;"><?php echo $content ?></textarea>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<script>
|
|
||||||
|
<script type="text/javascript" src="/js/hotkeys.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
$('.editor').ace({ theme: 'twilight', lang: 'ruby' });
|
$('.editor').ace({ theme: 'twilight', lang: 'ruby' });
|
||||||
|
|
||||||
var dcrt = $('#editor').data('ace');
|
var dcrt = $('#editor').data('ace');
|
||||||
var editor = dcrt.editor.ace;
|
var editor = dcrt.editor.ace;
|
||||||
editor.gotoLine(0);
|
editor.gotoLine(0);
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
|
||||||
|
|
||||||
|
var makeBackup = function() {
|
||||||
|
var params = {
|
||||||
|
action: 'backup',
|
||||||
|
path: '<?= $path ?>'
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({url: "/file_manager/fm_api.php",
|
||||||
|
method: "POST",
|
||||||
|
data: params,
|
||||||
|
dataType: 'JSON',
|
||||||
|
success: function(reply) {
|
||||||
|
var fadeTimeout = 3000;
|
||||||
|
if (reply.result) {
|
||||||
|
$('#message').text('File backed up as ' + reply.filename);
|
||||||
|
clearTimeout(window.msg_tmt);
|
||||||
|
$('#message').show();
|
||||||
|
window.msg_tmt = setTimeout(function() {$('#message').fadeOut();}, fadeTimeout);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#error-message').text(reply.message);
|
||||||
|
clearTimeout(window.errmsg_tmt);
|
||||||
|
$('#error-message').show();
|
||||||
|
window.errmsg_tmt = setTimeout(function() {$('#error-message').fadeOut();}, fadeTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#do-backup').on('click', function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
makeBackup();
|
||||||
|
});
|
||||||
|
//
|
||||||
|
// Shortcuts
|
||||||
|
//
|
||||||
|
shortcut.add("Ctrl+s",function() {
|
||||||
|
var inp = $('<input>').attr({'type': 'hidden', 'name': 'save'}).val('Save');
|
||||||
|
$('#edit-file-form').append(inp);
|
||||||
|
$('#edit-file-form').submit();
|
||||||
|
},{
|
||||||
|
'type': 'keydown',
|
||||||
|
'propagate': false,
|
||||||
|
'disable_in_input': false,
|
||||||
|
'target': document
|
||||||
|
});
|
||||||
|
shortcut.add("Ctrl+F2",function() {
|
||||||
|
makeBackup();
|
||||||
|
},{
|
||||||
|
'type': 'keydown',
|
||||||
|
'propagate': false,
|
||||||
|
'disable_in_input': false,
|
||||||
|
'target': document
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -97,6 +97,10 @@ switch ($_REQUEST['action']) {
|
||||||
$item = $_REQUEST['item'];
|
$item = $_REQUEST['item'];
|
||||||
print json_encode($fm->packItem($item, $dir, $target_dir, $filename));
|
print json_encode($fm->packItem($item, $dir, $target_dir, $filename));
|
||||||
break;
|
break;
|
||||||
|
case 'backup':
|
||||||
|
$path = $_REQUEST['path'];
|
||||||
|
print json_encode($fm->backupItem($path));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
//print json_encode($fm->init());
|
//print json_encode($fm->init());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -207,6 +207,47 @@ class FileManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function backupItem($item) {
|
||||||
|
|
||||||
|
$src_item = $this->formatFullPath($item);
|
||||||
|
|
||||||
|
$dst_item_name = $item . '~' . date('Ymd_His');
|
||||||
|
|
||||||
|
$dst_item = $this->formatFullPath($dst_item_name);
|
||||||
|
|
||||||
|
//print VESTA_CMD . "v-add-fs-archive {$this->user} {$item} {$dst_item}";die();
|
||||||
|
exec (VESTA_CMD . "v-copy-fs-file {$this->user} {$src_item} {$dst_item}", $output, $return_var);
|
||||||
|
|
||||||
|
$error = self::check_return_code($return_var, $output);
|
||||||
|
|
||||||
|
if (empty($error)) {
|
||||||
|
return array(
|
||||||
|
'result' => true,
|
||||||
|
'filename' => $dst_item_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return array(
|
||||||
|
'result' => false,
|
||||||
|
'message' => $error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = self::check_return_code($return_var, $output);
|
||||||
|
|
||||||
|
if (empty($error)) {
|
||||||
|
return array(
|
||||||
|
'result' => true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return array(
|
||||||
|
'result' => false,
|
||||||
|
'message' => $error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function unpackItem($item, $dir, $target_dir, $filename) {
|
function unpackItem($item, $dir, $target_dir, $filename) {
|
||||||
$item = $this->formatFullPath($item);
|
$item = $this->formatFullPath($item);
|
||||||
$dst_item = $this->formatFullPath($target_dir);
|
$dst_item = $this->formatFullPath($target_dir);
|
||||||
|
|
|
@ -1,7 +1,22 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
|
||||||
|
|
||||||
|
// todo: set in session?
|
||||||
|
if (empty($panel)) {
|
||||||
|
$command = VESTA_CMD."v-list-user '".$user."' 'json'";
|
||||||
|
exec ($command, $output, $return_var);
|
||||||
|
if ( $return_var > 0 ) {
|
||||||
|
header("Location: /error/");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$panel = json_decode(implode('', $output), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Define a destination
|
// Define a destination
|
||||||
$targetFolder = '/home/admin/'; // Relative to the root
|
//$targetFolder = '/home/admin/'; // Relative to the root
|
||||||
|
$targetFolder = $panel[$user]['HOME']; // Relative to the root
|
||||||
|
|
||||||
$verifyToken = md5('unique_salt' . $_POST['timestamp']);
|
$verifyToken = md5('unique_salt' . $_POST['timestamp']);
|
||||||
|
|
||||||
|
@ -10,16 +25,14 @@ if (!empty($_FILES) && $_POST['token'] == $verifyToken) {
|
||||||
$targetPath = $targetFolder;
|
$targetPath = $targetFolder;
|
||||||
$targetFile = rtrim($targetPath,'/') . '/' . $_FILES['Filedata']['name'];
|
$targetFile = rtrim($targetPath,'/') . '/' . $_FILES['Filedata']['name'];
|
||||||
|
|
||||||
// Validate the file type
|
exec (VESTA_CMD . "v-copy-fs-file {$user} {$tempFile} {$targetFile}", $output, $return_var);
|
||||||
//$fileTypes = array('jpg','jpeg','gif','png'); // File extensions
|
|
||||||
//$fileParts = pathinfo($_FILES['Filedata']['name']);
|
|
||||||
|
|
||||||
//if (in_array($fileParts['extension'],$fileTypes)) {
|
$error = self::check_return_code($return_var, $output);
|
||||||
move_uploaded_file($tempFile,$targetFile);
|
if ($return_var != 0) {
|
||||||
|
echo '0';
|
||||||
|
} else {
|
||||||
echo '1';
|
echo '1';
|
||||||
//} else {
|
}
|
||||||
// echo 'Invalid file type.';
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -1106,7 +1106,7 @@ FM.unpackItem = function() {
|
||||||
|
|
||||||
var tpl = Tpl.get('popup_unpack', 'FM');
|
var tpl = Tpl.get('popup_unpack', 'FM');
|
||||||
tpl.set(':FILENAME', src.name);
|
tpl.set(':FILENAME', src.name);
|
||||||
tpl.set(':DST_DIRNAME', dst + '/' + src.name + '_extracted');
|
tpl.set(':DST_DIRNAME', dst + '/' + src.name);
|
||||||
FM.popupOpen(tpl.finalize());
|
FM.popupOpen(tpl.finalize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,12 @@
|
||||||
define('NO_AUTH_REQUIRED',true);
|
define('NO_AUTH_REQUIRED',true);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Main include
|
// Main include
|
||||||
include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
|
include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
|
||||||
|
|
||||||
|
//echo $_SESSION['request_uri'];
|
||||||
|
|
||||||
|
|
||||||
$TAB = 'LOGIN';
|
$TAB = 'LOGIN';
|
||||||
|
|
||||||
|
|
|
@ -684,8 +684,21 @@
|
||||||
<td>
|
<td>
|
||||||
<select class="vst-list" name="v_filemanager">
|
<select class="vst-list" name="v_filemanager">
|
||||||
<option value='no'><?php print __('no'); ?></option>
|
<option value='no'><?php print __('no'); ?></option>
|
||||||
|
<option value='yes'><?php print __('yes'); ?></option>
|
||||||
</select>
|
</select>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
<div class="filemanager description" style="display:none">
|
||||||
|
Web Filemanager is a commercial module. By purchasing it you not only get powerfull filemanagement tool but also help VestaCP get better and stronger.<br>
|
||||||
|
You can see the bref video review of the module functionality.<br>
|
||||||
|
<span>(to try it free for a week please use "TMKE765" as a licence key)</span>
|
||||||
|
<div class="licence">
|
||||||
|
To activate filemanager insert licence key: <input type="text" class="vst-input" name="v_filemanager_licence" /><br>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li><a class="purchase" href="read.com">get monthly licence 3$/month</a></li>
|
||||||
|
<li><a class="purchase" href="read.com">get forever licence 50$</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -700,7 +713,7 @@
|
||||||
<input type="submit" class="button" name="save" value="<?php print __('Save');?>">
|
<input type="submit" class="button" name="save" value="<?php print __('Save');?>">
|
||||||
</td>
|
</td>
|
||||||
<td class="step-top">
|
<td class="step-top">
|
||||||
<input type="button" class="button" value="<?php print __('Back');?>" onclick="<?php echo $back ?>">
|
<input type="button" class="button cancel" value="<?php print __('Back');?>" onclick="<?php echo $back ?>">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -709,3 +722,15 @@
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('select[name=v_filemanager]').change(function(){
|
||||||
|
if($(this).val() == 'yes'){
|
||||||
|
$('.filemanager.description').show();
|
||||||
|
} else {
|
||||||
|
$('.filemanager.description').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -475,6 +475,8 @@
|
||||||
FM['CURRENT_A_LINE'] = 0;
|
FM['CURRENT_A_LINE'] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FM.preselectedItems.A = [];
|
||||||
|
|
||||||
/*FM.setTabActive(FM.TAB_A, 'skip_highlights');
|
/*FM.setTabActive(FM.TAB_A, 'skip_highlights');
|
||||||
$(".listing-left .selected, .listing-left .ui-selectee").each(function(i, o) {
|
$(".listing-left .selected, .listing-left .ui-selectee").each(function(i, o) {
|
||||||
if (!$(o).hasClass('ui-selected')) {
|
if (!$(o).hasClass('ui-selected')) {
|
||||||
|
@ -539,6 +541,8 @@
|
||||||
FM['CURRENT_B_LINE'] = 0;
|
FM['CURRENT_B_LINE'] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FM.preselectedItems.B = [];
|
||||||
|
|
||||||
},
|
},
|
||||||
unselected: function (event, ui) {
|
unselected: function (event, ui) {
|
||||||
FM.setTabActive(FM.TAB_B, 'skip_highlights');
|
FM.setTabActive(FM.TAB_B, 'skip_highlights');
|
||||||
|
|
|
@ -1,7 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
|
||||||
|
|
||||||
if (!empty($_REQUEST['path'])) {
|
if (!empty($_REQUEST['path'])) {
|
||||||
$path = $_REQUEST['path'];
|
$path = $_REQUEST['path'];
|
||||||
if (is_readable($path) && !empty($_REQUEST['raw'])) {
|
if (is_readable($path) && !empty($_REQUEST['raw'])) {
|
||||||
|
//print file_get_contents($path);
|
||||||
|
exec (VESTA_CMD . "v-check-fs-permission {$user} {$path}", $content, $return_var);
|
||||||
|
|
||||||
|
if ($return_var != 0) {
|
||||||
|
print 'Error while opening file'; // todo: handle this more styled
|
||||||
|
exit;
|
||||||
|
}
|
||||||
header('content-type: image/jpeg');
|
header('content-type: image/jpeg');
|
||||||
print file_get_contents($path);
|
print file_get_contents($path);
|
||||||
exit;
|
exit;
|
||||||
|
@ -11,9 +21,7 @@ else {
|
||||||
die('File not found');
|
die('File not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue