Gebruikerslogin

Nu online

Er zijn momenteel 1 gebruiker en 16 gasten online.

Online gebruikers

Enquête

Wat is leuker?
White hat SEO
70%
Black hat SEO
30%
Totaal aantal stemmen: 98

Hoe je een modulair CMS kan opzetten

Erik-Jan Bulthuis

10 maart 2007

Gezien het aantal hits wat we binnenkrijgen op een eigen CMS bouwen, is hier nogal wat vraag naar. Nu sta ik echter voor een probleem. Ik heb inmiddels een siteje of 100/200 van een CMS voorzien, dus dat zit wel snor - maar wat is de concrete vraag áchter de vraag "Hou bouw ik een CMS?" Wat is de startsituatie van zo'n vragensteller en waar zitten de vragen? Laat ik daarom maar eens beginnen met een omschrijving te geven van de doelgroep die ik met dit artikel heb:

Als jij tot de doelgroep van dit artikel behoort...

  • ... heb je basis kennis en ervaring met PHP en MySQL. MySQL termen als SELECT en INSERT en de 90% meest gebruikte PHP functies hebben voor jou geen geheimen meer (of je weet waar je er informatie over kan vinden.
  • ... weet je hoe cookies en/of sessies werken. Sterker nog, een loginsysteem gebaseerd op cookies draai je zo in elkaar, met of zonder MD5 encryptie.
  • ... ben je in staat zelf kritisch mee te denken. Ik ga je in dit artikel niet een 72-stappenplan tot een compleet werkend systeem bieden, maar ik ga een aantal belangrijke zaken bespreken. De boel in elkaar zetten mag je zelf doen (of je huurt een willekeurige PHP programmeur in ;-)

Backoffice en frontoffice

De termen 'backoffice' en 'frontoffice' zou ik eigenlijk als bekend willen veronderstellen, maar voor de zekerheid: De backoffice is dat deel van de site waar de content beheerd wordt. Je hebt hier in de regel enkel toegang tot als je over een user/pass combinatie beschikt. De frontoffice is dat deel van de site wat de bezoeker ziet em waar de content wordt geprojecteerd. Je ziet dus dat mijn definitie van "site" zowel de backoffice als de frontoffice beslaat.

Nu is de praktijk van het programmeren dat het bouwen van de backoffice ongeveer 3 keer zo veel werk is als het bouwen van de frontoffice. Als de backoffice (het beheren van content) eenmaal staat, is het uitlezen niet veel werk meer.

Opmerking:
Hier zijn natuurlijk uitzonderingen voor te bedenken. Zeker een usersysteem waarbij de user kan registreren, inloggen, zijn wachtwoord opvragen enz. vraagt redelijk wat werk aan de frontoffice. Pak je echter een nieuwssysteempje, dan is bovenstaande uitspraak zeker waar.

Volgorde van programmeren

De basis van je PHP werk is je MySQL ontwerp. Vandaar dat de eerste stap van het ontwikkelen van een CMS het ontwerpen van de database is. Omdat we een modulair systeem gaan bouwen, zullen de databases niet al te zeer naar elkaar gelinkt zijn en is het ontwerp dus eenvoudig. Wat ik vaak doe ik een A4'tje maken waarop ik de database uitteken. Deze hou ik altijd bij de hand bij het programmeren.

Loginsysteem bouwen

Voor de backoffice hebben we uiteraard een loginsysteem nodig. Nu is het in de meeste gevallen best afdoende als je via bijvoorbeeld Plesk de betreffende directory beveiligd, maar ik ga nu even uit van een situatie waarin je meerdere users kan hebben. Voor deze users gebruik ik de volgende tabellen:

adminUsers:
+----+------+------+
| id | user | pass |
+----+------+------+

adminPogingen:
+----+--------+------+--------+-------+-----------+----+
| id | userID | user | status | datum | userAgent | ip |
+----+--------+------+--------+-------+-----------+----+

Het loginsysteem zelf zal met cookies gaan werken. Voor de veiligheid maken we gebruik van MD5() encryptie voro het wachtwoord. Ook hebben we interesse in het in de gaten houden van wanneer wie heeft ingelogd en of dat gelukt is of niet. Oftewel, alle loginpogingen worden opgeslagen. Voor het inloggen hebben we twee functies nodig:

<?php
function schrijfLoginPogingWeg($user$userID$gelukt){

    if(!
not_null($user)){
       
$user "Niet opgegeven";
    }
   
$query "INSERT INTO " DB_PRETABLE "adminPogingen (userID, user, status, datum, userAgent, ip) VALUES ('$user', '$userID', '$gelukt', " time() . ", '" $_SERVER['HTTP_USER_AGENT'] . "', '" $_SERVER['REMOTE_ADDR'] . "')";
   
mysql_query($query) or die (mysql_error());

}

function 
checkLogin($user$pass){

   
$result =   mysql_query("   SELECT  *
                                FROM    adminUsers
                                WHERE   user = '" 
$user "'
                                AND     pass = '" 
MD5($pass) . "'") or die (mysql_error());

    if (
mysql_num_rows($result) == 1){
   
       
// Er is succesvol ingelogd
       
$_SESSION['admin_userID']         = mysql_result($result0"id");
       
$_SESSION['admin_user']         = stripslashes($user);
       
       
// Schrijf een succesvolle loginpoging weg
       
schrijfLoginPogingWeg($user$_SESSION['admin_userID'], "1");

        return 
true;
       
    }else{
   
       
$result =   mysql_query("   SELECT  *
                                    FROM    adminUsers
                                    WHERE   user = '" 
$user "'") or die (mysql_error());
        if (
mysql_num_rows($result) == 1){
           
// De gebruiker bestaat wel
           
$userID mysql_result($result0"id");
        }else{
           
// De gebruiker bestaat wel
           
$userID 0;
        }
       
       
// Schrijf een niet-succesvolle loginpoging weg
       
schrijfLoginPogingWeg($user$userID"0");

        return 
false;
    }

}
?>

De loginprocedure die we in login.php zetten zal dan iets als dit moeten zijn:

<?php
if (!$_POST['submit']){

   
// Toon formulier
   
echo "  <h1>Inloggen</h1>
            <table border=\"0\">
                <form action=\"\" method=\"post\">
                   <tr>
                       <td width=\"80\">
                           <strong>Gebruikersnaam</strong>
                          </td><td>
                        <input type=\"text\" name=\"user\" style=\"width:150px;\">
                    </td>
                </tr><tr>
                       <td width=\"80\">
                           <strong>Wachtwoord</strong>
                          </td><td>
                        <input type=\"password\" name=\"pass\" style=\"width:150px;\">
                    </td>
                </tr><tr>
                       <td>
                           &nbsp;
                    </td><td>
                        <input name=\"submit\" type=\"submit\" value=\"Log in!\">
                    </td>
                </tr>
                </form>
            </table>"
;


}else{
   
   
// Check login
   
if (checkLogin($_POST[user], $_POST[pass])){

        echo 
"<h1>Inloggen</h1>";
        echo 
"<p>U bent ingelogd. Eén moment aub...</p>";
        echo 
"<META HTTP-EQUIV=refresh content=2;URL=index.php>";
       
    }else{
       
        echo 
"<h1>Inloggen</h1>";
        echo 
"<p>Inloggen mislukt. Eén moment aub...</p>";
        echo 
"<META HTTP-EQUIV=refresh content=2;URL=login.php>";

    }
}
?>

Op elke pagina waar de login verplicht is, zal nu iets als dit moeten komen te staan:

<?php
if(not_null($_SESSION['admin_userID'])){

   
header('location:index.php');

}
?>

Modulair bouwen

Met modulair bouwen doel ik op een CMS waarin je met zeer weinig moeite een extra module kan plaatsen. Dit betekent wat mij betreft dat de modules zeer snel in de backoffice hangen, maar ook dat de afzonderlijke modules niet met elkaar conflicteren. Dat laatste zou kunnen gebeuren als je bijvoorbeeld zowel in je nieuwsmodule als je artikelmodule een functie getItem() gebruikt en de files waarin deze functies staan beiden include.

Diverse bestanden includen

Als we bijvoorbeeld de backoffice in /beheer zetten, komen de modules in /beheer/modules/$moduleNaam. Dit betekent dat we met een PHP script de modules kunnen langslopen. Stel dat we in elke module-map een bestandje data.php zetten, dan kunnen in die bestandjes bijvoorbeeld de module-naam van het item noemen. Laten we voor nu deze inhoud maar eens in code.php zetten:

<?php
$moduleNaam 
"Nieuws";
?>

Via onderstaand script kunnen we dan een navigatiestructuur maken zodat we in één menu naar alle beschikbare modules kunnen linken.

<?php
function getModules(){

    if (
$handle opendir("modules")){

       
$return .= "\n<ul>\n";

       
// Doorloop alle submappen
           
while (($file readdir($handle)) !== false){

               if(
$file != "." && $file != ".." && file_exists($file)){

                include(
"modules/" $file "/data.php");

               
$return .= "\t<li><a href=\"index.php?module=" $file "\">" $moduleNaam "</a></li>\n";

            }

        }

       
closedir($handle);
       
$return .= "</ul>\n";

    }
   
    return 
$return;
   
}
?>

Als je in elke module een bestand output.php maakt met daarin de functies die nodig zijn voor de output, kun je ze op dezelfde manier includen. Let er hierbij wel op dat je voor unieke functienamen zorgt. Een optie zou zijn om de functienamen een prefix te geven die gelijk is aan hun directorie-naam in de map /modules.

Arjan
Avatar van Arjan
Aantal berichten: 532

Ik zie dit staan:

<?php
 
if($file != "." && $file != ".." && file_exists($datafile)){ 
?>

moet die $datafile niet gewoon $file zijn? De variabele $datafile wordt nl nergens gevuld. Of doe je daar wat anders mee?

Niet dat het waarschijnlijk heel veel uitmaakt, maar toch ;-)

lukeboy_2002
Aantal berichten: 2

Na het testen van dit script krijg ik de volgende fout melding:

Notice: Undefined variable: return in C:\Program Files\Apache Group\Apache2\htdocs\index.php on line 6

Erik-Jan
Avatar van Erik-Jan
Aantal berichten: 615

Bijzondere foutmelding. Geef de eerste 6 regels van je code eens?

Arjan
Avatar van Arjan
Aantal berichten: 532

als je de witruimte uit het voorbeeld weghaalt, kom je idd op regel 6 op de regel uit, waar ik het in mijn vorige post over had. heeft het misschien daar mee te maken?

lukeboy_2002
Aantal berichten: 2

dit is mijn code volgens mij compleet dezelfde als hier boven:

<?php
function getModules(){

    if (
$handle opendir("modules")){

       
$return .= "\n<ul>\n";

       
// Doorloop alle submappen
           
while (($file readdir($handle)) !== false){

               if(
$file != "." && $file != ".." && file_exists($file)){

                include(
"modules/" $file "/data.php");

               
$return .= "\t<li><a href=\"index.php?module=" $file "\">" $moduleNaam "</a></li>\n";

            }

        }

       
closedir($handle);
       
$return .= "</ul>\n";

    }
   
    return 
$return;
   
}
echo 
getModules()
?>

Arjan
Avatar van Arjan
Aantal berichten: 532

als ik het goed zie is regel 6 deze:

<?php
$return 
.= "\n<ul>\n";
?>

Je plakt daar een string meteen aan een variabele die nog niet eerder is gedefinieerd.

Nu is php wel een ranzig taal en zou dat best kunnen werken, maar je zou kunnen proberen om bovenaan in de functie de variabele te 'declareren' met een lege string. Dus:

<?php
$return 
"";
?>

Erik-Jan
Avatar van Erik-Jan
Aantal berichten: 615

Lukeboy, je zou eens kunnen proberen om $return .= "\n<ul>\n"; in regel 6 te vervangen door $return = "\n<ul>\n"; Het lijkt me wat vreemd als dat de oplossing is, maar dát is wel regel 6. Ik ben eigenlijk wel benieuwd welke PHP-versie je draait en hoe je error configuratie staat?