commit 136b0f9ae513cd5d5febba6a5fee27c6059174d7 Author: EsKuel Date: Tue Jun 17 17:41:03 2014 -0400 Add sources to repo diff --git a/sources/Configuration.class.php b/sources/Configuration.class.php new file mode 100755 index 0000000..a73903a --- /dev/null +++ b/sources/Configuration.class.php @@ -0,0 +1,95 @@ +valeur avec un cache session pour eviter les requête inutiles + */ + +class Configuration extends MysqlEntity{ + + protected $id,$key,$value,$confTab; + protected $TABLE_NAME = 'configuration'; + protected $CLASS_NAME = 'Configuration'; + protected $object_fields = + array( + 'id'=>'key', + 'key'=>'longstring', + 'value'=>'longstring' + ); + + function __construct(){ + parent::__construct(); + } + + public function getAll(){ + + if(!isset($_SESSION['configuration'])){ + + $configurationManager = new Configuration(); + $configs = $configurationManager->populate(); + $confTab = array(); + + foreach($configs as $config){ + $this->confTab[$config->getKey()] = $config->getValue(); + } + + $_SESSION['configuration'] = serialize($this->confTab); + + }else{ + $this->confTab = unserialize($_SESSION['configuration']); + } + } + + public function get($key){ + + return (isset($this->confTab[$key])?$this->confTab[$key]:''); + } + + public function put($key,$value){ + $configurationManager = new Configuration(); + if (isset($this->confTab[$key])){ + $configurationManager->change(array('value'=>$value),array('key'=>$key)); + } else { + $configurationManager->add($key,$value); + } + $this->confTab[$key] = $value; + unset($_SESSION['configuration']); + } + + public function add($key,$value){ + $config = new Configuration(); + $config->setKey($key); + $config->setValue($value); + $config->save(); + $this->confTab[$key] = $value; + unset($_SESSION['configuration']); + } + + function getId(){ + return $this->id; + } + + function getKey(){ + return $this->key; + } + + function setKey($key){ + $this->key = $key; + } + + function getValue(){ + return $this->value; + } + + function setValue($value){ + $this->value = $value; + } + + + + +} + +?> \ No newline at end of file diff --git a/sources/Event.class.php b/sources/Event.class.php new file mode 100755 index 0000000..26174cb --- /dev/null +++ b/sources/Event.class.php @@ -0,0 +1,208 @@ +'key', + 'guid'=>'longstring', + 'title'=>'string', + 'creator'=>'string', + 'content'=>'longstring', + 'description'=>'longstring', + 'link'=>'longstring', + 'unread'=>'integer', + 'feed'=>'integer', + 'unread'=>'integer', + 'favorite'=>'integer', + 'pubdate'=>'integer', + 'syncId'=>'integer', + ); + + protected $object_fields_index = + array( + 'feed'=>'index', + 'unread'=>'index', + 'favorite'=>'index' + ); + + function __construct($guid=null,$title=null,$description=null,$content=null,$pubdate=null,$link=null,$category=null,$creator=null){ + + $this->guid = $guid; + $this->title = $title; + $this->creator = $creator; + $this->content = $content; + $this->description = $description; + $this->pubdate = $pubdate; + $this->link = $link; + $this->category = $category; + parent::__construct(); + } + + + function getEventCountPerFolder(){ + $events = array(); + $results = $this->customQuery('SELECT COUNT('.MYSQL_PREFIX.$this->TABLE_NAME.'.id),'.MYSQL_PREFIX.'feed.folder FROM '.MYSQL_PREFIX.$this->TABLE_NAME.' INNER JOIN '.MYSQL_PREFIX.'feed ON ('.MYSQL_PREFIX.'event.feed = '.MYSQL_PREFIX.'feed.id) WHERE '.MYSQL_PREFIX.$this->TABLE_NAME.'.unread=1 GROUP BY '.MYSQL_PREFIX.'feed.folder'); + while($item = mysql_fetch_array($results)){ + $events[$item[1]] = $item[0]; + } + + return $events; + } + + function getEventCountNotVerboseFeed(){ + $results = $this->customQuery('SELECT COUNT(1) FROM '.MYSQL_PREFIX.$this->TABLE_NAME.' INNER JOIN '.MYSQL_PREFIX.'feed ON ('.MYSQL_PREFIX.'event.feed = '.MYSQL_PREFIX.'feed.id) WHERE '.MYSQL_PREFIX.$this->TABLE_NAME.'.unread=1 AND '.MYSQL_PREFIX.'feed.isverbose=0'); + while($item = mysql_fetch_array($results)){ + $nbitem = $item[0]; + } + + return $nbitem; + } + + function getEventsNotVerboseFeed($start=0,$limit=10000,$order,$columns='*'){ + $eventManager = new Event(); + $objects = array(); + $results = $this->customQuery('SELECT '.$columns.' FROM '.MYSQL_PREFIX.'event INNER JOIN '.MYSQL_PREFIX.'feed ON ('.MYSQL_PREFIX.'event.feed = '.MYSQL_PREFIX.'feed.id) WHERE '.MYSQL_PREFIX.'event.unread=1 AND '.MYSQL_PREFIX.'feed.isverbose = 0 ORDER BY '.$order.' LIMIT '.$start.','.$limit); + if($results!=false){ + while($item = mysql_fetch_array($results)){ + $object = new Event(); + foreach($object->getObject_fields() as $field=>$type){ + $setter = 'set'.ucFirst($field); + if(isset($item[$field])) $object->$setter($item[$field]); + } + $objects[] = $object; + unset($object); + } + } + return $objects; + } + + function setId($id){ + $this->id = $id; + } + + function getCreator(){ + return $this->creator; + } + + function setCreator($creator){ + $this->creator = $creator; + } + + function getCategory(){ + return $this->category; + } + + function setCategory($category){ + $this->category = $category; + } + + function getDescription(){ + return $this->description; + } + + function setDescription($description,$encoding = true){ + $this->description = $description; + } + + function getPubdate($format=false){ + if($this->pubdate!=0){ + return ($format!=false?date($format,$this->pubdate):$this->pubdate); + }else{ + return ''; + } + } + + function getPubdateWithInstant($instant){ + $alpha = $instant - $this->pubdate; + if ($alpha < 86400 ){ + $hour = floor($alpha/3600); + $alpha = ($hour!=0?$alpha-($hour*3600):$alpha); + $minuts = floor($alpha/60); + return 'il y a '.($hour!=0?$hour.'h et':'').' '.$minuts.'min'; + }else{ + return 'le '.$this->getPubdate('d/m/Y à H:i:s'); + } + } + + function setPubdate($pubdate){ + $this->pubdate = (is_numeric($pubdate)?$pubdate:strtotime($pubdate)); + } + + function getLink(){ + return $this->link; + } + + function setLink($link){ + $this->link = $link; + } + + function getId(){ + return $this->id; + } + + function getTitle(){ + return $this->title; + } + + function setTitle($title){ + $this->title = $title; + } + + function getContent(){ + return $this->content; + } + + function setContent($content,$encoding=true){ + $this->content = $content; + } + + + function getGuid(){ + return $this->guid; + } + + function setGuid($guid){ + $this->guid = $guid; + } + + function getSyncId(){ + return $this->syncId; + } + + function setSyncId($syncId){ + $this->syncId = $syncId; + } + + function getUnread(){ + return $this->unread; + } + + function setUnread($unread){ + $this->unread = $unread; + } + function setFeed($feed){ + $this->feed = $feed; + } + function getFeed(){ + return $this->feed; + } + function setFavorite($favorite){ + $this->favorite = $favorite; + } + function getFavorite(){ + return $this->favorite; + } + +} + +?> diff --git a/sources/Feed.class.php b/sources/Feed.class.php new file mode 100755 index 0000000..cf29dae --- /dev/null +++ b/sources/Feed.class.php @@ -0,0 +1,304 @@ +'key', + 'name'=>'string', + 'description'=>'longstring', + 'website'=>'longstring', + 'url'=>'longstring', + 'lastupdate'=>'string', + 'folder'=>'integer', + 'isverbose'=>'boolean', + ); + + protected $object_fields_index = + array( + 'folder'=>'index' + ); + + protected $error = ''; + + function __construct($name=null,$url=null){ + $this->name = $name; + $this->url = $url; + parent::__construct(); + } + + /** @TODO: ne faire qu'un seul chargement avec SimplePie et récupérer les + même informations. Mettre le chargement en cache au moins d'une méthode + loadLeed() qui ne chargera qu'une seule fois. Voire même en déclenchement + retardé, au dernier moment. */ + function getInfos(){ + $xml = @simplexml_load_file($this->url); + if($xml!=false){ + $this->name = array_shift ($xml->xpath('channel/title')); + $this->description = array_shift ($xml->xpath('channel/description')); + $this->website = array_shift ($xml->xpath('channel/link')); + } + } + + function getError() { return $this->error; } + + /*@TODO: fournir un extrait quand il 'y a pas de description. De même pour les médias. + @TODO: SimplePie remplace "é" par "é", il ne devrait pas le faire. + J'ai testé set_stupidly_fast(true) sans succès. + Il encadre les descriptions avec
, absents dans le source du flux. + @TODO: la vérification de doublon est sous la responsabilité de l'appelant. + Il serait peut-être utile de faire une méthode add() qui vérifie, plante si + nécessaire, et appelle parse(). Impossible de vérifier dans parse() même + car elle est appelée aussi pour autre chose que l'ajout. + */ + function parse($syncId,&$nbEvents =0, $enableCache=true, $forceFeed=false){ + $nbEvents = 0; + assert('is_int($syncId) && $syncId>0'); + if (empty($this->id) || 0 == $this->id) { + /* Le flux ne dispose pas pas d'id !. Ça arrive si on appelle + parse() sans avoir appelé save() pour un nouveau flux. + @TODO: un create() pour un nouveau flux ? */ + $msg = 'Empty or null id for a feed! ' + .'See '.__FILE__.' on line '.__LINE__; + error_log($msg, E_USER_ERROR); + die($msg); // Arrêt, sinon création événements sans flux associé. + } + $feed = new SimplePie(); + $feed->enable_cache($enableCache); + $feed->force_feed($forceFeed); + $feed->set_feed_url($this->url); + $feed->set_useragent('Mozilla/4.0 Leed (LightFeed Aggregator) '.VERSION_NAME.' by idleman http://projet.idleman.fr/leed'); + if (!$feed->init()) { + $this->error = $feed->error; + $this->lastupdate = $_SERVER['REQUEST_TIME']; + $this->save(); + return false; + } + + $feed->handle_content_type(); // UTF-8 par défaut pour SimplePie + + if($this->name=='') $this->name = $feed->get_title(); + if($this->name=='') $this->name = $this->url; + $this->website = $feed->get_link(); + $this->description = $feed->get_description(); + + $items = $feed->get_items(); + $eventManager = new Event(); + + $events = array(); + $iEvents = 0; + foreach($items as $item){ + // Ne retient que les 100 premiers éléments de flux. + if ($iEvents++>=100) break; + + // Si le guid existe déjà, on évite de le reparcourir. + $alreadyParsed = $eventManager->load(array('guid'=>$item->get_id(), 'feed'=>$this->id)); + if (isset($alreadyParsed)&&($alreadyParsed!=false)) { + $events[]=$alreadyParsed->getId(); + continue; + } + + // Initialisation des informations de l'événement (élt. de flux) + $event = new Event(); + $event->setSyncId($syncId); + $event->setGuid($item->get_id()); + $event->setTitle($item->get_title()); + $event->setPubdate($item->get_date()); + $event->setCreator( + ''==$item->get_author() + ? '' + : $item->get_author()->name + ); + $event->setLink($item->get_permalink()); + + $event->setFeed($this->id); + $event->setUnread(1); // inexistant, donc non-lu + + //Gestion de la balise enclosure pour les podcasts et autre cochonneries :) + $enclosure = $item->get_enclosure(); + if($enclosure!=null && $enclosure->link!=''){ + $enclosureName = substr( + $enclosure->link, + strrpos($enclosure->link, '/')+1, + strlen($enclosure->link) + ); + $enclosureArgs = strpos($enclosureName, '?'); + if($enclosureArgs!==false) + $enclosureName = substr($enclosureName,0,$enclosureArgs); + $enclosureFormat = isset($enclosure->handler) + ? $enclosure->handler + : substr($enclosureName, strrpos($enclosureName,'.')+1); + + $enclosure ='

Fichier média :

'.$enclosureName.' (Format '.strtoupper($enclosureFormat).', '.Functions::convertFileSize($enclosure->length).')
'; + }else{ + $enclosure = ''; + } + + $event->setContent($item->get_content().$enclosure); + $event->setDescription($item->get_description().$enclosure); + + if(trim($event->getDescription())=='') + $event->setDescription( + substr($event->getContent(),0,300) + .'…
Lire la suite de l\'article' + ); + if(trim($event->getContent())=='') + $event->setContent($event->getDescription()); + + $event->setCategory($item->get_category()); + $event->save(); + $nbEvents++; + } + + $listid = ""; + foreach($events as $item){ + $listid.=','.$item; + } + $query='UPDATE `'.MYSQL_PREFIX.'event` SET syncId='.$syncId.' WHERE id in (0'.$listid.');'; + $myQuery = $this->customQuery($query); + + $this->lastupdate = $_SERVER['REQUEST_TIME']; + $this->save(); + return true; + } + + + function removeOldEvents($maxEvent, $currentSyncId){ + if ($maxEvent<=0) return; + $eventManager = new Event(); + $nbLines = $eventManager->rowCount(array( + 'feed'=>$this->id, + 'unread'=>0, + 'favorite'=>0, + )); + $limit = $nbLines - $maxEvent; + if ($limit<=0) return; + $tableEvent = '`'.MYSQL_PREFIX."event`"; + $query = " + DELETE FROM {$tableEvent} WHERE feed={$this->id} AND favorite!=1 AND unread!=1 AND syncId!={$currentSyncId} ORDER BY pubdate ASC LIMIT {$limit} + "; + ///@TODO: escape the variables inside mysql + $this->customExecute($query); + } + + function setId($id){ + $this->id = $id; + } + + function getDescription(){ + return $this->description; + } + + function setDescription($description){ + $this->description = $description; + } + function getWebSite(){ + return $this->website; + } + + function setWebSite($website){ + $this->website = $website; + } + + function getId(){ + return $this->id; + } + + function getUrl(){ + return $this->url; + } + + function setUrl($url){ + $this->url = $url; + } + + function getName(){ + return (trim($this->name)!='' ? $this->name:$this->url); + } + + function setName($name){ + $this->name = $name; + } + + + function getEvents($start=0,$limit=10000,$order,$columns='*'){ + $eventManager = new Event(); + $events = $eventManager->loadAllOnlyColumn($columns,array('feed'=>$this->getId()),$order,$start.','.$limit); + return $events; + } + + function countUnreadEvents(){ + $unreads = array(); + $results = Feed::customQuery("SELECT COUNT(".MYSQL_PREFIX."event.id), ".MYSQL_PREFIX."event.feed FROM ".MYSQL_PREFIX."event WHERE ".MYSQL_PREFIX."event.unread = 1 GROUP BY ".MYSQL_PREFIX."event.feed") ; + if($results!=false){ + while($item = mysql_fetch_array($results)){ + $unreads[$item[1]] = $item[0]; + } + } + return $unreads; + } + + function getFeedsPerFolder(){ + $feedsFolderMap = array(); + $feedsIdMap = array(); + + $results = Feed::customQuery("SELECT ".MYSQL_PREFIX."feed.name AS name, ".MYSQL_PREFIX."feed.id AS id, ".MYSQL_PREFIX."feed.url AS url, ".MYSQL_PREFIX."folder.id AS folder FROM ".MYSQL_PREFIX."feed INNER JOIN ".MYSQL_PREFIX."folder ON ( ".MYSQL_PREFIX."feed.folder = ".MYSQL_PREFIX."folder.id ) ORDER BY ".MYSQL_PREFIX."feed.name ;"); + if($results!=false){ + while($item = mysql_fetch_array($results)){ + $name = $item['name']; + $feedsIdMap[$item['id']]['name'] = $name; + + + $feedsFolderMap[$item['folder']][$item['id']]['id'] = $item['id']; + $feedsFolderMap[$item['folder']][$item['id']]['name'] = $name; + $feedsFolderMap[$item['folder']][$item['id']]['url'] = $item['url']; + + } + } + $feeds['folderMap'] = $feedsFolderMap; + $feeds['idMap'] = $feedsIdMap; + return $feeds; + } + + function getFolder(){ + return $this->folder; + } + + function setFolder($folder){ + $this->folder = $folder; + } + + function getLastupdate(){ + return $this->lastUpdate; + } + + function setLastupdate($lastupdate){ + $this->lastupdate = $lastupdate; + } + + function getIsverbose(){ + return $this->isverbose; + } + + function setIsverbose($isverbose){ + $this->isverbose = $isverbose; + } + + /** @returns vrai si l'url n'est pas déjà connue .*/ + function notRegistered() { + return $this->rowCount(array('url' => $this->url)) == 0; + } + +} + +?> diff --git a/sources/Folder.class.php b/sources/Folder.class.php new file mode 100755 index 0000000..7960b27 --- /dev/null +++ b/sources/Folder.class.php @@ -0,0 +1,99 @@ +'key', + 'name'=>'string', + 'parent'=>'integer', + 'isopen'=>'integer' + ); + + function unreadCount(){ + $results = $this->customQuery('SELECT COUNT('.MYSQL_PREFIX.'event.id) FROM '.MYSQL_PREFIX.'event INNER JOIN '.MYSQL_PREFIX.'feed ON ('.MYSQL_PREFIX.'event.feed = '.MYSQL_PREFIX.'feed.id) WHERE '.MYSQL_PREFIX.'event.unread=1 AND '.MYSQL_PREFIX.'feed.folder = '.$this->getId()); + $number = mysql_fetch_array($results); + return $number[0]; + } + + + function getEvents($start=0,$limit=10000,$order,$columns='*'){ + $eventManager = new Event(); + $objects = array(); + $results = $this->customQuery('SELECT '.$columns.' FROM '.MYSQL_PREFIX.'event INNER JOIN '.MYSQL_PREFIX.'feed ON ('.MYSQL_PREFIX.'event.feed = '.MYSQL_PREFIX.'feed.id) WHERE '.MYSQL_PREFIX.'event.unread=1 AND '.MYSQL_PREFIX.'feed.folder = '.$this->getId().' ORDER BY '.$order.' LIMIT '.$start.','.$limit); + if($results!=false){ + while($item = mysql_fetch_array($results)){ + $object = new Event(); + foreach($object->getObject_fields() as $field=>$type){ + $setter = 'set'.ucFirst($field); + if(isset($item[$field])) $object->$setter($item[$field]); + } + $objects[] = $object; + unset($object); + } + } + + return $objects; + } + + function __construct(){ + parent::__construct(); + } + + function setId($id){ + $this->id = $id; + } + + function getFeeds(){ + $feedManager = new Feed(); + return $feedManager->loadAll(array('folder'=>$this->getId()),'name'); + } + + function getFolders(){ + $folderManager = new Folder(); + return $folderManager->loadAll(array('parent'=>$this->getId())); + } + + + function getId(){ + return $this->id; + } + + function getName(){ + return $this->name; + } + + function setName($name){ + $this->name = $name; + } + + function getParent(){ + return $this->parent; + } + + function setParent($parent){ + $this->parent = $parent; + } + + function getIsopen(){ + return $this->isopen; + } + + function setIsopen($isopen){ + $this->isopen = $isopen; + } + + + +} + +?> diff --git a/sources/Functions.class.php b/sources/Functions.class.php new file mode 100755 index 0000000..2bea86f --- /dev/null +++ b/sources/Functions.class.php @@ -0,0 +1,371 @@ + variable a sécuriser + * @param niveau de securisation + * @return variable securisée + */ + + public static function secure($var,$level = 1){ + $var = htmlspecialchars($var, ENT_QUOTES, "UTF-8"); + if($level<1)$var = mysql_real_escape_string($var); + if($level<2)$var = addslashes($var); + return $var; + } + + + /** + * Return l'environnement/serveur sur lequel on se situe, permet de changer les + * connexions bdd en fonction de la dev, la préprod ou la prod + */ + public static function whereImI(){ + + $maps = array ( + 'LOCAL'=>array('localhost','127.0.0.1','0.0.0.1','::0.0.0.0'), + 'LAN'=>array('192.168.10.','valentin'), + 'PWAN'=>array('test.sys1.fr'), + 'WAN'=>array('www.sys1.fr'), + ); + + + $return = 'UNKNOWN'; + foreach($maps as $map=>$values){ + + foreach($values as $ip){ + $pos = strpos(strtolower($_SERVER['HTTP_HOST']),$ip); + if ($pos!==false){ + $return = $map; + } + } + } + return $return; + } + + public static function isLocal($perimeter='LOCAL'){ + $return = false; + + $localTab = array('localhost','127.0.0.1','0.0.0.1','::0.0.0.0'); + $lanTab = array('192.168.10.','valentin'); + + switch($perimeter){ + case 'LOCAL': + foreach($localTab as $ip){ + $pos = strpos(strtolower($_SERVER['HTTP_HOST']),$ip); + if ($pos!==false){ + $return = true; + } + } + break; + case 'LAN': + foreach($lanTab as $ip){ + $pos = strpos(strtolower($_SERVER['HTTP_HOST']),$ip); + if ($pos!==false){ + $return = true; + } + } + break; + case 'ALL': + foreach($localTab as $ip){ + $pos = strpos(strtolower($_SERVER['HTTP_HOST']),$ip); + if ($pos!==false){ + $return = true; + } + } + foreach($lanTab as $ip){ + $pos = strpos(strtolower($_SERVER['HTTP_HOST']),$ip); + if ($pos!==false){ + $return = true; + } + } + break; + } + + return $return; + } + + + /** + * Convertis la chaine passée en timestamp quel que soit sont format + * (prend en charge les formats type dd-mm-yyy , dd/mm/yyy, yyyy/mm/ddd...) + */ + public static function toTime($string){ + $string = str_replace('/','-',$string); + $string = str_replace('\\','-',$string); + + $string = str_replace('Janvier','Jan',$string); + $string = str_replace('Fevrier','Feb',$string); + $string = str_replace('Mars','Mar',$string); + $string = str_replace('Avril','Apr',$string); + $string = str_replace('Mai','May',$string); + $string = str_replace('Juin','Jun',$string); + $string = str_replace('Juillet','Jul',$string); + $string = str_replace('Aout','Aug',$string); + $string = str_replace('Septembre','Sept',$string); + $string = str_replace('Octobre','Oct',$string); + $string = str_replace('Novembre','Nov',$string); + $string = str_replace('Decembre','Dec',$string); + return strtotime($string); + } + + /** + * Recupere l'ip de l'internaute courant + * @author Valentin + * @return ip de l'utilisateur + */ + + public static function getIP(){ + if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){ + $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];} + elseif(isset($_SERVER['HTTP_CLIENT_IP'])){ + $ip = $_SERVER['HTTP_CLIENT_IP'];} + else{ $ip = $_SERVER['REMOTE_ADDR'];} + return $ip; + } + + /** + * Retourne une version tronquée au bout de $limit caracteres de la chaine fournie + * @author Valentin + * @param message a tronquer + * @param limite de caracteres + * @return chaine tronquée + */ + public static function truncate($msg,$limit){ + if(mb_strlen($msg)>$limit){ + $fin='…' ; + $nb=$limit-mb_strlen($fin) ; + }else{ + $nb=mb_strlen($msg); + $fin=''; + } + return mb_substr($msg, 0, $nb).$fin; + } + + + function getExtension($fileName){ + $dot = explode('.',$fileName); + return $dot[sizeof($dot)-1]; + } + + /** + * Definis si la chaine fournie est existante dans la reference fournie ou non + * @param unknown_type $string + * @param unknown_type $reference + * @return false si aucune occurence du string, true dans le cas contraire + */ + public static function contain($string,$reference){ + $return = true; + $pos = strpos($reference,$string); + if ($pos === false) { + $return = false; + } + return strtolower($return); + } + + /** + * Définis si la chaine passée en parametre est une url ou non + */ + public static function isUrl($url){ + $return =false; + if (preg_match('/^(http|https|ftp)://([A-Z0-9][A-Z0-9_-]*(?:.[A-Z0-9][A-Z0-9_-]*)+):?(d+)?/?/i', $url)) { + $return =true; + } + return $return; + } + + /** + * Définis si la chaine passée en parametre est une couleur héxadécimale ou non + */ + public static function isColor($color){ + $return =false; + if (preg_match('/^#(?:(?:[a-fd]{3}){1,2})$/i', $color)) { + $return =true; + } + return $return; + } + + /** + * Définis si la chaine passée en parametre est un mail ou non + */ + public static function isMail($mail){ + $return =false; + if (filter_var($mail, FILTER_VALIDATE_EMAIL)) { + $return =true; + } + return $return; + } + + /** + * Définis si la chaine passée en parametre est une IP ou non + */ + public static function isIp($ip){ + $return =false; + if (preg_match('^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:[.](?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$',$ip)) { + $return =true; + } + return $return; + } + + public static function sourceName($string){ + $name = strtolower($string); + $name = str_replace(' ','-',$name); + $name = str_replace(''','-',$name); + $name = str_replace('\'','-',$name); + $name = str_replace(',','-',$name); + $name = str_replace(':','-',$name); + $name = str_replace('à','a',$name); + $name = trim($name); + $name = html_entity_decode($name,null,'UTF-8'); + return $name; + } + + public static function makeCookie($name, $value, $expire='') { + if($expire == '') { + setcookie($name, $value, mktime(0,0,0, date("d"), + date("m"), (date("Y")+1)),'/'); + }else { + setcookie($name, '', mktime(0,0,0, date("d"), + date("m"), (date("Y")-1)),'/'); + } + } + + public static function destroyCookie($name){ + Fonction::makeCookie($name,'',time()-3600); + unset($_COOKIE[$name]); + } + + public static function wordwrap($str, $width = 75, $break = "\n", $cut = false) + { + $str = html_entity_decode($str); + $str = htmlentities (wordwrap($str,$width,$break,$cut)); + $str = str_replace('<br/>','
',$str); + $str = str_replace('&','&',$str); + return $str; + } + + public static function createFile($filePath,$content){ + $fichier = fopen($filePath,"w+"); + $fwriteResult = fwrite($fichier,$content); + fclose($fichier); + } + + + + public static function convertFileSize($bytes) + { + if($bytes<1024){ + return round(($bytes / 1024), 2).' o'; + }elseif(1024<$bytes && $bytes<1048576){ + return round(($bytes / 1024), 2).' ko'; + }elseif(1048576<$bytes && $bytes<1073741824){ + return round(($bytes / 1024)/1024, 2).' Mo'; + }elseif(1073741824<$bytes){ + return round(($bytes / 1024)/1024/1024, 2).' Go'; + } + } + + + public static function hexaValue($str){ + $code = dechex(crc32($str)); + $code = substr($code, 0, 6); + return $code; + } + + public static function scanRecursiveDir($dir){ + $files = scandir($dir); + $allFiles = array(); + foreach($files as $file){ + if($file!='.' && $file!='..'){ + if(is_dir($dir.$file)){ + $allFiles = array_merge($allFiles,Fonction::scanRecursiveDir($dir.$file)); + }else{ + $allFiles[]=str_replace('//','/',$dir.'/'.$file); + } + } + } + return $allFiles; + } + + /** Permet la sortie directe de texte à l'écran, sans tampon. + Source : http://php.net/manual/fr/function.flush.php + */ + public static function triggerDirectOutput() { + // La ligne de commande n'en a pas besoin. + if ('cli'==php_sapi_name()) return; + if (function_exists('apache_setenv')) { + /* Selon l'hébergeur la fonction peut être désactivée. Alors Php + arrête le programme avec l'erreur : + "PHP Fatal error: Call to undefined function apache_setenv()". + */ + @apache_setenv('no-gzip', 1); + } + @ini_set('zlib.output_compression', 0); + @ini_set('implicit_flush', 1); + for ($i = 0; $i < ob_get_level(); $i++) { ob_end_flush(); } + ob_implicit_flush(1); + } + + public static function relativePath($from, $to, $ps = '/') { + $arFrom = explode($ps, rtrim($from, $ps)); + $arTo = explode($ps, rtrim($to, $ps)); + while(count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) { + array_shift($arFrom); + array_shift($arTo); + } + return str_pad("", count($arFrom) * 3, '..'.$ps).implode($ps, $arTo); + } + + + // Nettoyage de l'url avant la mise en base + public static function clean_url( $url ) { + $url = str_replace('&', '&', $url); + return $url; + } + + + + /** + * Méthode de test de connexion. + * @return true si ok + * @param server + * @param login + * @param pass + * @param db facultatif, si précisé alors tente de la séléctionner + */ + public static function testDb($server, $login, $pass, $db=null) { + /* Méthode hors des classes dédiées aux BDD afin de supporter le moins + de dépendances possibles. En particulier, pas besoin que le fichier + de configuration existe. */ + $link = mysql_connect($server, $login, $pass); + if (false===$link) return false; + if (!is_null($db) && false===mysql_select_db($db, $link)) return false; + mysql_close($link); + return true; + } + + /** + * @return les langues acceptées par le navigateur + */ + public static function getBrowserLanguages() { + /* http://www.w3.org/International/questions/qa-lang-priorities.en.php + * ex: da, en-gb;q=0.8,en;q=0.7 --> array('da','en'); + */ + $languages = array(); + $chunks = preg_split('/,\s*/', $_SERVER['HTTP_ACCEPT_LANGUAGE']); + foreach($chunks as $chunk) $languages []= substr($chunk, 0, 2); + return array_unique($languages); + } +} +?> diff --git a/sources/MysqlConnector.class.php b/sources/MysqlConnector.class.php new file mode 100755 index 0000000..c8775a3 --- /dev/null +++ b/sources/MysqlConnector.class.php @@ -0,0 +1,199 @@ +connect(); + } + + + + /** + * Methode de recuperation unique de l'instance + * @author Valentin CARRUESCO + * @category Singleton + * @param + * @return $instance + */ + + public static function getInstance(){ + + if (MysqlConnector::$instance === null) { + MysqlConnector::$instance = new self(); + } + return MysqlConnector::$instance; + } + + + + public function connect(){ + $this->connection = mysql_connect(MYSQL_HOST,MYSQL_LOGIN,MYSQL_MDP); + mysql_query('SET NAMES utf8'); + mysql_select_db(MYSQL_BDD,$this->connection); + } + + + + public function __toString(){ + $retour = ""; + $retour .= "instance de la classe MysqlConnector :
"; + $retour .= '$hote : '.$this->hote.'
'; + $retour .= '$login : '.$this->login.'
'; + $retour .= '$mdp : '.$this->mdp.'
'; + $retour .= '$bdd : '.$this->bdd.'
'; + $retour .= '$port : '.$this->port.'
'; + return $retour; + } + + private function __clone(){ + //Action lors du clonage de l'objet + } + + // ACCESSEURS + + public function getId(){ + return $this->id; + } + + public function setId($id){ + $this->id = $id; + } + + /** + * Méthode de récuperation de l'attribut hote de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param Aucun + * @return hote + */ + + public function getHote(){ + return $this->hote; + } + + /** + * Méthode de définition de l'attribut hote de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param $hote + * @return Aucun retour + */ + + public function setHote($hote){ + $this->hote = $hote; + } + + /** + * Méthode de récuperation de l'attribut login de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param Aucun + * @return login + */ + + public function getLogin(){ + return $this->login; + } + + /** + * Méthode de définition de l'attribut login de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param $login + * @return Aucun retour + */ + + public function setLogin($login){ + $this->login = $login; + } + + /** + * Méthode de récuperation de l'attribut mdp de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param Aucun + * @return mdp + */ + + public function getMdp(){ + return $this->mdp; + } + + /** + * Méthode de définition de l'attribut mdp de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param $mdp + * @return Aucun retour + */ + + public function setMdp($mdp){ + $this->mdp = $mdp; + } + + /** + * Méthode de récuperation de l'attribut bdd de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param Aucun + * @return bdd + */ + + public function getBdd(){ + return $this->bdd; + } + + /** + * Méthode de définition de l'attribut bdd de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param $bdd + * @return Aucun retour + */ + + public function setBdd($bdd){ + $this->bdd = $bdd; + } + + /** + * Méthode de récuperation de l'attribut port de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param Aucun + * @return port + */ + + public function getPort(){ + return $this->port; + } + + /** + * Méthode de définition de l'attribut port de la classe Mysql + * @author Valentin CARRUESCO + * @category Accesseur + * @param $port + * @return Aucun retour + */ + + public function setPort($port){ + $this->port = $port; + } +} +?> diff --git a/sources/MysqlEntity.class.php b/sources/MysqlEntity.class.php new file mode 100755 index 0000000..43dbf7b --- /dev/null +++ b/sources/MysqlEntity.class.php @@ -0,0 +1,446 @@ +object_fields[$field])) + $type = $this->object_fields[$field]; + + $return = false; + switch($type){ + case 'key': + case 'object': + case 'integer': + case 'boolean': + $return = intval($value); + break; + case 'string': + case 'timestamp': + case 'longstring': + default; + $return = mysql_real_escape_string((string)$value); + break; + } + return $return ; + } + + public function __construct(){ + MysqlConnector::getInstance(); + } + + public function __destruct(){ + + } + + // GESTION SQL + + /** + * Methode de suppression de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $debug=false active le debug mode (0 ou 1) + * @return Aucun retour + */ + public function destroy($debug=false) + { + $query = 'DROP TABLE IF EXISTS '.MYSQL_PREFIX.$this->TABLE_NAME.';'; + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + $myQuery = $this->customQuery($query); + } + + /** + * Methode de nettoyage de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $debug=false active le debug mode (0 ou 1) + * @return Aucun retour + */ + public function truncate($debug=false) + { + $query = 'TRUNCATE TABLE '.MYSQL_PREFIX.$this->TABLE_NAME.';'; + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + $myQuery = $this->customQuery($query); + } + + /** + * Methode de creation de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $debug=false active le debug mode (0 ou 1) + * @return Aucun retour + */ + public function create($debug=false){ + $query = 'CREATE TABLE IF NOT EXISTS `'.MYSQL_PREFIX.$this->TABLE_NAME.'` ('; + + $i=false; + foreach($this->object_fields as $field=>$type){ + if($i){$query .=',';}else{$i=true;} + $query .='`'.$field.'` '. $this->sgbdType($type).' NOT NULL'; + } + if (isset($this->object_fields_index)){ + foreach($this->object_fields_index as $field=>$type){ + $query .= ',KEY `index'.$field.'` (`'.$field.'`)'; + } + } + $query .= ') + ENGINE InnoDB, + DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci + ;'; + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + $myQuery = $this->customQuery($query); + } + + public function massiveInsert($events){ + if (empty($events)) return; + $query = 'INSERT INTO `'.MYSQL_PREFIX.$this->TABLE_NAME.'`('; + $i=false; + foreach($this->object_fields as $field=>$type){ + if($type!='key'){ + if($i){$query .=',';}else{$i=true;} + $query .='`'.$field.'`'; + } + } + $query .=') select'; + $u = false; + + foreach($events as $event){ + + if($u){$query .=' union select ';}else{$u=true;} + + $i=false; + foreach($event->object_fields as $field=>$type){ + if($type!='key'){ + if($i){$query .=',';}else{$i=true;} + $query .='"'.$this->secure($event->$field, $field).'"'; + } + } + + + } + + $query .=';'; + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + + $this->customQuery($query); + } + + /** + * Methode d'insertion ou de modifications d'elements de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param Aucun + * @return Aucun retour + */ + public function save(){ + if(isset($this->id)){ + $query = 'UPDATE `'.MYSQL_PREFIX.$this->TABLE_NAME.'`'; + $query .= ' SET '; + + $i=false; + foreach($this->object_fields as $field=>$type){ + if($i){$query .=',';}else{$i=true;} + $id = $this->$field; + $query .= '`'.$field.'`="'.$this->secure($id, $field).'"'; + } + + $query .= ' WHERE `id`="'.$this->id.'";'; + }else{ + $query = 'INSERT INTO `'.MYSQL_PREFIX.$this->TABLE_NAME.'`('; + $i=false; + foreach($this->object_fields as $field=>$type){ + if($i){$query .=',';}else{$i=true;} + $query .='`'.$field.'`'; + } + $query .=')VALUES('; + $i=false; + foreach($this->object_fields as $field=>$type){ + if($i){$query .=',';}else{$i=true;} + $query .='"'.$this->secure($this->$field, $field).'"'; + } + + $query .=');'; + } + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + $this->customQuery($query); + $this->id = (!isset($this->id)?mysql_insert_id():$this->id); + } + + /** + * Méthode de modification d'éléments de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $colonnes=>$valeurs + * @param $colonnes (WHERE) =>$valeurs (WHERE) + * @param $operation="=" definis le type d'operateur pour la requete select + * @param $debug=false active le debug mode (0 ou 1) + * @return Aucun retour + */ + public function change($columns,$columns2,$operation='=',$debug=false){ + $query = 'UPDATE `'.MYSQL_PREFIX.$this->TABLE_NAME.'` SET '; + $i=false; + foreach ($columns as $column=>$value){ + if($i){$query .=',';}else{$i=true;} + $query .= '`'.$column.'`="'.$this->secure($value, $column).'" '; + } + $query .=' WHERE '; + + $i = false; + foreach ($columns2 as $column=>$value){ + if($i){$query .='AND ';}else{$i=true;} + $query .= '`'.$column.'`'.$operation.'"'.$this->secure($value, $column).'" '; + } + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + $this->customQuery($query); + } + + /** + * Méthode de selection de tous les elements de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $ordre=null + * @param $limite=null + * @param $debug=false active le debug mode (0 ou 1) + * @return > $Entity + */ + public function populate($order=null,$limit=null,$debug=false){ + $results = $this->loadAll(array(),$order,$limit,'=',$debug); + return $results; + } + + /** + * Méthode de selection multiple d'elements de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $colonnes (WHERE) + * @param $valeurs (WHERE) + * @param $ordre=null + * @param $limite=null + * @param $operation="=" definis le type d'operateur pour la requete select + * @param $debug=false active le debug mode (0 ou 1) + * @return > $Entity + */ + public function loadAll($columns,$order=null,$limit=null,$operation="=",$debug=false,$selColumn='*'){ + $objects = array(); + $whereClause = ''; + + if($columns!=null && sizeof($columns)!=0){ + $whereClause .= ' WHERE '; + $i = false; + foreach($columns as $column=>$value){ + if($i){$whereClause .=' AND ';}else{$i=true;} + $whereClause .= '`'.$column.'`'.$operation.'"'.$this->secure($value, $column).'"'; + } + } + $query = 'SELECT '.$selColumn.' FROM `'.MYSQL_PREFIX.$this->TABLE_NAME.'` '.$whereClause.' '; + if($order!=null) $query .='ORDER BY '.$order.' '; + if($limit!=null) $query .='LIMIT '.$limit.' '; + $query .=';'; + + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + $execQuery = $this->customQuery($query); + while($queryReturn = mysql_fetch_assoc($execQuery)){ + + $object = new $this->CLASS_NAME(); + foreach($this->object_fields as $field=>$type){ + if(isset($queryReturn[$field])) $object->$field = $queryReturn[$field]; + } + $objects[] = $object; + unset($object); + } + return $objects; + } + + public function loadAllOnlyColumn($selColumn,$columns,$order=null,$limit=null,$operation="=",$debug=false){ + $objects = $this->loadAll($columns,$order,$limit,$operation,$debug,$selColumn); + if(count($objects)==0)$objects = array(); + return $objects; + } + + + /** + * Méthode de selection unique d'élements de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $colonnes (WHERE) + * @param $valeurs (WHERE) + * @param $operation="=" definis le type d'operateur pour la requete select + * @param $debug=false active le debug mode (0 ou 1) + * @return $Entity ou false si aucun objet n'est trouvé en base + */ + public function load($columns,$operation='=',$debug=false){ + $objects = $this->loadAll($columns,null,1,$operation,$debug); + if(!isset($objects[0]))$objects[0] = false; + return $objects[0]; + } + + /** + * Méthode de selection unique d'élements de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $colonnes (WHERE) + * @param $valeurs (WHERE) + * @param $operation="=" definis le type d'operateur pour la requete select + * @param $debug=false active le debug mode (0 ou 1) + * @return $Entity ou false si aucun objet n'est trouvé en base + */ + public function getById($id,$operation='=',$debug=false){ + return $this->load(array('id'=>$id),$operation,$debug); + } + + /** + * Methode de comptage des éléments de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $debug=false active le debug mode (0 ou 1) + * @return nombre de ligne dans l'entité' + */ + public function rowCount($columns=null) + { + $whereClause =''; + if($columns!=null){ + $whereClause = ' WHERE '; + $i=false; + foreach($columns as $column=>$value){ + if($i){$whereClause .=' AND ';}else{$i=true;} + $whereClause .= '`'.$column.'`="'.$this->secure($value, $column).'"'; + } + } + $query = 'SELECT COUNT(1) FROM '.MYSQL_PREFIX.$this->TABLE_NAME.$whereClause; + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + $myQuery = $this->customQuery($query); + $number = mysql_fetch_array($myQuery); + return $number[0]; + } + + /** + * Méthode de suppression d'éléments de l'entité + * @author Valentin CARRUESCO + * @category manipulation SQL + * @param $colonnes (WHERE) + * @param $valeurs (WHERE) + * @param $operation="=" definis le type d'operateur pour la requete select + * @param $debug=false active le debug mode (0 ou 1) + * @return Aucun retour + */ + public function delete($columns,$operation='=',$debug=false){ + $whereClause = ''; + + $i=false; + foreach($columns as $column=>$value){ + if($i){$whereClause .=' AND ';}else{$i=true;} + $whereClause .= '`'.$column.'`'.$operation.'"'.$this->secure($value, $column).'"'; + } + $query = 'DELETE FROM `'.MYSQL_PREFIX.$this->TABLE_NAME.'` WHERE '.$whereClause.' ;'; + if($this->debug)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$query.'
'.mysql_error(); + $this->customQuery($query); + + } + + ///@TODO: pourquoi deux méthodes différentes qui font la même chose ? + public function customExecute($request){ + if($this->debugAllQuery)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$request.'
'.mysql_error(); + $result = mysql_query($request); + if (false===$result) { + throw new Exception(mysql_error()); + } + return $result; + } + public function customQuery($request){ + if($this->debugAllQuery)echo '
'.$this->CLASS_NAME.' ('.__METHOD__ .') : Requete --> '.$request.'
'.mysql_error(); + $result = mysql_query($request); + if (false===$result) { + throw new Exception(mysql_error()); + } + return $result; + } + + + // ACCESSEURS + /** + * Méthode de récuperation de l'attribut debug de l'entité + * @author Valentin CARRUESCO + * @category Accesseur + * @param Aucun + * @return debug + */ + + public function getDebug(){ + return $this->debug; + } + + /** + * Méthode de définition de l'attribut debug de l'entité + * @author Valentin CARRUESCO + * @category Accesseur + * @param $debug + */ + + public function setDebug($debug){ + $this->debug = $debug; + } + + public function getObject_fields(){ + return $this->object_fields; + } + + /** + * @return VRAI si la table existe, FAUX sinon + */ + + public function tableExists() { + $table = MYSQL_PREFIX.$this->TABLE_NAME; + $result = $this->customQuery("SHOW TABLES LIKE '$table'"); + $assoc = mysql_fetch_assoc($result); + return false===$assoc ? false : true; + } +} +?> diff --git a/sources/Opml.class.php b/sources/Opml.class.php new file mode 100755 index 0000000..5741c84 --- /dev/null +++ b/sources/Opml.class.php @@ -0,0 +1,168 @@ +feeds = $feedManager->populate('name'); + $this->folders = $folderManager->loadAll(array('parent'=>-1),'name'); + } + + /** + * Convertit les caractères qui interfèrent avec le XML + */ + protected function escapeXml($string) { + /** Les entités sont utiles pour deux raisons : encodage et + échappement. L'encodage n'est pas un problème, l'application travaille + nativement avec Unicode. L'échappement dans XML impose d'échapper les + esperluettes (&) et les guillemets. Ces derniers sont utilisés comme + séparateur de chaine. Les simples cotes restent intactes. + * On retire toutes les entités de sorte à obtenir une chaîne totalement + en UTF-8. Elle peut alors contenir des & et des ", nocifs pour XML. + * On échappe les caractères & et " de sorte à ce que le contenu dans le + XML soit correct. On suppose qu'à la lecture, cet échappement est + annulé. + * Accessoirement, on remplace les espaces non signifiants par une seule + espace. C'est le cas des retours chariots physiques, non + interprétables. + */ + // Retire toutes les entités, & é etc. + $string = html_entity_decode($string, ENT_COMPAT, 'UTF-8' ); + // Remet les entités HTML comme & mais ne touche pas aux accents. + $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); + // Supprime les blancs non signifiants comme les sauts de ligne. + $string = preg_replace('/\s+/', ' ', $string); + return $string; + } + + /** + * Exporte récursivement les flux. + */ + protected function exportRecursive($folders, $identLevel=0) { + $_ = ' '; + $__ = ''; for($i=0;$i<$identLevel;$i++) $__.=$_; + $xmlStream = ''; + foreach($folders as $folder) { + // Pas utilisé, vu qu'il n'y a qu'un seul niveau de dossiers. + $xmlStream .= $this->exportRecursive( + $folder->getFolders(), $identLevel+1 + ); + $feeds = $folder->getFeeds(); + if (empty($feeds)) continue; + $text = $this->escapeXml($folder->getName()); + $xmlStream .= "{$__}\n"; + foreach($feeds as $feed){ + $url = $this->escapeXml($feed->getUrl()); + $website = $this->escapeXml($feed->getWebsite()); + $title = $this->escapeXml($feed->getName()); + $text = $title; + $description = $this->escapeXml($feed->getDescription()); + $xmlStream .= "{$__}{$_}\n"; + } + $xmlStream .= "{$__}\n"; + } + return $xmlStream; + } + + /** + * Exporte l'ensemble des flux et sort les en-têtes. + */ + function export() { + $this->update(); + $date = date('D, d M Y H:i:s O'); + $xmlStream = " + + + Leed export + Leed + idleman@idleman.fr + $date + + \n"; + $xmlStream .= $this->exportRecursive($this->folders, 2); + $xmlStream .= " \n\n"; + return $xmlStream; + } + + protected function importRec($folder, $folderId=1){ + $folderManager = new Folder(); + $feedManager = new Feed(); + foreach($folder as $item) { + // Cela varie selon les implémentations d'OPML. + $feedName = $item['text'] ? 'text' : 'title'; + if (isset($item->outline[0])) { // un dossier + $folder = $folderManager->load(array('name'=>$item[$feedName])); + $folder = (!$folder?new Folder():$folder); + $folder->setName($item[$feedName]); + $folder->setParent(($folderId==1?-1:$folderId)); + $folder->setIsopen(0); + if($folder->getId()=='') $folder->save(); + $this->importRec($item->outline,$folder->getId()); + } else { // un flux + $newFeed = $feedManager->load(array('url'=>$item[0]['xmlUrl'])); + $newFeed = (!$newFeed?new Feed():$newFeed); + if($newFeed->getId()=='') { + /* Ne télécharge pas à nouveau le même lien, même s'il est + dans un autre dossier. */ + $newFeed->setName($item[0][$feedName]); + $newFeed->setUrl($item[0]['xmlUrl']); + $newFeed->setDescription($item[0]['description']); + $newFeed->setWebsite($item[0]['htmlUrl']); + $newFeed->setFolder($folderId); + $newFeed->save(); + // $newFeed->parse(); + } else { + $this->alreadyKnowns[]= (object) array( + 'description' => $newFeed->getDescription(), + 'feedName' => $newFeed->getName(), + 'xmlUrl' => $newFeed->getUrl() + ); + } + } + } + } + + /** + * Importe les flux. + */ + function import() { + require_once("SimplePie.class.php"); + $file = $_FILES['newImport']['tmp_name']; + $internalErrors = libxml_use_internal_errors(true); + $xml = @simplexml_load_file($file); + $errorOutput = array(); + foreach (libxml_get_errors() as $error) { + $errorOutput []= "{$error->message} (line {$error->line})"; + } + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + if (!empty($xml) && empty($errorOutput)) { + $this->importRec($xml->body->outline); + } + return $errorOutput; + } + +} + +?> diff --git a/sources/Plugin.class.php b/sources/Plugin.class.php new file mode 100755 index 0000000..b6b7f80 --- /dev/null +++ b/sources/Plugin.class.php @@ -0,0 +1,350 @@ +append(new Translation(dirname($pluginFile))); + // Inclusion du coeur de plugin + include $pluginFile; + // Gestion des css du plugin en fonction du thème actif + $cssTheme = glob(dirname($pluginFile).'/*/'.DEFAULT_THEME.'.css'); + $cssDefault = glob(dirname($pluginFile).'/*/default.css'); + if(isset($cssTheme[0])){ + $GLOBALS['hooks']['css_files'][] = Functions::relativePath(str_replace('\\','/',dirname(__FILE__)),str_replace('\\','/',$cssTheme[0])); + }else if(isset($cssDefault[0])){ + $GLOBALS['hooks']['css_files'][] = Functions::relativePath(str_replace('\\','/',dirname(__FILE__)),str_replace('\\','/',$cssDefault[0])); + } + } + } + $i18n_js = $i18n->getJson(); + } + + private static function getStates(){ + $stateFile = dirname(__FILE__).Plugin::FOLDER.'/plugins.states.json'; + if(!file_exists($stateFile)) touch($stateFile); + return json_decode(file_get_contents($stateFile),true); + } + private static function setStates($states){ + $stateFile = dirname(__FILE__).Plugin::FOLDER.'/plugins.states.json'; + file_put_contents($stateFile,json_encode($states)); + } + public static function pruneStates() { + $statesBefore = self::getStates(); + if(empty($statesBefore)) + $statesBefore = array(); + + $statesAfter = array(); + $error = false; + if (is_array($statesBefore)) + { + foreach($statesBefore as $file=>$state) { + if (file_exists($file)) + $statesAfter[$file] = $state; + else + $error = true; + } + } + if ($error) self::setStates($statesAfter); + } + + + private static function getObject($pluginFile){ + $plugin = new Plugin(); + $fileLines = file_get_contents($pluginFile); + + if(preg_match("#@author\s(.+)\s\<#", $fileLines, $match)) + $plugin->setAuthor(trim($match[1])); + + if(preg_match("#@author\s(.+)\s\<([a-z\@\.A-Z\s\-]+)\>#", $fileLines, $match)) + $plugin->setMail(strtolower($match[2])); + + if(preg_match("#@name\s(.+)[\r\n]#", $fileLines, $match)) + $plugin->setName($match[1]); + + if(preg_match("#@licence\s(.+)[\r\n]#", $fileLines, $match)) + $plugin->setLicence($match[1]); + + if(preg_match("#@version\s(.+)[\r\n]#", $fileLines, $match)) + $plugin->setVersion($match[1]); + + if(preg_match("#@link\s(.+)[\r\n]#", $fileLines, $match)) + $plugin->setLink(trim($match[1])); + + if(preg_match("#@type\s(.+)[\r\n]#", $fileLines, $match)) + $plugin->setType(trim($match[1])); + + if(preg_match("#@description\s(.+)[\r\n]#", $fileLines, $match)) + $plugin->setDescription(trim($match[1])); + + if(Plugin::loadState($pluginFile) || $plugin->getType()=='component'){ + $plugin->setState(1); + }else{ + $plugin->setState(0); + } + $plugin->setPath($pluginFile); + return $plugin; + } + + public static function getAll(){ + $pluginFiles = Plugin::getFiles(); + + $plugins = array(); + if(is_array($pluginFiles)) { + foreach($pluginFiles as $pluginFile) { + $plugin = Plugin::getObject($pluginFile); + $plugins[]=$plugin; + } + } + usort($plugins, "Plugin::sortPlugin"); + return $plugins; + } + + + + public static function addHook($hookName, $functionName) { + $GLOBALS['hooks'][$hookName][] = $functionName; + } + + public static function addCss($css) { + $bt = debug_backtrace(); + $pathInfo = explode('/',dirname($bt[0]['file'])); + $count = count($pathInfo); + $name = $pathInfo[$count-1]; + $path = '.'.Plugin::FOLDER.'/'.$name.$css; + + $GLOBALS['hooks']['css_files'][] = $path; + } + + public static function callCss(){ + $return=''; + if(isset($GLOBALS['hooks']['css_files'])) { + foreach($GLOBALS['hooks']['css_files'] as $css_file) { + $return .=''."\n"; + } + } + return $return; + } + + public static function addLink($rel, $link) { + $GLOBALS['hooks']['head_link'][] = array("rel"=>$rel, "link"=>$link); + } + + public static function callLink(){ + $return=''; + if(isset($GLOBALS['hooks']['head_link'])) { + foreach($GLOBALS['hooks']['head_link'] as $head_link) { + $return .=''."\n"; + } + } + return $return; + } + + public static function path(){ + $bt = debug_backtrace(); + $pathInfo = explode('/',dirname($bt[0]['file'])); + $count = count($pathInfo); + $name = $pathInfo[$count-1]; + return '.'.Plugin::FOLDER.'/'.$name.'/'; + } + + public static function addJs($js) { + $bt = debug_backtrace(); + $pathInfo = explode('/',dirname($bt[0]['file'])); + $count = count($pathInfo); + $name = $pathInfo[$count-1]; + $path = '.'.Plugin::FOLDER.'/'.$name.$js; + + $GLOBALS['hooks']['js_files'][] = $path; + } + + public static function callJs(){ + $return=''; + if(isset($GLOBALS['hooks']['js_files'])) { + foreach($GLOBALS['hooks']['js_files'] as $js_file) { + $return .=''."\n"; + } + } + return $return; + } + + public static function callHook($hookName, $hookArguments) { + //echo '
'.$hookName.'
'; + if(isset($GLOBALS['hooks'][$hookName])) { + foreach($GLOBALS['hooks'][$hookName] as $functionName) { + call_user_func_array($functionName, $hookArguments); + } + } + } + + public static function getFiles($onlyActivated=false){ + + $enabled = $disabled = array(); + $files = glob(dirname(__FILE__). Plugin::FOLDER .'/*/*.plugin*.php'); + if(empty($files)) + $files = array(); + + foreach($files as $file){ + $plugin = Plugin::getObject($file); + if($plugin->getState()){ + $enabled [] = $file; + }else{ + $disabled [] = $file; + } + } + if(!$onlyActivated)$enabled = array_merge($enabled,$disabled); + return $enabled; + } + + + public static function loadState($plugin){ + $states = Plugin::getStates(); + return (isset($states[$plugin])?$states[$plugin]:false); + } + + public static function changeState($plugin,$state){ + $states = Plugin::getStates(); + $states[$plugin] = $state; + + Plugin::setStates($states); + } + + + public static function enabled($pluginUid){ + $plugins = Plugin::getAll(); + + foreach($plugins as $plugin){ + if($plugin->getUid()==$pluginUid){ + Plugin::changeState($plugin->getPath(),true); + $install = dirname($plugin->getPath()).'/install.php'; + if(file_exists($install))require_once($install); + } + } + } + public static function disabled($pluginUid){ + $plugins = Plugin::getAll(); + foreach($plugins as $plugin){ + if($plugin->getUid()==$pluginUid){ + Plugin::changeState($plugin->getPath(),false); + $uninstall = dirname($plugin->getPath()).'/uninstall.php'; + if(file_exists($uninstall))require_once($uninstall); + } + } + + } + + function getUid(){ + $pathInfo = explode('/',$this->getPath()); + $count = count($pathInfo); + $name = $pathInfo[$count-1]; + return $pathInfo[$count -2].'-'.substr($name,0,strpos($name,'.')); + } + + + static function sortPlugin($a, $b){ + if ($a->getName() == $b->getName()) + return 0; + return ($a->getName() < $b->getName()) ? -1 : 1; + } + + + + function getName(){ + return $this->name; + } + + function setName($name){ + $this->name = $name; + } + + function setAuthor($author){ + $this->author = $author; + } + + function getAuthor(){ + return $this->author; + } + + function getMail(){ + return $this->mail; + } + + function setMail($mail){ + $this->mail = $mail; + } + + function getLicence(){ + return $this->licence; + } + + function setLicence($licence){ + $this->licence = $licence; + } + + function getPath(){ + return $this->path; + } + + function setPath($path){ + $this->path = $path; + } + + function getDescription(){ + return $this->description; + } + + function setDescription($description){ + $this->description = $description; + } + + + function getLink(){ + return $this->link; + } + + function setLink($link){ + $this->link = $link; + } + + function getVersion(){ + return $this->version; + } + + function setVersion($version){ + $this->version = $version; + } + + function getState(){ + return $this->state; + } + function setState($state){ + $this->state = $state; + } + + function getType(){ + return $this->type; + } + + function setType($type){ + $this->type = $type; + } + +} + +?> diff --git a/sources/README.md b/sources/README.md new file mode 100755 index 0000000..ef13c9c --- /dev/null +++ b/sources/README.md @@ -0,0 +1,70 @@ +Leed +==== + +Leed (contraction de Light Feed) est un agrégateur [RSS](https://fr.wikipedia.org/wiki/Rss)/[ATOM](https://fr.wikipedia.org/wiki/Atom) minimaliste qui permet la consultation de flux RSS de manière rapide et non intrusive. + +Cet agrégateur peut s'installer sur votre propre serveur et fonctionne avec un système de tâches [cron](https://fr.wikipedia.org/wiki/Cron) afin de traiter les informations de manière transparente et de les afficher le plus rapidement possible lorsque vous vous y connectez. + +- Application : Leed (Light Feed) +- Version : Branche Stable +- Auteur : Valentin CARRUESCO aka Idleman (idleman@idleman.fr) +- Page du projet : http://projet.idleman.fr/leed +- Licence : [CC by-nc-sa](http://creativecommons.org/licenses/by-nc-sa/2.0/fr/) + +Toutes les tâches de traitements de flux sont effectuées de manière invisible par une tâche programmée (cron), ainsi, l'utilisateur ne subit pas les lenteurs dues à la récupération et au traitement de chacuns des flux suivis. + +A noter que Leed est compatible toutes résolutions, sur pc, tablette et smartphone. + +Leed est également compatible avec le format d'import/export [OPML](https://fr.wikipedia.org/wiki/OPML) ce qui le rend compatible avec les agrégateurs respectant ce standard. + +Pré-requis +==== + +- Serveur Apache conseillé (non testé sur les autres serveurs type Nginx…) +- PHP 5.3 minimum +- MySQL +- Un peu de bon sens :-) + +Installation +==== + +1. Récupérez le projet sur [idleman.fr](http://projet.idleman.fr/leed/?page=Téléchargement) ou sur la page [github](https://github.com/ldleman/Leed). +2. Placez le projet dans votre répertoire web et appliquez si nécessaire une permission _chmod 775_ (si vous êtes sur un hebergement ovh, préférez un _0755_ ou vous aurez une erreur 500) sur le dossier et son contenu. +3. Depuis votre navigateur, accédez à la page d'installation _install.php_ (ex : votre.domaine.fr/leed/install.php) et suivez les instructions. +4. Une fois l'installation terminée, supprimez le fichier _install.php_ par mesure de sécurité. +5. [Optionnel] Si vous souhaitez que les mises à jour de flux se fassent automatiquement, mettez en place un cron. Voir ci-après. Il est conseillé de ne pas mettre une fréquence trop rapide pour laisser le temps au script de s'exécuter. +6. Le script est installé, merci d'avoir choisi Leed, l'agrégateur RSS svelte :p + +Tâches programmées avec cron +==== + +On peut éditer les tâches programmées avec _crontab -e_. Il y a deux façons de mettre à jour les flux. Les exemples qui suivent mettent à jour toutes les heures. + +1. En appelant directement Leed. Cette méthode a l'avantage d'être directe et de produire une sortie formatée pour la console mais requiert un accès local : +``` crontab +0 * * * * cd (...)/leed && php action.php >> logs/cron.log 2>&1 +``` + +1. En appelant Leed depuis le client web _wget_. Cette méthode nécessite un accès réseau mais a l'avantage de pouvoir être déclenchée à distance. Afin de contrôler l'accès, il est nécessaire de fournir le code de synchronisation : +``` +0 * * * * wget --no-check-certificate --quiet --output-document /var/www/leed/cron.log +"http://127.0.0.1/leed/action.php?action=synchronize&code=votre_code_synchronisation" +``` + Si vous n'avez pas accès a la commande _wget_ sur votre serveur, vous pouvez essayer son chemin complet _/usr/bin/wget_. + +Foire Aux Questions (F.A.Q.) +==== + +Vous pouvez retrouver la FAQ du projet ici : http://projet.idleman.fr/leed/?page=FAQ + +Plugins +==== +Le dépot [Leed market](https://github.com/ldleman/Leed-market) contient tous les plugins à jour et approuvés officiellement pour le logiciel Leed. + +Bibliothèques utilisées +== + +- Responsive / Cross browser : Initializr (http://www.initializr.com) +- Javascript : JQuery (http://www.jquery.com) +- Moteur template : RainTPL (http://www.raintpl.com) +- Parseur RSS : SimplePie (http://simplepie.org) diff --git a/sources/RainTPL.php b/sources/RainTPL.php new file mode 100755 index 0000000..a66a65a --- /dev/null +++ b/sources/RainTPL.php @@ -0,0 +1,1071 @@ +), stylesheet (), script ("; + } + } + + // Flash Media Player file types. + // Preferred handler for MP3 file types. + elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== '')) + { + $height += 20; + if ($native) + { + $embed .= "get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\">"; + } + else + { + $embed .= ""; + } + } + + // QuickTime 7 file types. Need to test with QuickTime 6. + // Only handle MP3's if the Flash Media Player is not present. + elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === '')) + { + $height += 16; + if ($native) + { + if ($placeholder !== '') + { + $embed .= "get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\">"; + } + else + { + $embed .= "get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\">"; + } + } + else + { + $embed .= ""; + } + } + + // Windows Media + elseif ($handler === 'wmedia') + { + $height += 45; + if ($native) + { + $embed .= "get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\">"; + } + else + { + $embed .= ""; + } + } + + // Everything else + else $embed .= '' . $alt . ''; + + return $embed; + } + + /** + * Get the real media type + * + * Often, feeds lie to us, necessitating a bit of deeper inspection. This + * converts types to their canonical representations based on the file + * extension + * + * @see get_type() + * @param bool $find_handler Internal use only, use {@see get_handler()} instead + * @return string MIME type + */ + public function get_real_type($find_handler = false) + { + // Mime-types by handler. + $types_flash = array('application/x-shockwave-flash', 'application/futuresplash'); // Flash + $types_fmedia = array('video/flv', 'video/x-flv','flv-application/octet-stream'); // Flash Media Player + $types_quicktime = array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video'); // QuickTime + $types_wmedia = array('application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'); // Windows Media + $types_mp3 = array('audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg'); // MP3 + + if ($this->get_type() !== null) + { + $type = strtolower($this->type); + } + else + { + $type = null; + } + + // If we encounter an unsupported mime-type, check the file extension and guess intelligently. + if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3))) + { + switch (strtolower($this->get_extension())) + { + // Audio mime-types + case 'aac': + case 'adts': + $type = 'audio/acc'; + break; + + case 'aif': + case 'aifc': + case 'aiff': + case 'cdda': + $type = 'audio/aiff'; + break; + + case 'bwf': + $type = 'audio/wav'; + break; + + case 'kar': + case 'mid': + case 'midi': + case 'smf': + $type = 'audio/midi'; + break; + + case 'm4a': + $type = 'audio/x-m4a'; + break; + + case 'mp3': + case 'swa': + $type = 'audio/mp3'; + break; + + case 'wav': + $type = 'audio/wav'; + break; + + case 'wax': + $type = 'audio/x-ms-wax'; + break; + + case 'wma': + $type = 'audio/x-ms-wma'; + break; + + // Video mime-types + case '3gp': + case '3gpp': + $type = 'video/3gpp'; + break; + + case '3g2': + case '3gp2': + $type = 'video/3gpp2'; + break; + + case 'asf': + $type = 'video/x-ms-asf'; + break; + + case 'flv': + $type = 'video/x-flv'; + break; + + case 'm1a': + case 'm1s': + case 'm1v': + case 'm15': + case 'm75': + case 'mp2': + case 'mpa': + case 'mpeg': + case 'mpg': + case 'mpm': + case 'mpv': + $type = 'video/mpeg'; + break; + + case 'm4v': + $type = 'video/x-m4v'; + break; + + case 'mov': + case 'qt': + $type = 'video/quicktime'; + break; + + case 'mp4': + case 'mpg4': + $type = 'video/mp4'; + break; + + case 'sdv': + $type = 'video/sd-video'; + break; + + case 'wm': + $type = 'video/x-ms-wm'; + break; + + case 'wmv': + $type = 'video/x-ms-wmv'; + break; + + case 'wvx': + $type = 'video/x-ms-wvx'; + break; + + // Flash mime-types + case 'spl': + $type = 'application/futuresplash'; + break; + + case 'swf': + $type = 'application/x-shockwave-flash'; + break; + } + } + + if ($find_handler) + { + if (in_array($type, $types_flash)) + { + return 'flash'; + } + elseif (in_array($type, $types_fmedia)) + { + return 'fmedia'; + } + elseif (in_array($type, $types_quicktime)) + { + return 'quicktime'; + } + elseif (in_array($type, $types_wmedia)) + { + return 'wmedia'; + } + elseif (in_array($type, $types_mp3)) + { + return 'mp3'; + } + else + { + return null; + } + } + else + { + return $type; + } + } +} + +/** + * Class to validate and to work with IPv6 addresses. + * + * @package SimplePie + * @subpackage HTTP + * @copyright 2003-2005 The PHP Group + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/package/Net_IPv6 + * @author Alexander Merz + * @author elfrink at introweb dot nl + * @author Josh Peck + * @author Geoffrey Sneddon + */ +class SimplePie_Net_IPv6 +{ + /** + * Uncompresses an IPv6 address + * + * RFC 4291 allows you to compress concecutive zero pieces in an address to + * '::'. This method expects a valid IPv6 address and expands the '::' to + * the required number of zero pieces. + * + * Example: FF01::101 -> FF01:0:0:0:0:0:0:101 + * ::1 -> 0:0:0:0:0:0:0:1 + * + * @author Alexander Merz + * @author elfrink at introweb dot nl + * @author Josh Peck + * @copyright 2003-2005 The PHP Group + * @license http://www.opensource.org/licenses/bsd-license.php + * @param string $ip An IPv6 address + * @return string The uncompressed IPv6 address + */ + public static function uncompress($ip) + { + $c1 = -1; + $c2 = -1; + if (substr_count($ip, '::') === 1) + { + list($ip1, $ip2) = explode('::', $ip); + if ($ip1 === '') + { + $c1 = -1; + } + else + { + $c1 = substr_count($ip1, ':'); + } + if ($ip2 === '') + { + $c2 = -1; + } + else + { + $c2 = substr_count($ip2, ':'); + } + if (strpos($ip2, '.') !== false) + { + $c2++; + } + // :: + if ($c1 === -1 && $c2 === -1) + { + $ip = '0:0:0:0:0:0:0:0'; + } + // ::xxx + else if ($c1 === -1) + { + $fill = str_repeat('0:', 7 - $c2); + $ip = str_replace('::', $fill, $ip); + } + // xxx:: + else if ($c2 === -1) + { + $fill = str_repeat(':0', 7 - $c1); + $ip = str_replace('::', $fill, $ip); + } + // xxx::xxx + else + { + $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); + $ip = str_replace('::', $fill, $ip); + } + } + return $ip; + } + + /** + * Compresses an IPv6 address + * + * RFC 4291 allows you to compress concecutive zero pieces in an address to + * '::'. This method expects a valid IPv6 address and compresses consecutive + * zero pieces to '::'. + * + * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 + * 0:0:0:0:0:0:0:1 -> ::1 + * + * @see uncompress() + * @param string $ip An IPv6 address + * @return string The compressed IPv6 address + */ + public static function compress($ip) + { + // Prepare the IP to be compressed + $ip = self::uncompress($ip); + $ip_parts = self::split_v6_v4($ip); + + // Replace all leading zeros + $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); + + // Find bunches of zeros + if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) + { + $max = 0; + $pos = null; + foreach ($matches[0] as $match) + { + if (strlen($match[0]) > $max) + { + $max = strlen($match[0]); + $pos = $match[1]; + } + } + + $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); + } + + if ($ip_parts[1] !== '') + { + return implode(':', $ip_parts); + } + else + { + return $ip_parts[0]; + } + } + + /** + * Splits an IPv6 address into the IPv6 and IPv4 representation parts + * + * RFC 4291 allows you to represent the last two parts of an IPv6 address + * using the standard IPv4 representation + * + * Example: 0:0:0:0:0:0:13.1.68.3 + * 0:0:0:0:0:FFFF:129.144.52.38 + * + * @param string $ip An IPv6 address + * @return array [0] contains the IPv6 represented part, and [1] the IPv4 represented part + */ + private static function split_v6_v4($ip) + { + if (strpos($ip, '.') !== false) + { + $pos = strrpos($ip, ':'); + $ipv6_part = substr($ip, 0, $pos); + $ipv4_part = substr($ip, $pos + 1); + return array($ipv6_part, $ipv4_part); + } + else + { + return array($ip, ''); + } + } + + /** + * Checks an IPv6 address + * + * Checks if the given IP is a valid IPv6 address + * + * @param string $ip An IPv6 address + * @return bool true if $ip is a valid IPv6 address + */ + public static function check_ipv6($ip) + { + $ip = self::uncompress($ip); + list($ipv6, $ipv4) = self::split_v6_v4($ip); + $ipv6 = explode(':', $ipv6); + $ipv4 = explode('.', $ipv4); + if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) + { + foreach ($ipv6 as $ipv6_part) + { + // The section can't be empty + if ($ipv6_part === '') + return false; + + // Nor can it be over four characters + if (strlen($ipv6_part) > 4) + return false; + + // Remove leading zeros (this is safe because of the above) + $ipv6_part = ltrim($ipv6_part, '0'); + if ($ipv6_part === '') + $ipv6_part = '0'; + + // Check the value is valid + $value = hexdec($ipv6_part); + if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) + return false; + } + if (count($ipv4) === 4) + { + foreach ($ipv4 as $ipv4_part) + { + $value = (int) $ipv4_part; + if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) + return false; + } + } + return true; + } + else + { + return false; + } + } + + /** + * Checks if the given IP is a valid IPv6 address + * + * @codeCoverageIgnore + * @deprecated Use {@see SimplePie_Net_IPv6::check_ipv6()} instead + * @see check_ipv6 + * @param string $ip An IPv6 address + * @return bool true if $ip is a valid IPv6 address + */ + public static function checkIPv6($ip) + { + return self::check_ipv6($ip); + } +} + +/** + * Parses XML into something sane + * + * + * This class can be overloaded with {@see SimplePie::set_parser_class()} + * + * @package SimplePie + * @subpackage Parsing + */ +class SimplePie_Parser +{ + var $error_code; + var $error_string; + var $current_line; + var $current_column; + var $current_byte; + var $separator = ' '; + var $namespace = array(''); + var $element = array(''); + var $xml_base = array(''); + var $xml_base_explicit = array(false); + var $xml_lang = array(''); + var $data = array(); + var $datas = array(array()); + var $current_xhtml_construct = -1; + var $encoding; + protected $registry; + + public function set_registry(SimplePie_Registry $registry) + { + $this->registry = $registry; + } + + public function parse(&$data, $encoding) + { + $xmlEncoding = ''; //Update for Leed + if (!empty($encoding)) //Update for Leed (ajout de la condition. si vrai, on prend le standard SimplePie) + { + // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character + if (strtoupper($encoding) === 'US-ASCII') + { + $this->encoding = 'UTF-8'; + } + else + { + $this->encoding = $encoding; + } + + // Strip BOM: + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") + { + $data = substr($data, 4); + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") + { + $data = substr($data, 4); + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") + { + $data = substr($data, 2); + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") + { + $data = substr($data, 2); + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") + { + $data = substr($data, 3); + } + + if (substr($data, 0, 5) === '')) !== false) + { + $declaration = $this->registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5))); + if ($declaration->parse()) + { + $xmlEncoding = strtoupper($declaration->encoding); //Update for Leed + $data = substr($data, $pos + 2); + $data = 'version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; + } + else + { + $this->error_string = 'SimplePie bug! Please report this!'; + return false; + } + } + } + + if ($xmlEncoding === '' || $xmlEncoding === 'UTF-8') // Update for Leed by FreshRSS: case of no explicit HTTP encoding, and lax UTF-8 + { + try + { + $dom = new DOMDocument(); + $dom->recover = true; + $dom->strictErrorChecking = false; + ini_set('display_errors','0'); + $dom->loadXml($data); + ini_set('display_errors','1'); + $this->encoding = $encoding = $dom->encoding = 'UTF-8'; + $data2 = $dom->saveXML(); + if (function_exists('mb_convert_encoding')) + { + $data2 = mb_convert_encoding($data2, 'UTF-8', 'UTF-8'); + } + if (strlen($data2) > (strlen($data) / 2.0)) + { + $data = $data2; + } + unset($data2); + } + catch (Exception $e) + { + } + } // fin Update for Leed + + $return = true; + + static $xml_is_sane = null; + if ($xml_is_sane === null) + { + $parser_check = xml_parser_create(); + xml_parse_into_struct($parser_check, '&', $values); + xml_parser_free($parser_check); + $xml_is_sane = isset($values[0]['value']); + } + + // Create the parser + if ($xml_is_sane) + { + $xml = xml_parser_create_ns($this->encoding, $this->separator); + xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($xml, $this); + xml_set_character_data_handler($xml, 'cdata'); + xml_set_element_handler($xml, 'tag_open', 'tag_close'); + + // Parse! + if (!xml_parse($xml, $data, true)) + { + $this->error_code = xml_get_error_code($xml); + $this->error_string = xml_error_string($this->error_code); + $return = false; + } + $this->current_line = xml_get_current_line_number($xml); + $this->current_column = xml_get_current_column_number($xml); + $this->current_byte = xml_get_current_byte_index($xml); + xml_parser_free($xml); + return $return; + } + else + { + libxml_clear_errors(); + $xml = new XMLReader(); + $xml->xml($data); + while (@$xml->read()) + { + switch ($xml->nodeType) + { + + case constant('XMLReader::END_ELEMENT'): + if ($xml->namespaceURI !== '') + { + $tagName = $xml->namespaceURI . $this->separator . $xml->localName; + } + else + { + $tagName = $xml->localName; + } + $this->tag_close(null, $tagName); + break; + case constant('XMLReader::ELEMENT'): + $empty = $xml->isEmptyElement; + if ($xml->namespaceURI !== '') + { + $tagName = $xml->namespaceURI . $this->separator . $xml->localName; + } + else + { + $tagName = $xml->localName; + } + $attributes = array(); + while ($xml->moveToNextAttribute()) + { + if ($xml->namespaceURI !== '') + { + $attrName = $xml->namespaceURI . $this->separator . $xml->localName; + } + else + { + $attrName = $xml->localName; + } + $attributes[$attrName] = $xml->value; + } + $this->tag_open(null, $tagName, $attributes); + if ($empty) + { + $this->tag_close(null, $tagName); + } + break; + case constant('XMLReader::TEXT'): + + case constant('XMLReader::CDATA'): + $this->cdata(null, $xml->value); + break; + } + } + if ($error = libxml_get_last_error()) + { + $this->error_code = $error->code; + $this->error_string = $error->message; + $this->current_line = $error->line; + $this->current_column = $error->column; + return false; + } + else + { + return true; + } + } + } + + public function get_error_code() + { + return $this->error_code; + } + + public function get_error_string() + { + return $this->error_string; + } + + public function get_current_line() + { + return $this->current_line; + } + + public function get_current_column() + { + return $this->current_column; + } + + public function get_current_byte() + { + return $this->current_byte; + } + + public function get_data() + { + return $this->data; + } + + public function tag_open($parser, $tag, $attributes) + { + list($this->namespace[], $this->element[]) = $this->split_ns($tag); + + $attribs = array(); + foreach ($attributes as $name => $value) + { + list($attrib_namespace, $attribute) = $this->split_ns($name); + $attribs[$attrib_namespace][$attribute] = $value; + } + + if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['base'])) + { + $base = $this->registry->call('Misc', 'absolutize_url', array($attribs[SIMPLEPIE_NAMESPACE_XML]['base'], end($this->xml_base))); + if ($base !== false) + { + $this->xml_base[] = $base; + $this->xml_base_explicit[] = true; + } + } + else + { + $this->xml_base[] = end($this->xml_base); + $this->xml_base_explicit[] = end($this->xml_base_explicit); + } + + if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['lang'])) + { + $this->xml_lang[] = $attribs[SIMPLEPIE_NAMESPACE_XML]['lang']; + } + else + { + $this->xml_lang[] = end($this->xml_lang); + } + + if ($this->current_xhtml_construct >= 0) + { + $this->current_xhtml_construct++; + if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML) + { + $this->data['data'] .= '<' . end($this->element); + if (isset($attribs[''])) + { + foreach ($attribs[''] as $name => $value) + { + $this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"'; + } + } + $this->data['data'] .= '>'; + } + } + else + { + $this->datas[] =& $this->data; + $this->data =& $this->data['child'][end($this->namespace)][end($this->element)][]; + $this->data = array('data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang)); + if ((end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_03 && in_array(end($this->element), array('title', 'tagline', 'copyright', 'info', 'summary', 'content')) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml') + || (end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_10 && in_array(end($this->element), array('rights', 'subtitle', 'summary', 'info', 'title', 'content')) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml') + || (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_20 && in_array(end($this->element), array('title'))) + || (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_090 && in_array(end($this->element), array('title'))) + || (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_10 && in_array(end($this->element), array('title')))) + { + $this->current_xhtml_construct = 0; + } + } + } + + public function cdata($parser, $cdata) + { + if ($this->current_xhtml_construct >= 0) + { + $this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding); + } + else + { + $this->data['data'] .= $cdata; + } + } + + public function tag_close($parser, $tag) + { + if ($this->current_xhtml_construct >= 0) + { + $this->current_xhtml_construct--; + if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML && !in_array(end($this->element), array('area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'))) + { + $this->data['data'] .= 'element) . '>'; + } + } + if ($this->current_xhtml_construct === -1) + { + $this->data =& $this->datas[count($this->datas) - 1]; + array_pop($this->datas); + } + + array_pop($this->element); + array_pop($this->namespace); + array_pop($this->xml_base); + array_pop($this->xml_base_explicit); + array_pop($this->xml_lang); + } + + public function split_ns($string) + { + static $cache = array(); + if (!isset($cache[$string])) + { + if ($pos = strpos($string, $this->separator)) + { + static $separator_length; + if (!$separator_length) + { + $separator_length = strlen($this->separator); + } + $namespace = substr($string, 0, $pos); + $local_name = substr($string, $pos + $separator_length); + if (strtolower($namespace) === SIMPLEPIE_NAMESPACE_ITUNES) + { + $namespace = SIMPLEPIE_NAMESPACE_ITUNES; + } + + // Normalize the Media RSS namespaces + if ($namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG || + $namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG2 || + $namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG3 || + $namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG4 || + $namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG5 ) + { + $namespace = SIMPLEPIE_NAMESPACE_MEDIARSS; + } + $cache[$string] = array($namespace, $local_name); + } + else + { + $cache[$string] = array('', $string); + } + } + return $cache[$string]; + } +} + +/** + * IRI parser/serialiser/normaliser + * + * @package SimplePie + * @subpackage HTTP + * @author Geoffrey Sneddon + * @author Steve Minutillo + * @author Ryan McCue + * @copyright 2007-2012 Geoffrey Sneddon, Steve Minutillo, Ryan McCue + * @license http://www.opensource.org/licenses/bsd-license.php + */ +class SimplePie_IRI +{ + /** + * Scheme + * + * @var string + */ + protected $scheme = null; + + /** + * User Information + * + * @var string + */ + protected $iuserinfo = null; + + /** + * ihost + * + * @var string + */ + protected $ihost = null; + + /** + * Port + * + * @var string + */ + protected $port = null; + + /** + * ipath + * + * @var string + */ + protected $ipath = ''; + + /** + * iquery + * + * @var string + */ + protected $iquery = null; + + /** + * ifragment + * + * @var string + */ + protected $ifragment = null; + + /** + * Normalization database + * + * Each key is the scheme, each value is an array with each key as the IRI + * part and value as the default value for that part. + */ + protected $normalization = array( + 'acap' => array( + 'port' => 674 + ), + 'dict' => array( + 'port' => 2628 + ), + 'file' => array( + 'ihost' => 'localhost' + ), + 'http' => array( + 'port' => 80, + 'ipath' => '/' + ), + 'https' => array( + 'port' => 443, + 'ipath' => '/' + ), + ); + + /** + * Return the entire IRI when you try and read the object as a string + * + * @return string + */ + public function __toString() + { + return $this->get_iri(); + } + + /** + * Overload __set() to provide access via properties + * + * @param string $name Property name + * @param mixed $value Property value + */ + public function __set($name, $value) + { + if (method_exists($this, 'set_' . $name)) + { + call_user_func(array($this, 'set_' . $name), $value); + } + elseif ( + $name === 'iauthority' + || $name === 'iuserinfo' + || $name === 'ihost' + || $name === 'ipath' + || $name === 'iquery' + || $name === 'ifragment' + ) + { + call_user_func(array($this, 'set_' . substr($name, 1)), $value); + } + } + + /** + * Overload __get() to provide access via properties + * + * @param string $name Property name + * @return mixed + */ + public function __get($name) + { + // isset() returns false for null, we don't want to do that + // Also why we use array_key_exists below instead of isset() + $props = get_object_vars($this); + + if ( + $name === 'iri' || + $name === 'uri' || + $name === 'iauthority' || + $name === 'authority' + ) + { + $return = $this->{"get_$name"}(); + } + elseif (array_key_exists($name, $props)) + { + $return = $this->$name; + } + // host -> ihost + elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) + { + $name = $prop; + $return = $this->$prop; + } + // ischeme -> scheme + elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) + { + $name = $prop; + $return = $this->$prop; + } + else + { + trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); + $return = null; + } + + if ($return === null && isset($this->normalization[$this->scheme][$name])) + { + return $this->normalization[$this->scheme][$name]; + } + else + { + return $return; + } + } + + /** + * Overload __isset() to provide access via properties + * + * @param string $name Property name + * @return bool + */ + public function __isset($name) + { + if (method_exists($this, 'get_' . $name) || isset($this->$name)) + { + return true; + } + else + { + return false; + } + } + + /** + * Overload __unset() to provide access via properties + * + * @param string $name Property name + */ + public function __unset($name) + { + if (method_exists($this, 'set_' . $name)) + { + call_user_func(array($this, 'set_' . $name), ''); + } + } + + /** + * Create a new IRI object, from a specified string + * + * @param string $iri + */ + public function __construct($iri = null) + { + $this->set_iri($iri); + } + + /** + * Create a new IRI object by resolving a relative IRI + * + * Returns false if $base is not absolute, otherwise an IRI. + * + * @param IRI|string $base (Absolute) Base IRI + * @param IRI|string $relative Relative IRI + * @return IRI|false + */ + public static function absolutize($base, $relative) + { + if (!($relative instanceof SimplePie_IRI)) + { + $relative = new SimplePie_IRI($relative); + } + if (!$relative->is_valid()) + { + return false; + } + elseif ($relative->scheme !== null) + { + return clone $relative; + } + else + { + if (!($base instanceof SimplePie_IRI)) + { + $base = new SimplePie_IRI($base); + } + if ($base->scheme !== null && $base->is_valid()) + { + if ($relative->get_iri() !== '') + { + if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) + { + $target = clone $relative; + $target->scheme = $base->scheme; + } + else + { + $target = new SimplePie_IRI; + $target->scheme = $base->scheme; + $target->iuserinfo = $base->iuserinfo; + $target->ihost = $base->ihost; + $target->port = $base->port; + if ($relative->ipath !== '') + { + if ($relative->ipath[0] === '/') + { + $target->ipath = $relative->ipath; + } + elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') + { + $target->ipath = '/' . $relative->ipath; + } + elseif (($last_segment = strrpos($base->ipath, '/')) !== false) + { + $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; + } + else + { + $target->ipath = $relative->ipath; + } + $target->ipath = $target->remove_dot_segments($target->ipath); + $target->iquery = $relative->iquery; + } + else + { + $target->ipath = $base->ipath; + if ($relative->iquery !== null) + { + $target->iquery = $relative->iquery; + } + elseif ($base->iquery !== null) + { + $target->iquery = $base->iquery; + } + } + $target->ifragment = $relative->ifragment; + } + } + else + { + $target = clone $base; + $target->ifragment = null; + } + $target->scheme_normalization(); + return $target; + } + else + { + return false; + } + } + } + + /** + * Parse an IRI into scheme/authority/path/query/fragment segments + * + * @param string $iri + * @return array + */ + protected function parse_iri($iri) + { + $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); + if (preg_match('/^((?P[^:\/?#]+):)?(\/\/(?P[^\/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?$/', $iri, $match)) + { + if ($match[1] === '') + { + $match['scheme'] = null; + } + if (!isset($match[3]) || $match[3] === '') + { + $match['authority'] = null; + } + if (!isset($match[5])) + { + $match['path'] = ''; + } + if (!isset($match[6]) || $match[6] === '') + { + $match['query'] = null; + } + if (!isset($match[8]) || $match[8] === '') + { + $match['fragment'] = null; + } + return $match; + } + else + { + // This can occur when a paragraph is accidentally parsed as a URI + return false; + } + } + + /** + * Remove dot segments from a path + * + * @param string $input + * @return string + */ + protected function remove_dot_segments($input) + { + $output = ''; + while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') + { + // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, + if (strpos($input, '../') === 0) + { + $input = substr($input, 3); + } + elseif (strpos($input, './') === 0) + { + $input = substr($input, 2); + } + // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, + elseif (strpos($input, '/./') === 0) + { + $input = substr($input, 2); + } + elseif ($input === '/.') + { + $input = '/'; + } + // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, + elseif (strpos($input, '/../') === 0) + { + $input = substr($input, 3); + $output = substr_replace($output, '', strrpos($output, '/')); + } + elseif ($input === '/..') + { + $input = '/'; + $output = substr_replace($output, '', strrpos($output, '/')); + } + // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, + elseif ($input === '.' || $input === '..') + { + $input = ''; + } + // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer + elseif (($pos = strpos($input, '/', 1)) !== false) + { + $output .= substr($input, 0, $pos); + $input = substr_replace($input, '', 0, $pos); + } + else + { + $output .= $input; + $input = ''; + } + } + return $output . $input; + } + + /** + * Replace invalid character with percent encoding + * + * @param string $string Input string + * @param string $extra_chars Valid characters not in iunreserved or + * iprivate (this is ASCII-only) + * @param bool $iprivate Allow iprivate + * @return string + */ + protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) + { + // Normalize as many pct-encoded sections as possible + $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $string); + + // Replace invalid percent characters + $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); + + // Add unreserved and % to $extra_chars (the latter is safe because all + // pct-encoded sections are now valid). + $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; + + // Now replace any bytes that aren't allowed with their pct-encoded versions + $position = 0; + $strlen = strlen($string); + while (($position += strspn($string, $extra_chars, $position)) < $strlen) + { + $value = ord($string[$position]); + + // Start position + $start = $position; + + // By default we are valid + $valid = true; + + // No one byte sequences are valid due to the while. + // Two byte sequence: + if (($value & 0xE0) === 0xC0) + { + $character = ($value & 0x1F) << 6; + $length = 2; + $remaining = 1; + } + // Three byte sequence: + elseif (($value & 0xF0) === 0xE0) + { + $character = ($value & 0x0F) << 12; + $length = 3; + $remaining = 2; + } + // Four byte sequence: + elseif (($value & 0xF8) === 0xF0) + { + $character = ($value & 0x07) << 18; + $length = 4; + $remaining = 3; + } + // Invalid byte: + else + { + $valid = false; + $length = 1; + $remaining = 0; + } + + if ($remaining) + { + if ($position + $length <= $strlen) + { + for ($position++; $remaining; $position++) + { + $value = ord($string[$position]); + + // Check that the byte is valid, then add it to the character: + if (($value & 0xC0) === 0x80) + { + $character |= ($value & 0x3F) << (--$remaining * 6); + } + // If it is invalid, count the sequence as invalid and reprocess the current byte: + else + { + $valid = false; + $position--; + break; + } + } + } + else + { + $position = $strlen - 1; + $valid = false; + } + } + + // Percent encode anything invalid or not in ucschar + if ( + // Invalid sequences + !$valid + // Non-shortest form sequences are invalid + || $length > 1 && $character <= 0x7F + || $length > 2 && $character <= 0x7FF + || $length > 3 && $character <= 0xFFFF + // Outside of range of ucschar codepoints + // Noncharacters + || ($character & 0xFFFE) === 0xFFFE + || $character >= 0xFDD0 && $character <= 0xFDEF + || ( + // Everything else not in ucschar + $character > 0xD7FF && $character < 0xF900 + || $character < 0xA0 + || $character > 0xEFFFD + ) + && ( + // Everything not in iprivate, if it applies + !$iprivate + || $character < 0xE000 + || $character > 0x10FFFD + ) + ) + { + // If we were a character, pretend we weren't, but rather an error. + if ($valid) + $position--; + + for ($j = $start; $j <= $position; $j++) + { + $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); + $j += 2; + $position += 2; + $strlen += 2; + } + } + } + + return $string; + } + + /** + * Callback function for preg_replace_callback. + * + * Removes sequences of percent encoded bytes that represent UTF-8 + * encoded characters in iunreserved + * + * @param array $match PCRE match + * @return string Replacement + */ + protected function remove_iunreserved_percent_encoded($match) + { + // As we just have valid percent encoded sequences we can just explode + // and ignore the first member of the returned array (an empty string). + $bytes = explode('%', $match[0]); + + // Initialize the new string (this is what will be returned) and that + // there are no bytes remaining in the current sequence (unsurprising + // at the first byte!). + $string = ''; + $remaining = 0; + + // Loop over each and every byte, and set $value to its value + for ($i = 1, $len = count($bytes); $i < $len; $i++) + { + $value = hexdec($bytes[$i]); + + // If we're the first byte of sequence: + if (!$remaining) + { + // Start position + $start = $i; + + // By default we are valid + $valid = true; + + // One byte sequence: + if ($value <= 0x7F) + { + $character = $value; + $length = 1; + } + // Two byte sequence: + elseif (($value & 0xE0) === 0xC0) + { + $character = ($value & 0x1F) << 6; + $length = 2; + $remaining = 1; + } + // Three byte sequence: + elseif (($value & 0xF0) === 0xE0) + { + $character = ($value & 0x0F) << 12; + $length = 3; + $remaining = 2; + } + // Four byte sequence: + elseif (($value & 0xF8) === 0xF0) + { + $character = ($value & 0x07) << 18; + $length = 4; + $remaining = 3; + } + // Invalid byte: + else + { + $valid = false; + $remaining = 0; + } + } + // Continuation byte: + else + { + // Check that the byte is valid, then add it to the character: + if (($value & 0xC0) === 0x80) + { + $remaining--; + $character |= ($value & 0x3F) << ($remaining * 6); + } + // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: + else + { + $valid = false; + $remaining = 0; + $i--; + } + } + + // If we've reached the end of the current byte sequence, append it to Unicode::$data + if (!$remaining) + { + // Percent encode anything invalid or not in iunreserved + if ( + // Invalid sequences + !$valid + // Non-shortest form sequences are invalid + || $length > 1 && $character <= 0x7F + || $length > 2 && $character <= 0x7FF + || $length > 3 && $character <= 0xFFFF + // Outside of range of iunreserved codepoints + || $character < 0x2D + || $character > 0xEFFFD + // Noncharacters + || ($character & 0xFFFE) === 0xFFFE + || $character >= 0xFDD0 && $character <= 0xFDEF + // Everything else not in iunreserved (this is all BMP) + || $character === 0x2F + || $character > 0x39 && $character < 0x41 + || $character > 0x5A && $character < 0x61 + || $character > 0x7A && $character < 0x7E + || $character > 0x7E && $character < 0xA0 + || $character > 0xD7FF && $character < 0xF900 + ) + { + for ($j = $start; $j <= $i; $j++) + { + $string .= '%' . strtoupper($bytes[$j]); + } + } + else + { + for ($j = $start; $j <= $i; $j++) + { + $string .= chr(hexdec($bytes[$j])); + } + } + } + } + + // If we have any bytes left over they are invalid (i.e., we are + // mid-way through a multi-byte sequence) + if ($remaining) + { + for ($j = $start; $j < $len; $j++) + { + $string .= '%' . strtoupper($bytes[$j]); + } + } + + return $string; + } + + protected function scheme_normalization() + { + if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) + { + $this->iuserinfo = null; + } + if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) + { + $this->ihost = null; + } + if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) + { + $this->port = null; + } + if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) + { + $this->ipath = ''; + } + if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) + { + $this->iquery = null; + } + if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) + { + $this->ifragment = null; + } + } + + /** + * Check if the object represents a valid IRI. This needs to be done on each + * call as some things change depending on another part of the IRI. + * + * @return bool + */ + public function is_valid() + { + $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; + if ($this->ipath !== '' && + ( + $isauthority && ( + $this->ipath[0] !== '/' || + substr($this->ipath, 0, 2) === '//' + ) || + ( + $this->scheme === null && + !$isauthority && + strpos($this->ipath, ':') !== false && + (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) + ) + ) + ) + { + return false; + } + + return true; + } + + /** + * Set the entire IRI. Returns true on success, false on failure (if there + * are any invalid characters). + * + * @param string $iri + * @return bool + */ + public function set_iri($iri) + { + static $cache; + if (!$cache) + { + $cache = array(); + } + + if ($iri === null) + { + return true; + } + elseif (isset($cache[$iri])) + { + list($this->scheme, + $this->iuserinfo, + $this->ihost, + $this->port, + $this->ipath, + $this->iquery, + $this->ifragment, + $return) = $cache[$iri]; + return $return; + } + else + { + $parsed = $this->parse_iri((string) $iri); + if (!$parsed) + { + return false; + } + + $return = $this->set_scheme($parsed['scheme']) + && $this->set_authority($parsed['authority']) + && $this->set_path($parsed['path']) + && $this->set_query($parsed['query']) + && $this->set_fragment($parsed['fragment']); + + $cache[$iri] = array($this->scheme, + $this->iuserinfo, + $this->ihost, + $this->port, + $this->ipath, + $this->iquery, + $this->ifragment, + $return); + return $return; + } + } + + /** + * Set the scheme. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @param string $scheme + * @return bool + */ + public function set_scheme($scheme) + { + if ($scheme === null) + { + $this->scheme = null; + } + elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) + { + $this->scheme = null; + return false; + } + else + { + $this->scheme = strtolower($scheme); + } + return true; + } + + /** + * Set the authority. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @param string $authority + * @return bool + */ + public function set_authority($authority) + { + static $cache; + if (!$cache) + $cache = array(); + + if ($authority === null) + { + $this->iuserinfo = null; + $this->ihost = null; + $this->port = null; + return true; + } + elseif (isset($cache[$authority])) + { + list($this->iuserinfo, + $this->ihost, + $this->port, + $return) = $cache[$authority]; + + return $return; + } + else + { + $remaining = $authority; + if (($iuserinfo_end = strrpos($remaining, '@')) !== false) + { + $iuserinfo = substr($remaining, 0, $iuserinfo_end); + $remaining = substr($remaining, $iuserinfo_end + 1); + } + else + { + $iuserinfo = null; + } + if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) + { + if (($port = substr($remaining, $port_start + 1)) === false) + { + $port = null; + } + $remaining = substr($remaining, 0, $port_start); + } + else + { + $port = null; + } + + $return = $this->set_userinfo($iuserinfo) && + $this->set_host($remaining) && + $this->set_port($port); + + $cache[$authority] = array($this->iuserinfo, + $this->ihost, + $this->port, + $return); + + return $return; + } + } + + /** + * Set the iuserinfo. + * + * @param string $iuserinfo + * @return bool + */ + public function set_userinfo($iuserinfo) + { + if ($iuserinfo === null) + { + $this->iuserinfo = null; + } + else + { + $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); + $this->scheme_normalization(); + } + + return true; + } + + /** + * Set the ihost. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @param string $ihost + * @return bool + */ + public function set_host($ihost) + { + if ($ihost === null) + { + $this->ihost = null; + return true; + } + elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') + { + if (SimplePie_Net_IPv6::check_ipv6(substr($ihost, 1, -1))) + { + $this->ihost = '[' . SimplePie_Net_IPv6::compress(substr($ihost, 1, -1)) . ']'; + } + else + { + $this->ihost = null; + return false; + } + } + else + { + $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); + + // Lowercase, but ignore pct-encoded sections (as they should + // remain uppercase). This must be done after the previous step + // as that can add unescaped characters. + $position = 0; + $strlen = strlen($ihost); + while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) + { + if ($ihost[$position] === '%') + { + $position += 3; + } + else + { + $ihost[$position] = strtolower($ihost[$position]); + $position++; + } + } + + $this->ihost = $ihost; + } + + $this->scheme_normalization(); + + return true; + } + + /** + * Set the port. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @param string $port + * @return bool + */ + public function set_port($port) + { + if ($port === null) + { + $this->port = null; + return true; + } + elseif (strspn($port, '0123456789') === strlen($port)) + { + $this->port = (int) $port; + $this->scheme_normalization(); + return true; + } + else + { + $this->port = null; + return false; + } + } + + /** + * Set the ipath. + * + * @param string $ipath + * @return bool + */ + public function set_path($ipath) + { + static $cache; + if (!$cache) + { + $cache = array(); + } + + $ipath = (string) $ipath; + + if (isset($cache[$ipath])) + { + $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; + } + else + { + $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); + $removed = $this->remove_dot_segments($valid); + + $cache[$ipath] = array($valid, $removed); + $this->ipath = ($this->scheme !== null) ? $removed : $valid; + } + + $this->scheme_normalization(); + return true; + } + + /** + * Set the iquery. + * + * @param string $iquery + * @return bool + */ + public function set_query($iquery) + { + if ($iquery === null) + { + $this->iquery = null; + } + else + { + $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); + $this->scheme_normalization(); + } + return true; + } + + /** + * Set the ifragment. + * + * @param string $ifragment + * @return bool + */ + public function set_fragment($ifragment) + { + if ($ifragment === null) + { + $this->ifragment = null; + } + else + { + $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); + $this->scheme_normalization(); + } + return true; + } + + /** + * Convert an IRI to a URI (or parts thereof) + * + * @return string + */ + public function to_uri($string) + { + static $non_ascii; + if (!$non_ascii) + { + $non_ascii = implode('', range("\x80", "\xFF")); + } + + $position = 0; + $strlen = strlen($string); + while (($position += strcspn($string, $non_ascii, $position)) < $strlen) + { + $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1); + $position += 3; + $strlen += 2; + } + + return $string; + } + + /** + * Get the complete IRI + * + * @return string + */ + public function get_iri() + { + if (!$this->is_valid()) + { + return false; + } + + $iri = ''; + if ($this->scheme !== null) + { + $iri .= $this->scheme . ':'; + } + if (($iauthority = $this->get_iauthority()) !== null) + { + $iri .= '//' . $iauthority; + } + if ($this->ipath !== '') + { + $iri .= $this->ipath; + } + elseif (!empty($this->normalization[$this->scheme]['ipath']) && $iauthority !== null && $iauthority !== '') + { + $iri .= $this->normalization[$this->scheme]['ipath']; + } + if ($this->iquery !== null) + { + $iri .= '?' . $this->iquery; + } + if ($this->ifragment !== null) + { + $iri .= '#' . $this->ifragment; + } + + return $iri; + } + + /** + * Get the complete URI + * + * @return string + */ + public function get_uri() + { + return $this->to_uri($this->get_iri()); + } + + /** + * Get the complete iauthority + * + * @return string + */ + protected function get_iauthority() + { + if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null) + { + $iauthority = ''; + if ($this->iuserinfo !== null) + { + $iauthority .= $this->iuserinfo . '@'; + } + if ($this->ihost !== null) + { + $iauthority .= $this->ihost; + } + if ($this->port !== null) + { + $iauthority .= ':' . $this->port; + } + return $iauthority; + } + else + { + return null; + } + } + + /** + * Get the complete authority + * + * @return string + */ + protected function get_authority() + { + $iauthority = $this->get_iauthority(); + if (is_string($iauthority)) + return $this->to_uri($iauthority); + else + return $iauthority; + } +} + +/** + * Used for data cleanup and post-processing + * + * + * This class can be overloaded with {@see SimplePie::set_sanitize_class()} + * + * @package SimplePie + * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags + */ +class SimplePie_Sanitize +{ + // Private vars + var $base; + + // Options + var $remove_div = true; + var $image_handler = ''; + //var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); + var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'font', 'form', 'frame', 'frameset', 'html', 'input', 'marquee', 'meta', 'noscript', 'script', 'style'); // Update for Leed + var $encode_instead_of_strip = false; + var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); + var $strip_comments = false; + var $output_encoding = 'UTF-8'; + var $enable_cache = true; + var $cache_location = './cache'; + var $cache_name_function = 'md5'; + var $timeout = 10; + var $useragent = ''; + var $force_fsockopen = false; + var $replace_url_attributes = null; + + public function __construct() + { + // Set defaults + $this->set_url_replacements(null); + } + + public function remove_div($enable = true) + { + $this->remove_div = (bool) $enable; + } + + public function set_image_handler($page = false) + { + if ($page) + { + $this->image_handler = (string) $page; + } + else + { + $this->image_handler = false; + } + } + + public function set_registry(SimplePie_Registry $registry) + { + $this->registry = $registry; + } + + public function pass_cache_data($enable_cache = true, $cache_location = './cache', $cache_name_function = 'md5', $cache_class = 'SimplePie_Cache') + { + if (isset($enable_cache)) + { + $this->enable_cache = (bool) $enable_cache; + } + + if ($cache_location) + { + $this->cache_location = (string) $cache_location; + } + + if ($cache_name_function) + { + $this->cache_name_function = (string) $cache_name_function; + } + } + + public function pass_file_data($file_class = 'SimplePie_File', $timeout = 10, $useragent = '', $force_fsockopen = false) + { + if ($timeout) + { + $this->timeout = (string) $timeout; + } + + if ($useragent) + { + $this->useragent = (string) $useragent; + } + + if ($force_fsockopen) + { + $this->force_fsockopen = (string) $force_fsockopen; + } + } + + public function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style')) + { + if ($tags) + { + if (is_array($tags)) + { + $this->strip_htmltags = $tags; + } + else + { + $this->strip_htmltags = explode(',', $tags); + } + } + else + { + $this->strip_htmltags = false; + } + } + + public function encode_instead_of_strip($encode = false) + { + $this->encode_instead_of_strip = (bool) $encode; + } + + public function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc')) + { + if ($attribs) + { + if (is_array($attribs)) + { + $this->strip_attributes = $attribs; + } + else + { + $this->strip_attributes = explode(',', $attribs); + } + } + else + { + $this->strip_attributes = false; + } + } + + public function strip_comments($strip = false) + { + $this->strip_comments = (bool) $strip; + } + + public function set_output_encoding($encoding = 'UTF-8') + { + $this->output_encoding = (string) $encoding; + } + + /** + * Set element/attribute key/value pairs of HTML attributes + * containing URLs that need to be resolved relative to the feed + * + * Defaults to |a|@href, |area|@href, |blockquote|@cite, |del|@cite, + * |form|@action, |img|@longdesc, |img|@src, |input|@src, |ins|@cite, + * |q|@cite + * + * @since 1.0 + * @param array|null $element_attribute Element/attribute key/value pairs, null for default + */ + public function set_url_replacements($element_attribute = null) + { + if ($element_attribute === null) + { + $element_attribute = array( + 'a' => 'href', + 'area' => 'href', + 'blockquote' => 'cite', + 'del' => 'cite', + 'form' => 'action', + 'img' => array( + 'longdesc', + 'src' + ), + 'input' => 'src', + 'ins' => 'cite', + 'q' => 'cite' + ); + } + $this->replace_url_attributes = (array) $element_attribute; + } + + public function sanitize($data, $type, $base = '') + { + $data = trim($data); + if ($data !== '' || $type & SIMPLEPIE_CONSTRUCT_IRI) + { + if ($type & SIMPLEPIE_CONSTRUCT_MAYBE_HTML) + { + if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>)/', $data)) + { + $type |= SIMPLEPIE_CONSTRUCT_HTML; + } + else + { + $type |= SIMPLEPIE_CONSTRUCT_TEXT; + } + } + + if ($type & SIMPLEPIE_CONSTRUCT_BASE64) + { + $data = base64_decode($data); + } + + if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML)) + { + + if (!class_exists('DOMDocument')) + { + throw new SimplePie_Exception('DOMDocument not found, unable to use sanitizer'); + } + $document = new DOMDocument(); + $document->encoding = 'UTF-8'; + $data = $this->preprocess($data, $type); + + set_error_handler(array('SimplePie_Misc', 'silence_errors')); + $document->loadHTML($data); + restore_error_handler(); + + // Strip comments + if ($this->strip_comments) + { + $xpath = new DOMXPath($document); + $comments = $xpath->query('//comment()'); + + foreach ($comments as $comment) + { + $comment->parentNode->removeChild($comment); + } + } + + // Strip out HTML tags and attributes that might cause various security problems. + // Based on recommendations by Mark Pilgrim at: + // http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely + if ($this->strip_htmltags) + { + foreach ($this->strip_htmltags as $tag) + { + $this->strip_tag($tag, $document, $type); + } + } + + if ($this->strip_attributes) + { + foreach ($this->strip_attributes as $attrib) + { + $this->strip_attr($attrib, $document); + } + } + + // Replace relative URLs + $this->base = $base; + foreach ($this->replace_url_attributes as $element => $attributes) + { + $this->replace_urls($document, $element, $attributes); + } + + // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags. + if (isset($this->image_handler) && ((string) $this->image_handler) !== '' && $this->enable_cache) + { + $images = $document->getElementsByTagName('img'); + foreach ($images as $img) + { + if ($img->hasAttribute('src')) + { + $image_url = call_user_func($this->cache_name_function, $img->getAttribute('src')); + $cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, $image_url, 'spi')); + + if ($cache->load()) + { + $img->setAttribute('src', $this->image_handler . $image_url); + } + else + { + $file = $this->registry->create('File', array($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen)); + $headers = $file->headers; + + if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) + { + if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) + { + $img->setAttribute('src', $this->image_handler . $image_url); + } + else + { + trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); + } + } + } + } + } + } + + // Remove the DOCTYPE + // Seems to cause segfaulting if we don't do this + if ($document->firstChild instanceof DOMDocumentType) + { + $document->removeChild($document->firstChild); + } + + // Move everything from the body to the root + $real_body = $document->getElementsByTagName('body')->item(0)->childNodes->item(0); + $document->replaceChild($real_body, $document->firstChild); + + // Finally, convert to a HTML string + $data = trim($document->saveHTML()); + + if ($this->remove_div) + { + $data = preg_replace('/^/', '', $data); + $data = preg_replace('/<\/div>$/', '', $data); + } + else + { + $data = preg_replace('/^/', '
', $data); + } + } + + if ($type & SIMPLEPIE_CONSTRUCT_IRI) + { + $absolute = $this->registry->call('Misc', 'absolutize_url', array($data, $base)); + if ($absolute !== false) + { + $data = $absolute; + } + } + + if ($type & (SIMPLEPIE_CONSTRUCT_TEXT | SIMPLEPIE_CONSTRUCT_IRI)) + { + $data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8'); + } + + if ($this->output_encoding !== 'UTF-8') + { + $data = $this->registry->call('Misc', 'change_encoding', array($data, 'UTF-8', $this->output_encoding)); + } + } + return $data; + } + + protected function preprocess($html, $type) + { + $ret = ''; + if ($type & ~SIMPLEPIE_CONSTRUCT_XHTML) + { + // Atom XHTML constructs are wrapped with a div by default + // Note: No protection if $html contains a stray
! + $html = '
' . $html . '
'; + $ret .= ''; + $content_type = 'text/html'; + } + else + { + $ret .= ''; + $content_type = 'application/xhtml+xml'; + } + + $ret .= ''; + $ret .= ''; + $ret .= '' . $html . ''; + return $ret; + } + + public function replace_urls($document, $tag, $attributes) + { + if (!is_array($attributes)) + { + $attributes = array($attributes); + } + + if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) + { + $elements = $document->getElementsByTagName($tag); + foreach ($elements as $element) + { + foreach ($attributes as $attribute) + { + if ($element->hasAttribute($attribute)) + { + $value = $this->registry->call('Misc', 'absolutize_url', array($element->getAttribute($attribute), $this->base)); + if ($value !== false) + { + $element->setAttribute($attribute, $value); + } + } + } + } + } + } + + public function do_strip_htmltags($match) + { + if ($this->encode_instead_of_strip) + { + if (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style'))) + { + $match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8'); + $match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8'); + return "<$match[1]$match[2]>$match[3]</$match[1]>"; + } + else + { + return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8'); + } + } + elseif (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style'))) + { + return $match[4]; + } + else + { + return ''; + } + } + + protected function strip_tag($tag, $document, $type) + { + $xpath = new DOMXPath($document); + $elements = $xpath->query('body//' . $tag); + if ($this->encode_instead_of_strip) + { + foreach ($elements as $element) + { + $fragment = $document->createDocumentFragment(); + + // For elements which aren't script or style, include the tag itself + if (!in_array($tag, array('script', 'style'))) + { + $text = '<' . $tag; + if ($element->hasAttributes()) + { + $attrs = array(); + foreach ($element->attributes as $name => $attr) + { + $value = $attr->value; + + // In XHTML, empty values should never exist, so we repeat the value + if (empty($value) && ($type & SIMPLEPIE_CONSTRUCT_XHTML)) + { + $value = $name; + } + // For HTML, empty is fine + elseif (empty($value) && ($type & SIMPLEPIE_CONSTRUCT_HTML)) + { + $attrs[] = $name; + continue; + } + + // Standard attribute text + $attrs[] = $name . '="' . $attr->value . '"'; + } + $text .= ' ' . implode(' ', $attrs); + } + $text .= '>'; + $fragment->appendChild(new DOMText($text)); + } + + $number = $element->childNodes->length; + for ($i = $number; $i > 0; $i--) + { + $child = $element->childNodes->item(0); + $fragment->appendChild($child); + } + + if (!in_array($tag, array('script', 'style'))) + { + $fragment->appendChild(new DOMText('')); + } + + $element->parentNode->replaceChild($fragment, $element); + } + + return; + } + elseif (in_array($tag, array('script', 'style'))) + { + foreach ($elements as $element) + { + $element->parentNode->removeChild($element); + } + + return; + } + else + { + foreach ($elements as $element) + { + $fragment = $document->createDocumentFragment(); + $number = $element->childNodes->length; + for ($i = $number; $i > 0; $i--) + { + $child = $element->childNodes->item(0); + $fragment->appendChild($child); + } + + $element->parentNode->replaceChild($fragment, $element); + } + } + } + + protected function strip_attr($attrib, $document) + { + $xpath = new DOMXPath($document); + $elements = $xpath->query('//*[@' . $attrib . ']'); + + foreach ($elements as $element) + { + $element->removeAttribute($attrib); + } + } +} + +/** + * Used for fetching remote files and reading local files + * + * Supports HTTP 1.0 via cURL or fsockopen, with spotty HTTP 1.1 support + * + * This class can be overloaded with {@see SimplePie::set_file_class()} + * + * @package SimplePie + * @subpackage HTTP + * @todo Move to properly supporting RFC2616 (HTTP/1.1) + */ +class SimplePie_File +{ + var $url; + var $useragent; + var $success = true; + var $headers = array(); + var $body; + var $status_code; + var $redirects = 0; + var $error; + var $method = SIMPLEPIE_FILE_SOURCE_NONE; + + public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) + { + if (class_exists('idna_convert')) + { + $idn = new idna_convert(); + $parsed = SimplePie_Misc::parse_url($url); + $url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); + } + $this->url = $url; + $this->useragent = $useragent; + if (preg_match('/^http(s)?:\/\//i', $url)) + { + if ($useragent === null) + { + $useragent = ini_get('user_agent'); + $this->useragent = $useragent; + } + if (!is_array($headers)) + { + $headers = array(); + } + if (!$force_fsockopen && function_exists('curl_exec')) + { + $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL; + $fp = curl_init(); + $headers2 = array(); + foreach ($headers as $key => $value) + { + $headers2[] = "$key: $value"; + } + if (version_compare(SimplePie_Misc::get_curl_version(), '7.10.5', '>=')) + { + curl_setopt($fp, CURLOPT_ENCODING, ''); + } + curl_setopt($fp, CURLOPT_URL, $url); + curl_setopt($fp, CURLOPT_HEADER, 1); + curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($fp, CURLOPT_TIMEOUT, $timeout); + curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout); + curl_setopt($fp, CURLOPT_REFERER, $url); + curl_setopt($fp, CURLOPT_USERAGENT, $useragent); + curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); + curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false); //Update for Leed - gestion HTTPS + if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>=')) + { + curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects); + } + + $this->headers = curl_exec($fp); + if (curl_errno($fp) === 23 || curl_errno($fp) === 61) + { + curl_setopt($fp, CURLOPT_ENCODING, 'none'); + $this->headers = curl_exec($fp); + } + if (curl_errno($fp)) + { + $this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); + $this->success = false; + } + else + { + $info = curl_getinfo($fp); + curl_close($fp); + $this->headers = explode("\r\n\r\n", $this->headers, $info['redirect_count'] + 1); + $this->headers = array_pop($this->headers); + $parser = new SimplePie_HTTP_Parser($this->headers); + if ($parser->parse()) + { + $this->headers = $parser->headers; + $this->body = $parser->body; + $this->status_code = $parser->status_code; + if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) + { + $this->redirects++; + $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); + return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + } + } + } + } + else + { + $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN; + $url_parts = parse_url($url); + $socket_host = $url_parts['host']; + if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') + { + $socket_host = "ssl://$url_parts[host]"; + $url_parts['port'] = 443; + } + if (!isset($url_parts['port'])) + { + $url_parts['port'] = 80; + } + $fp = @fsockopen($socket_host, $url_parts['port'], $errno, $errstr, $timeout); + if (!$fp) + { + $this->error = 'fsockopen error: ' . $errstr; + $this->success = false; + } + else + { + stream_set_timeout($fp, $timeout); + if (isset($url_parts['path'])) + { + if (isset($url_parts['query'])) + { + $get = "$url_parts[path]?$url_parts[query]"; + } + else + { + $get = $url_parts['path']; + } + } + else + { + $get = '/'; + } + $out = "GET $get HTTP/1.1\r\n"; + $out .= "Host: $url_parts[host]\r\n"; + $out .= "User-Agent: $useragent\r\n"; + if (extension_loaded('zlib')) + { + $out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n"; + } + + if (isset($url_parts['user']) && isset($url_parts['pass'])) + { + $out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n"; + } + foreach ($headers as $key => $value) + { + $out .= "$key: $value\r\n"; + } + $out .= "Connection: Close\r\n\r\n"; + fwrite($fp, $out); + + $info = stream_get_meta_data($fp); + + $this->headers = ''; + while (!$info['eof'] && !$info['timed_out']) + { + $this->headers .= fread($fp, 1160); + $info = stream_get_meta_data($fp); + } + if (!$info['timed_out']) + { + $parser = new SimplePie_HTTP_Parser($this->headers); + if ($parser->parse()) + { + $this->headers = $parser->headers; + $this->body = $parser->body; + $this->status_code = $parser->status_code; + if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) + { + $this->redirects++; + $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); + return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + } + if (isset($this->headers['content-encoding'])) + { + // Hey, we act dumb elsewhere, so let's do that here too + switch (strtolower(trim($this->headers['content-encoding'], "\x09\x0A\x0D\x20"))) + { + case 'gzip': + case 'x-gzip': + $decoder = new SimplePie_gzdecode($this->body); + if (!$decoder->parse()) + { + $this->error = 'Unable to decode HTTP "gzip" stream'; + $this->success = false; + } + else + { + $this->body = $decoder->data; + } + break; + + case 'deflate': + if (($decompressed = gzinflate($this->body)) !== false) + { + $this->body = $decompressed; + } + else if (($decompressed = gzuncompress($this->body)) !== false) + { + $this->body = $decompressed; + } + else if (function_exists('gzdecode') && ($decompressed = gzdecode($this->body)) !== false) + { + $this->body = $decompressed; + } + else + { + $this->error = 'Unable to decode HTTP "deflate" stream'; + $this->success = false; + } + break; + + default: + $this->error = 'Unknown content coding'; + $this->success = false; + } + } + } + } + else + { + $this->error = 'fsocket timed out'; + $this->success = false; + } + fclose($fp); + } + } + } + else + { + $this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS; + if (empty($url) || !($this->body = file_get_contents($url))) + { + $this->error = 'file_get_contents could not read the file'; + $this->success = false; + } + } + } +} + +/** + * Handles `` captions as defined in Media RSS. + * + * Used by {@see SimplePie_Enclosure::get_caption()} and {@see SimplePie_Enclosure::get_captions()} + * + * This class can be overloaded with {@see SimplePie::set_caption_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Caption +{ + /** + * Content type + * + * @var string + * @see get_type() + */ + var $type; + + /** + * Language + * + * @var string + * @see get_language() + */ + var $lang; + + /** + * Start time + * + * @var string + * @see get_starttime() + */ + var $startTime; + + /** + * End time + * + * @var string + * @see get_endtime() + */ + var $endTime; + + /** + * Caption text + * + * @var string + * @see get_text() + */ + var $text; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct($type = null, $lang = null, $startTime = null, $endTime = null, $text = null) + { + $this->type = $type; + $this->lang = $lang; + $this->startTime = $startTime; + $this->endTime = $endTime; + $this->text = $text; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the end time + * + * @return string|null Time in the format 'hh:mm:ss.SSS' + */ + public function get_endtime() + { + if ($this->endTime !== null) + { + return $this->endTime; + } + else + { + return null; + } + } + + /** + * Get the language + * + * @link http://tools.ietf.org/html/rfc3066 + * @return string|null Language code as per RFC 3066 + */ + public function get_language() + { + if ($this->lang !== null) + { + return $this->lang; + } + else + { + return null; + } + } + + /** + * Get the start time + * + * @return string|null Time in the format 'hh:mm:ss.SSS' + */ + public function get_starttime() + { + if ($this->startTime !== null) + { + return $this->startTime; + } + else + { + return null; + } + } + + /** + * Get the text of the caption + * + * @return string|null + */ + public function get_text() + { + if ($this->text !== null) + { + return $this->text; + } + else + { + return null; + } + } + + /** + * Get the content type (not MIME type) + * + * @return string|null Either 'text' or 'html' + */ + public function get_type() + { + if ($this->type !== null) + { + return $this->type; + } + else + { + return null; + } + } +} + +/** + * Decode 'gzip' encoded HTTP data + * + * @package SimplePie + * @subpackage HTTP + * @link http://www.gzip.org/format.txt + */ +class SimplePie_gzdecode +{ + /** + * Compressed data + * + * @access private + * @var string + * @see gzdecode::$data + */ + var $compressed_data; + + /** + * Size of compressed data + * + * @access private + * @var int + */ + var $compressed_size; + + /** + * Minimum size of a valid gzip string + * + * @access private + * @var int + */ + var $min_compressed_size = 18; + + /** + * Current position of pointer + * + * @access private + * @var int + */ + var $position = 0; + + /** + * Flags (FLG) + * + * @access private + * @var int + */ + var $flags; + + /** + * Uncompressed data + * + * @access public + * @see gzdecode::$compressed_data + * @var string + */ + var $data; + + /** + * Modified time + * + * @access public + * @var int + */ + var $MTIME; + + /** + * Extra Flags + * + * @access public + * @var int + */ + var $XFL; + + /** + * Operating System + * + * @access public + * @var int + */ + var $OS; + + /** + * Subfield ID 1 + * + * @access public + * @see gzdecode::$extra_field + * @see gzdecode::$SI2 + * @var string + */ + var $SI1; + + /** + * Subfield ID 2 + * + * @access public + * @see gzdecode::$extra_field + * @see gzdecode::$SI1 + * @var string + */ + var $SI2; + + /** + * Extra field content + * + * @access public + * @see gzdecode::$SI1 + * @see gzdecode::$SI2 + * @var string + */ + var $extra_field; + + /** + * Original filename + * + * @access public + * @var string + */ + var $filename; + + /** + * Human readable comment + * + * @access public + * @var string + */ + var $comment; + + /** + * Don't allow anything to be set + * + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + trigger_error("Cannot write property $name", E_USER_ERROR); + } + + /** + * Set the compressed string and related properties + * + * @param string $data + */ + public function __construct($data) + { + $this->compressed_data = $data; + $this->compressed_size = strlen($data); + } + + /** + * Decode the GZIP stream + * + * @return bool Successfulness + */ + public function parse() + { + if ($this->compressed_size >= $this->min_compressed_size) + { + // Check ID1, ID2, and CM + if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08") + { + return false; + } + + // Get the FLG (FLaGs) + $this->flags = ord($this->compressed_data[3]); + + // FLG bits above (1 << 4) are reserved + if ($this->flags > 0x1F) + { + return false; + } + + // Advance the pointer after the above + $this->position += 4; + + // MTIME + $mtime = substr($this->compressed_data, $this->position, 4); + // Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness + if (current(unpack('S', "\x00\x01")) === 1) + { + $mtime = strrev($mtime); + } + $this->MTIME = current(unpack('l', $mtime)); + $this->position += 4; + + // Get the XFL (eXtra FLags) + $this->XFL = ord($this->compressed_data[$this->position++]); + + // Get the OS (Operating System) + $this->OS = ord($this->compressed_data[$this->position++]); + + // Parse the FEXTRA + if ($this->flags & 4) + { + // Read subfield IDs + $this->SI1 = $this->compressed_data[$this->position++]; + $this->SI2 = $this->compressed_data[$this->position++]; + + // SI2 set to zero is reserved for future use + if ($this->SI2 === "\x00") + { + return false; + } + + // Get the length of the extra field + $len = current(unpack('v', substr($this->compressed_data, $this->position, 2))); + $this->position += 2; + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 4; + if ($this->compressed_size >= $this->min_compressed_size) + { + // Set the extra field to the given data + $this->extra_field = substr($this->compressed_data, $this->position, $len); + $this->position += $len; + } + else + { + return false; + } + } + + // Parse the FNAME + if ($this->flags & 8) + { + // Get the length of the filename + $len = strcspn($this->compressed_data, "\x00", $this->position); + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 1; + if ($this->compressed_size >= $this->min_compressed_size) + { + // Set the original filename to the given string + $this->filename = substr($this->compressed_data, $this->position, $len); + $this->position += $len + 1; + } + else + { + return false; + } + } + + // Parse the FCOMMENT + if ($this->flags & 16) + { + // Get the length of the comment + $len = strcspn($this->compressed_data, "\x00", $this->position); + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 1; + if ($this->compressed_size >= $this->min_compressed_size) + { + // Set the original comment to the given string + $this->comment = substr($this->compressed_data, $this->position, $len); + $this->position += $len + 1; + } + else + { + return false; + } + } + + // Parse the FHCRC + if ($this->flags & 2) + { + // Check the length of the string is still valid + $this->min_compressed_size += $len + 2; + if ($this->compressed_size >= $this->min_compressed_size) + { + // Read the CRC + $crc = current(unpack('v', substr($this->compressed_data, $this->position, 2))); + + // Check the CRC matches + if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc) + { + $this->position += 2; + } + else + { + return false; + } + } + else + { + return false; + } + } + + // Decompress the actual data + if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) + { + return false; + } + else + { + $this->position = $this->compressed_size - 8; + } + + // Check CRC of data + $crc = current(unpack('V', substr($this->compressed_data, $this->position, 4))); + $this->position += 4; + /*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc)) + { + return false; + }*/ + + // Check ISIZE of data + $isize = current(unpack('V', substr($this->compressed_data, $this->position, 4))); + $this->position += 4; + if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize)) + { + return false; + } + + // Wow, against all odds, we've actually got a valid gzip string + return true; + } + else + { + return false; + } + } +} + +/** + * Handles `` or `` tags as defined in Media RSS and iTunes RSS respectively + * + * Used by {@see SimplePie_Enclosure::get_rating()} and {@see SimplePie_Enclosure::get_ratings()} + * + * This class can be overloaded with {@see SimplePie::set_rating_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Rating +{ + /** + * Rating scheme + * + * @var string + * @see get_scheme() + */ + var $scheme; + + /** + * Rating value + * + * @var string + * @see get_value() + */ + var $value; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct($scheme = null, $value = null) + { + $this->scheme = $scheme; + $this->value = $value; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the organizational scheme for the rating + * + * @return string|null + */ + public function get_scheme() + { + if ($this->scheme !== null) + { + return $this->scheme; + } + else + { + return null; + } + } + + /** + * Get the value of the rating + * + * @return string|null + */ + public function get_value() + { + if ($this->value !== null) + { + return $this->value; + } + else + { + return null; + } + } +} + +/** + * Manages all author-related data + * + * Used by {@see SimplePie_Item::get_author()} and {@see SimplePie::get_authors()} + * + * This class can be overloaded with {@see SimplePie::set_author_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Author +{ + /** + * Author's name + * + * @var string + * @see get_name() + */ + var $name; + + /** + * Author's link + * + * @var string + * @see get_link() + */ + var $link; + + /** + * Author's email address + * + * @var string + * @see get_email() + */ + var $email; + + /** + * Constructor, used to input the data + * + * @param string $name + * @param string $link + * @param string $email + */ + public function __construct($name = null, $link = null, $email = null) + { + $this->name = $name; + $this->link = $link; + $this->email = $email; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Author's name + * + * @return string|null + */ + public function get_name() + { + if ($this->name !== null) + { + return $this->name; + } + else + { + return null; + } + } + + /** + * Author's link + * + * @return string|null + */ + public function get_link() + { + if ($this->link !== null) + { + return $this->link; + } + else + { + return null; + } + } + + /** + * Author's email address + * + * @return string|null + */ + public function get_email() + { + if ($this->email !== null) + { + return $this->email; + } + else + { + return null; + } + } +} + +/** + * SimplePie class. + * + * Class for backward compatibility. + * + * @deprecated Use {@see SimplePie} directly + * @package SimplePie + * @subpackage API + */ +class SimplePie_Core extends SimplePie +{ + +} + +/** + * Used to create cache objects + * + * This class can be overloaded with {@see SimplePie::set_cache_class()}, + * although the preferred way is to create your own handler + * via {@see register()} + * + * @package SimplePie + * @subpackage Caching + */ +class SimplePie_Cache +{ + /** + * Cache handler classes + * + * These receive 3 parameters to their constructor, as documented in + * {@see register()} + * @var array + */ + protected static $handlers = array( + 'mysql' => 'SimplePie_Cache_MySQL', + 'memcache' => 'SimplePie_Cache_Memcache', + ); + + /** + * Don't call the constructor. Please. + */ + private function __construct() { } + + /** + * Create a new SimplePie_Cache object + * + * @param string $location URL location (scheme is used to determine handler) + * @param string $filename Unique identifier for cache object + * @param string $extension 'spi' or 'spc' + * @return SimplePie_Cache_Base Type of object depends on scheme of `$location` + */ + public static function get_handler($location, $filename, $extension) + { + $type = explode(':', $location, 2); + $type = $type[0]; + if (!empty(self::$handlers[$type])) + { + $class = self::$handlers[$type]; + return new $class($location, $filename, $extension); + } + + return new SimplePie_Cache_File($location, $filename, $extension); + } + + /** + * Create a new SimplePie_Cache object + * + * @deprecated Use {@see get_handler} instead + */ + public function create($location, $filename, $extension) + { + trigger_error('Cache::create() has been replaced with Cache::get_handler(). Switch to the registry system to use this.', E_USER_DEPRECATED); + return self::get_handler($location, $filename, $extension); + } + + /** + * Register a handler + * + * @param string $type DSN type to register for + * @param string $class Name of handler class. Must implement SimplePie_Cache_Base + */ + public static function register($type, $class) + { + self::$handlers[$type] = $class; + } + + /** + * Parse a URL into an array + * + * @param string $url + * @return array + */ + public static function parse_URL($url) + { + $params = parse_url($url); + $params['extras'] = array(); + if (isset($params['query'])) + { + parse_str($params['query'], $params['extras']); + } + return $params; + } +} + +/** + * Manages `` copyright tags as defined in Media RSS + * + * Used by {@see SimplePie_Enclosure::get_copyright()} + * + * This class can be overloaded with {@see SimplePie::set_copyright_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Copyright +{ + /** + * Copyright URL + * + * @var string + * @see get_url() + */ + var $url; + + /** + * Attribution + * + * @var string + * @see get_attribution() + */ + var $label; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct($url = null, $label = null) + { + $this->url = $url; + $this->label = $label; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the copyright URL + * + * @return string|null URL to copyright information + */ + public function get_url() + { + if ($this->url !== null) + { + return $this->url; + } + else + { + return null; + } + } + + /** + * Get the attribution text + * + * @return string|null + */ + public function get_attribution() + { + if ($this->label !== null) + { + return $this->label; + } + else + { + return null; + } + } +} + +/** + * Handles `` as defined in Media RSS + * + * Used by {@see SimplePie_Enclosure::get_restriction()} and {@see SimplePie_Enclosure::get_restrictions()} + * + * This class can be overloaded with {@see SimplePie::set_restriction_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Restriction +{ + /** + * Relationship ('allow'/'deny') + * + * @var string + * @see get_relationship() + */ + var $relationship; + + /** + * Type of restriction + * + * @var string + * @see get_type() + */ + var $type; + + /** + * Restricted values + * + * @var string + * @see get_value() + */ + var $value; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct($relationship = null, $type = null, $value = null) + { + $this->relationship = $relationship; + $this->type = $type; + $this->value = $value; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the relationship + * + * @return string|null Either 'allow' or 'deny' + */ + public function get_relationship() + { + if ($this->relationship !== null) + { + return $this->relationship; + } + else + { + return null; + } + } + + /** + * Get the type + * + * @return string|null + */ + public function get_type() + { + if ($this->type !== null) + { + return $this->type; + } + else + { + return null; + } + } + + /** + * Get the list of restricted things + * + * @return string|null + */ + public function get_value() + { + if ($this->value !== null) + { + return $this->value; + } + else + { + return null; + } + } +} + +/** + * Handles creating objects and calling methods + * + * Access this via {@see SimplePie::get_registry()} + * + * @package SimplePie + */ +class SimplePie_Registry +{ + /** + * Default class mapping + * + * Overriding classes *must* subclass these. + * + * @var array + */ + protected $default = array( + 'Cache' => 'SimplePie_Cache', + 'Locator' => 'SimplePie_Locator', + 'Parser' => 'SimplePie_Parser', + 'File' => 'SimplePie_File', + 'Sanitize' => 'SimplePie_Sanitize', + 'Item' => 'SimplePie_Item', + 'Author' => 'SimplePie_Author', + 'Category' => 'SimplePie_Category', + 'Enclosure' => 'SimplePie_Enclosure', + 'Caption' => 'SimplePie_Caption', + 'Copyright' => 'SimplePie_Copyright', + 'Credit' => 'SimplePie_Credit', + 'Rating' => 'SimplePie_Rating', + 'Restriction' => 'SimplePie_Restriction', + 'Content_Type_Sniffer' => 'SimplePie_Content_Type_Sniffer', + 'Source' => 'SimplePie_Source', + 'Misc' => 'SimplePie_Misc', + 'XML_Declaration_Parser' => 'SimplePie_XML_Declaration_Parser', + 'Parse_Date' => 'SimplePie_Parse_Date', + ); + + /** + * Class mapping + * + * @see register() + * @var array + */ + protected $classes = array(); + + /** + * Legacy classes + * + * @see register() + * @var array + */ + protected $legacy = array(); + + /** + * Constructor + * + * No-op + */ + public function __construct() { } + + /** + * Register a class + * + * @param string $type See {@see $default} for names + * @param string $class Class name, must subclass the corresponding default + * @param bool $legacy Whether to enable legacy support for this class + * @return bool Successfulness + */ + public function register($type, $class, $legacy = false) + { + if (!is_subclass_of($class, $this->default[$type])) + { + return false; + } + + $this->classes[$type] = $class; + + if ($legacy) + { + $this->legacy[] = $class; + } + + return true; + } + + /** + * Get the class registered for a type + * + * Where possible, use {@see create()} or {@see call()} instead + * + * @param string $type + * @return string|null + */ + public function get_class($type) + { + if (!empty($this->classes[$type])) + { + return $this->classes[$type]; + } + if (!empty($this->default[$type])) + { + return $this->default[$type]; + } + + return null; + } + + /** + * Create a new instance of a given type + * + * @param string $type + * @param array $parameters Parameters to pass to the constructor + * @return object Instance of class + */ + public function &create($type, $parameters = array()) + { + $class = $this->get_class($type); + + if (in_array($class, $this->legacy)) + { + switch ($type) + { + case 'locator': + // Legacy: file, timeout, useragent, file_class, max_checked_feeds, content_type_sniffer_class + // Specified: file, timeout, useragent, max_checked_feeds + $replacement = array($this->get_class('file'), $parameters[3], $this->get_class('content_type_sniffer')); + array_splice($parameters, 3, 1, $replacement); + break; + } + } + + if (!method_exists($class, '__construct')) + { + $instance = new $class; + } + else + { + $reflector = new ReflectionClass($class); + $instance = $reflector->newInstanceArgs($parameters); + } + + if (method_exists($instance, 'set_registry')) + { + $instance->set_registry($this); + } + return $instance; + } + + /** + * Call a static method for a type + * + * @param string $type + * @param string $method + * @param array $parameters + * @return mixed + */ + public function &call($type, $method, $parameters = array()) + { + $class = $this->get_class($type); + + if (in_array($class, $this->legacy)) + { + switch ($type) + { + case 'Cache': + // For backwards compatibility with old non-static + // Cache::create() methods + if ($method === 'get_handler') + { + $result = @call_user_func_array(array($class, 'create'), $parameters); + return $result; + } + break; + } + } + + $result = call_user_func_array(array($class, $method), $parameters); + return $result; + } +} + +/** + * Date Parser + * + * @package SimplePie + * @subpackage Parsing + */ +class SimplePie_Parse_Date +{ + /** + * Input data + * + * @access protected + * @var string + */ + var $date; + + /** + * List of days, calendar day name => ordinal day number in the week + * + * @access protected + * @var array + */ + var $day = array( + // English + 'mon' => 1, + 'monday' => 1, + 'tue' => 2, + 'tuesday' => 2, + 'wed' => 3, + 'wednesday' => 3, + 'thu' => 4, + 'thursday' => 4, + 'fri' => 5, + 'friday' => 5, + 'sat' => 6, + 'saturday' => 6, + 'sun' => 7, + 'sunday' => 7, + // Dutch + 'maandag' => 1, + 'dinsdag' => 2, + 'woensdag' => 3, + 'donderdag' => 4, + 'vrijdag' => 5, + 'zaterdag' => 6, + 'zondag' => 7, + // French + 'lundi' => 1, + 'mardi' => 2, + 'mercredi' => 3, + 'jeudi' => 4, + 'vendredi' => 5, + 'samedi' => 6, + 'dimanche' => 7, + // German + 'montag' => 1, + 'dienstag' => 2, + 'mittwoch' => 3, + 'donnerstag' => 4, + 'freitag' => 5, + 'samstag' => 6, + 'sonnabend' => 6, + 'sonntag' => 7, + // Italian + 'lunedì' => 1, + 'martedì' => 2, + 'mercoledì' => 3, + 'giovedì' => 4, + 'venerdì' => 5, + 'sabato' => 6, + 'domenica' => 7, + // Spanish + 'lunes' => 1, + 'martes' => 2, + 'miércoles' => 3, + 'jueves' => 4, + 'viernes' => 5, + 'sábado' => 6, + 'domingo' => 7, + // Finnish + 'maanantai' => 1, + 'tiistai' => 2, + 'keskiviikko' => 3, + 'torstai' => 4, + 'perjantai' => 5, + 'lauantai' => 6, + 'sunnuntai' => 7, + // Hungarian + 'hétfő' => 1, + 'kedd' => 2, + 'szerda' => 3, + 'csütörtok' => 4, + 'péntek' => 5, + 'szombat' => 6, + 'vasárnap' => 7, + // Greek + 'Δευ' => 1, + 'Τρι' => 2, + 'Τετ' => 3, + 'Πεμ' => 4, + 'Παρ' => 5, + 'Σαβ' => 6, + 'Κυρ' => 7, + ); + + /** + * List of months, calendar month name => calendar month number + * + * @access protected + * @var array + */ + var $month = array( + // English + 'jan' => 1, + 'january' => 1, + 'feb' => 2, + 'february' => 2, + 'mar' => 3, + 'march' => 3, + 'apr' => 4, + 'april' => 4, + 'may' => 5, + // No long form of May + 'jun' => 6, + 'june' => 6, + 'jul' => 7, + 'july' => 7, + 'aug' => 8, + 'august' => 8, + 'sep' => 9, + 'september' => 8, + 'oct' => 10, + 'october' => 10, + 'nov' => 11, + 'november' => 11, + 'dec' => 12, + 'december' => 12, + // Dutch + 'januari' => 1, + 'februari' => 2, + 'maart' => 3, + 'april' => 4, + 'mei' => 5, + 'juni' => 6, + 'juli' => 7, + 'augustus' => 8, + 'september' => 9, + 'oktober' => 10, + 'november' => 11, + 'december' => 12, + // French + 'janvier' => 1, + 'février' => 2, + 'mars' => 3, + 'avril' => 4, + 'mai' => 5, + 'juin' => 6, + 'juillet' => 7, + 'août' => 8, + 'septembre' => 9, + 'octobre' => 10, + 'novembre' => 11, + 'décembre' => 12, + // German + 'januar' => 1, + 'februar' => 2, + 'märz' => 3, + 'april' => 4, + 'mai' => 5, + 'juni' => 6, + 'juli' => 7, + 'august' => 8, + 'september' => 9, + 'oktober' => 10, + 'november' => 11, + 'dezember' => 12, + // Italian + 'gennaio' => 1, + 'febbraio' => 2, + 'marzo' => 3, + 'aprile' => 4, + 'maggio' => 5, + 'giugno' => 6, + 'luglio' => 7, + 'agosto' => 8, + 'settembre' => 9, + 'ottobre' => 10, + 'novembre' => 11, + 'dicembre' => 12, + // Spanish + 'enero' => 1, + 'febrero' => 2, + 'marzo' => 3, + 'abril' => 4, + 'mayo' => 5, + 'junio' => 6, + 'julio' => 7, + 'agosto' => 8, + 'septiembre' => 9, + 'setiembre' => 9, + 'octubre' => 10, + 'noviembre' => 11, + 'diciembre' => 12, + // Finnish + 'tammikuu' => 1, + 'helmikuu' => 2, + 'maaliskuu' => 3, + 'huhtikuu' => 4, + 'toukokuu' => 5, + 'kesäkuu' => 6, + 'heinäkuu' => 7, + 'elokuu' => 8, + 'suuskuu' => 9, + 'lokakuu' => 10, + 'marras' => 11, + 'joulukuu' => 12, + // Hungarian + 'január' => 1, + 'február' => 2, + 'március' => 3, + 'április' => 4, + 'május' => 5, + 'június' => 6, + 'július' => 7, + 'augusztus' => 8, + 'szeptember' => 9, + 'október' => 10, + 'november' => 11, + 'december' => 12, + // Greek + 'Ιαν' => 1, + 'Φεβ' => 2, + 'Μάώ' => 3, + 'Μαώ' => 3, + 'Απρ' => 4, + 'Μάι' => 5, + 'Μαϊ' => 5, + 'Μαι' => 5, + 'Ιούν' => 6, + 'Ιον' => 6, + 'Ιούλ' => 7, + 'Ιολ' => 7, + 'Αύγ' => 8, + 'Αυγ' => 8, + 'Σεπ' => 9, + 'Οκτ' => 10, + 'Νοέ' => 11, + 'Δεκ' => 12, + ); + + /** + * List of timezones, abbreviation => offset from UTC + * + * @access protected + * @var array + */ + var $timezone = array( + 'ACDT' => 37800, + 'ACIT' => 28800, + 'ACST' => 34200, + 'ACT' => -18000, + 'ACWDT' => 35100, + 'ACWST' => 31500, + 'AEDT' => 39600, + 'AEST' => 36000, + 'AFT' => 16200, + 'AKDT' => -28800, + 'AKST' => -32400, + 'AMDT' => 18000, + 'AMT' => -14400, + 'ANAST' => 46800, + 'ANAT' => 43200, + 'ART' => -10800, + 'AZOST' => -3600, + 'AZST' => 18000, + 'AZT' => 14400, + 'BIOT' => 21600, + 'BIT' => -43200, + 'BOT' => -14400, + 'BRST' => -7200, + 'BRT' => -10800, + 'BST' => 3600, + 'BTT' => 21600, + 'CAST' => 18000, + 'CAT' => 7200, + 'CCT' => 23400, + 'CDT' => -18000, + 'CEDT' => 7200, + 'CET' => 3600, + 'CGST' => -7200, + 'CGT' => -10800, + 'CHADT' => 49500, + 'CHAST' => 45900, + 'CIST' => -28800, + 'CKT' => -36000, + 'CLDT' => -10800, + 'CLST' => -14400, + 'COT' => -18000, + 'CST' => -21600, + 'CVT' => -3600, + 'CXT' => 25200, + 'DAVT' => 25200, + 'DTAT' => 36000, + 'EADT' => -18000, + 'EAST' => -21600, + 'EAT' => 10800, + 'ECT' => -18000, + 'EDT' => -14400, + 'EEST' => 10800, + 'EET' => 7200, + 'EGT' => -3600, + 'EKST' => 21600, + 'EST' => -18000, + 'FJT' => 43200, + 'FKDT' => -10800, + 'FKST' => -14400, + 'FNT' => -7200, + 'GALT' => -21600, + 'GEDT' => 14400, + 'GEST' => 10800, + 'GFT' => -10800, + 'GILT' => 43200, + 'GIT' => -32400, + 'GST' => 14400, + 'GST' => -7200, + 'GYT' => -14400, + 'HAA' => -10800, + 'HAC' => -18000, + 'HADT' => -32400, + 'HAE' => -14400, + 'HAP' => -25200, + 'HAR' => -21600, + 'HAST' => -36000, + 'HAT' => -9000, + 'HAY' => -28800, + 'HKST' => 28800, + 'HMT' => 18000, + 'HNA' => -14400, + 'HNC' => -21600, + 'HNE' => -18000, + 'HNP' => -28800, + 'HNR' => -25200, + 'HNT' => -12600, + 'HNY' => -32400, + 'IRDT' => 16200, + 'IRKST' => 32400, + 'IRKT' => 28800, + 'IRST' => 12600, + 'JFDT' => -10800, + 'JFST' => -14400, + 'JST' => 32400, + 'KGST' => 21600, + 'KGT' => 18000, + 'KOST' => 39600, + 'KOVST' => 28800, + 'KOVT' => 25200, + 'KRAST' => 28800, + 'KRAT' => 25200, + 'KST' => 32400, + 'LHDT' => 39600, + 'LHST' => 37800, + 'LINT' => 50400, + 'LKT' => 21600, + 'MAGST' => 43200, + 'MAGT' => 39600, + 'MAWT' => 21600, + 'MDT' => -21600, + 'MESZ' => 7200, + 'MEZ' => 3600, + 'MHT' => 43200, + 'MIT' => -34200, + 'MNST' => 32400, + 'MSDT' => 14400, + 'MSST' => 10800, + 'MST' => -25200, + 'MUT' => 14400, + 'MVT' => 18000, + 'MYT' => 28800, + 'NCT' => 39600, + 'NDT' => -9000, + 'NFT' => 41400, + 'NMIT' => 36000, + 'NOVST' => 25200, + 'NOVT' => 21600, + 'NPT' => 20700, + 'NRT' => 43200, + 'NST' => -12600, + 'NUT' => -39600, + 'NZDT' => 46800, + 'NZST' => 43200, + 'OMSST' => 25200, + 'OMST' => 21600, + 'PDT' => -25200, + 'PET' => -18000, + 'PETST' => 46800, + 'PETT' => 43200, + 'PGT' => 36000, + 'PHOT' => 46800, + 'PHT' => 28800, + 'PKT' => 18000, + 'PMDT' => -7200, + 'PMST' => -10800, + 'PONT' => 39600, + 'PST' => -28800, + 'PWT' => 32400, + 'PYST' => -10800, + 'PYT' => -14400, + 'RET' => 14400, + 'ROTT' => -10800, + 'SAMST' => 18000, + 'SAMT' => 14400, + 'SAST' => 7200, + 'SBT' => 39600, + 'SCDT' => 46800, + 'SCST' => 43200, + 'SCT' => 14400, + 'SEST' => 3600, + 'SGT' => 28800, + 'SIT' => 28800, + 'SRT' => -10800, + 'SST' => -39600, + 'SYST' => 10800, + 'SYT' => 7200, + 'TFT' => 18000, + 'THAT' => -36000, + 'TJT' => 18000, + 'TKT' => -36000, + 'TMT' => 18000, + 'TOT' => 46800, + 'TPT' => 32400, + 'TRUT' => 36000, + 'TVT' => 43200, + 'TWT' => 28800, + 'UYST' => -7200, + 'UYT' => -10800, + 'UZT' => 18000, + 'VET' => -14400, + 'VLAST' => 39600, + 'VLAT' => 36000, + 'VOST' => 21600, + 'VUT' => 39600, + 'WAST' => 7200, + 'WAT' => 3600, + 'WDT' => 32400, + 'WEST' => 3600, + 'WFT' => 43200, + 'WIB' => 25200, + 'WIT' => 32400, + 'WITA' => 28800, + 'WKST' => 18000, + 'WST' => 28800, + 'YAKST' => 36000, + 'YAKT' => 32400, + 'YAPT' => 36000, + 'YEKST' => 21600, + 'YEKT' => 18000, + ); + + /** + * Cached PCRE for SimplePie_Parse_Date::$day + * + * @access protected + * @var string + */ + var $day_pcre; + + /** + * Cached PCRE for SimplePie_Parse_Date::$month + * + * @access protected + * @var string + */ + var $month_pcre; + + /** + * Array of user-added callback methods + * + * @access private + * @var array + */ + var $built_in = array(); + + /** + * Array of user-added callback methods + * + * @access private + * @var array + */ + var $user = array(); + + /** + * Create new SimplePie_Parse_Date object, and set self::day_pcre, + * self::month_pcre, and self::built_in + * + * @access private + */ + public function __construct() + { + $this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')'; + $this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')'; + + static $cache; + if (!isset($cache[get_class($this)])) + { + $all_methods = get_class_methods($this); + + foreach ($all_methods as $method) + { + if (strtolower(substr($method, 0, 5)) === 'date_') + { + $cache[get_class($this)][] = $method; + } + } + } + + foreach ($cache[get_class($this)] as $method) + { + $this->built_in[] = $method; + } + } + + /** + * Get the object + * + * @access public + */ + public static function get() + { + static $object; + if (!$object) + { + $object = new SimplePie_Parse_Date; + } + return $object; + } + + /** + * Parse a date + * + * @final + * @access public + * @param string $date Date to parse + * @return int Timestamp corresponding to date string, or false on failure + */ + public function parse($date) + { + foreach ($this->user as $method) + { + if (($returned = call_user_func($method, $date)) !== false) + { + return $returned; + } + } + + foreach ($this->built_in as $method) + { + if (($returned = call_user_func(array($this, $method), $date)) !== false) + { + return $returned; + } + } + + return false; + } + + /** + * Add a callback method to parse a date + * + * @final + * @access public + * @param callback $callback + */ + public function add_callback($callback) + { + if (is_callable($callback)) + { + $this->user[] = $callback; + } + else + { + trigger_error('User-supplied function must be a valid callback', E_USER_WARNING); + } + } + + /** + * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as + * well as allowing any of upper or lower case "T", horizontal tabs, or + * spaces to be used as the time seperator (including more than one)) + * + * @access protected + * @return int Timestamp + */ + public function date_w3cdtf($date) + { + static $pcre; + if (!$pcre) + { + $year = '([0-9]{4})'; + $month = $day = $hour = $minute = $second = '([0-9]{2})'; + $decimal = '([0-9]*)'; + $zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))'; + $pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/'; + } + if (preg_match($pcre, $date, $match)) + { + /* + Capturing subpatterns: + 1: Year + 2: Month + 3: Day + 4: Hour + 5: Minute + 6: Second + 7: Decimal fraction of a second + 8: Zulu + 9: Timezone ± + 10: Timezone hours + 11: Timezone minutes + */ + + // Fill in empty matches + for ($i = count($match); $i <= 3; $i++) + { + $match[$i] = '1'; + } + + for ($i = count($match); $i <= 7; $i++) + { + $match[$i] = '0'; + } + + // Numeric timezone + if (isset($match[9]) && $match[9] !== '') + { + $timezone = $match[10] * 3600; + $timezone += $match[11] * 60; + if ($match[9] === '-') + { + $timezone = 0 - $timezone; + } + } + else + { + $timezone = 0; + } + + // Convert the number of seconds to an integer, taking decimals into account + $second = round($match[6] + $match[7] / pow(10, strlen($match[7]))); + + return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone; + } + else + { + return false; + } + } + + /** + * Remove RFC822 comments + * + * @access protected + * @param string $data Data to strip comments from + * @return string Comment stripped string + */ + public function remove_rfc2822_comments($string) + { + $string = (string) $string; + $position = 0; + $length = strlen($string); + $depth = 0; + + $output = ''; + + while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) + { + $output .= substr($string, $position, $pos - $position); + $position = $pos + 1; + if ($string[$pos - 1] !== '\\') + { + $depth++; + while ($depth && $position < $length) + { + $position += strcspn($string, '()', $position); + if ($string[$position - 1] === '\\') + { + $position++; + continue; + } + elseif (isset($string[$position])) + { + switch ($string[$position]) + { + case '(': + $depth++; + break; + + case ')': + $depth--; + break; + } + $position++; + } + else + { + break; + } + } + } + else + { + $output .= '('; + } + } + $output .= substr($string, $position); + + return $output; + } + + /** + * Parse RFC2822's date format + * + * @access protected + * @return int Timestamp + */ + public function date_rfc2822($date) + { + static $pcre; + if (!$pcre) + { + $wsp = '[\x09\x20]'; + $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)'; + $optional_fws = $fws . '?'; + $day_name = $this->day_pcre; + $month = $this->month_pcre; + $day = '([0-9]{1,2})'; + $hour = $minute = $second = '([0-9]{2})'; + $year = '([0-9]{2,4})'; + $num_zone = '([+\-])([0-9]{2})([0-9]{2})'; + $character_zone = '([A-Z]{1,5})'; + $zone = '(?:' . $num_zone . '|' . $character_zone . ')'; + $pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i'; + } + if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match)) + { + /* + Capturing subpatterns: + 1: Day name + 2: Day + 3: Month + 4: Year + 5: Hour + 6: Minute + 7: Second + 8: Timezone ± + 9: Timezone hours + 10: Timezone minutes + 11: Alphabetic timezone + */ + + // Find the month number + $month = $this->month[strtolower($match[3])]; + + // Numeric timezone + if ($match[8] !== '') + { + $timezone = $match[9] * 3600; + $timezone += $match[10] * 60; + if ($match[8] === '-') + { + $timezone = 0 - $timezone; + } + } + // Character timezone + elseif (isset($this->timezone[strtoupper($match[11])])) + { + $timezone = $this->timezone[strtoupper($match[11])]; + } + // Assume everything else to be -0000 + else + { + $timezone = 0; + } + + // Deal with 2/3 digit years + if ($match[4] < 50) + { + $match[4] += 2000; + } + elseif ($match[4] < 1000) + { + $match[4] += 1900; + } + + // Second is optional, if it is empty set it to zero + if ($match[7] !== '') + { + $second = $match[7]; + } + else + { + $second = 0; + } + + return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone; + } + else + { + return false; + } + } + + /** + * Parse RFC850's date format + * + * @access protected + * @return int Timestamp + */ + public function date_rfc850($date) + { + static $pcre; + if (!$pcre) + { + $space = '[\x09\x20]+'; + $day_name = $this->day_pcre; + $month = $this->month_pcre; + $day = '([0-9]{1,2})'; + $year = $hour = $minute = $second = '([0-9]{2})'; + $zone = '([A-Z]{1,5})'; + $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i'; + } + if (preg_match($pcre, $date, $match)) + { + /* + Capturing subpatterns: + 1: Day name + 2: Day + 3: Month + 4: Year + 5: Hour + 6: Minute + 7: Second + 8: Timezone + */ + + // Month + $month = $this->month[strtolower($match[3])]; + + // Character timezone + if (isset($this->timezone[strtoupper($match[8])])) + { + $timezone = $this->timezone[strtoupper($match[8])]; + } + // Assume everything else to be -0000 + else + { + $timezone = 0; + } + + // Deal with 2 digit year + if ($match[4] < 50) + { + $match[4] += 2000; + } + else + { + $match[4] += 1900; + } + + return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone; + } + else + { + return false; + } + } + + /** + * Parse C99's asctime()'s date format + * + * @access protected + * @return int Timestamp + */ + public function date_asctime($date) + { + static $pcre; + if (!$pcre) + { + $space = '[\x09\x20]+'; + $wday_name = $this->day_pcre; + $mon_name = $this->month_pcre; + $day = '([0-9]{1,2})'; + $hour = $sec = $min = '([0-9]{2})'; + $year = '([0-9]{4})'; + $terminator = '\x0A?\x00?'; + $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i'; + } + if (preg_match($pcre, $date, $match)) + { + /* + Capturing subpatterns: + 1: Day name + 2: Month + 3: Day + 4: Hour + 5: Minute + 6: Second + 7: Year + */ + + $month = $this->month[strtolower($match[2])]; + return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]); + } + else + { + return false; + } + } + + /** + * Parse dates using strtotime() + * + * @access protected + * @return int Timestamp + */ + public function date_strtotime($date) + { + $strtotime = strtotime($date); + if ($strtotime === -1 || $strtotime === false) + { + return false; + } + else + { + return $strtotime; + } + } +} + +/** + * Content-type sniffing + * + * Based on the rules in http://tools.ietf.org/html/draft-abarth-mime-sniff-06 + * + * This is used since we can't always trust Content-Type headers, and is based + * upon the HTML5 parsing rules. + * + * + * This class can be overloaded with {@see SimplePie::set_content_type_sniffer_class()} + * + * @package SimplePie + * @subpackage HTTP + */ +class SimplePie_Content_Type_Sniffer +{ + /** + * File object + * + * @var SimplePie_File + */ + var $file; + + /** + * Create an instance of the class with the input file + * + * @param SimplePie_Content_Type_Sniffer $file Input file + */ + public function __construct($file) + { + $this->file = $file; + } + + /** + * Get the Content-Type of the specified file + * + * @return string Actual Content-Type + */ + public function get_type() + { + if (isset($this->file->headers['content-type'])) + { + if (!isset($this->file->headers['content-encoding']) + && ($this->file->headers['content-type'] === 'text/plain' + || $this->file->headers['content-type'] === 'text/plain; charset=ISO-8859-1' + || $this->file->headers['content-type'] === 'text/plain; charset=iso-8859-1' + || $this->file->headers['content-type'] === 'text/plain; charset=UTF-8')) + { + return $this->text_or_binary(); + } + + if (($pos = strpos($this->file->headers['content-type'], ';')) !== false) + { + $official = substr($this->file->headers['content-type'], 0, $pos); + } + else + { + $official = $this->file->headers['content-type']; + } + $official = trim(strtolower($official)); + + if ($official === 'unknown/unknown' + || $official === 'application/unknown') + { + return $this->unknown(); + } + elseif (substr($official, -4) === '+xml' + || $official === 'text/xml' + || $official === 'application/xml') + { + return $official; + } + elseif (substr($official, 0, 6) === 'image/') + { + if ($return = $this->image()) + { + return $return; + } + else + { + return $official; + } + } + elseif ($official === 'text/html') + { + return $this->feed_or_html(); + } + else + { + return $official; + } + } + else + { + return $this->unknown(); + } + } + + /** + * Sniff text or binary + * + * @return string Actual Content-Type + */ + public function text_or_binary() + { + if (substr($this->file->body, 0, 2) === "\xFE\xFF" + || substr($this->file->body, 0, 2) === "\xFF\xFE" + || substr($this->file->body, 0, 4) === "\x00\x00\xFE\xFF" + || substr($this->file->body, 0, 3) === "\xEF\xBB\xBF") + { + return 'text/plain'; + } + elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $this->file->body)) + { + return 'application/octect-stream'; + } + else + { + return 'text/plain'; + } + } + + /** + * Sniff unknown + * + * @return string Actual Content-Type + */ + public function unknown() + { + $ws = strspn($this->file->body, "\x09\x0A\x0B\x0C\x0D\x20"); + if (strtolower(substr($this->file->body, $ws, 14)) === 'file->body, $ws, 5)) === 'file->body, $ws, 7)) === 'file->body, 0, 5) === '%PDF-') + { + return 'application/pdf'; + } + elseif (substr($this->file->body, 0, 11) === '%!PS-Adobe-') + { + return 'application/postscript'; + } + elseif (substr($this->file->body, 0, 6) === 'GIF87a' + || substr($this->file->body, 0, 6) === 'GIF89a') + { + return 'image/gif'; + } + elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") + { + return 'image/png'; + } + elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF") + { + return 'image/jpeg'; + } + elseif (substr($this->file->body, 0, 2) === "\x42\x4D") + { + return 'image/bmp'; + } + elseif (substr($this->file->body, 0, 4) === "\x00\x00\x01\x00") + { + return 'image/vnd.microsoft.icon'; + } + else + { + return $this->text_or_binary(); + } + } + + /** + * Sniff images + * + * @return string Actual Content-Type + */ + public function image() + { + if (substr($this->file->body, 0, 6) === 'GIF87a' + || substr($this->file->body, 0, 6) === 'GIF89a') + { + return 'image/gif'; + } + elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") + { + return 'image/png'; + } + elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF") + { + return 'image/jpeg'; + } + elseif (substr($this->file->body, 0, 2) === "\x42\x4D") + { + return 'image/bmp'; + } + elseif (substr($this->file->body, 0, 4) === "\x00\x00\x01\x00") + { + return 'image/vnd.microsoft.icon'; + } + else + { + return false; + } + } + + /** + * Sniff HTML + * + * @return string Actual Content-Type + */ + public function feed_or_html() + { + $len = strlen($this->file->body); + $pos = strspn($this->file->body, "\x09\x0A\x0D\x20"); + + while ($pos < $len) + { + switch ($this->file->body[$pos]) + { + case "\x09": + case "\x0A": + case "\x0D": + case "\x20": + $pos += strspn($this->file->body, "\x09\x0A\x0D\x20", $pos); + continue 2; + + case '<': + $pos++; + break; + + default: + return 'text/html'; + } + + if (substr($this->file->body, $pos, 3) === '!--') + { + $pos += 3; + if ($pos < $len && ($pos = strpos($this->file->body, '-->', $pos)) !== false) + { + $pos += 3; + } + else + { + return 'text/html'; + } + } + elseif (substr($this->file->body, $pos, 1) === '!') + { + if ($pos < $len && ($pos = strpos($this->file->body, '>', $pos)) !== false) + { + $pos++; + } + else + { + return 'text/html'; + } + } + elseif (substr($this->file->body, $pos, 1) === '?') + { + if ($pos < $len && ($pos = strpos($this->file->body, '?>', $pos)) !== false) + { + $pos += 2; + } + else + { + return 'text/html'; + } + } + elseif (substr($this->file->body, $pos, 3) === 'rss' + || substr($this->file->body, $pos, 7) === 'rdf:RDF') + { + return 'application/rss+xml'; + } + elseif (substr($this->file->body, $pos, 4) === 'feed') + { + return 'application/atom+xml'; + } + else + { + return 'text/html'; + } + } + + return 'text/html'; + } +} + +/** + * Caches data to the filesystem + * + * @package SimplePie + * @subpackage Caching + */ +class SimplePie_Cache_File implements SimplePie_Cache_Base +{ + /** + * Location string + * + * @see SimplePie::$cache_location + * @var string + */ + protected $location; + + /** + * Filename + * + * @var string + */ + protected $filename; + + /** + * File extension + * + * @var string + */ + protected $extension; + + /** + * File path + * + * @var string + */ + protected $name; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct($location, $name, $type) + { + $this->location = $location; + $this->filename = $name; + $this->extension = $type; + $this->name = "$this->location/$this->filename.$this->extension"; + } + + /** + * Save data to the cache + * + * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data) + { + if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location)) + { + if ($data instanceof SimplePie) + { + $data = $data->data; + } + + $data = serialize($data); + return (bool) file_put_contents($this->name, $data); + } + return false; + } + + /** + * Retrieve the data saved to the cache + * + * @return array Data for SimplePie::$data + */ + public function load() + { + if (file_exists($this->name) && is_readable($this->name)) + { + return unserialize(file_get_contents($this->name)); + } + return false; + } + + /** + * Retrieve the last modified time for the cache + * + * @return int Timestamp + */ + public function mtime() + { + if (file_exists($this->name)) + { + return filemtime($this->name); + } + return false; + } + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch() + { + if (file_exists($this->name)) + { + return touch($this->name); + } + return false; + } + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink() + { + if (file_exists($this->name)) + { + return unlink($this->name); + } + return false; + } +} +/** + * Base class for database-based caches + * + * @package SimplePie + * @subpackage Caching + */ + +abstract class SimplePie_Cache_DB implements SimplePie_Cache_Base +{ + /** + * Helper for database conversion + * + * Converts a given {@see SimplePie} object into data to be stored + * + * @param SimplePie $data + * @return array First item is the serialized data for storage, second item is the unique ID for this item + */ + protected static function prepare_simplepie_object_for_cache($data) + { + $items = $data->get_items(); + $items_by_id = array(); + + if (!empty($items)) + { + foreach ($items as $item) + { + $items_by_id[$item->get_id()] = $item; + } + + if (count($items_by_id) !== count($items)) + { + $items_by_id = array(); + foreach ($items as $item) + { + $items_by_id[$item->get_id(true)] = $item; + } + } + + if (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0])) + { + $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]; + } + elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0])) + { + $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]; + } + elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0])) + { + $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]; + } + elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0])) + { + $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0]; + } + else + { + $channel = null; + } + + if ($channel !== null) + { + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']); + } + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']); + } + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']); + } + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']); + } + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']); + } + } + if (isset($data->data['items'])) + { + unset($data->data['items']); + } + if (isset($data->data['ordered_items'])) + { + unset($data->data['ordered_items']); + } + } + return array(serialize($data->data), $items_by_id); + } +} + +/** + * Caches data to a MySQL database + * + * Registered for URLs with the "mysql" protocol + * + * For example, `mysql://root:password@localhost:3306/mydb?prefix=sp_` will + * connect to the `mydb` database on `localhost` on port 3306, with the user + * `root` and the password `password`. All tables will be prefixed with `sp_` + * + * @package SimplePie + * @subpackage Caching + */ +class SimplePie_Cache_MySQL extends SimplePie_Cache_DB +{ + /** + * PDO instance + * + * @var PDO + */ + protected $mysql; + + /** + * Options + * + * @var array + */ + protected $options; + + /** + * Cache ID + * + * @var string + */ + protected $id; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct($location, $name, $type) + { + $this->options = array( + 'user' => null, + 'pass' => null, + 'host' => '127.0.0.1', + 'port' => '3306', + 'path' => '', + 'extras' => array( + 'prefix' => '', + ), + ); + // $this->options = array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); // Leed SimplePie en v1.3.1 + $this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); + + // Path is prefixed with a "/" + $this->options['dbname'] = substr($this->options['path'], 1); + + try + { + $this->mysql = new PDO("mysql:dbname={$this->options['dbname']};host={$this->options['host']};port={$this->options['port']}", $this->options['user'], $this->options['pass'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')); + } + catch (PDOException $e) + { + $this->mysql = null; + return; + } + + $this->id = $name . $type; + + if (!$query = $this->mysql->query('SHOW TABLES')) + { + $this->mysql = null; + return; + } + + $db = array(); + while ($row = $query->fetchColumn()) + { + $db[] = $row; + } + + if (!in_array($this->options['extras']['prefix'] . 'cache_data', $db)) + { + $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))'); + if ($query === false) + { + $this->mysql = null; + } + } + + if (!in_array($this->options['extras']['prefix'] . 'items', $db)) + { + $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'); + if ($query === false) + { + $this->mysql = null; + } + } + } + + /** + * Save data to the cache + * + * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data) + { + if ($this->mysql === null) + { + return false; + } + + if ($data instanceof SimplePie) + { + $data = clone $data; + + $prepared = self::prepare_simplepie_object_for_cache($data); + + $query = $this->mysql->prepare('SELECT COUNT(*) FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed'); + $query->bindValue(':feed', $this->id); + if ($query->execute()) + { + if ($query->fetchColumn() > 0) + { + $items = count($prepared[1]); + if ($items) + { + $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = :items, `data` = :data, `mtime` = :time WHERE `id` = :feed'; + $query = $this->mysql->prepare($sql); + $query->bindValue(':items', $items); + } + else + { + $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `data` = :data, `mtime` = :time WHERE `id` = :feed'; + $query = $this->mysql->prepare($sql); + } + + $query->bindValue(':data', $prepared[0]); + $query->bindValue(':time', time()); + $query->bindValue(':feed', $this->id); + if (!$query->execute()) + { + return false; + } + } + else + { + $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:feed, :count, :data, :time)'); + $query->bindValue(':feed', $this->id); + $query->bindValue(':count', count($prepared[1])); + $query->bindValue(':data', $prepared[0]); + $query->bindValue(':time', time()); + if (!$query->execute()) + { + return false; + } + } + + $ids = array_keys($prepared[1]); + if (!empty($ids)) + { + foreach ($ids as $id) + { + $database_ids[] = $this->mysql->quote($id); + } + + $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `id` = ' . implode(' OR `id` = ', $database_ids) . ' AND `feed_id` = :feed'); + $query->bindValue(':feed', $this->id); + + if ($query->execute()) + { + $existing_ids = array(); + while ($row = $query->fetchColumn()) + { + $existing_ids[] = $row; + } + + $new_ids = array_diff($ids, $existing_ids); + + foreach ($new_ids as $new_id) + { + if (!($date = $prepared[1][$new_id]->get_date('U'))) + { + $date = time(); + } + + $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(:feed, :id, :data, :date)'); + $query->bindValue(':feed', $this->id); + $query->bindValue(':id', $new_id); + $query->bindValue(':data', serialize($prepared[1][$new_id]->data)); + $query->bindValue(':date', $date); + if (!$query->execute()) + { + return false; + } + } + return true; + } + } + else + { + return true; + } + } + } + else + { + $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed'); + $query->bindValue(':feed', $this->id); + if ($query->execute()) + { + if ($query->rowCount() > 0) + { + $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = 0, `data` = :data, `mtime` = :time WHERE `id` = :feed'); + $query->bindValue(':data', serialize($data)); + $query->bindValue(':time', time()); + $query->bindValue(':feed', $this->id); + if ($this->execute()) + { + return true; + } + } + else + { + $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:id, 0, :data, :time)'); + $query->bindValue(':id', $this->id); + $query->bindValue(':data', serialize($data)); + $query->bindValue(':time', time()); + if ($query->execute()) + { + return true; + } + } + } + } + return false; + } + + /** + * Retrieve the data saved to the cache + * + * @return array Data for SimplePie::$data + */ + public function load() + { + if ($this->mysql === null) + { + return false; + } + + $query = $this->mysql->prepare('SELECT `items`, `data` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); + $query->bindValue(':id', $this->id); + if ($query->execute() && ($row = $query->fetch())) + { + $data = unserialize($row[1]); + + if (isset($this->options['items'][0])) + { + $items = (int) $this->options['items'][0]; + } + else + { + $items = (int) $row[0]; + } + + if ($items !== 0) + { + if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0])) + { + $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]; + } + elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0])) + { + $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]; + } + elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0])) + { + $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]; + } + elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0])) + { + $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]; + } + else + { + $feed = null; + } + + if ($feed !== null) + { + $sql = 'SELECT `data` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :feed ORDER BY `posted` DESC'; + if ($items > 0) + { + $sql .= ' LIMIT ' . $items; + } + + $query = $this->mysql->prepare($sql); + $query->bindValue(':feed', $this->id); + if ($query->execute()) + { + while ($row = $query->fetchColumn()) + { + $feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row); + } + } + else + { + return false; + } + } + } + return $data; + } + return false; + } + + /** + * Retrieve the last modified time for the cache + * + * @return int Timestamp + */ + public function mtime() + { + if ($this->mysql === null) + { + return false; + } + + $query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); + $query->bindValue(':id', $this->id); + if ($query->execute() && ($time = $query->fetchColumn())) + { + return $time; + } + else + { + return false; + } + } + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch() + { + if ($this->mysql === null) + { + return false; + } + + $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id'); + $query->bindValue(':time', time()); + $query->bindValue(':id', $this->id); + if ($query->execute() && $query->rowCount() > 0) + { + return true; + } + else + { + return false; + } + } + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink() + { + if ($this->mysql === null) + { + return false; + } + + $query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); + $query->bindValue(':id', $this->id); + $query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id'); + $query2->bindValue(':id', $this->id); + if ($query->execute() && $query2->execute()) + { + return true; + } + else + { + return false; + } + } +} + +/** + * Base for cache objects + * + * Classes to be used with {@see SimplePie_Cache::register()} are expected + * to implement this interface. + * + * @package SimplePie + * @subpackage Caching + */ +interface SimplePie_Cache_Base +{ + /** + * Feed cache type + * + * @var string + */ + const TYPE_FEED = 'spc'; + + /** + * Image cache type + * + * @var string + */ + const TYPE_IMAGE = 'spi'; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct($location, $name, $type); + + /** + * Save data to the cache + * + * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data); + + /** + * Retrieve the data saved to the cache + * + * @return array Data for SimplePie::$data + */ + public function load(); + + /** + * Retrieve the last modified time for the cache + * + * @return int Timestamp + */ + public function mtime(); + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch(); + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink(); +} + +/** + * Caches data to memcache + * + * Registered for URLs with the "memcache" protocol + * + * For example, `memcache://localhost:11211/?timeout=3600&prefix=sp_` will + * connect to memcache on `localhost` on port 11211. All tables will be + * prefixed with `sp_` and data will expire after 3600 seconds + * + * @package SimplePie + * @subpackage Caching + * @uses Memcache + */ +class SimplePie_Cache_Memcache implements SimplePie_Cache_Base +{ + /** + * Memcache instance + * + * @var Memcache + */ + protected $cache; + + /** + * Options + * + * @var array + */ + protected $options; + + /** + * Cache name + * + * @var string + */ + protected $name; + + /** + * Create a new cache object + * + * @param string $location Location string (from SimplePie::$cache_location) + * @param string $name Unique ID for the cache + * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data + */ + public function __construct($location, $name, $type) + { + $this->options = array( + 'host' => '127.0.0.1', + 'port' => 11211, + 'extras' => array( + 'timeout' => 3600, // one hour + 'prefix' => 'simplepie_', + ), + ); + + // Leed SimplePie en v1.3.1 + // $parsed = SimplePie_Cache::parse_URL($location); + // $this->options['host'] = empty($parsed['host']) ? $this->options['host'] : $parsed['host']; + // $this->options['port'] = empty($parsed['port']) ? $this->options['port'] : $parsed['port']; + // $this->options['extras'] = array_merge($this->options['extras'], $parsed['extras']); + $this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); + + $this->name = $this->options['extras']['prefix'] . md5("$name:$type"); + + $this->cache = new Memcache(); + $this->cache->addServer($this->options['host'], (int) $this->options['port']); + } + + /** + * Save data to the cache + * + * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property + * @return bool Successfulness + */ + public function save($data) + { + if ($data instanceof SimplePie) + { + $data = $data->data; + } + return $this->cache->set($this->name, serialize($data), MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']); + } + + /** + * Retrieve the data saved to the cache + * + * @return array Data for SimplePie::$data + */ + public function load() + { + $data = $this->cache->get($this->name); + + if ($data !== false) + { + return unserialize($data); + } + return false; + } + + /** + * Retrieve the last modified time for the cache + * + * @return int Timestamp + */ + public function mtime() + { + $data = $this->cache->get($this->name); + + if ($data !== false) + { + // essentially ignore the mtime because Memcache expires on its own + return time(); + } + + return false; + } + + /** + * Set the last modified time to the current time + * + * @return bool Success status + */ + public function touch() + { + $data = $this->cache->get($this->name); + + if ($data !== false) + { + return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->duration); + } + + return false; + } + + /** + * Remove the cache + * + * @return bool Success status + */ + public function unlink() + { + return $this->cache->delete($this->name, 0); + } +} + + +/** + * HTTP Response Parser + * + * @package SimplePie + * @subpackage HTTP + */ +class SimplePie_HTTP_Parser +{ + /** + * HTTP Version + * + * @var float + */ + public $http_version = 0.0; + + /** + * Status code + * + * @var int + */ + public $status_code = 0; + + /** + * Reason phrase + * + * @var string + */ + public $reason = ''; + + /** + * Key/value pairs of the headers + * + * @var array + */ + public $headers = array(); + + /** + * Body of the response + * + * @var string + */ + public $body = ''; + + /** + * Current state of the state machine + * + * @var string + */ + protected $state = 'http_version'; + + /** + * Input data + * + * @var string + */ + protected $data = ''; + + /** + * Input data length (to avoid calling strlen() everytime this is needed) + * + * @var int + */ + protected $data_length = 0; + + /** + * Current position of the pointer + * + * @var int + */ + protected $position = 0; + + /** + * Name of the hedaer currently being parsed + * + * @var string + */ + protected $name = ''; + + /** + * Value of the hedaer currently being parsed + * + * @var string + */ + protected $value = ''; + + /** + * Create an instance of the class with the input data + * + * @param string $data Input data + */ + public function __construct($data) + { + $this->data = $data; + $this->data_length = strlen($this->data); + } + + /** + * Parse the input data + * + * @return bool true on success, false on failure + */ + public function parse() + { + while ($this->state && $this->state !== 'emit' && $this->has_data()) + { + $state = $this->state; + $this->$state(); + } + $this->data = ''; + if ($this->state === 'emit' || $this->state === 'body') + { + return true; + } + else + { + $this->http_version = ''; + $this->status_code = ''; + $this->reason = ''; + $this->headers = array(); + $this->body = ''; + return false; + } + } + + /** + * Check whether there is data beyond the pointer + * + * @return bool true if there is further data, false if not + */ + protected function has_data() + { + return (bool) ($this->position < $this->data_length); + } + + /** + * See if the next character is LWS + * + * @return bool true if the next character is LWS, false if not + */ + protected function is_linear_whitespace() + { + return (bool) ($this->data[$this->position] === "\x09" + || $this->data[$this->position] === "\x20" + || ($this->data[$this->position] === "\x0A" + && isset($this->data[$this->position + 1]) + && ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20"))); + } + + /** + * Parse the HTTP version + */ + protected function http_version() + { + if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/') + { + $len = strspn($this->data, '0123456789.', 5); + $this->http_version = substr($this->data, 5, $len); + $this->position += 5 + $len; + if (substr_count($this->http_version, '.') <= 1) + { + $this->http_version = (float) $this->http_version; + $this->position += strspn($this->data, "\x09\x20", $this->position); + $this->state = 'status'; + } + else + { + $this->state = false; + } + } + else + { + $this->state = false; + } + } + + /** + * Parse the status code + */ + protected function status() + { + if ($len = strspn($this->data, '0123456789', $this->position)) + { + $this->status_code = (int) substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = 'reason'; + } + else + { + $this->state = false; + } + } + + /** + * Parse the reason phrase + */ + protected function reason() + { + $len = strcspn($this->data, "\x0A", $this->position); + $this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20"); + $this->position += $len + 1; + $this->state = 'new_line'; + } + + /** + * Deal with a new line, shifting data around as needed + */ + protected function new_line() + { + $this->value = trim($this->value, "\x0D\x20"); + if ($this->name !== '' && $this->value !== '') + { + $this->name = strtolower($this->name); + // We should only use the last Content-Type header. c.f. issue #1 + if (isset($this->headers[$this->name]) && $this->name !== 'content-type') + { + $this->headers[$this->name] .= ', ' . $this->value; + } + else + { + $this->headers[$this->name] = $this->value; + } + } + $this->name = ''; + $this->value = ''; + if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A") + { + $this->position += 2; + $this->state = 'body'; + } + elseif ($this->data[$this->position] === "\x0A") + { + $this->position++; + $this->state = 'body'; + } + else + { + $this->state = 'name'; + } + } + + /** + * Parse a header name + */ + protected function name() + { + $len = strcspn($this->data, "\x0A:", $this->position); + if (isset($this->data[$this->position + $len])) + { + if ($this->data[$this->position + $len] === "\x0A") + { + $this->position += $len; + $this->state = 'new_line'; + } + else + { + $this->name = substr($this->data, $this->position, $len); + $this->position += $len + 1; + $this->state = 'value'; + } + } + else + { + $this->state = false; + } + } + + /** + * Parse LWS, replacing consecutive LWS characters with a single space + */ + protected function linear_whitespace() + { + do + { + if (substr($this->data, $this->position, 2) === "\x0D\x0A") + { + $this->position += 2; + } + elseif ($this->data[$this->position] === "\x0A") + { + $this->position++; + } + $this->position += strspn($this->data, "\x09\x20", $this->position); + } while ($this->has_data() && $this->is_linear_whitespace()); + $this->value .= "\x20"; + } + + /** + * See what state to move to while within non-quoted header values + */ + protected function value() + { + if ($this->is_linear_whitespace()) + { + $this->linear_whitespace(); + } + else + { + switch ($this->data[$this->position]) + { + case '"': + // Workaround for ETags: we have to include the quotes as + // part of the tag. + if (strtolower($this->name) === 'etag') + { + $this->value .= '"'; + $this->position++; + $this->state = 'value_char'; + break; + } + $this->position++; + $this->state = 'quote'; + break; + + case "\x0A": + $this->position++; + $this->state = 'new_line'; + break; + + default: + $this->state = 'value_char'; + break; + } + } + } + + /** + * Parse a header value while outside quotes + */ + protected function value_char() + { + $len = strcspn($this->data, "\x09\x20\x0A\"", $this->position); + $this->value .= substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = 'value'; + } + + /** + * See what state to move to while within quoted header values + */ + protected function quote() + { + if ($this->is_linear_whitespace()) + { + $this->linear_whitespace(); + } + else + { + switch ($this->data[$this->position]) + { + case '"': + $this->position++; + $this->state = 'value'; + break; + + case "\x0A": + $this->position++; + $this->state = 'new_line'; + break; + + case '\\': + $this->position++; + $this->state = 'quote_escaped'; + break; + + default: + $this->state = 'quote_char'; + break; + } + } + } + + /** + * Parse a header value while within quotes + */ + protected function quote_char() + { + $len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position); + $this->value .= substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = 'value'; + } + + /** + * Parse an escaped character within quotes + */ + protected function quote_escaped() + { + $this->value .= $this->data[$this->position]; + $this->position++; + $this->state = 'quote'; + } + + /** + * Parse the body + */ + protected function body() + { + $this->body = substr($this->data, $this->position); + if (!empty($this->headers['transfer-encoding'])) + { + unset($this->headers['transfer-encoding']); + $this->state = 'chunked'; + } + else + { + $this->state = 'emit'; + } + } + + /** + * Parsed a "Transfer-Encoding: chunked" body + */ + protected function chunked() + { + if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($this->body))) + { + $this->state = 'emit'; + return; + } + + $decoded = ''; + $encoded = $this->body; + + while (true) + { + $is_chunked = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches ); + if (!$is_chunked) + { + // Looks like it's not chunked after all + $this->state = 'emit'; + return; + } + + $length = hexdec(trim($matches[1])); + if ($length === 0) + { + // Ignore trailer headers + $this->state = 'emit'; + $this->body = $decoded; + return; + } + + $chunk_length = strlen($matches[0]); + $decoded .= $part = substr($encoded, $chunk_length, $length); + $encoded = substr($encoded, $chunk_length + $length + 2); + + if (trim($encoded) === '0' || empty($encoded)) + { + $this->state = 'emit'; + $this->body = $decoded; + return; + } + } + } +} + +/** + * Used for feed auto-discovery + * + * + * This class can be overloaded with {@see SimplePie::set_locator_class()} + * + * @package SimplePie + */ +class SimplePie_Locator +{ + var $useragent; + var $timeout; + var $file; + var $local = array(); + var $elsewhere = array(); + var $cached_entities = array(); + var $http_base; + var $base; + var $base_location = 0; + var $checked_feeds = 0; + var $max_checked_feeds = 10; + protected $registry; + + public function __construct(SimplePie_File $file, $timeout = 10, $useragent = null, $max_checked_feeds = 10) + { + $this->file = $file; + $this->useragent = $useragent; + $this->timeout = $timeout; + $this->max_checked_feeds = $max_checked_feeds; + + if (class_exists('DOMDocument')) + { + $this->dom = new DOMDocument(); + + set_error_handler(array('SimplePie_Misc', 'silence_errors')); + $this->dom->loadHTML($this->file->body); + restore_error_handler(); + } + else + { + $this->dom = null; + } + } + + public function set_registry(SimplePie_Registry $registry) + { + $this->registry = $registry; + } + + public function find($type = SIMPLEPIE_LOCATOR_ALL, &$working) + { + if ($this->is_feed($this->file)) + { + return $this->file; + } + + if ($this->file->method & SIMPLEPIE_FILE_SOURCE_REMOTE) + { + $sniffer = $this->registry->create('Content_Type_Sniffer', array($this->file)); + if ($sniffer->get_type() !== 'text/html') + { + return null; + } + } + + if ($type & ~SIMPLEPIE_LOCATOR_NONE) + { + $this->get_base(); + } + + if ($type & SIMPLEPIE_LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery()) + { + return $working[0]; + } + + if ($type & (SIMPLEPIE_LOCATOR_LOCAL_EXTENSION | SIMPLEPIE_LOCATOR_LOCAL_BODY | SIMPLEPIE_LOCATOR_REMOTE_EXTENSION | SIMPLEPIE_LOCATOR_REMOTE_BODY) && $this->get_links()) + { + if ($type & SIMPLEPIE_LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local)) + { + return $working; + } + + if ($type & SIMPLEPIE_LOCATOR_LOCAL_BODY && $working = $this->body($this->local)) + { + return $working; + } + + if ($type & SIMPLEPIE_LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere)) + { + return $working; + } + + if ($type & SIMPLEPIE_LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere)) + { + return $working; + } + } + return null; + } + + public function is_feed($file) + { + if ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE) + { + $sniffer = $this->registry->create('Content_Type_Sniffer', array($file)); + $sniffed = $sniffer->get_type(); + if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml'))) + { + return true; + } + else + { + return false; + } + } + elseif ($file->method & SIMPLEPIE_FILE_SOURCE_LOCAL) + { + return true; + } + else + { + return false; + } + } + + public function get_base() + { + if ($this->dom === null) + { + throw new SimplePie_Exception('DOMDocument not found, unable to use locator'); + } + $this->http_base = $this->file->url; + $this->base = $this->http_base; + $elements = $this->dom->getElementsByTagName('base'); + foreach ($elements as $element) + { + if ($element->hasAttribute('href')) + { + $base = $this->registry->call('Misc', 'absolutize_url', array(trim($element->getAttribute('href')), $this->http_base)); + if ($base === false) + { + continue; + } + $this->base = $base; + $this->base_location = method_exists($element, 'getLineNo') ? $element->getLineNo() : 0; + break; + } + } + } + + public function autodiscovery() + { + $done = array(); + $feeds = array(); + $feeds = array_merge($feeds, $this->search_elements_by_tag('link', $done, $feeds)); + $feeds = array_merge($feeds, $this->search_elements_by_tag('a', $done, $feeds)); + $feeds = array_merge($feeds, $this->search_elements_by_tag('area', $done, $feeds)); + + if (!empty($feeds)) + { + return array_values($feeds); + } + else + { + return null; + } + } + + protected function search_elements_by_tag($name, &$done, $feeds) + { + if ($this->dom === null) + { + throw new SimplePie_Exception('DOMDocument not found, unable to use locator'); + } + + $links = $this->dom->getElementsByTagName($name); + foreach ($links as $link) + { + if ($this->checked_feeds === $this->max_checked_feeds) + { + break; + } + if ($link->hasAttribute('href') && $link->hasAttribute('rel')) + { + $rel = array_unique($this->registry->call('Misc', 'space_seperated_tokens', array(strtolower($link->getAttribute('rel'))))); + $line = method_exists($link, 'getLineNo') ? $link->getLineNo() : 1; + + if ($this->base_location < $line) + { + $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base)); + } + else + { + $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->http_base)); + } + if ($href === false) + { + continue; + } + + if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !in_array('stylesheet', $rel) && $link->hasAttribute('type') && in_array(strtolower($this->registry->call('Misc', 'parse_mime', array($link->getAttribute('type')))), array('application/rss+xml', 'application/atom+xml'))) && !isset($feeds[$href])) + { + $this->checked_feeds++; + $headers = array( + 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', + ); + $feed = $this->registry->create('File', array($href, $this->timeout, 5, $headers, $this->useragent)); + if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) + { + $feeds[$href] = $feed; + } + } + $done[] = $href; + } + } + + return $feeds; + } + + public function get_links() + { + if ($this->dom === null) + { + throw new SimplePie_Exception('DOMDocument not found, unable to use locator'); + } + + $links = $this->dom->getElementsByTagName('a'); + foreach ($links as $link) + { + if ($link->hasAttribute('href')) + { + $href = trim($link->getAttribute('href')); + $parsed = $this->registry->call('Misc', 'parse_url', array($href)); + if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme'])) + { + if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo()) + // si Call to undefined method DOMElement::getLineNo() // Leed info + //$text = $link->ownerDocument->saveXML(); + //$line += substr_count($text, "\n"); + //if ($this->base_location < $line) + { + $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base)); + } + else + { + $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->http_base)); + } + if ($href === false) + { + continue; + } + + $current = $this->registry->call('Misc', 'parse_url', array($this->file->url)); + + if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority']) + { + $this->local[] = $href; + } + else + { + $this->elsewhere[] = $href; + } + } + } + } + $this->local = array_unique($this->local); + $this->elsewhere = array_unique($this->elsewhere); + if (!empty($this->local) || !empty($this->elsewhere)) + { + return true; + } + return null; + } + + public function extension(&$array) + { + foreach ($array as $key => $value) + { + if ($this->checked_feeds === $this->max_checked_feeds) + { + break; + } + if (in_array(strtolower(strrchr($value, '.')), array('.rss', '.rdf', '.atom', '.xml'))) + { + $this->checked_feeds++; + + $headers = array( + 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', + ); + $feed = $this->registry->create('File', array($value, $this->timeout, 5, $headers, $this->useragent)); + if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) + { + return $feed; + } + else + { + unset($array[$key]); + } + } + } + return null; + } + + public function body(&$array) + { + foreach ($array as $key => $value) + { + if ($this->checked_feeds === $this->max_checked_feeds) + { + break; + } + if (preg_match('/(rss|rdf|atom|xml)/i', $value)) + { + $this->checked_feeds++; + $headers = array( + 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', + ); + $feed = $this->registry->create('File', array($value, $this->timeout, 5, null, $this->useragent)); + if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) + { + return $feed; + } + else + { + unset($array[$key]); + } + } + } + return null; + } +} + +/** + * Miscellanous utilities + * + * @package SimplePie + */ +class SimplePie_Misc +{ + public static function time_hms($seconds) + { + $time = ''; + + $hours = floor($seconds / 3600); + $remainder = $seconds % 3600; + if ($hours > 0) + { + $time .= $hours.':'; + } + + $minutes = floor($remainder / 60); + $seconds = $remainder % 60; + if ($minutes < 10 && $hours > 0) + { + $minutes = '0' . $minutes; + } + if ($seconds < 10) + { + $seconds = '0' . $seconds; + } + + $time .= $minutes.':'; + $time .= $seconds; + + return $time; + } + + public static function absolutize_url($relative, $base) + { + // Update for Leed + if (substr($relative, 0, 2) === '//') //FreshRSS: disable absolutize_url for "//www.example.net" which will pick HTTP or HTTPS automatically + { + return $relative; + } + // fin update for Leed + $iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative); + if ($iri === false) + { + return false; + } + return $iri->get_uri(); + } + + /** + * Get a HTML/XML element from a HTML string + * + * @deprecated Use DOMDocument instead (parsing HTML with regex is bad!) + * @param string $realname Element name (including namespace prefix if applicable) + * @param string $string HTML document + * @return array + */ + public static function get_element($realname, $string) + { + $return = array(); + $name = preg_quote($realname, '/'); + if (preg_match_all("/<($name)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) + { + for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++) + { + $return[$i]['tag'] = $realname; + $return[$i]['full'] = $matches[$i][0][0]; + $return[$i]['offset'] = $matches[$i][0][1]; + if (strlen($matches[$i][3][0]) <= 2) + { + $return[$i]['self_closing'] = true; + } + else + { + $return[$i]['self_closing'] = false; + $return[$i]['content'] = $matches[$i][4][0]; + } + $return[$i]['attribs'] = array(); + if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER)) + { + for ($j = 0, $total_attribs = count($attribs); $j < $total_attribs; $j++) + { + if (count($attribs[$j]) === 2) + { + $attribs[$j][2] = $attribs[$j][1]; + } + //$return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j])); + $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); //Update for Leed + } + } + } + } + return $return; + } + + public static function element_implode($element) + { + $full = "<$element[tag]"; + foreach ($element['attribs'] as $key => $value) + { + $key = strtolower($key); + //$full .= " $key=\"" . htmlspecialchars($value['data']) . '"'; + $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; //Update for Leed + } + if ($element['self_closing']) + { + $full .= ' />'; + } + else + { + $full .= ">$element[content]"; + } + return $full; + } + + public static function error($message, $level, $file, $line) + { + if ((ini_get('error_reporting') & $level) > 0) + { + switch ($level) + { + case E_USER_ERROR: + $note = 'PHP Error'; + break; + case E_USER_WARNING: + $note = 'PHP Warning'; + break; + case E_USER_NOTICE: + $note = 'PHP Notice'; + break; + default: + $note = 'Unknown Error'; + break; + } + + $log_error = true; + if (!function_exists('error_log')) + { + $log_error = false; + } + + $log_file = @ini_get('error_log'); + if (!empty($log_file) && ('syslog' !== $log_file) && !@is_writable($log_file)) + { + $log_error = false; + } + + if ($log_error) + { + @error_log("$note: $message in $file on line $line", 0); + } + } + + return $message; + } + + public static function fix_protocol($url, $http = 1) + { + $url = SimplePie_Misc::normalize_url($url); + $parsed = SimplePie_Misc::parse_url($url); + if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') + { + return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http); + } + + if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url)) + { + return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http); + } + + if ($http === 2 && $parsed['scheme'] !== '') + { + return "feed:$url"; + } + elseif ($http === 3 && strtolower($parsed['scheme']) === 'http') + { + return substr_replace($url, 'podcast', 0, 4); + } + elseif ($http === 4 && strtolower($parsed['scheme']) === 'http') + { + return substr_replace($url, 'itpc', 0, 4); + } + else + { + return $url; + } + } + + public static function array_merge_recursive($array1, $array2) + { + foreach ($array2 as $key => $value) + { + if (is_array($value)) + { + $array1[$key] = SimplePie_Misc::array_merge_recursive($array1[$key], $value); + } + else + { + $array1[$key] = $value; + } + } + + return $array1; + } + + public static function parse_url($url) + { + $iri = new SimplePie_IRI($url); + return array( + 'scheme' => (string) $iri->scheme, + 'authority' => (string) $iri->authority, + 'path' => (string) $iri->path, + 'query' => (string) $iri->query, + 'fragment' => (string) $iri->fragment + ); + } + + public static function compress_parse_url($scheme = '', $authority = '', $path = '', $query = '', $fragment = '') + { + $iri = new SimplePie_IRI(''); + $iri->scheme = $scheme; + $iri->authority = $authority; + $iri->path = $path; + $iri->query = $query; + $iri->fragment = $fragment; + return $iri->get_uri(); + } + + public static function normalize_url($url) + { + $iri = new SimplePie_IRI($url); + return $iri->get_uri(); + } + + public static function percent_encoding_normalization($match) + { + $integer = hexdec($match[1]); + if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E) + { + return chr($integer); + } + else + { + return strtoupper($match[0]); + } + } + + /** + * Converts a Windows-1252 encoded string to a UTF-8 encoded string + * + * @static + * @param string $string Windows-1252 encoded string + * @return string UTF-8 encoded string + */ + public static function windows_1252_to_utf8($string) + { + static $convert_table = array("\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\xE2\x80\x99", "\x93" => "\xE2\x80\x9C", "\x94" => "\xE2\x80\x9D", "\x95" => "\xE2\x80\xA2", "\x96" => "\xE2\x80\x93", "\x97" => "\xE2\x80\x94", "\x98" => "\xCB\x9C", "\x99" => "\xE2\x84\xA2", "\x9A" => "\xC5\xA1", "\x9B" => "\xE2\x80\xBA", "\x9C" => "\xC5\x93", "\x9D" => "\xEF\xBF\xBD", "\x9E" => "\xC5\xBE", "\x9F" => "\xC5\xB8", "\xA0" => "\xC2\xA0", "\xA1" => "\xC2\xA1", "\xA2" => "\xC2\xA2", "\xA3" => "\xC2\xA3", "\xA4" => "\xC2\xA4", "\xA5" => "\xC2\xA5", "\xA6" => "\xC2\xA6", "\xA7" => "\xC2\xA7", "\xA8" => "\xC2\xA8", "\xA9" => "\xC2\xA9", "\xAA" => "\xC2\xAA", "\xAB" => "\xC2\xAB", "\xAC" => "\xC2\xAC", "\xAD" => "\xC2\xAD", "\xAE" => "\xC2\xAE", "\xAF" => "\xC2\xAF", "\xB0" => "\xC2\xB0", "\xB1" => "\xC2\xB1", "\xB2" => "\xC2\xB2", "\xB3" => "\xC2\xB3", "\xB4" => "\xC2\xB4", "\xB5" => "\xC2\xB5", "\xB6" => "\xC2\xB6", "\xB7" => "\xC2\xB7", "\xB8" => "\xC2\xB8", "\xB9" => "\xC2\xB9", "\xBA" => "\xC2\xBA", "\xBB" => "\xC2\xBB", "\xBC" => "\xC2\xBC", "\xBD" => "\xC2\xBD", "\xBE" => "\xC2\xBE", "\xBF" => "\xC2\xBF", "\xC0" => "\xC3\x80", "\xC1" => "\xC3\x81", "\xC2" => "\xC3\x82", "\xC3" => "\xC3\x83", "\xC4" => "\xC3\x84", "\xC5" => "\xC3\x85", "\xC6" => "\xC3\x86", "\xC7" => "\xC3\x87", "\xC8" => "\xC3\x88", "\xC9" => "\xC3\x89", "\xCA" => "\xC3\x8A", "\xCB" => "\xC3\x8B", "\xCC" => "\xC3\x8C", "\xCD" => "\xC3\x8D", "\xCE" => "\xC3\x8E", "\xCF" => "\xC3\x8F", "\xD0" => "\xC3\x90", "\xD1" => "\xC3\x91", "\xD2" => "\xC3\x92", "\xD3" => "\xC3\x93", "\xD4" => "\xC3\x94", "\xD5" => "\xC3\x95", "\xD6" => "\xC3\x96", "\xD7" => "\xC3\x97", "\xD8" => "\xC3\x98", "\xD9" => "\xC3\x99", "\xDA" => "\xC3\x9A", "\xDB" => "\xC3\x9B", "\xDC" => "\xC3\x9C", "\xDD" => "\xC3\x9D", "\xDE" => "\xC3\x9E", "\xDF" => "\xC3\x9F", "\xE0" => "\xC3\xA0", "\xE1" => "\xC3\xA1", "\xE2" => "\xC3\xA2", "\xE3" => "\xC3\xA3", "\xE4" => "\xC3\xA4", "\xE5" => "\xC3\xA5", "\xE6" => "\xC3\xA6", "\xE7" => "\xC3\xA7", "\xE8" => "\xC3\xA8", "\xE9" => "\xC3\xA9", "\xEA" => "\xC3\xAA", "\xEB" => "\xC3\xAB", "\xEC" => "\xC3\xAC", "\xED" => "\xC3\xAD", "\xEE" => "\xC3\xAE", "\xEF" => "\xC3\xAF", "\xF0" => "\xC3\xB0", "\xF1" => "\xC3\xB1", "\xF2" => "\xC3\xB2", "\xF3" => "\xC3\xB3", "\xF4" => "\xC3\xB4", "\xF5" => "\xC3\xB5", "\xF6" => "\xC3\xB6", "\xF7" => "\xC3\xB7", "\xF8" => "\xC3\xB8", "\xF9" => "\xC3\xB9", "\xFA" => "\xC3\xBA", "\xFB" => "\xC3\xBB", "\xFC" => "\xC3\xBC", "\xFD" => "\xC3\xBD", "\xFE" => "\xC3\xBE", "\xFF" => "\xC3\xBF"); + + return strtr($string, $convert_table); + } + + /** + * Change a string from one encoding to another + * + * @param string $data Raw data in $input encoding + * @param string $input Encoding of $data + * @param string $output Encoding you want + * @return string|boolean False if we can't convert it + */ + public static function change_encoding($data, $input, $output) + { + $input = SimplePie_Misc::encoding($input); + $output = SimplePie_Misc::encoding($output); + + // We fail to fail on non US-ASCII bytes + if ($input === 'US-ASCII') + { + static $non_ascii_octects = ''; + if (!$non_ascii_octects) + { + for ($i = 0x80; $i <= 0xFF; $i++) + { + $non_ascii_octects .= chr($i); + } + } + $data = substr($data, 0, strcspn($data, $non_ascii_octects)); + } + + // This is first, as behaviour of this is completely predictable + if ($input === 'windows-1252' && $output === 'UTF-8') + { + return SimplePie_Misc::windows_1252_to_utf8($data); + } + // This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported). + elseif (function_exists('mb_convert_encoding') && ($return = SimplePie_Misc::change_encoding_mbstring($data, $input, $output))) + { + return $return; + } + // This is last, as behaviour of this varies with OS userland and PHP version + elseif (function_exists('iconv') && ($return = SimplePie_Misc::change_encoding_iconv($data, $input, $output))) + { + return $return; + } + // If we can't do anything, just fail + else + { + return false; + } + } + + protected static function change_encoding_mbstring($data, $input, $output) + { + if ($input === 'windows-949') + { + $input = 'EUC-KR'; + } + if ($output === 'windows-949') + { + $output = 'EUC-KR'; + } + if ($input === 'Windows-31J') + { + $input = 'SJIS'; + } + if ($output === 'Windows-31J') + { + $output = 'SJIS'; + } + + // Check that the encoding is supported + if (@mb_convert_encoding("\x80", 'UTF-16BE', $input) === "\x00\x80") + { + return false; + } + if (!in_array($input, mb_list_encodings())) + { + return false; + } + + // Let's do some conversion + if ($return = @mb_convert_encoding($data, $output, $input)) + { + return $return; + } + + return false; + } + + protected static function change_encoding_iconv($data, $input, $output) + { + return @iconv($input, $output, $data); + } + + /** + * Normalize an encoding name + * + * This is automatically generated by create.php + * + * To generate it, run `php create.php` on the command line, and copy the + * output to replace this function. + * + * @param string $charset Character set to standardise + * @return string Standardised name + */ + public static function encoding($charset) + { + // Normalization from UTS #22 + switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset))) + { + case 'adobestandardencoding': + case 'csadobestandardencoding': + return 'Adobe-Standard-Encoding'; + + case 'adobesymbolencoding': + case 'cshppsmath': + return 'Adobe-Symbol-Encoding'; + + case 'ami1251': + case 'amiga1251': + return 'Amiga-1251'; + + case 'ansix31101983': + case 'csat5001983': + case 'csiso99naplps': + case 'isoir99': + case 'naplps': + return 'ANSI_X3.110-1983'; + + case 'arabic7': + case 'asmo449': + case 'csiso89asmo449': + case 'iso9036': + case 'isoir89': + return 'ASMO_449'; + + case 'big5': + case 'csbig5': + return 'Big5'; + + case 'big5hkscs': + return 'Big5-HKSCS'; + + case 'bocu1': + case 'csbocu1': + return 'BOCU-1'; + + case 'brf': + case 'csbrf': + return 'BRF'; + + case 'bs4730': + case 'csiso4unitedkingdom': + case 'gb': + case 'iso646gb': + case 'isoir4': + case 'uk': + return 'BS_4730'; + + case 'bsviewdata': + case 'csiso47bsviewdata': + case 'isoir47': + return 'BS_viewdata'; + + case 'cesu8': + case 'cscesu8': + return 'CESU-8'; + + case 'ca': + case 'csa71': + case 'csaz243419851': + case 'csiso121canadian1': + case 'iso646ca': + case 'isoir121': + return 'CSA_Z243.4-1985-1'; + + case 'csa72': + case 'csaz243419852': + case 'csiso122canadian2': + case 'iso646ca2': + case 'isoir122': + return 'CSA_Z243.4-1985-2'; + + case 'csaz24341985gr': + case 'csiso123csaz24341985gr': + case 'isoir123': + return 'CSA_Z243.4-1985-gr'; + + case 'csiso139csn369103': + case 'csn369103': + case 'isoir139': + return 'CSN_369103'; + + case 'csdecmcs': + case 'dec': + case 'decmcs': + return 'DEC-MCS'; + + case 'csiso21german': + case 'de': + case 'din66003': + case 'iso646de': + case 'isoir21': + return 'DIN_66003'; + + case 'csdkus': + case 'dkus': + return 'dk-us'; + + case 'csiso646danish': + case 'dk': + case 'ds2089': + case 'iso646dk': + return 'DS_2089'; + + case 'csibmebcdicatde': + case 'ebcdicatde': + return 'EBCDIC-AT-DE'; + + case 'csebcdicatdea': + case 'ebcdicatdea': + return 'EBCDIC-AT-DE-A'; + + case 'csebcdiccafr': + case 'ebcdiccafr': + return 'EBCDIC-CA-FR'; + + case 'csebcdicdkno': + case 'ebcdicdkno': + return 'EBCDIC-DK-NO'; + + case 'csebcdicdknoa': + case 'ebcdicdknoa': + return 'EBCDIC-DK-NO-A'; + + case 'csebcdices': + case 'ebcdices': + return 'EBCDIC-ES'; + + case 'csebcdicesa': + case 'ebcdicesa': + return 'EBCDIC-ES-A'; + + case 'csebcdicess': + case 'ebcdicess': + return 'EBCDIC-ES-S'; + + case 'csebcdicfise': + case 'ebcdicfise': + return 'EBCDIC-FI-SE'; + + case 'csebcdicfisea': + case 'ebcdicfisea': + return 'EBCDIC-FI-SE-A'; + + case 'csebcdicfr': + case 'ebcdicfr': + return 'EBCDIC-FR'; + + case 'csebcdicit': + case 'ebcdicit': + return 'EBCDIC-IT'; + + case 'csebcdicpt': + case 'ebcdicpt': + return 'EBCDIC-PT'; + + case 'csebcdicuk': + case 'ebcdicuk': + return 'EBCDIC-UK'; + + case 'csebcdicus': + case 'ebcdicus': + return 'EBCDIC-US'; + + case 'csiso111ecmacyrillic': + case 'ecmacyrillic': + case 'isoir111': + case 'koi8e': + return 'ECMA-cyrillic'; + + case 'csiso17spanish': + case 'es': + case 'iso646es': + case 'isoir17': + return 'ES'; + + case 'csiso85spanish2': + case 'es2': + case 'iso646es2': + case 'isoir85': + return 'ES2'; + + case 'cseucpkdfmtjapanese': + case 'eucjp': + case 'extendedunixcodepackedformatforjapanese': + return 'EUC-JP'; + + case 'cseucfixwidjapanese': + case 'extendedunixcodefixedwidthforjapanese': + return 'Extended_UNIX_Code_Fixed_Width_for_Japanese'; + + case 'gb18030': + return 'GB18030'; + + case 'chinese': + case 'cp936': + case 'csgb2312': + case 'csiso58gb231280': + case 'gb2312': + case 'gb231280': + case 'gbk': + case 'isoir58': + case 'ms936': + case 'windows936': + return 'GBK'; + + case 'cn': + case 'csiso57gb1988': + case 'gb198880': + case 'iso646cn': + case 'isoir57': + return 'GB_1988-80'; + + case 'csiso153gost1976874': + case 'gost1976874': + case 'isoir153': + case 'stsev35888': + return 'GOST_19768-74'; + + case 'csiso150': + case 'csiso150greekccitt': + case 'greekccitt': + case 'isoir150': + return 'greek-ccitt'; + + case 'csiso88greek7': + case 'greek7': + case 'isoir88': + return 'greek7'; + + case 'csiso18greek7old': + case 'greek7old': + case 'isoir18': + return 'greek7-old'; + + case 'cshpdesktop': + case 'hpdesktop': + return 'HP-DeskTop'; + + case 'cshplegal': + case 'hplegal': + return 'HP-Legal'; + + case 'cshpmath8': + case 'hpmath8': + return 'HP-Math8'; + + case 'cshppifont': + case 'hppifont': + return 'HP-Pi-font'; + + case 'cshproman8': + case 'hproman8': + case 'r8': + case 'roman8': + return 'hp-roman8'; + + case 'hzgb2312': + return 'HZ-GB-2312'; + + case 'csibmsymbols': + case 'ibmsymbols': + return 'IBM-Symbols'; + + case 'csibmthai': + case 'ibmthai': + return 'IBM-Thai'; + + case 'cp37': + case 'csibm37': + case 'ebcdiccpca': + case 'ebcdiccpnl': + case 'ebcdiccpus': + case 'ebcdiccpwt': + case 'ibm37': + return 'IBM037'; + + case 'cp38': + case 'csibm38': + case 'ebcdicint': + case 'ibm38': + return 'IBM038'; + + case 'cp273': + case 'csibm273': + case 'ibm273': + return 'IBM273'; + + case 'cp274': + case 'csibm274': + case 'ebcdicbe': + case 'ibm274': + return 'IBM274'; + + case 'cp275': + case 'csibm275': + case 'ebcdicbr': + case 'ibm275': + return 'IBM275'; + + case 'csibm277': + case 'ebcdiccpdk': + case 'ebcdiccpno': + case 'ibm277': + return 'IBM277'; + + case 'cp278': + case 'csibm278': + case 'ebcdiccpfi': + case 'ebcdiccpse': + case 'ibm278': + return 'IBM278'; + + case 'cp280': + case 'csibm280': + case 'ebcdiccpit': + case 'ibm280': + return 'IBM280'; + + case 'cp281': + case 'csibm281': + case 'ebcdicjpe': + case 'ibm281': + return 'IBM281'; + + case 'cp284': + case 'csibm284': + case 'ebcdiccpes': + case 'ibm284': + return 'IBM284'; + + case 'cp285': + case 'csibm285': + case 'ebcdiccpgb': + case 'ibm285': + return 'IBM285'; + + case 'cp290': + case 'csibm290': + case 'ebcdicjpkana': + case 'ibm290': + return 'IBM290'; + + case 'cp297': + case 'csibm297': + case 'ebcdiccpfr': + case 'ibm297': + return 'IBM297'; + + case 'cp420': + case 'csibm420': + case 'ebcdiccpar1': + case 'ibm420': + return 'IBM420'; + + case 'cp423': + case 'csibm423': + case 'ebcdiccpgr': + case 'ibm423': + return 'IBM423'; + + case 'cp424': + case 'csibm424': + case 'ebcdiccphe': + case 'ibm424': + return 'IBM424'; + + case '437': + case 'cp437': + case 'cspc8codepage437': + case 'ibm437': + return 'IBM437'; + + case 'cp500': + case 'csibm500': + case 'ebcdiccpbe': + case 'ebcdiccpch': + case 'ibm500': + return 'IBM500'; + + case 'cp775': + case 'cspc775baltic': + case 'ibm775': + return 'IBM775'; + + case '850': + case 'cp850': + case 'cspc850multilingual': + case 'ibm850': + return 'IBM850'; + + case '851': + case 'cp851': + case 'csibm851': + case 'ibm851': + return 'IBM851'; + + case '852': + case 'cp852': + case 'cspcp852': + case 'ibm852': + return 'IBM852'; + + case '855': + case 'cp855': + case 'csibm855': + case 'ibm855': + return 'IBM855'; + + case '857': + case 'cp857': + case 'csibm857': + case 'ibm857': + return 'IBM857'; + + case 'ccsid858': + case 'cp858': + case 'ibm858': + case 'pcmultilingual850euro': + return 'IBM00858'; + + case '860': + case 'cp860': + case 'csibm860': + case 'ibm860': + return 'IBM860'; + + case '861': + case 'cp861': + case 'cpis': + case 'csibm861': + case 'ibm861': + return 'IBM861'; + + case '862': + case 'cp862': + case 'cspc862latinhebrew': + case 'ibm862': + return 'IBM862'; + + case '863': + case 'cp863': + case 'csibm863': + case 'ibm863': + return 'IBM863'; + + case 'cp864': + case 'csibm864': + case 'ibm864': + return 'IBM864'; + + case '865': + case 'cp865': + case 'csibm865': + case 'ibm865': + return 'IBM865'; + + case '866': + case 'cp866': + case 'csibm866': + case 'ibm866': + return 'IBM866'; + + case 'cp868': + case 'cpar': + case 'csibm868': + case 'ibm868': + return 'IBM868'; + + case '869': + case 'cp869': + case 'cpgr': + case 'csibm869': + case 'ibm869': + return 'IBM869'; + + case 'cp870': + case 'csibm870': + case 'ebcdiccproece': + case 'ebcdiccpyu': + case 'ibm870': + return 'IBM870'; + + case 'cp871': + case 'csibm871': + case 'ebcdiccpis': + case 'ibm871': + return 'IBM871'; + + case 'cp880': + case 'csibm880': + case 'ebcdiccyrillic': + case 'ibm880': + return 'IBM880'; + + case 'cp891': + case 'csibm891': + case 'ibm891': + return 'IBM891'; + + case 'cp903': + case 'csibm903': + case 'ibm903': + return 'IBM903'; + + case '904': + case 'cp904': + case 'csibbm904': + case 'ibm904': + return 'IBM904'; + + case 'cp905': + case 'csibm905': + case 'ebcdiccptr': + case 'ibm905': + return 'IBM905'; + + case 'cp918': + case 'csibm918': + case 'ebcdiccpar2': + case 'ibm918': + return 'IBM918'; + + case 'ccsid924': + case 'cp924': + case 'ebcdiclatin9euro': + case 'ibm924': + return 'IBM00924'; + + case 'cp1026': + case 'csibm1026': + case 'ibm1026': + return 'IBM1026'; + + case 'ibm1047': + return 'IBM1047'; + + case 'ccsid1140': + case 'cp1140': + case 'ebcdicus37euro': + case 'ibm1140': + return 'IBM01140'; + + case 'ccsid1141': + case 'cp1141': + case 'ebcdicde273euro': + case 'ibm1141': + return 'IBM01141'; + + case 'ccsid1142': + case 'cp1142': + case 'ebcdicdk277euro': + case 'ebcdicno277euro': + case 'ibm1142': + return 'IBM01142'; + + case 'ccsid1143': + case 'cp1143': + case 'ebcdicfi278euro': + case 'ebcdicse278euro': + case 'ibm1143': + return 'IBM01143'; + + case 'ccsid1144': + case 'cp1144': + case 'ebcdicit280euro': + case 'ibm1144': + return 'IBM01144'; + + case 'ccsid1145': + case 'cp1145': + case 'ebcdices284euro': + case 'ibm1145': + return 'IBM01145'; + + case 'ccsid1146': + case 'cp1146': + case 'ebcdicgb285euro': + case 'ibm1146': + return 'IBM01146'; + + case 'ccsid1147': + case 'cp1147': + case 'ebcdicfr297euro': + case 'ibm1147': + return 'IBM01147'; + + case 'ccsid1148': + case 'cp1148': + case 'ebcdicinternational500euro': + case 'ibm1148': + return 'IBM01148'; + + case 'ccsid1149': + case 'cp1149': + case 'ebcdicis871euro': + case 'ibm1149': + return 'IBM01149'; + + case 'csiso143iecp271': + case 'iecp271': + case 'isoir143': + return 'IEC_P27-1'; + + case 'csiso49inis': + case 'inis': + case 'isoir49': + return 'INIS'; + + case 'csiso50inis8': + case 'inis8': + case 'isoir50': + return 'INIS-8'; + + case 'csiso51iniscyrillic': + case 'iniscyrillic': + case 'isoir51': + return 'INIS-cyrillic'; + + case 'csinvariant': + case 'invariant': + return 'INVARIANT'; + + case 'iso2022cn': + return 'ISO-2022-CN'; + + case 'iso2022cnext': + return 'ISO-2022-CN-EXT'; + + case 'csiso2022jp': + case 'iso2022jp': + return 'ISO-2022-JP'; + + case 'csiso2022jp2': + case 'iso2022jp2': + return 'ISO-2022-JP-2'; + + case 'csiso2022kr': + case 'iso2022kr': + return 'ISO-2022-KR'; + + case 'cswindows30latin1': + case 'iso88591windows30latin1': + return 'ISO-8859-1-Windows-3.0-Latin-1'; + + case 'cswindows31latin1': + case 'iso88591windows31latin1': + return 'ISO-8859-1-Windows-3.1-Latin-1'; + + case 'csisolatin2': + case 'iso88592': + case 'iso885921987': + case 'isoir101': + case 'l2': + case 'latin2': + return 'ISO-8859-2'; + + case 'cswindows31latin2': + case 'iso88592windowslatin2': + return 'ISO-8859-2-Windows-Latin-2'; + + case 'csisolatin3': + case 'iso88593': + case 'iso885931988': + case 'isoir109': + case 'l3': + case 'latin3': + return 'ISO-8859-3'; + + case 'csisolatin4': + case 'iso88594': + case 'iso885941988': + case 'isoir110': + case 'l4': + case 'latin4': + return 'ISO-8859-4'; + + case 'csisolatincyrillic': + case 'cyrillic': + case 'iso88595': + case 'iso885951988': + case 'isoir144': + return 'ISO-8859-5'; + + case 'arabic': + case 'asmo708': + case 'csisolatinarabic': + case 'ecma114': + case 'iso88596': + case 'iso885961987': + case 'isoir127': + return 'ISO-8859-6'; + + case 'csiso88596e': + case 'iso88596e': + return 'ISO-8859-6-E'; + + case 'csiso88596i': + case 'iso88596i': + return 'ISO-8859-6-I'; + + case 'csisolatingreek': + case 'ecma118': + case 'elot928': + case 'greek': + case 'greek8': + case 'iso88597': + case 'iso885971987': + case 'isoir126': + return 'ISO-8859-7'; + + case 'csisolatinhebrew': + case 'hebrew': + case 'iso88598': + case 'iso885981988': + case 'isoir138': + return 'ISO-8859-8'; + + case 'csiso88598e': + case 'iso88598e': + return 'ISO-8859-8-E'; + + case 'csiso88598i': + case 'iso88598i': + return 'ISO-8859-8-I'; + + case 'cswindows31latin5': + case 'iso88599windowslatin5': + return 'ISO-8859-9-Windows-Latin-5'; + + case 'csisolatin6': + case 'iso885910': + case 'iso8859101992': + case 'isoir157': + case 'l6': + case 'latin6': + return 'ISO-8859-10'; + + case 'iso885913': + return 'ISO-8859-13'; + + case 'iso885914': + case 'iso8859141998': + case 'isoceltic': + case 'isoir199': + case 'l8': + case 'latin8': + return 'ISO-8859-14'; + + case 'iso885915': + case 'latin9': + return 'ISO-8859-15'; + + case 'iso885916': + case 'iso8859162001': + case 'isoir226': + case 'l10': + case 'latin10': + return 'ISO-8859-16'; + + case 'iso10646j1': + return 'ISO-10646-J-1'; + + case 'csunicode': + case 'iso10646ucs2': + return 'ISO-10646-UCS-2'; + + case 'csucs4': + case 'iso10646ucs4': + return 'ISO-10646-UCS-4'; + + case 'csunicodeascii': + case 'iso10646ucsbasic': + return 'ISO-10646-UCS-Basic'; + + case 'csunicodelatin1': + case 'iso10646': + case 'iso10646unicodelatin1': + return 'ISO-10646-Unicode-Latin1'; + + case 'csiso10646utf1': + case 'iso10646utf1': + return 'ISO-10646-UTF-1'; + + case 'csiso115481': + case 'iso115481': + case 'isotr115481': + return 'ISO-11548-1'; + + case 'csiso90': + case 'isoir90': + return 'iso-ir-90'; + + case 'csunicodeibm1261': + case 'isounicodeibm1261': + return 'ISO-Unicode-IBM-1261'; + + case 'csunicodeibm1264': + case 'isounicodeibm1264': + return 'ISO-Unicode-IBM-1264'; + + case 'csunicodeibm1265': + case 'isounicodeibm1265': + return 'ISO-Unicode-IBM-1265'; + + case 'csunicodeibm1268': + case 'isounicodeibm1268': + return 'ISO-Unicode-IBM-1268'; + + case 'csunicodeibm1276': + case 'isounicodeibm1276': + return 'ISO-Unicode-IBM-1276'; + + case 'csiso646basic1983': + case 'iso646basic1983': + case 'ref': + return 'ISO_646.basic:1983'; + + case 'csiso2intlrefversion': + case 'irv': + case 'iso646irv1983': + case 'isoir2': + return 'ISO_646.irv:1983'; + + case 'csiso2033': + case 'e13b': + case 'iso20331983': + case 'isoir98': + return 'ISO_2033-1983'; + + case 'csiso5427cyrillic': + case 'iso5427': + case 'isoir37': + return 'ISO_5427'; + + case 'iso5427cyrillic1981': + case 'iso54271981': + case 'isoir54': + return 'ISO_5427:1981'; + + case 'csiso5428greek': + case 'iso54281980': + case 'isoir55': + return 'ISO_5428:1980'; + + case 'csiso6937add': + case 'iso6937225': + case 'isoir152': + return 'ISO_6937-2-25'; + + case 'csisotextcomm': + case 'iso69372add': + case 'isoir142': + return 'ISO_6937-2-add'; + + case 'csiso8859supp': + case 'iso8859supp': + case 'isoir154': + case 'latin125': + return 'ISO_8859-supp'; + + case 'csiso10367box': + case 'iso10367box': + case 'isoir155': + return 'ISO_10367-box'; + + case 'csiso15italian': + case 'iso646it': + case 'isoir15': + case 'it': + return 'IT'; + + case 'csiso13jisc6220jp': + case 'isoir13': + case 'jisc62201969': + case 'jisc62201969jp': + case 'katakana': + case 'x2017': + return 'JIS_C6220-1969-jp'; + + case 'csiso14jisc6220ro': + case 'iso646jp': + case 'isoir14': + case 'jisc62201969ro': + case 'jp': + return 'JIS_C6220-1969-ro'; + + case 'csiso42jisc62261978': + case 'isoir42': + case 'jisc62261978': + return 'JIS_C6226-1978'; + + case 'csiso87jisx208': + case 'isoir87': + case 'jisc62261983': + case 'jisx2081983': + case 'x208': + return 'JIS_C6226-1983'; + + case 'csiso91jisc62291984a': + case 'isoir91': + case 'jisc62291984a': + case 'jpocra': + return 'JIS_C6229-1984-a'; + + case 'csiso92jisc62991984b': + case 'iso646jpocrb': + case 'isoir92': + case 'jisc62291984b': + case 'jpocrb': + return 'JIS_C6229-1984-b'; + + case 'csiso93jis62291984badd': + case 'isoir93': + case 'jisc62291984badd': + case 'jpocrbadd': + return 'JIS_C6229-1984-b-add'; + + case 'csiso94jis62291984hand': + case 'isoir94': + case 'jisc62291984hand': + case 'jpocrhand': + return 'JIS_C6229-1984-hand'; + + case 'csiso95jis62291984handadd': + case 'isoir95': + case 'jisc62291984handadd': + case 'jpocrhandadd': + return 'JIS_C6229-1984-hand-add'; + + case 'csiso96jisc62291984kana': + case 'isoir96': + case 'jisc62291984kana': + return 'JIS_C6229-1984-kana'; + + case 'csjisencoding': + case 'jisencoding': + return 'JIS_Encoding'; + + case 'cshalfwidthkatakana': + case 'jisx201': + case 'x201': + return 'JIS_X0201'; + + case 'csiso159jisx2121990': + case 'isoir159': + case 'jisx2121990': + case 'x212': + return 'JIS_X0212-1990'; + + case 'csiso141jusib1002': + case 'iso646yu': + case 'isoir141': + case 'js': + case 'jusib1002': + case 'yu': + return 'JUS_I.B1.002'; + + case 'csiso147macedonian': + case 'isoir147': + case 'jusib1003mac': + case 'macedonian': + return 'JUS_I.B1.003-mac'; + + case 'csiso146serbian': + case 'isoir146': + case 'jusib1003serb': + case 'serbian': + return 'JUS_I.B1.003-serb'; + + case 'koi7switched': + return 'KOI7-switched'; + + case 'cskoi8r': + case 'koi8r': + return 'KOI8-R'; + + case 'koi8u': + return 'KOI8-U'; + + case 'csksc5636': + case 'iso646kr': + case 'ksc5636': + return 'KSC5636'; + + case 'cskz1048': + case 'kz1048': + case 'rk1048': + case 'strk10482002': + return 'KZ-1048'; + + case 'csiso19latingreek': + case 'isoir19': + case 'latingreek': + return 'latin-greek'; + + case 'csiso27latingreek1': + case 'isoir27': + case 'latingreek1': + return 'Latin-greek-1'; + + case 'csiso158lap': + case 'isoir158': + case 'lap': + case 'latinlap': + return 'latin-lap'; + + case 'csmacintosh': + case 'mac': + case 'macintosh': + return 'macintosh'; + + case 'csmicrosoftpublishing': + case 'microsoftpublishing': + return 'Microsoft-Publishing'; + + case 'csmnem': + case 'mnem': + return 'MNEM'; + + case 'csmnemonic': + case 'mnemonic': + return 'MNEMONIC'; + + case 'csiso86hungarian': + case 'hu': + case 'iso646hu': + case 'isoir86': + case 'msz77953': + return 'MSZ_7795.3'; + + case 'csnatsdano': + case 'isoir91': + case 'natsdano': + return 'NATS-DANO'; + + case 'csnatsdanoadd': + case 'isoir92': + case 'natsdanoadd': + return 'NATS-DANO-ADD'; + + case 'csnatssefi': + case 'isoir81': + case 'natssefi': + return 'NATS-SEFI'; + + case 'csnatssefiadd': + case 'isoir82': + case 'natssefiadd': + return 'NATS-SEFI-ADD'; + + case 'csiso151cuba': + case 'cuba': + case 'iso646cu': + case 'isoir151': + case 'ncnc1081': + return 'NC_NC00-10:81'; + + case 'csiso69french': + case 'fr': + case 'iso646fr': + case 'isoir69': + case 'nfz62010': + return 'NF_Z_62-010'; + + case 'csiso25french': + case 'iso646fr1': + case 'isoir25': + case 'nfz620101973': + return 'NF_Z_62-010_(1973)'; + + case 'csiso60danishnorwegian': + case 'csiso60norwegian1': + case 'iso646no': + case 'isoir60': + case 'no': + case 'ns45511': + return 'NS_4551-1'; + + case 'csiso61norwegian2': + case 'iso646no2': + case 'isoir61': + case 'no2': + case 'ns45512': + return 'NS_4551-2'; + + case 'osdebcdicdf3irv': + return 'OSD_EBCDIC_DF03_IRV'; + + case 'osdebcdicdf41': + return 'OSD_EBCDIC_DF04_1'; + + case 'osdebcdicdf415': + return 'OSD_EBCDIC_DF04_15'; + + case 'cspc8danishnorwegian': + case 'pc8danishnorwegian': + return 'PC8-Danish-Norwegian'; + + case 'cspc8turkish': + case 'pc8turkish': + return 'PC8-Turkish'; + + case 'csiso16portuguese': + case 'iso646pt': + case 'isoir16': + case 'pt': + return 'PT'; + + case 'csiso84portuguese2': + case 'iso646pt2': + case 'isoir84': + case 'pt2': + return 'PT2'; + + case 'cp154': + case 'csptcp154': + case 'cyrillicasian': + case 'pt154': + case 'ptcp154': + return 'PTCP154'; + + case 'scsu': + return 'SCSU'; + + case 'csiso10swedish': + case 'fi': + case 'iso646fi': + case 'iso646se': + case 'isoir10': + case 'se': + case 'sen850200b': + return 'SEN_850200_B'; + + case 'csiso11swedishfornames': + case 'iso646se2': + case 'isoir11': + case 'se2': + case 'sen850200c': + return 'SEN_850200_C'; + + case 'csiso102t617bit': + case 'isoir102': + case 't617bit': + return 'T.61-7bit'; + + case 'csiso103t618bit': + case 'isoir103': + case 't61': + case 't618bit': + return 'T.61-8bit'; + + case 'csiso128t101g2': + case 'isoir128': + case 't101g2': + return 'T.101-G2'; + + case 'cstscii': + case 'tscii': + return 'TSCII'; + + case 'csunicode11': + case 'unicode11': + return 'UNICODE-1-1'; + + case 'csunicode11utf7': + case 'unicode11utf7': + return 'UNICODE-1-1-UTF-7'; + + case 'csunknown8bit': + case 'unknown8bit': + return 'UNKNOWN-8BIT'; + + case 'ansix341968': + case 'ansix341986': + case 'ascii': + case 'cp367': + case 'csascii': + case 'ibm367': + case 'iso646irv1991': + case 'iso646us': + case 'isoir6': + case 'us': + case 'usascii': + return 'US-ASCII'; + + case 'csusdk': + case 'usdk': + return 'us-dk'; + + case 'utf7': + return 'UTF-7'; + + case 'utf8': + return 'UTF-8'; + + case 'utf16': + return 'UTF-16'; + + case 'utf16be': + return 'UTF-16BE'; + + case 'utf16le': + return 'UTF-16LE'; + + case 'utf32': + return 'UTF-32'; + + case 'utf32be': + return 'UTF-32BE'; + + case 'utf32le': + return 'UTF-32LE'; + + case 'csventurainternational': + case 'venturainternational': + return 'Ventura-International'; + + case 'csventuramath': + case 'venturamath': + return 'Ventura-Math'; + + case 'csventuraus': + case 'venturaus': + return 'Ventura-US'; + + case 'csiso70videotexsupp1': + case 'isoir70': + case 'videotexsuppl': + return 'videotex-suppl'; + + case 'csviqr': + case 'viqr': + return 'VIQR'; + + case 'csviscii': + case 'viscii': + return 'VISCII'; + + case 'csshiftjis': + case 'cswindows31j': + case 'mskanji': + case 'shiftjis': + case 'windows31j': + return 'Windows-31J'; + + case 'iso885911': + case 'tis620': + return 'windows-874'; + + case 'cseuckr': + case 'csksc56011987': + case 'euckr': + case 'isoir149': + case 'korean': + case 'ksc5601': + case 'ksc56011987': + case 'ksc56011989': + case 'windows949': + return 'windows-949'; + + case 'windows1250': + return 'windows-1250'; + + case 'windows1251': + return 'windows-1251'; + + case 'cp819': + case 'csisolatin1': + case 'ibm819': + case 'iso88591': + case 'iso885911987': + case 'isoir100': + case 'l1': + case 'latin1': + case 'windows1252': + return 'windows-1252'; + + case 'windows1253': + return 'windows-1253'; + + case 'csisolatin5': + case 'iso88599': + case 'iso885991989': + case 'isoir148': + case 'l5': + case 'latin5': + case 'windows1254': + return 'windows-1254'; + + case 'windows1255': + return 'windows-1255'; + + case 'windows1256': + return 'windows-1256'; + + case 'windows1257': + return 'windows-1257'; + + case 'windows1258': + return 'windows-1258'; + + default: + return $charset; + } + } + + public static function get_curl_version() + { + if (is_array($curl = curl_version())) + { + $curl = $curl['version']; + } + elseif (substr($curl, 0, 5) === 'curl/') + { + $curl = substr($curl, 5, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 5)); + } + elseif (substr($curl, 0, 8) === 'libcurl/') + { + $curl = substr($curl, 8, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 8)); + } + else + { + $curl = 0; + } + return $curl; + } + + /** + * Strip HTML comments + * + * @param string $data Data to strip comments from + * @return string Comment stripped string + */ + public static function strip_comments($data) + { + $output = ''; + while (($start = strpos($data, '', $start)) !== false) + { + $data = substr_replace($data, '', 0, $end + 3); + } + else + { + $data = ''; + } + } + return $output . $data; + } + + public static function parse_date($dt) + { + $parser = SimplePie_Parse_Date::get(); + return $parser->parse($dt); + } + + /** + * Decode HTML entities + * + * @deprecated Use DOMDocument instead + * @param string $data Input data + * @return string Output data + */ + public static function entities_decode($data) + { + $decoder = new SimplePie_Decode_HTML_Entities($data); + return $decoder->parse(); + } + + /** + * Remove RFC822 comments + * + * @param string $data Data to strip comments from + * @return string Comment stripped string + */ + public static function uncomment_rfc822($string) + { + $string = (string) $string; + $position = 0; + $length = strlen($string); + $depth = 0; + + $output = ''; + + while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) + { + $output .= substr($string, $position, $pos - $position); + $position = $pos + 1; + if ($string[$pos - 1] !== '\\') + { + $depth++; + while ($depth && $position < $length) + { + $position += strcspn($string, '()', $position); + if ($string[$position - 1] === '\\') + { + $position++; + continue; + } + elseif (isset($string[$position])) + { + switch ($string[$position]) + { + case '(': + $depth++; + break; + + case ')': + $depth--; + break; + } + $position++; + } + else + { + break; + } + } + } + else + { + $output .= '('; + } + } + $output .= substr($string, $position); + + return $output; + } + + public static function parse_mime($mime) + { + if (($pos = strpos($mime, ';')) === false) + { + return trim($mime); + } + else + { + return trim(substr($mime, 0, $pos)); + } + } + + public static function atom_03_construct_type($attribs) + { + if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode']) === 'base64')) + { + $mode = SIMPLEPIE_CONSTRUCT_BASE64; + } + else + { + $mode = SIMPLEPIE_CONSTRUCT_NONE; + } + if (isset($attribs['']['type'])) + { + switch (strtolower(trim($attribs['']['type']))) + { + case 'text': + case 'text/plain': + return SIMPLEPIE_CONSTRUCT_TEXT | $mode; + + case 'html': + case 'text/html': + return SIMPLEPIE_CONSTRUCT_HTML | $mode; + + case 'xhtml': + case 'application/xhtml+xml': + return SIMPLEPIE_CONSTRUCT_XHTML | $mode; + + default: + return SIMPLEPIE_CONSTRUCT_NONE | $mode; + } + } + else + { + return SIMPLEPIE_CONSTRUCT_TEXT | $mode; + } + } + + public static function atom_10_construct_type($attribs) + { + if (isset($attribs['']['type'])) + { + switch (strtolower(trim($attribs['']['type']))) + { + case 'text': + return SIMPLEPIE_CONSTRUCT_TEXT; + + case 'html': + return SIMPLEPIE_CONSTRUCT_HTML; + + case 'xhtml': + return SIMPLEPIE_CONSTRUCT_XHTML; + + default: + return SIMPLEPIE_CONSTRUCT_NONE; + } + } + return SIMPLEPIE_CONSTRUCT_TEXT; + } + + public static function atom_10_content_construct_type($attribs) + { + if (isset($attribs['']['type'])) + { + $type = strtolower(trim($attribs['']['type'])); + switch ($type) + { + case 'text': + return SIMPLEPIE_CONSTRUCT_TEXT; + + case 'html': + return SIMPLEPIE_CONSTRUCT_HTML; + + case 'xhtml': + return SIMPLEPIE_CONSTRUCT_XHTML; + } + if (in_array(substr($type, -4), array('+xml', '/xml')) || substr($type, 0, 5) === 'text/') + { + return SIMPLEPIE_CONSTRUCT_NONE; + } + else + { + return SIMPLEPIE_CONSTRUCT_BASE64; + } + } + else + { + return SIMPLEPIE_CONSTRUCT_TEXT; + } + } + + public static function is_isegment_nz_nc($string) + { + return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string); + } + + public static function space_seperated_tokens($string) + { + $space_characters = "\x20\x09\x0A\x0B\x0C\x0D"; + $string_length = strlen($string); + + $position = strspn($string, $space_characters); + $tokens = array(); + + while ($position < $string_length) + { + $len = strcspn($string, $space_characters, $position); + $tokens[] = substr($string, $position, $len); + $position += $len; + $position += strspn($string, $space_characters, $position); + } + + return $tokens; + } + + /** + * Converts a unicode codepoint to a UTF-8 character + * + * @static + * @param int $codepoint Unicode codepoint + * @return string UTF-8 character + */ + public static function codepoint_to_utf8($codepoint) + { + $codepoint = (int) $codepoint; + if ($codepoint < 0) + { + return false; + } + else if ($codepoint <= 0x7f) + { + return chr($codepoint); + } + else if ($codepoint <= 0x7ff) + { + return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f)); + } + else if ($codepoint <= 0xffff) + { + return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); + } + else if ($codepoint <= 0x10ffff) + { + return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); + } + else + { + // U+FFFD REPLACEMENT CHARACTER + return "\xEF\xBF\xBD"; + } + } + + /** + * Similar to parse_str() + * + * Returns an associative array of name/value pairs, where the value is an + * array of values that have used the same name + * + * @static + * @param string $str The input string. + * @return array + */ + public static function parse_str($str) + { + $return = array(); + $str = explode('&', $str); + + foreach ($str as $section) + { + if (strpos($section, '=') !== false) + { + list($name, $value) = explode('=', $section, 2); + $return[urldecode($name)][] = urldecode($value); + } + else + { + $return[urldecode($section)][] = null; + } + } + + return $return; + } + + /** + * Detect XML encoding, as per XML 1.0 Appendix F.1 + * + * @todo Add support for EBCDIC + * @param string $data XML data + * @param SimplePie_Registry $registry Class registry + * @return array Possible encodings + */ + public static function xml_encoding($data, $registry) + { + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") + { + $encoding[] = 'UTF-32BE'; + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") + { + $encoding[] = 'UTF-32LE'; + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") + { + $encoding[] = 'UTF-16BE'; + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") + { + $encoding[] = 'UTF-16LE'; + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") + { + $encoding[] = 'UTF-8'; + } + // UTF-32 Big Endian Without BOM + elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C") + { + if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E")) + { + $parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8'))); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-32BE'; + } + // UTF-32 Little Endian Without BOM + elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00") + { + if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00")) + { + $parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8'))); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-32LE'; + } + // UTF-16 Big Endian Without BOM + elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C") + { + if ($pos = strpos($data, "\x00\x3F\x00\x3E")) + { + $parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8'))); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-16BE'; + } + // UTF-16 Little Endian Without BOM + elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00") + { + if ($pos = strpos($data, "\x3F\x00\x3E\x00")) + { + $parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8'))); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-16LE'; + } + // US-ASCII (or superset) + elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C") + { + if ($pos = strpos($data, "\x3F\x3E")) + { + $parser = $registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5))); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-8'; + } + // Fallback to UTF-8 + else + { + $encoding[] = 'UTF-8'; + } + return $encoding; + } + + public static function output_javascript() + { + if (function_exists('ob_gzhandler')) + { + ob_start('ob_gzhandler'); + } + header('Content-type: text/javascript; charset: UTF-8'); + header('Cache-Control: must-revalidate'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days + ?> + function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) { + if (placeholder != '') { + document.writeln(''); + } + else { + document.writeln(''); + } + } + + function embed_flash(bgcolor, width, height, link, loop, type) { + document.writeln(''); + } + + function embed_flv(width, height, link, placeholder, loop, player) { + document.writeln(''); + } + + function embed_wmedia(width, height, link) { + document.writeln(''); + } + $time) + { + $time = $mtime; + } + } + return $time; + } + elseif (file_exists(dirname(__FILE__) . '/Core.php')) + { + return filemtime(dirname(__FILE__) . '/Core.php'); + } + else + { + return filemtime(__FILE__); + } + } + + /** + * Format debugging information + */ + public static function debug(&$sp) + { + $info = 'SimplePie ' . SIMPLEPIE_VERSION . ' Build ' . SIMPLEPIE_BUILD . "\n"; + $info .= 'PHP ' . PHP_VERSION . "\n"; + if ($sp->error() !== null) + { + $info .= 'Error occurred: ' . $sp->error() . "\n"; + } + else + { + $info .= "No error found.\n"; + } + $info .= "Extensions:\n"; + $extensions = array('pcre', 'curl', 'zlib', 'mbstring', 'iconv', 'xmlreader', 'xml'); + foreach ($extensions as $ext) + { + if (extension_loaded($ext)) + { + $info .= " $ext loaded\n"; + switch ($ext) + { + case 'pcre': + $info .= ' Version ' . PCRE_VERSION . "\n"; + break; + case 'curl': + $version = curl_version(); + $info .= ' Version ' . $version['version'] . "\n"; + break; + case 'mbstring': + $info .= ' Overloading: ' . mb_get_info('func_overload') . "\n"; + break; + case 'iconv': + $info .= ' Version ' . ICONV_VERSION . "\n"; + break; + case 'xml': + $info .= ' Version ' . LIBXML_DOTTED_VERSION . "\n"; + break; + } + } + else + { + $info .= " $ext not loaded\n"; + } + } + return $info; + } + + public static function silence_errors($num, $str) + { + // No-op + } +} + +/** + * Manages all item-related data + * + * Used by {@see SimplePie::get_item()} and {@see SimplePie::get_items()} + * + * This class can be overloaded with {@see SimplePie::set_item_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Item +{ + /** + * Parent feed + * + * @access private + * @var SimplePie + */ + var $feed; + + /** + * Raw data + * + * @access private + * @var array + */ + var $data = array(); + + /** + * Registry object + * + * @see set_registry + * @var SimplePie_Registry + */ + protected $registry; + + /** + * Create a new item object + * + * This is usually used by {@see SimplePie::get_items} and + * {@see SimplePie::get_item}. Avoid creating this manually. + * + * @param SimplePie $feed Parent feed + * @param array $data Raw data + */ + public function __construct($feed, $data) + { + $this->feed = $feed; + $this->data = $data; + } + + /** + * Set the registry handler + * + * This is usually used by {@see SimplePie_Registry::create} + * + * @since 1.3 + * @param SimplePie_Registry $registry + */ + public function set_registry(SimplePie_Registry $registry) + { + $this->registry = $registry; + } + + /** + * Get a string representation of the item + * + * @return string + */ + public function __toString() + { + return md5(serialize($this->data)); + } + + /** + * Remove items that link back to this before destroying this object + */ + public function __destruct() + { + if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode')) + { + unset($this->feed); + } + } + + /** + * Get data for an item-level element + * + * This method allows you to get access to ANY element/attribute that is a + * sub-element of the item/entry tag. + * + * See {@see SimplePie::get_feed_tags()} for a description of the return value + * + * @since 1.0 + * @see http://simplepie.org/wiki/faq/supported_xml_namespaces + * @param string $namespace The URL of the XML namespace of the elements you're trying to access + * @param string $tag Tag name + * @return array + */ + public function get_item_tags($namespace, $tag) + { + if (isset($this->data['child'][$namespace][$tag])) + { + return $this->data['child'][$namespace][$tag]; + } + else + { + return null; + } + } + + /** + * Get the base URL value from the parent feed + * + * Uses `` + * + * @param array $element + * @return string + */ + public function get_base($element = array()) + { + return $this->feed->get_base($element); + } + + /** + * Sanitize feed data + * + * @access private + * @see SimplePie::sanitize() + * @param string $data Data to sanitize + * @param int $type One of the SIMPLEPIE_CONSTRUCT_* constants + * @param string $base Base URL to resolve URLs against + * @return string Sanitized data + */ + public function sanitize($data, $type, $base = '') + { + return $this->feed->sanitize($data, $type, $base); + } + + /** + * Get the parent feed + * + * Note: this may not work as you think for multifeeds! + * + * @link http://simplepie.org/faq/typical_multifeed_gotchas#missing_data_from_feed + * @since 1.0 + * @return SimplePie + */ + public function get_feed() + { + return $this->feed; + } + + /** + * Get the unique identifier for the item + * + * This is usually used when writing code to check for new items in a feed. + * + * Uses ``, ``, `` or the `about` attribute + * for RDF. If none of these are supplied (or `$hash` is true), creates an + * MD5 hash based on the permalink and title. If either of those are not + * supplied, creates a hash based on the full feed data. + * + * @since Beta 2 + * @param boolean $hash Should we force using a hash instead of the supplied ID? + * @return string + */ + public function get_id($hash = false) + { + if (!$hash) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'identifier')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'identifier')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($this->data['attribs'][SIMPLEPIE_NAMESPACE_RDF]['about'])) + { + return $this->sanitize($this->data['attribs'][SIMPLEPIE_NAMESPACE_RDF]['about'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (($return = $this->get_permalink()) !== null) + { + return $return; + } + elseif (($return = $this->get_title()) !== null) + { + return $return; + } + } + if ($this->get_permalink() !== null || $this->get_title() !== null) + { + return md5($this->get_permalink() . $this->get_title()); + } + else + { + return md5(serialize($this->data)); + } + } + + /** + * Get the title of the item + * + * Uses ``, `` or `<dc:title>` + * + * @since Beta 2 (previously called `get_item_title` since 0.8) + * @return string|null + */ + public function get_title() + { + if (!isset($this->data['title'])) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $this->data['title'] = null; + } + } + return $this->data['title']; + } + + /** + * Get the content for the item + * + * Prefers summaries over full content , but will return full content if a + * summary does not exist. + * + * To prefer full content instead, use {@see get_content} + * + * Uses `<atom:summary>`, `<description>`, `<dc:description>` or + * `<itunes:subtitle>` + * + * @since 0.8 + * @param boolean $description_only Should we avoid falling back to the content? + * @return string|null + */ + public function get_description($description_only = false) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'summary')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'summary')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML); + } + + elseif (!$description_only) + { + return $this->get_content(true); + } + else + { + return null; + } + } + + /** + * Get the content for the item + * + * Prefers full content over summaries, but will return a summary if full + * content does not exist. + * + * To prefer summaries instead, use {@see get_description} + * + * Uses `<atom:content>` or `<content:encoded>` (RSS 1.0 Content Module) + * + * @since 1.0 + * @param boolean $content_only Should we avoid falling back to the description? + * @return string|null + */ + public function get_content($content_only = false) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'content')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_content_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'content')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif (!$content_only) + { + return $this->get_description(true); + } + else + { + return null; + } + } + + /** + * Get a category for the item + * + * @since Beta 3 (previously called `get_categories()` since Beta 2) + * @param int $key The category that you want to return. Remember that arrays begin with 0, not 1 + * @return SimplePie_Category|null + */ + public function get_category($key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) + { + return $categories[$key]; + } + else + { + return null; + } + } + + /** + * Get all categories for the item + * + * Uses `<atom:category>`, `<category>` or `<dc:subject>` + * + * @since Beta 3 + * @return array|null List of {@see SimplePie_Category} objects + */ + public function get_categories() + { + $categories = array(); + + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['attribs']['']['term'])) + { + $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) + { + // This is really the label, but keep this as the term also for BC. + // Label will also work on retrieving because that falls back to term. + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + if (isset($category['attribs']['']['domain'])) + { + $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = null; + } + $categories[] = $this->registry->create('Category', array($term, $scheme, null)); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) + { + $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) + { + $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + + if (!empty($categories)) + { + return array_unique($categories); + } + else + { + return null; + } + } + + /** + * Get an author for the item + * + * @since Beta 2 + * @param int $key The author that you want to return. Remember that arrays begin with 0, not 1 + * @return SimplePie_Author|null + */ + public function get_author($key = 0) + { + $authors = $this->get_authors(); + if (isset($authors[$key])) + { + return $authors[$key]; + } + else + { + return null; + } + } + + /** + * Get a contributor for the item + * + * @since 1.1 + * @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1 + * @return SimplePie_Author|null + */ + public function get_contributor($key = 0) + { + $contributors = $this->get_contributors(); + if (isset($contributors[$key])) + { + return $contributors[$key]; + } + else + { + return null; + } + } + + /** + * Get all contributors for the item + * + * Uses `<atom:contributor>` + * + * @since 1.1 + * @return array|null List of {@see SimplePie_Author} objects + */ + public function get_contributors() + { + $contributors = array(); + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) + { + $name = null; + $uri = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $contributors[] = $this->registry->create('Author', array($name, $uri, $email)); + } + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) + { + $name = null; + $url = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $contributors[] = $this->registry->create('Author', array($name, $url, $email)); + } + } + + if (!empty($contributors)) + { + return array_unique($contributors); + } + else + { + return null; + } + } + + /** + * Get all authors for the item + * + * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>` + * + * @since Beta 2 + * @return array|null List of {@see SimplePie_Author} objects + */ + public function get_authors() + { + $authors = array(); + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) + { + $name = null; + $uri = null; + $email = null; + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $authors[] = $this->registry->create('Author', array($name, $uri, $email)); + } + } + if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) + { + $name = null; + $url = null; + $email = null; + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $authors[] = $this->registry->create('Author', array($name, $url, $email)); + } + } + if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'author')) + { + $authors[] = $this->registry->create('Author', array(null, null, $this->sanitize($author[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT))); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) + { + $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) + { + $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) + { + $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + + if (!empty($authors)) + { + return array_unique($authors); + } + elseif (($source = $this->get_source()) && ($authors = $source->get_authors())) + { + return $authors; + } + elseif ($authors = $this->feed->get_authors()) + { + return $authors; + } + else + { + return null; + } + } + + /** + * Get the copyright info for the item + * + * Uses `<atom:rights>` or `<dc:rights>` + * + * @since 1.1 + * @return string + */ + public function get_copyright() + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + /** + * Get the posting date/time for the item + * + * Uses `<atom:published>`, `<atom:updated>`, `<atom:issued>`, + * `<atom:modified>`, `<pubDate>` or `<dc:date>` + * + * Note: obeys PHP's timezone setting. To get a UTC date/time, use + * {@see get_gmdate} + * + * @since Beta 2 (previously called `get_item_date` since 0.8) + * + * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data) + * @return int|string|null + */ + public function get_date($date_format = 'j F Y, g:i a') + { + if (!isset($this->data['date'])) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + + if (!empty($this->data['date']['raw'])) + { + $parser = $this->registry->call('Parse_Date', 'get'); + $this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']); + } + else + { + $this->data['date'] = null; + } + } + if ($this->data['date']) + { + $date_format = (string) $date_format; + switch ($date_format) + { + case '': + return $this->sanitize($this->data['date']['raw'], SIMPLEPIE_CONSTRUCT_TEXT); + + case 'U': + return $this->data['date']['parsed']; + + default: + return date($date_format, $this->data['date']['parsed']); + } + } + else + { + return null; + } + } + + /** + * Get the update date/time for the item + * + * Uses `<atom:updated>` + * + * Note: obeys PHP's timezone setting. To get a UTC date/time, use + * {@see get_gmdate} + * + * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data) + * @return int|string|null + */ + public function get_updated_date($date_format = 'j F Y, g:i a') + { + if (!isset($this->data['updated'])) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated')) + { + $this->data['updated']['raw'] = $return[0]['data']; + } + + if (!empty($this->data['updated']['raw'])) + { + $parser = $this->registry->call('Parse_Date', 'get'); + // $this->data['updated']['parsed'] = $parser->parse($this->data['date']['raw']); // Leed SimplePie en v1.3.1 + $this->data['updated']['parsed'] = $parser->parse($this->data['updated']['raw']); + } + else + { + $this->data['updated'] = null; + } + } + if ($this->data['updated']) + { + $date_format = (string) $date_format; + switch ($date_format) + { + case '': + return $this->sanitize($this->data['updated']['raw'], SIMPLEPIE_CONSTRUCT_TEXT); + + case 'U': + return $this->data['updated']['parsed']; + + default: + return date($date_format, $this->data['updated']['parsed']); + } + } + else + { + return null; + } + } + + /** + * Get the localized posting date/time for the item + * + * Returns the date formatted in the localized language. To display in + * languages other than the server's default, you need to change the locale + * with {@link http://php.net/setlocale setlocale()}. The available + * localizations depend on which ones are installed on your web server. + * + * @since 1.0 + * + * @param string $date_format Supports any PHP date format from {@see http://php.net/strftime} (empty for the raw data) + * @return int|string|null + */ + public function get_local_date($date_format = '%c') + { + if (!$date_format) + { + return $this->sanitize($this->get_date(''), SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (($date = $this->get_date('U')) !== null && $date !== false) + { + return strftime($date_format, $date); + } + else + { + return null; + } + } + + /** + * Get the posting date/time for the item (UTC time) + * + * @see get_date + * @param string $date_format Supports any PHP date format from {@see http://php.net/date} + * @return int|string|null + */ + public function get_gmdate($date_format = 'j F Y, g:i a') + { + $date = $this->get_date('U'); + if ($date === null) + { + return null; + } + + return gmdate($date_format, $date); + } + + /** + * Get the update date/time for the item (UTC time) + * + * @see get_updated_date + * @param string $date_format Supports any PHP date format from {@see http://php.net/date} + * @return int|string|null + */ + public function get_updated_gmdate($date_format = 'j F Y, g:i a') + { + $date = $this->get_updated_date('U'); + if ($date === null) + { + return null; + } + + return gmdate($date_format, $date); + } + + /** + * Get the permalink for the item + * + * Returns the first link available with a relationship of "alternate". + * Identical to {@see get_link()} with key 0 + * + * @see get_link + * @since 0.8 + * @return string|null Permalink URL + */ + public function get_permalink() + { + $link = $this->get_link(); + $enclosure = $this->get_enclosure(0); + if ($link !== null) + { + return $link; + } + elseif ($enclosure !== null) + { + return $enclosure->get_link(); + } + else + { + return null; + } + } + + /** + * Get a single link for the item + * + * @since Beta 3 + * @param int $key The link that you want to return. Remember that arrays begin with 0, not 1 + * @param string $rel The relationship of the link to return + * @return string|null Link URL + */ + public function get_link($key = 0, $rel = 'alternate') + { + $links = $this->get_links($rel); + if ($links[$key] !== null) + { + return $links[$key]; + } + else + { + return null; + } + } + + /** + * Get all links for the item + * + * Uses `<atom:link>`, `<link>` or `<guid>` + * + * @since Beta 2 + * @param string $rel The relationship of links to return + * @return array|null Links found for the item (strings) + */ + public function get_links($rel = 'alternate') + { + if (!isset($this->data['links'])) + { + $this->data['links'] = array(); + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + + } + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + } + } + if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) + { + if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true') + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + } + + $keys = array_keys($this->data['links']); + foreach ($keys as $key) + { + if ($this->registry->call('Misc', 'is_isegment_nz_nc', array($key))) + { + if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); + $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; + } + else + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; + } + } + elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) + { + $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; + } + $this->data['links'][$key] = array_unique($this->data['links'][$key]); + } + } + if (isset($this->data['links'][$rel])) + { + return $this->data['links'][$rel]; + } + else + { + return null; + } + } + + /** + * Get an enclosure from the item + * + * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. + * + * @since Beta 2 + * @todo Add ability to prefer one type of content over another (in a media group). + * @param int $key The enclosure that you want to return. Remember that arrays begin with 0, not 1 + * @return SimplePie_Enclosure|null + */ + public function get_enclosure($key = 0, $prefer = null) + { + $enclosures = $this->get_enclosures(); + if (isset($enclosures[$key])) + { + return $enclosures[$key]; + } + else + { + return null; + } + } + + /** + * Get all available enclosures (podcasts, etc.) + * + * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. + * + * At this point, we're pretty much assuming that all enclosures for an item + * are the same content. Anything else is too complicated to + * properly support. + * + * @since Beta 2 + * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4). + * @todo If an element exists at a level, but its value is empty, we should fall back to the value from the parent (if it exists). + * @return array|null List of SimplePie_Enclosure items + */ + public function get_enclosures() + { + if (!isset($this->data['enclosures'])) + { + $this->data['enclosures'] = array(); + + // Elements + $captions_parent = null; + $categories_parent = null; + $copyrights_parent = null; + $credits_parent = null; + $description_parent = null; + $duration_parent = null; + $hashes_parent = null; + $keywords_parent = null; + $player_parent = null; + $ratings_parent = null; + $restrictions_parent = null; + $thumbnails_parent = null; + $title_parent = null; + + // Let's do the channel and item-level ones first, and just re-use them if we need to. + $parent = $this->get_feed(); + + // CAPTIONS + if ($captions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text')) + { + foreach ($captions as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions_parent[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); + } + } + elseif ($captions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text')) + { + foreach ($captions as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions_parent[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); + } + } + if (is_array($captions_parent)) + { + $captions_parent = array_values(array_unique($captions_parent)); + } + + // CATEGORIES + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label)); + } + foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label)); + } + foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'category') as $category) + { + $term = null; + $scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + $label = null; + if (isset($category['attribs']['']['text'])) + { + $label = $this->sanitize($category['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label)); + + if (isset($category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'])) + { + foreach ((array) $category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'] as $subcategory) + { + if (isset($subcategory['attribs']['']['text'])) + { + $label = $this->sanitize($subcategory['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label)); + } + } + } + if (is_array($categories_parent)) + { + $categories_parent = array_values(array_unique($categories_parent)); + } + + // COPYRIGHT + if ($copyright = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright')) + { + $copyright_url = null; + $copyright_label = null; + if (isset($copyright[0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($copyright[0]['data'])) + { + $copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights_parent = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); + } + elseif ($copyright = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright')) + { + $copyright_url = null; + $copyright_label = null; + if (isset($copyright[0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($copyright[0]['data'])) + { + $copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights_parent = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); + } + + // CREDITS + if ($credits = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit')) + { + foreach ($credits as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits_parent[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); + } + } + elseif ($credits = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit')) + { + foreach ($credits as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits_parent[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); + } + } + if (is_array($credits_parent)) + { + $credits_parent = array_values(array_unique($credits_parent)); + } + + // DESCRIPTION + if ($description_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description')) + { + if (isset($description_parent[0]['data'])) + { + $description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + } + elseif ($description_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description')) + { + if (isset($description_parent[0]['data'])) + { + $description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + } + + // DURATION + if ($duration_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'duration')) + { + $seconds = null; + $minutes = null; + $hours = null; + if (isset($duration_parent[0]['data'])) + { + $temp = explode(':', $this->sanitize($duration_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + if (sizeof($temp) > 0) + { + $seconds = (int) array_pop($temp); + } + if (sizeof($temp) > 0) + { + $minutes = (int) array_pop($temp); + $seconds += $minutes * 60; + } + if (sizeof($temp) > 0) + { + $hours = (int) array_pop($temp); + $seconds += $hours * 3600; + } + unset($temp); + $duration_parent = $seconds; + } + } + + // HASHES + if ($hashes_iterator = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash')) + { + foreach ($hashes_iterator as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes_parent[] = $algo.':'.$value; + } + } + elseif ($hashes_iterator = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash')) + { + foreach ($hashes_iterator as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes_parent[] = $algo.':'.$value; + } + } + if (is_array($hashes_parent)) + { + $hashes_parent = array_values(array_unique($hashes_parent)); + } + + // KEYWORDS + if ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords')) + { + if (isset($keywords[0]['data'])) + { + $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + elseif ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords')) + { + if (isset($keywords[0]['data'])) + { + $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords')) + { + if (isset($keywords[0]['data'])) + { + $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords')) + { + if (isset($keywords[0]['data'])) + { + $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + if (is_array($keywords_parent)) + { + $keywords_parent = array_values(array_unique($keywords_parent)); + } + + // PLAYER + if ($player_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player')) + { + if (isset($player_parent[0]['attribs']['']['url'])) + { + $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + } + elseif ($player_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player')) + { + if (isset($player_parent[0]['attribs']['']['url'])) + { + $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + } + + // RATINGS + if ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating')) + { + foreach ($ratings as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); + } + } + elseif ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit')) + { + foreach ($ratings as $rating) + { + $rating_scheme = 'urn:itunes'; + $rating_value = null; + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); + } + } + elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating')) + { + foreach ($ratings as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); + } + } + elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit')) + { + foreach ($ratings as $rating) + { + $rating_scheme = 'urn:itunes'; + $rating_value = null; + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); + } + } + if (is_array($ratings_parent)) + { + $ratings_parent = array_values(array_unique($ratings_parent)); + } + + // RESTRICTIONS + if ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction')) + { + foreach ($restrictions as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); + } + } + elseif ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block')) + { + foreach ($restrictions as $restriction) + { + $restriction_relationship = 'allow'; + $restriction_type = null; + $restriction_value = 'itunes'; + if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') + { + $restriction_relationship = 'deny'; + } + $restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); + } + } + elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction')) + { + foreach ($restrictions as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); + } + } + elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block')) + { + foreach ($restrictions as $restriction) + { + $restriction_relationship = 'allow'; + $restriction_type = null; + $restriction_value = 'itunes'; + if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') + { + $restriction_relationship = 'deny'; + } + $restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); + } + } + if (is_array($restrictions_parent)) + { + $restrictions_parent = array_values(array_unique($restrictions_parent)); + } + else + { + $restrictions_parent = array(new SimplePie_Restriction('allow', null, 'default')); + } + + // THUMBNAILS + if ($thumbnails = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail')) + { + foreach ($thumbnails as $thumbnail) + { + if (isset($thumbnail['attribs']['']['url'])) + { + $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + } + } + elseif ($thumbnails = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail')) + { + foreach ($thumbnails as $thumbnail) + { + if (isset($thumbnail['attribs']['']['url'])) + { + $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + } + } + + // TITLES + if ($title_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title')) + { + if (isset($title_parent[0]['data'])) + { + $title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + } + elseif ($title_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title')) + { + if (isset($title_parent[0]['data'])) + { + $title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + } + + // Clear the memory + unset($parent); + + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // If we have media:group tags, loop through them. + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group') as $group) + { + if(isset($group['child']) && isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'])) + { + // If we have media:content tags, loop through them. + foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content) + { + if (isset($content['attribs']['']['url'])) + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // Start checking the attributes of media:content + if (isset($content['attribs']['']['bitrate'])) + { + $bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['channels'])) + { + $channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['duration'])) + { + $duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $duration = $duration_parent; + } + if (isset($content['attribs']['']['expression'])) + { + $expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['framerate'])) + { + $framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['height'])) + { + $height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['lang'])) + { + $lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['fileSize'])) + { + $length = ceil($content['attribs']['']['fileSize']); + } + if (isset($content['attribs']['']['medium'])) + { + $medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['samplingrate'])) + { + $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['type'])) + { + $type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['width'])) + { + $width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + + // Checking the other optional media: elements. Priority: media:content, media:group, item, channel + + // CAPTIONS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); + } + if (is_array($captions)) + { + $captions = array_values(array_unique($captions)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); + } + if (is_array($captions)) + { + $captions = array_values(array_unique($captions)); + } + } + else + { + $captions = $captions_parent; + } + + // CATEGORIES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) + { + foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); + } + } + if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) + { + foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); + } + } + if (is_array($categories) && is_array($categories_parent)) + { + $categories = array_values(array_unique(array_merge($categories, $categories_parent))); + } + elseif (is_array($categories)) + { + $categories = array_values(array_unique($categories)); + } + elseif (is_array($categories_parent)) + { + $categories = array_values(array_unique($categories_parent)); + } + + // COPYRIGHTS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) + { + $copyright_url = null; + $copyright_label = null; + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) + { + $copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) + { + $copyright_url = null; + $copyright_label = null; + if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) + { + $copyright_label = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); + } + else + { + $copyrights = $copyrights_parent; + } + + // CREDITS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); + } + if (is_array($credits)) + { + $credits = array_values(array_unique($credits)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); + } + if (is_array($credits)) + { + $credits = array_values(array_unique($credits)); + } + } + else + { + $credits = $credits_parent; + } + + // DESCRIPTION + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) + { + $description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) + { + $description = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $description = $description_parent; + } + + // HASHES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) + { + $hashes = array_values(array_unique($hashes)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) + { + $hashes = array_values(array_unique($hashes)); + } + } + else + { + $hashes = $hashes_parent; + } + + // KEYWORDS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) + { + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) + { + $temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) + { + $keywords = array_values(array_unique($keywords)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) + { + if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) + { + $temp = explode(',', $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) + { + $keywords = array_values(array_unique($keywords)); + } + } + else + { + $keywords = $keywords_parent; + } + + // PLAYER + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) + { + $player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) + { + $player = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + $player = $player_parent; + } + + // RATINGS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); + } + if (is_array($ratings)) + { + $ratings = array_values(array_unique($ratings)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); + } + if (is_array($ratings)) + { + $ratings = array_values(array_unique($ratings)); + } + } + else + { + $ratings = $ratings_parent; + } + + // RESTRICTIONS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); + } + if (is_array($restrictions)) + { + $restrictions = array_values(array_unique($restrictions)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); + } + if (is_array($restrictions)) + { + $restrictions = array_values(array_unique($restrictions)); + } + } + else + { + $restrictions = $restrictions_parent; + } + + // THUMBNAILS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) + { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + if (is_array($thumbnails)) + { + $thumbnails = array_values(array_unique($thumbnails)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) + { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + if (is_array($thumbnails)) + { + $thumbnails = array_values(array_unique($thumbnails)); + } + } + else + { + $thumbnails = $thumbnails_parent; + } + + // TITLES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) + { + $title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) + { + $title = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $title = $title_parent; + } + + $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width)); + } + } + } + } + + // If we have standalone media:content tags, loop through them. + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'])) + { + foreach ((array) $this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content) + { + if (isset($content['attribs']['']['url']) || isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // Start checking the attributes of media:content + if (isset($content['attribs']['']['bitrate'])) + { + $bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['channels'])) + { + $channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['duration'])) + { + $duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $duration = $duration_parent; + } + if (isset($content['attribs']['']['expression'])) + { + $expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['framerate'])) + { + $framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['height'])) + { + $height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['lang'])) + { + $lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['fileSize'])) + { + $length = ceil($content['attribs']['']['fileSize']); + } + if (isset($content['attribs']['']['medium'])) + { + $medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['samplingrate'])) + { + $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['type'])) + { + $type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['width'])) + { + $width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['url'])) + { + $url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + // Checking the other optional media: elements. Priority: media:content, media:group, item, channel + + // CAPTIONS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text)); + } + if (is_array($captions)) + { + $captions = array_values(array_unique($captions)); + } + } + else + { + $captions = $captions_parent; + } + + // CATEGORIES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) + { + foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); + } + } + if (is_array($categories) && is_array($categories_parent)) + { + $categories = array_values(array_unique(array_merge($categories, $categories_parent))); + } + elseif (is_array($categories)) + { + $categories = array_values(array_unique($categories)); + } + elseif (is_array($categories_parent)) + { + $categories = array_values(array_unique($categories_parent)); + } + else + { + $categories = null; + } + + // COPYRIGHTS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) + { + $copyright_url = null; + $copyright_label = null; + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) + { + $copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label)); + } + else + { + $copyrights = $copyrights_parent; + } + + // CREDITS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name)); + } + if (is_array($credits)) + { + $credits = array_values(array_unique($credits)); + } + } + else + { + $credits = $credits_parent; + } + + // DESCRIPTION + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) + { + $description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $description = $description_parent; + } + + // HASHES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) + { + $hashes = array_values(array_unique($hashes)); + } + } + else + { + $hashes = $hashes_parent; + } + + // KEYWORDS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) + { + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) + { + $temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) + { + $keywords = array_values(array_unique($keywords)); + } + } + else + { + $keywords = $keywords_parent; + } + + // PLAYER + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) + { + $player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + $player = $player_parent; + } + + // RATINGS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value)); + } + if (is_array($ratings)) + { + $ratings = array_values(array_unique($ratings)); + } + } + else + { + $ratings = $ratings_parent; + } + + // RESTRICTIONS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value)); + } + if (is_array($restrictions)) + { + $restrictions = array_values(array_unique($restrictions)); + } + } + else + { + $restrictions = $restrictions_parent; + } + + // THUMBNAILS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) + { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + if (is_array($thumbnails)) + { + $thumbnails = array_values(array_unique($thumbnails)); + } + } + else + { + $thumbnails = $thumbnails_parent; + } + + // TITLES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) + { + $title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $title = $title_parent; + } + + $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width)); + } + } + } + + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link) + { + if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + if (isset($link['attribs']['']['type'])) + { + $type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($link['attribs']['']['length'])) + { + $length = ceil($link['attribs']['']['length']); + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width)); + } + } + + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link) + { + if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + if (isset($link['attribs']['']['type'])) + { + $type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($link['attribs']['']['length'])) + { + $length = ceil($link['attribs']['']['length']); + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width)); + } + } + + if ($enclosure = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure')) + { + if (isset($enclosure[0]['attribs']['']['url'])) + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($enclosure[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure[0])); + if (isset($enclosure[0]['attribs']['']['type'])) + { + $type = $this->sanitize($enclosure[0]['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($enclosure[0]['attribs']['']['length'])) + { + $length = ceil($enclosure[0]['attribs']['']['length']); + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width)); + } + } + + if (sizeof($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $restrictions_parent || $samplingrate || $thumbnails_parent || $title_parent || $width)) + { + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width)); + } + + $this->data['enclosures'] = array_values(array_unique($this->data['enclosures'])); + } + if (!empty($this->data['enclosures'])) + { + return $this->data['enclosures']; + } + else + { + return null; + } + } + + /** + * Get the latitude coordinates for the item + * + * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications + * + * Uses `<geo:lat>` or `<georss:point>` + * + * @since 1.0 + * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo + * @link http://www.georss.org/ GeoRSS + * @return string|null + */ + public function get_latitude() + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[1]; + } + else + { + return null; + } + } + + /** + * Get the longitude coordinates for the item + * + * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications + * + * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>` + * + * @since 1.0 + * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo + * @link http://www.georss.org/ GeoRSS + * @return string|null + */ + public function get_longitude() + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) + { + return (float) $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[2]; + } + else + { + return null; + } + } + + /** + * Get the `<atom:source>` for the item + * + * @since 1.1 + * @return SimplePie_Source|null + */ + public function get_source() + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'source')) + { + return $this->registry->create('Source', array($this, $return[0])); + } + else + { + return null; + } + } +} + +/** + * Manages all category-related data + * + * Used by {@see SimplePie_Item::get_category()} and {@see SimplePie_Item::get_categories()} + * + * This class can be overloaded with {@see SimplePie::set_category_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Category +{ + /** + * Category identifier + * + * @var string + * @see get_term + */ + var $term; + + /** + * Categorization scheme identifier + * + * @var string + * @see get_scheme() + */ + var $scheme; + + /** + * Human readable label + * + * @var string + * @see get_label() + */ + var $label; + + /** + * Constructor, used to input the data + * + * @param string $term + * @param string $scheme + * @param string $label + */ + public function __construct($term = null, $scheme = null, $label = null) + { + $this->term = $term; + $this->scheme = $scheme; + $this->label = $label; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the category identifier + * + * @return string|null + */ + public function get_term() + { + if ($this->term !== null) + { + return $this->term; + } + else + { + return null; + } + } + + /** + * Get the categorization scheme identifier + * + * @return string|null + */ + public function get_scheme() + { + if ($this->scheme !== null) + { + return $this->scheme; + } + else + { + return null; + } + } + + /** + * Get the human readable label + * + * @return string|null + */ + public function get_label() + { + if ($this->label !== null) + { + return $this->label; + } + else + { + return $this->get_term(); + } + } +} + +/** + * Handles `<atom:source>` + * + * Used by {@see SimplePie_Item::get_source()} + * + * This class can be overloaded with {@see SimplePie::set_source_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Source +{ + var $item; + var $data = array(); + protected $registry; + + public function __construct($item, $data) + { + $this->item = $item; + $this->data = $data; + } + + public function set_registry(SimplePie_Registry $registry) + { + $this->registry = $registry; + } + + public function __toString() + { + return md5(serialize($this->data)); + } + + public function get_source_tags($namespace, $tag) + { + if (isset($this->data['child'][$namespace][$tag])) + { + return $this->data['child'][$namespace][$tag]; + } + else + { + return null; + } + } + + public function get_base($element = array()) + { + return $this->item->get_base($element); + } + + public function sanitize($data, $type, $base = '') + { + return $this->item->sanitize($data, $type, $base); + } + + public function get_item() + { + return $this->item; + } + + public function get_title() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + public function get_category($key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) + { + return $categories[$key]; + } + else + { + return null; + } + } + + public function get_categories() + { + $categories = array(); + + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['attribs']['']['term'])) + { + $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = $this->registry->create('Category', array($term, $scheme, $label)); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) + { + // This is really the label, but keep this as the term also for BC. + // Label will also work on retrieving because that falls back to term. + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + if (isset($category['attribs']['']['domain'])) + { + $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = null; + } + $categories[] = $this->registry->create('Category', array($term, $scheme, null)); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) + { + $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) + { + $categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + + if (!empty($categories)) + { + return array_unique($categories); + } + else + { + return null; + } + } + + public function get_author($key = 0) + { + $authors = $this->get_authors(); + if (isset($authors[$key])) + { + return $authors[$key]; + } + else + { + return null; + } + } + + public function get_authors() + { + $authors = array(); + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) + { + $name = null; + $uri = null; + $email = null; + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $authors[] = $this->registry->create('Author', array($name, $uri, $email)); + } + } + if ($author = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) + { + $name = null; + $url = null; + $email = null; + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $authors[] = $this->registry->create('Author', array($name, $url, $email)); + } + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) + { + $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) + { + $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) + { + $authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null)); + } + + if (!empty($authors)) + { + return array_unique($authors); + } + else + { + return null; + } + } + + public function get_contributor($key = 0) + { + $contributors = $this->get_contributors(); + if (isset($contributors[$key])) + { + return $contributors[$key]; + } + else + { + return null; + } + } + + public function get_contributors() + { + $contributors = array(); + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) + { + $name = null; + $uri = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $contributors[] = $this->registry->create('Author', array($name, $uri, $email)); + } + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) + { + $name = null; + $url = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $contributors[] = $this->registry->create('Author', array($name, $url, $email)); + } + } + + if (!empty($contributors)) + { + return array_unique($contributors); + } + else + { + return null; + } + } + + public function get_link($key = 0, $rel = 'alternate') + { + $links = $this->get_links($rel); + if (isset($links[$key])) + { + return $links[$key]; + } + else + { + return null; + } + } + + /** + * Added for parity between the parent-level and the item/entry-level. + */ + public function get_permalink() + { + return $this->get_link(0); + } + + public function get_links($rel = 'alternate') + { + if (!isset($this->data['links'])) + { + $this->data['links'] = array(); + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link')) + { + foreach ($links as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + } + } + } + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link')) + { + foreach ($links as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + + } + } + } + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + + $keys = array_keys($this->data['links']); + foreach ($keys as $key) + { + if ($this->registry->call('Misc', 'is_isegment_nz_nc', array($key))) + { + if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); + $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; + } + else + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; + } + } + elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) + { + $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; + } + $this->data['links'][$key] = array_unique($this->data['links'][$key]); + } + } + + if (isset($this->data['links'][$rel])) + { + return $this->data['links'][$rel]; + } + else + { + return null; + } + } + + public function get_description() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + else + { + return null; + } + } + + public function get_copyright() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright')) + { + return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + public function get_language() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($this->data['xml_lang'])) + { + return $this->sanitize($this->data['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + public function get_latitude() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[1]; + } + else + { + return null; + } + } + + public function get_longitude() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) + { + return (float) $return[0]['data']; + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[2]; + } + else + { + return null; + } + } + + public function get_image_url() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image')) + { + return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + else + { + return null; + } + } +} + +/** + * Handles `<media:credit>` as defined in Media RSS + * + * Used by {@see SimplePie_Enclosure::get_credit()} and {@see SimplePie_Enclosure::get_credits()} + * + * This class can be overloaded with {@see SimplePie::set_credit_class()} + * + * @package SimplePie + * @subpackage API + */ +class SimplePie_Credit +{ + /** + * Credited role + * + * @var string + * @see get_role() + */ + var $role; + + /** + * Organizational scheme + * + * @var string + * @see get_scheme() + */ + var $scheme; + + /** + * Credited name + * + * @var string + * @see get_name() + */ + var $name; + + /** + * Constructor, used to input the data + * + * For documentation on all the parameters, see the corresponding + * properties and their accessors + */ + public function __construct($role = null, $scheme = null, $name = null) + { + $this->role = $role; + $this->scheme = $scheme; + $this->name = $name; + } + + /** + * String-ified version + * + * @return string + */ + public function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + /** + * Get the role of the person receiving credit + * + * @return string|null + */ + public function get_role() + { + if ($this->role !== null) + { + return $this->role; + } + else + { + return null; + } + } + + /** + * Get the organizational scheme + * + * @return string|null + */ + public function get_scheme() + { + if ($this->scheme !== null) + { + return $this->scheme; + } + else + { + return null; + } + } + + /** + * Get the credited person/entity's name + * + * @return string|null + */ + public function get_name() + { + if ($this->name !== null) + { + return $this->name; + } + else + { + return null; + } + } +} + +/** + * Parses the XML Declaration + * + * @package SimplePie + * @subpackage Parsing + */ +class SimplePie_XML_Declaration_Parser +{ + /** + * XML Version + * + * @access public + * @var string + */ + var $version = '1.0'; + + /** + * Encoding + * + * @access public + * @var string + */ + var $encoding = 'UTF-8'; + + /** + * Standalone + * + * @access public + * @var bool + */ + var $standalone = false; + + /** + * Current state of the state machine + * + * @access private + * @var string + */ + var $state = 'before_version_name'; + + /** + * Input data + * + * @access private + * @var string + */ + var $data = ''; + + /** + * Input data length (to avoid calling strlen() everytime this is needed) + * + * @access private + * @var int + */ + var $data_length = 0; + + /** + * Current position of the pointer + * + * @var int + * @access private + */ + var $position = 0; + + /** + * Create an instance of the class with the input data + * + * @access public + * @param string $data Input data + */ + public function __construct($data) + { + $this->data = $data; + $this->data_length = strlen($this->data); + } + + /** + * Parse the input data + * + * @access public + * @return bool true on success, false on failure + */ + public function parse() + { + while ($this->state && $this->state !== 'emit' && $this->has_data()) + { + $state = $this->state; + $this->$state(); + } + $this->data = ''; + if ($this->state === 'emit') + { + return true; + } + else + { + $this->version = ''; + $this->encoding = ''; + $this->standalone = ''; + return false; + } + } + + /** + * Check whether there is data beyond the pointer + * + * @access private + * @return bool true if there is further data, false if not + */ + public function has_data() + { + return (bool) ($this->position < $this->data_length); + } + + /** + * Advance past any whitespace + * + * @return int Number of whitespace characters passed + */ + public function skip_whitespace() + { + $whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position); + $this->position += $whitespace; + return $whitespace; + } + + /** + * Read value + */ + public function get_value() + { + $quote = substr($this->data, $this->position, 1); + if ($quote === '"' || $quote === "'") + { + $this->position++; + $len = strcspn($this->data, $quote, $this->position); + if ($this->has_data()) + { + $value = substr($this->data, $this->position, $len); + $this->position += $len + 1; + return $value; + } + } + return false; + } + + public function before_version_name() + { + if ($this->skip_whitespace()) + { + $this->state = 'version_name'; + } + else + { + $this->state = false; + } + } + + public function version_name() + { + if (substr($this->data, $this->position, 7) === 'version') + { + $this->position += 7; + $this->skip_whitespace(); + $this->state = 'version_equals'; + } + else + { + $this->state = false; + } + } + + public function version_equals() + { + if (substr($this->data, $this->position, 1) === '=') + { + $this->position++; + $this->skip_whitespace(); + $this->state = 'version_value'; + } + else + { + $this->state = false; + } + } + + public function version_value() + { + if ($this->version = $this->get_value()) + { + $this->skip_whitespace(); + if ($this->has_data()) + { + $this->state = 'encoding_name'; + } + else + { + $this->state = 'emit'; + } + } + else + { + $this->state = false; + } + } + + public function encoding_name() + { + if (substr($this->data, $this->position, 8) === 'encoding') + { + $this->position += 8; + $this->skip_whitespace(); + $this->state = 'encoding_equals'; + } + else + { + $this->state = 'standalone_name'; + } + } + + public function encoding_equals() + { + if (substr($this->data, $this->position, 1) === '=') + { + $this->position++; + $this->skip_whitespace(); + $this->state = 'encoding_value'; + } + else + { + $this->state = false; + } + } + + public function encoding_value() + { + if ($this->encoding = $this->get_value()) + { + $this->skip_whitespace(); + if ($this->has_data()) + { + $this->state = 'standalone_name'; + } + else + { + $this->state = 'emit'; + } + } + else + { + $this->state = false; + } + } + + public function standalone_name() + { + if (substr($this->data, $this->position, 10) === 'standalone') + { + $this->position += 10; + $this->skip_whitespace(); + $this->state = 'standalone_equals'; + } + else + { + $this->state = false; + } + } + + public function standalone_equals() + { + if (substr($this->data, $this->position, 1) === '=') + { + $this->position++; + $this->skip_whitespace(); + $this->state = 'standalone_value'; + } + else + { + $this->state = false; + } + } + + public function standalone_value() + { + if ($standalone = $this->get_value()) + { + switch ($standalone) + { + case 'yes': + $this->standalone = true; + break; + + case 'no': + $this->standalone = false; + break; + + default: + $this->state = false; + return; + } + + $this->skip_whitespace(); + if ($this->has_data()) + { + $this->state = false; + } + else + { + $this->state = 'emit'; + } + } + else + { + $this->state = false; + } + } +} + +/** + * General SimplePie exception class + * + * @package SimplePie + */ +class SimplePie_Exception extends Exception +{ +} + +/** + * Decode HTML Entities + * + * This implements HTML5 as of revision 967 (2007-06-28) + * + * @deprecated Use DOMDocument instead! + * @package SimplePie + */ +class SimplePie_Decode_HTML_Entities +{ + /** + * Data to be parsed + * + * @access private + * @var string + */ + var $data = ''; + + /** + * Currently consumed bytes + * + * @access private + * @var string + */ + var $consumed = ''; + + /** + * Position of the current byte being parsed + * + * @access private + * @var int + */ + var $position = 0; + + /** + * Create an instance of the class with the input data + * + * @access public + * @param string $data Input data + */ + public function __construct($data) + { + $this->data = $data; + } + + /** + * Parse the input data + * + * @access public + * @return string Output data + */ + public function parse() + { + while (($this->position = strpos($this->data, '&', $this->position)) !== false) + { + $this->consume(); + $this->entity(); + $this->consumed = ''; + } + return $this->data; + } + + /** + * Consume the next byte + * + * @access private + * @return mixed The next byte, or false, if there is no more data + */ + public function consume() + { + if (isset($this->data[$this->position])) + { + $this->consumed .= $this->data[$this->position]; + return $this->data[$this->position++]; + } + else + { + return false; + } + } + + /** + * Consume a range of characters + * + * @access private + * @param string $chars Characters to consume + * @return mixed A series of characters that match the range, or false + */ + public function consume_range($chars) + { + if ($len = strspn($this->data, $chars, $this->position)) + { + $data = substr($this->data, $this->position, $len); + $this->consumed .= $data; + $this->position += $len; + return $data; + } + else + { + return false; + } + } + + /** + * Unconsume one byte + * + * @access private + */ + public function unconsume() + { + $this->consumed = substr($this->consumed, 0, -1); + $this->position--; + } + + /** + * Decode an entity + * + * @access private + */ + public function entity() + { + switch ($this->consume()) + { + case "\x09": + case "\x0A": + case "\x0B": + case "\x0B": + case "\x0C": + case "\x20": + case "\x3C": + case "\x26": + case false: + break; + + case "\x23": + switch ($this->consume()) + { + case "\x78": + case "\x58": + $range = '0123456789ABCDEFabcdef'; + $hex = true; + break; + + default: + $range = '0123456789'; + $hex = false; + $this->unconsume(); + break; + } + + if ($codepoint = $this->consume_range($range)) + { + static $windows_1252_specials = array(0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99", 0x93 => "\xE2\x80\x9C", 0x94 => "\xE2\x80\x9D", 0x95 => "\xE2\x80\xA2", 0x96 => "\xE2\x80\x93", 0x97 => "\xE2\x80\x94", 0x98 => "\xCB\x9C", 0x99 => "\xE2\x84\xA2", 0x9A => "\xC5\xA1", 0x9B => "\xE2\x80\xBA", 0x9C => "\xC5\x93", 0x9D => "\xEF\xBF\xBD", 0x9E => "\xC5\xBE", 0x9F => "\xC5\xB8"); + + if ($hex) + { + $codepoint = hexdec($codepoint); + } + else + { + $codepoint = intval($codepoint); + } + + if (isset($windows_1252_specials[$codepoint])) + { + $replacement = $windows_1252_specials[$codepoint]; + } + else + { + $replacement = SimplePie_Misc::codepoint_to_utf8($codepoint); + } + + if (!in_array($this->consume(), array(';', false), true)) + { + $this->unconsume(); + } + + $consumed_length = strlen($this->consumed); + $this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length); + $this->position += strlen($replacement) - $consumed_length; + } + break; + + default: + static $entities = array( + 'Aacute' => "\xC3\x81", + 'aacute' => "\xC3\xA1", + 'Aacute;' => "\xC3\x81", + 'aacute;' => "\xC3\xA1", + 'Acirc' => "\xC3\x82", + 'acirc' => "\xC3\xA2", + 'Acirc;' => "\xC3\x82", + 'acirc;' => "\xC3\xA2", + 'acute' => "\xC2\xB4", + 'acute;' => "\xC2\xB4", + 'AElig' => "\xC3\x86", + 'aelig' => "\xC3\xA6", + 'AElig;' => "\xC3\x86", + 'aelig;' => "\xC3\xA6", + 'Agrave' => "\xC3\x80", + 'agrave' => "\xC3\xA0", + 'Agrave;' => "\xC3\x80", + 'agrave;' => "\xC3\xA0", + 'alefsym;' => "\xE2\x84\xB5", + 'Alpha;' => "\xCE\x91", + 'alpha;' => "\xCE\xB1", + 'AMP' => "\x26", + 'amp' => "\x26", + 'AMP;' => "\x26", + 'amp;' => "\x26", + 'and;' => "\xE2\x88\xA7", + 'ang;' => "\xE2\x88\xA0", + 'apos;' => "\x27", + 'Aring' => "\xC3\x85", + 'aring' => "\xC3\xA5", + 'Aring;' => "\xC3\x85", + 'aring;' => "\xC3\xA5", + 'asymp;' => "\xE2\x89\x88", + 'Atilde' => "\xC3\x83", + 'atilde' => "\xC3\xA3", + 'Atilde;' => "\xC3\x83", + 'atilde;' => "\xC3\xA3", + 'Auml' => "\xC3\x84", + 'auml' => "\xC3\xA4", + 'Auml;' => "\xC3\x84", + 'auml;' => "\xC3\xA4", + 'bdquo;' => "\xE2\x80\x9E", + 'Beta;' => "\xCE\x92", + 'beta;' => "\xCE\xB2", + 'brvbar' => "\xC2\xA6", + 'brvbar;' => "\xC2\xA6", + 'bull;' => "\xE2\x80\xA2", + 'cap;' => "\xE2\x88\xA9", + 'Ccedil' => "\xC3\x87", + 'ccedil' => "\xC3\xA7", + 'Ccedil;' => "\xC3\x87", + 'ccedil;' => "\xC3\xA7", + 'cedil' => "\xC2\xB8", + 'cedil;' => "\xC2\xB8", + 'cent' => "\xC2\xA2", + 'cent;' => "\xC2\xA2", + 'Chi;' => "\xCE\xA7", + 'chi;' => "\xCF\x87", + 'circ;' => "\xCB\x86", + 'clubs;' => "\xE2\x99\xA3", + 'cong;' => "\xE2\x89\x85", + 'COPY' => "\xC2\xA9", + 'copy' => "\xC2\xA9", + 'COPY;' => "\xC2\xA9", + 'copy;' => "\xC2\xA9", + 'crarr;' => "\xE2\x86\xB5", + 'cup;' => "\xE2\x88\xAA", + 'curren' => "\xC2\xA4", + 'curren;' => "\xC2\xA4", + 'Dagger;' => "\xE2\x80\xA1", + 'dagger;' => "\xE2\x80\xA0", + 'dArr;' => "\xE2\x87\x93", + 'darr;' => "\xE2\x86\x93", + 'deg' => "\xC2\xB0", + 'deg;' => "\xC2\xB0", + 'Delta;' => "\xCE\x94", + 'delta;' => "\xCE\xB4", + 'diams;' => "\xE2\x99\xA6", + 'divide' => "\xC3\xB7", + 'divide;' => "\xC3\xB7", + 'Eacute' => "\xC3\x89", + 'eacute' => "\xC3\xA9", + 'Eacute;' => "\xC3\x89", + 'eacute;' => "\xC3\xA9", + 'Ecirc' => "\xC3\x8A", + 'ecirc' => "\xC3\xAA", + 'Ecirc;' => "\xC3\x8A", + 'ecirc;' => "\xC3\xAA", + 'Egrave' => "\xC3\x88", + 'egrave' => "\xC3\xA8", + 'Egrave;' => "\xC3\x88", + 'egrave;' => "\xC3\xA8", + 'empty;' => "\xE2\x88\x85", + 'emsp;' => "\xE2\x80\x83", + 'ensp;' => "\xE2\x80\x82", + 'Epsilon;' => "\xCE\x95", + 'epsilon;' => "\xCE\xB5", + 'equiv;' => "\xE2\x89\xA1", + 'Eta;' => "\xCE\x97", + 'eta;' => "\xCE\xB7", + 'ETH' => "\xC3\x90", + 'eth' => "\xC3\xB0", + 'ETH;' => "\xC3\x90", + 'eth;' => "\xC3\xB0", + 'Euml' => "\xC3\x8B", + 'euml' => "\xC3\xAB", + 'Euml;' => "\xC3\x8B", + 'euml;' => "\xC3\xAB", + 'euro;' => "\xE2\x82\xAC", + 'exist;' => "\xE2\x88\x83", + 'fnof;' => "\xC6\x92", + 'forall;' => "\xE2\x88\x80", + 'frac12' => "\xC2\xBD", + 'frac12;' => "\xC2\xBD", + 'frac14' => "\xC2\xBC", + 'frac14;' => "\xC2\xBC", + 'frac34' => "\xC2\xBE", + 'frac34;' => "\xC2\xBE", + 'frasl;' => "\xE2\x81\x84", + 'Gamma;' => "\xCE\x93", + 'gamma;' => "\xCE\xB3", + 'ge;' => "\xE2\x89\xA5", + 'GT' => "\x3E", + 'gt' => "\x3E", + 'GT;' => "\x3E", + 'gt;' => "\x3E", + 'hArr;' => "\xE2\x87\x94", + 'harr;' => "\xE2\x86\x94", + 'hearts;' => "\xE2\x99\xA5", + 'hellip;' => "\xE2\x80\xA6", + 'Iacute' => "\xC3\x8D", + 'iacute' => "\xC3\xAD", + 'Iacute;' => "\xC3\x8D", + 'iacute;' => "\xC3\xAD", + 'Icirc' => "\xC3\x8E", + 'icirc' => "\xC3\xAE", + 'Icirc;' => "\xC3\x8E", + 'icirc;' => "\xC3\xAE", + 'iexcl' => "\xC2\xA1", + 'iexcl;' => "\xC2\xA1", + 'Igrave' => "\xC3\x8C", + 'igrave' => "\xC3\xAC", + 'Igrave;' => "\xC3\x8C", + 'igrave;' => "\xC3\xAC", + 'image;' => "\xE2\x84\x91", + 'infin;' => "\xE2\x88\x9E", + 'int;' => "\xE2\x88\xAB", + 'Iota;' => "\xCE\x99", + 'iota;' => "\xCE\xB9", + 'iquest' => "\xC2\xBF", + 'iquest;' => "\xC2\xBF", + 'isin;' => "\xE2\x88\x88", + 'Iuml' => "\xC3\x8F", + 'iuml' => "\xC3\xAF", + 'Iuml;' => "\xC3\x8F", + 'iuml;' => "\xC3\xAF", + 'Kappa;' => "\xCE\x9A", + 'kappa;' => "\xCE\xBA", + 'Lambda;' => "\xCE\x9B", + 'lambda;' => "\xCE\xBB", + 'lang;' => "\xE3\x80\x88", + 'laquo' => "\xC2\xAB", + 'laquo;' => "\xC2\xAB", + 'lArr;' => "\xE2\x87\x90", + 'larr;' => "\xE2\x86\x90", + 'lceil;' => "\xE2\x8C\x88", + 'ldquo;' => "\xE2\x80\x9C", + 'le;' => "\xE2\x89\xA4", + 'lfloor;' => "\xE2\x8C\x8A", + 'lowast;' => "\xE2\x88\x97", + 'loz;' => "\xE2\x97\x8A", + 'lrm;' => "\xE2\x80\x8E", + 'lsaquo;' => "\xE2\x80\xB9", + 'lsquo;' => "\xE2\x80\x98", + 'LT' => "\x3C", + 'lt' => "\x3C", + 'LT;' => "\x3C", + 'lt;' => "\x3C", + 'macr' => "\xC2\xAF", + 'macr;' => "\xC2\xAF", + 'mdash;' => "\xE2\x80\x94", + 'micro' => "\xC2\xB5", + 'micro;' => "\xC2\xB5", + 'middot' => "\xC2\xB7", + 'middot;' => "\xC2\xB7", + 'minus;' => "\xE2\x88\x92", + 'Mu;' => "\xCE\x9C", + 'mu;' => "\xCE\xBC", + 'nabla;' => "\xE2\x88\x87", + 'nbsp' => "\xC2\xA0", + 'nbsp;' => "\xC2\xA0", + 'ndash;' => "\xE2\x80\x93", + 'ne;' => "\xE2\x89\xA0", + 'ni;' => "\xE2\x88\x8B", + 'not' => "\xC2\xAC", + 'not;' => "\xC2\xAC", + 'notin;' => "\xE2\x88\x89", + 'nsub;' => "\xE2\x8A\x84", + 'Ntilde' => "\xC3\x91", + 'ntilde' => "\xC3\xB1", + 'Ntilde;' => "\xC3\x91", + 'ntilde;' => "\xC3\xB1", + 'Nu;' => "\xCE\x9D", + 'nu;' => "\xCE\xBD", + 'Oacute' => "\xC3\x93", + 'oacute' => "\xC3\xB3", + 'Oacute;' => "\xC3\x93", + 'oacute;' => "\xC3\xB3", + 'Ocirc' => "\xC3\x94", + 'ocirc' => "\xC3\xB4", + 'Ocirc;' => "\xC3\x94", + 'ocirc;' => "\xC3\xB4", + 'OElig;' => "\xC5\x92", + 'oelig;' => "\xC5\x93", + 'Ograve' => "\xC3\x92", + 'ograve' => "\xC3\xB2", + 'Ograve;' => "\xC3\x92", + 'ograve;' => "\xC3\xB2", + 'oline;' => "\xE2\x80\xBE", + 'Omega;' => "\xCE\xA9", + 'omega;' => "\xCF\x89", + 'Omicron;' => "\xCE\x9F", + 'omicron;' => "\xCE\xBF", + 'oplus;' => "\xE2\x8A\x95", + 'or;' => "\xE2\x88\xA8", + 'ordf' => "\xC2\xAA", + 'ordf;' => "\xC2\xAA", + 'ordm' => "\xC2\xBA", + 'ordm;' => "\xC2\xBA", + 'Oslash' => "\xC3\x98", + 'oslash' => "\xC3\xB8", + 'Oslash;' => "\xC3\x98", + 'oslash;' => "\xC3\xB8", + 'Otilde' => "\xC3\x95", + 'otilde' => "\xC3\xB5", + 'Otilde;' => "\xC3\x95", + 'otilde;' => "\xC3\xB5", + 'otimes;' => "\xE2\x8A\x97", + 'Ouml' => "\xC3\x96", + 'ouml' => "\xC3\xB6", + 'Ouml;' => "\xC3\x96", + 'ouml;' => "\xC3\xB6", + 'para' => "\xC2\xB6", + 'para;' => "\xC2\xB6", + 'part;' => "\xE2\x88\x82", + 'permil;' => "\xE2\x80\xB0", + 'perp;' => "\xE2\x8A\xA5", + 'Phi;' => "\xCE\xA6", + 'phi;' => "\xCF\x86", + 'Pi;' => "\xCE\xA0", + 'pi;' => "\xCF\x80", + 'piv;' => "\xCF\x96", + 'plusmn' => "\xC2\xB1", + 'plusmn;' => "\xC2\xB1", + 'pound' => "\xC2\xA3", + 'pound;' => "\xC2\xA3", + 'Prime;' => "\xE2\x80\xB3", + 'prime;' => "\xE2\x80\xB2", + 'prod;' => "\xE2\x88\x8F", + 'prop;' => "\xE2\x88\x9D", + 'Psi;' => "\xCE\xA8", + 'psi;' => "\xCF\x88", + 'QUOT' => "\x22", + 'quot' => "\x22", + 'QUOT;' => "\x22", + 'quot;' => "\x22", + 'radic;' => "\xE2\x88\x9A", + 'rang;' => "\xE3\x80\x89", + 'raquo' => "\xC2\xBB", + 'raquo;' => "\xC2\xBB", + 'rArr;' => "\xE2\x87\x92", + 'rarr;' => "\xE2\x86\x92", + 'rceil;' => "\xE2\x8C\x89", + 'rdquo;' => "\xE2\x80\x9D", + 'real;' => "\xE2\x84\x9C", + 'REG' => "\xC2\xAE", + 'reg' => "\xC2\xAE", + 'REG;' => "\xC2\xAE", + 'reg;' => "\xC2\xAE", + 'rfloor;' => "\xE2\x8C\x8B", + 'Rho;' => "\xCE\xA1", + 'rho;' => "\xCF\x81", + 'rlm;' => "\xE2\x80\x8F", + 'rsaquo;' => "\xE2\x80\xBA", + 'rsquo;' => "\xE2\x80\x99", + 'sbquo;' => "\xE2\x80\x9A", + 'Scaron;' => "\xC5\xA0", + 'scaron;' => "\xC5\xA1", + 'sdot;' => "\xE2\x8B\x85", + 'sect' => "\xC2\xA7", + 'sect;' => "\xC2\xA7", + 'shy' => "\xC2\xAD", + 'shy;' => "\xC2\xAD", + 'Sigma;' => "\xCE\xA3", + 'sigma;' => "\xCF\x83", + 'sigmaf;' => "\xCF\x82", + 'sim;' => "\xE2\x88\xBC", + 'spades;' => "\xE2\x99\xA0", + 'sub;' => "\xE2\x8A\x82", + 'sube;' => "\xE2\x8A\x86", + 'sum;' => "\xE2\x88\x91", + 'sup;' => "\xE2\x8A\x83", + 'sup1' => "\xC2\xB9", + 'sup1;' => "\xC2\xB9", + 'sup2' => "\xC2\xB2", + 'sup2;' => "\xC2\xB2", + 'sup3' => "\xC2\xB3", + 'sup3;' => "\xC2\xB3", + 'supe;' => "\xE2\x8A\x87", + 'szlig' => "\xC3\x9F", + 'szlig;' => "\xC3\x9F", + 'Tau;' => "\xCE\xA4", + 'tau;' => "\xCF\x84", + 'there4;' => "\xE2\x88\xB4", + 'Theta;' => "\xCE\x98", + 'theta;' => "\xCE\xB8", + 'thetasym;' => "\xCF\x91", + 'thinsp;' => "\xE2\x80\x89", + 'THORN' => "\xC3\x9E", + 'thorn' => "\xC3\xBE", + 'THORN;' => "\xC3\x9E", + 'thorn;' => "\xC3\xBE", + 'tilde;' => "\xCB\x9C", + 'times' => "\xC3\x97", + 'times;' => "\xC3\x97", + 'TRADE;' => "\xE2\x84\xA2", + 'trade;' => "\xE2\x84\xA2", + 'Uacute' => "\xC3\x9A", + 'uacute' => "\xC3\xBA", + 'Uacute;' => "\xC3\x9A", + 'uacute;' => "\xC3\xBA", + 'uArr;' => "\xE2\x87\x91", + 'uarr;' => "\xE2\x86\x91", + 'Ucirc' => "\xC3\x9B", + 'ucirc' => "\xC3\xBB", + 'Ucirc;' => "\xC3\x9B", + 'ucirc;' => "\xC3\xBB", + 'Ugrave' => "\xC3\x99", + 'ugrave' => "\xC3\xB9", + 'Ugrave;' => "\xC3\x99", + 'ugrave;' => "\xC3\xB9", + 'uml' => "\xC2\xA8", + 'uml;' => "\xC2\xA8", + 'upsih;' => "\xCF\x92", + 'Upsilon;' => "\xCE\xA5", + 'upsilon;' => "\xCF\x85", + 'Uuml' => "\xC3\x9C", + 'uuml' => "\xC3\xBC", + 'Uuml;' => "\xC3\x9C", + 'uuml;' => "\xC3\xBC", + 'weierp;' => "\xE2\x84\x98", + 'Xi;' => "\xCE\x9E", + 'xi;' => "\xCE\xBE", + 'Yacute' => "\xC3\x9D", + 'yacute' => "\xC3\xBD", + 'Yacute;' => "\xC3\x9D", + 'yacute;' => "\xC3\xBD", + 'yen' => "\xC2\xA5", + 'yen;' => "\xC2\xA5", + 'yuml' => "\xC3\xBF", + 'Yuml;' => "\xC5\xB8", + 'yuml;' => "\xC3\xBF", + 'Zeta;' => "\xCE\x96", + 'zeta;' => "\xCE\xB6", + 'zwj;' => "\xE2\x80\x8D", + 'zwnj;' => "\xE2\x80\x8C" + ); + + for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++) + { + $consumed = substr($this->consumed, 1); + if (isset($entities[$consumed])) + { + $match = $consumed; + } + } + + if ($match !== null) + { + $this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1); + $this->position += strlen($entities[$match]) - strlen($consumed) - 1; + } + break; + } + } +} \ No newline at end of file diff --git a/sources/Update.class.php b/sources/Update.class.php new file mode 100755 index 0000000..74f26a3 --- /dev/null +++ b/sources/Update.class.php @@ -0,0 +1,113 @@ +<?php +/** + @nom: Update + @auteur: Maël ILLOUZ (mael.illouz@cobestran.com) + @description: Classe de gestion des mises à jour en BDD liées aux améliorations apportées dans Leed + @todo : Ajouter la possiblité d'executer des fichiers php de maj. + */ + +class Update{ + const FOLDER = '/updates'; + + /** + * Description : Récupération des fichiers déjà passés lors des anciennes mises à jour. + */ + private static function getUpdateFile(){ + $updateFile = dirname(__FILE__).Update::FOLDER.'/update.json'; + if(!file_exists($updateFile)) { + if (!touch($updateFile)) { + die ('Impossible d\'écrire dans le répertoire .'.dirname($updateFile).'. Merci d\'ajouter les droits necessaires.'); + } + } + + return json_decode(file_get_contents($updateFile),true); + } + + private static function addUpdateFile($addFile){ + $updateFile = dirname(__FILE__).Update::FOLDER.'/update.json'; + $originFile = Update::getUpdateFile(); + if(empty($originFile)) + $originFile = array(); + $newfile = array_merge($originFile,$addFile); + if (is_writable($updateFile)){ + file_put_contents($updateFile,json_encode($newfile)); + } else { + die ('Impossible d\'écrire dans le fichier .'.$updateFile.'. Merci d\'ajouter les droits nécessaires.'); + } + } + + + /** + * Description : Permet de trouver les fichiers qui n'ont pas encore été joués + */ + private static function getNewPatch() { + $files = glob(dirname(__FILE__). Update::FOLDER .'/*.sql'); + if(empty($files)) + $files = array(); + + $jsonFiles = Update::getUpdateFile(); + + $alreadyPassed = array(); + $notPassed = array(); + + if ($jsonFiles==''){ + $jsonFiles[0] = array(); + } + foreach($files as $file){ + if(in_array(basename($file), $jsonFiles[0])){ + $alreadyPassed [] = basename($file); + }else{ + $notPassed [] = basename($file); + } + } + return $notPassed; + } + + /** + * Description : Permet l'execution des fichiers sql non joués + * @simulation : true pour ne pas faire les actions en bdd + */ + public static function ExecutePatch($simulation=false) { + $newFilesForUpdate = Update::getNewPatch(); + + //si aucun nouveau fichier de mise à jour à traiter @return : false + if(count($newFilesForUpdate)==0) return false; + if (!$simulation) { + foreach($newFilesForUpdate as $file){ + // récupération du contenu du sql + $sql = file_get_contents(dirname(__FILE__).Update::FOLDER.'/'.$file); + + $conn = new MysqlEntity(); + //on sépare chaque requête par les ; + $sql_array = explode (";",$sql); + foreach ($sql_array as $val) { + $val = preg_replace('#([-].*)|(\n)#','',$val); + if ($val != '') { + //remplacement des préfixes de table + $val = str_replace('##MYSQL_PREFIX##',MYSQL_PREFIX,$val); + $result = mysql_query($val); + $ficlog = dirname(__FILE__).Update::FOLDER.'/'.substr($file,0,strlen($file)-3).'log'; + if (false===$result) { + file_put_contents($ficlog, date('d/m/Y H:i:s').' : SQL : '.$val."\n", FILE_APPEND); + file_put_contents($ficlog, date('d/m/Y H:i:s').' : '.mysql_error()."\n", FILE_APPEND); + } else { + file_put_contents($ficlog, date('d/m/Y H:i:s').' : SQL : '.$val."\n", FILE_APPEND); + file_put_contents($ficlog, date('d/m/Y H:i:s').' : '.mysql_affected_rows().' rows affected'."\n", FILE_APPEND); + } + } + } + unset($conn); + } + $_SESSION = array(); + session_unset(); + session_destroy(); + } + // quand toutes les requêtes ont été executées, on insert le sql dans le json + Update::addUpdateFile(array($newFilesForUpdate)); + + return true; + } + +} + +?> \ No newline at end of file diff --git a/sources/User.class.php b/sources/User.class.php new file mode 100755 index 0000000..f2c4a59 --- /dev/null +++ b/sources/User.class.php @@ -0,0 +1,95 @@ +<?php + +/* + @nom: User + @auteur: Idleman (idleman@idleman.fr) + @description: Classe de gestion des utilisateurs + */ + +class User extends MysqlEntity{ + + protected $id,$login,$password; + protected $TABLE_NAME = 'user'; + protected $CLASS_NAME = 'User'; + protected $object_fields = + array( + 'id'=>'key', + 'login'=>'string', + 'password'=>'string' + ); + + function __construct(){ + parent::__construct(); + } + + function setId($id){ + $this->id = $id; + } + + function exist($login,$password,$salt=''){ + $userManager = new User(); + return $userManager->load(array('login'=>$login,'password'=>User::encrypt($password,$salt))); + } + + function get($login){ + $userManager = new User(); + return $userManager->load(array('login'=>$login,)); + } + + function getToken() { + assert('!empty($this->password)'); + assert('!empty($this->login)'); + return sha1($this->password.$this->login); + } + + static function existAuthToken($auth=null){ + $result = false; + $userManager = new User(); + $users = $userManager->populate('id'); + if (empty($auth)) $auth = @$_COOKIE['leedStaySignedIn']; + foreach($users as $user){ + if($user->getToken()==$auth) $result = $user; + } + return $result; + } + + function setStayConnected() { + ///@TODO: set the current web directory, here and on del + setcookie('leedStaySignedIn', $this->getToken(), time()+31536000); + } + + static function delStayConnected() { + setcookie('leedStaySignedIn', '', -1); + } + + function getId(){ + return $this->id; + } + + function getLogin(){ + return $this->login; + } + + function setLogin($login){ + $this->login = $login; + } + + function getPassword(){ + return $this->password; + } + + function setPassword($password,$salt=''){ + $this->password = User::encrypt($password,$salt); + } + + static function encrypt($password, $salt=''){ + return sha1($password.$salt); + } + + static function generateSalt() { + return ''.mt_rand().mt_rand(); + } + +} + +?> diff --git a/sources/about.php b/sources/about.php new file mode 100755 index 0000000..d166e19 --- /dev/null +++ b/sources/about.php @@ -0,0 +1,17 @@ +<?php + +/* + @nom: about + @auteur: Idleman (idleman@idleman.fr) + @description: Page "A propos" d'information contextuelles sur le projet + */ + +require_once('header.php'); +require_once('constant.php'); + +$tpl->assign('VERSION_NUMBER',VERSION_NUMBER); +$tpl->assign('VERSION_NAME',VERSION_NAME); +$view = 'about'; +require_once('footer.php'); + +?> diff --git a/sources/action.php b/sources/action.php new file mode 100755 index 0000000..0f29010 --- /dev/null +++ b/sources/action.php @@ -0,0 +1,604 @@ +<?php + +/* + @nom: action + @auteur: Idleman (idleman@idleman.fr) + @description: Page de gestion des évenements non liés a une vue particulière (appels ajax, requetes sans resultats etc...) + */ + +if(!ini_get('safe_mode')) @set_time_limit(0); +require_once("common.php"); + +///@TODO: déplacer dans common.php? +$commandLine = 'cli'==php_sapi_name(); + +if ($commandLine) { + $action = 'commandLine'; +} else { + $action = @$_['action']; +} +///@TODO: pourquoi ne pas refuser l'accès dès le début ? +Plugin::callHook("action_pre_case", array(&$_,$myUser)); + +//Execution du code en fonction de l'action +switch ($action){ + case 'commandLine': + case 'synchronize': + require_once("SimplePie.class.php"); + $syncCode = $configurationManager->get('synchronisationCode'); + if ( false==$myUser + && !$commandLine + && !(isset($_['code']) + && $configurationManager->get('synchronisationCode')!=null + && $_['code']==$configurationManager->get('synchronisationCode') + ) + ) { + die(_t('YOU_MUST_BE_CONNECTED_ACTION')); + } + Functions::triggerDirectOutput(); + + if (!$commandLine) + echo '<html> + <head> + <link rel="stylesheet" href="./templates/'.DEFAULT_THEME.'/css/style.css"> + </head> + <body> + <div class="sync">'; + $synchronisationType = $configurationManager->get('synchronisationType'); + $maxEvents = $configurationManager->get('feedMaxEvents'); + if('graduate'==$synchronisationType){ + // sélectionne les 10 plus vieux flux + $feeds = $feedManager->loadAll(null,'lastupdate',defined('SYNC_GRAD_COUNT') ? SYNC_GRAD_COUNT : 10); + $syncTypeStr = _t('SYNCHRONISATION_TYPE').' : '._t('GRADUATE_SYNCHRONISATION'); + }else{ + // sélectionne tous les flux, triés par le nom + $feeds = $feedManager->populate('name'); + $syncTypeStr = _t('SYNCHRONISATION_TYPE').' : '._t('FULL_SYNCHRONISATION'); + } + + + $currentDate = date('d/m/Y H:i:s'); + if (!$commandLine) { + echo "<p>{$syncTypeStr} {$currentDate}</p>\n"; + echo "<dl>\n"; + } else { + echo "{$syncTypeStr}\t{$currentDate}\n"; + } + $nbErrors = 0; + $nbOk = 0; + $nbTotal = 0; + $localTotal = 0; // somme de tous les temps locaux, pour chaque flux + $nbTotalEvents = 0; + $syncId = time(); + $enableCache = ($configurationManager->get('synchronisationEnableCache')=='')?0:$configurationManager->get('synchronisationEnableCache'); + $forceFeed = ($configurationManager->get('synchronisationForceFeed')=='')?0:$configurationManager->get('synchronisationForceFeed'); + + foreach ($feeds as $feed) { + $nbEvents = 0; + $nbTotal++; + $startLocal = microtime(true); + $parseOk = $feed->parse($syncId,$nbEvents, $enableCache, $forceFeed); + $parseTime = microtime(true)-$startLocal; + $localTotal += $parseTime; + $parseTimeStr = number_format($parseTime, 3); + if ($parseOk) { // It's ok + $errors = array(); + $nbTotalEvents += $nbEvents; + $nbOk++; + } else { + // tableau au cas où il arrive plusieurs erreurs + $errors = array($feed->getError()); + + $nbErrors++; + } + $feedName = Functions::truncate($feed->getName(),30); + $feedUrl = $feed->getUrl(); + $feedUrlTxt = Functions::truncate($feedUrl, 30); + if ($commandLine) { + echo date('d/m/Y H:i:s')."\t".$parseTimeStr."\t"; + echo "{$feedName}\t{$feedUrlTxt}\n"; + } else { + + if (!$parseOk) echo '<div class="errorSync">'; + echo "<dt><i>{$parseTimeStr}s</i> | <a href='{$feedUrl}'>{$feedName}</a></dt>\n"; + + } + foreach($errors as $error) { + if ($commandLine) + echo "$error\n"; + else + echo "<dd>$error</dd>\n"; + } + if (!$parseOk && !$commandLine) echo '</div>'; +// if ($commandLine) echo "\n"; + $feed->removeOldEvents($maxEvents, $syncId); + } + assert('$nbTotal==$nbOk+$nbErrors'); + $totalTime = microtime(true)-$start; + assert('$totalTime>=$localTotal'); + $totalTimeStr = number_format($totalTime, 3); + $currentDate = date('d/m/Y H:i:s'); + if ($commandLine) { + echo "\t{$nbErrors}\t"._t('ERRORS')."\n"; + echo "\t{$nbOk}\t"._t('GOOD')."\n"; + echo "\t{$nbTotal}\t"._t('AT_TOTAL')."\n"; + echo "\t$currentDate\n"; + echo "\t$nbTotalEvents\n"; + echo "\t{$totalTimeStr}\t"._t('SECONDS')."\n"; + } else { + echo "</dl>\n"; + echo "<div id='syncSummary'\n"; + echo "<p>"._t('SYNCHRONISATION_COMPLETE')."</p>\n"; + echo "<ul>\n"; + echo "<li>{$nbErrors} "._t('ERRORS')."\n"; + echo "<li>{$nbOk} "._t('GOOD')."\n"; + echo "<li>{$nbTotal} "._t('AT_TOTAL')."\n"; + echo "<li>{$totalTimeStr}\t"._t('SECONDS')."\n"; + echo "<li>{$nbTotalEvents} nouveaux articles\n"; + echo "</ul>\n"; + echo "</div>\n"; + } + + if (!$commandLine) { + echo '</div></body></html>'; + } + + break; + + + case 'readAll': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + $whereClause = array(); + $whereClause['unread'] = '1'; + if(isset($_['feed']))$whereClause['feed'] = $_['feed']; + $eventManager->change(array('unread'=>'0'),$whereClause); + header('location: ./index.php'); + break; + + case 'readFolder': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + + $feeds = $feedManager->loadAllOnlyColumn('id',array('folder'=>$_['folder'])); + + foreach($feeds as $feed){ + $eventManager->change(array('unread'=>'0'),array('feed'=>$feed->getId())); + } + + header('location: ./index.php'); + + break; + + case 'updateConfiguration': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + + //Ajout des préférences et réglages + $configurationManager->put('root',(substr($_['root'], strlen($_['root'])-1)=='/'?$_['root']:$_['root'].'/')); + //$configurationManager->put('view',$_['view']); + if(isset($_['articleView'])) + $configurationManager->put('articleView',$_['articleView']); + $configurationManager->put('articleDisplayContent',$_['articleDisplayContent']); + $configurationManager->put('articleDisplayAnonymous',$_['articleDisplayAnonymous']); + + $configurationManager->put('articlePerPages',$_['articlePerPages']); + $configurationManager->put('articleDisplayLink',$_['articleDisplayLink']); + $configurationManager->put('articleDisplayDate',$_['articleDisplayDate']); + $configurationManager->put('articleDisplayAuthor',$_['articleDisplayAuthor']); + $configurationManager->put('articleDisplayHomeSort',$_['articleDisplayHomeSort']); + $configurationManager->put('articleDisplayFolderSort',$_['articleDisplayFolderSort']); + $configurationManager->put('synchronisationType',$_['synchronisationType']); + $configurationManager->put('synchronisationEnableCache',$_['synchronisationEnableCache']); + $configurationManager->put('synchronisationForceFeed',$_['synchronisationForceFeed']); + $configurationManager->put('feedMaxEvents',$_['feedMaxEvents']); + + $userManager->change(array('login'=>$_['login']),array('id'=>$myUser->getId())); + if(trim($_['password'])!='') { + $salt = User::generateSalt(); + $userManager->change(array('password'=>User::encrypt($_['password'], $salt)),array('id'=>$myUser->getId())); + /* /!\ En multi-utilisateur, il faudra changer l'information au + niveau du compte lui-même et non au niveau du déploiement comme + ici. C'est ainsi parce que c'est plus efficace de stocker le sel + dans la config que dans le fichier de constantes, difficile à + modifier. */ + $oldSalt = $configurationManager->get('cryptographicSalt'); + if (empty($oldSalt)) + /* Pendant la migration à ce système, les déploiements + ne posséderont pas cette donnée. */ + $configurationManager->add('cryptographicSalt', $salt); + else + $configurationManager->change(array('value'=>$salt), array('key'=>'cryptographicSalt')); + + } + + header('location: ./settings.php#preferenceBloc'); + break; + + + case 'purge': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + $eventManager->truncate(); + header('location: ./settings.php'); + break; + + + case 'exportFeed': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + /*********************/ + /** Export **/ + /*********************/ + if(isset($_POST['exportButton'])){ + $opml = new Opml(); + $xmlStream = $opml->export(); + + header('Content-Description: File Transfer'); + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename=leed-'.date('d-m-Y').'.opml'); + header('Content-Transfer-Encoding: binary'); + header('Expires: 0'); + header('Cache-Control: must-revalidate'); + header('Pragma: public'); + header('Content-Length: ' . strlen($xmlStream)); + /* + //A decommenter dans le cas ou on a des pb avec ie + if(preg_match('/msie|(microsoft internet explorer)/i', $_SERVER['HTTP_USER_AGENT'])){ + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Pragma: public'); + }else{ + header('Pragma: no-cache'); + } + */ + ob_clean(); + flush(); + echo $xmlStream; + } + break; + + + case 'importForm': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + echo '<html style="height:auto;"><link rel="stylesheet" href="templates/marigolds/css/style.css"> + <body style="height:auto;"> + <form action="action.php?action=importFeed" method="POST" enctype="multipart/form-data"> + <p>'._t('OPML_FILE').' : <input name="newImport" type="file"/> <button name="importButton">'._t('IMPORT').'</button></p> + <p>'._t('IMPORT_COFFEE_TIME').'</p> + </form> + </body> + </html> + + '; + break; + + case 'synchronizeForm': + if(isset($myUser) && $myUser!=false){ + echo '<link rel="stylesheet" href="templates/marigolds/css/style.css"> + <a class="button" href="action.php?action=synchronize">'._t('SYNCHRONIZE_NOW').'</a> + <p>'._t('SYNCHRONIZE_COFFEE_TIME').'</p> + + '; + }else{ + echo _t('YOU_MUST_BE_CONNECTED_ACTION'); + } + + break; + + case 'changeFolderState': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + $folderManager->change(array('isopen'=>$_['isopen']),array('id'=>$_['id'])); + break; + + case 'importFeed': + // On ne devrait pas mettre de style ici. + echo "<html> + <style> + a { + color:#F16529; + } + + html,body{ + font-family:Verdana; + font-size: 11px; + } + .error{ + background-color:#C94141; + color:#ffffff; + padding:5px; + border-radius:5px; + margin:10px 0px 10px 0px; + box-shadow: 0 0 3px 0 #810000; + } + .error a{ + color:#ffffff; + } + </style> + </style><body> +\n"; + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + if(!isset($_POST['importButton'])) break; + $opml = new Opml(); + echo "<h3>"._t('IMPORT')."</h3><p>"._t('PENDING')."</p>\n"; + try { + $errorOutput = $opml->import($_FILES['newImport']['tmp_name']); + } catch (Exception $e) { + $errorOutput = array($e->getMessage()); + } + if (empty($errorOutput)) { + echo "<p>"._t('IMPORT_NO_PROBLEM')."</p>\n"; + } else { + echo "<div class='error'>"._t('IMPORT_ERROR')."\n"; + foreach($errorOutput as $line) { + echo "<p>$line</p>\n"; + } + echo "</div>"; + } + if (!empty($opml->alreadyKnowns)) { + echo "<h3>"._t('IMPORT_FEED_ALREADY_KNOWN')." : </h3>\n<ul>\n"; + foreach($opml->alreadyKnowns as $alreadyKnown) { + foreach($alreadyKnown as &$elt) $elt = htmlspecialchars($elt); + $text = Functions::truncate($alreadyKnown->feedName, 60); + echo "<li><a target='_parent' href='{$alreadyKnown->xmlUrl}'>" + ."{$text}</a></li>\n"; + } + echo "</ul>\n"; + } + $syncLink = "action.php?action=synchronize&format=html"; + echo "<p>"; + echo "<a href='$syncLink' style='text-decoration:none;font-size:3em'>" + ."↺</a>"; + echo "<a href='$syncLink'>"._t('CLIC_HERE_SYNC_IMPORT')."</a>"; + echo "<p></body></html>\n"; + break; + + + case 'addFeed': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + require_once("SimplePie.class.php"); + if(!isset($_['newUrl'])) break; + $newFeed = new Feed(); + $newFeed->setUrl(Functions::clean_url($_['newUrl'])); + if ($newFeed->notRegistered()) { + ///@TODO: avertir l'utilisateur du doublon non ajouté + $newFeed->getInfos(); + $newFeed->setFolder( + (isset($_['newUrlCategory'])?$_['newUrlCategory']:1) + ); + $newFeed->save(); + $enableCache = ($configurationManager->get('synchronisationEnableCache')=='')?0:$configurationManager->get('synchronisationEnableCache'); + $forceFeed = ($configurationManager->get('synchronisationForceFeed')=='')?0:$configurationManager->get('synchronisationForceFeed'); + $newFeed->parse(time(), $_, $enableCache, $forceFeed); + } + header('location: ./settings.php#manageBloc'); + break; + + case 'changeFeedFolder': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + if(isset($_['feed'])){ + $feedManager->change(array('folder'=>$_['folder']),array('id'=>$_['feed'])); + } + header('location: ./settings.php'); + break; + + case 'removeFeed': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + if(isset($_GET['id'])){ + $feedManager->delete(array('id'=>$_['id'])); + $eventManager->delete(array('feed'=>$_['id'])); + } + header('location: ./settings.php'); + break; + + case 'addFolder': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + if(isset($_['newFolder'])){ + $folder = new Folder(); + if($folder->rowCount(array('name'=>$_['newFolder']))==0){ + $folder->setParent(-1); + $folder->setIsopen(0); + $folder->setName($_['newFolder']); + $folder->save(); + } + } + header('location: ./settings.php'); + break; + + + case 'renameFolder': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + if(isset($_['id'])){ + $folderManager->change(array('name'=>$_['name']),array('id'=>$_['id'])); + } + break; + + case 'renameFeed': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + if(isset($_['id'])){ + $feedManager->change(array('name'=>$_['name'],'url'=>Functions::clean_url($_['url'])),array('id'=>$_['id'])); + } + break; + + case 'removeFolder': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + if(isset($_['id']) && is_numeric($_['id']) && $_['id']>0){ + $eventManager->customExecute('DELETE FROM '.MYSQL_PREFIX.'event WHERE '.MYSQL_PREFIX.'event.feed in (SELECT '.MYSQL_PREFIX.'feed.id FROM '.MYSQL_PREFIX.'feed WHERE '.MYSQL_PREFIX.'feed.folder =\''.intval($_['id']).'\') ;'); + $feedManager->delete(array('folder'=>$_['id'])); + $folderManager->delete(array('id'=>$_['id'])); + } + header('location: ./settings.php'); + break; + + case 'readContent': + if($myUser==false) { + $response_array['status'] = 'noconnect'; + $response_array['texte'] = _t('YOU_MUST_BE_CONNECTED_ACTION'); + header('Content-type: application/json'); + echo json_encode($response_array); + exit(); + } + if(isset($_['id'])){ + $event = $eventManager->load(array('id'=>$_['id'])); + $eventManager->change(array('unread'=>'0'),array('id'=>$_['id'])); + } + break; + + case 'unreadContent': + if($myUser==false) { + $response_array['status'] = 'noconnect'; + $response_array['texte'] = _t('YOU_MUST_BE_CONNECTED_ACTION'); + header('Content-type: application/json'); + echo json_encode($response_array); + exit(); + } + if(isset($_['id'])){ + $event = $eventManager->load(array('id'=>$_['id'])); + $eventManager->change(array('unread'=>'1'),array('id'=>$_['id'])); + } + break; + + case 'addFavorite': + if($myUser==false) { + $response_array['status'] = 'noconnect'; + $response_array['texte'] = _t('YOU_MUST_BE_CONNECTED_ACTION'); + header('Content-type: application/json'); + echo json_encode($response_array); + exit(); + } + $eventManager->change(array('favorite'=>'1'),array('id'=>$_['id'])); + break; + + case 'removeFavorite': + if($myUser==false) { + $response_array['status'] = 'noconnect'; + $response_array['texte'] = _t('YOU_MUST_BE_CONNECTED_ACTION'); + header('Content-type: application/json'); + echo json_encode($response_array); + exit(); + } + $eventManager->change(array('favorite'=>'0'),array('id'=>$_['id'])); + break; + + case 'login': + + define('RESET_PASSWORD_FILE', 'resetPassword'); + if (file_exists(RESET_PASSWORD_FILE)) { + /* Pour réinitialiser le mot de passe : + * créer le fichier RESET_PASSWORD_FILE vide. + * Le nouveau mot de passe sera celui fourni à la connexion. + */ + @unlink(RESET_PASSWORD_FILE); + if (file_exists(RESET_PASSWORD_FILE)) { + $message = 'Unable to remove "'.RESET_PASSWORD_FILE.'"!'; + /* Pas supprimable ==> on ne remet pas à zéro */ + } else { + $resetPassword = $_['password']; + assert('!empty($resetPassword)'); + $tmpUser = User::get($_['login']); + if (false===$tmpUser) { + $message = "Unknown user '{$_['login']}'! No password reset."; + } else { + $id = $tmpUser->getId(); + $salt = $configurationManager->get('cryptographicSalt'); + $userManager->change( + array('password'=>User::encrypt($resetPassword, $salt)), + array('id'=>$id) + ); + $message = "User '{$_['login']}' (id=$id) Password reset to '$resetPassword'."; + } + } + error_log($message); + } + + if(isset($_['usr'])){ + $user = User::existAuthToken($_['usr']); + if($user==false){ + exit("erreur identification : le compte est inexistant"); + }else{ + $_SESSION['currentUser'] = serialize($user); + header('location: ./action.php?action=addFeed&newUrl='.$_['newUrl']); + } + }else{ + $salt = $configurationManager->get('cryptographicSalt'); + if (empty($salt)) $salt = ''; + $user = $userManager->exist($_['login'],$_['password'],$salt); + if($user==false){ + exit("erreur identification : le compte est inexistant"); + }else{ + $_SESSION['currentUser'] = serialize($user); + if (isset($_['rememberMe'])) $user->setStayConnected(); + } + header('location: ./index.php'); + } + + + + break; + + case 'changePluginState': + if($myUser==false) exit(_t('YOU_MUST_BE_CONNECTED_ACTION')); + + if($_['state']=='0'){ + Plugin::enabled($_['plugin']); + + }else{ + Plugin::disabled($_['plugin']); + } + header('location: ./settings.php#pluginBloc'); + break; + + + + case 'logout': + User::delStayConnected(); + $_SESSION = array(); + session_unset(); + session_destroy(); + header('location: ./index.php'); + break; + + case 'displayOnlyUnreadFeedFolder': + if($myUser==false) { + $response_array['status'] = 'noconnect'; + $response_array['texte'] = _t('YOU_MUST_BE_CONNECTED_ACTION'); + header('Content-type: application/json'); + echo json_encode($response_array); + exit(); + } + $configurationManager->put('displayOnlyUnreadFeedFolder',$_['displayOnlyUnreadFeedFolder']); + break; + + case 'displayFeedIsVerbose': + if($myUser==false) { + $response_array['status'] = 'noconnect'; + $response_array['texte'] = _t('YOU_MUST_BE_CONNECTED_ACTION'); + header('Content-type: application/json'); + echo json_encode($response_array); + exit(); + } + // changement du statut isverbose du feed + $feed = new Feed(); + $feed = $feed->getById($_['idFeed']); + $feed->setIsverbose(($_['displayFeedIsVerbose']=="0"?1:0)); + $feed->save(); + break; + + case 'optionFeedIsVerbose': + if($myUser==false) { + $response_array['status'] = 'noconnect'; + $response_array['texte'] = _t('YOU_MUST_BE_CONNECTED_ACTION'); + header('Content-type: application/json'); + echo json_encode($response_array); + exit(); + } + // changement du statut de l'option + $configurationManager = new Configuration(); + $conf = $configurationManager->getAll(); + $configurationManager->put('optionFeedIsVerbose',($_['optionFeedIsVerbose']=="0"?0:1)); + + break; + + default: + require_once("SimplePie.class.php"); + Plugin::callHook("action_post_case", array(&$_,$myUser)); + //exit('0'); + break; +} + + +?> diff --git a/sources/article.php b/sources/article.php new file mode 100755 index 0000000..6b92de6 --- /dev/null +++ b/sources/article.php @@ -0,0 +1,93 @@ +<?php + +/* + @nom: article + @auteur: Maël ILLOUZ (mael.illouz@cobestran.com) + @description: Page de gestion de l'affichage des articles. Sera utilisé de base ainsi que pour le scroll infini + */ + +include ('common.php'); +$view = "article"; +//recuperation de tous les flux +$allFeeds = $feedManager->getFeedsPerFolder(); +$tpl->assign('allFeeds',$allFeeds); +$tpl->assign('scrollpage',$_['scroll']); +// récupération des variables pour l'affichage +$articleDisplayContent = $configurationManager->get('articleDisplayContent'); +$articleView = $configurationManager->get('articleView'); +$articlePerPages = $configurationManager->get('articlePerPages'); +$articleDisplayLink = $configurationManager->get('articleDisplayLink'); +$articleDisplayDate = $configurationManager->get('articleDisplayDate'); +$articleDisplayAuthor = $configurationManager->get('articleDisplayAuthor'); +$articleDisplayHomeSort = $configurationManager->get('articleDisplayHomeSort'); +$articleDisplayFolderSort = $configurationManager->get('articleDisplayFolderSort'); +$optionFeedIsVerbose = $configurationManager->get('optionFeedIsVerbose'); + +$tpl->assign('articleView',$articleView); +$tpl->assign('articleDisplayLink',$articleDisplayLink); +$tpl->assign('articleDisplayDate',$articleDisplayDate); +$tpl->assign('articleDisplayAuthor',$articleDisplayAuthor); +$tpl->assign('articleDisplayContent',$articleDisplayContent); + + +$hightlighted = $_['hightlighted']; +$tpl->assign('hightlighted',$hightlighted); + +$tpl->assign('time',$_SERVER['REQUEST_TIME']); + +$target = MYSQL_PREFIX.'event.title,'.MYSQL_PREFIX.'event.unread,'.MYSQL_PREFIX.'event.favorite,'.MYSQL_PREFIX.'event.feed,'; +if($articleDisplayContent && $articleView=='partial') $target .= MYSQL_PREFIX.'event.description,'; +if($articleDisplayContent && $articleView!='partial') $target .= MYSQL_PREFIX.'event.content,'; +if($articleDisplayLink) $target .= MYSQL_PREFIX.'event.link,'; +if($articleDisplayDate) $target .= MYSQL_PREFIX.'event.pubdate,'; +if($articleDisplayAuthor) $target .= MYSQL_PREFIX.'event.creator,'; +$target .= MYSQL_PREFIX.'event.id'; + +$startArticle = ($_['scroll']*$articlePerPages)-$_['nblus']; +if ($startArticle < 0) $startArticle=0; +$action = $_['action']; + +switch($action){ + /* AFFICHAGE DES EVENEMENTS D'UN FLUX EN PARTICULIER */ + case 'selectedFeed': + $currentFeed = $feedManager->getById($_['feed']); + $allowedOrder = array('date'=>'pubdate DESC','older'=>'pubdate','unread'=>'unread DESC,pubdate DESC'); + $order = (isset($_['order'])?$allowedOrder[$_['order']]:$allowedOrder['date']); + $events = $currentFeed->getEvents($startArticle,$articlePerPages,$order,$target); + break; + /* AFFICHAGE DES EVENEMENTS D'UN FLUX EN PARTICULIER en mode non lus */ + case 'selectedFeedNonLu': + $currentFeed = $feedManager->getById($_['feed']); + $filter = array('unread'=>1, 'feed'=>$currentFeed->getId()); + $order = 'pubdate DESC'; + $events = $eventManager->loadAllOnlyColumn($target,$filter,$order,$startArticle.','.$articlePerPages); + break; + /* AFFICHAGE DES EVENEMENTS D'UN DOSSIER EN PARTICULIER */ + case 'selectedFolder': + $currentFolder = $folderManager->getById($_['folder']); + if($articleDisplayFolderSort) {$order = MYSQL_PREFIX.'event.pubdate desc';} else {$order = MYSQL_PREFIX.'event.pubdate asc';} + $events = $currentFolder->getEvents($startArticle,$articlePerPages,$order,$target); + break; + /* AFFICHAGE DES EVENEMENTS FAVORIS */ + case 'favorites': + $events = $eventManager->loadAllOnlyColumn($target,array('favorite'=>1),'pubdate DESC',$startArticle.','.$articlePerPages); + break; + /* AFFICHAGE DES EVENEMENTS NON LUS (COMPORTEMENT PAR DEFAUT) */ + case 'unreadEvents': + default: + $filter = array('unread'=>1); + if($articleDisplayHomeSort) {$order = 'pubdate desc';} else {$order = 'pubdate asc';} + if($optionFeedIsVerbose) { + $events = $eventManager->loadAllOnlyColumn($target,$filter,$order,$startArticle.','.$articlePerPages); + } else { + $events = $eventManager->getEventsNotVerboseFeed($startArticle,$articlePerPages,$order,$target); + } + break; +} +$tpl->assign('events',$events); +$tpl->assign('scroll',$_['scroll']); +$view = "article"; +Plugin::callHook("index_post_treatment", array(&$events)); +$html = $tpl->draw($view); + +?> diff --git a/sources/common.php b/sources/common.php new file mode 100755 index 0000000..93dd5bb --- /dev/null +++ b/sources/common.php @@ -0,0 +1,127 @@ +<?php + +/* + @nom: common + @auteur: Idleman (idleman@idleman.fr) + @description: Page incluse dans tous (ou presque) les fichiers du projet, inclus les entitées SQL et récupère/traite les variables de requetes + */ + +define('VERSION_NUMBER_CODE','1.6.1'); +define('VERSION_NAME_CODE','Stable'); + +/* ----------MAJ de la version du constant.php--------------------- */ +if (is_writable('constant.php')) { + $content = file_get_contents('constant.php'); + preg_match('#define\(\'VERSION_NUMBER\',\'([A-Za-z0-9.]+)\'\);?#',$content,$matches_version); + preg_match('#define\(\'VERSION_NAME\',\'([A-Za-z0-9.]+)\'\);?#',$content,$matches_name); + if ($matches_version[1]!=VERSION_NUMBER_CODE or $matches_name[1]!=VERSION_NAME_CODE) + { + $content = preg_replace('#define\(\'VERSION_NUMBER\',\'([A-Za-z0-9.]+)\'\);?#','define(\'VERSION_NUMBER\',\''.VERSION_NUMBER_CODE.'\');', $content); + $content = preg_replace('#define\(\'VERSION_NAME\',\'([A-Za-z0-9.]+)\'\);?#','define(\'VERSION_NAME\',\''.VERSION_NAME_CODE.'\');', $content); + file_put_contents('constant.php', $content); + } +}; +/* ---------------------------------------------------------------- */ +// Mise en place d'un timezone par default pour utiliser les fonction de date en php +$timezone_default = 'Europe/Paris'; // valeur par défaut :) +date_default_timezone_set($timezone_default); +$timezone_phpini = ini_get('date.timezone'); +if (($timezone_phpini!='') && (strcmp($timezone_default, $timezone_phpini))) { + date_default_timezone_set($timezone_phpini); +} +/* ---------------------------------------------------------------- */ +$cookiedir = ''; +if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/'; +session_set_cookie_params(0, $cookiedir); +session_start(); +mb_internal_encoding('UTF-8'); // UTF8 pour fonctions mb_* +$start=microtime(true); +require_once('constant.php'); +//@todo requis pour la MAJ 1.5 vers 1.6 mais pourra être supprimé. +if (!defined('LANGUAGE')) { + preg_match('#define\(\'LANGAGE\',\'([A-Za-z0-9.]+)\'\);?#',$content,$matches_language); + if (isset($matches_language[1]) && isset($matches_language[1])!='') { + // pour ceux qui viennent de la branche de dev avant update en LANGUAGE. + $content = preg_replace('#define\(\'LANGAGE\',\'([A-Za-z0-9.]+)\'\);?#','define(\'LANGUAGE\',\''.$matches_language[1].'\');', $content); + file_put_contents('constant.php', $content); + define('LANGUAGE', $matches_language[1]); // ancienne constante encore utilisée + } else { + // pour ceux qui viennent de la v1.5. la variable n'existait pas + $content = preg_replace('#\?\>#',"//Langue utilisée\ndefine('LANGUAGE','fr');\n?>", $content); + file_put_contents('constant.php', $content); + define('LANGUAGE', 'fr'); + } +} +// fin MAJ 1.5 vers 1.6 +require_once('RainTPL.php'); +require_once('i18n.php'); +class_exists('Plugin') or require_once('Plugin.class.php'); +class_exists('MysqlEntity') or require_once('MysqlEntity.class.php'); +class_exists('Update') or require_once('Update.class.php'); +$resultUpdate = Update::ExecutePatch(); +class_exists('Feed') or require_once('Feed.class.php'); +class_exists('Event') or require_once('Event.class.php'); +class_exists('Functions') or require_once('Functions.class.php'); +class_exists('User') or require_once('User.class.php'); +class_exists('Folder') or require_once('Folder.class.php'); +class_exists('Configuration') or require_once('Configuration.class.php'); +class_exists('Opml') or require_once('Opml.class.php'); + + +//error_reporting(E_ALL); + +//Calage de la date +date_default_timezone_set('Europe/Paris'); + +$userManager = new User(); +$myUser = (isset($_SESSION['currentUser'])?unserialize($_SESSION['currentUser']):false); +if (empty($myUser)) { + /* Pas d'utilisateur dans la session ? + * On tente de récupérer une nouvelle session avec un jeton. */ + $myUser = User::existAuthToken(); + $_SESSION['currentUser'] = serialize($myUser); +} + +$feedManager = new Feed(); +$eventManager = new Event(); +$folderManager = new Folder(); +$configurationManager = new Configuration(); +$conf = $configurationManager->getAll(); + +//Instanciation du template +$tpl = new RainTPL(); +//Definition des dossiers de template +raintpl::configure("base_url", null ); +raintpl::configure("tpl_dir", './templates/'.DEFAULT_THEME.'/' ); +raintpl::configure("cache_dir", "./cache/tmp/" ); + +i18n_init(LANGUAGE); +if ($resultUpdate) die (_t('LEED_UPDATE_MESSAGE')); + +$view = ''; +$tpl->assign('myUser',$myUser); +$tpl->assign('feedManager',$feedManager); +$tpl->assign('eventManager',$eventManager); +$tpl->assign('userManager',$userManager); +$tpl->assign('folderManager',$folderManager); +$tpl->assign('configurationManager',$configurationManager); +$tpl->assign('synchronisationCode',$configurationManager->get('synchronisationCode')); + +//Récuperation et sécurisation de toutes les variables POST et GET +$_ = array(); +foreach($_POST as $key=>$val){ + $_[$key]=Functions::secure($val, 2); // on ne veut pas d'addslashes +} +foreach($_GET as $key=>$val){ + $_[$key]=Functions::secure($val, 2); // on ne veut pas d'addslashes +} + +$tpl->assign('_',$_); +$tpl->assign('action',''); + +//Inclusion des plugins +Plugin::includeAll(); +// pour inclure aussi les traductions des plugins dans les js +$tpl->assign('i18n_js',$i18n_js); + +?> diff --git a/sources/footer.php b/sources/footer.php new file mode 100755 index 0000000..6f752eb --- /dev/null +++ b/sources/footer.php @@ -0,0 +1,5 @@ +<?php + +$tpl->assign('executionTime',number_format(microtime(true)-$start,3)); +$html = $tpl->draw($view); +?> \ No newline at end of file diff --git a/sources/header.php b/sources/header.php new file mode 100755 index 0000000..fa4beec --- /dev/null +++ b/sources/header.php @@ -0,0 +1,8 @@ +<?php +if(!file_exists('constant.php')) { + header('location: install.php'); + exit(); +} +require_once('common.php'); + +?> diff --git a/sources/humans.txt b/sources/humans.txt new file mode 100755 index 0000000..839a562 --- /dev/null +++ b/sources/humans.txt @@ -0,0 +1,21 @@ +/* the humans responsible & colophon */ +/* humanstxt.org */ + + +/* TEAM */ + Creator: Idleman (idleman@idleman.fr) + Contributors: Sbgodin (http://sbgodin.fr) + Simounet (contact@simounet.net) + Alef-Burzmali (thomas.fargeix@burzmali.com) + Cobalt74 (cobalt74@gmail.com) + Site: http://blog.idleman.fr + Twitter: none + Location: Nowhere + +/* THANKS */ + Names (& URL): BoilerPlate, Modernizr, jQuery, Initializr, PHP authors & chuck norris + +/* SITE */ + Standards: HTML5, CSS3 + Components: Modernizr, jQuery + Software: Sublime Text2 diff --git a/sources/i18n.php b/sources/i18n.php new file mode 100755 index 0000000..408769b --- /dev/null +++ b/sources/i18n.php @@ -0,0 +1,129 @@ +<?php + +/* + @nom: i18n + @auteur: Idleman (idleman@idleman.fr) + @description: Fonctions de gestion de la traduction + */ + +class Translation { + + // Répertoire contenant les traductions + const LOCALE_DIR = 'locale'; + + /* Langue utilisée si aucune langue n'est demandée ou si les langues + * demandées ne sont pas disponibles. Idem pour les traductions.*/ + const DEFAULT_LANGUAGE = 'fr'; + + // tableau associatif des traductions + var $trans = array(); + var $language = ''; // langue courante + var $languages = array(); // langues disponibles + + /** @param location L'endroit où se trouve le dossier 'locale' + * @param languages Les langues demandées */ + function __construct($location, $languages=array()) { + $this->location = $location; + if (!is_array($languages)) $languages = array($languages); + $this->listLanguages(); + $languages[]=self::DEFAULT_LANGUAGE; + foreach ($languages as $language) + if ($this->load($language)) { + $this->language = $language; + break; + } + } + + /* Peuple la liste des langues avec une traduction */ + protected function listLanguages() { + $this->languages = array(); + $files = glob($this->location.'/'.self::LOCALE_DIR.'/*.json'); + if (is_array($files)) { + foreach($files as $file){ + preg_match('/([a-z]{2})\.json$/', $file, $matches); + assert('!empty($matches)'); + $this->languages [] = $matches[1]; + } + } + } + + /* Charge la traduction + * @param language la langue sélectionnée + * @return TRUE si le chargement s'est bien fait, FALSE sinon */ + protected function load($language) { + if (!preg_match('/^[a-z]{2}$/', $language)) { + error_log("Invalid language: '$language'"); + return false; + } + $trans = $this->loadFile($language); + if (empty($trans)) return false; + assert('in_array($language, $this->languages)'); + if ($language!=self::DEFAULT_LANGUAGE) { + $defaultTrans = $this->loadFile(self::DEFAULT_LANGUAGE); + assert('!empty($defaultTrans)'); + $trans = array_merge($defaultTrans, $trans); + } + $this->trans = $trans; + return true; + } + + /* Charge un fichier + * @param $language Le fichier de langue concerné + * @return Tableau associatif contenant les traductions */ + protected function loadFile($language) { + $fileName = $this->location.'/'.self::LOCALE_DIR.'/'.$language.'.json'; + $content = @file_get_contents($fileName); + if (empty($content)) { + $translations = array(); + } else { + $translations = json_decode($content, true); + } + return $translations; + } + + /* Retourne la traduction et substitue les variables. + * get('TEST_TRANS', array('4')) + * Retournera 'Nombre : 4' si TEST_TRANS == 'Nombre : $1' */ + function get($key, $args=array()) { + if (isset($this->trans[$key])) { + $value = $this->trans[$key]; + for($i=0;$i<count($args);$i++){ + $value = str_replace('$'.($i+1), $args[$i], $value); + } + } else { + $value = $key; + } + return $value; + } + + /* Ajoute une traduction à la suite de celle-ci. + * Note : il faudra appeler getJson() si nécessaire */ + function append(Translation $other) { + $this->trans = array_merge($this->trans, $other->trans); + } + + /* @return la version Json des traductions */ + function getJson() { + return json_encode($this->trans); + } + +} + +// Initialise le singleton, avec les langues possibles +function i18n_init($languages){ + global $i18n,$i18n_js; + if (!isset($i18n)) { + $i18n = new Translation(dirname(__FILE__), $languages); + $i18n_js = $i18n->getJson(); + } + return $i18n->language; +} + +// Appel rapide de la traduction +function _t($key,$args=array(),$debug=false){ + global $i18n; + return $i18n->get($key, $args); +} + + +?> diff --git a/sources/index.php b/sources/index.php new file mode 100755 index 0000000..f5d195a --- /dev/null +++ b/sources/index.php @@ -0,0 +1,161 @@ +<?php + +/* + @nom: index + @auteur: Idleman (idleman@idleman.fr) + @description: Page d'accueil et de lecture des flux +*/ + +require_once('header.php'); + + +Plugin::callHook("index_pre_treatment", array(&$_)); + +//Récuperation de l'action (affichage) demandée +$action = (isset($_['action'])?$_['action']:''); +$tpl->assign('action',$action); +//Récuperation des dossiers de flux par ordre de nom +$tpl->assign('folders',$folderManager->populate('name')); +//Recuperation de tous les non Lu +$tpl->assign('unread',$feedManager->countUnreadEvents()); +//recuperation de tous les flux +$allFeeds = $feedManager->getFeedsPerFolder(); +$tpl->assign('allFeeds',$allFeeds); +//recuperation de tous les flux par dossier +$tpl->assign('allFeedsPerFolder',$allFeeds['folderMap']); +//recuperation de tous les event nons lu par dossiers +$tpl->assign('allEvents',$eventManager->getEventCountPerFolder()); + + +$articleDisplayContent = $configurationManager->get('articleDisplayContent'); +$articleView = $configurationManager->get('articleView'); +$articlePerPages = $configurationManager->get('articlePerPages'); +$articleDisplayLink = $configurationManager->get('articleDisplayLink'); +$articleDisplayDate = $configurationManager->get('articleDisplayDate'); +$articleDisplayAuthor = $configurationManager->get('articleDisplayAuthor'); +$articleDisplayHomeSort = $configurationManager->get('articleDisplayHomeSort'); +$articleDisplayFolderSort = $configurationManager->get('articleDisplayFolderSort'); +$displayOnlyUnreadFeedFolder = $configurationManager->get('displayOnlyUnreadFeedFolder'); +if (!isset($displayOnlyUnreadFeedFolder)) $displayOnlyUnreadFeedFolder=false; +($displayOnlyUnreadFeedFolder=='true')?$displayOnlyUnreadFeedFolder_reverse='false':$displayOnlyUnreadFeedFolder_reverse='true'; +$optionFeedIsVerbose = $configurationManager->get('optionFeedIsVerbose'); + +$tpl->assign('articleDisplayContent',$configurationManager->get('articleDisplayContent')); +$tpl->assign('articleView',$configurationManager->get('articleView')); +$tpl->assign('articlePerPages',$configurationManager->get('articlePerPages')); +$tpl->assign('articleDisplayLink',$configurationManager->get('articleDisplayLink')); +$tpl->assign('articleDisplayDate',$configurationManager->get('articleDisplayDate')); +$tpl->assign('articleDisplayAuthor',$configurationManager->get('articleDisplayAuthor')); +$tpl->assign('articleDisplayHomeSort',$configurationManager->get('articleDisplayHomeSort')); +$tpl->assign('articleDisplayFolderSort',$configurationManager->get('articleDisplayFolderSort')); +$tpl->assign('displayOnlyUnreadFeedFolder',$displayOnlyUnreadFeedFolder); +$tpl->assign('displayOnlyUnreadFeedFolder_reverse',$displayOnlyUnreadFeedFolder_reverse); + +$target = MYSQL_PREFIX.'event.title,'.MYSQL_PREFIX.'event.unread,'.MYSQL_PREFIX.'event.favorite,'.MYSQL_PREFIX.'event.feed,'; +if($articleDisplayContent && $articleView=='partial') $target .= MYSQL_PREFIX.'event.description,'; +if($articleDisplayContent && $articleView!='partial') $target .= MYSQL_PREFIX.'event.content,'; +$target .= MYSQL_PREFIX.'event.link,'; +if($articleDisplayDate) $target .= MYSQL_PREFIX.'event.pubdate,'; +if($articleDisplayAuthor) $target .= MYSQL_PREFIX.'event.creator,'; +$target .= MYSQL_PREFIX.'event.id'; + +$tpl->assign('target',$target); +$tpl->assign('feeds',''); +$tpl->assign('order',''); +$tpl->assign('unreadEventsForFolder',''); +$pagesArray = array(); + +switch($action){ + /* AFFICHAGE DES EVENEMENTS D'UN FLUX EN PARTICULIER */ + case 'selectedFeed': + $currentFeed = $feedManager->getById($_['feed']); + $tpl->assign('currentFeed',$currentFeed); + $numberOfItem = $eventManager->rowCount(array('feed'=>$currentFeed->getId())); + $allowedOrder = array('date'=>'pubdate DESC','older'=>'pubdate','unread'=>'unread DESC,pubdate DESC'); + $order = (isset($_['order'])?$allowedOrder[$_['order']]:$allowedOrder['date']); + $page = (isset($_['page'])?$_['page']:1); + $pages = ceil($numberOfItem/$articlePerPages); + $startArticle = ($page-1)*$articlePerPages; + $events = $currentFeed->getEvents($startArticle,$articlePerPages,$order,$target); + + $tpl->assign('order',(isset($_['order'])?$_['order']:'')); + + break; + /* AFFICHAGE DES EVENEMENTS D'UN FLUX EN PARTICULIER en mode non lus */ + case 'selectedFeedNonLu': + $currentFeed = $feedManager->getById($_['feed']); + $tpl->assign('currentFeed',$currentFeed); + $filter = array('unread'=>1, 'feed'=>$currentFeed->getId()); + $numberOfItem = $eventManager->rowCount($filter); + $order = 'pubdate DESC'; + $page = (isset($_['page'])?$_['page']:1); + $pages = ceil($numberOfItem/$articlePerPages); + $startArticle = ($page-1)*$articlePerPages; + $events = $eventManager->loadAllOnlyColumn($target,$filter,$order,$startArticle.','.$articlePerPages); + + break; + /* AFFICHAGE DES EVENEMENTS D'UN DOSSIER EN PARTICULIER */ + case 'selectedFolder': + $currentFolder = $folderManager->getById($_['folder']); + $tpl->assign('currentFolder',$currentFolder); + $numberOfItem = $currentFolder->unreadCount(); + $page = (isset($_['page'])?$_['page']:1); + $pages = ceil($numberOfItem/$articlePerPages); + $startArticle = ($page-1)*$articlePerPages; + if($articleDisplayFolderSort) {$order = MYSQL_PREFIX.'event.pubdate desc';} else {$order = MYSQL_PREFIX.'event.pubdate asc';} + $events = $currentFolder->getEvents($startArticle,$articlePerPages,$order,$target); + + + break; + /* AFFICHAGE DES EVENEMENTS FAVORIS */ + case 'favorites': + $numberOfItem = $eventManager->rowCount(array('favorite'=>1)); + $page = (isset($_['page'])?$_['page']:1); + $pages = ceil($numberOfItem/$articlePerPages); + $startArticle = ($page-1)*$articlePerPages; + $events = $eventManager->loadAllOnlyColumn($target,array('favorite'=>1),'pubdate DESC',$startArticle.','.$articlePerPages); + $tpl->assign('numberOfItem',$numberOfItem); + break; + + /* AFFICHAGE DES EVENEMENTS NON LUS (COMPORTEMENT PAR DEFAUT) */ + case 'unreadEvents': + default: + $filter = array('unread'=>1); + if($optionFeedIsVerbose) { + $numberOfItem = $eventManager->rowCount($filter); + } else { + $numberOfItem = $eventManager->getEventCountNotVerboseFeed(); + } + $page = (isset($_['page'])?$_['page']:1); + $pages = ($articlePerPages>0?ceil($numberOfItem/$articlePerPages):1); + $startArticle = ($page-1)*$articlePerPages; + if($articleDisplayHomeSort) {$order = 'pubdate desc';} else {$order = 'pubdate asc';} + if($optionFeedIsVerbose) { + $events = $eventManager->loadAllOnlyColumn($target,$filter,$order,$startArticle.','.$articlePerPages); + } else { + $events = $eventManager->getEventsNotVerboseFeed($startArticle,$articlePerPages,$order,$target); + } + $tpl->assign('numberOfItem',$numberOfItem); + + break; +} +$tpl->assign('pages',$pages); +$tpl->assign('page',$page); + +for($i=($page-PAGINATION_SCALE<=0?1:$page-PAGINATION_SCALE);$i<($page+PAGINATION_SCALE>$pages+1?$pages+1:$page+PAGINATION_SCALE);$i++){ + $pagesArray[]=$i; +} +$tpl->assign('pagesArray',$pagesArray); +$tpl->assign('previousPages',($page-PAGINATION_SCALE<0?-1:$page-PAGINATION_SCALE-1)); +$tpl->assign('nextPages',($page+PAGINATION_SCALE>$pages+1?-1:$page+PAGINATION_SCALE)); + + +Plugin::callHook("index_post_treatment", array(&$events)); +$tpl->assign('events',$events); +$tpl->assign('time',$_SERVER['REQUEST_TIME']); +$tpl->assign('hightlighted',0); +$tpl->assign('scroll',false); + +$view = 'index'; +require_once('footer.php'); +?> diff --git a/sources/install.php b/sources/install.php new file mode 100755 index 0000000..aa63d26 --- /dev/null +++ b/sources/install.php @@ -0,0 +1,386 @@ +<?php + +/* + @nom: install + @auteur: Idleman (idleman@idleman.fr) + @description: Page d'installation du script (a supprimer après installation) + */ + +require_once('Functions.class.php'); +require_once('i18n.php'); +global $i18n; +$install_terminee=false; + +if (isset($_GET['lang'])) + $currentLanguage = i18n_init($_GET['lang']); +else + $currentLanguage = i18n_init(Functions::getBrowserLanguages()); + +$languageList = $i18n->languages; + +if (file_exists('constant.php')) { + die(_t('ALREADY_INSTALLED')); +} + +// Cookie de la session +$cookiedir = ''; +if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/'; +session_set_cookie_params(0, $cookiedir); +session_start(); + +// Protection des variables +$_ = array_merge($_GET, $_POST); +$whiteList = array( + /* La liste blanche recense les variables ne devant pas être passées via + la sécurisation, mais simplement échappées pour Php. */ + 'mysqlHost', 'mysqlLogin', 'mysqlMdp', 'mysqlBase', 'mysqlPrefix', +); +foreach($_ as $key=>&$val){ + $val = in_array($key, $whiteList) + ? str_replace("'", "\'", $val) + : Functions::secure($val); +} + +// Valeurs par défaut, remplacées si une autre valeur est saisie. +foreach (array('login','mysqlBase','mysqlHost','mysqlLogin','mysqlMdp','mysqlPrefix','password','root') as $var) { + /* Initalise les variables avec le contenu des champs + * pour rappeler les valeurs déjà saisies. */ + if (!empty($_[$var])) + $$var = $_[$var]; + else + $$var = ''; +} +if (empty($root)) { + // Ne peut être vide, alors on met la valeur par défaut + $root = str_replace( + basename(__FILE__), + '', + 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'] + ); +} +if (!isset($_['mysqlPrefix'])) { + // Le formulaire n'étant pas soumis, on met cette valeur par défaut. + $mysqlPrefix = 'leed_'; +} + +$lib_errors = _t('ERROR'); +$lib_success = _t('SUCCESS'); + +if(isset($_['installButton'])){ + if (empty($_['password']) || empty($_['login'])) { + $test[$lib_errors][] = _t('INSTALL_ERROR_USERPWD'); + } + if (!Functions::testDb( + $_['mysqlHost'], $_['mysqlLogin'], $_['mysqlMdp'], $_['mysqlBase'] + )) { + $test[$lib_errors][] = _t('INSTALL_ERROR_CONNEXION'); + } else { + $test[$lib_success][] = _t('INSTALL_INFO_CONNEXION'); + } +} +if(!is_writable('./')){ + $test[$lib_errors][]=_t('INSTALL_ERROR_RIGHT', array(str_replace(basename(__FILE__),'',__FILE__))); +}else{ + $test[$lib_success][]=_t('INSTALL_INFO_RIGHT'); +} +if (!@function_exists('mysql_connect')){ + $test[$lib_errors][] = _t('INSTALL_ERROR_MYSQLCONNECT'); +}else{ + $test[$lib_success][] = _t('INSTALL_INFO_MYSQLCONNECT'); +} +if (!@function_exists('file_get_contents')){ + $test[$lib_errors][] = _t('INSTALL_ERROR_FILEGET'); +}else{ + $test[$lib_success][] = _t('INSTALL_INFO_FILEGET'); +} +if (!@function_exists('file_put_contents')){ + $test[$lib_errors][] = _t('INSTALL_ERROR_FILEPUT'); +}else{ + $test[$lib_success][] = _t('INSTALL_INFO_FILEPUT'); +} +if (@version_compare(PHP_VERSION, '5.1.0') <= 0){ + $test[$lib_errors][] = _t('INSTALL_ERROR_PHPV', array(PHP_VERSION)); +}else{ + $test[$lib_success][] = _t('INSTALL_INFO_PHPV', array(PHP_VERSION)); +} +if(ini_get('safe_mode') && ini_get('max_execution_time')!=0){ + $test[$lib_errors][] = _t('INSTALL_ERROR_SAFEMODE'); +}else{ + $test[$lib_success][] = _t('INSTALL_INFO_SAFEMODE'); +} + +if (isset($_['installButton']) && empty($test[$lib_errors])) { // Pas d'erreur, l'installation peut se faire. + $constant = "<?php + define('VERSION_NUMBER','1.6'); + define('VERSION_NAME','Stable'); + + //Host de Mysql, le plus souvent localhost ou 127.0.0.1 + define('MYSQL_HOST','{$mysqlHost}'); + //Identifiant MySQL + define('MYSQL_LOGIN','{$mysqlLogin}'); + //mot de passe MySQL + define('MYSQL_MDP','{$mysqlMdp}'); + //Nom de la base MySQL ou se trouvera leed + define('MYSQL_BDD','{$mysqlBase}'); + //Prefix des noms des tables leed pour les bases de données uniques + define('MYSQL_PREFIX','{$mysqlPrefix}'); + //Theme graphique + define('DEFAULT_THEME','marigolds'); + //Nombre de pages affichées dans la barre de pagination + define('PAGINATION_SCALE',5); + //Nombre de flux mis à jour lors de la synchronisation graduée + define('SYNC_GRAD_COUNT',10); + //Langue utilisée + define('LANGUAGE','".$_POST['install_changeLngLeed']."'); +?>"; + + file_put_contents('constant.php', $constant); + if (!is_readable('constant.php')) + die('"constant.php" not found!'); + + require_once('constant.php'); + require_once('MysqlEntity.class.php'); + class_exists('Update') or require_once('Update.class.php'); + Update::ExecutePatch(true); + require_once('Feed.class.php'); + require_once('Event.class.php'); + + require_once('User.class.php'); + require_once('Folder.class.php'); + require_once('Configuration.class.php'); + + $cryptographicSalt = User::generateSalt(); + $synchronisationCode = substr(sha1(rand(0,30).time().rand(0,30)),0,10); + $root = (substr($_['root'], strlen($_['root'])-1)=='/'?$_['root']:$_['root'].'/'); + + // DOSSIERS À CONSERVER TELS QUELS, SI DÉJÀ EXISTANTS + $feedManager = new Feed(); $feedManager->create(); + $eventManager = new Event(); $eventManager->create(); + + // COMPTE ADMINISTRATEUR, RÀZ SI NÉCESSAIRE + $userManager = new User(); + if ($userManager->tableExists()) { + // Suppose qu'il n'y a qu'un seul utilisateur + $userManager->truncate(); + } + $userManager->create(); + $admin = new User(); + $admin->setLogin($_['login']); + $admin->setPassword($_['password'],$cryptographicSalt); + $admin->save(); + $_SESSION['currentUser'] = serialize($admin); + + // DOSSIERS DE FLUX, RECRÉE LE DOSSIER GÉNÉRAL SI NÉCESSAIRE + $folderManager = new Folder(); + $folderManager->create(); + if ($folderManager->rowCount()==0) { + //Création du dossier général + $folder = new Folder(); + $folder->setName(_t('GENERAL_FOLDER')); + $folder->setParent(-1); + $folder->setIsopen(1); + $folder->save(); + } + + // REMET À ZÉRO LA CONFIGURATION + $configurationManager = new Configuration(); + if ($configurationManager->tableExists()) { + $configurationManager->truncate(); + } + $configurationManager->create(); + $configurationManager->add('root',$root); + $configurationManager->add('articleView','partial'); + $configurationManager->add('articleDisplayContent','1'); + $configurationManager->add('articleDisplayAnonymous','0'); + $configurationManager->add('articlePerPages','5'); + $configurationManager->add('articleDisplayLink','1'); + $configurationManager->add('articleDisplayDate','1'); + $configurationManager->add('articleDisplayAuthor','1'); + $configurationManager->add('articleDisplayHomeSort','1'); + $configurationManager->add('articleDisplayFolderSort','1'); + $configurationManager->add('displayOnlyUnreadFeedFolder','false'); + $configurationManager->add('optionFeedIsVerbose',1); + $configurationManager->add('synchronisationType','auto'); + $configurationManager->add('feedMaxEvents','50'); + $configurationManager->add('synchronisationCode',$synchronisationCode); + $configurationManager->add('synchronisationEnableCache','1'); + $configurationManager->add('synchronisationForceFeed','0'); + $configurationManager->add('cryptographicSalt', $cryptographicSalt); + + $install_terminee=true; +} /* Ci-dessous, on y va si : +- la page est simplement affichée, sans avoir été validée +- le formulaire est soumis, mais l'installation ne peut se faire +*/ +?> +<!doctype html> +<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]--> +<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]--> +<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]--> +<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]--> +<head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> + <title><?php echo _t('INSTALL_TITLE') ?> + + + + + +
+
+
+

Leed

+ +
+
+ +
+ +

'._t('INSTALL_TITLE_END').'

+ '._t('INSTALL_END').' +
+ + '; + // écriture des balises de fin et ne pas faire la suite + echo '
+ + + '; + exit(); + } + + + ?> +
+
+ +
+

+

+
    +
  • + + +
  • +
  • + + +
  • +
+

+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+

+
    +
  • + + +
  • +
  • + + +
  • +
+ +
+
+ + + diff --git a/sources/locale/en.json b/sources/locale/en.json new file mode 100755 index 0000000..9032e2c --- /dev/null +++ b/sources/locale/en.json @@ -0,0 +1,196 @@ +{ + "ABOUT":"About", + "ABOUT_LEED":"About Leed (Light Feed)", + "ADD":"Add", + "ADD_FEED":"Add feed", + "ADD_FOLDER":"Add folder", + "ADD_TO_LEED":"Add to Leed", + "ALLOW_ANONYMOUS_READ":"Allow anonymous readers", + "ALLOW_ANONYMOUS_READ_DESC":"NB : If you choose this option, everybody can read your feeds (without the possibility to mark them as read/unread).", + "ALREADY_INSTALLED":"Leed is already configured. Delete or rename the configuration file.", + "APPLICATION":"Application", + "AT_TOTAL":"in total", + "AUTHOR":"Author", + "AUTOMATIC_FULL":"Complete", + "AUTOMATIC_FULL_DESC":"The script will update automatically all your feeds, this will update all your feeds at once but can slow down the server, don't use too frequent calls in your cron", + "AUTOMATIC_GRADUATE":"Graduated", + "AUTOMATIC_GRADUATE_DESC":"The script will automaticaly update the oldest 10 feeds, this will relax the server and avoid too frequent timeouts, but requires more frequent calls from your cron", + "AVAILABLES_PLUGINS":"Available plugins", + "AVAILABLE_PLUGIN_LIST":"Installed plugins", + "BLOG":"Blog", + "BOOKMARKLET":"Bookmarklet", + "BY":"by", + "CAN_DOWNLOAD_PLUGINS":"You can download and install new plugins for free", + "CLIC_HERE_SYNC_IMPORT":"Click here to synchronize your imported feeds.", + "CONFIRM_DELETE_FEED":"Do you really want to delete this feed?", + "CONFIRM_DELETE_FOLDER":"Do you really want to delete this folder? All its feeds will be also deleted.", + "CONFIRM_MARK_ALL_AS_READ":"Mark all as read for all the feeds?", + "CONFIRM_MARK_FEED_AS_READ":"Do you really want to mark all the news as read for this feed?", + "CONFIRM_TRASH_EVENTS":"Do you really want to empty the events?", + "CONTRIBUTORS":"Contributors", + "DELETE":"Delete", + "DISABLE":"Disable", + "DISCONNECT":"Log out", + "DISPLAY_ONLY_UNREAD_FEEDFOLDER":"Show/hide feeds without unread articles", + "ENABLE":"Enable", + "ENABLE_CACHE":"Enable caching", + "ENABLE_CACHE_DESC":"This option allows you to disable caching. However, disabling caching may cause longer loading time.", + "ERROR":"Errors", + "ERRORS":"error(s)", + "EVENT_NUMBER_PER_PAGES":"Number of articles per page", + "EXPORT":"Export", + "EXPORT_FEED_OPML_FORMAT":"Export feeds as opml format", + "FABULOUS_AGREGATOR_LAUNCHED_IN":"fabulous news aggregator executed in $1 secondes by $2", + "FAVORITES":"Favorites", + "FAVORITES_EVENTS":"Favorite articles ($1)", + "FAVORIZE":"Favorize", + "FEED":"Feed", + "FEED_MANAGING":"Feed managing", + "FEED_OPTION":"Feed options", + "FEED_OPTION_ISVERBOSE":"Hide selected RSS feeds from the home page:", + "FEED_RSS_LINK":"RSS feed link ", + "FEED_SYNCHRONISATION":"Feed synchronization", + "FOLD":"Fold", + "FOLDER":"Folder", + "FOLD_UNFOLD_FOLDER":"Fold/Unfold folder", + "FORCE_INTEGRATION":"Force integration", + "FORCE_INTEGRATION_DESC":"RSS and Atom feeds are supposed to have specific MIME ypes to allow the softwares to identify them. Some feeds don't follow the rules (for example text/plain). By default SimplePie follows the best pratices, but you can still force the integration with this option. ", + "FULL":"Complete", + "FULL_SYNCHRONISATION":"Complete synchronization…", + "GENERAL_FOLDER":"General", + "GENERALITY":"Generality", + "GIT_REPOSITORY":"Git repository", + "GOOD":"good", + "GRADUATE_SYNCHRONISATION":"Graduated synchronization", + "HELP_H":"h show/hide the help panel", + "HELP_H_?":"Key h show/hide the help panel", + "HELP_K":"k previous element (opening it)", + "HELP_L":"l mark the previous element as unread", + "HELP_M":"m mark the selected element as read / unread", + "HELP_N":"n next element (without opening)", + "HELP_O_ENTER":"o or enter open selected element", + "HELP_P":"p previous element (without opening it)", + "HELP_S":"s mark the selected element as favorite / not favorite", + "HELP_SPC":"espace next element (opening it)", + "HELP_V":"v open the selected element URL", + "HIDE_FEED_IS_VERBOSE":"Do not load on the home page", + "HOME":"Home", + "HOWTO_RESET_PASSWORD":"To reset the password, create a file resetPassword at the site's root. The login will always work, the provided password will replace the former one.", + "IDENTIFIED_WITH":"Identified as $1", + "IF_ADMIN_THEN_CONFIG":"If you are admin, you can manage access rights from the admin panel.", + "IMPORT":"Import", + "IMPORT_COFFEE_TIME":"NB : the import can take time, leave your browser do the job and go grab a coffee :).", + "IMPORT_ERROR":"Errors during the import!", + "IMPORT_FEED_ALREADY_KNOWN":"Some feeds were alreadw known, they were not imported again", + "IMPORT_FEED_OPML_FORMAT":"Import feeds as opml format", + "IMPORT_NO_PROBLEM":"Import successfuly done.", + "INSTALLATION":"INSTALLATION", + "INSTALLATION_PARAGRAPH":"
  • 1. Get the current version of the project from the GIT repository or the archive
  • 2. Place the content on your web folder and give it and its content all the read/write rights (chmod 777)
  • 3. From your browser, navigate to the installation page (http://mysite.com/leed/install.php) and follow the instructions.
  • 4. Once the installation is finished, delete the install.php file for security reasons.
  • 5. Edit your cron (to access the cron file: sudo crontab -e) and place a call to the following page http://mysite.com/leed/action.php?action=synchronize&code=votre_code_synchronisation ex :
    0 * * * * wget --no-check-certificate -q -O /var/www/leed/logsCron 'http://mysite.com/leed/action.php?action=synchronize&code=votre_code_synchronisation'
    The synchronization code is indicated in Manage > Preferences
    To update all the feeds every hour at minute 0 (do not place too frequent call to the script to allow it to takes place correctly).
  • 6. The script is installed, thank you for choosing Leed, the free and svelte RSS aggregator :p.
", + "INSTALL_BDD":"Base", + "INSTALL_BTN":"Start the installation", + "INSTALL_BTN_END":"Acces to my Leed", + "INSTALL_COMMENT_BDD":"(to be created before)", + "INSTALL_COMMENT_HOST":"(Generally 'localhost')", + "INSTALL_DISPLAY_CLEAR":"(will be displayed in clear)", + "INSTALL_END":"You can customize your installation through many plugins available Leed-market.", + "INSTALL_ERROR_CONNEXION":"Unable to connect to database", + "INSTALL_ERROR_FILEGET":"The required function 'file_get_contents' is inaccessible on your server, check your version of PHP.", + "INSTALL_ERROR_FILEPUT":"The required 'file_put_contents' is inaccessible on your server, check your version of PHP.", + "INSTALL_ERROR_MYSQLCONNECT":"The required function 'mysql_connect' is inaccessible on your server, check your MySql installation.", + "INSTALL_ERROR_PHPV":"Your PHP version ($1) is too old, it is possible that some features script include malfunctions.", + "INSTALL_ERROR_RIGHT":"Can't write in Leed directory, please add write permissions on the entire folder (sudo chmod 777 -R $1, think about shielding permissions later)", + "INSTALL_ERROR_SAFEMODE":"The script can not manage the timeout alone because your safe mode is enabled,
in your PHP configuration file, set the max_execution_time variable to 0 or disable safemode.", + "INSTALL_ERROR_USERPWD":"For safety, it is necessary to provide a user name and password.", + "INSTALL_HOST":"Host", + "INSTALL_INFO_CONNEXION":"Connecting to the database : OK", + "INSTALL_INFO_FILEGET":"Requested function 'file_get_contents' : OK", + "INSTALL_INFO_FILEPUT":"Requested function 'file_put_contents' : OK", + "INSTALL_INFO_MYSQLCONNECT":"Requested function 'mysql_connect' : OK", + "INSTALL_INFO_PHPV":"Php version compatibility ($1) : OK", + "INSTALL_INFO_RIGHT":"Permissions on the current folder: OK ", + "INSTALL_INFO_SAFEMODE":"Management timeout: OK", + "INSTALL_LANGUAGE":"Language", + "INSTALL_PREFIX_TABLE":"Table prefix", + "INSTALL_PRE_REQUIS":"Prerequisites for installation", + "INSTALL_TAB_ADMIN":"Administrator", + "INSTALL_TAB_BDD":"Database", + "INSTALL_TAB_GENERAL":"General", + "INSTALL_TITLE":"Installing Leed", + "INSTALL_TITLE_END":"Leed Installation Complete!", + "IN_FIRST":"first", + "KEEP_LAST_X_EVENTS_FEED":"Keep the last $1 events of a feed", + "KEEP_LAST_X_EVENTS_FEED_DESC":"NB : To keep a better performance, we suggest you to keep the last 50 events at most. Notice that events marked as favorites will never be deleted.", + "LAUNCH_SYNCHRONISATION":"Start a manual synchronization", + "LEED_UPDATE_MESSAGE":"Leed has been updated. Refresh the page to return to your Leed.", + "LET_EMPTY_IF_NO_PASS_CHANGE":"Leave this part empty if you don't want to change your password.", + "LET_SLASH_AT_END":"Leave a '/' at the end of the URL, example : http://mysite.com/leed/", + "LIBRARIES":"LIBRARIES", + "LIBRARIES_PARAGRAPHE":"", + "LICENCE":"LICENCE", + "LOADING":"Loading...", + "LOGIN":"Username", + "MAIL":"E-mail", + "MANAGE":"Manage", + "MANUAL_FEED_UPDATE":"Manual feed update", + "MARK_ALL_AS_READ":"Mark all as read", + "MARK_AS_READ":"Mark as read", + "MARK_AS_READ_FOLDER_ITEMS":"mark as read the first $1 unread events of this folder", + "NAME":"Name", + "NEW_FOLDER":"New folder", + "NO":"No", + "NO_INSTALLED_PLUGINS":"There is no installed plugin yet.", + "OLDER":"oldest", + "OPML_FILE":"OPML File", + "PARTIAL":"Partial", + "PASSWORD":"Password", + "PENDING":"In progress...", + "PLUGINS":"Plugins", + "PLUGINS_INSTALLED":"Plugins configuration", + "PREFERENCES":"Preferences", + "PRESENTATION":"PRESENTATION", + "PRESENTATION_PARAGRAPH":"Leed (contraction of Light Feed) is a minimalistic RSS feed aggregator which allows quick and non-intrusive reading of feeds.

All the tasks are done silently with a scheduled task (Cron), thus the user is not faced with the slow down due to the retrieveing and processing of the feeds.

Leed is compatible with all monitors' resolution, desktop computers, tablets and smartphones and accessible from all the browsers.

The script is also compatible with OPML import/export, which makes easier the migration from aggregators respecting the OPML standard.", + "PROJECT_PAGE":"Project page", + "PROJECT_ROOT":"Project root", + "QUESTIONS_SUGGESTIONS":"QUESTIONS & SUGGESTIONS", + "QUESTIONS_SUGGESTIONS_PARAGRAPH":"

  • For any question and suggestion: Check first whether the answer is present in the FAQ or the projects' wiki, if not send me your questions/suggestions at idleman@idleman.fr
  • To reset the password, create a file resetPassword at the site's root. The login will always work, the provided password will replace the former one.
", + "READ":"Read", + "READ_ALL_FOLDER_CONFIRM":"Mark all as read for this folder?", + "READ_FOLDER_ITEMS":"Read events from this folder", + "REMEMBER_ME":"Remember me", + "RENAME":"Rename", + "RETURN_TO_TOP":"Return to top", + "SAVE":"Save", + "SECONDS":"secondes", + "SEE_EVENTS_FOR_FOLDER":"All unread events for folder $1", + "SEE_THE":"See the", + "SHOW_EVENT_AUTHOR":"Show the author of the article", + "SHOW_EVENT_CONTENT":"Show the content of the article", + "SHOW_EVENT_DATE":"Show the date of the article", + "SHOW_EVENT_LINK":"Show direct link to the article", + "SHOW_PARTIAL_CONTENT_DESC":"NB : if you choose partial, a click on the article will redirect to the authors' blog.", + "SITE":"Website", + "SORT_BY_RECENT_EVENT_FOLDER":"Most recent articles first (in folders)", + "SORT_BY_RECENT_EVENT_HOME":"Most recent articles first (on the home page)", + "SUCCESS":"Success", + "SYNCHRONISATION":"Synchronization", + "SYNCHRONISATION_CODE":"Synchronization code", + "SYNCHRONISATION_COMPLETE":"Synchronization finished.", + "SYNCHRONISATION_OPTION":"Synchronization options", + "SYNCHRONISATION_TYPE":"Synchronization type", + "SYNCHRONIZE_COFFEE_TIME":"NB : The synchronization can take time, leave your browser do the job and go grab a coffee :).", + "SYNCHRONIZE_NOW":"Synchronize now", + "TRASH_EVENTS":"Empty events", + "UNFAVORIZE":"Unfavorize", + "UNFOLD":"Unfold", + "UNREAD":"Unread", + "USER":"User", + "USE_BOOKMARK":"Use the bookmarklet", + "USE_BOOKMARK_DESC":"You can add the following bookmarklet to your browser, this will allow you to subscribe more rapidly to a new feed. ", + "VERSION":"Version", + "YES":"Yes", + "YOU_MUST_BE_CONNECTED_ACTION":"You must be logged in for this action.", + "YOU_MUST_BE_CONNECTED_BOOKMARK":"You must be logged in to see the bookmarklet.", + "YOU_MUST_BE_CONNECTED_FEED":"You must be logged in to see your feeds.", + "YOU_MUST_BE_CONNECTED_PLUGIN":"You must be logged in to see the plugins.", + "YOU_MUST_BE_LOGGED":"You must be logged to read your feeds" +} \ No newline at end of file diff --git a/sources/locale/es.json b/sources/locale/es.json new file mode 100755 index 0000000..540b2d7 --- /dev/null +++ b/sources/locale/es.json @@ -0,0 +1,196 @@ +{ + "ABOUT":"Acerca de Leed", + "ABOUT_LEED":"Acerca de Leed (Light Feed)", + "ADD":"Añadir", + "ADD_FEED":"Añadir un RSS", + "ADD_FOLDER":"Añadir un directorio", + "ADD_TO_LEED":"Añadir a Leed", + "ALLOW_ANONYMOUS_READ":"Permitir la lectura anónima", + "ALLOW_ANONYMOUS_READ_DESC":"NB : si usted elige esta opción, los usuarios que no han iniciado sesión seran capaz de ver sus RSS (sin ser capaz de marcarlos como leídos / no leídos).", + "ALREADY_INSTALLED":"Leed ya está configurado. Suprimir o renombrar el archivo de configuración.", + "APPLICATION":"Applicación", + "AT_TOTAL":"en total", + "AUTHOR":"Autor", + "AUTOMATIC_FULL":"Completo", + "AUTOMATIC_FULL_DESC":"El script actualizará automáticamente todos los RSS en una vez. Esto permite la actualización de todos los RSS en una vez pero puede ralentizar su servidor, las llamadas cron no deben ser demasiado cercanas.", + "AUTOMATIC_GRADUATE":"Gradual", + "AUTOMATIC_GRADUATE_DESC":"El script actualizará automáticamente los 10 RSS más antiguos. Esto permite aligerar la carga del servidor y evitar los timeouts intempestivos pero necesita llamadas cron más frecuentes para actualizar el mayor número de RSS posible.", + "AVAILABLES_PLUGINS":"Complementos disponibles", + "AVAILABLE_PLUGIN_LIST":"Aquí está la lista de los complementos instalados", + "BLOG":"Blog", + "BOOKMARKLET":"Bookmarklet", + "BY":"por", + "CAN_DOWNLOAD_PLUGINS":"Usted puede descargar e instalar gratis nuevos complementos", + "CLIC_HERE_SYNC_IMPORT":"Haz clic aquí para actualisar los RSS importados.", + "CONFIRM_DELETE_FEED":"¿ Está seguro que desea suprimir este RSS ?", + "CONFIRM_DELETE_FOLDER":"¿ Está seguro que desea suprimir este directorio ? Va a suprimir también los RSS que él contiene.", + "CONFIRM_MARK_ALL_AS_READ":"¿ Marcar como leído todos los RSS ?", + "CONFIRM_MARK_FEED_AS_READ":"¿ Está seguro que desea marcar todos los eventos como leidos para este RSS ?", + "CONFIRM_TRASH_EVENTS":"¿ Está seguro que desea suprimir todos los eventos ?", + "CONTRIBUTORS":"Contribuidor", + "DELETE":"Suprimir", + "DISABLE":"Desactivar", + "DISCONNECT":"Desconexión", + "DISPLAY_ONLY_UNREAD_FEEDFOLDER":"Mostrar/disfrazar los RSS sin articulos no leidos", + "ENABLE":"Activar", + "ENABLE_CACHE":"Activar el caché", + "ENABLE_CACHE_DESC":"Esta opción permite desactivar el caché. Sin embargo, la desactivación del caché puede llevar tiempos de cargamiento más largos.", + "ERROR":"Errores", + "ERRORS":"error(es)", + "EVENT_NUMBER_PER_PAGES":"Número de articulos por página", + "EXPORT":"Exportación ", + "EXPORT_FEED_OPML_FORMAT":"Exportar los RSS con el formato OPML", + "FABULOUS_AGREGATOR_LAUNCHED_IN":"fabuloso agregador ejecutado en $1 segundos por $2", + "FAVORITES":"Favoritos", + "FAVORITES_EVENTS":"Articulos favoritos ($1)", + "FAVORIZE":"Marcar favorito", + "FEED":"RSS", + "FEED_MANAGING":"Gestión de los RSS", + "FEED_OPTION":"Opción de los RSS", + "FEED_OPTION_ISVERBOSE":"Disfrazar los RSS seleccionados en la portada.", + "FEED_RSS_LINK":"Vínculo del RSS", + "FEED_SYNCHRONISATION":"Syncronización de los RSS", + "FOLD":"Plegar", + "FOLDER":"Directorio", + "FOLD_UNFOLD_FOLDER":"Plegar/Desplegar el directorio", + "FORCE_INTEGRATION":"Forzar la integración", + "FORCE_INTEGRATION_DESC":"Los RSS y Atom deben tener tipos MIME asociados especificados para que el script sepa de que tipo de datos se trata. Algunos RSS no siguen estas reglas (por ejemplo text/plain). SimplePie sigue las mejoras practicas por defecto, pero se puede forzar la integración con este parámetro.", + "FULL":"Completo", + "FULL_SYNCHRONISATION":"Syncronización completa…", + "GENERAL_FOLDER":"General", + "GENERALITY":"Generalidades", + "GIT_REPOSITORY":"Repositorio Git", + "GOOD":"bueno(s)", + "GRADUATE_SYNCHRONISATION":"Syncronización gradual", + "HELP_H":"h Mostrar/disfrazar el panel de ayuda", + "HELP_H_?":"tecla h para mostrar/disfrazar el panel de ayuda", + "HELP_K":"k elémento precedente (sin abrirlo)", + "HELP_L":"l marcar el elemento precedente como no leído", + "HELP_M":"m marcar el elemento seleccionado como leído / no leído", + "HELP_N":"n elemento siguiente (sin abrirlo)", + "HELP_O_ENTER":"o o enter abrir el elemento seleccionado", + "HELP_P":"p elemento precedente (sin abrirlo)", + "HELP_S":"s marcar el elemento seleccionado como favorito / no favorito", + "HELP_SPC":"tecla de espacio elemento siguiente (y abrirlo)", + "HELP_V":"v abre la URL del elemento seleccionado", + "HIDE_FEED_IS_VERBOSE":"No cargar en la portada", + "HOME":"Portada", + "HOWTO_RESET_PASSWORD":"Para reiniciar la contraseña, crear un archivo resetPassword en la raíz del script. La connexión seguirá funcionando, la contraseña dada reemplazará la antigua.", + "IDENTIFIED_WITH":"Identificado con $1", + "IF_ADMIN_THEN_CONFIG":"Si usted es administrador, puede arreglar los derechos de visualización en la parte administración.", + "IMPORT":"Importación", + "IMPORT_COFFEE_TIME":"NB : La importación puede tomar cierto tiempo, deje su navegador y vaya tomarse un cafe. :)", + "IMPORT_ERROR":"Errores durante la importación !", + "IMPORT_FEED_ALREADY_KNOWN":"Algunos RSS ya estaban conocidos, no fueron importados", + "IMPORT_FEED_OPML_FORMAT":"Importar los RSS con el formato OPML", + "IMPORT_NO_PROBLEM":"La importación se ha pasado sin problema.", + "INSTALLATION":"INSTALACIÓN", + "INSTALLATION_PARAGRAPH":"
  1. Recuperar el proyecto sobre el repositorio GIT de la versión actual o descarga el archivo.
  2. Poner el proyecto en su directorio web y aplicar un permiso chmod 777 sobre el directorio y su contenido.
  3. Desde el navegador, ir a la página de configuración install.php (por ejemplo : http://su.sitio.fr/leed/install.php) y seguir las instrucciones.
  4. Una vez terminada la instalación, suprimir el archivo install.php por medida de seguridad.
  5. Establecer un cron (sudo crontab -e para abrir el archivo de cron) y poner una llamada a la página http://su.sitio.fr/leed/action.php?action=synchronize&code=su_código_de_sincronización.
    Por ejemplo :
    0 * * * * wget --no-check-certificate -q -O /var/www/leed/logsCron 'http://su.sitio.fr/leed/action.php?action=synchronize&code=su_código_de_sincronización'
    Se puede encontrar el código de sincronización en las Preferencias
    para actualizar sus RSS todas las horas en el minuto 0 (se aconseja no poner una frecuencia demasiada rápida para dejar tiempo al script para ejecutarse).
  6. El script está instalado, gracias por elegir Leed, el agregator RSS libre y esbelta :p.
", + "INSTALL_BDD":"Base", + "INSTALL_BTN":"Empezar la instalación", + "INSTALL_BTN_END":"Ir a mi Leed", + "INSTALL_COMMENT_BDD":"(crearlo antes)", + "INSTALL_COMMENT_HOST":"(En general 'localhost')", + "INSTALL_DISPLAY_CLEAR":"(no va a ser escrito cifrado en el campo)", + "INSTALL_END":"Puede personalizar su instancia gracias a numerosos complementos que son disponibles sobre el Leed-market.", + "INSTALL_ERROR_CONNEXION":"No se puede conectar a la base de datos.", + "INSTALL_ERROR_FILEGET":"Se necesita la función 'file_get_contents' pero no es disponible sobre el servidor. Verificar su versión de PHP.", + "INSTALL_ERROR_FILEPUT":"Se necesita la función 'file_put_contents' pero no es disponible sobre el servidor. Verificar su versión de PHP.", + "INSTALL_ERROR_MYSQLCONNECT":"Se necesita la función 'mysql_connect' pero no es disponible sobre el servidor. Verificar su versión de PHP.", + "INSTALL_ERROR_PHPV":"Su versión de PHP ($1) es demasiada antigua, se puede que algunas funcionalidades del script no funcionan.", + "INSTALL_ERROR_RIGHT":"No se puede escribir en el directorio de Leed. Añadir los permisos para escribir sobre todo el directorio (sudo chmod 777 -R $1, pensar a modificarlo de nuevo por medida se seguridad después de la instalación)", + "INSTALL_ERROR_SAFEMODE":"El script no puede administrar el timeout solo ya que el safe mode está activado,
en su archivo de configuración de PHP, poner la variable max_execution_time a 0 o desactivar el safe mode.", + "INSTALL_ERROR_USERPWD":"Por medida de seguridad, es necesario que proporzione un login y una contraseña.", + "INSTALL_HOST":"Huésped", + "INSTALL_INFO_CONNEXION":"Connexión a la base de datos : OK", + "INSTALL_INFO_FILEGET":"Función necesaria 'file_get_contents' : OK", + "INSTALL_INFO_FILEPUT":"Función necesaria 'file_put_contents' : OK", + "INSTALL_INFO_MYSQLCONNECT":"Función necesaria 'mysql_connect' : OK", + "INSTALL_INFO_PHPV":"Version de PHP ($1) compatible : OK", + "INSTALL_INFO_RIGHT":"Permisos sobre el directorio corriente : OK", + "INSTALL_INFO_SAFEMODE":"Gestión del timeout : OK", + "INSTALL_LANGUAGE":"Idioma", + "INSTALL_PREFIX_TABLE":"Prefijo de las tablas", + "INSTALL_PRE_REQUIS":"Prerequisito para la instalación", + "INSTALL_TAB_ADMIN":"Administrador", + "INSTALL_TAB_BDD":"Base de datos", + "INSTALL_TAB_GENERAL":"General", + "INSTALL_TITLE":"Instalación de Leed", + "INSTALL_TITLE_END":"Instalación de Leed terminada !", + "IN_FIRST":"el primero", + "KEEP_LAST_X_EVENTS_FEED":"Conservar los $1 ultimos eventos de un RSS", + "KEEP_LAST_X_EVENTS_FEED_DESC":"NB : Cuanto más conservará eventos, más larga será la base de datos. Le aconsejamos a usted que mantenga sólo los 50 ultimos eventos al máximo para mantener una buena reactividad.
Los eventos marcados como favoritos no serán nunca suprimidos.", + "LAUNCH_SYNCHRONISATION":"Lanzar una syncronisación manual", + "LEED_UPDATE_MESSAGE":"Se ha actualizado Leed. Actualizar la página para volver a Leed.", + "LET_EMPTY_IF_NO_PASS_CHANGE":"Deja el campo vacio si no quiere cambiar de contraseña.", + "LET_SLASH_AT_END":"Deja un '/' en el fin de la cadena. Por ejemplo : http://monsite.com/leed/", + "LIBRARIES":"BIBLIOTECAS", + "LIBRARIES_PARAGRAPHE":"", + "LICENCE":"LICENCIA", + "LOADING":"Cargamiento en curso…", + "LOGIN":"Login", + "MAIL":"E-mail", + "MANAGE":"Gestión", + "MANUAL_FEED_UPDATE":"Actualización manual de los RSS", + "MARK_ALL_AS_READ":"Marcarlo todo como leído", + "MARK_AS_READ":"Marcar como leído", + "MARK_AS_READ_FOLDER_ITEMS":"marcar como leído el (los) $1 evento(s) no leído(s) de este directorio", + "NAME":"Nombre", + "NEW_FOLDER":"Nuevo directorio", + "NO":"No", + "NO_INSTALLED_PLUGINS":"No se ha instalado ningún complemento ya.", + "OLDER":"Más antiguo", + "OPML_FILE":"Archivo OPML", + "PARTIAL":"Parcial", + "PASSWORD":"Contraseña", + "PENDING":"En curso…", + "PLUGINS":"Complementos", + "PLUGINS_INSTALLED":"Configuración de los complementos", + "PREFERENCES":"Preferencias", + "PRESENTATION":"PRESENTACIÓN", + "PRESENTATION_PARAGRAPH":"Leed (contracción de Light Feed) es un agregator RSS minimalista que permite leer sus RSS rapidamente y facilmente.

Todas las tareas de tratamiento de los RSS se efectuan de manera invisible gracias a una tarea sincronizada (Cron). Así, el usuario no debe sufrir los largos tiempos necesarios para recuperar y tratar los RSS.

Se debe notar que Leed es compatible con todas las resoluciones, sobre un ordenador, una tablet o un móvil y funciona con todos los navegadores.

El script también está compatible con los archivos de exportación/importación OPML para permitir una migración rápida y fácil a partir de todos los agregadores que respetan el formato OPML.", + "PROJECT_PAGE":"Página del proyecto", + "PROJECT_ROOT":"Raíz del proyecto", + "QUESTIONS_SUGGESTIONS":"PREGUNTAS y SUGERENCIAS", + "QUESTIONS_SUGGESTIONS_PARAGRAPH":"

  • Para preguntas y comentarios : En un primer tiempo, asegúrese que la respuesta no está ya en las PP.FF. o en el wiki del proyecto. Si no es el caso, envieme su preguntas / sugerencias / comentarios sobre idleman@idleman.fr
  • Para reiniciar la contraseña, crear un archivo resetPassword en la raíz del script. La connexión seguirá funcionando, la contraseña dada reemplazará la antigua.
", + "READ":"Leído", + "READ_ALL_FOLDER_CONFIRM":"¿ Marcar lo todo como leído para este directorio ?", + "READ_FOLDER_ITEMS":"Leer los eventos de este directorio", + "REMEMBER_ME":"Recordarme", + "RENAME":"Renombrar", + "RETURN_TO_TOP":"Volver al principio", + "SAVE":"Guardar", + "SECONDS":"segundos", + "SEE_EVENTS_FOR_FOLDER":"Todos los eventos no leidos para el directorio $1", + "SEE_THE":"Ver los", + "SHOW_EVENT_AUTHOR":"Mostrar el autor del artículo", + "SHOW_EVENT_CONTENT":"Mostrar el contenido del artículo", + "SHOW_EVENT_DATE":"Mostrar la fecha del artículo", + "SHOW_EVENT_LINK":"Mostrar el vínculo directo del artículo", + "SHOW_PARTIAL_CONTENT_DESC":"NB : si usted elige una visualización parcial de los articulos, un clic sobre ellos permitirá ir sobre el blog del autor.", + "SITE":"Sitio web", + "SORT_BY_RECENT_EVENT_FOLDER":"Articulos los más recientes en primero (en los directorios)", + "SORT_BY_RECENT_EVENT_HOME":"Articles los más recientes en primero (en la portada)", + "SUCCESS":"Éxito", + "SYNCHRONISATION":"Syncronisación", + "SYNCHRONISATION_CODE":"Código de syncronisación", + "SYNCHRONISATION_COMPLETE":"Syncronisación terminada.", + "SYNCHRONISATION_OPTION":"Opciones de syncronisación", + "SYNCHRONISATION_TYPE":"Tipo de syncronisación", + "SYNCHRONIZE_COFFEE_TIME":"NB : La syncronisación puede tomar cierto tiempo, deje su navegador y vaya tomarse un cafe. :)", + "SYNCHRONIZE_NOW":"Syncronisar ahora", + "TRASH_EVENTS":"Vaciar los eventos", + "UNFAVORIZE":"Marcar no favorito", + "UNFOLD":"Desplegar", + "UNREAD":"No leído ", + "USER":"Usuario", + "USE_BOOKMARK":"Utilizar el bookmarklet", + "USE_BOOKMARK_DESC":"Puede añadir el bookmarklet más abajo a su navegador para inscribirse más rápidamente a los RSS.", + "VERSION":"Versión", + "YES":"Si", + "YOU_MUST_BE_CONNECTED_ACTION":"Usted debe haber iniciado una sesión para continuar.", + "YOU_MUST_BE_CONNECTED_BOOKMARK":"Usted debe haber iniciado una sesión para ver el bookmarklet.", + "YOU_MUST_BE_CONNECTED_FEED":"Usted debe haber iniciado una sesión para ver sus RSS.", + "YOU_MUST_BE_CONNECTED_PLUGIN":"Usted debe haber iniciado una sesión para ver los complementos.", + "YOU_MUST_BE_LOGGED":"Usted debe haber iniciado una sesión para ver sus RSS." +} diff --git a/sources/locale/fr.json b/sources/locale/fr.json new file mode 100755 index 0000000..3ecde3c --- /dev/null +++ b/sources/locale/fr.json @@ -0,0 +1,196 @@ +{ + "ABOUT":"À propos", + "ABOUT_LEED":"À propos de Leed (Light Feed)", + "ADD":"Ajouter", + "ADD_FEED":"Ajout d’un flux", + "ADD_FOLDER":"Ajout d’un dossier", + "ADD_TO_LEED":"Ajouter à Leed", + "ALLOW_ANONYMOUS_READ":"Autoriser la lecture anonyme", + "ALLOW_ANONYMOUS_READ_DESC":"NB : si vous choisissez cette option, les utilisateurs non authentifiés pourront consulter vos flux (sans pouvoir les marquer comme lu/non lu).", + "ALREADY_INSTALLED":"Leed est déjà configuré. Supprimez ou renommez le fichier de configuration.", + "APPLICATION":"Application", + "AT_TOTAL":"au total", + "AUTHOR":"Auteur", + "AUTOMATIC_FULL":"Complet", + "AUTOMATIC_FULL_DESC":"Le script mettra à jour automatiquement tous vos flux en une seule fois, ceci permet la mise à jour en une fois de tous vos flux mais peut faire ramer votre serveur, les appels cron ne doivent pas être trop rapprochés.", + "AUTOMATIC_GRADUATE":"Gradué", + "AUTOMATIC_GRADUATE_DESC":"Le script mettra à jour automatiquement les 10 flux les plus vieux en termes de mise à jour, ceci permet d’alléger la charge serveur et d’éviter les timeouts intempestifs mais nécessite un appel de cron plus fréquent afin de mettre à jour le plus de flux possible.", + "AVAILABLES_PLUGINS":"Plugins disponibles", + "AVAILABLE_PLUGIN_LIST":"Voici la liste des plugins installés", + "BLOG":"Blog", + "BOOKMARKLET":"Bookmarklet", + "BY":"par", + "CAN_DOWNLOAD_PLUGINS":"Vous pouvez télécharger et installer gratuitement de nouveaux plugins", + "CLIC_HERE_SYNC_IMPORT":"Cliquez ici pour synchroniser vos flux importés.", + "CONFIRM_DELETE_FEED":"Êtes vous sur de vouloir supprimer ce flux ?", + "CONFIRM_DELETE_FOLDER":"Êtes vous sur de vouloir supprimer ce dossier ? Cela supprimera tous les flux qu’il contient.", + "CONFIRM_MARK_ALL_AS_READ":"Tout marquer comme lu pour tous les flux ?", + "CONFIRM_MARK_FEED_AS_READ":"Êtes vous sûr de vouloir marquer tous les événements comme lus pour ce flux ?", + "CONFIRM_TRASH_EVENTS":"Êtes vous sûr de vouloir vider tous les événements ?", + "CONTRIBUTORS":"Contributeurs", + "DELETE":"Supprimer", + "DISABLE":"Désactiver", + "DISCONNECT":"Déconnexion", + "DISPLAY_ONLY_UNREAD_FEEDFOLDER":"Afficher/masquer les feeds sans articles non lus", + "ENABLE":"Activer", + "ENABLE_CACHE":"Activer le cache", + "ENABLE_CACHE_DESC":"Cette option vous permet de désactiver la mise en cache. Cependant, la désactivation du cache peut entraîner des temps de chargement plus longs.", + "ERROR":"Erreurs", + "ERRORS":"erreur(s)", + "EVENT_NUMBER_PER_PAGES":"Nombre d’articles par page", + "EXPORT":"Export", + "EXPORT_FEED_OPML_FORMAT":"Exporter les flux au format OPML", + "FABULOUS_AGREGATOR_LAUNCHED_IN":"fabuleux agrégateur exécuté en $1 secondes par $2", + "FAVORITES":"Favoris", + "FAVORITES_EVENTS":"Articles favoris ($1)", + "FAVORIZE":"Favoriser", + "FEED":"Flux", + "FEED_MANAGING":"Gestion des flux", + "FEED_OPTION":"Option des flux", + "FEED_OPTION_ISVERBOSE":"Cacher les flux sélectionnés de la page d'accueil :", + "FEED_RSS_LINK":"Lien du flux RSS", + "FEED_SYNCHRONISATION":"Synchronisation des flux", + "FOLD":"Plier", + "FOLDER":"Dossier", + "FOLD_UNFOLD_FOLDER":"Plier/Déplier le dossier", + "FORCE_INTEGRATION":"Forcer l’intégration", + "FORCE_INTEGRATION_DESC":"Les flux RSS et Atom sont censés avoir des types MIME associés spécifiques afin que le logiciel sache quel type de données il s’agit. Certains flux ne suivent pas ces règles (par exemple text/plain). SimplePie suit les meilleures pratiques par défaut, mais vous pouvez forcer l’intégration avec ce paramètre.", + "FULL":"Complet", + "FULL_SYNCHRONISATION":"Synchronisation complète…", + "GENERAL_FOLDER":"Générale", + "GENERALITY":"Généralités", + "GIT_REPOSITORY":"Dépôt Git", + "GOOD":"bon(s)", + "GRADUATE_SYNCHRONISATION":"Synchronisation graduée", + "HELP_H":"h afficher/masquer le panneau d’aide", + "HELP_H_?":"touche h pour afficher/masquer le panneau d’aide", + "HELP_K":"k élément précédent (et l’ouvrir)", + "HELP_L":"l marque l’élément précédent comme non lu", + "HELP_M":"m marque l’élément sélectionné comme lu / non lu", + "HELP_N":"n élément suivant (sans l’ouvrir)", + "HELP_O_ENTER":"o ou enter ouvrir l’élément sélectionné", + "HELP_P":"p élément précédent (sans l’ouvrir)", + "HELP_S":"s marque l’élément sélectionné comme favori / non favori", + "HELP_SPC":"espace élément suivant (et l’ouvrir)", + "HELP_V":"v ouvre l’URL de l’élément sélectionné", + "HIDE_FEED_IS_VERBOSE":"Ne pas charger sur la page d'accueil", + "HOME":"Accueil", + "HOWTO_RESET_PASSWORD":"Pour réinitialiser le mot de passe, créez un fichier resetPassword à la racine du site. La connexion marchera toujours, le mot de passe fourni remplacera l'ancien.", + "IDENTIFIED_WITH":"Identifié avec $1", + "IF_ADMIN_THEN_CONFIG":"Si vous êtes administrateur, vous pouvez régler les droits de visualisation dans la partie administration.", + "IMPORT":"Import", + "IMPORT_COFFEE_TIME":"NB : L’import peut prendre un certain temps, laissez votre navigateur tourner et allez vous prendre un café. :)", + "IMPORT_ERROR":"Erreurs durant l’import !", + "IMPORT_FEED_ALREADY_KNOWN":"Certains flux étaient déjà connus, ils n’ont pas été réimportés", + "IMPORT_FEED_OPML_FORMAT":"Importer les flux au format OPML", + "IMPORT_NO_PROBLEM":"L’import s’est déroulé sans problème.", + "INSTALLATION":"INSTALLATION", + "INSTALLATION_PARAGRAPH":"
  1. Récupérez le projet sur le dépot GIT de la version courante ou en téléchargeant l’archive.
  2. Placez le projet dans votre répertoire web et appliquez une permission chmod 777 sur le dossier et son contenu.
  3. Depuis votre navigateur, accédez à la page d’installation install.php (par exemple : http://votre.domaine.fr/leed/install.php) et suivez les instructions.
  4. Une fois l’installation terminée, supprimez le fichier install.php par mesure de sécurité.
  5. Mettez en place un cron (sudo crontab -e pour ouvrir le fichier de cron) et placez y un appel vers la page http://votre.domaine.fr/leed/action.php?action=synchronize&code=votre_code_synchronisation.
    Par exemple :
    0 * * * * wget --no-check-certificate -q -O /var/www/leed/logsCron 'http://votre.domaine.fr/leed/action.php?action=synchronize&code=votre_code_synchronisation'
    Le code de synchronisation est indiqué dans Gestion > Préférences.
    Pour mettre à jour vos flux toutes les heures à la minute 0 (il est conseillé de ne pas mettre une fréquence trop rapide pour laisser le temps au script de s’exécuter).
  6. Le script est installé, merci d’avoir choisi Leed, l’agrégateur RSS libre et svelte :p.
", + "INSTALL_BDD":"Base", + "INSTALL_BTN":"Lancer l'installation", + "INSTALL_BTN_END":"Accéder à mon Leed", + "INSTALL_COMMENT_BDD":"(à créer avant)", + "INSTALL_COMMENT_HOST":"(Généralement 'localhost')", + "INSTALL_DISPLAY_CLEAR":"(sera affiché en clair)", + "INSTALL_END":"Vous pouvez personnaliser votre installation graçe à de nombreux plugins disponibles sur Leed-market.", + "INSTALL_ERROR_CONNEXION":"Connexion impossible à la base de données.", + "INSTALL_ERROR_FILEGET":"La fonction requise 'file_get_contents' est inaccessible sur votre serveur, verifiez votre version de PHP.", + "INSTALL_ERROR_FILEPUT":"La fonction requise 'file_put_contents' est inaccessible sur votre serveur, verifiez votre version de PHP.", + "INSTALL_ERROR_MYSQLCONNECT":"La fonction requise 'mysql_connect' est inaccessible sur votre serveur, verifiez vote installation de MySql.", + "INSTALL_ERROR_PHPV":"Votre version de PHP ($1) est trop ancienne, il est possible que certaines fonctionalitees du script comportent des disfonctionnements.", + "INSTALL_ERROR_RIGHT":"Écriture impossible dans le répertoire Leed, veuillez ajouter les permissions en écriture sur tout le dossier (sudo chmod 777 -R $1, pensez à blinder les permissions par la suite)", + "INSTALL_ERROR_SAFEMODE":"Le script ne peux pas gerer le timeout tout seul car votre safe mode est activé,
dans votre fichier de configuration PHP, mettez la variable max_execution_time à 0 ou désactivez le safemode.", + "INSTALL_ERROR_USERPWD":"Par sécurité, il est nécessaire de fournir un nom d'utilisateur et un mot de passe.", + "INSTALL_HOST":"Hôte", + "INSTALL_INFO_CONNEXION":"Connexion à la base de données : OK", + "INSTALL_INFO_FILEGET":"Fonction requise 'file_get_contents' : OK", + "INSTALL_INFO_FILEPUT":"Fonction requise 'file_put_contents' : OK", + "INSTALL_INFO_MYSQLCONNECT":"Fonction requise 'mysql_connect' : OK", + "INSTALL_INFO_PHPV":"Compatibilité version PHP ($1) : OK", + "INSTALL_INFO_RIGHT":"Permissions sur le dossier courant : OK", + "INSTALL_INFO_SAFEMODE":"Gestion du timeout : OK", + "INSTALL_LANGUAGE":"Langue", + "INSTALL_PREFIX_TABLE":"Préfixe des tables", + "INSTALL_PRE_REQUIS":"Pré-requis à l'installation", + "INSTALL_TAB_ADMIN":"Administrateur", + "INSTALL_TAB_BDD":"Base de donnée", + "INSTALL_TAB_GENERAL":"Général", + "INSTALL_TITLE":"Installation de Leed", + "INSTALL_TITLE_END":"Installation de Leed terminée !", + "IN_FIRST":"en premier", + "KEEP_LAST_X_EVENTS_FEED":"Conserver les $1 derniers événements d’un flux", + "KEEP_LAST_X_EVENTS_FEED_DESC":"NB : Plus il y aura d’événements à conserver, plus votre base de données sera importante. Nous vous conseillons de garder les 50 derniers événements au maximum pour conserver une performance correcte.
Notez que vos événements marqués comme favoris ne seront jamais supprimés.", + "LAUNCH_SYNCHRONISATION":"Lancer une synchronisation manuelle", + "LEED_UPDATE_MESSAGE":"Leed à été mis à jour. Rafraîchir la page pour retourner sur votre Leed.", + "LET_EMPTY_IF_NO_PASS_CHANGE":"Laissez le champ vide si vous ne souhaitez pas changer le mot de passe.", + "LET_SLASH_AT_END":"Laissez bien un '/' en fin de chaine. Par exemple : http://monsite.com/leed/", + "LIBRARIES":"LIBRAIRIES", + "LIBRARIES_PARAGRAPHE":"", + "LICENCE":"LICENCE", + "LOADING":"Chargement en cours…", + "LOGIN":"Identifiant", + "MAIL":"E-mail", + "MANAGE":"Gestion", + "MANUAL_FEED_UPDATE":"Mise à jour manuelle des flux", + "MARK_ALL_AS_READ":"Tout marquer comme lu", + "MARK_AS_READ":"Marquer comme lu", + "MARK_AS_READ_FOLDER_ITEMS":"marquer comme lu le(s) $1 evenement(s) non lu(s) de ce dossier", + "NAME":"Nom", + "NEW_FOLDER":"Nouveau dossier", + "NO":"Non", + "NO_INSTALLED_PLUGINS":"Aucun plugin n’est installé pour le moment.", + "OLDER":"Plus vieux", + "OPML_FILE":"Fichier OPML", + "PARTIAL":"Partiel", + "PASSWORD":"Mot de passe", + "PENDING":"En cours…", + "PLUGINS":"Plugins", + "PLUGINS_INSTALLED":"Configuration des plugins", + "PREFERENCES":"Préférences", + "PRESENTATION":"PRÉSENTATION", + "PRESENTATION_PARAGRAPH":"Leed (contraction de Light Feed) est un agrégateur RSS minimaliste qui permet la consultation de flux RSS de manière rapide et non intrusive.

Toutes les tâches de traitement de flux sont effectuées de manière invisible par une tâche planifiée (Cron). Ainsi, l’utilisateur ne subit pas les lenteurs dues à la récupération et au traitement de chacun des flux suivis.

À noter que Leed est compatible toutes résolutions, sur pc, tablettes et smartphones et fonctionne avec tous les navigateurs.

Le script est également compatible avec les fichiers d’export/import OPML ce qui rend la migration de tous les agrégateurs respectant le standard OPML simple et rapide.", + "PROJECT_PAGE":"Page projet", + "PROJECT_ROOT":"Racine du projet", + "QUESTIONS_SUGGESTIONS":"QUESTIONS & SUGGESTIONS", + "QUESTIONS_SUGGESTIONS_PARAGRAPH":"

  • Pour toutes questions et remarques: Vérifiez dans un premier temps que la réponse ne se trouve pas dans la FAQ ou sur le wiki du projet, si ce n’est pas le cas, envoyez moi vos questions/suggestions/remarques sur idleman@idleman.fr
  • Pour réinitialiser le mot de passe, créez un fichier resetPassword à la racine du site. La connexion marchera toujours, le mot de passe fourni remplacera l'ancien.
", + "READ":"Lu", + "READ_ALL_FOLDER_CONFIRM":"Tout marquer comme lu pour ce dossier ?", + "READ_FOLDER_ITEMS":"Lire les événements de ce dossier", + "REMEMBER_ME":"Se souvenir de moi", + "RENAME":"Renommer", + "RETURN_TO_TOP":"Revenir en haut de page", + "SAVE":"Enregistrer", + "SECONDS":"secondes", + "SEE_EVENTS_FOR_FOLDER":"Tous les événements non lus pour le dossier $1", + "SEE_THE":"Voir les", + "SHOW_EVENT_AUTHOR":"Affichage de l’auteur de l’article", + "SHOW_EVENT_CONTENT":"Affichage du contenu de l’article", + "SHOW_EVENT_DATE":"Affichage de la date de l’article", + "SHOW_EVENT_LINK":"Affichage du lien direct de l’article", + "SHOW_PARTIAL_CONTENT_DESC":"NB : si vous choissisez un affichage partiel des articles, un clic sur ces derniers mènera à l’article sur le blog de l’auteur.", + "SITE":"Site web", + "SORT_BY_RECENT_EVENT_FOLDER":"Articles les plus récents en premier (sur les dossiers)", + "SORT_BY_RECENT_EVENT_HOME":"Articles les plus récents en premier (sur la page d’accueil)", + "SUCCESS":"Succès", + "SYNCHRONISATION":"Synchronisation", + "SYNCHRONISATION_CODE":"Code de synchronisation", + "SYNCHRONISATION_COMPLETE":"Synchronisation terminée.", + "SYNCHRONISATION_OPTION":"Options de synchronisation", + "SYNCHRONISATION_TYPE":"Type de synchronisation", + "SYNCHRONIZE_COFFEE_TIME":"NB : La synchronisation peut prendre un certain temps, laissez votre navigateur tourner et allez vous prendre un café. :)", + "SYNCHRONIZE_NOW":"Synchroniser maintenant", + "TRASH_EVENTS":"Vider les événements", + "UNFAVORIZE":"Défavoriser", + "UNFOLD":"Déplier", + "UNREAD":"Non lu", + "USER":"Utilisateur", + "USE_BOOKMARK":"Utiliser le bookmarklet", + "USE_BOOKMARK_DESC":"Vous pouvez ajouter le bookmarklet ci-dessus à votre navigateur pour vous inscrire plus rapidement aux flux.", + "VERSION":"Version", + "YES":"Oui", + "YOU_MUST_BE_CONNECTED_ACTION":"Vous devez être connecté pour effecuer cette action.", + "YOU_MUST_BE_CONNECTED_BOOKMARK":"Vous devez être connecté pour voir le bookmarklet.", + "YOU_MUST_BE_CONNECTED_FEED":"Vous devez être connecté pour voir vos flux.", + "YOU_MUST_BE_CONNECTED_PLUGIN":"Vous devez être connecté pour voir les plugins.", + "YOU_MUST_BE_LOGGED":"Vous devez être connecté pour consulter vos flux" +} diff --git a/sources/logs/.htaccess b/sources/logs/.htaccess new file mode 100755 index 0000000..3418e55 --- /dev/null +++ b/sources/logs/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/sources/plugins/index.html b/sources/plugins/index.html new file mode 100755 index 0000000..e69de29 diff --git a/sources/settings.php b/sources/settings.php new file mode 100755 index 0000000..77d106d --- /dev/null +++ b/sources/settings.php @@ -0,0 +1,35 @@ +assign('feeds',$feedManager->populate('name')); +$tpl->assign('folders',$folderManager->populate('name')); +$tpl->assign('synchronisationType',$configurationManager->get('synchronisationType')); +$tpl->assign('synchronisationEnableCache',$configurationManager->get('synchronisationEnableCache')); +$tpl->assign('synchronisationForceFeed',$configurationManager->get('synchronisationForceFeed')); +$tpl->assign('articleDisplayAnonymous', $configurationManager->get('articleDisplayAnonymous')); +$tpl->assign('articleDisplayLink', $configurationManager->get('articleDisplayLink')); +$tpl->assign('articleDisplayDate', $configurationManager->get('articleDisplayDate')); +$tpl->assign('articleDisplayAuthor', $configurationManager->get('articleDisplayAuthor')); +$tpl->assign('articleDisplayHomeSort', $configurationManager->get('articleDisplayHomeSort')); +$tpl->assign('articleDisplayFolderSort', $configurationManager->get('articleDisplayFolderSort')); +$tpl->assign('articleDisplayContent', $configurationManager->get('articleDisplayContent')); +$tpl->assign('articleView', $configurationManager->get('articleView')); +$tpl->assign('optionFeedIsVerbose', $configurationManager->get('optionFeedIsVerbose')); + +//Suppression de l'état des plugins inexistants +Plugin::pruneStates(); + +//Récuperation des plugins +$tpl->assign('plugins',Plugin::getAll()); + +$view = "settings"; +require_once('footer.php'); ?> diff --git a/sources/templates/marigolds/about.html b/sources/templates/marigolds/about.html new file mode 100755 index 0000000..2e20e20 --- /dev/null +++ b/sources/templates/marigolds/about.html @@ -0,0 +1,57 @@ +{include="header"} + +
+ + + +
+
+

{function="_t('ABOUT')"}

+

{function="_t('ABOUT_LEED')"}

+
+ + +
+

{function="_t('GENERALITY')"} :

+ + +

{function="_t('PRESENTATION')"}

+

{function="_t('PRESENTATION_PARAGRAPH')"}

+ +

{function="_t('INSTALLATION')"}

+ {function="_t('INSTALLATION_PARAGRAPH')"} + +

{function="_t('QUESTIONS_SUGGESTIONS')"}

+ {function="_t('QUESTIONS_SUGGESTIONS_PARAGRAPH')"} + +

{function="_t('LIBRARIES')"}

+ {function="_t('LIBRARIES_PARAGRAPHE')"} + +
+ + + +
+ + +
+ + +{include="footer"} diff --git a/sources/templates/marigolds/article.html b/sources/templates/marigolds/article.html new file mode 100755 index 0000000..b2b8a24 --- /dev/null +++ b/sources/templates/marigolds/article.html @@ -0,0 +1,72 @@ +{loop="$events"} + {$plainDescription=strip_tags($value->getDescription())} + + + + {function="Plugin::callHook("event_pre_section", array(&$value))"} +
getUnread()"}eventRead{/if} {$hightlighted%2==0?'eventHightLighted':''}{$scroll?' scroll':''}" {$scroll?'style="display: none;"':''}> + ˆ + +

+ {function="Plugin::callHook("event_pre_title", array(&$value))"} + {$value->getTitle()} + {function="Plugin::callHook("event_post_title", array(&$value))"} +

+ +

+ {function="Plugin::callHook("event_pre_top_options", array(&$value))"} + {if="$articleDisplayLink"} + {$allFeeds['idMap'][$value->getFeed()]['name']} + {/if} + {if="$articleDisplayAuthor"} + {if="$value->getCreator()"} + {function="_t('BY')"} {$value->getCreator()} + {/if} + {/if} + + {if="$articleDisplayDate"} + {$value->getPubdateWithInstant($time)} + {/if} + {if="$value->getFavorite()!=1"} - {function="_t('FAVORIZE')"} + {else} + {function="_t('UNFAVORIZE')"} + {/if} + + ({function="_t('READ')"}/{function="_t('UNREAD')"}) + {function="Plugin::callHook("event_post_top_options", array(&$value))"} +

+ + + {if="$articleDisplayContent"} +
+ {if="$articleView=='partial'"} + {function="Plugin::callHook("event_pre_description", array(&$value))"} + {$value->getDescription()} + {function="Plugin::callHook("event_post_description", array(&$value))"} + {else} + {function="Plugin::callHook("event_pre_content", array(&$value))"} + {$value->getContent()} + {function="Plugin::callHook("event_post_content", array(&$value))"} + {/if} +
+ + {if="$articleView!='partial'"} + +

+ {function="Plugin::callHook("event_pre_bottom_options", array(&$value))"} + + (lu/non lu) + {if="$value->getFavorite()!=1"}{function="_t('FAVORIZE')"} + {else} + {function="_t('UNFAVORIZE')"} + {/if} +
+ {function="Plugin::callHook("event_post_bottom_options", array(&$value))"} +

+ {/if} + {/if} +
+ {function="Plugin::callHook("event_post_section", array(&$value))"} + {$hightlighted=$hightlighted+1} +{/loop} +{if="$scroll && $events"}
{/if} diff --git a/sources/templates/marigolds/css/style.css b/sources/templates/marigolds/css/style.css new file mode 100755 index 0000000..d1cea05 --- /dev/null +++ b/sources/templates/marigolds/css/style.css @@ -0,0 +1,812 @@ +/* ============================================================================= + HTML5 Boilerplate CSS: h5bp.com/css + ========================================================================== */ + +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } +audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } +audio:not([controls]) { display: none; } +[hidden] { display: none; } + +html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } +html, button, input, select, textarea { font-family: sans-serif; color: #222; } +body { margin: 0; font-size: 1em; line-height: 1.4; } + +::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } +::selection { background: #fe57a1; color: #fff; text-shadow: none; } + +a { color: #00e; } +a:visited { color: #551a8b; } +a:hover { color: #06e; } + + + + +a:focus { outline: thin dotted; } +a:hover, a:active { outline: 0; } + +abbr[title] { border-bottom: 1px dotted; } +b, strong { font-weight: bold; } +blockquote { margin: 1em 40px; } +dfn { font-style: italic; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } +ins { background: #ff9; color: #000; text-decoration: none; } +mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } +pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; } +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } +q { quotes: none; } +q:before, q:after { content: ""; content: none; } +small { font-size: 85%; } + +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + +ul, ol { margin: 1em 0; padding: 0 0 0 40px; } +dd { margin: 0 0 0 40px; } +nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; } + +img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } + +svg:not(:root) { overflow: hidden; } + +figure { margin: 0; } + +form { margin: 0; } +fieldset { border: 1px solid #CCCCCC; margin: 0; padding: 5px; } +label { cursor: pointer; } +legend { border: 0; *margin-left: -7px; padding: 5px; white-space: normal; } +button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } +button, input { line-height: normal; } +button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; } +button[disabled], input[disabled] { cursor: default; } +input[type="checkbox"], input[type="radio"] { box-sizing: border-box; margin: 0 5px 0 10px; padding: 0; *width: 13px; *height: 13px; } +input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } +button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } +textarea { overflow: auto; vertical-align: top; resize: vertical; } +input:valid, textarea:valid { } +input:invalid, textarea:invalid { background-color: #f0dddd; } + +table { border-collapse: collapse; border-spacing: 0; } +td { vertical-align: top; } + +.chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; } + + +/* ===== Initializr Styles ===================================================== + Author: Jonathan Verrecchia - verekia.com/initializr/responsive-template + ========================================================================== */ + +body{ font:16px/26px Helvetica, Helvetica Neue, Arial; } + +.wrapper{ + width:96%; + margin:0 2%; +} + +/* =================== + ALL: Orange Theme + =================== */ + +html, body { position: relative; height: 100%; } +.global-wrapper { + min-height: 100%; + position: relative; +} +#header-container{ border-bottom: 5px solid #e44d26; } +#footer-container{ position: absolute; width: 100%; bottom: 0; border-top: 5px solid #e44d26; } +#main aside { border-top: 5px solid #e44d26; } + +#header-container, +#footer-container, +#main aside{ + background:#222222; +} + +#main aside{ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; +} + +#title{ color:#ffffff; } + +::-moz-selection { background: #f16529; color: #fff; text-shadow: none; } +::selection { background: #f16529; color: #fff; text-shadow: none; } + +/* ============== + MOBILE: Menu + ============== */ + +code{ + padding:5px; + color:#f16529; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + display:block; + margin:5px; + font-size:12px; + font-family:Courier,Verdana,Arial; + background:#FFEBE2; +} + +nav a{ + display:block; + margin-bottom:10px; + background:#e44d26; + color:#ffffff; + text-align:center; + text-decoration:none; + font-weight:bold; +} + +nav a.synchronyzeButton{ + cursor:pointer; + font-size:40px; +} + +nav a:hover, nav a:visited{ + color:#ffffff; +} + +nav a:hover, nav a:visited{ + color:#ffffff; +} + +header a,header a:hover,header a:visited{ + text-decoration:none; + color:#ffffff; +} + +/* ============== + MOBILE: Main + ============== */ + +#main{ + padding:15px 0 45px; +} + +#main article a.goTopButton{ + background-color:#CECECE; + border:0px; + color:#ffffff; + padding:10px 5px 0px 5px; + line-height: 5px; + float:right; + text-decoration:none; +} + +#main button, .loginBloc button,.button,.readUnreadButton{ + background-color:#f16529; + border:0px; + color:#ffffff; + text-decoration:none; + padding:3px 8px 3px 8px; + font-size:10px; + font-weight:bold; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; + min-width: 35px; + text-align: center; +} + +.readUnreadButton{ +-moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + + line-height:20px; +} + +#main aside a.unreadForFolder,#main aside a.readFolder{ + + background-color:#F16529; + border:0px; + color:#ffffff; + margin:3px 3px 3px 0px; + font-size:10px; + font-weight:bold; + padding:0px 3px 0px 3px; + line-height: 20px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + min-width:55px; + display:block; + text-align: center; + float:right; +} + +#main aside a.readFolder{ + background-color:#222222; + margin-left:3px; + min-width: 36px; + text-align: center; +} + +#main aside ul li h1.folder{ + text-align:left; + padding-left:5px; +} + +.loginBloc span{ + color:#fff; + margin-right:5px; + padding:0; + display:inline; +} +.loginBloc span span{ + font-weight:bold; +} + +#main .aside a button a{ + text-decoration:none; +} + +#main article header.articleHead{ + + padding:5px; + background-color:#222222; + color:#ffffff; +} + +#main article div.articleContent{ + + margin:10px 0px 0px 0px; + text-align: justify; + clear:both; +} + +#main article header h1.articleSection{ + font-size:1em; + color:#ffffff; + margin:0px; + float:left; + margin-right:10px; +} +#main article section h2 a{ + text-decoration:none; + color: #222222; + cursor: pointer; +} +#main article header h1 a{ + text-decoration:none; + color: #ffffff; + cursor: pointer; +} +#main article section h2.articleTitle, .preferenceBloc h3{ + margin-bottom:5px; + font-size:1.1em; +} +#main article section h3.articleDetails, .preferenceBloc h4{ + font-size:10px; + font-weight:normal; + color:#939393; + margin:0px; +} +#main article section .articleDetails .readUnreadButton, +#main article section .articleDetails .readUnreadButton:hover +{ + color: #FFF; +} + +#main article div.articleContent .enclosure{ + color:#F16529; + display:block; + font-size: 10px; +} +#main article div.articleContent .enclosure h1{ + color:#222222; + margin:5px 0 0 0; + font-weight: bold; + font-size: 12px; +} +#main article div.articleContent .enclosure a{ + + color:#F16529; + font-size: 10px; +} + +#main article div.articleContent .enclosure span{ + padding:5px 0 5px 0; + color:#707070; + font-size: 10px; +} + + +#main article section img{ + max-width: 100%; + height: auto; +} +#main article section embed,#main article section iframe{ + max-width: 100%; + +} +#main aside ul,#main aside ul li ul{ + padding:0; + margin:0; +} +#main aside ul li ul li{ + list-style-type:none; + padding:0 5px 0; + margin:0; +} + +#main aside ul li { + list-style-type:none; +} +#main aside ul li h1 { + font-size:1em; + padding:0px; + margin:3px; + text-align:center; + background-color:#666666; + cursor: pointer; +} + +.favorite:before{ + content: "✰ "; + font-size: 15px; + font-weight: bold; +} +.favorite{ + margin-right:10px; + } + +.importFrame{ + border:none; +} + + +.logo{ + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAwCAYAAAB9sggoAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAB3ZJREFUeNrEWQlMFFcYZpdly3KWq1yGgmIqUA6L1tpIUVugNtGkBmtJGhOjFm2atonYVkQSNZF6kKImLSXUKFGBllgKKFCghKMKuICwqywEEJVzF+RmOZal36MzZBxnz4K85MvOm3fMN//73/f/b5ZnampqslRleHg4eGZmZr1KpeLPzs4+GRoaeujn59eBJrWusTxDiOFBC9e2trba+h2am5v7gsfjvclum5qaymtqaooNDQ1tQlW1JMRQt8LvGKNdBEK/Az6wULtarZ7A/B7ASpC0YUw13d/f/+2qVatScK1cTGJWQDwQDGLbyIPIzcHBwUQsV19HR0elWCzumZiYmLW2thb4+vo6+fv7R1pYWHzC5/M96Em6urqi0PYnJzlCTF+MjY0R7AY6gbmBgQE57u8FPFEPbmxsfAvXtppQU1MTDuLVZCzB6Oio4vDhw25oM2c/yxBSbwB/0ZPS8PDwOIj2bbCQPYPEK6b/FR4DpJjfuXPnPZB7QI+Hdb+mxvCYz+PrsXxWQAIuG4EwdvvmzZudhULhqxs2bBgkdZlMZo8lTcPDFRinhlWl7e3t0WRxgMmQkJAquVx+Dn44QfpjqT+gpjJjzsvXQepj/DwAvgeEXH2CgoJcpqenzfEgx97eXidnZ+cj2HlKSMR9OP9TgUDg6+Dg8DPafkJ3ETB79OjRTIz5m3KlFdRUAp3EQMgbyMflTcBDG3lPT8/X5ncRj6eGz4wcOHDgRGRkZAx23M76+vrdIFlA2kUi0b6GhoaPyAtmZ2crR0ZGblFWM+XiwmcREgEnqGX7UJ+d6u7u7kIryOrVqxVZWVmTlZWVM5hnbuvWrQ9TUlIOQmTLSQcXF5dIyvICyIkYxJ7gt5NrXj6D1A5q2eIpk+tV7OzsnKhLolNzwAgFoi3DcXFxXbDUd1jWDiybG9VXCELdQC82QDF1b445rwCEvPCbBOwwJNzgbdVw6vulpaX3aItxPYCoO/xLBquJQY4WQp6jo6MI9bGLFy9mUPdmniNGPeR9+IhBvOA/5bGxsbmQiSGG6HKWgICASZBQNjc3X6FuqbEpAvr6+tKTkpLGuYjxodz96enpvxlCqqCg4OaWLVvS7t69OwhL0BaSaVluC6xMBeKjhNSrq6tFiApKHx+fW1SXiRcsjXW3JuotkUga2OLJBejUQysrq31kjKWl5T68VAaCshS7LIWIMJc4KxSKFQzxNcPSOtL1U6dOWWLc28DngJCt/OvXrl37FQLriC5ily5d+oUKQ3tbWloKWe1DgDsHOT4hkZub64j2UIjvN21tbenPnj2T0GOxrNnos4aKEAuidg9xzi41NfXqlyja1rGkpITkUybr1q2zdXNzC2c1kw1wCIijdjqRnCAS7LEDg/BA73nHFghMnJycTBDQFwaePHlSip+NFNKYatt07Ngxd4SMisDAwBBNxODE874AjarV0MWecZ3PTBaec24GKSxtW3JychtVJS8+yxTYp0BDVFTUDSh4vyZimzZtmg8hZ8+eJY7MRS6TshZf392UkZFxi1GVcYWkOuRI0vPnz/9KdIprkoiIiI0MvdoFFNAvDuzFLi+bFy+V6l19SGHTyBMSEhqpqgLo0RQrZdCWZoSVAq6JvLy8AmH2UKICIPGIJIoAD3AFrtChDX70gz7EiouLiyYnJ9VsydGUwfrb2Ni8g1ASb29vv4LdCEeewWZJx04+BDITrHjrh58fuVIkjvx/HHPEYJWmqFtX6YOKJj+QwMRSZJfJcHYVR9ZrhnTns56enuzW1tadILMS8CWHEDRf14cUKVVVVaUMUveYpydtOT8JMbuuXbsWsX379t2LfbQjLxwWFnYEIY2On9fps4OuRJGcfqr2799f1N3d3bzYxBAtahikGpmkdGawZDwcUxodHZ2KjFO5mMQuXLhQwJYIQ4iRUlteXj6QmZl5Q58HIsx0Itzc19bn8ePHEsxHJ4jtwLgxxIjzlyFS/YPYKGY2wJqjhASyjSyElHPe3t4HISfxly9fztchqMXMVeHqI9DT8uSt7Pfs2XMVSzAolUof3b59uxUa1G/oEuIE1X369GkJfeYF5P+HGCliOKxNeHj4uK6Orq6uGr9J5OXl5WvzLWOI0WZ/na2T1FsrKMhx1vyUa7BSqRxG1ltNazHwZFGIQUB7oPR/4HINTQL3Rl/YLbW1nOJYUVFRAuFWafMtYy1GyJEcv0rrpAKBkCOMTSNbLWPcatE2B99kCYqZmZk5+15dXV0F4itt3XrC9aUTg3X47KNeYmJiEeOWzkiyJMTIp83ntKa9vTE/P1/O2InKZSEmEomsmfW0tLRCXYL6UogxC8nnSeLJSN+Hlo0YlnJBLnJycor0EdSXQgwnoPmPcOPj4wPHjx8XM/L5zmUlBh2bl4uysrISrnx+2YhBHngkf4uJiSllJQLLS4zoWE1NTRkjn68l32OXnZhQKLQ4c+ZMiTFOb3Ss1KcUFhYWIeu1pKoPYK1pQ+cw+i8bbQUZSDD5XkdVs+gMRNv/T/+LmAHFgfp0mgMMGDMBbwn/FnQwlhQp/wowAPxkPsdZ4yiuAAAAAElFTkSuQmCC) 0 center no-repeat; + padding-left:60px; + padding-top:15px; + height:40px; + font-size:3em; + margin:0; +} + +.loginBloc{ + background-color: #222222; + border-radius: 5px 5px 5px 5px; + float: none; + margin: 10px 0px 10px; + padding: 5px; + width: auto; +} +.loginBloc input{ + width:100px; + border:none; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + margin-right:1%; + +} + +#rememberMe span{ + color:#ccc; + font-size: 10px; +} + +#rememberMe input{ + margin-left: 10px; + width: 14px; +} + +.logo i{ + font-size:0.7em; +} + +#main aside ul li ul li:hover{ + background-color:#333333; +} + +.feedChip{ + margin-top:9px; + float:left; + width:10px; + height:10px; + border-left:10px solid; +} + +#main article section { + +} + +#main article section.eventRead .readUnreadButton { + /*opacity:0.8; + -moz-opacity : 0.8; + -ms-filter: "alpha(opacity=80)"; + */ + background-color: #222222; +} + +#main article section.eventRead,#main article section.eventHightLighted.eventRead{ + background-color: #EBEBEB +} + +#main article section.eventHightLighted{ + background-color: #fbfbfb; +} +#main article section.eventSelected, +.settings article > section{ + box-shadow: 0 0 20px 0 #C2C2C2; + border-top: 3px solid #F16529; +} +#feedTable{ + width:100%; +} +#feedTable tr td:first{ + width:70%; +} + +.addBloc{ + margin-left: 40px; +} + +#main.settings aside a { + font-size: 16px; +} + +.settings article > section:not(:first-child){ + display:none; +} +.settings .feedsList { + margin: 0; + padding: 0; + list-style: none; +} +.settings .feedListItem { + clear: right; +} +.settings .feedTitle { + display: inline-block; + width:45%; + word-wrap:break-word +} +.settings .feedFolder, +.settings .feedVerbose, +.settings .feedRename, +.settings .feedDelete { + vertical-align: top; +} + +#main.settings .feedAction { + width: 25%; + float: right; +} + +#main.settings .feedFolder { + width: 72%; +} + +#main.settings .feedVerbose { + min-width: 1em; + float:left; +} + +.settings .feedButtons { + float: right; + width: 28%; +} +#main.settings .feedRename, +#main.settings .feedDelete { + min-width: 7em; + width: 48%; +} + +.pointer{ + cursor:pointer; +} + +#main article section a,#main article a{ + color:#F16529; +} + +footer a,#main aside a{ + color:#FFFFFF; + text-decoration:none; + font-size:12px; +} + +#main aside a span{ + font-weight:bold; + font-size:14px; +} + +#main article section{ + border-bottom:1px dashed #cecece; + padding:5px; + overflow: hidden; +} + +#main aside{ + color:#ffffff; + padding:0px 1% 10px; + margin-bottom: 10px; +} + +#main article section a.underlink, #main article a.underlink,.underlink{ + font-size:9px; + color:#222222; + display:block; + line-height: 9px; + margin-bottom: 10px; +} + +#footer-container footer p,#footer-container footer{ + color:#ffffff; + padding:0px; + margin:0px; + text-align:center; + font-size:10px; +} + +#footer-container footer p a,#footer-container footer p a:visited{ + font-size:10px; + color:#ffffff; +} + +#main article section a.button { + color:#ffffff; + font-size:10px; + font-weight:bold; + text-decoration:none; +} + +.pluginBloc li,.pluginBloc ul{ + list-style-type: none; + margin: 0px; + padding:0px; +} +.pluginBloc ul li{ + padding:5px 0 5px 0; + border-bottom: 1px dotted #cecece; + margin:0 0 5px 0; +} +.pluginBloc ul li ul li{ + padding: 0; + border-bottom: none; + margin:0; +} +.pluginBloc ul li ul li h4,.pluginBloc ul li ul li code{ + display:inline; +} + +.errorSync{ + background-color:#C94141; + color:#ffffff; + padding:5px; + border-radius:5px; + margin:10px 0px 10px 0px; + box-shadow: 0 0 3px 0 #810000; +} + +.errorSync a{ + color:#ffffff; +} + +.sync{ + padding-left:10px; +} + +.sync a{ + color:#f16529; +} + +/* =============== + ALL: IE Fixes + =============== */ + +.ie7 #title{ padding-top:20px; } + + +/* ===== Primary Styles ======================================================== + Author: + ========================================================================== */ + + + + + + + + + + + +/* ============================================================================= + Media Queries + ========================================================================== */ + +@media only screen and (min-width: 480px) { + +/* ==================== + INTERMEDIATE: Menu + ==================== */ + + + + nav a{ + float:left; + width:20%; + margin:0 1.7%; + padding:15px 1%; + margin-bottom:0; + border-bottom: 5px solid #E44D26; + } + + nav a:hover{ + border-bottom: 5px solid #F7BC79; + } + + nav li:first-child a{ margin-left:0; } + nav li:last-child a{ margin-right:0; } + +/* ======================== + INTERMEDIATE: IE Fixes + ======================== */ + + nav ul li{ + display:inline; + } + .oldie nav a{ + margin:0 0.7%; + } +} + +@media only screen and (min-width: 768px) { + .loginBloc{ + float: left; + width: 40%; + margin: 15px 20px 0; + } + +#main{ + padding:30px 0; + } + +/* ==================== + WIDE: CSS3 Effects + ==================== */ + + #header-container, + #main aside{ + -webkit-box-shadow:0 5px 10px #aaa; + -moz-box-shadow:0 5px 10px #aaa; + box-shadow:0 5px 10px #aaa; + } + +/* ============ + WIDE: Menu + ============ */ + + #title{ + float:left; + } + + nav{ + float:right; + width:38%; + } + +/* ============ + WIDE: Main + ============ */ + + #main article{ + float:left; + width:67%; + } + + #main #menuBar{ + float:right; + width:32%; + } + #main #menuBar aside{ + padding:5px; + } +} + +@media only screen and (min-width: 1140px) { + +/* =============== + Maximal Width + =============== */ + + .wrapper{ + width:1026px; /* 1140px - 10% for margins */ + margin:0 auto; + } + + #main{ + padding:30px 0; + } + +} + +/* ============================================================================= + Non-Semantic Helper Classes + ========================================================================== */ + +.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } +.ir br { display: none; } +.hidden { display: none !important; visibility: hidden; } +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } +.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } +.invisible { visibility: hidden; } +.clearfix:before, .clearfix:after { content: ""; display: table; } +.clearfix:after { clear: both; } +.clearfix { *zoom: 1; } + +/* ============================================================================= + Print Styles + ========================================================================== */ + +@media print { + * { background: transparent !important; color: black !important; box-shadow:none !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */ + a, a:visited { text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */ + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } /* h5bp.com/t */ + tr, img { page-break-inside: avoid; } + img { max-width: 100% !important; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3 { page-break-after: avoid; } +} + +/* ============================================================================= + Tools + ========================================================================== */ +.left{ + float:left; +} +.right{ + float:right; +} +.clear{ + clear:both; +} +.hidden{ + display:none; +} +.nochip{ + list-style-type: none; +} + +article #loader{ + display: none; +} + +/* ============================================================================= + Help panel + ========================================================================== */ +#helpPanel { + background: #333; + opacity: 0.9; + filter:alpha(opacity=90); /* For IE8 and earlier */ + color: #fff; + padding: 20px; + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + border-radius: 20px; + position: fixed; + left: 50%; + top: 50%; + width: 500px; + height: 21em; + margin-top: -200px; /* moitié de la hauteur */ + margin-left: -250px; /* moitié de la largeur */ + z-Index: 1; + display: none; +} +#helpPanel strong { + color: #ff0; +} diff --git a/sources/templates/marigolds/favicon.png b/sources/templates/marigolds/favicon.png new file mode 100755 index 0000000..cfa2c7a Binary files /dev/null and b/sources/templates/marigolds/favicon.png differ diff --git a/sources/templates/marigolds/footer.html b/sources/templates/marigolds/footer.html new file mode 100755 index 0000000..d0ef877 --- /dev/null +++ b/sources/templates/marigolds/footer.html @@ -0,0 +1,24 @@ + + +
+ + +
+ + + + +{function="Plugin::callJs()"} + + diff --git a/sources/templates/marigolds/header.html b/sources/templates/marigolds/header.html new file mode 100755 index 0000000..d530cd7 --- /dev/null +++ b/sources/templates/marigolds/header.html @@ -0,0 +1,55 @@ + + + + + + + Leed + + + + + + + + {function="Plugin::callLink()"} + {function="Plugin::callCss()"} + + +
+ + + +
+
+

Leed

+
+ {if="!$myUser"} +
+ + + + + + {function="_t('REMEMBER_ME')"} + +
+
+ {else} + {function="_t('IDENTIFIED_WITH',array(''.$myUser->getLogin().''))"} + {/if} +
+
+ + +
+
+
\ No newline at end of file diff --git a/sources/templates/marigolds/index.html b/sources/templates/marigolds/index.html new file mode 100755 index 0000000..10d064e --- /dev/null +++ b/sources/templates/marigolds/index.html @@ -0,0 +1,208 @@ +{include="header"} + + + + +{if="($configurationManager->get('articleDisplayAnonymous')=='1') || ($myUser!=false)"} + + +
+

Raccourcis clavier

+
    +
  • {function="_t('HELP_M')"}
  • +
  • {function="_t('HELP_L')"}
  • +
  • {function="_t('HELP_S')"}
  • +
  • {function="_t('HELP_N')"}
  • +
  • {function="_t('HELP_V')"}
  • +
  • {function="_t('HELP_P')"}
  • +
  • {function="_t('HELP_SPC')"}
  • +
  • {function="_t('HELP_K')"}
  • +
  • {function="_t('HELP_O_ENTER')"}
  • +
  • {function="_t('HELP_H')"}
  • +
+
+ +
+ + + + + + + + + + +
+ +
+ + {if="$action=='selectedFeed' || ($action=='selectedFeedNonLu')"} + + + +

{$currentFeed->getName()}

+
+ {$currentFeed->getDescription()} + {function="_t('SEE_THE')"} + {function="ucfirst(_t('UNREAD'))"} | + {function="_t('OLDER')"} {function="_t('IN_FIRST')"} + {/if} + + {if="$action=='selectedFolder'"} + +

{function="_t('FOLDER')"} : {$currentFolder->getName()}

+

{function="_t('SEE_EVENTS_FOR_FOLDER',array($currentFolder->getName()))"}

+ {/if} + + {if="$action=='favorites'"} + +

{function="_t('FAVORITES_EVENTS',array(''.$numberOfItem.''))"}

+ {/if} + + + {if="($action=='unreadEvents') || ($action=='')"} + +

{function="_t('UNREAD')"} ({$numberOfItem})

?
+ {/if} + +
+
+ {include="article"} +
+ + +
+ +{else} +
+
+

{function="_t('YOU_MUST_BE_LOGGED')"}

+

{function="_t('IF_ADMIN_THEN_CONFIG')"}

+
+
+{/if} + +{include="footer"} diff --git a/sources/templates/marigolds/js/libs/jqueryAndModernizr.min.js b/sources/templates/marigolds/js/libs/jqueryAndModernizr.min.js new file mode 100755 index 0000000..6e313e5 --- /dev/null +++ b/sources/templates/marigolds/js/libs/jqueryAndModernizr.min.js @@ -0,0 +1,8 @@ +/* Modernizr 2.5.3 (Custom Build) | MIT & BSD*/ +;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a)if(j[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.substr(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function L(){e.input=function(c){for(var d=0,e=c.length;d",a,""].join(""),k.id=h,m.innerHTML+=f,m.appendChild(k),l||(m.style.background="",g.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e});var K=function(c,d){var f=c.join(""),g=d.length;y(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch||(j.touch&&j.touch.offsetTop)===9,e.csstransforms3d=(j.csstransforms3d&&j.csstransforms3d.offsetLeft)===9&&j.csstransforms3d.offsetHeight===3,e.generatedcontent=(j.generatedcontent&&j.generatedcontent.offsetHeight)>=1,e.fontface=/src/i.test(h)&&h.indexOf(d.split(" ")[0])===0},g,d)}(['@font-face {font-family:"font";src:url("https://")}',["@media (",n.join("touch-enabled),("),h,")","{#touch{top:9px;position:absolute}}"].join(""),["@media (",n.join("transform-3d),("),h,")","{#csstransforms3d{left:9px;position:absolute;height:3px;}}"].join(""),['#generatedcontent:after{content:"',l,'";visibility:hidden}'].join("")],["fontface","touch","csstransforms3d","generatedcontent"]);s.flexbox=function(){return J("flexOrder")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){try{var d=b.createElement("canvas"),e;e=!(!a.WebGLRenderingContext||!d.getContext("experimental-webgl")&&!d.getContext("webgl")),d=c}catch(f){e=!1}return e},s.touch=function(){return e.touch},s.geolocation=function(){return!!navigator.geolocation},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){for(var b=-1,c=p.length;++b",d.insertBefore(c.lastChild,d.firstChild)}function h(){var a=k.elements;return typeof a=="string"?a.split(" "):a}function i(a){var b={},c=a.createElement,e=a.createDocumentFragment,f=e();a.createElement=function(a){var e=(b[a]||(b[a]=c(a))).cloneNode();return k.shivMethods&&e.canHaveChildren&&!d.test(a)?f.appendChild(e):e},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+h().join().replace(/\w+/g,function(a){return b[a]=c(a),f.createElement(a),'c("'+a+'")'})+");return n}")(k,f)}function j(a){var b;return a.documentShived?a:(k.shivCSS&&!e&&(b=!!g(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),f||(b=!i(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea)$/i,e,f;(function(){var a=b.createElement("a");a.innerHTML="",e="hidden"in a,f=a.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var k={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:j};a.html5=k,j(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f #mq-test-1 { width: 42px; }';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document); +(function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var u=e.document,r=u.documentElement,h=[],j=[],p=[],n={},g=30,f=u.getElementsByTagName("head")[0]||r,b=f.getElementsByTagName("link"),d=[],a=function(){var C=b,x=C.length,A=0,z,y,B,w;for(;A-1,minw:E.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:E.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}i()},k,q,v=function(){var y,z=u.createElement("div"),w=u.body,x=false;z.style.cssText="position:absolute;font-size:1em;width:1em";if(!w){w=x=u.createElement("body")}w.appendChild(z);r.insertBefore(w,r.firstChild);y=z.offsetWidth;if(x){r.removeChild(w)}else{w.removeChild(z)}y=o=parseFloat(y);return y},o,i=function(H){var w="clientWidth",A=r[w],G=u.compatMode==="CSS1Compat"&&A||u.body[w]||A,C={},F=b[b.length-1],y=(new Date()).getTime();if(H&&k&&y-k-1?(o||v()):1)}if(!!I){I=parseFloat(I)*(I.indexOf(x)>-1?(o||v()):1)}if(!J.hasquery||(!z||!K)&&(z||G>=B)&&(K||G<=I)){if(!C[J.media]){C[J.media]=[]}C[J.media].push(j[J.rules])}}for(var D in p){if(p[D]&&p[D].parentNode===f){f.removeChild(p[D])}}for(var D in C){var L=u.createElement("style"),E=C[D].join("\n");L.type="text/css";L.media=D;f.insertBefore(L,F.nextSibling);if(L.styleSheet){L.styleSheet.cssText=E}else{L.appendChild(u.createTextNode(E))}p.push(L)}},m=function(w,y){var x=c();if(!x){return}x.open("GET",w,true);x.onreadystatechange=function(){if(x.readyState!=4||x.status!=200&&x.status!=304){return}y(x.responseText)};if(x.readyState==4){return}x.send(null)},c=(function(){var w=false;try{w=new XMLHttpRequest()}catch(x){w=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return w}})();a();respond.update=a;function s(){i(true)}if(e.addEventListener){e.addEventListener("resize",s,false)}else{if(e.attachEvent){e.attachEvent("onresize",s)}}})(this); +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
"+""+"
",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
t
",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/sources/templates/marigolds/js/script.js b/sources/templates/marigolds/js/script.js new file mode 100755 index 0000000..2d73544 --- /dev/null +++ b/sources/templates/marigolds/js/script.js @@ -0,0 +1,645 @@ +var keyCode = new Array(); + +keyCode['shift'] = 16; +keyCode['ctrl'] = 17; +keyCode['enter'] = 13; +keyCode['l'] = 76; +keyCode['m'] = 77; +keyCode['s'] = 83; +keyCode['n'] = 78; +keyCode['v'] = 86; +keyCode['p'] = 80; +keyCode['k'] = 75; +keyCode['o'] = 79; +keyCode['h'] = 72; +keyCode['space'] = 32; + +$(document).ready(function(){ + + // Page settings + if($('.settings').length){ + + // Gestion affichage partiel ou complet en fonction de affichage du contenu + if($("input[name='articleDisplayContent']").length){ + $("input[name='articleDisplayContent']").click(function(){ + toggleArticleView(); + }); + } + + // Si nom du bloc en hash dans url + var hash=window.location.hash; + if(hash.length){ + toggleBlocks(hash); + } + + // Affichage des differents blocs apres clic sur le menu + $('.toggle').click(function(){ + toggleBlocks($(this).attr("href")); + } + ); + + }else{ + + targetThisEvent($('article section:first'),true); + addEventsButtonLuNonLus(); + + // on initialise ajaxready à true au premier chargement de la fonction + $(window).data('ajaxready', true); + $('article').append('
'+_t('LOADING')+'
'); + $(window).data('page', 1); + $(window).data('nblus', 0); + + if ($(window).scrollTop()==0) scrollInfini(); + } + //alert(_t('IDENTIFIED_WITH',['idleman'])); + + // focus sur l'input du login + if (document.getElementById('inputlogin')) document.getElementById('inputlogin').focus(); +}); + +function _t(key,args){ + value = i18n[key]; + if(args!=null){ + for(i=0;i= $(document).height() + || agentID && ($(window).scrollTop() + $(window).height()) + 150 > $(document).height()) + { + // lorsqu'on commence un traitement, on met ajaxready à false + $(window).data('ajaxready', false); + + //j'affiche mon loader pour indiquer le chargement + $('article #loader').show(); + + //utilisé pour l'alternance des couleurs d'un article à l'autre + if ($('article section:last').hasClass('eventHightLighted')) { + hightlighted = 1; + } else { + hightlighted = 2; + } + + // récupération des variables passées en Get + var action = getUrlVars()['action']; + var folder = getUrlVars()['folder']; + var feed = getUrlVars()['feed']; + var order = getUrlVars()['order']; + if (order) { + order = '&order='+order + } else { + order = '' + } + + $.ajax({ + url: './article.php', + type: 'post', + data: 'scroll='+$(window).data('page')+'&nblus='+$(window).data('nblus')+'&hightlighted='+hightlighted+'&action='+action+'&folder='+folder+'&feed='+feed+order, + + //Succès de la requête + success: function(data) { + if (data.replace(/^\s+/g,'').replace(/\s+$/g,'') != '') + { // on les insère juste avant le loader + $('article #loader').before(data); + //on supprime de la page le script pour ne pas intéragir avec les next & prev + $('article .scriptaddbutton').remove(); + //si l'élement courant est caché, selectionner le premier élément du scroll + //ou si le div loader est sélectionné (quand 0 article restant suite au raccourcis M) + if (($('article section.eventSelected').attr('style')=='display: none;') + || ($('article div.eventSelected').attr('id')=='loader')) + { + targetThisEvent($('article section.scroll:first'), true); + } + // on les affiche avec un fadeIn + $('article section.scroll').fadeIn(600); + // on supprime le tag de classe pour le prochain scroll + $('article section.scroll').removeClass('scroll'); + $(window).data('ajaxready', true); + $(window).data('page', $(window).data('page')+1); + $(window).data('enCoursScroll',0); + // appel récursif tant qu'un scroll n'est pas detecté. + if ($(window).scrollTop()==0) scrollInfini(); + } else { + $('article #loader').addClass('finScroll'); + } + }, + complete: function(){ + // le chargement est terminé, on fait disparaitre notre loader + $('article #loader').fadeOut(400); + } + }); + } + } +}; + +/* Fonctions de séléctions */ +/* Cette fonction sera utilisé pour le scroll infinie, afin d'ajouter les évènements necessaires */ +function addEventsButtonLuNonLus(){ + var handler = function(event){ + var target = event.target; + var id = this.id; + if($(target).hasClass('readUnreadButton')){ + buttonAction(target,id); + }else{ + targetThisEvent(this); + } + } + // on vire tous les évènements afin de ne pas avoir des doublons d'évènements + $('article section').unbind('click'); + // on bind proprement les click sur chaque section + $('article section').bind('click', handler); +} + +function targetPreviousEvent(){ + targetThisEvent($('.eventSelected').prev(':visible'),true); +} +function targetNextEvent(){ + + targetThisEvent($('.eventSelected').next(':visible'),true); +} + +function targetThisEvent(event,focusOn){ + target = $(event); + if(target.prop("tagName")=='SECTION'){ + $('.eventSelected').removeClass('eventSelected'); + target.addClass('eventSelected'); + var id = target.attr('id'); + if(id && focusOn)window.location = '#'+id; + } + if(target.prop("tagName")=='DIV'){ + $('.eventSelected').removeClass('eventSelected'); + target.addClass('eventSelected'); + } + // on débloque les touches le plus tard possible afin de passer derrière l'appel ajax +} +function openTargetEvent(){ + window.open($('.eventSelected .articleTitle a').attr('href'), '_blank'); +} + +function readTargetEvent(){ + var buttonElement = $('.eventSelected .readUnreadButton'); + var id = $(target).attr('id'); + readThis(buttonElement,id,null,function(){ + // on fait un focus sur l'Event suivant + targetThisEvent($('.eventSelected').next(),true); + }); +} + +function targetPreviousEventRead(){ + targetThisEvent($('.eventSelected').prev().css('display','block'),true); + var buttonElement = $('.eventSelected .readUnreadButton'); + var id = $(target).attr('id'); + unReadThis(buttonElement,id,null); +} + +function readAllDisplayedEvents(){ + $('article section').each(function(i,article){ + var buttonElement = $('.readUnreadButton',article); + var id = $('.anchor',article).attr('id'); + readThis(buttonElement,id); + }); +} + +function switchFavoriteTargetEvent(){ + var id = $(target).attr('id'); + if($('.favorite',target).html()=='Favoriser'){ + addFavorite($('.favorite',target),id); + }else{ + removeFavorite($('.favorite',target),id); + } + // on débloque les touches le plus tard possible afin de passer derrière l'appel ajax +} + +/* Fonctions de séléctions fin */ + +function toggleFolder(element,folder){ + feedBloc = $('ul',$(element).parent().parent()); + + open = 0; + if(feedBloc.css('display')=='none') open = 1; + feedBloc.slideToggle(200); + $(element).html((!open?_t('UNFOLD'):_t('FOLD'))); + $.ajax({ + url: "./action.php?action=changeFolderState", + data:{id:folder,isopen:open} + }); +} + +function addFavorite(element,id){ + var activeScreen = $('#pageTop').html(); + $.ajax({ + url: "./action.php?action=addFavorite", + data:{id:id}, + success:function(msg){ + if(msg.status == 'noconnect') { + alert(msg.texte) + } else { + if( console && console.log && msg!="" ) console.log(msg); + $(element).attr('onclick','removeFavorite(this,'+id+');').html(_t('UNFAVORIZE')); + // on compte combien d'article ont été remis en favoris sur la pages favoris (scroll infini) + if (activeScreen=='favorites') { + $(window).data('nblus', $(window).data('nblus')-1); + $('#nbarticle').html(parseInt($('#nbarticle').html()) + 1); + } + } + } + }); +} + +function removeFavorite(element,id){ + var activeScreen = $('#pageTop').html(); + $.ajax({ + url: "./action.php?action=removeFavorite", + data:{id:id}, + success:function(msg){ + if(msg.status == 'noconnect') { + alert(msg.texte) + } else { + if( console && console.log && msg!="" ) console.log(msg); + $(element).attr('onclick','addFavorite(this,'+id+');').html(_t('FAVORIZE')); + // on compte combien d'article ont été remis en favoris sur la pages favoris (scroll infini) + if (activeScreen=='favorites') { + $(window).data('nblus', $(window).data('nblus')+1); + $('#nbarticle').html(parseInt($('#nbarticle').html()) - 1); + } + } + } + }); +} + +function renameFolder(element,folder){ + var folderLine = $(element).parent(); + var folderNameCase = $('span',folderLine); + var value = folderNameCase.html(); + $(element).html('Enregistrer'); + $(element).attr('style','background-color:#0C87C9;'); + $(element).attr('onclick','saveRenameFolder(this,'+folder+')'); + folderNameCase.replaceWith(''); +} + + +function saveRenameFolder(element,folder){ + var folderLine = $(element).parent(); + var folderNameCase = $('span',folderLine); + var value = $('input',folderNameCase).val(); + $(element).html(_t('RENAME')); + $(element).attr('style','background-color:#F16529;'); + $(element).attr('onclick','renameFolder(this,'+folder+')'); + folderNameCase.replaceWith(''+value+''); + $.ajax({ + url: "./action.php?action=renameFolder", + data:{id:folder,name:value} + }); +} + + +function renameFeed(element,feed){ + var feedLine = $(element).parent().parent(); + var feedNameCase = feedLine.children('.js-feedTitle').children('a:nth-child(1)'); + var feedNameValue = feedNameCase.html(); + var feedUrlCase = feedLine.children('.js-feedTitle').children('a:nth-child(2)'); + var feedUrlValue = feedUrlCase.attr('href'); + var url = feedNameCase.attr('href'); + $(element).html(_t('SAVE')); + $(element).attr('style','background-color:#0C87C9;'); + $(element).attr('onclick','saveRenameFeed(this,'+feed+',"'+url+'")'); + feedNameCase.replaceWith(''); + feedUrlCase.replaceWith(''); +} + +function saveRenameFeed(element,feed,url){ + var feedLine = $(element).parent().parent(); + var feedNameCase = feedLine.children('.js-feedTitle:first').children('input[name="feedName"]'); + var feedNameValue = feedNameCase.val(); + var feedUrlCase = feedLine.children('.js-feedTitle:first').children('input[name="feedUrl"]'); + var feedUrlValue = feedUrlCase.val(); + $(element).html('Renommer'); + $(element).attr('style','background-color:#F16529;'); + $(element).attr('onclick','renameFeed(this,'+feed+')'); + feedNameCase.replaceWith(''+feedNameValue+''); + feedUrlCase.replaceWith(''+feedUrlValue+''); + $.ajax({ + url: "./action.php?action=renameFeed", + data:{id:feed,name:feedNameValue,url:feedUrlValue} + }); +} + + +function changeFeedFolder(element,id){ + var value = $(element).val(); + window.location = "./action.php?action=changeFeedFolder&feed="+id+"&folder="+value; +} + + +function readThis(element,id,from,callback){ + var activeScreen = $('#pageTop').html(); + var parent = $(element).parent().parent(); + var nextEvent = $('#'+id).next(); + //sur les éléments non lus + if(!parent.hasClass('eventRead')){ + $.ajax({ + url: "./action.php?action=readContent", + data:{id:id}, + success:function(msg){ + if(msg.status == 'noconnect') { + alert(msg.texte) + } else { + if( console && console.log && msg!="" ) console.log(msg); + switch (activeScreen){ + case '': + // cas de la page d'accueil + parent.addClass('eventRead'); + parent.fadeOut(200,function(){ + if(callback){ + callback(); + }else{ + targetThisEvent(nextEvent,true); + } + // on simule un scroll si tous les events sont cachés + if($('article section:last').attr('style')=='display: none;') { + $(window).scrollTop($(document).height()); + } + }); + // on compte combien d'article ont été lus afin de les soustraires de la requête pour le scroll infini + $(window).data('nblus', $(window).data('nblus')+1); + // on diminue le nombre d'article en haut de page + $('#nbarticle').html(parseInt($('#nbarticle').html()) - 1) + break; + case 'selectedFolder': + case 'selectedFeedNonLu': + parent.addClass('eventRead'); + if(callback){ + callback(); + }else{ + targetThisEvent(nextEvent,true); + } + // on compte combien d'article ont été lus afin de les soustraires de la requête pour le scroll infini + $(window).data('nblus', $(window).data('nblus')+1); + break; + default: + // autres cas : favoris, selectedFeed ... + parent.addClass('eventRead'); + if(callback){ + callback(); + }else{ + targetThisEvent(nextEvent,true); + } + break; + } + } + } + }); + }else{ // sur les éléments lus + // si ce n'est pas un clic sur le titre de l'event + if(from!='title'){ + $.ajax({ + url: "./action.php?action=unreadContent", + data:{id:id}, + success:function(msg){ + if(msg.status == 'noconnect') { + alert(msg.texte) + } else { + if( console && console.log && msg!="" ) console.log(msg); + parent.removeClass('eventRead'); + // on compte combien d'article ont été remis à non lus + if ((activeScreen=='') || (activeScreen=='selectedFolder')|| (activeScreen=='selectedFeedNonLu')) + $(window).data('nblus', $(window).data('nblus')-1); + if(callback){ + callback(); + } + } + } + }); + } + } + +} + +function unReadThis(element,id,from){ + var activeScreen = $('#pageTop').html(); + var parent = $(element).parent().parent(); + if(parent.hasClass('eventRead')){ + if(from!='title'){ + $.ajax({ + url: "./action.php?action=unreadContent", + data:{id:id}, + success:function(msg){ + if(msg.status == 'noconnect') { + alert(msg.texte) + } else { + if( console && console.log && msg!="" ) console.log(msg); + parent.removeClass('eventRead'); + // on compte combien d'article ont été remis à non lus + if ((activeScreen=='') || (activeScreen=='selectedFolder')|| (activeScreen=='selectedFeedNonLu')) + $(window).data('nblus', $(window).data('nblus')-1); + // on augmente le nombre d'article en haut de page + if (activeScreen=='') $('#nbarticle').html(parseInt($('#nbarticle').html()) + 1); + } + } + }); + } + } + +} + +//synchronisation manuelle lancée depuis le boutton du menu +function synchronize(code){ + if(code!=''){ + $('article').prepend('
'+ + ''+ + '
'); + }else{ + alert(_t('YOU_MUST_BE_CONNECTED_FEED')); + } +} + +// Active ou desactive inputs type affichage des events +function toggleArticleView(){ + var element = $("input[name=articleView]"); + element.prop("disabled",!element.prop("disabled")); +} + +// Disparition block et affichage block clique +function toggleBlocks(target){ + target=target.substring(1); + $('#main article > section').hide();$('.'+target).fadeToggle(200); +} + +// affiche ou cache les feeds n'ayant pas d'article non lus. +function toggleUnreadFeedFolder(button,action){ + $.ajax({ + url: "./action.php?action=displayOnlyUnreadFeedFolder&displayOnlyUnreadFeedFolder="+action, + success:function(msg){ + if(msg.status == 'noconnect') { + alert(msg.texte) + } else { + if( console && console.log && msg!="" ) console.log(msg); + //Afficher ou cacher les feeds + if(action){ + $('.hidefeed').hide(); + }else{ + $('.hidefeed').show(); + } + //changement de l'évènement onclick pour faire l'inverse lors du prochain clic + $(button).attr('onclick','toggleUnreadFeedFolder(this,'+!action+');'); + + } + } + }); +} + +function buttonAction(target,id){ + // Check unreadEvent + if($('#pageTop').html()){ + var from=true; + }else{ + var from=''; + } + readThis(target,id,from); +} + + +// permet de récupérer les variables passée en get dans l'URL et des les parser +function getUrlVars() +{ + var vars = [], hash; + var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); + for(var i = 0; i < hashes.length; i++) + { + hash = hashes[i].split('='); + vars.push(hash[0]); + if (hash[1]){ + rehash = hash[1].split('#'); + vars[hash[0]] = rehash[0]; + } else { + vars[hash[0]] = ''; + } + + + } + return vars; +} + +// affiche ou cache les feeds n'ayant pas d'article non lus. +function toggleFeedVerbose(button,action,idFeed){ + $.ajax({ + url: "./action.php?action=displayFeedIsVerbose&displayFeedIsVerbose="+action+"&idFeed="+idFeed, + success:function(msg){ + if(msg.status == 'noconnect') { + alert(msg.texte) + } else { + if( console && console.log && msg!="" ) console.log(msg); + //changement de l'évènement onclick pour faire l'inverse lors du prochain clic + var reverseaction = 0 + if (action==0) { reverseaction = 1 } + $(button).attr('onclick','toggleFeedVerbose(this,'+reverseaction+', '+idFeed+');'); + } + } + }); +} + +// Bouton permettant l'affichage des options d'affichage et de non affichage des flux souhaités en page d'accueil +function toggleOptionFeedVerbose(button,action){ + $.ajax({ + url: "./action.php?action=optionFeedIsVerbose&optionFeedIsVerbose="+action, + success:function(msg){ + if(msg.status == 'noconnect') { + alert(msg.texte) + } else { + if( console && console.log && msg!="" ) console.log(msg); + //changement de l'évènement onclick pour faire l'inverse lors du prochain clic + var reverseaction = 0 + if (action==0) { reverseaction = 1 } + $(button).attr('onclick','toggleOptionFeedVerbose(this,'+reverseaction+');'); + //Changement du statut des cases à cocher sur les feed (afficher ou cacher) + if (action==1){ + $('.feedVerbose').hide(); + }else{ + $('.feedVerbose').show(); + } + } + } + }); +} \ No newline at end of file diff --git a/sources/templates/marigolds/settings.html b/sources/templates/marigolds/settings.html new file mode 100755 index 0000000..8421131 --- /dev/null +++ b/sources/templates/marigolds/settings.html @@ -0,0 +1,318 @@ +{include="header"} + + + +{if="($configurationManager->get('articleDisplayAnonymous')=='1') || ($myUser!=false)"} + + +
+ +
+ + +
+ +

{function="_t('FEED_MANAGING')"} :

+ +
+
+

{function="_t('ADD_FEED')"}

+

{function="_t('FEED_RSS_LINK')"} :   + +

+ +
+ + +
+

{function="_t('ADD_FOLDER')"}

+ +
+ {function="_t('NEW_FOLDER')"} + + +
+ +
+

{function="_t('FEED_OPTION')"}

+ {function="_t('FEED_OPTION_ISVERBOSE')"} + {if="$optionFeedIsVerbose==0"} + + {else} + + {/if} +
+ +
    + {$feedsForFolder=""} + {loop="$folders"} + + {$feedsForFolder=$value->getFeeds()} + +
  • {if="$value->getId()==1"}{/if} +

    {$value->getName()} ({function="count($feedsForFolder)"}) + + + {if="$value->getId()!='1'"} + + {/if} + +
    +

    +
    +
      + {if="count($feeds)!=0"} + {loop="$feedsForFolder"} +
    • + + {function="Functions::truncate($value2->getName(),40)"}{$value2->getUrl()} + +
      + + +
      +
      + + {if="$optionFeedIsVerbose==0"} + {if="$value2->getIsverbose()==0"} + + {else} + + {/if} + {else} + {if="$value2->getIsverbose()==0"} + + {else} + + {/if} + {/if} +
      +
    • + {/loop} + {/if} +
    +
  • + {/loop} +
+
+ +
+

{function="_t('FEED_SYNCHRONISATION')"} :

+ +
+ + {if="(isset($myUser)) && ($myUser!=false)"} +
+

{function="_t('PREFERENCES')"} :

+
+
+

{function="_t('GENERALITY')"}

+

+

{function="_t('LET_SLASH_AT_END')"}

+
+ +
+

{function="_t('USER')"}

+

+

+

{function="_t('LET_EMPTY_IF_NO_PASS_CHANGE')"}

+

{function="_t('HOWTO_RESET_PASSWORD')"}

+
+ +
+

{function="_t('SYNCHRONISATION')"}

+

{function="_t('AUTOMATIC_FULL_DESC')"}

+

{function="_t('AUTOMATIC_GRADUATE_DESC')"}

+ +

{function="_t('SYNCHRONISATION_CODE')"} : + {$synchronisationCode} +

+

{function="_t('SYNCHRONISATION_OPTION')"} +

+ {function="_t('ENABLE_CACHE')"} + + +

{function="_t('ENABLE_CACHE_DESC')"}

+
+
+ {function="_t('FORCE_INTEGRATION')"} + + +

{function="_t('FORCE_INTEGRATION_DESC')"}

+
+

+
+ +
+ {if="$myUser!=false"} +

{function="_t('PREFERENCES')"}

+
+ {function="_t('ALLOW_ANONYMOUS_READ')"} + + +

{function="_t('ALLOW_ANONYMOUS_READ_DESC')"}

+
+
+ + +
+
+ {function="_t('SORT_BY_RECENT_EVENT_HOME')"} + + +
+
+ {function="_t('SORT_BY_RECENT_EVENT_FOLDER')"} + + +
+
+ {function="_t('SHOW_EVENT_LINK')"} + + +
+
+ {function="_t('SHOW_EVENT_DATE')"} + + +
+
+ {function="_t('SHOW_EVENT_AUTHOR')"} + + +
+
+ {function="_t('SHOW_EVENT_CONTENT')"} + + +
+ + +
+

{function="_t('SHOW_PARTIAL_CONTENT_DESC')"}

+
+
+ {function="_t('KEEP_LAST_X_EVENTS_FEED',array(''))"} +

{function="_t('KEEP_LAST_X_EVENTS_FEED_DESC')"}

+
+ +
+ + + + + + + {else} +

{function="_t('YOU_MUST_BE_CONNECTED_BOOKMARK')"}

+ {/if} +
+ {/if} + + +
+

{function="_t('IMPORT_FEED_OPML_FORMAT')"}

+ +
+ + +
+
+

{function="_t('EXPORT_FEED_OPML_FORMAT')"}

+

{function="_t('OPML_FILE')"} :

+ +
+ +
+

{function="_t('USE_BOOKMARK')"} :

+ {if="$myUser!=false"} + getToken()"}'">+ {function="_t('ADD_TO_LEED')"} +

{function="_t('USE_BOOKMARK_DESC')"}

+ {else} +

{function="_t('YOU_MUST_BE_CONNECTED_BOOKMARK')"}

+ {/if} +
+ + + +
+

{function="_t('PLUGINS')"} :

+

{function="_t('CAN_DOWNLOAD_PLUGINS')"} : Leed Market.

+

{function="_t('AVAILABLE_PLUGIN_LIST')"} :

+ {if="$myUser!=false"} +
    + {if="count($plugins)==0"} + {function="_t('NO_INSTALLED_PLUGINS')"} + + {else} + {loop="$plugins"} +
  • + +
  • + {/loop} + {/if} +
+ + {else} +

{function="_t('YOU_MUST_BE_CONNECTED_PLUGIN')"}

+ {/if} +
+ + + + + {function="Plugin::callHook("setting_post_section", array(&$myUser))"} +
+ + +
+ +{else} +
+
+

{function="_t('YOU_MUST_BE_CONNECTED_FEED')"}

+

{function="_t('IF_ADMIN_THEN_CONFIG')"}

+
+
+ +{/if} +{include="footer"} diff --git a/sources/update-1.5.sql b/sources/update-1.5.sql new file mode 100755 index 0000000..08e9f72 --- /dev/null +++ b/sources/update-1.5.sql @@ -0,0 +1,23 @@ +/* ############# +### MISE À JOUR Base de données de Leed pour fonctionnement en v1.5 + +Conseils : +- Avant d'effectuer la mise à jour, sauvegardez votre BDD et exportez vos flux en OPML. +- Attention : "leed_" est à remplacer par votre préfix de table. +- Ce fichier est à supprimer après installation. + +Description : +- Les requêtes suivantes sont a exécuter sur votre Base de données Leed avec phpMyAdmin par exemple + +############### */ + +-- Mise à jour index Table des Event +ALTER TABLE `leed_event` ADD KEY `indexfeed` (`feed`); +ALTER TABLE `leed_event` ADD KEY `indexunread` (`unread`); +ALTER TABLE `leed_event` ADD KEY `indexfavorite` (`favorite`); + +-- Mise à jour index Table des Feed +ALTER TABLE `leed_feed` ADD KEY `indexfolder` (`folder`); + +-- Mise à jour table Event pour la synchronisation des flux (OBLIGATOIRE) +ALTER TABLE `leed_event` ADD `syncId` INT UNSIGNED NOT NULL; \ No newline at end of file diff --git a/sources/update-r93.php b/sources/update-r93.php new file mode 100755 index 0000000..d0343d2 --- /dev/null +++ b/sources/update-r93.php @@ -0,0 +1,85 @@ + MYSQL_PREFIX.'configuration', + 'e' => MYSQL_PREFIX.'event', + 'f' => MYSQL_PREFIX.'feed', + 'd' => MYSQL_PREFIX.'folder', + 'u' => MYSQL_PREFIX.'user', +); + +// on convertit toutes les tables +foreach ($tables as $tb) +{ + echo '
conversion de la structure de la table '.$tb.' ... '; + ob_flush(); flush(); + if (!$mysql->query('ALTER TABLE '.$tb.' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci, ENGINE=InnoDB')) + { + echo 'erreur !
'; + print_r($mysql->error_list); + ob_flush(); flush(); + die(1); + } + echo 'ok'; +} + +// on passe la connexion en utf-8 pour traiter les données +$mysql->query('SET NAMES utf8'); + +// maintenant on va récupérer toutes les tables pour faire html_entity_decode +function convert($table, $champs) +{ + global $mysql; + echo '
conversion des données de la table '.$table.' ... '; + ob_flush(); flush(); + + $res = $mysql->query('SELECT * FROM '.$table); + if ($res) + { + while ($row = $res->fetch_assoc()) + { + $sql = 'UPDATE '.$table.' SET '; + $first = true; + foreach ($champs as $c) + { + $row[$c] = html_entity_decode($row[$c]); + $sql .= ($first?'':', '). $c .'="'.$mysql->real_escape_string($row[$c]).'"'; + $first = false; + } + $sql .= ' WHERE id = '.$row['id']; + if (!$mysql->query($sql)) + { + echo 'erreur champ '.$row['id'].'
'; + print_r($mysql->error_list); + + echo '
on continue ... '; + ob_flush(); flush(); + } + } + echo 'ok'; + $res->free(); + } +} + +// evenements +convert($tables['e'], array('title','creator','content','description')); +// feed +convert($tables['f'], array('name','description')); +// folder +convert($tables['d'], array('name')); + +echo '
Conversion terminée'; diff --git a/sources/updates/00001-Update-20140213.sql b/sources/updates/00001-Update-20140213.sql new file mode 100755 index 0000000..7f84e1c --- /dev/null +++ b/sources/updates/00001-Update-20140213.sql @@ -0,0 +1,19 @@ +--###################################################################################################### +--##### +--##### MISE À JOUR Base de données de Leed +--##### Date : 13/02/2014 +--##### +--##### Préfixe des tables : ##MYSQL_PREFIX## est remplacé automatiquement +--##### +--##### Feature(s) : +--##### - Option pour cacher les flux souhaités sur la page d'accueil +--##### +--###################################################################################################### + +-- Mise à jour table FOLDER (Obligatoire) +ALTER TABLE `##MYSQL_PREFIX##feed` DROP `isverbose`; +ALTER TABLE `##MYSQL_PREFIX##feed` ADD `isverbose` INT(1) NOT NULL; + +-- évolution pour les flux RSS défini verbeux qu'il faut ou ne faut pas afficher sur la page d'accueil. +DELETE FROM `##MYSQL_PREFIX##configuration` WHERE `key` = 'optionFeedIsVerbose'; +INSERT INTO `##MYSQL_PREFIX##configuration` (`key`,`value`) VALUES ('optionFeedIsVerbose',1); \ No newline at end of file