settings = $settings; add_action( 'login_enqueue_scripts', array( $this, 'enqueue' ) ); add_filter( 'login_body_class', array( $this, 'body_class' ) ); add_filter( 'login_message', array( $this, 'render_above' ), 20 ); add_action( 'login_footer', array( $this, 'render_below' ) ); add_shortcode( 'm365_login_button', array( $this, 'shortcode' ) ); // Custom login pages built with wp_login_form(). add_filter( 'login_form_top', array( $this, 'form_top' ), 10, 2 ); add_filter( 'login_form_bottom', array( $this, 'form_bottom' ), 10, 2 ); } /** * Whether the plugin should hook into wp_login_form() output. * * @return bool */ private function inject_into_forms() { return $this->settings->is_configured() && (bool) $this->settings->get( 'inject_form' ) && ! is_user_logged_in(); } /** * wp_login_form(): messages above the fields; in button-only mode the fields are wrapped and hidden. * * @param string $content Existing content. * @param array $args wp_login_form() arguments. * @return string */ public function form_top( $content, $args = array() ) { if ( ! $this->inject_into_forms() ) { return $content; } $this->enqueue_frontend(); $content .= $this->messages_markup(); if ( $this->password_login_hidden() ) { $content .= ''; } $divider = $hidden ? '' : $this->divider_markup(); return $content . '
' . $divider . $this->button( $redirect_to ) . '
'; } /** * Whether password fields should be hidden right now (button-only mode without fallback). * * @return bool */ private function password_login_hidden() { return $this->settings->button_only() && ! M365_Login::instance()->auth->fallback_active(); } /** * Enqueues the stylesheet on the front end (custom login pages). */ public function enqueue_frontend() { wp_enqueue_style( 'm365-login', M365_LOGIN_URL . 'assets/css/login.css', array(), M365_LOGIN_VERSION ); wp_add_inline_style( 'm365-login', $this->css_variables() ); } /** * Divider line markup ('' when the divider text is empty). * * @return string */ private function divider_markup() { $divider = (string) $this->settings->get( 'divider_text' ); return '' === trim( $divider ) ? '' : ''; } /** * Error / info messages for custom login pages. * * @return string */ public function messages_markup() { $html = ''; foreach ( M365_Login::instance()->auth->current_messages() as $msg ) { $html .= '
' . esc_html( $msg['text'] ) . '
'; } return $html; } /** * Full markup for templates and shortcodes. * * @param array $args { * @type string $redirect Destination after login. * @type bool $show_messages Show error/info messages. Default true. * @type bool $show_divider Show the divider line. Default false. * } * @return string */ public function standalone( $args = array() ) { if ( ! $this->settings->is_configured() || is_user_logged_in() ) { return ''; } $args = wp_parse_args( $args, array( 'redirect' => '', 'show_messages' => true, 'show_divider' => false, ) ); $this->enqueue_frontend(); $html = '
'; if ( $args['show_messages'] ) { $html .= $this->messages_markup(); } if ( $args['show_divider'] && ! $this->password_login_hidden() ) { $html .= $this->divider_markup(); } $html .= $this->button( esc_url_raw( (string) $args['redirect'] ) ); return $html . '
'; } /** * Whether the button should be shown for the current login screen. * * @return bool */ private function should_render() { if ( ! $this->settings->is_configured() || ! $this->is_login_action() ) { return false; } /** * Filters whether the Microsoft button is displayed on the login screen. * * @param bool $show Show the button. */ return (bool) apply_filters( 'm365_login_show_button', true ); } /** * Whether the current wp-login.php request shows the sign-in form (not interim login or another action). * * @return bool */ private function is_login_action() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only routing check. $action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : 'login'; $interim = ! empty( $_REQUEST['interim-login'] ); // phpcs:enable WordPress.Security.NonceVerification.Recommended return ! $interim && in_array( $action, array( '', 'login' ), true ); } /** * Whether the password form is hidden for this request. * * Also while the connection is broken: password sign-in is refused anyway, the fields would only mislead. * * @return bool */ private function form_hidden() { if ( ! $this->settings->button_only() || M365_Login::instance()->auth->fallback_active() || ! $this->is_login_action() ) { return false; } return $this->should_render() || ! $this->settings->is_configured(); } /** * Adds a body class while the password form is hidden. * * @param string[] $classes Body classes. * @return string[] */ public function body_class( $classes ) { if ( $this->should_render() ) { $classes[] = 'm365-login-page'; } if ( $this->form_hidden() ) { $classes[] = 'm365-button-only'; } return $classes; } /** * Enqueues login styles and the small positioning script. */ public function enqueue() { // Also when only the form is hidden (e.g. broken connection): the stylesheet hides the password form and links. if ( ! $this->should_render() && ! $this->form_hidden() ) { return; } wp_enqueue_style( 'm365-login', M365_LOGIN_URL . 'assets/css/login.css', array(), M365_LOGIN_VERSION ); wp_add_inline_style( 'm365-login', $this->css_variables() ); if ( 'below' === $this->settings->get( 'button_position' ) ) { wp_enqueue_script( 'm365-login', M365_LOGIN_URL . 'assets/js/login.js', array(), M365_LOGIN_VERSION, true ); } } /** * CSS custom properties derived from the settings. * * @return string */ public function css_variables() { $s = $this->settings->all(); return sprintf( '.m365-login{--m365-bg:%1$s;--m365-bg-hover:%2$s;--m365-color:%3$s;--m365-border:%4$s;--m365-radius:%5$dpx;}', sanitize_hex_color( $s['button_bg'] ), sanitize_hex_color( $s['button_bg_hover'] ), sanitize_hex_color( $s['button_color'] ), sanitize_hex_color( $s['button_border'] ), absint( $s['button_radius'] ) ); } /** * Output above the form (via login_message). * * @param string $message Existing message HTML. * @return string */ public function render_above( $message ) { if ( 'above' !== $this->settings->get( 'button_position' ) || ! $this->should_render() ) { return $message; } return $message . $this->markup( 'above' ); } /** * Output below the form (moved into place by login.js). */ public function render_below() { if ( 'below' !== $this->settings->get( 'button_position' ) || ! $this->should_render() ) { return; } echo $this->markup( 'below' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- markup() escapes everything. } /** * Shortcode for placing the button on custom login pages. * * @param array $atts Attributes. * @return string */ public function shortcode( $atts ) { $atts = shortcode_atts( array( 'redirect' => '', 'messages' => 'yes', 'divider' => 'no', ), $atts, 'm365_login_button' ); return $this->standalone( array( 'redirect' => $atts['redirect'], 'show_messages' => 'no' !== strtolower( (string) $atts['messages'] ), 'show_divider' => 'yes' === strtolower( (string) $atts['divider'] ), ) ); } /** * Full block: divider + button. * * @param string $position 'above' or 'below'. * @return string */ public function markup( $position ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- passed through to the flow, validated there. $redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : ''; $divider = $this->form_hidden() ? '' : $this->divider_markup(); $html = '
'; $html .= 'above' === $position ? $this->button( $redirect_to ) . $divider : $divider . $this->button( $redirect_to ); $html .= '
'; return $html; } /** * Button markup. * * @param string $redirect_to Post-login destination. * @return string */ public function button( $redirect_to = '' ) { $auth = M365_Login::instance()->auth; $url = $auth->start_url( $redirect_to ); $icon = ''; if ( $this->settings->get( 'button_show_icon' ) ) { $custom = (string) $this->settings->get( 'button_icon' ); if ( '' !== $custom && M365_Login_Settings::is_safe_image_url( $custom ) ) { $icon = ''; } else { $icon = self::microsoft_logo(); } } return '' . $icon . '' . esc_html( $this->settings->get( 'button_text' ) ) . '' . ''; } /** * Bundled Microsoft logo (inline SVG, four coloured squares). * * @return string */ public static function microsoft_logo() { return ''; } }