nextcloud_webdav_client-1.0.x-dev/src/Form/NextCloudWebDavSettingsForm.php
src/Form/NextCloudWebDavSettingsForm.php
<?php
namespace Drupal\nextcloud_webdav_client\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\nextcloud_webdav_client\Service\NextCloudWebDavClient;
use Drupal\nextcloud_webdav_client\Service\NextCloudOAuth2Manager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure NextCloud WebDAV settings.
*/
class NextCloudWebDavSettingsForm extends ConfigFormBase {
/**
* The NextCloud WebDAV client service.
*
* @var \Drupal\nextcloud_webdav_client\Service\NextCloudWebDavClient
*/
protected $webdavClient;
/**
* The OAuth2 manager service.
*
* @var \Drupal\nextcloud_webdav_client\Service\NextCloudOAuth2Manager
*/
protected $oauth2Manager;
/**
* Constructs a NextCloudWebDavSettingsForm object.
*
* @param \Drupal\nextcloud_webdav_client\Service\NextCloudWebDavClient $webdav_client
* The NextCloud WebDAV client service.
* @param \Drupal\nextcloud_webdav_client\Service\NextCloudOAuth2Manager $oauth2_manager
* The OAuth2 manager service.
*/
public function __construct(NextCloudWebDavClient $webdav_client, NextCloudOAuth2Manager $oauth2_manager) {
$this->webdavClient = $webdav_client;
$this->oauth2Manager = $oauth2_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('nextcloud_webdav_client.client'),
$container->get('nextcloud_webdav_client.oauth2_manager')
);
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'nextcloud_webdav_client.settings',
'oauth2_server.oauth',
];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'nextcloud_webdav_client_settings';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('nextcloud_webdav_client.settings');
// Authentication Mode Selection.
$form['auth_mode'] = [
'#type' => 'radios',
'#title' => $this->t('Authentication Mode'),
'#options' => [
'basic' => $this->t('Basic Authentication (Username & Password)'),
'oauth2' => $this->t('OAuth2 / SSO Authentication'),
],
'#default_value' => $config->get('auth_mode') ?: 'basic',
'#description' => $this->t('Select how to authenticate with NextCloud. Basic Auth uses username/password, OAuth2 uses token-based SSO.'),
'#required' => TRUE,
];
// Connection Settings.
$form['connection'] = [
'#type' => 'fieldset',
'#title' => $this->t('Connection Settings'),
'#collapsible' => FALSE,
];
$form['connection']['server_url'] = [
'#type' => 'url',
'#title' => $this->t('Server URL'),
'#description' => $this->t('The base URL of your NextCloud server (e.g., https://cloud.example.com)'),
'#default_value' => $config->get('server_url'),
'#required' => TRUE,
];
$form['connection']['base_path'] = [
'#type' => 'textfield',
'#title' => $this->t('Base Path'),
'#description' => $this->t('The WebDAV base path (usually /remote.php/dav/files/)'),
'#default_value' => $config->get('base_path') ?: '/remote.php/dav/files/',
'#required' => TRUE,
];
// Basic Auth Fields (conditional).
$form['basic_auth'] = [
'#type' => 'fieldset',
'#title' => $this->t('Basic Authentication Credentials'),
'#states' => [
'visible' => [
':input[name="auth_mode"]' => ['value' => 'basic'],
],
],
];
$form['basic_auth']['username'] = [
'#type' => 'textfield',
'#title' => $this->t('Username'),
'#description' => $this->t('Your NextCloud username'),
'#default_value' => $config->get('username'),
'#states' => [
'required' => [
':input[name="auth_mode"]' => ['value' => 'basic'],
],
],
];
// Check if password already exists.
$existing_password = $config->get('password');
$password_description = $this->t('Your NextCloud password or app password (recommended)');
if (!empty($existing_password)) {
$password_description = $this->t('Your NextCloud password or app password (recommended). Leave empty to keep the current password.');
$form['basic_auth']['password_status'] = [
'#type' => 'markup',
'#markup' => '<div class="messages messages--status">' .
$this->t('✓ Password is currently set. Leave the password field empty to keep the existing password.') .
'</div>',
];
}
$form['basic_auth']['password'] = [
'#type' => 'password',
'#title' => $this->t('Password'),
'#description' => $password_description,
];
// OAuth2 Configuration Fields (conditional).
$form['oauth2_config'] = [
'#type' => 'fieldset',
'#title' => $this->t('OAuth2 Configuration'),
'#states' => [
'visible' => [
':input[name="auth_mode"]' => ['value' => 'oauth2'],
],
],
];
$form['oauth2_config']['oauth2_client_id'] = [
'#type' => 'textfield',
'#title' => $this->t('Client ID'),
'#description' => $this->t('OAuth2 Client ID from your NextCloud OAuth2 app'),
'#default_value' => $config->get('oauth2_client_id'),
'#states' => [
'required' => [
':input[name="auth_mode"]' => ['value' => 'oauth2'],
],
],
];
$form['oauth2_config']['oauth2_client_secret'] = [
'#type' => 'password',
'#title' => $this->t('Client Secret'),
'#description' => $this->t('OAuth2 Client Secret from your NextCloud OAuth2 app'),
'#default_value' => $config->get('oauth2_client_secret'),
];
if (!empty($config->get('oauth2_client_secret'))) {
$form['oauth2_config']['oauth2_client_secret']['#description'] = $this->t('OAuth2 Client Secret (leave empty to keep existing secret)');
}
$form['oauth2_config']['oauth2_authorize_endpoint'] = [
'#type' => 'url',
'#title' => $this->t('Authorization Endpoint'),
'#description' => $this->t('OAuth2 authorization endpoint URL (e.g., https://cloud.example.com/apps/oauth2/authorize)'),
'#default_value' => $config->get('oauth2_authorize_endpoint'),
'#states' => [
'required' => [
':input[name="auth_mode"]' => ['value' => 'oauth2'],
],
],
];
$form['oauth2_config']['oauth2_token_endpoint'] = [
'#type' => 'url',
'#title' => $this->t('Token Endpoint'),
'#description' => $this->t('OAuth2 token endpoint URL (e.g., https://cloud.example.com/apps/oauth2/api/v1/token)'),
'#default_value' => $config->get('oauth2_token_endpoint'),
'#states' => [
'required' => [
':input[name="auth_mode"]' => ['value' => 'oauth2'],
],
],
];
$form['oauth2_config']['oauth2_userinfo_endpoint'] = [
'#type' => 'url',
'#title' => $this->t('User Info Endpoint (Optional)'),
'#description' => $this->t('OAuth2 user info endpoint URL'),
'#default_value' => $config->get('oauth2_userinfo_endpoint'),
];
$form['oauth2_config']['oauth2_scopes'] = [
'#type' => 'textfield',
'#title' => $this->t('Scopes'),
'#description' => $this->t('OAuth2 scopes (space-separated)'),
'#default_value' => $config->get('oauth2_scopes') ?: 'openid profile email',
];
$form['oauth2_config']['oauth2_mode'] = [
'#type' => 'radios',
'#title' => $this->t('OAuth2 Mode'),
'#options' => [
'site' => $this->t('Site-wide (single NextCloud account for all users)'),
'per-user' => $this->t('Per-user (each Drupal user links their own NextCloud account)'),
],
'#default_value' => $config->get('oauth2_mode') ?: 'site',
'#description' => $this->t('Choose how OAuth2 authentication works. Site-wide uses one account for all operations. Per-user allows each user to link their own account.'),
'#states' => [
'visible' => [
':input[name="auth_mode"]' => ['value' => 'oauth2'],
],
],
];
$form['oauth2_config']['oauth2_username'] = [
'#type' => 'textfield',
'#title' => $this->t('NextCloud Username'),
'#description' => $this->t('Your NextCloud username (required for WebDAV file paths)'),
'#default_value' => $config->get('oauth2_username'),
'#states' => [
'required' => [
':input[name="auth_mode"]' => ['value' => 'oauth2'],
':input[name="oauth2_mode"]' => ['value' => 'site'],
],
'visible' => [
':input[name="oauth2_mode"]' => ['value' => 'site'],
],
],
];
$form['oauth2_config']['per_user_info'] = [
'#type' => 'markup',
'#markup' => '<div class="messages messages--info">' .
$this->t('In per-user mode, users link their own NextCloud accounts at /user/{uid}/nextcloud. Configure OAuth2 endpoints above, then grant users the "link nextcloud account" permission.') .
'</div>',
'#states' => [
'visible' => [
':input[name="oauth2_mode"]' => ['value' => 'per-user'],
],
],
];
$form['oauth2_config']['oauth2_use_index_php'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use /index.php/ prefix in OAuth2 URLs'),
'#description' => $this->t('Check this if your NextCloud requires /index.php/ in the path (e.g., https://nextcloud.com/index.php/apps/oauth2/authorize)'),
'#default_value' => $config->get('oauth2_use_index_php'),
];
// Get current OAuth2 Server user_sub_property setting.
$oauth2_server_config = $this->configFactory->get('oauth2_server.oauth');
$current_user_sub = $oauth2_server_config->get('user_sub_property') ?: 'uid';
$form['oauth2_config']['oauth2_user_identifier'] = [
'#type' => 'radios',
'#title' => $this->t('User Identifier for OAuth2'),
'#options' => [
'uid' => $this->t('User ID (numeric, e.g., 1, 2, 3)'),
'name' => $this->t('Username (e.g., john_doe, admin)'),
'mail' => $this->t('Email Address (e.g., user@example.com)'),
],
'#default_value' => $current_user_sub,
'#description' => $this->t('Choose which Drupal user field to use as the identifier when users log in to NextCloud via OAuth2. This determines what NextCloud will use as the username. <strong>Note:</strong> Changing this after users have already logged in may require them to re-authenticate.'),
'#states' => [
'visible' => [
':input[name="auth_mode"]' => ['value' => 'oauth2'],
],
],
];
// OAuth2 Authorization Status.
$has_tokens = !empty($config->get('oauth2_access_token'));
$token_expiry = $this->oauth2Manager->getTokenExpiry();
$form['oauth2_status'] = [
'#type' => 'fieldset',
'#title' => $this->t('OAuth2 Authorization Status'),
'#states' => [
'visible' => [
':input[name="auth_mode"]' => ['value' => 'oauth2'],
],
],
];
if ($has_tokens) {
$status_markup = '<div class="messages messages--status">';
$status_markup .= '<strong>' . $this->t('✓ OAuth2 Authorization Active') . '</strong><br>';
if ($token_expiry) {
$expiry_date = \Drupal::service('date.formatter')->format($token_expiry, 'medium');
$status_markup .= $this->t('Token expires: @date', ['@date' => $expiry_date]);
}
$status_markup .= '</div>';
$form['oauth2_status']['status'] = [
'#type' => 'markup',
'#markup' => $status_markup,
];
// Refresh Token Link.
$form['oauth2_status']['refresh_link'] = [
'#type' => 'markup',
'#markup' => '<p>' . $this->t('<a href="@url">Refresh Token</a> | <a href="@clear_url">Clear Authorization</a>', [
'@url' => Url::fromRoute('nextcloud_webdav_client.oauth2_refresh')->toString(),
'@clear_url' => Url::fromRoute('nextcloud_webdav_client.oauth2_clear')->toString(),
]) . '</p>',
];
}
else {
$form['oauth2_status']['no_auth'] = [
'#type' => 'markup',
'#markup' => '<div class="messages messages--warning">' .
$this->t('OAuth2 authorization required. Save your OAuth2 configuration first, then click the "Authorize with NextCloud" button to begin authorization.') .
'</div>',
];
// Authorization button (only show if config is saved).
if ($this->oauth2Manager->isConfigured()) {
$initiate_url = Url::fromRoute('nextcloud_webdav_client.oauth2_initiate')->toString();
$form['oauth2_status']['authorize_button'] = [
'#type' => 'markup',
'#markup' => '<p><a href="' . $initiate_url . '" class="button button--primary">' .
$this->t('Authorize with NextCloud') .
'</a></p>',
];
}
}
// Advanced Settings.
$form['advanced'] = [
'#type' => 'fieldset',
'#title' => $this->t('Advanced Settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$form['advanced']['timeout'] = [
'#type' => 'number',
'#title' => $this->t('Timeout'),
'#description' => $this->t('Request timeout in seconds'),
'#default_value' => $config->get('timeout') ?: 30,
'#min' => 1,
'#max' => 300,
];
$form['advanced']['verify_ssl'] = [
'#type' => 'checkbox',
'#title' => $this->t('Verify SSL certificates'),
'#description' => $this->t('Uncheck this only for development or self-signed certificates'),
'#default_value' => $config->get('verify_ssl') !== FALSE,
];
// Security Information.
$form['security_info'] = [
'#type' => 'fieldset',
'#title' => $this->t('Security Information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$form['security_info']['info'] = [
'#type' => 'markup',
'#markup' => '<div class="description">' .
'<div class="messages messages--warning" style="margin-bottom: 1em;">' .
'<strong>' . $this->t('⚠️ Security Notice:') . '</strong> ' .
$this->t('Credentials are stored <strong>unencrypted</strong> in Drupal\'s configuration. Ensure config exports and database backups are properly secured. For enhanced security, consider using OAuth2 authentication instead.') .
'</div>' .
'<h4>' . $this->t('Authentication Methods') . '</h4>' .
'<p><strong>' . $this->t('Basic Authentication:') . '</strong> ' .
$this->t('Credentials are stored in Drupal\'s configuration system. Use NextCloud app passwords for better security.') .
'</p>' .
'<p><strong>' . $this->t('OAuth2 Authentication (Recommended):') . '</strong> ' .
$this->t('Uses secure token-based authentication. Tokens are automatically refreshed. Provides better security than storing passwords.') .
'</p>' .
'<h4>' . $this->t('How to create NextCloud App Password (for Basic Auth):') . '</h4>' .
'<ol>' .
'<li>' . $this->t('Log into your NextCloud web interface') . '</li>' .
'<li>' . $this->t('Go to Settings > Personal > Security') . '</li>' .
'<li>' . $this->t('Scroll to "App passwords" section') . '</li>' .
'<li>' . $this->t('Enter a name (e.g., "Drupal WebDAV") and click "Create new app password"') . '</li>' .
'<li>' . $this->t('Copy the generated password and use it here') . '</li>' .
'</ol>' .
'</div>',
];
// Connection Test.
$form['test'] = [
'#type' => 'fieldset',
'#title' => $this->t('Connection Test'),
'#collapsible' => FALSE,
];
$form['test']['test_connection'] = [
'#type' => 'button',
'#value' => $this->t('Test Connection'),
'#ajax' => [
'callback' => '::testConnectionCallback',
'wrapper' => 'test-result',
'method' => 'replace',
'effect' => 'fade',
],
];
$form['test']['test_result'] = [
'#type' => 'markup',
'#markup' => '',
'#prefix' => '<div id="test-result">',
'#suffix' => '</div>',
];
return parent::buildForm($form, $form_state);
}
/**
* Ajax callback for testing the connection.
*/
public function testConnectionCallback(array &$form, FormStateInterface $form_state) {
// Get form values
$test_password = $form_state->getValue('password');
$existing_password = $this->config('nextcloud_webdav_client.settings')->get('password');
// Use existing password if no new password provided
$password_to_test = !empty($test_password) ? $test_password : $existing_password;
// Temporarily update config with form values for testing.
$temp_config = [
'auth_mode' => $form_state->getValue('auth_mode'),
'server_url' => $form_state->getValue('server_url'),
'username' => $form_state->getValue('username'),
'password' => $password_to_test,
'base_path' => $form_state->getValue('base_path'),
'timeout' => $form_state->getValue('timeout'),
'verify_ssl' => $form_state->getValue('verify_ssl'),
'oauth2_username' => $form_state->getValue('oauth2_username'),
];
// Save current config.
$current_config = $this->config('nextcloud_webdav_client.settings');
$original_values = [];
foreach ($temp_config as $key => $value) {
$original_values[$key] = $current_config->get($key);
}
// Set temporary values.
$config = $this->configFactory->getEditable('nextcloud_webdav_client.settings');
foreach ($temp_config as $key => $value) {
$config->set($key, $value);
}
$config->save();
// Test connection.
$success = $this->webdavClient->testConnection();
// Restore original values.
foreach ($original_values as $key => $value) {
$config->set($key, $value);
}
$config->save();
// Prepare result message.
if ($success) {
$message = [
'#type' => 'markup',
'#markup' => '<div class="messages messages--status">' . $this->t('Connection successful!') . '</div>',
];
}
else {
$message = [
'#type' => 'markup',
'#markup' => '<div class="messages messages--error">' . $this->t('Connection failed. Please check your settings.') . '</div>',
];
}
$form['test']['test_result'] = $message + [
'#prefix' => '<div id="test-result">',
'#suffix' => '</div>',
];
return $form['test']['test_result'];
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this->configFactory->getEditable('nextcloud_webdav_client.settings');
$auth_mode = $form_state->getValue('auth_mode');
// Save authentication mode.
$config->set('auth_mode', $auth_mode);
// Save common settings.
$config
->set('server_url', $form_state->getValue('server_url'))
->set('base_path', $form_state->getValue('base_path'))
->set('timeout', $form_state->getValue('timeout'))
->set('verify_ssl', $form_state->getValue('verify_ssl'));
// Save authentication-specific settings.
if ($auth_mode === 'basic') {
// Handle Basic Auth credentials.
$new_password = $form_state->getValue('password');
$existing_password = $config->get('password');
$password_to_save = !empty($new_password) ? $new_password : $existing_password;
$config
->set('username', $form_state->getValue('username'))
->set('password', $password_to_save);
$config->save();
// Add message about password handling.
if (empty($new_password) && !empty($existing_password)) {
$this->messenger()->addStatus($this->t('Settings saved. The existing password was kept unchanged.'));
}
else {
$this->messenger()->addStatus($this->t('Settings saved successfully.'));
}
}
else {
// Handle OAuth2 configuration.
$new_client_secret = $form_state->getValue('oauth2_client_secret');
$existing_client_secret = $config->get('oauth2_client_secret');
$client_secret_to_save = !empty($new_client_secret) ? $new_client_secret : $existing_client_secret;
$config
->set('oauth2_client_id', $form_state->getValue('oauth2_client_id'))
->set('oauth2_client_secret', $client_secret_to_save)
->set('oauth2_authorize_endpoint', $form_state->getValue('oauth2_authorize_endpoint'))
->set('oauth2_token_endpoint', $form_state->getValue('oauth2_token_endpoint'))
->set('oauth2_userinfo_endpoint', $form_state->getValue('oauth2_userinfo_endpoint'))
->set('oauth2_scopes', $form_state->getValue('oauth2_scopes'))
->set('oauth2_mode', $form_state->getValue('oauth2_mode'))
->set('oauth2_username', $form_state->getValue('oauth2_username'))
->set('oauth2_use_index_php', $form_state->getValue('oauth2_use_index_php'));
$config->save();
// Save the OAuth2 user identifier setting to the OAuth2 Server config.
$user_identifier = $form_state->getValue('oauth2_user_identifier');
if ($user_identifier) {
$oauth2_server_config = $this->configFactory->getEditable('oauth2_server.oauth');
$oauth2_server_config->set('user_sub_property', $user_identifier)->save();
}
if ($this->oauth2Manager->isConfigured()) {
$this->messenger()->addStatus($this->t('OAuth2 configuration saved successfully. You can now authorize with NextCloud.'));
}
else {
$this->messenger()->addStatus($this->t('Settings saved successfully.'));
}
}
}
} 