Storybook

Sorakuilu

Harjoituksen osat

Tunti 1. Toimeksianto ja storybook-sovelluksen esitutkimus
Tunti 2. Suunnittele storybook
Tunti 3. Toteuta tietokanta
Tunti 4. Toteuta käyttöliittymä
Tunti 5. Laadi runko ja reititys järjestelmään
Tunti 6. Toteuta lukijan etusivu (tietojen haku lukijan näkymässä)
Tunti 7. Toteuta rekisteröityminen(tietojen lisäys lukijan näkymässä)
Tunti 8. Toteuta autentikointi (kirjaudu ja kirjaudu ulos)
Tunti 9. Toteuta ylläpidon etusivu (tietojen haku)
Tunti 10.Toteuta jutun lisäys ylläpitonäkymässä, tietojen tarkistus mukaan
Tunti 11.Toteuta jutun muokkaus ylläpitonäkymässä, tietojen tarkistus mukaan
Tunti 12.Toteuta jutun poistaminen ylläpitonäkymässä
Omat tehtävät: suunnittele ja toteuta admin-näkymän yhden jutun näyttäminen (linkki on jo olemassa), omien tietojen muokkaaminen sekä salasanan vaihtaminen (piirrä lisäksi kaaviot)


Tunti 7. Toteuta rekisteröityminen(tietojen lisäys lukijan näkymässä) (./register)

Lukijan pääsivun toteuttamiseen tehdään seuraavat toimet:

  1. aloitetaan malli User.php (class User), luodaan siihen metodit public function __construct(), public function __destruct sekä public function add_account(), jolla lisätään tietoja kantaan
  2. lisätään 2 reittiä rekisteröitymistä varten index.php-tiedostoon
  3. aloitetaan kontrollereista toinen: UserController (userController.php) ja laaditaan siihen metodi register(), joka tarkistaa käyttäjän syötteet ja kutsuu käyttäjän lisäävää metodia (post)
  4. tehdään apufunktioita, joiden avulla tarkistetaan syötteiden oikeellisuutta (./helpers/helper.php)
  5. laaditaan näkymä rekisteröintilomaketta varten (get) ./views/registerform.view.php
  6. laaditaan näkymä rekisteröitymisen jälkeen ./views/registered.view.php

toteutettavat osat

Kuvassa näkyvät tässä vaiheessa tarvittavat osat, jo toteutetut ovat harmaalla pohjalla.

Alla oleva sekvenssikaavio kuvaa periaatetta - tarkemmin kuvattaessa kannattaa käyttää meteodien ja muuttujien nimiä:

sekvenssikaavio, index

Aluksi pyyntö on käyttää metodia get, joka hakee tyhjän lomakkeen kuvaruudulle. Kun käyttäjä on täyttänyt lomakkeen (metodi post), pyyntö ohjataan konntrollerille, joka tarkistaa syötteet ja ohjaa pyynnön tietokantaa käsittelevälle modelille.

Aloitetaan malli (class User), ./database/models/User.php

Tämän luokan luomisessa käytetään pohjana alexwebdevelop.com -sivuston User-luokkaa. Ko. luokka tukee istuntoja käyttävää autentikointia ja siinä on olemassa metodit sekä istuntojen luomiseen, tallentamiseen, trakistamiseen ja poistamiseen - eli pyörän keksiminen on taas jätetty muiden tehtäväksi.

Tietokantaa käsittelevään luokkaan kannattaa yleensä laittaa tietojäseniksi taulun kentät. Niin teemme myös tässä järjestelmässä.

Tietojäsenet ovat:

Järjestelmässä käytetään autentikoinnin takia paljon oliota $user, joten metodit eivät ole staattisia.

Metodi __construct() luo uuden olion, mutta asettaa kaikki tietojäsenten arvot tyhjiksi.

Metodia __destruct eli tuhoajaa ei ole toteutettu.

Metodi addAccount saa syötteekseen merkkijonot $last_name, $first_name, $name ja $passwd. Jos tietojen lisääminen onnistuu, se palauttaa lisätyn käyttäjän id:n.Käyttäjän id luodaan automaattisesti tietokannassa (account_id on tyypiltään integer ja autoincrement).

<?php
// Modified from https://alexwebdevelop.com/user-authentication/
class User {
    
    private 
$id//KIRJAUTUNEEN KÄYTTÄJÄN id TAI null
    
private $name//Kirjautuneen käyttäjän account_name tai NULL
    
private $last_name;
    private 
$first_name;
    private 
$account_passwd;
    
    public function 
__construct() // Alustetaan arvoiksi NULL
    
{
        
$this->id NULL;
        
$this->name NULL;
        
$this->last_name NULL;
        
$this-> first_name NULL;
    }

    public function 
__destruct() //tuhoaja - ei toteutettu
    
{
        
    }    

    public function 
addAccount(string $last_name,string $first_name,string $namestring $passwd): int
    
{
        global 
$db;
        
        if (!
is_null($this->getIdFromName($name,$db))) //tarkistaa, ettei kenelläkä'än muulla ole samaa nimeä
        
{
            throw new 
Exception('User name not available');
        }

        
$sql 'INSERT INTO users (last_name,first_name,account_name, account_passwd) VALUES (:last_name, :first_name, :name, :passwd)';
        
$values = array(':last_name' => $last_name':first_name' => $first_name':name' => $name':passwd' => $passwd);
        try
        {
            
$res $db->prepare($sql);
            
$res->execute($values);
        }
        catch (
PDOException $e//If there is a PDO exception, throw a standard exception
        
{
           throw new 
Exception('Database query error');
        }
        return 
$db->lastInsertId();        //palauttaa uuden id:n
    
}
    
    
    public function 
getIdFromName(string $name): ?int  //jos jollakulla käyttäjällä on sama nimi, palauttaa sen id:n, muuten NULL
    
{
        global 
$db;
                
        
$id NULL;/* Alustaa palautusarvon - jos ei onnistu, palauttaa NULL */
    
        
$query 'SELECT account_id FROM users WHERE (account_name = :name)';//    Hakee ID:tä
        
$values = array(':name' => $name);
        try
        {
            
$res $db->prepare($query);
            
$res->execute($values);
        }
        catch (
PDOException $e// jos tulee virhe, palauttaa standardi-ilmoituksen
        
{
           throw new 
Exception('Database query error');
        }
        
$row $res->fetch(PDO::FETCH_ASSOC);

        if (
is_array($row)) //         There is a result: get it's ID 
        
{
            
$id intval($row['account_id'], 10);
        }
        return 
$id;
    }
}

Lisätään UserController sekä reitit rekisteröitymislomaketta (get) ja sen lähettämistä (post) varten index.php-tiedostoon

Sisällytetään UserController.php ja luodaan olio $userController. HUom. $userControlleria luotaessa luodaan samalla $user (UserController.php sisällyttää User.php:n).

Lisätään reitti index.php-tiedostoon, koodissa näkyy lopputulos:

<?php
require './Route.php'// routing class
require './database/db.php'// database connection

require './controllers/StoryController.php';
$storyController = new StoryController();

require 
'./controllers/UserController.php';
$userController = new UserController();


Route::add('/',function() {
    global 
$storyController;
    
$storyController->index();    
},
'get');


Route::add('/register/',function() {
    require 
'./views/registerform.view.php';
},
'get');

Route::add('/register/',function() use ($userController) {
    
$userController->register();
},
'post');


//start...
Route::run('/');
?>

Aloitetaan ./controllers/UserController.php

UserController-luokan tietojäsen on suojattu $user.

Konstruktori luo uuden $user:in.

Metodi register() tarkistaa syötteet ja kutsuu User-luokan addAccount-metodia, joka lisää käyttäjän kantaan ja palauttaa käyttäjän id:n. Sen jälkeen register-metodi kutsuu registered.view-näkymää, jossa id näkyy.

Jos kaikki syötteet eivät ole kunnossa, register-metodi kutsuu uudestaan lomaketta ja lähettää mukana viestin ($message). Jos tietokantaan lisääminen ei onnistu, registered.view-näkymään tulee viesti ($message), jossa on virheilmoitus.

<?php

require './database/models/User.php'// user management for authentication

class UserController
{
    protected 
$user;
    
    public function 
__construct()
    {
        
$this->user = new User();
    }

    public function 
register()
    {
        require 
"./helpers/helper.php";

        if(isset(
$_POST["account_name"],$_POST["password1"],$_POST["password2"],$_POST["last_name"],$_POST["first_name"]) && $_POST["password1"]==$_POST["password2"])
        {
            
$password=sanitize($_POST["password1"]);
            
$account_name=$_POST["account_name"];
            
$last_namesanitize($_POST["last_name"]);
            
$first_namesanitize($_POST["first_name"]);
    
            
$password password_hash($passwordPASSWORD_DEFAULT);
    
            
$newAccount=$this->user->addAccount($last_name,$first_name,$account_name,$password);
            require 
'./views/registered.view.php';
        } else {
            
$message ="Tarkista salasanat";
            require 
'./views/registerform.view.php';
        }
    }
}

./helpers/helper.php

helpers.php sisältää kolme tarkistufunktiota:

<?php
//käytä AINA PDO ja prepare -yhdistelmää, estää SQL-injektiot

function sanitize($data) {
  
$data trim($data); //tyhjät pois
  
$data htmlentities($dataENT_QUOTES'UTF-8'); //html-tagit merkkijonoiksi
  
return $data;
}


/*vaihtoehto toki filter_var
function sanitize($data) {
    $data = trim($data);
    $data = filter_var($data,FILTER_SANITIZE_STRING));
    return $data;
}
*/

//boolean, palauttaa 1, jos päiväys on todellinen
function isRealDate($date) { 
    if (
false === strtotime($date)) { 
        return 
false;
    } 
    list(
$year$month$day) = explode('-'$date); 
    return 
checkdate($month$day$year);
}

//ja jos päiväys on todellinen, tarkistaa, että se on tämän päivän jälkeen
function isValidDate($hidedate)
{
    if (
isRealDate($hidedate)) {
        
$hidedate =strtotime($hidedate);
        if(
$hidedate time()) return TRUE;
        else return 
FALSE;
    }
    else {
        return 
FALSE;
    }
}

./views/registerform.view.php

Tässä action tulee määritellä oikean polun päähän. Esimerkin actionin sovellus sijaitsee web-palvelun juurikansiossa.

<?php
require './views/partials/head.php';
if(isset(
$warning)) echo $warning;
?>

<form method="post" action = "/register/">
    
    <p>
    <label for="first_name">Etunimi </label><br>
    <input type="text" name="first_name" 
    value="<?php if(isset($_POST["first_name"])) echo $_POST["first_name"];?>"
    required>
    </p>

    <p>
    <label for="last_name">Sukunimi </label><br>
    <input type="text" name="last_name" 
    value="<?php if(isset($_POST["last_name"])) echo $_POST["last_name"];?>"
    required>
    </p>

    <p>
    <label for="account_name">Käyttäjätunnus </label><br>
    <input type="text" name="account_name" 
    value="<?php if(isset($_POST["account_name"])) echo $_POST["account_name"];?>"
    required>
    </p>

    <p>
    <label for="password1">Salasana</label><br>
    <input type="password" name="password1"></p>

    <p><label for="password2">Salasana uudelleen </label><br>
    <input type="password" name="password2" required>
    </p>

    <p>
    <input class="button" type="submit" value="Rekisteröidy">
    </p>
    
    </form> 





<?php    
require './views/partials/end.php';
?>

./views/registered.view.php

<?php
require './views/partials/head.php';
?>

Rekisteröinti onnistui, uusi ID on 
<?= $newAccount ?>


<?php
require './views/partials/end.php';
?>


>> Tunti 8. Toteuta autentikointi (kirjaudu ja kirjaudu ulos) >>