22

Content structure

Create site19 by copying site18.

  1. /cms
    1. ...
    2. site18
    3. site19

In this chapter, we are going to program how the content of the site is managed and displayed.

To test the result online, enter http://www.frasq.org/cms/site19/en/node/1 in the address bar of your navigator. The site displays the content of node 1 in English. Enter http://www.frasq.org/cms/site19/fr/noeud/1 to display its translation in French.

A node has a title, a name, an abstract and a cloud of words associated to it. A node has a content, in this case, a text in HTML.

Display the source code of the page. Notice that the tag <meta name="description" /> contains the abstact of the node and that the tag <meta name="keywords" /> contains the cloud of words. This information is normally exploited by search engines. The name of a node will be used to designate it in a URL. Its title is generally displayed at the beginning of a page.

Start the command processor of MySQL and enter the site's DB:

$ mysql -u root -p
mysql> use frasqdb2;

NOTE: Use phpMyAdmin for more comfort.

Add the table node then the table node_locale to the DB of the site:

CREATE TABLE node (
node_id INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT ,
user_id INT( 10 ) UNSIGNED NOT NULL ,
created datetime NOT NULL ,
modified datetime NOT NULL ,
PRIMARY KEY ( node_id )
) ENGINE = MYISAM DEFAULT CHARSET = utf8;

node contains the properties of a node which are in common for all languages: its identifier, the identifier of the user who has modified it for the last time, its creation and modification dates. The primary key of a node is its identifier.

CREATE TABLE node_locale (
node_id INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT ,
locale enum( 'fr', 'en' ) NOT NULL DEFAULT 'fr',
name VARCHAR( 100 ) NOT NULL ,
title VARCHAR( 100 ) NOT NULL ,
abstract text,
cloud text,
PRIMARY KEY ( node_id , locale )
) ENGINE = MYISAM DEFAULT CHARSET = utf8;

A node_locale contains the properties of a node which depend on a language: the designation of the language of the content, the name of the node, its title, the abstract and the list of words associated to the node. The identifier of a node_locale matches the identifier of a node. The designations of the languages must match the languages listed by the configuration parameter $supported_languages defined in the file includes/config.inc. The primary key of a node_locale is its identifier and the designation of the language of its content.

CREATE TABLE content_text (
content_id INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT ,
locale enum( 'fr', 'en' ) NOT NULL DEFAULT 'fr',
text text,
PRIMARY KEY ( content_id , locale )
) ENGINE = MYISAM DEFAULT CHARSET = utf8;

A content_text contains a content of type text for a given language, usually in HTML. The primary key of a content_text is its identifier and the designation of the language of its content.

The link between a node and its contents is done by the table node_content:

CREATE TABLE node_content (
node_id INT( 10 ) UNSIGNED NOT NULL ,
content_id INT( 10 ) UNSIGNED NOT NULL ,
content_type enum( 'text' ) CHARACTER SET ascii NOT NULL DEFAULT 'text',
`number` INT( 3 ) UNSIGNED NOT NULL ,
PRIMARY KEY ( node_id , content_id , content_type )
) ENGINE = MYISAM DEFAULT CHARSET = utf8;

A node_content lists and orders the contents of a node. The identifier node_id matches the identifier of a node. content_id associated to a content_type matches the identifier of a content in content_text. The primary key of a node_content is the identifier of a node, the identifier of a content and its type.

Create a first node:

INSERT INTO node (node_id, user_id, created, modified) VALUES
(1, 1, NOW(), NOW());

Node 1 is created by the user 1, the administrator of the site. Check in the table user.

Add its properties in English:

INSERT INTO node_locale (node_id, locale, name, title, abstract, cloud) VALUES
(1, 'en', 'legal-information', 'Legal information', 'Author''s liability - Rights.', 'copyright liability author rights distribution');

Add the version in French:

INSERT INTO node_locale (node_id, locale, name, title, abstract, cloud) VALUES
(1, 'fr', 'informations-legales', 'Informations légales', 'Responsabilité de l''auteur - Droits.', 'copyright responsabilité auteur droits diffusion');

Add a content in English:

INSERT INTO content_text (content_id, locale, text) VALUES
(1, 'en', '<p>The information, the techniques and the software ("CONTENT")\r\nprovided by this web site are for educational purposes only\r\nand should not be construed as a commitment by their author ("AUTHOR").</p>\r\n<p>The CONTENT is provided "AS IS", without warranty of any kind, express or implied,\r\nincluding but not limited to the warranties of merchantability,\r\nfitness for a particular purpose and noninfringment of third party rights.\r\nIn no event shall the AUTHOR be liable for any claim,\r\nor any special indirect or consequential damages,\r\nor any damages whatsoever resulting from loss of use, data or profits,\r\nwhether in an action of contract, negligence or other tortious action,\r\narising out of or in connection with the use or performance of the CONTENT.</p>');

Add its translation in French:

INSERT INTO content_text (content_id, locale, text) VALUES
(1, 'fr', '<p>Les informations, les techniques et les logiciels ("CONTENU")\r\nfournis par ce site ont un but purement éducatif\r\net ne doivent pas être interprétés comme un engagement par leur auteur ("AUTEUR").</p>\r\n<p>Le CONTENU est fourni "EN L''ÉTAT" sans garantie d''aucune sorte, expresse ou implicite,\r\ny compris, mais sans limitation, les garanties de qualité marchande,\r\nd''adéquation à un usage particulier et de non-violation des droits de tierces parties.\r\nL''AUTEUR ne saurait en aucun cas être tenu responsable de toute réclamation\r\nou dommage indirect ou consécutif ou de tout autre dommage\r\nlié à la perte d''utilisation, de données ou de bénéfices,\r\nque ce soit dans le cadre d''un contrat, d''une négligence ou d''une autre action préjudiciable,\r\ndus ou liés à l''utilisation ou aux performances du CONTENU.</p>');

Note that the identifiers of the two versions are identical but that the language differ.

Create the association between the node and its content:

INSERT INTO node_content (node_id, content_id, content_type, `number`) VALUES
('1', '1', 'text', '1');

Associates node 1 to content 1 of type text and places it at position 1.

Try a few requests on the DB:

SELECT node_id FROM node WHERE node_id=1 LIMIT 1;

Checks that node 1 is actually created.

SELECT nloc.name AS node_name, nloc.title AS node_title, nloc.abstract AS node_abstract, nloc.cloud AS node_cloud, n.user_id AS node_user, n.created AS node_created, n.modified AS node_modified
FROM node n
JOIN node_locale nloc ON nloc.node_id=n.node_id AND nloc.locale='fr'
WHERE n.node_id=1
LIMIT 1;

Returns all the attributes of the French version of node 1.

SELECT nc.content_id AS content_id, nc.content_type AS content_type, `nc.number` AS content_number, ct.text AS content_text
FROM node_content nc
JOIN content_text ct ON nc.content_type='text' AND ct.content_id=nc.content_id AND ct.locale='fr'
WHERE nc.node_id=1
ORDER BY `nc.number`;

Returns the content in French of node 1.

Add the file node.inc in the folder models with the following content:

  1. function node_id($node) {
  2.     if (!is_numeric($node)) {
  3.         return false;
  4.     }
  5.  
  6.     $tabnode=db_prefix_table('node');
  7.  
  8.     $sql="SELECT node_id FROM $tabnode WHERE node_id=$node LIMIT 1";
  9.  
  10.     $r = db_query($sql);
  11.  
  12.     return $r ? $r[0]['node_id'] : false;
  13. }

node_id returns the identifier of the node whose identifier is $node, or false if the node $node doesn't exist.

  1. function node_get($lang, $node_id) {
  2.     $sqllang=db_sql_arg($lang, false);
  3.  
  4.     $tabnode=db_prefix_table('node');
  5.     $tabnodelocale=db_prefix_table('node_locale');
  6.  
  7.     $sql="SELECT nloc.name AS node_name, nloc.title AS node_title, nloc.abstract AS node_abstract, nloc.cloud AS node_cloud, n.user_id AS node_user, UNIX_TIMESTAMP(n.created) AS node_created, UNIX_TIMESTAMP(n.modified) AS node_modified FROM $tabnode n JOIN $tabnodelocale nloc ON nloc.node_id=n.node_id AND nloc.locale=$sqllang WHERE n.node_id=$node_id LIMIT 1";
  8.  
  9.     $r = db_query($sql);
  10.  
  11.     return $r ? $r[0] : false;
  12. }

node_get returns all the attributes for the language $lang of the node whose identifier is $node_id, or false in case of error. Notice that the fields node_created and node_modified are converted to a Unix time.

  1. function node_get_contents($lang, $node_id) {
  2.     $sqllang=db_sql_arg($lang, false);
  3.  
  4.     $tabnodecontent=db_prefix_table('node_content');
  5.     $tabcontenttext=db_prefix_table('content_text');
  6.  
  7.     $sql="SELECT nc.content_id AS content_id, nc.content_type AS content_type, nc.number AS content_number, ct.text AS content_text FROM $tabnodecontent nc JOIN $tabcontenttext ct ON nc.content_type='text' AND ct.content_id=nc.content_id AND ct.locale=$sqllang WHERE nc.node_id=$node_id ORDER BY nc.number";
  8.  
  9.     $r = db_query($sql);
  10.  
  11.     return $r;
  12. }

node_get_contents returns the content of the node $node_id for the language $lang, or false in case of error.

Create the action node by adding the file node.php in the folder actions with the following content:

  1. /cms/site19
    1. actions
      1. node.php
  1. require_once 'models/node.inc';
  2.  
  3. function node($lang, $arglist=false) {
  4.     $node=false;
  5.  
  6.     if (is_array($arglist)) {
  7.         if (isset($arglist[0])) {
  8.             $node=$arglist[0];
  9.         }
  10.     }
  11.  
  12.     if (!$node) {
  13.         return run('error/notfound', $lang);
  14.     }
  15.  
  16.     $node_id = node_id($node);
  17.     if (!$node_id) {
  18.         return run('error/notfound', $lang);
  19.     }
  20.  
  21.     $r = node_get($lang, $node_id);
  22.     if (!$r) {
  23.         return run('error/notfound', $lang);
  24.     }
  25.     extract($r); /* node_name node_title node_abstract node_cloud */
  26.  
  27.     head('title', $node_id);
  28.     head('description', $node_abstract);
  29.     head('keywords', $node_cloud);
  30.     head('robots', 'noindex, nofollow');
  31.  
  32.     $banner = build('banner', $lang);
  33.  
  34.     $node_contents = build('nodecontent', $lang, $node_id);
  35.  
  36.     $content = view('node', $lang, compact('node_name', 'node_title', 'node_abstract', 'node_cloud', 'node_contents'));
  37.  
  38.     $output = layout('standard', compact('banner', 'content'));
  39.  
  40.     return $output;
  41. }

The action node returns a page displaying the content of a node built by the block nodecontent. node takes one argument, a node number. If the node number is missing or invalid, node sends back an HTTP 404 Not Found error.

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

  1.         'node'                  => 'node',
  1.         'noeud'                 => 'node',

Add the view of a node in the folders views/en for the English version and views/fr for the version in French:

  1. /cms/site19
    1. views
      1. en
        1. node.phtml
      2. fr
        1. node.phtml
  1. <h3><?php echo htmlspecialchars($node_title, ENT_COMPAT, 'UTF-8'); ?></h3>
  2. <?php if ($node_abstract): ?>
  3. <h5>Abstract</h5>
  4. <p><?php echo htmlspecialchars($node_abstract, ENT_COMPAT, 'UTF-8'); ?></p>
  5. <?php endif; ?>
  6. <?php if ($node_cloud): ?>
  7. <h6>Cloud</h6>
  8. <p class="smaller"><?php echo htmlspecialchars($node_cloud, ENT_COMPAT, 'UTF-8'); ?></p>
  9. <?php endif; ?>
  10. <?php if ($node_contents): ?>
  11. <h4>Contents</h4>
  12. <?php echo $node_contents; ?>
  13. <?php endif; ?>
  1. <h3><?php echo htmlspecialchars($node_title, ENT_COMPAT, 'UTF-8'); ?></h3>
  2. <?php if ($node_abstract): ?>
  3. <h5>Extrait</h5>
  4. <p><?php echo htmlspecialchars($node_abstract, ENT_COMPAT, 'UTF-8'); ?></p>
  5. <?php endif; ?>
  6. <?php if ($node_cloud): ?>
  7. <h6>Nuage</h6>
  8. <p class="smaller"><?php echo htmlspecialchars($node_cloud, ENT_COMPAT, 'UTF-8'); ?></p>
  9. <?php endif; ?>
  10. <?php if ($node_contents): ?>
  11. <h4>Contenu</h4>
  12. <?php echo $node_contents; ?>
  13. <?php endif; ?>

Add the function nodecontent which returns the content of a node:

  1. /cms/site19
    1. blocks
      1. nodecontent.php
  1. require_once 'models/node.inc';
  2.  
  3. function nodecontent($lang, $node_id) {
  4.     $contents = array();
  5.     $r = node_get_contents($lang, $node_id);
  6.  
  7.     if ($r) {
  8.         foreach ($r as $c) {    /* content_id content_number content_type content_text */
  9.             $type=$c['content_type'];
  10.             switch($type) {
  11.                 case 'text':
  12.                     $s=$c['content_text'];
  13.                     if (!empty($s)) {
  14.                         $text = $s;
  15.                         $contents[] = compact('type', 'text');
  16.                     }
  17.                     break;
  18.                 default:
  19.                     break;
  20.             }
  21.         }
  22.     }
  23.  
  24.     $output = view('nodecontent', false, compact('contents'));
  25.  
  26.     return $output;
  27. }

Since the view of the block doesn't depend on the language, add it directly to the views folder:

  1. /cms/site19
    1. views
      1. nodecontent.phtml
  1. <?php
  2. foreach ($contents as $c) {
  3.     switch($c['type']) {
  4.         case 'text':
  5.             $text_content = $c['text'];
  6.             echo $text_content, PHP_EOL;
  7.             break;
  8.         default:
  9.             break;
  10.     }
  11. }
  12. ?>

Enter http://localhost/cms/site19/en/node/1 then http://localhost/cms/site19/fr/noeud/1 in the address bar of your navigator.

Add a second content to the page:

INSERT INTO content_text (content_id, locale, text) VALUES
(2, 'fr', '<p><i>13 mai 2010</i></p>\r\n<p><b>frasq.org</b></p>'),
(2, 'en', '<p><i>May 13, 2010</i></p>\r\n<p><b>frasq.org</b></p>');
INSERT INTO node_content (node_id, content_id, content_type, `number`) VALUES
('1', '2', 'text', '2');

Reload the pages Legal information and Informations légales.

Comments

Your comment:
[p] [b] [i] [u] [s] [quote] [pre] [br] [code] [url] [email] strip help 2000

Enter a maximum of 2000 characters.
Improve the presentation of your text with the following formatting tags:
[p]paragraph[/p], [b]bold[/b], [i]italics[/i], [u]underline[/u], [s]strike[/s], [quote]citation[/quote], [pre]as is[/pre], [br]line break,
[url]http://www.izend.org[/url], [url=http://www.izend.org]site[/url], [email]izend@izend.org[/email], [email=izend@izend.org]izend[/email],
[code]command[/code], [code=language]source code in c, java, php, html, javascript, xml, css, sql, bash, dos, make, etc.[/code].