Merge branch 'master' of git://github.com/fguillot/miniflux
Conflicts: miniflux/templates/read_item.php miniflux/templates/unread_items.php
This commit is contained in:
commit
2003f47c4c
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,4 @@
|
|||||||
update*
|
update*
|
||||||
|
make-archive.sh
|
||||||
|
*.sqlite
|
||||||
|
*.db
|
@ -14,26 +14,33 @@ Features
|
|||||||
- Import/Export OPML feeds
|
- Import/Export OPML feeds
|
||||||
- Feeds update by a cronjob or with the user interface in one click
|
- Feeds update by a cronjob or with the user interface in one click
|
||||||
- Protected by a login/password (only one possible user)
|
- Protected by a login/password (only one possible user)
|
||||||
- Use secure headers (only external images are allowed)
|
- Use secure headers (only external images and Youtube/Vimeo videos are allowed)
|
||||||
- Open external links inside a new tab with a `rel="noreferrer"` attribute
|
- Open external links inside a new tab with a `rel="noreferrer"` attribute
|
||||||
- Mobile CSS (responsive design)
|
- Mobile CSS (responsive design)
|
||||||
|
- Keyboard shortcuts
|
||||||
|
|
||||||
Todo
|
Todo and known bugs
|
||||||
----
|
-------------------
|
||||||
|
|
||||||
- Remove older items from the database
|
- See Issues: <https://github.com/fguillot/miniflux/issues>
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
- AGPL: <http://www.gnu.org/licenses/agpl-3.0.txt>
|
- AGPL: <http://www.gnu.org/licenses/agpl-3.0.txt>
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
- Original author: [Frédéric Guillot](http://fredericguillot.com/)
|
||||||
|
- Contributors: [Pull requesters](https://github.com/fguillot/miniflux/pulls?direction=desc&page=1&sort=created&state=closed) and [Bug reporters](https://github.com/fguillot/miniflux/issues?page=1&state=closed)
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
- PHP >= 5.3.7
|
- PHP >= 5.3.7
|
||||||
- PHP XML extensions (SimpleXML, DOM...)
|
- PHP XML extensions (SimpleXML, DOM...)
|
||||||
- PHP Sqlite extensions
|
- PHP Sqlite extension
|
||||||
|
|
||||||
Libraries used
|
Libraries used
|
||||||
--------------
|
--------------
|
||||||
@ -81,6 +88,11 @@ Your life is cluttered.
|
|||||||
|
|
||||||
Miniflux is a minimalist software. Less is more.
|
Miniflux is a minimalist software. Less is more.
|
||||||
|
|
||||||
|
### Why there is no favourites?
|
||||||
|
|
||||||
|
Use the right tool for the right job.
|
||||||
|
Your browser already have bookmarks, if you don't like it there is many online tools for that.
|
||||||
|
|
||||||
### I found a bug, what next?
|
### I found a bug, what next?
|
||||||
|
|
||||||
Report the bug to the [issues tracker](https://github.com/fguillot/miniflux/issues) and I will fix it.
|
Report the bug to the [issues tracker](https://github.com/fguillot/miniflux/issues) and I will fix it.
|
||||||
|
@ -24,13 +24,13 @@ body {
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
color: #3366CC;
|
color: #3366CC;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:focus {
|
a:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
color: red;
|
color: red;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 3px;
|
|
||||||
border: 1px dotted #aaa;
|
border: 1px dotted #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +63,12 @@ blockquote {
|
|||||||
font-family: Georgia, serif;
|
font-family: Georgia, serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
q {
|
||||||
|
color: purple;
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
blockquote + p {
|
blockquote + p {
|
||||||
color: #555;
|
color: #555;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@ -127,8 +133,6 @@ form {
|
|||||||
label {
|
label {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
|
||||||
width: 10em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@ -144,7 +148,8 @@ input[type="text"] {
|
|||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
font-size: 99%;
|
font-size: 99%;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="email"]:focus,
|
input[type="email"]:focus,
|
||||||
@ -196,14 +201,14 @@ textarea.form-error {
|
|||||||
|
|
||||||
.form-errors {
|
.form-errors {
|
||||||
color: #b94a48;
|
color: #b94a48;
|
||||||
margin-left: 10em;
|
margin-left: 15px;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-help {
|
.form-help {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: brown;
|
color: brown;
|
||||||
display: inline;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* alerts */
|
/* alerts */
|
||||||
@ -234,6 +239,12 @@ textarea.form-error {
|
|||||||
border-color: #bce8f1;
|
border-color: #bce8f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-normal {
|
||||||
|
color: #333;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* buttons */
|
/* buttons */
|
||||||
.btn {
|
.btn {
|
||||||
@ -340,8 +351,7 @@ nav .active a {
|
|||||||
.page-header li {
|
.page-header li {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
display: inline;
|
display: inline;
|
||||||
padding-left: 10px;
|
padding-right: 5px;
|
||||||
padding-right: 10px;
|
|
||||||
border-right: 1px dotted #ccc;
|
border-right: 1px dotted #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,17 +399,25 @@ nav .active a {
|
|||||||
font-family: Georgia, serif;
|
font-family: Georgia, serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.items #current-item {
|
||||||
|
border: 2px dashed #d14;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* item */
|
/* item */
|
||||||
.item {
|
.item {
|
||||||
color: #333;
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
padding-bottom: 50px;
|
padding-bottom: 50px;
|
||||||
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item p,
|
.item p,
|
||||||
.item li {
|
.item li {
|
||||||
font-family: Georgia, serif;
|
font-family: Georgia, serif;
|
||||||
line-height: 1.6em;
|
line-height: 1.6em;
|
||||||
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item h2,
|
.item h2,
|
||||||
@ -443,6 +461,44 @@ nav .active a {
|
|||||||
color: purple;
|
color: purple;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item nav span,
|
||||||
|
.item nav a,
|
||||||
|
.item nav a:visited {
|
||||||
|
color: #3366CC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item nav {
|
||||||
|
border-top: 1px dotted #ddd;
|
||||||
|
padding-top: 8px;
|
||||||
|
margin-top: 50px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-left {
|
||||||
|
width: 30%;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-middle {
|
||||||
|
text-align: center;
|
||||||
|
float: left;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-right {
|
||||||
|
text-align: right;
|
||||||
|
margin-left: 70%;
|
||||||
|
width: 30%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* other pages */
|
||||||
|
section li {
|
||||||
|
margin-left: 15px;
|
||||||
|
list-style-type: square;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* mobile design */
|
/* mobile design */
|
||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
@ -460,6 +516,7 @@ nav .active a {
|
|||||||
.logo {
|
.logo {
|
||||||
display: block;
|
display: block;
|
||||||
float: none;
|
float: none;
|
||||||
|
border-bottom: 1px dotted #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
@ -479,11 +536,19 @@ nav .active a {
|
|||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
clear: both;
|
clear: both;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page li {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
BIN
miniflux/assets/img/favicon.png
Normal file
BIN
miniflux/assets/img/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 847 B |
BIN
miniflux/assets/img/touch-icon-ipad-retina.png
Normal file
BIN
miniflux/assets/img/touch-icon-ipad-retina.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
miniflux/assets/img/touch-icon-ipad.png
Normal file
BIN
miniflux/assets/img/touch-icon-ipad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
miniflux/assets/img/touch-icon-iphone-retina.png
Normal file
BIN
miniflux/assets/img/touch-icon-iphone-retina.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
miniflux/assets/img/touch-icon-iphone.png
Normal file
BIN
miniflux/assets/img/touch-icon-iphone.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 947 B |
@ -5,21 +5,48 @@
|
|||||||
var queue_length = 5;
|
var queue_length = 5;
|
||||||
|
|
||||||
|
|
||||||
|
function switch_status(item_id)
|
||||||
|
{
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
|
request.onreadystatechange = function() {
|
||||||
|
|
||||||
|
if (request.readyState === 4 && is_listing()) {
|
||||||
|
|
||||||
|
var response = JSON.parse(request.responseText);
|
||||||
|
|
||||||
|
if (response.status == "read" || response.status == "unread") {
|
||||||
|
|
||||||
|
find_next_item();
|
||||||
|
remove_item(response.item_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.open("POST", "?action=change-item-status&id=" + item_id, true);
|
||||||
|
request.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function mark_as_read(item_id)
|
function mark_as_read(item_id)
|
||||||
{
|
{
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
request.onload = function() {
|
request.onload = function() {
|
||||||
|
|
||||||
var article = document.getElementById("item-" + item_id);
|
remove_item(item_id);
|
||||||
|
|
||||||
if (article) {
|
|
||||||
|
|
||||||
article.style.display = "none";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
request.open("POST", "?action=read&id=" + item_id, true);
|
request.open("POST", "?action=mark-item-read&id=" + item_id, true);
|
||||||
|
request.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function mark_as_unread(item_id)
|
||||||
|
{
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
|
request.open("POST", "?action=mark-item-unread&id=" + item_id, true);
|
||||||
request.send();
|
request.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +87,9 @@
|
|||||||
|
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
request.onload = function() {
|
request.onreadystatechange = function() {
|
||||||
|
|
||||||
|
if (request.readyState === 4) {
|
||||||
|
|
||||||
hide_refresh_icon(feed_id);
|
hide_refresh_icon(feed_id);
|
||||||
|
|
||||||
@ -79,9 +108,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {}
|
catch (e) {}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.open("GET", "?action=ajax-refresh-feed&feed_id=" + feed_id, true);
|
request.open("POST", "?action=refresh-feed&feed_id=" + feed_id, true);
|
||||||
request.send();
|
request.send();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -137,6 +167,161 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function remove_item(item_id)
|
||||||
|
{
|
||||||
|
var item = document.getElementById("item-" + item_id);
|
||||||
|
if (item) item.parentNode.removeChild(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function open_original_item()
|
||||||
|
{
|
||||||
|
var link = document.getElementById("original-item");
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
|
||||||
|
if (link.getAttribute("data-action") == "mark-read") {
|
||||||
|
|
||||||
|
mark_as_read(link.getAttribute("data-item-id"));
|
||||||
|
find_next_item();
|
||||||
|
}
|
||||||
|
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function open_item()
|
||||||
|
{
|
||||||
|
var link = document.getElementById("open-item");
|
||||||
|
if (link) link.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function open_next_item()
|
||||||
|
{
|
||||||
|
var link = document.getElementById("next-item");
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
else if (is_listing()) {
|
||||||
|
|
||||||
|
find_next_item();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function open_previous_item()
|
||||||
|
{
|
||||||
|
var link = document.getElementById("previous-item");
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
else if (is_listing()) {
|
||||||
|
|
||||||
|
find_previous_item();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function change_item_status()
|
||||||
|
{
|
||||||
|
var item = document.getElementById("current-item");
|
||||||
|
if (item) switch_status(item.getAttribute("data-item-id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function set_links_item(item_id)
|
||||||
|
{
|
||||||
|
var link = document.getElementById("original-item");
|
||||||
|
if (link) link.id = "original-" + link.getAttribute("data-item-id");
|
||||||
|
|
||||||
|
var link = document.getElementById("open-item");
|
||||||
|
if (link) link.id = "open-" + link.getAttribute("data-item-id");
|
||||||
|
|
||||||
|
var link = document.getElementById("original-" + item_id);
|
||||||
|
if (link) link.id = "original-item";
|
||||||
|
|
||||||
|
var link = document.getElementById("open-" + item_id);
|
||||||
|
if (link) link.id = "open-item";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function find_next_item()
|
||||||
|
{
|
||||||
|
var items = document.getElementsByTagName("article");
|
||||||
|
|
||||||
|
if (! document.getElementById("current-item")) {
|
||||||
|
|
||||||
|
items[0].id = "current-item";
|
||||||
|
set_links_item(items[0].getAttribute("data-item-id"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
for (var i = 0, ilen = items.length; i < ilen; i++) {
|
||||||
|
|
||||||
|
if (items[i].id == "current-item") {
|
||||||
|
|
||||||
|
items[i].id = "item-" + items[i].getAttribute("data-item-id");
|
||||||
|
|
||||||
|
if (i + 1 < ilen) {
|
||||||
|
|
||||||
|
items[i + 1].id = "current-item";
|
||||||
|
set_links_item(items[i + 1].getAttribute("data-item-id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function find_previous_item()
|
||||||
|
{
|
||||||
|
var items = document.getElementsByTagName("article");
|
||||||
|
|
||||||
|
if (! document.getElementById("current-item")) {
|
||||||
|
|
||||||
|
items[items.length - 1].id = "current-item";
|
||||||
|
set_links_item(items[items.length - 1].getAttribute("data-item-id"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
for (var i = items.length - 1; i >= 0; i--) {
|
||||||
|
|
||||||
|
if (items[i].id == "current-item") {
|
||||||
|
|
||||||
|
items[i].id = "item-" + items[i].getAttribute("data-item-id");
|
||||||
|
|
||||||
|
if (i - 1 >= 0) {
|
||||||
|
|
||||||
|
items[i - 1].id = "current-item";
|
||||||
|
set_links_item(items[i - 1].getAttribute("data-item-id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function is_listing()
|
||||||
|
{
|
||||||
|
if (document.getElementById("listing")) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
document.onclick = function(e) {
|
document.onclick = function(e) {
|
||||||
|
|
||||||
var action = e.target.getAttribute("data-action");
|
var action = e.target.getAttribute("data-action");
|
||||||
@ -161,4 +346,25 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
document.onkeypress = function(e) {
|
||||||
|
|
||||||
|
switch (e.keyCode || e.which) {
|
||||||
|
case 112:
|
||||||
|
open_previous_item();
|
||||||
|
break;
|
||||||
|
case 110:
|
||||||
|
open_next_item();
|
||||||
|
break;
|
||||||
|
case 118:
|
||||||
|
open_original_item();
|
||||||
|
break;
|
||||||
|
case 111:
|
||||||
|
open_item();
|
||||||
|
break;
|
||||||
|
case 109:
|
||||||
|
change_item_status();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
})();
|
})();
|
@ -3,20 +3,33 @@
|
|||||||
require 'check_setup.php';
|
require 'check_setup.php';
|
||||||
require 'vendor/password.php';
|
require 'vendor/password.php';
|
||||||
require 'vendor/PicoTools/Dependency_Injection.php';
|
require 'vendor/PicoTools/Dependency_Injection.php';
|
||||||
|
require 'vendor/PicoTools/Translator.php';
|
||||||
require 'vendor/PicoDb/Database.php';
|
require 'vendor/PicoDb/Database.php';
|
||||||
require 'vendor/PicoDb/Table.php';
|
require 'vendor/PicoDb/Table.php';
|
||||||
require 'schema.php';
|
require 'schema.php';
|
||||||
require 'model.php';
|
require 'model.php';
|
||||||
|
|
||||||
|
|
||||||
|
const DB_VERSION = 4;
|
||||||
|
const APP_VERSION = 'master';
|
||||||
|
const APP_USERAGENT = 'Miniflux - http://miniflux.net';
|
||||||
|
const HTTP_TIMEOUT = 5;
|
||||||
|
|
||||||
|
|
||||||
|
function get_db_filename()
|
||||||
|
{
|
||||||
|
return 'data/db.sqlite';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
PicoTools\container('db', function() {
|
PicoTools\container('db', function() {
|
||||||
|
|
||||||
$db = new PicoDb\Database(array(
|
$db = new PicoDb\Database(array(
|
||||||
'driver' => 'sqlite',
|
'driver' => 'sqlite',
|
||||||
'filename' => 'data/db.sqlite'
|
'filename' => get_db_filename()
|
||||||
));
|
));
|
||||||
|
|
||||||
if ($db->schema()->check(1)) {
|
if ($db->schema()->check(DB_VERSION)) {
|
||||||
|
|
||||||
return $db;
|
return $db;
|
||||||
}
|
}
|
||||||
|
BIN
miniflux/favicon.ico
Normal file
BIN
miniflux/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
@ -22,11 +22,30 @@ Router\before(function($action) {
|
|||||||
|
|
||||||
if ($action !== 'login' && ! isset($_SESSION['user'])) {
|
if ($action !== 'login' && ! isset($_SESSION['user'])) {
|
||||||
|
|
||||||
PicoFarad\Response\redirect('?action=login');
|
Response\redirect('?action=login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$language = 'en_US';
|
||||||
|
|
||||||
|
if (isset($_SESSION['user']['language'])) {
|
||||||
|
|
||||||
|
$language = $_SESSION['user']['language'];
|
||||||
|
}
|
||||||
|
else if (isset($_COOKIE['language'])) {
|
||||||
|
|
||||||
|
$language = $_COOKIE['language'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($language !== 'en_US') {
|
||||||
|
|
||||||
|
PicoTools\Translator\load($language);
|
||||||
|
}
|
||||||
|
|
||||||
|
setcookie('language', $language, time()+365*24*3600, dirname($_SERVER['PHP_SELF']));
|
||||||
|
|
||||||
Response\csp(array(
|
Response\csp(array(
|
||||||
'img-src' => '*'
|
'img-src' => '*',
|
||||||
|
'frame-src' => 'http://www.youtube.com https://www.youtube.com http://player.vimeo.com https://player.vimeo.com'
|
||||||
));
|
));
|
||||||
|
|
||||||
Response\xframe();
|
Response\xframe();
|
||||||
@ -74,31 +93,85 @@ Router\post_action('login', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Router\get_action('read', function() {
|
Router\get_action('show', function() {
|
||||||
|
|
||||||
$id = Request\param('id');
|
$id = Request\param('id');
|
||||||
|
|
||||||
Model\set_item_read($id);
|
|
||||||
|
|
||||||
Response\html(Template\layout('read_item', array(
|
Response\html(Template\layout('read_item', array(
|
||||||
'item' => Model\get_item($id)
|
'item' => Model\get_item($id)
|
||||||
)));
|
)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Router\post_action('read', function() {
|
Router\get_action('read', function() {
|
||||||
|
|
||||||
$id = Request\param('id');
|
$id = Request\param('id');
|
||||||
|
$item = Model\get_item($id);
|
||||||
|
$nav = Model\get_nav_item($item); // must be placed before set_item_read()
|
||||||
|
|
||||||
Model\set_item_read($id);
|
Model\set_item_read($id);
|
||||||
|
|
||||||
|
Response\html(Template\layout('read_item', array(
|
||||||
|
'item' => $item,
|
||||||
|
'item_nav' => $nav
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Router\get_action('mark-item-read', function() {
|
||||||
|
|
||||||
|
$id = Request\param('id');
|
||||||
|
Model\set_item_read($id);
|
||||||
|
Response\Redirect('?action=default');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Router\get_action('mark-item-unread', function() {
|
||||||
|
|
||||||
|
$id = Request\param('id');
|
||||||
|
Model\set_item_unread($id);
|
||||||
|
Response\Redirect('?action=history');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Router\get_action('mark-item-removed', function() {
|
||||||
|
|
||||||
|
$id = Request\param('id');
|
||||||
|
Model\set_item_removed($id);
|
||||||
|
Response\Redirect('?action=history');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Router\post_action('mark-item-read', function() {
|
||||||
|
|
||||||
|
$id = Request\param('id');
|
||||||
|
Model\set_item_read($id);
|
||||||
Response\json(array('Ok'));
|
Response\json(array('Ok'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Router\post_action('mark-item-unread', function() {
|
||||||
|
|
||||||
|
$id = Request\param('id');
|
||||||
|
Model\set_item_unread($id);
|
||||||
|
Response\json(array('Ok'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Router\post_action('change-item-status', function() {
|
||||||
|
|
||||||
|
$id = Request\param('id');
|
||||||
|
|
||||||
|
Response\json(array(
|
||||||
|
'item_id' => urlencode($id),
|
||||||
|
'status' => Model\switch_item_status($id)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Router\get_action('history', function() {
|
Router\get_action('history', function() {
|
||||||
|
|
||||||
Response\html(Template\layout('read_items', array(
|
Response\html(Template\layout('history', array(
|
||||||
'items' => Model\get_read_items(),
|
'items' => Model\get_read_items(),
|
||||||
'menu' => 'history'
|
'menu' => 'history'
|
||||||
)));
|
)));
|
||||||
@ -122,11 +195,11 @@ Router\get_action('remove', function() {
|
|||||||
|
|
||||||
if ($id && Model\remove_feed($id)) {
|
if ($id && Model\remove_feed($id)) {
|
||||||
|
|
||||||
Session\flash('This subscription has been removed successfully');
|
Session\flash(t('This subscription has been removed successfully.'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
Session\flash_error('Unable to remove this subscription');
|
Session\flash_error(t('Unable to remove this subscription.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Response\redirect('?action=feeds');
|
Response\redirect('?action=feeds');
|
||||||
@ -146,7 +219,7 @@ Router\get_action('refresh-feed', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Router\get_action('ajax-refresh-feed', function() {
|
Router\post_action('refresh-feed', function() {
|
||||||
|
|
||||||
$id = Request\int_param('feed_id');
|
$id = Request\int_param('feed_id');
|
||||||
|
|
||||||
@ -159,13 +232,21 @@ Router\get_action('ajax-refresh-feed', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Router\get_action('flush-unread', function() {
|
Router\get_action('mark-as-read', function() {
|
||||||
|
|
||||||
Model\flush_unread();
|
Model\mark_as_read();
|
||||||
Response\redirect('?action=unread');
|
Response\redirect('?action=unread');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Router\get_action('confirm-flush-history', function() {
|
||||||
|
|
||||||
|
Response\html(Template\layout('confirm_flush', array(
|
||||||
|
'menu' => 'history'
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Router\get_action('flush-history', function() {
|
Router\get_action('flush-history', function() {
|
||||||
|
|
||||||
Model\flush_read();
|
Model\flush_read();
|
||||||
@ -176,7 +257,7 @@ Router\get_action('flush-history', function() {
|
|||||||
Router\get_action('refresh-all', function() {
|
Router\get_action('refresh-all', function() {
|
||||||
|
|
||||||
Model\update_feeds();
|
Model\update_feeds();
|
||||||
Session\flash('Your subscriptions are updated');
|
Session\flash(t('Your subscriptions are updated'));
|
||||||
Response\redirect('?action=unread');
|
Response\redirect('?action=unread');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -205,26 +286,32 @@ Router\post_action('add', function() {
|
|||||||
|
|
||||||
if (Model\import_feed($_POST['url'])) {
|
if (Model\import_feed($_POST['url'])) {
|
||||||
|
|
||||||
Session\flash('Subscription added successfully.');
|
Session\flash(t('Subscription added successfully.'));
|
||||||
Response\redirect('?action=feeds');
|
Response\redirect('?action=feeds');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
Session\flash_error('Unable to find a subscription.');
|
Session\flash_error(t('Unable to find a subscription.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Response\html(Template\layout('add', array(
|
Response\html(Template\layout('add', array(
|
||||||
'values' => array('url' => $_POST['url']),
|
'values' => array('url' => $_POST['url']),
|
||||||
'errors' => array('url' => 'Unable to find a news feed.'),
|
|
||||||
'menu' => 'feeds'
|
'menu' => 'feeds'
|
||||||
)));
|
)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Router\get_action('optimize-db', function() {
|
||||||
|
|
||||||
|
\PicoTools\singleton('db')->getConnection()->exec('VACUUM');
|
||||||
|
Response\redirect('?action=config');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Router\get_action('download-db', function() {
|
Router\get_action('download-db', function() {
|
||||||
|
|
||||||
Response\force_download('db.sqlite.gz');
|
Response\force_download('db.sqlite.gz');
|
||||||
Response\binary(gzencode(file_get_contents('data/db.sqlite')));
|
Response\binary(gzencode(file_get_contents(get_db_filename())));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -248,14 +335,14 @@ Router\post_action('import', function() {
|
|||||||
|
|
||||||
if (Model\import_feeds(Request\file_content('file'))) {
|
if (Model\import_feeds(Request\file_content('file'))) {
|
||||||
|
|
||||||
Session\flash('Your feeds are imported.');
|
Session\flash(t('Your feeds have been imported.'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
Session\flash_error('Unable to import your OPML file.');
|
Session\flash_error(t('Unable to import your OPML file.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Response\redirect('?action=feeds');
|
Response\redirect('?action=import');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -264,6 +351,8 @@ Router\get_action('config', function() {
|
|||||||
Response\html(Template\layout('config', array(
|
Response\html(Template\layout('config', array(
|
||||||
'errors' => array(),
|
'errors' => array(),
|
||||||
'values' => Model\get_config(),
|
'values' => Model\get_config(),
|
||||||
|
'db_size' => filesize(get_db_filename()),
|
||||||
|
'languages' => Model\get_languages(),
|
||||||
'menu' => 'config'
|
'menu' => 'config'
|
||||||
)));
|
)));
|
||||||
});
|
});
|
||||||
@ -278,11 +367,11 @@ Router\post_action('config', function() {
|
|||||||
|
|
||||||
if (Model\save_config($values)) {
|
if (Model\save_config($values)) {
|
||||||
|
|
||||||
Session\flash('Your preferences are updated.');
|
Session\flash(t('Your preferences are updated.'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
Session\flash_error('Unable to update your preferences.');
|
Session\flash_error(t('Unable to update your preferences.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Response\redirect('?action=config');
|
Response\redirect('?action=config');
|
||||||
@ -291,6 +380,8 @@ Router\post_action('config', function() {
|
|||||||
Response\html(Template\layout('config', array(
|
Response\html(Template\layout('config', array(
|
||||||
'errors' => $errors,
|
'errors' => $errors,
|
||||||
'values' => $values,
|
'values' => $values,
|
||||||
|
'db_size' => filesize(get_db_filename()),
|
||||||
|
'languages' => Model\get_languages(),
|
||||||
'menu' => 'config'
|
'menu' => 'config'
|
||||||
)));
|
)));
|
||||||
});
|
});
|
||||||
|
87
miniflux/locales/fr_FR/translations.php
Normal file
87
miniflux/locales/fr_FR/translations.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'French' => 'Français',
|
||||||
|
'English' => 'Anglais',
|
||||||
|
'unread' => 'non lus',
|
||||||
|
'history' => 'historique',
|
||||||
|
'subscriptions' => 'abonnements',
|
||||||
|
'Subscriptions' => 'Abonnements',
|
||||||
|
'preferences' => 'préférences',
|
||||||
|
'Preferences' => 'Préférences',
|
||||||
|
'logout' => 'déconnexion',
|
||||||
|
'Username' => 'Utilisateur',
|
||||||
|
'Password' => 'Mot de passe',
|
||||||
|
'Confirmation' => 'Confirmation',
|
||||||
|
'Language' => 'Langue',
|
||||||
|
'Update' => 'Mettre à jour',
|
||||||
|
'More informations' => 'Plus d\'informations',
|
||||||
|
'Database' => 'Base de données',
|
||||||
|
'Database size:' => 'Taille de la base de données :',
|
||||||
|
'Optimize the database' => 'Optimiser la base de données',
|
||||||
|
'(VACUUM command)' => '(commande SQL VACUUM)',
|
||||||
|
'Download the entire database' => 'Télécharger la base de données complète',
|
||||||
|
'(Gzip compressed Sqlite file)' => '(Fichier Sqlite compressé en Gzip)',
|
||||||
|
'Keyboard shortcuts' => 'Raccourcis clavier',
|
||||||
|
'Previous item' => 'Élément précédent',
|
||||||
|
'Next item' => 'Élément suivant',
|
||||||
|
'Mark as read or unread' => 'Marquer comme lu ou non lu',
|
||||||
|
'Open original link' => 'Ouvrir le lien original',
|
||||||
|
'Open item' => 'Ouvrir un élément',
|
||||||
|
'About' => 'A propos',
|
||||||
|
'Miniflux version:' => 'Version de Miniflux :',
|
||||||
|
'Nothing to read' => 'Rien à lire',
|
||||||
|
'Unread items' => 'Éléments non lus',
|
||||||
|
'mark all as read' => 'tout marquer comme lu',
|
||||||
|
'original link' => 'lien original',
|
||||||
|
'mark as read' => 'marquer comme lu',
|
||||||
|
'No history' => 'Aucun historique',
|
||||||
|
'mark as unread' => 'marquer comme non lu',
|
||||||
|
'History' => 'Historique',
|
||||||
|
'flush these items' => 'supprimer ces éléments',
|
||||||
|
'Item not found' => 'Élément introuvable',
|
||||||
|
'Unread items' => 'Éléments non lus',
|
||||||
|
'Next' => 'Suivant',
|
||||||
|
'Previous' => 'Précédent',
|
||||||
|
'Sign in' => 'Connexion',
|
||||||
|
'feeds' => 'abonnements',
|
||||||
|
'add' => 'ajouter',
|
||||||
|
'import' => 'importer',
|
||||||
|
'export' => 'exporter',
|
||||||
|
'OPML Import' => 'Importation OPML',
|
||||||
|
'OPML file' => 'Fichier OPML',
|
||||||
|
'Import' => 'Importer',
|
||||||
|
'refresh all' => 'actualiser',
|
||||||
|
'No subscription' => 'Aucun abonnement',
|
||||||
|
'remove' => 'supprimer',
|
||||||
|
'refresh' => 'actualiser',
|
||||||
|
'feed link' => 'lien du flux',
|
||||||
|
'New subscription' => 'Nouvel abonnement',
|
||||||
|
'Website or Feed URL' => 'URL du site ou du flux',
|
||||||
|
'Add' => 'Ajouter',
|
||||||
|
'http://website/' => 'http://siteweb/',
|
||||||
|
'Yes' => 'Oui',
|
||||||
|
'cancel' => 'annuler',
|
||||||
|
'or' => 'ou',
|
||||||
|
'Official website:' => 'Site officiel :',
|
||||||
|
'Bad username or password' => 'Mauvais utilisateur ou mot de passe',
|
||||||
|
'Unable to update your preferences.' => 'Impossible de mettre à jour vos préférences.',
|
||||||
|
'Your preferences are updated.' => 'Vos préférences ont été mises à jour.',
|
||||||
|
'Unable to import your OPML file.' => 'Impossible d\'importer votre fichier OPML',
|
||||||
|
'Your feeds have been imported.' => 'Vos abonnements ont été importés avec succès.',
|
||||||
|
'Unable to find a subscription.' => 'Impossible de trouver un abonnement.',
|
||||||
|
'Subscription added successfully.' => 'Abonnement ajouté avec succès.',
|
||||||
|
'Your subscriptions are updated' => 'Vos abonnements ont été mis à jour',
|
||||||
|
'Unable to remove this subscription.' => 'Impossible de supprimer cet abonnement.',
|
||||||
|
'This subscription has been removed successfully.' => 'L\'abonnement a été supprimé avec succès.',
|
||||||
|
'The user name is required' => 'Le nom d\'utilisateur est obligatoire',
|
||||||
|
'The maximum length is 50 characters' => 'La longueur maximale est de 50 caractères',
|
||||||
|
'The password is required' => 'Le mot de passe est obligatoire',
|
||||||
|
'The minimum length is 6 characters' => 'La longueur minimale est de 6 caractères',
|
||||||
|
'The confirmation is required' => 'La confirmation est obligatoire',
|
||||||
|
'Passwords doesn\'t match' => 'Les mots de passe ne sont pas identique',
|
||||||
|
'Do you really want to remove these items from your history?' => 'Voulez-vous vraiment supprimer les éléments de votre historique ?',
|
||||||
|
'Do you really want to remove this subscription: "%s"?' => 'Voulez-vous vraiment supprimer cet abonnement : "%s" ?',
|
||||||
|
'Nothing to read, do you want to <a href="?action=refresh-all" data-action="refresh-all">update your subscriptions?</a>' =>
|
||||||
|
'Il n\'y a rien à lire, voulez-vous <a href="?action=refresh-all" data-action="refresh-all">mettre à jour vos abonnements ?</a>'
|
||||||
|
);
|
@ -4,7 +4,6 @@ namespace Model;
|
|||||||
|
|
||||||
require_once 'vendor/PicoFeed/Export.php';
|
require_once 'vendor/PicoFeed/Export.php';
|
||||||
require_once 'vendor/PicoFeed/Import.php';
|
require_once 'vendor/PicoFeed/Import.php';
|
||||||
require_once 'vendor/PicoFeed/Parser.php';
|
|
||||||
require_once 'vendor/PicoFeed/Reader.php';
|
require_once 'vendor/PicoFeed/Reader.php';
|
||||||
require_once 'vendor/SimpleValidator/Validator.php';
|
require_once 'vendor/SimpleValidator/Validator.php';
|
||||||
require_once 'vendor/SimpleValidator/Base.php';
|
require_once 'vendor/SimpleValidator/Base.php';
|
||||||
@ -22,6 +21,15 @@ use PicoFeed\Reader;
|
|||||||
use PicoFeed\Export;
|
use PicoFeed\Export;
|
||||||
|
|
||||||
|
|
||||||
|
function get_languages()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'en_US' => t('English'),
|
||||||
|
'fr_FR' => t('French')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function export_feeds()
|
function export_feeds()
|
||||||
{
|
{
|
||||||
$opml = new Export(get_feeds());
|
$opml = new Export(get_feeds());
|
||||||
@ -64,7 +72,7 @@ function import_feeds($content)
|
|||||||
function import_feed($url)
|
function import_feed($url)
|
||||||
{
|
{
|
||||||
$reader = new Reader;
|
$reader = new Reader;
|
||||||
$reader->download($url);
|
$resource = $reader->download($url, '', '', HTTP_TIMEOUT, APP_USERAGENT);
|
||||||
|
|
||||||
$parser = $reader->getParser();
|
$parser = $reader->getParser();
|
||||||
|
|
||||||
@ -72,10 +80,14 @@ function import_feed($url)
|
|||||||
|
|
||||||
$feed = $parser->execute();
|
$feed = $parser->execute();
|
||||||
|
|
||||||
|
if ($feed === false) return false;
|
||||||
|
if (! $feed->title || ! $feed->url) return false;
|
||||||
|
|
||||||
$db = \PicoTools\singleton('db');
|
$db = \PicoTools\singleton('db');
|
||||||
|
|
||||||
if (! $db->table('feeds')->eq('feed_url', $reader->getUrl())->count()) {
|
if (! $db->table('feeds')->eq('feed_url', $reader->getUrl())->count()) {
|
||||||
|
|
||||||
|
// Etag and LastModified are added the next update
|
||||||
$rs = $db->table('feeds')->save(array(
|
$rs = $db->table('feeds')->save(array(
|
||||||
'title' => $feed->title,
|
'title' => $feed->title,
|
||||||
'site_url' => $feed->url,
|
'site_url' => $feed->url,
|
||||||
@ -96,6 +108,65 @@ function import_feed($url)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function update_feeds()
|
||||||
|
{
|
||||||
|
foreach (get_feeds_id() as $feed_id) {
|
||||||
|
|
||||||
|
update_feed($feed_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-vacuum for people using the cronjob
|
||||||
|
\PicoTools\singleton('db')->getConnection()->exec('VACUUM');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function update_feed($feed_id)
|
||||||
|
{
|
||||||
|
$feed = get_feed($feed_id);
|
||||||
|
|
||||||
|
$reader = new Reader;
|
||||||
|
|
||||||
|
$resource = $reader->download(
|
||||||
|
$feed['feed_url'],
|
||||||
|
$feed['last_modified'],
|
||||||
|
$feed['etag'],
|
||||||
|
HTTP_TIMEOUT,
|
||||||
|
APP_USERAGENT
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! $resource->isModified()) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parser = $reader->getParser();
|
||||||
|
|
||||||
|
if ($parser !== false) {
|
||||||
|
|
||||||
|
$feed = $parser->execute();
|
||||||
|
|
||||||
|
if ($feed !== false) {
|
||||||
|
|
||||||
|
update_feed_cache_infos($feed_id, $resource->getLastModified(), $resource->getEtag());
|
||||||
|
update_items($feed_id, $feed->items);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function get_feeds_id()
|
||||||
|
{
|
||||||
|
return \PicoTools\singleton('db')
|
||||||
|
->table('feeds')
|
||||||
|
->asc('updated')
|
||||||
|
->listing('id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function get_feeds()
|
function get_feeds()
|
||||||
{
|
{
|
||||||
return \PicoTools\singleton('db')
|
return \PicoTools\singleton('db')
|
||||||
@ -114,10 +185,21 @@ function get_feed($feed_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function update_feed_cache_infos($feed_id, $last_modified, $etag)
|
||||||
|
{
|
||||||
|
\PicoTools\singleton('db')
|
||||||
|
->table('feeds')
|
||||||
|
->eq('id', $feed_id)
|
||||||
|
->save(array(
|
||||||
|
'last_modified' => $last_modified,
|
||||||
|
'etag' => $etag
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function remove_feed($feed_id)
|
function remove_feed($feed_id)
|
||||||
{
|
{
|
||||||
$db = \PicoTools\singleton('db');
|
$db = \PicoTools\singleton('db');
|
||||||
|
|
||||||
$db->table('items')->eq('feed_id', $feed_id)->remove();
|
$db->table('items')->eq('feed_id', $feed_id)->remove();
|
||||||
|
|
||||||
return $db->table('feeds')->eq('id', $feed_id)->remove();
|
return $db->table('feeds')->eq('id', $feed_id)->remove();
|
||||||
@ -157,6 +239,44 @@ function get_item($id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function get_nav_item($item)
|
||||||
|
{
|
||||||
|
$unread_items = \PicoTools\singleton('db')
|
||||||
|
->table('items')
|
||||||
|
->columns('items.id')
|
||||||
|
->eq('status', 'unread')
|
||||||
|
->desc('updated')
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
$next_item = null;
|
||||||
|
$previous_item = null;
|
||||||
|
|
||||||
|
for ($i = 0, $ilen = count($unread_items); $i < $ilen; $i++) {
|
||||||
|
|
||||||
|
if ($unread_items[$i]['id'] == $item['id']) {
|
||||||
|
|
||||||
|
if ($i > 0) $previous_item = $unread_items[$i - 1];
|
||||||
|
if ($i < ($ilen - 1)) $next_item = $unread_items[$i + 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'next' => $next_item,
|
||||||
|
'previous' => $previous_item
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function set_item_removed($id)
|
||||||
|
{
|
||||||
|
\PicoTools\singleton('db')
|
||||||
|
->table('items')
|
||||||
|
->eq('id', $id)
|
||||||
|
->save(array('status' => 'removed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function set_item_read($id)
|
function set_item_read($id)
|
||||||
{
|
{
|
||||||
\PicoTools\singleton('db')
|
\PicoTools\singleton('db')
|
||||||
@ -166,6 +286,55 @@ function set_item_read($id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function set_item_unread($id)
|
||||||
|
{
|
||||||
|
\PicoTools\singleton('db')
|
||||||
|
->table('items')
|
||||||
|
->eq('id', $id)
|
||||||
|
->save(array('status' => 'unread'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function switch_item_status($id)
|
||||||
|
{
|
||||||
|
$item = \PicoTools\singleton('db')
|
||||||
|
->table('items')
|
||||||
|
->columns('status')
|
||||||
|
->eq('id', $id)
|
||||||
|
->findOne();
|
||||||
|
|
||||||
|
if ($item['status'] == 'unread') {
|
||||||
|
|
||||||
|
\PicoTools\singleton('db')
|
||||||
|
->table('items')
|
||||||
|
->eq('id', $id)
|
||||||
|
->save(array('status' => 'read'));
|
||||||
|
|
||||||
|
return 'read';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
\PicoTools\singleton('db')
|
||||||
|
->table('items')
|
||||||
|
->eq('id', $id)
|
||||||
|
->save(array('status' => 'unread'));
|
||||||
|
|
||||||
|
return 'unread';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function mark_as_read()
|
||||||
|
{
|
||||||
|
\PicoTools\singleton('db')
|
||||||
|
->table('items')
|
||||||
|
->eq('status', 'unread')
|
||||||
|
->save(array('status' => 'read'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function flush_unread()
|
function flush_unread()
|
||||||
{
|
{
|
||||||
\PicoTools\singleton('db')
|
\PicoTools\singleton('db')
|
||||||
@ -184,49 +353,20 @@ function flush_read()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function update_feeds()
|
|
||||||
{
|
|
||||||
foreach (get_feeds() as $feed) {
|
|
||||||
|
|
||||||
$reader = new Reader;
|
|
||||||
$reader->download($feed['feed_url']);
|
|
||||||
$parser = $reader->getParser();
|
|
||||||
|
|
||||||
if ($parser !== false) {
|
|
||||||
|
|
||||||
update_items($feed['id'], $parser->execute()->items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function update_feed($feed_id)
|
|
||||||
{
|
|
||||||
$feed = get_feed($feed_id);
|
|
||||||
|
|
||||||
$reader = new Reader;
|
|
||||||
$reader->download($feed['feed_url']);
|
|
||||||
$parser = $reader->getParser();
|
|
||||||
|
|
||||||
if ($parser !== false) {
|
|
||||||
|
|
||||||
update_items($feed['id'], $parser->execute()->items);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function update_items($feed_id, array $items)
|
function update_items($feed_id, array $items)
|
||||||
{
|
{
|
||||||
|
$items_in_feed = array();
|
||||||
$db = \PicoTools\singleton('db');
|
$db = \PicoTools\singleton('db');
|
||||||
|
|
||||||
$db->startTransaction();
|
$db->startTransaction();
|
||||||
|
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
|
|
||||||
if ($item->id && ! $db->table('items')->eq('id', $item->id)->count()) {
|
// Item parsed correctly?
|
||||||
|
if ($item->id) {
|
||||||
|
|
||||||
|
// Insert only new item
|
||||||
|
if ($db->table('items')->eq('id', $item->id)->count() !== 1) {
|
||||||
|
|
||||||
$db->table('items')->save(array(
|
$db->table('items')->save(array(
|
||||||
'id' => $item->id,
|
'id' => $item->id,
|
||||||
@ -239,6 +379,22 @@ function update_items($feed_id, array $items)
|
|||||||
'feed_id' => $feed_id
|
'feed_id' => $feed_id
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Items inside this feed
|
||||||
|
$items_in_feed[] = $item->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from the database items marked as "removed"
|
||||||
|
// and not present inside the feed
|
||||||
|
if (! empty($items_in_feed)) {
|
||||||
|
|
||||||
|
\PicoTools\singleton('db')
|
||||||
|
->table('items')
|
||||||
|
->notin('id', $items_in_feed)
|
||||||
|
->eq('status', 'removed')
|
||||||
|
->eq('feed_id', $feed_id)
|
||||||
|
->remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
$db->closeTransaction();
|
$db->closeTransaction();
|
||||||
@ -249,7 +405,7 @@ function get_config()
|
|||||||
{
|
{
|
||||||
return \PicoTools\singleton('db')
|
return \PicoTools\singleton('db')
|
||||||
->table('config')
|
->table('config')
|
||||||
->columns('username', 'history')
|
->columns('username', 'language')
|
||||||
->findOne();
|
->findOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +414,7 @@ function get_user()
|
|||||||
{
|
{
|
||||||
return \PicoTools\singleton('db')
|
return \PicoTools\singleton('db')
|
||||||
->table('config')
|
->table('config')
|
||||||
->columns('username', 'password')
|
->columns('username', 'password', 'language')
|
||||||
->findOne();
|
->findOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,9 +422,9 @@ function get_user()
|
|||||||
function validate_login(array $values)
|
function validate_login(array $values)
|
||||||
{
|
{
|
||||||
$v = new Validator($values, array(
|
$v = new Validator($values, array(
|
||||||
new Validators\Required('username', 'The user name is required'),
|
new Validators\Required('username', t('The user name is required')),
|
||||||
new Validators\MaxLength('username', 'The maximum length is 50 characters', 50),
|
new Validators\MaxLength('username', t('The maximum length is 50 characters'), 50),
|
||||||
new Validators\Required('password', 'The password is required')
|
new Validators\Required('password', t('The password is required'))
|
||||||
));
|
));
|
||||||
|
|
||||||
$result = $v->execute();
|
$result = $v->execute();
|
||||||
@ -280,12 +436,13 @@ function validate_login(array $values)
|
|||||||
|
|
||||||
if ($user && \password_verify($values['password'], $user['password'])) {
|
if ($user && \password_verify($values['password'], $user['password'])) {
|
||||||
|
|
||||||
|
unset($user['password']);
|
||||||
$_SESSION['user'] = $user;
|
$_SESSION['user'] = $user;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
$result = false;
|
$result = false;
|
||||||
$errors['login'] = 'Bad username or password';
|
$errors['login'] = t('Bad username or password');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,19 +458,19 @@ function validate_config_update(array $values)
|
|||||||
if (! empty($values['password'])) {
|
if (! empty($values['password'])) {
|
||||||
|
|
||||||
$v = new Validator($values, array(
|
$v = new Validator($values, array(
|
||||||
new Validators\Required('username', 'The user name is required'),
|
new Validators\Required('username', t('The user name is required')),
|
||||||
new Validators\MaxLength('username', 'The maximum length is 50 characters', 50),
|
new Validators\MaxLength('username', t('The maximum length is 50 characters'), 50),
|
||||||
new Validators\Required('password', 'The password is required'),
|
new Validators\Required('password', t('The password is required')),
|
||||||
new Validators\MinLength('password', 'The minimum length is 6 characters', 6),
|
new Validators\MinLength('password', t('The minimum length is 6 characters'), 6),
|
||||||
new Validators\Required('confirmation', 'The confirmation is required'),
|
new Validators\Required('confirmation', t('The confirmation is required')),
|
||||||
new Validators\Equals('password', 'confirmation', 'Passwords doesn\'t match')
|
new Validators\Equals('password', 'confirmation', t('Passwords doesn\'t match'))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
$v = new Validator($values, array(
|
$v = new Validator($values, array(
|
||||||
new Validators\Required('username', 'The user name is required'),
|
new Validators\Required('username', t('The user name is required')),
|
||||||
new Validators\MaxLength('username', 'The maximum length is 50 characters', 50)
|
new Validators\MaxLength('username', t('The maximum length is 50 characters'), 50)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,5 +494,10 @@ function save_config(array $values)
|
|||||||
|
|
||||||
unset($values['confirmation']);
|
unset($values['confirmation']);
|
||||||
|
|
||||||
|
$_SESSION['user']['language'] = $values['language'];
|
||||||
|
unset($_COOKIE['language']);
|
||||||
|
|
||||||
|
\PicoTools\Translator\load($values['language']);
|
||||||
|
|
||||||
return \PicoTools\singleton('db')->table('config')->update($values);
|
return \PicoTools\singleton('db')->table('config')->update($values);
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,32 @@
|
|||||||
|
|
||||||
namespace Schema;
|
namespace Schema;
|
||||||
|
|
||||||
|
|
||||||
|
function version_4($pdo)
|
||||||
|
{
|
||||||
|
$pdo->exec("CREATE INDEX idx_status ON items(status)");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function version_3($pdo)
|
||||||
|
{
|
||||||
|
$pdo->exec("ALTER TABLE config ADD COLUMN language TEXT DEFAULT 'en_US'");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function version_2($pdo)
|
||||||
|
{
|
||||||
|
$pdo->exec('ALTER TABLE feeds ADD COLUMN last_modified TEXT');
|
||||||
|
$pdo->exec('ALTER TABLE feeds ADD COLUMN etag TEXT');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function version_1($pdo)
|
function version_1($pdo)
|
||||||
{
|
{
|
||||||
$pdo->exec("
|
$pdo->exec("
|
||||||
CREATE TABLE config (
|
CREATE TABLE config (
|
||||||
username TEXT DEFAULT 'admin',
|
username TEXT DEFAULT 'admin',
|
||||||
password TEXT,
|
password TEXT
|
||||||
history INTEGER DEFAULT '15'
|
|
||||||
)
|
)
|
||||||
");
|
");
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>New subscription</h2>
|
<h2><?= t('New subscription') ?></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="?action=feeds">feeds</a></li>
|
<li><a href="?action=feeds"><?= t('feeds') ?></a></li>
|
||||||
<li><a href="?action=import">import</a></li>
|
<li><a href="?action=import"><?= t('import') ?></a></li>
|
||||||
<li><a href="?action=export">export</a></li>
|
<li><a href="?action=export"><?= t('export') ?></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" action="?action=add" autocomplete="off">
|
<form method="post" action="?action=add" autocomplete="off">
|
||||||
<label for="url">Site or Feed URL</label>
|
<label for="url"><?= t('Website or Feed URL') ?></label>
|
||||||
<input type="text" name="url" id="url" placeholder="http://website/" autofocus required/>
|
<input type="text" name="url" id="url" placeholder="<?= t('http://website/') ?>" autofocus required/>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button type="submit" class="btn btn-blue">Add</button>
|
<button type="submit" class="btn btn-blue"><?= t('Add') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
@ -2,21 +2,27 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<link rel="icon" type="image/png" href="./assets/img/favicon.png">
|
||||||
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="./assets/img/touch-icon-iphone.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="./assets/img/touch-icon-ipad.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="./assets/img/touch-icon-iphone-retina.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="./assets/img/touch-icon-ipad-retina.png">
|
||||||
<title>miniflux</title>
|
<title>miniflux</title>
|
||||||
<link href="./assets/css/app.css?v2" rel="stylesheet" media="screen">
|
<link href="./assets/css/app.css?v<?= filemtime('assets/css/app.css') ?>" rel="stylesheet" media="screen">
|
||||||
<script type="text/javascript" src="./assets/js/app.js?v1" defer></script>
|
<script type="text/javascript" src="./assets/js/app.js?v<?= filemtime('assets/js/app.js') ?>" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<nav>
|
<nav>
|
||||||
<a class="logo" href="?">mini<span>flux</span></a>
|
<a class="logo" href="?">mini<span>flux</span></a>
|
||||||
<ul>
|
<ul>
|
||||||
<li <?= isset($menu) && $menu === 'unread' ? 'class="active"' : '' ?>><a href="?action=default">unread</a></li>
|
<li <?= isset($menu) && $menu === 'unread' ? 'class="active"' : '' ?>><a href="?action=default"><?= t('unread') ?></a></li>
|
||||||
<li <?= isset($menu) && $menu === 'history' ? 'class="active"' : '' ?>><a href="?action=history">history</a></li>
|
<li <?= isset($menu) && $menu === 'history' ? 'class="active"' : '' ?>><a href="?action=history"><?= t('history') ?></a></li>
|
||||||
<li <?= isset($menu) && $menu === 'feeds' ? 'class="active"' : '' ?>><a href="?action=feeds">subscriptions</a></li>
|
<li <?= isset($menu) && $menu === 'feeds' ? 'class="active"' : '' ?>><a href="?action=feeds"><?= t('subscriptions') ?></a></li>
|
||||||
<li <?= isset($menu) && $menu === 'config' ? 'class="active"' : '' ?>><a href="?action=config">preferences</a></li>
|
<li <?= isset($menu) && $menu === 'config' ? 'class="active"' : '' ?>><a href="?action=config"><?= t('preferences') ?></a></li>
|
||||||
<li><a href="?action=logout">logout</a></li>
|
<li><a href="?action=logout"><?= t('logout') ?></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
@ -1,26 +1,54 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>Preferences</h2>
|
<h2><?= t('Preferences') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
<section>
|
||||||
<form method="post" action="?action=config" autocomplete="off">
|
<form method="post" action="?action=config" autocomplete="off">
|
||||||
|
|
||||||
<?= Helper\form_label('Username', 'username') ?>
|
<?= Helper\form_label(t('Username'), 'username') ?>
|
||||||
<?= Helper\form_text('username', $values, $errors, array('required')) ?><br/>
|
<?= Helper\form_text('username', $values, $errors, array('required')) ?><br/>
|
||||||
|
|
||||||
<?= Helper\form_label('Password', 'password') ?>
|
<?= Helper\form_label(t('Password'), 'password') ?>
|
||||||
<?= Helper\form_password('password', $values, $errors) ?>
|
<?= Helper\form_password('password', $values, $errors) ?><br/>
|
||||||
<span class="form-help">Don't use the same password everywhere!</span><br/>
|
|
||||||
|
|
||||||
<?= Helper\form_label('Confirmation', 'confirmation') ?>
|
<?= Helper\form_label(t('Confirmation'), 'confirmation') ?>
|
||||||
<?= Helper\form_password('confirmation', $values, $errors) ?><br/>
|
<?= Helper\form_password('confirmation', $values, $errors) ?><br/>
|
||||||
|
|
||||||
|
<?= Helper\form_label(t('Language'), 'language') ?>
|
||||||
|
<?= Helper\form_select('language', $languages, $values, $errors) ?><br/>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<input type="submit" value="Update" class="btn btn-blue"/>
|
<input type="submit" value="<?= t('Update') ?>" class="btn btn-blue"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="page-section">
|
<div class="page-section">
|
||||||
<h2>My data</h2>
|
<h2><?= t('More informations') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
<section>
|
||||||
<p><a href="?action=download-db">Download the entire database</a> (Gzip compressed Sqlite file).</p>
|
<div class="alert alert-normal">
|
||||||
|
<h3><?= t('Database') ?></h3>
|
||||||
|
<ul>
|
||||||
|
<li><?= t('Database size:') ?> <?= Helper\format_bytes($db_size) ?></li>
|
||||||
|
<li><a href="?action=optimize-db"><?= t('Optimize the database') ?></a> <?= t('(VACUUM command)') ?></li>
|
||||||
|
<li><a href="?action=download-db"><?= t('Download the entire database') ?></a> <?= t('(Gzip compressed Sqlite file)') ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-normal">
|
||||||
|
<h3><?= t('Keyboard shortcuts') ?></h3>
|
||||||
|
<ul>
|
||||||
|
<li><?= t('Previous item') ?> = <strong>p</strong></li>
|
||||||
|
<li><?= t('Next item') ?> = <strong>n</strong></li>
|
||||||
|
<li><?= t('Mark as read or unread') ?> = <strong>m</strong></li>
|
||||||
|
<li><?= t('Open original link') ?> = <strong>v</strong></li>
|
||||||
|
<li><?= t('Open item') ?> = <strong>o</strong></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-normal">
|
||||||
|
<h3><?= t('About') ?></h3>
|
||||||
|
<ul>
|
||||||
|
<li><?= t('Miniflux version:') ?> <strong><?= APP_VERSION ?></strong></li>
|
||||||
|
<li><?= t('Official website:') ?> <a href="http://miniflux.net" target="_blank">http://miniflux.net</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
10
miniflux/templates/confirm_flush.php
Normal file
10
miniflux/templates/confirm_flush.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<div class="page-header">
|
||||||
|
<h2><?= t('Confirmation') ?></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="alert alert-info"><?= t('Do you really want to remove these items from your history?') ?></p>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<a href="?action=flush-history" class="btn btn-red"><?= t('Yes') ?></a>
|
||||||
|
<?= t('or') ?> <a href="?action=history"><?= t('cancel') ?></a>
|
||||||
|
</div>
|
@ -1,10 +1,10 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>Confirmation</h2>
|
<h2><?= t('Confirmation') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="alert alert-info">Do you really want to remove this subscription: "<?= Helper\escape($feed['title']) ?>"?</p>
|
<p class="alert alert-info"><?= t('Do you really want to remove this subscription: "%s"?', Helper\escape($feed['title'])) ?></p>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<a href="?action=remove&feed_id=<?= $feed['id'] ?>" class="btn btn-red">Yes</a>
|
<a href="?action=remove&feed_id=<?= $feed['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||||
or <a href="?action=feeds">cancel</a>
|
<?= t('or') ?> <a href="?action=feeds"><?= t('cancel') ?></a>
|
||||||
</div>
|
</div>
|
@ -1,21 +1,21 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>Subscriptions</h2>
|
<h2><?= t('Subscriptions') ?></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="?action=add">add</a></li>
|
<li><a href="?action=add"><?= t('add') ?></a></li>
|
||||||
<li><a href="?action=import">import</a></li>
|
<li><a href="?action=import"><?= t('import') ?></a></li>
|
||||||
<li><a href="?action=export">export</a></li>
|
<li><a href="?action=export"><?= t('export') ?></a></li>
|
||||||
<li><a href="?action=refresh-all" data-action="refresh-all">refresh all</a></li>
|
<li><a href="?action=refresh-all" data-action="refresh-all"><?= t('refresh all') ?></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (empty($feeds)): ?>
|
<?php if (empty($feeds)): ?>
|
||||||
|
|
||||||
<p class="alert alert-info">No subscription.</p>
|
<p class="alert alert-info"><?= t('No subscription') ?></p>
|
||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
|
||||||
<?php if ($nothing_to_read): ?>
|
<?php if ($nothing_to_read): ?>
|
||||||
<p class="alert">Nothing to read, do you want to <a href="?action=refresh-all" data-action="refresh-all">update your subscriptions?</a></p>
|
<p class="alert"><?= t('Nothing to read, do you want to <a href="?action=refresh-all" data-action="refresh-all">update your subscriptions?</a>') ?></p>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<section class="items">
|
<section class="items">
|
||||||
@ -26,10 +26,10 @@
|
|||||||
<a href="<?= $feed['site_url'] ?>" rel="noreferrer" target="_blank"><?= Helper\escape($feed['title']) ?></a>
|
<a href="<?= $feed['site_url'] ?>" rel="noreferrer" target="_blank"><?= Helper\escape($feed['title']) ?></a>
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
<?= Helper\get_host_from_url($feed['site_url']) ?> |
|
<a href="<?= $feed['site_url'] ?>" rel="noreferrer" target="_blank"><?= Helper\get_host_from_url($feed['site_url']) ?></a> |
|
||||||
<a href="<?= Helper\escape($feed['feed_url']) ?>">feed link</a> |
|
<a href="<?= Helper\escape($feed['feed_url']) ?>" rel="noreferrer" target="_blank"><?= t('feed link') ?></a> |
|
||||||
<a href="?action=confirm-remove&feed_id=<?= $feed['id'] ?>">remove</a> |
|
<a href="?action=confirm-remove&feed_id=<?= $feed['id'] ?>"><?= t('remove') ?></a> |
|
||||||
<a href="?action=refresh-feed&feed_id=<?= $feed['id'] ?>" data-feed-id="<?= $feed['id'] ?>" data-action="refresh-feed">refresh</a>
|
<a href="?action=refresh-feed&feed_id=<?= $feed['id'] ?>" data-feed-id="<?= $feed['id'] ?>" data-action="refresh-feed"><?= t('refresh') ?></a>
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
|
44
miniflux/templates/history.php
Normal file
44
miniflux/templates/history.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php if (empty($items)): ?>
|
||||||
|
|
||||||
|
<p class="alert alert-info"><?= t('No history') ?></p>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h2><?= t('History') ?></h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="?action=confirm-flush-history"><?= t('flush these items') ?></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="items" id="listing">
|
||||||
|
<?php foreach ($items as $item): ?>
|
||||||
|
<article id="item-<?= urlencode($item['id']) ?>" data-item-id="<?= urlencode($item['id']) ?>">
|
||||||
|
<h2>
|
||||||
|
<a
|
||||||
|
href="?action=show&id=<?= urlencode($item['id']) ?>"
|
||||||
|
id="open-<?= urlencode($item['id']) ?>"
|
||||||
|
>
|
||||||
|
<?= Helper\escape($item['title']) ?>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<?= Helper\get_host_from_url($item['url']) ?> |
|
||||||
|
<?= dt('%A %e %B %Y %k:%M', $item['updated']) ?> |
|
||||||
|
<a href="?action=mark-item-unread&id=<?= urlencode($item['id']) ?>"><?= t('mark as unread') ?></a> |
|
||||||
|
<a href="?action=mark-item-removed&id=<?= urlencode($item['id']) ?>"><?= t('remove') ?></a> |
|
||||||
|
<a
|
||||||
|
href="<?= $item['url'] ?>"
|
||||||
|
id="original-<?= urlencode($item['id']) ?>"
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
data-item-id="<?= urlencode($item['id']) ?>"
|
||||||
|
>
|
||||||
|
<?= t('original link') ?>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php endif ?>
|
@ -1,16 +1,16 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>OPML Import</h2>
|
<h2><?= t('OPML Import') ?></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="?action=feeds">feeds</a></li>
|
<li><a href="?action=feeds"><?= t('feeds') ?></a></li>
|
||||||
<li><a href="?action=add">add</a></li>
|
<li><a href="?action=add"><?= t('add') ?></a></li>
|
||||||
<li><a href="?action=export">export</a></li>
|
<li><a href="?action=export"><?= t('export') ?></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" action="?action=import" enctype="multipart/form-data">
|
<form method="post" action="?action=import" enctype="multipart/form-data">
|
||||||
<label for="file">OPML file</label>
|
<label for="file"><?= t('OPML file') ?></label>
|
||||||
<input type="file" name="file" required/>
|
<input type="file" name="file" required/>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button type="submit" class="btn btn-blue">Import</button>
|
<button type="submit" class="btn btn-blue"><?= t('Import') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
@ -3,13 +3,19 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" type="image/png" href="./assets/img/favicon.png">
|
||||||
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="./assets/img/touch-icon-iphone.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="./assets/img/touch-icon-ipad.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="./assets/img/touch-icon-iphone-retina.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="./assets/img/touch-icon-ipad-retina.png">
|
||||||
<title>miniflux</title>
|
<title>miniflux</title>
|
||||||
<link href="./assets/css/app.css?v1" rel="stylesheet" media="screen">
|
<link href="./assets/css/app.css?v<?= filemtime('assets/css/app.css') ?>" rel="stylesheet" media="screen">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>Login</h1>
|
<h1><?= t('Sign in') ?></h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (isset($errors['login'])): ?>
|
<?php if (isset($errors['login'])): ?>
|
||||||
@ -20,14 +26,14 @@
|
|||||||
|
|
||||||
<form method="post" action="?action=login">
|
<form method="post" action="?action=login">
|
||||||
|
|
||||||
<?= Helper\form_label('Username', 'username') ?>
|
<?= Helper\form_label(t('Username'), 'username') ?>
|
||||||
<?= Helper\form_text('username', $values, $errors, array('autofocus', 'required')) ?><br/>
|
<?= Helper\form_text('username', $values, $errors, array('autofocus', 'required')) ?><br/>
|
||||||
|
|
||||||
<?= Helper\form_label('Password', 'password') ?>
|
<?= Helper\form_label(t('Password'), 'password') ?>
|
||||||
<?= Helper\form_password('password', $values, $errors, array('required')) ?>
|
<?= Helper\form_password('password', $values, $errors, array('required')) ?>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<input type="submit" value="Login" class="btn btn-blue"/>
|
<input type="submit" value="<?= t('Sign in') ?>" class="btn btn-blue"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,20 +1,51 @@
|
|||||||
<?php if (empty($item)): ?>
|
<?php if (empty($item)): ?>
|
||||||
|
|
||||||
<p class="alert alert-info">Article not found.</p>
|
<p class="alert alert-info"><?= t('Item not found') ?></p>
|
||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
<article class="item" id="current-item" data-item-id="<?= urlencode($item['id']) ?>">
|
||||||
<article class="item">
|
|
||||||
<h1>
|
<h1>
|
||||||
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank"><?= Helper\escape($item['title']) ?></a>
|
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank" id="original-item"><?= Helper\escape($item['title']) ?></a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="infos">
|
<p class="infos">
|
||||||
<?= Helper\get_host_from_url($item['url']) ?> |
|
<?= Helper\get_host_from_url($item['url']) ?> |
|
||||||
<?= date('l, j F Y H:i', $item['updated']) ?>
|
<?= dt('%A %e %B %Y %k:%M', $item['updated']) ?>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<?= $item['content'] ?>
|
<?= $item['content'] ?>
|
||||||
|
|
||||||
|
<?php if (isset($item_nav)): ?>
|
||||||
|
<nav>
|
||||||
|
<span class="nav-left">
|
||||||
|
<?php if ($item_nav['previous']): ?>
|
||||||
|
<a href="?action=read&id=<?= urlencode($item_nav['previous']['id']) ?>" id="previous-item">« <?= t('Previous') ?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
« <?= t('Previous') ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="nav-middle">
|
||||||
|
<?php if ($item_nav['previous'] && $item_nav['next']): ?>
|
||||||
|
<a href="?action=default#item-<?= urlencode($item_nav['next']['id']) ?>"><?= t('Unread items') ?></a>
|
||||||
|
<?php elseif ($item_nav['previous'] && ! $item_nav['next']): ?>
|
||||||
|
<a href="?action=default#item-<?= urlencode($item_nav['previous']['id']) ?>"><?= t('Unread items') ?></a>
|
||||||
|
<?php elseif (! $item_nav['previous'] && $item_nav['next']): ?>
|
||||||
|
<a href="?action=default#item-<?= urlencode($item_nav['next']['id']) ?>"><?= t('Unread items') ?></a>
|
||||||
|
<?php elseif (! $item_nav['previous'] && ! $item_nav['next']): ?>
|
||||||
|
<a href="?action=default"><?= t('Unread items') ?></a>
|
||||||
|
<?php endif ?>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="nav-right">
|
||||||
|
<?php if ($item_nav['next']): ?>
|
||||||
|
<a href="?action=read&id=<?= urlencode($item_nav['next']['id']) ?>" id="next-item"><?= t('Next') ?> »</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= t('Next') ?> »
|
||||||
|
<?php endif ?>
|
||||||
|
</span>
|
||||||
|
</nav>
|
||||||
|
<?php endif ?>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
@ -1,27 +0,0 @@
|
|||||||
<?php if (empty($items)): ?>
|
|
||||||
|
|
||||||
<p class="alert alert-info">No history.</p>
|
|
||||||
|
|
||||||
<?php else: ?>
|
|
||||||
|
|
||||||
<div class="page-header">
|
|
||||||
<h2>History</h2>
|
|
||||||
<ul>
|
|
||||||
<li><a href="?action=flush-history">flush</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="items">
|
|
||||||
<?php foreach ($items as $item): ?>
|
|
||||||
<article>
|
|
||||||
<h2><a href="?action=read&id=<?= urlencode($item['id']) ?>"><?= Helper\escape($item['title']) ?></a></h2>
|
|
||||||
<p>
|
|
||||||
<?= Helper\get_host_from_url($item['url']) ?> |
|
|
||||||
<?= date('l, j F Y H:i', $item['updated']) ?> |
|
|
||||||
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank">direct link</a>
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<?php endif ?>
|
|
@ -1,28 +1,43 @@
|
|||||||
<?php if (empty($items)): ?>
|
<?php if (empty($items)): ?>
|
||||||
|
|
||||||
<p class="alert alert-info">Nothing to read.</p>
|
<p class="alert alert-info"><?= t('Nothing to read') ?></p>
|
||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>Unread items</h2>
|
<h2><?= t('Unread items') ?></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="?action=flush-unread">flush</a></li>
|
<li><a href="?action=mark-as-read"><?= t('mark all as read') ?></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="items">
|
<section class="items" id="listing">
|
||||||
<?php foreach ($items as $item): ?>
|
<?php foreach ($items as $item): ?>
|
||||||
<article id="item-<?= urlencode($item['id']) ?>">
|
<article id="item-<?= urlencode($item['id']) ?>" data-item-id="<?= urlencode($item['id']) ?>">
|
||||||
<h2><a href="?action=read&id=<?= urlencode($item['id']) ?>"><?= Helper\escape($item['title']) ?></a></h2>
|
<h2>
|
||||||
<a href="?action" class="mark-read">mark read</a>
|
<a
|
||||||
|
href="?action=read&id=<?= urlencode($item['id']) ?>"
|
||||||
|
id="open-<?= urlencode($item['id']) ?>"
|
||||||
|
>
|
||||||
|
<?= Helper\escape($item['title']) ?>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
<p class="preview">
|
<p class="preview">
|
||||||
<?= Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
|
<?= Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<?= Helper\get_host_from_url($item['url']) ?> |
|
<?= Helper\get_host_from_url($item['url']) ?> |
|
||||||
<a href="<?= $item['url'] ?>" rel="noreferrer" target="_blank" data-item-id="<?= urlencode($item['id']) ?>" data-action="mark-read">direct link</a> |
|
<a href="?action=mark-item-read&id=<?= urlencode($item['id']) ?>"><?= t('mark as read') ?></a> |
|
||||||
<a href="#read" rel="noreferrer" data-item-id="<?= urlencode($item['id']) ?>" data-action="mark-read">mark read</a>
|
<a
|
||||||
|
href="<?= $item['url'] ?>"
|
||||||
|
id="original-<?= urlencode($item['id']) ?>"
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
data-item-id="<?= urlencode($item['id']) ?>"
|
||||||
|
data-action="mark-read"
|
||||||
|
>
|
||||||
|
<?= t('original link') ?>
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
|
20
miniflux/vendor/PicoDb/Table.php
vendored
20
miniflux/vendor/PicoDb/Table.php
vendored
@ -63,7 +63,14 @@ class Table
|
|||||||
$this->conditions()
|
$this->conditions()
|
||||||
);
|
);
|
||||||
|
|
||||||
return false !== $this->db->execute($sql, $values);
|
$result = $this->db->execute($sql, $values);
|
||||||
|
|
||||||
|
if ($result !== false && $result->rowCount() > 0) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -290,6 +297,17 @@ class Table
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'notin':
|
||||||
|
if (is_array($arguments[1])) {
|
||||||
|
|
||||||
|
$sql = sprintf(
|
||||||
|
'%s NOT IN (%s)',
|
||||||
|
$this->db->escapeIdentifier($column),
|
||||||
|
implode(', ', array_fill(0, count($arguments[1]), '?'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'like':
|
case 'like':
|
||||||
$sql = sprintf('%s LIKE ?', $this->db->escapeIdentifier($column));
|
$sql = sprintf('%s LIKE ?', $this->db->escapeIdentifier($column));
|
||||||
break;
|
break;
|
||||||
|
19
miniflux/vendor/PicoFeed/Export.php
vendored
19
miniflux/vendor/PicoFeed/Export.php
vendored
@ -6,6 +6,12 @@ class Export
|
|||||||
{
|
{
|
||||||
private $content = array();
|
private $content = array();
|
||||||
|
|
||||||
|
public $required_fields = array(
|
||||||
|
'title',
|
||||||
|
'site_url',
|
||||||
|
'feed_url'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
public function __construct(array $content)
|
public function __construct(array $content)
|
||||||
{
|
{
|
||||||
@ -24,6 +30,19 @@ class Export
|
|||||||
|
|
||||||
foreach ($this->content as $feed) {
|
foreach ($this->content as $feed) {
|
||||||
|
|
||||||
|
$valid = true;
|
||||||
|
|
||||||
|
foreach ($this->required_fields as $field) {
|
||||||
|
|
||||||
|
if (! isset($feed[$field])) {
|
||||||
|
|
||||||
|
$valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $valid) continue;
|
||||||
|
|
||||||
$outline = $body->addChild('outline');
|
$outline = $body->addChild('outline');
|
||||||
$outline->addAttribute('xmlUrl', $feed['feed_url']);
|
$outline->addAttribute('xmlUrl', $feed['feed_url']);
|
||||||
$outline->addAttribute('htmlUrl', $feed['site_url']);
|
$outline->addAttribute('htmlUrl', $feed['site_url']);
|
||||||
|
104
miniflux/vendor/PicoFeed/Filter.php
vendored
104
miniflux/vendor/PicoFeed/Filter.php
vendored
@ -7,11 +7,9 @@ class Filter
|
|||||||
private $data = '';
|
private $data = '';
|
||||||
private $url = '';
|
private $url = '';
|
||||||
private $input = '';
|
private $input = '';
|
||||||
private $empty_tag = false;
|
private $empty_tags = array();
|
||||||
private $strip_content = false;
|
private $strip_content = false;
|
||||||
|
|
||||||
public $ignored_tags = array();
|
|
||||||
|
|
||||||
public $allowed_tags = array(
|
public $allowed_tags = array(
|
||||||
'dt' => array(),
|
'dt' => array(),
|
||||||
'dd' => array(),
|
'dd' => array(),
|
||||||
@ -45,7 +43,9 @@ class Filter
|
|||||||
'figcaption' => array(),
|
'figcaption' => array(),
|
||||||
'cite' => array(),
|
'cite' => array(),
|
||||||
'time' => array('datetime'),
|
'time' => array('datetime'),
|
||||||
'abbr' => array('title')
|
'abbr' => array('title'),
|
||||||
|
'iframe' => array('width', 'height', 'frameborder', 'src'),
|
||||||
|
'q' => array('cite')
|
||||||
);
|
);
|
||||||
|
|
||||||
public $strip_tags_content = array(
|
public $strip_tags_content = array(
|
||||||
@ -56,8 +56,11 @@ class Filter
|
|||||||
'http://',
|
'http://',
|
||||||
'https://',
|
'https://',
|
||||||
'ftp://',
|
'ftp://',
|
||||||
'mailto://',
|
'mailto:',
|
||||||
'//'
|
'//',
|
||||||
|
'data:image/png;base64,',
|
||||||
|
'data:image/gif;base64,',
|
||||||
|
'data:image/jpg;base64,'
|
||||||
);
|
);
|
||||||
|
|
||||||
public $protocol_attributes = array(
|
public $protocol_attributes = array(
|
||||||
@ -67,25 +70,46 @@ class Filter
|
|||||||
|
|
||||||
public $blacklist_media = array(
|
public $blacklist_media = array(
|
||||||
'feeds.feedburner.com',
|
'feeds.feedburner.com',
|
||||||
'feedsportal.com',
|
'da.feedsportal.com',
|
||||||
|
'rss.feedsportal.com',
|
||||||
|
'res.feedsportal.com',
|
||||||
|
'pi.feedsportal.com',
|
||||||
'rss.nytimes.com',
|
'rss.nytimes.com',
|
||||||
'feeds.wordpress.com',
|
'feeds.wordpress.com',
|
||||||
'stats.wordpress.com'
|
'stats.wordpress.com',
|
||||||
|
'rss.cnn.com',
|
||||||
|
'twitter.com/home?status=',
|
||||||
|
'twitter.com/share',
|
||||||
|
'twitter_icon_large.png',
|
||||||
|
'www.facebook.com/sharer.php',
|
||||||
|
'facebook_icon_large.png',
|
||||||
|
'plus.google.com/share',
|
||||||
|
'www.gstatic.com/images/icons/gplus-16.png',
|
||||||
|
'www.gstatic.com/images/icons/gplus-32.png',
|
||||||
|
'www.gstatic.com/images/icons/gplus-64.png'
|
||||||
);
|
);
|
||||||
|
|
||||||
public $required_attributes = array(
|
public $required_attributes = array(
|
||||||
'a' => array('href'),
|
'a' => array('href'),
|
||||||
'img' => array('src')
|
'img' => array('src'),
|
||||||
|
'iframe' => array('src')
|
||||||
);
|
);
|
||||||
|
|
||||||
public $add_attributes = array(
|
public $add_attributes = array(
|
||||||
'a' => 'rel="noreferrer" target="_blank"'
|
'a' => 'rel="noreferrer" target="_blank"'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public $iframe_allowed_resources = array(
|
||||||
|
'http://www.youtube.com/',
|
||||||
|
'https://www.youtube.com/',
|
||||||
|
'http://player.vimeo.com/',
|
||||||
|
'https://player.vimeo.com/'
|
||||||
|
);
|
||||||
|
|
||||||
public function __construct($data, $url)
|
|
||||||
|
public function __construct($data, $site_url)
|
||||||
{
|
{
|
||||||
$this->url = $url;
|
$this->url = $site_url;
|
||||||
|
|
||||||
// Convert bad formatted documents to XML
|
// Convert bad formatted documents to XML
|
||||||
$dom = new \DOMDocument;
|
$dom = new \DOMDocument;
|
||||||
@ -104,7 +128,7 @@ class Filter
|
|||||||
|
|
||||||
if (! xml_parse($parser, $this->input, true)) {
|
if (! xml_parse($parser, $this->input, true)) {
|
||||||
|
|
||||||
var_dump($this->input);
|
//var_dump($this->input);
|
||||||
die(xml_get_current_line_number($parser).'|'.xml_error_string(xml_get_error_code($parser)));
|
die(xml_get_current_line_number($parser).'|'.xml_error_string(xml_get_error_code($parser)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,12 +140,12 @@ class Filter
|
|||||||
|
|
||||||
public function startTag($parser, $name, $attributes)
|
public function startTag($parser, $name, $attributes)
|
||||||
{
|
{
|
||||||
$this->empty_tag = false;
|
$empty_tag = false;
|
||||||
$this->strip_content = false;
|
$this->strip_content = false;
|
||||||
|
|
||||||
if ($this->isPixelTracker($name, $attributes)) {
|
if ($this->isPixelTracker($name, $attributes)) {
|
||||||
|
|
||||||
$this->empty_tag = true;
|
$empty_tag = true;
|
||||||
}
|
}
|
||||||
else if ($this->isAllowedTag($name)) {
|
else if ($this->isAllowedTag($name)) {
|
||||||
|
|
||||||
@ -130,17 +154,33 @@ class Filter
|
|||||||
|
|
||||||
foreach ($attributes as $attribute => $value) {
|
foreach ($attributes as $attribute => $value) {
|
||||||
|
|
||||||
if ($this->isAllowedAttribute($name, $attribute)) {
|
if ($value != '' && $this->isAllowedAttribute($name, $attribute)) {
|
||||||
|
|
||||||
if ($this->isResource($attribute)) {
|
if ($this->isResource($attribute)) {
|
||||||
|
|
||||||
if ($this->isRelativePath($value)) {
|
if ($name === 'iframe') {
|
||||||
|
|
||||||
|
if ($this->isAllowedIframeResource($value)) {
|
||||||
|
|
||||||
|
$attr_data .= ' '.$attribute.'="'.$value.'"';
|
||||||
|
$used_attributes[] = $attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($this->isRelativePath($value)) {
|
||||||
|
|
||||||
$attr_data .= ' '.$attribute.'="'.$this->getAbsoluteUrl($value, $this->url).'"';
|
$attr_data .= ' '.$attribute.'="'.$this->getAbsoluteUrl($value, $this->url).'"';
|
||||||
$used_attributes[] = $attribute;
|
$used_attributes[] = $attribute;
|
||||||
}
|
}
|
||||||
else if ($this->isAllowedProtocol($value) && ! $this->isBlacklistMedia($value)) {
|
else if ($this->isAllowedProtocol($value) && ! $this->isBlacklistMedia($value)) {
|
||||||
|
|
||||||
|
if ($attribute == 'src' &&
|
||||||
|
isset($attributes['data-src']) &&
|
||||||
|
$this->isAllowedProtocol($attributes['data-src']) &&
|
||||||
|
! $this->isBlacklistMedia($attributes['data-src'])) {
|
||||||
|
|
||||||
|
$value = $attributes['data-src'];
|
||||||
|
}
|
||||||
|
|
||||||
$attr_data .= ' '.$attribute.'="'.$value.'"';
|
$attr_data .= ' '.$attribute.'="'.$value.'"';
|
||||||
$used_attributes[] = $attribute;
|
$used_attributes[] = $attribute;
|
||||||
}
|
}
|
||||||
@ -153,45 +193,46 @@ class Filter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for required attributes
|
||||||
if (isset($this->required_attributes[$name])) {
|
if (isset($this->required_attributes[$name])) {
|
||||||
|
|
||||||
foreach ($this->required_attributes[$name] as $required_attribute) {
|
foreach ($this->required_attributes[$name] as $required_attribute) {
|
||||||
|
|
||||||
if (! in_array($required_attribute, $used_attributes)) {
|
if (! in_array($required_attribute, $used_attributes)) {
|
||||||
|
|
||||||
$this->empty_tag = true;
|
$empty_tag = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->empty_tag) {
|
if (! $empty_tag) {
|
||||||
|
|
||||||
$this->data .= '<'.$name.$attr_data;
|
$this->data .= '<'.$name.$attr_data;
|
||||||
|
|
||||||
|
// Add custom attributes
|
||||||
if (isset($this->add_attributes[$name])) {
|
if (isset($this->add_attributes[$name])) {
|
||||||
|
|
||||||
$this->data .= ' '.$this->add_attributes[$name].' ';
|
$this->data .= ' '.$this->add_attributes[$name].' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If img or br, we don't close it here
|
||||||
if ($name !== 'img' && $name !== 'br') $this->data .= '>';
|
if ($name !== 'img' && $name !== 'br') $this->data .= '>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
|
||||||
$this->ignored_tags[] = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array($name, $this->strip_tags_content)) {
|
if (in_array($name, $this->strip_tags_content)) {
|
||||||
|
|
||||||
$this->strip_content = true;
|
$this->strip_content = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->empty_tags[] = $empty_tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function endTag($parser, $name)
|
public function endTag($parser, $name)
|
||||||
{
|
{
|
||||||
if (! $this->empty_tag && $this->isAllowedTag($name)) {
|
if (! array_pop($this->empty_tags) && $this->isAllowedTag($name)) {
|
||||||
|
|
||||||
$this->data .= $name !== 'img' && $name !== 'br' ? '</'.$name.'>' : '/>';
|
$this->data .= $name !== 'img' && $name !== 'br' ? '</'.$name.'>' : '/>';
|
||||||
}
|
}
|
||||||
@ -216,7 +257,6 @@ class Filter
|
|||||||
else {
|
else {
|
||||||
|
|
||||||
// Relative path
|
// Relative path
|
||||||
|
|
||||||
$url_path = $components['path'];
|
$url_path = $components['path'];
|
||||||
|
|
||||||
if ($url_path{strlen($url_path) - 1} !== '/') {
|
if ($url_path{strlen($url_path) - 1} !== '/') {
|
||||||
@ -236,6 +276,8 @@ class Filter
|
|||||||
|
|
||||||
public function isRelativePath($value)
|
public function isRelativePath($value)
|
||||||
{
|
{
|
||||||
|
if (strpos($value, 'data:') === 0) return false;
|
||||||
|
|
||||||
return strpos($value, '://') === false && strpos($value, '//') !== 0;
|
return strpos($value, '://') === false && strpos($value, '//') !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +300,20 @@ class Filter
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function isAllowedIframeResource($value)
|
||||||
|
{
|
||||||
|
foreach ($this->iframe_allowed_resources as $url) {
|
||||||
|
|
||||||
|
if (strpos($value, $url) === 0) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function isAllowedProtocol($value)
|
public function isAllowedProtocol($value)
|
||||||
{
|
{
|
||||||
foreach ($this->allowed_protocols as $protocol) {
|
foreach ($this->allowed_protocols as $protocol) {
|
||||||
|
261
miniflux/vendor/PicoFeed/Parser.php
vendored
261
miniflux/vendor/PicoFeed/Parser.php
vendored
@ -14,6 +14,7 @@ abstract class Parser
|
|||||||
public $title = '';
|
public $title = '';
|
||||||
public $updated = '';
|
public $updated = '';
|
||||||
public $items = array();
|
public $items = array();
|
||||||
|
public $debug = false;
|
||||||
|
|
||||||
|
|
||||||
abstract public function execute();
|
abstract public function execute();
|
||||||
@ -37,253 +38,25 @@ abstract class Parser
|
|||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Atom extends Parser
|
public function displayXmlErrors()
|
||||||
{
|
{
|
||||||
public function execute()
|
foreach(\libxml_get_errors() as $error) {
|
||||||
|
|
||||||
|
printf("Message: %s\nLine: %d\nColumn: %d\nCode: %d\n",
|
||||||
|
$error->message,
|
||||||
|
$error->line,
|
||||||
|
$error->column,
|
||||||
|
$error->code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Dirty quickfix before XML parsing
|
||||||
|
public function normalizeData($data)
|
||||||
{
|
{
|
||||||
try {
|
return str_replace("\xc3\x20", '', $data);
|
||||||
|
|
||||||
\libxml_use_internal_errors(true);
|
|
||||||
|
|
||||||
$xml = new \SimpleXMLElement($this->content);
|
|
||||||
|
|
||||||
$this->url = $this->getUrl($xml);
|
|
||||||
$this->title = (string) $xml->title;
|
|
||||||
$this->id = (string) $xml->id;
|
|
||||||
$this->updated = strtotime((string) $xml->updated);
|
|
||||||
$author = (string) $xml->author->name;
|
|
||||||
|
|
||||||
foreach ($xml->entry as $entry) {
|
|
||||||
|
|
||||||
if (isset($entry->author->name)) {
|
|
||||||
|
|
||||||
$author = $entry->author->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
$item = new \StdClass;
|
|
||||||
$item->id = (string) $entry->id;
|
|
||||||
$item->title = (string) $entry->title;
|
|
||||||
$item->url = $this->getUrl($entry);
|
|
||||||
$item->updated = strtotime((string) $entry->updated);
|
|
||||||
$item->author = $author;
|
|
||||||
$item->content = $this->filterHtml($this->getContent($entry), $item->url);
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (\Exception $e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function getContent($entry)
|
|
||||||
{
|
|
||||||
if (isset($entry->content) && ! empty($entry->content)) {
|
|
||||||
|
|
||||||
if (count($entry->content->children())) {
|
|
||||||
|
|
||||||
return (string) $entry->content->asXML();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
return (string) $entry->content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (isset($entry->summary) && ! empty($entry->summary)) {
|
|
||||||
|
|
||||||
return (string) $entry->summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function getUrl($xml)
|
|
||||||
{
|
|
||||||
foreach ($xml->link as $link) {
|
|
||||||
|
|
||||||
if ((string) $link['type'] === 'text/html') {
|
|
||||||
|
|
||||||
return (string) $link['href'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (string) $xml->link['href'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Rss20 extends Parser
|
|
||||||
{
|
|
||||||
public function execute()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
\libxml_use_internal_errors(true);
|
|
||||||
|
|
||||||
$xml = new \SimpleXMLElement($this->content);
|
|
||||||
$ns = $xml->getNamespaces(true);
|
|
||||||
|
|
||||||
$this->title = (string) $xml->channel->title;
|
|
||||||
$this->url = (string) $xml->channel->link;
|
|
||||||
$this->id = $this->url;
|
|
||||||
$this->updated = isset($xml->channel->pubDate) ? (string) $xml->channel->pubDate : (string) $xml->channel->lastBuildDate;
|
|
||||||
|
|
||||||
if ($this->updated) {
|
|
||||||
|
|
||||||
$this->updated = strtotime($this->updated);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
$this->updated = time();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($xml->channel->item as $entry) {
|
|
||||||
|
|
||||||
$author = '';
|
|
||||||
$content = '';
|
|
||||||
$pubdate = '';
|
|
||||||
$link = '';
|
|
||||||
|
|
||||||
if (isset($ns['feedburner'])) {
|
|
||||||
|
|
||||||
$ns_fb = $entry->children($ns['feedburner']);
|
|
||||||
$link = $ns_fb->origLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($ns['dc'])) {
|
|
||||||
|
|
||||||
$ns_dc = $entry->children($ns['dc']);
|
|
||||||
$author = (string) $ns_dc->creator;
|
|
||||||
$pubdate = (string) $ns_dc->date;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($ns['content'])) {
|
|
||||||
|
|
||||||
$ns_content = $entry->children($ns['content']);
|
|
||||||
$content = (string) $ns_content->encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($content === '' && isset($entry->description)) {
|
|
||||||
|
|
||||||
$content = (string) $entry->description;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($author === '') {
|
|
||||||
|
|
||||||
if (isset($entry->author)) {
|
|
||||||
|
|
||||||
$author = (string) $entry->author;
|
|
||||||
}
|
|
||||||
else if (isset($xml->channel->webMaster)) {
|
|
||||||
|
|
||||||
$author = (string) $xml->channel->webMaster;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$item = new \StdClass;
|
|
||||||
$item->title = (string) $entry->title;
|
|
||||||
$item->url = $link ?: (string) $entry->link;
|
|
||||||
$item->id = isset($entry->guid) ? (string) $entry->guid : $item->url;
|
|
||||||
$item->updated = strtotime($pubdate ?: (string) $entry->pubDate) ?: $this->updated;
|
|
||||||
$item->content = $this->filterHtml($content, $item->url);
|
|
||||||
$item->author = $author;
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (\Exception $e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Rss10 extends Parser
|
|
||||||
{
|
|
||||||
public function execute()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
\libxml_use_internal_errors(true);
|
|
||||||
|
|
||||||
$xml = new \SimpleXMLElement($this->content);
|
|
||||||
$ns = $xml->getNamespaces(true);
|
|
||||||
|
|
||||||
$this->title = (string) $xml->channel->title;
|
|
||||||
$this->url = (string) $xml->channel->link;
|
|
||||||
$this->id = $this->url;
|
|
||||||
|
|
||||||
if (isset($ns['dc'])) {
|
|
||||||
|
|
||||||
$ns_dc = $xml->channel->children($ns['dc']);
|
|
||||||
$this->updated = isset($ns_dc->date) ? strtotime($ns_dc->date) : time();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
$this->updated = time();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($xml->item as $entry) {
|
|
||||||
|
|
||||||
$author = '';
|
|
||||||
$content = '';
|
|
||||||
$pubdate = '';
|
|
||||||
$link = '';
|
|
||||||
|
|
||||||
if (isset($ns['feedburner'])) {
|
|
||||||
|
|
||||||
$ns_fb = $entry->children($ns['feedburner']);
|
|
||||||
$link = $ns_fb->origLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($ns['dc'])) {
|
|
||||||
|
|
||||||
$ns_dc = $entry->children($ns['dc']);
|
|
||||||
$author = (string) $ns_dc->creator;
|
|
||||||
$pubdate = (string) $ns_dc->date;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($ns['content'])) {
|
|
||||||
|
|
||||||
$ns_content = $entry->children($ns['content']);
|
|
||||||
$content = (string) $ns_content->encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($content === '' && isset($entry->description)) {
|
|
||||||
|
|
||||||
$content = (string) $entry->description;
|
|
||||||
}
|
|
||||||
|
|
||||||
$item = new \StdClass;
|
|
||||||
$item->title = (string) $entry->title;
|
|
||||||
$item->url = $link ?: (string) $entry->link;
|
|
||||||
$item->id = $item->url;
|
|
||||||
$item->updated = $pubdate ? strtotime($pubdate) : time();
|
|
||||||
$item->content = $this->filterHtml($content, $item->url);
|
|
||||||
$item->author = $author ?: (string) $xml->channel->webMaster;
|
|
||||||
|
|
||||||
$this->items[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (\Exception $e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Rss92 extends Rss20 {}
|
|
||||||
|
|
||||||
|
|
||||||
class Rss91 extends Rss20 {}
|
|
||||||
|
85
miniflux/vendor/PicoFeed/Parsers/Atom.php
vendored
Normal file
85
miniflux/vendor/PicoFeed/Parsers/Atom.php
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PicoFeed;
|
||||||
|
|
||||||
|
class Atom extends Parser
|
||||||
|
{
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$this->content = $this->normalizeData($this->content);
|
||||||
|
|
||||||
|
\libxml_use_internal_errors(true);
|
||||||
|
|
||||||
|
$xml = \simplexml_load_string($this->content);
|
||||||
|
|
||||||
|
if ($xml === false) {
|
||||||
|
|
||||||
|
if ($this->debug) $this->displayXmlErrors();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->url = $this->getUrl($xml);
|
||||||
|
$this->title = (string) $xml->title;
|
||||||
|
$this->id = (string) $xml->id;
|
||||||
|
$this->updated = strtotime((string) $xml->updated);
|
||||||
|
$author = (string) $xml->author->name;
|
||||||
|
|
||||||
|
foreach ($xml->entry as $entry) {
|
||||||
|
|
||||||
|
if (isset($entry->author->name)) {
|
||||||
|
|
||||||
|
$author = $entry->author->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = new \StdClass;
|
||||||
|
$item->id = (string) $entry->id;
|
||||||
|
$item->title = (string) $entry->title;
|
||||||
|
$item->url = $this->getUrl($entry);
|
||||||
|
$item->updated = strtotime((string) $entry->updated);
|
||||||
|
$item->author = $author;
|
||||||
|
$item->content = $this->filterHtml($this->getContent($entry), $item->url);
|
||||||
|
|
||||||
|
if (empty($item->title)) $item->title = $item->url;
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getContent($entry)
|
||||||
|
{
|
||||||
|
if (isset($entry->content) && ! empty($entry->content)) {
|
||||||
|
|
||||||
|
if (count($entry->content->children())) {
|
||||||
|
|
||||||
|
return (string) $entry->content->asXML();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
return (string) $entry->content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isset($entry->summary) && ! empty($entry->summary)) {
|
||||||
|
|
||||||
|
return (string) $entry->summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getUrl($xml)
|
||||||
|
{
|
||||||
|
foreach ($xml->link as $link) {
|
||||||
|
|
||||||
|
if ((string) $link['type'] === 'text/html' || (string) $link['type'] === 'application/xhtml+xml') {
|
||||||
|
|
||||||
|
return (string) $link['href'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $xml->link['href'];
|
||||||
|
}
|
||||||
|
}
|
85
miniflux/vendor/PicoFeed/Parsers/Rss10.php
vendored
Normal file
85
miniflux/vendor/PicoFeed/Parsers/Rss10.php
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PicoFeed;
|
||||||
|
|
||||||
|
class Rss10 extends Parser
|
||||||
|
{
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$this->content = $this->normalizeData($this->content);
|
||||||
|
|
||||||
|
\libxml_use_internal_errors(true);
|
||||||
|
$xml = \simplexml_load_string($this->content);
|
||||||
|
|
||||||
|
if ($xml === false) {
|
||||||
|
|
||||||
|
if ($this->debug) $this->displayXmlErrors();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$namespaces = $xml->getNamespaces(true);
|
||||||
|
|
||||||
|
$this->title = (string) $xml->channel->title;
|
||||||
|
$this->url = (string) $xml->channel->link;
|
||||||
|
$this->id = $this->url;
|
||||||
|
|
||||||
|
if (isset($namespaces['dc'])) {
|
||||||
|
|
||||||
|
$ns_dc = $xml->channel->children($namespaces['dc']);
|
||||||
|
$this->updated = isset($ns_dc->date) ? strtotime($ns_dc->date) : time();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$this->updated = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($xml->item as $entry) {
|
||||||
|
|
||||||
|
$item = new \StdClass;
|
||||||
|
$item->title = (string) $entry->title;
|
||||||
|
$item->url = '';
|
||||||
|
$item->author= '';
|
||||||
|
$item->updated = '';
|
||||||
|
$item->content = '';
|
||||||
|
|
||||||
|
foreach ($namespaces as $name => $url) {
|
||||||
|
|
||||||
|
$namespace = $entry->children($namespaces[$name]);
|
||||||
|
|
||||||
|
if (! $item->url && ! empty($namespace->origLink)) $item->url = (string) $namespace->origLink;
|
||||||
|
if (! $item->author && ! empty($namespace->creator)) $item->author = (string) $namespace->creator;
|
||||||
|
if (! $item->updated && ! empty($namespace->date)) $item->updated = strtotime((string) $namespace->date);
|
||||||
|
if (! $item->updated && ! empty($namespace->updated)) $item->updated = strtotime((string) $namespace->updated);
|
||||||
|
if (! $item->content && ! empty($namespace->encoded)) $item->content = (string) $namespace->encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($item->url)) $item->url = (string) $entry->link;
|
||||||
|
if (empty($item->updated)) $item->updated = $this->updated;
|
||||||
|
|
||||||
|
if (empty($item->content)) {
|
||||||
|
|
||||||
|
$item->content = isset($entry->description) ? (string) $entry->description : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($item->author)) {
|
||||||
|
|
||||||
|
if (isset($entry->author)) {
|
||||||
|
|
||||||
|
$item->author = (string) $entry->author;
|
||||||
|
}
|
||||||
|
else if (isset($xml->channel->webMaster)) {
|
||||||
|
|
||||||
|
$item->author = (string) $xml->channel->webMaster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($item->title)) $item->title = $item->url;
|
||||||
|
|
||||||
|
$item->id = $item->url;
|
||||||
|
$item->content = $this->filterHtml($item->content, $item->url);
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
102
miniflux/vendor/PicoFeed/Parsers/Rss20.php
vendored
Normal file
102
miniflux/vendor/PicoFeed/Parsers/Rss20.php
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PicoFeed;
|
||||||
|
|
||||||
|
class Rss20 extends Parser
|
||||||
|
{
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$this->content = $this->normalizeData($this->content);
|
||||||
|
|
||||||
|
\libxml_use_internal_errors(true);
|
||||||
|
$xml = \simplexml_load_string($this->content);
|
||||||
|
|
||||||
|
if ($xml === false) {
|
||||||
|
|
||||||
|
if ($this->debug) $this->displayXmlErrors();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$namespaces = $xml->getNamespaces(true);
|
||||||
|
|
||||||
|
if ($xml->channel->link->count() > 1) {
|
||||||
|
|
||||||
|
foreach ($xml->channel->link as $xml_link) {
|
||||||
|
|
||||||
|
$link = (string) $xml_link;
|
||||||
|
|
||||||
|
if ($link !== '') {
|
||||||
|
|
||||||
|
$this->url = (string) $link;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$this->url = (string) $xml->channel->link;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->title = (string) $xml->channel->title;
|
||||||
|
$this->id = $this->url;
|
||||||
|
$this->updated = isset($xml->channel->pubDate) ? (string) $xml->channel->pubDate : (string) $xml->channel->lastBuildDate;
|
||||||
|
$this->updated = $this->updated ? strtotime($this->updated) : time();
|
||||||
|
|
||||||
|
foreach ($xml->channel->item as $entry) {
|
||||||
|
|
||||||
|
$item = new \StdClass;
|
||||||
|
$item->title = (string) $entry->title;
|
||||||
|
$item->url = '';
|
||||||
|
$item->author= '';
|
||||||
|
$item->updated = '';
|
||||||
|
$item->content = '';
|
||||||
|
|
||||||
|
foreach ($namespaces as $name => $url) {
|
||||||
|
|
||||||
|
$namespace = $entry->children($namespaces[$name]);
|
||||||
|
|
||||||
|
if (! $item->url && ! empty($namespace->origLink)) $item->url = (string) $namespace->origLink;
|
||||||
|
if (! $item->author && ! empty($namespace->creator)) $item->author = (string) $namespace->creator;
|
||||||
|
if (! $item->updated && ! empty($namespace->date)) $item->updated = strtotime((string) $namespace->date);
|
||||||
|
if (! $item->updated && ! empty($namespace->updated)) $item->updated = strtotime((string) $namespace->updated);
|
||||||
|
if (! $item->content && ! empty($namespace->encoded)) $item->content = (string) $namespace->encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($item->url)) $item->url = (string) $entry->link;
|
||||||
|
if (empty($item->updated)) $item->updated = strtotime((string) $entry->pubDate) ?: $this->updated;
|
||||||
|
|
||||||
|
if (empty($item->content)) {
|
||||||
|
|
||||||
|
$item->content = isset($entry->description) ? (string) $entry->description : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($item->author)) {
|
||||||
|
|
||||||
|
if (isset($entry->author)) {
|
||||||
|
|
||||||
|
$item->author = (string) $entry->author;
|
||||||
|
}
|
||||||
|
else if (isset($xml->channel->webMaster)) {
|
||||||
|
|
||||||
|
$item->author = (string) $xml->channel->webMaster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($entry->guid) && isset($entry->guid['isPermaLink']) && (string) $entry->guid['isPermaLink'] != 'false') {
|
||||||
|
|
||||||
|
$item->id = (string) $entry->guid;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$item->id = $item->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($item->title)) $item->title = $item->url;
|
||||||
|
|
||||||
|
$item->content = $this->filterHtml($item->content, $item->url);
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
7
miniflux/vendor/PicoFeed/Parsers/Rss91.php
vendored
Normal file
7
miniflux/vendor/PicoFeed/Parsers/Rss91.php
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PicoFeed;
|
||||||
|
|
||||||
|
require_once __DIR__.'/Rss20.php';
|
||||||
|
|
||||||
|
class Rss91 extends Rss20 {}
|
7
miniflux/vendor/PicoFeed/Parsers/Rss92.php
vendored
Normal file
7
miniflux/vendor/PicoFeed/Parsers/Rss92.php
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PicoFeed;
|
||||||
|
|
||||||
|
require_once __DIR__.'/Rss20.php';
|
||||||
|
|
||||||
|
class Rss92 extends Rss20 {}
|
30
miniflux/vendor/PicoFeed/Reader.php
vendored
30
miniflux/vendor/PicoFeed/Reader.php
vendored
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace PicoFeed;
|
namespace PicoFeed;
|
||||||
|
|
||||||
|
require_once __DIR__.'/Parser.php';
|
||||||
|
require_once __DIR__.'/RemoteResource.php';
|
||||||
|
|
||||||
class Reader
|
class Reader
|
||||||
{
|
{
|
||||||
private $url = '';
|
private $url = '';
|
||||||
@ -16,17 +19,22 @@ class Reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function download($url)
|
public function download($url, $last_modified = '', $etag = '', $timeout = 5, $user_agent = 'PicoFeed (https://github.com/fguillot/picoFeed)')
|
||||||
{
|
{
|
||||||
if (strpos($url, 'http') !== 0) {
|
if (strpos($url, 'http') !== 0) {
|
||||||
|
|
||||||
$url = 'http://'.$url;
|
$url = 'http://'.$url;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->url = $url;
|
$resource = new RemoteResource($url, $timeout, $user_agent);
|
||||||
$this->content = @file_get_contents($this->url);
|
$resource->setLastModified($last_modified);
|
||||||
|
$resource->setEtag($etag);
|
||||||
|
$resource->execute();
|
||||||
|
|
||||||
return $this;
|
$this->content = $resource->getContent();
|
||||||
|
$this->url = $resource->getUrl();
|
||||||
|
|
||||||
|
return $resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -69,22 +77,30 @@ class Reader
|
|||||||
|
|
||||||
if (strpos($first_tag, '<feed') !== false) {
|
if (strpos($first_tag, '<feed') !== false) {
|
||||||
|
|
||||||
|
require_once __DIR__.'/Parsers/Atom.php';
|
||||||
return new Atom($this->content);
|
return new Atom($this->content);
|
||||||
}
|
}
|
||||||
else if (strpos($first_tag, '<rss ') !== false && strpos($first_tag, 'version="2.0"') !== false) {
|
else if (strpos($first_tag, '<rss') !== false &&
|
||||||
|
(strpos($first_tag, 'version="2.0"') !== false || strpos($first_tag, 'version=\'2.0\'') !== false)) {
|
||||||
|
|
||||||
|
require_once __DIR__.'/Parsers/Rss20.php';
|
||||||
return new Rss20($this->content);
|
return new Rss20($this->content);
|
||||||
}
|
}
|
||||||
else if (strpos($first_tag, '<rss ') !== false && strpos($first_tag, 'version="0.92"') !== false) {
|
else if (strpos($first_tag, '<rss') !== false &&
|
||||||
|
(strpos($first_tag, 'version="0.92"') !== false || strpos($first_tag, 'version=\'0.92\'') !== false)) {
|
||||||
|
|
||||||
|
require_once __DIR__.'/Parsers/Rss92.php';
|
||||||
return new Rss92($this->content);
|
return new Rss92($this->content);
|
||||||
}
|
}
|
||||||
else if (strpos($first_tag, '<rss ') !== false && strpos($first_tag, 'version="0.91"') !== false) {
|
else if (strpos($first_tag, '<rss') !== false &&
|
||||||
|
(strpos($first_tag, 'version="0.91"') !== false || strpos($first_tag, 'version=\'0.91\'') !== false)) {
|
||||||
|
|
||||||
|
require_once __DIR__.'/Parsers/Rss91.php';
|
||||||
return new Rss91($this->content);
|
return new Rss91($this->content);
|
||||||
}
|
}
|
||||||
else if (strpos($first_tag, '<rdf:') !== false && strpos($first_tag, 'xmlns="http://purl.org/rss/1.0/"') !== false) {
|
else if (strpos($first_tag, '<rdf:') !== false && strpos($first_tag, 'xmlns="http://purl.org/rss/1.0/"') !== false) {
|
||||||
|
|
||||||
|
require_once __DIR__.'/Parsers/Rss10.php';
|
||||||
return new Rss10($this->content);
|
return new Rss10($this->content);
|
||||||
}
|
}
|
||||||
else if ($discover === true) {
|
else if ($discover === true) {
|
||||||
|
166
miniflux/vendor/PicoFeed/RemoteResource.php
vendored
Normal file
166
miniflux/vendor/PicoFeed/RemoteResource.php
vendored
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PicoFeed;
|
||||||
|
|
||||||
|
class RemoteResource
|
||||||
|
{
|
||||||
|
public $user_agent;
|
||||||
|
public $timeout;
|
||||||
|
public $url;
|
||||||
|
public $etag;
|
||||||
|
public $last_modified;
|
||||||
|
public $is_modified = true;
|
||||||
|
public $content = '';
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($url, $timeout = 5, $user_agent = 'PicoFeed (https://github.com/fguillot/picoFeed)')
|
||||||
|
{
|
||||||
|
$this->url = $url;
|
||||||
|
$this->timeout = $timeout;
|
||||||
|
$this->user_agent = $user_agent;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setLastModified($last_modified)
|
||||||
|
{
|
||||||
|
$this->last_modified = $last_modified;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getLastModified()
|
||||||
|
{
|
||||||
|
return $this->last_modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setEtag($etag)
|
||||||
|
{
|
||||||
|
$this->etag = $etag;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getEtag()
|
||||||
|
{
|
||||||
|
return $this->etag;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getUrl()
|
||||||
|
{
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getContent()
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function isModified()
|
||||||
|
{
|
||||||
|
return $this->is_modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$response = $this->makeRequest();
|
||||||
|
|
||||||
|
$this->etag = isset($response['headers']['ETag']) ? $response['headers']['ETag'] : '';
|
||||||
|
$this->last_modified = isset($response['headers']['Last-Modified']) ? $response['headers']['Last-Modified'] : '';
|
||||||
|
|
||||||
|
if ($response['status'] == 304) {
|
||||||
|
|
||||||
|
$this->is_modified = false;
|
||||||
|
}
|
||||||
|
else if ($response['status'] == 301 || $response['status'] == 302) {
|
||||||
|
|
||||||
|
if (isset($response['headers']['Location'])) {
|
||||||
|
|
||||||
|
$this->url = $response['headers']['Location'];
|
||||||
|
}
|
||||||
|
else if (isset($response['headers']['location'])) {
|
||||||
|
|
||||||
|
$this->url = $response['headers']['location'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->execute();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$this->content = $response['body'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function makeRequest()
|
||||||
|
{
|
||||||
|
$http_code = 200;
|
||||||
|
$http_body = '';
|
||||||
|
$http_headers = array();
|
||||||
|
|
||||||
|
if (! function_exists('curl_init')) {
|
||||||
|
|
||||||
|
$http_body = @file_get_contents($this->url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$headers = array('Connection: close');
|
||||||
|
|
||||||
|
if ($this->etag) $headers[] = 'If-None-Match: '.$this->etag;
|
||||||
|
if ($this->last_modified) $headers[] = 'If-Modified-Since: '.$this->last_modified;
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $this->url);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
|
||||||
|
curl_setopt($ch, CURLOPT_USERAGENT, $this->user_agent);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||||
|
|
||||||
|
$http_response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$http_body = '';
|
||||||
|
$http_headers = array();
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$lines = explode("\r\n", $http_response);
|
||||||
|
$body_start = 0;
|
||||||
|
$i = 0;
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
|
||||||
|
if ($line === '') {
|
||||||
|
|
||||||
|
$body_start = $i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (($p = strpos($line, ':')) !== false) {
|
||||||
|
|
||||||
|
$key = substr($line, 0, $p);
|
||||||
|
$value = substr($line, $p + 1);
|
||||||
|
|
||||||
|
$http_headers[trim($key)] = trim($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$http_body = implode("\r\n", array_splice($lines, $i + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'status' => $http_code,
|
||||||
|
'body' => $http_body,
|
||||||
|
'headers' => $http_headers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
100
miniflux/vendor/PicoTools/Chrono.php
vendored
100
miniflux/vendor/PicoTools/Chrono.php
vendored
@ -1,100 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of picoTools.
|
|
||||||
*
|
|
||||||
* (c) Frédéric Guillot http://fredericguillot.com
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PicoTools;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chrono, tool for benchmarking
|
|
||||||
* Calculate the duration of your code
|
|
||||||
*
|
|
||||||
* @author Frédéric Guillot
|
|
||||||
*/
|
|
||||||
class Chrono
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Chronos values
|
|
||||||
*
|
|
||||||
* @access private
|
|
||||||
* @static
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $chronos = array();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a chrono
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @static
|
|
||||||
* @param string $name Chrono name
|
|
||||||
*/
|
|
||||||
public static function start($name)
|
|
||||||
{
|
|
||||||
self::$chronos[$name] = array(
|
|
||||||
'start' => microtime(true),
|
|
||||||
'finish' => 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop a chrono
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @static
|
|
||||||
* @param string $name Chrono name
|
|
||||||
*/
|
|
||||||
public static function stop($name)
|
|
||||||
{
|
|
||||||
if (! isset(self::$chronos[$name])) {
|
|
||||||
|
|
||||||
throw new \RuntimeException('Chrono not started!');
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$chronos[$name]['finish'] = microtime(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a duration of a chrono
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @static
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
public static function duration($name)
|
|
||||||
{
|
|
||||||
if (! isset(self::$chronos[$name])) {
|
|
||||||
|
|
||||||
throw new \RuntimeException('Chrono not started!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$chronos[$name]['finish'] - self::$chronos[$name]['start'];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show all durations
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
public static function show()
|
|
||||||
{
|
|
||||||
foreach (self::$chronos as $name => $values) {
|
|
||||||
|
|
||||||
echo $name.' = ';
|
|
||||||
echo round($values['finish'] - $values['start'], 2).'s';
|
|
||||||
echo PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
36
miniflux/vendor/PicoTools/Crypto.php
vendored
36
miniflux/vendor/PicoTools/Crypto.php
vendored
@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of PicoTools.
|
|
||||||
*
|
|
||||||
* (c) Frédéric Guillot http://fredericguillot.com
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PicoTools\Crypto;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random token
|
|
||||||
*
|
|
||||||
* @return string Random token
|
|
||||||
*/
|
|
||||||
function token()
|
|
||||||
{
|
|
||||||
return hash('sha256', uniqid('', true).microtime());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a signature with a key
|
|
||||||
*
|
|
||||||
* @param string $data Data
|
|
||||||
* @param string $key Key
|
|
||||||
* @return string Signature
|
|
||||||
*/
|
|
||||||
function signature($data, $key)
|
|
||||||
{
|
|
||||||
return hash_hmac('sha256', $data, $key);
|
|
||||||
}
|
|
21
miniflux/vendor/PicoTools/Helper.php
vendored
21
miniflux/vendor/PicoTools/Helper.php
vendored
@ -49,6 +49,15 @@ function flash_error($html)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
function get_host_from_url($url)
|
||||||
{
|
{
|
||||||
return escape(parse_url($url, PHP_URL_HOST));
|
return escape(parse_url($url, PHP_URL_HOST));
|
||||||
@ -185,6 +194,18 @@ function form_password($name, $values = array(), array $errors = array(), array
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function form_email($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
|
{
|
||||||
|
$class .= error_class($errors, $name);
|
||||||
|
|
||||||
|
$html = '<input type="email" name="'.$name.'" id="form-'.$name.'" '.form_value($values, $name).' class="'.$class.'" ';
|
||||||
|
$html .= implode(' ', $attributes).'/>';
|
||||||
|
$html .= error_list($errors, $name);
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function form_textarea($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
function form_textarea($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||||
{
|
{
|
||||||
$class .= error_class($errors, $name);
|
$class .= error_class($errors, $name);
|
||||||
|
243
miniflux/vendor/PicoTools/Pixtag.php
vendored
243
miniflux/vendor/PicoTools/Pixtag.php
vendored
@ -1,243 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of picoTools.
|
|
||||||
*
|
|
||||||
* (c) Frédéric Guillot http://fredericguillot.com
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PicoTools;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper around exiv2 command line utility
|
|
||||||
* You can write and read IPTC, XMP and EXIF metadata inside a picture
|
|
||||||
*
|
|
||||||
* @author Frédéric Guillot
|
|
||||||
*/
|
|
||||||
class Pixtag implements \ArrayAccess, \Iterator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Filename
|
|
||||||
*
|
|
||||||
* @access private
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $filename;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Container
|
|
||||||
*
|
|
||||||
* @access private
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $container = array();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @param string $filename Path to the picture
|
|
||||||
*/
|
|
||||||
public function __construct($filename)
|
|
||||||
{
|
|
||||||
$this->filename = $filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read metadata from the picture
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
*/
|
|
||||||
public function read()
|
|
||||||
{
|
|
||||||
$c = new Command('exiv2 -PIEXkt '.$this->filename);
|
|
||||||
$c->execute();
|
|
||||||
$this->parse($c->getStdout());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse metadata bloc from exiv2 output command
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @param string $data Raw command output of exiv2
|
|
||||||
*/
|
|
||||||
public function parse($data)
|
|
||||||
{
|
|
||||||
$lines = explode("\n", trim($data));
|
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
|
|
||||||
$results = preg_split('/ /', $line, -1, PREG_SPLIT_OFFSET_CAPTURE);
|
|
||||||
|
|
||||||
if (isset($results[0][0])) {
|
|
||||||
|
|
||||||
$key = $results[0][0];
|
|
||||||
$value = '';
|
|
||||||
|
|
||||||
for ($i = 1, $ilen = count($results); $i < $ilen; ++$i) {
|
|
||||||
|
|
||||||
if ($results[$i][0] !== '') {
|
|
||||||
|
|
||||||
$value = substr($line, $results[$i][1]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($value === '(Binary value suppressed)') {
|
|
||||||
|
|
||||||
$value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->container[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write metadata to the picture
|
|
||||||
* This method erase all keys and then add them to the picture
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
*/
|
|
||||||
public function write()
|
|
||||||
{
|
|
||||||
$commands = array();
|
|
||||||
|
|
||||||
foreach ($this->container as $key => $value) {
|
|
||||||
|
|
||||||
$commands[] = sprintf('-M "del %s"', $key);
|
|
||||||
$commands[] = sprintf('-M "add %s %s"', $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$c = new Command(sprintf(
|
|
||||||
'exiv2 %s %s',
|
|
||||||
implode(' ', $commands),
|
|
||||||
$this->filename
|
|
||||||
));
|
|
||||||
|
|
||||||
$c->execute();
|
|
||||||
|
|
||||||
if ($c->getReturnValue() !== 0) {
|
|
||||||
|
|
||||||
throw new \RuntimeException('Unable to write metadata');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a metadata
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @param string $offset Key name, see exiv2 documentation for keys list
|
|
||||||
* @param string $value Key value
|
|
||||||
*/
|
|
||||||
public function offsetSet($offset, $value)
|
|
||||||
{
|
|
||||||
$this->container[$offset] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a key exists
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @param string $offset Key name, see exiv2 documentation for keys list
|
|
||||||
* @return boolean True if the key exists
|
|
||||||
*/
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
return isset($this->container[$offset]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a metadata
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @param string $offset Key name, see exiv2 documentation for keys list
|
|
||||||
*/
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
unset($this->container[$offset]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a metadata
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @param string $offset Key name, see exiv2 documentation for keys list
|
|
||||||
* @return string Key value
|
|
||||||
*/
|
|
||||||
public function offsetGet($offset)
|
|
||||||
{
|
|
||||||
return isset($this->container[$offset]) ? $this->container[$offset] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the position of the container
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
*/
|
|
||||||
public function rewind()
|
|
||||||
{
|
|
||||||
reset($this->container);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @return string Current value
|
|
||||||
*/
|
|
||||||
public function current()
|
|
||||||
{
|
|
||||||
return current($this->container);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @return string Current key
|
|
||||||
*/
|
|
||||||
public function key()
|
|
||||||
{
|
|
||||||
return key($this->container);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Next
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
*/
|
|
||||||
public function next()
|
|
||||||
{
|
|
||||||
next($this->container);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valid
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @return boolean True if the current key is valid
|
|
||||||
*/
|
|
||||||
public function valid()
|
|
||||||
{
|
|
||||||
return isset($this->container[key($this->container)]);
|
|
||||||
}
|
|
||||||
}
|
|
18
miniflux/vendor/PicoTools/Translator.php
vendored
18
miniflux/vendor/PicoTools/Translator.php
vendored
@ -19,7 +19,7 @@ namespace PicoTools\Translator {
|
|||||||
$args = \func_get_args();
|
$args = \func_get_args();
|
||||||
|
|
||||||
\array_shift($args);
|
\array_shift($args);
|
||||||
\array_unshift($args, get($identifier));
|
\array_unshift($args, get($identifier, $identifier));
|
||||||
|
|
||||||
return \call_user_func_array(
|
return \call_user_func_array(
|
||||||
'sprintf',
|
'sprintf',
|
||||||
@ -61,6 +61,12 @@ namespace PicoTools\Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function datetime($format, $timestamp)
|
||||||
|
{
|
||||||
|
return strftime($format, $timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function get($identifier, $default = '')
|
function get($identifier, $default = '')
|
||||||
{
|
{
|
||||||
$locales = container();
|
$locales = container();
|
||||||
@ -78,6 +84,8 @@ namespace PicoTools\Translator {
|
|||||||
|
|
||||||
function load($language)
|
function load($language)
|
||||||
{
|
{
|
||||||
|
setlocale(LC_TIME, $language.'.UTF-8');
|
||||||
|
|
||||||
$path = PATH.$language;
|
$path = PATH.$language;
|
||||||
$locales = array();
|
$locales = array();
|
||||||
|
|
||||||
@ -102,7 +110,7 @@ namespace PicoTools\Translator {
|
|||||||
{
|
{
|
||||||
static $values = array();
|
static $values = array();
|
||||||
|
|
||||||
if ($locales) {
|
if ($locales !== null) {
|
||||||
|
|
||||||
$values = $locales;
|
$values = $locales;
|
||||||
}
|
}
|
||||||
@ -118,4 +126,10 @@ namespace {
|
|||||||
|
|
||||||
return call_user_func_array('\PicoTools\Translator\translate', func_get_args());
|
return call_user_func_array('\PicoTools\Translator\translate', func_get_args());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function dt() {
|
||||||
|
|
||||||
|
return call_user_func_array('\PicoTools\Translator\datetime', func_get_args());
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user