Каталог товаров

Итак, каталог продукции некоторой фирмы. Для хранения информации о товарах и категориях используем две таблицы: categories и products. Категория может содержать вложенные категории и/или товары. Проводя аналогию с файловой системой, директория может содержать вложенные директории и/или файлы. Каждый элемент таблиц categories и products содержит ссылку на родительский элемент - parent.

Таблица categories:
id_ctg - уникальный идентификатор категории
parent - ссылка на родительский элемент
title - название категории
sortorder - порядок сортировки

Таблица products:
id_prd - уникальный идентификатор товарной позиции
parent - ссылка на родительский элемент
title - наименование товарной позиции
description - описание товара
sortorder - порядок сортировки

Таблица categories:

1;0;Извещатели охранные;1
2;0;Извещатели пожарные;2
3;0;Приборы приемно-контрольные;3
6;1;Извещатели охранные магнитоконтактные;1
7;1;Извещатели охранные электроконтактные;2
8;1;Извещатели охранные ударноконтактные;3
9;2;Извещатели пожарные тепловые;1
10;2;Извещатели пожарные дымовые;2
11;2;Извещатели пожарные комбинированные;3
17;3;Приборы приемно-контрольные охранные;1
18;3;Приборы приемно-контрольные пожарные;2
19;3;Приборы приемно-контрольные охранно-пожарные;3

Таблица products:

1;6;Извещатель охранный магнитоконтактный ИО 102-2;Технические характеристики ИО 102-2;1
2;6;Извещатель охранный магнитоконтактный ИО 102-4;Технические характеристики ИО 102-4;2
3;6;Извещатель охранный магнитоконтактный ИО 102-14;Технические характеристики ИО 102-14;3
4;7;Извещатель охранный электроконтактный ИО 201-1;Технические характеристики ИО 201-1;1
5;7;Извещатель охранный электроконтактный ВПК 2112;Технические характеристики ВПК 2112;2
6;8;Извещатель охранный ударноконтактный "Окно-4";Технические характеристики "Окно-4";1
7;8;Извещатель охранный ударноконтактный "Окно-5";Технические характеристики "Окно-5";2
8;8;Извещатель охранный ударноконтактный "Окно-6";Технические характеристики "Окно-6";3
9;9;Извещатель пожарный тепловой ИП 114-01;Технические характеристики ИП 114-01;1
10;9;Извещатель пожарный тепловой ИП 101-1A;Технические характеристики ИП 101-1A;2
11;9;Извещатель пожарный тепловой ИП 101-30;Технические характеристики ИП 101-30;3
12;10;Извещатель пожарный дымовой ИП 212-3СМ;Технические характеристики ИП 212-3СМ;1
13;10;Извещатель пожарный дымовой ИП 212-18СИ;Технические характеристики ИП 212-18СИ;2
14;11;Извещатель пожарный комбинированный ИП 212/101-78-А1;Технические характеристики ИП 212/101-78-А1;1
15;11;Извещатель пожарный комбинированный ИП 212/101-18 А3R1;Технические характеристики ИП 212/101-18 А3R1;2
и т.д.

Скрипт каталога товаров:

<?php
$dblocation = "localhost";   // Имя сервера
$dbuser     = "root";        // Имя пользователя
$dbpswrd    = "";            // Пароль
$dbname     = "catalog";     // Имя базы данных

// Соединение с сервером базы данных
$dblink = mysql_connect( $dblocation, $dbuser, $dbpswrd );
mysql_query( 'SET NAMES cp1251' );
// Выбираем базу данных
mysql_select_db( $dbname, $dblink );

if ( isset( $_GET['action'] ) ) {
  $action = $_GET['action'];
  $actions = array( 'showctg', 'showprd' );
  if ( !in_array($action, $actions) ) $action = 'showctg';
} else {
  $action = 'showctg';
}

if ( isset( $_GET['id_ctg'] ) ) {
  $id_ctg = (int)$_GET['id_ctg'];
  if ( $id_ctg < 0 ) $id_ctg = 0;
} else {
  $id_ctg = 0;
}
   
if ( isset( $_GET['id_prd'] ) ) {
  $id_prd = (int)$_GET['id_prd'];
  if ( $id_prd < 0 ) $id_prd = 0;
} else {
    $id_prd = 0;   
}

?>
<html>
<head>
<title>Каталог товаров</title>
</head>
<body>
<table border="1" cellpadding="4" cellspacing="0">
<tr><td colspan="2"><h1>Логотип компании</h1></td></tr>
<tr valign="top">
<td width="30%">
<?php
  // В левой части страницы выводим меню каталога
  // Сначала получаем путь до выбранной категории
  $path = category_path( $id_ctg );
  $out = process_categories(0, $path, $id_ctg);
  // Выводим меню каталога
  foreach ( $out as $ctg )
  {
    // Делаем небольшой отступ слева - чтобы выводить категории "ступенькой":
    // чем больше уровено вложенности, тем болше отступ
    echo '<div style="margin-left:'.($ctg[2]*20).'px">';
    if ( $id_ctg == $ctg[0] )
      echo '<a href="'.$_SERVER['PHP_SELF'].'?action=showctg&id_ctg='.$ctg[0].'" style="font-weight:bold">'.$ctg[1].'</a>';
    else
      echo '<a href="'.$_SERVER['PHP_SELF'].'?action=showctg&id_ctg='.$ctg[0].'">'.$ctg[1].'</a>';
    echo '</div>';
  }
?>
</td>
<td width="70%">
<?php
  // Выводим главное содержание страницы - в зависимости от значения
  // переменной $_GET['action'] мы выводим либо содержимое категории
  // либо карточку товара
  switch( $action ) {
    case 'showctg':
      // запрашиваем из БД вложенные категории и товары и выводим их на страницу
      get_category( $id_ctg );
      break;
    case 'showprd':
      // запрашиваем из БД конкретный товар и выводим его на страницу
      get_product( $id_prd );
    break;
}
?>
</td>
</tr>
<tr><td colspan="2" align="right">Copyright</td></tr>
</table>
</body>
</html>

<?php
// Функция возвращает список подкатегорий и товаров выбраной категории $id_ctg
function get_category( $id_ctg )
{   
  if ( $id_ctg != 0 ) {
    // Получаем из БД название категории
    $query = 'SELECT title FROM categories WHERE id_ctg='.$id_ctg;
    $res = mysql_query( $query );
    // если такая категория не найдена
    if ( mysql_num_rows( $res ) > 0 ) {
      echo '<h2>'.mysql_result( $res, 0, 0 ).'</h2>';
    } else {
      $id_ctg = 0;
      echo '<h2>Каталог</h2>'."\n";
    }
  } else {
    echo '<h2>Каталог</h2>'."\n";
  }
 
  // Получаем данные о всех подкатегориях текущей категории
  $query = 'SELECT id_ctg, title FROM categories WHERE parent='.$id_ctg.' ORDER BY sortorder';
  $res = mysql_query( $query );
  if ( mysql_num_rows( $res ) > 0 ) {
    echo '<ul>';
    while( $ctg = mysql_fetch_array( $res ) ) {
      echo '<li><a href="'.$_SERVER['PHP_SELF'].'?action=showctg&id_ctg='.$ctg['id_ctg'].'">'.$ctg['title'].'</a></li>';
    }
    echo '</ul>';
  }   
   
  // Получаем данные о всех товарах текущей категории
  $query = 'SELECT id_prd, title FROM products WHERE parent='.$id_ctg.'  ORDER BY sortorder';
  $res = mysql_query( $query );
  if ( mysql_num_rows( $res ) > 0 ) {
    echo '<ul>';
    while( $prd = mysql_fetch_array( $res ) ) {
      echo '<li><a href="'.$_SERVER['PHP_SELF'].'?action=showprd&id_ctg='.$id_ctg.'&id_prd='.$prd['id_prd'].'">'.$prd['title'].'</a></li>';
    }
    echo '</ul>';
  } else {
    echo '<p>Нет товаров в этой категории</p>';
  }
}

// Просмотр подробной информации о товаре (карточка товара)
function get_product( $id_prd )
{
  $query = "SELECT title, description FROM products WHERE id_prd=".$id_prd;
  $res = mysql_query( $query );
  if ( mysql_num_rows( $res ) == 1 ) {
    $prd = mysql_fetch_array( $res );
    // Выводим подробную информацию о товаре
    echo '<h2>'.$prd['title'].'</h2>';
    echo '<div>'.$prd['description'].'</div>';
  } else {
    echo '<p>Товар не найден</p>';
  }
}

// Функция возвращает массив категорий с уровнями вложенности для построения
// навигационной панели слева
function process_categories($level, $path, $id_ctg)
{
  // $level - текущий уровень: 0 для корня каталога, 1 для его подкатегорий, и т.д.
  // $path - путь от корня каталога до выбранной категории
  // $id_ctg - ID выбранной категории
  $out = array();
  $cnt = 0;
  $query = "SELECT id_ctg, title FROM categories WHERE parent=".$path[$level]." ORDER BY sortorder";
  $res = mysql_query( $query );
  while ($row = mysql_fetch_row($res))
  {
    $out[$cnt][0] = $row[0];   // category ID
    $out[$cnt][1] = $row[1];   // category name
    $out[$cnt][2] = $level;    // catagory level
    $cnt++;

    // получить подкатегории?
    if ($level+1 < count($path) && $row[0] == $path[$level+1])
    {
      // рекурсивный вызов функции process_categories
      $sub_out = process_categories($level+1, $path$id_ctg);
      //добавляем подкатегории в конец массива $out
      for ($j=0; $j<count($sub_out); $j++)
      {
        $out[] = $sub_out[$j];
        $cnt++;
      }
    }
  }
  return $out;
}

// Функция возвращает путь до текущей категории
// Возвращает массив $path, содержащий categoryID от корня до текущей категории
// $path[0]=0, $path[1]=5, $path[2]=$currentCategoryID
function category_path( $id_ctg )
{
  // строим путь до категории (каталога)
  $path[0] = $id_ctg;
  $curr = $id_ctg;
  while ($curr)
  {
    $res = mysql_query( 'SELECT parent FROM categories WHERE id_ctg='.$curr );
    if ( mysql_num_rows( $res ) > 0 ) {
      $row = mysql_fetch_row($res);
      $curr = $row[0];
      $path[] = $curr;
    } else {
      $curr = 0;
      $path[0] = 0;
    }
  }
  // теперь переворачиваем массив $path
  $path = array_reverse($path);
  return $path;
}
?>

Скрипт импорта RSS новостей

RSS — это формат, предназначенный для публикации новостей на новостных и подобных им сайтах, но по сути, публиковать можно не только новости. Практически любой материал, который можно разделить на отдельные части, можно публиковать с помощью RSS.

RSS — это разновидность XML. Изначально придуманный Netscape для их портала Netcenter, он быстро завоевал популярность и стал черезвычайно широко использоваться. Сегодня мы напишем скрипт для импорта RSS новостей с другого сайта.

Пример RSS-ленты:

<?xml version="1.0" encoding="Windows-1251"?>
<rss version="2.0">
  <channel>

  <title>Новости по системам безопасности</title>
  <link>http://www.secnews.ru</link>
  <description>
  Российские новости по техническим средствам и системам безопасности (видеонаблюдение, контроль доступа,
  охранно-пожарная сигализация...).
  </description>
  <lastBuildDate>Fri, 04 Jul 2008 09:58:29 +0400</lastBuildDate>
  <ttl>6</ttl>

  <item>
    <title>&quot;Техносила&quot; и СМ ТРЭЙД -- 37 магазинов за полгода</title>
    <link>http://www.secnews.ru/russian/11392.htm</link>
    <description>
    В I и II кварталах 2008 года компания СМ ТРЭЙД выполнила работы по оснащению противокражными
    антеннами и системами защиты товаров на стеллажах 37 магазинов &quot;Техносила&quot; в различных
    городах.
    </description>
    <enclosure url="http://www.secnews.ru/upload/iblock/5c7/5c7a8b666bcffb125c34bcabac4c9510.jpg"
    length="4438" type="image/jpeg" width="100" height="100"/>

    <category>Новости компаний/</category>
    <pubDate>Wed, 02 Jul 2008 00:00:00 +0400</pubDate>
  </item>

  <item>
    <title>CCTV от JVC -- подтверждено соответствие ГОСТ Р</title>
    <link>http://www.secnews.ru/russian/11382.htm</link>
    <description>
    В июне 2008 г компанией JVC получены сертификаты соответствия ГОСТ Р на все производимые
    продукты для профессионального использования. Это коснулось и компонентов систем безопасности.
    </description>
    <enclosure url="http://www.secnews.ru/upload/iblock/958/958b332757db459af234b994be125422.jpg"
    length="3772" type="image/jpeg" width="100" height="100"/>

    <category>Новости компаний/</category>
    <pubDate>Tue, 01 Jul 2008 00:00:00 +0400</pubDate>
  </item>

  <item>
    <title>Поддержка камер Arecont Vision в видеосерверах Domination</title>
    <link>http://www.secnews.ru/russian/11384.htm</link>
    <description>
    Привлекательность камер от Arecont Vision неоспорима: разрешение от 1,3 до 8 Мпикс, встроенные детекторы
    движения, подача электропитания по кабелям Ethernet и при этом -- весьма умеренная цена. А с июня 2008 г.
    у камер появляется еще одно преимущество -- способность работать с видеосерверами семейства Domination IP,
    производимыми и поставляемыми компанией ВИПАКС.
    </description>
    <enclosure url="http://www.secnews.ru/upload/iblock/ac3/ac370d5de67a7d543daf085698809611.jpg"
    length="3582" type="image/jpeg" width="100" height="100"/>

    <category>Новости компаний/</category>
    <pubDate>Tue, 01 Jul 2008 00:00:00 +0400</pubDate>
  </item>

</channel>
</rss>

Скрипт, читающий RSS-ленту со стороннего сайта:

<?php
$url = 'http://www.secnews.ru/russian/rss.php';

$reg_exp  = '#<item>.*?<title>(.*?)<\/title>.*?';
$reg_exp .='<link>(.*?)<\/link>.*?<description>';
$reg_exp .='(.*?)<\/description>.*?<\/item>#si';

$pattern = '<a href="%s">%s</a><br>%s<hr>';

if ( $xml_data = file_get_contents($url) ) {
    $rss_data = parse_rss($reg_exp, $xml_data);
    echo output_rss($pattern, $rss_data);
}

function parse_rss($reg_exp, $xml_data) {
    preg_match_all($reg_exp, $xml_data, $temp);
    return array(
        'count'=>count($temp[0]),
        'title'=>$temp[1],
        'link'=>$temp[2],
        'desc'=>$temp[3]
    );
}

function output_rss($pattern, $rss_data) {
    for($i=0; $i<$rss_data['count']; $i++) {
        $temp .= sprintf($pattern,
            $rss_data['link'][$i],
            html_entity_decode($rss_data['title'][$i]),
            html_entity_decode($rss_data['desc'][$i])
        );
    }
    return $temp;
}
?>

Как видите, для извлечения информации здесь используются регулярные выражения. Но использовать PCRE для решения такой задачи — это стрельба из пушки по воробьям. В PHP есть гораздо более удобные средства для работы с XML — например, SimpleXML:

<?php
$rss = simplexml_load_file ( 'http://www.secnews.ru/russian/rss.php' );
header("Content-Type: text/html; charset=utf-8");
// print_r( $rss );
echo '<h1><a href="'.$rss->channel->link.'">'.$rss->channel->title.'</a></h1>'."\n";
echo '<table>'."\n";
foreach ( $rss->channel->item as $item ) 
{
  echo '<tr valign="top">';
  $image = $item->enclosure;
  echo '<td>';
  echo '<img src="'.$image['url'].'" width="'.$image['width'].'" height="'.$image['height'].'" alt="" />';
  echo '</td>';
  echo '<td>';
  echo '<h3><a href="'.$item->link.'">'.$item->title.'</a></h3>';
  echo '<div>'.$item->description.'</div>';
  echo '</td>';
  echo '</tr>'."\n";
  echo '<tr><td colspan="2">&nbsp;</td></tr>'."\n"
 }
echo '</table>';
?>

Ниже показана структура, созданная после вызова функции simplexml_load_file()

SimpleXMLElement Object
(
    [@attributes] => Array
        (
            [version] => 2.0
        )

    [channel] => SimpleXMLElement Object
        (
            [title] => Новости по системам безопасности
            [link] => http://www.secnews.ru
            [description] =>
  Российские новости по техническим средствам и системам безопасности (видеонаблюдение, контроль доступа,
  охранно-пожарная сигализация...).
 
            [lastBuildDate] => Fri, 04 Jul 2008 09:58:29 +0400
            [ttl] => 6
            [item] => Array
                (
                    [0] => SimpleXMLElement Object
                        (
                            [title] => "Техносила" и СМ ТРЭЙД -- 37 магазинов за полгода
                            [link] => http://www.secnews.ru/russian/11392.htm
                            [description] =>
    В I и II кварталах 2008 года компания СМ ТРЭЙД выполнила работы по оснащению противокражными
    антеннами и системами защиты товаров на стеллажах 37 магазинов "Техносила" в различных
    городах.
   
                            [enclosure] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [url] => http://www.secnews.ru/upload/iblock/5c7/5c7a8b666bcffb125c34bcabac4c9510.jpg
                                            [length] => 4438
                                            [type] => image/jpeg
                                            [width] => 100
                                            [height] => 100
                                        )

                                )

                            [category] => Новости компаний/
                            [pubDate] => Wed, 02 Jul 2008 00:00:00 +0400
                        )

                    [1] => SimpleXMLElement Object
                        (
                            [title] => CCTV от JVC -- подтверждено соответствие ГОСТ Р
                            [link] => http://www.secnews.ru/russian/11382.htm
                            [description] =>
    В июне 2008 г компанией JVC получены сертификаты соответствия ГОСТ Р на все производимые
    продукты для профессионального использования. Это коснулось и компонентов систем безопасности.
   
                            [enclosure] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [url] => http://www.secnews.ru/upload/iblock/958/958b332757db459af234b994be125422.jpg
                                            [length] => 3772
                                            [type] => image/jpeg
                                            [width] => 100
                                            [height] => 100
                                        )

                                )

                            [category] => Новости компаний/
                            [pubDate] => Tue, 01 Jul 2008 00:00:00 +0400
                        )

                    [2] => SimpleXMLElement Object
                        (
                            [title] => Поддержка камер Arecont Vision в видеосерверах Domination
                            [link] => http://www.secnews.ru/russian/11384.htm
                            [description] =>
    Привлекательность камер от Arecont Vision неоспорима: разрешение от 1,3 до 8 Мпикс, встроенные детекторы
    движения, подача электропитания по кабелям Ethernet и при этом -- весьма умеренная цена. А с июня 2008 г.
    у камер появляется еще одно преимущество -- способность работать с видеосерверами семейства Domination IP,
    производимыми и поставляемыми компанией ВИПАКС.
   
                            [enclosure] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [url] => http://www.secnews.ru/upload/iblock/ac3/ac370d5de67a7d543daf085698809611.jpg
                                            [length] => 3582
                                            [type] => image/jpeg
                                            [width] => 100
                                            [height] => 100
                                        )

                                )

                            [category] => Новости компаний/
                            [pubDate] => Tue, 01 Jul 2008 00:00:00 +0400
                        )

                )

        )

)

Разумеется, запрашивать каждый раз файл со стороннего сайта, парсить его, формировать html — дело накладное (и по времени и по нагрузке). Надо запросить этот XML-документ раз в сутки (для этого идеально подходит CRON), сформировать HTML и записать в файл или базу данных. А скрипт, который выводит RSS-ленту будет просто брать готовый HTML из файла или из БД.

Ссылки по теме:

Общее представление о JSON

JSON (JavaScript Object Notation) означает запись объектов JavaScript. В литературе по JavaScript массивы определяются следующим образом:

var ajax = new Array( "Asynchronous", "JavaScript", "+", "XML" );

Однако существует и более компактный способ их записи с помощью квадратных скобок:

var ajax = [ "Asynchronous", "JavaScript", "+", "XML" ];

Это же относится и к объектам, которые в JavaScript представляют собой в какой-то степени массивы более высокого уровня. Но в данном случае для компактной записи объектов используются фигурные скобки. В следующем фрагменте кода определяется объект с тремя свойствами:

var book = {
    "title": "JavaScript Phrasebook",
    "publisher": "Sams Publishing",
    "pubdate": 2006
};

Оба вида сокращенной записи можно объединить вместе, чтобы представить сложные данные в виде строки. В этом, собственно, и заключается весь смысл JSON. Благодаря возможности компактно записывать данные в строке этот формат отлично подходит для преобразования данных в строку и обратно. Создать строковое представление данных в формате JSON на стороне сервера не представляет труда. А еще проще преобразовать строку из формата JSON в объект JavaScript на стороне клиента:

var json = XMLHttp.responseText;
var book = eval( "(" + json + ")" );

JSON постепенно становится стандартом форматом обмена данными для приложений AJAX. В версии PHP 6, скорее всего, появится поддержка JSON на базовом уровне.

Файл json.html

<script type="text/javascript">
var XMLHttp = getXMLHttp();
XMLHttp.open("GET", "json.php", true);
XMLHttp.onreadystatechange = handlerFunction;
XMLHttp.send(null);

function handlerFunction() {
  if (XMLHttp.readyState == 4) {
    var json = XMLHttp.responseText;
    var book = eval("(" + json + ")");
    var pubdate = book.pubdate;
    var title = book.title;
    var publisher = book.publisher;
    window.alert(title + " by " + publisher + " (" + pubdate + ")");
  }
}

function getXMLHttp() {
  if (window.XMLHttpRequest) {
    try {
      XMLHttp = new XMLHttpRequest();
    } catch (e) { }
  } else if (window.ActiveXObject) {
    try {
      XMLHttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        XMLHttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) { }
    }
  }
  return XMLHttp;
}
</script>

Файл json.php

<?php
$json = '{
  "title": "JavaScript Phrasebook",
  "publisher": "Sams Publishing",
  "pubdate": 2006
}'
;
echo $json;
?>

Пользоваться функцией eval() обычно не рекомендуется, поскольку это может серьезно нарушить безопасность системы, если данные в формате JSON поступают из ненадежного источника. В связи с тем что действие объекта XMLHTTPRequest ограничивается тем же доменом, где находится web-сервер, данным в формате JSON можно доверять. Но для большей надежности рекомендуется загрузить библиотеку json.js по адресу http://www.json.org/js и заменить вызов функции eval() следующей строкой кода:

var book = json.parseJSON();