Add RTL language support
This commit is contained in:
parent
056673b70c
commit
e6e6db71f8
@ -4,6 +4,7 @@ require __DIR__.'/check_setup.php';
|
|||||||
require __DIR__.'/vendor/PicoTools/Translator.php';
|
require __DIR__.'/vendor/PicoTools/Translator.php';
|
||||||
require __DIR__.'/vendor/PicoDb/Database.php';
|
require __DIR__.'/vendor/PicoDb/Database.php';
|
||||||
require __DIR__.'/vendor/PicoFeed/Client.php';
|
require __DIR__.'/vendor/PicoFeed/Client.php';
|
||||||
|
require __DIR__.'/vendor/PicoFeed/Parser.php';
|
||||||
require __DIR__.'/models/config.php';
|
require __DIR__.'/models/config.php';
|
||||||
require __DIR__.'/models/user.php';
|
require __DIR__.'/models/user.php';
|
||||||
require __DIR__.'/models/feed.php';
|
require __DIR__.'/models/feed.php';
|
||||||
|
242
helpers.php
242
helpers.php
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
namespace Helper;
|
namespace Helper;
|
||||||
|
|
||||||
|
function isRTL($language)
|
||||||
|
{
|
||||||
|
return \PicoFeed\Parser::isLanguageRTL($language);
|
||||||
|
}
|
||||||
|
|
||||||
function css()
|
function css()
|
||||||
{
|
{
|
||||||
$theme = \Model\Config\get('theme');
|
$theme = \Model\Config\get('theme');
|
||||||
@ -17,3 +22,240 @@ function css()
|
|||||||
|
|
||||||
return 'assets/css/app.css?version='.filemtime('assets/css/app.css');
|
return 'assets/css/app.css?version='.filemtime('assets/css/app.css');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function get_current_base_url()
|
||||||
|
{
|
||||||
|
$url = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
|
||||||
|
$url .= $_SERVER['SERVER_NAME'];
|
||||||
|
$url .= $_SERVER['SERVER_PORT'] == 80 || $_SERVER['SERVER_PORT'] == 443 ? '' : ':'.$_SERVER['SERVER_PORT'];
|
||||||
|
$url .= dirname($_SERVER['PHP_SELF']) !== '/' ? dirname($_SERVER['PHP_SELF']).'/' : '/';
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escape($value)
|
||||||
|
{
|
||||||
|
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function flash($html)
|
||||||
|
{
|
||||||
|
$data = '';
|
||||||
|
|
||||||
|
if (isset($_SESSION['flash_message'])) {
|
||||||
|
$data = sprintf($html, escape($_SESSION['flash_message']));
|
||||||
|
unset($_SESSION['flash_message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flash_error($html)
|
||||||
|
{
|
||||||
|
$data = '';
|
||||||
|
|
||||||
|
if (isset($_SESSION['flash_error_message'])) {
|
||||||
|
$data = sprintf($html, escape($_SESSION['flash_error_message']));
|
||||||
|
unset($_SESSION['flash_error_message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function format_bytes($size, $precision = 2)
|
||||||
|
{
|
||||||
|
$base = log($size) / log(1024);
|
||||||
|
$suffixes = array('', 'k', 'M', 'G', 'T');
|
||||||
|
|
||||||
|
return round(pow(1024, $base - floor($base)), $precision).$suffixes[floor($base)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_host_from_url($url)
|
||||||
|
{
|
||||||
|
return escape(parse_url($url, PHP_URL_HOST)) ?: $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function summary($value, $min_length = 5, $max_length = 120, $end = '[...]')
|
||||||
|
{
|
||||||
|
$length = strlen($value);
|
||||||
|
|
||||||
|
if ($length > $max_length) {
|
||||||
|
return substr($value, 0, strpos($value, ' ', $max_length)).' '.$end;
|
||||||
|
}
|
||||||
|
else if ($length < $min_length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function in_list($id, array $listing)
|
||||||
|
{
|
||||||
|
if (isset($listing[$id])) {
|
||||||
|
return escape($listing[$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
function relative_time($timestamp, $fallback_date_format = '%e %B %Y %k:%M')
|
||||||
|
{
|
||||||
|
$diff = time() - $timestamp;
|
||||||
|
|
||||||
|
if ($diff < 60) return \t('%d second'.($diff > 1 ? 's' : '').' ago', $diff);
|
||||||
|
|
||||||
|
$diff = floor($diff / 60);
|
||||||
|
if ($diff < 60) return \t('%d minute'.($diff > 1 ? 's' : '').' ago', $diff);
|
||||||
|
|
||||||
|
$diff = floor($diff / 60);
|
||||||
|
if ($diff < 24) return \t('%d hour'.($diff > 1 ? 's' : '').' ago', $diff);
|
||||||
|
|
||||||
|
$diff = floor($diff / 24);
|
||||||
|
if ($diff < 7) return \t('%d day'.($diff > 1 ? 's' : '').' ago', $diff);
|
||||||
|
|
||||||
|
$diff = floor($diff / 7);
|
||||||
|
if ($diff < 4) return \t('%d week'.($diff > 1 ? 's' : '').' ago', $diff);
|
||||||
|
|
||||||
|
$diff = floor($diff / 4);
|
||||||
|
if ($diff < 12) return \t('%d month'.($diff > 1 ? 's' : '').' ago', $diff);
|
||||||
|
|
||||||
|
return \dt($fallback_date_format, $timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function error_class(array $errors, $name)
|
||||||
|
{
|
||||||
|
return ! isset($errors[$name]) ? '' : ' form-error';
|
||||||
|
}
|
||||||
|
|
||||||
|
function error_list(array $errors, $name)
|
||||||
|
{
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
if (isset($errors[$name])) {
|
||||||
|
|
||||||
|
$html .= '<ul class="form-errors">';
|
||||||
|
|
||||||
|
foreach ($errors[$name] as $error) {
|
||||||
|
$html .= '<li>'.escape($error).'</li>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</ul>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_value($values, $name)
|
||||||
|
{
|
||||||
|
if (isset($values->$name)) {
|
||||||
|
return 'value="'.escape($values->$name).'"';
|
||||||
|
}
|
||||||
|
|
||||||
|
return isset($values[$name]) ? 'value="'.escape($values[$name]).'"' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_hidden($name, $values = array())
|
||||||
|
{
|
||||||
|
return '<input type="hidden" name="'.$name.'" id="form-'.$name.'" '.form_value($values, $name).'/>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_default_select($name, array $options, $values = array(), array $errors = array(), $class = '')
|
||||||
|
{
|
||||||
|
$options = array('' => '?') + $options;
|
||||||
|
return form_select($name, $options, $values, $errors, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_select($name, array $options, $values = array(), array $errors = array(), $class = '')
|
||||||
|
{
|
||||||
|
$html = '<select name="'.$name.'" id="form-'.$name.'" class="'.$class.'">';
|
||||||
|
|
||||||
|
foreach ($options as $id => $value) {
|
||||||
|
|
||||||
|
$html .= '<option value="'.escape($id).'"';
|
||||||
|
|
||||||
|
if (isset($values->$name) && $id == $values->$name) $html .= ' selected="selected"';
|
||||||
|
if (isset($values[$name]) && $id == $values[$name]) $html .= ' selected="selected"';
|
||||||
|
|
||||||
|
$html .= '>'.escape($value).'</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</select>';
|
||||||
|
$html .= error_list($errors, $name);
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_radios($name, array $options, array $values = array())
|
||||||
|
{
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
foreach ($options as $value => $label) {
|
||||||
|
$html .= form_radio($name, $label, $value, isset($values[$name]) && $values[$name] == $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_radio($name, $label, $value, $selected = false, $class = '')
|
||||||
|
{
|
||||||
|
return '<label><input type="radio" name="'.$name.'" class="'.$class.'" value="'.escape($value).'" '.($selected ? 'selected="selected"' : '').'>'.escape($label).'</label>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_checkbox($name, $label, $value, $checked = false, $class = '')
|
||||||
|
{
|
||||||
|
return '<label><input type="checkbox" name="'.$name.'" class="'.$class.'" value="'.escape($value).'" '.($checked ? 'checked="checked"' : '').'> '.escape($label).'</label>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_label($label, $name, $class = '')
|
||||||
|
{
|
||||||
|
return '<label for="form-'.$name.'" class="'.$class.'">'.escape($label).'</label>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_textarea($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
|
{
|
||||||
|
$class .= error_class($errors, $name);
|
||||||
|
|
||||||
|
$html = '<textarea name="'.$name.'" id="form-'.$name.'" class="'.$class.'" ';
|
||||||
|
$html .= implode(' ', $attributes).'>';
|
||||||
|
$html .= isset($values->$name) ? escape($values->$name) : isset($values[$name]) ? $values[$name] : '';
|
||||||
|
$html .= '</textarea>';
|
||||||
|
$html .= error_list($errors, $name);
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_input($type, $name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
|
{
|
||||||
|
$class .= error_class($errors, $name);
|
||||||
|
|
||||||
|
$html = '<input type="'.$type.'" name="'.$name.'" id="form-'.$name.'" '.form_value($values, $name).' class="'.$class.'" ';
|
||||||
|
$html .= implode(' ', $attributes).'/>';
|
||||||
|
$html .= error_list($errors, $name);
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_text($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
|
{
|
||||||
|
return form_input('text', $name, $values, $errors, $attributes, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_password($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
|
{
|
||||||
|
return form_input('password', $name, $values, $errors, $attributes, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_email($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
|
{
|
||||||
|
return form_input('email', $name, $values, $errors, $attributes, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_date($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
|
{
|
||||||
|
return form_input('date', $name, $values, $errors, $attributes, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
function form_number($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
|
{
|
||||||
|
return form_input('number', $name, $values, $errors, $attributes, $class);
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
require __DIR__.'/common.php';
|
require __DIR__.'/common.php';
|
||||||
require __DIR__.'/vendor/PicoTools/Template.php';
|
require __DIR__.'/vendor/PicoTools/Template.php';
|
||||||
require __DIR__.'/vendor/PicoTools/Helper.php';
|
|
||||||
require __DIR__.'/vendor/PicoFarad/Response.php';
|
require __DIR__.'/vendor/PicoFarad/Response.php';
|
||||||
require __DIR__.'/vendor/PicoFarad/Request.php';
|
require __DIR__.'/vendor/PicoFarad/Request.php';
|
||||||
require __DIR__.'/vendor/PicoFarad/Session.php';
|
require __DIR__.'/vendor/PicoFarad/Session.php';
|
||||||
|
@ -16,7 +16,7 @@ use SimpleValidator\Validator;
|
|||||||
use SimpleValidator\Validators;
|
use SimpleValidator\Validators;
|
||||||
use PicoDb\Database;
|
use PicoDb\Database;
|
||||||
|
|
||||||
const DB_VERSION = 22;
|
const DB_VERSION = 23;
|
||||||
const HTTP_USERAGENT = 'Miniflux - http://miniflux.net';
|
const HTTP_USERAGENT = 'Miniflux - http://miniflux.net';
|
||||||
const HTTP_FAKE_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36';
|
const HTTP_FAKE_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36';
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ function get_everything()
|
|||||||
'items.feed_id',
|
'items.feed_id',
|
||||||
'items.status',
|
'items.status',
|
||||||
'items.content',
|
'items.content',
|
||||||
|
'items.language',
|
||||||
'feeds.site_url',
|
'feeds.site_url',
|
||||||
'feeds.title AS feed_title'
|
'feeds.title AS feed_title'
|
||||||
)
|
)
|
||||||
@ -49,6 +50,7 @@ function get_everything_since($timestamp)
|
|||||||
'items.feed_id',
|
'items.feed_id',
|
||||||
'items.status',
|
'items.status',
|
||||||
'items.content',
|
'items.content',
|
||||||
|
'items.language',
|
||||||
'feeds.site_url',
|
'feeds.site_url',
|
||||||
'feeds.title AS feed_title'
|
'feeds.title AS feed_title'
|
||||||
)
|
)
|
||||||
@ -85,6 +87,7 @@ function get_all($status, $offset = null, $limit = null, $order_column = 'update
|
|||||||
'items.feed_id',
|
'items.feed_id',
|
||||||
'items.status',
|
'items.status',
|
||||||
'items.content',
|
'items.content',
|
||||||
|
'items.language',
|
||||||
'feeds.site_url',
|
'feeds.site_url',
|
||||||
'feeds.title AS feed_title'
|
'feeds.title AS feed_title'
|
||||||
)
|
)
|
||||||
@ -131,6 +134,7 @@ function get_bookmarks($offset = null, $limit = null)
|
|||||||
'items.status',
|
'items.status',
|
||||||
'items.content',
|
'items.content',
|
||||||
'items.feed_id',
|
'items.feed_id',
|
||||||
|
'items.language',
|
||||||
'feeds.site_url',
|
'feeds.site_url',
|
||||||
'feeds.title AS feed_title'
|
'feeds.title AS feed_title'
|
||||||
)
|
)
|
||||||
@ -169,6 +173,7 @@ function get_all_by_feed($feed_id, $offset = null, $limit = null, $order_column
|
|||||||
'items.status',
|
'items.status',
|
||||||
'items.content',
|
'items.content',
|
||||||
'items.bookmark',
|
'items.bookmark',
|
||||||
|
'items.language',
|
||||||
'feeds.site_url'
|
'feeds.site_url'
|
||||||
)
|
)
|
||||||
->join('feeds', 'id', 'feed_id')
|
->join('feeds', 'id', 'feed_id')
|
||||||
@ -433,6 +438,7 @@ function update_all($feed_id, array $items, $grabber = false)
|
|||||||
'feed_id' => $feed_id,
|
'feed_id' => $feed_id,
|
||||||
'enclosure' => isset($item->enclosure) ? $item->enclosure : null,
|
'enclosure' => isset($item->enclosure) ? $item->enclosure : null,
|
||||||
'enclosure_type' => isset($item->enclosure_type) ? $item->enclosure_type : null,
|
'enclosure_type' => isset($item->enclosure_type) ? $item->enclosure_type : null,
|
||||||
|
'language' => $item->language,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
namespace Schema;
|
namespace Schema;
|
||||||
|
|
||||||
|
|
||||||
|
function version_23($pdo)
|
||||||
|
{
|
||||||
|
$pdo->exec('ALTER TABLE items ADD COLUMN language TEXT');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function version_22($pdo)
|
function version_22($pdo)
|
||||||
{
|
{
|
||||||
$pdo->exec("ALTER TABLE config ADD COLUMN timezone TEXT DEFAULT 'UTC'");
|
$pdo->exec("ALTER TABLE config ADD COLUMN timezone TEXT DEFAULT 'UTC'");
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
data-item-page="<?= $menu ?>"
|
data-item-page="<?= $menu ?>"
|
||||||
<?= $hide ? 'data-hide="true"' : '' ?>
|
<?= $hide ? 'data-hide="true"' : '' ?>
|
||||||
>
|
>
|
||||||
<h2>
|
<h2 <?= Helper\isRTL($item['language']) ? 'dir="rtl"' : '' ?>>
|
||||||
<?= $item['bookmark'] ? '<span id="bookmark-icon-'.$item['id'].'">★ </span>' : '' ?>
|
<?= $item['bookmark'] ? '<span id="bookmark-icon-'.$item['id'].'">★ </span>' : '' ?>
|
||||||
<?= $item['status'] === 'read' ? '<span id="read-icon-'.$item['id'].'">✔ </span>' : '' ?>
|
<?= $item['status'] === 'read' ? '<span id="read-icon-'.$item['id'].'">✔ </span>' : '' ?>
|
||||||
<a
|
<a
|
||||||
@ -18,7 +18,7 @@
|
|||||||
<?= Helper\escape($item['title']) ?>
|
<?= Helper\escape($item['title']) ?>
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
<p class="preview">
|
<p class="preview" <?= Helper\isRTL($item['language']) ? 'dir="rtl"' : '' ?>>
|
||||||
<?= Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
|
<?= Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
|
||||||
</p>
|
</p>
|
||||||
<ul class="item-menu">
|
<ul class="item-menu">
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<h1>
|
<h1 <?= Helper\isRTL($item['language']) ? 'dir="rtl"' : '' ?>>
|
||||||
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank" id="original-<?= $item['id'] ?>">
|
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank" id="original-<?= $item['id'] ?>">
|
||||||
<?= Helper\escape($item['title']) ?>
|
<?= Helper\escape($item['title']) ?>
|
||||||
</a>
|
</a>
|
||||||
@ -82,7 +82,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div id="item-content">
|
<div id="item-content" <?= Helper\isRTL($item['language']) ? 'dir="rtl"' : '' ?>>
|
||||||
<?= $item['content'] ?>
|
<?= $item['content'] ?>
|
||||||
|
|
||||||
<?php if ($item['enclosure']): ?>
|
<?php if ($item['enclosure']): ?>
|
||||||
|
27
vendor/PicoFeed/Clients/Stream.php
vendored
27
vendor/PicoFeed/Clients/Stream.php
vendored
@ -4,14 +4,27 @@ namespace PicoFeed\Clients;
|
|||||||
|
|
||||||
use \PicoFeed\Logging;
|
use \PicoFeed\Logging;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream context HTTP client
|
||||||
|
*
|
||||||
|
* @author Frederic Guillot
|
||||||
|
* @package client
|
||||||
|
*/
|
||||||
class Stream extends \PicoFeed\Client
|
class Stream extends \PicoFeed\Client
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Do the HTTP request
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return array HTTP response ['body' => ..., 'status' => ..., 'headers' => ...]
|
||||||
|
*/
|
||||||
public function doRequest()
|
public function doRequest()
|
||||||
{
|
{
|
||||||
// Prepare HTTP headers for the request
|
// Prepare HTTP headers for the request
|
||||||
$headers = array(
|
$headers = array(
|
||||||
'Connection: close',
|
'Connection: close',
|
||||||
'User-Agent: '.$this->user_agent,
|
'User-Agent: '.$this->user_agent,
|
||||||
|
'Accept-Encoding: gzip',
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($this->etag) $headers[] = 'If-None-Match: '.$this->etag;
|
if ($this->etag) $headers[] = 'If-None-Match: '.$this->etag;
|
||||||
@ -61,6 +74,10 @@ class Stream extends \PicoFeed\Client
|
|||||||
$body = $this->decodeChunked($body);
|
$body = $this->decodeChunked($body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($headers['Content-Encoding']) && $headers['Content-Encoding'] === 'gzip') {
|
||||||
|
$body = gzdecode($body);
|
||||||
|
}
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
'body' => $body,
|
'body' => $body,
|
||||||
@ -68,7 +85,13 @@ class Stream extends \PicoFeed\Client
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a chunked body
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $str Raw body
|
||||||
|
* @return string Decoded body
|
||||||
|
*/
|
||||||
public function decodeChunked($str)
|
public function decodeChunked($str)
|
||||||
{
|
{
|
||||||
for ($result = ''; ! empty($str); $str = trim($str)) {
|
for ($result = ''; ! empty($str); $str = trim($str)) {
|
||||||
@ -84,4 +107,4 @@ class Stream extends \PicoFeed\Client
|
|||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
vendor/PicoFeed/Import.php
vendored
2
vendor/PicoFeed/Import.php
vendored
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace PicoFeed;
|
namespace PicoFeed;
|
||||||
|
|
||||||
|
require_once __DIR__.'/Logging.php';
|
||||||
|
|
||||||
class Import
|
class Import
|
||||||
{
|
{
|
||||||
private $content = '';
|
private $content = '';
|
||||||
|
184
vendor/PicoFeed/Parser.php
vendored
184
vendor/PicoFeed/Parser.php
vendored
@ -7,24 +7,69 @@ require_once __DIR__.'/Filter.php';
|
|||||||
require_once __DIR__.'/Encoding.php';
|
require_once __DIR__.'/Encoding.php';
|
||||||
require_once __DIR__.'/Grabber.php';
|
require_once __DIR__.'/Grabber.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base parser class
|
||||||
|
*
|
||||||
|
* @author Frederic Guillot
|
||||||
|
* @package parser
|
||||||
|
*/
|
||||||
abstract class Parser
|
abstract class Parser
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Hash algorithm used to generate item id, any value supported by PHP, see hash_algos()
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @static
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $hashAlgo = 'crc32b'; // crc32b seems to be faster and shorter than other hash algorithms
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feed content (XML data)
|
||||||
|
*
|
||||||
|
* @access protected
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
protected $content = '';
|
protected $content = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feed properties (values parsed)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
public $id = '';
|
public $id = '';
|
||||||
public $url = '';
|
public $url = '';
|
||||||
public $title = '';
|
public $title = '';
|
||||||
public $updated = '';
|
public $updated = '';
|
||||||
|
public $language = '';
|
||||||
public $items = array();
|
public $items = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content grabber parameters
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
public $grabber = false;
|
public $grabber = false;
|
||||||
public $grabber_ignore_urls = array();
|
public $grabber_ignore_urls = array();
|
||||||
public $grabber_timeout = null;
|
public $grabber_timeout = null;
|
||||||
public $grabber_user_agent = null;
|
public $grabber_user_agent = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse feed content
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
* @access public
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
abstract public function execute();
|
abstract public function execute();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $content Feed content
|
||||||
|
* @param string $http_encoding HTTP encoding (headers)
|
||||||
|
*/
|
||||||
public function __construct($content, $http_encoding = '')
|
public function __construct($content, $http_encoding = '')
|
||||||
{
|
{
|
||||||
$xml_encoding = Filter::getEncodingFromXmlTag($content);
|
$xml_encoding = Filter::getEncodingFromXmlTag($content);
|
||||||
@ -45,7 +90,14 @@ abstract class Parser
|
|||||||
$this->content = $this->normalizeData($this->content);
|
$this->content = $this->normalizeData($this->content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter HTML for entry content
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $item_content Item content
|
||||||
|
* @param string $item_url Item URL
|
||||||
|
* @return string Filtered content
|
||||||
|
*/
|
||||||
public function filterHtml($item_content, $item_url)
|
public function filterHtml($item_content, $item_url)
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
@ -64,7 +116,12 @@ abstract class Parser
|
|||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get XML parser errors
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getXmlErrors()
|
public function getXmlErrors()
|
||||||
{
|
{
|
||||||
$errors = array();
|
$errors = array();
|
||||||
@ -82,8 +139,13 @@ abstract class Parser
|
|||||||
return implode(', ', $errors);
|
return implode(', ', $errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
// Dirty quickfix before XML parsing
|
* Dirty quickfixes before XML parsing
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $data Raw data
|
||||||
|
* @return string Normalized data
|
||||||
|
*/
|
||||||
public function normalizeData($data)
|
public function normalizeData($data)
|
||||||
{
|
{
|
||||||
$data = str_replace("\xc3\x20", '', $data);
|
$data = str_replace("\xc3\x20", '', $data);
|
||||||
@ -91,8 +153,13 @@ abstract class Parser
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each href attribute, replace & by &
|
/**
|
||||||
// Useful for broken XML feeds
|
* Replace & by & for each href attribute (Fix broken feeds)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $content Raw data
|
||||||
|
* @return string Normalized data
|
||||||
|
*/
|
||||||
public function replaceEntityAttribute($content)
|
public function replaceEntityAttribute($content)
|
||||||
{
|
{
|
||||||
$content = preg_replace_callback('/href="[^"]+"/', function(array $matches) {
|
$content = preg_replace_callback('/href="[^"]+"/', function(array $matches) {
|
||||||
@ -102,8 +169,13 @@ abstract class Parser
|
|||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
// Trim whitespace from the begining, the end and inside a string and don't break utf-8 string
|
* Trim whitespace from the begining, the end and inside a string and don't break utf-8 string
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $value Raw data
|
||||||
|
* @return string Normalized data
|
||||||
|
*/
|
||||||
public function stripWhiteSpace($value)
|
public function stripWhiteSpace($value)
|
||||||
{
|
{
|
||||||
$value = str_replace("\r", "", $value);
|
$value = str_replace("\r", "", $value);
|
||||||
@ -112,14 +184,25 @@ abstract class Parser
|
|||||||
return trim($value);
|
return trim($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique id for an entry (hash all arguments)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $args Pieces of data to hash
|
||||||
|
* @return string Id
|
||||||
|
*/
|
||||||
public function generateId()
|
public function generateId()
|
||||||
{
|
{
|
||||||
// crc32b seems to be faster and shorter than other hash algorithms
|
return hash(self::$hashAlgo, implode(func_get_args()));
|
||||||
return hash('crc32b', implode(func_get_args()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to parse all date format for broken feeds
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $value Original date format
|
||||||
|
* @return integer Timestamp
|
||||||
|
*/
|
||||||
public function parseDate($value)
|
public function parseDate($value)
|
||||||
{
|
{
|
||||||
// Format => truncate to this length if not null
|
// Format => truncate to this length if not null
|
||||||
@ -168,7 +251,14 @@ abstract class Parser
|
|||||||
return time();
|
return time();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a valid date from a given format
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $format Date format
|
||||||
|
* @param string $value Original date value
|
||||||
|
* @return integer Timestamp
|
||||||
|
*/
|
||||||
public function getValidDate($format, $value)
|
public function getValidDate($format, $value)
|
||||||
{
|
{
|
||||||
$date = \DateTime::createFromFormat($format, $value);
|
$date = \DateTime::createFromFormat($format, $value);
|
||||||
@ -181,8 +271,13 @@ abstract class Parser
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
// Hardcoded list of hostname/token to exclude from id generation
|
* Hardcoded list of hostname/token to exclude from id generation
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $url URL
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
public function isExcludedFromId($url)
|
public function isExcludedFromId($url)
|
||||||
{
|
{
|
||||||
$exclude_list = array('ap.org', 'jacksonville.com');
|
$exclude_list = array('ap.org', 'jacksonville.com');
|
||||||
@ -193,4 +288,59 @@ abstract class Parser
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get xml:lang value
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $xml XML string
|
||||||
|
* @return string Language
|
||||||
|
*/
|
||||||
|
public function getXmlLang($xml)
|
||||||
|
{
|
||||||
|
$dom = new \DOMDocument;
|
||||||
|
$dom->loadXML($this->content);
|
||||||
|
|
||||||
|
$xpath = new \DOMXPath($dom);
|
||||||
|
return $xpath->evaluate('string(//@xml:lang[1])') ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given language is "Right to Left"
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @access public
|
||||||
|
* @param string $language Language: fr-FR, en-US
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isLanguageRTL($language)
|
||||||
|
{
|
||||||
|
$language = strtolower($language);
|
||||||
|
|
||||||
|
// Arabic (ar-**)
|
||||||
|
if (strpos($language, 'ar') === 0) return true;
|
||||||
|
|
||||||
|
// Farsi (fa-**)
|
||||||
|
if (strpos($language, 'fa') === 0) return true;
|
||||||
|
|
||||||
|
// Urdu (ur-**)
|
||||||
|
if (strpos($language, 'ur') === 0) return true;
|
||||||
|
|
||||||
|
// Pashtu (ps-**)
|
||||||
|
if (strpos($language, 'ps') === 0) return true;
|
||||||
|
|
||||||
|
// Syriac (syr-**)
|
||||||
|
if (strpos($language, 'syr') === 0) return true;
|
||||||
|
|
||||||
|
// Divehi (dv-**)
|
||||||
|
if (strpos($language, 'dv') === 0) return true;
|
||||||
|
|
||||||
|
// Hebrew (he-**)
|
||||||
|
if (strpos($language, 'he') === 0) return true;
|
||||||
|
|
||||||
|
// Yiddish (yi-**)
|
||||||
|
if (strpos($language, 'yi') === 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
30
vendor/PicoFeed/Parsers/Atom.php
vendored
30
vendor/PicoFeed/Parsers/Atom.php
vendored
@ -2,8 +2,20 @@
|
|||||||
|
|
||||||
namespace PicoFeed\Parsers;
|
namespace PicoFeed\Parsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atom parser
|
||||||
|
*
|
||||||
|
* @author Frederic Guillot
|
||||||
|
* @package parser
|
||||||
|
*/
|
||||||
class Atom extends \PicoFeed\Parser
|
class Atom extends \PicoFeed\Parser
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Parse the document
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed Atom instance or false
|
||||||
|
*/
|
||||||
public function execute()
|
public function execute()
|
||||||
{
|
{
|
||||||
\PicoFeed\Logging::log(\get_called_class().': begin parsing');
|
\PicoFeed\Logging::log(\get_called_class().': begin parsing');
|
||||||
@ -17,6 +29,7 @@ class Atom extends \PicoFeed\Parser
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->language = $this->getXmlLang($this->content);
|
||||||
$this->url = $this->getUrl($xml);
|
$this->url = $this->getUrl($xml);
|
||||||
$this->title = $this->stripWhiteSpace((string) $xml->title) ?: $this->url;
|
$this->title = $this->stripWhiteSpace((string) $xml->title) ?: $this->url;
|
||||||
$this->id = (string) $xml->id;
|
$this->id = (string) $xml->id;
|
||||||
@ -41,6 +54,7 @@ class Atom extends \PicoFeed\Parser
|
|||||||
$item->updated = $this->parseDate((string) $entry->updated);
|
$item->updated = $this->parseDate((string) $entry->updated);
|
||||||
$item->author = $author;
|
$item->author = $author;
|
||||||
$item->content = $this->filterHtml($this->getContent($entry), $item->url);
|
$item->content = $this->filterHtml($this->getContent($entry), $item->url);
|
||||||
|
$item->language = $this->language;
|
||||||
|
|
||||||
if (empty($item->title)) $item->title = $item->url;
|
if (empty($item->title)) $item->title = $item->url;
|
||||||
|
|
||||||
@ -65,7 +79,13 @@ class Atom extends \PicoFeed\Parser
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the entry content
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param SimpleXMLElement $entry XML Entry
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getContent($entry)
|
public function getContent($entry)
|
||||||
{
|
{
|
||||||
if (isset($entry->content) && ! empty($entry->content)) {
|
if (isset($entry->content) && ! empty($entry->content)) {
|
||||||
@ -84,7 +104,13 @@ class Atom extends \PicoFeed\Parser
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL from a link tag
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param SimpleXMLElement $xml XML tag
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getUrl($xml)
|
public function getUrl($xml)
|
||||||
{
|
{
|
||||||
foreach ($xml->link as $link) {
|
foreach ($xml->link as $link) {
|
||||||
|
19
vendor/PicoFeed/Parsers/Rss20.php
vendored
19
vendor/PicoFeed/Parsers/Rss20.php
vendored
@ -2,8 +2,20 @@
|
|||||||
|
|
||||||
namespace PicoFeed\Parsers;
|
namespace PicoFeed\Parsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSS 2.0 Parser
|
||||||
|
*
|
||||||
|
* @author Frederic Guillot
|
||||||
|
* @package parser
|
||||||
|
*/
|
||||||
class Rss20 extends \PicoFeed\Parser
|
class Rss20 extends \PicoFeed\Parser
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Parse the document
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed Rss20 instance or false
|
||||||
|
*/
|
||||||
public function execute()
|
public function execute()
|
||||||
{
|
{
|
||||||
\PicoFeed\Logging::log(\get_called_class().': begin parsing');
|
\PicoFeed\Logging::log(\get_called_class().': begin parsing');
|
||||||
@ -26,7 +38,6 @@ class Rss20 extends \PicoFeed\Parser
|
|||||||
$link = (string) $xml_link;
|
$link = (string) $xml_link;
|
||||||
|
|
||||||
if ($link !== '') {
|
if ($link !== '') {
|
||||||
|
|
||||||
$this->url = (string) $link;
|
$this->url = (string) $link;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -37,6 +48,7 @@ class Rss20 extends \PicoFeed\Parser
|
|||||||
$this->url = (string) $xml->channel->link;
|
$this->url = (string) $xml->channel->link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->language = isset($xml->channel->language) ? (string) $xml->channel->language : '';
|
||||||
$this->title = $this->stripWhiteSpace((string) $xml->channel->title) ?: $this->url;
|
$this->title = $this->stripWhiteSpace((string) $xml->channel->title) ?: $this->url;
|
||||||
$this->id = $this->url;
|
$this->id = $this->url;
|
||||||
$this->updated = $this->parseDate(isset($xml->channel->pubDate) ? (string) $xml->channel->pubDate : (string) $xml->channel->lastBuildDate);
|
$this->updated = $this->parseDate(isset($xml->channel->pubDate) ? (string) $xml->channel->pubDate : (string) $xml->channel->lastBuildDate);
|
||||||
@ -60,6 +72,7 @@ class Rss20 extends \PicoFeed\Parser
|
|||||||
$item->content = '';
|
$item->content = '';
|
||||||
$item->enclosure = '';
|
$item->enclosure = '';
|
||||||
$item->enclosure_type = '';
|
$item->enclosure_type = '';
|
||||||
|
$item->language = $this->language;
|
||||||
|
|
||||||
foreach ($namespaces as $name => $url) {
|
foreach ($namespaces as $name => $url) {
|
||||||
|
|
||||||
@ -94,22 +107,18 @@ class Rss20 extends \PicoFeed\Parser
|
|||||||
if (empty($item->author)) {
|
if (empty($item->author)) {
|
||||||
|
|
||||||
if (isset($entry->author)) {
|
if (isset($entry->author)) {
|
||||||
|
|
||||||
$item->author = (string) $entry->author;
|
$item->author = (string) $entry->author;
|
||||||
}
|
}
|
||||||
else if (isset($xml->channel->webMaster)) {
|
else if (isset($xml->channel->webMaster)) {
|
||||||
|
|
||||||
$item->author = (string) $xml->channel->webMaster;
|
$item->author = (string) $xml->channel->webMaster;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($entry->guid) && isset($entry->guid['isPermaLink']) && (string) $entry->guid['isPermaLink'] != 'false') {
|
if (isset($entry->guid) && isset($entry->guid['isPermaLink']) && (string) $entry->guid['isPermaLink'] != 'false') {
|
||||||
|
|
||||||
$id = (string) $entry->guid;
|
$id = (string) $entry->guid;
|
||||||
$item->id = $this->generateId($id !== '' && $id !== $item->url ? $id : $item->url, $this->isExcludedFromId($this->url) ? '' : $this->url);
|
$item->id = $this->generateId($id !== '' && $id !== $item->url ? $id : $item->url, $this->isExcludedFromId($this->url) ? '' : $this->url);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
$item->id = $this->generateId($item->url, $this->isExcludedFromId($this->url) ? '' : $this->url);
|
$item->id = $this->generateId($item->url, $this->isExcludedFromId($this->url) ? '' : $this->url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user