--------------- <> -----------------
--- KHOA HỌC - CÔNG NGHỆ - GIÁO DỤC - VIỆC LÀM ---
--- Học để đi cùng bà con trên thế giới ---

Tìm kiếm trong Blog

Ngu ngơ học làm web (x45) - CakePHP2 - Tạo helper, Admin books

Tiếp theo của: Ngu ngơ học làm web (x44) - CakePHP2 – Dependent, confirm, moveUp, moveDown
-----

Phần x45. CakePHP2 – Tạo helper, Admin books


Xem (clip số 43c – chickenrainshop):

- find(threaded) (43c) (1":34)

- Tạo mới một helper (2":08)

- Hiển thị theo cấu trúc cây phân cấp (4":37)

Hiển thị danh mục sách ở trang chủ theo đúng cấu trúc cây (chứ không phải theo tên):

[CategoriesController.php]

public function menu() {
                        if ($this->request->is('requested')) {
                                    $categories = $this->Category->find('threaded', [
                                                'recursive' => -1,
                                                'order' => ['lft' => 'asc']
                                    ]);
                                    return $categories;
                        }
            }

Tạo một helper:

[View\Helper\DisplayHelper.php]

<?php
class DisplayHelper extends AppHelper {
            public function menu($categories, $text = null) {
                        $text .= '<ul>';
                        foreach ($categories as $category) {
                                    $text .= '<li>'.$category['Category']['name'].'</li>';
                                    if (!empty($category['children'])) {
                                                $text = $this->menu($category['children'], $text);
                                    }
                        }
                        $text .= '</ul>';
                        return $text;
            }
}          
?>

Khai báo để sử dụng DisplayHelper:

[AppController.php]

public $helpers = array('Display');
public function beforeFilter() {

Sửa lại [DisplayHelper.php]

<?php
class DisplayHelper extends AppHelper {
            public $helpers = ['Html'];
            public function menu($categories, $text = null) {
                        $text .= '<ul>';
                        foreach ($categories as $category) {
                                    $text .= '<li>'.$this->Html->link($category['Category']['name'], '/danh-muc/'.$category['Category']['slug']).'</li>';
                                    if (!empty($category['children'])) {
                                                $text = $this->menu($category['children'], $text);
                                    }
                        }
                        $text .= '</ul>';
                        return $text;
            }
}          
?>

Xem (clip số 44 – chickenrainshop):

- Tích hợp trình soạn thảo CKEditor (44)

- include tập tin javascript vào view (4":08)

- Sửa lỗi tiếng Việt trong CKEditor (7":05)

- Sử dụng block để gọi javascript (12":10)

Viết mã cho màn hình /admin/books:

Viết action admin_index(),

[BooksController.php]

public function admin_index() {
                        $this->set('title_for_layout', 'Book Admin');
            }

Route cho /admin/books:

[routes.php]

Router::connect('/admin/books', ['controller' => 'books', 'action' => 'index', 'admin' => true]);

Viết mã cho view admin_index:

[admin_index.ctp]

<?php //pr($books); ?>
<div class="books index">
            <h2><?php echo __('Sách'); ?></h2>
            <table cellpadding="0" cellspacing="0">
            <thead>
            <tr>
                                    <th><?php echo $this->Paginator->sort('Danh mục'); ?></th>
                                    <th><?php echo $this->Paginator->sort('Tên sách'); ?></th>
                                    <th><?php echo $this->Paginator->sort('Ảnh'); ?></th>
                                    <th><?php echo $this->Paginator->sort('Giá bán'); ?></th>
                                    <th><?php echo $this->Paginator->sort('Ngày tạo'); ?></th>
                                    <th><?php echo $this->Paginator->sort('Ngày sửa'); ?></th>
                                    <th class="actions"><?php echo __('Actions'); ?></th>
            </tr>
            </thead>
            <tbody>
            <?php foreach ($books as $book): ?>
            <tr>
                        <td>
                                    <?php echo $book['Category']['name']; ?>&nbsp;
                        </td>
                        <td><?php echo $book['Book']['title']; ?>&nbsp;</td>
                        <td><?php echo $this->Html->image($book['Book']['image'], ['height' => 150, 'width' => 100]); ?>&nbsp;</td>
                        <td><?php echo $this->Number->currency($book['Book']['sale_price'],' VND',['places' => 0,'wholePosition' => 'after']); ?> &nbsp;</td>
                        <td><?php echo $book['Book']['created']; ?>&nbsp;</td>
                        <td><?php echo $book['Book']['modified']; ?>&nbsp;</td>
                        <td class="actions">
                                    <?php echo $this->Html->link(__('View'), array('action' => 'view', $book['Book']['id'])); ?>
                                    <?php echo $this->Html->link(__('Edit'), array('action' => 'edit', $book['Book']['id'])); ?>
                                    <?php echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $book['Book']['id']), array('confirm' => __('Bạn có đồng ý xóa cuốn sách không?'))); ?>
                        </td>
            </tr>
<?php endforeach; ?>
            </tbody>
            </table>
            <p>
            <?php
            echo $this->Paginator->counter(array(
                        'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
            ));
            ?>       </p>
            <div class="paging">
            <?php
                        echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
                        echo $this->Paginator->numbers(array('separator' => ''));
                        echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
            ?>
            </div>
</div>

Viết tiếp mã cho action admin_index(),

[BooksController.php]

public function admin_index() {
                        $this->paginate = [
                                    'fields' => ['id', 'category_id', 'title', 'slug', 'image', 'sale_price', 'created', 'modified' ],
                                    'order' => ['created' => 'desc'],
                                    'limit' => 10,
                                    'contain' => ['Category' => ['name', 'slug']],
                        ];
                        $books = $this->paginate();
                        $this->set('books', $books);
                        $this->set('title_for_layout', 'Book Admin');
            }

Trong [BooksController.php] đổi tên action edit() thành admin_edit().


Trong view Books đổi tên edit.ctp thành admin_edit.ctp.
-----------
Cập nhật 27/7/2017
-----------
Xem thêm:
Tổng hợp các bài viết về Ngu ngơ học làm web

Ngu ngơ học làm web (x44) - CakePHP2 - Dependent, confirm, moveUp, moveDown

Tiếp theo của: Ngu ngơ học làm web (x43) - CakePHP2 – Tree behavior
-----

Phần x44. CakePHP2 – Dependent, confirm, moveUp, moveDown


Xem (clip số 43b – chickenrainshop):

- Hiển thị dữ liệu dạng cây (43b)

- Hàm removeFromTree để xóa dữ liệu (4":03)

- Làm việc với quan hệ hasMany, dependent (6":42)

- Chuyển danh mục lên/xuống một bậc (10":08)

- Hàm exists, moveUp, moveDown (11":13)

Hiển thị các danh mục sách theo kiểu phân cấp trong view admin_index:

Ý tưởng là sắp xếp lại các danh mục sách tăng dần theo giá trị của trường lft.

[Controller\CategoriesController.php]

public function admin_index() {
                        $this->paginate = [
                                    'order' => ['lft' => 'asc']
                        ];
                        $this->Category->recursive = -1;
                        $this->set('categories', $this->Paginator->paginate());
            }

Chỉnh lại phần hiển thị một chút:

[admin_index.ctp]

<thead>
            <tr>
                                    <th><?php echo 'STT'; ?></th>
                                    <th><?php echo $this->Paginator->sort('name'); ?></th>
<tbody>
            <?php $i = 1; ?>
            <?php foreach ($categories as $category): ?>
            <tr>
                        <td><?php echo $i++; ?>&nbsp;</td>
                        <td>
                                    <?php if ($category['Category']['parent_id'] != null): ?>
                                                +
                                    <?php endif ?>
                                    <?php echo h($category['Category']['name']); ?>&nbsp;
                        </td>

Thay hàm delete() bằng hàm removeFromTree():

[CategoriesController.php action admin_delete()]

$this->request->allowMethod('post', 'delete');
                        if ($this->Category->removeFromTree($id, true)) {
                                    $this->Flash->success(__('The category has been deleted.'));

Khi thực hiện xóa một Category, để xóa luôn các Book và Comment liên quan, cần cấu hình thông tin về dependent trong model.

[Model\Category.php]

public $hasMany = array(
                        'Book' => array(
                                    …
                                    'dependent' => true,
                                    …
                        )
            );

[Model\Book.php]

public $hasMany = array(
                        'Comment' => array(
                                    …
                                    'dependent' => false,
                                    …
                        )
            );

Cách xuất một câu thông báo yêu cầu người dùng xác nhận (confirm):

<?php echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $category['Category']['id']), array('confirm' => __('Bạn có đồng ý xóa tất cả cuốn sách trong danh mục %s hay không?', $category['Category']['name']))); ?>

Thực hiện di chuyển một nút trong cây, chuyển lên:

[admin_index.ctp]

<td class="actions">
            <?php echo $this->Form->postlink('Lên', ['controller' => 'categories', 'action' => 'up', $category['Category']['id']]); ?>
            <?php echo $this->Html->link(__('View'), array('action' => 'view', $category['Category']['id'])); ?>

Viết mã cho action up():

[CategoriesController.php]

public function admin_up($id = null) {
                        if ($this->request->is('post')) {
                                    $this->Category->id = $id;
                                    if ($this->Category->exists()) {
                                                $this->Category->moveUp($id, 1);
                                    }
                        }
                        $this->redirect($this->referer());
            }

Để ý là trường lft và rght thể hiện thứ tự của một nút trong cây, thực hiện moveUp rồi quan sát database để rõ hơn.

Viết tiếp cho chức năng moveDown:

[admin_index.ctp]

<?php echo $this->Form->postlink('Lên', ['controller' => 'categories', 'action' => 'up', $category['Category']['id']]); ?>
<?php echo $this->Form->postlink('Xuống', ['controller' => 'categories', 'action' => 'down', $category['Category']['id']]); ?>

Viết mã cho action down():

[CategoriesController.php]

public function admin_down($id = null) {
                        if ($this->request->is('post')) {
                                    $this->Category->id = $id;
                                    if ($this->Category->exists()) {
                                                $this->Category->moveDown($id, 1);
                                    }
                        }
                        $this->redirect($this->referer());

            }
-----------
Cập nhật 25/7/2017
-----------
Xem thêm:
Tổng hợp các bài viết về Ngu ngơ học làm web

Ngu ngơ học làm web (x43) - CakePHP2 - Tree behavior

Tiếp theo của: Ngu ngơ học làm web (x42) - CakePHP2 – admin, route prefix, request->params
-----

Phần x43. CakePHP2 – Tree behavior


Xem (clip số 43a – chickenrainshop):

- Sử dụng tree behavior để tạo danh mục phân cấp (43a) (3":51)

- Sử dụng recover để xây dựng dữ liệu cho parent, children (4":35)

- Sử dụng select box (8":03)

- generateTreeList (9":09)

Thêm các trường parent_id, lft và rght cho bảng categories:

alter table categories
add parent_id int default null after id,
add lft int default null after parent_id,
add rght int default null after lft;

Khai báo sử dụng tree behavior trong model Category:

[Model\Category.php]

class Category extends AppModel {

            public $actsAs = ['Tree'];


Sử dụng hàm recover() của tree behavior để thêm dữ liệu cho các trường parent_id, lft, rght trong bảng categories:

[Controller\CategoriesController.php]

public function admin_index() {
                        //$this->layout = 'admin';
                        $this->Category->recover();
                        $this->Category->recursive = -1;

Sửa lại giao diện của form ‘thêm danh mục sách’ (New Category), để cho phép người dùng nhập vào giá trị của trường parent_id khi thêm một danh mục sách mới.

[View\Categories\admin_add.ctp]

<div class="input">
                        <?php echo $this->Form->select('parent_id'); ?>
            </div>
            <?php
                        echo $this->Form->input('name');

Đưa dữ liệu vào trường parent_id trên view:

[Controller\CategoriesController.php]

public function admin_add() {
                        if ($this->request->is('post')) {
                                    $this->Category->create();
                                    if ($this->Category->save($this->request->data)) {
                                                $this->Flash->success(__('The category has been saved.'));
                                                return $this->redirect(array('action' => 'index'));
                                    } else {
                                                $this->Flash->error(__('The category could not be saved. Please, try again.'));
                                    }
                        }
                        // sinh ra một cấu trúc cây
                        $categories = $this->Category->generateTreeList();
                        $this->set('categories', $categories);
            }

Sửa lại một chút [View\Categories\admin_add.ctp]

<div class="input">
                        <?php echo $this->Form->select('parent_id', $categories, ['empty' => '--Chọn danh mục--']); ?>
            </div>

Thực hiện thêm trường parent_id trên view admin_edit:

[View\Categories\admin_edit.ctp]

<div class="input">
                        <?php echo $this->Form->select('parent_id', $categories, ['empty' => '--Chọn danh mục--']); ?>
            </div>
            <?php
                        echo $this->Form->input('id');

[Controller\CategoriesController.php]

public function admin_edit($id = null) {
                        if (!$this->Category->exists($id)) {
                                    throw new NotFoundException(__('Invalid category'));
                        }
                        if ($this->request->is(array('post', 'put'))) {
                                    if ($this->Category->save($this->request->data)) {
                                                $this->Flash->success(__('The category has been saved.'));
                                                return $this->redirect(array('action' => 'index'));
                                    } else {
                                                $this->Flash->error(__('The category could not be saved. Please, try again.'));
                                    }
                        } else {
                                    $options = array('conditions' => array('Category.' . $this->Category->primaryKey => $id));
                                    $this->request->data = $this->Category->find('first', $options);
                        }
                        // sinh ra một cấu trúc cây
                        $categories = $this->Category->generateTreeList();
                        $this->set('categories', $categories);

            }
-----------
Cập nhật 24/7/2017
-----------
Xem thêm:
Tổng hợp các bài viết về Ngu ngơ học làm web

Ngu ngơ học làm web (x42) - CakePHP2 - admin, route prefix, request->params

Tiếp theo của: Ngu ngơ học làm web (x41) - CakePHP2 – updateAll, CakeEmail, config smtp
-----

Phần x42. CakePHP2 – admin, route prefix, request->params


Xem (clip số 42 – chickenrainshop):

- Lập trình chức năng admin (42)

- Cấu hình prefixes (2":28)

- Lựa chọn layout mặc định cho một controller (7":03) 

- Chọn layout trong beforeFilter để không phải chọn layout nhiều lần trong các controller (10":28)  

- this->request->params (11":01)

- substr  (12":24)

Định dạng URI của các chức năng admin luôn có dạng admin_... Vì vậy, cần thiết lập định dạng cho URI bằng cách cấu hình prefixes trong Config\core.php.

Configure::write('Routing.prefixes', array('admin'));

Thiết lập các action và view cho phần backend, ví dụ thực hiện trên Categories:

Trong CategoriesController có hai action thuộc frontend là menu() và view(); các action còn lại là thuộc backend. Vì vậy, cần thực hiện thêm prefix admin cho các action thuộc backend.

Các view mà có action thuộc backend cũng được đổi tên với prefix là admin.

Nội dung của layout admin.

[admin.ctp]

<?php
$description = 'Chickenrainshop - admin';
?>
<!DOCTYPE html>
<html>
<head>
            <?php echo $this->Html->charset(); ?>
            <title>
                        <?php echo $description ?>:
                        <?php echo $title_for_layout; ?>
            </title>
            <?php
                        echo $this->Html->meta('icon');

                        echo $this->Html->css('cake.generic');

                        echo $this->fetch('meta');
                        echo $this->fetch('css');
                        echo $this->fetch('script');
            ?>
</head>
<body>
            <div id="container">
                        <div id="header">
                                    <h1><?php echo $this->Html->link($description, 'http://local.chickenrainshop'); ?><small> | Chào <strong><?php echo $userInfo['fullname']; ?></strong>! <?php echo $this->Html->link('Thoát', '/logout'); ?></small></h1>
                        </div>
                        <div id="content">

                                    <?php echo $this->Flash->render(); ?>
                                    <?php echo $this->element('admin/menu'); ?>
                                    <?php echo $this->fetch('content'); ?>
                                   
                        </div>
            </div>
</body>
</html>
[Elements\admin\menu.ctp]
 <div class="actions">
            <h3><?php echo __('Menu'); ?></h3>
            <ul>
                        <li><?php echo $this->Html->link(__('Categories'), ['controller' => 'categories', 'action' => 'index']); ?></li>
                        <li><?php echo $this->Html->link(__('Books'), ['controller' => 'books', 'action' => 'index']); ?></li>
                        <li><?php echo $this->Html->link(__('Coupons'), ['controller' => 'coupons', 'action' => 'index']); ?></li>
                        <li><?php echo $this->Html->link(__('Writers'), ['controller' => 'writers', 'action' => 'index']); ?></li>
                        <li><?php echo $this->Html->link(__('Comments'), ['controller' => 'comments', 'action' => 'index']); ?></li>
                        <li><?php echo $this->Html->link(__('Orders'), ['controller' => 'orders', 'action' => 'index']); ?></li>
                        <li><?php echo $this->Html->link(__('Groups'), ['controller' => 'groups', 'action' => 'index']); ?></li>
                        <li><?php echo $this->Html->link(__('Users'), ['controller' => 'users', 'action' => 'index']); ?></li>
            </ul>
</div>

[View\Categories\admin_index.ctp]

<div class="categories index">
            <h2><?php echo __('Categories'); ?></h2>
            <table cellpadding="0" cellspacing="0">
            <thead>
            <tr>
                                    <th><?php echo $this->Paginator->sort('id'); ?></th>
                                    <th><?php echo $this->Paginator->sort('name'); ?></th>
                                    <th><?php echo $this->Paginator->sort('slug'); ?></th>
                                    <th><?php echo $this->Paginator->sort('description'); ?></th>
                                    <th><?php echo $this->Paginator->sort('created'); ?></th>
                                    <th class="actions"><?php echo __('Actions'); ?></th>
            </tr>
            </thead>
            <tbody>
            <?php foreach ($categories as $category): ?>
            <tr>
                        <td><?php echo h($category['Category']['id']); ?>&nbsp;</td>
                        <td><?php echo h($category['Category']['name']); ?>&nbsp;</td>
                        <td><?php echo h($category['Category']['slug']); ?>&nbsp;</td>
                        <td><?php echo h($category['Category']['description']); ?>&nbsp;</td>
                        <td><?php echo h($category['Category']['created']); ?>&nbsp;</td>
                        <td class="actions">
                                    <?php echo $this->Html->link(__('View'), array('action' => 'view', $category['Category']['id'])); ?>
                                    <?php echo $this->Html->link(__('Edit'), array('action' => 'edit', $category['Category']['id'])); ?>
                                    <?php echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $category['Category']['id']), array('confirm' => __('Are you sure you want to delete # %s?', $category['Category']['id']))); ?>
                        </td>
            </tr>
<?php endforeach; ?>
            </tbody>
            </table>
            <p>
            <?php
            echo $this->Paginator->counter(array(
                        'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
            ));
            ?>       </p>
            <div class="paging">
            <?php
                        echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
                        echo $this->Paginator->numbers(array('separator' => ''));
                        echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
            ?>
            </div>
</div>
<div class="actions">
            <ul>
                        <li><?php echo $this->Html->link(__('New Category'), array('action' => 'add')); ?></li>
                        <li><?php echo $this->Html->link(__('List Books'), array('controller' => 'books', 'action' => 'index')); ?> </li>
                        <li><?php echo $this->Html->link(__('New Book'), array('controller' => 'books', 'action' => 'add')); ?> </li>
            </ul>
</div>

Thay vì thiết lập layout trong  từng action, có thể làm một lần tại hàm beforeFilter, bằng cách kiểm tra biến $this->request->params và thiết lập layout cho phù hợp.

[Controller\AppController.php]

            public function beforeFilter() {
                        $this->Auth->allow('confirm','menu', 'index', 'register', 'forgotPassword');
                        $this->set('userInfo', $this->getUser());
                        //pr($this->request->params);
                        if (substr($this->request->params['action'], 0, 6) == 'admin_') {
                                    $this->layout = 'admin';
                        }
            }

[View\Categories\admin_add.ctp]

<div class="categories form">
<?php echo $this->Form->create('Category'); ?>
            <fieldset>
                        <legend><?php echo __('Add Category'); ?></legend>
            <?php
                        echo $this->Form->input('name');
                        echo $this->Form->input('slug');
                        echo $this->Form->input('description');
            ?>
            </fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>

Viết route cho ‘/admin’:

[routes.php]

Router::connect('/', ['controller' => 'books', 'action' => 'index']);

Router::connect('/admin', ['controller' => 'categories', 'action' => 'index', 'admin' => true]);
-----------
Cập nhật 24/7/2017
-----------
Xem thêm:
Tổng hợp các bài viết về Ngu ngơ học làm web

Ngu ngơ học làm web (x41) - CakePHP2 - updateAll, CakeEmail, config smtp

Tiếp theo của: Ngu ngơ học làm web (x40) - CakePHP2 – forgot password, generate code
-----

Phần x41. CakePHP2 – updateAll, CakeEmail, config smtp


Xem (clip số 41c– chickenrainshop):

- Hàm updateAll

Viết tiếp xử lý cho hàm confirm():

[Controller\UsersController.php action confirm()]

public function confirm($code = null) {
                        $confirm = false;
                        if (!empty($code)) {
                                    $user = $this->User->findByCode($code);
                                    if (!empty($user)) {
                                                $confirm = true;
                                                if ($this->request->is('post')) {
                                                            $this->User->set($this->request->data);
                                                            if ($this->User->validates()) {
                                                                        if ($this->updatePassword($user['User']['id'])) {
                                                                                    // xóa mã xác nhận trong cơ sở dữ liệu
                                                                                    $this->User->updateAll(['User.code' => null], ['User.id' == $user['User']['id']]);
                                                                                    $this->Flash->success('Đăng nhập với mật khẩu mới!', ['key' => 'auth', 'params' => ['class' => 'alert alert-success']]);
                                                                                    $this->redirect('/login');
                                                                        }
                                                            } else {
                                                                        $this->Flash->error($this->User->validationErrors, ['params' => ['class' => 'alert alert-error']]);
                                                            }
                                                }
                                    }
                        }
                        $this->set('confirm', $confirm);
                        $this->set('title_for_layout', 'Yêu cầu đổi mật khẩu mới');
            }

[Controller\UsersController.php hàm updatePassword()]

private function updatePassword($id) {
            $this->User->id = $id;
            if ($this->User->saveField('password', $this->request->data['User']['password'])) {
                        return true;
            } else {
                        return false;
            }
}

Xem (clip số 41d– chickenrainshop):

- Gửi link confirm về email người dùng (41d)

- Sử dụng CakeEmail (1":00)

- Cấu hình smtp (6":44)

Khai báo sử dụng CakeEmail:

[Controller\UsersController.php]

<?php
App::uses('AppController', 'Controller');
App::uses('CakeEmail', 'Network/Email');


Cấu hình smtp để gửi email trên máy cục bộ:

[Config\email.php]

public $smtp = array(
                        'transport' => 'Smtp',
                        'from' => array('email1@gmail.com' => 'Email1'),
                        'to' => 'email2@gmail.com',
                        'host' => 'ssl://smtp.gmail.com',
                        'port' => 465,
                        'timeout' => 30,
                        'username' => 'email1',
                        'password' => 'matkhau_email1',
                        'client' => null,
                        'log' => false,
                        //'charset' => 'utf-8',
                        //'headerCharset' => 'utf-8',
            );

Thêm mã cho action forgotPassword()

[Controller\UsersController.php]

public function forgotPassword() {
                        $this->set('title_for_layout', 'Quên mật khẩu');
                        if ($this->request->is('post')) {
                                    $user = $this->User->findByEmail($this->request->data['User']['email']);
                                    if (!empty($user)) {
                                                // tạo mã xác thực
                                                $code = $this->Tool->generateCode();
                                                // tạo link để người dùng bấm vào
                                                $linkConfirm = 'http://local.chickenrainshop/xac-nhan/'.$code;
                                                // lưu code vào cơ sở dữ liệu
                                                $this->User->id = $user['User']['id'];
                                                $this->User->saveField('code', $code);
                                                // gửi email link xác nhận
                                                $email = new CakeEmail();
                                                // $email->from(['chickenrain@gmail.com' => 'Admin']);
                                                // $email->to([$user['User']['email'] => $user['User']['name']]);
                                                // $email->subject('Xác nhận thay đổi mật khẩu');
                                                // $email->send('Bạn vừa yêu cầu thay đổi mật khẩu, vui lòng nhấn vào link để xác nhận: '.$linkConfirm);
                                                $email->config('smtp')
                                                            //->from(['chickenrain@gmail.com' => 'Admin']);
                                                            //->to([$user['User']['email'] => $user['User']['name']]);
                                                            ->subject('Xác nhận thay đổi mật khẩu')
                                                            ->send('Bạn vừa yêu cầu thay đổi mật khẩu, vui lòng nhấn vào link để xác nhận: '.$linkConfirm);
                                                $this->Flash->success('Vui lòng kiểm tra hộp thư để xác nhận yêu cầu!', ['params' => ['class' => 'alert alert-success'], 'key' => 'forgot']);
                                    } else {
                                                $this->Flash->error('Email này chưa được đăng kí trên hệ thống', ['params' => ['class' => 'alert alert-error'], 'key' => 'forgot']);
                                    }
                        }
            }

Do email của gmail được bảo mật, nên không cho ứng dụng được phép truy cập vào để gửi email, kết quả là ứng dụng web sẽ bị lỗi. Nghĩa là ‘Allow less secure apps’ đang ở chế độ OFF, cần chuyển sang chế độ ON để chạy được đoạn mã và kiểm tra. Sau đó nhớ chuyển email về lại chế độ OFF để đảm bảo an toàn cho hộp thư.


-----------
Cập nhật 22/7/2017
-----------
Xem thêm:
Tổng hợp các bài viết về Ngu ngơ học làm web