Implemented application login.

Minor UI improvement.
Fixed memory leaks.
print
Josef Rokos 9 years ago
parent 3d68281c87
commit c722fe743c

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>icons/login_32.png</file>
<file>icons/login_64.png</file>
</qresource>
</RCC>

@ -22,10 +22,13 @@ win32 {
SOURCES += main.cpp\ SOURCES += main.cpp\
mainwindow.cpp \ mainwindow.cpp \
logindialog.cpp
HEADERS += mainwindow.h HEADERS += mainwindow.h \
logindialog.h
FORMS += mainwindow.ui FORMS += mainwindow.ui \
logindialog.ui
unix { unix {
@ -43,3 +46,6 @@ else:unix: LIBS += -L$$OUT_PWD/../core/ -lcore
INCLUDEPATH += $$PWD/../core INCLUDEPATH += $$PWD/../core
DEPENDPATH += $$PWD/../core DEPENDPATH += $$PWD/../core
RESOURCES += \
appRc.qrc

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

@ -0,0 +1,45 @@
#include "logindialog.h"
#include "ui_logindialog.h"
#include "../core/core.h"
#include <QMessageBox>
LoginDialog::LoginDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::LoginDialog)
{
ui->setupUi(this);
}
LoginDialog::~LoginDialog()
{
delete ui;
}
QString LoginDialog::login() const
{
return ui->editLogin->text();
}
QString LoginDialog::password() const
{
return ui->editPassword->text();
}
void LoginDialog::reset()
{
ui->editLogin->setText("");
ui->editPassword->setText("");
}
void LoginDialog::accept()
{
PermissionService srv;
if (srv.checkLogin(ui->editLogin->text(), ui->editPassword->text()))
{
QDialog::accept();
}
else
{
QMessageBox::critical(this, "Bad login", "Bad login or password");
}
}

@ -0,0 +1,29 @@
#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H
#include <QDialog>
namespace Ui {
class LoginDialog;
}
class LoginDialog : public QDialog
{
Q_OBJECT
public:
explicit LoginDialog(QWidget *parent = 0);
~LoginDialog();
QString login() const;
QString password() const;
void reset();
private:
Ui::LoginDialog *ui;
// QDialog interface
public slots:
void accept() override;
};
#endif // LOGINDIALOG_H

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LoginDialog</class>
<widget class="QDialog" name="LoginDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>408</width>
<height>220</height>
</rect>
</property>
<property name="windowTitle">
<string>Login</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 255, 255);</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="appRc.qrc">:/icons/login_64.png</pixmap>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Login</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="editLogin"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="editPassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="appRc.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LoginDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LoginDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -1,5 +1,6 @@
#include "mainwindow.h" #include "mainwindow.h"
#include <QApplication> #include <QApplication>
#include <QDesktopWidget>
#include <QStringList> #include <QStringList>
@ -7,6 +8,7 @@ int main(int argc, char *argv[])
{ {
QApplication a(argc, argv); QApplication a(argc, argv);
MainWindow w; MainWindow w;
w.move(QApplication::desktop()->screen()->rect().center() - w.rect().center());
w.show(); w.show();
return a.exec(); return a.exec();

@ -13,6 +13,22 @@ MainWindow::MainWindow(QWidget *parent) :
ui(new Ui::MainWindow) ui(new Ui::MainWindow)
{ {
ui->setupUi(this); ui->setupUi(this);
m_lblUser = new QLabel(this);
ui->statusBar->addWidget(m_lblUser);
m_loginDialog = new LoginDialog(this);
connect(m_loginDialog, &LoginDialog::accepted, [this]{
PermissionService service;
QSharedPointer<User> u = service.loadUser(m_loginDialog->login());
m_lblUser->setText(u->name());
m_loginDialog->reset();
Context::instance().setCurrentUser(u);
});
connect(m_loginDialog, &LoginDialog::rejected, [this]{
close();
});
Context::instance().loadPlugins(); Context::instance().loadPlugins();
int i = 0; int i = 0;
@ -27,6 +43,11 @@ MainWindow::MainWindow(QWidget *parent) :
} }
((QVBoxLayout*)ui->navigation->layout())->addStretch(1); ((QVBoxLayout*)ui->navigation->layout())->addStretch(1);
if (Context::instance().db() != NULL)
{
ui->navigation->setEnabled(true);
}
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
@ -59,13 +80,37 @@ void MainWindow::openPlugin()
void MainWindow::on_actionOpen_database_triggered() void MainWindow::on_actionOpen_database_triggered()
{ {
QFileDialog dialog(this); /*QFileDialog dialog(this);
dialog.setNameFilter(tr("Database Files (*.db)")); dialog.setNameFilter(tr("Database Files (*.db)"));
dialog.setWindowTitle(tr("Open Database")); dialog.setWindowTitle(tr("Open Database"));*/
Context::instance().openDb(dialog.getOpenFileName());
QString dbFile = QFileDialog::getOpenFileName(this, "Open Database", "", "Database Files (*.db)");
if (!dbFile.isEmpty())
{
Context::instance().openDb(dbFile);
ui->navigation->setEnabled(true);
on_actionLogin_triggered();
}
} }
void MainWindow::on_tabWidget_tabCloseRequested(int index) void MainWindow::on_tabWidget_tabCloseRequested(int index)
{ {
ui->tabWidget->removeTab(index); ui->tabWidget->removeTab(index);
} }
void MainWindow::on_actionLogin_triggered()
{
QSharedPointer<User> u;
Context::instance().setCurrentUser(u);
m_lblUser->setText("");
m_loginDialog->show();
}
void MainWindow::showEvent(QShowEvent *evt)
{
QWidget::showEvent(evt);
if (Context::instance().db() != NULL && Context::instance().currentUser().data() == NULL)
{
m_loginDialog->show();
}
}

@ -2,6 +2,9 @@
#define MAINWINDOW_H #define MAINWINDOW_H
#include <QMainWindow> #include <QMainWindow>
#include <QLabel>
#include "logindialog.h"
#define PLUGIN_INDEX "plug_index" #define PLUGIN_INDEX "plug_index"
@ -25,8 +28,16 @@ private slots:
void on_tabWidget_tabCloseRequested(int index); void on_tabWidget_tabCloseRequested(int index);
void on_actionLogin_triggered();
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
LoginDialog *m_loginDialog;
QLabel *m_lblUser;
// QWidget interface
protected:
void showEvent(QShowEvent *evt);
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

@ -15,8 +15,17 @@
</property> </property>
<widget class="QWidget" name="centralWidget"> <widget class="QWidget" name="centralWidget">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<item> <item>
<widget class="QWidget" name="navigation" native="true"> <widget class="QWidget" name="navigation" native="true">
<property name="enabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2"/> <layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget> </widget>
</item> </item>
@ -52,6 +61,8 @@
<string>File</string> <string>File</string>
</property> </property>
<addaction name="actionOpen_database"/> <addaction name="actionOpen_database"/>
<addaction name="actionLogin"/>
<addaction name="separator"/>
<addaction name="actionExit"/> <addaction name="actionExit"/>
</widget> </widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
@ -63,6 +74,7 @@
<attribute name="toolBarBreak"> <attribute name="toolBarBreak">
<bool>false</bool> <bool>false</bool>
</attribute> </attribute>
<addaction name="actionLogin"/>
</widget> </widget>
<widget class="QStatusBar" name="statusBar"/> <widget class="QStatusBar" name="statusBar"/>
<action name="actionExit"> <action name="actionExit">
@ -75,8 +87,19 @@
<string>Open database...</string> <string>Open database...</string>
</property> </property>
</action> </action>
<action name="actionLogin">
<property name="icon">
<iconset resource="appRc.qrc">
<normaloff>:/icons/login_32.png</normaloff>:/icons/login_32.png</iconset>
</property>
<property name="text">
<string>Login...</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<resources/> <resources>
<include location="appRc.qrc"/>
</resources>
<connections/> <connections/>
</ui> </ui>

@ -15,6 +15,11 @@
#include "roles/roles.h" #include "roles/roles.h"
#include "permissionservice.h" #include "permissionservice.h"
Context::~Context()
{
this->destroy();
}
Context &Context::instance() Context &Context::instance()
{ {
static Context ctx; static Context ctx;
@ -91,7 +96,10 @@ void Context::destroy()
m_dbOpened = false; m_dbOpened = false;
} }
delete m_settings; if (m_settings != NULL && m_settings->parent() == NULL)
{
delete m_settings;
}
foreach (IPlugin *plugin, m_plugins) foreach (IPlugin *plugin, m_plugins)
{ {
@ -112,6 +120,16 @@ Context::Context()
m_dbOpened = false; m_dbOpened = false;
} }
QSharedPointer<User> Context::currentUser() const
{
return m_currentUser;
}
void Context::setCurrentUser(const QSharedPointer<User> &currentUser)
{
m_currentUser = currentUser;
}
void Context::checkDb(const QString &path) void Context::checkDb(const QString &path)
{ {
{ {
@ -226,4 +244,6 @@ void Context::checkPermissions()
} }
} }
} }
permService.checkForAdmin();
} }

@ -7,10 +7,12 @@
#include <QSqlDatabase> #include <QSqlDatabase>
#include <QStringList> #include <QStringList>
#include <QSettings> #include <QSettings>
#include <QSharedPointer>
#include "define.h" #include "define.h"
#include "core_global.h" #include "core_global.h"
#include "transaction.h" #include "transaction.h"
#include "data/core-data.h"
#include <odb/database.hxx> #include <odb/database.hxx>
#include <odb/session.hxx> #include <odb/session.hxx>
@ -20,6 +22,7 @@ class IPlugin;
class CORESHARED_EXPORT Context class CORESHARED_EXPORT Context
{ {
public: public:
~Context();
static Context &instance(); static Context &instance();
QList<IPlugin*> plugins(); QList<IPlugin*> plugins();
IPlugin *plugin(const QString &pluginId); IPlugin *plugin(const QString &pluginId);
@ -31,6 +34,9 @@ public:
void destroy(); void destroy();
QStringList defaultPerms(); QStringList defaultPerms();
QSharedPointer<User> currentUser() const;
void setCurrentUser(const QSharedPointer<User> &currentUser);
private: private:
Context(); Context();
QList<IPlugin*> m_plugins; QList<IPlugin*> m_plugins;
@ -38,6 +44,7 @@ private:
QSettings *m_settings; QSettings *m_settings;
bool m_dbOpened; bool m_dbOpened;
odb::session m_session; odb::session m_session;
QSharedPointer<User> m_currentUser;
QStringList m_solved; QStringList m_solved;

@ -7,5 +7,6 @@
#include "imetadataplugin.h" #include "imetadataplugin.h"
#include "transaction.h" #include "transaction.h"
#include "gridform.h" #include "gridform.h"
#include "permissionservice.h"
#endif // CORE_H #endif // CORE_H

@ -70,14 +70,11 @@
<property name="toolTip"> <property name="toolTip">
<string>Apply</string> <string>Apply</string>
</property> </property>
<property name="toolTipDuration">
<number>1</number>
</property>
<property name="text"> <property name="text">
<string>Go</string> <string>Go</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset resource="rc.qrc">
<normaloff>:/icons/ok.svg</normaloff>:/icons/ok.svg</iconset> <normaloff>:/icons/ok.svg</normaloff>:/icons/ok.svg</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -119,14 +116,11 @@
<property name="toolTip"> <property name="toolTip">
<string>Save</string> <string>Save</string>
</property> </property>
<property name="toolTipDuration">
<number>1</number>
</property>
<property name="text"> <property name="text">
<string>Save</string> <string>Save</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset resource="rc.qrc">
<normaloff>:/icons/save.svg</normaloff>:/icons/save.svg</iconset> <normaloff>:/icons/save.svg</normaloff>:/icons/save.svg</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -145,14 +139,11 @@
<property name="toolTip"> <property name="toolTip">
<string>Manage</string> <string>Manage</string>
</property> </property>
<property name="toolTipDuration">
<number>1</number>
</property>
<property name="text"> <property name="text">
<string>Manage</string> <string>Manage</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset resource="rc.qrc">
<normaloff>:/icons/list.svg</normaloff>:/icons/list.svg</iconset> <normaloff>:/icons/list.svg</normaloff>:/icons/list.svg</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -229,6 +220,8 @@
</property> </property>
</action> </action>
</widget> </widget>
<resources/> <resources>
<include location="rc.qrc"/>
</resources>
<connections/> <connections/>
</ui> </ui>

@ -30,6 +30,16 @@ public:
} }
virtual ~GridForm() virtual ~GridForm()
{ {
if (m_form != NULL && m_form->parent() == NULL)
{
delete m_form;
}
if (m_tableModel != NULL && m_tableModel->parent() == NULL)
{
delete m_tableModel;
}
delete m_formHandler; delete m_formHandler;
} }

@ -19,6 +19,9 @@
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QToolButton" name="btnNew"> <widget class="QToolButton" name="btnNew">
<property name="toolTip">
<string>Add record</string>
</property>
<property name="autoFillBackground"> <property name="autoFillBackground">
<bool>false</bool> <bool>false</bool>
</property> </property>
@ -45,6 +48,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="btnEdit"> <widget class="QToolButton" name="btnEdit">
<property name="toolTip">
<string>Edit record</string>
</property>
<property name="text"> <property name="text">
<string>E</string> <string>E</string>
</property> </property>
@ -65,6 +71,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="btnDelete"> <widget class="QToolButton" name="btnDelete">
<property name="toolTip">
<string>Delete record</string>
</property>
<property name="text"> <property name="text">
<string>D</string> <string>D</string>
</property> </property>
@ -85,6 +94,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="btnFilter"> <widget class="QToolButton" name="btnFilter">
<property name="toolTip">
<string>Filter</string>
</property>
<property name="text"> <property name="text">
<string>F</string> <string>F</string>
</property> </property>
@ -108,6 +120,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="btnPrint"> <widget class="QToolButton" name="btnPrint">
<property name="toolTip">
<string>Print</string>
</property>
<property name="text"> <property name="text">
<string>P</string> <string>P</string>
</property> </property>

@ -21,6 +21,7 @@ IGridForm::IGridForm(QWidget *parent) :
IGridForm::~IGridForm() IGridForm::~IGridForm()
{ {
delete ui;
} }
void IGridForm::setPluginId(const QString &pluginId) void IGridForm::setPluginId(const QString &pluginId)

@ -10,14 +10,11 @@
IMetaDataPlugin::IMetaDataPlugin() IMetaDataPlugin::IMetaDataPlugin()
{ {
m_service = NULL; m_service = NULL;
m_ui = NULL;
} }
IMetaDataPlugin::~IMetaDataPlugin() IMetaDataPlugin::~IMetaDataPlugin()
{ {
if (m_service != NULL)
{
delete m_service;
}
} }
QString IMetaDataPlugin::pluginName() QString IMetaDataPlugin::pluginName()

@ -10,7 +10,7 @@ class CORESHARED_EXPORT IMetaDataPlugin : public IPlugin
{ {
public: public:
IMetaDataPlugin(); IMetaDataPlugin();
~IMetaDataPlugin(); virtual ~IMetaDataPlugin();
// IPlugin interface // IPlugin interface
public: public:

@ -19,7 +19,18 @@ public:
m_service = NULL; m_service = NULL;
} }
virtual ~IPlugin() { } virtual ~IPlugin() {
if (m_service != NULL)
{
delete m_service;
}
if (m_ui != NULL && m_ui->parent() == NULL)
{
delete m_ui;
}
}
virtual QString pluginName() = 0; virtual QString pluginName() = 0;
virtual QString pluginId() = 0; virtual QString pluginId() = 0;
virtual QString pluginDescription() = 0; virtual QString pluginDescription() = 0;

@ -1,4 +1,9 @@
#include "core-odb.hxx"
#include "permissionservice.h" #include "permissionservice.h"
#include <QCryptographicHash>
typedef odb::query<Permission> permQuery;
typedef odb::result<Permission> permResult;
PermissionService::PermissionService() PermissionService::PermissionService()
{ {
@ -36,3 +41,51 @@ QSharedPointer<Permission> PermissionService::forNameAndPlugin(const QString &na
return p; return p;
} }
bool PermissionService::checkLogin(const QString &login, const QString &password)
{
QSharedPointer<User> user = loadUser(login);
if (user.data())
{
return user->password() == encryptPassword(password) && user->active();
}
return false;
}
QSharedPointer<User> PermissionService::loadUser(const QString &login)
{
odb::database *db = Context::instance().db();
Transaction tr;
return db->query_one<User>("login = " + odb::query<User>::_ref(login));
}
void PermissionService::checkForAdmin()
{
odb::database *db = Context::instance().db();
Transaction tr;
odb::query<User> q(odb::query<User>::isAdmin == true);
odb::result<User> r = db->query<User>(q);
if (r.empty())
{
QSharedPointer<User> admin(new User);
admin->setLogin("admin");
admin->setName("Administrator");
admin->setIsAdmin(true);
admin->setPassword(encryptPassword("admin"));
admin->setActive(true);
db->persist(admin);
}
tr.commit();
}
QString PermissionService::encryptPassword(const QString &plainPasswd)
{
return QString(QCryptographicHash::hash(plainPasswd.toUtf8(),QCryptographicHash::Sha256).toBase64());
}

@ -2,8 +2,7 @@
#define PERMISSIONSERVICE_H #define PERMISSIONSERVICE_H
#include "service.h" #include "service.h"
#include "permission.h" #include "data/core-data.h"
#include "core-odb.hxx"
#include "core_global.h" #include "core_global.h"
#include <odb/core.hxx> #include <odb/core.hxx>
#include <odb/database.hxx> #include <odb/database.hxx>
@ -14,9 +13,6 @@
#include <QSharedPointer> #include <QSharedPointer>
#include <QString> #include <QString>
typedef odb::query<Permission> permQuery;
typedef odb::result<Permission> permResult;
class CORESHARED_EXPORT PermissionService : public Service<Permission> class CORESHARED_EXPORT PermissionService : public Service<Permission>
{ {
public: public:
@ -25,6 +21,10 @@ public:
QList<QSharedPointer<Permission> > forPlugin(const QString &pluginId); QList<QSharedPointer<Permission> > forPlugin(const QString &pluginId);
QSharedPointer<Permission> forNameAndPlugin(const QString &name, const QString &pluginId); QSharedPointer<Permission> forNameAndPlugin(const QString &name, const QString &pluginId);
bool checkLogin(const QString &login, const QString &password);
QSharedPointer<User> loadUser(const QString &login);
void checkForAdmin();
QString encryptPassword(const QString &plainPasswd);
}; };
#endif // PERMISSIONSERVICE_H #endif // PERMISSIONSERVICE_H

@ -5,9 +5,9 @@
#include <QHeaderView> #include <QHeaderView>
#include "../data/core-data.h" #include "../data/core-data.h"
#include "../service.h" #include "../service.h"
#include "../permissionservice.h"
#include "../emptystringvalidator.h" #include "../emptystringvalidator.h"
#include "../samestringvalidator.h" #include "../samestringvalidator.h"
#include <QCryptographicHash>
UserForm::UserForm(QWidget *parent) : UserForm::UserForm(QWidget *parent) :
AutoForm<User>(parent), AutoForm<User>(parent),
@ -69,8 +69,9 @@ bool UserForm::bindOtherToData()
this->entity()->addRole(srv.loadById(ui->tableWidget->item(i,0)->data(Qt::UserRole).toInt())); this->entity()->addRole(srv.loadById(ui->tableWidget->item(i,0)->data(Qt::UserRole).toInt()));
} }
} }
PermissionService permService;
if (m_passChanged){ if (m_passChanged){
this->entity()->setPassword(QString(QCryptographicHash::hash(ui->password->text().toUtf8(),QCryptographicHash::Sha256).toBase64())); this->entity()->setPassword(permService.encryptPassword(ui->password->text()));
} }
return true; return true;
} }

Loading…
Cancel
Save