Ebben a fejezetben megnézzük, hogy mik a főbb konfigurációs direktívái az Apache webszerverünknek, és hogy milyen főbb beépülő modulokkal rendelkezik, és ezek hogyan konfigurálhatóak, ti. moduláris a felépítése.
Van egy kisebb bemutató szócikk a magyar wikipédián, ez a https://hu.wikipedia.org/wiki/Apache_HTTP_Server cím alatt érhető el. Amit kiemelnék ez alapján az angol nyelvű szócikkből az, hogy a világ webes kiszolgálóinak majd egyharmadát 2020-ban az Apache 2-es szerver adta, míg a top 10 milló website 40%-át. Ez igen komoly részesedés. Továbbá azt is érdemes megemlíteni, hogy a magyarországi Internet hőskorában a 90-es években gyakorlatilag Apache volt vagy semmi. Mondjuk anno írtunk 20 soros saját webszervert is perl-ben. 🙂
Mindenféle Apache dokumentáció kiindulásául szolgáljon a hivatalos angol nyelvű weblap. E cikk keletkezésének idején az aktuális verzió a 2.4-es, webcíme a http://httpd.apache.org/docs/2.4/.
A konfigurációs állaományt httpd.conf-nak hívják, és saját fordításnál a /usr/local/apache2/conf könyvár alatt található. Az előreinstallált különféle disztribúciós csomagokban viszont a rendszer /etc könyvtárában találhatóak a különböző konfigurációs állományok. Nálam, Debian 10.5 alatt történetesen a /etc/apache2 könyvtár alatt rejlik minden beállítás. Ez egyébként nem is standard megoldású konfiguráció, de könnyen ki lehet találni a működését. Az Include direktíva (http://httpd.apache.org/docs/2.4/mod/core.html#include) teszi lehetővé a konfiguráció több állományra osztását, illetve beillesztését végsősoron egy konfigurációs file-ba.
Amennyiben a beálításban lehetőséget adunk az AllowOverride direktívával (http://httpd.apache.org/docs/2.4/mod/core.html#allowoverride), akkor az ún. .htaccess file-okban a különböző könyvtárainkban az ún. DocumentRoot könyvtár, azaz a webes gyökér könyvtárunk alatt felülírhatunk bizonyos beállításokat. Ez főleg <VirtualHost> direktívával történő konfiguráció esetén (http://httpd.apache.org/docs/2.4/mod/core.html#virtualhost), tehát virtuális szerverek, magyarul egy fizikai gépen több webcím üzemeltetése esetén hasznosak igazán – végső soron masszív többfelhasználós módban. Pláne szükségszerű, ha továbbértékesítjük a virtuális webhelyeket, hiszen így – bizonyos főbb biztonsági megfontolások mellett – minden ügyfelünk saját maga állíthatja be bizonyos dolgokat. Példa: .pl kiterjesztésű – többnyire perl – programkód ún. CGI-ként (https://hu.wikipedia.org/wiki/Common_Gateway_Interface) vagy akár beépülő perl modulként fusson. Ez mindenkor beállítás kérdése, mert hívhatnám akár .kutyafule-nek is ezeket az állományokat. Természetesen ezek a dinamikus tartalmat generáló programocskák pont így jelennek meg az URL-ben is a böngészőablakban kliensoldalon. 🙂
Nézzük meg a <Directory> direktívát egy konkrét példában.
Ez azt jelenti, hogy a /usr/local/httpd/htdocs könyvtár – általában ServerRoot, ami az “alap” hoszt DocumentRoot könyvtára – mutasson könytár indexeket és “kövesse” a szimbolikus linkeket (részletek: https://gvamosi.wordpress.com/2019/09/08/jogosutsagkezeles-filerendszer-szinten/). Sok kis trükk és beállítási lehetőség, nem célom mindet bemutatni.
Az egész konfiguráció lényege tulajdonképpen az, hogy filerendszerben lévő könyvtárak, programocskák és állományok milyen módon kerülnek a webre, azaz a böngésző címsorába.
A modulokról is ejtsünk néhány szót. Betöltésük, ha jelen vannak a rendszerben, a LoadModule direktívával történik (http://httpd.apache.org/docs/2.4/mod/mod_so.html#loadmodule). Konfigurációjuk feltételes az <IfModule> direktíva segítségével történik (http://httpd.apache.org/docs/2.4/mod/core.html#ifmodule). Nyilván azért, hogy csak akkor hajtótdjon végre a beállítás, ha lehetséges, tehát a modul betöltött. Ez amúgy sima Linux dinamikus könytárként van megvalósítva. 🙂
A modulok listája az alábbi oldalon elérhető http://httpd.apache.org/docs/2.4/mod/. Nem teljes, vannak alprojektek is, mint például a FastCGI modul (http://httpd.apache.org/mod_fcgid/), amely segítségével a CGI-k folyton kiszolgálják a kéréseket – tehát a memóriában maradnak. Így gyorsabban futnak, mivel nem kell újra és újra “elindulniuk”. Ez a teljesítménybeli kérdéseknél lesz majd fontos.
Hasznos modulok a mod_cgi – CGI-k végrehajtása, a mod_cache – mindenféle gyorsítótárazás, a mod_proxy – proxy-szerver, azaz “előtét” (https://hu.wikipedia.org/wiki/Proxyszerver), a mod_rewrite – URL-ek szabály alapú átírása (így tűntethető el az a bizonyos index.php rész a CodeIgniter URL-ekből :)) vagy a mod_ssl – HTTPS biztonság.
Látható, hogy van pár opciónk. Megjegyzem, ez a Debian vagy Ubuntu struktúra. Root-ként az apache webszerverünket elindíthatjuk, leállíthatjk, újraindíthatjuk vagy a konfigurációt “menet közben” újratölthetjük. 🙂
Látható, hogy az apache2 folyamataink www-data felhasználó alatt futnak, kivéve a starter folyamatot, ami root-ként fut. Abból “forkol” ki a többi folyamat. Ez a rendszerünk biztonságát szolgálja, hiszen a www-data user nem interaktív és különleges jogok nélküli felhasználó a linuxunkon.
Láthatjuk tehát, hogy alapból ez a 6+1 folyamat szolgálja ki a rendszerünkön a HTTP kéréseket.
Magát a HTTP protokoll-t nagyon nem is részletezném, egy elég jó leírás található a https://hu.wikipedia.org/wiki/HTTP webcímen magyar nyelven.
Hoznék viszont pár gyakorlati megfontolást és példát. Jelenleg a HTTP 1.1 szabvány van érvényben. Ez remekül ki tud szolgálni akár egy facebook (https://www.facebook.com/) alkalmazást, ahol kis piros ikonok jelzik a folyamatos tartalomfrissülést, sőt még tájékoztatást is kapunk arról, akár a Yahoo! (https://mail.yahoo.com/) levelező alkalmazásában, hogy van-e aktuálisan webes (internet) kapcsolatunk. Továbbá egy YouYube https://www.youtube.com/ video stream is, de akár egy on-line rádió is (https://www.kissfm.pt/) kiválóan “elketyeg” ezen a protokoll változaton keresztül. 🙂
Egyébként az előző részben bemutatott AJAX tudás szükséges a folyamatos weblapfrissítéshez illetve a kapcsolatfigyeléshez.
gvamosi@gergo1:/var/www/html$ telnet localhost 80
Trying ::1...
Connected to localhost.
Escape character is '^]'.
HEAD / HTTP/1.1
HOST: localhost
HTTP/1.1 200 OK
Date: Mon, 10 Aug 2020 02:09:29 GMT
Server: Apache/2.4.38 (Debian)
Last-Modified: Mon, 28 Aug 2017 10:20:32 GMT
ETag: "29cd-557cda50c52f6"
Accept-Ranges: bytes
Content-Length: 10701
Vary: Accept-Encoding
Content-Type: text/html
Connection closed by foreign host.
A kiadott parancs a HEAD / HTTP/1.1<ENTER>HOST: localhost<ENTER><ENTER>. Látható a két enter begépelése után az egyszerű http header (fejléc). A kapcsolat, mivel HTTP 1.1-ben vagyunk, megmarad, idővel automatikusan bezáródik. Ez az utolsó sor.
Látható a válaszüzenetben továbbá a 200-as OK státuszkód is, ami a sikeres kérés-kiszolgálást jelzi.
Nézzünk egy “nem található” GET parancsot is.
gvamosi@gergo1:/var/www/html$ telnet localhost 80
Trying ::1...
Connected to localhost.
Escape character is '^]'.
GET /alma
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.38 (Debian) Server at 127.0.1.1 Port 80</address>
</body></html>
Connection closed by foreign host.
Látható, hogy alma néven nincs erőforrás a szerverünkön, 404 Not Found (nem található) választ kapunk.
A Cache (gyorsítótár) funkciót még bemutatnám egy rövid példán keresztül. Ehhez nézzük meg az előző részben lefejlesztett weblapunk képek oldalát bemutató oldalt (https://wlammpp.wordpress.com/2020/07/28/egyszeru-oldal-letrehozasa-kepek-oldal/) böngésző oldalon. Boncasztalon a könyv saját maga! 🙂 A chrome böngészőnkben a Developer tools segítségével (Ctrl+Shift+I) nézhetünk bele a fejlécbe. Kiadhanánk parancssorból is GET parancsokkal is az egész többmenetes weblaplekérést, csak az egy picit bonyolult volna. 🙂 Ti. a böngészőnk letölti a weblapot, aztán a scripteket és a képeket is, tehát a különböző erőforrásokat. Amint megvizsgáljuk a style.css file-t egy újratöltés után a “Network” tabfül alatt, láthatjuk, hogy a fejlécben van egy cache-control: max-age=31536000 bejegyzés. Ez azt fogja jelenteni, hogy a stíluslapunk elég hosszú ideig nem lesz újratöltve, ha frissítjük vagy újra megnyitjuk a weblapot. Csak akkor, ha Ctrl gombbal együtt frissítjuk a weblapot vagy ha a “Clear browsing data…” (Ctrl+Shift+Del) funkcióval kitötöljük a böngészőadatokat. Hogy ez mire jó? Ha fejlesztünk, figyelembe vehetjük a Cache implementációját. Ilyenkor a nagyméretű, de ritkán változó képeket és egyéb file-okat cache-elhetővé tehetjük, ezáltal csökkentve az újratöltési időt. Ugyanakkor amint fejlesztünk, és átírunk egy cache-elt (gyorsítótárazott) JavaScript (js) könyvtárat, akkor Ctrl+F5-tel újra kell töltsük az oldalunkat teszteléshez vagy debug-oláshoz. 🙂
A kis lakat ikon a weblap címe mellett balra a böngésző címsávjában. Nos ez a biztonságos ún. HTTPS adatátvitelt jelöli. Ez csupán annyit lejent, hogy a webszerver és a böngészőnk közötti adatátvitel elég jól kódolt. Tehát harmadik fél nem tud belenézni, kivéve a kormányokat, dehát ezt most ne is taglaljuk. 🙂
Megállapíthatjuk ennyi ismertetés után, hogy nem ördöngősség a web. 🙂
Először is telepítsük a sendmail levélszervert és a mail levelező programot, hogy legalább localhost-on le tudjuk tesztelni a levélküldést. Nagyon nem lesz nehéz dolgunk a CodeIgniter-t illetőleg, mivel támogatja a sendmail levelező csomagot.
gvamosi@gergo1:/var/www/html/apartman/application$ mail -s "Teszt levél" gvamosi@localhost < /dev/null
mail: Null message body; hope that's ok
gvamosi@gergo1:/var/www/html/apartman/application$ mail
"/var/mail/gvamosi": 1 message 1 new
>N 1 gvamosi Fri Aug 7 00:46 14/520 Teszt levél
? q
Held 1 message in /var/mail/gvamosi
Láthatjuk, hogy a levelünk “Teszt levél” tárggyal és üres tartalommal elküldésre került és szerencsésen meg is érkezett. 🙂 Helyben került kézbesítve, helyi feladótól. Ne ijedjünk meg, ha picit lassú a kézbesítés, a névfeloldás megy nehezen a sendmail-ünknek, mivel nem vagyunk internetre kötött ún. SMTP host, azaz hivatalos levélküldő szerver. Attól sem kell félnünk, hogy igazi levelet tudunk küldeni a nagyvilágba, ez így lehetetlen. 🙂
Mindez szép és jó, de hogyan küldjük el akkor egy webes form-ból a levelet az adminisztrátornak, tehát a kapcsolatfelvételi formot hogyan is programozzuk le?
Vegyünk egy sima, nem JavaScript-es form-ot az egyszerűség kedvéért, majd használjuk a CodeIgniter email könyvtárát. Annyi lesz az egész, hogy be kell konfiguráljuk a sendmail-ünket, és meg kell hívnunk a megfelelő rutinokat a controller-ünkben űrlapküldés után.
Elsőnek hozzuk létre a kapcsolati űrlapotviews/contact.php néven!
Nézzük meg azt a három metódust, amivel a controllers/Apartments.php vezérlőnket bővítettük!
public function contact() {
$data = array();
$this->template->set('title', $this->lang->line('menu_contact'));
$this->template->load('menu_layout', 'contents', 'contact', $data);
}
public function send() {
$this->load->library('email');
$config['protocol'] = 'sendmail';
$config['mailpath'] = '/usr/sbin/sendmail';
$config['charset'] = 'utf-8';
$this->email->initialize($config);
$this->email->from($this->input->post('email'), $this->input->post('name'));
// mail of administrator
$this->email->to('gvamosi@localhost');
$this->email->subject('Mail from booking site');
$this->email->message($this->input->post('message'));
$this->email->send();
redirect(base_url('index.php/apartments/contact_sent'));
}
public function contact_sent() {
$data = array();
$this->template->set('title', $this->lang->line('menu_contact'));
$this->template->load('menu_layout', 'contents', 'contact_sent', $data);
}
A contact() metódus csupán kirajzol egy oldalt, ahogy a contact_sent() is. Az érdekesebb metódusunk a send(), amiben a tulajdonképpeni levélküldés történik. Ennyi az egész! 🙂 A valóságban persze az adminisztrátorok és a fejlesztők együttműködésére és közreműködésére van szükség mind fejlesztés, mind pedig a webalkalmazás telepítése során.
Kis várakozás után megkapjuk a levelünket. Kettes sorszámmal látható a parancssori levelezőnkben. 🙂
gvamosi@gergo1:/var/www/html/apartman/application$ mail
"/var/mail/gvamosi": 2 messages 1 new 1 unread
U 1 gvamosi Fri Aug 7 00:46 17/564 Teszt levél
>N 2 Teszt Elek Fri Aug 7 01:14 23/823 Mail from booking site
? q
Held 2 messages in /var/mail/gvamosi
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. 🙂
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. 🙂
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.
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 amodels/Reservations_model.phpmodellü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.phpview-nkat, hogy mi is kerül bele.
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ő “click” esemé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.phpcontroller-ü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. 🙂
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. 🙂
Szeretnénk elérni, hogy a dátum mezőket ki lehessen választani a népszerű módon úgy, hogy “feljön” egy kis naptár egy ún. popup ablakban, ahol rákattintunk a kívánt napra, és a kivlasztott dátum bemásolódik a beviteli mezőnkbe a megadott formátumban.
Ehhez le kell tölenünk a jQuery UI-t a https://jqueryui.com/download/all/ webcímről, majd az assets könyvtárunk megfelelő js, css és css/image könyvtáraiba kell bemásolnunk a megfelelő file-okat.
Ezek után a views/booking_ajax.php view-nkban a <script> részhez az alábbi két új függvényt fűzzük hozzá a $(document).ready(function() rész alá új elemként.
Itt egy sima inicializálásról van szó ID Selector alapján, a megfelelő ISO 8601 formátumban. Már működik is! 🙂
Lehetne hozzá sőt kell is még írni egy belső validációt, ami mégegyszer ellenőrzi a dátumformátumot adatbázisba írás előtt, de most ettől eltekintunk.
Ugyanígy kimaradtak az form serialize aspektusai is az előző fejezet AJAX űrlapküldésénél, továbbá még két apróság. Ugyanis a kettővel ezelőtti fejezetben nem másoljuk be a mező értékeket abban az esetben a form-unkba, ha valami validációs hiba történik, illetve az előző fejezetben nem töröljük a form mezőket (reset), ha sikeres a foglalás.
Apróságnak tűnnek, de ezek különböztetnek meg egy professzionális weblapot egy kezdetlegesebbtől. 🙂
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.
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!
Bár a dolgok “belseje”, mégis nézzük csak meg, hogy a model rétegnek mit kell tudnia. A models/Reservations_model.php file tartalmazza az adatbázis beszúrást, azt a bizonyos DB INSERT-et a címből.
Az insert során vesszük a HTTP POST asszociatív tömbből a megfelelő értékeket, néhányat pedig “generálunk”, illetve “kiszámítunk”. Ilyen az apartment_id, a created_at, az updated_at és a locale (nyelv).
Ezt a model metódust a controller-ből fogjuk hívni. Picit megbonyolítottam ezt a controller-t, ahogy az látszani fog. Tettem bele egy viszonyag egyszerű validációt. A validáció vagyis ellenőrzés arra szolgál, hogy a formban – ahogy az példánkban látható – ne lehessen a név, email, foglalás tól-ig adatok megadása nélkül elküldeni a foglalást. 🙂
Nézzük a controllers/Apartments.php vezérlőnket! Kiegészítjük a booking() és a book() metódusokkal.
Ez annyit jelent, hogy szöveg beviteli mező, name névvel, ahol a vakszöveg a “Name” felirat lesz.
Fontos továbbá az elküldő gomb megadása is, més a form tag-en belül. A tag amúgy, HTML tag, azaz egy két kacsacsőr közé rejtett literál (sztring), amiben nincs további kacsacsőr karakter. Példa nyitó és záró tag-ekre: <form></form>. 🙂
Ez egy submit button – elküldő gomb, “Book” felirattal.
Ezt a formot a submit gomb megnyomása után a book() metódus fogja “megkapni” a controller-ben egy HTTP POST metódushívás után. A form mezőit egy a name attribútumokban megadott nevekkel kulcsolt asszociatív tömb fogja tartalmazni. Ennyi az egész! 🙂
Látható az adatbázisunkban, hogy a megfelelő rekord bekerült a reservations táblába (SQL konzol hozzáférés mysql -u apartman -p apartman_devel parancs kiadásával).
mysql> select * from reservations;
+----+-----------------+------------+------------+------------+------------+-----------------------+----------+--------------+---------+---------------------+---------------------+
| id | name | email | phone | checkin | checkout | message | approved | apartment_id | locale | created_at | updated_at |
+----+-----------------+------------+------------+------------+------------+-----------------------+----------+--------------+---------+---------------------+---------------------+
| 5 | Gergely Vámosi | mail@to.me | 0123456789 | 2020-07-29 | 2020-08-01 | Szeretnék egy szobát! | NULL | 1 | english | 2020-07-29 01:33:20 | 2020-07-29 01:33:20 |
+----+-----------------+------------+------------+------------+------------+-----------------------+----------+--------------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
Nézzük meg még a views/booking_accepted.php oldalt. Ez arról ad tájékoztatást az apartmant lefoglalni szándékozó ügyfélnek, hogy sikeres a foglalása!
Vegyük kicsit górcső alá a biztonságot! Hisz adatot közlünk a szerverrel, bármit beírhatunk a weblapon az űrlapba, amit pár szűrőn áteresztve adatbázisban tárolunk!
Először is érdemes megvizsgálni azt, hogy mit írhatunk be a mezőkbe: speciális karakterek, többek között a HTML formátum speciális jelei, a <>” és ‘ jelek. Aztán az ún. SQL injection veszélyei, amikor is az adatbáziselérésen keresztül speciális, például rosszakaratú SQL parancsokat tudunk kiadni, ami akár lehet egy ;DELETE FROM <TÁBLANÉV> pontosvessző után beszúrt parancs is akár. Szerencsés esetben ezeknek az eseteknek a kiszűrését a keretrendszerünk biztosítja. 🙂
Érdemes továbbá megvizsgálni az esetleges ún. CSRF – Cross-site request forgery veszélyét is. Bővebben a https://hu.wikipedia.org/wiki/Cross-site_request_forgery cím alatt olvashatunk róla. Röviden arról van szó, hogy a böngészőn keresztül nem kívánt parancsok hajthatók végre egy weblapon a felhasználó tudta nélkül. Ebbe a témakörbe tartozik egyébként még az XSS – Cross-site scripting is. A CSRF ellen egyébként többnyire egy minden formhoz (űrlapohoz) egyedileg generált azonosító, egy speciális kód véd, ami üzenetváltásonként újragenerált (töken). Ez véd többek között egy form újraküldése ellen, azaz adott esetben egy művelet (pl. fizetés) adott esetben rossz szándékú újbóli végrehajtása ellen. Az XSS csupán egy weblap funkcióinak más weblapbóli történő meghívása. 🙂 Játék. A baj, ha nem barátágos.
Aztán ott van az úgynevezett Captcha-teszt szükségessége is. Bővebben a https://hu.wikipedia.org/wiki/Captcha címen olvashatunk róla. Itt arról van szó, hogy weblapunk az emberi felhasználót megkülönböztesse egy generált robot, azaz gépi hozzáféréstől. Csak valós emberektől szeretnénk apartman foglalást elfogadni! Semmi esetre sem rossz szándékú hekkerek által készített programoktól. Ismerősök ugye az elhanyagolt facebook profilok mindenféle “fake” felhasználó általi “hello, ismerjük egymást” üzenetei? Főleg attraktív “csajoktól”? 🙂
Egy egyszerű oldalt az apartmanunkat bemutató képekkel három egyszerű lépésben hozhatunk létre. A demo képeket letöltöttük ingyenesen a https://www.pexels.com/ oldalról és előzőleg már előkészítettük.
Először is a controller-ünket kell megbűvölni. A controllers/Apartments.php file-hoz az alábbi sorokat fűzzük hozzá.
public function photos()
{
$data = array();
$this->template->set('title', $this->lang->line('menu_photos'));
$this->template->load('menu_layout', 'contents', 'photos', $data);
}
Másodjára létrehozzuk a views/layouts/menu_layout.php sablon-t, ami alapvetően annyival tud többet a default_layout.php file-unknál, hogy a menüt is meg lehet benne változtatni. Erre csupán azért van szükségünk, hogy az aktuális menüpont piros lehessen. 🙂
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.
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. 🙂
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.