mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-28 23:48:19 +01:00
Add initial create screen for auth providers
Summary: Ref T1536. Adds an initial "choose a provider type" screen for adding a new provider. This doesn't go anywhere yet. Test Plan: {F46316} Reviewers: btrahan Reviewed By: btrahan CC: aran, chad Maniphest Tasks: T1536 Differential Revision: https://secure.phabricator.com/D6199
This commit is contained in:
parent
b927dc057d
commit
abb367dd5b
9 changed files with 177 additions and 51 deletions
|
@ -814,7 +814,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'aphront-form-view-css' =>
|
'aphront-form-view-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/a668dc36/rsrc/css/aphront/form-view.css',
|
'uri' => '/res/40b6b684/rsrc/css/aphront/form-view.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -4057,7 +4057,7 @@ celerity_register_resource_map(array(
|
||||||
), array(
|
), array(
|
||||||
'packages' =>
|
'packages' =>
|
||||||
array(
|
array(
|
||||||
'116c8dcd' =>
|
'f2a3a549' =>
|
||||||
array(
|
array(
|
||||||
'name' => 'core.pkg.css',
|
'name' => 'core.pkg.css',
|
||||||
'symbols' =>
|
'symbols' =>
|
||||||
|
@ -4105,7 +4105,7 @@ celerity_register_resource_map(array(
|
||||||
40 => 'phabricator-property-list-view-css',
|
40 => 'phabricator-property-list-view-css',
|
||||||
41 => 'phabricator-tag-view-css',
|
41 => 'phabricator-tag-view-css',
|
||||||
),
|
),
|
||||||
'uri' => '/res/pkg/116c8dcd/core.pkg.css',
|
'uri' => '/res/pkg/f2a3a549/core.pkg.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
),
|
),
|
||||||
'f2ad0683' =>
|
'f2ad0683' =>
|
||||||
|
@ -4299,16 +4299,16 @@ celerity_register_resource_map(array(
|
||||||
'reverse' =>
|
'reverse' =>
|
||||||
array(
|
array(
|
||||||
'aphront-attached-file-view-css' => 'a7ca34a9',
|
'aphront-attached-file-view-css' => 'a7ca34a9',
|
||||||
'aphront-dialog-view-css' => '116c8dcd',
|
'aphront-dialog-view-css' => 'f2a3a549',
|
||||||
'aphront-error-view-css' => '116c8dcd',
|
'aphront-error-view-css' => 'f2a3a549',
|
||||||
'aphront-form-view-css' => '116c8dcd',
|
'aphront-form-view-css' => 'f2a3a549',
|
||||||
'aphront-list-filter-view-css' => '116c8dcd',
|
'aphront-list-filter-view-css' => 'f2a3a549',
|
||||||
'aphront-pager-view-css' => '116c8dcd',
|
'aphront-pager-view-css' => 'f2a3a549',
|
||||||
'aphront-panel-view-css' => '116c8dcd',
|
'aphront-panel-view-css' => 'f2a3a549',
|
||||||
'aphront-table-view-css' => '116c8dcd',
|
'aphront-table-view-css' => 'f2a3a549',
|
||||||
'aphront-tokenizer-control-css' => '116c8dcd',
|
'aphront-tokenizer-control-css' => 'f2a3a549',
|
||||||
'aphront-tooltip-css' => '116c8dcd',
|
'aphront-tooltip-css' => 'f2a3a549',
|
||||||
'aphront-typeahead-control-css' => '116c8dcd',
|
'aphront-typeahead-control-css' => 'f2a3a549',
|
||||||
'differential-changeset-view-css' => 'dd27a69b',
|
'differential-changeset-view-css' => 'dd27a69b',
|
||||||
'differential-core-view-css' => 'dd27a69b',
|
'differential-core-view-css' => 'dd27a69b',
|
||||||
'differential-inline-comment-editor' => '9488bb69',
|
'differential-inline-comment-editor' => '9488bb69',
|
||||||
|
@ -4322,7 +4322,7 @@ celerity_register_resource_map(array(
|
||||||
'differential-table-of-contents-css' => 'dd27a69b',
|
'differential-table-of-contents-css' => 'dd27a69b',
|
||||||
'diffusion-commit-view-css' => 'c8ce2d88',
|
'diffusion-commit-view-css' => 'c8ce2d88',
|
||||||
'diffusion-icons-css' => 'c8ce2d88',
|
'diffusion-icons-css' => 'c8ce2d88',
|
||||||
'global-drag-and-drop-css' => '116c8dcd',
|
'global-drag-and-drop-css' => 'f2a3a549',
|
||||||
'inline-comment-summary-css' => 'dd27a69b',
|
'inline-comment-summary-css' => 'dd27a69b',
|
||||||
'javelin-aphlict' => 'f2ad0683',
|
'javelin-aphlict' => 'f2ad0683',
|
||||||
'javelin-behavior' => 'a9f14d76',
|
'javelin-behavior' => 'a9f14d76',
|
||||||
|
@ -4396,55 +4396,55 @@ celerity_register_resource_map(array(
|
||||||
'javelin-util' => 'a9f14d76',
|
'javelin-util' => 'a9f14d76',
|
||||||
'javelin-vector' => 'a9f14d76',
|
'javelin-vector' => 'a9f14d76',
|
||||||
'javelin-workflow' => 'a9f14d76',
|
'javelin-workflow' => 'a9f14d76',
|
||||||
'lightbox-attachment-css' => '116c8dcd',
|
'lightbox-attachment-css' => 'f2a3a549',
|
||||||
'maniphest-task-summary-css' => 'a7ca34a9',
|
'maniphest-task-summary-css' => 'a7ca34a9',
|
||||||
'maniphest-transaction-detail-css' => 'a7ca34a9',
|
'maniphest-transaction-detail-css' => 'a7ca34a9',
|
||||||
'phabricator-action-list-view-css' => '116c8dcd',
|
'phabricator-action-list-view-css' => 'f2a3a549',
|
||||||
'phabricator-application-launch-view-css' => '116c8dcd',
|
'phabricator-application-launch-view-css' => 'f2a3a549',
|
||||||
'phabricator-busy' => 'f2ad0683',
|
'phabricator-busy' => 'f2ad0683',
|
||||||
'phabricator-content-source-view-css' => 'dd27a69b',
|
'phabricator-content-source-view-css' => 'dd27a69b',
|
||||||
'phabricator-core-css' => '116c8dcd',
|
'phabricator-core-css' => 'f2a3a549',
|
||||||
'phabricator-crumbs-view-css' => '116c8dcd',
|
'phabricator-crumbs-view-css' => 'f2a3a549',
|
||||||
'phabricator-drag-and-drop-file-upload' => '9488bb69',
|
'phabricator-drag-and-drop-file-upload' => '9488bb69',
|
||||||
'phabricator-dropdown-menu' => 'f2ad0683',
|
'phabricator-dropdown-menu' => 'f2ad0683',
|
||||||
'phabricator-file-upload' => 'f2ad0683',
|
'phabricator-file-upload' => 'f2ad0683',
|
||||||
'phabricator-filetree-view-css' => '116c8dcd',
|
'phabricator-filetree-view-css' => 'f2a3a549',
|
||||||
'phabricator-flag-css' => '116c8dcd',
|
'phabricator-flag-css' => 'f2a3a549',
|
||||||
'phabricator-form-view-css' => '116c8dcd',
|
'phabricator-form-view-css' => 'f2a3a549',
|
||||||
'phabricator-header-view-css' => '116c8dcd',
|
'phabricator-header-view-css' => 'f2a3a549',
|
||||||
'phabricator-hovercard' => 'f2ad0683',
|
'phabricator-hovercard' => 'f2ad0683',
|
||||||
'phabricator-jump-nav' => '116c8dcd',
|
'phabricator-jump-nav' => 'f2a3a549',
|
||||||
'phabricator-keyboard-shortcut' => 'f2ad0683',
|
'phabricator-keyboard-shortcut' => 'f2ad0683',
|
||||||
'phabricator-keyboard-shortcut-manager' => 'f2ad0683',
|
'phabricator-keyboard-shortcut-manager' => 'f2ad0683',
|
||||||
'phabricator-main-menu-view' => '116c8dcd',
|
'phabricator-main-menu-view' => 'f2a3a549',
|
||||||
'phabricator-menu-item' => 'f2ad0683',
|
'phabricator-menu-item' => 'f2ad0683',
|
||||||
'phabricator-nav-view-css' => '116c8dcd',
|
'phabricator-nav-view-css' => 'f2a3a549',
|
||||||
'phabricator-notification' => 'f2ad0683',
|
'phabricator-notification' => 'f2ad0683',
|
||||||
'phabricator-notification-css' => '116c8dcd',
|
'phabricator-notification-css' => 'f2a3a549',
|
||||||
'phabricator-notification-menu-css' => '116c8dcd',
|
'phabricator-notification-menu-css' => 'f2a3a549',
|
||||||
'phabricator-object-item-list-view-css' => '116c8dcd',
|
'phabricator-object-item-list-view-css' => 'f2a3a549',
|
||||||
'phabricator-object-selector-css' => 'dd27a69b',
|
'phabricator-object-selector-css' => 'dd27a69b',
|
||||||
'phabricator-phtize' => 'f2ad0683',
|
'phabricator-phtize' => 'f2ad0683',
|
||||||
'phabricator-prefab' => 'f2ad0683',
|
'phabricator-prefab' => 'f2ad0683',
|
||||||
'phabricator-project-tag-css' => 'a7ca34a9',
|
'phabricator-project-tag-css' => 'a7ca34a9',
|
||||||
'phabricator-property-list-view-css' => '116c8dcd',
|
'phabricator-property-list-view-css' => 'f2a3a549',
|
||||||
'phabricator-remarkup-css' => '116c8dcd',
|
'phabricator-remarkup-css' => 'f2a3a549',
|
||||||
'phabricator-shaped-request' => '9488bb69',
|
'phabricator-shaped-request' => '9488bb69',
|
||||||
'phabricator-side-menu-view-css' => '116c8dcd',
|
'phabricator-side-menu-view-css' => 'f2a3a549',
|
||||||
'phabricator-standard-page-view' => '116c8dcd',
|
'phabricator-standard-page-view' => 'f2a3a549',
|
||||||
'phabricator-tag-view-css' => '116c8dcd',
|
'phabricator-tag-view-css' => 'f2a3a549',
|
||||||
'phabricator-textareautils' => 'f2ad0683',
|
'phabricator-textareautils' => 'f2ad0683',
|
||||||
'phabricator-tooltip' => 'f2ad0683',
|
'phabricator-tooltip' => 'f2ad0683',
|
||||||
'phabricator-transaction-view-css' => '116c8dcd',
|
'phabricator-transaction-view-css' => 'f2a3a549',
|
||||||
'phabricator-zindex-css' => '116c8dcd',
|
'phabricator-zindex-css' => 'f2a3a549',
|
||||||
'phui-button-css' => '116c8dcd',
|
'phui-button-css' => 'f2a3a549',
|
||||||
'phui-form-css' => '116c8dcd',
|
'phui-form-css' => 'f2a3a549',
|
||||||
'phui-icon-view-css' => '116c8dcd',
|
'phui-icon-view-css' => 'f2a3a549',
|
||||||
'phui-spacing-css' => '116c8dcd',
|
'phui-spacing-css' => 'f2a3a549',
|
||||||
'sprite-apps-large-css' => '116c8dcd',
|
'sprite-apps-large-css' => 'f2a3a549',
|
||||||
'sprite-gradient-css' => '116c8dcd',
|
'sprite-gradient-css' => 'f2a3a549',
|
||||||
'sprite-icons-css' => '116c8dcd',
|
'sprite-icons-css' => 'f2a3a549',
|
||||||
'sprite-menu-css' => '116c8dcd',
|
'sprite-menu-css' => 'f2a3a549',
|
||||||
'syntax-highlighting-css' => '116c8dcd',
|
'syntax-highlighting-css' => 'f2a3a549',
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
|
@ -821,6 +821,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
|
'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
|
||||||
'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php',
|
'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php',
|
||||||
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
|
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
|
||||||
|
'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php',
|
||||||
'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
|
'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
|
||||||
'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php',
|
'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php',
|
||||||
'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php',
|
'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php',
|
||||||
|
@ -2698,6 +2699,7 @@ phutil_register_library_map(array(
|
||||||
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
|
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
|
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
|
||||||
|
'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController',
|
||||||
'PhabricatorAuthProviderConfig' =>
|
'PhabricatorAuthProviderConfig' =>
|
||||||
array(
|
array(
|
||||||
0 => 'PhabricatorAuthDAO',
|
0 => 'PhabricatorAuthDAO',
|
||||||
|
|
|
@ -40,6 +40,7 @@ final class PhabricatorApplicationAuth extends PhabricatorApplication {
|
||||||
|
|
||||||
'(query/(?P<key>[^/]+)/)?' =>
|
'(query/(?P<key>[^/]+)/)?' =>
|
||||||
'PhabricatorAuthListController',
|
'PhabricatorAuthListController',
|
||||||
|
'config/new/' => 'PhabricatorAuthNewController',
|
||||||
|
|
||||||
*/
|
*/
|
||||||
'login/(?P<pkey>[^/]+)/' => 'PhabricatorAuthLoginController',
|
'login/(?P<pkey>[^/]+)/' => 'PhabricatorAuthLoginController',
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthNewController
|
||||||
|
extends PhabricatorAuthProviderConfigController {
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
|
$providers = PhabricatorAuthProvider::getAllBaseProviders();
|
||||||
|
|
||||||
|
$e_provider = null;
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$provider_string = $request->getStr('provider');
|
||||||
|
if (!strlen($provider_string)) {
|
||||||
|
$e_provider = pht('Required');
|
||||||
|
$errors[] = pht('You must select an authentication provider.');
|
||||||
|
} else {
|
||||||
|
$found = false;
|
||||||
|
foreach ($providers as $provider) {
|
||||||
|
if (get_class($provider) === $provider_string) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$found) {
|
||||||
|
$e_provider = pht('Invalid');
|
||||||
|
$errors[] = pht('You must select a valid provider.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errors) {
|
||||||
|
return id(new AphrontRedirectResponse())->setURI(
|
||||||
|
$this->getApplicationURI('/config/new/'.$provider_string.'/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors) {
|
||||||
|
$errors = id(new AphrontErrorView())->setErrors($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = id(new AphrontFormRadioButtonControl())
|
||||||
|
->setLabel(pht('Provider'))
|
||||||
|
->setName('provider')
|
||||||
|
->setError($e_provider);
|
||||||
|
|
||||||
|
$providers = msort($providers, 'getProviderName');
|
||||||
|
foreach ($providers as $provider) {
|
||||||
|
$options->addButton(
|
||||||
|
get_class($provider),
|
||||||
|
$provider->getNameForCreate(),
|
||||||
|
$provider->getDescriptionForCreate());
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->appendChild($options)
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormSubmitControl())
|
||||||
|
->addCancelButton($this->getApplicationURI())
|
||||||
|
->setValue(pht('Continue')));
|
||||||
|
|
||||||
|
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$crumbs->addCrumb(
|
||||||
|
id(new PhabricatorCrumbView())
|
||||||
|
->setName(pht('Add Provider')));
|
||||||
|
|
||||||
|
return $this->buildApplicationPage(
|
||||||
|
array(
|
||||||
|
$crumbs,
|
||||||
|
$errors,
|
||||||
|
$form,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => pht('Add Authentication Provider'),
|
||||||
|
'dust' => true,
|
||||||
|
'device' => true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
abstract class PhabricatorAuthProvider {
|
abstract class PhabricatorAuthProvider {
|
||||||
|
|
||||||
|
public function getNameForCreate() {
|
||||||
|
return $this->getProviderName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescriptionForCreate() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public function getProviderKey() {
|
public function getProviderKey() {
|
||||||
return $this->getAdapter()->getAdapterKey();
|
return $this->getAdapter()->getAdapterKey();
|
||||||
}
|
}
|
||||||
|
@ -14,13 +22,24 @@ abstract class PhabricatorAuthProvider {
|
||||||
return $this->getAdapter()->getAdapterDomain();
|
return $this->getAdapter()->getAdapterDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAllProviders() {
|
public static function getAllBaseProviders() {
|
||||||
static $providers;
|
static $providers;
|
||||||
|
|
||||||
if ($providers === null) {
|
if ($providers === null) {
|
||||||
$objects = id(new PhutilSymbolLoader())
|
$objects = id(new PhutilSymbolLoader())
|
||||||
->setAncestorClass(__CLASS__)
|
->setAncestorClass(__CLASS__)
|
||||||
->loadObjects();
|
->loadObjects();
|
||||||
|
$providers = $objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAllProviders() {
|
||||||
|
static $providers;
|
||||||
|
|
||||||
|
if ($providers === null) {
|
||||||
|
$objects = self::getAllBaseProviders();
|
||||||
|
|
||||||
$providers = array();
|
$providers = array();
|
||||||
$from_class_map = array();
|
$from_class_map = array();
|
||||||
|
|
|
@ -9,6 +9,13 @@ final class PhabricatorAuthProviderLDAP
|
||||||
return pht('LDAP');
|
return pht('LDAP');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDescriptionForCreate() {
|
||||||
|
return pht(
|
||||||
|
'Configure a connection to an LDAP server so that users can use their '.
|
||||||
|
'LDAP credentials to log in to Phabricator.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function isEnabled() {
|
public function isEnabled() {
|
||||||
return parent::isEnabled() &&
|
return parent::isEnabled() &&
|
||||||
PhabricatorEnv::getEnvConfig('ldap.auth-enabled');
|
PhabricatorEnv::getEnvConfig('ldap.auth-enabled');
|
||||||
|
|
|
@ -8,6 +8,11 @@ abstract class PhabricatorAuthProviderOAuth extends PhabricatorAuthProvider {
|
||||||
abstract protected function getOAuthClientSecret();
|
abstract protected function getOAuthClientSecret();
|
||||||
abstract protected function newOAuthAdapter();
|
abstract protected function newOAuthAdapter();
|
||||||
|
|
||||||
|
|
||||||
|
public function getDescriptionForCreate() {
|
||||||
|
return pht('Configure %s OAuth.', $this->getProviderName());
|
||||||
|
}
|
||||||
|
|
||||||
public function getAdapter() {
|
public function getAdapter() {
|
||||||
if (!$this->adapter) {
|
if (!$this->adapter) {
|
||||||
$adapter = $this->newOAuthAdapter();
|
$adapter = $this->newOAuthAdapter();
|
||||||
|
|
|
@ -9,6 +9,11 @@ final class PhabricatorAuthProviderPassword
|
||||||
return pht('Username/Password');
|
return pht('Username/Password');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDescriptionForCreate() {
|
||||||
|
return pht(
|
||||||
|
'Allow users to login or register using a username and password.');
|
||||||
|
}
|
||||||
|
|
||||||
public function isEnabled() {
|
public function isEnabled() {
|
||||||
return parent::isEnabled() &&
|
return parent::isEnabled() &&
|
||||||
PhabricatorEnv::getEnvConfig('auth.password-auth-enabled');
|
PhabricatorEnv::getEnvConfig('auth.password-auth-enabled');
|
||||||
|
|
|
@ -180,9 +180,11 @@ table.aphront-form-control-checkbox-layout {
|
||||||
|
|
||||||
table.aphront-form-control-radio-layout th,
|
table.aphront-form-control-radio-layout th,
|
||||||
table.aphront-form-control-checkbox-layout th {
|
table.aphront-form-control-checkbox-layout th {
|
||||||
padding-top: 2px;
|
padding-top: 3px;
|
||||||
padding-left: 0.35em;
|
padding-left: 8px;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #222222;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aphront-form-control-radio-layout td input,
|
.aphront-form-control-radio-layout td input,
|
||||||
|
@ -192,9 +194,10 @@ table.aphront-form-control-checkbox-layout th {
|
||||||
}
|
}
|
||||||
|
|
||||||
.aphront-form-radio-caption {
|
.aphront-form-radio-caption {
|
||||||
font-size: 11px;
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
color: #444444;
|
color: #444444;
|
||||||
max-width: 400px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.aphront-form-control-image span {
|
.aphront-form-control-image span {
|
||||||
|
|
Loading…
Add table
Reference in a new issue