diff --git a/README.md b/README.md
index 7f8fca07..cbf46867 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,9 @@ via [wp-cli](https://developer.wordpress.org/cli/commands/config/).
| `rtcamp.google_login_state` | Filters the state to pass to the Google API. |
- `state_data` - contains the default state data.
| `rtcamp.default_algorithm` | Filters default algorithm for openssl signature verification | - `default_algo` - Default algorithm.
- `algo` - Algorithm from JWT header.
| `rtcamp.google_redirect_url` | Filters the URL to which the user will be redirected post successful authentication | - `redirect_uri` - contains the URL to be redirected to. Defaults to the current URL.
+| `rtcamp.google_use_saved_profile_picture_for_avatar` | Filter to bypass the use of saved profile picture for avatar | - `bypass` - Whether to bypass using the google profile picture or not.
- `url` - The URL of the avatar
- `$id_or_email` - The avatar to retrieve. Accepts a user ID or, user email, WP_User object, WP_User object, WP_Post object, or WP_Comment object.
- `args` - Arguments passed to get_avatar_data() , after processing.
+| `rtcamp.google_should_save_user_profile_picture` | Filter to bypass the profile picture saving process. | - `save` - Whether to save profile picture or not.
- `user_id` - WP User ID.
- `user` - User object returned by Google.
+| `rtcamp.google_download_profile_picture` | Filter to control downloading the profile picture if it is not already downloaded. By default, the profile picture is downloaded only if it has not already been downloaded | - `download_profile_picture` - Whether to download profile picture.
- `user_id` - WP User ID.
- `user` - User object returned by Google.
#### Actions
diff --git a/src/Container.php b/src/Container.php
index 8ce01d65..56e17827 100644
--- a/src/Container.php
+++ b/src/Container.php
@@ -23,6 +23,7 @@
use RtCamp\GoogleLogin\Modules\Login;
use RtCamp\GoogleLogin\Modules\OneTapLogin;
use RtCamp\GoogleLogin\Modules\Settings;
+use RtCamp\GoogleLogin\Modules\UserProfile;
use RtCamp\GoogleLogin\Utils\Authenticator;
use RtCamp\GoogleLogin\Utils\GoogleClient;
use RtCamp\GoogleLogin\Modules\Shortcode;
@@ -190,6 +191,18 @@ public function define_services(): void {
};
+ /**
+ * Define User Profile service to manage user profile.
+ *
+ * @param PimpleContainer $c Pimple container object.
+ *
+ * @return UserProfile
+ */
+ $this->container['user_profile'] = function ( PimpleContainer $c ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
+ return new UserProfile();
+ };
+
+
/**
* Define any additional services.
*
diff --git a/src/Modules/UserProfile.php b/src/Modules/UserProfile.php
new file mode 100644
index 00000000..195bf216
--- /dev/null
+++ b/src/Modules/UserProfile.php
@@ -0,0 +1,209 @@
+
+ */
+
+declare(strict_types=1);
+
+namespace RtCamp\GoogleLogin\Modules;
+
+use RtCamp\GoogleLogin\Interfaces\Module as ModuleInterface;
+use RtCamp\GoogleLogin\Utils\UserProfileHelper;
+use function RtCamp\GoogleLogin\plugin;
+
+/**
+ * Class UserProfile.
+ *
+ * @package RtCamp\GoogleLogin\Modules
+ */
+class UserProfile implements ModuleInterface {
+
+ /**
+ * Module name.
+ *
+ * @since n.e.x.t
+ *
+ * @return string
+ */
+ public function name(): string {
+ return 'user_profile';
+ }
+
+ /**
+ * Initialize the UserProfile module.
+ *
+ * @since n.e.x.t
+ * @return void
+ */
+ public function init(): void {
+ add_action( 'get_avatar_url', [ $this, 'return_avatar_url' ], 10, 3 );
+
+ // Render the profile edit options.
+ add_action( 'show_user_profile', [ $this, 'render_user_profile_edit_options' ] );
+ add_action( 'edit_user_profile', [ $this, 'render_user_profile_edit_options' ] );
+
+ // Save the profile edit options.
+ add_action( 'personal_options_update', [ $this, 'save_user_profile_edit_options' ] );
+ add_action( 'edit_user_profile_update', [ $this, 'save_user_profile_edit_options' ] );
+
+ add_action( 'admin_notices', [ $this, 'display_google_profile_picture_notice' ] );
+ }
+
+ /**
+ * Return the stored profile picture during the account creation.
+ *
+ * @since n.e.x.t
+ *
+ * @param string $url The URL of the avatar.
+ * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash, user email, WP_User object, WP_Post object, or WP_Comment object.
+ * @param array $args Arguments passed to get_avatar_data() , after processing.
+ *
+ * @return string The URL of the avatar.
+ */
+ public function return_avatar_url( $url, $id_or_email, $args ): string {
+ /**
+ * Filter to bypass the use of saved profile picture for avatar.
+ *
+ * @since n.e.x.t
+ *
+ * @param boolean $use_saved_profile_picture_for_avatar Whether to bypass the use of the saved profile picture for avatar or not.
+ * @param string $url The URL of the avatar.
+ * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID or, user email, WP_User object, WP_User object, WP_Post object, or WP_Comment object.
+ * @param array $args Arguments passed to get_avatar_data() , after processing.
+ */
+ $use_avatar_url = apply_filters( 'rtcamp.google_use_saved_profile_picture_for_avatar', true, $url, $id_or_email, $args );
+
+ if ( ! $use_avatar_url ) {
+ return $url;
+ }
+
+ // Do not interfere on profile edit page.
+ if ( defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE ) {
+ return $url;
+ }
+
+ // Do not interfere on user edit screen in admin.
+ $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
+ if ( $current_screen && 'user-edit' === $current_screen->id ) {
+ return $url;
+ }
+
+ $wp_user = null;
+ if ( is_int( $id_or_email ) ) {
+ $wp_user = get_user_by( 'id', $id_or_email );
+ } elseif ( is_string( $id_or_email ) && is_email( $id_or_email ) ) {
+ $wp_user = get_user_by( 'email', $id_or_email );
+ } elseif ( $id_or_email instanceof \WP_User ) {
+ $wp_user = $id_or_email;
+ } elseif ( $id_or_email instanceof \WP_Post ) {
+ $wp_user = get_user_by( 'id', (int) $id_or_email->post_author );
+ } elseif ( $id_or_email instanceof \WP_Comment ) {
+ $wp_user = get_user_by( 'id', (int) $id_or_email->user_id );
+ }
+
+ // Bail early if the user is not found.
+ if ( ! $wp_user ) {
+ return $url;
+ }
+
+ // Bail if user has chosen gravatar as avatar source.
+ $profile_picture_source = UserProfileHelper::get_profile_picture_source( $wp_user->ID );
+ if ( $profile_picture_source && 'gravatar' === $profile_picture_source ) {
+ return $url;
+ }
+
+ // Return the saved google avatar URL.
+ $width = isset( $args['width'] ) ? absint( $args['width'] ) : 96;
+ $height = isset( $args['height'] ) ? absint( $args['height'] ) : 96;
+
+ $profile_picture_id = UserProfileHelper::get_saved_google_profile_picture_id( $wp_user->ID );
+
+ if ( ! empty( $profile_picture_id ) ) {
+ $profile_picture_url = wp_get_attachment_image_url( $profile_picture_id, [ $width, $height ] );
+ if ( $profile_picture_url ) {
+ $url = $profile_picture_url;
+ }
+ }
+
+ return $url;
+ }
+
+ /**
+ * Render user profile edit template
+ *
+ * @since n.e.x.t
+ * @param \WP_User $wp_user WP_User object.
+ * @return void
+ */
+ public function render_user_profile_edit_options( \WP_User $wp_user ) {
+ require_once plugin()->template_dir . 'user-profile-edit.php'; //phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingCustomFunction
+ }
+
+ /**
+ * Save user profile edit options.
+ *
+ * @since n.e.x.t
+ *
+ * @param int $user_id User ID.
+ * @return void
+ */
+ public function save_user_profile_edit_options( int $user_id ) {
+ if ( empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) ) {
+ return;
+ }
+
+ if ( ! current_user_can( 'edit_user', $user_id ) ) {
+ return;
+ }
+
+ if ( isset( $_POST['rtlwg_profile_picture_source'] ) ) {
+ $avatar_source = sanitize_text_field( wp_unslash( $_POST['rtlwg_profile_picture_source'] ) );
+ if ( ! in_array( $avatar_source, [ 'google', 'gravatar' ], true ) ) {
+ return;
+ }
+ UserProfileHelper::save_profile_picture_source( $user_id, $avatar_source );
+ }
+ }
+
+ /**
+ * Display notice to the user that profile picture has been downloaded and they can use that.
+ *
+ * @since n.e.x.t
+ *
+ * @return void
+ */
+ public function display_google_profile_picture_notice(): void {
+ $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
+ $profile_picture_source = UserProfileHelper::get_profile_picture_source( get_current_user_id() );
+ $has_google_profile_picture = UserProfileHelper::has_google_profile_picture( get_current_user_id() );
+
+ // Bail early if the current screen is not profile screen.
+ if ( null === $screen || 'profile' !== $screen->id ) {
+ return;
+ }
+
+ // Bail early if user has set google profile picture source.
+ if ( 'google' === $profile_picture_source ) {
+ return;
+ }
+
+ if ( ! $has_google_profile_picture ) {
+ return;
+ }
+
+ ?>
+
+ email ) ) {
$user_wp = get_user_by( 'email', $user->email );
+ $this->save_user_profile_picture( $user_wp->ID, $user );
+
/**
* Fires once the user has been authenticated.
*
@@ -112,6 +115,11 @@ public function register( stdClass $user ): ?WP_User {
]
);
+ $this->save_user_profile_picture( $uid, $user );
+
+ // Save the profile picture source to google for the first login where the user registration happens.
+ UserProfileHelper::save_profile_picture_source( $uid, 'google' );
+
/**
* Fires once the user has been registered successfully.
*/
@@ -179,4 +187,150 @@ private function can_register_with_email( string $email ): bool {
return in_array( $email_parts[1], $whitelisted_domains, true );
}
+
+ /**
+ * Save user profile picture.
+ *
+ * @since n.e.x.t
+ *
+ * @param int $user_id WP User ID.
+ * @param \stdClass $user User object returned by Google.
+ * @return void
+ */
+ private function save_user_profile_picture( $user_id, $user ): void {
+ global $wp_filesystem;
+
+ /**
+ * Filter to bypass the profile picture saving process.
+ *
+ * @since n.e.x.t
+ *
+ * @param boolean $save Whether to save profile picture or not.
+ * @param int $user_id WP User ID.
+ * @param \stdClass $user User object returned by Google.
+ */
+ $save_profile_picture = apply_filters( 'rtcamp.google_should_save_user_profile_picture', true, $user_id, $user );
+
+ if ( ! $save_profile_picture ) {
+ return;
+ }
+
+ if ( is_null( $wp_filesystem ) ) {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ WP_Filesystem();
+ }
+
+ if ( ! function_exists( 'media_handle_sideload' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/media.php';
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ require_once ABSPATH . 'wp-admin/includes/image.php';
+ }
+
+ if ( ! isset( $user->picture ) || empty( $user->picture ) ) {
+ return;
+ }
+
+ $user_has_google_profile_picture = UserProfileHelper::has_google_profile_picture( $user_id );
+
+ /**
+ * Filter to control downloading the profile picture if it is not already downloaded.
+ * By default, the profile picture is downloaded only if it has not already been downloaded.
+ *
+ * @since n.e.x.t
+ *
+ * @param boolean $download_profile_picture Whether to download profile picture.
+ * @param int $user_id WP User ID.
+ * @param \stdClass $user User object returned by Google.
+ */
+ $download_profile_picture = apply_filters(
+ 'rtcamp.google_download_profile_picture',
+ ! $user_has_google_profile_picture,
+ $user_id,
+ $user
+ );
+
+ // Bail early if we are not to download the profile picture.
+ if ( false === $download_profile_picture ) {
+ return;
+ }
+
+ $profile_picture_filename = $this->download_profile_picture( $user->picture );
+
+ if ( null === $profile_picture_filename ) {
+ return;
+ }
+
+ $file_array = array(
+ 'name' => basename( $profile_picture_filename ),
+ 'tmp_name' => $profile_picture_filename,
+ );
+
+ // Intentionally passing 0 as $post_id to create an orphaned attachment for user profile picture.
+ // The attachment is tracked via user meta for management and cleanup.
+ $attachment_id = media_handle_sideload( $file_array, 0 );
+
+ if ( is_wp_error( $attachment_id ) ) {
+ // Cleanup temporary file.
+ $wp_filesystem->delete( $profile_picture_filename );
+ return;
+ }
+
+ // Set the google profile picture attachment to the user.
+ UserProfileHelper::set_google_profile_picture_to_user( $user_id, $attachment_id );
+
+ // Save the original google profile picture URL.
+ UserProfileHelper::save_original_google_profile_picture_url( $user_id, $user->picture );
+ }
+
+ /**
+ * Download profile picture from given URL and return the saved profile picture file path.
+ *
+ * @since n.e.x.t
+ *
+ * @param string $profile_picture_url Profile picture URL.
+ * @return string|null Profile picture file path or null on failure.
+ */
+ private function download_profile_picture( string $profile_picture_url ) {
+ global $wp_filesystem;
+
+ if ( is_null( $wp_filesystem ) ) {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ WP_Filesystem();
+ }
+
+ // Using larger image size. By default, profile picture has 96 width size with cropped.
+ $stripped_picture_url = str_replace( '=s96-c', '', $profile_picture_url );
+
+ $profile_picture_filename = download_url( $stripped_picture_url );
+
+ if ( is_wp_error( $profile_picture_filename ) ) {
+ return null;
+ }
+
+ if ( str_ends_with( $profile_picture_filename, '.tmp' ) && $wp_filesystem ) {
+ $profile_picture_mime_type = wp_get_image_mime( $profile_picture_filename );
+
+ $profile_picture_extension = 'jpg'; // Default extension.
+ $mime_types = wp_get_mime_types();
+ foreach ( $mime_types as $ext => $mime_type ) {
+ if ( $profile_picture_mime_type === $mime_type ) {
+ $profile_picture_extension = current( explode( '|', $ext ) );
+ break;
+ }
+ }
+
+ $new_profile_picture_filename = str_replace( '.tmp', ".{$profile_picture_extension}", $profile_picture_filename );
+ $is_file_moved = $wp_filesystem->move( $profile_picture_filename, $new_profile_picture_filename, true );
+
+ if ( ! $is_file_moved ) {
+ // Cleanup temporary file.
+ $wp_filesystem->delete( $profile_picture_filename );
+ return null;
+ }
+
+ $profile_picture_filename = $new_profile_picture_filename;
+ }
+
+ return $profile_picture_filename;
+ }
}
diff --git a/src/Utils/UserProfileHelper.php b/src/Utils/UserProfileHelper.php
new file mode 100644
index 00000000..c7ea8a61
--- /dev/null
+++ b/src/Utils/UserProfileHelper.php
@@ -0,0 +1,124 @@
+ID );
+$rtlwg_profile_picture_source = UserProfileHelper::get_profile_picture_source( $wp_user->ID );
+?>
+
+