Magento2 | PWA | GraphQL

Add Customer Profile Image/Avatar to customer account information Magento2


In this post we wiil check how to add Customer Profile Image/Avatar to customer account information in Magento2. That profile image will display on customer account and in product review listing.

And it will also display in customer listing grid at admin.

Let's start by creating custom module.

You can find complete module on Github at Magelearn_CustomerAvatar






Add registration.php file in it:

<?php

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Magelearn_CustomerAvatar',
    __DIR__
);

Add composer.json file in it:

{
    "name": "magelearn/module-customeravatar",
    "description": "This module adds ability to Customer to upload the profile picture in their account.",
    "type": "magento2-module",
    "license": "OSL-3.0",
    "authors": [
        {
            "email": "vijaymrami@gmail.com",
            "name": "vijay rami"
        }
    ],
    "minimum-stability": "dev",
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\CustomerAvatar\\": ""
        }
    }
}

Add etc/module.xml file in it:

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Magelearn_CustomerAvatar" setup_version="0.0.1" >
    </module>
</config>

Now we will add database installation and uninstall script to add this customer attribute (profile_picture) properly.

Create app/code/Magelearn/CustomerAvatar/Setup/InstallData.php file.

<?php

namespace Magelearn\CustomerAvatar\Setup;

use Magento\Customer\Setup\CustomerSetupFactory;
use Magento\Customer\Model\Customer;
use Magento\Eav\Model\Entity\Attribute\Set as AttributeSet;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{

    /**
     * @var CustomerSetupFactory
     */
    protected $customerSetupFactory;

    /**
     * @var AttributeSetFactory
     */
    private $attributeSetFactory;

    /**
     * @param CustomerSetupFactory $customerSetupFactory
     * @param AttributeSetFactory $attributeSetFactory
     */
    public function __construct(
        CustomerSetupFactory $customerSetupFactory,
        AttributeSetFactory $attributeSetFactory
    ) {
        $this->customerSetupFactory = $customerSetupFactory;
        $this->attributeSetFactory = $attributeSetFactory;
    }


    /**
     * {@inheritdoc}
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {

        /** @var CustomerSetup $customerSetup */
        $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);

        $customerEntity = $customerSetup->getEavConfig()->getEntityType('customer');
        $attributeSetId = $customerEntity->getDefaultAttributeSetId();

        /** @var $attributeSet AttributeSet */
        $attributeSet = $this->attributeSetFactory->create();
        $attributeGroupId = $attributeSet->getDefaultGroupId($attributeSetId);

        $customerSetup->addAttribute(Customer::ENTITY, 'profile_picture', [
            'type' => 'varchar',
            'label' => 'Profile Picture',
            'input' => 'image',
            'backend' => 'Magelearn\CustomerAvatar\Model\Attribute\Backend\Avatar',
            'required' => false,
            'visible' => true,
            'user_defined' => true,
            'sort_order' => 10,
            'position' => 10,
            'system' => 0,
            'is_used_in_grid' => true,
            'is_visible_in_grid' => true,
            'is_html_allowed_on_front' => true,
            'visible_on_front' => true
        ]);

        $attribute = $customerSetup->getEavConfig()->getAttribute(Customer::ENTITY, 'profile_picture')
        ->addData([
            'attribute_set_id' => $attributeSetId,
            'attribute_group_id' => $attributeGroupId,
            'used_in_forms' => [
                                'adminhtml_customer',
                                'customer_account_create',
                                'customer_account_edit'
                               ]
        ]);

        $attribute->save();
    }
}

Now as per highlighted code above we will add app/code/Magelearn/CustomerAvatar/Model/Attribute/Backend/Avatar.php file.

<?php

namespace Magelearn\CustomerAvatar\Model\Attribute\Backend;

use \Magelearn\CustomerAvatar\Model\Source\Validation\Image;

class Avatar extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
{
    /**
     * @param \Magento\Framework\DataObject $object
     *
     * @return $this
     */
    public function beforeSave($object)
    {
        $validation = new Image();
        $attrCode = $this->getAttribute()->getAttributeCode();
        if ($attrCode == 'profile_picture') {
            if ($validation->isImageValid('tmpp_name', $attrCode) === false) {
                throw new \Magento\Framework\Exception\LocalizedException(
                    __('The profile picture is not a valid image.')
                );
            }
        }

        return parent::beforeSave($object);
    }
}
Now as per highlighted code above we will add app/code/Magelearn/CustomerAvatar/Model/Source/Validation/Image.php file.
<?php

namespace Magelearn\CustomerAvatar\Model\Source\Validation;

class Image
{
    /**
     * Check the image file
     * @param $tmp_name, $attrCode
     * @return bool
     */
    public function isImageValid($tmp_name, $attrCode)
    {
        if ($attrCode == 'profile_picture') {
            if (!empty($_FILES[$attrCode][$tmp_name])) {
                $imageFile = @getimagesize($_FILES[$attrCode][$tmp_name]);
                if ($imageFile === false) {
                    return false;
                } else {
                    $valid_types = ['image/gif', 'image/jpeg', 'image/png'];
                    if (!in_array($imageFile['mime'], $valid_types)) {
                        return false;
                    }
                }
                return true;
            }
        }
        return true;
    }
}

We will add our un-install script file at app/code/Magelearn/CustomerAvatar/Setup/Uninstall.php file.

<?php

namespace Magelearn\CustomerAvatar\Setup;

use Magento\Framework\Setup\UninstallInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\ModuleContextInterface;

class Uninstall implements UninstallInterface
{
    /**
     * Eav setup factory
     * @var EavSetupFactory
     */
    private $eavSetupFactory;

    /**
     * Init
     * @param CategorySetupFactory $categorySetupFactory
     */
    public function __construct(\Magento\Eav\Setup\EavSetupFactory $eavSetupFactory)
    {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();
        $eavSetup = $this->eavSetupFactory->create();
        $eavSetup->removeAttribute(
            \Magento\Customer\Model\Customer::ENTITY,
            'profile_picture'
        );
        $setup->endSetup();
    }
} 

Now we will add app/code/Magelearn/CustomerAvatar/etc/di.xml file and add Plugin defination in that file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Customer\Model\Metadata\Form\Image">
        <plugin name="Validate_Image" type="Magelearn\CustomerAvatar\Plugin\Metadata\Form\Image" sortOrder="1"/>
    </type>
    <type name="Magento\Customer\CustomerData\Customer">
        <plugin name="customerData" type="Magelearn\CustomerAvatar\Plugin\CustomerData\Customer" sortOrder="1"/>
    </type>
</config>

With <type name="Magento\Customer\CustomerData\Customer"> we will add additional information in customer object with afterGetSectionData() function.

And with <type name="Magento\Customer\Model\Metadata\Form\Image"> we will validate image file type with beforeExtractValue() function.

Now add app/code/Magelearn/CustomerAvatar/Plugin/CustomerData/Customer.php file.

<?php

namespace Magelearn\CustomerAvatar\Plugin\CustomerData;

use Magento\Customer\Helper\Session\CurrentCustomer;
use Magento\Customer\Helper\View;
use Magelearn\CustomerAvatar\Block\Attributes\Avatar;

class Customer
{
    /**
     * @var CurrentCustomer
     */
    protected $currentCustomer;

    /**
     * @var View
     */
    protected $customerViewHelper;

    /**
     * @var Avatar
     */
    protected $customerAvatar;

    /**
     * @param CurrentCustomer $currentCustomer
     * @param View $customerViewHelper
     * @param Avatar $customerAvatar
     */
    public function __construct(
        CurrentCustomer $currentCustomer,
        View $customerViewHelper,
        Avatar $customerAvatar
    ) {
        $this->currentCustomer = $currentCustomer;
        $this->customerViewHelper = $customerViewHelper;
        $this->customerAvatar = $customerAvatar;
    }

    /**
     * {@inheritdoc}
     */
    public function afterGetSectionData()
    {
        if (!$this->currentCustomer->getCustomerId()) {
            return [];
        }
        $customer = $this->currentCustomer->getCustomer();
        if (!empty($customer->getCustomAttribute('profile_picture'))) {
            $file = $customer->getCustomAttribute('profile_picture')->getValue();
        } else {
            $file = '';
        }
        return [
            'fullname' => $this->customerViewHelper->getCustomerName($customer),
            'firstname' => $customer->getFirstname(),
            'avatar' => $this->customerAvatar->getAvatarCurrentCustomer($file)
        ];
    }
}

As per highlighted code above we will add app/code/Magelearn/CustomerAvatar/Block/Attributes/Avatar.php file.

<?php

namespace Magelearn\CustomerAvatar\Block\Attributes;

use Magento\Framework\View\Element\Template\Context;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Customer\Api\CustomerMetadataInterface;

class Avatar extends \Magento\Framework\View\Element\Template
{   
    /**
     * @var \Magento\Framework\Filesystem
     */
    protected $_filesystem;
    
    /**
     * Core file storage
     *
     * @var \Magento\MediaStorage\Helper\File\Storage
     */
    protected $coreFileStorage;
    
    /**
     * @var \Magento\Framework\View\Element\AbstractBlock
     */
    protected $viewFileUrl;

    /**
     * @var \Magento\Customer\Model\Customer
     */
    protected $customer;

    /**
     *
     * @param Context $context
     * @param \Magento\Framework\Filesystem $filesystem
     * @param \Magento\MediaStorage\Helper\File\Storage $coreFileStorage
     * @param \Magento\Framework\View\Asset\Repository $viewFileUrl
     * @param \Magento\Customer\Model\Customer $customer
     */
    public function __construct(
        Context $context,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\MediaStorage\Helper\File\Storage $coreFileStorage,
        \Magento\Framework\View\Asset\Repository $viewFileUrl,
        \Magento\Customer\Model\Customer $customer
    ) {
        $this->_filesystem = $filesystem;
        $this->coreFileStorage = $coreFileStorage;
        $this->viewFileUrl = $viewFileUrl;
        $this->customer = $customer;
        parent::__construct($context);
    }

    /**
     * Check the file is already exist in the path.
     * @return boolean
     */
    public function checkImageFile($file)
    {
        $file = base64_decode($file);
        $directory = $this->_filesystem->getDirectoryRead(DirectoryList::MEDIA);
        $fileName = CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER . '/' . ltrim($file, '/');
        $path = $this->_filesystem->getDirectoryRead(
                DirectoryList::MEDIA
            )->getAbsolutePath($fileName);
        if (!$directory->isFile($fileName)
            && !$this->coreFileStorage->processStorageFile($path)
        ) {
            return false;
        }
        return true;
    }

    /**
     * Get the avatar of the customer is already logged in
     * @return string
     */
    public function getAvatarCurrentCustomer($file)
    {
        if ($this->checkImageFile(base64_encode($file)) === true) {
            return $this->getUrl('viewfile/avatar/view/', ['image' => base64_encode($file)]);
        }
        return $this->viewFileUrl->getUrl('Magelearn_CustomerAvatar::images/no-profile-photo.jpg');
    }

    /**
     * Get the avatar of the customer by the customer id
     * @return string
     */
    public function getCustomerAvatarById($customer_id = false)
    {
        if ($customer_id) {
            $customerDetail = $this->customer->load($customer_id);
            if ($customerDetail && !empty($customerDetail->getProfilePicture())) {
                if ($this->checkImageFile(base64_encode($customerDetail->getProfilePicture())) === true) {
                    return $this->getUrl('viewfile/avatar/view/', ['image' => base64_encode($customerDetail->getProfilePicture())]);
                }
            }
        }
        return $this->viewFileUrl->getUrl('Magelearn_CustomerAvatar::images/no-profile-photo.jpg');
    }
}

Now as per highlighted code above, we will create our controller/action file to provide proper image URL path for customer profile image.

Add file at app/code/Magelearn/CustomerAvatar/etc/frontend/routes.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="viewfile" frontName="viewfile">
            <module name="Magelearn_CustomerAvatar" />
        </route>
    </router>
</config>

Now add app/code/Magelearn/CustomerAvatar/Controller/Avatar/View.php file.

<?php

namespace Magelearn\CustomerAvatar\Controller\Avatar;

use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Customer\Api\CustomerMetadataInterface;
use Magento\Framework\Exception\NotFoundException;

class View extends \Magento\Framework\App\Action\Action
{
    /**
     * @var \Magento\Framework\Filesystem
     */
    protected $_filesystem;
    
    /**
     * Core file storage
     *
     * @var \Magento\MediaStorage\Helper\File\Storage
     */
    protected $coreFileStorage;
    
    /**
     * @var \Magento\Framework\Controller\Result\RawFactory
     */
    protected $resultRawFactory;

    /**
     * @var \Magento\Framework\Url\DecoderInterface
     */
    protected $urlDecoder;

    /**
     * @var \Magento\Framework\App\Response\Http\FileFactory
     */
    protected $fileFactory;

    /**
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\Filesystem $filesystem
     * @param \Magento\MediaStorage\Helper\File\Storage $coreFileStorage
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Magento\Framework\Url\DecoderInterface $urlDecoder
     * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\MediaStorage\Helper\File\Storage $coreFileStorage,
        \Magento\Framework\Controller\Result\RawFactory $resultRawFactory,
        \Magento\Framework\Url\DecoderInterface $urlDecoder,
        \Magento\Framework\App\Response\Http\FileFactory $fileFactory
    ) {
        $this->_filesystem = $filesystem;
        $this->coreFileStorage = $coreFileStorage;
        $this->resultRawFactory    = $resultRawFactory;
        $this->urlDecoder  = $urlDecoder;
        $this->fileFactory = $fileFactory;
        return parent::__construct($context);
    }

    /**
     * View action
     *
     * @return \Magento\Framework\View\Result\PageFactory
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function execute()
    {
        $file = null;
        $plain = false;
        if ($this->getRequest()->getParam('file')) {
            // download file
            $file = $this->urlDecoder->decode(
                $this->getRequest()->getParam('file')
            );
        } elseif ($this->getRequest()->getParam('image')) {
            // show plain image
            $file = $this->urlDecoder->decode(
                $this->getRequest()->getParam('image')
            );
            $plain = true;
        } else {
            throw new NotFoundException(__('Page not found.'));
        }
        
        $directory = $this->_filesystem->getDirectoryRead(DirectoryList::MEDIA);
        $fileName = CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER . '/' . ltrim($file, '/');
        $path = $directory->getAbsolutePath($fileName);

        if (!$directory->isFile($fileName)
            && !$this->coreFileStorage->processStorageFile($path)
        ) {
            throw new NotFoundException(__('Page not found.'));
        }

        if ($plain) {
            $extension = pathinfo($path, PATHINFO_EXTENSION);
            switch (strtolower($extension)) {
                case 'gif':
                    $contentType = 'image/gif';
                    break;
                case 'jpg':
                    $contentType = 'image/jpeg';
                    break;
                case 'png':
                    $contentType = 'image/png';
                    break;
                default:
                    $contentType = 'application/octet-stream';
                    break;
            }
            $stat = $directory->stat($fileName);
            $contentLength = $stat['size'];
            $contentModify = $stat['mtime'];

            /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
            $resultRaw = $this->resultRawFactory->create();
            $resultRaw->setHttpResponseCode(200)
                ->setHeader('Pragma', 'public', true)
                ->setHeader('Content-type', $contentType, true)
                ->setHeader('Content-Length', $contentLength)
                ->setHeader('Last-Modified', date('r', $contentModify));
            $resultRaw->setContents($directory->readFile($fileName));
            return $resultRaw;
        } else {
            $name = pathinfo($path, PATHINFO_BASENAME);
            $this->fileFactory->create(
                $name,
                ['type' => 'filename', 'value' => $fileName],
                DirectoryList::MEDIA
            );
        }
    }
}

Now add app/code/Magelearn/CustomerAvatar/Plugin/Metadata/Form/Image.php file.

<?php

namespace Magelearn\CustomerAvatar\Plugin\Metadata\Form;

class Image
{
    /**
     * {@inheritdoc}
     *
     * @return ImageContentInterface|array|string|null
     */
    public function beforeExtractValue(\Magento\Customer\Model\Metadata\Form\Image $subject, $value)
    {
        $attrCode = $subject->getAttribute()->getAttributeCode();
        if ($this->isImageValid('tmp_name', $attrCode) === false) {
            $_FILES[$attrCode]['tmpp_name'] = $_FILES[$attrCode]['tmp_name'];
            unset($_FILES[$attrCode]['tmp_name']);
        }

        return [$value];
    }
    
    /**
     * Check the image file
     * @param $tmp_name, $attrCode
     * @return bool
     */
    public function isImageValid($tmp_name, $attrCode)
    {
        if ($attrCode == 'profile_picture') {
            if (!empty($_FILES[$attrCode][$tmp_name])) {
                $imageFile = @getimagesize($_FILES[$attrCode][$tmp_name]);
                if ($imageFile === false) {
                    return false;
                } else {
                    $valid_types = ['image/gif', 'image/jpeg', 'image/png'];
                    if (!in_array($imageFile['mime'], $valid_types)) {
                        return false;
                    }
                }
                return true;
            }
        }
        return true;
    }
}

Now we will add our frontend files (layout, tempates, blocks, CSS and JS file) to display cutomer profile picture properly at different places.

First we will add app/code/Magelearn/CustomerAvatar/view/frontend/layout/customer_account_create.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Magelearn_CustomerAvatar::css/avatar.css"/>
        <script src="Magelearn_CustomerAvatar::js/avatar-validation.js"/>
    </head>
    <body>
        <referenceBlock name="customer_form_register">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Magelearn_CustomerAvatar::form/register.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

Now as per highlighted code above we will copy file from vendor/magento/module-customer/view/frontend/templates/form/register.phtml file and add at app/code/Magelearn/CustomerAvatar/view/frontend/templates/form/register.phtml

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

use Magento\Customer\Helper\Address;

/** @var \Magento\Customer\Block\Form\Register $block */
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */

/** @var Magento\Customer\Helper\Address $addressHelper */
$addressHelper = $block->getData('addressHelper');
/** @var \Magento\Directory\Helper\Data $directoryHelper */
$directoryHelper = $block->getData('directoryHelper');
$formData = $block->getFormData();
?>
<?php $displayAll = $block->getConfig('general/region/display_all'); ?>
<?= $block->getChildHtml('form_fields_before') ?>
<?php /* Extensions placeholder */ ?>
<?= $block->getChildHtml('customer.form.register.extra') ?>
<form class="form create account form-create-account"
      action="<?= $escaper->escapeUrl($block->getPostActionUrl()) ?>"
      method="post"
      id="form-validate"
      enctype="multipart/form-data"
      autocomplete="off">
    <?= /* @noEscape */ $block->getBlockHtml('formkey') ?>
    <fieldset class="fieldset create info">
        <legend class="legend"><span><?= $escaper->escapeHtml(__('Personal Information')) ?></span></legend><br>
        <input type="hidden" name="success_url" value="<?= $escaper->escapeUrl($block->getSuccessUrl()) ?>">
        <input type="hidden" name="error_url" value="<?= $escaper->escapeUrl($block->getErrorUrl()) ?>">
        <?= $block->getLayout()
                ->createBlock(\Magento\Customer\Block\Widget\Name::class)
                ->setObject($formData)
                ->setForceUseCustomerAttributes(true)
                ->toHtml() ?>
        <div class="field field-name-avatar">
        	<?php $avatar = $block->getViewFileUrl('Magelearn_CustomerAvatar::images/no-profile-photo.jpg');?>
            <label class="label" for="profile_picture">
                <span class="avatar-title"><?php echo __('Profile Picture');?></span>
            </label>
            <div class="control">
            	<div class="holder">
                	<img src="<?php echo $avatar; ?>" width="150px" height="150px" alt="profile-picture" title="<?php echo __('Upload new avatar'); ?>" class="profile-image" id="profile-picture-output" />
            	</div>
                <label for="profile_picture" class="avatar-file-upload">
                    <?php echo __('Upload new avatar'); ?>
                </label><br/>
                <input type="file" id="profile_picture" name="profile_picture" value="" title="Your avatar" class="avatar validate-image" accept="image/*" onchange="loadFile(event)" />
            </div>
        </div>
        <?php if ($block->isNewsletterEnabled()): ?>
            <div class="field choice newsletter">
                <input type="checkbox"
                       name="is_subscribed"
                       title="<?= $escaper->escapeHtmlAttr(__('Sign Up for Newsletter')) ?>"
                       value="1"
                       id="is_subscribed"
                       <?php if ($formData->getIsSubscribed()): ?>checked="checked"<?php endif; ?>
                       class="checkbox">
                <label for="is_subscribed" class="label">
                    <span><?= $escaper->escapeHtml(__('Sign Up for Newsletter')) ?></span>
                </label>
            </div>
            <?php /* Extensions placeholder */ ?>
            <?= $block->getChildHtml('customer.form.register.newsletter') ?>
        <?php endif ?>

        <?php $_dob = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Dob::class) ?>
        <?php if ($_dob->isEnabled()): ?>
            <?= $_dob->setDate($formData->getDob())->toHtml() ?>
        <?php endif ?>

        <?php $_taxvat = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Taxvat::class) ?>
        <?php if ($_taxvat->isEnabled()): ?>
            <?= $_taxvat->setTaxvat($formData->getTaxvat())->toHtml() ?>
        <?php endif ?>

        <?php $_gender = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Gender::class) ?>
        <?php if ($_gender->isEnabled()): ?>
            <?= $_gender->setGender($formData->getGender())->toHtml() ?>
        <?php endif ?>
        <?= $block->getChildHtml('fieldset_create_info_additional') ?>
    </fieldset>
    <?php if ($block->getShowAddressFields()): ?>
        <?php $cityValidationClass = $addressHelper->getAttributeValidationClass('city'); ?>
        <?php $postcodeValidationClass = $addressHelper->getAttributeValidationClass('postcode'); ?>
        <?php $regionValidationClass = $addressHelper->getAttributeValidationClass('region'); ?>
        <fieldset class="fieldset address">
            <legend class="legend"><span><?= $escaper->escapeHtml(__('Address Information')) ?></span></legend><br>
            <input type="hidden" name="create_address" value="1" />

            <?php $_company = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Company::class) ?>
            <?php if ($_company->isEnabled()): ?>
                <?= $_company->setCompany($formData->getCompany())->toHtml() ?>
            <?php endif ?>

            <?php $_telephone = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Telephone::class) ?>
            <?php if ($_telephone->isEnabled()): ?>
                <?= $_telephone->setTelephone($formData->getTelephone())->toHtml() ?>
            <?php endif ?>

            <?php $_fax = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Fax::class) ?>
            <?php if ($_fax->isEnabled()): ?>
                <?= $_fax->setFax($formData->getFax())->toHtml() ?>
            <?php endif ?>

            <?php
                $_streetValidationClass = $addressHelper->getAttributeValidationClass('street');
            ?>

            <div class="field street required">
                <label for="street_1" class="label">
                    <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('street') ?></span>
                </label>
                <div class="control">
                    <input type="text"
                           name="street[]"
                           value="<?= $escaper->escapeHtmlAttr($formData->getStreet(0)) ?>"
                           title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('street') ?>"
                           id="street_1"
                           class="input-text <?= $escaper->escapeHtmlAttr($_streetValidationClass) ?>">
                    <div class="nested">
                        <?php
                            $_streetValidationClass = trim(str_replace('required-entry', '', $_streetValidationClass));
                            $streetLines = $addressHelper->getStreetLines();
                        ?>
                        <?php for ($_i = 2, $_n = $streetLines; $_i <= $_n; $_i++): ?>
                            <div class="field additional">
                                <label class="label" for="street_<?= /* @noEscape */ $_i ?>">
                                    <span><?= $escaper->escapeHtml(__('Address')) ?></span>
                                </label>
                                <div class="control">
                                    <input type="text"
                                           name="street[]"
                                           value="<?= $escaper->escapeHtml($formData->getStreetLine($_i - 1)) ?>"
                                           title="<?= $escaper->escapeHtmlAttr(__('Street Address %1', $_i)) ?>"
                                           id="street_<?= /* @noEscape */ $_i ?>"
                                           class="input-text <?= $escaper->escapeHtmlAttr($_streetValidationClass) ?>">
                                </div>
                            </div>
                        <?php endfor; ?>
                    </div>
                </div>
            </div>

            <?php if ($addressHelper->isVatAttributeVisible()): ?>
                <?php $_vatidValidationClass = $addressHelper->getAttributeValidationClass('vat_id'); ?>
                <div class="field taxvat">
                    <label class="label" for="vat_id">
                        <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('vat_id') ?></span>
                    </label>
                    <div class="control">
                        <input type="text"
                               name="vat_id"
                               value="<?= $escaper->escapeHtmlAttr($formData->getVatId()) ?>"
                               title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('vat_id') ?>"
                               class="input-text <?= $escaper->escapeHtmlAttr($_vatidValidationClass) ?>"
                               id="vat_id">
                    </div>
                </div>
            <?php endif; ?>

            <div class="field country required">
                <label for="country" class="label">
                    <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('country_id') ?></span>
                </label>
                <div class="control">
                    <?= $block->getCountryHtmlSelect() ?>
                </div>
            </div>

            <div class="field region required">
                <label for="region_id" class="label">
                    <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?></span>
                </label>
                <div class="control">
                    <select id="region_id"
                            name="region_id"
                            title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?>"
                            class="validate-select region_id">
                        <option value="">
                            <?= $escaper->escapeHtml(__('Please select a region, state or province.')) ?>
                        </option>
                    </select>
                    <?= /* @noEscape */ $secureRenderer->renderStyleAsTag("display: none;", 'select#region_id') ?>
                    <input type="text"
                           id="region"
                           name="region"
                           value="<?= $escaper->escapeHtml($block->getRegion()) ?>"
                           title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?>"
                           class="input-text <?= $escaper->escapeHtmlAttr($regionValidationClass) ?>">
                    <?= /* @noEscape */ $secureRenderer->renderStyleAsTag("display: none;", 'input#region') ?>
                </div>
            </div>

            <div class="field required">
                <label for="city" class="label">
                    <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('city') ?></span>
                </label>
                <div class="control">
                    <input type="text"
                           name="city"
                           value="<?= $escaper->escapeHtmlAttr($formData->getCity()) ?>"
                           title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('city') ?>"
                           class="input-text <?= $escaper->escapeHtmlAttr($cityValidationClass) ?>"
                           id="city">
                </div>
            </div>

            <div class="field zip required">
                <label for="zip" class="label">
                    <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?></span>
                </label>
                <div class="control">
                    <input type="text"
                           name="postcode"
                           value="<?= $escaper->escapeHtmlAttr($formData->getPostcode()) ?>"
                           title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?>"
                           id="zip"
                           class="input-text validate-zip-international
                            <?= $escaper->escapeHtmlAttr($postcodeValidationClass) ?>">
                </div>
            </div>

            <?php $addressAttributes = $block->getChildBlock('customer_form_address_user_attributes');?>
            <?php if ($addressAttributes): ?>
                <?php $addressAttributes->setEntityType('customer_address'); ?>
                <?php $addressAttributes->setFieldIdFormat('address:%1$s')->setFieldNameFormat('address[%1$s]');?>
                <?php $block->restoreSessionData($addressAttributes->getMetadataForm(), 'address');?>
                <?= $addressAttributes->setShowContainer(false)->toHtml() ?>
            <?php endif;?>
            <input type="hidden" name="default_billing" value="1">
            <input type="hidden" name="default_shipping" value="1">
        </fieldset>

    <?php endif; ?>
    <fieldset class="fieldset create account"
              data-hasrequired="<?= $escaper->escapeHtmlAttr(__('* Required Fields')) ?>">
        <legend class="legend"><span><?= $escaper->escapeHtml(__('Sign-in Information')) ?></span></legend><br>
        <div class="field required">
            <label for="email_address" class="label"><span><?= $escaper->escapeHtml(__('Email')) ?></span></label>
            <div class="control">
                <input type="email"
                       name="email"
                       autocomplete="email"
                       id="email_address"
                       value="<?= $escaper->escapeHtmlAttr($formData->getEmail()) ?>"
                       title="<?= $escaper->escapeHtmlAttr(__('Email')) ?>"
                       class="input-text"
                       data-mage-init='{"mage/trim-input":{}}'
                       data-validate="{required:true, 'validate-email':true}">
            </div>
        </div>
        <div class="field password required">
            <label for="password" class="label"><span><?= $escaper->escapeHtml(__('Password')) ?></span></label>
            <div class="control">
                <input type="password" name="password" id="password"
                       title="<?= $escaper->escapeHtmlAttr(__('Password')) ?>"
                       class="input-text"
                       data-password-min-length="<?=
                        $escaper->escapeHtmlAttr($block->getMinimumPasswordLength()) ?>"
                       data-password-min-character-sets="<?=
                        $escaper->escapeHtmlAttr($block->getRequiredCharacterClassesNumber()) ?>"
                       data-validate="{required:true, 'validate-customer-password':true}"
                       autocomplete="off">
                <div id="password-strength-meter-container" data-role="password-strength-meter" aria-live="polite">
                    <div id="password-strength-meter" class="password-strength-meter">
                        <?= $escaper->escapeHtml(__('Password Strength')) ?>:
                        <span id="password-strength-meter-label" data-role="password-strength-meter-label">
                            <?= $escaper->escapeHtml(__('No Password')) ?>
                        </span>
                    </div>
                </div>
            </div>

        </div>
        <div class="field confirmation required">
            <label for="password-confirmation" class="label">
                <span><?= $escaper->escapeHtml(__('Confirm Password')) ?></span>
            </label>
            <div class="control">
                <input type="password"
                       name="password_confirmation"
                       title="<?= $escaper->escapeHtmlAttr(__('Confirm Password')) ?>"
                       id="password-confirmation"
                       class="input-text"
                       data-validate="{required:true, equalTo:'#password'}"
                       autocomplete="off">
            </div>
        </div>
        <div class="field choice" data-bind="scope: 'showPassword'">
            <!-- ko template: getTemplate() --><!-- /ko -->
        </div>
    </fieldset>

    <fieldset class="fieldset additional_info">
        <?= $block->getChildHtml('form_additional_info') ?>
    </fieldset>

    <div class="actions-toolbar">
        <div class="primary">
            <button type="submit"
                    class="action submit primary"
                    title="<?= $escaper->escapeHtmlAttr(__('Create an Account')) ?>">
                <span><?= $escaper->escapeHtml(__('Create an Account')) ?></span>
            </button>
        </div>
        <div class="secondary">
            <a class="action back"
               href="<?= $escaper->escapeUrl($block->getBackUrl()) ?>">
                <span><?= $escaper->escapeHtml(__('Back')) ?></span>
            </a>
        </div>
    </div>
</form>
<?php $ignore = /* @noEscape */ $_dob->isEnabled() ? '\'input[id$="full"]\'' : 'null';
$scriptString = <<<script
require([
    'jquery',
    'mage/mage'
], function($){

    var dataForm = $('#form-validate');
    var ignore = {$ignore};

    dataForm.mage('validation', {
script;
if ($_dob->isEnabled()):
    $scriptString .= <<<script
        errorPlacement: function(error, element) {
            if (element.prop('id').search('full') !== -1) {
                var dobElement = $(element).parents('.customer-dob'),
                    errorClass = error.prop('class');
                error.insertAfter(element.parent());
                dobElement.find('.validate-custom').addClass(errorClass)
                    .after('<div class="' + errorClass + '"></div>');
            }
            else {
                error.insertAfter(element);
            }
        },
        ignore: ':hidden:not(' + ignore + ')'
script;
else:
    $scriptString .= <<<script
        ignore: ignore ? ':hidden:not(' + ignore + ')' : ':hidden'
script;
endif;
$scriptString .= <<<script
    }).find('input:text').attr('autocomplete', 'off');
});
script;
?>
<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false) ?>
<?php if ($block->getShowAddressFields()): ?>
    <?php
    $regionJson = /* @noEscape */ $directoryHelper->getRegionJson();
    $regionId = (int) $formData->getRegionId();
    $countriesWithOptionalZip = /* @noEscape */ $directoryHelper->getCountriesWithOptionalZip(true);
    ?>
<script type="text/x-magento-init">
    {
        "#country": {
            "regionUpdater": {
                "optionalRegionAllowed": <?= /* @noEscape */ $displayAll ? 'true' : 'false' ?>,
                "regionListId": "#region_id",
                "regionInputId": "#region",
                "postcodeId": "#zip",
                "form": "#form-validate",
                "regionJson": <?= $regionJson ?>,
                "defaultRegion": <?= $regionId ?>,
                "countriesWithOptionalZip": <?= $countriesWithOptionalZip ?>
            }
        }
    }
</script>
<?php endif; ?>

<script type="text/x-magento-init">
    {
        ".field.password": {
            "passwordStrengthIndicator": {
                "formSelector": "form.form-create-account"
            }
        },
        "*": {
            "Magento_Customer/js/block-submit-on-send": {
                "formId": "form-validate"
            },
            "Magento_Ui/js/core/app": {
                "components": {
                    "showPassword": {
                        "component": "Magento_Customer/js/show-password",
                        "passwordSelector": "#password,#password-confirmation"
                    }
                }
            }
        }
    }
</script>
<script>
  var loadFile = function(event) {
    var output = document.getElementById('profile-picture-output');
    output.src = URL.createObjectURL(event.target.files[0]);
    output.onload = function() {
      URL.revokeObjectURL(output.src) // free memory
    }
  };
</script>

We will add app/code/Magelearn/CustomerAvatar/view/frontend/layout/customer_account_edit.xml file.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Magelearn_CustomerAvatar::css/avatar.css"/>
        <script src="Magelearn_CustomerAvatar::js/avatar-validation.js"/>
    </head>
    <body>
        <referenceContainer name="content">
            <block class="Magento\Customer\Block\Form\Edit" name="customer_edit" template="Magelearn_CustomerAvatar::form/edit.phtml" cacheable="false">
                <container name="form.additional.info" as="form_additional_info"/>
            </block>
        </referenceContainer>
    </body>
</page>

Now as per highlighted code above we will copy file from vendor/magento/module-customer/view/frontend/templates/form/edit.phtml and add at app/code/Magelearn/CustomerAvatar/view/frontend/templates/form/edit.phtml

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

use Magento\Customer\Block\Widget\Name;

/** @var \Magento\Customer\Block\Form\Edit $block */
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
?>
<form class="form form-edit-account"
      action="<?= $block->escapeUrl($block->getUrl('customer/account/editPost')) ?>"
      method="post" id="form-validate"
      enctype="multipart/form-data"
      data-hasrequired="<?= $block->escapeHtmlAttr(__('* Required Fields')) ?>"
      autocomplete="off">
    <fieldset class="fieldset info">
        <?= $block->getBlockHtml('formkey') ?>
        <legend class="legend"><span><?= $block->escapeHtml(__('Account Information')) ?></span></legend><br>
        <?= $block->getLayout()->createBlock(Name::class)->setObject($block->getCustomer())->toHtml() ?>
		<div class="field field-name-avatar">
            <?php $avatarFile = !empty($block->getCustomer()->getCustomAttribute('profile_picture')) ? $block->getLayout()->createBlock('\Magelearn\CustomerAvatar\Block\Attributes\Avatar')->checkImageFile(base64_encode($block->getCustomer()->getCustomAttribute('profile_picture')->getValue())) : false; ?>
            <?php $avatar = ($avatarFile) ? $block->getUrl('viewfile/avatar/view/', ['image' => base64_encode($block->getCustomer()->getCustomAttribute('profile_picture')->getValue())]) : $block->getViewFileUrl('Magelearn_CustomerAvatar::images/no-profile-photo.jpg');?>
            <label class="label" for="profile_picture">
                <span class="avatar-title"><?php echo __('Profile Picture');?></span>
                <img src="<?php echo $avatar; ?>" width="150px" height="150px" alt="profile-picture" title="<?php echo __('Upload new avatar'); ?>" class="profile-image" id="profile-image"/>
            </label>
            <div class="control">
                <label for="profile_picture" class="avatar-file-upload">
                    <?php echo __('Upload new avatar'); ?>
                </label>
                <input type="file" id="profile_picture" name="profile_picture" value="" title="Your avatar" class="avatar validate-image" accept="image/*" onchange="loadFile(event)" />
            </div>
        </div>
        <?php $_dob = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Dob::class) ?>
        <?php $_taxvat = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Taxvat::class) ?>
        <?php $_gender = $block->getLayout()->createBlock(\Magento\Customer\Block\Widget\Gender::class) ?>
        <?php if ($_dob->isEnabled()): ?>
            <?= $_dob->setDate($block->getCustomer()->getDob())->toHtml() ?>
        <?php endif ?>
        <?php if ($_taxvat->isEnabled()): ?>
            <?= $_taxvat->setTaxvat($block->getCustomer()->getTaxvat())->toHtml() ?>
        <?php endif ?>
        <?php if ($_gender->isEnabled()): ?>
            <?= $_gender->setGender($block->getCustomer()->getGender())->toHtml() ?>
        <?php endif ?>
        <div class="field choice">
            <input type="checkbox" name="change_email" id="change-email" data-role="change-email" value="1"
                   title="<?= $block->escapeHtmlAttr(__('Change Email')) ?>" class="checkbox" />
            <label class="label" for="change-email">
                <span><?= $block->escapeHtml(__('Change Email')) ?></span>
            </label>
        </div>
        <div class="field choice">
            <input type="checkbox" name="change_password" id="change-password" data-role="change-password" value="1"
                   title="<?= $block->escapeHtmlAttr(__('Change Password')) ?>"
                <?php if ($block->getChangePassword()): ?> checked="checked"<?php endif; ?> class="checkbox" />
            <label class="label" for="change-password">
                <span><?= $block->escapeHtml(__('Change Password')) ?></span>
            </label>
        </div>
        <?= $block->getChildHtml('fieldset_edit_info_additional') ?>
    </fieldset>

    <fieldset class="fieldset password" data-container="change-email-password">
        <legend class="legend">
            <span data-title="change-email-password"><?= $block->escapeHtml(__('Change Email and Password')) ?></span>
        </legend><br>
        <div class="field email required" data-container="change-email">
            <label class="label" for="email"><span><?= $block->escapeHtml(__('Email')) ?></span></label>
            <div class="control">
                <input type="email" name="email" id="email" autocomplete="email" data-input="change-email"
                       value="<?= $block->escapeHtmlAttr($block->getCustomer()->getEmail()) ?>"
                       title="<?= $block->escapeHtmlAttr(__('Email')) ?>"
                       class="input-text"
                       data-validate="{required:true, 'validate-email':true}" />
            </div>
        </div>
        <div class="field password current required">
            <label class="label" for="current-password">
                <span><?= $block->escapeHtml(__('Current Password')) ?></span>
            </label>
            <div class="control">
                <input type="password" class="input-text" name="current_password" id="current-password"
                       data-input="current-password"
                       autocomplete="off" />
            </div>
        </div>
        <div class="field new password required" data-container="new-password">
            <label class="label" for="password"><span><?= $block->escapeHtml(__('New Password')) ?></span></label>
            <div class="control">
                <?php $minCharacterSets = $block->getRequiredCharacterClassesNumber() ?>
                <input type="password" class="input-text" name="password" id="password"
                    data-password-min-length="<?= $block->escapeHtml($block->getMinimumPasswordLength()) ?>"
                    data-password-min-character-sets="<?= $block->escapeHtml($minCharacterSets) ?>"
                    data-input="new-password"
                    data-validate="{required:true, 'validate-customer-password':true}"
                    autocomplete="off" />
                <div id="password-strength-meter-container" data-role="password-strength-meter" aria-live="polite">
                    <div id="password-strength-meter" class="password-strength-meter">
                        <?= $block->escapeHtml(__('Password Strength')) ?>:
                        <span id="password-strength-meter-label" data-role="password-strength-meter-label">
                            <?= $block->escapeHtml(__('No Password')) ?>
                        </span>
                    </div>
                </div>
            </div>
        </div>
        <div class="field confirmation password required" data-container="confirm-password">
            <label class="label" for="password-confirmation">
                <span><?= $block->escapeHtml(__('Confirm New Password')) ?></span>
            </label>
            <div class="control">
                <input type="password" class="input-text" name="password_confirmation" id="password-confirmation"
                    data-input="confirm-password"
                    autocomplete="off" />
            </div>
        </div>
        <div class="field choice" data-bind="scope: 'showPassword'">
            <!-- ko template: getTemplate() --><!-- /ko -->
        </div>
    </fieldset>

    <fieldset class="fieldset additional_info">
        <?= $block->getChildHtml('form_additional_info') ?>
    </fieldset>

    <div class="actions-toolbar">
        <div class="primary">
            <button type="submit" class="action save primary" title="<?= $block->escapeHtmlAttr(__('Save')) ?>">
                <span><?= $block->escapeHtml(__('Save')) ?></span>
            </button>
        </div>
        <div class="secondary">
            <a class="action back" href="<?= $block->escapeUrl($block->getBackUrl()) ?>">
                <span><?= $block->escapeHtml(__('Go back')) ?></span>
            </a>
        </div>
    </div>
</form>
<?php $ignore = /* @noEscape */ $_dob->isEnabled() ? '\'input[id$="full"]\'' : 'null';
$scriptString = <<<script
    require([
        "jquery",
        "mage/mage"
    ], function($){
        var dataForm = $('#form-validate');
        var ignore = {$ignore};

        dataForm.mage('validation', {
script;
if ($_dob->isEnabled()):
    $scriptString .= <<<script
            errorPlacement: function(error, element) {
                if (element.prop('id').search('full') !== -1) {
                    var dobElement = $(element).parents('.customer-dob'),
                        errorClass = error.prop('class');
                    error.insertAfter(element.parent());
                    dobElement.find('.validate-custom').addClass(errorClass)
                        .after('<div class="' + errorClass + '"></div>');
                }
                else {
                    error.insertAfter(element);
                }
            },
            ignore: ':hidden:not(' + ignore + ')'
script;
else:
    $scriptString .= <<<script
            ignore: ignore ? ':hidden:not(' + ignore + ')' : ':hidden'
script;
endif;
$scriptString .= <<<script
        });

    });
script;
?>
<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false) ?>
<?php $changeEmailAndPasswordTitle = $block->escapeHtml(__('Change Email and Password')) ?>
<script type="text/x-magento-init">
    {
        "[data-role=change-email], [data-role=change-password]": {
            "changeEmailPassword": {
                "titleChangeEmail": "<?= $block->escapeJs($block->escapeHtml(__('Change Email'))) ?>",
                "titleChangePassword": "<?= $block->escapeJs($block->escapeHtml(__('Change Password'))) ?>",
                "titleChangeEmailAndPassword": "<?= $block->escapeJs($changeEmailAndPasswordTitle) ?>"
            }
        },
        "[data-container=new-password]": {
            "passwordStrengthIndicator": {
                "formSelector": "form.form-edit-account"
            }
        },
        "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                    "showPassword": {
                        "component": "Magento_Customer/js/show-password",
                        "passwordSelector": "#current-password,#password,#password-confirmation"
                    }
                }
            }
        }
    }
</script>
<script>
  var loadFile = function(event) {
    var output = document.getElementById('profile-image');
    output.src = URL.createObjectURL(event.target.files[0]);
    output.onload = function() {
      URL.revokeObjectURL(output.src) // free memory
    }
  };
</script>

Now as per highlighted code above in form action URL when we update customer account information, we will also update the customer section data.

For that we will add app/code/Magelearn/CustomerAvatar/etc/frontend/sections.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd">
    <action name="customer/account/editPost">
        <section name="customer"/>
    </action>
</config>

We will add app/code/Magelearn/CustomerAvatar/view/frontend/layout/default.xml file.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Magelearn_CustomerAvatar::css/global-avatar.css"/>
        <css src="Magelearn_CustomerAvatar::css/review-item.css"/>
    </head>
    <body>
        <referenceBlock name="customer">
            <action method='setTemplate'>
                <argument name='template' xsi:type='string'>Magelearn_CustomerAvatar::account/customer.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

As per highlighted code above we will copy file from vendor/magento/module-customer/view/frontend/templates/account/customer.phtml and will put it at app/code/Magelearn/CustomerAvatar/view/frontend/templates/account/customer.phtml

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

/** @var Magento\Customer\Block\Account\Customer $block */
?>
<?php if($block->customerLoggedIn()): ?>
    <li class="customer-welcome">
        <span class="customer-name"
              role="link"
              tabindex="0"
              data-mage-init='{"dropdown":{}}'
              data-toggle="dropdown"
              data-trigger-keypress-button="true"
              data-bind="scope: 'customer'">
            <img data-bind="attr:{src: customer().avatar}" width="30px" height="30px" class="avatar-header-links action switch" data-action="customer-menu-toggle" tabindex="-1"/>
            <button type="button"
                    class="profile_image action switch"
                    tabindex="-1"
                    data-action="customer-menu-toggle">
                <span><?= $block->escapeHtml(__('Change')) ?></span>
            </button>
        </span>
        <script type="text/x-magento-init">
        {
            "*": {
                "Magento_Ui/js/core/app": {
                    "components": {
                        "customer": {
                            "component": "Magento_Customer/js/view/customer"
                        }
                    }
                }
            }
        }
        </script>
        <?php if($block->getChildHtml()):?>
            <div class="customer-menu" data-target="dropdown">
                <?php echo $block->getChildHtml();?>
            </div>
        <?php endif; ?>
    </li>
<?php endif; ?> 

We will add app/code/Magelearn/CustomerAvatar/view/frontend/layout/review_product_listajax.xml file.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <container name="root">
        <block class="Magento\Review\Block\Product\View\ListView" name="product.info.product_additional_data" as="product_additional_data" template="Magelearn_CustomerAvatar::review/product/view/list.phtml" ifconfig="catalog/review/active" />
        <block class="Magento\Theme\Block\Html\Pager" name="product_review_list.toolbar" ifconfig="catalog/review/active">
            <arguments>
                <argument name="show_per_page" xsi:type="boolean">false</argument>
                <argument name="show_amounts" xsi:type="boolean">false</argument>
            </arguments>
        </block>
    </container>
</layout>

As per highlighted code above we will copy file from vendor/magento/module-review/view/frontend/templates/product/view/list.phtml and will put it at app/code/Magelearn/CustomerAvatar/view/frontend/templates/review/product/view/list.phtml

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

use Magento\Framework\Escaper;
use Magento\Framework\View\Helper\SecureHtmlRenderer;
use Magento\Review\Block\Product\View\ListView;

/**
 * @var ListView $block
 * @var SecureHtmlRenderer $secureRenderer
 * @var Escaper $escaper
 */

$_items = $block->getReviewsCollection()->getItems();
$format = $block->getDateFormat() ?: \IntlDateFormatter::SHORT;
?>
<?php if (count($_items)):?>
<div class="block review-list" id="customer-reviews">
    <?php if (!$block->getHideTitle()): ?>
        <div class="block-title">
            <strong><?= $escaper->escapeHtml(__('Customer Reviews')) ?></strong>
        </div>
    <?php endif ?>
    <div class="block-content">
        <div class="toolbar review-toolbar">
            <?php echo $block->getChildHtml('toolbar') ?>
        </div>
        <ol class="items review-items">
        <?php foreach ($_items as $_review):?>
            <li class="item review-item" itemscope itemprop="review" itemtype="http://schema.org/Review">
                <div class="review-image">
                    <img src="<?php echo $block->getLayout()->createBlock('\Magelearn\CustomerAvatar\Block\Attributes\Avatar')->getCustomerAvatarById($_review->getData('customer_id')); ?>" width="150px" height="150px" alt="avatar" />
                </div>
                <div class="review-infor">
                    <div class="review-title" itemprop="name">
                        <?= $escaper->escapeHtml($_review->getTitle()) ?>
                    </div>
                    <div class="review-details">
                        <?php if (count($_review->getRatingVotes())): ?>
                            <div class="review-ratings">
                                <?php foreach ($_review->getRatingVotes() as $_vote): ?>
                                <div class="rating-summary item"
                                	 itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
                                    <span class="label rating-label">
                                        <span><?= $escaper->escapeHtml($_vote->getRatingCode()) ?></span>
                                    </span>
                                    <div class="rating-result"
                                         id="review_<?= /* @noEscape */ $_review->getReviewId()
                                            . '_vote_'
                                            . $_vote->getVoteId() ?>"
                                         title="<?= $escaper->escapeHtmlAttr($_vote->getPercent()) ?>%">
                                        <meta itemprop="worstRating" content="1"/>
                                        <meta itemprop="bestRating" content="100"/>
                                        <span>
                                            <span itemprop="ratingValue">
                                                <?= $escaper->escapeHtml($_vote->getPercent()) ?>%
                                            </span>
                                        </span>
                                    </div>
                                    <?= /* @noEscape */ $secureRenderer->renderStyleAsTag(
                                        'width:' . $_vote->getPercent() . '%',
                                        'div#review_' . $_review->getReviewId()
                                        . '_vote_' . $_vote->getVoteId() . ' span'
                                    ) ?>
                                </div>
                                <?php endforeach; ?>
                            </div>
                        <?php endif; ?>
                        <div class="review-content-container">
                            <div class="review-content" itemprop="description">
                                <?= /* @noEscape */ nl2br($escaper->escapeHtml($_review->getDetail())) ?>
                            </div>
                            <div class="review-details">
                                <p class="review-author">
                                    <span class="review-details-label">
                                        <?= $escaper->escapeHtml(__('Review by')) ?>
                                    </span>
                                    <strong class="review-details-value" itemprop="author">
                                        <?= $escaper->escapeHtml($_review->getNickname()) ?>
                                    </strong>
                                </p>
                                <p class="review-date">
                                    <span class="review-details-label">
                                        <?= $escaper->escapeHtml(__('Posted on')) ?>
                                    </span>
                                    <time class="review-details-value" itemprop="datePublished"
                                          datetime="<?= $escaper->escapeHtmlAttr($block->formatDate(
                                              $_review->getCreatedAt(),
                                              $format
                                          )) ?>">
                                        <?= $escaper->escapeHtml(
                                            $block->formatDate(
                                                $_review->getCreatedAt(),
                                                $format
                                            )
                                        ) ?>
                                    </time>
                                </p>
                            </div>
                        </div>
                    </div>
                </div>
            </li>
        <?php endforeach; ?>
        </ol>
        <div class="toolbar review-toolbar">
            <?php echo $block->getChildHtml('toolbar') ?>
        </div>
    </div>
</div>
<?php endif;?>

Now we will add layout file to display customer profile image at admin customer grid.

Add file at app/code/Magelearn/CustomerAvatar/view/adminhtml/ui_component/customer_listing.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <columns name="customer_columns" class="Magento\Customer\Ui\Component\Listing\Columns">
        <column name="profile_picture" class="Magelearn\CustomerAvatar\Ui\Component\Listing\Columns\Avatar">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/thumbnail</item>
                    <item name="sortable" xsi:type="boolean">false</item>
                    <item name="has_preview" xsi:type="string">1</item>
                    <item name="label" xsi:type="string" translate="true">Avatar</item>
                    <item name="sortOrder" xsi:type="number">1</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>

As per highligted code above we will add app/code/Magelearn/CustomerAvatar/Ui/Component/Listing/Columns/Avatar.php file.

<?php

namespace Magelearn\CustomerAvatar\Ui\Component\Listing\Columns;

use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Framework\View\Element\UiComponent\ContextInterface;

class Avatar extends \Magento\Ui\Component\Listing\Columns\Column
{
    /**
     * @var \Magento\Framework\View\Element\AbstractBlock
     */
    protected $viewFileUrl;

    /**
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param \Magento\Catalog\Helper\Image $imageHelper
     * @param \Magento\Framework\UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        \Magento\Framework\UrlInterface $urlBuilder,
        \Magento\Framework\View\Asset\Repository $viewFileUrl,
        array $components = [],
        array $data = []
    ) {
        parent::__construct($context, $uiComponentFactory, $components, $data);
        $this->urlBuilder = $urlBuilder;
        $this->viewFileUrl = $viewFileUrl;
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            $fieldName = $this->getData('name');
            foreach ($dataSource['data']['items'] as & $item) {
                $customer = new \Magento\Framework\DataObject($item);
                $picture_url = !empty($customer["profile_picture"]) ? $this->urlBuilder->getUrl(
                    'customer/index/viewfile/image/'.base64_encode($customer["profile_picture"])) : $this->viewFileUrl->getUrl('Magelearn_CustomerAvatar::images/no-profile-photo.jpg');
                $item[$fieldName . '_src'] = $picture_url;
                $item[$fieldName . '_orig_src'] = $picture_url;
                $item[$fieldName . '_alt'] = 'The profile picture';
            }
        }

        return $dataSource;
    }
}

At last we will add some CSS files as per defined in layout xml files at app/code/Magelearn/CustomerAvatar/view/frontend/web/css folder. Images at app/code/Magelearn/CustomerAvatar/view/frontend/web/images folder.

One JS file we will add at app/code/Magelearn/CustomerAvatar/view/frontend/web/js/avatar-validation.js

require([
        'jquery',
        'jquery/ui',
        'jquery/validate',
        'mage/translate'
    ], function ($) {
    //Validate Image FileSize
    $.validator.addMethod(
        'validate-image', function (v, elm) {
            if (elm.value != '') {
                var ext = elm.value.split('.').pop().toLowerCase();
                if ($.inArray(ext, ['gif', 'png', 'jpg', 'jpeg']) == -1) {
                    return false;
                }
            }
            return true;
        }, $.mage.__('Image invalid (Accepting format .gif .png .jpg .jpeg)')
    );
});
1 Comments On "Add Customer Profile Image/Avatar to customer account information Magento2"

Thanks for such a great article here. I was searching for something like this for quite a long time and at last I’ve found it on your blog. It was definitely interesting for me to read. Keep it up…
Magento Development Company
Magento 2 Development Company
Magento Development Services
Magento ecommerce developer
Magento Ecommerce Agency

Back To Top