Az alábbi leírás a webalklamazás implementációját és működését mutatja be. Célja, hogy érthető legyen az alkalmazás felépítése és működése.
Az alkalmazást több különböző PHP fájl valósítja meg:
index.php
: ez a főoldal, ahol megjelenik az oldal címe és linkek az egyes aloldalakra.books.php
: ez az oldal megjeleníti a könyveket és lehetőség van új könyv létrehozására.edit-book.php
: ezen a oldalon egy-egy könyvet lehet szerkesztenimembers.php
: ez az oldal megjeleníti az adatbázisban tárolt tagok listáját, ugyanitt lehet új tagok létrehozni.edit-member.php
: ezen az oldalon egy-egy tag adatait lehet szerkeszteni.lendings.php
: ez az oldal megjeleníti a jelenlegi kölcsönzéseket, ugyanitt lehet új kölcsönzést indítani.edit-lending.php
: ezen az oldalon lehet egy-egy kölcsönzés adatait módosítani.
A fentieken kívül néhány további segédfájlt is fogunk használni:
library.css
: közöss css fájl, amiben a stílusokat definiálhatjuk. erre minden oldal hivatkozik majd.menu.html
: minden oldal tetején megjelenik majd egy menü, amely linkeket tartalmaz a könyvek, tagok és kölcsönzések aloldalakra. Az ezt a menüt megvalósító HTML kódrészletet - hogy ne kelljen minden oldalon külön megírni -, egy külön fájlba helyezzük, amelyet importálunk majd minden oldalon.library.php
: az adatbáziscsatlakozáshoz szükséges PHP nyelven megírt segédfüggvényeket tartalmazza.
Összefoglalva tehát az odalak felépítését:
- Lesz tehát egy közös menü, amely minden oldalon megjelenik. Itt linkeket jelenítünk meg a könyvek, tagok és kölcsönzések listáira, vagyis a
books.php
,members.php
éslendings.php
oldalakra. - Az
index.php
oldal megjeleníti a menüt és kiírja az oldal címét.
Kezdjük az index.php fájl implementációjával:
<html>
<head>
<link rel="stylesheet" href="library.css">
<title>Könytár</title>
</head>
<body>
<div>
<!-- menu -->
<a href="index.php">Főoldal</a>
<a href="books.php">Könyvek</a>
<a href="members.php">Tagok</a>
<a href="lendings.php">Kölcsönzések</a>
</div>
<div class="main-content">
<h1>Könyvespolc</h1>
<p>Könyvtár adatbázis, kölcsönzések adminisztrációja</p>
</div>
</body>
</html>
A fenti kódrészlet egy egyszerű HTML oldalt tartalmaz. A menüben 4 link található, egy a főoldalra mutat, három pedig az egyes aloldalakra.
A főoldalon lévő tartalmat egy main-content
css osztállyal ellátott div
tartalmazza. Ez a css osztály, a hivatkozott library.css
-ben található és egy felső margót definiál:
/* library.css */
.main-content {
margin-top:1em;
}
(Az 1em
mértékegység egy betűmagasságot jelent.)
A XAMPP program gyökérkönyvtárában a htdocs
mappában találhatók azok a fájlok, amelyeket kiszolgál a webszerver. Hozzuk létre ezen belül az info2/library könyvtárakat! A fenti index.php
és library.css
fájlokat pedig helyezzük el a <XAMPP_Gyökér>/htdocs/info2/library könyvtárba.
Ezután a böngészőben a localhost://info2/library/index.php
címet beírva az alábbi oldalt kell lássuk:
Mivel a menüt minden oldalon szeretnénk megjeleníteni, érdemes azt külön fájlba kitenni. A menüt tartalmazó div
et tehát helyezzük át az index.php
-ból a menu.html
fájlba, majd hivatkozzuk ezt az eredeti index.php
-ban:
A módosított index.php
:
<html>
<head>
<link rel="stylesheet" href="library.css">
<title>Könytár</title>
</head>
<body>
<?php include 'menu.html'; ?>
<div class="main-content">
<h1>Könyvespolc</h1>
<p>Könyvtár adatbázis, kölcsönzések adminisztrációja</p>
</div>
</body>
</html>
Az új menu.html
:
<div class="main-content">
<h1>Könyvespolc</h1>
<p>Könyvtár adatbázis, kölcsönzések adminisztrációja</p>
</div>
Már szó volt róla, hogy a főbb CSS osztályokat a különálló library.css
fájlba helyezhetjük.
Ahhoz, hogy egy alkalmazás szépen nézzen ki, a HTML kód mellett megfelelően összehangolt CSS szabályokra van szükség. Egy egységes design elérése általában nagy munka, de szerencsére vannak előre felhasználható keretrendszerek. Ezek közül az egyik legelterjedtebb a Bootstrap.
Alapvető funkcionalitása az, hogy biztosít egy CSS könyvtárat, amely egységes és igényes megjelenítést biztosít. A könyvtárat importálnunk kell az oldalunkon, ezután pedig a különböző elemeken alkalmazhatjuk az előre definiált CSS osztályokat. Így egy egységes megjelenítést érhetünk el anélkül, hogy ennek részleteivel a továbbiakban külön kelljen foglalkoznunk. Az elérhető osztályokat és működésüket részletesen dokumentálták.
Amikor egy CSS fájlt szeretnénk használni (mint például a library.css
-t), azt el kell tárolni a könyvtárunkban, majd hivatkozni kell a HTML fájlokban. Az olyan általános fájlokat, mint amilyet például a bootstrap keretrendszer biztosít, gyakran frissítik, ezért ilyenkor biztosítani kell, hogy mindig a legfrissebb verzió legyen meg nálunk is. Egy alternatív megoldás, hogy ahelyett, hogy letöltenénk ezt a fájlt, valamilyen központi oldalról hivatkozzuk. A bootstrap aktuális verziója például elérhető a következő linken:
https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css
Amennyiben ezt hivatkozzuk, akkor nincs szükség a fájl letöltésére. Alakítsuk át az index.php-t a következő módon:
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="library.css">
<title>Könytár</title>
</head>
<body>
<?php include 'menu.html'; ?>
<div class="container main-content">
<div class="jumbotron">
<h1>Könyvespolc</h1>
<p class="lead">Könyvtár adatbázis, kölcsönzések adminisztrációja</p>
</div>
</div>
</body>
</html>
A menu.html tartalma pedig legyen a következő:
<div class="container">
<nav class="navbar navbar-dark bg-dark navbar-expand-sm">
<a class="navbar-brand" href="index.php">Főoldal</a>
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a href="books.php" class="nav-link">Könyvek</a>
</li>
<li class="nav-item">
<a href="members.php" class="nav-link">Tagok</a>
</li>
<li class="nav-item">
<a href="lendings.php" class="nav-link">Kölcsönzések</a>
</li>
</ul>
</nav>
</div>
A fenti változtatások eredményeképpen az oldal a következőképpen néz ki:
Nézzük mit kellett átalakítani, kezdjük az index.php
változásaival!
- Az oldal fejlécében (
head
elem) hivatkozzuk a bootstrap CSS fájlát. - A címet és a leírást tartalmazó kódrészletet egy
container
CSS osztállyal ellátottdiv
be, azon belül pedig egyjumbotron
CSS osztállyal ellátottdiv
be helyezzük.- A
container
div
-eket arra használjuk, hogy az oldal tartalma fix szélességű legyen és középre legyen igazítva. Minden további tartalmat ilyen container elemebe helyezünk. Így ha a böngészőt átméretezzük a tartalom szélessége akkor sem változik. Részletesebben lásd a dokumentációban. - A jubotron egy nagy, szürke, keretes címsort jelenít meg, amit tipikusan a főoldalak címének leírására szoktunk használni.
- A
A menu.html
továbbra is csak a három linket tartalmazza, de a bootstrap által biztosított menüsorban (navbar) jeleníti meg ezeket.
Összefoglalva elmondható, hogy néhány egyszerű szabály segítségével sokkal szebbé tehetjük az alkalmazás kinézetét. Ehhez általában elegendő néhány CSS osztályt alkalmazni. Azonban ezek a beállítások az oldal tartalmát és működését nem befolyásolják.
Egy webalkalmazás mindig jobban néz ki, ha sok képet és ikont tartalmaz. Egy ingyenesen elérhető ikon könyvtár a fontawesome
, amely a bootstraphez hasonlóan nagyon egyszerűen használható.
Működéséhez mindössze egy újabb CSS fájlra lesz szükség:
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" crossorigin="anonymous">
Ezután a használata igen egyszerű: egy új ikon megjelenítéséhez a következő HTML elemet kell elhelyezni: <i class="fa fa-search"></i>
, ahol a fa
osztály a fontawesome könyvtárra utal, a konkrét ikont pedig a fa-search
osztály azonosítja. Ebben az esetben egy nagyító jelenik meg. Ha más ikont szeretnénk megjeleníteni, akkor csak a search szöveget kell kicserélni. A használható ikonok listája a könyvtár oldalán elérhető.
Az oldalon szükség lesz arra, hogy csatlakozzunk az adatbázishoz és lekérdezzük annak tartalmát, vagy más SQL szkriptet hajtsunk végre rajta. Az adatbázis sémáját a specifikáció tartalmazza, a létrehozó szkript a db/db_create.sql
, aminek működéséről szintén készült részletes dokumentáció.
MySQL adatbázis eléréséhez PHP nyelven a mysqli
osztálykönyvtárat fogjuk használni. A könyvtár függvényei mind a mysqli_
prefixszel kezdődnek. Használtának főbb lépései a következők:
- Először csatlakozni kell az adatbázishoz, ehhez a
mysqli_connect
függvényre lesz szükség, amelynek paraméterként át kell adni az adatbázisszerver címét és a belépéshez használt felhasználónevet és jelszót. A függvény visszatérési értéke az adatbázis kapcsolatot leíró azonosító. Minden további függvénynek ezt az azonosítót kell átadni. - Kapcsolódás után a
mysqli_select
függvénnyel ki kell választani az adatbázis szerveren belüli adatbázist, amelyen a lekérdezésket futtatni fogjuk. Jelen esetben ez a konyvtar adatbázis lesz. - Bármilyen SQL szkriptet a
mysqli_query
függvénnyel tudunk lefuttatni. - Az adatbáziskezelés befejeztével érdemes lezárni a kapcsolatot, hogy felszabadítsuk az erőforrásokat, erre a
mysqli_close
függvény való.
Gyakran előfordul, hogy az adatbáziskapcsolat használata közben valamilyen hiba történik. Ezt általában a megfelelő függvény visszatérési értéke jelzi. Amennyiben a visszatérési érték alapján tudjuk, hogy hiba történt, akkor a mysqli_error
függvénnyel tudjuk a hiba részleteit lekérdezni.
Egy egyszerű hibakezelési módszer a következő: tegyük fel, hogy a mysqli_query($db, $query)
hívást szereténk lefuttatni és eredményét elmenteni a $result
változóba. Ehelyett írhatjuk a következő kódot!
$result = mysqli_query($db, $query)
or die(mysqli_error($db));
A fenti kódrészletekben a $db
változó az adatbáziskapcsolat azonosítója, amivel a mysqli_connect
visszatér, $query
pedig egy string, ami a végrehajtandó utasítást tartalmazza. A $result
változó tartlmazza a lekérdezés visszatérési eredményét. Amennyiben azonban hiba történik, akkor nem lesz a mysqli_query
függvénynek visszatérési értéke, pontosabban null
lesz. Ilyenkor - mivel or
operátor lusta kiértékelésű -, a jobb oldali operandus, vagyis a die(mysqli_error($db))
kifejezés is kiértékelődik. Ha az or
bal oldalán álló kifejezés nem üres értékkel tér vissza, akkor a jobb oldal nem kerül kiértékelésre, vagyis a die
függvény nem fut le. A die
függvény megállítja a php
szkript további részeinek végrehajtását és a generálandó HTML tartalom helyett a paraméterként megkapott szöveget adja vissza. Összefoglalva tehát, amennyiben a lekérdezés rendben végrehajtódott, akkor a program futása folytatódik, amennyiben viszont hiba történt, akkor egy HTML tartalom helyett a hiba szövegét leíró szöveget kapunk vissza a szervertől.
A mysqli_query
segítéségével bármilyen SQL
utasítást elküldhetünk. Egy SQL
utasításnak többféle visszatérési értéke is lehet:
- Történhet hiba a végrehajtásnál, ennek a kezelését a fentiekben már láttuk.
- Van olyan utasítás, amely esetén a visszatérési érték egy skalár érték, amely a módosított sorok számát tartalmazza. Ilyen utasítás például az
INSERT
, vagy aDELETE
. - Lekérdezés (
SELECT
) esetén viszont a visszatérési érték egy reláció lesz, vagyis egy tábla, amelynek több sora és oszlopa lehet.
Nézzük, hogy egy lekérdezés eredményét hogyan lehet PHP-ban feldolgozni:
$result = mysqli_query($db, "SELECT id, cim FROM konyv") or die(mysqli_error($db));
while ($row = mysqli_fetch_array($result)) {
//aktuális sor id-ja: $row['id']
//aktuális sor címe: $row['cim']
}
A mysqli_query
-nek két paramétere van. Az első az adatbáziskapcsolat azonosítója, amely a mysqli_connect
visszatérési értéke. A második az SQL utasítás, amit futtatni szeretnénk.
A fenti kódban a mysqli_query
visszatérési értéke egy olyan objektum, amely segít feldolgozni lekérdezés eredményét mégpedig úgy, hogy sorról sorra lépkedunk egy kurzor segítségével. A kurzur tehát mindig egy adott sorra mutat és lekérdezhető az adott sorban az egyes cellák értéke. Amikor egy sor feldolgozását befejeztük, továbblépünk a következő sorra.
A $result
objektumon ciklusban meg kell hívni tehát mysqli_fetch_array
függvényt, amely mindig az aktuális sorra mutató kurzorral tér vissza. Amikor nincs már több sor, a visszatérési érték is null
lesz.
Az adott sorra mutató kurzus (a fenti példában a $row
változó) egy asszociatív tömb, amelyben a megfelelő cellák értékeit az oszlopok neveivel indexelve érhetjük el. Péládul, ha a lekérdezés eredményében szerepel egy cim
nevű oszlop, annak aktuális sorbeli értéke a $row['cim']
kifejezéssel kérdezhető le.
Tegyük fel, hogy írunk egy oldalt, ahol a felhasználó megad egy szöveget és az ilyen című könyv adatait megjelenítjük. Ekkor a következő lekérdezést kellene megírnunk: SELECT * FROM konyv WHERE cim = <cim>
, ahol a <cim>
helyére behelyettesítjük a megfelelő szöveget. Tegyük fel , hogy a felhasználó által beírt szöveget a $cim
változóba mentjük el. Ekkor a lekérdezést például a következő módon állíthatjuk össze:
$query = sprintf("SELECT * from konyv WHERE cim = '%s'", $cim);
Az sprintf
függvény a %s
helyére beilleszti a $cim értékét. Tegyük fel, hogy a $cim
tartalma pontosan az a szöveg, amit a felhasználó beírt az egyik szövegmezőbe az oldalon. Amennyiben a $cim
értéke "A gyűrűk ura", akkor ay ilyen című könyv kerül lekérdezésre.
Tegyük fel most, hogy a felhasználó a következő szöveget adja meg címként: "A gyűrűk ura '; DELETE FROM konyv WHERE id > 0; #"
Amennyiben összefűzzük a két sztringet, a következő utasítást kapjuk:
SELECT * from konyv WHERE cim = 'A gyűrűk ura '; DELETE FROM konyv WHERE id > 0; #'
A #
jel a megjegyzés kezdete MySQL-ben. Könnyen látható, hogy a fenti utasítást lefuttatva törölni fogjuk az adatbázisban a könyv tábla tartalmát. Ezt a fajta sebezhetőséget SQL injectionnek nevezzük és mindenképpen fel kell készíteni az adatbázisunkat az ilyen támadás elleni védekezésre.
A megoldás az, hogy nem engedünk bármilyen stringet előzetes ellenőrzés nélkül beilleszteni egy utasításba, amelyet elküldünk az adatbázisszervernek. Előtte a felhasználótól jövő szöveget ún. escape-elni kell. Ez azt jelenti, hogy azokat a karaktereket, amelyek lezárhatják az idézőjeleket speciális escape szekvenciákkal kell helyettesíteni. Szerencsére ezt a feladatot elvégzi helyettünk a mysqli_real_escape_string
függvény. Tehát nincs más dolgunk, mint mielőtt egy string-et felhasználunk, előtte használnunk kell ezt függvényt.
$query = sprintf("SELECT * from konyv WHERE cim = '%s'",
mysqli_real_escape_string($db, $cim));
Ezzel a kóddal például a támadó által megadott támadó jellegű cím ellen tudunk védekezni, így a következő, immár biztonságos, parancsot kapjuk:
SELECT * from konyv WHERE cim = 'A gyűrűk ura \'; DELETE FROM konyv WHERE id > 0; #'
Az adatbáziskezelés megkönnyítésére néhány segédfüggvényt definiálunk, amelyeket egy külön fájlba, a library.php
-ba teszünk:
<?php
function getDb() {
$link = mysqli_connect("localhost", "root", "")
or die("Kapcsolódási hiba: " . mysqli_error());
mysqli_select_db($link, "konyvtar");
mysqli_query ($link, "set character_set_results='utf8'");
mysqli_query ($link, "set character_set_client='utf8'");
return $link;
}
function closeDb($link) {
mysqli_close($link);
}
?>
A getDB
segédfüggvény csatlakozik az adatbázishoz, megadja, hogy a konyvtar
adatbázist szeretnénk használni, majd lefuttat két SQL
utasítást. Ezek biztosítják, hogy UTF-8 (unicode) kódolást használjunk az adatcsere során, vagyis a magyar ékezetets karakterek is jól működjenek. A függvény visszatérési értéke ($link
) az adatbáziskapcsolat azonosítója.
A másik segédfüggvény lezárja a paraméterként megkapott adatbázis kapcsolatot.
A könyv tábla tartalmát a books.php
oldalon jelenítjük meg. Ez kezdetben tartalmazza a következő kódot!
<?php
include 'library.php';
$link = getDb();
?>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="library.css">
<title>Könytár</title>
</head>
<body>
<?php include 'menu.html'; ?>
<div class="container main-content">
<h1>Könyvek</h1
<?php
$querySelect = "SELECT id, isbn, cim, szerzo, kiado, megjelenesev FROM konyv";
$eredmeny = mysqli_query($link, $querySelect) or die(mysqli_error($link));
?>
<table class="table table-striped table-sm table-bordered">
<thead class="thead-dark">
<tr>
<th>Cím</th>
<th>Szerző</th>
<th>Kiadó</th>
<th style="white-space:nowrap;">Megjelenés éve</th>
<th>ISBN</th>
</tr>
</thead>
<tbody>
<?php while ($row = mysqli_fetch_array($eredmeny)): ?>
<tr>
<td><?=$row['cim']?></td>
<td><?=$row['szerzo']?></td>
<td><?=$row['kiado']?></td>
<td><?=$row['megjelenesev']?></td>
<td><?=$row['isbn']?></td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
<?php
closeDb($link);
?>
</div>
</body>
</html>
Nézzük végig az oldal működését részletesen:
- Az oldal elején importáljuk a
library.php
tartalmát és inicializáljuk az adatbázis kapcsolatot. - A
head
elemben importáljuk a saját CSS fájlunkat (library.css
) és a hivatkozott bootstrap, ill. fontawesome könyvtárakat. - Az oldal tetejére beillesztjük a
menu.html
tartalmát. - Megírjuk a megfelelő lekérdezést, majd végigiterálva a sorain, kilistázzuk a könyveket egy táblázatban.
- A táblázat egy egyszetű HTML táblázat. A fejlécét
thead
, a törzsének a soraittbody
elemek közé írjuk. - A táblázat formázására a bootstrap CSS osztályait használjuk:
table
: ezzel jelezzük, hogy bootstrap tábla formázását használjuktable-striped
: a páros és páratlan sorok háttere legyen különböző ("csíkos" táblázat)table-sm
: kisméretű táblázat (kisebb betűtítpus használata)table-bordered
: a táblázat szegélyei legyenek láthatók
- A táblázat egy egyszetű HTML táblázat. A fejlécét
Egészítsük ki az oldalt egy form
elemmel, amely megjelenít egy űrlabpot, amivel új könyvet hozhatunk létre! Az oldal alján lesznek beviteli mezők, amelyek az új könyv címét, szerzőjét és egyéb adatait kérik be. Legyen továbbá egy gomb, amivel ez a tartalmat elküldjük és létrehozzuk a könyvet.
<form method="post" action="">
<div class="card">
<div class="card-header">
Új könyv hozzáadása
</div>
<div class="card-body">
<div class="form-group">
<label for="isbn">ISBN</label>
<input class="form-control" name="isbn" id="isbn" type="text" />
</div>
<div class="form-group">
<label for="cim">Cím</label>
<input required class="form-control" name="cim" id="cim" type="text" />
</div>
<div class="form-group">
<label for="szerzo">Szerző</label>
<input class="form-control" name="szerzo" id="szerzo" type="text" />
</div>
<div class="form-group">
<label for="kiado">Kiadó</label>
<input class="form-control" name="kiado" id="kiado" type="text" />
</div>
<div class="form-group">
<label for="megjelenesev">Megjelenés éve</label>
<input class="form-control" name="megjelenesev" id="megjelenesev" type="number" />
</div>
</div>
<div class="card-footer">
<input class="btn btn-success" name="create" type="submit" value="Létrehozás" />
</div>
</div>
</form>
- A
form
tehát egy űrlapot jelenít meg. Az űrlapban a különbözőinput
elemek adatokat tartalmaznak. - A
card
egy "doboz" kinézetű elemet tartalmat valósít meg, aminek van kerete, fejléce, törzse és lábrésze. Csak a design miatt használjuk, tartalmi szempontból nincs rá szükség. - Szintén csak a design miatt használjuk a
form-group
ésform-control
CSS osztályokat.
Amikor egy űrlapot elküldünk a szervernek, akkor a megfelelő beviteli mezők tartalma elmegy a kérésben a szervernek, amely ezt fel tudja dolgozni. Az egyes mezőket a az input
elemek name
attribútuma azonosítja. Amennyiben kiteszünk egy submit
típusú input
elemet, akkor megjelenik egy gomb. Ezt a gombot megnyomva küldi ez a böngésző a form
tartalmát. Az elküldés tehát azt jelenti, hogy a böngésző létrehoz egy új HTTP kérést, ezt elküldi a szervernek és a visszatérési értékként kapott HTML oldalt újra megjeleníti. A kérésben pedig benne lesz a form
aktuális tartalma.
A form
elem attribútumaiban megadható, hogy a szerver melyik erőforrásához küldjük a kérését (action
attribútum). Amennyiben nem adunk meg semmit, vagy üresen hagyjuk, akkor az aktuális oldalhoz küljük, vagyis jelen esetben a books.php
-hoz. A method
attribútum határozza meg, hogy milyen HTTP kérést küldünk (például POST, vagy GET típusút). GET esetében az URL-ben, míg POST esetében a kérés törzsében lesz elküldve az űrlap tartalma.
Jelen esetben például a következő HTTP POST kérés mehet el a szervernek:
POST /info2/library/books.php HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 136
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Referer: http://localhost/info2/library/books.php
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
isbn=%C3%BAj+isbn&cim=%C3%BAj+c%C3%ADm&szerzo=%C3%BAj+szerz%C5%91&kiado=%C3%BAj+kiad%C3%B3&megjelenesev=2018&create=L%C3%A9trehoz%C3%A1s
Jól látható, hogy a kérés POST típusú, a books.php
-nak megy és a fejléc beállításai után tartlmaz egy tözset, amelyben URL kódolva láthatók az űrlapba beírt értékek. A fenti példában a az isbn hez "új isbn", a címhez "új cím" stb. értékeket adtunk meg. Az URL kódolás az ékezeteket és a szóközöket helyettesíti speciális karakterkódokkal, de ettől eltekintve a következő formátumnak felel meg: isbn=<isbn értéke>&cim=<cim értéke>&...
.
Amennyiben a fenti kódot fel akarjuk dolgozni a szerveren nincs más dolgunk, mint megvizsgálni, hogy a fenti paramétereket tartalmazza-e a kérés. Ne felejtsük el, hogy amikor egyszerűen a menüben található linket követve jutunk el a books.php
oldalra, akkor a böngésző egy egyszerű HTTP GET kérést küld, amiben nincsenek további paraméterek. Amikor kitöltjük az oldalon az űrlapot, akkor ugyanúgy egy HTTP kérés megy a books.php
-nak, de itt már POST típusú a kérés és az űrlapnak megfelelő paraméterek szerepelnek. Tehát mindenképpen le kell generálnunk a könyvek listáját, de amennyiben egy űrlapot küldtünk el, akkor létre is kell hoznunk egy új könyvet.
Illesszük be az alábbi kódot a oldal tetejére!
<?php
include 'library.php';
$link = getDb();
$created = false;
if (isset($_POST['create'])) {
$cim = mysqli_real_escape_string($link, $_POST['cim']);
$szerzo = mysqli_real_escape_string($link, $_POST['szerzo']);
$kiado = mysqli_real_escape_string($link, $_POST['kiado']);
$isbn = mysqli_real_escape_string($link, $_POST['isbn']);
$megjelenesev = mysqli_real_escape_string($link, $_POST['megjelenesev']);
$createQuery = sprintf("INSERT INTO konyv(cim, szerzo, kiado, isbn, megjelenesev) VALUES ('%s', '%s', '%s', '%s', %s)",
$cim,
$szerzo,
$kiado,
$isbn,
$megjelenesev
);
mysqli_query($link, $createQuery) or die(mysqli_error($link));
$created = true;
}
?>
Az űrlapban az elküldő gomb name
attribútumát create
értékre állítottuk be, ezért amikor elküldjük az oldalt, ez szerepelni fog a paraméterek között. A POST kérés paramétereit a $_POST
változón keresztül érjük el, amely egy asszociatív tömb, ezért indexeléssel (string indexeket használva) érjük el az egyes paraméterket. Például a cim
nevű input
mező által elküldott tartalmat a $_POST['cim']
kifejezéssel. A create
paraméter valóban szerpelni fog a $_POST
tömbben, de itt nem az a fontos, hogy mi az értéke, csak az, hogy egyáltalán szerpel-e. Ez jelzi, hogy az adott gombbal küldtük el az űrlapot.
Az asszociatív tömbben egy paraméter jelenlétét az isset
függvénnyel ellenőrizzük, pl. isset($_POST['create'])
.
Amennyiben egy űrlap adatait tartalmazza a kérés, akkor kiolvassuk a megfelelő paramétereket, összeállítjuk az SQL INSERT
utasítást (nem elfeledkezve az SQL injection elleni védelemről), majd elküdjük azt az adatbázisszervernek. Amennyiben új adatot szúrtunk be, ezt a $created
segédváltozóban jelezzük.
A books.php
oldalon, közvetlenül a cím után írjuk ki, ha egy új könyvet beszúrtunk! Természetesen csak akkor kell megjeleníteni az üzenetet, ha a $created
változó értéke igaz. A megjelenítéshez a badge
CSS osztályt használjuk.
<h1>Könyvek</h1>
<?php if ($created): ?>
<p>
<span class="badge badge-success">Új könyv létrehozva</span>
</p>
<?php endif; ?>
Még egy fontos funkció hiányzik a könyvek főoldaláról, ez a keresés. Ezt következőképpen valósítjuk meg:
- Kiteszünk egy új úrlapot, amelyben egy szöveget lehet beírni. Az űrlapban egy
search
azonosítójű gomb lesz. - A
books.php
oldalon, amennyiben vansearch
paramétere a kérésnek, akkor azt kiolvassuk és módosítjuk a lekérdezést, hogy csak a ilyen című könyvek szerepejenek a lekérdezésben.
Az űrlap:
<form class="form-inline" method="post">
<div class="card">
<div class="card-body">
Keresés:
<input style="width:600px;margin-left:1em;" class="form-control" type="search" name="search" value="<?=$search?>">
<button class="btn btn-success" style="margin-left:1em;" type="submit" >Search</button>
</div>
</div>
</form>
A lekérdezés módosítása:
<?php
$querySelect = "SELECT id, isbn, cim, szerzo, kiado, megjelenesev FROM konyv";
if ($search) {
$querySelect = $querySelect . sprintf(" WHERE LOWER(cim) LIKE '%%%s%%'", mysqli_real_escape_string($link, strtolower($search)));
}
$eredmeny = mysqli_query($link, $querySelect) or die(mysqli_error($link));
?>
A végleges oldal a books.php
fájlban található.
A könyvek táblázatában minden sor végére kiteszünk egy gombot. A gomb valójában egy link (a
elem), ami átnavigál a megfelelő könyvet szerkesztő oldalra. Ez az oldal az edit-book.php
lesz. Honnan tudjuk ezen az oldalon, hogy melyik könyvet szerkesztjünk? Onnan, hogy az URL-ben paraméterként átadjuk a könyv azonosítóját, pl. edit-book.php?bookid=2
.
Az alábbi kód mutatja a módosított táblázatot. Annyi különbség van a korábbiakhoz képest, hogy megjelenik plusz egy oszlop, abban pedig egy link. A linket a btn
és btn-success
css osztályokkal formázzuk meg, amelyek úgy jelenítik meg, mint egy gombot. A linkben kiteszünk egy szerkesztést jelző ikont is.
<table class="table table-striped table-sm table-bordered">
<thead class="thead-dark">
<tr>
<th>Cím</th>
<th>Szerző</th>
<th>Kiadó</th>
<th style="white-space:nowrap;">Megjelenés éve</th>
<th>ISBN</th>
<th></th>
</tr>
</thead>
<tbody>
<?php while ($row = mysqli_fetch_array($eredmeny)): ?>
<tr>
<td><?=$row['cim']?></td>
<td><?=$row['szerzo']?></td>
<td><?=$row['kiado']?></td>
<td><?=$row['megjelenesev']?></td>
<td><?=$row['isbn']?></td>
<td>
<a class="btn btn-success btn-sm" href="edit-book.php?bookid=<?=$row['id']?>">
<i class="fa fa-edit"></i> Szerkesztés
</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
Ezen az oldalon egy űrlapot jelenítünk meg, amely a books.php
oldalon látotthoz hasonló, azonban eleve fel van töltve a könyv aktuális adataival. Lesz továbbá egy "Mentés" gomb, amely elküldi az űrlap tartalmát. Kiteszünk mégegy gombot, amellyel törölhetjük az aktuális gombot.
<?php
include 'library.php';
$link = getDb();
?>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="library.css">
<title>Könytár</title>
</head>
<body>
<?php include 'menu.html'; ?>
<div class="container main-content">
<?php
if (!isset($_GET['bookid'])) {
header("Location: books.php");
return;
}
$bookid = $_GET['bookid'];
$query = sprintf("SELECT id, isbn, cim, szerzo, kiado, megjelenesev FROM konyv where id = %s",
mysqli_real_escape_string($link, $bookid)) or die(mysqli_error($link));
$eredmeny = mysqli_query($link, $query);
$row = mysqli_fetch_array($eredmeny);
if (!$row) {
header("Location: books.php");
return;
}
?>
<h1>Könyv adatainak módosítása</h1>
<form method="post" action="">
<input type="hidden" name="id" id="id" value="<?=$bookid?>" />
<div class="form-group">
<label for="isbn">ISBN</label>
<input class="form-control" name="isbn" id="isbn" type="text" value="<?=$row['isbn']?>" />
</div>
<div class="form-group">
<label for="cim">Cím</label>
<input required class="form-control" name="cim" id="cim" type="text" value="<?=$row['cim']?>" />
</div>
<div class="form-group">
<label for="szerzo">Szerző</label>
<input class="form-control" name="szerzo" id="szerzo" type="text" value="<?=$row['szerzo']?>" />
</div>
<div class="form-group">
<label for="kiado">Kiadó</label>
<input class="form-control" name="kiado" id="kiado" type="text" value="<?=$row['kiado']?>" />
</div>
<div class="form-group">
<label for="megjelenesev">Megjelenés éve</label>
<input class="form-control" name="megjelenesev" id="megjelenesev" type="number" value="<?=$row['megjelenesev']?>" />
</div>
<input class="btn btn-success" name="update" type="submit" value="Mentés" />
<input class="btn btn-danger" name="delete" type="submit" value="Törlés" />
</form>
<?php
closeDb($link);
?>
</div>
</body>
</html>
A fenti kódrészlet tehát lekérdezi az URL-ből az aktuális könyv azonosítóját, ehhez a $_GET
asszociatív tömböt használjuk. Amennyiben az URL-ben nincsen megadva a azonosító, vagy az aktuális könyv nem létezik, akkor visszanavigálunk a books.php
oldalra. Ehhez a következő php parancsot használjuk:
header("Location: books.php");
Látható, hogy két gombot is megjelenítünk, az egyik a mentés, a másik a törlésre való. Ha a mentés gombbal küldjük el az űrlapot, akkor az udate
paraméter lesz benne a kérésben, tehát a $_POST
tömbben. Ha a törléssel küldtük el az űrlapot, akkor a delete
parameter lesz elérhető. Ezek az értékek a megfelelő gomb name
attribútumából következnek.
Az űrlapban nem csak a felhasználó számára látható beviteli mezőket jeleníthetünk meg, hanem ún. rejtett (hidden) mezőket is. Ez egy olyan input
elem, aminek a type
attribútumát hidden értékre állítjuk, majd a value
attribútumába megadjuk azt az értéket, amit szeretnénk elküldeni az űrlappal. Ezt a mezőt a felhasználó számára nem jeleníti meg a böngésző, de az űrlap elküldésekor a tartalma ugyanúgy bekerül a paraméterek közé, mint az egyéb beviteli mezőké. Jelen esetben a rejtett mezőt arra használjuk, hogy az aktuális könyv azonosítóját eltároljuk. Természetesen használhatnánk ugyanerre az URL-ben lévő azonosítót is.
Már csak az a két PHP kódrészlet hiányzik, amellyel feldolgozzuk a mentés és törlés funkciókat:
<?php
include 'library.php';
$link = getDb();
//modositas
$successful_update = false;
if (isset($_POST['update'])) {
$id = mysqli_real_escape_string($link, $_POST['id']);
$cim = mysqli_real_escape_string($link, $_POST['cim']);
$szerzo = mysqli_real_escape_string($link, $_POST['szerzo']);
$kiado = mysqli_real_escape_string($link, $_POST['kiado']);
$isbn = mysqli_real_escape_string($link, $_POST['isbn']);
$megjelenesev = mysqli_real_escape_string($link, $_POST['megjelenesev']);
if (!$cim) {
die('A cím nem lehet üres');
} else {
$query = sprintf("UPDATE konyv SET cim='%s', szerzo='%s', kiado='%s', isbn='%s', megjelenesev=%s WHERE id=%s",
$cim, $szerzo, $kiado, $isbn, $megjelenesev, $id);
mysqli_query($link, $query) or die(mysqli_error($link));
$successful_update = true;
}
} else if (isset($_POST['delete'])) {
$query1 = sprintf('DELETE FROM kolcsonzes WHERE konyvid = %s',
mysqli_real_escape_string($link, $_POST['id']));
$query2 = sprintf('DELETE FROM konyv WHERE id = %s',
mysqli_real_escape_string($link, $_POST['id']));
$ret1 = mysqli_query($link, $query1) or die(mysqli_error($link));
$ret2 = mysqli_query($link, $query2) or die(mysqli_error($link));
header("Location: books.php");
return;
}
?>
A members.php
oldal pontosan ugyanolyan módon működik, mint a books.php
, csak itt nincsen keresés és a tagokat eleve abc szerinti sorrenben listázzuk ki. Az edit-member.php
szintén az edit-book.php
analógiájára készült.
Kicsit speciálisabb a kölcsönzések oldala:
- Itt is megjelenítjük egy táblázatban a kölcsönzéseket.
- Azoknál a kölcsönzéseknél, amelyeket még nem hoztak vissza, minden sorba kiteszünk egy "Visszahozatal mai dátummal" gombot. Ennek célja, hogy az aktuális kölcsönzést egyből módosítsuk és a mai dátummal visszahozzuk a könyvet. Értelemszerűen azoknál a kölcsönzéseknél, amelyeket már visszahoztak, erre a gombra nincs szükség.
- Megjelenítünk minden sorban egy "Törlés" gombot, amellyel közvetlenül lehet törölni az aktuális kölcsönzést.
- Végezetül kiteszünk egy szerkesztés gombot, azonban csak azokhoz a kölcsönzésekhez, amelyeket még nem hoztunk vissza. A már lezárt kölcsönzéseket csak törölni lehet.
Minden műveletet az edit-lending.php
fog végrehajtani, amelynek paraméterül átadjuk az aktuális kölcsönzés azonosítóját. Tehát például a 2-es kölcsönzés szerkesztését a következő linkek végezetjük el:
edit-lending.php?lendingid=2
Amennyiben törölni akarjuk, akkor még a delete paramétert is beletesszük az URL-be:
edit-lending.php?lendingid=2&delete
Végül volt egy gomb, amivel az aktuális dátummal visszahozzuk a kölcsönzést, ennek URL-je így néz ki:
edit-lending.php?lendingid=2&returnCurrentDate
Először nézzük a kölcsönzések listáját és a megfelelő gombok kirajzolását.
<?php
$query = "SELECT kolcsonzes.id, tagid, kivitel, konyvid, kivitel, vissza, tag.nev as tagnev, konyv.cim as cim FROM kolcsonzes INNER JOIN tag ON tag.id = tagid INNER JOIN konyv ON konyvid=konyv.id";
$eredmeny = mysqli_query($link, $query) or die(mysqli_error($link));
?>
<table class="table table-striped table-sm table-bordered">
<thead class="thead-dark">
<tr>
<th>Tag</th>
<th>Könyv</th>
<th style="white-space:nowrap;">Kivitel</th>
<th style="white-space:nowrap;">Visszahozatal</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<?php while ($row = mysqli_fetch_array($eredmeny)): ?>
<tr>
<td><?=$row['tagnev']?></td>
<td><?=$row['cim']?></td>
<td>
<?=$row['kivitel']?>
</td>
<td>
<?php if ($row['vissza']): ?>
<?=$row['vissza']?>
<?php else: ?>
<a class="btn btn-success btn-sm" href="edit-lending.php?lendingid=<?=$row['id']?>&returnCurrentDate">
<i class="fa fa-calendar"></i> Vissza mai dátummal
</a>
<?php endif; ?>
</td>
<td>
<?php if (!$row['vissza']): ?>
<a class="btn btn-success btn-sm" href="edit-lending.php?lendingid=<?=$row['id']?>">
<i class="fa fa-edit"></i>
</a>
<?php endif; ?>
</td>
<td>
<a class="btn btn-danger btn-sm" href="edit-lending.php?lendingid=<?=$row['id']?>&delete">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
Szintén speciális az új kölcsönzés felviteléhez szükséges űrlap. Egy kölcsönzéshez ki kell választani egy könyvet és egy tagot. Természetesen megadhatnánk az adott könyv és tag azonosítóját, de ez nem lenne túl felhasználóbarát. Ráadásul tudnunk kellene, hogy melyik könyvek vannak éppen kikölcsönözve. Egy egyszerű és felhasználóbarát megoldás, hogy legördülő listát biztosítunk, vagyis felsoroljuk a lehetséges tagokat és a lehetséges könyveket. Itt azonosítók helyett a tagok neveit és a könyvek címeit használjuk.
Erre a select
elemet használjuk. A select
is lehet eleme egy form
nak és hasonlóan működik, mint az input
elemek. A select
nek is megadunk egy name
attribútumot, így a kiválasztott érték itt is bekerül majd az űrlap adatai közé. A lehetséges értékeket option
elemek jelzik. Minden könyvnél az adott könyv címét fogjuk megjeleníteni, azonban azt szeretnénk, hogy ha kiválasztunk egy címet, akkor az aktuális érték ne a cím, hanem a könyv azonosítója legyen, hiszen arra lesz majd szükség az új kölcsönzés létrehozásánál. Erre az option
elem value
attribútumát használjuk. Figyeljünk arra is, hogy a lekérdezésnél eleve csak azokat a könyveket választjuk ki, amelyekhez nincsen aktív kölcsönzés.
Hasonló módon működik a tagok listázása.
<form method="post">
<div class="card">
<div class="card-header">
Új kölcsönzés
</div>
<div class="card-body">
<div class="form-group">
<label for='tagid'>Tag</label>
<select class="form-control" name='tagid' id='tagid'>
<?php
$queryMembers = 'SELECT id, nev FROM tag';
$resultQueryMembers = mysqli_query($link, $queryMembers) or die(mysqli_error($link));
while ($rowMember = mysqli_fetch_array($resultQueryMembers)):
?>
<option value="<?=$rowMember['id']?>"><?=$rowMember['nev']?></option>
<?php endwhile; ?>
</select>
</div>
<div class="form-group">
<label for='konyvid'>Könyv</label>
<select class="form-control" name='konyvid' id='konyvid'>
<?php
$queryBooks = 'SELECT id, cim, isbn FROM konyv WHERE id NOT IN (SELECT konyvid FROM kolcsonzes WHERE vissza IS NULL)';
$resultQueryBooks = mysqli_query($link, $queryBooks) or die(mysqli_error($link));
while ($rowBook = mysqli_fetch_array($resultQueryBooks)):
?>
<option value="<?=$rowBook['id']?>"><?=$rowBook['cim']?> (<?=$rowBook['isbn']?>)</option>
<?php endwhile; ?>
</select>
</div>
</div>
<div class="card-footer">
<input class="btn btn-success" name="create" type="submit" value="Létrehozás mai dátummal" />
</div>
</div>
</form>
A fenti kód eredményekképpen tehát az elküldött kérésbe a konyvid
és tagid
paraméterek is bekerülnek, mert ezeket adtuk meg a select
elemek name
attribútumában.
A kivitel dátumát nem adjuk meg külön, amikor létrehozzuk az új kölcsönzést, a mai dátumot használjuk majd. A létrehozás gomb megnyomását úgy tudjuk majd ellenőrizni, hogy a create
paraméter be lesz állítva a HTTP kérésben.
Végezetül nézzük a létrehozást intéző PHP kódrészletet:
<?php
include 'library.php';
$link = getDb();
$create = false;
if (isset($_POST['create'])) {
$tagid = mysqli_real_escape_string($link, $_POST['tagid']);
$konyvid = mysqli_real_escape_string($link, $_POST['konyvid']);
$query = sprintf("INSERT INTO kolcsonzes (tagid, konyvid, kivitel) VALUES (%s, %s, curdate())", $tagid, $konyvid);
mysqli_query($link, $query) or die(mysqli_error($link));
$create = true;
}
?>
Utolsó lépés a kölcsönzéseket szerkesztő oldal, vagyis az edit-lending.php
. Ez hasonlóan működik, mint a korábbi szerkesztő oldalak.
- Ugyanúgy megjelenítünk egy űrlapot, amelyen szerkeszthetők az egyes mezők. A dátumok szerkesztésére
date
típusúinput
mezőket használunk majd. - Az aktutális adatok lekérdezésénél nem csak a tag és könyv azonostókat, hanem a kapcsolódó könyv címet és tag nevét is lekérdezzük és ezt jelenítjük meg.
- A kapcsolódó könyvet és tagot, továbbá a kiviteli dátumot nem engedjük módosítani (
readonly
attribútum a megfelelőinput
elemeken). - Az oldal elején lévő php szkriptben nem csak a törlést és lérehozást kell kezelni, hanem a korábbi oldalon leírt "visszahozás mai dátummal" paramétert is.
A teljes oldal kódja megtalálható az edit-lending.php
oldalon.