Tutorials

Wiki ping. Doe meer wiki's

Wiki's zijn een ideaal voor een community. Nu even hoe je deze wiki nog meer kan verbeteren.

Pagina 1

Wiki ping

Net voordat ik aan mijn stage begon hebben wij besloten om met wat vaste bezoekers van PHPhulp een nieuwe PHP site op te zetten. Geen forum maar handige script en classes database. Om zo veel mogelijke kennis proberen te verzamelen.

Na een paar uur denken was CodeLabs.nl snel geboren. Omdat op nieuw het wiel uitvinden zonde van de tijd is hebben wij in dit geval gekozen voor Wikka Wiki.

Tijdens het modden van de wiki kwam ik achter een leuke mogelijkheid namelijk wikiping. Aangezien ik ook nog een andere PHP site had (PHPnieuws.nl) leek het me leuk om een het in PHPnieuws te impleteren. Helaas was de wikiping gedeelte in de handleiding erg onduidelijk en kon ik maar weinig informatie vinden over het protocool. Wel was ik er achter dat het doormiddel van XML ging. En de beschikbare vermelde wikiping server werkte niet.

Na een lange tijd op de tekentafel ben ik tijdens mijn stage er toch maar aan begonnen. En zo moeilijk was het eigenlijk niet.

Voorbeeld
Pagina 2

Wat is een wikiping

Sommige wiki onder steunen de mogelijkheid om een XML request te versturen naar andere web server wanneer er een pagina wordt aangepast.

Waarom niet via RSS of atom. Alle wiki's ondersteunen immers dat. Hellaas heeft RSS / Atom ook nadelen. Als eerste werkt het nooit real time en dien je elke keer een bestand op te vragen. Zo zou kunnen zeggen dan cache je het toch. Een ander voordeel deze xml betand is kleiner dan de RSS feed en bespaard zowel voor de wiki als ontvanger brandbreedte.

Daarnaast denken jullie misschien waarom gewoon niet doormiddel van Mysql. PHPnieuws.nl en CodeLabs.nl staan immers op de zelfde server en ik kan zonder problemen verbinding maken met mysql. Dit zelfs zonder enige aanpassingen. Daarnaast een SQL query doen. En klaar is Eris. Ja dat klopt alleen het werkt niet voor andere wiki's en misschien is het voor hun ook in de toekomst interessant.
Pagina 3

XML bestand

Voorbeeld van een XML bestand

<methodCall>
<methodName>wiki.ping</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>tag</name>
<value>CodeLabs</value>
</member>
<member>
<name>url</name>
<value>http://www.codelabs.nl/CodeLabs</value>
</member>
<member>
<name>wiki</name>
<value>Codelabs.nl</value>
</member>
<member>
<name>author</name>
<value>EriS</value>
</member>
<member>
<name>authorpage</name>
<value>http://www.codelabs.nl/EriS</value>
</member>
<member>
<name>history</name>
<value>http://www.codelabs.nl/CodeLabs/revisions</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>


Zoals je zit zijn alleen de waardes tussen de member tags interessant
Pagina 4

De script


<?php
/**
 * @author Eris First setup
 * @license GPL 2.0 or newer
 * @date 02-02-2007
 * Description: Recives a wikiping from a wiki and put it into a database.
 * Written for PHPnieuws.nl
 * 
 * Due leak of ability to test a lot with different wiki's 
 * I can't tell any thing about the support of other wiki's
 * Script is written for WikkaWiki (http://www.wikkawiki.org)   
 */
error_reporting(0);
//Connect towards the database
if($link = mysql_connect('localhost','root',''))
{
  if(!mysql_select_db('test',$link))
  {
    $link = false;

  }
}
if(!$link)
{
$error = 'Unable to connect towards the database';
}
  
//define the data array;
$data = array();
//strip any non supported tags
$allowed_keys = array('tag','url','wiki','author','authorpage','history');
if(!empty($HTTP_RAW_POST_DATA))
{
  //we could not use SimpleXML due no support for PHP 4
  $xml_praser = xml_parser_create();
  //tell the vars to the xml phrasers
  xml_parse_into_struct($xml_praser,$HTTP_RAW_POST_DATA,$xml_keys,$xml_index);

  xml_parser_free($xml_praser);
  foreach($xml_index['MEMBER'] as $key)
  {

    if($xml_keys[$key]['type'] == 'open')
    {
      if(in_array($xml_keys[$key + 1]['value'],$allowed_keys))
      {
        //don't ask me why but this seems to be working
        $data[$xml_keys[$key + 1]['value']] = $xml_keys[$key + 3]['value'];
      }    
    }
  }
  if(!empty($data))
  {
    //put data into database
    if($link)
    {
      $sql = 'insert into wikiping set';
      //create sql QUERY
      foreach($data as $label => $value)
      {
       $sql.= ' '.$label.'="'.mysql_real_escape_string($value,$link).'",';
      }
      //add a date
      $sql.= ' date_received = now()';
      //send to database
      if(!mysql_query($sql,$link))
      {
        $error = 'Error during insert into database';
      }
    }
    else
    {
    $error = 'No valid data availble';
    }
  }
}
else
{
    $error = 'No Raw post data availble';
}
if (isset($error))
{
  echo '<?xml version="1.0" encoding="utf-8"?'.">\n";
  echo "<response>\n";
  echo "<error>1</error>\n";
  echo "<message>$error</message>\n";
  echo "</response>";
}
else
{
  echo '<?xml version="1.0" encoding="utf-8"?'.">\n";
  echo "<response>\n";
  echo "<error>0</error>\n";
  echo "</response>";
}
?>
Pagina 5

Uitleg van de PHP code

error_reporting(0);


Schakel uit veiligheid alle errors uit. De wiki is niet geintreseert in de PHP errors. Daarnaast willen wij de hacker geen gevoelige informatie geven.

<?php
if($link = mysql_connect('localhost','root',''))
{
  if(!mysql_select_db('test',$link))
  {
    $link = false;
  }
}
if(!$link)
{
$error = 'Unable to connect towards the database';
}
?>

Maak verbinding met de Mysql server. Wij slaan de resultaat op in een var. Daarna kijken wij of we de database naam kunnen selecteren. Gaat dat niet dan maken wij de resource ($link) ongeldig.

Wanneer het mislukt setten wij een error.

<?php
//define the data array;
$data = array();
//strip any non supported tags
$allowed_keys = array('tag','url','wiki','author','authorpage','history');
?>

Defineer een lege array waar de data in komt. Om te voorkomen dat de wiki die de wikiping verstuurd ongeldige data mee stuurt (Tags die niet worden ondersteund) gaan wij deze tags filteren. Het is nog wel mogelijk om html en andere gevaarlijke dingen mee te sturen. Die worden tijdens het uitlezen verwijderd.

<?php
if(!empty($HTTP_RAW_POST_DATA))
{
?>

Wat blijkt wanneer je door middel van een HTTP post request gegevens verstuurd kan je in PHP deze gegevens niet in de $_POST array vinden. Maar hoe moet het dan anders? Ik heb met dit probleem echt 4 dagen zitten te klooien en omdat ik geen Internet verbinding had kon ik het even niet snel vragen op een internet forum. Na uren de handleiding door lezen kwam ik er nog steeds niet achter. En op het moment dat ik de moed wou opgeven kwam ik achter deze functie.

<?php
get_defined_vars()
?>

En toen kwam ik er achter dat $HTTP_RAW_POST_DATA bestond en dat daar de gegevens instaan als je door middel van HTTP Post request data verstuurd.
Nu controleren wij of deze variabele bestaat zo niet dan stoppen wij er mee . En uiteraard geven wij een error op het einde. Zie einde van het script.

<?php
  //we could not use SimpleXML due no support for PHP 4
  $xml_praser = xml_parser_create();
  //tell the vars to the xml phrasers
  xml_parse_into_struct($xml_praser,$HTTP_RAW_POST_DATA,$xml_keys,$xml_index);
  xml_parser_free($xml_praser);
?>

Nu komt het grootste probleem. De Post request bestaat uit een XML bestand en direct een XML bestand invoegen in een database is niet slim. We zullen deze gegevens eerst moeten verwerken.

Hoewel ik deze script op een PHP 5 server heb geschreven moest hij werken op een PHP4 server. Hier door kon ik geen gebruik maken van de Simple XML extensie.

De PHP DOM functies was ook niet ideaal omdat daar net alles wordt omgezet van functies naar classes. Conclusie dit was de meest ideale cross "server" oplossing.

Wat doen we hier.
[ol]
[*] We maken een XML praser aan
[*] We vertellen PHP dat hij de var $HTTP_RAW_POST_DATA moet verwerken naar 2 vars. 1 met de waardes en de andere met de key namen.
[*] Daarna moet hij het verwerken
[/ol]

Hoe het exact werkt weet ik niet maar het werkt. Vergeet niet dat alle array keys UPPERCASE zijn.

Nu komt werkelijk het verwerken van de gegevens.

Wij hebben nu 2 arrays. 1 array ($xml_keys) bevat een $array met alle benodige gegevens. Hellaas zijn daar alle XML tag namen vervangen door een integer. Waar wij hellaas niets mee kunnen. We zouden dan een onhandig loop constructie moeten doen.

Gelukkig hebben wij daarbij ook nog een andere array namelijk de $xml_index. In deze array zijn alle tags op "alfabet" gesorteerd en alle keys met de zelfde naam zijn onder een andere array gevoegd. Dit maakt alles nu 100 keer simpeler.

Wat gaan wij doen. Wij zoeken naar alle keys die de waarde "MEMBER" zouden hebben in de XML document.

<?php
  foreach($xml_index['MEMBER'] as $key)
  {

    if($xml_keys[$key]['type'] == 'open')
    {
      if(in_array($xml_keys[$key + 1]['value'],$allowed_keys))
      {
        //don't ask me why but this seems to be working
        $data[$xml_keys[$key + 1]['value']] = $xml_keys[$key + 3]['value'];
      }    
    }
  }
?>

Wij doen dat simpel gewoon met een foreach loop. Nu komt het leuke. Als wij alle indexen met de waardes van "member" zullen gebruiken hebben wij ook de sluit tags en de cdata tags. En die moeten wij niet. Dus wij kijken doormiddel van een if statment of de type "open" is.

Maar nu komt het volgende probleem zoals je zit staan de gegevens van de naam van de waarde niet in de MEMBER tag maar in een subtag. Dus wij tellen er bij. Controleren of de waarde in de $allowed_keys staat. Zo ja dan voegen wij hem toe. Zo niet. Gewoon doorgaan

Is het waar dan stoppen wij de waarde van de name in de array als "key" en de waarde van de value tag ($index +3) als "waarde" in de array.

Na afloop van de deze script krijg je dit als antwoord

    Var $data:
array(
'tag' => 'CodeLabs',
'url' => 'http://www.codelabs.nl/CodeLabs',
'wiki' => 'CodeLabs',
'author' => 'EriS',
'authorpage' => 'http://www.codelabs.nl/EriS',
'history' => 'http://www.codelabs.nl/CodeLabs/revisions'
)

Nu is het simpel trappen alle gegevens in een mysql database.

Kijk hier voor de tabel structuur (SQL code).

Dit doen wij heel simpel

<?php
  if(!empty($data))
  {
    //put data into database
    if($link)
    {
?>

Wij kijken eerst of er wel iets instaat anders hoeven wij ook niets in te voeren.

Daarna controleren wij of wij een database verbinding hebben zo niet gaat het verhaaltje niet door en maken wij alleen errors.

<?
     $sql = 'insert into wikiping set';
      //create sql QUERY
      foreach($data as $label => $value)
      {
       $sql.= ' '.$label.'="'.mysql_real_escape_string($value,$link).'",';
      }
      //add a date
      $sql.= ' date_received = now()';
?>

INSERT INTO wikiping set lijk mij duidelijk. Wij willen de gegevens in de tabel wikiping trappen.

Daarna lopen wij door de data array waarbij wij de key en waarde gaan gebruiken om de SQL op te bouwen. Omdat wij hebben gecontroleerd dat de key van geldig is het niet nodig om onze database te beschremen voor mysql injection. Voor de waarde moet dat uiteraard wel. We zouden addslashes() kunnen gebruiken maar mysql_real_escape_string is veiliger. Uiteraard heeft deze wel een waarde en de database verbinding nodig. Wanneer wij klaar zijn met de array voegen wij als laatst de toevoeg datum toe. Die wordt niet met de XML request mee gezonden. Hier zorgen wij dat wij op soorteren op laatst toegevoegd. Bij het uit lezen. Omdat er maximaal 1 misschien 2 seconden verschil is het niet zo erg dat het de server datum is. Andere tijdzones hebben maar pech gehad.

<?php
      if(!mysql_query($sql,$link))
      {
          $error = 'No valid data availble';
      }
    }
  }
?>

Wij voegen het nu in de database en vangen eventueel de error en geven een error als het mislukt. Als het goed gaat melden wij niets en ronden wij de script af.

<?php
else
{
     $error = 'No Raw post data availble';
}
?>

Nu komt nog een error van de eerste controle

<?php
if (isset($error))
{
  echo '<?xml version="1.0" encoding="utf-8"?'.">\n";
  echo "<response>\n";
  echo "<error>1</error>\n";
  echo "<message>$error</message>\n";
  echo "</response>";
}
else
{
  echo '<?xml version="1.0" encoding="utf-8"?'.">\n";
  echo "<response>\n";
  echo "<error>0</error>\n";
  echo "</response>";
}
?>

Wij geven nu een bericht terug. Met daarin de resultaat.
Pagina 6

Uitlezen

Nu heb je gegevens in een database en wil je het uit lezen. Aangezien wij hier phpers zijn die wel een bassis kennis hebben ga ik deze script niet echt uit leggen. De werking van de script lijkt mij duidelijk.

<?php
/**
 *  @author Eris
 *  @license LGPL 2.0 or newer
 *  @date 03-02-2007
 *  Description: Read database and send it towards the browser
 */

if($link = mysql_connect('localhost','root',''))
{
  if(!mysql_select_db('test',$link))
  {
    $link = false;
    echo 'Unable to connect to database';
  }
}
if($link)
{
  $sql = 'select * from wikiping order by date_received desc LIMIT 0,30';
  //execute query 
  if(!$result = mysql_query($sql,$link))
  {
   echo 'Error: '. mysql_errno() .':'. mysql_error();
  }
  else
  {
    
   while($row = mysql_fetch_assoc($result))
   {
      //prevent XSS shit
      foreach($row as $label => $value)
      {
        $row[$label] =  htmlspecialchars(preg_replace('/&amp;/','&',$value),ENT_COMPAT);
      }  
      echo '<a href="'.$row['url'].'">'.$row['tag'].'</a>
 op '.$row['wiki'].' 
Geschreven door <a href="'.$row['authorpage'].'">'.$row['author'].'</a> 
op '.date('d-m-Y',strtotime($row['date_received'])).'. 
<a href="'.$row['history'].'">Geschiedenis</a><br />';     
   }
  }
}
?>


Enige stukje code die er even uit wil halen is dit

<?php
      foreach($row as $label => $value)
      {
        $row[$label] =  htmlspecialchars(preg_replace('/&amp;/','&',$value),ENT_COMPAT);
      } 
      ?>

Als ik het wil kan ik in de XML request bepaalde dingen mee sturen die ik in mijn "pagina" niet wil hebben alleen. wanneer er gebruik wordt gemaakt van & in de urls moet dat eerst even worden omgezet. Anders krijgen wij problemen met de links als wij later linken.

Uiteraard kan je de weergave van de updates zelf aanpassen. Net zoals pings die selecteerd. Alleen de laatste ping van elke pagina wilt hebben. Ga je gang dat mag je zelf doen. Zelfde als de weer gave. Misschien een mooie tabel of iets anders.
Pagina 7

Tabel Structuur

De Tabel

CREATE TABLE wikiping (
  tag varchar(255) NOT NULL,
  url varchar(255) NOT NULL,
  wiki varchar(255) NOT NULL,
  author varchar(255) NOT NULL,
  authorpage varchar(255) NOT NULL,
  history varchar(255) NOT NULL,
  date_received datetime NOT NULL,
  KEY tag (tag)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Pagina 8

Ondersteuning van wiki's

De volgende wiki's ondersteunen het:
Naam Aanpassingen
WikkaWika URL van de ping bestand toevoegen in wikka.config.php op regel 45


Heb je zelf een toevoeging PM me even...
Pagina 9

Waarom zo uitgebreid

Als je deze tutorail zal lezen zal je denken dit is geen tutorial. Maar een script en hij hoort dus niet onder de tutorails. Maar ik denk dat je door gebruikte technieken toch even interessant is wat er gebreurd. En ik denk dat bijna niemand hier een website heeft die deze wiki ping zo kan gebruiken. Maar misschien wil je samen met een colega blogger een ping systeem op zetten. En kan je deze code goed gebruiken. Alleen met veel aanpassingen.

Reacties

0
Nog geen reacties.