treehouse : what would you like to learn today?
Web Design Web Development iOS Development

Edit XML with PHP

  • I created an admin system for a photography flash portfolio website, in the wordpress admin, in the functions.php file. An XML file is loaded into the flash website but I wanted a way to edit that data. So this system loads the form values in from an xml file.

    http://www.condiffphotography.com/pricing-admin.png

    The only thing thats not functional is, it doesn't save the data entered. I can add, remove, and edit the packages on the page, but it won't save it to the xml file it loads from. If anyone can lend me a hand that would be great!

    Thanks.
  • I'm not exactly sure what it is you are looking for, just how to save an xml file, how to create one with php... ect.

    Could you please be more specific?

    Anyways, this is how I learned: http://www.ibm.com/developerworks/library/os-xmldomphp/
  • Thanks for the reply. I just want to make this form functional. It already loads in data from my XML file. But I want the user to be able to edit it with this form. So since it loads the information in from the xml file, I could just dump all of the XML data and replace it with the new data that are in the fields when it is saved. I would assume this is all done with PHP. This is my xml file currently:

     <pricing>
    <package>
    <title>Package 1</title>
    <price>$750</price>
    <description>This is a description for Package 1.</description>
    </package>
    <package>
    <title>Package 2</title>
    <price>$1250</price>
    <description>This is a description for Package 2.</description>
    </package>
    <package>
    <title>Package 3</title>
    <price>$1750</price>
    <description>This is a description for Package 3.</description>
    </package>
    <package>
    <title>Package 4</title>
    <price>$2000</price>
    <description>This is a description for Package 4.</description>
    </package>
    <package>
    <title>Package 5</title>
    <price>$3000</price>
    <description>This is a description for Package 5.</description>
    </package>
    </pricing>


    This is the code I wrote for the wordpress admin menu as seen above:
        <?php function pricing() { ?>
    <style>
    .package {
    width:400px;
    background: #eee;
    padding:20px;
    margin:20px;
    overflow: hidden;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    }
    .package h3 {
    margin:0 0 10px 0;
    }
    .package .label {
    width:90px;
    clear:both;
    float:left;
    margin-bottom: 5px;
    }
    .package input[type=\"text\"], .package textarea {
    float:right;
    width:300px;
    margin-bottom: 5px;
    padding:5px;
    }
    .package input[type=\"button\"]{
    float: right;
    margin-top: 5px;
    }

    </style>
    <div class=\"wrap\" style=\"padding:0 0 20px 0;\">
    <h2>Edit Pricing</h2>
    <form action=\"submit.php\" method=\"post\">
    <div class=\"content\">
    <?php
    $doc = new DOMDocument();
    $doc->load( 'http://www.condiffphotography.com/pricing.xml' );

    $pricing = $doc->getElementsByTagName( \"package\" );
    foreach( $pricing as $package )
    {
    $titles = $package->getElementsByTagName( \"title\" );
    $title = $titles->item(0)->nodeValue;

    $prices = $package->getElementsByTagName( \"price\" );
    $price = $prices->item(0)->nodeValue;

    $descriptions = $package->getElementsByTagName( \"description\" );
    $description = $descriptions->item(0)->nodeValue;

    echo \"
    <div class=\\"package\\">
    <h3>Package:</h3>
    <label class=\\"label\\">Title:</label><input type=\\"text\\" value=\\"$title\\" name=\\"title\\" />
    <br />
    <label class=\\"label\\">Price:</label><input type=\\"text\\" value=\\"$price\\" name=\\"price\\" />
    <br />
    <label class=\\"label\\">Description:</label>
    <textarea name=\\"description\\" cols=\\"32\\" rows=\\"7\\" />$description</textarea><br />
    <input type=\\"button\\" class=\\"remove button-secondary\\" name=\\"remove\\" value=\\"Remove Package\\">
    </div>
    \";
    }
    ?>

    </div>
    <input type=\"button\" name=\"add\" onclick='addPackage()' class=\"button-secondary\" value=\"Add Package\">
    <input type=\"submit\" name=\"mysubmit\" class=\"button-primary\" value=\"Save Changes\">
    <input type=button value=\"Cancel\" class=\"button-primary\" onClick=\"window.location.reload()\">
    </form>
    <script>
    function addPackage(){
    $(\".content\").append('<div class=\"package\"><h3>Package:</h3><label class=\"label\">Title:</label><input type=\"text\" value=\"\" name=\"title\" /><br /><label class=\"label\">Price:</label><input type=\"text\" value=\"\" name=\"price\" /><br /><label class=\"label\">Description:</label><br /><textarea name=\"description\" cols=\"32\" rows=\"7\"></textarea><br /><input type=\"button\" class=\"remove button-secondary\" name=\"remove\" value=\"Remove Package\"></div>');
    $('.remove:button').click(function(){
    $(this).parent().remove();
    });
    }
    $('.remove:button').click(function(){
    $(this).parent().remove();
    });
    </script>
    </div>

    <?php } ?>


    So looking at that image in my last post, PHP would take each box and write a <package> node, for each box, in the xml file when you click save.

    I just need to figure out how to write this with PHP. Does that make more sense?
  • Makes much more sense. I actually did something like this not too long ago. So here's how I did it...

    I took the current xml file, and looped through it. I had my variables set for the old title, and the newly updated title so I knew which one to replace. I looped through the xml file and copied everything into a new xml, until I got to the outdated block. I then replaced it with the new information I wanted and continued my loop until the end. Once finished, I saved it overwriting the old xml file. And that's it!

    I modified my large piece of code to give you a smaller one that you should be able to use with little or no modifications. Let me know if you have any questions.

    <?php
    //Notice how I load the xml location outside the function, incase you have already loaded it.
    $xmlFile = \"http://www.condiffphotography.com/pricing.xml\";//directory/location of your xml file

    function updateXML($oldtitle, $oldprice, $olddesc, $newtitle, $newprice, $newdesc){
    global $xmlFile;//loads the file location set above

    $xml = simplexml_load_file($xmlFile);//loads xml into $xml variable
    $doc = new DOMDocument('1.0');//new xml file that will overwrite the above
    $doc->formatOutput = true;//why not?

    $library = $doc->appendChild($doc->createElement(\"pricing\"));//creates <pricing>

    foreach($xml->package as $package) {//loops through the xml file

    $b = $doc->createElement( \"package\" );//creates <package>

    if ($package->title == $oldtitle && $package->price == $oldprice && $package->description == $olddesc){//if we are at the outdated package, update information
    $name = $doc->createElement( \"title\" );//creates element <title>
    $name->appendChild($doc->createTextNode( $newtitle ));//adds $newtitle value inside the <title>
    $b->appendChild( $name );//closes element </title>
    $price = $doc->createElement( \"price\" );
    $price->appendChild($doc->createTextNode( $newprice ));
    $b->appendChild( $price );
    $desc = $doc->createElement( \"description\" );
    $desc->appendChild($doc->createTextNode( $newdesc ));
    $b->appendChild( $desc );
    } else {//not at the package we want to update so simply copy over to our new xml
    $name = $doc->createElement( \"title\" );
    $name->appendChild($doc->createTextNode( $package->title ));
    $b->appendChild( $name );
    $price = $doc->createElement( \"price\" );
    $price->appendChild($doc->createTextNode( $package->price ));
    $b->appendChild( $price );
    $desc = $doc->createElement( \"description\" );
    $desc->appendChild($doc->createTextNode( $package->description ));
    $b->appendChild( $desc );
    }

    $library->appendChild( $b );//</pricing>
    }

    $doc->save($xmlFile);//save our new xml in place of the old one. overwrites automatically.

    }
    updateXML('Package 1','$750','This is a description for Package 1.','a','a','a');//will change package 1 to a for everything. example only

    ?>


    -T
  • Thank you so much, but I get the following error:
    Warning: DOMDocument::save(http://www.condiffphotography.com/pricing.xml) [domdocument.save]: failed to open stream: HTTP wrapper does not support writeable connections in /[server-path]/xmledit.php on line 43

    whats wrong here? i set the chmod of pricing.xml to 777
  • First, do you have "" around it if you aren't using the variable?

    $doc->save($xmlFile);

    OR

    $doc->save("http://www.condiffphotography.com/pricing.xml");

    Is the php file you are running on the same server as the xml file? I'm pretty sure they have to be. Also try changing the location to a relative location.
  • both the xml file and xmledit.php are on the root folder (public_html) of my server. I tried both with the var (without quotes) and with the direct location (with quotes) and still, I get the same error. It looks like it would work but maybe I am overlooking something...

    also how do I use this script, do I just open this straight out of my browser, or do I use this as my form action?
  • Try

    $doc->save(\"pricing.xml\");
  • ok it works, but how do I use it? It just reset the first group of nodes to "a".
  • Well its a function. You send it the variables that it takes and it will do the work for you.

    Put the function in the page you want to edit the xml file. Then call the function sending it the information it needs.

    updateXML('Package 1','$750','This is a description for Package 1.','New Title','New Price','New Description');


    The first 3 parameters are the old information of the block of xml you want to change. The last three are the new values you want to change them to. If you used a form, you would need to find a way to store the old information and send both the old and new values to the function. That line above executes the function. So take that out of the example I gave you. Say you sent all the data using a form, you would end up with something like

    updateXML($_Post['oldtitle'], $_POST['oldprice'], $_POST['olddesc'], $_Post['newtitle'], $_POST['newprice'], $_POST['newdesc']);


    Sorry this is a little hard to explain if you don't know how to use a function. Make any sense?
  • yes, it does make some sense. I just have to figure out how to pass the veriables. my problem is, this is a dynamic form that you are able to add and remove field groups, as you can see in the image, so i am going to have to figure out how to have them always be:
    title1, pricing1, desc1
    title2, pricing2, desc2
    title3, pricing3, desc3
    title4, pricing4, desc4
    title5, pricing5, desc5

    etc.

    because lets say the user removes the second box. then it would go:
    title1, pricing1, desc1
    title3, pricing3, desc3
    title4, pricing4, desc4
    title5, pricing5, desc5

    not:
    title1, pricing1, desc1
    title2, pricing2, desc2
    title3, pricing3, desc3
    title4, pricing4, desc4
    like it should.

    then when I add a group, it would have to be the number after the last.

    so everytime you add or remove a field group, jquery (or something) will have to rename all of the field attributes to be in order.
    does that sound about right?

    even once I get that in place, how will I tell it the right number of packages to save? (make sense?)
  • if i use a li instead of divs to put my form group in, would jquery be able to associate the number of the li with the attribute of the field? or would I have to use php for this?
  • Do you know sql and managing tables/databases? You are attempting to do basically the same thing here without a database. In that case, I would add an id attribute to your packages.

    Also look into using ajax (jQuery can do it easily and well) to save your xml after every necessary change. It sounds like this is a pretty complicated script you are writing here; write out/plan everything you need the script to accomplish and how you would go about doing it. Then knock things out one at a time.

    And you could give each li an attribute and have jquery read/change that value.
  • I think I will stick with xml. I don't know how difficult it would be to load data from a database into flash but I think it would be easier to do that with xml because you wouldn't need a php file to retrieve the variables from the database.

    So you know what I am trying to accomplish, is there any good resources you know of to learn how to do all this?

    Thanks a lot.
  • Actually just found these two links. Not how we have been going about doing this, but it looks promising and can possibly give you an idea to how things work this way. Let me know what ya think.

    http://www.kirupa.com/web/xml/XMLmanageData3.htm
    http://www.kirupa.com/developer/actionscript/create_edit_xml.htm
  • Well it looks great, but it looks like they are using as1/as2 in this article. I did find several other things but my main area of concern is getting jquery to rename attributes in multiple html tags.

    //edit

    I did find this:
    $('input.title[name]').each(function() {   

    /* create a jquery object from the <a> DOM object */
    var $input_with_name = $(this);

    /* add the new attribute with the title value */
    $input_with_name.attr(\"newNameAttribute\", $input_with_name.getAttribute('name'));

    /* remove the old attribute */
    $input_with_name.removeAttr('name');

    });


    but I need to know how to title them sequentially
    ie. title1, title2, title3, etc.
  • The only way can think of are to add another tag with an id/position number for your reference. Then after every change they make, save. Otherwise you need to go into extreme detail and a fair bit of coding to determine what exactly the user is doing and when.... I'll think about it and let you know if I come up with anything.
  • well maybe i could put it all in a ul then each group of fields would be in its own li. isnt there an nth something or other? jquery could detect the li position and then place that number after "title"
  • Just a fyi, I figured out how to re-name my attributes dynamically, even after the user adds and/or removes field groups.

    This is my jQuery:



    function renameAttr(){
    $(\"input.title\").attr(\"name\", function (arr) {
    var $t = $(this);
    $t.removeAttr('name');
    return \"title\" + arr;
    });
    $(\"input.price\").attr(\"name\", function (arr) {
    var $t = $(this);
    $t.removeAttr('name');
    return \"price\" + arr;
    });
    $(\"textarea.description\").attr(\"name\", function (arr) {
    var $t = $(this);
    $t.removeAttr('name');
    return \"description\" + arr;
    });
    };
    renameAttr();
    function addPackage(){
    $(\".metabox-holder\").append('<div class=\"postbox\"><div class=\"handlediv\" title=\"Click to toggle\"><br></div><h3 class=\"hndle\"><span>Package</span></h3><div class=\"inside\"><table><tr><td><label class=\"label\">Title</label></td><td><input type=\"text\" name=\"title\" class=\\"title\\" /></td></tr><tr><td><label class=\"label\">Price</label></td><td><input type=\"text\" name=\"price\" class=\\"price\\" /></td></tr><tr><td><label class=\"label\">Description</label></td><td><textarea name=\"description\" class=\\"description\\" cols=\"32\" rows=\"7\" /></textarea><br /></td></tr><tr><td colspan=\"2\" align=\"right\"><input type=\"button\" class=\"remove button-secondary\" name=\"remove\" value=\"Remove Package\"></td></tr></table></div></div>');

    renameAttr();

    $('.remove:button').click(function(){
    $(this).parents('[class*=postbox]:first').remove();
    renameAttr();
    });
    }
    $('.remove:button').click(function(){
    $(this).parents('[class*=postbox]:first').remove();
    renameAttr();
    });


    Now I just need to figure out how to save the XML using that script you gave me earlier. It would somehow have to detect the number of field groups...or erase all the values altogether and replace them with the new values in the form.

    It would have to go like this:

    Make and Loop through form array / detect number of field groups
    for each:
    add element "package"
    add element "title"
    get text from field with attribute name=" "title" + "#" "
    close element "title"
    add element "price"
    get text from field with attribute name=" "price" + "#" "
    close element "price"
    add element "description"
    get text from field with attribute name=" "description" + "#" "
    close element "title"
    close element "package"

    I did find this post though:
    http://www.daniweb.com/forums/thread148513.html

    can you help me to make sense of it?

    thanks.
  • So when the user submits the page, you will be sending every single variable to the next page to be written to the xml... which is why you need a dynamic variable...

    Look up creating a session with php, making that an array, and using it to pass.

    http://www.phpriot.com/articles/intro-php-sessions/7

    Get that working well, and I will help you write the xml file.
  • Ok here is what I have found:

    Instead of giving the nam attribute, title0, title1, title2, title3, etc.
    I gave it, title[0], title[1], title[2], title[3], etc. and the same for pricing and description.

    So that automatically puts it into an array. So when I click submit on my form it goes to a php file:
    <?php
    $title = $_POST['title'];
    foreach ($title as $t) {
    echo \"$t<br />\";
    }
    $price = $_POST['price'];
    foreach ($price as $p) {
    echo \"$p<br />\";
    }
    $description = $_POST['description'];
    foreach ($description as $d) {
    echo \"$d<br />\";
    }
    ?>


    and the output is:
    Package 1
    Package 2
    Package 3
    Package 4
    Package 5
    $750
    $1250
    $1750
    $2000
    $3000
    This is a description for Package 1.
    This is a description for Package 2.
    This is a description for Package 3.
    This is a description for Package 4.
    This is a description for Package 5.

    Its not using sessions, but is this what you were talking about?
  • I also found that this php
    <?php
    $title = $_POST['title'];

    $price = $_POST['price'];

    $description = $_POST['description'];

    for( $i = 0, $c = count($title); $i < $c; $i++ ) {

    echo \"Package: <br />\";
    echo \"Title: {$title[$i]} <br />\";
    echo \"Price: {$price[$i]} <br />\";
    echo \"Description: {$description[$i]} <br />\";
    echo \"<br />\";

    }

    ?>


    Outputs:

    Package:
    Title: Package 1
    Price: $750
    Description: This is a description for Package 1.

    Package:
    Title: Package 2
    Price: $1250
    Description: This is a description for Package 2.

    Package:
    Title: Package 3
    Price: $1750
    Description: This is a description for Package 3.

    Package:
    Title: Package 4
    Price: $2000
    Description: This is a description for Package 4.

    Package:
    Title: Package 5
    Price: $3000
    Description: This is a description for Package 5.
  • Perfect. Now here is a quick xml file writer...

    <?php

    $xmlLoc = 'hah.xml'//LOCATION OF XML FILE

    $doc = new DOMDocument();//$doc is your xml document variable
    $doc->formatOutput = true;

    $r = $doc->createElement( \"pricing\" );//creates <pricing>
    $doc->appendChild( $r );

    for( $i = 0, $c = count($title); $i < $c; $i++ ) {//loops through your vars...
    $b = $doc->createElement( \"package\" );//creates <package>

    $a1 = $doc->createElement( \"title\" );//creates <title>
    $a1->appendChild($doc->createTextNode( $title[$i] ) );//places $title[$i]'s value into <title>
    $b->appendChild( $a1 );//closes </title>

    $a2 = $doc->createElement( \"price\" );
    $a2->appendChild($doc->createTextNode( $price[$i] ) );
    $b->appendChild( $a2 );

    $a3 = $doc->createElement( \"description\" );
    $a3->appendChild($doc->createTextNode( $description[$i] ) );
    $b->appendChild( $a3 );

    $r->appendChild( $b );//closes </package>
    }

    $doc->save($xmlLoc);//and save.


    ?>



    I actually didn't test this, because I don't want to take the time to set everything up but all looks in order. I commented out so you can see what I did, see if you can figure out how things work. Actually simple once you get it. Modify if needed, best of luck.
  • I got the following error:

    Warning: DOMDocument::save([path]/pricing.xml) [domdocument.save]: failed to open stream: HTTP wrapper does not support writeable connections in /[path]/submit-pricing.php on line 35

    Warning: DOMDocument::save([path]/pricing.xml) [domdocument.save]: failed to open stream: HTTP wrapper does not support writeable connections in /[path]/submit-pricing.php on line 35

    line 35: $doc->save( $xmlLoc );

    I set the chmod to 777 so i dont know what is wrong
  • There needs to be an xml file already on your server with the name you changed $xmlLoc to, and in the correct directory. Do you have that?
  • yes there is.
  • It works. Ok now, do you know of a way to execute that inside the current page so I don't end up in a blank page?

    Can I put that php in the page with my form? Then I could append some html at the bottom that says that the XML was updated successfully.
  • Here's how I would do that...

    Place the xml file writing script at the top of your page. Then wrap it in an if statement, to check if the user has submitted the form. Also check your form sends to itself.

    Then you can change the saving file to this...


    if ($doc->save($xmlLoc)){
    $varName = 'You have saved successfully... blah blah';
    } else {
    $varName = 'An error has occurred'.
    }


    Then echo $varName where ever you want on the page if it isset().
  • Ok so I set me form to: action="<?php echo $_SERVER['PHP_SELF']; ?>" but the problem is that it goes here:
    http://www.condiffphotography.com/new/w ... /admin.php

    when it should go here:
    http://www.condiffphotography.com/new/w ... php?page=3

    It's probably because it's inside the wordpress admin. Are there any other ways of calling the processing script at the top?

    Thanks.
  • It it because of your page variable in the url...?

    Try this instead

    $_SERVER['REQUEST_URI']
  • I actually tried '#' (before I saw this) and surprisingly it works! Is there any advantage in using the other?
  • The method I gave carries over variables in the url...


    Instead of

    about.php


    it would display the variables if they were there.

    about.php?page=3
  • ok, now I would like to make the boxes sortable with jquery. this is what ive got but it's not working. whats wrong here?

    $(function() {
    $( \".postbox\" ).sortable({ handle: 'h3.hndle' });
    var handle = $( \".selector\" ).sortable( \"option\", \"handle\" );
    $( \".postbox\" ).sortable( \"option\", \"handle\", 'h3.hndle' );
    $(\".postbox\").disableSelection();
    });


    It's even still selectable...
  • What do you mean by sort them? Can you show me the code for what you want to sort?
  • I want to be able to drag and drop field groups so if the user wants them in a different order he can simply drag and drop the package box containing the title, price, and description fields.

    http://jqueryui.com/demos/sortable/