Files
objects/inc/Feed.php
2022-02-09 22:02:01 +01:00

287 lines
9.6 KiB
PHP
Executable File

<?php
namespace Franzz\Objects;
use \Settings;
/**
* RSS Feed Class
* @author franzz
* @version 2.1
*/
class Feed extends PhpObject
{
const CHANNEL_TAGS = array('title', 'link', 'copyright', 'description', 'language', 'lastBuildDate', 'generator', 'webMaster');
const ITEM_TAGS = array('title', 'author', 'link', 'category', 'description', 'pubDate', 'guid');
private $asChannel;
private $asItems;
/**
* Constructor
* @param array $asChannel Description of the feed: fields 'title', 'link' (optional), 'copyright', 'description', 'language', 'webMaster'
* @param array $asItems Feed item data: fields 'title', 'author', 'link', 'category', 'description', 'pubDate', 'guid'
*/
public function __construct($asChannel=array(), $asItems=array())
{
parent::__construct(__CLASS__, Settings::DEBUG);
if(!empty($asChannel) && !array_key_exists('link', $asChannel))
{
$asChannel['link'] = $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['SERVER_NAME'].$_SERVER['SCRIPT_NAME'].'/rss';
}
$this->asChannel = $asChannel;
//Items
$this->removeItems();
array_walk($asItems, array($this, 'addItem'));
}
public function loadRss($sUrl)
{
$oCurl = curl_init();
curl_setopt($oCurl, CURLOPT_URL, $sUrl);
curl_setopt($oCurl, CURLOPT_HEADER, false);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($oCurl, CURLOPT_USERAGENT, array_key_exists('HTTP_USER_AGENT', $_SERVER)?$_SERVER['HTTP_USER_AGENT']:'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
$fCookieJar = @tempnam('/tmp','cookie');
curl_setopt($oCurl, CURLOPT_COOKIESESSION, true);
curl_setopt($oCurl, CURLOPT_COOKIEJAR, $fCookieJar);
curl_setopt($oCurl, CURLOPT_COOKIEFILE, $fCookieJar);
//curl_setopt($oCurl, CURLOPT_ENCODING, 'gzip');
$sRssContent = curl_exec($oCurl);
curl_close($oCurl);
//Parse document encoding (useless)
$sEncoding = $this->getPregMatch("'encoding=[\'\"](.*?)[\'\"]'si", $sRssContent);
//Parse Channel info
if(preg_match("'<channel.*?>(.*?)</channel>'si", $sRssContent, $sChannelContent)) {
foreach(self::CHANNEL_TAGS as $sChannelTag)
{
$sTagContent = $this->getPregMatch("'<$sChannelTag.*?>(.*?)</$sChannelTag>'si", $sChannelContent[1]);
if($sTagContent != '') $this->asChannel[$sChannelTag] = $sTagContent;
}
}
//Parse Text Input info
/*preg_match("'<textinput(|[^>]*[^/])>(.*?)</textinput>'si", $sRssContent, $out_textinfo);
if (isset($out_textinfo[2])) {
foreach($this->textinputtags as $textinputtag) {
$temp = $this->getPregMatch("'<$textinputtag.*?>(.*?)</$textinputtag>'si", $out_textinfo[2]);
if ($temp != '') $result['textinput_'.$textinputtag] = $temp; // Set only if not empty
}
}*/
//Parse Image info
/*preg_match("'<image.*?>(.*?)</image>'si", $sRssContent, $out_imageinfo);
if (isset($out_imageinfo[1])) {
foreach($this->imagetags as $imagetag) {
$temp = $this->getPregMatch("'<$imagetag.*?>(.*?)</$imagetag>'si", $out_imageinfo[1]);
if ($temp != '') $result['image_'.$imagetag] = $temp; // Set only if not empty
}
}*/
//Parse Items
preg_match_all("'<item(| .*?)>(.*?)</item>'si", $sRssContent, $asItems);
//$i = 0;
foreach($asItems[2] as $asItem)
{
$asItemTags = array();
foreach(self::ITEM_TAGS as $sItemTag)
{
$sTagContent = $this->getPregMatch("'<$sItemTag.*?>(.*?)</$sItemTag>'si", $asItem);
if($sTagContent != '') $asItemTags[$sItemTag] = $sTagContent;
}
//Strip HTML tags and other bullshit from DESCRIPTION
//if($this->stripHTML && $this->asItems[$i]['description']) $this->asItems[$i]['description'] = strip_tags($this->unhtmlentities(strip_tags($this->asItems[$i]['description'])));
//Strip HTML tags and other bullshit from TITLE
//if($this->stripHTML && $this->asItems[$i]['title']) $this->asItems[$i]['title'] = strip_tags($this->unhtmlentities(strip_tags($this->asItems[$i]['title'])));
//Fix for author
if(!array_key_exists('author', $asItemTags))
{
$sTagContent = $this->getPregMatch("'<dc\:creator.*?>(.*?)</dc\:creator>'si", $asItem);
if($sTagContent != '') $asItemTags['author'] = $sTagContent;
}
$this->addItem($asItemTags);
}
}
public function addItem($asItem)
{
$this->asItems[] = $asItem;
return count($this->asItems) - 1;
}
public function changeItem($iItemId, $asAttributes) {
if(array_key_exists($iItemId, $this->asItems)) {
$this->asItems[$iItemId] = array_merge($this->asItems[$iItemId], $asAttributes);
}
}
public function removeItem($iItemId)
{
$bExist = array_key_exists($iItemId, $this->asItems);
if($bExist) unset($this->asItems[$iItemId]);
return $bExist;
}
public function removeItems()
{
$this->asItems = [];
}
public function filterItems($sField, $sRegex)
{
$this->asItems = array_filter($this->asItems, function($asItem) use ($sField, $sRegex) {
return preg_match($sRegex, $asItem[$sField]);
});
}
public function getItems()
{
return $this->asItems;
}
private function getGlobalPubDate()
{
$iGlobalPubDate = 0;
foreach($this->asItems as $asItem)
{
$iItemPubDate = strtotime($asItem['pubDate']);
if($iItemPubDate>$iGlobalPubDate)
{
$iGlobalPubDate = $iItemPubDate;
}
}
return self::cleanRss(self::getDate($iGlobalPubDate));
}
public function getFeed($bSetMime=true)
{
//Feed Channel
$sRssChannel = array_key_exists('title', $this->asChannel)?self::getHtml($this->asChannel['title'], 'title'):'';
$sRssChannel .= array_key_exists('link', $this->asChannel)?self::getHtml($this->asChannel['link'], 'link'):'';
$sRssChannel .= array_key_exists('copyright', $this->asChannel)?self::getHtml($this->asChannel['copyright'], 'copyright'):'';
$sRssChannel .= array_key_exists('description', $this->asChannel)?self::getHtml($this->asChannel['description'], 'description'):'';
$sRssChannel .= array_key_exists('link', $this->asChannel)?self::getHtml('', 'atom:link', '', '', array('href'=>$this->asChannel['link'], 'rel'=>'self', 'type'=>'application/atom+xml'), true):'';
$sRssChannel .= array_key_exists('language', $this->asChannel)?self::getHtml($this->asChannel['language'], 'language'):'';
$sRssChannel .= self::getHtml($this->getGlobalPubDate(), 'lastBuildDate');
$sRssChannel .= self::getHtml('Lutran.fr RSS Feed Generator', 'generator');
$sRssChannel .= array_key_exists('webMaster', $this->asChannel)?self::getHtml($this->asChannel['webMaster'].' (Webmaster)', 'webMaster'):'';
//Feed Items
$asSortedItems = $this->rSortTimeMatrix($this->asItems, 'pubDate');
$sItems = implode("\n", array_map(array($this, 'buildItem'), $asSortedItems));
//Global Feed
$sFeed = '<?xml version="1.0" encoding="'.Settings::TEXT_ENC.'" ?>';
$sFeed .= self::getHtml(self::getHtml($sRssChannel.$sItems, 'channel'), 'rss', '', '', array('version'=>'2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom'));
if($bSetMime) header('Content-type: application/rss+xml');
return $sFeed;
}
private static function getDate($sDate)
{
if(!is_numeric($sDate))
{
$sDate = strtotime($sDate);
}
return date('r', $sDate);
}
private function buildItem($asItem)
{
$sRssItem = self::getHtml(self::cleanRss($asItem['title']), 'title');
$sRssItem .= array_key_exists('author', $asItem)?self::getHtml(self::cleanRss($asItem['author']), 'author'):'';
$sRssItem .= array_key_exists('link', $asItem)?self::getHtml($asItem['link'], 'link'):'';
$sRssItem .= array_key_exists('category', $asItem)?self::getHtml($asItem['category'], 'category'):'';
$sRssItem .= self::getHtml(self::cleanRss($asItem['description']), 'description');
$sRssItem .= array_key_exists('pubDate', $asItem)?self::getHtml(self::getDate($asItem['pubDate']), 'pubDate'):'';
$sRssItem .= array_key_exists('guid', $asItem)?self::getHtml($asItem['guid'], 'guid', '', '', array('isPermaLink'=>'false')):'';
return self::getHtml($sRssItem, 'item');
}
private function getPregMatch($pattern, $subject)
{
preg_match($pattern, $subject, $out);
//if there is some result... process it and return it
if(isset($out[1]))
{
//If code page is set convert character encoding to required
//if ($this->cp != '') $out[1] = iconv($this->rsscp, $this->cp.'//TRANSLIT', $out[1]);
return str_replace(array('<![CDATA[', ']]>'), '', trim($out[1]));
}
else return '';
}
private static function getHtml($oText, $sTag, $sClass='', $sStyle='', $asExtraAttr=array(), $bAutoClose=false, $sInter='')
{
$sHtmlAttr = '';
if($sClass!='')
{
$asExtraAttr['class'] = $sClass;
}
if($sStyle!='')
{
$asExtraAttr['style'] = $sStyle;
}
foreach($asExtraAttr as $sAttrName=>$sAttrValue)
{
$sHtmlAttr .= ' '.$sAttrName.'="'.$sAttrValue.'"';
}
if($bAutoClose)
{
$sHtml = self::encapsulate('', "\n".'<'.$sTag.$sHtmlAttr, ' />');
}
else
{
$sHtml = self::encapsulate($oText, "\n".'<'.$sTag.$sHtmlAttr.'>', '</'.$sTag.'>', $sInter);
}
return $sHtml;
}
private static function cleanRss($oText)
{
if(!is_array($oText))
{
return htmlspecialchars($oText, ENT_QUOTES);
}
elseif(count($oText)>0)
{
$oTextKeys = array_map(array(self, 'cleanRss'), array_keys($oText));
$oTextValues = array_map(array(self, 'cleanRss'), array_values($oText));
return array_combine($oTextKeys, $oTextValues);
}
else
{
return $oText;
}
}
private static function encapsulate($oText, $sPre='', $sPost=false, $sInter='')
{
if($sPost===false)
{
$sPost = $sPre;
}
if(is_array($oText))
{
$oText = implode($sPost.$sInter.$sPre, $oText);
}
return $sPre.$oText.$sPost;
}
private static function rSortTimeMatrix($asMatrix, $sTimeCol)
{
$asResult = array();
foreach($asMatrix as $iRowId=>$asLine) $asKeys[$iRowId] = strtotime($asLine[$sTimeCol]);
arsort($asKeys);
foreach($asKeys as $iRowId=>$iTimeStamp) $asResult[$iRowId] = $asMatrix[$iRowId];
return $asResult;
}
}