A “like” alkalmazás előtt

Mit is kell tudjon egy tetszőleges oldalba beágyazható like alkalmazás? Tulajdonképpen esetünkben arról van szó, hogy egy tetszőleges oldalon a weblapról egy tetszésnyilvánítást rögzíthessünk illetve kijelezzük a tetszésnyilvánítások számát. Nyilvánvalóan adatbázisra lesz szükségünk a dinamikus működés biztosításához.

I. Először is két részre kell bontsuk a megvalósítást. Van egy számláló-kijelzés funkciónk, azaz hogy hány like volt eddig illetve egy “like” gomb, hogy a számláló értékét növelhessük. Továbbá beágyazhatóvá kell tennünk egy tetszőleges weboldalba a funkciót. Innentől like-widget-ről beszélhetünk (https://hu.wikipedia.org/wiki/Vez%C3%A9rl%C5%91elem).

II. Másodszor is hozzá kell kötnünk a like widget-ünket a megjelenés helyéhez. Ez a funkcionalitás a REFERER HTTP fejléc attribútum adatbázisban letárolásával és vizsgálatával oldható meg. Ez az attribútum a hivatkozott oldal címét tartalmazza (https://hu.wikipedia.org/wiki/Referer). Innen fogjuk tudni, hogy melyik a “hívó” webcím.

III. Harmadszor pedig meg kell vizsgálnunk, hogy a like funkciót felhasználóhoz vagy böngésző példányhoz kössük-e nyilvánvalóan azért, hogy ne lehessen egy személynek többször like-olnia ugyanazon a helyen. Ha felhasználóhoz kötjük, úgy a facebook stílusú megoldást kapjuk. Ez nekünk azonban túl bonyolult volna, így egyszerűen a böngészőben fogunk letárolni egy cookie-t, hogy az adott kliensen már történt egy like-olás azaz egy tetszésnyilvánítás adott webcímre.

A tervezési megfontolások nagyjából ennyit tesznek ki. Még foglalkozhatnánk a színes-szagos külcsínyről is, de azt most elhanyagoljuk. Egyszerűen egy félkövér szám lesz a like-ok száma, alatta pedig sortöréssel egy “like” feliratú gomb. Ha nem “szürke” azaz “disabled”, akkor adott kliens még nem like-olt, azaz lehet like-olni. Ennyi. 🙂

Jelszóvédelem az admin oldalra, avagy az authentikáció egyszerű megoldása

Az adminisztrátori vagyis foglalás-menedzsment funkcionalitás eleve rejtve van a kíváncsi tekintetek elől, hiszen ha kint is van az Interneten, vagyis a “webtérben” az oldal, link nincs az admin oldalunkra a honlapon. Azonban ennél azért nagyobb biztonságra lesz szükségünk, ezért az egész admin funkcionalitást el kell rejtsük egy jelszóval védett területre.

A jelszavas védelmet a CodeIgniter keretrendszer alapban nem támogatja, viszont egyszerűen megvalósíthatjuk. Egyszerűen úgy működik, hogy a controller-ben a kívánt funkciók hívása előtt megvizsgáljuk, hogy előzőleg tároltunk-e el a szerver session-jében “user” változót a korábban lezajló login folyamat során, amelyben adatbázisban ellenőrizzük a felhasználó/jelszó párt. Van benne még technikailag pár átirányítás, hogy a webalkalmazásunk “visszataláljon” a “hívó” oldalra (referer) a sikeres jelszó hitelesítés (authentikáció) vagyis belépés (login) után.

És akkor most nézzük a konkrét implementációt.

Először is kell egy modell. Ez az models/Auth_model.php file-unk. Összezsúfoltam picit a kódot – nem szokásom amúgy -, hogy minél kevesebb sorban oldjam meg a feladatot. 🙂

<?php
class Auth_model extends CI_Model {
 public function login(){
  $query = $this->db->get_where('users', array('name' => $this->input->post('username'), 'password' => $this->input->post('password'))); 
  $result = $query->result();
  return (empty($result)?false:(empty($result[0])?false:true));
 }
}

Ez a login() metódus egy boolean – logikai – értékkel tér vissza attól függően, hogy megvan-e az adatbázisban a felhasználó/jelszó páros. Pár technikai észrevétel: a felhasználónévnek egyedinek kell lenni (ezt egy ún. unique key vagyis egyedi kulcs bitosítja SQL technikában), hogy nyilvánvalóan különbözzenek a felhasználóink. A gyakorlatban általában az e-mail címeket választják ki a mérnökök erre a célra, hiszen az mindenképpen egyedi. 🙂 Aztán a jelszó. Nem kódoltam le ebben a pilot alkalmazásban, de illik valamilyen egyirányú kódoló algoritmussal titkosítani, hogy senki se fejthesse vissza a jelszavakat az adatbázis ismeretében. 🙂

Nézzük a meg views/login.php view-nkat.

<?php
        $my_base_url = '/apartman/';
?>
                                <div class="row">
                                        <div class="col-sm-2">
                                                <div id="fh5co-logo"><a href="index.html">Apartman<span>.</span></a></div>
                                        </div>
                                        <div class="col-sm-10 text-right menu-1">
                                                <ul>
                                                        <li><a href="photos"><?php echo $this->lang->line('menu_photos'); ?></a></li>
                                                        <li><a href="booking"><?php echo $this->lang->line('menu_booking'); ?></a></li>
                                                        <li><a href="contact"><?php echo $this->lang->line('menu_contact'); ?></a></li>
                                                </ul>
                                        </div>
                                </div>
                                
                        </div>
                </div>
        </nav>
        <div class="container">
                <div id="fh5co-intro">
                        <div class="row animate-box">
                                <div class="col-md-8 col-md-offset-2 col-md-pull-2">
                                        <h2><?php echo $this->lang->line('menu_booking'); ?></h2>
                                </div>
                        </div>
                </div>
                <div id="fh5co-contact">
                        <div class="row">
                                <div class="col-md-8 animate-box">
                                        <div class="form-wrap">
                                                <div class="row">
                                                        <form action="login" method="POST">
                                                        <?php if ($auth_errors) { ?>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <h3><font color="red"><?php echo $auth_errors; ?></font></h3>
                                                                </div>
                                                        </div>
                                                        <?php } ?>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input type="text" name="username" class="form-control" placeholder="User Name">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input type="password" name="password" class="form-control" placeholder="Password">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input type="submit" value="Login" class="btn btn-primary btn-modify">
                                                                </div>
                                                        </div>
                                                        </form>
                                                </div>
                                        </div>
                                </div>
                        </div>
                </div>
        </div><!-- END container -->

Ebben érdekes, hogy egy sima formot használunk, nem JavaScript-eset, és hogy password típusú mezőbe kérjük be a jelszót, amit nem lehet elolvasni a képernyőn, miközben begépeljük, csak pontokat látni a karakterek helyén. Ez a biztonságot szolgálja. Más kérdés, hogy a billentyűzetről is le tudja olvasni egy ügyes tekintetű illető a jelszót, úgyhogy ilyenkor illik “nem oda nézni” a harmadik félnek. 🙂

Majd nézzük a szintén nem túl bonyolult controller-ünket (controllers/AuthController.php). Megjegyzem, hogy végre beállítottam a config/config.php => $config['base_url'] = 'http://localhost/apartman/'; értéket, hogy használhassam az a base_url($path) metódust a redirect-ek (átirányítások) miatt. 🙂

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class AuthController extends CI_Controller {
  public function __construct(){
    parent::__construct();
  }
  public function is_logged($referer){
    $user = $this->session->userdata('user');
    if (empty($user)) {
      // not authenticated
      $this->session->set_userdata('login_url', $referer);
      redirect(base_url('index.php/AuthController/login_form'));
    }
  }
  public function login_form(){
    $data = array();

    $data['auth_errors'] = '';
    if (!empty($this->session->flashdata('auth_errors'))) {
      $data['auth_errors'] = $this->session->flashdata('auth_errors');
    }

    $this->template->set('title', 'Login');
    $this->template->load('menu_layout', 'contents', 'login', $data);
  }
  public function login(){
   $this->load->model('auth_model');

   $success = $this->auth_model->login(); 

   if ($success) {
     $this->session->set_userdata('user', $this->input->post('username'));
     redirect($this->session->userdata('login_url'));
   } else {
     // login fail
     $this->session->set_flashdata('auth_errors', 'Login failed.');
     redirect($_SERVER['HTTP_REFERER']);
   }
  }
  public function logout(){
    $this->session->set_userdata('login_url', null);
    $this->session->set_userdata('user', null);
  }
}

Látható, hogy három fontos metódust tartamaz. Az is_logged($referer) gondoskodik arról, hogy megvizsgáljuk, van-e belépett felhasználó (user) a session-ben. A login_form() hibaüzenetek kezelését, illetve a login form kirajzolását végzi, míg a login() a tulajdonképpeni beléptetést a modellhívással. A logout() csak játék, technikailag vettem fel. Egyszerűen töröl minden session-ban tárolt változót.

Ezt a controller-t az alkalmazásunk fő vezérlőjében (controllers/Apartments.php) az alábbi módon használjuk.

<?php
include APPPATH.'controllers/AuthController.php';
class Apartments extends AuthController {

        public function admin() {

                // check if user is authenticated
                // config/config.php => $config['base_url'] = 'http://localhost/apartman/';
                $this->is_logged(base_url('index.php/apartments/admin'));
                
                $this->load->model('reservations_model');

                $data['query'] = $this->reservations_model->get_reservations();

                $this->template->set('title', 'Admin');
                $this->template->load('menu_layout', 'contents' , 'admin', $data);
        }

        public function admin_approve_ajax($id) {

                // check if user is authenticated
                $this->is_logged(base_url('index.php/apartments/admin'));

                $this->load->model('reservations_model');

                $this->reservations_model->approve_reservation($id);
                $data = $this->reservations_model->get_reservation($id)[0];

                header('Content-Type: application/json');
                echo json_encode($data);
        }

}

Látható, hogy az AuthController-t include-oljuk a kód elején, majd extends-szel alaposztályként hivatkozunk rá. Innentől egy is_logged($referer) hívással mindkét adminisztrátori metódus legelején eldöntjük, hogy “be van-e lépve” az adminisztrátor. Ha nem, akkor átirányítjuk a login form-ra, ha igen, megy tovább a buli. 🙂

És akkor nézzük a login form-unkat! A http://localhost/apartman/index.php/AuthController/login_form webcím alatt látható, ha az admin oldalt szeretnénk megnyitni, és a session változó már elévült a webszerverünkön vagy netán kipróbáltuk a logout() funkciót. 🙂

Többnyelvűség implementálása: Magyar, English, Deutsch

Szeretnénk, ha az oldalunk több nyelven is tudna, mivel lehetnek az apartmannak külföldi vendégei is.

A CodeIgniter ezt alapból jól megtámogatja úgynevezett nyelvi file-okkal, illetve a hook funkcióval, nézzünk a megvalósításra egy egyszerű utat. Az alábbi webhelyen elég jó útmutatást kapunk, csupán egy picit változtatunk az implementáción.

http://programmerblog.net/multilingual-website-in-codeigniter/

Először hozzunk létre nyelvi könyvtárakat. Nálunk az alapértelmezett nyelv a magyar lesz (angol, “english” ugye már alapból van).

gvamosi@gergo1:/var/www/html/apartman/application$ mkdir language/magyar
gvamosi@gergo1:/var/www/html/apartman/application$ mkdir language/deutsch

Az language/magyar/base_lang.php file tartalma a követező legyen.

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

$lang['menu_photos'] = 'Képek';
$lang['menu_booking'] = 'Foglalás';
$lang['menu_contact'] = 'Kapcsolat';
?>

Erre a mintára készüljön az angol nyelvű nyelvi file is: language/english/base_lang.php.

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

$lang['menu_photos'] = 'Photos';
$lang['menu_booking'] = 'Booking';
$lang['menu_contact'] = 'Contact';
?>

Majd a német is: language/deutsch/base_lang.php. 🙂

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

$lang['menu_photos'] = 'Fotos';
$lang['menu_booking'] = 'Reservieren';
$lang['menu_contact'] = 'Kontakt';
?>

A config/config.php-ban engedélyeznünk kell a hook funkciót.

$config['enable_hooks'] = TRUE;

Meg kell írnunk a hook-unkat a config/hooks.php file-ban.

$hook['post_controller_constructor'] = array(
	'class' => 'LanguageLoader',
	'function' => 'initialize',
	'filename' => 'LanguageLoader.php',
	'filepath' => 'hooks'
);

Az application könyvtár alatt létre kell hozzuk a hooks/LanguageLoader.php osztályt.

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class LanguageLoader
{
   function initialize() {
       $ci =& get_instance();
       $ci->load->helper('language');
       $siteLang = $ci->session->userdata('site_lang');
       if ($siteLang) {
           $ci->lang->load('base', $siteLang);
       } else {
           $ci->lang->load('base', 'magyar');
       }
   }
}
?>

Ezután létre kell hoznunk egy controller-t történetesen controllers/LanguageSwitcher.php néven.

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class LanguageSwitcher extends CI_Controller {
  public function __construct(){
    parent::__construct();
  }
  public function switchLang($language = "") {
    $this->session->set_userdata('site_lang', $language);
    redirect($_SERVER['HTTP_REFERER']);
  }
}

Némi magyarázatot fűznék a fenti két kódblokkhoz. A “session” magyar fordítáa munkamenet szokott lenni. Ha itt beállítunk egy változó értéket, akkor kliens oldalon a browser-ben egy weblaphoz kötött változót, egy ún. cookie-t, magyarul sütit tárol el a szerver alkalmazás ilyenkor. A szerver gép pedig az adott süti által meghatározott böngésző munkamenethez egy session változót regisztrál, esetünkben az oldal nyelvét, vagyis hogy magyar, angol vagy német nyelven kívánjuk olvasni az weblapot. Nagyon szépen meg lehet ezt nézni pl. a Chrome böngészőben a Developer Tools-ban az Application / Storage / Cookies rész alatt. Ott találunk egy localhost-tól bejegyzett sütit, ci_session néven, egy egyedi azonosítóval, egy ID-vel. A webszerverünkön a session, vagyis a HTTP SESSION egy idő után, ha nem történik újabb kommunikáció a weboldalon a szerver és a kliens között, elévül és törlődik. Milyen elmés! 🙂

Magyarázzuk meg még a “HTTP_REFERER” HTTP header, magyarul fejléc változó használatát. A HTTP kérés és válasz tartalmaz egy fejlécet és egy törzset (body), mint minden normális IP üzenet. Ez a fejléc érték azt az url-t tartalmazza, ahonnan az adott url-t “hívtuk”. Ebben a megoldásban, mivel bármely oldalon állíthatjuk a nyelvet, a hívó oldalra visszairányítjuk automatikusan a klienst, miután a /LanguageSwitcher/switchLang/<NYELV> url segítségével a megfelelő controller-ben átállítottuk a nyelvet a session-ben. 🙂

Bővebben az előbbiekről a https://hu.wikipedia.org/wiki/HTTP-s%C3%BCti és a https://hu.wikipedia.org/wiki/Referer címeken olvashatunk.

Ezután nem marad más hátra, mint “bedrótozni” egy menüstruktúrát a sablonfile-unkban, illetve a $this->lang->line('message') hívással előszedni a megfelelő sztringeket a nyelvi file-okból. Nézzük az új navigációt.

        <nav class="fh5co-nav" role="navigation">
                <div class="container">
                        <div class="top-menu">
                                <div class="row">
                                        <div class="col-sm-10 text-right menu-1">
                                                <ul>
                                                        <li><a style="font-size: 14px; padding: 5px 5px;" href="<?php echo $my_base_url?>index.php/LanguageSwitcher/switchLang/magyar">Magyar</a></li>
                                                        <li><a style="font-size: 14px; padding: 5px 5px;" href="<?php echo $my_base_url?>index.php/LanguageSwitcher/switchLang/english">English</a></li>
                                                        <li><a style="font-size: 14px; padding: 5px 5px;" href="<?php echo $my_base_url?>index.php/LanguageSwitcher/switchLang/deutsch">Deutsch</a></li>
                                                </ul>
                                        </div>
                                </div>
                                <div class="row">
                                         
                                </div>
                                <div class="row">
                                        <div class="col-sm-2">
                                                <div id="fh5co-logo"><a href="index.html">Apartman<span>.</span></a></div>
                                        </div>
                                        <div class="col-sm-10 text-right menu-1">
                                                <ul>
                                                        <li class="active"><a href="index.html"><?php echo $this->lang->line('menu_photos'); ?></a></li>
                                                        <li><a href="single.html"><?php echo $this->lang->line('menu_booking'); ?></a></li>
                                                        <li><a href="contact.html"><?php echo $this->lang->line('menu_contact'); ?></a></li>
                                                </ul>
                                        </div>
                                </div>
                                
                        </div>
                </div>
        </nav>

Kész a többnyelvű oldalunk! 🙂