XML Search and addChild - php

Here is what I have so far, I want to add date/time logs to the xml file.
<?php
// Load the XML file we want to write to
$visitors = simplexml_load_file("data.xml");
// Set e-mail xml search
$search_id = htmlentities($_POST['email']);
$visitors = $visitors->xpath("visitor[email='$search_id']");
// If we have a search result, email already exists in xml file
if(isset($visitors[0])){
$visitor = $visitors[0];
$email = (string) $visitor->email;
// ********** Need to add date/time for each visit here.
// So, I guess I need to search for e-mail address, then append to it.
// Help required here... and again below to confirm formatting for multiple
// dates.
} else {
$xml = simplexml_load_file("data.xml");
$sxe = new SimpleXMLElement($xml->asXML());
$search_id = preg_replace('/[^(\x20-\x7F)]*/','', $search_id);
$visitor = $sxe->addChild("visitor");
$visitor->addChild("email", $search_id);
// ******** Not sure this is the correct xml formatting.
$date = $visitor->addChild('date');
$date->addChild("email", date("Y-m-d H:i:s"));
$sxe->asXML("data.xml");
} // if(isset($visitors[0])){
} // if(isset($data)){ ?>
Which generates
<?xml version="1.0" encoding="utf-8"?>
<visitors>
<visitor>
<email>jon.doe#aol.com</email>
</visitor>
</visitors>
What I want to do is add a date log (incementing/appending) for each visit.
So the result would be (I am not sure my formatting is correct, which of course is not helping to matters). Don't worry about the PHP for the date/time.
<?xml version="1.0" encoding="utf-8"?>
<visitors>
<visitor>
<email>jon.doe#aol.com</email>
<date>2012-11-01 11:00:00</date>
<date>2012-11-02 14:00:00</date>
</visitor>
</visitors>

I think you were on the right track here, the main thing is that your call to asXML('data.xml') is inside your ELSE block, so the file only gets written to file if it is being created from scratch (ie. The search_id is not found).
I made a few changes, mostly to make it easier to follow. More changes to come!:
<?php
// Load the XML file we want to write to
$xmlFile = simplexml_load_file("data.xml");
// Set e-mail xml search
$search_id = 'jon.doe#aol.com';
$results = $xmlFile->xpath("visitor[email='$search_id']");
// If we have a search result, email already exists in xml file
if(isset($results[0]))
{
$visitor = $results[0];
$email = (string) $visitor->email;
echo "Found Email: " . $email . "\n";
echo "Date: " . $visitor->date . "\n";
// ********** Need to add date/time for each visit here.
// So, I guess I need to search for e-mail address, then append to it.
// Help required here... and again below to confirm formatting for multiple
// dates.
echo "Adding new date";
$visitor->addChild('date', date('Y-m-d H:i:s'));
echo "New XML: " . $xmlFile->asXML();
$xmlFile->asXML('data.xml');
} else {
$sxe = new SimpleXMLElement($xmlFile->asXML());
$search_id = preg_replace('/[^(\x20-\x7F)]*/','', $search_id);
$visitor = $sxe->addChild("visitor");
$visitor->addChild("email", $search_id);
// ******** Not sure this is the correct xml formatting.
$date = $visitor->addChild('date', date('Y-m-d H:i:s'));
$sxe->asXML("data.xml");
}
?>
After running this script a few times, I got:
<?xml version="1.0"?>
<visitors>
<visitor>
<email>jon.doe#aol.com</email>
<date>2012-11-03 02:13:28</date>
<date>2012-11-03 02:20:20</date>
<date>2012-11-03 02:22:07</date>
<date>2012-11-03 02:22:10</date>
<date>2012-11-03 02:22:13</date>
</visitor>
</visitors>
Which I think is what you want? Does this help?
EDIT:
Regarding your mentioning of whether or not it's the correct XML format. I suppose there's no exclusive right answer. I would say that this format is fine, provided that you remember to separate each individual email address / set of dates in its own visitor tags.
To do that, you'd probably want something like so (untested):
$newVisitor = $xmlFile->addChild('visitor');
$newVisitor->addChild('email', $newEmail);
$newVisotor->addChild('date', $newDate);
$xmlFile->asXML('data.xml');
EDIT 2:
One last thing that I've used in the past is the following code. It will format the XML much nicer, and will also prevent any corruption of the original XML file if you have any issues mid-save (or at least give you a backup). Personally, I try to make a habit of using a temporary file like this any time I have to write to file.
Of course, just add this, then replace your calls to:
$xmlFile->asXML('data.xml');
$sxe->asXML("data.xml");
With:
writeXML('data.xml', $xmlFile);
writeXML('data.xml', $sxe);
And it should work OK.
//Writes final version of the SimpleXML object to file.
//Includes writing to a temp file for safety.
//This way is safer because if the code crashes mid-write it won't garble the original file.
function writeXML($fileName, $xmlObject)
{
//Sanity check before copy, prevent any visible errors:
if (file_exists($fileName))
copy($fileName, $fileName . '.backup');
//Write to temp file first in-case something kills the write; don't want to corrupt the original.
$tmpName = $fileName . '~$.' . microtime();
$handle = fopen($tmpName, 'w');
fwrite($handle, formatXML($xmlObject));
fclose($handle);
copy($tmpName, $fileName);
unlink($tmpName);
}
//asXML does no indentation formatting, so we'll do it here instead. This is called by WriteXML so that we're writing formatted XML to file.
function formatXML($xmlObject)
{
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xmlObject->asXML());
return $dom->saveXML();
}

This is an addition to #Geekmans answer
Your code does not look too far of. However you can greatly improve it which is why add another answer here (I would have normally edited #Geekmans answer but it's already very large).
The point of improvement is in the program flow. If the email is not found, you don't need to create a new SimpleXMLElement, you only need to create a new <visitor> element. Then adding the <date> element as well as outputting the file is actually the same for both cases. This allows to remove the duplicate code:
// Specify the XML file we want to write to
$inFile = "data.xml";
// Set e-mail xml search and insert id
$search_id = 'jon.doe#aol.com';
$insert_id = preg_replace('/[^(\x20-\x7F)]*/', '', $search_id);
// Load the XML file we want to write to
$xmlFile = simplexml_load_file($inFile);
$results = $xmlFile->xpath("visitor[email='$search_id']");
// If we have a search result, email already exists in xml file
if ($results) {
$visitor = $results[0];
echo "Found Visitor: ", $visitor->email, "\n",
"First Date: ", $visitor->date, "\n";
} else {
echo "Adding new Visitor: ", $insert_id, "\n";
$visitor = $xmlFile->addChild("visitor");
$visitor->addChild("email", $insert_id);
}
echo "Adding new Date: ", $date = date('Y-m-d H:i:s'), "\n";
$visitor->addChild('date', $date);
echo "Changed XML: \n" . simplexml_pretty_print($xmlFile);
$xmlFile->asXML($inFile);
/**
* #param SimpleXMLElement $simplexml
* #link https://stackoverflow.com/a/798986/367456
*/
function simplexml_pretty_print(SimpleXMLElement $simplexml) {
$dom = new DOMDocument();
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($simplexml->asXML());
return $dom->saveXML();
}
As a bonus I added the simplexml_pretty_print function to have the output in a nicer way.
The output for jon.doe#aol.com:
Found Visitor: jon.doe#aol.com
First Date: 2012-11-01 11:00:00
Adding new Date: 2012-11-03 10:48:05
Changed XML:
<?xml version="1.0" encoding="utf-8"?>
<visitors>
<visitor>
<email>jon.doe#aol.com</email>
<date>2012-11-01 11:00:00</date>
<date>2012-11-02 14:00:00</date>
<date>2012-11-03 10:48:05</date>
</visitor>
</visitors>
The output for info#example.com:
Adding new Visitor: info#example.com
Adding new Date: 2012-11-03 10:52:09
Changed XML:
<?xml version="1.0" encoding="utf-8"?>
<visitors>
<visitor>
<email>jon.doe#aol.com</email>
<date>2012-11-01 11:00:00</date>
<date>2012-11-02 14:00:00</date>
</visitor>
<visitor>
<email>info#example.com</email>
<date>2012-11-03 10:52:09</date>
</visitor>
</visitors>
Tip: Whenever you spot duplicate code, there often is a way to write it in a much simpler form.

Related

PHP appendChild to XML root

I know this should be simple, but I'm new to PHP and just do not understand the documentation I've come across. I need a simple explanation.
I have an XML document that I would like to add nodes to. I can add nodes to the document, they only appear outside of the root node and cause errors to happen on subsequent attempts.
Here is my XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
</root>
and here is my PHP:
$playerID = "id_" . $_POST['player'];
//load xml file to edit
$xml = new DOMDocument();
$xml->load('../../saves/playerPositions.xml') or die("Error: Cannot load file.");
//check if the player is already in the database
if(isset($xlm->$playerID)){
echo "Player ID was found.";
}
else{
echo "Player ID was not found.";
//so we want to create a new node with this player information
$playerElement = $xml->createElement($playerID, "");
$playerName = $xml->createElement("name", "John Doe");
//add the name to the playerElement
$playerElement->appendChild($playerName);
//add the playerElement to the document
//THIS IS WHERE THE ERROR OCCURS
$xml->root->appendChild($playerElement);
//save and close the document
$xml->formatOutput = true;
$xml->save("../../saves/playerPositions.xml");
}
echo "done";
If I just use $xml->appendChild() then I can modify the document, but the new text appears outside of <root></root>.
The exact error is:
Notice: Undefined property: DOMDocument::$root
$xml->root isn't the correct way to access root element in this context, since $xml is an instance of DOMDocument (It will work if $xml were SimpleXMLElement instead). You can get root element of a DOMDocument object from documentElement property :
$xml->documentElement->appendChild($playerElement);
eval.in demo 1
Or, more generally, you can get element by name using getElementsByTagName() :
$xml->getElementsByTagName("root")->item(0)->appendChild($playerElement);
eval.in demo 2
Further reading : PHP DOM: How to get child elements by tag name in an elegant manner?
That said, this part of your code is also not correct for the same reason (plus a typo?):
if(isset($xlm->$playerID)){
echo "Player ID was found.";
}
Replace with getElementsByTagName() :
if($xml->getElementsByTagName($playerID)->length > 0){
echo "Player ID was found.";
}
You are trying to handle the dom like a Standar Object, but is not.
To look for elements, you need to use the function getElementsByTagName, and handle the dom like a collection of nodes, like this:
$xml = new DOMDocument();
$xml->loadXML('<?xml version="1.0" encoding="UTF-8"?>
<root>
</root>') or die("Error: Cannot load file.");
$len = $xml->getElementsByTagName('player')->length;
if ( $len > 0 ) {
} else {
$player = $xml->createElement('player', '');
$playerName = $xml->createElement('playerName', "Jhon Doe");
$player->appendChild( $playerName );
$root = $xml->getElementsByTagName('root');
if ( $root->length > 0 ) {
$root[0]->appendChild( $player );
}
$xml->formatOutput = true;
echo $xml->saveXML();
}
This code produces:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<player><playerName>Jhon Doe</playerName></player></root>

XML Database Array doing weird things. Trying to authenticate login using XML and PHP

The Objective
To fetch the data from the XML "database" as a simple (and I know, very insecure) way of verifying login data.
The code here is that responsible for the database and for retrieving the values. Option one is what I originally tried, so it still contains the code which cross-references the input data of the username and password with the data in the XML database.
Option two is where I tried a different method. Neither seem to be working.
Screenshots attached.
Option One:
login.php:
<?php
if( !isset($_SESSION['user']) ) { ?>
<div>Please login to view your message log, and see whether anyone has left you a message.</div>
<form action="php/authenticate.php" method="POST" >
<input type="text" name="Username" />
<input type="password" name="Password" />
<input type="submit" value="Login" />
</form>
<?php };
$passwordValid = $_GET['error'];
if( $passwordValid == 1 ) {
echo "Falsches Password";
};
?>
authenticate.php:
<?php
session_start();
// Load Credential Database
$xml = new DOMDocument();
$xml->load('../logins.xml');
$xpath = new DOMXPath($xml);
// Variable Declarations
$user = $_POST['Username'];
$password = $_POST['Password'];
// XPath Query for Correct Credential
$queryUsers = '//authentication/client/user[. = "' . $user . '"]';
$userActual1 = $xpath->query($queryUsers);
$userActual = $userActual1->nodeValue; // Output: Anton
$passwordActual1 = $userActual1 . 'following-sibling::*[1]';
$passwordActual = $xpath->query($passwordActual1); // Output: damn
// Authentication Checker
if($user == 'Arend' && $password == 'damn') {
$userLogin = true;
$_SESSION['user'] = $user;
header('Location: ../index.php');
} else {
$userLogin = false;
header('Location: ../index.php?error=1');
};
if($userLogin == true) {
header('Location: ../index.php');
};
?>
XML "logins.xml":
<?xml version="1.0" encoding="UTF-8"?>
<authentication>
<client>
<user>Arend</user>
<password>damn</password>
</client>
<client>
<user>Felipe</user>
<password>damned</password>
</client>
<client>
<user>Paula</user>
<password>damnest</password>
</client>
</authentication>
Option Two:
XML code "test.xml":
<?xml version="1.0" encoding="UTF-8"?>
<authentication>
<user name="Arend">
</user>
<password>damn</password>
<user name="Paula">
</user>
<password>damnest</password>
</authentication>
php code:
<?php
$xml = simplexml_load_file('test.xml');
$nodes = $xml->xpath('//authentication/user[#name="Arend"]');
// Variable Declarations
$user = "Arend";
$password = "damn";
echo 'second test' . '<br>';
print_r ($nodes);
echo '<br>';
print_r ($nodes[0]['name']);
echo '<br>';
echo $nodes[0]['name'];
?>
Result:
As you can see, it grabs the XML data and the array and how it is structured cen be viewed in the screenshot. I am able to get the username, but if I try to specify the password (echo $nodes[0]['password'];) as the thing I want, it comes up with the second image below:
Don't store plain text passwords, use password_hash() to create hashes and password_verify() to verify them.
With DOMXpath::evaluate() you can fetch the password for a user directly:
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<authentication>
<client>
<user>Arend</user>
<password>$2y$10$WjYhm/JoG3Pn.nT7C91cweOCrshRSZOgYFoYvrG8Ry1SS/tohGfA.</password>
</client>
<client>
<user>Felipe</user>
<password>$2y$10$PlUKayvRmTy61sFMtAhlxePKGAkTL8LU84gvP1EjUn/3e0E2jHMgi</password>
</client>
<client>
<user>Paula</user>
<password>$2y$10$3XDNia4TqVJ5QlqH/zoX/e9.t7YJF5K1hNVZnnuoslI/hjvv0St1W</password>
</client>
</authentication>
XML;
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$passwordHash = $xpath->evaluate(
'string(/authentication/client[user = "Arend"]/password)'
);
var_dump(password_verify('damn', $passwordHash));
Output:
bool(true)
You acknowledge that this is insecure, so I won't badger or comment anymore on that.
As for using logins.xml, as in your first example, I had a proper XPath and solution for it. I used simpleXML though, which comes with PHP.
//load credentials
$xml = simplexml_load_file(__DIR__.'/logins.xml');
// Variable Declarations
$user = $_POST['Username'];
$password = $_POST['Password'];
$search = $xml->xpath("//authentication/client[./user = '{$user}'][./password = '{$password}']");
if(count($search))
{
//authenticated!
}
else
{
//DENIED
}
It does a match of a <client> node having BOTH a child <user> equal to $user and a <password> equal to $password, not either-or, but both.
count($search) returns 0 (false) if there are none, and then anything else is cast to true, which means they exist in your dataset.
Disclaimer, I used __DIR__ because the XML and PHP were in the same folder for me when I tested my XPath expression. I want to say NEVER have your sensitive credentials within the web root, or somewhere not protected by some form of HTAccess or other restriction.
For using DOMDocument and DOMXPath, they have instead of a countable set a length parameter. You instead would have this for checking IF/ELSE
$xmlpath= $xpath->query("//authentication/client[./user = '{$user}'][./password = '{$password}']");
if($xmlpath->length > 0)
{
//authenticate
}
else
{
//not
}
One final security warning, you use direct $_POST variables, and I want to add awareness that there is such a thing as XPath Injection, much like SQL injection. Your function isn't used to display data, so it wouldn't be leaking information from the document. BUT they could use it to make a query that always returns true and lets them log in.
See for examples, including login credentials in XML like yours!: https://www.owasp.org/index.php/XPATH_Injection

Take form data and write it to XML file

I am trying to take form data (via _POST) and write it to a document using SimpleXML. This is what I have tried and I can't seem to get it to work.
<?php
$title = $_POST['title'];
$link = $_POST['link'];
$description = $_POST['description'];
$rss = new SimpleXMLElement($xmlstr);
$rss->loadfile("feed.xml");
$item = $rss->channel->addChild('item');
$item->addChild('title', $title);
$item->addChild('link', $link);
$item->addChild('description', $description);
echo $rss->asXML();
header("Location: /success.html");
exit;
?>
Any help or a point in the right direction would be much appreciated.
You use the asXML() function wrong. If you want to write your XML to a file, you must pass a filename parameter to it. Check the SimpleXMLElement::asXML manual
so your code line oututing xml should be changed from
echo $rss->asXML();
to
$rss->asXML('myNewlyCreatedXML.xml');
rather than using SimpleXMLElement you can create XML directly like this
$xml = '<?xml version="1.0" encoding="utf-8"?>';
$xml .= '<item>';
$xml .= '<title>'.$title.'</title>';
$xml .= '<link>'.$title.'</link>';
$xml .= '<description>'.$title.'</description>';
$xml .= '</item>';
$xml_file = "feed.xml";
file_put_contents($xml_file,$xml);
may this will help you
There are a few problems with your code
<?php
$title = $_POST['title'];
$link = $_POST['link'];
$description = $_POST['description'];
//$rss = new SimpleXMLElement($xmlstr); // need to have $xmlstr defined before you construct the simpleXML
//$rss->loadfile("feed.xml");
//easier to just load the file when creating your XML object
$rss = new SimpleXML("feed.xml",null,true) // url/path, options, is_url
$item = $rss->channel->addChild('item');
$item->addChild('title', $title);
$item->addChild('link', $link);
$item->addChild('description', $description);
//header("Location: /success.html");
//if you want to redirect you should put a timer on it and echo afterwards but by
//this time if something went wrong there will be output already sent,
//so you can't send more headers, i.e. the next line will cause an error
header('refresh: 4; URL=/success.html');
echo $rss->asXML(); // you may want to output to a file, rather than the client
// $rss->asXML('outfputfile.xml');
exit;
?>

The Curse of XML and PHP Whitespace

I am having an issue with DOMDocument and whitespace. Currently I have two types of XML files. One file was created manually about a year ago, I will call this file A. The second file, file B, is being generated using a PHP DOMDocument. I have been trying very hard (unsuccessfully) to make the whitespace in file A match file B.
Here's how it works... The user is given an option to add new <Slide> elements to the XML file. After new slides have been added the user has the option to add new <Items> to the XML file as a child of the <Slide> element.
When I add a <Slide> element to file B it works like a charm. I can even add a new <Item> element with zero problem. However, when I try to access the new <Identifier> element I just added in file B using the second PHP script below with $order != 'remove' I miss the node by one and select <Information/> instead.
It appears that manually created file A has white space that is not present in my generated file B. I have experimented with the preserveWhitespace property but it did not help.
Are there any suggestions on how I can correct this problem. Constructive criticism is also welcome as this is my first shot at dynamic XML manipulation. I apologize for the length and appreciate your time!!
File A - Created Manually - I am trying to match this file!
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Areas>Head & Neck</Areas>
<Area>Head & Neck</Area>
<Type>Angiograph</Type>
<Slide>Ag-01a
<Title>Catheter Angiography</Title>
<Item1>
<Identifier interestCoord=".51,.73" locator="point" labelBool="true" labelTxt="" leaderBool="true">Aortic Arch
</Identifier>
<Information/>
<Question A="" B="" C="" D="" E="" Answer=""/>
</Item1>
.... More Items
File B - Before user adds <Slide>. This portion is created Manually. A template if you will. After the user enters slide names new slides are generated using the chunk of code below.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Areas>Head & Neck</Areas>
<Area>Head & Neck</Area>
<Type>Brain Sections</Type>
</root>
File B - After users adds new <Slide> and <Item>. Formatting shown represents formatting created by DOMDocument. I think this is where the error is occuring! Whitespace!!!
<Slide>Ag-09a
<Title>Catheter Angiography</Title>
<Item1><Identifier locator="point" interestCoord="0.143,0.65" labelBool="true" labelTxt="" leaderBool="false">Orbit</Identifier><Information/><Question A="" B="" C="" D="" E="" Answer=""/></Item1></Slide>
PHP script used to add new <Slide> elements to XML
<?php
session_start();
//Constants
$SECTION_SEP = "========================================================================</br>";
//Variables used to construct file path
$area = trim($_POST['area']);
$slideType = trim($_POST['slideType']);
$rawSlides = trim($_POST['theseSlides']);
$newSlideList = explode(",", $rawSlides);
$fileLocation = "../XML/".$area."/".$slideType."/".$area.".XML";
$dom = new DOMDocument();
echo('New DOMDocument created!</br>');
$dom->load($fileLocation);
echo('XML file loaded!</br>');
/*$dom->preserveWhiteSpace = false;
echo('White space removed!</br>');*/
$dom->documentElement;
echo('DOM initialized!</br>');
if ($dom->getElementsByTagName('Slide')->length == 0){ //New file with no slides
foreach ($newSlideList as $slide){
$newSlide = $dom->createElement('Slide', $slide);
$newTitle = $dom->createElement('Title', 'Scan');
//Add the title element to the Item
$newSlide->appendChild($newTitle);
$dom->childNodes->item(0)->appendChild($newSlide);
echo($slide." has been added to the list!</br>");
}
} else {
$locators = $dom->getElementsByTagName('Slide');
}
if($dom->save($fileLocation)){
echo("File saved successfully!!");
}else echo("There was a problem saving the file!");
PHP script used to add/edit/remove <Item> and <Identifier> nodes depending on value of $orders == WARNING! Lengthy :/
<?php
session_start();
//Constants
$SECTION_SEP = "========================================================================</br>";
//Variables used to construct file path
$area = trim($_POST['area']);
$slideType = trim($_POST['slideType']);
$fileLocation = "../XML/".$area."/".$slideType."/".$area.".XML";
//echo("File location:".$fileLocation);
//Current data (c_ for current)
$c_poi = "";
$c_type = "";
$c_lblBool = "";
$c_lblOverride = "";
$c_leaderBool = "";
//Determine if this visit is for new or old data
$orders = trim($_POST['orders']);
//Variables used to replace information in XML file loaded below (n_ for new)
$n_slideName = trim($_POST['slideName']); //slide name in view format ie Ag-01a
$n_identName = trim($_POST['ident']); //contains multiple information separated by comma ie 0,Aortic Arch
$n_type = trim($_POST['type']); //locator type
$n_poi = trim($_POST['poi']);
$n_lblBool = trim($_POST['lblBool']);
$n_lblOverride = trim($_POST['lblOverride']);
echo("Modified: ".date('c')."</br>");
$dom = new DOMDocument();
echo('New DOMDocument created!</br>');
$dom->load($fileLocation);
echo('XML file loaded!</br>');
/*$dom->preserveWhiteSpace = false;
echo('White space removed!</br>');*/
$dom->documentElement;
echo('DOM initialized!</br>');
$locators = $dom->getElementsByTagName('Slide');
echo($locators->length.' elements retrieved</br>');
$slideEntryFound = false;
$identEntryFound = false;
$identAttributesFound = false;
echo($SECTION_SEP);
//Locate the correct slide node
foreach ($locators as $locator){
//If there is a match, store the infomation
// rawSlide[x].childNode[0].nodeValue
if(strcmp(trim($locator->childNodes->item(0)->nodeValue),$n_slideName) == 0){
$slideEntryFound = true;
$slideChildren = $locator->childNodes;
//Locate the correct identifier node
foreach($slideChildren as $child){
if( strcmp(trim($child->nodeValue), substr($n_identName,strpos($n_identName,",")+1)) == 0){
$identEntryFound = true;
if (strcmp($orders, "remove") == 0){//Removing an element
echo("The identifier being removed is: ".trim($child->nodeValue."</br>"));
echo("The node path is: ".($child->childNodes->item(1)->getNodePath())."</br>");
echo($SECTION_SEP);
$locator->removeChild($child);
echo("Identifier successfully removed!</br>");
echo($SECTION_SEP);
break;
} else {//Not removing anything - Adding or Editing
echo("The identifier being modified is: ".trim($child->nodeValue."</br>"));
echo("The node path is: ".($child->childNodes->item(1)->getNodePath())."</br>");
echo($SECTION_SEP);
if($child->childNodes->item(1)->hasAttributes()){
$identAttributesFound = true;
$c_poi = $child->childNodes->item(1)->getAttribute('interestCoord');
echo("--Current interestCoord: ".$c_poi."</br>");
echo("++New interestCoord: ".$n_poi."</br>");
if(strcmp($c_poi, $n_poi) != 0){
$child->childNodes->item(1)->setAttribute('interestCoord',$n_poi);
}
$c_type = $child->childNodes->item(1)->getAttribute('locator');
echo("--Current locator: ".$c_type."</br>");
echo("++New locator: ".$n_type."</br>");
$c_lblBool = $child->childNodes->item(1)->getAttribute('labelBool');
echo("--Current labelBool: ".$c_lblBool."</br>");
//echo("++New labelBool: ".$n_lblBool."</br>");
$c_lblOverride = $child->childNodes->item(1)->getAttribute('labelTxt');
echo("--Current labelOverride: ".$c_lblOverride."</br>");
echo("++New labelOverride: ".$n_lblOverride."</br>");
$c_leaderBool = $child->childNodes->item(1)->getAttribute('leaderBool');
echo("--Current leaderBool: ".$c_leaderBool."</br>");
//echo("++New leaderBool: ".$n_leaderBool."</br>");
if($n_lblOverride != ""){
echo("**A new label override was detected. The identifier will have the alias ".$n_lblOverride.".");
}
break;
} else echo("Fatal Error - Node does not contain attributes!</br>");
if($identEntryFound == true && $identAttributesFound == false)
echo("Error - Attribute entry not found!");
break;
}
}
}
if($slideEntryFound == true && $identEntryFound == false && $orders != "remove"){
echo("The identifier was not found... creating a new identifier!</br>");
//Create a new Element
$newElement = $dom->createElement("Item".((integer)(substr($n_identName,0,strpos($n_identName,",")))+1));
echo("New element created!!</br>");
//Create new Item children
$newSubElem = $dom->createElement("Identifier", substr($n_identName,strpos($n_identName,",")+1));
$newSubElem->setAttribute('locator',$n_type);
$newSubElem ->setAttribute('interestCoord',$n_poi);
$newSubElem->setAttribute('labelBool', $n_lblBool);
$newSubElem->setAttribute('labelTxt', $n_lblOverride);
//TODO link this next one to a variable instead of hard coding
$newSubElem->setAttribute('leaderBool', "false");
//Info Child
$newInfoElem = $dom->createElement("Information");
//Question Child
$newQuestion = $dom->createElement("Question");
$newQuestion->setAttribute('A', "");
$newQuestion->setAttribute('B', "");
$newQuestion->setAttribute('C', "");
$newQuestion->setAttribute('D', "");
$newQuestion->setAttribute('E', "");
$newQuestion->setAttribute('Answer', "");
//Add new children to main Item
$newElement->appendChild($newSubElem);
$newElement->appendChild($newInfoElem);
$newElement->appendChild($newQuestion);
$locator->appendChild($newElement);
echo("New identifier added!!</br>");
break;
}
} else {
}
}
if($slideEntryFound == false)
echo("Error - Slide entry not found!");
if($dom->save($fileLocation)){
echo("File saved successfully!!");
echo('<div id="phpHandleBtns>"></br><form><button type="submit" id="continueEdit" formaction="../edit.php">Continue Editing</button>'.
'</br><button type="submit" id="doneEdit" formaction="../main.php">Done Editing</button></form></div>');
}else echo("There was a problem saving the file!");
?>
I would strongly recommend that you use an XPath API like this http://php.net/manual/en/class.domxpath.php to find the nodes you are interested in. Attempting to use the DOM API directly is only going to cause you heartache.
More specifically, I think that your call to childNode() is getting tripped up by white space, but if you used childElement() instead (not sure if that exists, but with XPath it is easy), it would just ignore any whitespace.

XML file appears to be randomly corrupting and can't figure it out

I have an xml file being used on our wordpress site that gets updated with the post ID every time someone views a post.
The file structure looks like this…
<?xml version="1.0"?>
<posts>
<refresh>
<datetime>2013-01-04 08:48:21</datetime>
</refresh>
<post id="21931">
<postviews>5</postviews>
</post>
<post id="25420">
<postviews>18</postviews>
</post>
<post id="17770">
<postviews>25</postviews>
</post>
<post id="24925">
<postviews>4</postviews>
</post>
</posts>
Every time someone view a post if the post ID isn't in the file it adds a new node or if it exists it updates the current value by 1.
The problem I have, is that sometimes the file isn't being written to correctly, and it ends up being corrupted and looking like the following (the error is always at the bottom of the file)…
<?xml version="1.0"?>
<posts>
<refresh>
<datetime>2013-01-04 08:48:21</datetime>
</refresh>
<post id="21931">
<postviews>5</postviews>
</post>
<post id="25420">
<postviews>18</postviews>
</post>
<post id="17770">
<postviews>25</postviews>
</post>
</posts>
id="24925">
<postviews>4</postviews>
</post>
</posts>
I have spoken to our hosting company and the problem isn't showing up as a server error. Could it be a problem with my script that writes to the XML file? The script I have written is below…
<?php
// echo get_the_ID();
if($post->post_status != "publish" || is_user_logged_in()) {
//Get the wordpress postID
$postID = get_the_ID();
$postData = get_post($postID);
// Don't do anything if the post isn't published or being viewed by logged in users - will give a false impression
// echo $postID.'<br />'.$postData->post_title.'<br />'.$postData->post_date_gmt.'<br />';
} else {
//Get the wordpress postID
$postID = get_the_ID();
$postData = get_post($postID);
// echo $postID.'<br />'.$postData->post_title.'<br />'.$postData->post_date_gmt.'<br />';
$xmlFile = get_stylesheet_directory().'/most-read/most-read-posts.xml';
// load the document
$xml = simplexml_load_file($xmlFile);
// Get the server time
$serverTime = date('Y-m-d H:i:s');
// Is their a refresh node with an entry of datetime?
$refreshNodeExists = $xml->xpath("//refresh");
// Count it if
$countrefreshNodeExists = count($refreshNodeExists);
// If it does exists
if($countrefreshNodeExists != 0) { // If the ID is already in the file
$xmlRefresh = $xml->refresh->datetime;
// echo 'Refresh time is already set<br /><br />';
} else {
// echo 'Refresh time is not set<br /><br />';
$refreshNode = $xml->addChild('refresh');
$refreshNode->addChild('datetime', $serverTime);
}
// Check the time elapsed between the server time and the refreshNode
// Get and format the refreshNode
$to_time = strtotime($xmlRefresh);
// Get and format the server time
$from_time = strtotime($serverTime);
// Calculate the time elapsed
$refreshTimeElapsed = round(abs($to_time - $from_time) / 60,2);
// How long do we want to have between file refreshing - in minutes?
$refreshInterval = 120;
// Check if the time elapsed is more or less than $refreshInterval
if ($refreshTimeElapsed > $refreshInterval) {
// FOR TESTING ONLY - Show the time elapsed
// echo 'Ooops were over the refresh limit. It\'s currently at: '.$refreshTimeElapsed.' minutes.<br /><br />';
$fh = fopen($xmlFile, 'w') or die("can't open file");
$stringData = '<?xml version="1.0"?><posts><refresh><datetime>'.date('Y-m-d H:i:s').'</datetime></refresh></posts>';
fwrite($fh, $stringData);
fclose($fh);
/*$test = $xml->xpath("//post");
if (!empty($test)) {
echo 'Houston, we have posts';
} else {
echo 'That\'s a negative Houston, we have no posts here';
}*/
// Reset the datetime node with the current datetime to start the refresh loop again.
$xml->refresh->datetime = date('Y-m-d H:i:s');
} else { // Don't need to do anything here really
// FOR TESTING ONLY - show how much time has elapsed
// echo 'No need to change the refresh node. It\'s only at '.$refreshTimeElapsed.' minutes.<br /><br />';
// Check to see if the post id is already in the xml file - has it already been set?
$nodeExists = $xml->xpath("//post[#id=".$postID."]");
//Count the results
$countNodeExists = count($nodeExists);
if($countNodeExists > 0) { // If the ID is already in the file
// echo 'ID already here';
// get the correct node
$result = $xml->xpath("//post[#id=".$postID."]/postviews");
// heres the trick - the first result of xpath, and the node value (stored in [0])
$result[0][0] = $result[0][0]+1;
} else { // If the ID isn;'t there, add a new entry in the xml file for the post
//echo 'ID added';
$postNode = $xml->addChild('post'); // adding a new <post> to the top level node
$postNode->addAttribute('id', $postID); // adding a <postid> inside the new <post>
$postNode->addChild('postviews', 1); // adding a postviews inside the new <post>
}
// save the updated document and format it using the DOMDocument class
//$xml->asXML($xmlFile);
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
$dom->save($xmlFile);
} // End if ($refreshTimeElapsed > $refreshInterval) {
} // End if logged in or not published
?>
Every time someone view a post if the post ID isn't in the file it
adds a new node or if it exists it updates the current value by 1.
I'm willing to bet this is being caused by multiple simultaneous executions. Are you locking access to that file ? What happens if two people view a post at the same time ?
I think (longer term) that you're going to be better off storing this info in a database or similar. I don't think writing to a common XML file will scale.

Categories