Tworzenie aplikacji „zakładki” część II

cakephpTo jest kolejna część kursu CakePHP. Poprzednią część znajdziesz tutaj. W niniejszej części zajmiemy się autentykacją oraz dodaniem restrykcji w celu ograniczenia dostępu do zakładek dla poszczególnych użytkowników.
Dodajemy logowanie

W CakePHP autentykacja jest realizowana przez komponent. Komponenty są elementami do wielokrotnego wykorzystania i realizują określona funkcję lub koncepcję. Do celów logowania w aplikacji wykorzystamy komponent Auth. Jako że będziemy go używać w całej aplikacji, należy dodać AuthComponent kontrolerze AppController. Dzięki temu, będzie można z niego korzystać w całej aplikacji:

// Plik: src/Controller/AppController.php
namespace App\Controller;
use Cake\Controller\Controller;
 
class AppController extends Controller
{
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
]
]);
 
// Allow the display action so our pages controller
// continues to work.
$this->Auth->allow(['display']);
}
}

Właśnie poinformowalismy CakePHP, że chcemy załadować komponenty Flash i Auth. Dodatkowo, w przypadku komponentu Auth wprowadziliśmy parametry informujące framework, że logowanie do aplikacji odbywa się na podstawie danych w tabeli users, zaś pole email będzie używane jako nazwa użytkownika, zaś pole password – jako hasło. Teraz, otworzysz aplikację w przeglądarce i dopiszesz do URLa /users/login, ujrzysz stronę w błędem, informującą o braku metody login w kontrolerze UsersController. Należy więc dopisać do kontrolera akcję login:

// Plik: src/Controller/UsersController.php
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Nieprawidłowy login lub hasło.');
}
}

Dodatkowo należy utworzyć widok src/Template/Users/login.ctp jak poniżej:

<h1>Logowanie</h1>
<?= $this->Form->create() ?>
<?= $this->Form->input('email') ?>
<?= $this->Form->input('password') ?>
<?= $this->Form->button('Zaloguj') ?>
<?= $this->Form->end() ?>

Obecnie mamy gotowe logowanie do systemu wraz z haszowaniem hasła. W przypadku, gdy użytkownicy nie posiadają haszowanych haseł, logowanie nie powiedzie się. Należy więc zakomentować linię loadComponent(‚Auth’), a następnie przejść do edycji użytkownika, zapisać do bazy nowe hasło (zostanie automatycznie zapisane jako hasz) i dopero wówczas odkomentować linię loadComponent(‚Auth’). Po tych czynnościach można się logować do aplikacji.

Wylogowanie z systemu

Aplikacja powinna mieć także funkcję wylogowania. Zmodyfikuj więc ponownie UsersController, dopisując kolejną akcję:

public function initialize()
{
parent::initialize();
$this->Auth->allow(['logout']);
}
 
public function logout()
{
$this->Flash->success('Wylogowałeś się z systemu.');
return $this->redirect($this->Auth->logout());
}

Powyższy kod realizuje w kontrolerze publiczną akcję wylogowania. Otwórz w przeglądarce /users/logout aby się wylogować. Zostaniesz przeniesiony na stronę logowania.

Rejestracja użytkowników

Jeśli nie jesteś zalogowany i spróbujesz odwiedzić stronę dodawania użytkowników /users/add, zostaniesz automatycznie przeniesiony na stronę logowania. Należy więc umożliwić użytkownikom możliwość rejestracji. W kontrolerze UsersController dodaj następujące wiersze:

public function initialize()
{
parent::initialize();
// Zezwalaj niezalogowanym na dostęp do akcji logout i add.
$this->Auth->allow(['logout', 'add']);
}

Powyższy kod informuje komponent Auth, że akcje logout() oraz add() nie wymagają autentykacji czy autoryzacji. Dzięki temu, użytkownik niezalogowany będzie miał do nich dostęp. Warto także zmodyfikować plik widoku add.ctp i wyciąć z niego odnośniki do innych akcji, pozostawiając jedynie opcję Zapisz, ponieważ użytkownik, który się rejestruje, nie będzie miał do nich dostępu. Jednak możesz to zrobić w późniejszym czasie.

Restrykcje dostępu do zakładek

Logowanie do aplikacji jest już gotowe. Teraz przyszedł czas, by umożliwić użytkownikom dostęp do wyłącznie własnych zakładek. W tym celu wykorzystamy autoryzację. Ponieważ nasze wymagania sa bardzo proste, wpiszemy kilka linii kodu do kontrolera BookmarksController, jednakże zanim to zrobimy, musimy poinformować AuthComponent jak nasza aplikacja ma ma realizować akcję autoryzacji. W AppController dodaj następujące linie:

public function isAuthorized($user)
{
return false;
}

Dodatkowo należy dodać konfigurację dla Auth w AppController:

'authorize' => 'Controller',

Metoda initialize() będzie zaś wyglądała tak:

public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authorize'=> 'Controller', //dodaj tę linię
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => $this->referer()
]);
// Allow the display action so our pages controller
// continues to work.
$this->Auth->allow(['display']);
}

Domyślnie odbieramy uprawnienia i stopniowo przyznajemy dostęp, gdzie ma to sens. W pierwszej kolejności autoryzujemy logikę dla zakładek. W kontrolerze BookmarksController dodaj:

public function isAuthorized($user)
{
$action = $this->request->params['action'];
 
// The add and index actions are always allowed.
if (in_array($action, ['index', 'add', 'tags'])) {
return true;
}
// All other actions require an id.
if (empty($this->request->params['pass'][0])) {
return false;
}
 
// Check that the bookmark belongs to the current user.
$id = $this->request->params['pass'][0];
$bookmark = $this->Bookmarks->get($id);
if ($bookmark->user_id == $user['id']) {
return true;
}
return parent::isAuthorized($user);
}

Teraz można wypróbować przeglądanie, edycję oraz usuwanie zakładek, które należą do zalogowanego użytkownika. Jeśli wpis nie należy do ciebie, zostaniesz przekierowany do strony, z której przeszedłeś. Jednakże w tym przypadku nie zostanie wyświetlony żaden błąd, dlatego należy to poprawić:

// Plik: src/Template/Layout/default.ctp
// Powyżej istniejącego komunikatu flash.
<?= $this->Flash->render('auth') ?>

Po tej modyfikacji ujrzysz komunikaty autoryzacji.

Dostosowywanie list i form

Choć podgląd i usuwanie działają poprawnie, edycja, dodawanie oraz index zawierają pewne problemy:

Gdy dodajesz zakładkę, możesz wybierać użytkownika.
Kiedy edytujesz zakładkę, możesz wybierać użytkownika.
Strona listy wciąż wyświetla zakładki innych użytkowników.

Zajmijmy się najpierw formularzem dodawania. W pierwszej kolejności należy usunąć input(‚user_id’) z pliku src/Template/Bookmarks/add.ctp. Po usunięciu, należy także zaktualizować akcję add() w pliku src/Controller/BookmarksController.php jak na poniższym przykładzie:

public function add()
{
$bookmark = $this->Bookmarks->newEntity();
if ($this->request->is('post')) {
$bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
$bookmark->user_id = $this->Auth->user('id');
if ($this->Bookmarks->save($bookmark)) {
$this->Flash->success('Zakładka została zapisana poprawnie.');
return $this->redirect(['action' => 'index']);
}
$this->Flash->error('Błąd podczas zapisu zakładki. Proszę ponowić próbę.');
}
$tags = $this->Bookmarks->Tags->find('list');
$this->set(compact('bookmark', 'tags'));
$this->set('_serialize', ['bookmark']);
}

Poprzez ustawienie właściwości w sesji danych, usuniemy teraz możliwość modyfikacji przez użytkownika zakładek innych należących do użytkowników. Podobnie postąpimy w akcji edit(). Akcja edit() w pliku src/Controller/BookmarksController.php będzie wyglądać teraz tak:

public function edit($id = null)
{
$bookmark = $this->Bookmarks->get($id, [
'contain' => ['Tags']
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
$bookmark->user_id = $this->Auth->user('id');
if ($this->Bookmarks->save($bookmark)) {
$this->Flash->success('Edycja zakładki powiodła się.');
return $this->redirect(['action' => 'index']);
}
$this->Flash->error('Błąd podczas edycji zakładki. Proszę ponowić próbę.');
}
$tags = $this->Bookmarks->Tags->find('list');
$this->set(compact('bookmark', 'tags'));
$this->set('_serialize', ['bookmark']);
}

Widok listy

Teraz wystarczy wyświetlić tylko zakładki, które należą do aktualnie zalogowanego użytkownika. W tym celu można wykorzystać paginate(). Zmień w kontrolerze akcję index() w pliku src/Controller/BookmarksController.php jak poniżej:

public function index()
{
$this->paginate = [
'conditions' => [
'Bookmarks.user_id' => $this->Auth->user('id'),
]
];
$this->set('bookmarks', $this->paginate($this->Bookmarks));
$this->set('_serialize', ['bookmarks']);
}

Uaktualnimy także akcję tags() oraz powiązaną metodę finder, lecz tę część możesz wykonać we własnym zakresie.

Poprawa tagów

Obecnie dodawanie nowych tagów jest trudnym procesem, ponieważ TagsController uniemożliwia dostęp. Jednak zamiast udzielać dostępu, wdrożymy możliwość wprowadzania tagów w polu tekstowym, zaś tagi będzie można oddzielać średnikiem. Będzie to dobrym udogodnieniem dla użytkowników oraz umożliwi użycie większej funkcjonalności zawartej w ORM.

Dodawanie pola wirtualnego (Computed Field)

Ponieważ chcemy w prosty sposób sformatować tagi dla encji, dodamy do niej pole virtual/computed. W pliku src/Model/Entity/Bookmark.php dodaj następujące wiersze:

use Cake\Collection\Collection;
 
protected function _getTagString()
{
if (isset($this->_properties['tag_string'])) {
return $this->_properties['tag_string'];
}
if (empty($this->tags)) {
return '';
}
$tags = new Collection($this->tags);
$str = $tags->reduce(function ($string, $tag) {
return $string . $tag->title . ', ';
}, '');
return trim($str, ', ');
}

To umożliwi dostęp do właściwości $bookmark->tag_string. Z tego obiektu skorzystamy nieco później. Pamiętaj tylko, by dodać właściwość tag_string do listy _accessible w encji.

W pliku src/Model/Entity/Bookmark.php dodaj tag_string to $_accessible:

protected $_accessible = [
'user_id' => true,
'title' => true,
'description' => true,
'url' => true,
'user' => true,
'tags' => true,
'tag_string' => true,
];

Aktualizacja widoków

Podczas aktualizacji encji, dodamy pole do wprowadzania tagów. W pliku src/Template/Bookmarks/add.ctp and src/Template/Bookmarks/edit.ctp, zastąp existing tags._ids input następującym kodem:

echo $this->Form->input('tag_string', ['type' => 'text']);

Persisting the Tag String

Teraz można wyświetlić istniejące taki jako łańcuch znaków, lecz trzeba także jakoś zapisywać takie dane. Ponieważ zaznaczyliśmy wcześniej, że jest dostęp do tag_string, ORM będzie kopiował dane z żądania do naszej encji. Użyjemy metody typu hook beforeSave() do parsowania tagów w stringu i znajdziemy/zbudujemy powiązaną encję. Dodaj poniższy kod do pliku src/Model/Table/BookmarksTable.php:

public function beforeSave($event, $entity, $options)
{
if ($entity->tag_string) {
$entity->tags = $this->_buildTags($entity->tag_string);
}
}
 
protected function _buildTags($tagString)
{
// Przytnij tagi
$newTags = array_map('trim', explode(',', $tagString));
// Usuń wszystkie puste tagi
$newTags = array_filter($newTags);
// Reduce duplicated tags
$newTags = array_unique($newTags);
 
$out = [];
$query = $this->Tags->find()
->where(['Tags.title IN' => $newTags]);
 
// Usuń istniejące tagi z listy nowych znaczników.
foreach ($query->extract('title') as $existing) {
$index = array_search($existing, $newTags);
if ($index !== false) {
unset($newTags[$index]);
}
}
// Dodawanie istniejących tagów.
foreach ($query as $tag) {
$out[] = $tag;
}
// Dodawanie nowego tagu.
foreach ($newTags as $tag) {
$out[] = $this->Tags->newEntity(['title' => $tag]);
}
return $out;
}

612total visits,1visits today

Tagi , , .Dodaj do zakładek Link.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

+ 61 = 62

This site uses Akismet to reduce spam. Learn how your comment data is processed.