A “like” gomb működése és a cookie-k

Nézzük a két részletet, amelyek az előző részből kimaradtak. Az első rész a számláló növeléséért felelős. Ezt egy sima adatbázis UPDATE végzi a DBI interfészen keresztül. Fontos, hogy a finish() metódussal felszabadítsuk az utasítást, különben “lógna” a levegőben azaz a memóriában. A cookie-zás (sütizés) az egy kliensből történő többszöri like-olás megakadályozását szolgálja és az üzenetfejlécben állítjuk illetve kérdezzük le, mégis teszt célokból jelen kódban ki van “ütve”.

 my $cookie = $r->headers_in->get('Cookie');
 my $uri = URI::Encode->new( { encode_reserved => 1 } );
 my $uri_encoded_referer = $uri->encode($http_referer);
 #print $uri_encoded_referer;
 my $wasliked = $cookie ne '' && $cookie =~ /$uri_encoded_referer/;
 if ($like && ! $wasliked ) {
  # increment counter
  my $sthupd = $dbh->prepare('UPDATE mylike SET counter = counter + 1 WHERE approved = 1 and referer = ?');
  $sthupd->execute($http_referer);
  $sthupd->finish();

  my $cookie = CGI::Cookie->new(-name  => 'mylike'.$http_referer,
                                -value => 'SET');
  $r->headers_out->set('Set-Cookie' => $cookie);
  $wasliked = 1;

  # !! TEST ONLY !! temporary turn off cookie-mechanism !! COMMMENT OUT FOLLOWING TWO LINES FOR USE !!
  $r->headers_out->set('Set-Cookie' => '');
  $wasliked = 0;
 }

A like változó értékadása az előző részben szerepel, a GET metódus révén átadott paraméterként kerül kiértékelésre. Az alábbi gomb onclick javascript eseményéből kerül meghívásra like=1 URI változóként.

my $req = CGI->new($r);
my $like = $req->param('like');

A második kimaradt részecske maga a “like” gomb megjelenítéséért felelős. Ha már like-oltuk az oldalt, akkor disabled a gomb, azaz nem lehet többször megnyomni. 🙂

 print "<input type=\"button\" onclick=\"\$('#mylike').load('http://localhost/perl/mylike-dbi.cgi?like=1');\" value=\"like\" ".($wasliked?'disabled ':'')."/>\n";

És akkor nézzük meg a kimenetet!

Ennyi! That’s all! 🙂

Megjegyzés: azért a felhasznált javascript miatt ügyelni kell arra, hogy a site-unk include-olja script forrásként a jquery lib-eket.

Az utolsó részben megnézzük, hogyan lehet letesztelni illetve a teherbírását megmérni a like modulunknak.

A like-számlálójának működése, a REFERER a HTTP fejlécből

Minden további leírás helyett ideollózom a mylike-dbi.cgi azon részeit, amelyek a like-ok számának megjelenítéséért felelősek, illetve amelyek az első bejegyzés létrehozására alkalmasak.

#!/usr/bin/perl

use strict;
use warnings;
use DBI;
use Apache2::RequestRec ();
use Apache2::RequestUtil ();
use CGI;
use CGI::Cookie;
use URI::Encode;


my $r = Apache2::RequestUtil->request;

my $req = CGI->new($r);
my $like = $req->param('like');
#print $like;
my $http_referer = $ENV{'HTTP_REFERER'};

my $dbh = DBI->connect('DBI:mysql:mylike', 'mylike', 'mylike1'
                   ) || die "Could not connect to database: $DBI::errstr";

if ($http_referer) {

 # LIKE BUTTON CODE WAS HERE ##################

 my $sth = $dbh->prepare('SELECT id FROM mylike WHERE approved = 1 and referer = ?');
 $sth->execute($http_referer);
 my @result = $sth->fetchrow_array();
 my $len = @result;
 #print "numof result $len\n";
 $sth->finish();

 if ($len == 0) {
  # insert data into the links table
  my $sql = "INSERT INTO mylike (referer) VALUES(?)";

  my $stmt = $dbh->prepare($sql);

  # execute the query
  if($stmt->execute($http_referer)) {
   # ok
   #print "Mylike $http_referer inserted successfully";
  }

  $stmt->finish();
 }

 $sth = $dbh->prepare('SELECT counter FROM mylike WHERE approved = 1 and referer = ?');
 $sth->execute($http_referer);
 @result = $sth->fetchrow_array();

 print "$result[0] like(s)<br>\n";

 # LIKE BUTTON CODE WAS HERE ##################

 $sth->finish();
 
} else {
 # test

 my $results = $dbh->selectall_hashref('SELECT * FROM mylike', 'id');
 foreach my $id (keys %$results) {
  print "!!TEST!! ID $id: referer is $results->{$id}->{'referer'}, counter is $results->{$id}->{'counter'}\n";
 }

}

$dbh->disconnect();

A kód magyarázata.

Kivesszük a fejlécből a HTTP_REFERER-t. Ha ez definiált, akkor “éles” működés, ha nem akkor tesztüzemmód. Tesztüzemben az alábbi kimenetet kapjuk.

Látható, hogy két REFERER szerepel az adatbázisunkban. Ezeket sorolja fel tesztüzemmódban a script.

Éles üzemnél megvizsgálja a programunk, hogy létezik-e adatbázis-bejegyzés adott REFERER-rel. Ha még nincs, létrehoz egyet 0-ás like-oltsággal. 🙂 Ezután egy sima print utasítással kijelzi a kedvelések számát.

A következő részben megnézhetjük magának a “like” gombnak is a működését és a mögötte álló programkódot is. Ebben a forrásban ezt a

# LIKE BUTTON CODE WAS HERE ##################

részek jelölik.

A SELECT-jeinkben az approved = 1 az adminisztrátori funkció további lehetőségét hordozza magában. Tehát le lehet “tiltani” egy-egy beágyazást.

Látható a kód elején a use kulcsszavaknál, hogy használunk egynéhány modult. 🙂 Szinte gyerekjáték a Perl programozás.

Megjegyzem, hogy ez a like megvalósítás különböző webcímű (URI) weboldalanként ad lehetőséget like-olásra. Egy webcímen belül több like nem lehet. Nem a tökély a cél, csupán a megvalósíthatóság bemutatása.

A Hello World! mod_perl DBI alkalmazás

A /etc/apache2/conf-enabled/mod_perl.conf file-t az alábbiak szerint módosítottuk.

PerlModule ModPerl::Registry

Alias /perl /var/www/perl
<Directory /var/www/perl>
   AddHandler perl-script .cgi .pl
   PerlResponseHandler ModPerl::Registry
   Options +ExecCGI
</Directory>

Lehet saját handler-t is írni, de most enm tértünk ki erre a lehetőségre. 🙂

Nézzük a hello-world-dbi.cgi programocskánkat majd a beágyazását az apartman “Képek” oldalába.

#!/usr/bin/perl

use strict;
use warnings;
use DBI;
use Apache2::RequestRec ();
use Apache2::RequestUtil ();

my $r = Apache2::RequestUtil->request;

print "The referer in your HTTP-Header is: ", $ENV{'HTTP_REFERER'}, "\n";

my $dbh = DBI->connect('DBI:mysql:mylike', 'mylike', 'mylike1'
               ) || die "Could not connect to database: $DBI::errstr";

print "<br>\n";

my $results = $dbh->selectall_hashref('SELECT * FROM mylike', 'id');
foreach my $id (keys %$results) {
 print "Value of ID $id is $results->{$id}->{'referer'}\n";
}

$dbh->disconnect();

Használjuk a DBI és az új Apache2-es Request modulokat, majd lekérdezzük a HTTP_REFERER-t illetve egy select-tel belefetchelünk az adatbázisba. 🙂

Figyelem! Nagyon fontos a helyes beállítás az apache config oldalon.

A kis appunkat beágyazzuk mint widget-et az alábbi módon.

<div id="mylike"></div>
<script>$("#mylike").load("http://localhost/perl/hello-world-dbi.cgi</script>

És akkor lássuk az eredményt.

MySQL adatbázis és a Perl DBI és DBD::mysql modulok

Az adatbázis egyszerűen egyetlen egy táblából áll. Nézzük az SQL DDL kódot!

CREATE TABLE mylike (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
referer VARCHAR(200) NOT NULL,
counter DECIMAL(9) NOT NULL DEFAULT 0,
approved TINYINT NOT NULL DEFAULT 1,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

Pár különleges megjelölést használunk a séma létrehozásakor. Alapértékeket állítunk be a DEFAULT kódszóval, illetve AUTO_INCREMENT-tet használunk, tehát automatikus id generálást, sőt: UPDATE esetén az updated mező értéke az aktuális időpőponttal automatikusan frissül!

Parancssorból a mysql paranccsal “játszhatjuk be” az új adatbázisba új felhasználóval a DDL kódrészletet. Az adatbázis és a felhasználó létrehozása az alábbi módon történik, root-kéni futtatva a mysql parancsokat. Sima copy’n’paste. 🙂

CREATE DATABASE mylike CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE USER 'mylike'@'localhost' IDENTIFIED BY 'mylike1';
GRANT ALL PRIVILEGES ON mylike.* TO 'mylike'@'localhost';
FLUSH PRIVILEGES;

Szükség esetén a “use mylike” paranccsal jelöljük ki a használt adatbázist, amikor a táblát hozzuk létre. 🙂

Teszteljük le a létrehozott adatbázis táblánkat!

mysql> insert into mylike (referer) values ('http://localhost/valami');
Query OK, 1 row affected (0.01 sec)
mysql> select * from mylike;
+----+-------------------------+---------+----------+---------------------+---------------------+
| id | referer | counter | approved | created | updated |
+----+-------------------------+---------+----------+---------------------+---------------------+
| 1 | http://localhost/valami | 0 | 1 | 2021-01-06 15:00:35 | 2021-01-06 15:00:35 |
+----+-------------------------+---------+----------+---------------------+---------------------+
1 row in set (0.01 sec)
mysql>

A Perl megfelelő moduljait az adatbázis-eléréshez installálni szükséges. Ezek a Perl Database Interface illetve a megfelelő Perl DBI Database Driver (https://metacpan.org/pod/DBI::DBD) Ezt a Debian 10.7-ünk alatt az alábbi módon tehetjük meg.

root@gergo1:~# apt-get install libdbi-perl libdbd-mysql-perl
Reading package lists… Done
Building dependency tree
Reading state information… Done
The following packages were automatically installed and are no longer required:
libappindicator3-1 libindicator3-7
Use 'apt autoremove' to remove them.
Suggested packages:
libclone-perl libmldbm-perl libnet-daemon-perl libsql-statement-perl
The following NEW packages will be installed:
libdbd-mysql-perl libdbi-perl
0 upgraded, 2 newly installed, 0 to remove and 4 not upgraded.
Need to get 896 kB of archives.
After this operation, 2,464 kB of additional disk space will be used.
Get:1 http://httpredir.debian.org/debian buster/main amd64 libdbi-perl amd64 1.642-1+deb10u1 [775 kB]
Get:2 http://httpredir.debian.org/debian buster/main amd64 libdbd-mysql-perl amd64 4.050-2 [121 kB]
Fetched 896 kB in 1s (1,535 kB/s)
Selecting previously unselected package libdbi-perl:amd64.
(Reading database … 533195 files and directories currently installed.)
Preparing to unpack …/libdbi-perl_1.642-1+deb10u1_amd64.deb …
Unpacking libdbi-perl:amd64 (1.642-1+deb10u1) …
Selecting previously unselected package libdbd-mysql-perl:amd64.
Preparing to unpack …/libdbd-mysql-perl_4.050-2_amd64.deb …
Unpacking libdbd-mysql-perl:amd64 (4.050-2) …
Setting up libdbi-perl:amd64 (1.642-1+deb10u1) …
Setting up libdbd-mysql-perl:amd64 (4.050-2) …
Processing triggers for man-db (2.8.5-2) …
root@gergo1:~#

Megjegyzés: installálhatjuk a szükséges modulokat a Perl CPAN modulkezelőjén belül is a cpan paranccsal a konzolról, de akkor elveszítjuk a rendszerhez szállított előrecsomagolt szoftverek előnyeit. Különleges csomagokhoz valóban hasznos kiegészítő lehet a Perl CPAN modulja (https://www.cpan.org/).

A következő bejegyzésben megnézzük hogyan lehet mod_perl-ben adatbázist elérni web-ről egy Perl Hello World! kóddal. 🙂

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. 🙂

Admin funkció, foglalás jóváhagyása, AJAX JSON :)

Most már, hogy egy űrlapon keresztül foglalásokat lehet elküldeni, szükségünk lesz egy admin oldalra, ahol az apartman ügyintézője meg tudja tekinteni, illetve jóvá tudja hagyni a foglalásokat.

Hogy mit jelent itt az hogy AJAX JSON? Elég érdekes öszvér elnevezés. 🙂 AJAX-szal normál esetben egy XML adatstruktúrát vinnénk át az aszinkron javascript üzenetben, ami valahogy így nézne ki egy konkrét foglalás (reservation) adataira.

<xml>
<reservation>
<apartment_id>1</apartment_id>
<approved>1</approved>
..
</reservation>
</xml>

Mégis azonban az ún. JSON formátumot használjuk, ami egy konkrét esetben az alábbi szerverválaszt adja.

{
apartment_id: "1",
approved: "1",
checkin: "2020-07-29",
checkout: "2020-08-01",
created_at: "2020-08-03 03:34:11",
email: "aaa@aaa.hu",
id: "10",
locale: null,
message: "aaa",
name: "aaa",
phone: "",
updated_at: "2020-08-05 01:08:08"
}

Látható, hogy egy eléggé egyszerű adatszerkezet. A JSON leírása részletesen az alábbi címen érhető el: https://hu.wikipedia.org/wiki/JSON.

És akkor nézzük meg a konkrét implementációt. Először is bővítenünk kell a models/Reservations_model.php modellünket három metódussal.

        public function get_reservations() {
                $this->db->select()->from('reservations')->order_by('created_at', 'DESC');
                $query = $this->db->get();
                return $query->result();
        }

        public function get_reservation($id) {
                $query = $this->db->get_where('reservations', array('id' => $id));
                return $query->result();
        }

        public function approve_reservation($id) {
                $date = date('Y-m-d H:i:s');
                $this->db->set('updated_at', $date);
                $this->db->set('approved', 1);
                $this->db->where('id', $id);
                $this->db->update('reservations');
        }

Az első get_reservations() metódus a foglalások keletkezésének idejével csökkenő sorrendben egy listát ad vissza a foglalások adataival.

A második get_reservation($id) metódus egy konkrét foglalás adatait adja vissza.

A harmadik approve_reservation($id) metódus pedig UPDATE-eli, tehát frissíti a megadott ID-jű foglalás rekordunkat a frissítés dátumával és beállítja az jóváhagyott foglalás értékét 1-esre, ami esetünkben a jóváhagyás tényét jelzi adatbázis szinten.

Nézzük a views/admin.php view-nkat, hogy mi is kerül bele.

<?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_ajax"><?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>
<table border="1">
<tr>
<th>Name</th><th>Email</th><th>Message</th><th>Checkin</th><th>Checkout</th><th>Created at</th><th>Updated at</th><th>Locale</th><th>Approved</th><th></th>
</tr>
<?php foreach ($query as $item):?>
<tr>
<form>
<td><?php echo $item->name;?></td>
<td><?php echo $item->email;?></td>
<td><?php echo $item->message;?></td>
<td><?php echo $item->checkin;?></td>
<td><?php echo $item->checkout;?></td>
<td><?php echo $item->created_at;?></td>
<td id="updated<?php echo $item->id;?>"><?php echo $item->updated_at;?></td>
<td><?php echo $item->locale;?></td>
<td id="approved<?php echo $item->id;?>"><?php echo $item->approved==1?'true':'false';?></td>
<td><input id="approve_button<?php echo $item->id;?>" type="button" value="Approve" class="btn btn-primary btn-modify" <?php echo $item->approved==1?'disabled':'';?>></td>
</form>
<script>
    $(document).ready(function(){
        $( "#approve_button<?php echo $item->id;?>" ).click(function(event)
        {
            $.ajax(
                {
                    type:"post",
                    url: "<?php echo $my_base_url; ?>index.php/apartments/admin_approve_ajax/<?php echo $item->id;?>",
                    data:{ },
                    contentType: "application/json",
                    dataType:"json", 
                    success:function(response)
                    {
                        console.log(response);
                        var obj = $.parseJSON(JSON.stringify(response));
                        $("#updated<?php echo $item->id;?>").html(obj.updated_at);
                        $('#approved<?php echo $item->id;?>').html(obj.approved==1?'true':'false');
                        $('#approve_button<?php echo $item->id;?>').prop('disabled', obj.approved==1?true:false);
                    },
                    error: function(response)
                    {
                        console.log(response);
                        alert("Invalide id=<?php echo $item->id;?>!");
                    }
                }
            );
        });
    });
</script>
</tr>
<?php endforeach;?>
</table>
                                </div>
                        </div>
                </div>
        </div><!-- END container -->

Látható, hogy ebbe egy táblázat került a foglalások adataival, melynek minden sora egy-egy foglalást tartalmaz. Erről az iterációról a <?php foreach ($query as $item):?><?php endforeach;?> blokk gondoskodik. Minden sort a foglalás ID-je azonosít. Minden sorhoz kerül egy-egy JavaScript blokk is a megfelelő Approve (Jóváhagy) gomb megfelelő “clickeseményének lekezelésével.

Felhívnám a figyelmet a JSON üzenet dekódolására. Ez a var obj = $.parseJSON(JSON.stringify(response)); sorban történik. Először szükséges sztringgé alakítani a válaszüzenetet, mivel az objektum-ként érkezik. Ez a JSON.stringify metódussal történik. Utána csak egy sima parse-olásra lesz szükségünk. 🙂

Nézzük meg végül a controllers/Apartments.php controller-ünket. Két új metódussal bővül. Az admin() metódusban csak kirajzoljuk az oldalunkat a modell réteg lista SQL SELECT metódusának meghívásával. Az admin_approve_ajax($id) metódusban pedig SQL UPDATE hívás majd egy SQL SELECT hívás történik a modell rétegben, illetve a válasz üzenet JSON becsomagolása.

        public function 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) {

                $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 viszonylag egyszerűen megoldottuk az egész adminisztrátori funkcionalitást az AJAX form vezérlés / adatátvitel és a JSON adatformátum hsználatával. 🙂

És akkor lássuk az admin oldalt (http://localhost/apartman/index.php/apartments/admin), egy jóváhagyott foglalással!

Látható, hogy a legfelső, az Approved true, tehát jóváhagyott rendelés – mivel csak angol nyelven implementáltam mindent az egyszerűség kedvéért – melletti Approve gomb már kiszürkült, nem megnyomható újra. Az Updated at – frissítés dátuma – pedig a késői vagy kora hajnali órát jelzi, amikor elkészültem a pilot kódokkal. 🙂

jQuery AJAX – form elküldése a “háttérben” – foglalás oldal újratöltve

Az AJAX, azaz Asynchronous JavaScript and XML arra szolgál, hogy ne kelljen az egész weblapot újratölteni a form elküldésekor, hanem csak az adatokat, mintegy csendben a “háttérben” elküldeni, majd valami <div> tag-ben megjeleníteni a kívánt felhasználói üzeneteket. Részletesebb leírás az alábbi címen található https://hu.wikipedia.org/wiki/Ajax_(programoz%C3%A1s).

Nem nagyobb feladatra vállalkozunk, minthogy az előbbi foglalás-űrlap elküldésünket aszinkron JavaScript hívásra cseréljük.

Először pár apróság. A views/layouts/menu_layout.php file-ban előre kell vennünk a fejlécbe a JavaScript forrás beillesztéseket, hogy tudjuk használni a jQuery funkcionalitásait.

Aztán át kell “drótoznunk” a menüt pl. a views/photos.php file-ban.

<li><a href="booking_ajax"><?php echo $this->lang->line('menu_booking'); ?></a></li>

Nézzük a views/booking_ajax.php oldalunk tartalmát.

<?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 class="active"><a href="booking_ajax"><?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-4 animate-box">
                                        <h3>Contact Information</h3>
                                        <ul class="contact-info">
                                                <li><i class="icon-location4"></i>1117 Budapest, Irinyi J. utca 42.</li>
                                                <li><i class="icon-phone3"></i>+ 1235 2355 98</li>
                                                <li><i class="icon-location3"></i><a href="#">info@yoursite.com</a></li>
                                                <li><i class="icon-globe2"></i><a href="#">www.yoursite.com</a></li>
                                        </ul>
                                </div>
                                <div class="col-md-8 animate-box">
                                        <div class="form-wrap">
                                                <div class="row">
                                                        <form id="ajax_form" action="book_ajax" method="POST">
                                                        <div class="col-md-12" id="response_message" style="display: none;">
                                                                <div class="form-group" id="response_message_text">
                                                                        <!--<h3><font color="red">MESSAGE</font></h3>-->
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="name" type="text" name="name" class="form-control" placeholder="Name">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="email" type="text" name="email" class="form-control" placeholder="Email">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="phone" type="text" name="phone" class="form-control" placeholder="Phone">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <textarea id="message" name="message" class="form-control" id="" cols="30" rows="15" placeholder="Message"></textarea>
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="checkin" type="text" name="checkin" class="form-control" placeholder="Check-in">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="checkout" type="text" name="checkout" class="form-control" placeholder="Check-out">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="submit_button" type="button" value="Book" class="btn btn-primary btn-modify">
                                                                </div>
                                                        </div>
                                                        </form>
                                                </div>
                                        </div>
                                </div>
                        </div>
                </div>
        </div><!-- END container -->
<script>
    $(document).ready(function(){
        $( "#submit_button" ).click(function(event)
        {
            event.preventDefault();
            var name = $("#name").val();
            var email = $("#email").val();
            var phone = $("#phone").val();
            var message = $("#message").val();
            var checkin = $("#checkin").val();
            var checkout = $("#checkout").val();
            var data = $("#ajax_form").serialize();

            $.ajax(
                {
                    type:"post",
                    url: "<?php echo $my_base_url; ?>index.php/apartments/book_ajax",
                    data:{ name: name, email: email, phone: phone, message: message, checkin: checkin, checkout: checkout },
                    success:function(response)
                    {
                        console.log(response);
                        $("#response_message_text").html("<h3><font color=\"red\">" + response + "</font></h3>");
                        $('#response_message').show();
                    },
                    error: function()
                    {
                        alert("Invalide!");
                    }
                }
            );
        });
    });
</script>

Nézzük mi változott! Minden form mezőnek, az elküldő gombnak és a felhasználói üzenet résznek (#response_message_text és #response_message) egyedi azonosítót, id-attribútumot adtunk, hogy a JavaScript részben #id néven hivatkozhassunk rájuk. Ezt a hivatkozási módszert amúgy css szelektornak hívjuk, ezen belül is ún. ID Selector, részletek itt https://www.w3.org/wiki/CSS3/Selectors.

Látható a <script> tag-ben az ajax hívásunk. Kiolvassuk az űrlapmezők értékeit, majd egy hívást indítunk, mind success (siker), mind error (hiba) esetére megfelelő válaszokkal.

Modellünk nem változik viszont a controller-ünkbe elkél két új metódus. Nézzük a controllers/Apartments.php file-t.

        public function booking_ajax() {
                $data = array();

                $this->template->set('title', $this->lang->line('menu_booking'));
                $this->template->load('menu_layout', 'contents', 'booking_ajax', $data);

        }

        public function book_ajax() {
                $this->load->library('form_validation');
                $this->form_validation->set_rules('name', 'Name', 'required');
                $this->form_validation->set_rules('email','Email','trim|required|valid_email');
                $this->form_validation->set_rules('checkin', 'Check-in', 'required');
                $this->form_validation->set_rules('checkout', 'Check-out', 'required');

                if($this->form_validation->run() == FALSE) {
                        echo validation_errors();
                } else {
                        $this->load->model('reservations_model');
                        $this->reservations_model->insert_reservation();
                        echo "Thanks for booking!";
                }
        }

A booking_ajax metódus csak betölti a view-nkat.

A book_ajax metódus már összetettebb. Tartalmaz egy ellenőrzést, ami email címnél valid_email-lel is bővül, tehát megvizsgálja a formátumot is és egy trim-mel, ami az esetleges felesleges ún. “trailing space” karaktereket vágja le (üres karakterek az elején és a végén).

Látható, hogy validációs hibák esetén visszatér hibával, ez ki is lesz írva a view-nkban a megfelelő div-nél, illetve ha sikerült, akkor a “Köszönet a foglalásért!” üzenettel válaszol. Ennyi az egész :).

És akkor vessünk egy pillantást egy sikeres foglalásra!

Nézzük a http://localhost/apartman/index.php/apartments/booking_ajax oldalunkat.

Láthatjuk, hogy az űrlap (form) helyes kitöltése és elküldése után az adatbázis konzolban látható az adatbázisba felvett sor.

mysql> select * from reservations where id = 10;
+----+------+------------+-------+------------+------------+---------+----------+--------------+--------+---------------------+---------------------+
| id | name | email      | phone | checkin    | checkout   | message | approved | apartment_id | locale | created_at          | updated_at          |
+----+------+------------+-------+------------+------------+---------+----------+--------------+--------+---------------------+---------------------+
| 10 | aaa  | aaa@aaa.hu |       | 2020-07-29 | 2020-08-01 | aaa     |     NULL |            1 | NULL   | 2020-08-03 03:34:11 | 2020-08-03 03:34:11 |
+----+------+------------+-------+------------+------------+---------+----------+--------------+--------+---------------------+---------------------+
1 row in set (0.00 sec)

CodeIgniter modell réteg az adatbázissal és a Hello, World!

Programozóknak ugyan nem kell bemutatni Hello, World! fogalmát, de most kell hogy beszéljünk róla. A mai fejezetben szeretnénk létrehozni az adatbázist MySQL-ben, legyártani a modell réteget, majd adattal feltölteni, és a weben megjelentíteni. Mindezt mind saját kézzel írt kóddal, lehetőleg kevés kódsorban megvalósítva! Ez a Hello, World! – Helló, Világ! példánkban.

https://en.wikipedia.org/wiki/%22Hello,_World!%22_program

Hozzunk létre egy adatbázist a MySQL szerverünkön a https://wlammpp.wordpress.com/2019/10/04/i-resz-wordpress-webfejlesztes-a-wordpress-telepitese korábbi fejezetben ismertetett módon. Van egy adatbázis dump-om, ezt kell “bejátszani”.

-- MySQL dump 10.13 Distrib 5.7.29, for Linux (x86_64)
-- Host: localhost Database: evaap_devel

-- Server version 5.7.29
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table apartments
DROP TABLE IF EXISTS apartments;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE apartments (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255) DEFAULT NULL,
address varchar(255) DEFAULT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table apartments
LOCK TABLES apartments WRITE;
/*!40000 ALTER TABLE apartments DISABLE KEYS */;
INSERT INTO apartments VALUES (1,'Budapest I','VII. kerulet','2020-03-20 00:26:35','2020-03-20 00:26:35');
/*!40000 ALTER TABLE apartments ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table reservations
DROP TABLE IF EXISTS reservations;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE reservations (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255) DEFAULT NULL,
email varchar(255) DEFAULT NULL,
phone varchar(255) DEFAULT NULL,
checkin date DEFAULT NULL,
checkout date DEFAULT NULL,
message text,
approved tinyint(1) DEFAULT NULL,
apartment_id int(11) DEFAULT NULL,
locale varchar(255) DEFAULT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table users
DROP TABLE IF EXISTS users;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE users (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255) DEFAULT NULL,
email varchar(255) DEFAULT NULL,
password varchar(255) DEFAULT NULL,
is_admin tinyint(1) DEFAULT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table users
LOCK TABLES users WRITE;
/*!40000 ALTER TABLE users DISABLE KEYS */;
INSERT INTO users VALUES (1,'admin','gvamosi@linux.hu','manager',1,'2020-03-19 00:00:00','2020-03-19 00:00:00');
/*!40000 ALTER TABLE users ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2020-04-07 14:05:14

Az alábbi módon:

gvamosi@gergo1:/var/www/html/apartman$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 50
Server version: 5.7.29 MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> CREATE DATABASE apartman_devel CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 1 row affected (0.01 sec)
mysql> CREATE USER 'apartman'@'localhost' IDENTIFIED BY 'apartman1';
Query OK, 0 rows affected (0.02 sec)
mysql> GRANT ALL PRIVILEGES ON apartman_devel.* TO 'apartman'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)
mysql> \q
Bye
gvamosi@gergo1:/var/www/html/apartman$ mysql -u apartman -p apartman_devel < db/create_and_insert_tables.sql
Enter password:
gvamosi@gergo1:/var/www/html/apartman$

Hozzuk létre az alkalmazás modell rétegét! Ehhez először be kell konfigurálnunk az adatbázist.

gvamosi@gergo1:/var/www/html/apartman/application$ vi config/database.php
$db['default'] = array(
'dsn' => '',
'hostname' => 'localhost',
'username' => 'apartman',
'password' => 'apartman1',
'database' => 'apartman_devel',
'dbdriver' => 'mysqli',
'dbprefix' => '',
'pconnect' => FALSE,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8',
'dbcollat' => 'utf8_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);

Ez eddig 3 string (karakterlánc)! 🙂 Aztán meg kell írnunk az Apartments_model.php file-t!

gvamosi@gergo1:/var/www/html/apartman/application$ vi models/Apartments_model.php
<?php
class Apartments_model extends CI_Model {
 public function get_last_ten_entries() {
  $query = $this->db->get('apartments', 10);
  return $query->result();
 }
}

Ez 8 sor. 🙂 Most írjuk meg a controller-t!

gvamosi@gergo1:/var/www/html/apartman/application$ vi controllers/Apartments_controller.php
<?php
class Apartments_controller extends CI_Controller {
 public function index() {
  echo 'Helló, Világ!!';
  $this->load->model('apartments_model');
  $data['query'] = $this->apartments_model->get_last_ten_entries();
  $this->load->view('apartments', $data);
 }
}

Ez újabb 9 sor! Nem sok ez egy listáért? És akkor írjuk meg a view-t is!

gvamosi@gergo1:/var/www/html/apartman/application/views$ vi apartments.php
<ul>
<?php foreach ($query as $item):?>
<li><?php echo $item->address;?></li>
<?php endforeach;?>
</ul>

Ez húzós 5 sor! 🙂 Állítsuk be az útvonalakat, az autoload-ot (még két sor :)), majd hívjuk meg a következő módon a böngészőnkből az “oldalunkat”!

gvamosi@gergo1:/var/www/html/apartman/application$ vi config/routes.php
$route['apartmanok'] = 'apartments_controller/index';
gvamosi@gergo1:/var/www/html/apartman/application$ vi config/autoload.php
$autoload['libraries'] = array('database','session');

http://localhost/apartman/index.php/apartmanok

Listázza az egy darab apartmanunk címét, Budapesten. Siker! 🙂

A WordPress – és ami mögötte van: adatbázis és DbVisualizer

Tehát már van egy honlap-vázunk WordPress-ben. De mi köze ennek az adatbázishoz? Ennek járunk ma utána. 🙂 A DbVisualizer egy JAVA-ban írt adatbázis-feltérképező szoftver. Van Debian “.deb” csomagban is, az alábbi linkről telepíthetjük.

https://www.dbvis.com/download

Installálni igen egyszerű.

root@gergo1:~# dpkg -i dbvis_linux_10_0_22.deb

Ezután jöhet a konfiguráció – új kapcsolat varázsló – pár lépésben. A DbVisualizer ún. JDBC-n (Java Database Connectivity) keresztül kapcsolódik a MySQL adatbázisunkhoz. Esetünkben reverse engineering-re fogjuk használni a szoftvert, azaz a kész adatbázisból nyerjük ki a “tervet”. 🙂

Megfelelő konfiguráció után a DbVisualizer gombnyomásra felrajzolja nekünk mind a 12 adatbázistáblát a “wordpress2019” sémában. Az alábbi ábra egy ún. ER-diagram (Entity-Relationship), magyarul EK-diagram (egyed-kapcsolat). A táblák közti kapcsolatok ugyan nincsenek jelölve, azonban indirekten léteznek. Ennyi az egész WordPress! 🙂

Egy adatbázison (sémán) belül az adatok táblákba vannak rendezve, azon belül mezők vannak, és sorokban adatok, hasonlóan egy fejléccel ellátott Excel-táblázathoz. Az adatbázisunk táblái (entitásai, egyedei) rendre:

  • wp_commentmeta
  • wp_comments
  • wp_links
  • wp_options
  • wp_postmeta
  • wp_posts
  • wp_term_relationships
  • wp_term_taxonomy
  • wp_termmeta
  • wp_terms
  • wp_usermeta
  • wp_users

Talán hihetetlenül hangzik, de a fenti 12 táblában minden WordPress oldal, blog bejegyzés (post), felhasználó (user), beállítás, megjegyzés és metaadatok – tehát minden elfér. Ha ügyesek vagyunk, megnézhetjük az egyes táblák mezőit is. Látható, hogy minden táblában van ID, azaz egyedi azonosító vagy elsődleges kulcs – lásd pl. wp_users tábla, bizonyos táblákban pedig hivatkozásként szerepelnek az egyedi azonosítók, ún. idegen kulcsként – lásd wp_usermeta. (Ezek volnának az indirekt kapcsolatok.) Nézzük meg e két tábla mezőit.

Ez az adatszerkezet egy gyakori “trükk” SQL alapú adatbázisoknál. Tetszőleges kulcsokkal (meta_key) tetszőleges értékeket, azaz meta adatokat (meta_value) tárolhatunk el egy felhasználóhoz. A “wp_usermeta” tábla értékeit a “user_id” mezőben lévő kulcsérték köti a “wp_users” táblához, miközben “umeta_id” néven egyedi azonosítója is van minden táblabejegyzésnek, azaz “sornak”. Később látni fogjuk, hogy miért jó dolog egyedi azonosítót adni minden adatbázis táblának. 🙂

Hogy mi az az SQL (Structured Query Language)? Egész röviden: önálló programnyelv; a séma és az adatbázis objektumok (pl. táblák) definiálása – DDL (Data Definition Language), az adatok lekérdezése (SELECT) – DQL (Data Query Language), az adatok változtatása (INSERT, UPDATE, DELETE) – DML (Data Manipulation Language), továbbá a DCL(Data Control Language) illetve a TCL (Transaction Control Language). Ennyi nagyjából. Bővebben az alábbi linken olvashatunk róla, magyarul.

https://hu.wikipedia.org/wiki/SQL

A legtöbb webes alkalmazásnak az ún. CRUD felépítésnek kell megfelelnie. Ez pedig a Create (létrehoz), Read (olvas), Update (módosít) és Delete (töröl). A terminológia a perzisztens adattárolás alapja. Így tárolhatunk adatokat akár webes környezetben, ún. webes form-ok, azaz űrlapok segítségével. A CRUD-ot könnyen párosíthatjuk az SQL INSERT, SELECT, UPDATE és DELETE parancsokkal vagy akár a HTTP protokollal (bemutatása később). Részletek alább – sajna csak angolul.

https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

És egy jó tanács a végére. Ne próbáljunk meg kézzel belejavítani a tábla-adatokba! Dobhat tőle egy hátast az egész WordPress rendszerünk. Elégedjünk meg az architekturális betekintéssel és mérnöki feltárással! 😀