settings = $settings;
$this->auth = $auth;
$this->graph = $graph;
add_action( 'admin_menu', array( $this, 'menu' ) );
add_action( 'admin_init', array( $this, 'register' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
add_action( 'wp_ajax_' . self::AJAX_TEST, array( $this, 'ajax_test_connection' ) );
add_action( 'wp_ajax_' . self::AJAX_GROUPS, array( $this, 'ajax_search_groups' ) );
add_action( 'update_option_' . M365_LOGIN_OPTION, array( $this->graph, 'flush_token' ) );
add_action( 'admin_notices', array( $this, 'setup_notice' ) );
}
/**
* Adds the menu entry under Settings.
*/
public function menu() {
$this->hook = add_options_page(
__( 'M365 Login', 'm365-login' ),
__( 'M365 Login', 'm365-login' ),
'manage_options',
self::PAGE,
array( $this, 'render' )
);
}
/**
* Registers the option with the Settings API.
*/
public function register() {
register_setting(
self::GROUP,
M365_LOGIN_OPTION,
array(
'type' => 'array',
'sanitize_callback' => array( $this->settings, 'sanitize' ),
'default' => $this->settings->defaults(),
)
);
}
/**
* Nudges administrators to finish the setup.
*/
public function setup_notice() {
if ( $this->settings->is_configured() || ! current_user_can( 'manage_options' ) ) {
return;
}
$screen = get_current_screen();
if ( $screen && $this->hook === $screen->id ) {
return;
}
if ( ! $screen || ! in_array( $screen->id, array( 'plugins', 'dashboard' ), true ) ) {
return;
}
printf(
'
',
esc_html__( 'M365 Login is active but not connected to Microsoft Entra ID yet.', 'm365-login' ),
esc_url( admin_url( 'options-general.php?page=' . self::PAGE ) ),
esc_html__( 'Open the settings', 'm365-login' )
);
}
/**
* Loads assets on our screen only.
*
* @param string $hook Current screen hook.
*/
public function enqueue( $hook ) {
if ( $hook !== $this->hook ) {
return;
}
wp_enqueue_media();
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_style( 'm365-login-admin', M365_LOGIN_URL . 'assets/css/admin.css', array( 'wp-color-picker' ), M365_LOGIN_VERSION );
wp_enqueue_script( 'm365-login-admin', M365_LOGIN_URL . 'assets/js/admin.js', array( 'jquery', 'wp-color-picker' ), M365_LOGIN_VERSION, true );
wp_localize_script(
'm365-login-admin',
'm365LoginAdmin',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( self::NONCE_TEST ),
'action' => self::AJAX_TEST,
'groupAction' => self::AJAX_GROUPS,
'defaultLogo' => M365_Login_Button::microsoft_logo(),
'i18n' => array(
'chooseIcon' => __( 'Choose button icon', 'm365-login' ),
'useIcon' => __( 'Use this icon', 'm365-login' ),
'copied' => __( 'Copied!', 'm365-login' ),
'copy' => __( 'Copy', 'm365-login' ),
'testing' => __( 'Testing…', 'm365-login' ),
'testFailed' => __( 'The tenant could not be reached. Check the tenant ID and the server’s outgoing connections.', 'm365-login' ),
'noGroups' => __( 'No groups found.', 'm365-login' ),
'searching' => __( 'Searching…', 'm365-login' ),
'add' => __( 'Add', 'm365-login' ),
'remove' => __( 'Remove', 'm365-login' ),
'saveFirst' => __( 'Save the connection settings first, then search for groups.', 'm365-login' ),
'confirmKey' => __( 'Generate a new fallback key on save? The old link stops working.', 'm365-login' ),
),
)
);
}
/**
* AJAX: fetch the OpenID configuration for the tenant typed into the form.
*/
public function ajax_test_connection() {
check_ajax_referer( self::NONCE_TEST, 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You are not allowed to do this.', 'm365-login' ) ), 403 );
}
$tenant = isset( $_POST['tenant'] ) ? strtolower( sanitize_text_field( wp_unslash( $_POST['tenant'] ) ) ) : '';
if ( '' === $tenant || ! M365_Login_Settings::is_valid_tenant( $tenant ) ) {
wp_send_json_error( array( 'message' => __( 'Please enter a valid tenant ID first.', 'm365-login' ) ) );
}
$url = 'https://login.microsoftonline.com/' . rawurlencode( $tenant ) . '/v2.0/.well-known/openid-configuration';
$response = wp_remote_get( $url, array( 'timeout' => M365_Login_Auth::HTTP_TIMEOUT ) );
if ( is_wp_error( $response ) ) {
wp_send_json_error( array( 'message' => $response->get_error_message() ) );
}
$code = (int) wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( 200 !== $code || ! is_array( $body ) || empty( $body['issuer'] ) ) {
wp_send_json_error(
array(
/* translators: %d: HTTP status code */
'message' => sprintf( __( 'Microsoft answered with HTTP %d. Is the tenant ID correct?', 'm365-login' ), $code ),
)
);
}
wp_send_json_success(
array(
'issuer' => esc_url_raw( $body['issuer'] ),
'endpoint' => isset( $body['authorization_endpoint'] ) ? esc_url_raw( $body['authorization_endpoint'] ) : '',
'message' => __( 'Tenant reachable. The OpenID configuration was loaded successfully.', 'm365-login' ),
)
);
}
/**
* AJAX: search Entra groups through Microsoft Graph.
*/
public function ajax_search_groups() {
check_ajax_referer( self::NONCE_TEST, 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You are not allowed to do this.', 'm365-login' ) ), 403 );
}
if ( ! $this->settings->is_configured() ) {
wp_send_json_error( array( 'message' => __( 'Save the connection settings first, then search for groups.', 'm365-login' ) ) );
}
$query = isset( $_POST['query'] ) ? sanitize_text_field( wp_unslash( $_POST['query'] ) ) : '';
$groups = $this->graph->search_groups( mb_substr( $query, 0, 100 ) );
if ( is_wp_error( $groups ) ) {
$message = $groups->get_error_message();
if ( false !== stripos( $message, 'Authorization_RequestDenied' ) || false !== stripos( $message, 'Insufficient privileges' ) ) {
$message = __( 'Microsoft Graph refused the request. Grant the application permission "GroupMember.Read.All" (or "Directory.Read.All") with admin consent in Entra ID.', 'm365-login' );
}
wp_send_json_error( array( 'message' => $message ) );
}
wp_send_json_success( array( 'groups' => $groups ) );
}
/**
* Renders the settings screen.
*/
public function render() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to access this page.', 'm365-login' ) );
}
$s = $this->settings->all();
$configured = $this->settings->is_configured();
$has_secret = '' !== $this->settings->client_secret();
$option = M365_LOGIN_OPTION;
$field = function ( $key ) use ( $option ) {
return esc_attr( $option . '[' . $key . ']' );
};
?>