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:
1
2
3
4
5
6
7
8
9
<?php
 
use Magento\Framework\Component\ComponentRegistrar;
 
ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Magelearn_CustomerAvatar',
    __DIR__
);

Add composer.json file in it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
    "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:

1
2
3
4
5
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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.

1
2
3
4
5
6
7
8
9
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<?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.

1
2
3
4
5
6
7
8
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
<?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>
 
        <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>
 
                <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>
 
            <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>
 
        <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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
<?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>
 
        <?= $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>
 
        <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.

1
2
3
4
5
6
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?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; ?><b> </b>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)')
    );
});

Related Post:

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