<?php

// PHP-класс RSSChannel, версия 0.9.5
// Разработано www.hristianstvo.ru
// Некоторые идеи и фрагменты кода заимствованы из http://spectator.ru/technology/php/simple_XML
// и проекта http://rssgenesis.sourceforge.net/
// 
// Подробную документацию, историю развития проекта и примеры смотрите на странице
// http://www.hristianstvo.ru/rss/

//=============================================================
// Конфигурция

$RSSChannel_version = "0.9.5"; // версия скрипта
$use_rss_caching = false; 	// использовать или нет кэширование загружаемых RSS-каналов
$rss_cache_dir = "/tmp/";	// директория (абсолютная, оканчивающаяся на /) для хранения RSS-кэша
$request_delay = 60 * 5; 	// Если сервер не отдает HTTP-переменную Last-Modified в заголовке,
						 // считывать канал с сервера не чаще $request_delay секунд
						 // (в остальное время брать из кэша)
$autodetect_encoding = 1;	// автоматическое определение кодировки RSS-файла
$max_item_description_size = 10000; // максимальный размер описания загружаемой новости (в кБ): защита от публикации статей вместо аннотаций
$max_feed_size = 100*1024; // максимальный размер загружаемого RSS-файла (защита от огромных файлов)

$default_language = "ru";					// язык по умолчанию
$default_encoding = "utf-8";			// кодировка по умолчанию при создании новых RSS-каналов
$default_docs = "http://blogs.law.harvard.edu/tech/rss";	// ссыла на страницу с RSS-документацией
$default_channel_title = "RSS 2.0 feed";	// заголовок создаваемого канала, если он не указан Вами
$default_channel_link = "http://baraholka-ivanovo.ru/";		// ссылка на сайт канала по умолчанию
$default_max_num_items = 20;				// максимальное количество новостей в канале
if($new_default_max_num_items) $default_max_num_items = $new_default_max_num_items;
$default_item_ttl = (60*60*24*30)*2;		// максимальное время жизни новости в канале в секундах
$default_user_agent = "Mozilla/5.0 (compatible; Hristianstvo.ru)";	

function win2utf($str)
{
    static $table = array(
    "\xA8" => "\xD0\x81",
    "\xB8" => "\xD1\x91",
    "\xA1" => "\xD0\x8E",
    "\xA2" => "\xD1\x9E",
    "\xAA" => "\xD0\x84",
    "\xAF" => "\xD0\x87",
    "\xB2" => "\xD0\x86",
    "\xB3" => "\xD1\x96",
    "\xBA" => "\xD1\x94",
    "\xBF" => "\xD1\x97",
    "\x8C" => "\xD3\x90",
    "\x8D" => "\xD3\x96",
    "\x8E" => "\xD2\xAA",
    "\x8F" => "\xD3\xB2",
    "\x9C" => "\xD3\x91",
    "\x9D" => "\xD3\x97",
    "\x9E" => "\xD2\xAB",
    "\x9F" => "\xD3\xB3",
    );
    return preg_replace('#[\x80-\xFF]#se',
    ' "$0" >= "\xF0" ? "\xD1".chr(ord("$0")-0x70) :
                       ("$0" >= "\xC0" ? "\xD0".chr(ord("$0")-0x30) :
                        (isset($table["$0"]) ? $table["$0"] : "")
                       )',
    $str
    );
}
// User-agent Вашего RSS-бота при скачивании RSS-каналов

//=============================================================
// класс RSSChannel

class RSSChannel
{
 // публичные переменные
 var $items; 	// массив новостей - переменных типа RSSItem
 				// отсортированы в порядке увеличения даты публикации
 var $encoding, $title, $link, $description, $language;
 // кодировка, название канала, ссылка на сайт канала, описание канала, язык
 
 var $category, $copyright, $managingEditor, $webMaster, $rating, $docs;
 // категория для канала, копирайт, e-mail редактора канала и вебмастера,
 // рейтинг канала, ссылка на сайт с RSS-документацией
 
 var $skipDays, $skipHours;
 // перечень дней недели и часов в сутки, когда RSS-канал не должен считываться:
 // фрагменты XML-кода в формате <day>2</day><day>4</day><day>5</day>
 // Подробнее см. на http://blogs.law.harvard.edu/tech/skipHoursDays
 
 var $image_title, $image_url, $image_link, $image_width, $image_height, $image_description;
 // поля, соответствующие баннеру-изображению для канала:
 // заголовок баннера, адрес картинки, ссылка на сайт, ширина и высота баннера, описание баннера
 
 var $max_num_items; 	// максимальное число новостей в канале
 var $item_ttl; 		// максимальное время жизни новости в канале
 var $timestamp;	// Unix-timestamp последнего обновления канала
 var $XSL; // таблица стилей XSL
 var $last_error; // текстовое описание последней ошибки
 var $isEmptyFlag; // ==TRUE, если в канале нет новостей
 var $backup_XML_channel; // имя файла (по умолчанию пустое) для сохранения в нем старых RSS-записей, удаляемых из данного канала

 // конструктор по умолчанию
 function RSSChannel($max_num_items=0, $item_ttl=0)
 {
  global $default_max_num_items, $default_item_ttl;
  $this->items = array();
  $this->max_num_items = $max_num_items ? $max_num_items : $default_max_num_items;
  $this->item_ttl = $item_ttl ? $item_ttl : $default_item_ttl;
  $this->timestamp = 0; $this->XSL = ""; $this->last_error = "";
  $this->isEmptyFlag = TRUE;
 }




 // Загрузить канал из указанного файла
 // $url может быть адресом файла на локальном сервере
 // или http://-адресом файла на удаленном сервере.
 // Параметр $convert_to_Windows1251 определяет, нужно ли конвертировать
 // канал после скачивания в кодировку Windows-1251
 function load($url, $convert_to_Windows1251 = true)
 {
  global $use_rss_caching, $rss_cache_dir, $request_delay, $autodetect_encoding, $max_item_description_size, $max_feed_size;
  
  $is_local_file = !preg_match('/^(http|ftp|https):\/\//', $url);
  $cache_used = 0;
  if ($use_rss_caching && !$is_local_file)
  {
   $file = $rss_cache_dir.url2file($url); // полный путь к файлу в кэше
   $rss_last_request_time = @filemtime($file);   // время последней модификации файла в кэше
   $rss_last_modified_time = LastModified($url); // время последней модификации канала

   if ($rss_last_request_time > 0 && ($rss_last_modified_time > 0 && $rss_last_request_time > $rss_last_modified_time || 
       $rss_last_modified_time == 0 && time()-$rss_last_request_time < $request_delay))
   // если канал не изменился с момента последней проверки, берем его из кэша
   {
    $XML = _file_get_contents($file);
    $cache_used = 1;
   }
  }
  if ($is_local_file || !$XML) 
  {
   $XML = @_file_get_contents($url, $max_feed_size); // иначе берем файл с удаленной / локальной машины
   if (!$XML) 
   {
    $this->setLastError("Empty RSS-file of file not accessible");
    return FALSE; // пустой файл или такого файла нет
   }
  }
  
  if (strpos($XML, '<?xml') === false || strpos($XML, '<rss') === false) 
  {
   $this->setLastError("Not an RSS-XML file");
   return FALSE;
  }

  $header_len = strpos($XML, "<item");
  if (!$header_len) $header_len = strlen($XML);
  $header = substr($XML, 0, $header_len); // заголовок канала лежит до первого тэга <item>
  if (preg_match('/encoding\s*=\s*["\']([^"\']*?)["\']/is', $header, $m)) 
   $encoding = strtolower(trim($m[1])); // кодировка
  if (preg_match('/(<\?xml-stylesheet.*?>)/is', $header, $m)) 
   $this->XSL = strtolower(trim($m[1])); // таблица стилей XSL

  if (!$cache_used && $convert_to_Windows1251) // перекодируем, если файл взят не из кэша
  // (в кэше лежат уже перекодированные файлы)
  {
   if ($encoding!='utf-8' && $autodetect_encoding)
   {
    // попытка авто-определения UTF-8: ищем сигнатуру EF BB BF в начале файла
    if (strpos($XML, "\xEF\xBB\xBF") !== FALSE) $encoding='utf-8';
   }

   if ($encoding == 'windows-1251') 
   {
     $XML = win2utf ($XML);
   }
   else
   {
    if ($autodetect_encoding)
    {
     // автоматическое определение кодировки
     $encoding = auto_detect_encoding($XML, 2048);
    }
    //if ($encoding == 'koi8-r') $XML = convert_cyr_string($XML, "k", "utf-8"); // Koi8-r -> Win-1251
   }

   // если файл перекодирован, заменеяем кодировку и заново считываем заголовки
   if ($encoding && $encoding == 'windows-1251') 
   {
    $encoding = 'utf-8';
    $header_len = strpos($XML, "<item");
    if (!$header_len) $header_len = strlen($XML);
    $header = substr($XML, 0, $header_len);
   }
  }

  // обновляем данные в кэше  
  if ($file && !$cache_used && !$is_local_file) 
  // save data in cache if caching is ON
  { 
   _file_put_contents($file, $XML);
   chmod($file, 0666); // доступ на перезапись файла
  } 

  // элементы <item>...</item>
  $footer = substr($XML, $header_len);
  
  // извлекаем XML-код картинки для канала
  $image = xmf($header, 'image');
  // и удаляем ее из заголовков
  $header = preg_replace("(<image.*</image>)is", "", $header);

  // удаляем из заголовков, если есть, поле textInput
  $header = preg_replace("(<textInput.*</textInput>)is", "", $header);
  
  // извлекаем переменные, соответствующие заголовку канала, ссылке и описанию канала
  $title = xmf($header, 'title'); if (is_array($title)) $title=$title[0];
  $link = xmf($header, 'link'); if (is_array($link)) $link=$link[0];
  $description = xmf($header, 'description'); if (is_array($description)) $description=$description[0];
  // извлекаем язык канала, категорию, копирайт
  $language = xmf($header, 'language'); $copyright = xmf($header, 'copyright');
  $category = xmf($header, 'category'); if (is_array($category)) $category=$category[0];
  // извлекаем из XML-кода адреса редактора и вебмастера канала и рейтинг канала
  $managingEditor = xmf($header, 'managingEditor'); $webMaster = xmf($header, 'webMaster'); $rating = xmf($header, 'rating');
  $docs = xmf($header, 'docs'); // ссылка на документацию

  $skipDays_XML = xmf($header, 'skipDays'); // дни недели неактивности канала
  // превращаем XML-код в запись типа Sonday|Wednesday|Friday (дни недели, разделенные символом |)
  if ($skipDays_XML) { $days = xmf($skipDays_XML, 'day'); $skipDays = implode('|', $days); }

  $skipHours_XML = xmf($header, 'skipHours'); // часы неактивности канала
  // превращаем XML-код в запись типа 1|2|5|7 (часы, разделенные символом |)
  if ($skipHours_XML) { $hours = xmf($skipHours_XML, 'day'); $skipHours = implode('|', $hours); }

  // Извлекаем время последней модификации канала
  $timestamp = xmf($header, 'pubDate');
  $timestamp = $timestamp != "" ? _strtotime($timestamp) : 0;

  // вторая попытка - смотрим время последней сборки RSS-файла
  if (!$timestamp)
  {
   $timestamp = xmf($header, 'lastBuildDate');
   $timestamp = $timestamp != "" ? _strtotime($timestamp) : 0;
  }

  // создаем канал
  $this->create($title, $link, $description, $language, $encoding, $category, $copyright, $managingEditor, $webMaster, $rating, $docs, $skipDays, $skipHours, $timestamp);

  // создаем его баннер
  if ($image)
  {
   $image_url = xmf($image, 'url'); 
   $image_title = xmf($image, 'title'); $image_link = xmf($image, 'link');
   $image_width = xmf($image, 'width'); $image_height = xmf($image, 'height');
   $image_description = xmf($image, 'description');
   $this->setImage($image_url, $image_title, $image_link, $image_width, $image_height, $image_description);
  }
  
  // извлекаем все новостные записи из канала
  $items = xmf($footer, 'item', TRUE); // последний параметр должен быть TRUE
  if (is_array($items))
  {
   // пробегаемся по ним снизу вверх
   for ($i = count($items)-1; $i>=0; $i--)
   {
    // и добавляем каждую из них в канал
    $item = $items[$i];
    $title = xmf($item, 'title'); $link = xmf($item, 'link'); 
    $author = xmf($item, 'author'); $comments = xmf($item, 'comments');
    $pubDate = xmf($item, 'pubDate'); 

$guid = xmf($item, 'guid'); 
if(!$guid) $guid=$i+1;

$source = xmf($item, 'source');
    $enclosure = xmf($item, 'enclosure');
    $category = xmf($item, 'category'); if (is_array($category)) $category = $category[0];

    $description = xmf($item, 'description'); // обрезаем длинные описания новостей, добавляя в конце многоточие (...)
    if ($max_item_description_size>0 && strlen($description)>$max_item_description_size)
    {
     $description = sprintf("%.{$max_item_description_size}s...", $description);
    }

    $fulltext = xmf($item, 'yandex:full-text');

    $this->addItem($title, $link, $description, $author, $category, $comments, $pubDate, $guid, $enclosure, $source, true, $fulltext);
   }
  }
  
  return TRUE;
 }

 // проверить существование канала в указанном файле (локальном или удаленном)
 // и заполнить заголовок канала.
 // Работает аналогично функции load, но не скачивает текст новостей
 function check($url, $convert_to_Windows1251 = true, $use_cache = false)
 {
  global $use_rss_caching, $rss_cache_dir, $request_delay, $autodetect_encoding;
  
  $cache_used = 0;
  if ($use_rss_caching && $use_cache)
  {
   $file = $rss_cache_dir.url2file($url); // полный путь к файлу в кэше
   $rss_last_request_time = @filemtime($file);   // время последней модификации файла в кэше
   $rss_last_modified_time = LastModified($url); // время последней модификации канала
   if ($rss_last_request_time > 0 && ($rss_last_modified_time > 0 && $rss_last_request_time > $rss_last_modified_time || $rss_last_modified_time == 0 && time()-$rss_last_request_time > $request_delay))
   // если канал не изменился с момента последней проверки, берем его из кэша
   {
    $XML = _file_get_contents($file);
    $cache_used = 1;
   }
  }
  if (!$XML) 
  {
   if (!preg_match('/^(http|ftp|https):\/\//', $url))
   {
    $XML = @_file_get_contents($url); // берем файл с локальной машины
   }
   else
   {
    // скачиваем с удаленной машины только часть канала - до первого тега <item
	
    $XML = ""; $s_prev = "";
    $max_header_len = 5120; // считаем, что заголовок канала не превышает 5К
    $fp = fopen($url, "rt");
    while ($fp && $s = fgets($fp, 512))
    {
     $XML .= $s;
     if (strpos($s, '<item') !== FALSE) break;
     if (strpos($s_prev.$s, '<item') !== FALSE) break;
     if (strlen($XML)>$max_header_len) break;
     $s_prev = $s;
    }
    if ($fp) fclose($fp);
   }
   

   if (!$XML) 
   {
    $this->setLastError("Empty RSS-file of file not accessible");
    return FALSE; // пустой файл или такого файла нет
   }
  }
  
  if (strpos($XML, '<?xml') === false || strpos($XML, '<rss') === false) 
  {
   $this->setLastError("Not an RSS-XML file");
   return FALSE;
  }

  $header_len = strpos($XML, "<item");
  $isEmpty = TRUE;
  if (!$header_len) $header_len = strlen($XML);
  else $isEmpty = FALSE;

  $header = substr($XML, 0, $header_len); // заголовок канала лежит до первого тэга <item>
  if (preg_match('/encoding\s*=\s*["\']([^"\']*?)["\']/is', $header, $m)) 
   $encoding = strtolower(trim($m[1])); // кодировка
  if (preg_match('/(<\?xml-stylesheet.*?>)/is', $header, $m)) 
   $this->XSL = strtolower(trim($m[1])); // таблица стилей XSL

  if (!$cache_used && $convert_to_Windows1251) // перекодируем, если файл взят не из кэша
  // (в кэше лежат уже перекодированные файлы)
  {
   if ($encoding == 'windows-1251') $XML = win2utf($XML); 	 // Unicode -> Win-1251
   else
   {
    if ($autodetect_encoding)
    	{
     	// автоматическое определение кодировки
     	$encoding = auto_detect_encoding($XML, 2048);
    	}
    //if ($encoding == 'koi8-r') $XML = convert_cyr_string($XML, "k", "w"); // Koi8-r -> Win-1251
   }

   // если файл перекодирован, заменеяем кодировку и заново считываем заголовки
   if ($encoding && $encoding == 'windows-1251') 
   {
    $encoding = 'utf-8';
    $header_len = strpos($XML, "<item");
    if (!$header_len) $header_len = strlen($XML);
    $header = substr($XML, 0, $header_len);
   }
  }


  // извлекаем XML-код картинки для канала
  $image = xmf($header, 'image');
  // и удаляем ее из заголовков
  $header = preg_replace("(<image.*</image>)is", "", $header);

  // удаляем из заголовков, если есть, поле textInput
  $header = preg_replace("(<textInput.*</textInput>)is", "", $header);
  
  // извлекаем переменные, соответствующие заголовку канала, ссылке и описанию канала
  $title = xmf($header, 'title'); if (is_array($title)) $title=$title[0];
  $link = xmf($header, 'link'); if (is_array($link)) $link=$link[0];
  $description = xmf($header, 'description'); if (is_array($description)) $description=$description[0];
  // извлекаем язык канала, категорию, копирайт
  $language = xmf($header, 'language'); $category = xmf($header, 'category'); $copyright = xmf($header, 'copyright');
  // извлекаем из XML-кода адреса редактора и вебмастера канала и рейтинг канала
  $managingEditor = xmf($header, 'managingEditor'); $webMaster = xmf($header, 'webMaster'); $rating = xmf($header, 'rating');
  $docs = xmf($header, 'docs'); // ссылка на документацию

  $skipDays_XML = xmf($header, 'skipDays'); // дни недели неактивности канала
  // превращаем XML-код в запись типа Sonday|Wednesday|Friday (дни недели, разделенные символом |)
  if ($skipDays_XML) { $days = xmf($skipDays_XML, 'day'); $skipDays = implode('|', $days); }

  $skipHours_XML = xmf($header, 'skipHours'); // часы неактивности канала
  // превращаем XML-код в запись типа 1|2|5|7 (часы, разделенные символом |)
  if ($skipHours_XML) { $hours = xmf($skipHours_XML, 'day'); $skipHours = implode('|', $hours); }

  // Извлекаем время последней модификации канала
  $timestamp = xmf($header, 'pubDate');
  $timestamp = $timestamp != "" ? _strtotime($timestamp) : 0;

  // создаем канал
  $this->create($title, $link, $description, $language, $encoding, $category, $copyright, $managingEditor, $webMaster, $rating, $docs, $skipDays, $skipHours, $timestamp);

  // создаем его баннер
  if ($image)
  {
   $image_url = xmf($image, 'url'); 
   $image_title = xmf($image, 'title'); $image_link = xmf($image, 'link');
   $image_width = xmf($image, 'width'); $image_height = xmf($image, 'height');
   $image_description = xmf($image, 'description');
   $this->setImage($image_url, $image_title, $image_link, $image_width, $image_height, $image_description);
  }

  $this->isEmptyFlag = $isEmpty;
  
  return TRUE;
 }

 // установка и получение текста последней ошибки
 function setLastError($last_error) { $this->last_error = $last_error; }
 function getLastError() { return $this->last_error; }
 
 // Меняет заголовок канала, не внося изменений в его содержание.
 // Параметры вызова функции аналогичны параметрам функции create().
 // параметрами функции являются переменные, содержащие название канала, ссылку на сайт канала,
 // текстовое его описание, язык канала, кодировку, категорию, копирайт, адреса редактора и вебмастера,
 // рейтинг, RSS-документацию и списки неактивных дне недели и часов в сутки.
 // Только первые три параметра являются обязательными.
 // $skipDays и $skipHours - это текстовые строки с днями недели или часами, разделенными символом |
 // $language - код языка, см. http://blogs.law.harvard.edu/tech/stories/storyReader$15
 function edit($title, $link, $description, $language="", $encoding="", $category="", $copyright="", $managingEditor="", $webMaster="", $rating="", $docs="", $skipDays="", $skipHours="", $pubDate="")
 {
  global $default_language, $default_encoding, $default_docs, $default_channel_title, $default_channel_link;
 
  $this->encoding = $encoding ? SafeConvertStr($encoding) : $default_encoding;

  // Разбираем заголовок канала, экранируя соответствующие символы
  $this->title = $title ? SafeConvertStr($title)  :  $default_channel_title;

  // Проверяем, задан ли абсолютный http-адрес сайта канала
  if (!preg_match ("(^(ht|f)tp://)", $link)) $this->link = $default_channel_link;
  else $this->link = SafeConvertStr($link, false); 
  
  // Обрабатываем описание сайта
  $this->description = SafeConvertStr($description);
  
  // Разбираем язык канала. Допустимые формы задания языка: ru-ru или ru
  // Полный список допустимых языков см. на http://blogs.law.harvard.edu/tech/stories/storyReader$15
  if (preg_match ("([a-zA-Z]{2}(-[a-zA-Z]{2})?)", $language)) $this->language = $language; 
  else $this->language = ""; 
  
  // обрабатываем другие необязательные переметры
  $this->category = SafeConvertStr($category); 
  $this->copyright = SafeConvertStr($copyright); 
  $this->rating = SafeConvertStr($rating); 
  $this->managingEditor = SafeConvertStr($managingEditor); 
  $this->webMaster = SafeConvertStr($webMaster);
  $this->docs = preg_match ("(^(ht|f)tp://)", $docs) ? SafeConvertStr($docs, false) : $default_docs;

  // обрабатываем часы и дни недели, преобразуя закодированный список в XML-код
  $this->skipHours = Convert2List($skipHours, "hour");  // 2|4|5 -> <day>2</day><day>4</day><day>5</day>
  $this->skipDays = Convert2List($skipDays, "day"); 

  // обновляем, если нужно, время модификации канала
  if ($pubDate) $this->timestamp = $pubDate;
 }

 // Создает новый канал.
 // параметрами функции являются переменные, содержащие название канала, ссылку на сайт канала,
 // текстовое его описание, язык канала, кодировку, категорию, копирайт, адреса редактора и вебмастера,
 // рейтинг, RSS-документацию и списки неактивных дне недели и часов в сутки.
 // Только первые три параметра являются обязательными.
 // $skipDays и $skipHours - это текстовые строки с днями недели или часами, разделенными символом |
 // $language - код языка, см. http://blogs.law.harvard.edu/tech/stories/storyReader$15
 function create($title, $link, $description, $language="", $encoding="", $category="", $copyright="", $managingEditor="", $webMaster="", $rating="", $docs="", $skipDays="", $skipHours="", $pubDate="")
 {
  // редактируем заголовок канала
  $this->edit($title, $link, $description, $language, $encoding, $category, $copyright, $managingEditor, $webMaster, $rating, $docs, $skipDays, $skipHours, $pubDate);

  // добавляем время модификации канала
  $this->timestamp = $pubDate ? $pubDate : 0;

  // обнуляем массив с новостями, чтобы можно было вызывать повторно функцию load()
  $this->items = array();
  $this->isEmptyFlag = TRUE;
 }

 // Задает изображение для канала.
 // Обязательным является только первый параметр функции.
 // Остальные поля создаются автоматически, если их значения не заданы
 function setImage($url, $title="", $link="", $width="", $height="", $description="")
 {
  $this->image_url = SafeConvertStr($url, false);
  if ($title) $this->image_title = SafeConvertStr($title);
  if ($link) $this->image_link = SafeConvertStr($link, false);
  if ($width) $this->image_width = preg_match("(^\d+$)", trim($width)) ? $width+0 : "";
  if ($height) $this->image_height = preg_match("(^\d+$)", trim($height)) ? $height+0 : "";
  if ($description) $this->image_description = SafeConvertStr($description);

  if (!preg_match ("(^(ht|f)tp://)", $this->image_url)) $this->image_url = "";
  if (!preg_match ("(^(ht|f)tp://)", $this->image_link)) $this->image_link = "";
  
  if ($this->image_url && (!$this->image_width || !$this->image_height))
  {
   $dimensions = @getimagesize ($this->image_url);
   $this->image_width = $dimensions[0]; $this->image_height = $dimensions[1];
  }
  
  if ($this->image_url)
  {
   if (!$this->image_link) $this->image_link = $this->link;
   if (!$this->image_title) $this->image_title = $this->title;
   if (!$this->image_description) $this->image_description = $this->description;
  }
 }

 // Добавляет XSL-таблицу стилей для отображения RSS-канала в броузере
 function setXSL($style)
 {
  $this->XSL = "<?xml-stylesheet type=\"text/xsl\" href=\"$style\"?>";
 }

 // добавляет новое новостное сообщение в канал (<item>...</item>)
 // обязательными являются только название новости и ссылка
 // остальные поля восстанавливаются автоматически или оставляются пустыми
 function addItem($title, $link, $description="", $author="", $category="", 
	$comments="", $pubDate="", $guid="", 
	$enclosure="", $source="", $push_front=true, $fulltext="")
 {
  $item = new RSSItem($title, $link, $description, $author, $category, $comments, $pubDate, $guid, $enclosure, $source, $fulltext);

  // Проверяем, возможно, новость с таким guid уже есть в списке - тогда вместо добавления ее нужно заменить
  $i = $this->findItem($item->guid);
  $exists = $i>=0;
  if ($exists)
  {
   // Заменяем ее, сохраняя старую дату публикации
   $item->pubDate = $this->items[$i]->pubDate; 
   $item->timestamp = $this->items[$i]->timestamp;
   $this->items[$i] = $item; 
  }
  else // такой новости раньше не было - добавляем ее
  {
   if ($push_front) $this->items[] = $item; 
   else $this->items = array_merge(array($item), $this->items);
  }

  $this->timestamp = max($this->timestamp, $item->getTimestamp()); // обновляем время публикации канала
  $this->isEmptyFlag = FALSE;

  $this->removeOldItems(); // удаляем старые новости
 }
 
 // Возвращает число RSS-новостей в канале
 function itemsCount()
 {
  return count($this->items);
 }

 // Возвращает TRUE, если канал не содержит новостей. 
 // Работает, даже если канал был лишь проверен с помощью функции check()
 function isEmpty()
 {
  return $this->isEmptyFlag && $this->itemsCount()==0;
 }

 // Возвращает время последней модификации канала
 function getPubDate()
 {
  return $this->timestamp;
 }

 // создает XML-код канала
 function getXML()
 {
  global $RSSChannel_version;

  // начальная часть XML-кода
  $XML = "<?xml version=\"1.0\" encoding=\"". $this->encoding ."\" ?>\n";
  $XML .= "<rss version=\"2.0\">\n<channel>\n";
  $XML .= AddTag("title", $this->title, "\t"); 
  $XML .= AddTag("link", $this->link, "\t"); 
  $XML .= AddTag("description", $this->description, "\t"); 
  $XML .= AddTag("language", $this->language, "\t"); 
  $XML .= AddTag("category", $this->category, "\t"); 
  $XML .= AddTag("copyright", $this->copyright, "\t"); 
  $XML .= AddTag("managingEditor", $this->managingEditor, "\t"); 
  $XML .= AddTag("webMaster", $this->webMaster, "\t"); 
  $XML .= AddTag("rating", $this->rating, "\t"); 
  $XML .= AddTag("docs", $this->docs, "\t"); 
  $XML .= AddTag("skipDays", $this->skipDays, "\t"); 
  $XML .= AddTag("skipHours", $this->skipHours, "\t"); 
  $XML .= AddTag("generator", "RSSChannel $RSSChannel_version", "\t"); 
  
  // Дата публикации канала
  if ($this->timestamp) 
  {
   $this->pubDate = ConvertDate($this->timestamp); 
   $XML .= AddTag("pubDate", $this->pubDate, "\t"); 
  }

  // Параметр lastBuildDate полагаем равным pubDate
  if (!$this->lastBuildDate) $this->lastBuildDate = $this->pubDate;
  if ($this->lastBuildDate) $XML .= AddTag("lastBuildDate", $this->lastBuildDate, "\t"); 
  
  $XML .= "\n";

  // добавляем XML-код баннера канала
  $IMAGE = AddTag("url", $this->image_url, "\t\t"); 
  $IMAGE .= AddTag("title", $this->image_title, "\t\t"); 
  $IMAGE .= AddTag("link", $this->image_link, "\t\t"); 
  $IMAGE .= AddTag("width", $this->image_width, "\t\t"); 
  $IMAGE .= AddTag("height", $this->image_height, "\t\t"); 
  $IMAGE .= AddTag("description", $this->image_description, "\t\t"); 
  $XML .= "\t".AddTag("image", "\n".$IMAGE."\t"); 
  
  $XML .= "\n";

  // Добавляем все новости в порядке их поступления
  for ($i = $this->itemsCount()-1; $i>=0; $i--) 
  {
   $XML .= $this->items[$i]->getXML()."\n";
  }
  
  $XML .= "</channel>\n</rss>";
  return $XML;
 }

 // Сохраняет канал на диске
 function save($filename)
 {
  $xml = $this->getXML();
  return _file_put_contents($filename, $xml);
 }

 // возвращает индекс новости с данным идентификатором; в случае неудачи возвращает -1
 function findItem($guid)
 {
  for ($i=0; $i<$this->itemsCount(); $i++)
  {
   if (unhtmlentities($this->items[$i]->guid) == unhtmlentities($guid)) return $i;

  }
  return -1;
 }

 // удаляет новость с индексом $i
 function removeItem($i)
 {
  array_splice($this->items,$i,1);
  $this->isEmptyFlag = $this->itemsCount()==0;
 }

 // удаляет новость с идентификатором $guid
 function removeItem_byGuid($guid)
 {
  $i = $this->findItem($guid);
  if ($i!=-1) $this->removeItem($i);
 }

 // внутренние функции класса

 // Ограничивает число новостей на сайте и время жизни новости.
 // Непосредственно вызывать эту функцию не нужно, она автоматически вызывается каждый раз
 // при добавлении новой новости
 function removeOldItems()
 {
  $removeCount = 0;
  $maxOldTime = time() - $this->item_ttl;
  for ($i=0; $i<$this->itemsCount(); $i++)
  {
   if ($this->items[$i]->getTimestamp() < $maxTime) // старая новость
   {
    $removeCount++;
   }
  }
  $removeCount = max($removeCount, $this->itemsCount() - $this->max_num_items);
  if ($removeCount) 
  {
   if ($this->backup_XML_channel) // включено сохранение удаляемых новостей
   {
    // создаем или загружаем с диска канал для хранения удаляемых новостей
    $backup = new RSSChannel(1024, $this->item_ttl); // измените максимальное число новостей в архиве (1024) в случае необходимости
    if (!$backup->load($this->backup_XML_channel, false))
    {
     $backup = $this; // создаем
     $backup->items = array(); 
    }
    // помещаем новости в канал
    for ($i=0; $i<$removeCount; $i++) 
    {
     $backup->items[] = $this->items[$i];
     $backup->timestamp = max($backup->timestamp, $this->items[$i]->getTimestamp());
    }
    $this->isEmptyFlag = FALSE;
    // сохраняем канал
    $backup->save($this->backup_XML_channel);
   }
   // удаляем старые новости
   array_splice ($this->items, 0, $removeCount);
  }

  $this->isEmptyFlag = ($this->itemsCount()==0); // возможно, все новости были удалены как старые
 }
}

//=============================================================
// класс RSSItem

class RSSItem
{
 // публичные переменные
 var $title;		// заголовок новости
 var $link;			// ссылка на новость
 var $description;	// описание новости
 var $fulltext;	// полный текст новости (для news.yandex.ru)
 var $author;		// автор новости
 var $category;		// категория новости
 var $comments;		// url страницы комментариев к новости
 var $pubDate;		// дата публикации в формате RFC 2822
 var $guid; 		// уникальный идентификатор канала
 var $source;		// источник новости
 var $timestamp;	// Unix-timestamp новостного сообщения
 var $enclosure;	// любое вложение в новостное сообщение (например, картинка), в XML-формате
 // Подробнее: http://blogs.law.harvard.edu/tech/rss#ltenclosuregtSubelementOfLtitemgt

 // констурктор новости
 function RSSItem($title, $link, $description, $author="", $category="", $comments="", $pubDate="", $guid="", $enclosure="", $source="", $fulltext="")
 {
  global $default_channel_link; 

  // Проверка ссылки
  if (!preg_match ("(^(ht|f)tp://)", $link)) $this->link = $default_channel_link;
  else $this->link = SafeConvertStr($link, false); 

  // Проверка заголовка новости
  $this->title = $title ? SafeConvertStr($title, true)  :  $link;
  
  // Проверка описания новости
  $this->description = SafeConvertStr($description, true);

  // Проверка полного текста новости
  $this->fulltext = SafeConvertStr($fulltext, true);
  
  // Автор новости
  $this->author = SafeConvertStr($author, true);

  // Категория для новости
  $this->category = SafeConvertStr($category, true);

  // Комментарии к новости
  if (!preg_match ("(^(ht|f)tp://)", $comments)) $this->comments = $comments; else $this->comments = "";

  // дата публикации
  $pubDate = trim($pubDate);

  if (preg_match('/^\d+$/',$pubDate)) $this->timestamp = $pubDate+0;
  elseif ($pubDate) $this->timestamp = _strtotime($pubDate);
  $this->pubDate = date("r", $this->timestamp ? $this->timestamp : time());
  
  // Уникальный идентификатор новости
  $this->guid = SafeConvertStr($guid, false);
  if (!$this->guid) $this->guid = $this->link;
  
  // Вложение
  $this->enclosure = $enclosure;

  // Источник
  $this->source = SafeConvertStr($source, true);
 }
 
 // возвращает Unix timestamp времени публикации новости
 function getTimestamp()
 {
  return $this->timestamp;
 }

 // возвращает XML-код новости
 function getXML()
 {
  $permalink = preg_match('/^http:/', $this->guid);
 
  $ITEM = AddTag("title", $this->title, "\t\t"); 
  $ITEM .= AddTag("link", $this->link, "\t\t"); 
  $ITEM .= AddTag("description", $this->description, "\t\t"); 
  if ($this->fulltext) $ITEM .= AddTag("yandex:full-text", $this->fulltext, "\t\t"); 
  $ITEM .= AddTag("guid", $this->guid, "\t\t", $permalink ? "": 'isPermaLink="false"'); 
  $ITEM .= AddTag("author", $this->author, "\t\t"); 
  $ITEM .= AddTag("comments", $this->comments, "\t\t"); 
  $ITEM .= AddTag("pubDate", $this->pubDate, "\t\t"); 
  $ITEM .= AddTag("category", $this->category, "\t\t"); 
  $ITEM .= AddTag("enclosure", $this->enclosure, "\t\t"); 
  $XML .= "\t" . AddTag("item", "\n".$ITEM."\t"); 
  
  return $XML;
 }
}

//=============================================================
// Вспомогательные функции

// возвращает XML-тэг, если его содержимое непусто
// $prefix - необязательный префикс из пробельных символов
// $params = необязательный параметр тэга, например, isPermaLink="false"
function AddTag($tag, $value, $prefix="", $params="")
{
 if ($value) return "$prefix<$tag".($params ? " ".$params : "").">$value</$tag>\n";
 else return "";
}

// преобразует дату из UnixTimestamp в RFC 2822
// или возвращается ткекущее время в RFC-2822 - формате
function ConvertDate($date)
{
 if ($date=='') return date("r"); 				// empty -> return current date in RFC 2822 format
 if (preg_match('/^\d+$/',$date)) return date("r", $date); 	// number -> use in as a Unix timestamp
 return $date; 								// let it be a RFC 2822 string ;)
}

// преобразует дату из RFC 2822 в UnixTimestamp;
// корректно работает с русскими названиями месяцев и другими типичными ошибками
function _strtotime($date)
{
 $changes = array("Января"=>"Jan", "Февраля"=>"Feb", "Марта"=>"Mar", "Апреля"=>"Apr", "Мая"=>"May", "Июня"=>"Jun", "Июля"=>"Jul", "Августа"=>"Aug", "Сентября"=>"Sen", "Октября"=>"Oct", "Ноября"=>"Nov", "Декабря"=>"Dec", "GMT "=>"");
 foreach ($changes as $src=>$dst) $date = str_replace($src, $dst, $date);
 $ret = strtotime($date);
 if ($ret==-1) return ""; else return $ret;
}

function SafeConvertStr($str, $strip_slashes=true)
// безопасно преобразует строку, обрабатывая кавычки и тэги и удаляя символы \
{
 $ret = htmlspecialchars(trim ($str), ENT_QUOTES);
 if ($strip_slashes) $ret = stripslashes($ret);
 $ret = preg_replace('/&(?:amp;){2,}([a-z]+);/is', '&amp;\1;', $ret); // защита от многократного экранирования
 $ret = preg_replace('/&(?:amp;)+(#[\d]+|lt|gt|amp|quot|apos);/is', '&\1;', $ret); // разэкранируем стандартные сущности
 return $ret;
}

// заменяет &lt;=><, &gt;=>>, &quot;=>" и т.п.
function unhtmlentities($string) 
{
 $trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_QUOTES);
 $trans_tbl = array_flip($trans_tbl);
 $trans_tbl['&#39;'] = $trans_tbl['&#039;'] = "'"; // баг некоторых версий PHP
 return strtr($string, $trans_tbl);
}

// безопасно преобразует строку, обрабатывая кавычки и тэги и удаляя символы \
function Convert2List($list, $itemtag)
{
 $list = trim($list);
 if ($list)
 {
  $output = "\n";
  // разбиваем списк на элементы, разделенные |
  $output = explode ("|", $list);
  // Для каждого элеметна списка
  foreach ($list as $listitem)
  {
   // выполняем преобразование в XML
   $output .= "\t\t<$itemtag>$listitem</$itemtag>\n";
  }  
  $output .= "\t";
 } 
 return $output;
}

// строит имя файла по url-адресу канала. Используется для кэширования
function url2file($url)
{
 $file = preg_replace('/^(?:http|ftp):\/\/(?:www\.)?(.*?)(?:#.*)$/', '\1', $url);
 $file = preg_replace('/[\/\?&:\(\)]+/', '-', $file).".rss";
 return $file;
}

// Определяет время последней модификации удаленного или локального файла
// Необязательный параметр $timeout задает таймаут ожидания открытия сокета на удаленном сервере
// в секундах
function LastModified($url, $timeout = 30)
{
 if (!preg_match('/^http:/', $url)) return @filemtime($url); // файл рассматриваем как локальный

 global $default_user_agent;
 ini_set('user_agent', $default_user_agent);

 $components = parse_url($url); 
 if ($components['path']=='') $components['path'] = '/';
 if ($components['port']==0) $components['port'] = 80;
 if ($components['query']!='') $components['path'] .= '?'.$components['query'];

 $fp = fsockopen ($components['host'], $components['port'], $errno, $errstr, $timeout);
 fputs ($fp, "HEAD {$components['path']} HTTP/1.0\r\nHost: {$components['host']}\r\n\r\n");
 $result = fread($fp, 2048);
 fclose ($fp);

 if (preg_match('/Last-Modified:\s*([^\r\n]*)/is', $result, $m)) return strtotime(trim($m[1]));

 return 0;
}

// извлекает содержимое файла / url
function _file_get_contents($file, $max_file_size=-1)
{
 $num_bytes = 0; $portion = 4096;
 $fp = @fopen($file, "rb");
 if (!$fp) return "";
 $ret = "";
 while (!feof($fp) && ($s=fread($fp, $portion)))
 {
  $ret .= $s; $num_bytes += $portion;
  if ($max_file_size>0 && $num_bytes>=$max_file_size) 
  {
   $ret = substr($ret,0,$max_file_size);
   break;
  }
 }
 fclose($fp);
 return $ret;
}

// сохраняет содержимое в файл
function _file_put_contents($file, $str)
{
  fclose(fopen($file, "a+b"));

   $fp = @fopen($file, "r+"); 
   flock($fp, LOCK_EX);	
   ftruncate($fp, 0);
   fseek($fp, 0, SEEK_SET);
   fputs($fp, $str); 
   fclose($fp); 
   return true;
}

// приведение к нижнему регистру (работает даже при неправильной установке локали в PHP)
function rus_strtolower($str)
{
 $str = strtr($str, "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЫЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъьыэюя"); 
 return $str; 
}

// автоматическое определение кодировки (windows-1251 vs koi8-r)
function auto_detect_encoding($str, $maxlen=1024) 
{
 // статистика разностей частот биграм для этих кодировок
 $win_vs_koi = array(124142=>0.0215431042905, 123634=>0.0176536991504, 121568=>0.0141127804305, 116462=>0.0127244466063, 121576=>0.0126387881893, 121582=>0.0124380039158, 122082=>0.0119182006615, 120046=>0.0117563396267, 114923=>0.0112660286227, 123104=>0.0110282430819, 115950=>0.0108063099105, 122097=>0.010172215135, 120032=>0.0101238236916, 122098=>0.00997253090308, 120544=>0.00954952294102, 123118=>0.00921551073692, 117488=>0.00917740942804, 124156=>0.00899135267156, 120558=>0.00886564616343, 122606=>0.00884732800455, 122091=>0.00882698863107, 117485=>0.00872551506084, 122096=>0.00863620397494, 120552=>0.00856973526821, 122093=>0.00810361998589, 117483=>0.00807582121692, 124128=>0.00804688519019, 115936=>0.00799960619377, 122083=>0.00773261891989, 123112=>0.00757353900253, 122092=>0.00739944105102, 115941=>0.00720476283048, 118496=>0.00712828210098, 114930=>0.00696892407188, 122084=>0.00667523807061, 123626=>0.00654063198669, 114922=>0.00636792459389, 119019=>0.00629867476973, 117489=>0.00620300432992, 116960=>0.00597384025317, 120549=>0.00596966857702, 126706=>0.00586787967885, 114925=>0.00584674318633, 114919=>0.00580669509525, 121573=>0.00579259823412, 117989=>0.00568237914585, 115451=>0.00563092847327, 124136=>0.00562147267399, 116974=>0.00548658847833, 119026=>0.005479357573, 123621=>0.00544209059935, 120572=>0.00543096612961, 114929=>0.00511558741233, 122081=>0.00506497107499, 115944=>0.00488503277686, 119021=>0.00487196152491, 123647=>0.00484498468578, 117475=>0.00482829798116, 121595=>0.00479269967797, 114914=>0.00477823786731, 119025=>0.00476043871571, 124130=>0.00468979833283, 119013=>0.00454184288522, 119020=>0.00450068234716, 115953=>0.00448788920696, 121064=>0.00438192863263, 123627=>0.00424259464907, 114924=>0.00410020143634, 123630=>0.00387799015318, 123644=>0.00383794206209, 117484=>0.00382990163233, 124144=>0.00380568109983, 123123=>0.00376062699736, 121070=>0.00373338445189, 116965=>0.00370893542925, 129778=>0.00367107501591, 117481=>0.00357568268785, 114928=>0.00357012045298, 116968=>0.00354314361385, 119010=>0.00337154866803, 120040=>0.00336014608654, 127205=>0.00333511602961, 114943=>0.00332260100115, 115963=>0.00331842932499, 128747=>0.00325835718837, 119015=>0.00323277090795, 119039=>0.00319133225815, 121599=>0.00308537168383, 126688=>0.00295549349956, 125678=>0.0029251793195, 123618=>0.00285398271313, 122086=>0.00283423677933, 121587=>0.00275386248542, 115429=>0.00274885647404, 120575=>0.00271742984701, 119018=>0.00267126329757, 114916=>0.00263149331823, 121075=>0.00259952287311, 124644=>0.00257948642217, 123631=>0.00251357393893, 117992=>0.00250328380441, 116979=>0.00247575074179, 117477=>0.00246045459589, 127208=>0.0023992700123, 116973=>0.00239148288347, 123616=>0.00234865367495, 117474=>0.00234754122798, 122090=>0.00233280130557, 126696=>0.00230165279028, 123131=>0.00225632057607, 116976=>0.00221321325581, 121056=>0.00220293552669, 121581=>0.00214927236557, 119029=>0.000513975313027, 121061=>0.000174981908341, 124133=>-0.00045851942745, 122608=>-0.00205371876731, 118509=>-0.00323937115741, 122597=>-0.00429312414862, 117487=>-0.00475980805983, 123109=>-0.00485121998569, 117476=>-0.00510157017655, 122085=>-0.00555989833006, 122100=>-0.0059267649361, 125170=>-0.00619931444486, 121583=>-0.0062193384904, 127217=>-0.00622351016655, 118511=>-0.00622406639004, 122094=>-0.00694992563552, 124151=>-0.00696438744618, 126693=>-0.00717124055645, 123119=>-0.00718242705319, 122609=>-0.00724555841899, 126708=>-0.00726141078838, 115442=>-0.00828957749918, 122599=>-0.00829875518672, 115444=>-0.00829875518672, 124641=>-0.00857129229008, 117486=>-0.00880574048996, 119525=>-0.00933164979716, 122095=>-0.00951295384321, 117502=>-0.00992512785267, 117490=>-0.0102832613461, 117492=>-0.0103623195137, 122610=>-0.0123886168669, 124129=>-0.0124164280413, 130277=>-0.0124470203331, 125153=>-0.0124481327801, 120559=>-0.0154041452869, 124137=>-0.0155598878634, 123113=>-0.0155601659751, 118508=>-0.0161614311594, 122615=>-0.0165299292197, 125167=>-0.0165975103734, 124656=>-0.0168580886718, 115447=>-0.0176173337319, 115441=>-0.0185323089631, 121057=>-0.0217647645431, 122089=>-0.0219001429352, 119537=>-0.0228855300591, 115443=>-0.023475392662, 119539=>-0.0269709543568, 115438=>-0.0308905744338, 125176=>-0.0311200538385, 126689=>-0.0321576763485, 130273=>-0.0346575855958, 124660=>-0.035251910392);

 $win = 0;
 $koi = 0;
 $len = strlen($str);
 $len = min($len, $maxlen);
 $str = rus_strtolower(substr($str,0,$len));

 for ($i=0; $i<$len-1; $i++) 
 {
  $char1 = ord($str{$i}); 
  $char2 = ord($str{$i+1}); 
  $win += $win_vs_koi[($char1<<9)+$char2];
 }

 if ($win >= 0) return 'windows-1251'; // у английских текстов возвращаем кодировку Win-1251
 else return 'koi8-r';
}

//=============================================================
// Функция быстрого XML-разбора
// Извлекает из строки $string и помещает в выходной массив содержимое всех тэгов с именем $tag

function xmf($string, $tag, $force_array_output=0, $unhtmlentities=1) 
{
 $result = array();
 while(true) 
 {
  // Начало тэга
  $start = strpos($string, "<".$tag, $stop);
  if ($start === false) break;
  // Начало контента тэга
  $start = strpos($string, ">", $start);
  if ($start === false) break;
  $start++;
  // Конец тэга
  $stop = strpos($string, "</".$tag.">", $start);
  if ($stop === false) break;
  // извлекаем контент!
  $content = trim(substr($string, $start, $stop - $start));
  if ($unhtmlentities) $content = unhtmlentities($content);
  // удаляем тег CDATA
  $content = preg_replace('/(?:<|&(?:amp;)+lt;)!\[CDATA\[(.*?)\]\]>/is', '\1', $content); 
  $result[] = $content;
 }
 if (count($result) <= 1 && !$force_array_output) return $result[0];
 return $result;
}


?>