mirror of
https://github.com/PrivateBin/PrivateBin.git
synced 2024-03-22 13:10:41 +08:00
Merge branch 'Haocen-398'
This commit is contained in:
commit
ffe26a8841
|
@ -1,6 +1,7 @@
|
|||
# PrivateBin version history
|
||||
|
||||
* **1.4 (not yet released)**
|
||||
* ADDED: Option to send a mail with the link, when creating a paste (#398)
|
||||
* FIXED: Password disabling option (#527)
|
||||
* **1.3.1 (2019-09-22)**
|
||||
* ADDED: Translation for Bulgarian (#455)
|
||||
|
|
|
@ -180,3 +180,28 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 {
|
|||
padding-right: 4ch;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
margin: auto !important;
|
||||
}
|
||||
|
||||
/* makeup for the original margin on modal-dialog */
|
||||
@media (min-width: 768px) {
|
||||
.modal-content {
|
||||
margin: 30px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal .modal-content button {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
|
BIN
img/icon_email.png
Normal file
BIN
img/icon_email.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 253 B |
259
js/privatebin.js
259
js/privatebin.js
|
@ -403,6 +403,45 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
baseUri = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate expiration date given initial date and expiration period
|
||||
*
|
||||
* @name Helper.calculateExpirationDate
|
||||
* @function
|
||||
* @param {Date} initialDate - may not be empty
|
||||
* @param {string|number} expirationDisplayStringOrSecondsToExpire - may not be empty
|
||||
* @return {Date}
|
||||
*/
|
||||
me.calculateExpirationDate = function(initialDate, expirationDisplayStringOrSecondsToExpire) {
|
||||
let expirationDate = new Date(initialDate);
|
||||
|
||||
const expirationDisplayStringToSecondsDict = {
|
||||
'5min': 300,
|
||||
'10min': 600,
|
||||
'1hour': 3500,
|
||||
'1day': 86400,
|
||||
'1week': 604800,
|
||||
'1month': 2592000,
|
||||
'1year': 31536000,
|
||||
'never': 0
|
||||
};
|
||||
|
||||
let secondsToExpiration = expirationDisplayStringOrSecondsToExpire;
|
||||
if (typeof expirationDisplayStringOrSecondsToExpire === 'string') {
|
||||
secondsToExpiration = expirationDisplayStringToSecondsDict[expirationDisplayStringOrSecondsToExpire];
|
||||
}
|
||||
|
||||
if (typeof secondsToExpiration !== 'number') {
|
||||
throw new Error('Cannot calculate expiration date.');
|
||||
}
|
||||
if (secondsToExpiration === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
expirationDate = expirationDate.setUTCSeconds(expirationDate.getUTCSeconds() + secondsToExpiration);
|
||||
return expirationDate;
|
||||
}
|
||||
|
||||
return me;
|
||||
})();
|
||||
|
||||
|
@ -1822,7 +1861,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
`${$shortenButton.data('shortener')}${encodeURIComponent($pasteUrl.attr('href'))}`,
|
||||
'_blank',
|
||||
'noopener, noreferrer'
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3332,9 +3371,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$passwordInput,
|
||||
$rawTextButton,
|
||||
$qrCodeLink,
|
||||
$emailLink,
|
||||
$sendButton,
|
||||
$retryButton,
|
||||
pasteExpiration = '1week',
|
||||
pasteExpiration = null,
|
||||
retryButtonCallback;
|
||||
|
||||
/**
|
||||
|
@ -3542,6 +3582,133 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$('#qrcode-display').html(qrCanvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Template Email body.
|
||||
*
|
||||
* @name TopNav.templateEmailBody
|
||||
* @private
|
||||
* @param {string} expirationDateString
|
||||
* @param {bool} isBurnafterreading
|
||||
*/
|
||||
function templateEmailBody(expirationDateString, isBurnafterreading)
|
||||
{
|
||||
const EOL = '\n';
|
||||
const BULLET = ' - ';
|
||||
let emailBody = '';
|
||||
if (expirationDateString !== null || isBurnafterreading) {
|
||||
emailBody += I18n._('Notice:');
|
||||
emailBody += EOL;
|
||||
|
||||
if (expirationDateString !== null) {
|
||||
emailBody += EOL;
|
||||
emailBody += BULLET;
|
||||
emailBody += I18n._(
|
||||
'This link will expire after %s.',
|
||||
expirationDateString
|
||||
);
|
||||
}
|
||||
if (isBurnafterreading) {
|
||||
emailBody += EOL;
|
||||
emailBody += BULLET;
|
||||
emailBody += I18n._(
|
||||
'This link can only be accessed once, do not use back or refresh button in your browser.'
|
||||
);
|
||||
}
|
||||
|
||||
emailBody += EOL;
|
||||
emailBody += EOL;
|
||||
}
|
||||
emailBody += I18n._('Link:');
|
||||
emailBody += EOL;
|
||||
emailBody += `${window.location.href}`;
|
||||
return emailBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger Email send.
|
||||
*
|
||||
* @name TopNav.triggerEmailSend
|
||||
* @private
|
||||
* @param {string} emailBody
|
||||
*/
|
||||
function triggerEmailSend(emailBody)
|
||||
{
|
||||
window.open(
|
||||
`mailto:?body=${encodeURIComponent(emailBody)}`,
|
||||
'_self',
|
||||
'noopener, noreferrer'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Email with current paste (URL).
|
||||
*
|
||||
* @name TopNav.sendEmail
|
||||
* @private
|
||||
* @function
|
||||
* @param {Date|null} expirationDate date of expiration
|
||||
* @param {bool} isBurnafterreading whether it is burn after reading
|
||||
*/
|
||||
function sendEmail(expirationDate, isBurnafterreading)
|
||||
{
|
||||
const expirationDateRoundedToSecond = new Date(expirationDate);
|
||||
|
||||
// round down at least 30 seconds to make up for the delay of request
|
||||
expirationDateRoundedToSecond.setUTCSeconds(
|
||||
expirationDateRoundedToSecond.getUTCSeconds() - 30
|
||||
);
|
||||
expirationDateRoundedToSecond.setUTCSeconds(0);
|
||||
|
||||
const $emailconfirmmodal = $('#emailconfirmmodal');
|
||||
if ($emailconfirmmodal.length > 0) {
|
||||
if (expirationDate !== null) {
|
||||
$emailconfirmmodal.find('#emailconfirm-display').text(
|
||||
I18n._('Recipient may become aware of your timezone, convert time to UTC?')
|
||||
);
|
||||
const $emailconfirmTimezoneCurrent = $emailconfirmmodal.find('#emailconfirm-timezone-current');
|
||||
const $emailconfirmTimezoneUtc = $emailconfirmmodal.find('#emailconfirm-timezone-utc');
|
||||
$emailconfirmTimezoneCurrent.off('click.sendEmailCurrentTimezone');
|
||||
$emailconfirmTimezoneCurrent.on('click.sendEmailCurrentTimezone', function(expirationDateRoundedToSecond, isBurnafterreading) {
|
||||
return function() {
|
||||
const emailBody = templateEmailBody(expirationDateRoundedToSecond.toLocaleString(), isBurnafterreading);
|
||||
$emailconfirmmodal.modal('hide');
|
||||
triggerEmailSend(emailBody);
|
||||
};
|
||||
} (expirationDateRoundedToSecond, isBurnafterreading));
|
||||
$emailconfirmTimezoneUtc.off('click.sendEmailUtcTimezone');
|
||||
$emailconfirmTimezoneUtc.on('click.sendEmailUtcTimezone', function(expirationDateRoundedToSecond, isBurnafterreading) {
|
||||
return function() {
|
||||
const emailBody = templateEmailBody(expirationDateRoundedToSecond.toLocaleString(
|
||||
undefined,
|
||||
// we don't use Date.prototype.toUTCString() because we would like to avoid GMT
|
||||
{ timeZone: 'UTC', dateStyle: 'long', timeStyle: 'long' }
|
||||
), isBurnafterreading);
|
||||
$emailconfirmmodal.modal('hide');
|
||||
triggerEmailSend(emailBody);
|
||||
};
|
||||
} (expirationDateRoundedToSecond, isBurnafterreading));
|
||||
$emailconfirmmodal.modal('show');
|
||||
} else {
|
||||
triggerEmailSend(templateEmailBody(null, isBurnafterreading));
|
||||
}
|
||||
} else {
|
||||
let emailBody = '';
|
||||
if (expirationDate !== null) {
|
||||
const expirationDateString = window.confirm(
|
||||
I18n._('Recipient may become aware of your timezone, convert time to UTC?')
|
||||
) ? expirationDateRoundedToSecond.toLocaleString(
|
||||
undefined,
|
||||
// we don't use Date.prototype.toUTCString() because we would like to avoid GMT
|
||||
{ timeZone: 'UTC', dateStyle: 'long', timeStyle: 'long' }
|
||||
) : expirationDateRoundedToSecond.toLocaleString();
|
||||
emailBody = templateEmailBody(expirationDateString, isBurnafterreading);
|
||||
} else {
|
||||
emailBody = templateEmailBody(null, isBurnafterreading);
|
||||
}
|
||||
triggerEmailSend(emailBody);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows all navigation elements for viewing an existing paste
|
||||
*
|
||||
|
@ -3578,6 +3745,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$newButton.addClass('hidden');
|
||||
$rawTextButton.addClass('hidden');
|
||||
$qrCodeLink.addClass('hidden');
|
||||
me.hideEmailButton();
|
||||
|
||||
viewButtonsDisplayed = false;
|
||||
};
|
||||
|
@ -3675,6 +3843,50 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$retryButton.addClass('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* show the "email" button
|
||||
*
|
||||
* @name TopNav.showEmailbutton
|
||||
* @function
|
||||
* @param {int|undefined} optionalRemainingTimeInSeconds
|
||||
*/
|
||||
me.showEmailButton = function(optionalRemainingTimeInSeconds)
|
||||
{
|
||||
try {
|
||||
// we cache expiration date in closure to avoid inaccurate expiration datetime
|
||||
const expirationDate = Helper.calculateExpirationDate(
|
||||
new Date(),
|
||||
typeof optionalRemainingTimeInSeconds === 'number' ? optionalRemainingTimeInSeconds : TopNav.getExpiration()
|
||||
);
|
||||
const isBurnafterreading = TopNav.getBurnAfterReading();
|
||||
|
||||
$emailLink.removeClass('hidden');
|
||||
$emailLink.off('click.sendEmail');
|
||||
$emailLink.on('click.sendEmail', function(expirationDate, isBurnafterreading) {
|
||||
return function() {
|
||||
sendEmail(expirationDate, isBurnafterreading);
|
||||
};
|
||||
} (expirationDate, isBurnafterreading));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Alert.showError(
|
||||
I18n._('Cannot calculate expiration date.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* hide the "email" button
|
||||
*
|
||||
* @name TopNav.hideEmailButton
|
||||
* @function
|
||||
*/
|
||||
me.hideEmailButton = function()
|
||||
{
|
||||
$emailLink.addClass('hidden');
|
||||
$emailLink.off('click.sendEmail');
|
||||
}
|
||||
|
||||
/**
|
||||
* only hides the clone button
|
||||
*
|
||||
|
@ -3697,6 +3909,30 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$rawTextButton.addClass('hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* only hides the qr code button
|
||||
*
|
||||
* @name TopNav.hideQrCodeButton
|
||||
* @function
|
||||
*/
|
||||
me.hideQrCodeButton = function()
|
||||
{
|
||||
$qrCodeLink.addClass('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* hide all irrelevant buttons when viewing burn after reading paste
|
||||
*
|
||||
* @name TopNav.hideBurnAfterReadingButtons
|
||||
* @function
|
||||
*/
|
||||
me.hideBurnAfterReadingButtons = function()
|
||||
{
|
||||
me.hideCloneButton();
|
||||
me.hideQrCodeButton();
|
||||
me.hideEmailButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* hides the file selector in attachment
|
||||
*
|
||||
|
@ -3784,7 +4020,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
/**
|
||||
* returns the state of the burn after reading checkbox
|
||||
*
|
||||
* @name TopNav.getExpiration
|
||||
* @name TopNav.getBurnAfterReading
|
||||
* @function
|
||||
* @return {bool}
|
||||
*/
|
||||
|
@ -3914,6 +4150,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$retryButton = $('#retrybutton');
|
||||
$sendButton = $('#sendbutton');
|
||||
$qrCodeLink = $('#qrcodelink');
|
||||
$emailLink = $('#emaillink');
|
||||
|
||||
// bootstrap template drop down
|
||||
$('#language ul.dropdown-menu li a').click(setLanguage);
|
||||
|
@ -4258,6 +4495,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
history.pushState({type: 'newpaste'}, document.title, url);
|
||||
|
||||
TopNav.showViewButtons();
|
||||
|
||||
// this cannot be grouped with showViewButtons due to remaining time calculation
|
||||
TopNav.showEmailButton();
|
||||
|
||||
TopNav.hideRawButton();
|
||||
Editor.hide();
|
||||
|
||||
|
@ -4698,7 +4939,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
|
||||
// discourage cloning (it cannot really be prevented)
|
||||
if (paste.isBurnAfterReadingEnabled()) {
|
||||
TopNav.hideCloneButton();
|
||||
TopNav.hideBurnAfterReadingButtons();
|
||||
} else {
|
||||
// we have to pass in remaining_time here
|
||||
TopNav.showEmailButton(paste.getTimeToLive());
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
|
@ -4914,6 +5158,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
|
||||
DOMPurify.setConfig({SAFE_FOR_JQUERY: true});
|
||||
|
||||
// center all modals
|
||||
$('.modal').on('show.bs.modal', function(e) {
|
||||
$(e.target).css({
|
||||
display: 'flex'
|
||||
});
|
||||
});
|
||||
|
||||
// initialize other modules/"classes"
|
||||
Alert.init();
|
||||
Model.init();
|
||||
|
|
|
@ -326,7 +326,7 @@ describe('TopNav', function () {
|
|||
'returns the currently selected expiration date',
|
||||
function () {
|
||||
$.PrivateBin.TopNav.init();
|
||||
assert.ok($.PrivateBin.TopNav.getExpiration() === '1week');
|
||||
assert.ok($.PrivateBin.TopNav.getExpiration() === null);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -72,7 +72,7 @@ endif;
|
|||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.1.js" integrity="sha512-ddI36MdUoXp/o7yhQtr9/qj4G3oFwCRga4jCGaoUYtORg0PPmFKVKG4Ess3fIknYzxwwKMlrIL9o4NwuPTCc1Q==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-rgDjBvJguZa4Liw2R/0vtFNZBSBTLhjdHROUseVyl+x1aVUbo5U8x/5lewWzYxrS9dxHOtgQkEWjx5ZM31JiNg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-60d7BURzTV858Sry9vJEMgDXEqLT/IqQnjnx/iR3QfQ0LLWZI3bbM9nb4vbS0vQPYFVOP8lXpHRnNE7QEohsDw==" crossorigin="anonymous"></script>
|
||||
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
|
||||
|
@ -118,13 +118,41 @@ if ($QRCODE):
|
|||
<div class="modal-body">
|
||||
<div class="mx-auto" id="qrcode-display"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="btn-group col-xs-12">
|
||||
<span class="col-xs-12">
|
||||
<button type="button" class="btn btn-primary btn-block" data-dismiss="modal"><?php echo I18n::_('Close') ?></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
<div id="emailconfirmmodal" tabindex="-1" class="modal fade" aria-labelledby="emailconfirmmodalTitle" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div id="emailconfirm-display"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="btn-group col-xs-12" data-toggle="buttons">
|
||||
<span class="col-xs-12 col-md-4">
|
||||
<button id="emailconfirm-timezone-current" type="button" class="btn btn-danger btn-block" data-dismiss="modal"><?php echo I18n::_('Use Current Timezone') ?></button>
|
||||
</span>
|
||||
<span class="col-xs-12 col-md-4">
|
||||
<button id="emailconfirm-timezone-utc" type="button" class="btn btn-default btn-block" data-dismiss="modal"><?php echo I18n::_('Convert To UTC') ?></button>
|
||||
</span>
|
||||
<span class="col-xs-12 col-md-4">
|
||||
<button type="button" class="btn btn-primary btn-block" data-dismiss="modal"><?php echo I18n::_('Close') ?></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="navbar navbar-<?php echo $isDark ? 'inverse' : 'default'; ?> navbar-<?php echo $isCpct ? 'fixed' : 'static'; ?>-top"><?php
|
||||
if ($isCpct):
|
||||
?><div class="container"><?php
|
||||
|
@ -171,6 +199,9 @@ endif;
|
|||
<button id="rawtextbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
|
||||
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> <?php echo I18n::_('Raw text'), PHP_EOL; ?>
|
||||
</button>
|
||||
<button id="emaillink" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
|
||||
<span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> <?php echo I18n::_('Email'), PHP_EOL; ?>
|
||||
</button>
|
||||
<?php
|
||||
if ($QRCODE):
|
||||
?>
|
||||
|
|
|
@ -50,7 +50,7 @@ endif;
|
|||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.1.js" integrity="sha512-ddI36MdUoXp/o7yhQtr9/qj4G3oFwCRga4jCGaoUYtORg0PPmFKVKG4Ess3fIknYzxwwKMlrIL9o4NwuPTCc1Q==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-rgDjBvJguZa4Liw2R/0vtFNZBSBTLhjdHROUseVyl+x1aVUbo5U8x/5lewWzYxrS9dxHOtgQkEWjx5ZM31JiNg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-60d7BURzTV858Sry9vJEMgDXEqLT/IqQnjnx/iR3QfQ0LLWZI3bbM9nb4vbS0vQPYFVOP8lXpHRnNE7QEohsDw==" crossorigin="anonymous"></script>
|
||||
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
|
||||
|
@ -105,6 +105,7 @@ endif;
|
|||
<button id="sendbutton" class="hidden"><img src="img/icon_send.png" width="18" height="15" alt="" /><?php echo I18n::_('Send'); ?></button>
|
||||
<button id="clonebutton" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" /><?php echo I18n::_('Clone'); ?></button>
|
||||
<button id="rawtextbutton" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" /><?php echo I18n::_('Raw text'); ?></button>
|
||||
<button id="emaillink" class="hidden"><img src="img/icon_email.png" width="15" height="15" alt="" /><?php echo I18n::_('Email'); ?></button>
|
||||
<?php
|
||||
if ($QRCODE):
|
||||
?>
|
||||
|
|
Loading…
Reference in New Issue
Block a user