Merge pull request #13407 from annando/unify-user-conditions
The query condition for active users are unified
This commit is contained in:
commit
41919bdaea
20 changed files with 57 additions and 49 deletions
|
@ -34,7 +34,7 @@ function lostpass_post(App $a)
|
|||
DI::baseUrl()->redirect();
|
||||
}
|
||||
|
||||
$condition = ['(`email` = ? OR `nickname` = ?) AND `verified` = 1 AND `blocked` = 0 AND `account_removed` = 0 AND `account_expired` = 0', $loginame, $loginame];
|
||||
$condition = ['(`email` = ? OR `nickname` = ?) AND `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`', $loginame, $loginame];
|
||||
$user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'language'], $condition);
|
||||
if (!DBA::isResult($user)) {
|
||||
DI::sysmsg()->addNotice(DI::l10n()->t('No valid account found.'));
|
||||
|
|
|
@ -750,7 +750,7 @@ class Contact
|
|||
$user = DBA::selectFirst(
|
||||
'user',
|
||||
['uid', 'username', 'nickname', 'pubkey', 'prvkey'],
|
||||
['uid' => $uid, 'account_expired' => false]
|
||||
['uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]
|
||||
);
|
||||
if (!DBA::isResult($user)) {
|
||||
return false;
|
||||
|
@ -822,7 +822,7 @@ class Contact
|
|||
}
|
||||
|
||||
$fields = ['uid', 'username', 'nickname', 'page-flags', 'account-type', 'prvkey', 'pubkey'];
|
||||
$user = DBA::selectFirst('user', $fields, ['uid' => $uid, 'account_expired' => false]);
|
||||
$user = DBA::selectFirst('user', $fields, ['uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
if (!DBA::isResult($user)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -940,7 +940,7 @@ class Profile
|
|||
if (!empty($search)) {
|
||||
$publish = (DI::config()->get('system', 'publish_all') ? '' : "AND `publish` ");
|
||||
$searchTerm = '%' . $search . '%';
|
||||
$condition = ["NOT `blocked` AND NOT `account_removed`
|
||||
$condition = ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`
|
||||
$publish
|
||||
AND ((`name` LIKE ?) OR
|
||||
(`nickname` LIKE ?) OR
|
||||
|
@ -953,7 +953,7 @@ class Profile
|
|||
$searchTerm, $searchTerm, $searchTerm, $searchTerm,
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm];
|
||||
} else {
|
||||
$condition = ['blocked' => false, 'account_removed' => false];
|
||||
$condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
|
||||
if (!DI::config()->get('system', 'publish_all')) {
|
||||
$condition['publish'] = true;
|
||||
}
|
||||
|
|
|
@ -280,8 +280,7 @@ class User
|
|||
// List of possible actor names
|
||||
$possible_accounts = ['friendica', 'actor', 'system', 'internal'];
|
||||
foreach ($possible_accounts as $name) {
|
||||
if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'account_expired' => false]) &&
|
||||
!DBA::exists('userd', ['username' => $name])) {
|
||||
if (!DBA::exists('user', ['nickname' => $name]) && !DBA::exists('userd', ['username' => $name])) {
|
||||
DI::config()->set('system', 'actor_name', $name);
|
||||
return $name;
|
||||
}
|
||||
|
@ -326,7 +325,7 @@ class User
|
|||
public static function getByGuid(string $guid, array $fields = [], bool $active = true)
|
||||
{
|
||||
if ($active) {
|
||||
$cond = ['guid' => $guid, 'account_expired' => false, 'account_removed' => false];
|
||||
$cond = ['guid' => $guid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
|
||||
} else {
|
||||
$cond = ['guid' => $guid];
|
||||
}
|
||||
|
@ -702,7 +701,7 @@ class User
|
|||
$fields = ['uid', 'nickname', 'password', 'legacy_password'];
|
||||
$condition = [
|
||||
"(`email` = ? OR `username` = ? OR `nickname` = ?)
|
||||
AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`",
|
||||
AND `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`",
|
||||
$user_info, $user_info, $user_info
|
||||
];
|
||||
$user = DBA::selectFirst('user', $fields, $condition);
|
||||
|
@ -738,7 +737,7 @@ class User
|
|||
if ($user['last-activity'] != $current_day) {
|
||||
User::update(['last-activity' => $current_day], $uid);
|
||||
// Set the last activity for all identities of the user
|
||||
DBA::update('user', ['last-activity' => $current_day], ['parent-uid' => $uid, 'account_removed' => false]);
|
||||
DBA::update('user', ['last-activity' => $current_day], ['parent-uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1703,7 +1702,7 @@ class User
|
|||
|
||||
$identities = [];
|
||||
|
||||
$user = DBA::selectFirst('user', ['uid', 'nickname', 'username', 'parent-uid'], ['uid' => $uid]);
|
||||
$user = DBA::selectFirst('user', ['uid', 'nickname', 'username', 'parent-uid'], ['uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
if (!DBA::isResult($user)) {
|
||||
return $identities;
|
||||
}
|
||||
|
@ -1720,7 +1719,7 @@ class User
|
|||
$r = DBA::select(
|
||||
'user',
|
||||
['uid', 'username', 'nickname'],
|
||||
['parent-uid' => $user['uid'], 'account_removed' => false]
|
||||
['parent-uid' => $user['uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$identities = array_merge($identities, DBA::toArray($r));
|
||||
|
@ -1730,7 +1729,7 @@ class User
|
|||
$r = DBA::select(
|
||||
'user',
|
||||
['uid', 'username', 'nickname'],
|
||||
['uid' => $user['parent-uid'], 'account_removed' => false]
|
||||
['uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$identities = DBA::toArray($r);
|
||||
|
@ -1740,7 +1739,7 @@ class User
|
|||
$r = DBA::select(
|
||||
'user',
|
||||
['uid', 'username', 'nickname'],
|
||||
['parent-uid' => $user['parent-uid'], 'account_removed' => false]
|
||||
['parent-uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$identities = array_merge($identities, DBA::toArray($r));
|
||||
|
@ -1751,7 +1750,7 @@ class User
|
|||
"SELECT `user`.`uid`, `user`.`username`, `user`.`nickname`
|
||||
FROM `manage`
|
||||
INNER JOIN `user` ON `manage`.`mid` = `user`.`uid`
|
||||
WHERE `user`.`account_removed` = 0 AND `manage`.`uid` = ?",
|
||||
WHERE NOT `user`.`account_removed` AND `manage`.`uid` = ?",
|
||||
$user['uid']
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
|
@ -1773,7 +1772,7 @@ class User
|
|||
return false;
|
||||
}
|
||||
|
||||
$user = DBA::selectFirst('user', ['parent-uid'], ['uid' => $uid, 'account_removed' => false]);
|
||||
$user = DBA::selectFirst('user', ['parent-uid'], ['uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
if (!DBA::isResult($user)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1782,7 +1781,7 @@ class User
|
|||
return true;
|
||||
}
|
||||
|
||||
if (DBA::exists('user', ['parent-uid' => $uid, 'account_removed' => false])) {
|
||||
if (DBA::exists('user', ['parent-uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ abstract class BaseUsers extends BaseModeration
|
|||
protected function getTabsHTML(string $selectedTab): string
|
||||
{
|
||||
$all = $this->database->count('user', ["`uid` != ?", 0]);
|
||||
$active = $this->database->count('user', ["NOT `blocked` AND `verified` AND NOT `account_removed` AND `uid` != ?", 0]);
|
||||
$active = $this->database->count('user', ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `uid` != ?", 0]);
|
||||
$pending = Register::getPendingCount();
|
||||
$blocked = $this->database->count('user', ['blocked' => true, 'verified' => true, 'account_removed' => false]);
|
||||
$deleted = $this->database->count('user', ['account_removed' => true]);
|
||||
|
|
|
@ -125,7 +125,7 @@ class Active extends BaseUsers
|
|||
|
||||
$th_users = array_map(null, [$this->t('Name'), $this->t('Email'), $this->t('Register date'), $this->t('Last login'), $this->t('Last public item'), $this->t('Type')], $valid_orders);
|
||||
|
||||
$count = $this->database->count('user', ["NOT `blocked` AND `verified` AND NOT `account_removed` AND `uid` != ?", 0]);
|
||||
$count = $this->database->count('user', ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `uid` != ?", 0]);
|
||||
|
||||
$t = Renderer::getMarkupTemplate('moderation/users/active.tpl');
|
||||
return self::getTabsHTML('active') . Renderer::replaceMacros($t, [
|
||||
|
|
|
@ -63,7 +63,7 @@ class PubSub extends \Friendica\BaseModule
|
|||
$nickname = $this->parameters['nickname'] ?? '';
|
||||
$contact_id = $this->parameters['cid'] ?? 0;
|
||||
|
||||
$importer = $this->database->selectFirst('user', [], ['nickname' => $nickname, 'account_expired' => false, 'account_removed' => false]);
|
||||
$importer = $this->database->selectFirst('user', [], ['nickname' => $nickname, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
if (!$importer) {
|
||||
throw new HTTPException\OKException();
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ class PubSub extends \Friendica\BaseModule
|
|||
$this->logger->notice('Subscription start.', ['from' => $this->request->getRemoteAddress(), 'mode' => $hub_mode, 'nickname' => $nickname]);
|
||||
$this->logger->debug('Data: ', ['get' => $request]);
|
||||
|
||||
$owner = $this->database->selectFirst('user', ['uid'], ['nickname' => $nickname, 'account_expired' => false, 'account_removed' => false]);
|
||||
$owner = $this->database->selectFirst('user', ['uid'], ['nickname' => $nickname, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
if (!$owner) {
|
||||
$this->logger->notice('Local account not found.', ['nickname' => $nickname]);
|
||||
throw new HTTPException\NotFoundException();
|
||||
|
|
|
@ -111,7 +111,7 @@ class PubSubHubBub extends \Friendica\BaseModule
|
|||
}
|
||||
|
||||
// fetch user from database given the nickname
|
||||
$condition = ['nickname' => $nickname, 'account_expired' => false, 'account_removed' => false];
|
||||
$condition = ['nickname' => $nickname, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
|
||||
$owner = $this->database->selectFirst('user', ['uid', 'nickname'], $condition);
|
||||
if (!$owner) {
|
||||
$this->logger->notice('Local account not found', ['nickname' => $nickname, 'topic' => $hub_topic, 'callback' => $hub_callback]);
|
||||
|
|
|
@ -74,7 +74,7 @@ class Salmon extends \Friendica\BaseModule
|
|||
|
||||
$this->logger->debug('New Salmon', ['nickname' => $nickname, 'xml' => $xml]);
|
||||
|
||||
$importer = $this->database->selectFirst('user', [], ['nickname' => $nickname, 'account_expired' => false, 'account_removed' => false]);
|
||||
$importer = $this->database->selectFirst('user', [], ['nickname' => $nickname, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
if (!$this->database->isResult($importer)) {
|
||||
throw new HTTPException\InternalServerErrorException();
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ class Profile extends BaseProfile
|
|||
protected function rawContent(array $request = [])
|
||||
{
|
||||
if (ActivityPub::isRequest()) {
|
||||
$user = $this->database->selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname'] ?? '', 'account_removed' => false]);
|
||||
$user = $this->database->selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname'] ?? '', 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
if ($user) {
|
||||
try {
|
||||
$data = ActivityPub\Transmitter::getProfile($user['uid'], ActivityPub::isAcceptedRequester($user['uid']));
|
||||
|
|
|
@ -60,7 +60,7 @@ class OpenID extends BaseModule
|
|||
// in commit 8367cadeeffec4b6792a502847304b17ceba5882, so it might
|
||||
// have left mixed records in the user table
|
||||
//
|
||||
$condition = ['blocked' => false, 'account_expired' => false, 'account_removed' => false, 'verified' => true,
|
||||
$condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false,
|
||||
'openid' => [$authId, Strings::normaliseOpenID($authId)]];
|
||||
|
||||
$dba = DI::dba();
|
||||
|
|
|
@ -88,7 +88,7 @@ class DFRN
|
|||
$contact['senderName'] = $contact['name'];
|
||||
|
||||
if ($uid != 0) {
|
||||
$condition = ['uid' => $uid, 'account_expired' => false, 'account_removed' => false];
|
||||
$condition = ['uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
|
||||
$user = DBA::selectFirst('user', [], $condition);
|
||||
if (!DBA::isResult($user)) {
|
||||
return [];
|
||||
|
|
|
@ -3062,10 +3062,7 @@ class Diaspora
|
|||
// If the item belongs to a user, we take this user id.
|
||||
if ($item['uid'] == 0) {
|
||||
// @todo Possibly use an administrator account?
|
||||
$condition = [
|
||||
'verified' => true, 'blocked' => false,
|
||||
'account_removed' => false, 'account_expired' => false, 'account-type' => User::ACCOUNT_TYPE_PERSON
|
||||
];
|
||||
$condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false, 'account-type' => User::ACCOUNT_TYPE_PERSON];
|
||||
$first_user = DBA::selectFirst('user', ['uid'], $condition, ['order' => ['uid']]);
|
||||
$owner = User::getOwnerDataById($first_user['uid']);
|
||||
} else {
|
||||
|
|
|
@ -145,7 +145,7 @@ class Cron
|
|||
DBA::close($users);
|
||||
|
||||
// Update contact relations for our users
|
||||
$users = DBA::select('user', ['uid'], ["NOT `account_expired` AND NOT `account_removed` AND `uid` > ?", 0]);
|
||||
$users = DBA::select('user', ['uid'], ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `uid` > ?", 0]);
|
||||
while ($user = DBA::fetch($users)) {
|
||||
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscoveryForUser', $user['uid']);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class Directory
|
|||
}
|
||||
|
||||
private static function updateAll() {
|
||||
$users = DBA::select('owner-view', ['url'], ['net-publish' => true, 'account_expired' => false, 'verified' => true]);
|
||||
$users = DBA::select('owner-view', ['url'], ['net-publish' => true, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
while ($user = DBA::fetch($users)) {
|
||||
Worker::add(Worker::PRIORITY_LOW, 'Directory', $user['url']);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class ExpireAndRemoveUsers
|
|||
DBA::update('user', ['account_expired' => true], $condition);
|
||||
|
||||
// Ensure to never remove the user with uid=0
|
||||
DBA::update('user', ['account_expired' => false, 'account_removed' => false,
|
||||
DBA::update('user', ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false,
|
||||
'account_expires_on' => DBA::NULL_DATETIME], ['uid' => 0]);
|
||||
|
||||
// Remove any freshly expired account
|
||||
|
|
|
@ -45,10 +45,10 @@ class PollContacts
|
|||
|
||||
if (!empty($abandon_days)) {
|
||||
$condition = DBA::mergeConditions($condition,
|
||||
["`uid` != ? AND `uid` IN (SELECT `uid` FROM `user` WHERE NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `last-activity` > ?)", 0, DateTimeFormat::utc('now - ' . $abandon_days . ' days')]);
|
||||
["`uid` != ? AND `uid` IN (SELECT `uid` FROM `user` WHERE `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `last-activity` > ?)", 0, DateTimeFormat::utc('now - ' . $abandon_days . ' days')]);
|
||||
} else {
|
||||
$condition = DBA::mergeConditions($condition,
|
||||
["`uid` != ? AND `uid` IN (SELECT `uid` FROM `user` WHERE NOT `blocked` AND NOT `account_expired` AND NOT `account_removed`)", 0]);
|
||||
["`uid` != ? AND `uid` IN (SELECT `uid` FROM `user` WHERE `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`)", 0]);
|
||||
}
|
||||
|
||||
$contacts = DBA::select('contact', ['id', 'nick', 'name', 'network', 'archive', 'last-update', 'priority', 'rating'], $condition);
|
||||
|
|
|
@ -31,7 +31,7 @@ class UpdatePhotoAlbums
|
|||
{
|
||||
public static function execute()
|
||||
{
|
||||
$users = DBA::select('user', ['uid'], ['account_expired' => false, 'account_removed' => false]);
|
||||
$users = DBA::select('user', ['uid'], ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
while ($user = DBA::fetch($users)) {
|
||||
Photo::clearAlbumCache($user['uid']);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class UpdateScores
|
|||
{
|
||||
Logger::notice('Start score update');
|
||||
|
||||
$users = DBA::select('user', ['uid'], ["NOT `account_expired` AND NOT `account_removed` AND `uid` > ?", 0]);
|
||||
$users = DBA::select('user', ['uid'], ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `uid` > ?", 0]);
|
||||
while ($user = DBA::fetch($users)) {
|
||||
Relation::calculateInteractionScore($user['uid']);
|
||||
}
|
||||
|
|
|
@ -50,28 +50,40 @@ class UserTest extends MockedTest
|
|||
DI::init($diceMock, true);
|
||||
|
||||
$this->parent = [
|
||||
'uid' => 1,
|
||||
'username' => 'maxmuster',
|
||||
'nickname' => 'Max Muster'
|
||||
'uid' => 1,
|
||||
'username' => 'maxmuster',
|
||||
'nickname' => 'Max Muster',
|
||||
'verified' => true,
|
||||
'blocked' => false,
|
||||
'account_removed' => false,
|
||||
'account_expired' => false,
|
||||
];
|
||||
|
||||
$this->child = [
|
||||
'uid' => 2,
|
||||
'username' => 'johndoe',
|
||||
'nickname' => 'John Doe'
|
||||
'uid' => 2,
|
||||
'username' => 'johndoe',
|
||||
'nickname' => 'John Doe',
|
||||
'verified' => true,
|
||||
'blocked' => false,
|
||||
'account_removed' => false,
|
||||
'account_expired' => false,
|
||||
];
|
||||
|
||||
$this->manage = [
|
||||
'uid' => 3,
|
||||
'username' => 'janesmith',
|
||||
'nickname' => 'Jane Smith'
|
||||
'uid' => 3,
|
||||
'username' => 'janesmith',
|
||||
'nickname' => 'Jane Smith',
|
||||
'verified' => true,
|
||||
'blocked' => false,
|
||||
'account_removed' => false,
|
||||
'account_expired' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public function testIdentitiesEmpty()
|
||||
{
|
||||
$this->dbMock->shouldReceive('selectFirst')->with('user',
|
||||
['uid', 'nickname', 'username', 'parent-uid'],['uid' => $this->parent['uid']], [])->andReturn($this->parent)->once();
|
||||
['uid', 'nickname', 'username', 'parent-uid'],['uid' => $this->parent['uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false], [])->andReturn($this->parent)->once();
|
||||
$this->dbMock->shouldReceive('isResult')->with($this->parent)->andReturn(false)->once();
|
||||
|
||||
$record = User::identities($this->parent['uid']);
|
||||
|
@ -86,7 +98,7 @@ class UserTest extends MockedTest
|
|||
|
||||
// Select the user itself (=parent)
|
||||
$this->dbMock->shouldReceive('selectFirst')->with('user',
|
||||
['uid', 'nickname', 'username', 'parent-uid'],['uid' => $this->parent['uid']], [])->andReturn($parentSelect)->once();
|
||||
['uid', 'nickname', 'username', 'parent-uid'],['uid' => $this->parent['uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false], [])->andReturn($parentSelect)->once();
|
||||
$this->dbMock->shouldReceive('isResult')->with($parentSelect)->andReturn(true)->once();
|
||||
|
||||
// Select one child
|
||||
|
@ -120,7 +132,7 @@ class UserTest extends MockedTest
|
|||
|
||||
// Select the user itself (=child)
|
||||
$this->dbMock->shouldReceive('selectFirst')->with('user',
|
||||
['uid', 'nickname', 'username', 'parent-uid'],['uid' => $this->child['uid']], [])->andReturn($childSelect)->once();
|
||||
['uid', 'nickname', 'username', 'parent-uid'],['uid' => $this->child['uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false], [])->andReturn($childSelect)->once();
|
||||
$this->dbMock->shouldReceive('isResult')->with($childSelect)->andReturn(true)->once();
|
||||
|
||||
// Select the parent
|
||||
|
|
Loading…
Reference in a new issue