add 1.10 source
339
source/LICENSE
Normal file
|
@ -0,0 +1,339 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
41
source/README.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
Tiny Tiny RSS
|
||||
=============
|
||||
|
||||
Web-based news feed aggregator, designed to allow you to read news from
|
||||
any location, while feeling as close to a real desktop application as possible.
|
||||
|
||||
http://tt-rss.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright (c) 2005 Andrew Dolgov (unless explicitly stated otherwise).
|
||||
|
||||
Uses Silk icons by Mark James: http://www.famfamfam.com/lab/icons/silk/
|
||||
|
||||
## Requirements
|
||||
|
||||
* Compatible web browser (http://tt-rss.org/wiki/CompatibleBrowsers)
|
||||
* Web server, for example Apache
|
||||
* PHP (with support for mbstring functions)
|
||||
* PostgreSQL (tested on 8.3) or MySQL (InnoDB and version 4.1+ required)
|
||||
|
||||
## Installation Notes
|
||||
|
||||
http://tt-rss.org/wiki/InstallationNotes
|
||||
|
||||
## See also
|
||||
|
||||
* FAQ: http://tt-rss.org/wiki/FrequentlyAskedQuestions
|
||||
* Forum: http://tt-rss.org/forum
|
||||
* Wiki: http://tt-rss.org/wiki/WikiStart
|
74
source/api/index.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
require_once "../config.php";
|
||||
|
||||
set_include_path(dirname(__FILE__) . PATH_SEPARATOR .
|
||||
dirname(dirname(__FILE__)) . PATH_SEPARATOR .
|
||||
dirname(dirname(__FILE__)) . "/include" . PATH_SEPARATOR .
|
||||
get_include_path());
|
||||
|
||||
chdir("..");
|
||||
|
||||
define('TTRSS_SESSION_NAME', 'ttrss_api_sid');
|
||||
define('NO_SESSION_AUTOSTART', true);
|
||||
|
||||
require_once "autoload.php";
|
||||
require_once "db.php";
|
||||
require_once "db-prefs.php";
|
||||
require_once "functions.php";
|
||||
require_once "sessions.php";
|
||||
|
||||
ini_set("session.gc_maxlifetime", 86400);
|
||||
|
||||
define('AUTH_DISABLE_OTP', true);
|
||||
|
||||
if (defined('ENABLE_GZIP_OUTPUT') && ENABLE_GZIP_OUTPUT &&
|
||||
function_exists("ob_gzhandler")) {
|
||||
|
||||
ob_start("ob_gzhandler");
|
||||
} else {
|
||||
ob_start();
|
||||
}
|
||||
|
||||
$input = file_get_contents("php://input");
|
||||
|
||||
if (defined('_API_DEBUG_HTTP_ENABLED') && _API_DEBUG_HTTP_ENABLED) {
|
||||
// Override $_REQUEST with JSON-encoded data if available
|
||||
// fallback on HTTP parameters
|
||||
if ($input) {
|
||||
$input = json_decode($input, true);
|
||||
if ($input) $_REQUEST = $input;
|
||||
}
|
||||
} else {
|
||||
// Accept JSON only
|
||||
$input = json_decode($input, true);
|
||||
$_REQUEST = $input;
|
||||
}
|
||||
|
||||
if ($_REQUEST["sid"]) {
|
||||
session_id($_REQUEST["sid"]);
|
||||
@session_start();
|
||||
} else if (defined('_API_DEBUG_HTTP_ENABLED')) {
|
||||
@session_start();
|
||||
}
|
||||
|
||||
if (!init_plugins()) return;
|
||||
|
||||
$method = strtolower($_REQUEST["op"]);
|
||||
|
||||
$handler = new API($_REQUEST);
|
||||
|
||||
if ($handler->before($method)) {
|
||||
if ($method && method_exists($handler, $method)) {
|
||||
$handler->$method();
|
||||
} else if (method_exists($handler, 'index')) {
|
||||
$handler->index($method);
|
||||
}
|
||||
$handler->after();
|
||||
}
|
||||
|
||||
header("Api-Content-Length: " . ob_get_length());
|
||||
|
||||
ob_end_flush();
|
||||
?>
|
51
source/atom-to-html.xsl
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsl:stylesheet
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||
|
||||
<xsl:output method="html"/>
|
||||
|
||||
<xsl:template match="/atom:feed">
|
||||
<html>
|
||||
<head>
|
||||
<title><xsl:value-of select="atom:title"/></title>
|
||||
<link rel="stylesheet" type="text/css" href="css/utility.css"/>
|
||||
<script language="javascript" src="lib/xsl_mop-up.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="go_decoding()">
|
||||
|
||||
<div id="cometestme" style="display:none;">
|
||||
<xsl:text disable-output-escaping="yes">&amp;</xsl:text>
|
||||
</div>
|
||||
|
||||
<div class="rss">
|
||||
|
||||
<h1><xsl:value-of select="atom:title"/></h1>
|
||||
|
||||
<p class="description">This feed has been exported from
|
||||
<a target="_new" class="extlink" href="http://tt-rss.org">Tiny Tiny RSS</a>.
|
||||
It contains the following items:</p>
|
||||
|
||||
<xsl:for-each select="atom:entry">
|
||||
<h2><a target="_new" href="{atom:link/@href}"><xsl:value-of select="atom:title"/></a></h2>
|
||||
|
||||
<div name="decodeme" class="content">
|
||||
<xsl:value-of select="atom:content" disable-output-escaping="yes"/>
|
||||
</div>
|
||||
|
||||
<xsl:if test="enclosure">
|
||||
<p><a href="{enclosure/@url}">Extra...</a></p>
|
||||
</xsl:if>
|
||||
|
||||
|
||||
</xsl:for-each>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
154
source/backend.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
set_include_path(dirname(__FILE__) ."/include" . PATH_SEPARATOR .
|
||||
get_include_path());
|
||||
|
||||
/* remove ill effects of magic quotes */
|
||||
|
||||
if (get_magic_quotes_gpc()) {
|
||||
function stripslashes_deep($value) {
|
||||
$value = is_array($value) ?
|
||||
array_map('stripslashes_deep', $value) : stripslashes($value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
$_POST = array_map('stripslashes_deep', $_POST);
|
||||
$_GET = array_map('stripslashes_deep', $_GET);
|
||||
$_COOKIE = array_map('stripslashes_deep', $_COOKIE);
|
||||
$_REQUEST = array_map('stripslashes_deep', $_REQUEST);
|
||||
}
|
||||
|
||||
$op = $_REQUEST["op"];
|
||||
@$method = $_REQUEST['subop'] ? $_REQUEST['subop'] : $_REQUEST["method"];
|
||||
|
||||
if (!$method)
|
||||
$method = 'index';
|
||||
else
|
||||
$method = strtolower($method);
|
||||
|
||||
/* Public calls compatibility shim */
|
||||
|
||||
$public_calls = array("globalUpdateFeeds", "rss", "getUnread", "getProfiles", "share",
|
||||
"fbexport", "logout", "pubsub");
|
||||
|
||||
if (array_search($op, $public_calls) !== false) {
|
||||
header("Location: public.php?" . $_SERVER['QUERY_STRING']);
|
||||
return;
|
||||
}
|
||||
|
||||
@$csrf_token = $_REQUEST['csrf_token'];
|
||||
|
||||
require_once "autoload.php";
|
||||
require_once "sessions.php";
|
||||
require_once "functions.php";
|
||||
require_once "config.php";
|
||||
require_once "db.php";
|
||||
require_once "db-prefs.php";
|
||||
|
||||
startup_gettext();
|
||||
|
||||
$script_started = microtime(true);
|
||||
|
||||
if (!init_plugins()) return;
|
||||
|
||||
header("Content-Type: text/json; charset=utf-8");
|
||||
|
||||
if (ENABLE_GZIP_OUTPUT && function_exists("ob_gzhandler")) {
|
||||
ob_start("ob_gzhandler");
|
||||
}
|
||||
|
||||
if (SINGLE_USER_MODE) {
|
||||
authenticate_user( "admin", null);
|
||||
}
|
||||
|
||||
if ($_SESSION["uid"]) {
|
||||
if (!validate_session()) {
|
||||
header("Content-Type: text/json");
|
||||
print json_encode(array("error" => array("code" => 6)));
|
||||
return;
|
||||
}
|
||||
load_user_plugins( $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
$purge_intervals = array(
|
||||
0 => __("Use default"),
|
||||
-1 => __("Never purge"),
|
||||
5 => __("1 week old"),
|
||||
14 => __("2 weeks old"),
|
||||
31 => __("1 month old"),
|
||||
60 => __("2 months old"),
|
||||
90 => __("3 months old"));
|
||||
|
||||
$update_intervals = array(
|
||||
0 => __("Default interval"),
|
||||
-1 => __("Disable updates"),
|
||||
15 => __("Each 15 minutes"),
|
||||
30 => __("Each 30 minutes"),
|
||||
60 => __("Hourly"),
|
||||
240 => __("Each 4 hours"),
|
||||
720 => __("Each 12 hours"),
|
||||
1440 => __("Daily"),
|
||||
10080 => __("Weekly"));
|
||||
|
||||
$update_intervals_nodefault = array(
|
||||
-1 => __("Disable updates"),
|
||||
15 => __("Each 15 minutes"),
|
||||
30 => __("Each 30 minutes"),
|
||||
60 => __("Hourly"),
|
||||
240 => __("Each 4 hours"),
|
||||
720 => __("Each 12 hours"),
|
||||
1440 => __("Daily"),
|
||||
10080 => __("Weekly"));
|
||||
|
||||
$access_level_names = array(
|
||||
0 => __("User"),
|
||||
5 => __("Power User"),
|
||||
10 => __("Administrator"));
|
||||
|
||||
#$error = sanity_check();
|
||||
|
||||
#if ($error['code'] != 0 && $op != "logout") {
|
||||
# print json_encode(array("error" => $error));
|
||||
# return;
|
||||
#}
|
||||
|
||||
$op = str_replace("-", "_", $op);
|
||||
|
||||
$override = PluginHost::getInstance()->lookup_handler($op, $method);
|
||||
|
||||
if (class_exists($op) || $override) {
|
||||
|
||||
if ($override) {
|
||||
$handler = $override;
|
||||
} else {
|
||||
$handler = new $op($_REQUEST);
|
||||
}
|
||||
|
||||
if ($handler && implements_interface($handler, 'IHandler')) {
|
||||
if (validate_csrf($csrf_token) || $handler->csrf_ignore($method)) {
|
||||
if ($handler->before($method)) {
|
||||
if ($method && method_exists($handler, $method)) {
|
||||
$handler->$method();
|
||||
} else {
|
||||
if (method_exists($handler, "catchall")) {
|
||||
$handler->catchall($method);
|
||||
}
|
||||
}
|
||||
$handler->after();
|
||||
return;
|
||||
} else {
|
||||
header("Content-Type: text/json");
|
||||
print json_encode(array("error" => array("code" => 6)));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
header("Content-Type: text/json");
|
||||
print json_encode(array("error" => array("code" => 6)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header("Content-Type: text/json");
|
||||
print json_encode(array("error" => array("code" => 7)));
|
||||
|
||||
?>
|
2
source/cache/.htaccess
vendored
Executable file
|
@ -0,0 +1,2 @@
|
|||
Order deny,allow
|
||||
Deny from all
|
2
source/classes/.htaccess
Normal file
|
@ -0,0 +1,2 @@
|
|||
Order deny,allow
|
||||
Deny from all
|
784
source/classes/api.php
Normal file
|
@ -0,0 +1,784 @@
|
|||
<?php
|
||||
|
||||
class API extends Handler {
|
||||
|
||||
const API_LEVEL = 7;
|
||||
|
||||
const STATUS_OK = 0;
|
||||
const STATUS_ERR = 1;
|
||||
|
||||
private $seq;
|
||||
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
header("Content-Type: text/json");
|
||||
|
||||
if (!$_SESSION["uid"] && $method != "login" && $method != "isloggedin") {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'NOT_LOGGED_IN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($_SESSION["uid"] && $method != "logout" && !get_pref('ENABLE_API_ACCESS')) {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'API_DISABLED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->seq = (int) $_REQUEST['seq'];
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function wrap($status, $reply) {
|
||||
print json_encode(array("seq" => $this->seq,
|
||||
"status" => $status,
|
||||
"content" => $reply));
|
||||
}
|
||||
|
||||
function getVersion() {
|
||||
$rv = array("version" => VERSION);
|
||||
$this->wrap(self::STATUS_OK, $rv);
|
||||
}
|
||||
|
||||
function getApiLevel() {
|
||||
$rv = array("level" => self::API_LEVEL);
|
||||
$this->wrap(self::STATUS_OK, $rv);
|
||||
}
|
||||
|
||||
function login() {
|
||||
@session_destroy();
|
||||
@session_start();
|
||||
|
||||
$login = $this->dbh->escape_string($_REQUEST["user"]);
|
||||
$password = $_REQUEST["password"];
|
||||
$password_base64 = base64_decode($_REQUEST["password"]);
|
||||
|
||||
if (SINGLE_USER_MODE) $login = "admin";
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_users WHERE login = '$login'");
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$uid = $this->dbh->fetch_result($result, 0, "id");
|
||||
} else {
|
||||
$uid = 0;
|
||||
}
|
||||
|
||||
if (!$uid) {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => "LOGIN_ERROR"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_pref("ENABLE_API_ACCESS", $uid)) {
|
||||
if (authenticate_user($login, $password)) { // try login with normal password
|
||||
$this->wrap(self::STATUS_OK, array("session_id" => session_id(),
|
||||
"api_level" => self::API_LEVEL));
|
||||
} else if (authenticate_user($login, $password_base64)) { // else try with base64_decoded password
|
||||
$this->wrap(self::STATUS_OK, array("session_id" => session_id(),
|
||||
"api_level" => self::API_LEVEL));
|
||||
} else { // else we are not logged in
|
||||
$this->wrap(self::STATUS_ERR, array("error" => "LOGIN_ERROR"));
|
||||
}
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => "API_DISABLED"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function logout() {
|
||||
logout_user();
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
$this->wrap(self::STATUS_OK, array("status" => $_SESSION["uid"] != ''));
|
||||
}
|
||||
|
||||
function getUnread() {
|
||||
$feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]);
|
||||
$is_cat = $this->dbh->escape_string($_REQUEST["is_cat"]);
|
||||
|
||||
if ($feed_id) {
|
||||
$this->wrap(self::STATUS_OK, array("unread" => getFeedUnread($feed_id, $is_cat)));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_OK, array("unread" => getGlobalUnread()));
|
||||
}
|
||||
}
|
||||
|
||||
/* Method added for ttrss-reader for Android */
|
||||
function getCounters() {
|
||||
$this->wrap(self::STATUS_OK, getAllCounters());
|
||||
}
|
||||
|
||||
function getFeeds() {
|
||||
$cat_id = $this->dbh->escape_string($_REQUEST["cat_id"]);
|
||||
$unread_only = sql_bool_to_bool($_REQUEST["unread_only"]);
|
||||
$limit = (int) $this->dbh->escape_string($_REQUEST["limit"]);
|
||||
$offset = (int) $this->dbh->escape_string($_REQUEST["offset"]);
|
||||
$include_nested = sql_bool_to_bool($_REQUEST["include_nested"]);
|
||||
|
||||
$feeds = $this->api_get_feeds($cat_id, $unread_only, $limit, $offset, $include_nested);
|
||||
|
||||
$this->wrap(self::STATUS_OK, $feeds);
|
||||
}
|
||||
|
||||
function getCategories() {
|
||||
$unread_only = sql_bool_to_bool($_REQUEST["unread_only"]);
|
||||
$enable_nested = sql_bool_to_bool($_REQUEST["enable_nested"]);
|
||||
$include_empty = sql_bool_to_bool($_REQUEST['include_empty']);
|
||||
|
||||
// TODO do not return empty categories, return Uncategorized and standard virtual cats
|
||||
|
||||
if ($enable_nested)
|
||||
$nested_qpart = "parent_cat IS NULL";
|
||||
else
|
||||
$nested_qpart = "true";
|
||||
|
||||
$result = $this->dbh->query("SELECT
|
||||
id, title, order_id, (SELECT COUNT(id) FROM
|
||||
ttrss_feeds WHERE
|
||||
ttrss_feed_categories.id IS NOT NULL AND cat_id = ttrss_feed_categories.id) AS num_feeds,
|
||||
(SELECT COUNT(id) FROM
|
||||
ttrss_feed_categories AS c2 WHERE
|
||||
c2.parent_cat = ttrss_feed_categories.id) AS num_cats
|
||||
FROM ttrss_feed_categories
|
||||
WHERE $nested_qpart AND owner_uid = " .
|
||||
$_SESSION["uid"]);
|
||||
|
||||
$cats = array();
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
if ($include_empty || $line["num_feeds"] > 0 || $line["num_cats"] > 0) {
|
||||
$unread = getFeedUnread($line["id"], true);
|
||||
|
||||
if ($enable_nested)
|
||||
$unread += getCategoryChildrenUnread($line["id"]);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
array_push($cats, array("id" => $line["id"],
|
||||
"title" => $line["title"],
|
||||
"unread" => $unread,
|
||||
"order_id" => (int) $line["order_id"],
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array(-2,-1,0) as $cat_id) {
|
||||
if ($include_empty || !$this->isCategoryEmpty($cat_id)) {
|
||||
$unread = getFeedUnread($cat_id, true);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
array_push($cats, array("id" => $cat_id,
|
||||
"title" => getCategoryTitle($cat_id),
|
||||
"unread" => $unread));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, $cats);
|
||||
}
|
||||
|
||||
function getHeadlines() {
|
||||
$feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]);
|
||||
if ($feed_id != "") {
|
||||
|
||||
$limit = (int)$this->dbh->escape_string($_REQUEST["limit"]);
|
||||
|
||||
if (!$limit || $limit >= 200) $limit = 200;
|
||||
|
||||
$offset = (int)$this->dbh->escape_string($_REQUEST["skip"]);
|
||||
$filter = $this->dbh->escape_string($_REQUEST["filter"]);
|
||||
$is_cat = sql_bool_to_bool($_REQUEST["is_cat"]);
|
||||
$show_excerpt = sql_bool_to_bool($_REQUEST["show_excerpt"]);
|
||||
$show_content = sql_bool_to_bool($_REQUEST["show_content"]);
|
||||
/* all_articles, unread, adaptive, marked, updated */
|
||||
$view_mode = $this->dbh->escape_string($_REQUEST["view_mode"]);
|
||||
$include_attachments = sql_bool_to_bool($_REQUEST["include_attachments"]);
|
||||
$since_id = (int)$this->dbh->escape_string($_REQUEST["since_id"]);
|
||||
$include_nested = sql_bool_to_bool($_REQUEST["include_nested"]);
|
||||
$sanitize_content = !isset($_REQUEST["sanitize"]) ||
|
||||
sql_bool_to_bool($_REQUEST["sanitize"]);
|
||||
|
||||
$override_order = false;
|
||||
switch ($_REQUEST["order_by"]) {
|
||||
case "date_reverse":
|
||||
$override_order = "score DESC, date_entered, updated";
|
||||
break;
|
||||
case "feed_dates":
|
||||
$override_order = "updated DESC";
|
||||
break;
|
||||
}
|
||||
|
||||
/* do not rely on params below */
|
||||
|
||||
$search = $this->dbh->escape_string($_REQUEST["search"]);
|
||||
$search_mode = $this->dbh->escape_string($_REQUEST["search_mode"]);
|
||||
|
||||
$headlines = $this->api_get_headlines($feed_id, $limit, $offset,
|
||||
$filter, $is_cat, $show_excerpt, $show_content, $view_mode, $override_order,
|
||||
$include_attachments, $since_id, $search, $search_mode,
|
||||
$include_nested, $sanitize_content);
|
||||
|
||||
$this->wrap(self::STATUS_OK, $headlines);
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
|
||||
}
|
||||
}
|
||||
|
||||
function updateArticle() {
|
||||
$article_ids = array_filter(explode(",", $this->dbh->escape_string($_REQUEST["article_ids"])), is_numeric);
|
||||
$mode = (int) $this->dbh->escape_string($_REQUEST["mode"]);
|
||||
$data = $this->dbh->escape_string($_REQUEST["data"]);
|
||||
$field_raw = (int)$this->dbh->escape_string($_REQUEST["field"]);
|
||||
|
||||
$field = "";
|
||||
$set_to = "";
|
||||
|
||||
switch ($field_raw) {
|
||||
case 0:
|
||||
$field = "marked";
|
||||
$additional_fields = ",last_marked = NOW()";
|
||||
break;
|
||||
case 1:
|
||||
$field = "published";
|
||||
$additional_fields = ",last_published = NOW()";
|
||||
break;
|
||||
case 2:
|
||||
$field = "unread";
|
||||
$additional_fields = ",last_read = NOW()";
|
||||
break;
|
||||
case 3:
|
||||
$field = "note";
|
||||
};
|
||||
|
||||
switch ($mode) {
|
||||
case 1:
|
||||
$set_to = "true";
|
||||
break;
|
||||
case 0:
|
||||
$set_to = "false";
|
||||
break;
|
||||
case 2:
|
||||
$set_to = "NOT $field";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($field == "note") $set_to = "'$data'";
|
||||
|
||||
if ($field && $set_to && count($article_ids) > 0) {
|
||||
|
||||
$article_ids = join(", ", $article_ids);
|
||||
|
||||
$result = $this->dbh->query("UPDATE ttrss_user_entries SET $field = $set_to $additional_fields WHERE ref_id IN ($article_ids) AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
$num_updated = $this->dbh->affected_rows($result);
|
||||
|
||||
if ($num_updated > 0 && $field == "unread") {
|
||||
$result = $this->dbh->query("SELECT DISTINCT feed_id FROM ttrss_user_entries
|
||||
WHERE ref_id IN ($article_ids)");
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
ccache_update($line["feed_id"], $_SESSION["uid"]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($num_updated > 0 && $field == "published") {
|
||||
if (PUBSUBHUBBUB_HUB) {
|
||||
$rss_link = get_self_url_prefix() .
|
||||
"/public.php?op=rss&id=-2&key=" .
|
||||
get_feed_access_key(-2, false);
|
||||
|
||||
$p = new Publisher(PUBSUBHUBBUB_HUB);
|
||||
$pubsub_result = $p->publish_update($rss_link);
|
||||
}
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK",
|
||||
"updated" => $num_updated));
|
||||
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getArticle() {
|
||||
|
||||
$article_id = join(",", array_filter(explode(",", $this->dbh->escape_string($_REQUEST["article_id"])), is_numeric));
|
||||
|
||||
if ($article_id) {
|
||||
|
||||
$query = "SELECT id,title,link,content,feed_id,comments,int_id,
|
||||
marked,unread,published,score,
|
||||
".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
|
||||
author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title
|
||||
FROM ttrss_entries,ttrss_user_entries
|
||||
WHERE id IN ($article_id) AND ref_id = id AND owner_uid = " .
|
||||
$_SESSION["uid"] ;
|
||||
|
||||
$result = $this->dbh->query($query);
|
||||
|
||||
$articles = array();
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
|
||||
$attachments = get_article_enclosures($line['id']);
|
||||
|
||||
$article = array(
|
||||
"id" => $line["id"],
|
||||
"title" => $line["title"],
|
||||
"link" => $line["link"],
|
||||
"labels" => get_article_labels($line['id']),
|
||||
"unread" => sql_bool_to_bool($line["unread"]),
|
||||
"marked" => sql_bool_to_bool($line["marked"]),
|
||||
"published" => sql_bool_to_bool($line["published"]),
|
||||
"comments" => $line["comments"],
|
||||
"author" => $line["author"],
|
||||
"updated" => (int) strtotime($line["updated"]),
|
||||
"content" => $line["content"],
|
||||
"feed_id" => $line["feed_id"],
|
||||
"attachments" => $attachments,
|
||||
"score" => (int)$line["score"],
|
||||
"feed_title" => $line["feed_title"]
|
||||
);
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) {
|
||||
$article = $p->hook_render_article_api(array("article" => $article));
|
||||
}
|
||||
|
||||
|
||||
array_push($articles, $article);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, $articles);
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
$config = array(
|
||||
"icons_dir" => ICONS_DIR,
|
||||
"icons_url" => ICONS_URL);
|
||||
|
||||
$config["daemon_is_running"] = file_is_locked("update_daemon.lock");
|
||||
|
||||
$result = $this->dbh->query("SELECT COUNT(*) AS cf FROM
|
||||
ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
$num_feeds = $this->dbh->fetch_result($result, 0, "cf");
|
||||
|
||||
$config["num_feeds"] = (int)$num_feeds;
|
||||
|
||||
$this->wrap(self::STATUS_OK, $config);
|
||||
}
|
||||
|
||||
function updateFeed() {
|
||||
require_once "include/rssfuncs.php";
|
||||
|
||||
$feed_id = (int) $this->dbh->escape_string($_REQUEST["feed_id"]);
|
||||
|
||||
update_rss_feed($feed_id, true);
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
}
|
||||
|
||||
function catchupFeed() {
|
||||
$feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]);
|
||||
$is_cat = $this->dbh->escape_string($_REQUEST["is_cat"]);
|
||||
|
||||
catchup_feed($feed_id, $is_cat);
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
}
|
||||
|
||||
function getPref() {
|
||||
$pref_name = $this->dbh->escape_string($_REQUEST["pref_name"]);
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("value" => get_pref($pref_name)));
|
||||
}
|
||||
|
||||
function getLabels() {
|
||||
//$article_ids = array_filter(explode(",", $this->dbh->escape_string($_REQUEST["article_ids"])), is_numeric);
|
||||
|
||||
$article_id = (int)$_REQUEST['article_id'];
|
||||
|
||||
$rv = array();
|
||||
|
||||
$result = $this->dbh->query("SELECT id, caption, fg_color, bg_color
|
||||
FROM ttrss_labels2
|
||||
WHERE owner_uid = '".$_SESSION['uid']."' ORDER BY caption");
|
||||
|
||||
if ($article_id)
|
||||
$article_labels = get_article_labels($article_id);
|
||||
else
|
||||
$article_labels = array();
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
|
||||
$checked = false;
|
||||
foreach ($article_labels as $al) {
|
||||
if ($al[0] == $line['id']) {
|
||||
$checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
array_push($rv, array(
|
||||
"id" => (int)label_to_feed_id($line['id']),
|
||||
"caption" => $line['caption'],
|
||||
"fg_color" => $line['fg_color'],
|
||||
"bg_color" => $line['bg_color'],
|
||||
"checked" => $checked));
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, $rv);
|
||||
}
|
||||
|
||||
function setArticleLabel() {
|
||||
|
||||
$article_ids = array_filter(explode(",", $this->dbh->escape_string($_REQUEST["article_ids"])), is_numeric);
|
||||
$label_id = (int) $this->dbh->escape_string($_REQUEST['label_id']);
|
||||
$assign = (bool) $this->dbh->escape_string($_REQUEST['assign']) == "true";
|
||||
|
||||
$label = $this->dbh->escape_string(label_find_caption(
|
||||
$label_id, $_SESSION["uid"]));
|
||||
|
||||
$num_updated = 0;
|
||||
|
||||
if ($label) {
|
||||
|
||||
foreach ($article_ids as $id) {
|
||||
|
||||
if ($assign)
|
||||
label_add_article($id, $label, $_SESSION["uid"]);
|
||||
else
|
||||
label_remove_article($id, $label, $_SESSION["uid"]);
|
||||
|
||||
++$num_updated;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK",
|
||||
"updated" => $num_updated));
|
||||
|
||||
}
|
||||
|
||||
function index($method) {
|
||||
$plugin = PluginHost::getInstance()->get_api_method(strtolower($method));
|
||||
|
||||
if ($plugin && method_exists($plugin, $method)) {
|
||||
$reply = $plugin->$method();
|
||||
|
||||
$this->wrap($reply[0], $reply[1]);
|
||||
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'UNKNOWN_METHOD', "method" => $method));
|
||||
}
|
||||
}
|
||||
|
||||
function shareToPublished() {
|
||||
$title = $this->dbh->escape_string(strip_tags($_REQUEST["title"]));
|
||||
$url = $this->dbh->escape_string(strip_tags($_REQUEST["url"]));
|
||||
$content = $this->dbh->escape_string(strip_tags($_REQUEST["content"]));
|
||||
|
||||
if (Article::create_published_article($title, $url, $content, "", $_SESSION["uid"])) {
|
||||
$this->wrap(self::STATUS_OK, array("status" => 'OK'));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'Publishing failed'));
|
||||
}
|
||||
}
|
||||
|
||||
static function api_get_feeds($cat_id, $unread_only, $limit, $offset, $include_nested = false) {
|
||||
|
||||
$feeds = array();
|
||||
|
||||
/* Labels */
|
||||
|
||||
if ($cat_id == -4 || $cat_id == -2) {
|
||||
$counters = getLabelCounters(true);
|
||||
|
||||
foreach (array_values($counters) as $cv) {
|
||||
|
||||
$unread = $cv["counter"];
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
|
||||
$row = array(
|
||||
"id" => $cv["id"],
|
||||
"title" => $cv["description"],
|
||||
"unread" => $cv["counter"],
|
||||
"cat_id" => -2,
|
||||
);
|
||||
|
||||
array_push($feeds, $row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Virtual feeds */
|
||||
|
||||
if ($cat_id == -4 || $cat_id == -1) {
|
||||
foreach (array(-1, -2, -3, -4, -6, 0) as $i) {
|
||||
$unread = getFeedUnread($i);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
$title = getFeedTitle($i);
|
||||
|
||||
$row = array(
|
||||
"id" => $i,
|
||||
"title" => $title,
|
||||
"unread" => $unread,
|
||||
"cat_id" => -1,
|
||||
);
|
||||
array_push($feeds, $row);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Child cats */
|
||||
|
||||
if ($include_nested && $cat_id) {
|
||||
$result = db_query("SELECT
|
||||
id, title FROM ttrss_feed_categories
|
||||
WHERE parent_cat = '$cat_id' AND owner_uid = " . $_SESSION["uid"] .
|
||||
" ORDER BY id, title");
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
$unread = getFeedUnread($line["id"], true) +
|
||||
getCategoryChildrenUnread($line["id"]);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
$row = array(
|
||||
"id" => $line["id"],
|
||||
"title" => $line["title"],
|
||||
"unread" => $unread,
|
||||
"is_cat" => true,
|
||||
);
|
||||
array_push($feeds, $row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Real feeds */
|
||||
|
||||
if ($limit) {
|
||||
$limit_qpart = "LIMIT $limit OFFSET $offset";
|
||||
} else {
|
||||
$limit_qpart = "";
|
||||
}
|
||||
|
||||
if ($cat_id == -4 || $cat_id == -3) {
|
||||
$result = db_query("SELECT
|
||||
id, feed_url, cat_id, title, order_id, ".
|
||||
SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
|
||||
FROM ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"] .
|
||||
" ORDER BY cat_id, title " . $limit_qpart);
|
||||
} else {
|
||||
|
||||
if ($cat_id)
|
||||
$cat_qpart = "cat_id = '$cat_id'";
|
||||
else
|
||||
$cat_qpart = "cat_id IS NULL";
|
||||
|
||||
$result = db_query("SELECT
|
||||
id, feed_url, cat_id, title, order_id, ".
|
||||
SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
|
||||
FROM ttrss_feeds WHERE
|
||||
$cat_qpart AND owner_uid = " . $_SESSION["uid"] .
|
||||
" ORDER BY cat_id, title " . $limit_qpart);
|
||||
}
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
|
||||
$unread = getFeedUnread($line["id"]);
|
||||
|
||||
$has_icon = feed_has_icon($line['id']);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
|
||||
$row = array(
|
||||
"feed_url" => $line["feed_url"],
|
||||
"title" => $line["title"],
|
||||
"id" => (int)$line["id"],
|
||||
"unread" => (int)$unread,
|
||||
"has_icon" => $has_icon,
|
||||
"cat_id" => (int)$line["cat_id"],
|
||||
"last_updated" => (int) strtotime($line["last_updated"]),
|
||||
"order_id" => (int) $line["order_id"],
|
||||
);
|
||||
|
||||
array_push($feeds, $row);
|
||||
}
|
||||
}
|
||||
|
||||
return $feeds;
|
||||
}
|
||||
|
||||
static function api_get_headlines($feed_id, $limit, $offset,
|
||||
$filter, $is_cat, $show_excerpt, $show_content, $view_mode, $order,
|
||||
$include_attachments, $since_id,
|
||||
$search = "", $search_mode = "",
|
||||
$include_nested = false, $sanitize_content = true) {
|
||||
|
||||
$qfh_ret = queryFeedHeadlines($feed_id, $limit,
|
||||
$view_mode, $is_cat, $search, $search_mode,
|
||||
$order, $offset, 0, false, $since_id, $include_nested);
|
||||
|
||||
$result = $qfh_ret[0];
|
||||
$feed_title = $qfh_ret[1];
|
||||
|
||||
$headlines = array();
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
$line["content_preview"] = truncate_string(strip_tags($line["content_preview"]), 100);
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
|
||||
$line = $p->hook_query_headlines($line, 100, true);
|
||||
}
|
||||
|
||||
$is_updated = ($line["last_read"] == "" &&
|
||||
($line["unread"] != "t" && $line["unread"] != "1"));
|
||||
|
||||
$tags = explode(",", $line["tag_cache"]);
|
||||
$labels = json_decode($line["label_cache"], true);
|
||||
|
||||
//if (!$tags) $tags = get_article_tags($line["id"]);
|
||||
//if (!$labels) $labels = get_article_labels($line["id"]);
|
||||
|
||||
$headline_row = array(
|
||||
"id" => (int)$line["id"],
|
||||
"unread" => sql_bool_to_bool($line["unread"]),
|
||||
"marked" => sql_bool_to_bool($line["marked"]),
|
||||
"published" => sql_bool_to_bool($line["published"]),
|
||||
"updated" => (int) strtotime($line["updated"]),
|
||||
"is_updated" => $is_updated,
|
||||
"title" => $line["title"],
|
||||
"link" => $line["link"],
|
||||
"feed_id" => $line["feed_id"],
|
||||
"tags" => $tags,
|
||||
);
|
||||
|
||||
if ($include_attachments)
|
||||
$headline_row['attachments'] = get_article_enclosures(
|
||||
$line['id']);
|
||||
|
||||
if ($show_excerpt)
|
||||
$headline_row["excerpt"] = $line["content_preview"];
|
||||
|
||||
if ($show_content) {
|
||||
|
||||
if ($sanitize_content) {
|
||||
$headline_row["content"] = sanitize(
|
||||
$line["content"],
|
||||
sql_bool_to_bool($line['hide_images']),
|
||||
false, $line["site_url"], false, $line["id"]);
|
||||
} else {
|
||||
$headline_row["content"] = $line["content"];
|
||||
}
|
||||
}
|
||||
|
||||
// unify label output to ease parsing
|
||||
if ($labels["no-labels"] == 1) $labels = array();
|
||||
|
||||
$headline_row["labels"] = $labels;
|
||||
|
||||
$headline_row["feed_title"] = $line["feed_title"] ? $line["feed_title"] :
|
||||
$feed_title;
|
||||
|
||||
$headline_row["comments_count"] = (int)$line["num_comments"];
|
||||
$headline_row["comments_link"] = $line["comments"];
|
||||
|
||||
$headline_row["always_display_attachments"] = sql_bool_to_bool($line["always_display_enclosures"]);
|
||||
|
||||
$headline_row["author"] = $line["author"];
|
||||
|
||||
$headline_row["score"] = (int)$line["score"];
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) {
|
||||
$headline_row = $p->hook_render_article_api(array("headline" => $headline_row));
|
||||
}
|
||||
|
||||
array_push($headlines, $headline_row);
|
||||
}
|
||||
|
||||
return $headlines;
|
||||
}
|
||||
|
||||
function unsubscribeFeed() {
|
||||
$feed_id = (int) $this->dbh->escape_string($_REQUEST["feed_id"]);
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE
|
||||
id = '$feed_id' AND owner_uid = ".$_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
Pref_Feeds::remove_feed($feed_id, $_SESSION["uid"]);
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => "FEED_NOT_FOUND"));
|
||||
}
|
||||
}
|
||||
|
||||
function subscribeToFeed() {
|
||||
$feed_url = $this->dbh->escape_string($_REQUEST["feed_url"]);
|
||||
$category_id = (int) $this->dbh->escape_string($_REQUEST["category_id"]);
|
||||
$login = $this->dbh->escape_string($_REQUEST["login"]);
|
||||
$password = $this->dbh->escape_string($_REQUEST["password"]);
|
||||
|
||||
if ($feed_url) {
|
||||
$rc = subscribe_to_feed($feed_url, $category_id, $login, $password);
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => $rc));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
|
||||
}
|
||||
}
|
||||
|
||||
function getFeedTree() {
|
||||
$include_empty = sql_bool_to_bool($_REQUEST['include_empty']);
|
||||
|
||||
$pf = new Pref_Feeds($_REQUEST);
|
||||
|
||||
$_REQUEST['mode'] = 2;
|
||||
$_REQUEST['force_show_empty'] = $include_empty;
|
||||
|
||||
if ($pf){
|
||||
$data = $pf->makefeedtree();
|
||||
$this->wrap(self::STATUS_OK, array("categories" => $data));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" =>
|
||||
'UNABLE_TO_INSTANTIATE_OBJECT'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// only works for labels or uncategorized for the time being
|
||||
private function isCategoryEmpty($id) {
|
||||
|
||||
if ($id == -2) {
|
||||
$result = $this->dbh->query("SELECT COUNT(*) AS count FROM ttrss_labels2
|
||||
WHERE owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
return $this->dbh->fetch_result($result, 0, "count") == 0;
|
||||
|
||||
} else if ($id == 0) {
|
||||
$result = $this->dbh->query("SELECT COUNT(*) AS count FROM ttrss_feeds
|
||||
WHERE cat_id IS NULL AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
return $this->dbh->fetch_result($result, 0, "count") == 0;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
346
source/classes/article.php
Normal file
|
@ -0,0 +1,346 @@
|
|||
<?php
|
||||
class Article extends Handler_Protected {
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("redirect", "editarticletags");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function redirect() {
|
||||
$id = $this->dbh->escape_string($_REQUEST['id']);
|
||||
|
||||
$result = $this->dbh->query("SELECT link FROM ttrss_entries, ttrss_user_entries
|
||||
WHERE id = '$id' AND id = ref_id AND owner_uid = '".$_SESSION['uid']."'
|
||||
LIMIT 1");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 1) {
|
||||
$article_url = $this->dbh->fetch_result($result, 0, 'link');
|
||||
$article_url = str_replace("\n", "", $article_url);
|
||||
|
||||
header("Location: $article_url");
|
||||
return;
|
||||
|
||||
} else {
|
||||
print_error(__("Article not found."));
|
||||
}
|
||||
}
|
||||
|
||||
function view() {
|
||||
$id = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
$cids = explode(",", $this->dbh->escape_string($_REQUEST["cids"]));
|
||||
$mode = $this->dbh->escape_string($_REQUEST["mode"]);
|
||||
$omode = $this->dbh->escape_string($_REQUEST["omode"]);
|
||||
|
||||
// in prefetch mode we only output requested cids, main article
|
||||
// just gets marked as read (it already exists in client cache)
|
||||
|
||||
$articles = array();
|
||||
|
||||
if ($mode == "") {
|
||||
array_push($articles, format_article($id, false));
|
||||
} else if ($mode == "zoom") {
|
||||
array_push($articles, format_article($id, true, true));
|
||||
} else if ($mode == "raw") {
|
||||
if ($_REQUEST['html']) {
|
||||
header("Content-Type: text/html");
|
||||
print '<link rel="stylesheet" type="text/css" href="css/tt-rss.css"/>';
|
||||
}
|
||||
|
||||
$article = format_article($id, false);
|
||||
print $article['content'];
|
||||
return;
|
||||
}
|
||||
|
||||
$this->catchupArticleById($id, 0);
|
||||
|
||||
if (!$_SESSION["bw_limit"]) {
|
||||
foreach ($cids as $cid) {
|
||||
if ($cid) {
|
||||
array_push($articles, format_article($cid, false, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print json_encode($articles);
|
||||
}
|
||||
|
||||
private function catchupArticleById($id, $cmode) {
|
||||
|
||||
if ($cmode == 0) {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
unread = false,last_read = NOW()
|
||||
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
|
||||
} else if ($cmode == 1) {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
unread = true
|
||||
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
|
||||
} else {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
unread = NOT unread,last_read = NOW()
|
||||
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
$feed_id = getArticleFeed($id);
|
||||
ccache_update($feed_id, $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
static function create_published_article($title, $url, $content, $labels_str,
|
||||
$owner_uid) {
|
||||
|
||||
$guid = 'SHA1:' . sha1("ttshared:" . $url . $owner_uid); // include owner_uid to prevent global GUID clash
|
||||
$content_hash = sha1($content);
|
||||
|
||||
if ($labels_str != "") {
|
||||
$labels = explode(",", $labels_str);
|
||||
} else {
|
||||
$labels = array();
|
||||
}
|
||||
|
||||
$rc = false;
|
||||
|
||||
if (!$title) $title = $url;
|
||||
if (!$title && !$url) return false;
|
||||
|
||||
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) return false;
|
||||
|
||||
db_query("BEGIN");
|
||||
|
||||
// only check for our user data here, others might have shared this with different content etc
|
||||
$result = db_query("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE
|
||||
link = '$url' AND ref_id = id AND owner_uid = '$owner_uid' LIMIT 1");
|
||||
|
||||
if (db_num_rows($result) != 0) {
|
||||
$ref_id = db_fetch_result($result, 0, "id");
|
||||
|
||||
$result = db_query("SELECT int_id FROM ttrss_user_entries WHERE
|
||||
ref_id = '$ref_id' AND owner_uid = '$owner_uid' LIMIT 1");
|
||||
|
||||
if (db_num_rows($result) != 0) {
|
||||
$int_id = db_fetch_result($result, 0, "int_id");
|
||||
|
||||
db_query("UPDATE ttrss_entries SET
|
||||
content = '$content', content_hash = '$content_hash' WHERE id = '$ref_id'");
|
||||
|
||||
db_query("UPDATE ttrss_user_entries SET published = true,
|
||||
last_published = NOW() WHERE
|
||||
int_id = '$int_id' AND owner_uid = '$owner_uid'");
|
||||
} else {
|
||||
|
||||
db_query("INSERT INTO ttrss_user_entries
|
||||
(ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
|
||||
last_read, note, unread, last_published)
|
||||
VALUES
|
||||
('$ref_id', '', NULL, NULL, $owner_uid, true, '', '', NOW(), '', false, NOW())");
|
||||
}
|
||||
|
||||
if (count($labels) != 0) {
|
||||
foreach ($labels as $label) {
|
||||
label_add_article($ref_id, trim($label), $owner_uid);
|
||||
}
|
||||
}
|
||||
|
||||
$rc = true;
|
||||
|
||||
} else {
|
||||
$result = db_query("INSERT INTO ttrss_entries
|
||||
(title, guid, link, updated, content, content_hash, date_entered, date_updated)
|
||||
VALUES
|
||||
('$title', '$guid', '$url', NOW(), '$content', '$content_hash', NOW(), NOW())");
|
||||
|
||||
$result = db_query("SELECT id FROM ttrss_entries WHERE guid = '$guid'");
|
||||
|
||||
if (db_num_rows($result) != 0) {
|
||||
$ref_id = db_fetch_result($result, 0, "id");
|
||||
|
||||
db_query("INSERT INTO ttrss_user_entries
|
||||
(ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
|
||||
last_read, note, unread, last_published)
|
||||
VALUES
|
||||
('$ref_id', '', NULL, NULL, $owner_uid, true, '', '', NOW(), '', false, NOW())");
|
||||
|
||||
if (count($labels) != 0) {
|
||||
foreach ($labels as $label) {
|
||||
label_add_article($ref_id, trim($label), $owner_uid);
|
||||
}
|
||||
}
|
||||
|
||||
$rc = true;
|
||||
}
|
||||
}
|
||||
|
||||
db_query("COMMIT");
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
||||
function editArticleTags() {
|
||||
|
||||
print __("Tags for this article (separated by commas):")."<br>";
|
||||
|
||||
$param = $this->dbh->escape_string($_REQUEST['param']);
|
||||
|
||||
$tags = get_article_tags($this->dbh->escape_string($param));
|
||||
|
||||
$tags_str = join(", ", $tags);
|
||||
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"id\" value=\"$param\">";
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"article\">";
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"setArticleTags\">";
|
||||
|
||||
print "<table width='100%'><tr><td>";
|
||||
|
||||
print "<textarea dojoType=\"dijit.form.SimpleTextarea\" rows='4'
|
||||
style='font-size : 12px; width : 100%' id=\"tags_str\"
|
||||
name='tags_str'>$tags_str</textarea>
|
||||
<div class=\"autocomplete\" id=\"tags_choices\"
|
||||
style=\"display:none\"></div>";
|
||||
|
||||
print "</td></tr></table>";
|
||||
|
||||
print "<div class='dlgButtons'>";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"dijit.byId('editTagsDlg').execute()\">".__('Save')."</button> ";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"dijit.byId('editTagsDlg').hide()\">".__('Cancel')."</button>";
|
||||
print "</div>";
|
||||
|
||||
}
|
||||
|
||||
function setScore() {
|
||||
$ids = $this->dbh->escape_string($_REQUEST['id']);
|
||||
$score = (int)$this->dbh->escape_string($_REQUEST['score']);
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
score = '$score' WHERE ref_id IN ($ids) AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
print json_encode(array("id" => $ids,
|
||||
"score_pic" => get_score_pic($score)));
|
||||
}
|
||||
|
||||
|
||||
function setArticleTags() {
|
||||
|
||||
$id = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
|
||||
$tags_str = $this->dbh->escape_string($_REQUEST["tags_str"]);
|
||||
$tags = array_unique(trim_array(explode(",", $tags_str)));
|
||||
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
$result = $this->dbh->query("SELECT int_id FROM ttrss_user_entries WHERE
|
||||
ref_id = '$id' AND owner_uid = '".$_SESSION["uid"]."' LIMIT 1");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 1) {
|
||||
|
||||
$tags_to_cache = array();
|
||||
|
||||
$int_id = $this->dbh->fetch_result($result, 0, "int_id");
|
||||
|
||||
$this->dbh->query("DELETE FROM ttrss_tags WHERE
|
||||
post_int_id = $int_id AND owner_uid = '".$_SESSION["uid"]."'");
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$tag = sanitize_tag($tag);
|
||||
|
||||
if (!tag_is_valid($tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match("/^[0-9]*$/", $tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// print "<!-- $id : $int_id : $tag -->";
|
||||
|
||||
if ($tag != '') {
|
||||
$this->dbh->query("INSERT INTO ttrss_tags
|
||||
(post_int_id, owner_uid, tag_name) VALUES ('$int_id', '".$_SESSION["uid"]."', '$tag')");
|
||||
}
|
||||
|
||||
array_push($tags_to_cache, $tag);
|
||||
}
|
||||
|
||||
/* update tag cache */
|
||||
|
||||
sort($tags_to_cache);
|
||||
$tags_str = join(",", $tags_to_cache);
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_user_entries
|
||||
SET tag_cache = '$tags_str' WHERE ref_id = '$id'
|
||||
AND owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
|
||||
$tags = get_article_tags($id);
|
||||
$tags_str = format_tags_string($tags, $id);
|
||||
$tags_str_full = join(", ", $tags);
|
||||
|
||||
if (!$tags_str_full) $tags_str_full = __("no tags");
|
||||
|
||||
print json_encode(array("id" => (int)$id,
|
||||
"content" => $tags_str, "content_full" => $tags_str_full));
|
||||
}
|
||||
|
||||
|
||||
function completeTags() {
|
||||
$search = $this->dbh->escape_string($_REQUEST["search"]);
|
||||
|
||||
$result = $this->dbh->query("SELECT DISTINCT tag_name FROM ttrss_tags
|
||||
WHERE owner_uid = '".$_SESSION["uid"]."' AND
|
||||
tag_name LIKE '$search%' ORDER BY tag_name
|
||||
LIMIT 10");
|
||||
|
||||
print "<ul>";
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
print "<li>" . $line["tag_name"] . "</li>";
|
||||
}
|
||||
print "</ul>";
|
||||
}
|
||||
|
||||
function assigntolabel() {
|
||||
return $this->labelops(true);
|
||||
}
|
||||
|
||||
function removefromlabel() {
|
||||
return $this->labelops(false);
|
||||
}
|
||||
|
||||
private function labelops($assign) {
|
||||
$reply = array();
|
||||
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
$label_id = $this->dbh->escape_string($_REQUEST["lid"]);
|
||||
|
||||
$label = $this->dbh->escape_string(label_find_caption($label_id,
|
||||
$_SESSION["uid"]));
|
||||
|
||||
$reply["info-for-headlines"] = array();
|
||||
|
||||
if ($label) {
|
||||
|
||||
foreach ($ids as $id) {
|
||||
|
||||
if ($assign)
|
||||
label_add_article($id, $label, $_SESSION["uid"]);
|
||||
else
|
||||
label_remove_article($id, $label, $_SESSION["uid"]);
|
||||
|
||||
$labels = get_article_labels($id, $_SESSION["uid"]);
|
||||
|
||||
array_push($reply["info-for-headlines"],
|
||||
array("id" => $id, "labels" => format_article_labels($labels, $id)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$reply["message"] = "UPDATE_COUNTERS";
|
||||
|
||||
print json_encode($reply);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
61
source/classes/auth/base.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
class Auth_Base {
|
||||
private $dbh;
|
||||
|
||||
function __construct() {
|
||||
$this->dbh = Db::get();
|
||||
}
|
||||
|
||||
function check_password($owner_uid, $password) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function authenticate($login, $password) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Auto-creates specified user if allowed by system configuration
|
||||
// Can be used instead of find_user_by_login() by external auth modules
|
||||
function auto_create_user($login, $password = false) {
|
||||
if ($login && defined('AUTH_AUTO_CREATE') && AUTH_AUTO_CREATE) {
|
||||
$user_id = $this->find_user_by_login($login);
|
||||
|
||||
if (!$password) $password = make_password();
|
||||
|
||||
if (!$user_id) {
|
||||
$login = $this->dbh->escape_string($login);
|
||||
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
|
||||
$pwd_hash = encrypt_password($password, $salt, true);
|
||||
|
||||
$query = "INSERT INTO ttrss_users
|
||||
(login,access_level,last_login,created,pwd_hash,salt)
|
||||
VALUES ('$login', 0, null, NOW(), '$pwd_hash','$salt')";
|
||||
|
||||
$this->dbh->query($query);
|
||||
|
||||
return $this->find_user_by_login($login);
|
||||
|
||||
} else {
|
||||
return $user_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->find_user_by_login($login);
|
||||
}
|
||||
|
||||
function find_user_by_login($login) {
|
||||
$login = $this->dbh->escape_string($login);
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_users WHERE
|
||||
login = '$login'");
|
||||
|
||||
if ($this->dbh->num_rows($result) > 0) {
|
||||
return $this->dbh->fetch_result($result, 0, "id");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
119
source/classes/backend.php
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
class Backend extends Handler {
|
||||
function loading() {
|
||||
header("Content-type: text/html");
|
||||
print __("Loading, please wait...") . " " .
|
||||
"<img src='images/indicator_tiny.gif'>";
|
||||
}
|
||||
|
||||
function digestTest() {
|
||||
header("Content-type: text/html");
|
||||
|
||||
require_once "digest.php";
|
||||
|
||||
$rv = prepare_headlines_digest($_SESSION['uid'], 1, 1000);
|
||||
|
||||
$rv[3] = "<pre>" . $rv[3] . "</pre>";
|
||||
|
||||
print_r($rv);
|
||||
}
|
||||
|
||||
private function display_main_help() {
|
||||
$info = get_hotkeys_info();
|
||||
$imap = get_hotkeys_map();
|
||||
$omap = array();
|
||||
|
||||
foreach ($imap[1] as $sequence => $action) {
|
||||
if (!isset($omap[$action])) $omap[$action] = array();
|
||||
|
||||
array_push($omap[$action], $sequence);
|
||||
}
|
||||
|
||||
print_notice("<a target=\"_blank\" href=\"http://tt-rss.org/wiki/InterfaceTips\">".
|
||||
__("Other interface tips are available in the Tiny Tiny RSS wiki.") .
|
||||
"</a>");
|
||||
|
||||
print "<ul class='helpKbList' id='helpKbList'>";
|
||||
|
||||
print "<h2>" . __("Keyboard Shortcuts") . "</h2>";
|
||||
|
||||
foreach ($info as $section => $hotkeys) {
|
||||
|
||||
print "<li><h3>" . $section . "</h3></li>";
|
||||
|
||||
foreach ($hotkeys as $action => $description) {
|
||||
|
||||
if (is_array($omap[$action])) {
|
||||
foreach ($omap[$action] as $sequence) {
|
||||
if (strpos($sequence, "|") !== FALSE) {
|
||||
$sequence = substr($sequence,
|
||||
strpos($sequence, "|")+1,
|
||||
strlen($sequence));
|
||||
} else {
|
||||
$keys = explode(" ", $sequence);
|
||||
|
||||
for ($i = 0; $i < count($keys); $i++) {
|
||||
if (strlen($keys[$i]) > 1) {
|
||||
$tmp = '';
|
||||
foreach (str_split($keys[$i]) as $c) {
|
||||
switch ($c) {
|
||||
case '*':
|
||||
$tmp .= __('Shift') . '+';
|
||||
break;
|
||||
case '^':
|
||||
$tmp .= __('Ctrl') . '+';
|
||||
break;
|
||||
default:
|
||||
$tmp .= $c;
|
||||
}
|
||||
}
|
||||
$keys[$i] = $tmp;
|
||||
}
|
||||
}
|
||||
$sequence = join(" ", $keys);
|
||||
}
|
||||
|
||||
print "<li>";
|
||||
print "<span class='hksequence'>$sequence</span>";
|
||||
print $description;
|
||||
print "</li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
}
|
||||
|
||||
function help() {
|
||||
$topic = basename($_REQUEST["topic"]);
|
||||
|
||||
switch ($topic) {
|
||||
case "main":
|
||||
$this->display_main_help();
|
||||
break;
|
||||
case "prefs":
|
||||
//$this->display_prefs_help();
|
||||
break;
|
||||
default:
|
||||
print "<p>".__("Help topic not found.")."</p>";
|
||||
}
|
||||
|
||||
print "<div align='center'>";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"return dijit.byId('helpDlg').hide()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</div>";
|
||||
|
||||
/* if (file_exists("help/$topic.php")) {
|
||||
include("help/$topic.php");
|
||||
} else {
|
||||
print "<p>".__("Help topic not found.")."</p>";
|
||||
} */
|
||||
/* print "<div align='center'>
|
||||
<button onclick=\"javascript:window.close()\">".
|
||||
__('Close this window')."</button></div>"; */
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
98
source/classes/db.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
class Db implements IDb {
|
||||
private static $instance;
|
||||
private $adapter;
|
||||
private $link;
|
||||
|
||||
private function __construct() {
|
||||
|
||||
$er = error_reporting(E_ALL);
|
||||
|
||||
if (defined('_ENABLE_PDO') && _ENABLE_PDO && class_exists("PDO")) {
|
||||
$this->adapter = new Db_PDO();
|
||||
} else {
|
||||
switch (DB_TYPE) {
|
||||
case "mysql":
|
||||
if (function_exists("mysqli_connect")) {
|
||||
$this->adapter = new Db_Mysqli();
|
||||
} else {
|
||||
$this->adapter = new Db_Mysql();
|
||||
}
|
||||
break;
|
||||
case "pgsql":
|
||||
$this->adapter = new Db_Pgsql();
|
||||
break;
|
||||
default:
|
||||
die("Unknown DB_TYPE: " . DB_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->adapter) die("Error initializing database adapter for " . DB_TYPE);
|
||||
|
||||
$this->link = $this->adapter->connect(DB_HOST, DB_USER, DB_PASS, DB_NAME, defined('DB_PORT') ? DB_PORT : "");
|
||||
|
||||
if (!$this->link) {
|
||||
die("Error connecting through adapter: " . $this->adapter->last_error());
|
||||
}
|
||||
|
||||
error_reporting($er);
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
static function quote($str){
|
||||
return("'$str'");
|
||||
}
|
||||
|
||||
function reconnect() {
|
||||
$this->link = $this->adapter->connect(DB_HOST, DB_USER, DB_PASS, DB_NAME, defined('DB_PORT') ? DB_PORT : "");
|
||||
}
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
//return $this->adapter->connect($host, $user, $pass, $db, $port);
|
||||
return ;
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
return $this->adapter->escape_string($s, $strip_tags);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
return $this->adapter->query($query, $die_on_error);
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
return $this->adapter->fetch_assoc($result);
|
||||
}
|
||||
|
||||
function num_rows($result) {
|
||||
return $this->adapter->num_rows($result);
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
return $this->adapter->fetch_result($result, $row, $param);
|
||||
}
|
||||
|
||||
function close() {
|
||||
return $this->adapter->close();
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
return $this->adapter->affected_rows($result);
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return $this->adapter->last_error();
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
76
source/classes/db/mysql.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
class Db_Mysql implements IDb {
|
||||
private $link;
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
$this->link = mysql_connect($host, $user, $pass);
|
||||
|
||||
if ($this->link) {
|
||||
$result = mysql_select_db($db, $this->link);
|
||||
if (!$result) {
|
||||
die("Can't select DB: " . mysql_error($this->link));
|
||||
}
|
||||
|
||||
$this->init();
|
||||
|
||||
return $this->link;
|
||||
} else {
|
||||
die("Unable to connect to database (as $user to $host, database $db): " . mysql_error());
|
||||
}
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
if ($strip_tags) $s = strip_tags($s);
|
||||
|
||||
return mysql_real_escape_string($s, $this->link);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
$result = @mysql_query($query, $this->link);
|
||||
if (!$result) {
|
||||
$error = @mysql_error($this->link);
|
||||
|
||||
@mysql_query("ROLLBACK", $this->link);
|
||||
user_error("Query $query failed: " . ($this->link ? $error : "No connection"),
|
||||
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
return mysql_fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function num_rows($result) {
|
||||
return mysql_num_rows($result);
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
return mysql_result($result, $row, $param);
|
||||
}
|
||||
|
||||
function close() {
|
||||
return mysql_close($this->link);
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
return mysql_affected_rows($this->link);
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return mysql_error();
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->query("SET time_zone = '+0:0'");
|
||||
|
||||
if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
|
||||
$this->query("SET NAMES " . MYSQL_CHARSET);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
80
source/classes/db/mysqli.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
class Db_Mysqli implements IDb {
|
||||
private $link;
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
if ($port)
|
||||
$this->link = mysqli_connect($host, $user, $pass, $db, $port);
|
||||
else
|
||||
$this->link = mysqli_connect($host, $user, $pass, $db);
|
||||
|
||||
if ($this->link) {
|
||||
$this->init();
|
||||
|
||||
return $this->link;
|
||||
} else {
|
||||
die("Unable to connect to database (as $user to $host, database $db): " . mysqli_connect_error());
|
||||
}
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
if ($strip_tags) $s = strip_tags($s);
|
||||
|
||||
return mysqli_real_escape_string($this->link, $s);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
$result = @mysqli_query($this->link, $query);
|
||||
if (!$result) {
|
||||
$error = @mysqli_error($this->link);
|
||||
|
||||
@mysqli_query($this->link, "ROLLBACK");
|
||||
user_error("Query $query failed: " . ($this->link ? $error : "No connection"),
|
||||
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
return mysqli_fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function num_rows($result) {
|
||||
return mysqli_num_rows($result);
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
if (mysqli_data_seek($result, $row)) {
|
||||
$line = mysqli_fetch_assoc($result);
|
||||
return $line[$param];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
return mysqli_close($this->link);
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
return mysqli_affected_rows($this->link);
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return mysqli_error();
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->query("SET time_zone = '+0:0'");
|
||||
|
||||
if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
|
||||
$this->query("SET NAMES " . MYSQL_CHARSET);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
100
source/classes/db/pdo.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
class Db_PDO implements IDb {
|
||||
private $pdo;
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
$connstr = DB_TYPE . ":host=$host;dbname=$db";
|
||||
|
||||
if (DB_TYPE == "mysql") $connstr .= ";charset=utf8";
|
||||
|
||||
try {
|
||||
$this->pdo = new PDO($connstr, $user, $pass);
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$this->init();
|
||||
} catch (PDOException $e) {
|
||||
die($e->getMessage());
|
||||
}
|
||||
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
if ($strip_tags) $s = strip_tags($s);
|
||||
|
||||
$qs = $this->pdo->quote($s);
|
||||
|
||||
return mb_substr($qs, 1, mb_strlen($qs)-2);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
try {
|
||||
return new Db_Stmt($this->pdo->query($query));
|
||||
} catch (PDOException $e) {
|
||||
user_error($e->getMessage(), $die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
try {
|
||||
if ($result) {
|
||||
return $result->fetch();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
user_error($e->getMessage(), E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
function num_rows($result) {
|
||||
try {
|
||||
if ($result) {
|
||||
return $result->rowCount();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
user_error($e->getMessage(), E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
return $result->fetch_result($row, $param);
|
||||
}
|
||||
|
||||
function close() {
|
||||
$this->pdo = null;
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
try {
|
||||
if ($result) {
|
||||
return $result->rowCount();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
user_error($e->getMessage(), E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return join(" ", $this->pdo->errorInfo());
|
||||
}
|
||||
|
||||
function init() {
|
||||
switch (DB_TYPE) {
|
||||
case "pgsql":
|
||||
$this->query("set client_encoding = 'UTF-8'");
|
||||
$this->query("set datestyle = 'ISO, european'");
|
||||
$this->query("set TIME ZONE 0");
|
||||
case "mysql":
|
||||
$this->query("SET time_zone = '+0:0'");
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
85
source/classes/db/pgsql.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
class Db_Pgsql implements IDb {
|
||||
private $link;
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
$string = "dbname=$db user=$user";
|
||||
|
||||
if ($pass) {
|
||||
$string .= " password=$pass";
|
||||
}
|
||||
|
||||
if ($host) {
|
||||
$string .= " host=$host";
|
||||
}
|
||||
|
||||
if (is_numeric($port) && $port > 0) {
|
||||
$string = "$string port=" . $port;
|
||||
}
|
||||
|
||||
$this->link = pg_connect($string);
|
||||
|
||||
if (!$this->link) {
|
||||
die("Unable to connect to database (as $user to $host, database $db):" . pg_last_error());
|
||||
}
|
||||
|
||||
$this->init();
|
||||
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
if ($strip_tags) $s = strip_tags($s);
|
||||
|
||||
return pg_escape_string($s);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
$result = @pg_query($this->link, $query);
|
||||
|
||||
if (!$result) {
|
||||
$error = @pg_last_error($this->link);
|
||||
|
||||
@pg_query($this->link, "ROLLBACK");
|
||||
$query = htmlspecialchars($query); // just in case
|
||||
user_error("Query $query failed: " . ($this->link ? $error : "No connection"),
|
||||
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
return pg_fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function num_rows($result) {
|
||||
return pg_num_rows($result);
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
return pg_fetch_result($result, $row, $param);
|
||||
}
|
||||
|
||||
function close() {
|
||||
return pg_close($this->link);
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
return pg_affected_rows($result);
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return pg_last_error($this->link);
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->query("set client_encoding = 'UTF-8'");
|
||||
pg_set_client_encoding("UNICODE");
|
||||
$this->query("set datestyle = 'ISO, european'");
|
||||
$this->query("set TIME ZONE 0");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
?>
|
190
source/classes/db/prefs.php
Normal file
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
class Db_Prefs {
|
||||
private $dbh;
|
||||
private static $instance;
|
||||
private $cache;
|
||||
|
||||
function __construct() {
|
||||
$this->dbh = Db::get();
|
||||
$this->cache = array();
|
||||
|
||||
if ($_SESSION["uid"]) $this->cache();
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
function cache() {
|
||||
$profile = false;
|
||||
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
|
||||
if ($profile) {
|
||||
$profile_qpart = "profile = '$profile' AND";
|
||||
} else {
|
||||
$profile_qpart = "profile IS NULL AND";
|
||||
}
|
||||
|
||||
if (get_schema_version() < 63) $profile_qpart = "";
|
||||
|
||||
$result = db_query("SELECT
|
||||
value,ttrss_prefs_types.type_name as type_name,ttrss_prefs.pref_name AS pref_name
|
||||
FROM
|
||||
ttrss_user_prefs,ttrss_prefs,ttrss_prefs_types
|
||||
WHERE
|
||||
$profile_qpart
|
||||
ttrss_prefs.pref_name NOT LIKE '_MOBILE%' AND
|
||||
ttrss_prefs_types.id = type_id AND
|
||||
owner_uid = '$user_id' AND
|
||||
ttrss_user_prefs.pref_name = ttrss_prefs.pref_name");
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$pref_name = $line["pref_name"];
|
||||
|
||||
$this->cache[$pref_name]["type"] = $line["type_name"];
|
||||
$this->cache[$pref_name]["value"] = $line["value"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function read($pref_name, $user_id = false, $die_on_error = false) {
|
||||
|
||||
$pref_name = db_escape_string($pref_name);
|
||||
$profile = false;
|
||||
|
||||
if (!$user_id) {
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
} else {
|
||||
$user_id = sprintf("%d", $user_id);
|
||||
}
|
||||
|
||||
if (isset($this->cache[$pref_name])) {
|
||||
$tuple = $this->cache[$pref_name];
|
||||
return $this->convert($tuple["value"], $tuple["type"]);
|
||||
}
|
||||
|
||||
if ($profile) {
|
||||
$profile_qpart = "profile = '$profile' AND";
|
||||
} else {
|
||||
$profile_qpart = "profile IS NULL AND";
|
||||
}
|
||||
|
||||
if (get_schema_version() < 63) $profile_qpart = "";
|
||||
|
||||
$result = db_query("SELECT
|
||||
value,ttrss_prefs_types.type_name as type_name
|
||||
FROM
|
||||
ttrss_user_prefs,ttrss_prefs,ttrss_prefs_types
|
||||
WHERE
|
||||
$profile_qpart
|
||||
ttrss_user_prefs.pref_name = '$pref_name' AND
|
||||
ttrss_prefs_types.id = type_id AND
|
||||
owner_uid = '$user_id' AND
|
||||
ttrss_user_prefs.pref_name = ttrss_prefs.pref_name");
|
||||
|
||||
if (db_num_rows($result) > 0) {
|
||||
$value = db_fetch_result($result, 0, "value");
|
||||
$type_name = db_fetch_result($result, 0, "type_name");
|
||||
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$this->cache[$pref_name]["type"] = $type_name;
|
||||
$this->cache[$pref_name]["value"] = $value;
|
||||
}
|
||||
|
||||
return $this->convert($value, $type_name);
|
||||
|
||||
} else {
|
||||
user_error("Fatal error, unknown preferences key: $pref_name (owner: $user_id)", $die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function convert($value, $type_name) {
|
||||
if ($type_name == "bool") {
|
||||
return $value == "true";
|
||||
} else if ($type_name == "integer") {
|
||||
return (int)$value;
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
function write($pref_name, $value, $user_id = false, $strip_tags = true) {
|
||||
$pref_name = db_escape_string($pref_name);
|
||||
$value = db_escape_string($value, $strip_tags);
|
||||
|
||||
if (!$user_id) {
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
} else {
|
||||
$user_id = sprintf("%d", $user_id);
|
||||
$prefs_cache = false;
|
||||
}
|
||||
|
||||
if ($profile) {
|
||||
$profile_qpart = "AND profile = '$profile'";
|
||||
} else {
|
||||
$profile_qpart = "AND profile IS NULL";
|
||||
}
|
||||
|
||||
if (get_schema_version() < 63) $profile_qpart = "";
|
||||
|
||||
$type_name = "";
|
||||
$current_value = "";
|
||||
|
||||
if (isset($this->cache[$pref_name])) {
|
||||
$type_name = $this->cache[$pref_name]["type"];
|
||||
$current_value = $this->cache[$pref_name]["value"];
|
||||
}
|
||||
|
||||
if (!$type_name) {
|
||||
$result = db_query("SELECT type_name
|
||||
FROM ttrss_prefs,ttrss_prefs_types
|
||||
WHERE pref_name = '$pref_name' AND type_id = ttrss_prefs_types.id");
|
||||
|
||||
if (db_num_rows($result) > 0)
|
||||
$type_name = db_fetch_result($result, 0, "type_name");
|
||||
} else if ($current_value == $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($type_name) {
|
||||
if ($type_name == "bool") {
|
||||
if ($value == "1" || $value == "true") {
|
||||
$value = "true";
|
||||
} else {
|
||||
$value = "false";
|
||||
}
|
||||
} else if ($type_name == "integer") {
|
||||
$value = sprintf("%d", $value);
|
||||
}
|
||||
|
||||
if ($pref_name == 'USER_TIMEZONE' && $value == '') {
|
||||
$value = 'UTC';
|
||||
}
|
||||
|
||||
db_query("UPDATE ttrss_user_prefs SET
|
||||
value = '$value' WHERE pref_name = '$pref_name'
|
||||
$profile_qpart
|
||||
AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$this->cache[$pref_name]["type"] = $type_name;
|
||||
$this->cache[$pref_name]["value"] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
32
source/classes/db/stmt.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
class Db_Stmt {
|
||||
private $stmt;
|
||||
private $cache;
|
||||
|
||||
function __construct($stmt) {
|
||||
$this->stmt = $stmt;
|
||||
$this->cache = false;
|
||||
}
|
||||
|
||||
function fetch_result($row, $param) {
|
||||
if (!$this->cache) {
|
||||
$this->cache = $this->stmt->fetchAll();
|
||||
}
|
||||
|
||||
if (isset($this->cache[$row])) {
|
||||
return $this->cache[$row][$param];
|
||||
} else {
|
||||
user_error("Unable to jump to row $row", E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function rowCount() {
|
||||
return $this->stmt->rowCount();
|
||||
}
|
||||
|
||||
function fetch() {
|
||||
return $this->stmt->fetch();
|
||||
}
|
||||
}
|
||||
?>
|
65
source/classes/dbupdater.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
class DbUpdater {
|
||||
|
||||
private $dbh;
|
||||
private $db_type;
|
||||
private $need_version;
|
||||
|
||||
function __construct($dbh, $db_type, $need_version) {
|
||||
$this->dbh = $dbh;
|
||||
$this->db_type = $db_type;
|
||||
$this->need_version = (int) $need_version;
|
||||
}
|
||||
|
||||
function getSchemaVersion() {
|
||||
$result = db_query("SELECT schema_version FROM ttrss_version");
|
||||
return (int) db_fetch_result($result, 0, "schema_version");
|
||||
}
|
||||
|
||||
function isUpdateRequired() {
|
||||
return $this->getSchemaVersion() < $this->need_version;
|
||||
}
|
||||
|
||||
function getSchemaLines($version) {
|
||||
$filename = "schema/versions/".$this->db_type."/$version.sql";
|
||||
|
||||
if (file_exists($filename)) {
|
||||
return explode(";", preg_replace("/[\r\n]/", "", file_get_contents($filename)));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function performUpdateTo($version) {
|
||||
if ($this->getSchemaVersion() == $version - 1) {
|
||||
|
||||
$lines = $this->getSchemaLines($version);
|
||||
|
||||
if (is_array($lines)) {
|
||||
|
||||
db_query("BEGIN");
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, "--") !== 0 && $line) {
|
||||
db_query($line);
|
||||
}
|
||||
}
|
||||
|
||||
$db_version = $this->getSchemaVersion();
|
||||
|
||||
if ($db_version == $version) {
|
||||
db_query("COMMIT");
|
||||
return true;
|
||||
} else {
|
||||
db_query("ROLLBACK");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} ?>
|
272
source/classes/dlg.php
Normal file
|
@ -0,0 +1,272 @@
|
|||
<?php
|
||||
class Dlg extends Handler_Protected {
|
||||
private $param;
|
||||
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
header("Content-Type: text/html"); # required for iframe
|
||||
|
||||
$this->param = $this->dbh->escape_string($_REQUEST["param"]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function importOpml() {
|
||||
print __("If you have imported labels and/or filters, you might need to reload preferences to see your new data.") . "</p>";
|
||||
|
||||
print "<div class=\"prefFeedOPMLHolder\">";
|
||||
$owner_uid = $_SESSION["uid"];
|
||||
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
print "<ul class='nomarks'>";
|
||||
|
||||
$opml = new Opml($_REQUEST);
|
||||
|
||||
$opml->opml_import($_SESSION["uid"]);
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
|
||||
print "</ul>";
|
||||
print "</div>";
|
||||
|
||||
print "<div align='center'>";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"dijit.byId('opmlImportDlg').execute()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</div>";
|
||||
|
||||
print "</div>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function pubOPMLUrl() {
|
||||
$url_path = Opml::opml_publish_url();
|
||||
|
||||
print __("Your Public OPML URL is:");
|
||||
|
||||
print "<div class=\"tagCloudContainer\">";
|
||||
print "<a id='pub_opml_url' href='$url_path' target='_blank'>$url_path</a>";
|
||||
print "</div>";
|
||||
|
||||
print "<div align='center'>";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"return opmlRegenKey()\">".
|
||||
__('Generate new URL')."</button> ";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"return closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
|
||||
print "</div>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function explainError() {
|
||||
print "<div class=\"errorExplained\">";
|
||||
|
||||
if ($this->param == 1) {
|
||||
print __("Update daemon is enabled in configuration, but daemon process is not running, which prevents all feeds from updating. Please start the daemon process or contact instance owner.");
|
||||
|
||||
$stamp = (int) file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
|
||||
|
||||
print "<p>" . __("Last update:") . " " . date("Y.m.d, G:i", $stamp);
|
||||
|
||||
}
|
||||
|
||||
if ($this->param == 3) {
|
||||
print __("Update daemon is taking too long to perform a feed update. This could indicate a problem like crash or a hang. Please check the daemon process or contact instance owner.");
|
||||
|
||||
$stamp = (int) file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
|
||||
|
||||
print "<p>" . __("Last update:") . " " . date("Y.m.d, G:i", $stamp);
|
||||
|
||||
}
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<div align='center'>";
|
||||
|
||||
print "<button onclick=\"return closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
|
||||
print "</div>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function printTagCloud() {
|
||||
print "<div class=\"tagCloudContainer\">";
|
||||
|
||||
// from here: http://www.roscripts.com/Create_tag_cloud-71.html
|
||||
|
||||
$query = "SELECT tag_name, COUNT(post_int_id) AS count
|
||||
FROM ttrss_tags WHERE owner_uid = ".$_SESSION["uid"]."
|
||||
GROUP BY tag_name ORDER BY count DESC LIMIT 50";
|
||||
|
||||
$result = $this->dbh->query($query);
|
||||
|
||||
$tags = array();
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$tags[$line["tag_name"]] = $line["count"];
|
||||
}
|
||||
|
||||
if(count($tags) == 0 ){ return; }
|
||||
|
||||
ksort($tags);
|
||||
|
||||
$max_size = 32; // max font size in pixels
|
||||
$min_size = 11; // min font size in pixels
|
||||
|
||||
// largest and smallest array values
|
||||
$max_qty = max(array_values($tags));
|
||||
$min_qty = min(array_values($tags));
|
||||
|
||||
// find the range of values
|
||||
$spread = $max_qty - $min_qty;
|
||||
if ($spread == 0) { // we don't want to divide by zero
|
||||
$spread = 1;
|
||||
}
|
||||
|
||||
// set the font-size increment
|
||||
$step = ($max_size - $min_size) / ($spread);
|
||||
|
||||
// loop through the tag array
|
||||
foreach ($tags as $key => $value) {
|
||||
// calculate font-size
|
||||
// find the $value in excess of $min_qty
|
||||
// multiply by the font-size increment ($size)
|
||||
// and add the $min_size set above
|
||||
$size = round($min_size + (($value - $min_qty) * $step));
|
||||
|
||||
$key_escaped = str_replace("'", "\\'", $key);
|
||||
|
||||
echo "<a href=\"javascript:viewfeed('$key_escaped') \" style=\"font-size: " .
|
||||
$size . "px\" title=\"$value articles tagged with " .
|
||||
$key . '">' . $key . '</a> ';
|
||||
}
|
||||
|
||||
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<div align='center'>";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"return closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</div>";
|
||||
|
||||
}
|
||||
|
||||
function printTagSelect() {
|
||||
|
||||
print __("Match:"). " " .
|
||||
"<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\" type=\"radio\" checked value=\"any\" name=\"tag_mode\" id=\"tag_mode_any\">";
|
||||
print "<label for=\"tag_mode_any\">".__("Any")."</label>";
|
||||
print " ";
|
||||
print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\" type=\"radio\" value=\"all\" name=\"tag_mode\" id=\"tag_mode_all\">";
|
||||
print "<label for=\"tag_mode_all\">".__("All tags.")."</input>";
|
||||
|
||||
print "<select id=\"all_tags\" name=\"all_tags\" title=\"" . __('Which Tags?') . "\" multiple=\"multiple\" size=\"10\" style=\"width : 100%\">";
|
||||
$result = $this->dbh->query("SELECT DISTINCT tag_name FROM ttrss_tags WHERE owner_uid = ".$_SESSION['uid']."
|
||||
AND LENGTH(tag_name) <= 30 ORDER BY tag_name ASC");
|
||||
|
||||
while ($row = $this->dbh->fetch_assoc($result)) {
|
||||
$tmp = htmlspecialchars($row["tag_name"]);
|
||||
print "<option value=\"" . str_replace(" ", "%20", $tmp) . "\">$tmp</option>";
|
||||
}
|
||||
|
||||
print "</select>";
|
||||
|
||||
print "<div align='right'>";
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"viewfeed(get_all_tags($('all_tags')),
|
||||
get_radio_checked($('tag_mode')));\">" . __('Display entries') . "</button>";
|
||||
print " ";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"return closeInfoBox()\">" .
|
||||
__('Close this window') . "</button>";
|
||||
print "</div>";
|
||||
|
||||
}
|
||||
|
||||
function generatedFeed() {
|
||||
|
||||
$this->params = explode(":", $this->param, 3);
|
||||
$feed_id = $this->dbh->escape_string($this->params[0]);
|
||||
$is_cat = (bool) $this->params[1];
|
||||
|
||||
$key = get_feed_access_key($feed_id, $is_cat);
|
||||
|
||||
$url_path = htmlspecialchars($this->params[2]) . "&key=" . $key;
|
||||
|
||||
print "<h2>".__("You can view this feed as RSS using the following URL:")."</h2>";
|
||||
|
||||
print "<div class=\"tagCloudContainer\">";
|
||||
print "<a id='gen_feed_url' href='$url_path' target='_blank'>$url_path</a>";
|
||||
print "</div>";
|
||||
|
||||
print "<div align='center'>";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"return genUrlChangeKey('$feed_id', '$is_cat')\">".
|
||||
__('Generate new URL')."</button> ";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"return closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
|
||||
print "</div>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function newVersion() {
|
||||
|
||||
$version_data = check_for_update();
|
||||
$version = $version_data['version'];
|
||||
$id = $version_data['version_id'];
|
||||
|
||||
if ($version && $id) {
|
||||
print "<div class='tagCloudContainer'>";
|
||||
|
||||
print T_sprintf("New version of Tiny Tiny RSS is available (%s).",
|
||||
"<b>$version</b>");
|
||||
|
||||
print "</div>";
|
||||
|
||||
$details = "http://tt-rss.org/redmine/versions/$id";
|
||||
$download = "http://tt-rss.org/#Download";
|
||||
|
||||
print "<p align='center'>".__("You can update using built-in updater in the Preferences or by using update.php")."</p>";
|
||||
|
||||
print "<div style='text-align : center'>";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"return window.open('$details')\">".__("See the release notes")."</button>";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"return window.open('$download')\">".__("Download")."</button>";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"return dijit.byId('newVersionDlg').hide()\">".
|
||||
__('Close this window')."</button>";
|
||||
|
||||
} else {
|
||||
print "<div class='tagCloudContainer'>";
|
||||
|
||||
print "<p align='center'>".__("Error receiving version information or no new version available.")."</p>";
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<div style='text-align : center'>";
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"return dijit.byId('newVersionDlg').hide()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</div>";
|
||||
|
||||
}
|
||||
print "</div>";
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
8
source/classes/feedenclosure.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
class FeedEnclosure {
|
||||
public $link;
|
||||
public $type;
|
||||
public $length;
|
||||
public $title;
|
||||
}
|
||||
?>
|
15
source/classes/feeditem.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
abstract class FeedItem {
|
||||
abstract function get_id();
|
||||
abstract function get_date();
|
||||
abstract function get_link();
|
||||
abstract function get_title();
|
||||
abstract function get_description();
|
||||
abstract function get_content();
|
||||
abstract function get_comments_url();
|
||||
abstract function get_comments_count();
|
||||
abstract function get_categories();
|
||||
abstract function get_enclosures();
|
||||
abstract function get_author();
|
||||
}
|
||||
?>
|
159
source/classes/feeditem/atom.php
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
class FeedItem_Atom extends FeedItem_Common {
|
||||
|
||||
function get_id() {
|
||||
$id = $this->elem->getElementsByTagName("id")->item(0);
|
||||
|
||||
if ($id) {
|
||||
return $id->nodeValue;
|
||||
} else {
|
||||
return $this->get_link();
|
||||
}
|
||||
}
|
||||
|
||||
function get_date() {
|
||||
$updated = $this->elem->getElementsByTagName("updated")->item(0);
|
||||
|
||||
if ($updated) {
|
||||
return strtotime($updated->nodeValue);
|
||||
}
|
||||
|
||||
$published = $this->elem->getElementsByTagName("published")->item(0);
|
||||
|
||||
if ($published) {
|
||||
return strtotime($published->nodeValue);
|
||||
}
|
||||
|
||||
$date = $this->xpath->query("dc:date", $this->elem)->item(0);
|
||||
|
||||
if ($date) {
|
||||
return strtotime($date->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function get_link() {
|
||||
$links = $this->elem->getElementsByTagName("link");
|
||||
|
||||
foreach ($links as $link) {
|
||||
if ($link && $link->hasAttribute("href") &&
|
||||
(!$link->hasAttribute("rel")
|
||||
|| $link->getAttribute("rel") == "alternate"
|
||||
|| $link->getAttribute("rel") == "standout")) {
|
||||
$base = $this->xpath->evaluate("string(ancestor-or-self::*[@xml:base][1]/@xml:base)", $link);
|
||||
|
||||
if ($base)
|
||||
return rewrite_relative_url($base, $link->getAttribute("href"));
|
||||
else
|
||||
return $link->getAttribute("href");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_title() {
|
||||
$title = $this->elem->getElementsByTagName("title")->item(0);
|
||||
|
||||
if ($title) {
|
||||
return $title->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
function get_content() {
|
||||
$content = $this->elem->getElementsByTagName("content")->item(0);
|
||||
|
||||
if ($content) {
|
||||
if ($content->hasAttribute('type')) {
|
||||
if ($content->getAttribute('type') == 'xhtml') {
|
||||
for ($i = 0; $i < $content->childNodes->length; $i++) {
|
||||
$child = $content->childNodes->item($i);
|
||||
|
||||
if ($child->hasChildNodes()) {
|
||||
return $this->doc->saveXML($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $content->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
function get_description() {
|
||||
$content = $this->elem->getElementsByTagName("summary")->item(0);
|
||||
|
||||
if ($content) {
|
||||
if ($content->hasAttribute('type')) {
|
||||
if ($content->getAttribute('type') == 'xhtml') {
|
||||
for ($i = 0; $i < $content->childNodes->length; $i++) {
|
||||
$child = $content->childNodes->item($i);
|
||||
|
||||
if ($child->hasChildNodes()) {
|
||||
return $this->doc->saveXML($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $content->nodeValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function get_categories() {
|
||||
$categories = $this->elem->getElementsByTagName("category");
|
||||
$cats = array();
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
if ($cat->hasAttribute("term"))
|
||||
array_push($cats, $cat->getAttribute("term"));
|
||||
}
|
||||
|
||||
$categories = $this->xpath->query("dc:subject", $this->elem);
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
array_push($cats, $cat->nodeValue);
|
||||
}
|
||||
|
||||
return $cats;
|
||||
}
|
||||
|
||||
function get_enclosures() {
|
||||
$links = $this->elem->getElementsByTagName("link");
|
||||
|
||||
$encs = array();
|
||||
|
||||
foreach ($links as $link) {
|
||||
if ($link && $link->hasAttribute("href") && $link->hasAttribute("rel")) {
|
||||
if ($link->getAttribute("rel") == "enclosure") {
|
||||
$enc = new FeedEnclosure();
|
||||
|
||||
$enc->type = $link->getAttribute("type");
|
||||
$enc->link = $link->getAttribute("href");
|
||||
$enc->length = $link->getAttribute("length");
|
||||
|
||||
array_push($encs, $enc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$enclosures = $this->xpath->query("media:content | media:group/media:content", $this->elem);
|
||||
|
||||
foreach ($enclosures as $enclosure) {
|
||||
$enc = new FeedEnclosure();
|
||||
|
||||
$enc->type = $enclosure->getAttribute("type");
|
||||
$enc->link = $enclosure->getAttribute("url");
|
||||
$enc->length = $enclosure->getAttribute("length");
|
||||
|
||||
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
|
||||
if ($desc) $enc->title = strip_tags($desc->nodeValue);
|
||||
|
||||
array_push($encs, $enc);
|
||||
}
|
||||
|
||||
return $encs;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
62
source/classes/feeditem/common.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
abstract class FeedItem_Common extends FeedItem {
|
||||
protected $elem;
|
||||
protected $xpath;
|
||||
protected $doc;
|
||||
|
||||
function __construct($elem, $doc, $xpath) {
|
||||
$this->elem = $elem;
|
||||
$this->xpath = $xpath;
|
||||
$this->doc = $doc;
|
||||
|
||||
try {
|
||||
|
||||
$source = $elem->getElementsByTagName("source")->item(0);
|
||||
|
||||
// we don't need <source> element
|
||||
if ($source)
|
||||
$elem->removeChild($source);
|
||||
} catch (DOMException $e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
function get_author() {
|
||||
$author = $this->elem->getElementsByTagName("author")->item(0);
|
||||
|
||||
if ($author) {
|
||||
$name = $author->getElementsByTagName("name")->item(0);
|
||||
|
||||
if ($name) return $name->nodeValue;
|
||||
|
||||
$email = $author->getElementsByTagName("email")->item(0);
|
||||
|
||||
if ($email) return $email->nodeValue;
|
||||
|
||||
if ($author->nodeValue)
|
||||
return $author->nodeValue;
|
||||
}
|
||||
|
||||
$author = $this->xpath->query("dc:creator", $this->elem)->item(0);
|
||||
|
||||
if ($author) {
|
||||
return $author->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
// todo
|
||||
function get_comments_url() {
|
||||
|
||||
}
|
||||
|
||||
function get_comments_count() {
|
||||
$comments = $this->xpath->query("slash:comments", $this->elem)->item(0);
|
||||
|
||||
if ($comments) {
|
||||
return $comments->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
134
source/classes/feeditem/rss.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
class FeedItem_RSS extends FeedItem_Common {
|
||||
function get_id() {
|
||||
$id = $this->elem->getElementsByTagName("guid")->item(0);
|
||||
|
||||
if ($id) {
|
||||
return $id->nodeValue;
|
||||
} else {
|
||||
return $this->get_link();
|
||||
}
|
||||
}
|
||||
|
||||
function get_date() {
|
||||
$pubDate = $this->elem->getElementsByTagName("pubDate")->item(0);
|
||||
|
||||
if ($pubDate) {
|
||||
return strtotime($pubDate->nodeValue);
|
||||
}
|
||||
|
||||
$date = $this->xpath->query("dc:date", $this->elem)->item(0);
|
||||
|
||||
if ($date) {
|
||||
return strtotime($date->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
function get_link() {
|
||||
$links = $this->xpath->query("atom:link", $this->elem);
|
||||
|
||||
foreach ($links as $link) {
|
||||
if ($link && $link->hasAttribute("href") &&
|
||||
(!$link->hasAttribute("rel")
|
||||
|| $link->getAttribute("rel") == "alternate"
|
||||
|| $link->getAttribute("rel") == "standout")) {
|
||||
|
||||
return $link->getAttribute("href");
|
||||
}
|
||||
}
|
||||
|
||||
$link = $this->elem->getElementsByTagName("guid")->item(0);
|
||||
|
||||
if ($link && $link->hasAttributes() && $link->getAttribute("isPermaLink") == "true") {
|
||||
return $link->nodeValue;
|
||||
}
|
||||
|
||||
$link = $this->elem->getElementsByTagName("link")->item(0);
|
||||
|
||||
if ($link) {
|
||||
return $link->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
function get_title() {
|
||||
$title = $this->elem->getElementsByTagName("title")->item(0);
|
||||
|
||||
if ($title) {
|
||||
return $title->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
function get_content() {
|
||||
$content = $this->xpath->query("content:encoded", $this->elem)->item(0);
|
||||
|
||||
if ($content) {
|
||||
return $content->nodeValue;
|
||||
}
|
||||
|
||||
$content = $this->elem->getElementsByTagName("description")->item(0);
|
||||
|
||||
if ($content) {
|
||||
return $content->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
function get_description() {
|
||||
$summary = $this->elem->getElementsByTagName("description")->item(0);
|
||||
|
||||
if ($summary) {
|
||||
return $summary->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
function get_categories() {
|
||||
$categories = $this->elem->getElementsByTagName("category");
|
||||
$cats = array();
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
array_push($cats, $cat->nodeValue);
|
||||
}
|
||||
|
||||
$categories = $this->xpath->query("dc:subject", $this->elem);
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
array_push($cats, $cat->nodeValue);
|
||||
}
|
||||
|
||||
return $cats;
|
||||
}
|
||||
|
||||
function get_enclosures() {
|
||||
$enclosures = $this->elem->getElementsByTagName("enclosure");
|
||||
|
||||
$encs = array();
|
||||
|
||||
foreach ($enclosures as $enclosure) {
|
||||
$enc = new FeedEnclosure();
|
||||
|
||||
$enc->type = $enclosure->getAttribute("type");
|
||||
$enc->link = $enclosure->getAttribute("url");
|
||||
$enc->length = $enclosure->getAttribute("length");
|
||||
|
||||
array_push($encs, $enc);
|
||||
}
|
||||
|
||||
$enclosures = $this->xpath->query("media:content | media:group/media:content", $this->elem);
|
||||
|
||||
foreach ($enclosures as $enclosure) {
|
||||
$enc = new FeedEnclosure();
|
||||
|
||||
$enc->type = $enclosure->getAttribute("type");
|
||||
$enc->link = $enclosure->getAttribute("url");
|
||||
$enc->length = $enclosure->getAttribute("length");
|
||||
|
||||
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
|
||||
if ($desc) $enc->title = strip_tags($desc->nodeValue);
|
||||
|
||||
array_push($encs, $enc);
|
||||
}
|
||||
|
||||
return $encs;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
246
source/classes/feedparser.php
Normal file
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
class FeedParser {
|
||||
private $doc;
|
||||
private $error;
|
||||
private $items;
|
||||
private $link;
|
||||
private $title;
|
||||
private $type;
|
||||
private $xpath;
|
||||
|
||||
const FEED_RDF = 0;
|
||||
const FEED_RSS = 1;
|
||||
const FEED_ATOM = 2;
|
||||
|
||||
function __construct($data) {
|
||||
libxml_use_internal_errors(true);
|
||||
libxml_clear_errors();
|
||||
$this->doc = new DOMDocument();
|
||||
$this->doc->loadXML($data);
|
||||
|
||||
mb_substitute_character("none");
|
||||
|
||||
$error = libxml_get_last_error();
|
||||
|
||||
// libxml compiled without iconv?
|
||||
if ($error && ($error->code == 32 || $error->code == 9)) {
|
||||
if (preg_match('/^(<\?xml[\t\n\r ].*?encoding=["\'])(.+?)(["\'].*?\?>)/s', $data, $matches) === 1) {
|
||||
$enc = $matches[2];
|
||||
|
||||
$data = mb_convert_encoding($data, 'UTF-8', $enc);
|
||||
|
||||
$data = preg_replace('/^<\?xml[\t\n\r ].*?\?>/s', $matches[1] . "UTF-8" . $matches[3] , $data);
|
||||
|
||||
|
||||
// apparently not all UTF-8 characters are valid for XML
|
||||
$data = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $data);
|
||||
|
||||
if ($data) {
|
||||
libxml_clear_errors();
|
||||
|
||||
$this->doc = new DOMDocument();
|
||||
$this->doc->loadXML($data);
|
||||
|
||||
$error = libxml_get_last_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// some terrible invalid unicode entity?
|
||||
if ($error && $error->code == 9) {
|
||||
$data = mb_convert_encoding($data, 'UTF-8', 'UTF-8');
|
||||
|
||||
if ($data) {
|
||||
libxml_clear_errors();
|
||||
|
||||
$this->doc = new DOMDocument();
|
||||
$this->doc->loadXML($data);
|
||||
|
||||
$error = libxml_get_last_error();
|
||||
}
|
||||
}
|
||||
|
||||
$this->error = $this->format_error($error);
|
||||
libxml_clear_errors();
|
||||
|
||||
$this->items = array();
|
||||
}
|
||||
|
||||
function init() {
|
||||
$root = $this->doc->firstChild;
|
||||
$xpath = new DOMXPath($this->doc);
|
||||
$xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
|
||||
$xpath->registerNamespace('atom03', 'http://purl.org/atom/ns#');
|
||||
$xpath->registerNamespace('media', 'http://search.yahoo.com/mrss/');
|
||||
$xpath->registerNamespace('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
|
||||
$xpath->registerNamespace('slash', 'http://purl.org/rss/1.0/modules/slash/');
|
||||
$xpath->registerNamespace('dc', 'http://purl.org/dc/elements/1.1/');
|
||||
$xpath->registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/');
|
||||
|
||||
$this->xpath = $xpath;
|
||||
|
||||
$root = $xpath->query("(//atom03:feed|//atom:feed|//channel|//rdf:rdf|//rdf:RDF)");
|
||||
|
||||
if ($root) {
|
||||
$root = $root->item(0);
|
||||
|
||||
if ($root) {
|
||||
switch (mb_strtolower($root->tagName)) {
|
||||
case "rdf:rdf":
|
||||
$this->type = $this::FEED_RDF;
|
||||
break;
|
||||
case "channel":
|
||||
$this->type = $this::FEED_RSS;
|
||||
break;
|
||||
case "feed":
|
||||
$this->type = $this::FEED_ATOM;
|
||||
break;
|
||||
default:
|
||||
if( !isset($this->error) ){
|
||||
$this->error = "Unknown/unsupported feed type";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($this->type) {
|
||||
case $this::FEED_ATOM:
|
||||
|
||||
$title = $xpath->query("//atom:feed/atom:title")->item(0);
|
||||
|
||||
if (!$title)
|
||||
$title = $xpath->query("//atom03:feed/atom03:title")->item(0);
|
||||
|
||||
|
||||
if ($title) {
|
||||
$this->title = $title->nodeValue;
|
||||
}
|
||||
|
||||
$link = $xpath->query("//atom:feed/atom:link[not(@rel)]")->item(0);
|
||||
|
||||
if (!$link)
|
||||
$link = $xpath->query("//atom03:feed/atom03:link[not(@rel)]")->item(0);
|
||||
|
||||
|
||||
if ($link && $link->hasAttributes()) {
|
||||
$this->link = $link->getAttribute("href");
|
||||
}
|
||||
|
||||
$articles = $xpath->query("//atom:entry");
|
||||
|
||||
if (!$articles || $articles->length == 0)
|
||||
$articles = $xpath->query("//atom03:entry");
|
||||
|
||||
foreach ($articles as $article) {
|
||||
array_push($this->items, new FeedItem_Atom($article, $this->doc, $this->xpath));
|
||||
}
|
||||
|
||||
break;
|
||||
case $this::FEED_RSS:
|
||||
$title = $xpath->query("//channel/title")->item(0);
|
||||
|
||||
if ($title) {
|
||||
$this->title = $title->nodeValue;
|
||||
}
|
||||
|
||||
$link = $xpath->query("//channel/link")->item(0);
|
||||
|
||||
if ($link) {
|
||||
if ($link->getAttribute("href"))
|
||||
$this->link = $link->getAttribute("href");
|
||||
else if ($link->nodeValue)
|
||||
$this->link = $link->nodeValue;
|
||||
}
|
||||
|
||||
$articles = $xpath->query("//channel/item");
|
||||
|
||||
foreach ($articles as $article) {
|
||||
array_push($this->items, new FeedItem_RSS($article, $this->doc, $this->xpath));
|
||||
}
|
||||
|
||||
break;
|
||||
case $this::FEED_RDF:
|
||||
$xpath->registerNamespace('rssfake', 'http://purl.org/rss/1.0/');
|
||||
|
||||
$title = $xpath->query("//rssfake:channel/rssfake:title")->item(0);
|
||||
|
||||
if ($title) {
|
||||
$this->title = $title->nodeValue;
|
||||
}
|
||||
|
||||
$link = $xpath->query("//rssfake:channel/rssfake:link")->item(0);
|
||||
|
||||
if ($link) {
|
||||
$this->link = $link->nodeValue;
|
||||
}
|
||||
|
||||
$articles = $xpath->query("//rssfake:item");
|
||||
|
||||
foreach ($articles as $article) {
|
||||
array_push($this->items, new FeedItem_RSS($article, $this->doc, $this->xpath));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
} else {
|
||||
if( !isset($this->error) ){
|
||||
$this->error = "Unknown/unsupported feed type";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function format_error($error) {
|
||||
if ($error) {
|
||||
return sprintf("LibXML error %s at line %d (column %d): %s",
|
||||
$error->code, $error->line, $error->column,
|
||||
$error->message);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function error() {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
function get_link() {
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
function get_title() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
function get_items() {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
function get_links($rel) {
|
||||
$rv = array();
|
||||
|
||||
switch ($this->type) {
|
||||
case $this::FEED_ATOM:
|
||||
$links = $this->xpath->query("//atom:feed/atom:link");
|
||||
|
||||
foreach ($links as $link) {
|
||||
if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) {
|
||||
array_push($rv, $link->getAttribute('href'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case $this::FEED_RSS:
|
||||
$links = $this->xpath->query("//atom:link");
|
||||
|
||||
foreach ($links as $link) {
|
||||
if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) {
|
||||
array_push($rv, $link->getAttribute('href'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $rv;
|
||||
}
|
||||
} ?>
|
1161
source/classes/feeds.php
Normal file
24
source/classes/handler.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
class Handler implements IHandler {
|
||||
protected $dbh;
|
||||
protected $args;
|
||||
|
||||
function __construct($args) {
|
||||
$this->dbh = Db::get();
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
function csrf_ignore($method) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function before($method) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function after() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
8
source/classes/handler/protected.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
class Handler_Protected extends Handler {
|
||||
|
||||
function before($method) {
|
||||
return parent::before($method) && $_SESSION['uid'];
|
||||
}
|
||||
}
|
||||
?>
|
976
source/classes/handler/public.php
Normal file
|
@ -0,0 +1,976 @@
|
|||
<?php
|
||||
class Handler_Public extends Handler {
|
||||
|
||||
private function generate_syndicated_feed($owner_uid, $feed, $is_cat,
|
||||
$limit, $offset, $search, $search_mode,
|
||||
$view_mode = false, $format = 'atom', $order = false, $orig_guid = false) {
|
||||
|
||||
require_once "lib/MiniTemplator.class.php";
|
||||
|
||||
$note_style = "background-color : #fff7d5;
|
||||
border-width : 1px; ".
|
||||
"padding : 5px; border-style : dashed; border-color : #e7d796;".
|
||||
"margin-bottom : 1em; color : #9a8c59;";
|
||||
|
||||
if (!$limit) $limit = 60;
|
||||
|
||||
$date_sort_field = "date_entered DESC, updated DESC";
|
||||
|
||||
if ($feed == -2)
|
||||
$date_sort_field = "last_published DESC";
|
||||
else if ($feed == -1)
|
||||
$date_sort_field = "last_marked DESC";
|
||||
|
||||
switch ($order) {
|
||||
case "title":
|
||||
$date_sort_field = "ttrss_entries.title";
|
||||
break;
|
||||
case "date_reverse":
|
||||
$date_sort_field = "date_entered, updated";
|
||||
break;
|
||||
case "feed_dates":
|
||||
$date_sort_field = "updated DESC";
|
||||
break;
|
||||
}
|
||||
|
||||
$qfh_ret = queryFeedHeadlines($feed,
|
||||
1, $view_mode, $is_cat, $search, $search_mode,
|
||||
$date_sort_field, $offset, $owner_uid,
|
||||
false, 0, false, true);
|
||||
|
||||
$result = $qfh_ret[0];
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$ts = strtotime($this->dbh->fetch_result($result, 0, "date_entered"));
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
|
||||
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $ts) {
|
||||
header('HTTP/1.0 304 Not Modified');
|
||||
return;
|
||||
}
|
||||
|
||||
$last_modified = gmdate("D, d M Y H:i:s", $ts) . " GMT";
|
||||
header("Last-Modified: $last_modified", true);
|
||||
}
|
||||
|
||||
$qfh_ret = queryFeedHeadlines($feed,
|
||||
$limit, $view_mode, $is_cat, $search, $search_mode,
|
||||
$date_sort_field, $offset, $owner_uid,
|
||||
false, 0, false, true);
|
||||
|
||||
|
||||
$result = $qfh_ret[0];
|
||||
$feed_title = htmlspecialchars($qfh_ret[1]);
|
||||
$feed_site_url = $qfh_ret[2];
|
||||
$last_error = $qfh_ret[3];
|
||||
|
||||
$feed_self_url = get_self_url_prefix() .
|
||||
"/public.php?op=rss&id=$feed&key=" .
|
||||
get_feed_access_key($feed, false, $owner_uid);
|
||||
|
||||
if (!$feed_site_url) $feed_site_url = get_self_url_prefix();
|
||||
|
||||
if ($format == 'atom') {
|
||||
$tpl = new MiniTemplator;
|
||||
|
||||
$tpl->readTemplateFromFile("templates/generated_feed.txt");
|
||||
|
||||
$tpl->setVariable('FEED_TITLE', $feed_title, true);
|
||||
$tpl->setVariable('VERSION', VERSION, true);
|
||||
$tpl->setVariable('FEED_URL', htmlspecialchars($feed_self_url), true);
|
||||
|
||||
if (PUBSUBHUBBUB_HUB && $feed == -2) {
|
||||
$tpl->setVariable('HUB_URL', htmlspecialchars(PUBSUBHUBBUB_HUB), true);
|
||||
$tpl->addBlock('feed_hub');
|
||||
}
|
||||
|
||||
$tpl->setVariable('SELF_URL', htmlspecialchars(get_self_url_prefix()), true);
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$line["content_preview"] = truncate_string(strip_tags($line["content_preview"]), 100, '...');
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
|
||||
$line = $p->hook_query_headlines($line);
|
||||
}
|
||||
|
||||
$tpl->setVariable('ARTICLE_ID',
|
||||
htmlspecialchars($orig_guid ? $line['link'] :
|
||||
get_self_url_prefix() .
|
||||
"/public.php?url=" . urlencode($line['link'])), true);
|
||||
$tpl->setVariable('ARTICLE_LINK', htmlspecialchars($line['link']), true);
|
||||
$tpl->setVariable('ARTICLE_TITLE', htmlspecialchars($line['title']), true);
|
||||
$tpl->setVariable('ARTICLE_EXCERPT', $line["content_preview"], true);
|
||||
|
||||
$content = sanitize($line["content"], false, $owner_uid);
|
||||
|
||||
if ($line['note']) {
|
||||
$content = "<div style=\"$note_style\">Article note: " . $line['note'] . "</div>" .
|
||||
$content;
|
||||
$tpl->setVariable('ARTICLE_NOTE', htmlspecialchars($line['note']), true);
|
||||
}
|
||||
|
||||
$tpl->setVariable('ARTICLE_CONTENT', $content, true);
|
||||
|
||||
$tpl->setVariable('ARTICLE_UPDATED_ATOM',
|
||||
date('c', strtotime($line["updated"])), true);
|
||||
$tpl->setVariable('ARTICLE_UPDATED_RFC822',
|
||||
date(DATE_RFC822, strtotime($line["updated"])), true);
|
||||
|
||||
$tpl->setVariable('ARTICLE_AUTHOR', htmlspecialchars($line['author']), true);
|
||||
|
||||
$tpl->setVariable('ARTICLE_SOURCE_LINK', htmlspecialchars($line['site_url']), true);
|
||||
$tpl->setVariable('ARTICLE_SOURCE_TITLE', htmlspecialchars($line['feed_title']), true);
|
||||
|
||||
$tags = get_article_tags($line["id"], $owner_uid);
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$tpl->setVariable('ARTICLE_CATEGORY', htmlspecialchars($tag), true);
|
||||
$tpl->addBlock('category');
|
||||
}
|
||||
|
||||
$enclosures = get_article_enclosures($line["id"]);
|
||||
|
||||
foreach ($enclosures as $e) {
|
||||
$type = htmlspecialchars($e['content_type']);
|
||||
$url = htmlspecialchars($e['content_url']);
|
||||
$length = $e['duration'];
|
||||
|
||||
$tpl->setVariable('ARTICLE_ENCLOSURE_URL', $url, true);
|
||||
$tpl->setVariable('ARTICLE_ENCLOSURE_TYPE', $type, true);
|
||||
$tpl->setVariable('ARTICLE_ENCLOSURE_LENGTH', $length, true);
|
||||
|
||||
$tpl->addBlock('enclosure');
|
||||
}
|
||||
|
||||
$tpl->addBlock('entry');
|
||||
}
|
||||
|
||||
$tmp = "";
|
||||
|
||||
$tpl->addBlock('feed');
|
||||
$tpl->generateOutputToString($tmp);
|
||||
|
||||
if (@!$_REQUEST["noxml"]) {
|
||||
header("Content-Type: text/xml; charset=utf-8");
|
||||
} else {
|
||||
header("Content-Type: text/plain; charset=utf-8");
|
||||
}
|
||||
|
||||
print $tmp;
|
||||
} else if ($format == 'json') {
|
||||
|
||||
$feed = array();
|
||||
|
||||
$feed['title'] = $feed_title;
|
||||
$feed['version'] = VERSION;
|
||||
$feed['feed_url'] = $feed_self_url;
|
||||
|
||||
if (PUBSUBHUBBUB_HUB && $feed == -2) {
|
||||
$feed['hub_url'] = PUBSUBHUBBUB_HUB;
|
||||
}
|
||||
|
||||
$feed['self_url'] = get_self_url_prefix();
|
||||
|
||||
$feed['articles'] = array();
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$line["content_preview"] = truncate_string(strip_tags($line["content_preview"]), 100, '...');
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
|
||||
$line = $p->hook_query_headlines($line, 100);
|
||||
}
|
||||
$article = array();
|
||||
|
||||
$article['id'] = $line['link'];
|
||||
$article['link'] = $line['link'];
|
||||
$article['title'] = $line['title'];
|
||||
$article['excerpt'] = $line["content_preview"];
|
||||
$article['content'] = sanitize($line["content"], false, $owner_uid);
|
||||
$article['updated'] = date('c', strtotime($line["updated"]));
|
||||
|
||||
if ($line['note']) $article['note'] = $line['note'];
|
||||
if ($article['author']) $article['author'] = $line['author'];
|
||||
|
||||
$tags = get_article_tags($line["id"], $owner_uid);
|
||||
|
||||
if (count($tags) > 0) {
|
||||
$article['tags'] = array();
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
array_push($article['tags'], $tag);
|
||||
}
|
||||
}
|
||||
|
||||
$enclosures = get_article_enclosures($line["id"]);
|
||||
|
||||
if (count($enclosures) > 0) {
|
||||
$article['enclosures'] = array();
|
||||
|
||||
foreach ($enclosures as $e) {
|
||||
$type = $e['content_type'];
|
||||
$url = $e['content_url'];
|
||||
$length = $e['duration'];
|
||||
|
||||
array_push($article['enclosures'], array("url" => $url, "type" => $type, "length" => $length));
|
||||
}
|
||||
}
|
||||
|
||||
array_push($feed['articles'], $article);
|
||||
}
|
||||
|
||||
header("Content-Type: text/json; charset=utf-8");
|
||||
print json_encode($feed);
|
||||
|
||||
} else {
|
||||
header("Content-Type: text/plain; charset=utf-8");
|
||||
print json_encode(array("error" => array("message" => "Unknown format")));
|
||||
}
|
||||
}
|
||||
|
||||
function getUnread() {
|
||||
$login = $this->dbh->escape_string($_REQUEST["login"]);
|
||||
$fresh = $_REQUEST["fresh"] == "1";
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_users WHERE login = '$login'");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 1) {
|
||||
$uid = $this->dbh->fetch_result($result, 0, "id");
|
||||
|
||||
print getGlobalUnread($uid);
|
||||
|
||||
if ($fresh) {
|
||||
print ";";
|
||||
print getFeedArticles(-3, false, true, $uid);
|
||||
}
|
||||
|
||||
} else {
|
||||
print "-1;User not found";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getProfiles() {
|
||||
$login = $this->dbh->escape_string($_REQUEST["login"]);
|
||||
|
||||
$result = $this->dbh->query("SELECT ttrss_settings_profiles.* FROM ttrss_settings_profiles,ttrss_users
|
||||
WHERE ttrss_users.id = ttrss_settings_profiles.owner_uid AND login = '$login' ORDER BY title");
|
||||
|
||||
print "<select dojoType='dijit.form.Select' style='width : 220px; margin : 0px' name='profile'>";
|
||||
|
||||
print "<option value='0'>" . __("Default profile") . "</option>";
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$id = $line["id"];
|
||||
$title = $line["title"];
|
||||
|
||||
print "<option value='$id'>$title</option>";
|
||||
}
|
||||
|
||||
print "</select>";
|
||||
}
|
||||
|
||||
function pubsub() {
|
||||
$mode = $this->dbh->escape_string($_REQUEST['hub_mode']);
|
||||
$feed_id = (int) $this->dbh->escape_string($_REQUEST['id']);
|
||||
$feed_url = $this->dbh->escape_string($_REQUEST['hub_topic']);
|
||||
|
||||
if (!PUBSUBHUBBUB_ENABLED) {
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
echo "404 Not found";
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: implement hub_verifytoken checking
|
||||
|
||||
$result = $this->dbh->query("SELECT feed_url FROM ttrss_feeds
|
||||
WHERE id = '$feed_id'");
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
|
||||
$check_feed_url = $this->dbh->fetch_result($result, 0, "feed_url");
|
||||
|
||||
if ($check_feed_url && ($check_feed_url == $feed_url || !$feed_url)) {
|
||||
if ($mode == "subscribe") {
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_feeds SET pubsub_state = 2
|
||||
WHERE id = '$feed_id'");
|
||||
|
||||
print $_REQUEST['hub_challenge'];
|
||||
return;
|
||||
|
||||
} else if ($mode == "unsubscribe") {
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_feeds SET pubsub_state = 0
|
||||
WHERE id = '$feed_id'");
|
||||
|
||||
print $_REQUEST['hub_challenge'];
|
||||
return;
|
||||
|
||||
} else if (!$mode) {
|
||||
|
||||
// Received update ping, schedule feed update.
|
||||
//update_rss_feed($feed_id, true, true);
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_feeds SET
|
||||
last_update_started = '1970-01-01',
|
||||
last_updated = '1970-01-01' WHERE id = '$feed_id'");
|
||||
|
||||
}
|
||||
} else {
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
echo "404 Not found";
|
||||
}
|
||||
} else {
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
echo "404 Not found";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function logout() {
|
||||
logout_user();
|
||||
header("Location: index.php");
|
||||
}
|
||||
|
||||
function share() {
|
||||
$uuid = $this->dbh->escape_string($_REQUEST["key"]);
|
||||
|
||||
$result = $this->dbh->query("SELECT ref_id, owner_uid FROM ttrss_user_entries WHERE
|
||||
uuid = '$uuid'");
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
header("Content-Type: text/html");
|
||||
|
||||
$id = $this->dbh->fetch_result($result, 0, "ref_id");
|
||||
$owner_uid = $this->dbh->fetch_result($result, 0, "owner_uid");
|
||||
|
||||
$article = format_article($id, false, true, $owner_uid);
|
||||
|
||||
print_r($article['content']);
|
||||
|
||||
} else {
|
||||
print "Article not found.";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function rss() {
|
||||
$feed = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
$key = $this->dbh->escape_string($_REQUEST["key"]);
|
||||
$is_cat = sql_bool_to_bool($_REQUEST["is_cat"]);
|
||||
$limit = (int)$this->dbh->escape_string($_REQUEST["limit"]);
|
||||
$offset = (int)$this->dbh->escape_string($_REQUEST["offset"]);
|
||||
|
||||
$search = $this->dbh->escape_string($_REQUEST["q"]);
|
||||
$search_mode = $this->dbh->escape_string($_REQUEST["smode"]);
|
||||
$view_mode = $this->dbh->escape_string($_REQUEST["view-mode"]);
|
||||
$order = $this->dbh->escape_string($_REQUEST["order"]);
|
||||
|
||||
$format = $this->dbh->escape_string($_REQUEST['format']);
|
||||
$orig_guid = !sql_bool_to_bool($_REQUEST["no_orig_guid"]);
|
||||
|
||||
if (!$format) $format = 'atom';
|
||||
|
||||
if (SINGLE_USER_MODE) {
|
||||
authenticate_user("admin", null);
|
||||
}
|
||||
|
||||
$owner_id = false;
|
||||
|
||||
if ($key) {
|
||||
$result = $this->dbh->query("SELECT owner_uid FROM
|
||||
ttrss_access_keys WHERE access_key = '$key' AND feed_id = '$feed'");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 1)
|
||||
$owner_id = $this->dbh->fetch_result($result, 0, "owner_uid");
|
||||
}
|
||||
|
||||
if ($owner_id) {
|
||||
$this->generate_syndicated_feed($owner_id, $feed, $is_cat, $limit,
|
||||
$offset, $search, $search_mode, $view_mode, $format, $order, $orig_guid);
|
||||
} else {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
}
|
||||
}
|
||||
|
||||
function updateTask() {
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", $op);
|
||||
}
|
||||
|
||||
function housekeepingTask() {
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", $op);
|
||||
}
|
||||
|
||||
function globalUpdateFeeds() {
|
||||
RPC::updaterandomfeed_real($this->dbh);
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", $op);
|
||||
}
|
||||
|
||||
function sharepopup() {
|
||||
if (SINGLE_USER_MODE) {
|
||||
login_sequence();
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
print "<html><head><title>Tiny Tiny RSS</title>";
|
||||
|
||||
stylesheet_tag("css/utility.css");
|
||||
javascript_tag("lib/prototype.js");
|
||||
javascript_tag("lib/scriptaculous/scriptaculous.js?load=effects,dragdrop,controls");
|
||||
print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
|
||||
</head><body id='sharepopup'>";
|
||||
|
||||
$action = $_REQUEST["action"];
|
||||
|
||||
if ($_SESSION["uid"]) {
|
||||
|
||||
if ($action == 'share') {
|
||||
|
||||
$title = $this->dbh->escape_string(strip_tags($_REQUEST["title"]));
|
||||
$url = $this->dbh->escape_string(strip_tags($_REQUEST["url"]));
|
||||
$content = $this->dbh->escape_string(strip_tags($_REQUEST["content"]));
|
||||
$labels = $this->dbh->escape_string(strip_tags($_REQUEST["labels"]));
|
||||
|
||||
Article::create_published_article($title, $url, $content, $labels,
|
||||
$_SESSION["uid"]);
|
||||
|
||||
print "<script type='text/javascript'>";
|
||||
print "window.close();";
|
||||
print "</script>";
|
||||
|
||||
} else {
|
||||
$title = htmlspecialchars($_REQUEST["title"]);
|
||||
$url = htmlspecialchars($_REQUEST["url"]);
|
||||
|
||||
?>
|
||||
|
||||
<table height='100%' width='100%'><tr><td colspan='2'>
|
||||
<h1><?php echo __("Share with Tiny Tiny RSS") ?></h1>
|
||||
</td></tr>
|
||||
|
||||
<form id='share_form' name='share_form'>
|
||||
|
||||
<input type="hidden" name="op" value="sharepopup">
|
||||
<input type="hidden" name="action" value="share">
|
||||
|
||||
<tr><td align='right'><?php echo __("Title:") ?></td>
|
||||
<td width='80%'><input name='title' value="<?php echo $title ?>"></td></tr>
|
||||
<tr><td align='right'><?php echo __("URL:") ?></td>
|
||||
<td><input name='url' value="<?php echo $url ?>"></td></tr>
|
||||
<tr><td align='right'><?php echo __("Content:") ?></td>
|
||||
<td><input name='content' value=""></td></tr>
|
||||
<tr><td align='right'><?php echo __("Labels:") ?></td>
|
||||
<td><input name='labels' id="labels_value"
|
||||
placeholder='Alpha, Beta, Gamma' value="">
|
||||
</td></tr>
|
||||
|
||||
<tr><td>
|
||||
<div class="autocomplete" id="labels_choices"
|
||||
style="display : block"></div></td></tr>
|
||||
|
||||
<script type='text/javascript'>document.forms[0].title.focus();</script>
|
||||
|
||||
<script type='text/javascript'>
|
||||
new Ajax.Autocompleter('labels_value', 'labels_choices',
|
||||
"backend.php?op=rpc&method=completeLabels",
|
||||
{ tokens: ',', paramName: "search" });
|
||||
</script>
|
||||
|
||||
<tr><td colspan='2'>
|
||||
<div style='float : right' class='insensitive-small'>
|
||||
<?php echo __("Shared article will appear in the Published feed.") ?>
|
||||
</div>
|
||||
<button type="submit"><?php echo __('Share') ?></button>
|
||||
<button onclick="return window.close()"><?php echo __('Cancel') ?></button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</td></tr></table>
|
||||
</body></html>
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$return = urlencode($_SERVER["REQUEST_URI"])
|
||||
?>
|
||||
|
||||
<form action="public.php?return=<?php echo $return ?>"
|
||||
method="POST" id="loginForm" name="loginForm">
|
||||
|
||||
<input type="hidden" name="op" value="login">
|
||||
|
||||
<table height='100%' width='100%'><tr><td colspan='2'>
|
||||
<h1><?php echo __("Not logged in") ?></h1></td></tr>
|
||||
|
||||
<tr><td align="right"><?php echo __("Login:") ?></td>
|
||||
<td align="right"><input name="login"
|
||||
value="<?php echo $_SESSION["fake_login"] ?>"></td></tr>
|
||||
<tr><td align="right"><?php echo __("Password:") ?></td>
|
||||
<td align="right"><input type="password" name="password"
|
||||
value="<?php echo $_SESSION["fake_password"] ?>"></td></tr>
|
||||
<tr><td colspan='2'>
|
||||
<button type="submit">
|
||||
<?php echo __('Log in') ?></button>
|
||||
|
||||
<button onclick="return window.close()">
|
||||
<?php echo __('Cancel') ?></button>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
function login() {
|
||||
if (!SINGLE_USER_MODE) {
|
||||
|
||||
$login = $this->dbh->escape_string($_POST["login"]);
|
||||
$password = $_POST["password"];
|
||||
$remember_me = $_POST["remember_me"];
|
||||
|
||||
if ($remember_me) {
|
||||
session_set_cookie_params(SESSION_COOKIE_LIFETIME);
|
||||
} else {
|
||||
session_set_cookie_params(0);
|
||||
}
|
||||
|
||||
@session_start();
|
||||
|
||||
if (authenticate_user($login, $password)) {
|
||||
$_POST["password"] = "";
|
||||
|
||||
if (get_schema_version() >= 120) {
|
||||
$_SESSION["language"] = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
$_SESSION["ref_schema_version"] = get_schema_version(true);
|
||||
$_SESSION["bw_limit"] = !!$_POST["bw_limit"];
|
||||
|
||||
if ($_POST["profile"]) {
|
||||
|
||||
$profile = $this->dbh->escape_string($_POST["profile"]);
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_settings_profiles
|
||||
WHERE id = '$profile' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$_SESSION["profile"] = $profile;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$_SESSION["login_error_msg"] = __("Incorrect username or password");
|
||||
user_error("Failed login attempt from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING);
|
||||
}
|
||||
|
||||
if ($_REQUEST['return']) {
|
||||
header("Location: " . $_REQUEST['return']);
|
||||
} else {
|
||||
header("Location: " . SELF_URL_PATH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function subscribe() {
|
||||
if (SINGLE_USER_MODE) {
|
||||
login_sequence();
|
||||
}
|
||||
|
||||
if ($_SESSION["uid"]) {
|
||||
|
||||
$feed_url = $this->dbh->escape_string(trim($_REQUEST["feed_url"]));
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
print "<html>
|
||||
<head>
|
||||
<title>Tiny Tiny RSS</title>
|
||||
<link rel=\"stylesheet\" type=\"text/css\" href=\"css/utility.css\">
|
||||
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
|
||||
</head>
|
||||
<body>
|
||||
<img class=\"floatingLogo\" src=\"images/logo_small.png\"
|
||||
alt=\"Tiny Tiny RSS\"/>
|
||||
<h1>".__("Subscribe to feed...")."</h1><div class='content'>";
|
||||
|
||||
$rc = subscribe_to_feed($feed_url);
|
||||
|
||||
switch ($rc['code']) {
|
||||
case 0:
|
||||
print_warning(T_sprintf("Already subscribed to <b>%s</b>.", $feed_url));
|
||||
break;
|
||||
case 1:
|
||||
print_notice(T_sprintf("Subscribed to <b>%s</b>.", $feed_url));
|
||||
break;
|
||||
case 2:
|
||||
print_error(T_sprintf("Could not subscribe to <b>%s</b>.", $feed_url));
|
||||
break;
|
||||
case 3:
|
||||
print_error(T_sprintf("No feeds found in <b>%s</b>.", $feed_url));
|
||||
break;
|
||||
case 4:
|
||||
print_notice(__("Multiple feed URLs found."));
|
||||
$feed_urls = $rc["feeds"];
|
||||
break;
|
||||
case 5:
|
||||
print_error(T_sprintf("Could not subscribe to <b>%s</b>.<br>Can't download the Feed URL.", $feed_url));
|
||||
break;
|
||||
}
|
||||
|
||||
if ($feed_urls) {
|
||||
|
||||
print "<form action=\"public.php\">";
|
||||
print "<input type=\"hidden\" name=\"op\" value=\"subscribe\">";
|
||||
|
||||
print "<select name=\"feed_url\">";
|
||||
|
||||
foreach ($feed_urls as $url => $name) {
|
||||
$url = htmlspecialchars($url);
|
||||
$name = htmlspecialchars($name);
|
||||
|
||||
print "<option value=\"$url\">$name</option>";
|
||||
}
|
||||
|
||||
print "<input type=\"submit\" value=\"".__("Subscribe to selected feed").
|
||||
"\">";
|
||||
|
||||
print "</form>";
|
||||
}
|
||||
|
||||
$tp_uri = get_self_url_prefix() . "/prefs.php";
|
||||
$tt_uri = get_self_url_prefix();
|
||||
|
||||
if ($rc['code'] <= 2){
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE
|
||||
feed_url = '$feed_url' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
$feed_id = $this->dbh->fetch_result($result, 0, "id");
|
||||
} else {
|
||||
$feed_id = 0;
|
||||
}
|
||||
print "<p>";
|
||||
|
||||
if ($feed_id) {
|
||||
print "<form method=\"GET\" style='display: inline'
|
||||
action=\"$tp_uri\">
|
||||
<input type=\"hidden\" name=\"tab\" value=\"feedConfig\">
|
||||
<input type=\"hidden\" name=\"method\" value=\"editFeed\">
|
||||
<input type=\"hidden\" name=\"methodparam\" value=\"$feed_id\">
|
||||
<input type=\"submit\" value=\"".__("Edit subscription options")."\">
|
||||
</form>";
|
||||
}
|
||||
|
||||
print "<form style='display: inline' method=\"GET\" action=\"$tt_uri\">
|
||||
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
|
||||
</form></p>";
|
||||
|
||||
print "</div></body></html>";
|
||||
|
||||
} else {
|
||||
render_login_form();
|
||||
}
|
||||
}
|
||||
|
||||
function subscribe2() {
|
||||
$feed_url = $this->dbh->escape_string(trim($_REQUEST["feed_url"]));
|
||||
$cat_id = $this->dbh->escape_string($_REQUEST["cat_id"]);
|
||||
$from = $this->dbh->escape_string($_REQUEST["from"]);
|
||||
$feed_urls = array();
|
||||
|
||||
/* only read authentication information from POST */
|
||||
|
||||
$auth_login = $this->dbh->escape_string(trim($_POST["auth_login"]));
|
||||
$auth_pass = $this->dbh->escape_string(trim($_POST["auth_pass"]));
|
||||
|
||||
$rc = subscribe_to_feed($feed_url, $cat_id, $auth_login, $auth_pass);
|
||||
|
||||
switch ($rc) {
|
||||
case 1:
|
||||
print_notice(T_sprintf("Subscribed to <b>%s</b>.", $feed_url));
|
||||
break;
|
||||
case 2:
|
||||
print_error(T_sprintf("Could not subscribe to <b>%s</b>.", $feed_url));
|
||||
break;
|
||||
case 3:
|
||||
print_error(T_sprintf("No feeds found in <b>%s</b>.", $feed_url));
|
||||
break;
|
||||
case 0:
|
||||
print_warning(T_sprintf("Already subscribed to <b>%s</b>.", $feed_url));
|
||||
break;
|
||||
case 4:
|
||||
print_notice(__("Multiple feed URLs found."));
|
||||
$contents = @fetch_file_contents($url, false, $auth_login, $auth_pass);
|
||||
if (is_html($contents)) {
|
||||
$feed_urls = get_feeds_from_html($url, $contents);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
print_error(T_sprintf("Could not subscribe to <b>%s</b>.<br>Can't download the Feed URL.", $feed_url));
|
||||
break;
|
||||
}
|
||||
|
||||
if ($feed_urls) {
|
||||
print "<form action=\"backend.php\">";
|
||||
print "<input type=\"hidden\" name=\"op\" value=\"pref-feeds\">";
|
||||
print "<input type=\"hidden\" name=\"quiet\" value=\"1\">";
|
||||
print "<input type=\"hidden\" name=\"method\" value=\"add\">";
|
||||
|
||||
print "<select name=\"feed_url\">";
|
||||
|
||||
foreach ($feed_urls as $url => $name) {
|
||||
$url = htmlspecialchars($url);
|
||||
$name = htmlspecialchars($name);
|
||||
print "<option value=\"$url\">$name</option>";
|
||||
}
|
||||
|
||||
print "<input type=\"submit\" value=\"".__("Subscribe to selected feed")."\">";
|
||||
print "</form>";
|
||||
}
|
||||
|
||||
$tp_uri = get_self_url_prefix() . "/prefs.php";
|
||||
$tt_uri = get_self_url_prefix();
|
||||
|
||||
if ($rc <= 2){
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE
|
||||
feed_url = '$feed_url' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
$feed_id = $this->dbh->fetch_result($result, 0, "id");
|
||||
} else {
|
||||
$feed_id = 0;
|
||||
}
|
||||
|
||||
print "<p>";
|
||||
|
||||
if ($feed_id) {
|
||||
print "<form method=\"GET\" style='display: inline'
|
||||
action=\"$tp_uri\">
|
||||
<input type=\"hidden\" name=\"tab\" value=\"feedConfig\">
|
||||
<input type=\"hidden\" name=\"method\" value=\"editFeed\">
|
||||
<input type=\"hidden\" name=\"methodparam\" value=\"$feed_id\">
|
||||
<input type=\"submit\" value=\"".__("Edit subscription options")."\">
|
||||
</form>";
|
||||
}
|
||||
|
||||
print "<form style='display: inline' method=\"GET\" action=\"$tt_uri\">
|
||||
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
|
||||
</form></p>";
|
||||
|
||||
print "</body></html>";
|
||||
}
|
||||
|
||||
function index() {
|
||||
header("Content-Type: text/plain");
|
||||
print json_encode(array("error" => array("code" => 7)));
|
||||
}
|
||||
|
||||
function forgotpass() {
|
||||
startup_gettext();
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
print "<html><head><title>Tiny Tiny RSS</title>";
|
||||
|
||||
stylesheet_tag("css/utility.css");
|
||||
javascript_tag("lib/prototype.js");
|
||||
|
||||
print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
|
||||
</head><body id='forgotpass'>";
|
||||
|
||||
print '<div class="floatingLogo"><img src="images/logo_small.png"></div>';
|
||||
print "<h1>".__("Password recovery")."</h1>";
|
||||
print "<div class='content'>";
|
||||
|
||||
@$method = $_POST['method'];
|
||||
|
||||
if (!$method) {
|
||||
print_notice(__("You will need to provide valid account name and email. New password will be sent on your email address."));
|
||||
|
||||
print "<form method='POST' action='public.php'>";
|
||||
print "<input type='hidden' name='method' value='do'>";
|
||||
print "<input type='hidden' name='op' value='forgotpass'>";
|
||||
|
||||
print "<fieldset>";
|
||||
print "<label>".__("Login:")."</label>";
|
||||
print "<input type='text' name='login' value='' required>";
|
||||
print "</fieldset>";
|
||||
|
||||
print "<fieldset>";
|
||||
print "<label>".__("Email:")."</label>";
|
||||
print "<input type='email' name='email' value='' required>";
|
||||
print "</fieldset>";
|
||||
|
||||
print "<fieldset>";
|
||||
print "<label>".__("How much is two plus two:")."</label>";
|
||||
print "<input type='text' name='test' value='' required>";
|
||||
print "</fieldset>";
|
||||
|
||||
print "<p/>";
|
||||
print "<button type='submit'>".__("Reset password")."</button>";
|
||||
|
||||
print "</form>";
|
||||
} else if ($method == 'do') {
|
||||
|
||||
$login = $this->dbh->escape_string($_POST["login"]);
|
||||
$email = $this->dbh->escape_string($_POST["email"]);
|
||||
$test = $this->dbh->escape_string($_POST["test"]);
|
||||
|
||||
if (($test != 4 && $test != 'four') || !$email || !$login) {
|
||||
print_error(__('Some of the required form parameters are missing or incorrect.'));
|
||||
|
||||
print "<form method=\"GET\" action=\"public.php\">
|
||||
<input type=\"hidden\" name=\"op\" value=\"forgotpass\">
|
||||
<input type=\"submit\" value=\"".__("Go back")."\">
|
||||
</form>";
|
||||
|
||||
} else {
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_users
|
||||
WHERE login = '$login' AND email = '$email'");
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$id = $this->dbh->fetch_result($result, 0, "id");
|
||||
|
||||
Pref_Users::resetUserPassword($id, false);
|
||||
|
||||
print "<p>";
|
||||
|
||||
print "<p>"."Completed."."</p>";
|
||||
|
||||
print "<form method=\"GET\" action=\"index.php\">
|
||||
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
|
||||
</form>";
|
||||
|
||||
} else {
|
||||
print_error(__("Sorry, login and email combination not found."));
|
||||
|
||||
print "<form method=\"GET\" action=\"public.php\">
|
||||
<input type=\"hidden\" name=\"op\" value=\"forgotpass\">
|
||||
<input type=\"submit\" value=\"".__("Go back")."\">
|
||||
</form>";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
print "</div>";
|
||||
print "</body>";
|
||||
print "</html>";
|
||||
|
||||
}
|
||||
|
||||
function dbupdate() {
|
||||
startup_gettext();
|
||||
|
||||
if (!SINGLE_USER_MODE && $_SESSION["access_level"] < 10) {
|
||||
$_SESSION["login_error_msg"] = __("Your access level is insufficient to run this script.");
|
||||
render_login_form();
|
||||
exit;
|
||||
}
|
||||
|
||||
?><html>
|
||||
<head>
|
||||
<title>Database Updater</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<link rel="stylesheet" type="text/css" href="css/utility.css"/>
|
||||
</head>
|
||||
<style type="text/css">
|
||||
span.ok { color : #009000; font-weight : bold; }
|
||||
span.err { color : #ff0000; font-weight : bold; }
|
||||
</style>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
function confirmOP() {
|
||||
return confirm("Update the database?");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="floatingLogo"><img src="images/logo_small.png"></div>
|
||||
|
||||
<h1><?php echo __("Database Updater") ?></h1>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<?php
|
||||
@$op = $_REQUEST["subop"];
|
||||
$updater = new DbUpdater(Db::get(), DB_TYPE, SCHEMA_VERSION);
|
||||
|
||||
if ($op == "performupdate") {
|
||||
if ($updater->isUpdateRequired()) {
|
||||
|
||||
print "<h2>Performing updates</h2>";
|
||||
|
||||
print "<h3>Updating to schema version " . SCHEMA_VERSION . "</h3>";
|
||||
|
||||
print "<ul>";
|
||||
|
||||
for ($i = $updater->getSchemaVersion() + 1; $i <= SCHEMA_VERSION; $i++) {
|
||||
print "<li>Performing update up to version $i...";
|
||||
|
||||
$result = $updater->performUpdateTo($i);
|
||||
|
||||
if (!$result) {
|
||||
print "<span class='err'>FAILED!</span></li></ul>";
|
||||
|
||||
print_warning("One of the updates failed. Either retry the process or perform updates manually.");
|
||||
print "<p><form method=\"GET\" action=\"index.php\">
|
||||
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
|
||||
</form>";
|
||||
|
||||
break;
|
||||
} else {
|
||||
print "<span class='ok'>OK!</span></li>";
|
||||
}
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
|
||||
print_notice("Your Tiny Tiny RSS database is now updated to the latest version.");
|
||||
|
||||
print "<p><form method=\"GET\" action=\"index.php\">
|
||||
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
|
||||
</form>";
|
||||
|
||||
} else {
|
||||
print "<h2>Your database is up to date.</h2>";
|
||||
|
||||
print "<p><form method=\"GET\" action=\"index.php\">
|
||||
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
|
||||
</form>";
|
||||
}
|
||||
} else {
|
||||
if ($updater->isUpdateRequired()) {
|
||||
|
||||
print "<h2>Database update required</h2>";
|
||||
|
||||
print "<h3>";
|
||||
printf("Your Tiny Tiny RSS database needs update to the latest version: %d to %d.",
|
||||
$updater->getSchemaVersion(), SCHEMA_VERSION);
|
||||
print "</h3>";
|
||||
|
||||
print_warning("Please backup your database before proceeding.");
|
||||
|
||||
print "<form method='POST'>
|
||||
<input type='hidden' name='subop' value='performupdate'>
|
||||
<input type='submit' onclick='return confirmOP()' value='".__("Perform updates")."'>
|
||||
</form>";
|
||||
|
||||
} else {
|
||||
|
||||
print_notice("Tiny Tiny RSS database is up to date.");
|
||||
|
||||
print "<p><form method=\"GET\" action=\"index.php\">
|
||||
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
|
||||
</form>";
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
5
source/classes/iauthmodule.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
interface IAuthModule {
|
||||
function authenticate($login, $password);
|
||||
}
|
||||
?>
|
13
source/classes/idb.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
interface IDb {
|
||||
function connect($host, $user, $pass, $db, $port);
|
||||
function escape_string($s, $strip_tags = true);
|
||||
function query($query, $die_on_error = true);
|
||||
function fetch_assoc($result);
|
||||
function num_rows($result);
|
||||
function fetch_result($result, $row, $param);
|
||||
function close();
|
||||
function affected_rows($result);
|
||||
function last_error();
|
||||
}
|
||||
?>
|
7
source/classes/ihandler.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
interface IHandler {
|
||||
function csrf_ignore($method);
|
||||
function before($method);
|
||||
function after();
|
||||
}
|
||||
?>
|
65
source/classes/logger.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
class Logger {
|
||||
private static $instance;
|
||||
private $adapter;
|
||||
|
||||
public static $errornames = array(
|
||||
1 => 'E_ERROR',
|
||||
2 => 'E_WARNING',
|
||||
4 => 'E_PARSE',
|
||||
8 => 'E_NOTICE',
|
||||
16 => 'E_CORE_ERROR',
|
||||
32 => 'E_CORE_WARNING',
|
||||
64 => 'E_COMPILE_ERROR',
|
||||
128 => 'E_COMPILE_WARNING',
|
||||
256 => 'E_USER_ERROR',
|
||||
512 => 'E_USER_WARNING',
|
||||
1024 => 'E_USER_NOTICE',
|
||||
2048 => 'E_STRICT',
|
||||
4096 => 'E_RECOVERABLE_ERROR',
|
||||
8192 => 'E_DEPRECATED',
|
||||
16384 => 'E_USER_DEPRECATED',
|
||||
32767 => 'E_ALL');
|
||||
|
||||
function log_error($errno, $errstr, $file, $line, $context) {
|
||||
if ($errno == E_NOTICE) return false;
|
||||
|
||||
if ($this->adapter)
|
||||
return $this->adapter->log_error($errno, $errstr, $file, $line, $context);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
function log($string) {
|
||||
if ($this->adapter)
|
||||
return $this->adapter->log($string);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
function __construct() {
|
||||
switch (LOG_DESTINATION) {
|
||||
case "sql":
|
||||
$this->adapter = new Logger_SQL();
|
||||
break;
|
||||
case "syslog":
|
||||
$this->adapter = new Logger_Syslog();
|
||||
break;
|
||||
default:
|
||||
$this->adapter = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
28
source/classes/logger/sql.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
class Logger_SQL {
|
||||
|
||||
function log_error($errno, $errstr, $file, $line, $context) {
|
||||
if (Db::get() && get_schema_version() > 117) {
|
||||
|
||||
$errno = Db::get()->escape_string($errno);
|
||||
$errstr = Db::get()->escape_string($errstr);
|
||||
$file = Db::get()->escape_string($file);
|
||||
$line = Db::get()->escape_string($line);
|
||||
$context = ''; // backtrace is a lot of data which is not really critical to store
|
||||
//$context = $this->dbh->escape_string(serialize($context));
|
||||
|
||||
$owner_uid = $_SESSION["uid"] ? $_SESSION["uid"] : "NULL";
|
||||
|
||||
$result = Db::get()->query(
|
||||
"INSERT INTO ttrss_error_log
|
||||
(errno, errstr, filename, lineno, context, owner_uid, created_at) VALUES
|
||||
($errno, '$errstr', '$file', '$line', '$context', $owner_uid, NOW())");
|
||||
|
||||
return Db::get()->affected_rows($result) != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
31
source/classes/logger/syslog.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
class Logger_Syslog {
|
||||
|
||||
function log_error($errno, $errstr, $file, $line, $context) {
|
||||
|
||||
switch ($errno) {
|
||||
case E_ERROR:
|
||||
case E_PARSE:
|
||||
case E_CORE_ERROR:
|
||||
case E_COMPILE_ERROR:
|
||||
case E_USER_ERROR:
|
||||
$priority = LOG_ERR;
|
||||
break;
|
||||
case E_WARNING:
|
||||
case E_CORE_WARNING:
|
||||
case E_COMPILE_WARNING:
|
||||
case E_USER_WARNING:
|
||||
$priority = LOG_WARNING;
|
||||
break;
|
||||
default:
|
||||
$priority = LOG_INFO;
|
||||
}
|
||||
|
||||
$errname = Logger::$errornames[$errno] . " ($errno)";
|
||||
|
||||
syslog($priority, "[tt-rss] $errname ($file:$line) $errstr");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
523
source/classes/opml.php
Normal file
|
@ -0,0 +1,523 @@
|
|||
<?php
|
||||
class Opml extends Handler_Protected {
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("export", "import");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function export() {
|
||||
$output_name = $_REQUEST["filename"];
|
||||
if (!$output_name) $output_name = "TinyTinyRSS.opml";
|
||||
|
||||
$show_settings = $_REQUEST["settings"];
|
||||
|
||||
$owner_uid = $_SESSION["uid"];
|
||||
return $this->opml_export($output_name, $owner_uid, false, ($show_settings == 1));
|
||||
}
|
||||
|
||||
function import() {
|
||||
$owner_uid = $_SESSION["uid"];
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
print "<html>
|
||||
<head>
|
||||
<link rel=\"stylesheet\" href=\"css/utility.css\" type=\"text/css\">
|
||||
<title>".__("OPML Utility")."</title>
|
||||
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class=\"floatingLogo\"><img src=\"images/logo_small.png\"></div>
|
||||
<h1>".__('OPML Utility')."</h1><div class='content'>";
|
||||
|
||||
add_feed_category("Imported feeds");
|
||||
|
||||
$this->opml_notice(__("Importing OPML..."));
|
||||
$this->opml_import($owner_uid);
|
||||
|
||||
print "<br><form method=\"GET\" action=\"prefs.php\">
|
||||
<input type=\"submit\" value=\"".__("Return to preferences")."\">
|
||||
</form>";
|
||||
|
||||
print "</div></body></html>";
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Export
|
||||
|
||||
private function opml_export_category($owner_uid, $cat_id, $hide_private_feeds=false) {
|
||||
|
||||
if ($cat_id) {
|
||||
$cat_qpart = "parent_cat = '$cat_id'";
|
||||
$feed_cat_qpart = "cat_id = '$cat_id'";
|
||||
} else {
|
||||
$cat_qpart = "parent_cat IS NULL";
|
||||
$feed_cat_qpart = "cat_id IS NULL";
|
||||
}
|
||||
|
||||
if ($hide_private_feeds)
|
||||
$hide_qpart = "(private IS false AND auth_login = '' AND auth_pass = '')";
|
||||
else
|
||||
$hide_qpart = "true";
|
||||
|
||||
$out = "";
|
||||
|
||||
if ($cat_id) {
|
||||
$result = $this->dbh->query("SELECT title FROM ttrss_feed_categories WHERE id = '$cat_id'
|
||||
AND owner_uid = '$owner_uid'");
|
||||
$cat_title = htmlspecialchars($this->dbh->fetch_result($result, 0, "title"));
|
||||
}
|
||||
|
||||
if ($cat_title) $out .= "<outline text=\"$cat_title\">\n";
|
||||
|
||||
$result = $this->dbh->query("SELECT id,title
|
||||
FROM ttrss_feed_categories WHERE
|
||||
$cat_qpart AND owner_uid = '$owner_uid' ORDER BY order_id, title");
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$title = htmlspecialchars($line["title"]);
|
||||
$out .= $this->opml_export_category($owner_uid, $line["id"], $hide_private_feeds);
|
||||
}
|
||||
|
||||
$feeds_result = $this->dbh->query("select title, feed_url, site_url
|
||||
from ttrss_feeds where $feed_cat_qpart AND owner_uid = '$owner_uid' AND $hide_qpart
|
||||
order by order_id, title");
|
||||
|
||||
while ($fline = $this->dbh->fetch_assoc($feeds_result)) {
|
||||
$title = htmlspecialchars($fline["title"]);
|
||||
$url = htmlspecialchars($fline["feed_url"]);
|
||||
$site_url = htmlspecialchars($fline["site_url"]);
|
||||
|
||||
if ($site_url) {
|
||||
$html_url_qpart = "htmlUrl=\"$site_url\"";
|
||||
} else {
|
||||
$html_url_qpart = "";
|
||||
}
|
||||
|
||||
$out .= "<outline type=\"rss\" text=\"$title\" xmlUrl=\"$url\" $html_url_qpart/>\n";
|
||||
}
|
||||
|
||||
if ($cat_title) $out .= "</outline>\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
function opml_export($name, $owner_uid, $hide_private_feeds=false, $include_settings=true) {
|
||||
if (!$owner_uid) return;
|
||||
|
||||
if (!isset($_REQUEST["debug"])) {
|
||||
header("Content-type: application/xml+opml");
|
||||
header("Content-Disposition: attachment; filename=" . $name );
|
||||
} else {
|
||||
header("Content-type: text/xml");
|
||||
}
|
||||
|
||||
$out = "<?xml version=\"1.0\" encoding=\"utf-8\"?".">";
|
||||
|
||||
$out .= "<opml version=\"1.0\">";
|
||||
$out .= "<head>
|
||||
<dateCreated>" . date("r", time()) . "</dateCreated>
|
||||
<title>Tiny Tiny RSS Feed Export</title>
|
||||
</head>";
|
||||
$out .= "<body>";
|
||||
|
||||
$out .= $this->opml_export_category($owner_uid, false, $hide_private_feeds);
|
||||
|
||||
# export tt-rss settings
|
||||
|
||||
if ($include_settings) {
|
||||
$out .= "<outline text=\"tt-rss-prefs\" schema-version=\"".SCHEMA_VERSION."\">";
|
||||
|
||||
$result = $this->dbh->query("SELECT pref_name, value FROM ttrss_user_prefs WHERE
|
||||
profile IS NULL AND owner_uid = " . $_SESSION["uid"] . " ORDER BY pref_name");
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$name = $line["pref_name"];
|
||||
$value = htmlspecialchars($line["value"]);
|
||||
|
||||
$out .= "<outline pref-name=\"$name\" value=\"$value\"/>";
|
||||
}
|
||||
|
||||
$out .= "</outline>";
|
||||
|
||||
$out .= "<outline text=\"tt-rss-labels\" schema-version=\"".SCHEMA_VERSION."\">";
|
||||
|
||||
$result = $this->dbh->query("SELECT * FROM ttrss_labels2 WHERE
|
||||
owner_uid = " . $_SESSION['uid']);
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$name = htmlspecialchars($line['caption']);
|
||||
$fg_color = htmlspecialchars($line['fg_color']);
|
||||
$bg_color = htmlspecialchars($line['bg_color']);
|
||||
|
||||
$out .= "<outline label-name=\"$name\" label-fg-color=\"$fg_color\" label-bg-color=\"$bg_color\"/>";
|
||||
|
||||
}
|
||||
|
||||
$out .= "</outline>";
|
||||
|
||||
$out .= "<outline text=\"tt-rss-filters\" schema-version=\"".SCHEMA_VERSION."\">";
|
||||
|
||||
$result = $this->dbh->query("SELECT * FROM ttrss_filters2
|
||||
WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY id");
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
foreach (array('enabled', 'match_any_rule', 'inverse') as $b) {
|
||||
$line[$b] = sql_bool_to_bool($line[$b]);
|
||||
}
|
||||
|
||||
$line["rules"] = array();
|
||||
$line["actions"] = array();
|
||||
|
||||
$tmp_result = $this->dbh->query("SELECT * FROM ttrss_filters2_rules
|
||||
WHERE filter_id = ".$line["id"]);
|
||||
|
||||
while ($tmp_line = $this->dbh->fetch_assoc($tmp_result)) {
|
||||
unset($tmp_line["id"]);
|
||||
unset($tmp_line["filter_id"]);
|
||||
|
||||
$cat_filter = sql_bool_to_bool($tmp_line["cat_filter"]);
|
||||
|
||||
if ($cat_filter && $tmp_line["cat_id"] || $tmp_line["feed_id"]) {
|
||||
$tmp_line["feed"] = getFeedTitle(
|
||||
$cat_filter ? $tmp_line["cat_id"] : $tmp_line["feed_id"],
|
||||
$cat_filter);
|
||||
} else {
|
||||
$tmp_line["feed"] = "";
|
||||
}
|
||||
|
||||
$tmp_line["cat_filter"] = sql_bool_to_bool($tmp_line["cat_filter"]);
|
||||
$tmp_line["inverse"] = sql_bool_to_bool($tmp_line["inverse"]);
|
||||
|
||||
unset($tmp_line["feed_id"]);
|
||||
unset($tmp_line["cat_id"]);
|
||||
|
||||
array_push($line["rules"], $tmp_line);
|
||||
}
|
||||
|
||||
$tmp_result = $this->dbh->query("SELECT * FROM ttrss_filters2_actions
|
||||
WHERE filter_id = ".$line["id"]);
|
||||
|
||||
while ($tmp_line = $this->dbh->fetch_assoc($tmp_result)) {
|
||||
unset($tmp_line["id"]);
|
||||
unset($tmp_line["filter_id"]);
|
||||
|
||||
array_push($line["actions"], $tmp_line);
|
||||
}
|
||||
|
||||
unset($line["id"]);
|
||||
unset($line["owner_uid"]);
|
||||
$filter = json_encode($line);
|
||||
|
||||
$out .= "<outline filter-type=\"2\"><![CDATA[$filter]]></outline>";
|
||||
|
||||
}
|
||||
|
||||
|
||||
$out .= "</outline>";
|
||||
}
|
||||
|
||||
$out .= "</body></opml>";
|
||||
|
||||
// Format output.
|
||||
$doc = new DOMDocument();
|
||||
$doc->formatOutput = true;
|
||||
$doc->preserveWhiteSpace = false;
|
||||
$doc->loadXML($out);
|
||||
|
||||
$xpath = new DOMXpath($doc);
|
||||
$outlines = $xpath->query("//outline[@title]");
|
||||
|
||||
// cleanup empty categories
|
||||
foreach ($outlines as $node) {
|
||||
if ($node->getElementsByTagName('outline')->length == 0)
|
||||
$node->parentNode->removeChild($node);
|
||||
}
|
||||
|
||||
$res = $doc->saveXML();
|
||||
|
||||
/* // saveXML uses a two-space indent. Change to tabs.
|
||||
$res = preg_replace_callback('/^(?: )+/mu',
|
||||
create_function(
|
||||
'$matches',
|
||||
'return str_repeat("\t", intval(strlen($matches[0])/2));'),
|
||||
$res); */
|
||||
|
||||
print $res;
|
||||
}
|
||||
|
||||
// Import
|
||||
|
||||
private function opml_import_feed($doc, $node, $cat_id, $owner_uid) {
|
||||
$attrs = $node->attributes;
|
||||
|
||||
$feed_title = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('text')->nodeValue, 0, 250));
|
||||
if (!$feed_title) $feed_title = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('title')->nodeValue, 0, 250));
|
||||
|
||||
$feed_url = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('xmlUrl')->nodeValue, 0, 250));
|
||||
if (!$feed_url) $feed_url = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('xmlURL')->nodeValue, 0, 250));
|
||||
|
||||
$site_url = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('htmlUrl')->nodeValue, 0, 250));
|
||||
|
||||
if ($feed_url && $feed_title) {
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE
|
||||
feed_url = '$feed_url' AND owner_uid = '$owner_uid'");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
#$this->opml_notice("[FEED] [$feed_title/$feed_url] dst_CAT=$cat_id");
|
||||
$this->opml_notice(T_sprintf("Adding feed: %s", $feed_title));
|
||||
|
||||
if (!$cat_id) $cat_id = 'NULL';
|
||||
|
||||
$query = "INSERT INTO ttrss_feeds
|
||||
(title, feed_url, owner_uid, cat_id, site_url, order_id) VALUES
|
||||
('$feed_title', '$feed_url', '$owner_uid',
|
||||
$cat_id, '$site_url', 0)";
|
||||
$this->dbh->query($query);
|
||||
|
||||
} else {
|
||||
$this->opml_notice(T_sprintf("Duplicate feed: %s", $feed_title));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_import_label($doc, $node, $owner_uid) {
|
||||
$attrs = $node->attributes;
|
||||
$label_name = $this->dbh->escape_string($attrs->getNamedItem('label-name')->nodeValue);
|
||||
|
||||
if ($label_name) {
|
||||
$fg_color = $this->dbh->escape_string($attrs->getNamedItem('label-fg-color')->nodeValue);
|
||||
$bg_color = $this->dbh->escape_string($attrs->getNamedItem('label-bg-color')->nodeValue);
|
||||
|
||||
if (!label_find_id($label_name, $_SESSION['uid'])) {
|
||||
$this->opml_notice(T_sprintf("Adding label %s", htmlspecialchars($label_name)));
|
||||
label_create($label_name, $fg_color, $bg_color, $owner_uid);
|
||||
} else {
|
||||
$this->opml_notice(T_sprintf("Duplicate label: %s", htmlspecialchars($label_name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_import_preference($doc, $node, $owner_uid) {
|
||||
$attrs = $node->attributes;
|
||||
$pref_name = $this->dbh->escape_string($attrs->getNamedItem('pref-name')->nodeValue);
|
||||
|
||||
if ($pref_name) {
|
||||
$pref_value = $this->dbh->escape_string($attrs->getNamedItem('value')->nodeValue);
|
||||
|
||||
$this->opml_notice(T_sprintf("Setting preference key %s to %s",
|
||||
$pref_name, $pref_value));
|
||||
|
||||
set_pref($pref_name, $pref_value);
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_import_filter($doc, $node, $owner_uid) {
|
||||
$attrs = $node->attributes;
|
||||
|
||||
$filter_type = $this->dbh->escape_string($attrs->getNamedItem('filter-type')->nodeValue);
|
||||
|
||||
if ($filter_type == '2') {
|
||||
$filter = json_decode($node->nodeValue, true);
|
||||
|
||||
if ($filter) {
|
||||
$match_any_rule = bool_to_sql_bool($filter["match_any_rule"]);
|
||||
$enabled = bool_to_sql_bool($filter["enabled"]);
|
||||
$inverse = bool_to_sql_bool($filter["inverse"]);
|
||||
$title = db_escape_string($filter["title"]);
|
||||
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
$this->dbh->query("INSERT INTO ttrss_filters2 (match_any_rule,enabled,inverse,title,owner_uid)
|
||||
VALUES ($match_any_rule, $enabled, $inverse, '$title',
|
||||
".$_SESSION["uid"].")");
|
||||
|
||||
$result = $this->dbh->query("SELECT MAX(id) AS id FROM ttrss_filters2 WHERE
|
||||
owner_uid = ".$_SESSION["uid"]);
|
||||
$filter_id = $this->dbh->fetch_result($result, 0, "id");
|
||||
|
||||
if ($filter_id) {
|
||||
$this->opml_notice(T_sprintf("Adding filter..."));
|
||||
|
||||
foreach ($filter["rules"] as $rule) {
|
||||
$feed_id = "NULL";
|
||||
$cat_id = "NULL";
|
||||
|
||||
if (!$rule["cat_filter"]) {
|
||||
$tmp_result = $this->dbh->query("SELECT id FROM ttrss_feeds
|
||||
WHERE title = '".$this->dbh->escape_string($rule["feed"])."' AND owner_uid = ".$_SESSION["uid"]);
|
||||
if ($this->dbh->num_rows($tmp_result) > 0) {
|
||||
$feed_id = $this->dbh->fetch_result($tmp_result, 0, "id");
|
||||
}
|
||||
} else {
|
||||
$tmp_result = $this->dbh->query("SELECT id FROM ttrss_feed_categories
|
||||
WHERE title = '".$this->dbh->escape_string($rule["feed"])."' AND owner_uid = ".$_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($tmp_result) > 0) {
|
||||
$cat_id = $this->dbh->fetch_result($tmp_result, 0, "id");
|
||||
}
|
||||
}
|
||||
|
||||
$cat_filter = bool_to_sql_bool($rule["cat_filter"]);
|
||||
$reg_exp = $this->dbh->escape_string($rule["reg_exp"]);
|
||||
$filter_type = (int)$rule["filter_type"];
|
||||
$inverse = bool_to_sql_bool($rule["inverse"]);
|
||||
|
||||
$this->dbh->query("INSERT INTO ttrss_filters2_rules (feed_id,cat_id,filter_id,filter_type,reg_exp,cat_filter,inverse)
|
||||
VALUES ($feed_id, $cat_id, $filter_id, $filter_type, '$reg_exp', $cat_filter,$inverse)");
|
||||
}
|
||||
|
||||
foreach ($filter["actions"] as $action) {
|
||||
|
||||
$action_id = (int)$action["action_id"];
|
||||
$action_param = $this->dbh->escape_string($action["action_param"]);
|
||||
|
||||
$this->dbh->query("INSERT INTO ttrss_filters2_actions (filter_id,action_id,action_param)
|
||||
VALUES ($filter_id, $action_id, '$action_param')");
|
||||
}
|
||||
}
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_import_category($doc, $root_node, $owner_uid, $parent_id) {
|
||||
$body = $doc->getElementsByTagName('body');
|
||||
|
||||
$default_cat_id = (int) get_feed_category('Imported feeds', false);
|
||||
|
||||
if ($root_node) {
|
||||
$cat_title = $this->dbh->escape_string(mb_substr($root_node->attributes->getNamedItem('text')->nodeValue, 0, 250));
|
||||
|
||||
if (!$cat_title)
|
||||
$cat_title = $this->dbh->escape_string(mb_substr($root_node->attributes->getNamedItem('title')->nodeValue, 0, 250));
|
||||
|
||||
if (!in_array($cat_title, array("tt-rss-filters", "tt-rss-labels", "tt-rss-prefs"))) {
|
||||
$cat_id = get_feed_category($cat_title, $parent_id);
|
||||
$this->dbh->query("BEGIN");
|
||||
if ($cat_id === false) {
|
||||
add_feed_category($cat_title, $parent_id);
|
||||
$cat_id = get_feed_category($cat_title, $parent_id);
|
||||
}
|
||||
$this->dbh->query("COMMIT");
|
||||
} else {
|
||||
$cat_id = 0;
|
||||
}
|
||||
|
||||
$outlines = $root_node->childNodes;
|
||||
|
||||
} else {
|
||||
$xpath = new DOMXpath($doc);
|
||||
$outlines = $xpath->query("//opml/body/outline");
|
||||
|
||||
$cat_id = 0;
|
||||
}
|
||||
|
||||
#$this->opml_notice("[CAT] $cat_title id: $cat_id P_id: $parent_id");
|
||||
$this->opml_notice(T_sprintf("Processing category: %s", $cat_title ? $cat_title : __("Uncategorized")));
|
||||
|
||||
foreach ($outlines as $node) {
|
||||
if ($node->hasAttributes() && strtolower($node->tagName) == "outline") {
|
||||
$attrs = $node->attributes;
|
||||
$node_cat_title = $this->dbh->escape_string($attrs->getNamedItem('text')->nodeValue);
|
||||
|
||||
if (!$node_cat_title)
|
||||
$node_cat_title = $this->dbh->escape_string($attrs->getNamedItem('title')->nodeValue);
|
||||
|
||||
$node_feed_url = $this->dbh->escape_string($attrs->getNamedItem('xmlUrl')->nodeValue);
|
||||
|
||||
if ($node_cat_title && !$node_feed_url) {
|
||||
$this->opml_import_category($doc, $node, $owner_uid, $cat_id);
|
||||
} else {
|
||||
|
||||
if (!$cat_id) {
|
||||
$dst_cat_id = $default_cat_id;
|
||||
} else {
|
||||
$dst_cat_id = $cat_id;
|
||||
}
|
||||
|
||||
switch ($cat_title) {
|
||||
case "tt-rss-prefs":
|
||||
$this->opml_import_preference($doc, $node, $owner_uid);
|
||||
break;
|
||||
case "tt-rss-labels":
|
||||
$this->opml_import_label($doc, $node, $owner_uid);
|
||||
break;
|
||||
case "tt-rss-filters":
|
||||
$this->opml_import_filter($doc, $node, $owner_uid);
|
||||
break;
|
||||
default:
|
||||
$this->opml_import_feed($doc, $node, $dst_cat_id, $owner_uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function opml_import($owner_uid) {
|
||||
if (!$owner_uid) return;
|
||||
|
||||
$debug = isset($_REQUEST["debug"]);
|
||||
$doc = false;
|
||||
|
||||
# if ($debug) $doc = DOMDocument::load("/tmp/test.opml");
|
||||
|
||||
if ($_FILES['opml_file']['error'] != 0) {
|
||||
print_error(T_sprintf("Upload failed with error code %d",
|
||||
$_FILES['opml_file']['error']));
|
||||
return;
|
||||
}
|
||||
|
||||
$tmp_file = false;
|
||||
|
||||
if (is_uploaded_file($_FILES['opml_file']['tmp_name'])) {
|
||||
$tmp_file = tempnam(CACHE_DIR . '/upload', 'opml');
|
||||
|
||||
$result = move_uploaded_file($_FILES['opml_file']['tmp_name'],
|
||||
$tmp_file);
|
||||
|
||||
if (!$result) {
|
||||
print_error(__("Unable to move uploaded file."));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
print_error(__('Error: please upload OPML file.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_file($tmp_file)) {
|
||||
$doc = new DOMDocument();
|
||||
$doc->load($tmp_file);
|
||||
unlink($tmp_file);
|
||||
} else if (!$doc) {
|
||||
print_error(__('Error: unable to find moved OPML file.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($doc) {
|
||||
$this->opml_import_category($doc, false, $owner_uid, false);
|
||||
} else {
|
||||
print_error(__('Error while parsing document.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_notice($msg) {
|
||||
print "$msg<br/>";
|
||||
}
|
||||
|
||||
static function opml_publish_url(){
|
||||
|
||||
$url_path = get_self_url_prefix();
|
||||
$url_path .= "/opml.php?op=publish&key=" .
|
||||
get_feed_access_key('OPML:Publish', false, $_SESSION["uid"]);
|
||||
|
||||
return $url_path;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
30
source/classes/plugin.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
class Plugin {
|
||||
private $dbh;
|
||||
private $host;
|
||||
|
||||
const API_VERSION_COMPAT = 1;
|
||||
|
||||
function init($host) {
|
||||
$this->dbh = $host->get_dbh();
|
||||
$this->host = $host;
|
||||
}
|
||||
|
||||
function about() {
|
||||
// version, name, description, author, is_system
|
||||
return array(1.0, "plugin", "No description", "No author", false);
|
||||
}
|
||||
|
||||
function get_js() {
|
||||
return "";
|
||||
}
|
||||
|
||||
function get_prefs_js() {
|
||||
return "";
|
||||
}
|
||||
|
||||
function api_version() {
|
||||
return Plugin::API_VERSION_COMPAT;
|
||||
}
|
||||
}
|
||||
?>
|
22
source/classes/pluginhandler.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
class PluginHandler extends Handler_Protected {
|
||||
function csrf_ignore($method) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function catchall($method) {
|
||||
$plugin = PluginHost::getInstance()->get_plugin($_REQUEST["plugin"]);
|
||||
|
||||
if ($plugin) {
|
||||
if (method_exists($plugin, $method)) {
|
||||
$plugin->$method();
|
||||
} else {
|
||||
print json_encode(array("error" => "METHOD_NOT_FOUND"));
|
||||
}
|
||||
} else {
|
||||
print json_encode(array("error" => "PLUGIN_NOT_FOUND"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
389
source/classes/pluginhost.php
Normal file
|
@ -0,0 +1,389 @@
|
|||
<?php
|
||||
class PluginHost {
|
||||
private $dbh;
|
||||
private $hooks = array();
|
||||
private $plugins = array();
|
||||
private $handlers = array();
|
||||
private $commands = array();
|
||||
private $storage = array();
|
||||
private $feeds = array();
|
||||
private $api_methods = array();
|
||||
private $owner_uid;
|
||||
private $debug;
|
||||
private $last_registered;
|
||||
private static $instance;
|
||||
|
||||
const API_VERSION = 2;
|
||||
|
||||
const HOOK_ARTICLE_BUTTON = 1;
|
||||
const HOOK_ARTICLE_FILTER = 2;
|
||||
const HOOK_PREFS_TAB = 3;
|
||||
const HOOK_PREFS_TAB_SECTION = 4;
|
||||
const HOOK_PREFS_TABS = 5;
|
||||
const HOOK_FEED_PARSED = 6;
|
||||
const HOOK_UPDATE_TASK = 7;
|
||||
const HOOK_AUTH_USER = 8;
|
||||
const HOOK_HOTKEY_MAP = 9;
|
||||
const HOOK_RENDER_ARTICLE = 10;
|
||||
const HOOK_RENDER_ARTICLE_CDM = 11;
|
||||
const HOOK_FEED_FETCHED = 12;
|
||||
const HOOK_SANITIZE = 13;
|
||||
const HOOK_RENDER_ARTICLE_API = 14;
|
||||
const HOOK_TOOLBAR_BUTTON = 15;
|
||||
const HOOK_ACTION_ITEM = 16;
|
||||
const HOOK_HEADLINE_TOOLBAR_BUTTON = 17;
|
||||
const HOOK_HOTKEY_INFO = 18;
|
||||
const HOOK_ARTICLE_LEFT_BUTTON = 19;
|
||||
const HOOK_PREFS_EDIT_FEED = 20;
|
||||
const HOOK_PREFS_SAVE_FEED = 21;
|
||||
const HOOK_FETCH_FEED = 22;
|
||||
const HOOK_QUERY_HEADLINES = 23;
|
||||
const HOOK_HOUSE_KEEPING = 24;
|
||||
|
||||
const KIND_ALL = 1;
|
||||
const KIND_SYSTEM = 2;
|
||||
const KIND_USER = 3;
|
||||
|
||||
function __construct() {
|
||||
$this->dbh = Db::get();
|
||||
|
||||
$this->storage = array();
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function register_plugin($name, $plugin) {
|
||||
//array_push($this->plugins, $plugin);
|
||||
$this->plugins[$name] = $plugin;
|
||||
}
|
||||
|
||||
// needed for compatibility with API 1
|
||||
function get_link() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function get_dbh() {
|
||||
return $this->dbh;
|
||||
}
|
||||
|
||||
function get_plugins() {
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
function get_plugin($name) {
|
||||
return $this->plugins[$name];
|
||||
}
|
||||
|
||||
function run_hooks($type, $method, $args) {
|
||||
foreach ($this->get_hooks($type) as $hook) {
|
||||
$hook->$method($args);
|
||||
}
|
||||
}
|
||||
|
||||
function add_hook($type, $sender) {
|
||||
if (!is_array($this->hooks[$type])) {
|
||||
$this->hooks[$type] = array();
|
||||
}
|
||||
|
||||
array_push($this->hooks[$type], $sender);
|
||||
}
|
||||
|
||||
function del_hook($type, $sender) {
|
||||
if (is_array($this->hooks[$type])) {
|
||||
$key = array_Search($this->hooks[$type], $sender);
|
||||
if ($key !== FALSE) {
|
||||
unset($this->hooks[$type][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_hooks($type) {
|
||||
if (isset($this->hooks[$type])) {
|
||||
return $this->hooks[$type];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
function load_all($kind, $owner_uid = false) {
|
||||
$plugins = array_map("basename", glob("plugins/*"));
|
||||
$this->load(join(",", $plugins), $kind, $owner_uid);
|
||||
}
|
||||
|
||||
function load($classlist, $kind, $owner_uid = false) {
|
||||
$plugins = explode(",", $classlist);
|
||||
|
||||
$this->owner_uid = (int) $owner_uid;
|
||||
|
||||
foreach ($plugins as $class) {
|
||||
$class = trim($class);
|
||||
$class_file = strtolower(basename($class));
|
||||
|
||||
if (!is_dir(dirname(__FILE__)."/../plugins/$class_file")) continue;
|
||||
|
||||
$file = dirname(__FILE__)."/../plugins/$class_file/init.php";
|
||||
|
||||
if (!isset($this->plugins[$class])) {
|
||||
if (file_exists($file)) require_once $file;
|
||||
|
||||
if (class_exists($class) && is_subclass_of($class, "Plugin")) {
|
||||
$plugin = new $class($this);
|
||||
|
||||
$plugin_api = $plugin->api_version();
|
||||
|
||||
if ($plugin_api < PluginHost::API_VERSION) {
|
||||
user_error("Plugin $class is not compatible with current API version (need: " . PluginHost::API_VERSION . ", got: $plugin_api)", E_USER_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->last_registered = $class;
|
||||
|
||||
switch ($kind) {
|
||||
case $this::KIND_SYSTEM:
|
||||
if ($this->is_system($plugin)) {
|
||||
$plugin->init($this);
|
||||
$this->register_plugin($class, $plugin);
|
||||
}
|
||||
break;
|
||||
case $this::KIND_USER:
|
||||
if (!$this->is_system($plugin)) {
|
||||
$plugin->init($this);
|
||||
$this->register_plugin($class, $plugin);
|
||||
}
|
||||
break;
|
||||
case $this::KIND_ALL:
|
||||
$plugin->init($this);
|
||||
$this->register_plugin($class, $plugin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function is_system($plugin) {
|
||||
$about = $plugin->about();
|
||||
|
||||
return @$about[3];
|
||||
}
|
||||
|
||||
// only system plugins are allowed to modify routing
|
||||
function add_handler($handler, $method, $sender) {
|
||||
$handler = str_replace("-", "_", strtolower($handler));
|
||||
$method = strtolower($method);
|
||||
|
||||
if ($this->is_system($sender)) {
|
||||
if (!is_array($this->handlers[$handler])) {
|
||||
$this->handlers[$handler] = array();
|
||||
}
|
||||
|
||||
$this->handlers[$handler][$method] = $sender;
|
||||
}
|
||||
}
|
||||
|
||||
function del_handler($handler, $method, $sender) {
|
||||
$handler = str_replace("-", "_", strtolower($handler));
|
||||
$method = strtolower($method);
|
||||
|
||||
if ($this->is_system($sender)) {
|
||||
unset($this->handlers[$handler][$method]);
|
||||
}
|
||||
}
|
||||
|
||||
function lookup_handler($handler, $method) {
|
||||
$handler = str_replace("-", "_", strtolower($handler));
|
||||
$method = strtolower($method);
|
||||
|
||||
if (is_array($this->handlers[$handler])) {
|
||||
if (isset($this->handlers[$handler]["*"])) {
|
||||
return $this->handlers[$handler]["*"];
|
||||
} else {
|
||||
return $this->handlers[$handler][$method];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function add_command($command, $description, $sender, $suffix = "", $arghelp = "") {
|
||||
$command = str_replace("-", "_", strtolower($command));
|
||||
|
||||
$this->commands[$command] = array("description" => $description,
|
||||
"suffix" => $suffix,
|
||||
"arghelp" => $arghelp,
|
||||
"class" => $sender);
|
||||
}
|
||||
|
||||
function del_command($command) {
|
||||
$command = "-" . strtolower($command);
|
||||
|
||||
unset($this->commands[$command]);
|
||||
}
|
||||
|
||||
function lookup_command($command) {
|
||||
$command = "-" . strtolower($command);
|
||||
|
||||
if (is_array($this->commands[$command])) {
|
||||
return $this->commands[$command]["class"];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function get_commands() {
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
function run_commands($args) {
|
||||
foreach ($this->get_commands() as $command => $data) {
|
||||
if (isset($args[$command])) {
|
||||
$command = str_replace("-", "", $command);
|
||||
$data["class"]->$command($args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function load_data($force = false) {
|
||||
if ($this->owner_uid) {
|
||||
$result = $this->dbh->query("SELECT name, content FROM ttrss_plugin_storage
|
||||
WHERE owner_uid = '".$this->owner_uid."'");
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$this->storage[$line["name"]] = unserialize($line["content"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function save_data($plugin) {
|
||||
if ($this->owner_uid) {
|
||||
$plugin = $this->dbh->escape_string($plugin);
|
||||
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_plugin_storage WHERE
|
||||
owner_uid= '".$this->owner_uid."' AND name = '$plugin'");
|
||||
|
||||
if (!isset($this->storage[$plugin]))
|
||||
$this->storage[$plugin] = array();
|
||||
|
||||
$content = $this->dbh->escape_string(serialize($this->storage[$plugin]),
|
||||
false);
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$this->dbh->query("UPDATE ttrss_plugin_storage SET content = '$content'
|
||||
WHERE owner_uid= '".$this->owner_uid."' AND name = '$plugin'");
|
||||
|
||||
} else {
|
||||
$this->dbh->query("INSERT INTO ttrss_plugin_storage
|
||||
(name,owner_uid,content) VALUES
|
||||
('$plugin','".$this->owner_uid."','$content')");
|
||||
}
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
}
|
||||
}
|
||||
|
||||
function set($sender, $name, $value, $sync = true) {
|
||||
$idx = get_class($sender);
|
||||
|
||||
if (!isset($this->storage[$idx]))
|
||||
$this->storage[$idx] = array();
|
||||
|
||||
$this->storage[$idx][$name] = $value;
|
||||
|
||||
if ($sync) $this->save_data(get_class($sender));
|
||||
}
|
||||
|
||||
function get($sender, $name, $default_value = false) {
|
||||
$idx = get_class($sender);
|
||||
|
||||
if (isset($this->storage[$idx][$name])) {
|
||||
return $this->storage[$idx][$name];
|
||||
} else {
|
||||
return $default_value;
|
||||
}
|
||||
}
|
||||
|
||||
function get_all($sender) {
|
||||
$idx = get_class($sender);
|
||||
|
||||
return $this->storage[$idx];
|
||||
}
|
||||
|
||||
function clear_data($sender) {
|
||||
if ($this->owner_uid) {
|
||||
$idx = get_class($sender);
|
||||
|
||||
unset($this->storage[$idx]);
|
||||
|
||||
$this->dbh->query("DELETE FROM ttrss_plugin_storage WHERE name = '$idx'
|
||||
AND owner_uid = " . $this->owner_uid);
|
||||
}
|
||||
}
|
||||
|
||||
function set_debug($debug) {
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
function get_debug() {
|
||||
return $this->debug;
|
||||
}
|
||||
|
||||
// Plugin feed functions are *EXPERIMENTAL*!
|
||||
|
||||
// cat_id: only -1 is supported (Special)
|
||||
function add_feed($cat_id, $title, $icon, $sender) {
|
||||
if (!$this->feeds[$cat_id]) $this->feeds[$cat_id] = array();
|
||||
|
||||
$id = count($this->feeds[$cat_id]);
|
||||
|
||||
array_push($this->feeds[$cat_id],
|
||||
array('id' => $id, 'title' => $title, 'sender' => $sender, 'icon' => $icon));
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
function get_feeds($cat_id) {
|
||||
return $this->feeds[$cat_id];
|
||||
}
|
||||
|
||||
// convert feed_id (e.g. -129) to pfeed_id first
|
||||
function get_feed_handler($pfeed_id) {
|
||||
foreach ($this->feeds as $cat) {
|
||||
foreach ($cat as $feed) {
|
||||
if ($feed['id'] == $pfeed_id) {
|
||||
return $feed['sender'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function pfeed_to_feed_id($label) {
|
||||
return PLUGIN_FEED_BASE_INDEX - 1 - abs($label);
|
||||
}
|
||||
|
||||
static function feed_to_pfeed_id($feed) {
|
||||
return PLUGIN_FEED_BASE_INDEX - 1 + abs($feed);
|
||||
}
|
||||
|
||||
function add_api_method($name, $sender) {
|
||||
if ($this->is_system($sender)) {
|
||||
$this->api_methods[strtolower($name)] = $sender;
|
||||
}
|
||||
}
|
||||
|
||||
function get_api_method($name) {
|
||||
return $this->api_methods[$name];
|
||||
}
|
||||
}
|
||||
?>
|
1928
source/classes/pref/feeds.php
Normal file
1070
source/classes/pref/filters.php
Normal file
330
source/classes/pref/labels.php
Normal file
|
@ -0,0 +1,330 @@
|
|||
<?php
|
||||
class Pref_Labels extends Handler_Protected {
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("index", "getlabeltree", "edit");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function edit() {
|
||||
$label_id = $this->dbh->escape_string($_REQUEST['id']);
|
||||
|
||||
$result = $this->dbh->query("SELECT * FROM ttrss_labels2 WHERE
|
||||
id = '$label_id' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
$line = $this->dbh->fetch_assoc($result);
|
||||
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"id\" value=\"$label_id\">";
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"pref-labels\">";
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"save\">";
|
||||
|
||||
print "<div class=\"dlgSec\">".__("Caption")."</div>";
|
||||
|
||||
print "<div class=\"dlgSecCont\">";
|
||||
|
||||
$fg_color = $line['fg_color'];
|
||||
$bg_color = $line['bg_color'];
|
||||
|
||||
print "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-bottom : 4px; margin-right : 4px'>α</span>";
|
||||
|
||||
print "<input style=\"font-size : 16px\" name=\"caption\"
|
||||
dojoType=\"dijit.form.ValidationTextBox\"
|
||||
required=\"true\"
|
||||
value=\"".htmlspecialchars($line['caption'])."\">";
|
||||
|
||||
print "</div>";
|
||||
print "<div class=\"dlgSec\">" . __("Colors") . "</div>";
|
||||
print "<div class=\"dlgSecCont\">";
|
||||
|
||||
print "<table cellspacing=\"0\">";
|
||||
|
||||
print "<tr><td>".__("Foreground:")."</td><td>".__("Background:").
|
||||
"</td></tr>";
|
||||
|
||||
print "<tr><td style='padding-right : 10px'>";
|
||||
|
||||
print "<input dojoType=\"dijit.form.TextBox\"
|
||||
style=\"display : none\" id=\"labelEdit_fgColor\"
|
||||
name=\"fg_color\" value=\"$fg_color\">";
|
||||
print "<input dojoType=\"dijit.form.TextBox\"
|
||||
style=\"display : none\" id=\"labelEdit_bgColor\"
|
||||
name=\"bg_color\" value=\"$bg_color\">";
|
||||
|
||||
print "<div dojoType=\"dijit.ColorPalette\">
|
||||
<script type=\"dojo/method\" event=\"onChange\" args=\"fg_color\">
|
||||
dijit.byId(\"labelEdit_fgColor\").attr('value', fg_color);
|
||||
$('label-editor-indicator').setStyle({color: fg_color});
|
||||
</script>
|
||||
</div>";
|
||||
print "</div>";
|
||||
|
||||
print "</td><td>";
|
||||
|
||||
print "<div dojoType=\"dijit.ColorPalette\">
|
||||
<script type=\"dojo/method\" event=\"onChange\" args=\"bg_color\">
|
||||
dijit.byId(\"labelEdit_bgColor\").attr('value', bg_color);
|
||||
$('label-editor-indicator').setStyle({backgroundColor: bg_color});
|
||||
</script>
|
||||
</div>";
|
||||
print "</div>";
|
||||
|
||||
print "</td></tr></table>";
|
||||
print "</div>";
|
||||
|
||||
# print "</form>";
|
||||
|
||||
print "<div class=\"dlgButtons\">";
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelEditDlg').execute()\">".
|
||||
__('Save')."</button>";
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelEditDlg').hide()\">".
|
||||
__('Cancel')."</button>";
|
||||
print "</div>";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function getlabeltree() {
|
||||
$root = array();
|
||||
$root['id'] = 'root';
|
||||
$root['name'] = __('Labels');
|
||||
$root['items'] = array();
|
||||
|
||||
$result = $this->dbh->query("SELECT *
|
||||
FROM ttrss_labels2
|
||||
WHERE owner_uid = ".$_SESSION["uid"]."
|
||||
ORDER BY caption");
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
$label = array();
|
||||
$label['id'] = 'LABEL:' . $line['id'];
|
||||
$label['bare_id'] = $line['id'];
|
||||
$label['name'] = $line['caption'];
|
||||
$label['fg_color'] = $line['fg_color'];
|
||||
$label['bg_color'] = $line['bg_color'];
|
||||
$label['type'] = 'label';
|
||||
$label['checkbox'] = false;
|
||||
|
||||
array_push($root['items'], $label);
|
||||
}
|
||||
|
||||
$fl = array();
|
||||
$fl['identifier'] = 'id';
|
||||
$fl['label'] = 'name';
|
||||
$fl['items'] = array($root);
|
||||
|
||||
print json_encode($fl);
|
||||
return;
|
||||
}
|
||||
|
||||
function colorset() {
|
||||
$kind = $this->dbh->escape_string($_REQUEST["kind"]);
|
||||
$ids = explode(',', $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
$color = $this->dbh->escape_string($_REQUEST["color"]);
|
||||
$fg = $this->dbh->escape_string($_REQUEST["fg"]);
|
||||
$bg = $this->dbh->escape_string($_REQUEST["bg"]);
|
||||
|
||||
foreach ($ids as $id) {
|
||||
|
||||
if ($kind == "fg" || $kind == "bg") {
|
||||
$this->dbh->query("UPDATE ttrss_labels2 SET
|
||||
${kind}_color = '$color' WHERE id = '$id'
|
||||
AND owner_uid = " . $_SESSION["uid"]);
|
||||
} else {
|
||||
$this->dbh->query("UPDATE ttrss_labels2 SET
|
||||
fg_color = '$fg', bg_color = '$bg' WHERE id = '$id'
|
||||
AND owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
$caption = $this->dbh->escape_string(label_find_caption($id, $_SESSION["uid"]));
|
||||
|
||||
/* Remove cached data */
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET label_cache = ''
|
||||
WHERE label_cache LIKE '%$caption%' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function colorreset() {
|
||||
$ids = explode(',', $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$this->dbh->query("UPDATE ttrss_labels2 SET
|
||||
fg_color = '', bg_color = '' WHERE id = '$id'
|
||||
AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
$caption = $this->dbh->escape_string(label_find_caption($id, $_SESSION["uid"]));
|
||||
|
||||
/* Remove cached data */
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET label_cache = ''
|
||||
WHERE label_cache LIKE '%$caption%' AND owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function save() {
|
||||
|
||||
$id = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
$caption = $this->dbh->escape_string(trim($_REQUEST["caption"]));
|
||||
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
$result = $this->dbh->query("SELECT caption FROM ttrss_labels2
|
||||
WHERE id = '$id' AND owner_uid = ". $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$old_caption = $this->dbh->fetch_result($result, 0, "caption");
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_labels2
|
||||
WHERE caption = '$caption' AND owner_uid = ". $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
if ($caption) {
|
||||
$result = $this->dbh->query("UPDATE ttrss_labels2 SET
|
||||
caption = '$caption' WHERE id = '$id' AND
|
||||
owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
/* Update filters that reference label being renamed */
|
||||
|
||||
$old_caption = $this->dbh->escape_string($old_caption);
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_filters2_actions SET
|
||||
action_param = '$caption' WHERE action_param = '$old_caption'
|
||||
AND action_id = 7
|
||||
AND filter_id IN (SELECT id FROM ttrss_filters2 WHERE owner_uid = ".$_SESSION["uid"].")");
|
||||
|
||||
print $_REQUEST["value"];
|
||||
} else {
|
||||
print $old_caption;
|
||||
}
|
||||
} else {
|
||||
print $old_caption;
|
||||
}
|
||||
}
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function remove() {
|
||||
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
label_remove($id, $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function add() {
|
||||
$caption = $this->dbh->escape_string($_REQUEST["caption"]);
|
||||
$output = $this->dbh->escape_string($_REQUEST["output"]);
|
||||
|
||||
if ($caption) {
|
||||
|
||||
if (label_create($caption)) {
|
||||
if (!$output) {
|
||||
print T_sprintf("Created label <b>%s</b>", htmlspecialchars($caption));
|
||||
}
|
||||
}
|
||||
|
||||
if ($output == "select") {
|
||||
header("Content-Type: text/xml");
|
||||
|
||||
print "<rpc-reply><payload>";
|
||||
|
||||
print_label_select("select_label",
|
||||
$caption, "");
|
||||
|
||||
print "</payload></rpc-reply>";
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function index() {
|
||||
|
||||
$sort = $this->dbh->escape_string($_REQUEST["sort"]);
|
||||
|
||||
if (!$sort || $sort == "undefined") {
|
||||
$sort = "caption";
|
||||
}
|
||||
|
||||
$label_search = $this->dbh->escape_string($_REQUEST["search"]);
|
||||
|
||||
if (array_key_exists("search", $_REQUEST)) {
|
||||
$_SESSION["prefs_label_search"] = $label_search;
|
||||
} else {
|
||||
$label_search = $_SESSION["prefs_label_search"];
|
||||
}
|
||||
|
||||
print "<div id=\"pref-label-wrap\" dojoType=\"dijit.layout.BorderContainer\" gutters=\"false\">";
|
||||
print "<div id=\"pref-label-header\" dojoType=\"dijit.layout.ContentPane\" region=\"top\">";
|
||||
print "<div id=\"pref-label-toolbar\" dojoType=\"dijit.Toolbar\">";
|
||||
|
||||
print "<div dojoType=\"dijit.form.DropDownButton\">".
|
||||
"<span>" . __('Select')."</span>";
|
||||
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
|
||||
print "<div onclick=\"dijit.byId('labelTree').model.setAllChecked(true)\"
|
||||
dojoType=\"dijit.MenuItem\">".__('All')."</div>";
|
||||
print "<div onclick=\"dijit.byId('labelTree').model.setAllChecked(false)\"
|
||||
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
|
||||
print "</div></div>";
|
||||
|
||||
print"<button dojoType=\"dijit.form.Button\" onclick=\"return addLabel()\">".
|
||||
__('Create label')."</button dojoType=\"dijit.form.Button\"> ";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"removeSelectedLabels()\">".
|
||||
__('Remove')."</button dojoType=\"dijit.form.Button\"> ";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"labelColorReset()\">".
|
||||
__('Clear colors')."</button dojoType=\"dijit.form.Button\">";
|
||||
|
||||
|
||||
print "</div>"; #toolbar
|
||||
print "</div>"; #pane
|
||||
print "<div id=\"pref-label-content\" dojoType=\"dijit.layout.ContentPane\" region=\"center\">";
|
||||
|
||||
print "<div id=\"labellistLoading\">
|
||||
<img src='images/indicator_tiny.gif'>".
|
||||
__("Loading, please wait...")."</div>";
|
||||
|
||||
print "<div dojoType=\"dojo.data.ItemFileWriteStore\" jsId=\"labelStore\"
|
||||
url=\"backend.php?op=pref-labels&method=getlabeltree\">
|
||||
</div>
|
||||
<div dojoType=\"lib.CheckBoxStoreModel\" jsId=\"labelModel\" store=\"labelStore\"
|
||||
query=\"{id:'root'}\" rootId=\"root\"
|
||||
childrenAttrs=\"items\" checkboxStrict=\"false\" checkboxAll=\"false\">
|
||||
</div>
|
||||
<div dojoType=\"fox.PrefLabelTree\" id=\"labelTree\"
|
||||
model=\"labelModel\" openOnClick=\"true\">
|
||||
<script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
|
||||
Element.hide(\"labellistLoading\");
|
||||
</script>
|
||||
<script type=\"dojo/method\" event=\"onClick\" args=\"item\">
|
||||
var id = String(item.id);
|
||||
var bare_id = id.substr(id.indexOf(':')+1);
|
||||
|
||||
if (id.match('LABEL:')) {
|
||||
editLabel(bare_id);
|
||||
}
|
||||
</script>
|
||||
</div>";
|
||||
|
||||
print "</div>"; #pane
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
|
||||
"hook_prefs_tab", "prefLabels");
|
||||
|
||||
print "</div>"; #container
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
1127
source/classes/pref/prefs.php
Normal file
90
source/classes/pref/system.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
class Pref_System extends Handler_Protected {
|
||||
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
if ($_SESSION["access_level"] < 10) {
|
||||
print __("Your access level is insufficient to open this tab.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("index");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
$this->dbh->query("DELETE FROM ttrss_error_log");
|
||||
}
|
||||
|
||||
function index() {
|
||||
|
||||
print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">";
|
||||
print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Error Log')."\">";
|
||||
|
||||
if (LOG_DESTINATION == "sql") {
|
||||
|
||||
$result = $this->dbh->query("SELECT errno, errstr, filename, lineno,
|
||||
created_at, login FROM ttrss_error_log
|
||||
LEFT JOIN ttrss_users ON (owner_uid = ttrss_users.id)
|
||||
ORDER BY ttrss_error_log.id DESC
|
||||
LIMIT 100");
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"updateSystemList()\">".__('Refresh')."</button> ";
|
||||
|
||||
print " <button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"clearSqlLog()\">".__('Clear log')."</button> ";
|
||||
|
||||
print "<p><table width=\"100%\" cellspacing=\"10\" class=\"prefErrorLog\">";
|
||||
|
||||
print "<tr class=\"title\">
|
||||
<td width='5%'>".__("Error")."</td>
|
||||
<td>".__("Filename")."</td>
|
||||
<td>".__("Message")."</td>
|
||||
<td width='5%'>".__("User")."</td>
|
||||
<td width='5%'>".__("Date")."</td>
|
||||
</tr>";
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
print "<tr class=\"errrow\">";
|
||||
|
||||
foreach ($line as $k => $v) {
|
||||
$line[$k] = htmlspecialchars($v);
|
||||
}
|
||||
|
||||
print "<td class='errno'>" . Logger::$errornames[$line["errno"]] . " (" . $line["errno"] . ")</td>";
|
||||
print "<td class='filename'>" . $line["filename"] . ":" . $line["lineno"] . "</td>";
|
||||
print "<td class='errstr'>" . $line["errstr"] . "</td>";
|
||||
print "<td class='login'>" . $line["login"] . "</td>";
|
||||
|
||||
print "<td class='timestamp'>" .
|
||||
make_local_datetime(
|
||||
$line["created_at"], false) . "</td>";
|
||||
|
||||
print "</tr>";
|
||||
}
|
||||
|
||||
print "</table>";
|
||||
} else {
|
||||
|
||||
print_notice("Please set LOG_DESTINATION to 'sql' in config.php to enable database logging.");
|
||||
|
||||
}
|
||||
|
||||
print "</div>";
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
|
||||
"hook_prefs_tab", "prefSystem");
|
||||
|
||||
print "</div>"; #container
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
457
source/classes/pref/users.php
Normal file
|
@ -0,0 +1,457 @@
|
|||
<?php
|
||||
class Pref_Users extends Handler_Protected {
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
if ($_SESSION["access_level"] < 10) {
|
||||
print __("Your access level is insufficient to open this tab.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("index", "edit", "userdetails");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function userdetails() {
|
||||
|
||||
$uid = sprintf("%d", $_REQUEST["id"]);
|
||||
|
||||
$result = $this->dbh->query("SELECT login,
|
||||
".SUBSTRING_FOR_DATE."(last_login,1,16) AS last_login,
|
||||
access_level,
|
||||
(SELECT COUNT(int_id) FROM ttrss_user_entries
|
||||
WHERE owner_uid = id) AS stored_articles,
|
||||
".SUBSTRING_FOR_DATE."(created,1,16) AS created
|
||||
FROM ttrss_users
|
||||
WHERE id = '$uid'");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
print "<h1>".__('User not found')."</h1>";
|
||||
return;
|
||||
}
|
||||
|
||||
// print "<h1>User Details</h1>";
|
||||
|
||||
$login = $this->dbh->fetch_result($result, 0, "login");
|
||||
|
||||
print "<table width='100%'>";
|
||||
|
||||
$last_login = make_local_datetime(
|
||||
$this->dbh->fetch_result($result, 0, "last_login"), true);
|
||||
|
||||
$created = make_local_datetime(
|
||||
$this->dbh->fetch_result($result, 0, "created"), true);
|
||||
|
||||
$access_level = $this->dbh->fetch_result($result, 0, "access_level");
|
||||
$stored_articles = $this->dbh->fetch_result($result, 0, "stored_articles");
|
||||
|
||||
print "<tr><td>".__('Registered')."</td><td>$created</td></tr>";
|
||||
print "<tr><td>".__('Last logged in')."</td><td>$last_login</td></tr>";
|
||||
|
||||
$result = $this->dbh->query("SELECT COUNT(id) as num_feeds FROM ttrss_feeds
|
||||
WHERE owner_uid = '$uid'");
|
||||
|
||||
$num_feeds = $this->dbh->fetch_result($result, 0, "num_feeds");
|
||||
|
||||
print "<tr><td>".__('Subscribed feeds count')."</td><td>$num_feeds</td></tr>";
|
||||
|
||||
print "</table>";
|
||||
|
||||
print "<h1>".__('Subscribed feeds')."</h1>";
|
||||
|
||||
$result = $this->dbh->query("SELECT id,title,site_url FROM ttrss_feeds
|
||||
WHERE owner_uid = '$uid' ORDER BY title");
|
||||
|
||||
print "<ul class=\"userFeedList\">";
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
|
||||
$icon_file = ICONS_URL."/".$line["id"].".ico";
|
||||
|
||||
if (file_exists($icon_file) && filesize($icon_file) > 0) {
|
||||
$feed_icon = "<img class=\"tinyFeedIcon\" src=\"$icon_file\">";
|
||||
} else {
|
||||
$feed_icon = "<img class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\">";
|
||||
}
|
||||
|
||||
print "<li>$feed_icon <a href=\"".$line["site_url"]."\">".$line["title"]."</a></li>";
|
||||
|
||||
}
|
||||
|
||||
if ($this->dbh->num_rows($result) < $num_feeds) {
|
||||
// FIXME - add link to show ALL subscribed feeds here somewhere
|
||||
print "<li><img
|
||||
class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\"> ...</li>";
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
|
||||
print "<div align='center'>
|
||||
<button dojoType=\"dijit.form.Button\" type=\"submit\">".__("Close this window").
|
||||
"</button></div>";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function edit() {
|
||||
global $access_level_names;
|
||||
|
||||
$id = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
print "<form id=\"user_edit_form\" onsubmit='return false' dojoType=\"dijit.form.Form\">";
|
||||
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"id\" value=\"$id\">";
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"pref-users\">";
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"editSave\">";
|
||||
|
||||
$result = $this->dbh->query("SELECT * FROM ttrss_users WHERE id = '$id'");
|
||||
|
||||
$login = $this->dbh->fetch_result($result, 0, "login");
|
||||
$access_level = $this->dbh->fetch_result($result, 0, "access_level");
|
||||
$email = $this->dbh->fetch_result($result, 0, "email");
|
||||
|
||||
$sel_disabled = ($id == $_SESSION["uid"]) ? "disabled" : "";
|
||||
|
||||
print "<div class=\"dlgSec\">".__("User")."</div>";
|
||||
print "<div class=\"dlgSecCont\">";
|
||||
|
||||
if ($sel_disabled) {
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"login\" value=\"$login\">";
|
||||
}
|
||||
|
||||
print "<input size=\"30\" style=\"font-size : 16px\"
|
||||
dojoType=\"dijit.form.ValidationTextBox\" required=\"1\"
|
||||
onkeypress=\"return filterCR(event, userEditSave)\" $sel_disabled
|
||||
name=\"login\" value=\"$login\">";
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<div class=\"dlgSec\">".__("Authentication")."</div>";
|
||||
print "<div class=\"dlgSecCont\">";
|
||||
|
||||
print __('Access level: ') . " ";
|
||||
|
||||
if (!$sel_disabled) {
|
||||
print_select_hash("access_level", $access_level, $access_level_names,
|
||||
"dojoType=\"dijit.form.Select\" $sel_disabled");
|
||||
} else {
|
||||
print_select_hash("", $access_level, $access_level_names,
|
||||
"dojoType=\"dijit.form.Select\" $sel_disabled");
|
||||
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"access_level\" value=\"$access_level\">";
|
||||
}
|
||||
|
||||
print "<hr/>";
|
||||
|
||||
print "<input dojoType=\"dijit.form.TextBox\" type=\"password\" size=\"20\" onkeypress=\"return filterCR(event, userEditSave)\" placeholder=\"Change password\"
|
||||
name=\"password\">";
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<div class=\"dlgSec\">".__("Options")."</div>";
|
||||
print "<div class=\"dlgSecCont\">";
|
||||
|
||||
print "<input dojoType=\"dijit.form.TextBox\" size=\"30\" name=\"email\" onkeypress=\"return filterCR(event, userEditSave)\" placeholder=\"E-mail\"
|
||||
value=\"$email\">";
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "</table>";
|
||||
|
||||
print "</form>";
|
||||
|
||||
print "<div class=\"dlgButtons\">
|
||||
<button dojoType=\"dijit.form.Button\" type=\"submit\">".
|
||||
__('Save')."</button>
|
||||
<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('userEditDlg').hide()\">".
|
||||
__('Cancel')."</button></div>";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function editSave() {
|
||||
$login = $this->dbh->escape_string(trim($_REQUEST["login"]));
|
||||
$uid = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
$access_level = (int) $_REQUEST["access_level"];
|
||||
$email = $this->dbh->escape_string(trim($_REQUEST["email"]));
|
||||
$password = $_REQUEST["password"];
|
||||
|
||||
if ($password) {
|
||||
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
|
||||
$pwd_hash = encrypt_password($password, $salt, true);
|
||||
$pass_query_part = "pwd_hash = '$pwd_hash', salt = '$salt',";
|
||||
} else {
|
||||
$pass_query_part = "";
|
||||
}
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_users SET $pass_query_part login = '$login',
|
||||
access_level = '$access_level', email = '$email', otp_enabled = false
|
||||
WHERE id = '$uid'");
|
||||
|
||||
}
|
||||
|
||||
function remove() {
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if ($id != $_SESSION["uid"] && $id != 1) {
|
||||
$this->dbh->query("DELETE FROM ttrss_tags WHERE owner_uid = '$id'");
|
||||
$this->dbh->query("DELETE FROM ttrss_feeds WHERE owner_uid = '$id'");
|
||||
$this->dbh->query("DELETE FROM ttrss_users WHERE id = '$id'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function add() {
|
||||
|
||||
$login = $this->dbh->escape_string(trim($_REQUEST["login"]));
|
||||
$tmp_user_pwd = make_password(8);
|
||||
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
|
||||
$pwd_hash = encrypt_password($tmp_user_pwd, $salt, true);
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_users WHERE
|
||||
login = '$login'");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
|
||||
$this->dbh->query("INSERT INTO ttrss_users
|
||||
(login,pwd_hash,access_level,last_login,created, salt)
|
||||
VALUES ('$login', '$pwd_hash', 0, null, NOW(), '$salt')");
|
||||
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_users WHERE
|
||||
login = '$login' AND pwd_hash = '$pwd_hash'");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 1) {
|
||||
|
||||
$new_uid = $this->dbh->fetch_result($result, 0, "id");
|
||||
|
||||
print format_notice(T_sprintf("Added user <b>%s</b> with password <b>%s</b>",
|
||||
$login, $tmp_user_pwd));
|
||||
|
||||
initialize_user($new_uid);
|
||||
|
||||
} else {
|
||||
|
||||
print format_warning(T_sprintf("Could not create user <b>%s</b>", $login));
|
||||
|
||||
}
|
||||
} else {
|
||||
print format_warning(T_sprintf("User <b>%s</b> already exists.", $login));
|
||||
}
|
||||
}
|
||||
|
||||
static function resetUserPassword($uid, $show_password) {
|
||||
|
||||
$result = db_query("SELECT login,email
|
||||
FROM ttrss_users WHERE id = '$uid'");
|
||||
|
||||
$login = db_fetch_result($result, 0, "login");
|
||||
$email = db_fetch_result($result, 0, "email");
|
||||
$salt = db_fetch_result($result, 0, "salt");
|
||||
|
||||
$new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
|
||||
$tmp_user_pwd = make_password(8);
|
||||
|
||||
$pwd_hash = encrypt_password($tmp_user_pwd, $new_salt, true);
|
||||
|
||||
db_query("UPDATE ttrss_users SET pwd_hash = '$pwd_hash', salt = '$new_salt'
|
||||
WHERE id = '$uid'");
|
||||
|
||||
if ($show_password) {
|
||||
print T_sprintf("Changed password of user <b>%s</b> to <b>%s</b>", $login, $tmp_user_pwd);
|
||||
} else {
|
||||
print_notice(T_sprintf("Sending new password of user <b>%s</b> to <b>%s</b>", $login, $email));
|
||||
}
|
||||
|
||||
require_once 'classes/ttrssmailer.php';
|
||||
|
||||
if ($email) {
|
||||
require_once "lib/MiniTemplator.class.php";
|
||||
|
||||
$tpl = new MiniTemplator;
|
||||
|
||||
$tpl->readTemplateFromFile("templates/resetpass_template.txt");
|
||||
|
||||
$tpl->setVariable('LOGIN', $login);
|
||||
$tpl->setVariable('NEWPASS', $tmp_user_pwd);
|
||||
|
||||
$tpl->addBlock('message');
|
||||
|
||||
$message = "";
|
||||
|
||||
$tpl->generateOutputToString($message);
|
||||
|
||||
$mail = new ttrssMailer();
|
||||
|
||||
$rc = $mail->quickMail($email, $login,
|
||||
__("[tt-rss] Password change notification"),
|
||||
$message, false);
|
||||
|
||||
if (!$rc) print_error($mail->ErrorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
function resetPass() {
|
||||
$uid = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
Pref_Users::resetUserPassword($uid, true);
|
||||
}
|
||||
|
||||
function index() {
|
||||
|
||||
global $access_level_names;
|
||||
|
||||
print "<div id=\"pref-user-wrap\" dojoType=\"dijit.layout.BorderContainer\" gutters=\"false\">";
|
||||
print "<div id=\"pref-user-header\" dojoType=\"dijit.layout.ContentPane\" region=\"top\">";
|
||||
|
||||
print "<div id=\"pref-user-toolbar\" dojoType=\"dijit.Toolbar\">";
|
||||
|
||||
$user_search = $this->dbh->escape_string($_REQUEST["search"]);
|
||||
|
||||
if (array_key_exists("search", $_REQUEST)) {
|
||||
$_SESSION["prefs_user_search"] = $user_search;
|
||||
} else {
|
||||
$user_search = $_SESSION["prefs_user_search"];
|
||||
}
|
||||
|
||||
print "<div style='float : right; padding-right : 4px;'>
|
||||
<input dojoType=\"dijit.form.TextBox\" id=\"user_search\" size=\"20\" type=\"search\"
|
||||
value=\"$user_search\">
|
||||
<button dojoType=\"dijit.form.Button\" onclick=\"updateUsersList()\">".
|
||||
__('Search')."</button>
|
||||
</div>";
|
||||
|
||||
$sort = $this->dbh->escape_string($_REQUEST["sort"]);
|
||||
|
||||
if (!$sort || $sort == "undefined") {
|
||||
$sort = "login";
|
||||
}
|
||||
|
||||
print "<div dojoType=\"dijit.form.DropDownButton\">".
|
||||
"<span>" . __('Select')."</span>";
|
||||
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
|
||||
print "<div onclick=\"selectTableRows('prefUserList', 'all')\"
|
||||
dojoType=\"dijit.MenuItem\">".__('All')."</div>";
|
||||
print "<div onclick=\"selectTableRows('prefUserList', 'none')\"
|
||||
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
|
||||
print "</div></div>";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"addUser()\">".__('Create user')."</button>";
|
||||
|
||||
print "
|
||||
<button dojoType=\"dijit.form.Button\" onclick=\"selectedUserDetails()\">".
|
||||
__('Details')."</button dojoType=\"dijit.form.Button\">
|
||||
<button dojoType=\"dijit.form.Button\" onclick=\"editSelectedUser()\">".
|
||||
__('Edit')."</button dojoType=\"dijit.form.Button\">
|
||||
<button dojoType=\"dijit.form.Button\" onclick=\"removeSelectedUsers()\">".
|
||||
__('Remove')."</button dojoType=\"dijit.form.Button\">
|
||||
<button dojoType=\"dijit.form.Button\" onclick=\"resetSelectedUserPass()\">".
|
||||
__('Reset password')."</button dojoType=\"dijit.form.Button\">";
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
|
||||
"hook_prefs_tab_section", "prefUsersToolbar");
|
||||
|
||||
print "</div>"; #toolbar
|
||||
print "</div>"; #pane
|
||||
print "<div id=\"pref-user-content\" dojoType=\"dijit.layout.ContentPane\" region=\"center\">";
|
||||
|
||||
print "<div id=\"sticky-status-msg\"></div>";
|
||||
|
||||
if ($user_search) {
|
||||
|
||||
$user_search = explode(" ", $user_search);
|
||||
$tokens = array();
|
||||
|
||||
foreach ($user_search as $token) {
|
||||
$token = trim($token);
|
||||
array_push($tokens, "(UPPER(login) LIKE UPPER('%$token%'))");
|
||||
}
|
||||
|
||||
$user_search_query = "(" . join($tokens, " AND ") . ") AND ";
|
||||
|
||||
} else {
|
||||
$user_search_query = "";
|
||||
}
|
||||
|
||||
$result = $this->dbh->query("SELECT
|
||||
id,login,access_level,email,
|
||||
".SUBSTRING_FOR_DATE."(last_login,1,16) as last_login,
|
||||
".SUBSTRING_FOR_DATE."(created,1,16) as created
|
||||
FROM
|
||||
ttrss_users
|
||||
WHERE
|
||||
$user_search_query
|
||||
id > 0
|
||||
ORDER BY $sort");
|
||||
|
||||
if ($this->dbh->num_rows($result) > 0) {
|
||||
|
||||
print "<p><table width=\"100%\" cellspacing=\"0\"
|
||||
class=\"prefUserList\" id=\"prefUserList\">";
|
||||
|
||||
print "<tr class=\"title\">
|
||||
<td align='center' width=\"5%\"> </td>
|
||||
<td width='30%'><a href=\"#\" onclick=\"updateUsersList('login')\">".__('Login')."</a></td>
|
||||
<td width='30%'><a href=\"#\" onclick=\"updateUsersList('access_level')\">".__('Access Level')."</a></td>
|
||||
<td width='20%'><a href=\"#\" onclick=\"updateUsersList('created')\">".__('Registered')."</a></td>
|
||||
<td width='20%'><a href=\"#\" onclick=\"updateUsersList('last_login')\">".__('Last login')."</a></td></tr>";
|
||||
|
||||
$lnum = 0;
|
||||
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
|
||||
$uid = $line["id"];
|
||||
|
||||
print "<tr id=\"UMRR-$uid\">";
|
||||
|
||||
$line["login"] = htmlspecialchars($line["login"]);
|
||||
|
||||
$line["created"] = make_local_datetime($line["created"], false);
|
||||
$line["last_login"] = make_local_datetime($line["last_login"], false);
|
||||
|
||||
print "<td align='center'><input onclick='toggleSelectRow2(this);'
|
||||
dojoType=\"dijit.form.CheckBox\" type=\"checkbox\"
|
||||
id=\"UMCHK-$uid\"></td>";
|
||||
|
||||
$onclick = "onclick='editUser($uid, event)' title='".__('Click to edit')."'";
|
||||
|
||||
print "<td $onclick><img src='images/user.png' class='markedPic' alt=''> " . $line["login"] . "</td>";
|
||||
|
||||
if (!$line["email"]) $line["email"] = " ";
|
||||
|
||||
print "<td $onclick>" . $access_level_names[$line["access_level"]] . "</td>";
|
||||
print "<td $onclick>" . $line["created"] . "</td>";
|
||||
print "<td $onclick>" . $line["last_login"] . "</td>";
|
||||
|
||||
print "</tr>";
|
||||
|
||||
++$lnum;
|
||||
}
|
||||
|
||||
print "</table>";
|
||||
|
||||
} else {
|
||||
print "<p>";
|
||||
if (!$user_search) {
|
||||
print_warning(__('No users defined.'));
|
||||
} else {
|
||||
print_warning(__('No matching users found.'));
|
||||
}
|
||||
print "</p>";
|
||||
|
||||
}
|
||||
|
||||
print "</div>"; #pane
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
|
||||
"hook_prefs_tab", "prefUsers");
|
||||
|
||||
print "</div>"; #container
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
655
source/classes/rpc.php
Normal file
|
@ -0,0 +1,655 @@
|
|||
<?php
|
||||
class RPC extends Handler_Protected {
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("sanitycheck", "completelabels");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function setprofile() {
|
||||
$id = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
|
||||
$_SESSION["profile"] = $id;
|
||||
}
|
||||
|
||||
function remprofiles() {
|
||||
$ids = explode(",", $this->dbh->escape_string(trim($_REQUEST["ids"])));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if ($_SESSION["profile"] != $id) {
|
||||
$this->dbh->query("DELETE FROM ttrss_settings_profiles WHERE id = '$id' AND
|
||||
owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Silent
|
||||
function addprofile() {
|
||||
$title = $this->dbh->escape_string(trim($_REQUEST["title"]));
|
||||
if ($title) {
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_settings_profiles
|
||||
WHERE title = '$title' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
|
||||
$this->dbh->query("INSERT INTO ttrss_settings_profiles (title, owner_uid)
|
||||
VALUES ('$title', ".$_SESSION["uid"] .")");
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_settings_profiles WHERE
|
||||
title = '$title'");
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$profile_id = $this->dbh->fetch_result($result, 0, "id");
|
||||
|
||||
if ($profile_id) {
|
||||
initialize_user_prefs($_SESSION["uid"], $profile_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
}
|
||||
}
|
||||
|
||||
// Silent
|
||||
function saveprofile() {
|
||||
$id = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
$title = $this->dbh->escape_string(trim($_REQUEST["value"]));
|
||||
|
||||
if ($id == 0) {
|
||||
print __("Default profile");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($title) {
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_settings_profiles
|
||||
WHERE title = '$title' AND owner_uid =" . $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
$this->dbh->query("UPDATE ttrss_settings_profiles
|
||||
SET title = '$title' WHERE id = '$id' AND
|
||||
owner_uid = " . $_SESSION["uid"]);
|
||||
print $title;
|
||||
} else {
|
||||
$result = $this->dbh->query("SELECT title FROM ttrss_settings_profiles
|
||||
WHERE id = '$id' AND owner_uid =" . $_SESSION["uid"]);
|
||||
print $this->dbh->fetch_result($result, 0, "title");
|
||||
}
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
}
|
||||
}
|
||||
|
||||
// Silent
|
||||
function remarchive() {
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$result = $this->dbh->query("DELETE FROM ttrss_archived_feeds WHERE
|
||||
(SELECT COUNT(*) FROM ttrss_user_entries
|
||||
WHERE orig_feed_id = '$id') = 0 AND
|
||||
id = '$id' AND owner_uid = ".$_SESSION["uid"]);
|
||||
|
||||
$rc = $this->dbh->affected_rows($result);
|
||||
}
|
||||
}
|
||||
|
||||
function addfeed() {
|
||||
$feed = $this->dbh->escape_string($_REQUEST['feed']);
|
||||
$cat = $this->dbh->escape_string($_REQUEST['cat']);
|
||||
$login = $this->dbh->escape_string($_REQUEST['login']);
|
||||
$pass = trim($_REQUEST['pass']); // escaped later
|
||||
|
||||
$rc = subscribe_to_feed($feed, $cat, $login, $pass);
|
||||
|
||||
print json_encode(array("result" => $rc));
|
||||
}
|
||||
|
||||
function togglepref() {
|
||||
$key = $this->dbh->escape_string($_REQUEST["key"]);
|
||||
set_pref($key, !get_pref($key));
|
||||
$value = get_pref($key);
|
||||
|
||||
print json_encode(array("param" =>$key, "value" => $value));
|
||||
}
|
||||
|
||||
function setpref() {
|
||||
// set_pref escapes input, so no need to double escape it here
|
||||
$key = $_REQUEST['key'];
|
||||
$value = str_replace("\n", "<br/>", $_REQUEST['value']);
|
||||
|
||||
set_pref($key, $value, $_SESSION['uid'], $key != 'USER_STYLESHEET');
|
||||
|
||||
print json_encode(array("param" =>$key, "value" => $value));
|
||||
}
|
||||
|
||||
function mark() {
|
||||
$mark = $_REQUEST["mark"];
|
||||
$id = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
|
||||
if ($mark == "1") {
|
||||
$mark = "true";
|
||||
} else {
|
||||
$mark = "false";
|
||||
}
|
||||
|
||||
$result = $this->dbh->query("UPDATE ttrss_user_entries SET marked = $mark,
|
||||
last_marked = NOW()
|
||||
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function delete() {
|
||||
$ids = $this->dbh->escape_string($_REQUEST["ids"]);
|
||||
|
||||
$result = $this->dbh->query("DELETE FROM ttrss_user_entries
|
||||
WHERE ref_id IN ($ids) AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
purge_orphans();
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function unarchive() {
|
||||
$ids = explode(",", $_REQUEST["ids"]);
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$id = $this->dbh->escape_string(trim($id));
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
$result = $this->dbh->query("SELECT feed_url,site_url,title FROM ttrss_archived_feeds
|
||||
WHERE id = (SELECT orig_feed_id FROM ttrss_user_entries WHERE ref_id = $id
|
||||
AND owner_uid = ".$_SESSION["uid"].")");
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$feed_url = $this->dbh->escape_string(db_fetch_result($result, 0, "feed_url"));
|
||||
$site_url = $this->dbh->escape_string(db_fetch_result($result, 0, "site_url"));
|
||||
$title = $this->dbh->escape_string(db_fetch_result($result, 0, "title"));
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE feed_url = '$feed_url'
|
||||
AND owner_uid = " .$_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
|
||||
if (!$title) $title = '[Unknown]';
|
||||
|
||||
$result = $this->dbh->query(
|
||||
"INSERT INTO ttrss_feeds
|
||||
(owner_uid,feed_url,site_url,title,cat_id,auth_login,auth_pass,update_method)
|
||||
VALUES (".$_SESSION["uid"].",
|
||||
'$feed_url',
|
||||
'$site_url',
|
||||
'$title',
|
||||
NULL, '', '', 0)");
|
||||
|
||||
$result = $this->dbh->query(
|
||||
"SELECT id FROM ttrss_feeds WHERE feed_url = '$feed_url'
|
||||
AND owner_uid = ".$_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$feed_id = $this->dbh->fetch_result($result, 0, "id");
|
||||
}
|
||||
|
||||
} else {
|
||||
$feed_id = $this->dbh->fetch_result($result, 0, "id");
|
||||
}
|
||||
|
||||
if ($feed_id) {
|
||||
$result = $this->dbh->query("UPDATE ttrss_user_entries
|
||||
SET feed_id = '$feed_id', orig_feed_id = NULL
|
||||
WHERE ref_id = $id AND owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
}
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function archive() {
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$this->archive_article($id, $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
private function archive_article($id, $owner_uid) {
|
||||
$this->dbh->query("BEGIN");
|
||||
|
||||
$result = $this->dbh->query("SELECT feed_id FROM ttrss_user_entries
|
||||
WHERE ref_id = '$id' AND owner_uid = $owner_uid");
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
|
||||
/* prepare the archived table */
|
||||
|
||||
$feed_id = (int) $this->dbh->fetch_result($result, 0, "feed_id");
|
||||
|
||||
if ($feed_id) {
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_archived_feeds
|
||||
WHERE id = '$feed_id'");
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
$this->dbh->query("INSERT INTO ttrss_archived_feeds
|
||||
(id, owner_uid, title, feed_url, site_url)
|
||||
SELECT id, owner_uid, title, feed_url, site_url from ttrss_feeds
|
||||
WHERE id = '$feed_id'");
|
||||
}
|
||||
|
||||
$this->dbh->query("UPDATE ttrss_user_entries
|
||||
SET orig_feed_id = feed_id, feed_id = NULL
|
||||
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->dbh->query("COMMIT");
|
||||
}
|
||||
|
||||
function publ() {
|
||||
$pub = $_REQUEST["pub"];
|
||||
$id = $this->dbh->escape_string($_REQUEST["id"]);
|
||||
$note = trim(strip_tags($this->dbh->escape_string($_REQUEST["note"])));
|
||||
|
||||
if ($pub == "1") {
|
||||
$pub = "true";
|
||||
} else {
|
||||
$pub = "false";
|
||||
}
|
||||
|
||||
$result = $this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
published = $pub, last_published = NOW()
|
||||
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
$pubsub_result = false;
|
||||
|
||||
if (PUBSUBHUBBUB_HUB) {
|
||||
$rss_link = get_self_url_prefix() .
|
||||
"/public.php?op=rss&id=-2&key=" .
|
||||
get_feed_access_key(-2, false);
|
||||
|
||||
$p = new Publisher(PUBSUBHUBBUB_HUB);
|
||||
|
||||
$pubsub_result = $p->publish_update($rss_link);
|
||||
}
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS",
|
||||
"pubsub_result" => $pubsub_result));
|
||||
}
|
||||
|
||||
function getAllCounters() {
|
||||
$last_article_id = (int) $_REQUEST["last_article_id"];
|
||||
|
||||
$reply = array();
|
||||
|
||||
if (!empty($_REQUEST['seq'])) $reply['seq'] = (int) $_REQUEST['seq'];
|
||||
|
||||
if ($last_article_id != getLastArticleId()) {
|
||||
$reply['counters'] = getAllCounters();
|
||||
}
|
||||
|
||||
$reply['runtime-info'] = make_runtime_info();
|
||||
|
||||
print json_encode($reply);
|
||||
}
|
||||
|
||||
/* GET["cmode"] = 0 - mark as read, 1 - as unread, 2 - toggle */
|
||||
function catchupSelected() {
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
$cmode = sprintf("%d", $_REQUEST["cmode"]);
|
||||
|
||||
catchupArticlesById($ids, $cmode);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS", "ids" => $ids));
|
||||
}
|
||||
|
||||
function markSelected() {
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
$cmode = sprintf("%d", $_REQUEST["cmode"]);
|
||||
|
||||
$this->markArticlesById($ids, $cmode);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function publishSelected() {
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
$cmode = sprintf("%d", $_REQUEST["cmode"]);
|
||||
|
||||
$this->publishArticlesById($ids, $cmode);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function sanityCheck() {
|
||||
$_SESSION["hasAudio"] = $_REQUEST["hasAudio"] === "true";
|
||||
$_SESSION["hasSandbox"] = $_REQUEST["hasSandbox"] === "true";
|
||||
$_SESSION["hasMp3"] = $_REQUEST["hasMp3"] === "true";
|
||||
$_SESSION["clientTzOffset"] = $_REQUEST["clientTzOffset"];
|
||||
|
||||
$reply = array();
|
||||
|
||||
$reply['error'] = sanity_check();
|
||||
|
||||
if ($reply['error']['code'] == 0) {
|
||||
$reply['init-params'] = make_init_params();
|
||||
$reply['runtime-info'] = make_runtime_info();
|
||||
}
|
||||
|
||||
print json_encode($reply);
|
||||
}
|
||||
|
||||
function completeLabels() {
|
||||
$search = $this->dbh->escape_string($_REQUEST["search"]);
|
||||
|
||||
$result = $this->dbh->query("SELECT DISTINCT caption FROM
|
||||
ttrss_labels2
|
||||
WHERE owner_uid = '".$_SESSION["uid"]."' AND
|
||||
LOWER(caption) LIKE LOWER('$search%') ORDER BY caption
|
||||
LIMIT 5");
|
||||
|
||||
print "<ul>";
|
||||
while ($line = $this->dbh->fetch_assoc($result)) {
|
||||
print "<li>" . $line["caption"] . "</li>";
|
||||
}
|
||||
print "</ul>";
|
||||
}
|
||||
|
||||
function purge() {
|
||||
$ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"]));
|
||||
$days = sprintf("%d", $_REQUEST["days"]);
|
||||
|
||||
foreach ($ids as $id) {
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE
|
||||
id = '$id' AND owner_uid = ".$_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) == 1) {
|
||||
purge_feed($id, $days);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateFeedBrowser() {
|
||||
$search = $this->dbh->escape_string($_REQUEST["search"]);
|
||||
$limit = $this->dbh->escape_string($_REQUEST["limit"]);
|
||||
$mode = (int) $this->dbh->escape_string($_REQUEST["mode"]);
|
||||
|
||||
require_once "feedbrowser.php";
|
||||
|
||||
print json_encode(array("content" =>
|
||||
make_feed_browser($search, $limit, $mode),
|
||||
"mode" => $mode));
|
||||
}
|
||||
|
||||
// Silent
|
||||
function massSubscribe() {
|
||||
|
||||
$payload = json_decode($_REQUEST["payload"], false);
|
||||
$mode = $_REQUEST["mode"];
|
||||
|
||||
if (!$payload || !is_array($payload)) return;
|
||||
|
||||
if ($mode == 1) {
|
||||
foreach ($payload as $feed) {
|
||||
|
||||
$title = $this->dbh->escape_string($feed[0]);
|
||||
$feed_url = $this->dbh->escape_string($feed[1]);
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE
|
||||
feed_url = '$feed_url' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
$result = $this->dbh->query("INSERT INTO ttrss_feeds
|
||||
(owner_uid,feed_url,title,cat_id,site_url)
|
||||
VALUES ('".$_SESSION["uid"]."',
|
||||
'$feed_url', '$title', NULL, '')");
|
||||
}
|
||||
}
|
||||
} else if ($mode == 2) {
|
||||
// feed archive
|
||||
foreach ($payload as $id) {
|
||||
$result = $this->dbh->query("SELECT * FROM ttrss_archived_feeds
|
||||
WHERE id = '$id' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$site_url = $this->dbh->escape_string(db_fetch_result($result, 0, "site_url"));
|
||||
$feed_url = $this->dbh->escape_string(db_fetch_result($result, 0, "feed_url"));
|
||||
$title = $this->dbh->escape_string(db_fetch_result($result, 0, "title"));
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE
|
||||
feed_url = '$feed_url' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) == 0) {
|
||||
$result = $this->dbh->query("INSERT INTO ttrss_feeds
|
||||
(owner_uid,feed_url,title,cat_id,site_url)
|
||||
VALUES ('$id','".$_SESSION["uid"]."',
|
||||
'$feed_url', '$title', NULL, '$site_url')");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function catchupFeed() {
|
||||
$feed_id = $this->dbh->escape_string($_REQUEST['feed_id']);
|
||||
$is_cat = $this->dbh->escape_string($_REQUEST['is_cat']) == "true";
|
||||
$mode = $this->dbh->escape_string($_REQUEST['mode']);
|
||||
|
||||
catchup_feed($feed_id, $is_cat, false, false, $mode);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function quickAddCat() {
|
||||
$cat = $this->dbh->escape_string($_REQUEST["cat"]);
|
||||
|
||||
add_feed_category($cat);
|
||||
|
||||
$result = $this->dbh->query("SELECT id FROM ttrss_feed_categories WHERE
|
||||
title = '$cat' AND owner_uid = " . $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) == 1) {
|
||||
$id = $this->dbh->fetch_result($result, 0, "id");
|
||||
} else {
|
||||
$id = 0;
|
||||
}
|
||||
|
||||
print_feed_cat_select("cat_id", $id, '');
|
||||
}
|
||||
|
||||
function setpanelmode() {
|
||||
$wide = (int) $_REQUEST["wide"];
|
||||
|
||||
setcookie("ttrss_widescreen", $wide,
|
||||
time() + COOKIE_LIFETIME_LONG);
|
||||
|
||||
print json_encode(array("wide" => $wide));
|
||||
}
|
||||
|
||||
static function updaterandomfeed_real($dbh) {
|
||||
|
||||
// Test if the feed need a update (update interval exceded).
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$update_limit_qpart = "AND ((
|
||||
ttrss_feeds.update_interval = 0
|
||||
AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_user_prefs.value || ' minutes') AS INTERVAL)
|
||||
) OR (
|
||||
ttrss_feeds.update_interval > 0
|
||||
AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL)
|
||||
) OR ttrss_feeds.last_updated IS NULL
|
||||
OR last_updated = '1970-01-01 00:00:00')";
|
||||
} else {
|
||||
$update_limit_qpart = "AND ((
|
||||
ttrss_feeds.update_interval = 0
|
||||
AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(ttrss_user_prefs.value, SIGNED INTEGER) MINUTE)
|
||||
) OR (
|
||||
ttrss_feeds.update_interval > 0
|
||||
AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE)
|
||||
) OR ttrss_feeds.last_updated IS NULL
|
||||
OR last_updated = '1970-01-01 00:00:00')";
|
||||
}
|
||||
|
||||
// Test if feed is currently being updated by another process.
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < NOW() - INTERVAL '5 minutes')";
|
||||
} else {
|
||||
$updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < DATE_SUB(NOW(), INTERVAL 5 MINUTE))";
|
||||
}
|
||||
|
||||
$random_qpart = sql_random_function();
|
||||
|
||||
// we could be invoked from public.php with no active session
|
||||
if ($_SESSION["uid"]) {
|
||||
$owner_check_qpart = "AND ttrss_feeds.owner_uid = '".$_SESSION["uid"]."'";
|
||||
} else {
|
||||
$owner_check_qpart = "";
|
||||
}
|
||||
|
||||
// We search for feed needing update.
|
||||
$result = $dbh->query("SELECT ttrss_feeds.feed_url,ttrss_feeds.id
|
||||
FROM
|
||||
ttrss_feeds, ttrss_users, ttrss_user_prefs
|
||||
WHERE
|
||||
ttrss_feeds.owner_uid = ttrss_users.id
|
||||
AND ttrss_users.id = ttrss_user_prefs.owner_uid
|
||||
AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL'
|
||||
$owner_check_qpart
|
||||
$update_limit_qpart
|
||||
$updstart_thresh_qpart
|
||||
ORDER BY $random_qpart LIMIT 30");
|
||||
|
||||
$feed_id = -1;
|
||||
|
||||
require_once "rssfuncs.php";
|
||||
|
||||
$num_updated = 0;
|
||||
|
||||
$tstart = time();
|
||||
|
||||
while ($line = $dbh->fetch_assoc($result)) {
|
||||
$feed_id = $line["id"];
|
||||
|
||||
if (time() - $tstart < ini_get("max_execution_time") * 0.7) {
|
||||
update_rss_feed($feed_id, true);
|
||||
++$num_updated;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Purge orphans and cleanup tags
|
||||
purge_orphans();
|
||||
cleanup_tags(14, 50000);
|
||||
|
||||
if ($num_updated > 0) {
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS",
|
||||
"num_updated" => $num_updated));
|
||||
} else {
|
||||
print json_encode(array("message" => "NOTHING_TO_UPDATE"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function updaterandomfeed() {
|
||||
RPC::updaterandomfeed_real($this->dbh);
|
||||
}
|
||||
|
||||
private function markArticlesById($ids, $cmode) {
|
||||
|
||||
$tmp_ids = array();
|
||||
|
||||
foreach ($ids as $id) {
|
||||
array_push($tmp_ids, "ref_id = '$id'");
|
||||
}
|
||||
|
||||
$ids_qpart = join(" OR ", $tmp_ids);
|
||||
|
||||
if ($cmode == 0) {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
marked = false, last_marked = NOW()
|
||||
WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]);
|
||||
} else if ($cmode == 1) {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
marked = true, last_marked = NOW()
|
||||
WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]);
|
||||
} else {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
marked = NOT marked,last_marked = NOW()
|
||||
WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
}
|
||||
|
||||
private function publishArticlesById($ids, $cmode) {
|
||||
|
||||
$tmp_ids = array();
|
||||
|
||||
foreach ($ids as $id) {
|
||||
array_push($tmp_ids, "ref_id = '$id'");
|
||||
}
|
||||
|
||||
$ids_qpart = join(" OR ", $tmp_ids);
|
||||
|
||||
if ($cmode == 0) {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
published = false,last_published = NOW()
|
||||
WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]);
|
||||
} else if ($cmode == 1) {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
published = true,last_published = NOW()
|
||||
WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]);
|
||||
} else {
|
||||
$this->dbh->query("UPDATE ttrss_user_entries SET
|
||||
published = NOT published,last_published = NOW()
|
||||
WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
if (PUBSUBHUBBUB_HUB) {
|
||||
$rss_link = get_self_url_prefix() .
|
||||
"/public.php?op=rss&id=-2&key=" .
|
||||
get_feed_access_key(-2, false);
|
||||
|
||||
$p = new Publisher(PUBSUBHUBBUB_HUB);
|
||||
|
||||
$pubsub_result = $p->publish_update($rss_link);
|
||||
}
|
||||
}
|
||||
|
||||
function getlinktitlebyid() {
|
||||
$id = $this->dbh->escape_string($_REQUEST['id']);
|
||||
|
||||
$result = $this->dbh->query("SELECT link, title FROM ttrss_entries, ttrss_user_entries
|
||||
WHERE ref_id = '$id' AND ref_id = id AND owner_uid = ". $_SESSION["uid"]);
|
||||
|
||||
if ($this->dbh->num_rows($result) != 0) {
|
||||
$link = $this->dbh->fetch_result($result, 0, "link");
|
||||
$title = $this->dbh->fetch_result($result, 0, "title");
|
||||
|
||||
echo json_encode(array("link" => $link, "title" => $title));
|
||||
} else {
|
||||
echo json_encode(array("error" => "ARTICLE_NOT_FOUND"));
|
||||
}
|
||||
}
|
||||
|
||||
function log() {
|
||||
$logmsg = $this->dbh->escape_string($_REQUEST['logmsg']);
|
||||
|
||||
if ($logmsg) {
|
||||
Logger::get()->log_error(E_USER_WARNING,
|
||||
$logmsg, '[client-js]', 0, false);
|
||||
}
|
||||
|
||||
echo json_encode(array("message" => "HOST_ERROR_LOGGED"));
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
64
source/classes/ttrssmailer.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
/* @class ttrssMailer
|
||||
* @brief A TTRSS extension to the PHPMailer class
|
||||
* Configures default values through the __construct() function
|
||||
* @author Derek Murawsky
|
||||
* @version .1 (alpha)
|
||||
*
|
||||
*/
|
||||
require_once 'lib/phpmailer/class.phpmailer.php';
|
||||
require_once "config.php";
|
||||
|
||||
class ttrssMailer extends PHPMailer {
|
||||
|
||||
//define all items that we want to override with defaults in PHPMailer
|
||||
public $From = SMTP_FROM_ADDRESS;
|
||||
public $FromName = SMTP_FROM_NAME;
|
||||
public $CharSet = "UTF-8";
|
||||
public $PluginDir = "lib/phpmailer/";
|
||||
public $ContentType = "text/html"; //default email type is HTML
|
||||
|
||||
function __construct() {
|
||||
$this->SetLanguage("en", "lib/phpmailer/language/");
|
||||
|
||||
if (SMTP_SERVER) {
|
||||
$pair = explode(":", SMTP_SERVER, 2);
|
||||
$this->Mailer = "smtp";
|
||||
|
||||
$this->Host = $pair[0];
|
||||
$this->Port = $pair[1];
|
||||
|
||||
if (!$this->Port) $this->Port = 25;
|
||||
} else {
|
||||
$this->Host = '';
|
||||
$this->Port = '';
|
||||
}
|
||||
|
||||
|
||||
//if SMTP_LOGIN is specified, set credentials and enable auth
|
||||
if(SMTP_LOGIN){
|
||||
$this->SMTPAuth = true;
|
||||
$this->Username = SMTP_LOGIN;
|
||||
$this->Password = SMTP_PASSWORD;
|
||||
}
|
||||
if(SMTP_SECURE)
|
||||
$this->SMTPSecure = SMTP_SECURE;
|
||||
}
|
||||
/* @brief a simple mail function to send email using the defaults
|
||||
* This will send an HTML email using the configured defaults
|
||||
* @param $toAddress A string with the recipients email address
|
||||
* @param $toName A string with the recipients name
|
||||
* @param $subject A string with the emails subject
|
||||
* @param $body A string containing the body of the email
|
||||
*/
|
||||
public function quickMail ($toAddress, $toName, $subject, $body, $altbody=""){
|
||||
$this->addAddress($toAddress, $toName);
|
||||
$this->Subject = $subject;
|
||||
$this->Body = $body;
|
||||
$this->IsHTML($altbody != '');
|
||||
$rc=$this->send();
|
||||
return $rc;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
214
source/config.php-dist
Normal file
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
// *******************************************
|
||||
// *** Database configuration (important!) ***
|
||||
// *******************************************
|
||||
|
||||
define('DB_TYPE', "pgsql"); // or mysql
|
||||
define('DB_HOST', "localhost");
|
||||
define('DB_USER', "fox");
|
||||
define('DB_NAME', "fox");
|
||||
define('DB_PASS', "XXXXXX");
|
||||
define('DB_PORT', ''); // usually 5432 for PostgreSQL, 3306 for MySQL
|
||||
|
||||
define('MYSQL_CHARSET', 'UTF8');
|
||||
// Connection charset for MySQL. If you have a legacy database and/or experience
|
||||
// garbage unicode characters with this option, try setting it to a blank string.
|
||||
|
||||
// ***********************************
|
||||
// *** Basic settings (important!) ***
|
||||
// ***********************************
|
||||
|
||||
define('SELF_URL_PATH', 'http://example.org/tt-rss/');
|
||||
// Full URL of your tt-rss installation. This should be set to the
|
||||
// location of tt-rss directory, e.g. http://example.org/tt-rss/
|
||||
// You need to set this option correctly otherwise several features
|
||||
// including PUSH, bookmarklets and browser integration will not work properly.
|
||||
|
||||
define('FEED_CRYPT_KEY', '');
|
||||
// Key used for encryption of passwords for password-protected feeds
|
||||
// in the database. A string of 24 random characters. If left blank, encryption
|
||||
// is not used. Requires mcrypt functions.
|
||||
// Warning: changing this key will make your stored feed passwords impossible
|
||||
// to decrypt.
|
||||
|
||||
define('SINGLE_USER_MODE', false);
|
||||
// Operate in single user mode, disables all functionality related to
|
||||
// multiple users and authentication. Enabling this assumes you have
|
||||
// your tt-rss directory protected by other means (e.g. http auth).
|
||||
|
||||
define('SIMPLE_UPDATE_MODE', false);
|
||||
// Enables fallback update mode where tt-rss tries to update feeds in
|
||||
// background while tt-rss is open in your browser.
|
||||
// If you don't have a lot of feeds and don't want to or can't run
|
||||
// background processes while not running tt-rss, this method is generally
|
||||
// viable to keep your feeds up to date.
|
||||
// Still, there are more robust (and recommended) updating methods
|
||||
// available, you can read about them here: http://tt-rss.org/wiki/UpdatingFeeds
|
||||
|
||||
// *****************************
|
||||
// *** Files and directories ***
|
||||
// *****************************
|
||||
|
||||
define('PHP_EXECUTABLE', '/usr/bin/php');
|
||||
// Path to PHP *COMMAND LINE* executable, used for various command-line tt-rss
|
||||
// programs and update daemon. Do not try to use CGI binary here, it won't work.
|
||||
// If you see HTTP headers being displayed while running tt-rss scripts,
|
||||
// then most probably you are using the CGI binary. If you are unsure what to
|
||||
// put in here, ask your hosting provider.
|
||||
|
||||
define('LOCK_DIRECTORY', 'lock');
|
||||
// Directory for lockfiles, must be writable to the user you run
|
||||
// daemon process or cronjobs under.
|
||||
|
||||
define('CACHE_DIR', 'cache');
|
||||
// Local cache directory for RSS feed content.
|
||||
|
||||
define('ICONS_DIR', "feed-icons");
|
||||
define('ICONS_URL', "feed-icons");
|
||||
// Local and URL path to the directory, where feed favicons are stored.
|
||||
// Unless you really know what you're doing, please keep those relative
|
||||
// to tt-rss main directory.
|
||||
|
||||
// **********************
|
||||
// *** Authentication ***
|
||||
// **********************
|
||||
|
||||
// Please see PLUGINS below to configure various authentication modules.
|
||||
|
||||
define('AUTH_AUTO_CREATE', true);
|
||||
// Allow authentication modules to auto-create users in tt-rss internal
|
||||
// database when authenticated successfully.
|
||||
|
||||
define('AUTH_AUTO_LOGIN', true);
|
||||
// Automatically login user on remote or other kind of externally supplied
|
||||
// authentication, otherwise redirect to login form as normal.
|
||||
// If set to true, users won't be able to set application language
|
||||
// and settings profile.
|
||||
|
||||
// *********************
|
||||
// *** Feed settings ***
|
||||
// *********************
|
||||
|
||||
define('FORCE_ARTICLE_PURGE', 0);
|
||||
// When this option is not 0, users ability to control feed purging
|
||||
// intervals is disabled and all articles (which are not starred)
|
||||
// older than this amount of days are purged.
|
||||
|
||||
// *** PubSubHubbub settings ***
|
||||
|
||||
define('PUBSUBHUBBUB_HUB', '');
|
||||
// URL to a PubSubHubbub-compatible hub server. If defined, "Published
|
||||
// articles" generated feed would automatically become PUSH-enabled.
|
||||
|
||||
define('PUBSUBHUBBUB_ENABLED', false);
|
||||
// Enable client PubSubHubbub support in tt-rss. When disabled, tt-rss
|
||||
// won't try to subscribe to PUSH feed updates.
|
||||
|
||||
// *********************
|
||||
// *** Sphinx search ***
|
||||
// *********************
|
||||
|
||||
define('SPHINX_ENABLED', false);
|
||||
// Enable fulltext search using Sphinx (http://www.sphinxsearch.com)
|
||||
// Please see http://tt-rss.org/wiki/SphinxSearch for more information.
|
||||
|
||||
define('SPHINX_SERVER', 'localhost:9312');
|
||||
// Hostname:port combination for the Sphinx server.
|
||||
|
||||
define('SPHINX_INDEX', 'ttrss, delta');
|
||||
// Index name in Sphinx configuration. You can specify multiple indexes
|
||||
// as a comma-separated string.
|
||||
// Example configuration files are available on tt-rss wiki.
|
||||
|
||||
// ***********************************
|
||||
// *** Self-registrations by users ***
|
||||
// ***********************************
|
||||
|
||||
define('ENABLE_REGISTRATION', false);
|
||||
// Allow users to register themselves. Please be aware that allowing
|
||||
// random people to access your tt-rss installation is a security risk
|
||||
// and potentially might lead to data loss or server exploit. Disabled
|
||||
// by default.
|
||||
|
||||
define('REG_NOTIFY_ADDRESS', 'user@your.domain.dom');
|
||||
// Email address to send new user notifications to.
|
||||
|
||||
define('REG_MAX_USERS', 10);
|
||||
// Maximum amount of users which will be allowed to register on this
|
||||
// system. 0 - no limit.
|
||||
|
||||
// **********************************
|
||||
// *** Cookies and login sessions ***
|
||||
// **********************************
|
||||
|
||||
define('SESSION_COOKIE_LIFETIME', 86400);
|
||||
// Default lifetime of a session (e.g. login) cookie. In seconds,
|
||||
// 0 means cookie will be deleted when browser closes.
|
||||
|
||||
define('SESSION_CHECK_ADDRESS', 1);
|
||||
// Check client IP address when validating session:
|
||||
// 0 - disable checking
|
||||
// 1 - check first 3 octets of an address (recommended)
|
||||
// 2 - check first 2 octets of an address
|
||||
// 3 - check entire address
|
||||
|
||||
// *********************************
|
||||
// *** Email and digest settings ***
|
||||
// *********************************
|
||||
|
||||
define('SMTP_FROM_NAME', 'Tiny Tiny RSS');
|
||||
define('SMTP_FROM_ADDRESS', 'noreply@your.domain.dom');
|
||||
// Name, address and subject for sending outgoing mail. This applies
|
||||
// to password reset notifications, digest emails and any other mail.
|
||||
|
||||
define('DIGEST_SUBJECT', '[tt-rss] New headlines for last 24 hours');
|
||||
// Subject line for email digests
|
||||
|
||||
define('SMTP_SERVER', '');
|
||||
// Hostname:port combination to send outgoing mail (i.e. localhost:25).
|
||||
// Blank - use system MTA.
|
||||
|
||||
define('SMTP_LOGIN', '');
|
||||
define('SMTP_PASSWORD', '');
|
||||
// These two options enable SMTP authentication when sending
|
||||
// outgoing mail. Only used with SMTP_SERVER.
|
||||
|
||||
define('SMTP_SECURE', '');
|
||||
// Used to select a secure SMTP connection. Allowed values: ssl, tls,
|
||||
// or empty.
|
||||
|
||||
// ***************************************
|
||||
// *** Other settings (less important) ***
|
||||
// ***************************************
|
||||
|
||||
define('CHECK_FOR_NEW_VERSION', true);
|
||||
// Check for new versions of tt-rss automatically.
|
||||
|
||||
define('ENABLE_GZIP_OUTPUT', false);
|
||||
// Selectively gzip output to improve wire performance. This requires
|
||||
// PHP Zlib extension on the server.
|
||||
// Enabling this can break tt-rss in several httpd/php configurations,
|
||||
// if you experience weird errors and tt-rss failing to start, blank pages
|
||||
// after login, or content encoding errors, disable it.
|
||||
|
||||
define('PLUGINS', 'auth_internal, note, updater');
|
||||
// Comma-separated list of plugins to load automatically for all users.
|
||||
// System plugins have to be specified here. Please enable at least one
|
||||
// authentication plugin here (auth_*).
|
||||
// Users may enable other user plugins from Preferences/Plugins but may not
|
||||
// disable plugins specified in this list.
|
||||
// Disabling auth_internal in this list would automatically disable
|
||||
// reset password link on the login form.
|
||||
|
||||
define('LOG_DESTINATION', 'sql');
|
||||
// Log destination to use. Possible values: sql (uses internal logging
|
||||
// you can read in Preferences -> System), syslog - logs to system log.
|
||||
// Setting this to blank uses PHP logging (usually to http server
|
||||
// error.log).
|
||||
|
||||
define('CONFIG_VERSION', 26);
|
||||
// Expected config version. Please update this option in config.php
|
||||
// if necessary (after migrating all new options from this file).
|
||||
|
||||
// vim:ft=php
|
||||
?>
|
344
source/css/cdm.css
Normal file
|
@ -0,0 +1,344 @@
|
|||
div.cdmHeader img, div.cdmHeader input, div.cdmFooter img {
|
||||
vertical-align : middle;
|
||||
}
|
||||
|
||||
div.cdmHeader {
|
||||
display : table;
|
||||
}
|
||||
|
||||
div.cdmHeader > * {
|
||||
display : table-cell;
|
||||
padding : 5px;
|
||||
}
|
||||
|
||||
div.cdmHeader > div {
|
||||
white-space : nowrap;
|
||||
}
|
||||
|
||||
div.cdmHeader > span {
|
||||
width : 100%;
|
||||
}
|
||||
|
||||
div.cdmHeader span.updated {
|
||||
color : #555;
|
||||
font-weight : normal;
|
||||
font-size : 11px;
|
||||
white-space : nowrap;
|
||||
vertical-align : middle;
|
||||
}
|
||||
|
||||
div.cdmHeader input {
|
||||
margin-right : 5px;
|
||||
}
|
||||
|
||||
div.cdmHeader div.updPic {
|
||||
width : 25px;
|
||||
display : inline-block;
|
||||
text-align : center;
|
||||
}
|
||||
|
||||
div.cdmHeader div.updPic img {
|
||||
vertical-align : middle;
|
||||
}
|
||||
|
||||
div.cdmHeader img, div.cdmFooter img {
|
||||
margin : 0px 4px;
|
||||
}
|
||||
|
||||
div.cdmHeader input {
|
||||
margin-left : 4px;
|
||||
margin-right : 4px;
|
||||
}
|
||||
|
||||
div.cdmContentInner {
|
||||
margin : 10px;
|
||||
line-height : 20px;
|
||||
}
|
||||
|
||||
div.cdmContentInner img {
|
||||
border-width : 0px;
|
||||
max-width : 98%;
|
||||
height : auto;
|
||||
}
|
||||
|
||||
div.cdmFooter {
|
||||
padding : 5px;
|
||||
font-weight : normal;
|
||||
color : #555;
|
||||
clear : both;
|
||||
}
|
||||
|
||||
div.cdm.expanded {
|
||||
margin-top : 4px;
|
||||
margin-bottom : 4px;
|
||||
}
|
||||
|
||||
div.cdm.expandable {
|
||||
background-color : #f0f0f0;
|
||||
border-width : 0px 0px 1px 0px;
|
||||
border-color : #c0c0c0;
|
||||
border-style : solid;
|
||||
}
|
||||
|
||||
div.cdm.expandable > hr {
|
||||
display : none;
|
||||
}
|
||||
|
||||
div.cdm.expanded > hr {
|
||||
margin-top : 0px;
|
||||
margin-bottom : 0px;
|
||||
}
|
||||
|
||||
div.cdm.expandable.Unread {
|
||||
background : white;
|
||||
}
|
||||
|
||||
div.cdm.expandable.Selected {
|
||||
background : #f9fbff;
|
||||
}
|
||||
|
||||
div.cdm.expandable.active {
|
||||
box-shadow : inset 0px 0px 3px 0px rgba(0,0,0,0.1);
|
||||
border-color : #88b0f0;
|
||||
background : white ! important;
|
||||
}
|
||||
|
||||
div.cdm.expandable div.cdmHeader span.titleWrap {
|
||||
white-space : nowrap;
|
||||
text-overflow : ellipsis;
|
||||
overflow : hidden;
|
||||
max-width : 500px;
|
||||
}
|
||||
|
||||
div.cdm.expandable.active div.cdmHeader span.titleWrap {
|
||||
white-space : normal;
|
||||
}
|
||||
|
||||
div.cdm.expandable div.cdmHeader a.title {
|
||||
font-weight : bold;
|
||||
color : #555;
|
||||
-webkit-transition : color 0.2s;
|
||||
transition : color 0.2s;
|
||||
}
|
||||
|
||||
div.cdm.expandable.Unread div.cdmHeader a.title {
|
||||
color : black;
|
||||
}
|
||||
|
||||
div.cdm.expandable.active div.cdmHeader a.title {
|
||||
color : #4684ff;
|
||||
}
|
||||
|
||||
div.cdm.expanded div.cdmHeader {
|
||||
background : transparent ! important;
|
||||
}
|
||||
|
||||
div.cdm.expanded div.cdmHeader a.title {
|
||||
font-size : 14px;
|
||||
color : #999;
|
||||
font-weight : bold;
|
||||
-webkit-transition : color 0.2s;
|
||||
transition : color 0.2s;
|
||||
}
|
||||
|
||||
div.cdm.expanded.active {
|
||||
background : white;
|
||||
}
|
||||
|
||||
div.cdm.expanded.active div.cdmHeader a.title {
|
||||
color : #4684ff;
|
||||
}
|
||||
|
||||
div.cdm.expanded.Unread div.cdmHeader a.title {
|
||||
color : black;
|
||||
}
|
||||
|
||||
div.cdm.expanded div.cdmContent {
|
||||
color : #555;
|
||||
}
|
||||
|
||||
div.cdm.expanded.Unread div.cdmContent {
|
||||
color : black;
|
||||
}
|
||||
|
||||
div.cdm.active div.cdmContent {
|
||||
color : black;
|
||||
}
|
||||
|
||||
span.cdmExcerpt {
|
||||
font-size : 11px;
|
||||
color : #555;
|
||||
font-weight : normal;
|
||||
cursor : pointer;
|
||||
}
|
||||
|
||||
div.cdmContent div.postEnclosures {
|
||||
margin-top : 1em;
|
||||
color : #555;
|
||||
}
|
||||
|
||||
div.cdmFeedTitle {
|
||||
border-color : #a0a0a0;
|
||||
border-width : 0px 0px 1px 0px;
|
||||
border-style : solid;
|
||||
padding : 5px 3px 5px 5px;
|
||||
background : url("../images/toolbar.png") bottom left;
|
||||
background-repeat : repeat-x;
|
||||
}
|
||||
|
||||
div.cdmFeedTitle a.title {
|
||||
color : #555;
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
}
|
||||
|
||||
div.cdmFeedTitle a {
|
||||
color : #555;
|
||||
}
|
||||
|
||||
div.cdmFeedTitle a:hover {
|
||||
color : #4684ff;
|
||||
}
|
||||
|
||||
div.cdmHeader span.hlFeed {
|
||||
float : right;
|
||||
font-weight : normal;
|
||||
font-style : italic;
|
||||
}
|
||||
|
||||
div.cdmHeader div.hlFeed, div.cdmHeader div.hlFeed a {
|
||||
vertical-align : middle;
|
||||
color : #555;
|
||||
font-weight : normal;
|
||||
font-style : italic;
|
||||
font-size : 11px;
|
||||
}
|
||||
|
||||
div.cdm .hlFeed a {
|
||||
border-radius : 4px;
|
||||
display : inline-block;
|
||||
padding : 1px 4px 1px 4px;
|
||||
}
|
||||
|
||||
div.cdmContentInner p {
|
||||
max-width : 650px;
|
||||
text-align : justify;
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
div.cdmContentInner iframe {
|
||||
min-width : 50%;
|
||||
}
|
||||
|
||||
div.cdmHeader span.author {
|
||||
color : #555;
|
||||
font-size : 11px;
|
||||
font-weight : normal;
|
||||
}
|
||||
|
||||
|
||||
div#floatingTitle {
|
||||
position : absolute;
|
||||
z-index : 5;
|
||||
top : 25px;
|
||||
right : 0px;
|
||||
left : 0px;
|
||||
border-color : #ccc;
|
||||
border-width : 1px 0px 1px 0px;
|
||||
border-style : solid;
|
||||
background : #fcfcfc;
|
||||
color : #555;
|
||||
box-shadow : 0px 1px 1px 0px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
div#floatingTitle > * {
|
||||
display : table-cell;
|
||||
white-space : nowrap;
|
||||
vertical-align : middle;
|
||||
padding : 9px 5px;
|
||||
}
|
||||
|
||||
div#floatingTitle img {
|
||||
margin-right : 4px;
|
||||
margin-left : 4px;
|
||||
}
|
||||
|
||||
div#floatingTitle span.author {
|
||||
color : #555;
|
||||
font-size : 11px;
|
||||
font-weight : normal;
|
||||
}
|
||||
|
||||
div#floatingTitle a.title {
|
||||
font-size : 14px;
|
||||
color : #999;
|
||||
font-weight : bold;
|
||||
-webkit-transition : color 0.2s;
|
||||
transition : color 0.2s;
|
||||
}
|
||||
|
||||
div#floatingTitle.Unread a.title {
|
||||
color : black;
|
||||
}
|
||||
|
||||
div#floatingTitle img.anchor {
|
||||
margin-right : 1px;
|
||||
margin-left : 0px;
|
||||
}
|
||||
|
||||
div#floatingTitle div.hlFeed {
|
||||
padding-right : 10px;
|
||||
color : #555;
|
||||
font-weight : normal;
|
||||
font-style : italic;
|
||||
font-size : 11px;
|
||||
white-space : nowrap;
|
||||
}
|
||||
|
||||
div#floatingTitle div.hlFeed a {
|
||||
border-radius : 4px;
|
||||
display : inline-block;
|
||||
padding : 1px 4px 1px 4px;
|
||||
}
|
||||
|
||||
div#floatingTitle span.updated {
|
||||
padding-right : 10px;
|
||||
white-space : nowrap;
|
||||
color : #555;
|
||||
font-size : 11px;
|
||||
}
|
||||
|
||||
div#floatingTitle div.hlFeed a {
|
||||
color : #555;
|
||||
}
|
||||
|
||||
div#floatingTitle span.titleWrap {
|
||||
width : 100%;
|
||||
white-space : normal;
|
||||
}
|
||||
|
||||
div#floatingTitle .dijit,
|
||||
div#floatingTitle img.hlScorePic {
|
||||
display : none;
|
||||
}
|
||||
|
||||
.cdm.high .cdmHeader a.title.high, .cdm.high .cdmHeader .cdmExcerpt,
|
||||
.cdm.high .cdmHeader span.author {
|
||||
color : #00aa00;
|
||||
}
|
||||
|
||||
.cdm.Unread.high .cdmHeader a.title.high, .cdm.Unread.high .cdmHeader .cdmExcerpt,
|
||||
.cdm.Unread.high .cdmHeader span.author {
|
||||
color : #00dd00;
|
||||
}
|
||||
|
||||
.cdm .cdmHeader a.title.low, .cdm.low .cdmHeader .cdmExcerpt,
|
||||
.cdm.Unread .cdmHeader a.title.low, .cdm.Unread.low .cdmHeader .cdmExcerpt,
|
||||
.cdm.low .cdmHeader span.author {
|
||||
color : #909090;
|
||||
text-decoration : line-through;
|
||||
}
|
||||
|
||||
|
7
source/css/layout.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
html, body#ttrssMain, body#ttrssPrefs, #main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
126
source/css/prefs.css
Normal file
|
@ -0,0 +1,126 @@
|
|||
#header a:hover {
|
||||
color : black;
|
||||
}
|
||||
|
||||
#header img {
|
||||
vertical-align : middle;
|
||||
cursor : pointer;
|
||||
}
|
||||
|
||||
|
||||
div#pref-tabs .dijitContentPane {
|
||||
font-size : 13px;
|
||||
}
|
||||
|
||||
div#pref-tabs {
|
||||
margin : 0px 5px 0px 5px;
|
||||
}
|
||||
|
||||
div#pref-tabs .dijitContentPane h3 {
|
||||
font-size : 14px;
|
||||
font-weight : bold;
|
||||
}
|
||||
|
||||
#pref-filter-wrap, #pref-filter-header, #pref-filter-content,
|
||||
#pref-label-wrap, #pref-label-header, #pref-label-content,
|
||||
#pref-user-wrap, #pref-user-header, #pref-user-content,
|
||||
#pref-instance-wrap, #pref-instance-header, #pref-instance-content {
|
||||
margin : 0px;
|
||||
padding : 0px;
|
||||
border-width : 0px;
|
||||
}
|
||||
|
||||
#userConfigTab, #labelConfigTab, #filterConfigTab, #pref-feeds-feeds, #instanceConfigTab {
|
||||
padding : 0px;
|
||||
}
|
||||
|
||||
/* preferences */
|
||||
|
||||
table.prefPrefsList h3 {
|
||||
margin-top : 0.5em;
|
||||
margin-bottom : 0px;
|
||||
}
|
||||
|
||||
tr.title td {
|
||||
border-width : 0px 0px 1px 0px;
|
||||
border-color : #ecf4ff;
|
||||
border-style : solid;
|
||||
color : #4684ff;
|
||||
}
|
||||
|
||||
div.prefProfileHolder, div.prefFeedOPMLHolder, div.inactiveFeedHolder {
|
||||
height : 300px;
|
||||
overflow : auto;
|
||||
border-width : 0px 1px 1px 1px;
|
||||
border-color : #c0c0c0;
|
||||
border-style : solid;
|
||||
margin : 0px 0px 5px 0px;
|
||||
background-color : #ecf4ff;
|
||||
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
|
||||
}
|
||||
div.filterTestHolder, div.prefFeedOPMLHolder {
|
||||
border-width : 1px;
|
||||
}
|
||||
|
||||
ul.selfUpdateList, ul.userFeedList {
|
||||
height : 200px;
|
||||
overflow : auto;
|
||||
list-style-type : none;
|
||||
border : 1px solid #c0c0c0;
|
||||
background-color : #ecf4ff;
|
||||
margin : 0px 0px 5px 0px;
|
||||
padding : 5px;
|
||||
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
|
||||
border-radius : 4px;
|
||||
}
|
||||
|
||||
div#feedlistLoading, div#filterlistLoading, div#labellistLoading {
|
||||
text-align : center;
|
||||
padding : 5px;
|
||||
color : #555;
|
||||
}
|
||||
|
||||
div#feedlistLoading img, div#filterlistLoading img, div#labellistLoading {
|
||||
margin-right : 5px;
|
||||
}
|
||||
|
||||
#errorButton {
|
||||
color : red;
|
||||
}
|
||||
|
||||
a.bookmarklet {
|
||||
color : #4684ff;
|
||||
border : 1px solid #ecf4ff;
|
||||
padding : 2px;
|
||||
}
|
||||
|
||||
table.prefPluginsList td label, table.prefUserList td {
|
||||
cursor : pointer;
|
||||
}
|
||||
|
||||
table.prefPluginsList label {
|
||||
white-space : nowrap;
|
||||
}
|
||||
|
||||
table.prefPluginsList label img {
|
||||
vertical-align : middle;
|
||||
}
|
||||
|
||||
table.prefErrorLog tr.errrow td {
|
||||
font-size : 10px;
|
||||
}
|
||||
|
||||
table.prefErrorLog tr.errrow td.errno {
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
white-space : nowrap;
|
||||
}
|
||||
|
||||
table.prefErrorLog td.filename, table.prefErrorLog td.login, table.prefErrorLog td.timestamp {
|
||||
color : #555;
|
||||
}
|
||||
|
||||
.dijitAccordionContainer-child {
|
||||
box-shadow : inset 0px 0px 3px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
1200
source/css/tt-rss.css
Normal file
264
source/css/utility.css
Normal file
|
@ -0,0 +1,264 @@
|
|||
body {
|
||||
background : #f9fbff;
|
||||
color : black;
|
||||
padding : 0px;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
margin-left : auto;
|
||||
margin-right : auto;
|
||||
max-width : 800px;
|
||||
}
|
||||
|
||||
form {
|
||||
margin : 10px 0px 0px 0px;
|
||||
padding : 0px;
|
||||
}
|
||||
|
||||
div.content {
|
||||
background : white;
|
||||
border : 1px solid #ccc;
|
||||
padding : 10px;
|
||||
border-radius : 4px;
|
||||
box-shadow : inset 0 0 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
p.warning {
|
||||
color : red;
|
||||
}
|
||||
|
||||
p.query, code {
|
||||
color : green;
|
||||
}
|
||||
|
||||
p.insensitive {
|
||||
color : gray;
|
||||
}
|
||||
|
||||
div.insensitive-small {
|
||||
color : gray;
|
||||
font-size : 10px;
|
||||
}
|
||||
|
||||
.floatingLogo {
|
||||
float : right;
|
||||
position : relative;
|
||||
top : -10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color : #4684ff;
|
||||
text-decoration : none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color : black;
|
||||
}
|
||||
|
||||
div.notice, div.warning, div.error {
|
||||
padding : 4px 10px 4px 4px;
|
||||
display : inline-block;
|
||||
margin : 2px 0px 4px 0px;
|
||||
font-size : 12px;
|
||||
border-style : solid;
|
||||
border-color : #ccc;
|
||||
border-radius : 4px;
|
||||
border-width : 1px;
|
||||
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
div.notice div.inner, div.warning div.inner, div.error div.inner {
|
||||
vertical-align : middle;
|
||||
}
|
||||
|
||||
div.notice {
|
||||
background : #ecf4ff;
|
||||
border-color : #88b0f0;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
border-color : #EFDC88;
|
||||
background : #fff7d5;
|
||||
}
|
||||
|
||||
div.error {
|
||||
background : #ffcccc;
|
||||
border-color : #ff0000;
|
||||
}
|
||||
|
||||
div.warning img, div.notice img, div.error img {
|
||||
margin : 4px;
|
||||
vertical-align : middle;
|
||||
}
|
||||
|
||||
div.warning span, div.notice span, div.error span {
|
||||
display : table-cell;
|
||||
vertical-align : middle;
|
||||
|
||||
}
|
||||
|
||||
h1 {
|
||||
color : #88b0f0;
|
||||
font-size : 32px;
|
||||
margin : 20px 0px 5px 0px;
|
||||
text-shadow : 0 0 6px #fff;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color : #88b0f0;
|
||||
font-size : 14pt;
|
||||
border-width : 0px 0px 1px 0px;
|
||||
border-color : #f0f0f0;
|
||||
border-style : solid;
|
||||
}
|
||||
|
||||
div.content > h2 {
|
||||
margin-top : 0px;
|
||||
}
|
||||
|
||||
div.rss h1 {
|
||||
border-width : 0px 0px 1px 0px;
|
||||
border-color : gray;
|
||||
border-style : dotted;
|
||||
color : gray;
|
||||
}
|
||||
|
||||
div.rss h2 {
|
||||
font-size : 12pt;
|
||||
}
|
||||
|
||||
div.rss a.extlink {
|
||||
color : gray;
|
||||
border-width : 0px 0px 1px 0px;
|
||||
border-color : #778899;
|
||||
border-style : dotted;
|
||||
font-size : 9pt;
|
||||
}
|
||||
|
||||
div.rss img {
|
||||
max-width : 775px;
|
||||
}
|
||||
|
||||
div.rss p.description {
|
||||
color : gray;
|
||||
font-size : 9pt;
|
||||
}
|
||||
|
||||
div.rss div.content {
|
||||
margin-top : 0.5em;
|
||||
}
|
||||
|
||||
div.rss img.feedicon {
|
||||
float : right;
|
||||
}
|
||||
|
||||
div.rss hr {
|
||||
border-width : 0px 0px 1px 0px;
|
||||
border-style : dashed;
|
||||
border-color : #e0e0e0;
|
||||
}
|
||||
|
||||
body#sharepopup {
|
||||
background-color : white;
|
||||
background-image : url("../images/toolbar.png");
|
||||
background-repeat : repeat-x;
|
||||
background-position : bottom;
|
||||
margin : 10px;
|
||||
padding : 0px;
|
||||
}
|
||||
|
||||
body#sharepopup h1 {
|
||||
font-size : 14px;
|
||||
margin : 0px;
|
||||
color : #88b0f0;
|
||||
}
|
||||
|
||||
body#sharepopup table {
|
||||
background : white;
|
||||
border : 1px solid #88b0f0;
|
||||
padding : 5px;
|
||||
}
|
||||
|
||||
body#sharepopup form {
|
||||
height : 100%;
|
||||
}
|
||||
|
||||
body#sharepopup input {
|
||||
width : 100%;
|
||||
}
|
||||
|
||||
div.autocomplete {
|
||||
position : absolute;
|
||||
width : 250px;
|
||||
background-color : white;
|
||||
border :1px solid #778899;
|
||||
margin : 0px;
|
||||
padding : 0px;
|
||||
z-index : 4;
|
||||
}
|
||||
|
||||
div.autocomplete ul {
|
||||
list-style-type : none;
|
||||
margin : 0px;
|
||||
padding : 0px;
|
||||
font-size : 10px;
|
||||
}
|
||||
|
||||
div.autocomplete ul li.selected {
|
||||
background-color : #fff7d5;
|
||||
}
|
||||
|
||||
div.autocomplete ul li {
|
||||
list-style-type : none;
|
||||
display : block;
|
||||
margin : 0;
|
||||
padding : 2px;
|
||||
height : 32px;
|
||||
cursor : pointer;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border-width : 0px;
|
||||
padding : 0px 0px 5px 0px;
|
||||
margin : 0px;
|
||||
}
|
||||
|
||||
fieldset input {
|
||||
font-family : sans-serif;
|
||||
font-size : medium;
|
||||
border-spacing : 2px;
|
||||
border : 1px solid #b5bcc7;
|
||||
padding : 2px;
|
||||
}
|
||||
|
||||
fieldset label {
|
||||
width : 120px;
|
||||
margin-right : 20px;
|
||||
display : inline-block;
|
||||
text-align : right;
|
||||
color : gray;
|
||||
}
|
||||
|
||||
body.otp {
|
||||
margin : 1em;
|
||||
padding : 0px;
|
||||
}
|
||||
|
||||
form.otpform {
|
||||
margin : 0px;
|
||||
padding : 0px;
|
||||
}
|
||||
|
||||
form.otpform label {
|
||||
margin : 0px;
|
||||
padding : 0px;
|
||||
}
|
||||
|
||||
body.otp div.content {
|
||||
display : inline-block;
|
||||
width : auto;
|
||||
}
|
||||
|
||||
span.hint {
|
||||
font-size : 10px;
|
||||
color : gray;
|
||||
}
|
50
source/errors.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
set_include_path(dirname(__FILE__) ."/include" . PATH_SEPARATOR .
|
||||
get_include_path());
|
||||
|
||||
require_once "functions.php";
|
||||
|
||||
$ERRORS[0] = "";
|
||||
|
||||
$ERRORS[1] = __("This program requires XmlHttpRequest " .
|
||||
"to function properly. Your browser doesn't seem to support it.");
|
||||
|
||||
$ERRORS[2] = __("This program requires cookies " .
|
||||
"to function properly. Your browser doesn't seem to support them.");
|
||||
|
||||
$ERRORS[3] = __("Backend sanity check failed.");
|
||||
|
||||
$ERRORS[4] = __("Frontend sanity check failed.");
|
||||
|
||||
$ERRORS[5] = __("Incorrect database schema version. <a href='db-updater.php'>Please update</a>.");
|
||||
|
||||
$ERRORS[6] = __("Request not authorized.");
|
||||
|
||||
$ERRORS[7] = __("No operation to perform.");
|
||||
|
||||
$ERRORS[8] = __("Could not display feed: query failed. Please check label match syntax or local configuration.");
|
||||
|
||||
$ERRORS[8] = __("Denied. Your access level is insufficient to access this page.");
|
||||
|
||||
$ERRORS[9] = __("Configuration check failed");
|
||||
|
||||
$ERRORS[10] = __("Your version of MySQL is not currently supported. Please see official site for more information.");
|
||||
|
||||
$ERRORS[11] = "[This error is not returned by server]";
|
||||
|
||||
$ERRORS[12] = __("SQL escaping test failed, check your database and PHP configuration");
|
||||
|
||||
if ($_REQUEST['mode'] == 'js') {
|
||||
header("Content-Type: text/javascript; charset=UTF-8");
|
||||
|
||||
print "var ERRORS = [];\n";
|
||||
|
||||
foreach ($ERRORS as $id => $error) {
|
||||
|
||||
$error = preg_replace("/\n/", "", $error);
|
||||
$error = preg_replace("/\"/", "\\\"", $error);
|
||||
|
||||
print "ERRORS[$id] = \"$error\";\n";
|
||||
}
|
||||
}
|
||||
?>
|
54
source/image.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
set_include_path(dirname(__FILE__) ."/include" . PATH_SEPARATOR .
|
||||
get_include_path());
|
||||
|
||||
require_once "config.php";
|
||||
|
||||
// backwards compatible wrapper for old-style image caching
|
||||
/* if (isset($_GET['url'])) {
|
||||
$url = base64_decode($_GET['url']);
|
||||
|
||||
$filename = CACHE_DIR . '/images/' . sha1($url) . '.png';
|
||||
|
||||
if (file_exists($filename)) {
|
||||
header("Content-type: image/png");
|
||||
echo file_get_contents($filename);
|
||||
} else {
|
||||
header("Location: $url");
|
||||
}
|
||||
|
||||
return;
|
||||
} */
|
||||
|
||||
@$hash = basename($_GET['hash']);
|
||||
|
||||
if ($hash) {
|
||||
|
||||
$filename = CACHE_DIR . '/images/' . $hash . '.png';
|
||||
|
||||
if (file_exists($filename)) {
|
||||
/* See if we can use X-Sendfile */
|
||||
$xsendfile = false;
|
||||
if (function_exists('apache_get_modules') &&
|
||||
array_search('mod_xsendfile', apache_get_modules()))
|
||||
$xsendfile = true;
|
||||
|
||||
if ($xsendfile) {
|
||||
header("X-Sendfile: $filename");
|
||||
header("Content-type: application/octet-stream");
|
||||
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
|
||||
} else {
|
||||
header("Content-type: image/png");
|
||||
$stamp = gmdate("D, d M Y H:i:s", filemtime($filename)). " GMT";
|
||||
header("Last-Modified: $stamp", true);
|
||||
ob_clean(); // discard any data in the output buffer (if possible)
|
||||
flush(); // flush headers (if possible)
|
||||
readfile($filename);
|
||||
}
|
||||
} else {
|
||||
header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
|
||||
echo "File not found.";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
BIN
source/images/alert.png
Normal file
After Width: | Height: | Size: 701 B |
BIN
source/images/archive.png
Normal file
After Width: | Height: | Size: 555 B |
BIN
source/images/blank_icon.gif
Normal file
After Width: | Height: | Size: 80 B |
BIN
source/images/collapse.png
Normal file
After Width: | Height: | Size: 317 B |
BIN
source/images/cross.png
Normal file
After Width: | Height: | Size: 655 B |
BIN
source/images/error.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
source/images/favicon-72px.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
source/images/favicon.png
Normal file
After Width: | Height: | Size: 772 B |
BIN
source/images/feed.png
Normal file
After Width: | Height: | Size: 691 B |
BIN
source/images/filter.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
source/images/folder.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
source/images/fresh.png
Normal file
After Width: | Height: | Size: 633 B |
BIN
source/images/indicator_tiny.gif
Normal file
After Width: | Height: | Size: 723 B |
BIN
source/images/indicator_white.gif
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
source/images/information.png
Normal file
After Width: | Height: | Size: 778 B |
BIN
source/images/label.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
source/images/logo_small.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
source/images/logo_wide.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
source/images/mark_set.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
source/images/mark_unset.png
Normal file
After Width: | Height: | Size: 610 B |
BIN
source/images/new_version.png
Normal file
After Width: | Height: | Size: 372 B |
BIN
source/images/page_white_go.png
Normal file
After Width: | Height: | Size: 612 B |
BIN
source/images/plugin.png
Normal file
After Width: | Height: | Size: 591 B |
BIN
source/images/plugin_disabled.png
Normal file
After Width: | Height: | Size: 347 B |
BIN
source/images/pub_set.png
Normal file
After Width: | Height: | Size: 691 B |
BIN
source/images/pub_unset.png
Normal file
After Width: | Height: | Size: 718 B |
BIN
source/images/score_half_high.png
Normal file
After Width: | Height: | Size: 332 B |
BIN
source/images/score_half_low.png
Normal file
After Width: | Height: | Size: 295 B |
BIN
source/images/score_high.png
Normal file
After Width: | Height: | Size: 295 B |
BIN
source/images/score_low.png
Normal file
After Width: | Height: | Size: 211 B |
BIN
source/images/score_neutral.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
source/images/star.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
source/images/tag.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
source/images/time.png
Normal file
After Width: | Height: | Size: 793 B |
BIN
source/images/toolbar.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
source/images/user.png
Normal file
After Width: | Height: | Size: 741 B |
14
source/include/autoload.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
require_once "functions.php";
|
||||
|
||||
function __autoload($class) {
|
||||
$class_file = str_replace("_", "/", strtolower(basename($class)));
|
||||
|
||||
$file = dirname(__FILE__)."/../classes/$class_file.php";
|
||||
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
222
source/include/ccache.php
Normal file
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
/* function ccache_zero($feed_id, $owner_uid) {
|
||||
db_query("UPDATE ttrss_counters_cache SET
|
||||
value = 0, updated = NOW() WHERE
|
||||
feed_id = '$feed_id' AND owner_uid = '$owner_uid'");
|
||||
} */
|
||||
|
||||
function ccache_zero_all($owner_uid) {
|
||||
db_query("UPDATE ttrss_counters_cache SET
|
||||
value = 0 WHERE owner_uid = '$owner_uid'");
|
||||
|
||||
db_query("UPDATE ttrss_cat_counters_cache SET
|
||||
value = 0 WHERE owner_uid = '$owner_uid'");
|
||||
}
|
||||
|
||||
function ccache_remove($feed_id, $owner_uid, $is_cat = false) {
|
||||
|
||||
if (!$is_cat) {
|
||||
$table = "ttrss_counters_cache";
|
||||
} else {
|
||||
$table = "ttrss_cat_counters_cache";
|
||||
}
|
||||
|
||||
db_query("DELETE FROM $table WHERE
|
||||
feed_id = '$feed_id' AND owner_uid = '$owner_uid'");
|
||||
|
||||
}
|
||||
|
||||
function ccache_update_all($owner_uid) {
|
||||
|
||||
if (get_pref('ENABLE_FEED_CATS', $owner_uid)) {
|
||||
|
||||
$result = db_query("SELECT feed_id FROM ttrss_cat_counters_cache
|
||||
WHERE feed_id > 0 AND owner_uid = '$owner_uid'");
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
ccache_update($line["feed_id"], $owner_uid, true);
|
||||
}
|
||||
|
||||
/* We have to manually include category 0 */
|
||||
|
||||
ccache_update(0, $owner_uid, true);
|
||||
|
||||
} else {
|
||||
$result = db_query("SELECT feed_id FROM ttrss_counters_cache
|
||||
WHERE feed_id > 0 AND owner_uid = '$owner_uid'");
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
print ccache_update($line["feed_id"], $owner_uid);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function ccache_find($feed_id, $owner_uid, $is_cat = false,
|
||||
$no_update = false) {
|
||||
|
||||
if (!is_numeric($feed_id)) return;
|
||||
|
||||
if (!$is_cat) {
|
||||
$table = "ttrss_counters_cache";
|
||||
/* if ($feed_id > 0) {
|
||||
$tmp_result = db_query("SELECT owner_uid FROM ttrss_feeds
|
||||
WHERE id = '$feed_id'");
|
||||
$owner_uid = db_fetch_result($tmp_result, 0, "owner_uid");
|
||||
} */
|
||||
} else {
|
||||
$table = "ttrss_cat_counters_cache";
|
||||
}
|
||||
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$date_qpart = "updated > NOW() - INTERVAL '15 minutes'";
|
||||
} else if (DB_TYPE == "mysql") {
|
||||
$date_qpart = "updated > DATE_SUB(NOW(), INTERVAL 15 MINUTE)";
|
||||
}
|
||||
|
||||
$result = db_query("SELECT value FROM $table
|
||||
WHERE owner_uid = '$owner_uid' AND feed_id = '$feed_id'
|
||||
LIMIT 1");
|
||||
|
||||
if (db_num_rows($result) == 1) {
|
||||
return db_fetch_result($result, 0, "value");
|
||||
} else {
|
||||
if ($no_update) {
|
||||
return -1;
|
||||
} else {
|
||||
return ccache_update($feed_id, $owner_uid, $is_cat);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function ccache_update($feed_id, $owner_uid, $is_cat = false,
|
||||
$update_pcat = true) {
|
||||
|
||||
if (!is_numeric($feed_id)) return;
|
||||
|
||||
/* if (!$is_cat && $feed_id > 0) {
|
||||
$tmp_result = db_query("SELECT owner_uid FROM ttrss_feeds
|
||||
WHERE id = '$feed_id'");
|
||||
$owner_uid = db_fetch_result($tmp_result, 0, "owner_uid");
|
||||
} */
|
||||
|
||||
$prev_unread = ccache_find($feed_id, $owner_uid, $is_cat, true);
|
||||
|
||||
/* When updating a label, all we need to do is recalculate feed counters
|
||||
* because labels are not cached */
|
||||
|
||||
if ($feed_id < 0) {
|
||||
ccache_update_all($owner_uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$is_cat) {
|
||||
$table = "ttrss_counters_cache";
|
||||
} else {
|
||||
$table = "ttrss_cat_counters_cache";
|
||||
}
|
||||
|
||||
if ($is_cat && $feed_id >= 0) {
|
||||
if ($feed_id != 0) {
|
||||
$cat_qpart = "cat_id = '$feed_id'";
|
||||
} else {
|
||||
$cat_qpart = "cat_id IS NULL";
|
||||
}
|
||||
|
||||
/* Recalculate counters for child feeds */
|
||||
|
||||
$result = db_query("SELECT id FROM ttrss_feeds
|
||||
WHERE owner_uid = '$owner_uid' AND $cat_qpart");
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
ccache_update($line["id"], $owner_uid, false, false);
|
||||
}
|
||||
|
||||
$result = db_query("SELECT SUM(value) AS sv
|
||||
FROM ttrss_counters_cache, ttrss_feeds
|
||||
WHERE id = feed_id AND $cat_qpart AND
|
||||
ttrss_feeds.owner_uid = '$owner_uid'");
|
||||
|
||||
$unread = (int) db_fetch_result($result, 0, "sv");
|
||||
|
||||
} else {
|
||||
$unread = (int) getFeedArticles($feed_id, $is_cat, true, $owner_uid);
|
||||
}
|
||||
|
||||
db_query("BEGIN");
|
||||
|
||||
$result = db_query("SELECT feed_id FROM $table
|
||||
WHERE owner_uid = '$owner_uid' AND feed_id = '$feed_id' LIMIT 1");
|
||||
|
||||
if (db_num_rows($result) == 1) {
|
||||
db_query("UPDATE $table SET
|
||||
value = '$unread', updated = NOW() WHERE
|
||||
feed_id = '$feed_id' AND owner_uid = '$owner_uid'");
|
||||
|
||||
} else {
|
||||
db_query("INSERT INTO $table
|
||||
(feed_id, value, owner_uid, updated)
|
||||
VALUES
|
||||
($feed_id, $unread, $owner_uid, NOW())");
|
||||
}
|
||||
|
||||
db_query("COMMIT");
|
||||
|
||||
if ($feed_id > 0 && $prev_unread != $unread) {
|
||||
|
||||
if (!$is_cat) {
|
||||
|
||||
/* Update parent category */
|
||||
|
||||
if ($update_pcat) {
|
||||
|
||||
$result = db_query("SELECT cat_id FROM ttrss_feeds
|
||||
WHERE owner_uid = '$owner_uid' AND id = '$feed_id'");
|
||||
|
||||
$cat_id = (int) db_fetch_result($result, 0, "cat_id");
|
||||
|
||||
ccache_update($cat_id, $owner_uid, true);
|
||||
|
||||
}
|
||||
}
|
||||
} else if ($feed_id < 0) {
|
||||
ccache_update_all($owner_uid);
|
||||
}
|
||||
|
||||
return $unread;
|
||||
}
|
||||
|
||||
/* function ccache_cleanup($owner_uid) {
|
||||
|
||||
if (DB_TYPE == "pgsql") {
|
||||
db_query("DELETE FROM ttrss_counters_cache AS c1 WHERE
|
||||
(SELECT count(*) FROM ttrss_counters_cache AS c2
|
||||
WHERE c1.feed_id = c2.feed_id AND c2.owner_uid = c1.owner_uid) > 1
|
||||
AND owner_uid = '$owner_uid'");
|
||||
|
||||
db_query("DELETE FROM ttrss_cat_counters_cache AS c1 WHERE
|
||||
(SELECT count(*) FROM ttrss_cat_counters_cache AS c2
|
||||
WHERE c1.feed_id = c2.feed_id AND c2.owner_uid = c1.owner_uid) > 1
|
||||
AND owner_uid = '$owner_uid'");
|
||||
} else {
|
||||
db_query("DELETE c1 FROM
|
||||
ttrss_counters_cache AS c1,
|
||||
ttrss_counters_cache AS c2
|
||||
WHERE
|
||||
c1.owner_uid = '$owner_uid' AND
|
||||
c1.owner_uid = c2.owner_uid AND
|
||||
c1.feed_id = c2.feed_id");
|
||||
|
||||
db_query("DELETE c1 FROM
|
||||
ttrss_cat_counters_cache AS c1,
|
||||
ttrss_cat_counters_cache AS c2
|
||||
WHERE
|
||||
c1.owner_uid = '$owner_uid' AND
|
||||
c1.owner_uid = c2.owner_uid AND
|
||||
c1.feed_id = c2.feed_id");
|
||||
|
||||
}
|
||||
} */
|
||||
?>
|
351
source/include/colors.php
Normal file
|
@ -0,0 +1,351 @@
|
|||
<?php
|
||||
|
||||
if (file_exists("lib/floIcon.php")) {
|
||||
require_once "lib/floIcon.php";
|
||||
}
|
||||
|
||||
function _resolve_htmlcolor($color) {
|
||||
$htmlcolors = array ("aliceblue" => "#f0f8ff",
|
||||
"antiquewhite" => "#faebd7",
|
||||
"aqua" => "#00ffff",
|
||||
"aquamarine" => "#7fffd4",
|
||||
"azure" => "#f0ffff",
|
||||
"beige" => "#f5f5dc",
|
||||
"bisque" => "#ffe4c4",
|
||||
"black" => "#000000",
|
||||
"blanchedalmond" => "#ffebcd",
|
||||
"blue" => "#0000ff",
|
||||
"blueviolet" => "#8a2be2",
|
||||
"brown" => "#a52a2a",
|
||||
"burlywood" => "#deb887",
|
||||
"cadetblue" => "#5f9ea0",
|
||||
"chartreuse" => "#7fff00",
|
||||
"chocolate" => "#d2691e",
|
||||
"coral" => "#ff7f50",
|
||||
"cornflowerblue" => "#6495ed",
|
||||
"cornsilk" => "#fff8dc",
|
||||
"crimson" => "#dc143c",
|
||||
"cyan" => "#00ffff",
|
||||
"darkblue" => "#00008b",
|
||||
"darkcyan" => "#008b8b",
|
||||
"darkgoldenrod" => "#b8860b",
|
||||
"darkgray" => "#a9a9a9",
|
||||
"darkgrey" => "#a9a9a9",
|
||||
"darkgreen" => "#006400",
|
||||
"darkkhaki" => "#bdb76b",
|
||||
"darkmagenta" => "#8b008b",
|
||||
"darkolivegreen" => "#556b2f",
|
||||
"darkorange" => "#ff8c00",
|
||||
"darkorchid" => "#9932cc",
|
||||
"darkred" => "#8b0000",
|
||||
"darksalmon" => "#e9967a",
|
||||
"darkseagreen" => "#8fbc8f",
|
||||
"darkslateblue" => "#483d8b",
|
||||
"darkslategray" => "#2f4f4f",
|
||||
"darkslategrey" => "#2f4f4f",
|
||||
"darkturquoise" => "#00ced1",
|
||||
"darkviolet" => "#9400d3",
|
||||
"deeppink" => "#ff1493",
|
||||
"deepskyblue" => "#00bfff",
|
||||
"dimgray" => "#696969",
|
||||
"dimgrey" => "#696969",
|
||||
"dodgerblue" => "#1e90ff",
|
||||
"firebrick" => "#b22222",
|
||||
"floralwhite" => "#fffaf0",
|
||||
"forestgreen" => "#228b22",
|
||||
"fuchsia" => "#ff00ff",
|
||||
"gainsboro" => "#dcdcdc",
|
||||
"ghostwhite" => "#f8f8ff",
|
||||
"gold" => "#ffd700",
|
||||
"goldenrod" => "#daa520",
|
||||
"gray" => "#808080",
|
||||
"grey" => "#808080",
|
||||
"green" => "#008000",
|
||||
"greenyellow" => "#adff2f",
|
||||
"honeydew" => "#f0fff0",
|
||||
"hotpink" => "#ff69b4",
|
||||
"indianred " => "#cd5c5c",
|
||||
"indigo " => "#4b0082",
|
||||
"ivory" => "#fffff0",
|
||||
"khaki" => "#f0e68c",
|
||||
"lavender" => "#e6e6fa",
|
||||
"lavenderblush" => "#fff0f5",
|
||||
"lawngreen" => "#7cfc00",
|
||||
"lemonchiffon" => "#fffacd",
|
||||
"lightblue" => "#add8e6",
|
||||
"lightcoral" => "#f08080",
|
||||
"lightcyan" => "#e0ffff",
|
||||
"lightgoldenrodyellow" => "#fafad2",
|
||||
"lightgray" => "#d3d3d3",
|
||||
"lightgrey" => "#d3d3d3",
|
||||
"lightgreen" => "#90ee90",
|
||||
"lightpink" => "#ffb6c1",
|
||||
"lightsalmon" => "#ffa07a",
|
||||
"lightseagreen" => "#20b2aa",
|
||||
"lightskyblue" => "#87cefa",
|
||||
"lightslategray" => "#778899",
|
||||
"lightslategrey" => "#778899",
|
||||
"lightsteelblue" => "#b0c4de",
|
||||
"lightyellow" => "#ffffe0",
|
||||
"lime" => "#00ff00",
|
||||
"limegreen" => "#32cd32",
|
||||
"linen" => "#faf0e6",
|
||||
"magenta" => "#ff00ff",
|
||||
"maroon" => "#800000",
|
||||
"mediumaquamarine" => "#66cdaa",
|
||||
"mediumblue" => "#0000cd",
|
||||
"mediumorchid" => "#ba55d3",
|
||||
"mediumpurple" => "#9370db",
|
||||
"mediumseagreen" => "#3cb371",
|
||||
"mediumslateblue" => "#7b68ee",
|
||||
"mediumspringgreen" => "#00fa9a",
|
||||
"mediumturquoise" => "#48d1cc",
|
||||
"mediumvioletred" => "#c71585",
|
||||
"midnightblue" => "#191970",
|
||||
"mintcream" => "#f5fffa",
|
||||
"mistyrose" => "#ffe4e1",
|
||||
"moccasin" => "#ffe4b5",
|
||||
"navajowhite" => "#ffdead",
|
||||
"navy" => "#000080",
|
||||
"oldlace" => "#fdf5e6",
|
||||
"olive" => "#808000",
|
||||
"olivedrab" => "#6b8e23",
|
||||
"orange" => "#ffa500",
|
||||
"orangered" => "#ff4500",
|
||||
"orchid" => "#da70d6",
|
||||
"palegoldenrod" => "#eee8aa",
|
||||
"palegreen" => "#98fb98",
|
||||
"paleturquoise" => "#afeeee",
|
||||
"palevioletred" => "#db7093",
|
||||
"papayawhip" => "#ffefd5",
|
||||
"peachpuff" => "#ffdab9",
|
||||
"peru" => "#cd853f",
|
||||
"pink" => "#ffc0cb",
|
||||
"plum" => "#dda0dd",
|
||||
"powderblue" => "#b0e0e6",
|
||||
"purple" => "#800080",
|
||||
"red" => "#ff0000",
|
||||
"rosybrown" => "#bc8f8f",
|
||||
"royalblue" => "#4169e1",
|
||||
"saddlebrown" => "#8b4513",
|
||||
"salmon" => "#fa8072",
|
||||
"sandybrown" => "#f4a460",
|
||||
"seagreen" => "#2e8b57",
|
||||
"seashell" => "#fff5ee",
|
||||
"sienna" => "#a0522d",
|
||||
"silver" => "#c0c0c0",
|
||||
"skyblue" => "#87ceeb",
|
||||
"slateblue" => "#6a5acd",
|
||||
"slategray" => "#708090",
|
||||
"slategrey" => "#708090",
|
||||
"snow" => "#fffafa",
|
||||
"springgreen" => "#00ff7f",
|
||||
"steelblue" => "#4682b4",
|
||||
"tan" => "#d2b48c",
|
||||
"teal" => "#008080",
|
||||
"thistle" => "#d8bfd8",
|
||||
"tomato" => "#ff6347",
|
||||
"turquoise" => "#40e0d0",
|
||||
"violet" => "#ee82ee",
|
||||
"wheat" => "#f5deb3",
|
||||
"white" => "#ffffff",
|
||||
"whitesmoke" => "#f5f5f5",
|
||||
"yellow" => "#ffff00",
|
||||
"yellowgreen" => "#9acd32");
|
||||
|
||||
$color = strtolower($color);
|
||||
|
||||
if (isset($htmlcolors[$color]))
|
||||
return $htmlcolors[$color];
|
||||
else
|
||||
return $color;
|
||||
}
|
||||
|
||||
### RGB >> HSL
|
||||
function _color_rgb2hsl($rgb) {
|
||||
$r = $rgb[0]; $g = $rgb[1]; $b = $rgb[2];
|
||||
$min = min($r, min($g, $b)); $max = max($r, max($g, $b));
|
||||
$delta = $max - $min; $l = ($min + $max) / 2; $s = 0;
|
||||
if ($l > 0 && $l < 1) {
|
||||
$s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l));
|
||||
}
|
||||
$h = 0;
|
||||
if ($delta > 0) {
|
||||
if ($max == $r && $max != $g) $h += ($g - $b) / $delta;
|
||||
if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta);
|
||||
if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta);
|
||||
$h /= 6;
|
||||
} return array($h, $s, $l);
|
||||
}
|
||||
|
||||
### HSL >> RGB
|
||||
function _color_hsl2rgb($hsl) {
|
||||
$h = $hsl[0]; $s = $hsl[1]; $l = $hsl[2];
|
||||
$m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s;
|
||||
$m1 = $l * 2 - $m2;
|
||||
return array(_color_hue2rgb($m1, $m2, $h + 0.33333),
|
||||
_color_hue2rgb($m1, $m2, $h),
|
||||
_color_hue2rgb($m1, $m2, $h - 0.33333));
|
||||
}
|
||||
|
||||
### Helper function for _color_hsl2rgb().
|
||||
function _color_hue2rgb($m1, $m2, $h) {
|
||||
$h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h);
|
||||
if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
|
||||
if ($h * 2 < 1) return $m2;
|
||||
if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6;
|
||||
return $m1;
|
||||
}
|
||||
|
||||
### Convert a hex color into an RGB triplet.
|
||||
function _color_unpack($hex, $normalize = false) {
|
||||
|
||||
if (strpos($hex, '#') !== 0)
|
||||
$hex = _resolve_htmlcolor($hex);
|
||||
|
||||
if (strlen($hex) == 4) {
|
||||
$hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
|
||||
} $c = hexdec($hex);
|
||||
for ($i = 16; $i >= 0; $i -= 8) {
|
||||
$out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
|
||||
} return $out;
|
||||
}
|
||||
|
||||
### Convert an RGB triplet to a hex color.
|
||||
function _color_pack($rgb, $normalize = false) {
|
||||
foreach ($rgb as $k => $v) {
|
||||
$out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8));
|
||||
}return '#'. str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
function rgb2hsl($arr) {
|
||||
$r = $arr[0];
|
||||
$g = $arr[1];
|
||||
$b = $arr[2];
|
||||
|
||||
$var_R = ($r / 255);
|
||||
$var_G = ($g / 255);
|
||||
$var_B = ($b / 255);
|
||||
|
||||
$var_Min = min($var_R, $var_G, $var_B);
|
||||
$var_Max = max($var_R, $var_G, $var_B);
|
||||
$del_Max = $var_Max - $var_Min;
|
||||
|
||||
$v = $var_Max;
|
||||
|
||||
if ($del_Max == 0) {
|
||||
$h = 0;
|
||||
$s = 0;
|
||||
} else {
|
||||
$s = $del_Max / $var_Max;
|
||||
|
||||
$del_R = ((($var_Max - $var_R ) / 6 ) + ($del_Max / 2 ) ) / $del_Max;
|
||||
$del_G = ((($var_Max - $var_G ) / 6 ) + ($del_Max / 2 ) ) / $del_Max;
|
||||
$del_B = ((($var_Max - $var_B ) / 6 ) + ($del_Max / 2 ) ) / $del_Max;
|
||||
|
||||
if ($var_R == $var_Max) $h = $del_B - $del_G;
|
||||
else if ($var_G == $var_Max) $h = (1 / 3 ) + $del_R - $del_B;
|
||||
else if ($var_B == $var_Max) $h = (2 / 3 ) + $del_G - $del_R;
|
||||
|
||||
if ($h < 0) $h++;
|
||||
if ($h > 1) $h--;
|
||||
}
|
||||
|
||||
return array($h, $s, $v);
|
||||
}
|
||||
|
||||
function hsl2rgb($arr) {
|
||||
$h = $arr[0];
|
||||
$s = $arr[1];
|
||||
$v = $arr[2];
|
||||
|
||||
if($s == 0) {
|
||||
$r = $g = $B = $v * 255;
|
||||
} else {
|
||||
$var_H = $h * 6;
|
||||
$var_i = floor($var_H );
|
||||
$var_1 = $v * (1 - $s );
|
||||
$var_2 = $v * (1 - $s * ($var_H - $var_i ) );
|
||||
$var_3 = $v * (1 - $s * (1 - ($var_H - $var_i ) ) );
|
||||
|
||||
if ($var_i == 0) { $var_R = $v ; $var_G = $var_3 ; $var_B = $var_1 ; }
|
||||
else if ($var_i == 1) { $var_R = $var_2 ; $var_G = $v ; $var_B = $var_1 ; }
|
||||
else if ($var_i == 2) { $var_R = $var_1 ; $var_G = $v ; $var_B = $var_3 ; }
|
||||
else if ($var_i == 3) { $var_R = $var_1 ; $var_G = $var_2 ; $var_B = $v ; }
|
||||
else if ($var_i == 4) { $var_R = $var_3 ; $var_G = $var_1 ; $var_B = $v ; }
|
||||
else { $var_R = $v ; $var_G = $var_1 ; $var_B = $var_2 ; }
|
||||
|
||||
$r = $var_R * 255;
|
||||
$g = $var_G * 255;
|
||||
$B = $var_B * 255;
|
||||
}
|
||||
return array($r, $g, $B);
|
||||
}
|
||||
|
||||
function colorPalette($imageFile, $numColors, $granularity = 5) {
|
||||
$granularity = max(1, abs((int)$granularity));
|
||||
$colors = array();
|
||||
|
||||
$size = @getimagesize($imageFile);
|
||||
|
||||
// to enable .ico support place floIcon.php into lib/
|
||||
if (strtolower($size['mime']) == 'image/vnd.microsoft.icon') {
|
||||
|
||||
if (class_exists("floIcon")) {
|
||||
|
||||
$ico = new floIcon();
|
||||
@$ico->readICO($imageFile);
|
||||
|
||||
if(count($ico->images)==0)
|
||||
return false;
|
||||
else
|
||||
$img = @$ico->images[count($ico->images)-1]->getImageResource();
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if ($size[0] > 0 && $size[1] > 0) {
|
||||
$img = @imagecreatefromstring(file_get_contents($imageFile));
|
||||
}
|
||||
|
||||
if (!$img) return false;
|
||||
|
||||
for($x = 0; $x < $size[0]; $x += $granularity) {
|
||||
for($y = 0; $y < $size[1]; $y += $granularity) {
|
||||
$thisColor = imagecolorat($img, $x, $y);
|
||||
$rgb = imagecolorsforindex($img, $thisColor);
|
||||
$red = round(round(($rgb['red'] / 0x33)) * 0x33);
|
||||
$green = round(round(($rgb['green'] / 0x33)) * 0x33);
|
||||
$blue = round(round(($rgb['blue'] / 0x33)) * 0x33);
|
||||
$thisRGB = sprintf('%02X%02X%02X', $red, $green, $blue);
|
||||
if(array_key_exists($thisRGB, $colors)) {
|
||||
$colors[$thisRGB]++;
|
||||
} else{
|
||||
$colors[$thisRGB] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arsort($colors);
|
||||
return array_slice(array_keys($colors), 0, $numColors);
|
||||
}
|
||||
|
||||
function calculate_avg_color($iconFile) {
|
||||
$palette = colorPalette($iconFile, 4, 4);
|
||||
|
||||
if (is_array($palette)) {
|
||||
foreach ($palette as $p) {
|
||||
$hsl = rgb2hsl(_color_unpack("#$p"));
|
||||
|
||||
if ($hsl[1] > 0.25 && $hsl[2] > 0.25 &&
|
||||
!($hsl[0] >= 0 && $hsl[0] < 0.01 && $hsl[1] < 0.01) &&
|
||||
!($hsl[0] >= 0 && $hsl[0] < 0.01 && $hsl[2] > 0.99)) {
|
||||
|
||||
return _color_pack(hsl2rgb($hsl));
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
?>
|
36
source/include/crypt.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
function decrypt_string($str) {
|
||||
$pair = explode(":", $str);
|
||||
|
||||
if (count($pair) == 2) {
|
||||
@$iv = base64_decode($pair[0]);
|
||||
@$encstr = base64_decode($pair[1]);
|
||||
|
||||
if ($iv && $encstr) {
|
||||
$key = hash('SHA256', FEED_CRYPT_KEY, true);
|
||||
|
||||
$str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encstr,
|
||||
MCRYPT_MODE_CBC, $iv);
|
||||
|
||||
if ($str) return rtrim($str);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function encrypt_string($str) {
|
||||
$key = hash('SHA256', FEED_CRYPT_KEY, true);
|
||||
|
||||
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128,
|
||||
MCRYPT_MODE_CBC), MCRYPT_RAND);
|
||||
|
||||
$encstr = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str,
|
||||
MCRYPT_MODE_CBC, $iv);
|
||||
|
||||
$iv_base64 = base64_encode($iv);
|
||||
$encstr_base64 = base64_encode($encstr);
|
||||
|
||||
return "$iv_base64:$encstr_base64";
|
||||
}
|
||||
?>
|
11
source/include/db-prefs.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
require_once "db.php";
|
||||
|
||||
function get_pref($pref_name, $user_id = false, $die_on_error = false) {
|
||||
return Db_Prefs::get()->read($pref_name, $user_id, $die_on_error);
|
||||
}
|
||||
|
||||
function set_pref($pref_name, $value, $user_id = false, $strip_tags = true) {
|
||||
return Db_Prefs::get()->write($pref_name, $value, $user_id, $strip_tags);
|
||||
}
|
||||
?>
|
36
source/include/db.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
function db_escape_string($s, $strip_tags = true) {
|
||||
return Db::get()->escape_string($s, $strip_tags);
|
||||
}
|
||||
|
||||
function db_query($query, $die_on_error = true) {
|
||||
return Db::get()->query($query, $die_on_error);
|
||||
}
|
||||
|
||||
function db_fetch_assoc($result) {
|
||||
return Db::get()->fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function db_num_rows($result) {
|
||||
return Db::get()->num_rows($result);
|
||||
}
|
||||
|
||||
function db_fetch_result($result, $row, $param) {
|
||||
return Db::get()->fetch_result($result, $row, $param);
|
||||
}
|
||||
|
||||
function db_affected_rows($result) {
|
||||
return Db::get()->affected_rows($result);
|
||||
}
|
||||
|
||||
function db_last_error() {
|
||||
return Db::get()->last_error();
|
||||
}
|
||||
|
||||
function db_quote($str){
|
||||
return Db::get()->quote($str);
|
||||
}
|
||||
|
||||
?>
|
194
source/include/digest.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
/**
|
||||
* Send by mail a digest of last articles.
|
||||
*
|
||||
* @param mixed $link The database connection.
|
||||
* @param integer $limit The maximum number of articles by digest.
|
||||
* @return boolean Return false if digests are not enabled.
|
||||
*/
|
||||
function send_headlines_digests($debug = false) {
|
||||
|
||||
require_once 'classes/ttrssmailer.php';
|
||||
|
||||
$user_limit = 15; // amount of users to process (e.g. emails to send out)
|
||||
$limit = 1000; // maximum amount of headlines to include
|
||||
|
||||
if ($debug) _debug("Sending digests, batch of max $user_limit users, headline limit = $limit");
|
||||
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$interval_query = "last_digest_sent < NOW() - INTERVAL '1 days'";
|
||||
} else if (DB_TYPE == "mysql") {
|
||||
$interval_query = "last_digest_sent < DATE_SUB(NOW(), INTERVAL 1 DAY)";
|
||||
}
|
||||
|
||||
$result = db_query("SELECT id,email FROM ttrss_users
|
||||
WHERE email != '' AND (last_digest_sent IS NULL OR $interval_query)");
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
|
||||
if (@get_pref('DIGEST_ENABLE', $line['id'], false)) {
|
||||
$preferred_ts = strtotime(get_pref('DIGEST_PREFERRED_TIME', $line['id'], '00:00'));
|
||||
|
||||
// try to send digests within 2 hours of preferred time
|
||||
if ($preferred_ts && time() >= $preferred_ts &&
|
||||
time() - $preferred_ts <= 7200) {
|
||||
|
||||
if ($debug) _debug("Sending digest for UID:" . $line['id'] . " - " . $line["email"]);
|
||||
|
||||
$do_catchup = get_pref('DIGEST_CATCHUP', $line['id'], false);
|
||||
|
||||
global $tz_offset;
|
||||
|
||||
// reset tz_offset global to prevent tz cache clash between users
|
||||
$tz_offset = -1;
|
||||
|
||||
$tuple = prepare_headlines_digest($line["id"], 1, $limit);
|
||||
$digest = $tuple[0];
|
||||
$headlines_count = $tuple[1];
|
||||
$affected_ids = $tuple[2];
|
||||
$digest_text = $tuple[3];
|
||||
|
||||
if ($headlines_count > 0) {
|
||||
|
||||
$mail = new ttrssMailer();
|
||||
|
||||
$rc = $mail->quickMail($line["email"], $line["login"] , DIGEST_SUBJECT, $digest, $digest_text);
|
||||
|
||||
if (!$rc && $debug) _debug("ERROR: " . $mail->ErrorInfo);
|
||||
|
||||
if ($debug) _debug("RC=$rc");
|
||||
|
||||
if ($rc && $do_catchup) {
|
||||
if ($debug) _debug("Marking affected articles as read...");
|
||||
catchupArticlesById($affected_ids, 0, $line["id"]);
|
||||
}
|
||||
} else {
|
||||
if ($debug) _debug("No headlines");
|
||||
}
|
||||
|
||||
db_query("UPDATE ttrss_users SET last_digest_sent = NOW()
|
||||
WHERE id = " . $line["id"]);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($debug) _debug("All done.");
|
||||
|
||||
}
|
||||
|
||||
function prepare_headlines_digest($user_id, $days = 1, $limit = 1000) {
|
||||
|
||||
require_once "lib/MiniTemplator.class.php";
|
||||
|
||||
$tpl = new MiniTemplator;
|
||||
$tpl_t = new MiniTemplator;
|
||||
|
||||
$tpl->readTemplateFromFile("templates/digest_template_html.txt");
|
||||
$tpl_t->readTemplateFromFile("templates/digest_template.txt");
|
||||
|
||||
$user_tz_string = get_pref('USER_TIMEZONE', $user_id);
|
||||
$local_ts = convert_timestamp(time(), 'UTC', $user_tz_string);
|
||||
|
||||
$tpl->setVariable('CUR_DATE', date('Y/m/d', $local_ts));
|
||||
$tpl->setVariable('CUR_TIME', date('G:i', $local_ts));
|
||||
|
||||
$tpl_t->setVariable('CUR_DATE', date('Y/m/d', $local_ts));
|
||||
$tpl_t->setVariable('CUR_TIME', date('G:i', $local_ts));
|
||||
|
||||
$affected_ids = array();
|
||||
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$interval_query = "ttrss_entries.date_updated > NOW() - INTERVAL '$days days'";
|
||||
} else if (DB_TYPE == "mysql") {
|
||||
$interval_query = "ttrss_entries.date_updated > DATE_SUB(NOW(), INTERVAL $days DAY)";
|
||||
}
|
||||
|
||||
$result = db_query("SELECT ttrss_entries.title,
|
||||
ttrss_feeds.title AS feed_title,
|
||||
COALESCE(ttrss_feed_categories.title, '".__('Uncategorized')."') AS cat_title,
|
||||
date_updated,
|
||||
ttrss_user_entries.ref_id,
|
||||
link,
|
||||
score,
|
||||
content,
|
||||
".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
|
||||
FROM
|
||||
ttrss_user_entries,ttrss_entries,ttrss_feeds
|
||||
LEFT JOIN
|
||||
ttrss_feed_categories ON (cat_id = ttrss_feed_categories.id)
|
||||
WHERE
|
||||
ref_id = ttrss_entries.id AND feed_id = ttrss_feeds.id
|
||||
AND include_in_digest = true
|
||||
AND $interval_query
|
||||
AND ttrss_user_entries.owner_uid = $user_id
|
||||
AND unread = true
|
||||
AND score >= 0
|
||||
ORDER BY ttrss_feed_categories.title, ttrss_feeds.title, score DESC, date_updated DESC
|
||||
LIMIT $limit");
|
||||
|
||||
$cur_feed_title = "";
|
||||
|
||||
$headlines_count = db_num_rows($result);
|
||||
|
||||
$headlines = array();
|
||||
|
||||
while ($line = db_fetch_assoc($result)) {
|
||||
array_push($headlines, $line);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < sizeof($headlines); $i++) {
|
||||
|
||||
$line = $headlines[$i];
|
||||
|
||||
array_push($affected_ids, $line["ref_id"]);
|
||||
|
||||
$updated = make_local_datetime($line['last_updated'], false,
|
||||
$user_id);
|
||||
|
||||
/* if ($line["score"] != 0) {
|
||||
if ($line["score"] > 0) $line["score"] = '+' . $line["score"];
|
||||
|
||||
$line["title"] .= " (".$line['score'].")";
|
||||
} */
|
||||
|
||||
if (get_pref('ENABLE_FEED_CATS', $user_id)) {
|
||||
$line['feed_title'] = $line['cat_title'] . " / " . $line['feed_title'];
|
||||
}
|
||||
|
||||
$tpl->setVariable('FEED_TITLE', $line["feed_title"]);
|
||||
$tpl->setVariable('ARTICLE_TITLE', $line["title"]);
|
||||
$tpl->setVariable('ARTICLE_LINK', $line["link"]);
|
||||
$tpl->setVariable('ARTICLE_UPDATED', $updated);
|
||||
$tpl->setVariable('ARTICLE_EXCERPT',
|
||||
truncate_string(strip_tags($line["content"]), 300));
|
||||
// $tpl->setVariable('ARTICLE_CONTENT',
|
||||
// strip_tags($article_content));
|
||||
|
||||
$tpl->addBlock('article');
|
||||
|
||||
$tpl_t->setVariable('FEED_TITLE', $line["feed_title"]);
|
||||
$tpl_t->setVariable('ARTICLE_TITLE', $line["title"]);
|
||||
$tpl_t->setVariable('ARTICLE_LINK', $line["link"]);
|
||||
$tpl_t->setVariable('ARTICLE_UPDATED', $updated);
|
||||
// $tpl_t->setVariable('ARTICLE_EXCERPT',
|
||||
// truncate_string(strip_tags($line["excerpt"]), 100));
|
||||
|
||||
$tpl_t->addBlock('article');
|
||||
|
||||
if ($headlines[$i]['feed_title'] != $headlines[$i+1]['feed_title']) {
|
||||
$tpl->addBlock('feed');
|
||||
$tpl_t->addBlock('feed');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$tpl->addBlock('digest');
|
||||
$tpl->generateOutputToString($tmp);
|
||||
|
||||
$tpl_t->addBlock('digest');
|
||||
$tpl_t->generateOutputToString($tmp_t);
|
||||
|
||||
return array($tmp, $headlines_count, $affected_ids, $tmp_t);
|
||||
}
|
||||
?>
|
39
source/include/errorhandler.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
function ttrss_error_handler($errno, $errstr, $file, $line, $context) {
|
||||
global $logger;
|
||||
|
||||
if (error_reporting() == 0 || !$errno) return false;
|
||||
|
||||
$file = substr(str_replace(dirname(dirname(__FILE__)), "", $file), 1);
|
||||
|
||||
if (class_exists("Logger"))
|
||||
return Logger::get()->log_error($errno, $errstr, $file, $line, $context);
|
||||
}
|
||||
|
||||
function ttrss_fatal_handler() {
|
||||
global $logger;
|
||||
|
||||
$error = error_get_last();
|
||||
|
||||
if ($error !== NULL) {
|
||||
$errno = $error["type"];
|
||||
$file = $error["file"];
|
||||
$line = $error["line"];
|
||||
$errstr = $error["message"];
|
||||
|
||||
if (!$errno) return false;
|
||||
|
||||
$context = debug_backtrace();
|
||||
|
||||
$file = substr(str_replace(dirname(dirname(__FILE__)), "", $file), 1);
|
||||
|
||||
if (class_exists("Logger"))
|
||||
return Logger::get()->log_error($errno, $errstr, $file, $line, $context);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
register_shutdown_function('ttrss_fatal_handler');
|
||||
set_error_handler('ttrss_error_handler');
|
||||
?>
|