14

Editing a node

Create site24 by copying site23.

  1. /cms
    1. ...
    2. site23
    3. site24

In this chapter, we are going to create the node editor.

To test the result online, enter http://www.frasq.org/cms/site24 in the address bar of your navigator. Identify yourself with the name foobar and the password f00bar. Back to the home page, click on the link Legal information. Click on the Edit button in the banner of the page.

The editor displays the title, the URL, the abstract and the cloud of keywords of the node in separate text fields. Click on the French flag to edit the node in French. Click on the English flag to return to the version in English. Press the View button in the banner of the page.

The view of the node is displayed. Press on the Edit button to return to the editor. Add Publication to the abstract and the word publication to the cloud. Erase the URL and replace the title by Legal conditions. Press Edit to modify the node in the DB. The URL is automatically generated from the title.

NOTE: In this demonstration version, the content of the site can not be modified.

Start by creating the action editnode by adding the file editnode.php in the folder actions with the following content:

  1. /cms/site24
    1. actions
      1. editnode.php
  1. require_once 'userhasrole.php';
  2. require_once 'models/node.inc';

Loads the user_has_role function and the data model.

  1. function editnode($lang, $arglist=false) {
  2.     global $supported_languages;
  3.  
  4.     if (!user_has_role('writer')) {
  5.         return run('error/unauthorized', $lang);
  6.     }

Declares the global variable $supported_languages. Checks if the user is identified and if he is allowed to modify the content of the site.

  1.     $node=false;
  2.  
  3.     if (is_array($arglist)) {
  4.         if (isset($arglist[0])) {
  5.             $node=$arglist[0];
  6.         }
  7.     }
  8.  
  9.     if (!$node) {
  10.         return run('error/notfound', $lang);
  11.     }
  12.  
  13.     $node_id = node_id($node);
  14.     if (!$node_id) {
  15.         return run('error/notfound', $lang);
  16.     }

Extracts the node number from the parameters of the call request in $node_id. Checks that $node_id exists in the DB. Sends back an HTTP 404 Not Found document in case of error.

  1.     $clang=false;
  2.     foreach ($supported_languages as $slang) {
  3.         if (isset($_POST[$slang . '_x'])) {
  4.             $clang=$slang;
  5.             break;
  6.         }
  7.     }
  8.     if (!$clang) {
  9.         if (isset($_POST['clang'])) {
  10.             $clang = $_POST['clang'];
  11.         }
  12.         else if (isset($_GET['clang'])) {
  13.             $clang = $_GET['clang'];
  14.         }
  15.         else {
  16.             $clang=$lang;
  17.         }
  18.         if (!in_array($clang, $supported_languages)) {
  19.             return run('error/notfound', $lang);
  20.         }
  21.     }

Determines the language of the content to edit. First test if the user has pressed one of the language selectors among the ones which are supported. Otherwise, tries to extract the hidden field clang sent in a POST or the parameter clang from the URL of a GET. Clicking on a language selector or on Edit in the form sends a POST. Clicking on Edit or View in the banner sends a GET. If the language specified in a POST or in a GET isn't supported, an HTTP 404 Not Found document is sent back. By default, the language of the content is identical to the language of the request.

  1.     $node_editor = build('nodeeditor', $lang, $clang, $node_id);
  2.  
  3.     head('title', $node_id);
  4.     head('description', false);
  5.     head('keywords', false);
  6.     head('robots', 'noindex, nofollow');
  7.  
  8.     $view=url('node', $clang) . '/'. $node_id;
  9.     $validate=url('node', $clang) . '/'. $node_id;
  10.     $banner = build('banner', $lang, compact('view', 'validate'));
  11.  
  12.     $content = view('editing/editnode', $lang, compact('node_editor'));
  13.  
  14.     $output = layout('editing', compact('banner', 'content'));
  15.  
  16.     return $output;
  17. }

Builds the block of the node editor with the language of the request, the language of the content and the node number by calling the nodeeditor function. Fills in the header of the final document. Note that since the direct display of a node isn't normally accessible, the fields exploited by search engines are empty. Adds the buttons View and Validate to the banner menu with the URL of the content of the node in parameter. Builds the view of the editor. Assembles the final document with the layout editing and returns it.

Add the file editing.phtml in the folder layouts with the following content:

  1. /cms/site24
    1. layouts
      1. editing.phtml
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <?php echo $head; ?>
  5. </head>
  6. <body>
  7. <div id="editor">
  8. <?php echo $banner; ?>
  9. <div id="content">
  10. <?php echo $content; ?>
  11. </div>
  12. </div>
  13. </body>
  14. </html>

The editing.phtml layouts puts the whole body of the document in a <div id="editor"> tag. It doesn't display a footer. Modify the standard.phtml layout to embed the whole body of the document in a <div id="viewer"> tag:

  1. <body>
  2. <div id="viewer">
  3. <?php echo $banner; ?>
  4. <div id="content">

Create the folders views/en/editing and views/fr/editing. Add the view of the editor in English in the folder views/en/editing and the French version in the folder views/fr/editing :

  1. /cms/site24
    1. views
      1. en
        1. editing
          1. nodeeditor.phtml
      2. fr
        1. editing
          1. nodeeditor.phtml
  1. <?php echo $node_editor; ?>
  1. <?php echo $node_editor; ?>

To grant access to the editnode action, add an alias for each language in the file includes/aliases.inc:

  1.         'edit/node'             => 'editnode',
  1.         'edition/noeud'         => 'editnode',

Add the file nodeeditor.php in the folder blocks with the following content:

  1. /cms/site24
    1. blocks
      1. nodeeditor.php
  1. require_once 'readarg.php';
  2. require_once 'strtofname.php';
  3. require_once 'models/node.inc';

Loads the functions readarg and strtofname and the data model.

Modify the file readarg.php in the folder library:

  1. function readarg($s, $trim=true, $strip=true) {

Adds the parameter $strip.

  1.     if (is_array($s)) {
  2.         $r=array();
  3.         foreach ($s as $ss) {
  4.             $r[]=readarg($ss, $trim, $strip);
  5.         }
  6.         return $r;
  7.     }

Can filter a array of character strings.

  1. <?php
  2.  
  3. /**
  4.  *
  5.  * @copyright  2010 frasq.org
  6.  * @version    2
  7.  * @link       http://www.frasq.org
  8.  */
  9.  
  10. function readarg($s, $trim=true, $strip=true) {
  11.     if (is_array($s)) {
  12.         $r=array();
  13.         foreach ($s as $ss) {
  14.             $r[]=readarg($ss, $trim, $strip);
  15.         }
  16.         return $r;
  17.     }
  18.  
  19.     if (get_magic_quotes_gpc()) {
  20.         $s = stripslashes($s);
  21.     }
  22.  
  23.     if ($trim) {
  24.         $s = trim($s);
  25.     }
  26.  
  27.     if ($strip) {
  28.         $s = strip_tags($s);
  29.     }
  30.  
  31.     return $s;
  32. }

Removes all the \ (BACKSLASH) from $s automatically added by PHP to the content of the variables $_GET and $_POST if the configuration parameter magic_quotes_gpc is true. Removes spaces at the beginning and at the end of $s if $trim is $true. Removes all the tags in $s if $strip is true. This last option allows to protect the site from a content which could call another site or run a script. EXAMPLE: The text <script type="text/javascript">alert('DANGER');</script> input in a form runs a code in Javascript if it is injected without any precaution in the content of the site.

Add the file strtofname.php in the folder library with the following content:

  1. /cms/site24
    1. library
      1. strtofname.php
  1. require_once 'strflat.php';
  2.  
  3. function strtofname($s, $strict=false) {
  4.     /* remove accents */
  5.     $s = strflat($s);
  6.  
  7.     /* lower case */
  8.     $s = strtolower($s);
  9.  
  10.     /* keep letters, digits, underscores and dashes replacing others by a dash */
  11.     $s = preg_replace('#[^a-z0-9_-]#', '-', $s);
  12.  
  13.     /* replace consecutive dashes by one */
  14.     $s = preg_replace('/[-]+/', '-', $s);
  15.  
  16.     /* remove a dash at the beginning or at the end */
  17.     $s = preg_replace('/^-|-$/', '', $s);
  18.  
  19.     if (!$strict) {
  20.         return $s;
  21.     }
  22.  
  23.     /* remove words which are too short */
  24.     $r = array();
  25.     foreach (explode('-', $s) as $w)    {
  26.         if (strlen($w) > 2) {
  27.             $r[] = $w;
  28.         }
  29.     }
  30.  
  31.     return implode('-', $r);
  32. }

strtofname transforms $s in a file name. The editor uses this function to generate a URL from the title of a node. If $strict is true, strtofname withdraws all the words which have less than 3 letters.

  1. function nodeeditor($lang, $clang, $node_id) {

nodeeditor has 3 parameters: the language of the editor, the language of the content and the node number to edit.

  1.     $action='init';
  2.     if (isset($_POST['node_edit'])) {
  3.         $action='edit';
  4.     }
  5.  
  6.     $node_name=$node_title=$node_abstract=$node_cloud=false;

Initializes $action to 'edit' if the user has pressed the Edit button or to 'init' by default of the form is simply displayed. Initializes all the data transmitted in the form before they are read.

  1.     switch($action) {
  2.         case 'init':
  3.         case 'reset':
  4.             $r = node_get($clang, $node_id, false);
  5.             if ($r) {
  6.                 extract($r);
  7.             }
  8.             break;
  9.         case 'edit':
  10.             if (isset($_POST['node_title'])) {
  11.                 $node_title=readarg($_POST['node_title']);
  12.             }
  13.             if (isset($_POST['node_name'])) {
  14.                 $node_name=strtofname(readarg($_POST['node_name']));
  15.             }
  16.             if (empty($node_name) and !empty($node_title)) {
  17.                 $node_name = strtofname($node_title);
  18.             }
  19.             if (isset($_POST['node_abstract'])) {
  20.                 $node_abstract=readarg($_POST['node_abstract']);
  21.             }
  22.             if (isset($_POST['node_cloud'])) {
  23.                 $node_cloud=readarg($_POST['node_cloud'], true, false); // trim but DON'T strip!
  24.                 preg_match_all('/(\S+)/', $node_cloud, $r);
  25.                 $node_cloud=implode(' ', array_unique($r[0]));
  26.             }
  27.             break;
  28.         default:
  29.             break;
  30.     }

Extracts the input fields input from the form and filters them with readarg. The field 'node_name' is filtered with the function strtofname. If it's empty, it's automatically filled from the field 'node_title'. Notice that the field 'node_cloud' isn't filtered with the strip_tags function in order to be able to associate a tag as a keyword to a node. The content of the field 'node_cloud' is split in words then filtered to eliminate duplicates.

  1.     $missing_node_name=false;
  2.     $bad_node_name=false;
  3.  
  4.     $missing_node_title=false;
  5.  
  6.     switch($action) {
  7.         case 'edit':
  8.             if (empty($node_name)) {
  9.                 $missing_node_name = true;
  10.             }
  11.             else if (!preg_match('#^[\w-]{3,}$#', $node_name)) {
  12.                 $bad_node_name = true;
  13.             }
  14.             if (empty($node_title)) {
  15.                 $missing_node_title = true;
  16.             }
  17.             break;
  18.         default:
  19.             break;
  20.     }

Checks the contents $node_name and $node_title.

  1.     switch($action) {
  2.         case 'edit':
  3.             if ($missing_node_name or $bad_node_name or $missing_node_title) {
  4.                 break;
  5.             }
  6.  
  7.             $r = node_set($clang, $node_id, $node_name, $node_title, $node_abstract, $node_cloud);
  8.  
  9.             if (!$r) {
  10.                 break;
  11.             }
  12.  
  13.             break;
  14.  
  15.         default:
  16.             break;
  17.     }

Checks that no error has been detected and modifies the content of the node in the DB by calling the function node_set.

  1.     $errors = compact('missing_node_name', 'bad_node_name', 'missing_node_title');
  2.  
  3.     $output = view('editing/nodeeditor', $lang, compact('clang', 'node_name', 'node_title', 'node_abstract', 'node_cloud', 'errors'));
  4.  
  5.     return $output;
  6. }

Prepares all the parameters of the view, generates it and returns its content.

Add the view of the editor in English in the folder views/en/editing with the following content:

  1. /cms/site24
    1. views
      1. en
        1. editing
          1. editnode.phtml
  1. <?php extract($errors); ?>
  2. <form class="compact" action="" method="post">
  3. <input name="clang" type="hidden" value="<?php echo $clang; ?>" />
  4. <p>
  5. <?php if (in_array('fr', $supported_languages)): ?>
  6. <input name="fr" type="image" src="<?php echo $base_path; ?>/images/theme/flags/fr.png" alt="fr" title="Français"/>
  7. <?php endif; ?>
  8. <?php if (in_array('en', $supported_languages)): ?>
  9. <input name="en" type="image" src="<?php echo $base_path; ?>/images/theme/flags/en.png" alt="en" title="English"/>
  10. <?php endif; ?>
  11. </p>
  12. <p>
  13. <input id="node_title" name="node_title" type="text" size="40" maxlength="100" value="<?php echo htmlspecialchars($node_title, ENT_COMPAT, 'UTF-8'); ?>" title="Title" onkeypress="return submitonenter(event, 'node_edit')"/>
  14. <label>URL:</label>
  15. <input id="node_name" name="node_name" type="text" size="40" maxlength="100" value="<?php echo $node_name; ?>" onkeypress="return focusonenter(event, 'node_title')"/>
  16. </p>
  17. <p><label>Abstract:</label></p>
  18. <p>
  19. <textarea name="node_abstract" cols="80" rows="3"><?php echo htmlspecialchars($node_abstract, ENT_COMPAT, 'UTF-8'); ?></textarea>
  20. </p>
  21. <p><label>Cloud:</label></p>
  22. <p>
  23. <textarea name="node_cloud" cols="80" rows="2" ><?php echo htmlspecialchars($node_cloud, ENT_COMPAT, 'UTF-8'); ?></textarea>
  24. </p>
  25. <p>
  26. <input id="node_edit" name="node_edit" type="submit" value="Edit" />
  27. </p>
  28. <?php if ($missing_node_title or $missing_node_name or $bad_node_name): ?>
  29. <div class="alert">
  30. <?php if ($missing_node_title): ?>
  31. <p>You didn't input the title.</p>
  32. <?php endif; ?>
  33. <?php if ($missing_node_name): ?>
  34. <p>Specify a name for the link.</p>
  35. <?php elseif ($bad_node_name): ?>
  36. <p>The name of the link is invalid.</p>
  37. <?php endif; ?>
  38. </div>
  39. <?php endif; ?>
  40. </form>

Notice how the contents the fields are protected with the PHP function htmlspecialchars.

Add the view of the editor in French in the folder views/en/editing with the following content:

  1. /cms/site24
    1. views
      1. fr
        1. editing
          1. editnode.phtml
  1. <?php extract($errors); ?>
  2. <form class="compact" action="" method="post">
  3. <input name="clang" type="hidden" value="<?php echo $clang; ?>" />
  4. <p>
  5. <?php if (in_array('fr', $supported_languages)): ?>
  6. <input name="fr" type="image" src="<?php echo $base_path; ?>/images/theme/flags/fr.png" alt="fr" title="Français"/>
  7. <?php endif; ?>
  8. <?php if (in_array('en', $supported_languages)): ?>
  9. <input name="en" type="image" src="<?php echo $base_path; ?>/images/theme/flags/en.png" alt="en" title="English"/>
  10. <?php endif; ?>
  11. </p>
  12. <p>
  13. <input id="node_title" name="node_title" type="text" size="40" maxlength="100" value="<?php echo htmlspecialchars($node_title, ENT_COMPAT, 'UTF-8'); ?>" title="Titre" onkeypress="return submitonenter(event, 'node_edit')"/>
  14. <label>URL :</label>
  15. <input id="node_name" name="node_name" type="text" size="40" maxlength="100" value="<?php echo $node_name; ?>" onkeypress="return focusonenter(event, 'node_title')"/>
  16. </p>
  17. <p><label>Extrait :</label></p>
  18. <p>
  19. <textarea name="node_abstract" cols="80" rows="3"><?php echo htmlspecialchars($node_abstract, ENT_COMPAT, 'UTF-8'); ?></textarea>
  20. </p>
  21. <p><label>Nuage :</label></p>
  22. <p>
  23. <textarea name="node_cloud" cols="80" rows="2" ><?php echo htmlspecialchars($node_cloud, ENT_COMPAT, 'UTF-8'); ?></textarea>
  24. </p>
  25. <p>
  26. <input id="node_edit" name="node_edit" type="submit" value="Éditer" />
  27. </p>
  28. <?php if ($missing_node_title or $missing_node_name or $bad_node_name): ?>
  29. <div class="alert">
  30. <?php if ($missing_node_title): ?>
  31. <p>Vous n'avez pas saisi le titre.</p>
  32. <?php endif; ?>
  33. <?php if ($missing_node_name): ?>
  34. <p>Spécifiez un nom pour le lien.</p>
  35. <?php elseif ($bad_node_name): ?>
  36. <p>Le nom du lien est invalide.</p>
  37. <?php endif; ?>
  38. </div>
  39. <?php endif; ?>
  40. </form>

Edit the file models/node.inc and add the function node_set:

  1. function node_set($lang, $node_id, $node_name, $node_title, $node_abstract, $node_cloud) {
  2.     $tabnode=db_prefix_table('node');
  3.  
  4.     $sql="UPDATE $tabnode SET modified=NOW() WHERE node_id=$node_id LIMIT 1";
  5.     $r = db_update($sql);
  6.  
  7.     if (!$r) {
  8.         return false;
  9.     }
  10.  
  11.     $sqllang=db_sql_arg($lang, false);
  12.     $sqlname=db_sql_arg($node_name, true);
  13.     $sqltitle=db_sql_arg($node_title, true);
  14.     $sqlabstract=db_sql_arg($node_abstract, true, true);
  15.     $sqlcloud=db_sql_arg($node_cloud, true, true);
  16.  
  17.     $tabnodelocale=db_prefix_table('node_locale');
  18.  
  19.     $sql="INSERT $tabnodelocale SET node_id=$node_id, locale=$sqllang, name=$sqlname, title=$sqltitle, abstract=$sqlabstract, cloud=$sqlcloud ON DUPLICATE KEY UPDATE node_id=LAST_INSERT_ID(node_id), locale=VALUES(locale), name=VALUES(name), title=VALUES(title), abstract=VALUES(abstract), cloud=VALUES(cloud)";
  20.  
  21.     $r = db_insert($sql);
  22.  
  23.     return $r;
  24. }

node_set strats by updating the field modified of the node $node_id in the table node. If the operation fails, the node $node_id doesn't exist and node_set returns false. node_set inserts an entry in the table node_locale or replaces the data of the existing node. node_set returns true or false in case of error.

Edit the file blocks/banner.php to add the buttons Edit and View:

  1.     $is_writer = user_has_role('writer');

Sets $is_writer to true if the is allowed to modify the content of the site.

  1.                 case 'edit':
  2.                     if ($param) {
  3.                         if ($is_writer) {
  4.                             $edit_page=$param;
  5.                             $edit=true;
  6.                         }
  7.                     }
  8.                     break;

Adds a link to edit a content if $components contains 'edit' and if the is allowed to edit it. $param defines the URL of the link.

  1.                 case 'view':
  2.                     if ($param) {
  3.                         if ($is_writer) {
  4.                             $view_page=$param;
  5.                             $view=true;
  6.                         }
  7.                     }
  8.                     break;

Adds a link to view a content if $components contains 'view' and if the is allowed to edit it. $param defines the URL of the link.

  1.     if ($contact or $login or $logout or $edit or $view or $validate) {
  2.         $menu = view('bannermenu', $lang, compact('contact', 'contact_page', 'edit', 'edit_page', 'view', 'view_page', 'validate', 'validate_page', 'logout', 'nobody_page', 'login', 'user_page'));
  3.     }

Builds the view of the banner passing all the parameters computed before. Generates the content of the banner and returns it.

Complete the views of the menu in English and in French:

  1. <?php if ($edit): ?>
  2. <li><a id="edit" href="<?php echo $edit_page; ?>" rel="nofollow" title="Edit"><span>Edit</span></a></li>
  3. <?php elseif ($view): ?>
  4. <li><a id="view" href="<?php echo $view_page; ?>" rel="nofollow" title="View"><span>View</span></a></li>
  5. <?php endif; ?>
  1. <?php if ($edit): ?>
  2. <li><a id="edit" href="<?php echo $edit_page; ?>" rel="nofollow" title="Éditer"><span>Éditer</span></a></li>
  3. <?php elseif ($view): ?>
  4. <li><a id="view" href="<?php echo $view_page; ?>" rel="nofollow" title="Voir"><span>Afficher</span></a></li>
  5. <?php endif; ?>

Create the folder theme in the folder images then the folders icons and flags in the folder images/theme. From the folder buttons, move the files cancel.png, check.png, mail.png and user.png to the folder images/theme/icons then the files en.png and fr.png to the folder images/theme/flags. Delete the folder buttons. Finally, move the file heading.png from the folder images to the folder images/theme.

  1. /cms/site24
    1. buttons
    2. images
      1. theme
        1. icons
          1. cancel.png
          2. check.png
          3. mail.png
          4. user.png
        2. flags
          1. en.png
          2. fr.png
        3. heading.png

Add the icons edit.png and web.png in the folder images/theme/icons:

  1. /cms/site24
    1. images
      1. theme
        1. icons
          1. edit.png
          2. web.png

Reflect these changes in the file css/theme.css:

  1. h3 {line-height:24px;background:transparent url(../images/theme/heading.png) no-repeat;text-indent:30px;margin:0 0 0.5em 0;}

Moves the image displayed ahead of the <h3> titles in the folder images/theme.

  1. #bannermenu #mail {width:24px;height:24px;float:right;margin-left:6px;background:transparent url(../images/theme/icons/mail.png) no-repeat center center;}
  2. #bannermenu #exit {width:24px;height:24px;float:left;margin-right:6px;background:transparent url(../images/theme/icons/cancel.png) no-repeat center center;}
  3. #bannermenu #enter {width:24px;height:24px;float:right;margin-right:6px;background:transparent url(../images/theme/icons/user.png) no-repeat center center;}
  4. #bannermenu #edit {width:24px;height:24px;float:left;margin-right:6px;background:transparent url(../images/theme/icons/edit.png) no-repeat center center;}
  5. #bannermenu #view {width:24px;height:24px;float:left;margin-right:6px;background:transparent url(../images/theme/icons/web.png) no-repeat center center;}
  6. #bannermenu #validate {width:24px;height:24px;float:left;margin-right:6px;background:transparent url(../images/theme/icons/check.png) no-repeat center center;}

Associate the links in the banner menu with their icons in the folder images/theme/icons.

  1. #french {width:32px;height:24px;float:left;margin-right:2px;background:transparent url(../images/theme/flags/fr.png) no-repeat center center;}
  2. #english {width:32px;height:24px;float:left;margin-right:2px;background:transparent url(../images/theme/flags/en.png) no-repeat center center;}

Moves the images of the flags in the folder images/theme/flags.

  1. div.alert {color:#333333;background-color:#ffcccc;padding:0.5em 1em 0.5em 1em;}
  2. div.warning {color:#333333;background-color:#ffffe0;padding:0.5em 1em 0.5em 1em;}
  3. div.alert p, div.warning p {margin-top:0.5em; margin-bottom:0.5em;}

Defines the style of the error messages.

Enter http://localhost/cms/site24/en in the address bar of your navigator. Identify yourself with the name foobar and the password f00bar. Click on the link Legal information in the home page. Press the Edit button in the banner of the page.

Erase the URL, change the title of the node, its description, add a keyword to the cloud the press Edit. NOTE: If you change the URL, remember to modify the link in the footer. Edit the content in French. Enter http://localhost/cms/site24/fr/edition/noeud/1 in the address bar of your navigator to test the editor in French.

Comments

To add a comment, click here.