2011-12-13 06:00:42 +00:00
< ? php
2021-02-26 16:16:17 +00:00
use chillerlan\QRCode ;
2013-04-02 12:20:06 +00:00
2012-08-17 10:20:55 +00:00
class Pref_Prefs extends Handler_Protected {
2021-11-14 20:12:37 +00:00
/** @var array<Prefs::*, array<int, string>> */
2022-08-12 14:13:26 +00:00
private array $pref_help = [];
2021-11-14 20:12:37 +00:00
/** @var array<string, array<int, string>> pref items are Prefs::*|Pref_Prefs::BLOCK_SEPARATOR (PHPStan was complaining) */
2022-08-12 14:13:26 +00:00
private array $pref_item_map = [];
2021-11-14 20:12:37 +00:00
/** @var array<string, string> */
2022-08-12 14:13:26 +00:00
private array $pref_help_bottom = [];
2021-11-14 20:12:37 +00:00
/** @var array<int, string> */
2022-08-12 14:13:26 +00:00
private array $pref_blacklist = [];
2013-04-02 12:20:06 +00:00
2021-11-14 20:12:37 +00:00
private const BLOCK_SEPARATOR = 'BLOCK_SEPARATOR' ;
2021-03-03 16:07:39 +00:00
const PI_RES_ALREADY_INSTALLED = " PI_RES_ALREADY_INSTALLED " ;
const PI_RES_SUCCESS = " PI_RES_SUCCESS " ;
const PI_ERR_NO_CLASS = " PI_ERR_NO_CLASS " ;
const PI_ERR_NO_INIT_PHP = " PI_ERR_NO_INIT_PHP " ;
const PI_ERR_EXEC_FAILED = " PI_ERR_EXEC_FAILED " ;
const PI_ERR_NO_TEMPDIR = " PI_ERR_NO_TEMPDIR " ;
const PI_ERR_PLUGIN_NOT_FOUND = " PI_ERR_PLUGIN_NOT_FOUND " ;
const PI_ERR_NO_WORKDIR = " PI_ERR_NO_WORKDIR " ;
2021-12-02 17:57:19 +00:00
function csrf_ignore ( string $method ) : bool {
2021-02-12 12:22:10 +00:00
$csrf_ignored = array ( " index " , " updateself " , " otpqrcode " );
2011-12-26 08:02:52 +00:00
return array_search ( $method , $csrf_ignored ) !== false ;
}
2013-04-19 04:40:19 +00:00
function __construct ( $args ) {
parent :: __construct ( $args );
2013-04-02 12:20:06 +00:00
2019-02-21 19:08:21 +00:00
$this -> pref_item_map = [
__ ( 'General' ) => [
2021-02-25 11:45:11 +00:00
Prefs :: USER_LANGUAGE ,
Prefs :: USER_TIMEZONE ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: USER_CSS_THEME ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: ENABLE_API_ACCESS ,
2019-02-21 19:08:21 +00:00
],
__ ( 'Feeds' ) => [
2021-02-25 11:45:11 +00:00
Prefs :: DEFAULT_UPDATE_INTERVAL ,
Prefs :: FRESH_ARTICLE_MAX_AGE ,
Prefs :: DEFAULT_SEARCH_LANGUAGE ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: ENABLE_FEED_CATS ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: CONFIRM_FEED_CATCHUP ,
Prefs :: ON_CATCHUP_SHOW_NEXT_FEED ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: HIDE_READ_FEEDS ,
Prefs :: HIDE_READ_SHOWS_SPECIAL ,
2019-02-21 19:08:21 +00:00
],
__ ( 'Articles' ) => [
2021-02-25 11:45:11 +00:00
Prefs :: PURGE_OLD_DAYS ,
Prefs :: PURGE_UNREAD_ARTICLES ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: COMBINED_DISPLAY_MODE ,
Prefs :: CDM_EXPANDED ,
2021-03-10 05:33:56 +00:00
Prefs :: CDM_ENABLE_GRID ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: CDM_AUTO_CATCHUP ,
Prefs :: VFEED_GROUP_BY_FEED ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: SHOW_CONTENT_PREVIEW ,
Prefs :: STRIP_IMAGES ,
2019-02-21 19:08:21 +00:00
],
__ ( 'Digest' ) => [
2021-02-25 11:45:11 +00:00
Prefs :: DIGEST_ENABLE ,
Prefs :: DIGEST_CATCHUP ,
Prefs :: DIGEST_PREFERRED_TIME ,
2024-01-09 08:45:40 +00:00
Prefs :: DIGEST_MIN_SCORE ,
2019-02-21 19:08:21 +00:00
],
__ ( 'Advanced' ) => [
2021-02-25 11:45:11 +00:00
Prefs :: BLACKLISTED_TAGS ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: LONG_DATE_FORMAT ,
Prefs :: SHORT_DATE_FORMAT ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-25 11:45:11 +00:00
Prefs :: SSL_CERT_SERIAL ,
2021-11-14 20:12:37 +00:00
self :: BLOCK_SEPARATOR ,
2021-02-27 08:25:07 +00:00
Prefs :: DISABLE_CONDITIONAL_COUNTERS ,
2021-02-26 06:57:34 +00:00
Prefs :: HEADLINES_NO_DISTINCT ,
2021-02-26 09:34:50 +00:00
],
__ ( 'Debugging' ) => [
Prefs :: DEBUG_HEADLINE_IDS ,
],
2019-02-21 19:08:21 +00:00
];
2020-12-21 05:50:34 +00:00
$this -> pref_help_bottom = [
2021-02-25 11:45:11 +00:00
Prefs :: BLACKLISTED_TAGS => __ ( " Never apply these tags automatically (comma-separated list). " ),
2020-12-21 05:50:34 +00:00
];
2019-02-21 19:08:21 +00:00
$this -> pref_help = [
2021-02-25 11:45:11 +00:00
Prefs :: BLACKLISTED_TAGS => array ( __ ( " Blacklisted tags " ), " " ),
Prefs :: DEFAULT_SEARCH_LANGUAGE => array ( __ ( " Default language " ), __ ( " Used for full-text search " )),
Prefs :: CDM_AUTO_CATCHUP => array ( __ ( " Mark read on scroll " ), __ ( " Mark articles as read as you scroll past them " )),
Prefs :: CDM_EXPANDED => array ( __ ( " Always expand articles " )),
Prefs :: COMBINED_DISPLAY_MODE => array ( __ ( " Combined mode " ), __ ( " Show flat list of articles instead of separate panels " )),
Prefs :: CONFIRM_FEED_CATCHUP => array ( __ ( " Confirm marking feeds as read " )),
Prefs :: DEFAULT_UPDATE_INTERVAL => array ( __ ( " Default update interval " )),
Prefs :: DIGEST_CATCHUP => array ( __ ( " Mark sent articles as read " )),
Prefs :: DIGEST_ENABLE => array ( __ ( " Enable digest " ), __ ( " Send daily digest of new (and unread) headlines to your e-mail address " )),
Prefs :: DIGEST_PREFERRED_TIME => array ( __ ( " Try to send around this time " ), __ ( " Time in UTC " )),
Prefs :: ENABLE_API_ACCESS => array ( __ ( " Enable API " ), __ ( " Allows accessing this account through the API " )),
Prefs :: ENABLE_FEED_CATS => array ( __ ( " Enable categories " )),
Prefs :: FRESH_ARTICLE_MAX_AGE => array ( __ ( " Maximum age of fresh articles " ), " <strong> " . __ ( " hours " ) . " </strong> " ),
Prefs :: HIDE_READ_FEEDS => array ( __ ( " Hide read feeds " )),
Prefs :: HIDE_READ_SHOWS_SPECIAL => array ( __ ( " Always show special feeds " ), __ ( " While hiding read feeds " )),
Prefs :: LONG_DATE_FORMAT => array ( __ ( " Long date format " ), __ ( " Syntax is identical to PHP <a href='http://php.net/manual/function.date.php'>date()</a> function. " )),
Prefs :: ON_CATCHUP_SHOW_NEXT_FEED => array ( __ ( " Automatically show next feed " ), __ ( " After marking one as read " )),
Prefs :: PURGE_OLD_DAYS => array ( __ ( " Purge articles older than " ), __ ( " <strong>days</strong> (0 disables) " )),
Prefs :: PURGE_UNREAD_ARTICLES => array ( __ ( " Purge unread articles " )),
Prefs :: SHORT_DATE_FORMAT => array ( __ ( " Short date format " )),
Prefs :: SHOW_CONTENT_PREVIEW => array ( __ ( " Show content preview in headlines " )),
Prefs :: SSL_CERT_SERIAL => array ( __ ( " SSL client certificate " )),
Prefs :: STRIP_IMAGES => array ( __ ( " Do not embed media " )),
Prefs :: USER_TIMEZONE => array ( __ ( " Time zone " )),
Prefs :: VFEED_GROUP_BY_FEED => array ( __ ( " Group by feed " ), __ ( " Group multiple-feed output by originating feed " )),
Prefs :: USER_LANGUAGE => array ( __ ( " Language " )),
2021-02-26 06:57:34 +00:00
Prefs :: USER_CSS_THEME => array ( __ ( " Theme " )),
Prefs :: HEADLINES_NO_DISTINCT => array ( __ ( " Don't enforce DISTINCT headlines " ), __ ( " May produce duplicate entries " )),
2021-02-26 09:36:15 +00:00
Prefs :: DEBUG_HEADLINE_IDS => array ( __ ( " Show article and feed IDs " ), __ ( " In the headlines buffer " )),
2021-02-27 08:25:07 +00:00
Prefs :: DISABLE_CONDITIONAL_COUNTERS => array ( __ ( " Disable conditional counter updates " ), __ ( " May increase server load " )),
2021-03-10 08:44:16 +00:00
Prefs :: CDM_ENABLE_GRID => array ( __ ( " Grid view " ), __ ( " On wider screens, if always expanded " )),
2024-01-09 08:45:40 +00:00
Prefs :: DIGEST_MIN_SCORE => array ( __ ( " Required score " ), __ ( " Include articles with this or above score into digest " )),
2019-02-21 19:08:21 +00:00
];
2021-02-25 11:59:02 +00:00
// hidden in the main prefs UI (use to hide things that have description set above)
2021-02-25 11:49:58 +00:00
$this -> pref_blacklist = [
2021-02-25 11:59:02 +00:00
//
2021-02-25 11:49:58 +00:00
];
2013-04-02 12:20:06 +00:00
}
2021-11-14 20:12:37 +00:00
function changepassword () : void {
2011-12-13 06:00:42 +00:00
2021-02-23 06:01:27 +00:00
if ( Config :: get ( Config :: FORBID_PASSWORD_CHANGES )) {
print " ERROR: " . format_error ( " Access forbidden. " );
2019-12-06 14:45:22 +00:00
return ;
}
2017-12-03 20:35:38 +00:00
$old_pw = clean ( $_POST [ " old_password " ]);
$new_pw = clean ( $_POST [ " new_password " ]);
2020-09-14 17:53:00 +00:00
$new_unclean_pw = $_POST [ " new_password " ];
2017-12-03 20:35:38 +00:00
$con_pw = clean ( $_POST [ " confirm_password " ]);
2011-12-13 06:00:42 +00:00
2020-09-14 17:53:00 +00:00
if ( $new_unclean_pw != $new_pw ) {
print " ERROR: " . format_error ( " New password contains disallowed characters. " );
return ;
}
2019-10-09 06:04:51 +00:00
if ( $old_pw == $new_pw ) {
print " ERROR: " . format_error ( " New password must be different from the old one. " );
return ;
}
2011-12-13 06:00:42 +00:00
if ( $old_pw == " " ) {
2015-07-06 09:10:15 +00:00
print " ERROR: " . format_error ( " Old password cannot be blank. " );
2011-12-13 06:00:42 +00:00
return ;
}
if ( $new_pw == " " ) {
2015-07-06 09:10:15 +00:00
print " ERROR: " . format_error ( " New password cannot be blank. " );
2011-12-13 06:00:42 +00:00
return ;
}
if ( $new_pw != $con_pw ) {
2015-07-06 09:10:15 +00:00
print " ERROR: " . format_error ( " Entered passwords do not match. " );
2011-12-13 06:00:42 +00:00
return ;
}
2013-04-18 08:27:34 +00:00
$authenticator = PluginHost :: getInstance () -> get_plugin ( $_SESSION [ " auth_module " ]);
2011-12-13 06:00:42 +00:00
2023-10-27 19:21:30 +00:00
if ( implements_interface ( $authenticator , " IAuthModule2 " )) {
/** @var IAuthModule2 $authenticator */
2015-07-06 09:10:15 +00:00
print format_notice ( $authenticator -> change_password ( $_SESSION [ " uid " ], $old_pw , $new_pw ));
2012-01-23 08:20:09 +00:00
} else {
2015-07-06 09:10:15 +00:00
print " ERROR: " . format_error ( " Function not supported by authentication module. " );
2012-01-23 08:20:09 +00:00
}
2011-12-13 06:00:42 +00:00
}
2021-11-14 20:12:37 +00:00
function saveconfig () : void {
2017-12-03 20:35:38 +00:00
$boolean_prefs = explode ( " , " , clean ( $_POST [ " boolean_prefs " ]));
2013-03-19 19:14:23 +00:00
foreach ( $boolean_prefs as $pref ) {
2022-08-12 18:21:38 +00:00
$_POST [ $pref ] ? ? = 'false' ;
2013-03-19 19:14:23 +00:00
}
2013-04-04 14:15:37 +00:00
$need_reload = false ;
2011-12-13 06:00:42 +00:00
foreach ( array_keys ( $_POST ) as $pref_name ) {
2017-12-02 09:01:56 +00:00
$value = $_POST [ $pref_name ];
2011-12-13 06:00:42 +00:00
2018-12-07 07:35:46 +00:00
switch ( $pref_name ) {
2021-02-25 11:45:11 +00:00
case Prefs :: DIGEST_PREFERRED_TIME :
if ( get_pref ( Prefs :: DIGEST_PREFERRED_TIME ) != $value ) {
2012-01-31 11:52:33 +00:00
2018-12-07 07:35:46 +00:00
$sth = $this -> pdo -> prepare ( " UPDATE ttrss_users SET
2021-02-25 09:46:13 +00:00
last_digest_sent = NULL WHERE id = ? " );
2018-12-07 07:35:46 +00:00
$sth -> execute ([ $_SESSION [ 'uid' ]]);
2012-01-31 11:52:33 +00:00
2018-12-07 07:35:46 +00:00
}
break ;
2021-02-25 11:45:11 +00:00
case Prefs :: USER_LANGUAGE :
2018-12-07 07:35:46 +00:00
if ( ! $need_reload ) $need_reload = $_SESSION [ " language " ] != $value ;
break ;
2012-01-31 11:52:33 +00:00
2021-02-25 11:45:11 +00:00
case Prefs :: USER_CSS_THEME :
2018-12-07 07:35:46 +00:00
if ( ! $need_reload ) $need_reload = get_pref ( $pref_name ) != $value ;
break ;
2020-12-21 05:50:34 +00:00
2021-02-25 11:45:11 +00:00
case Prefs :: BLACKLISTED_TAGS :
2020-12-21 05:50:34 +00:00
$cats = FeedItem_Common :: normalize_categories ( explode ( " , " , $value ));
asort ( $cats );
$value = implode ( " , " , $cats );
break ;
2013-04-04 14:15:37 +00:00
}
2011-12-13 06:00:42 +00:00
2021-02-25 09:46:13 +00:00
if ( Prefs :: is_valid ( $pref_name )) {
Prefs :: set ( $pref_name , $value , $_SESSION [ " uid " ], $_SESSION [ " profile " ] ? ? null );
}
2011-12-13 06:00:42 +00:00
}
2013-04-04 14:15:37 +00:00
if ( $need_reload ) {
print " PREFS_NEED_RELOAD " ;
} else {
print __ ( " The configuration was saved. " );
}
2011-12-13 06:00:42 +00:00
}
2021-11-14 20:12:37 +00:00
function changePersonalData () : void {
2011-12-13 06:00:42 +00:00
2021-03-02 10:20:41 +00:00
$user = ORM :: for_table ( 'ttrss_users' ) -> find_one ( $_SESSION [ 'uid' ]);
$new_email = clean ( $_POST [ 'email' ]);
2011-12-13 06:00:42 +00:00
2021-03-02 10:20:41 +00:00
if ( $user ) {
$user -> full_name = clean ( $_POST [ 'full_name' ]);
2019-10-09 06:04:51 +00:00
2021-03-17 16:50:04 +00:00
if ( $user -> email != $new_email ) {
2022-11-12 16:20:59 +00:00
Logger :: log ( E_USER_NOTICE , " Email address of user { $user -> login } has been changed to { $new_email } . " );
2021-03-02 10:20:41 +00:00
2021-03-17 16:50:04 +00:00
if ( $user -> email ) {
$mailer = new Mailer ();
2019-10-09 06:04:51 +00:00
2021-03-17 16:50:04 +00:00
$tpl = new Templator ();
2019-10-09 06:04:51 +00:00
2021-03-17 16:50:04 +00:00
$tpl -> readTemplateFromFile ( " mail_change_template.txt " );
2019-10-09 06:04:51 +00:00
2021-03-17 16:50:04 +00:00
$tpl -> setVariable ( 'LOGIN' , $user -> login );
$tpl -> setVariable ( 'NEWMAIL' , $new_email );
2022-11-28 17:40:42 +00:00
$tpl -> setVariable ( 'TTRSS_HOST' , Config :: get_self_url ());
2019-10-09 06:04:51 +00:00
2021-03-17 16:50:04 +00:00
$tpl -> addBlock ( 'message' );
2019-10-09 06:04:51 +00:00
2021-03-17 16:50:04 +00:00
$tpl -> generateOutputToString ( $message );
2019-10-09 06:04:51 +00:00
2021-03-17 16:50:04 +00:00
$mailer -> mail ([ " to_name " => $user -> login ,
" to_address " => $user -> email ,
" subject " => " [tt-rss] Email address change notification " ,
" message " => $message ]);
}
2019-10-09 06:04:51 +00:00
2021-03-02 10:20:41 +00:00
$user -> email = $new_email ;
2019-10-09 06:04:51 +00:00
}
2021-03-02 10:20:41 +00:00
$user -> save ();
}
2011-12-13 06:00:42 +00:00
print __ ( " Your personal data has been saved. " );
}
2021-11-14 20:12:37 +00:00
function resetconfig () : void {
2022-06-05 08:14:42 +00:00
Prefs :: reset ( $_SESSION [ " uid " ], $_SESSION [ " profile " ] ? ? null );
2011-12-13 06:00:42 +00:00
2021-02-25 09:46:13 +00:00
print " PREFS_NEED_RELOAD " ;
2011-12-13 06:00:42 +00:00
}
2021-11-14 20:12:37 +00:00
private function index_auth_personal () : void {
2011-12-13 06:00:42 +00:00
2021-03-02 10:20:41 +00:00
$user = ORM :: for_table ( 'ttrss_users' ) -> find_one ( $_SESSION [ 'uid' ]);
2011-12-13 06:00:42 +00:00
2021-02-14 08:29:38 +00:00
?>
< form dojoType = 'dijit.form.Form' >
2011-12-13 06:00:42 +00:00
2023-10-25 09:55:09 +00:00
< ? = \Controls\hidden_tag ( " op " , " Pref_Prefs " ) ?>
2021-03-02 10:20:41 +00:00
< ? = \Controls\hidden_tag ( " method " , " changePersonalData " ) ?>
2012-05-16 07:56:21 +00:00
2021-02-18 09:27:26 +00:00
< script type = " dojo/method " event = " onSubmit " args = " evt " >
evt . preventDefault ();
if ( this . validate ()) {
Notify . progress ( 'Saving data...' , true );
2021-02-19 10:44:56 +00:00
xhr . post ( " backend.php " , this . getValues (), ( reply ) => {
Notify . info ( reply );
2021-02-18 09:27:26 +00:00
})
}
2021-02-14 08:29:38 +00:00
</ script >
< fieldset >
< label >< ? = __ ( 'Full name:' ) ?> </label>
2021-03-02 10:20:41 +00:00
< input dojoType = 'dijit.form.ValidationTextBox' name = 'full_name' required = '1' value = " <?= htmlspecialchars( $user->full_name ) ?> " >
2021-02-14 08:29:38 +00:00
</ fieldset >
< fieldset >
< label >< ? = __ ( 'E-mail:' ) ?> </label>
2021-03-02 10:20:41 +00:00
< input dojoType = 'dijit.form.ValidationTextBox' name = 'email' required = '1' value = " <?= htmlspecialchars( $user->email ) ?> " >
2021-02-14 08:29:38 +00:00
</ fieldset >
< hr />
< button dojoType = 'dijit.form.Button' type = 'submit' class = 'alt-primary' >
2021-03-05 12:16:41 +00:00
< ? = \Controls\icon ( " save " ) ?>
< ? = __ ( " Save " ) ?>
2021-02-14 08:29:38 +00:00
</ button >
</ form >
< ? php
}
2019-02-20 14:21:32 +00:00
2021-11-14 20:12:37 +00:00
private function index_auth_password () : void {
2012-12-27 11:14:44 +00:00
if ( $_SESSION [ " auth_module " ]) {
2013-04-18 08:27:34 +00:00
$authenticator = PluginHost :: getInstance () -> get_plugin ( $_SESSION [ " auth_module " ]);
2012-08-16 14:21:35 +00:00
} else {
$authenticator = false ;
}
2023-10-27 19:21:30 +00:00
if ( $authenticator && implements_interface ( $authenticator , " IAuthModule2 " )) {
2021-02-14 08:29:38 +00:00
?>
2011-12-13 06:00:42 +00:00
2021-02-14 08:29:38 +00:00
< div style = 'display : none' id = 'pwd_change_infobox' ></ div >
2011-12-13 06:00:42 +00:00
2021-02-14 08:29:38 +00:00
< form dojoType = 'dijit.form.Form' >
2011-12-13 06:00:42 +00:00
2023-10-25 09:55:09 +00:00
< ? = \Controls\hidden_tag ( " op " , " Pref_Prefs " ) ?>
2021-02-16 11:23:00 +00:00
< ? = \Controls\hidden_tag ( " method " , " changepassword " ) ?>
2015-07-06 09:10:15 +00:00
2021-02-18 09:27:26 +00:00
<!-- TODO : return JSON the backend call -->
< script type = " dojo/method " event = " onSubmit " args = " evt " >
2021-02-14 08:29:38 +00:00
evt . preventDefault ();
if ( this . validate ()) {
2021-02-18 09:27:26 +00:00
Notify . progress ( 'Saving data...' , true );
2021-02-19 10:44:56 +00:00
xhr . post ( " backend.php " , this . getValues (), ( reply ) => {
2021-02-18 09:27:26 +00:00
Notify . close ();
2021-02-19 10:44:56 +00:00
if ( reply . indexOf ( 'ERROR: ' ) == 0 ) {
2015-07-06 09:10:15 +00:00
2021-02-19 03:58:50 +00:00
App . byId ( 'pwd_change_infobox' ) . innerHTML =
2021-02-19 10:44:56 +00:00
reply . replace ( 'ERROR: ' , '' );
2015-07-06 09:10:15 +00:00
2021-02-18 09:27:26 +00:00
} else {
2021-02-19 03:58:50 +00:00
App . byId ( 'pwd_change_infobox' ) . innerHTML =
2021-02-19 10:44:56 +00:00
reply . replace ( 'ERROR: ' , '' );
2011-12-13 06:00:42 +00:00
2021-02-19 03:58:50 +00:00
const warn = App . byId ( 'default_pass_warning' );
2021-02-18 09:27:26 +00:00
if ( warn ) Element . hide ( warn );
2021-02-14 08:29:38 +00:00
}
2021-02-18 09:27:26 +00:00
2021-02-18 18:51:18 +00:00
Element . show ( 'pwd_change_infobox' );
2021-02-18 09:27:26 +00:00
})
2021-02-14 08:29:38 +00:00
}
</ script >
2012-09-04 08:39:33 +00:00
2021-02-14 08:29:38 +00:00
< fieldset >
< label >< ? = __ ( " Old password: " ) ?> </label>
< input dojoType = 'dijit.form.ValidationTextBox' type = 'password' required = '1' name = 'old_password' >
</ fieldset >
2011-12-13 06:00:42 +00:00
2021-02-14 08:29:38 +00:00
< fieldset >
< label >< ? = __ ( " New password: " ) ?> </label>
< input dojoType = 'dijit.form.ValidationTextBox' type = 'password' regexp = '^[^<>]+' required = '1' name = 'new_password' >
</ fieldset >
2011-12-13 06:00:42 +00:00
2021-02-14 08:29:38 +00:00
< fieldset >
< label >< ? = __ ( " Confirm password: " ) ?> </label>
< input dojoType = 'dijit.form.ValidationTextBox' type = 'password' regexp = '^[^<>]+' required = '1' name = 'confirm_password' >
</ fieldset >
2011-12-13 06:00:42 +00:00
2021-02-14 08:29:38 +00:00
< hr />
2019-02-20 14:21:32 +00:00
2021-02-14 08:29:38 +00:00
< button dojoType = 'dijit.form.Button' type = 'submit' class = 'alt-primary' >
2021-03-05 12:16:41 +00:00
< ? = \Controls\icon ( " security " ) ?>
2021-02-14 08:29:38 +00:00
< ? = __ ( " Change password " ) ?>
</ button >
</ form >
2011-12-13 06:00:42 +00:00
2021-02-14 08:29:38 +00:00
< ? php
2011-12-13 06:00:42 +00:00
2020-02-18 08:51:04 +00:00
} else {
print_notice ( T_sprintf ( " Authentication module used for this session (<b>%s</b>) does not provide an ability to set passwords. " ,
$_SESSION [ " auth_module " ]));
}
2021-02-14 08:29:38 +00:00
}
2019-11-01 09:23:11 +00:00
2021-11-14 20:12:37 +00:00
private function index_auth_app_passwords () : void {
2021-03-06 20:51:48 +00:00
print_notice ( " Separate passwords used for API clients. Required if you enable OTP. " );
2021-02-14 08:29:38 +00:00
?>
2019-11-01 09:23:11 +00:00
2021-02-14 08:29:38 +00:00
< div id = 'app_passwords_holder' >
< ? php $this -> appPasswordList () ?>
</ div >
2019-11-01 09:23:11 +00:00
2021-02-14 08:29:38 +00:00
< hr >
2019-11-01 09:23:11 +00:00
2021-02-14 08:29:38 +00:00
< button style = 'float : left' class = 'alt-primary' dojoType = 'dijit.form.Button' onclick = " Helpers.AppPasswords.generate() " >
2021-03-05 12:16:41 +00:00
< ? = \Controls\icon ( " add " ) ?>
< ? = __ ( 'Generate password' ) ?>
2021-02-14 08:29:38 +00:00
</ button >
2019-11-01 12:03:57 +00:00
2021-02-14 08:29:38 +00:00
< button style = 'float : left' class = 'alt-danger' dojoType = 'dijit.form.Button'
onclick = " Helpers.AppPasswords.removeSelected() " >
2021-03-05 12:16:41 +00:00
< ? = \Controls\icon ( " delete " ) ?>
< ? = __ ( 'Remove selected' ) ?>
2021-02-14 08:29:38 +00:00
</ button >
2019-11-01 12:03:57 +00:00
2021-02-14 08:29:38 +00:00
< ? php
}
2019-11-01 12:03:57 +00:00
2021-11-14 20:12:37 +00:00
private function index_auth_2fa () : void {
2021-02-26 16:16:17 +00:00
$otp_enabled = UserHelper :: is_otp_enabled ( $_SESSION [ " uid " ]);
2012-09-03 14:33:46 +00:00
2021-02-14 08:29:38 +00:00
if ( $_SESSION [ " auth_module " ] == " auth_internal " ) {
2020-02-18 08:51:04 +00:00
if ( $otp_enabled ) {
print_warning ( " One time passwords are currently enabled. Enter your current password below to disable. " );
2021-02-14 08:29:38 +00:00
?>
< form dojoType = 'dijit.form.Form' >
2023-10-25 09:55:09 +00:00
< ? = \Controls\hidden_tag ( " op " , " Pref_Prefs " ) ?>
2021-02-16 11:23:00 +00:00
< ? = \Controls\hidden_tag ( " method " , " otpdisable " ) ?>
2021-02-14 08:29:38 +00:00
2021-02-18 09:27:26 +00:00
<!-- TODO : return JSON from the backend call -->
< script type = " dojo/method " event = " onSubmit " args = " evt " >
2021-02-14 08:29:38 +00:00
evt . preventDefault ();
if ( this . validate ()) {
2021-02-18 09:27:26 +00:00
Notify . progress ( 'Saving data...' , true );
2021-02-19 10:44:56 +00:00
xhr . post ( " backend.php " , this . getValues (), ( reply ) => {
2021-02-18 09:27:26 +00:00
Notify . close ();
2021-02-19 10:44:56 +00:00
if ( reply . indexOf ( 'ERROR: ' ) == 0 ) {
Notify . error ( reply . replace ( 'ERROR: ' , '' ));
2021-02-18 09:27:26 +00:00
} else {
window . location . reload ();
2021-02-14 08:29:38 +00:00
}
2021-02-18 09:27:26 +00:00
})
2021-02-14 08:29:38 +00:00
}
</ script >
2020-02-18 08:51:04 +00:00
2021-02-14 08:29:38 +00:00
< fieldset >
< label >< ? = __ ( " Your password: " ) ?> </label>
< input dojoType = 'dijit.form.ValidationTextBox' type = 'password' required = '1' name = 'password' >
</ fieldset >
2012-09-04 08:39:33 +00:00
2021-02-14 08:29:38 +00:00
< hr />
2019-08-02 05:03:20 +00:00
2021-02-14 08:29:38 +00:00
< button dojoType = 'dijit.form.Button' type = 'submit' class = 'alt-danger' >
2021-03-05 12:16:41 +00:00
< ? = \Controls\icon ( " lock_open " ) ?>
2021-02-14 08:29:38 +00:00
< ? = __ ( " Disable OTP " ) ?>
</ button >
2012-09-04 08:39:33 +00:00
2021-02-14 08:29:38 +00:00
</ form >
2012-09-04 08:39:33 +00:00
2021-02-14 08:29:38 +00:00
< ? php
2012-09-04 08:39:33 +00:00
2020-02-18 08:51:04 +00:00
} else {
2012-09-04 08:39:33 +00:00
2021-03-06 20:51:48 +00:00
print " <img src= " . ( $this -> _get_otp_qrcode_img ()) . " > " ;
2012-09-04 08:39:33 +00:00
2021-03-06 20:51:48 +00:00
print_notice ( " You will need to generate app passwords for API clients if you enable OTP. " );
2012-09-03 14:33:46 +00:00
2021-02-26 16:16:17 +00:00
$otp_secret = UserHelper :: get_otp_secret ( $_SESSION [ " uid " ]);
2021-02-14 08:29:38 +00:00
?>
2021-02-16 11:23:00 +00:00
< form dojoType = 'dijit.form.Form' >
2021-02-14 08:29:38 +00:00
2023-10-25 09:55:09 +00:00
< ? = \Controls\hidden_tag ( " op " , " Pref_Prefs " ) ?>
2021-02-16 11:23:00 +00:00
< ? = \Controls\hidden_tag ( " method " , " otpenable " ) ?>
2021-02-14 08:29:38 +00:00
< fieldset >
2021-03-29 16:22:03 +00:00
< label >< ? = __ ( " OTP secret: " ) ?> </label>
< code >< ? = $this -> format_otp_secret ( $otp_secret ) ?> </code>
2021-02-14 08:29:38 +00:00
</ fieldset >
2021-02-18 09:27:26 +00:00
<!-- TODO : return JSON from the backend call -->
< script type = " dojo/method " event = " onSubmit " args = " evt " >
2021-02-14 08:29:38 +00:00
evt . preventDefault ();
if ( this . validate ()) {
Notify . progress ( 'Saving data...' , true );
2021-02-19 10:44:56 +00:00
xhr . post ( " backend.php " , this . getValues (), ( reply ) => {
2021-02-18 09:27:26 +00:00
Notify . close ();
2021-02-14 08:29:38 +00:00
2021-02-19 10:44:56 +00:00
if ( reply . indexOf ( 'ERROR:' ) == 0 ) {
Notify . error ( reply . replace ( 'ERROR:' , '' ));
2021-02-18 09:27:26 +00:00
} else {
window . location . reload ();
2021-02-14 08:29:38 +00:00
}
2021-02-18 09:27:26 +00:00
})
2021-02-14 08:29:38 +00:00
}
</ script >
2019-11-01 07:32:58 +00:00
2021-02-14 08:29:38 +00:00
< fieldset >
< label >< ? = __ ( " Your password: " ) ?> </label>
< input dojoType = 'dijit.form.ValidationTextBox' type = 'password' required = '1' name = 'password' >
</ fieldset >
2012-09-04 08:39:33 +00:00
2021-02-14 08:29:38 +00:00
< fieldset >
2021-03-29 16:22:03 +00:00
< label >< ? = __ ( " Verification code: " ) ?> </label>
2021-02-14 08:29:38 +00:00
< input dojoType = 'dijit.form.ValidationTextBox' autocomplete = 'off' required = '1' name = 'otp' >
</ fieldset >
2012-09-04 08:39:33 +00:00
2021-02-14 08:29:38 +00:00
< hr />
2012-09-03 14:33:46 +00:00
2021-02-14 08:29:38 +00:00
< button dojoType = 'dijit.form.Button' type = 'submit' class = 'alt-primary' >
2021-03-05 12:16:41 +00:00
< ? = \Controls\icon ( " lock " ) ?>
2021-02-14 08:29:38 +00:00
< ? = __ ( " Enable OTP " ) ?>
</ button >
2012-09-03 14:33:46 +00:00
2021-02-14 08:29:38 +00:00
</ form >
< ? php
2012-09-03 14:33:46 +00:00
}
2020-02-18 08:51:04 +00:00
} else {
print_notice ( " OTP is only available when using <b>auth_internal</b> authentication module. " );
2011-12-13 06:00:42 +00:00
}
2021-02-14 08:29:38 +00:00
}
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
function index_auth () : void {
2021-02-14 08:29:38 +00:00
?>
< div dojoType = 'dijit.layout.TabContainer' >
< div dojoType = 'dijit.layout.ContentPane' title = " <?= __('Personal data') ?> " >
< ? php $this -> index_auth_personal () ?>
</ div >
< div dojoType = 'dijit.layout.ContentPane' title = " <?= __('Password') ?> " >
< ? php $this -> index_auth_password () ?>
</ div >
< div dojoType = 'dijit.layout.ContentPane' title = " <?= __('App passwords') ?> " >
2023-06-14 17:19:18 +00:00
< ? php if ( PluginHost :: getInstance () -> get_plugin ( 'auth_internal' )) { ?>
< ? php $this -> index_auth_app_passwords () ?>
< ? php } else { ?>
< ? = format_warning ( " App passwords are only available if <b>auth_internal<b> plugin is enabled. " ); ?>
< ? php } ?>
2021-02-14 08:29:38 +00:00
</ div >
< div dojoType = 'dijit.layout.ContentPane' title = " <?= __('Authenticator (OTP)') ?> " >
< ? php $this -> index_auth_2fa () ?>
</ div >
</ div >
< ? php
}
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
private function index_prefs_list () : void {
2021-02-05 20:41:32 +00:00
$profile = $_SESSION [ " profile " ] ? ? null ;
2018-12-04 07:47:01 +00:00
if ( $profile ) {
2013-03-24 16:50:20 +00:00
print_notice ( __ ( " Some preferences are only available in default profile. " ));
2011-12-13 06:00:42 +00:00
}
2021-11-14 20:12:37 +00:00
/** @var array<string, array{'type_hint': Config::T_*, 'value': bool|int|string, 'help_text': string, 'short_desc': string}> */
2019-02-21 19:08:21 +00:00
$prefs_available = [];
2021-11-14 20:12:37 +00:00
/** @var array<int, string> */
2019-02-21 19:08:21 +00:00
$listed_boolean_prefs = [];
2013-03-19 19:14:23 +00:00
2021-11-14 20:12:37 +00:00
foreach ( Prefs :: get_all ( $_SESSION [ " uid " ], $profile ) as $pref ) {
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
if ( in_array ( $pref [ " pref_name " ], $this -> pref_blacklist )) {
2011-12-13 06:00:42 +00:00
continue ;
}
2021-11-14 20:12:37 +00:00
if ( $profile && in_array ( $pref [ " pref_name " ], Prefs :: _PROFILE_BLACKLIST )) {
2019-02-21 19:08:21 +00:00
continue ;
}
2013-04-02 12:20:06 +00:00
2021-11-14 20:12:37 +00:00
$pref_name = $pref [ " pref_name " ];
2021-02-15 13:07:22 +00:00
$short_desc = $this -> _get_short_desc ( $pref_name );
2013-04-02 12:20:06 +00:00
2019-02-21 19:08:21 +00:00
if ( ! $short_desc )
2011-12-13 06:00:42 +00:00
continue ;
2019-02-21 19:08:21 +00:00
$prefs_available [ $pref_name ] = [
2021-11-14 20:12:37 +00:00
'type_hint' => $pref [ 'type_hint' ],
'value' => $pref [ 'value' ],
2021-02-15 13:07:22 +00:00
'help_text' => $this -> _get_help_text ( $pref_name ),
2019-02-21 19:08:21 +00:00
'short_desc' => $short_desc
];
}
2011-12-13 06:00:42 +00:00
2019-02-21 19:08:21 +00:00
foreach ( array_keys ( $this -> pref_item_map ) as $section ) {
2011-12-13 06:00:42 +00:00
2019-02-21 19:08:21 +00:00
print " <h2> $section </h2> " ;
2011-12-13 06:00:42 +00:00
2019-02-21 19:08:21 +00:00
foreach ( $this -> pref_item_map [ $section ] as $pref_name ) {
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
if ( $pref_name == self :: BLOCK_SEPARATOR && ! $profile ) {
2019-02-21 19:08:21 +00:00
print " <hr/> " ;
continue ;
}
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
if ( $pref_name == Prefs :: DEFAULT_SEARCH_LANGUAGE && Config :: get ( Config :: DB_TYPE ) != " pgsql " ) {
2019-04-10 10:03:26 +00:00
continue ;
}
2021-02-14 08:29:38 +00:00
if ( isset ( $prefs_available [ $pref_name ])) {
$item = $prefs_available [ $pref_name ];
2013-04-29 11:54:23 +00:00
2019-02-25 16:22:20 +00:00
print " <fieldset class='prefs'> " ;
2011-12-13 06:00:42 +00:00
2019-02-25 14:15:05 +00:00
print " <label for='CB_ $pref_name '> " ;
2019-02-21 19:08:21 +00:00
print $item [ 'short_desc' ] . " : " ;
print " </label> " ;
2011-12-13 06:00:42 +00:00
2019-02-21 19:08:21 +00:00
$value = $item [ 'value' ];
2021-02-25 09:46:13 +00:00
$type_hint = $item [ 'type_hint' ];
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
if ( $pref_name == Prefs :: USER_LANGUAGE ) {
2021-02-16 11:23:00 +00:00
print \Controls\select_hash ( $pref_name , $value , get_translations (),
2021-02-16 13:59:21 +00:00
[ " style " => 'width : 220px; margin : 0px' ]);
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
} else if ( $pref_name == Prefs :: USER_TIMEZONE ) {
2013-03-28 17:04:29 +00:00
2019-02-21 19:08:21 +00:00
$timezones = explode ( " \n " , file_get_contents ( " lib/timezones.txt " ));
2013-03-28 17:04:29 +00:00
2021-02-16 13:59:21 +00:00
print \Controls\select_tag ( $pref_name , $value , $timezones , [ " dojoType " => " dijit.form.FilteringSelect " ]);
2020-12-21 05:50:34 +00:00
2021-11-14 20:12:37 +00:00
} else if ( $pref_name == Prefs :: BLACKLISTED_TAGS ) { # TODO: other possible <textarea> prefs go here
2020-12-21 05:50:34 +00:00
print " <div> " ;
print " <textarea dojoType='dijit.form.SimpleTextarea' rows='4'
style = 'width: 500px; font-size : 12px;'
name = '$pref_name' > $value </ textarea >< br /> " ;
print " <div class='help-text-bottom text-muted'> " . $this -> pref_help_bottom [ $pref_name ] . " </div> " ;
print " </div> " ;
2021-11-14 20:12:37 +00:00
} else if ( $pref_name == Prefs :: USER_CSS_THEME ) {
2017-01-26 19:37:22 +00:00
2022-08-12 15:31:19 +00:00
$theme_files = array_map ( " basename " , [
... glob ( " themes/*.php " ) ? : [],
... glob ( " themes/*.css " ) ? : [],
... glob ( " themes.local/*.css " ) ? : [],
]);
2018-12-06 11:49:33 +00:00
2021-03-05 12:16:41 +00:00
asort ( $theme_files );
2018-12-06 11:49:33 +00:00
2021-03-05 12:16:41 +00:00
$themes = [ " " => __ ( " default " ) ];
2018-12-06 11:49:33 +00:00
2021-03-05 12:16:41 +00:00
foreach ( $theme_files as $file ) {
$themes [ $file ] = basename ( $file , " .css " );
2019-02-21 19:08:21 +00:00
}
2021-03-05 12:16:41 +00:00
?>
2019-02-21 13:21:16 +00:00
2021-03-05 12:16:41 +00:00
< ? = \Controls\select_hash ( $pref_name , $value , $themes ) ?>
< ? = \Controls\button_tag ( \Controls\icon ( " palette " ) . " " . __ ( " Customize " ), " " ,
[ " onclick " => " Helpers.Prefs.customizeCSS() " ]) ?>
< ? = \Controls\button_tag ( \Controls\icon ( " open_in_new " ) . " " . __ ( " More themes... " ), " " ,
[ " class " => " alt-info " , " onclick " => " window.open( \" https://tt-rss.org/wiki/Themes \" ) " ]) ?>
2011-12-13 06:00:42 +00:00
2021-03-05 12:16:41 +00:00
< ? php
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
} else if ( $pref_name == Prefs :: DEFAULT_UPDATE_INTERVAL ) {
2011-12-13 06:00:42 +00:00
2019-02-21 19:08:21 +00:00
global $update_intervals_nodefault ;
2013-03-19 19:14:23 +00:00
2021-02-16 11:23:00 +00:00
print \Controls\select_hash ( $pref_name , $value , $update_intervals_nodefault );
2021-11-14 20:12:37 +00:00
} else if ( $pref_name == Prefs :: DEFAULT_SEARCH_LANGUAGE ) {
2019-04-10 10:03:26 +00:00
2021-02-16 11:23:00 +00:00
print \Controls\select_tag ( $pref_name , $value , Pref_Feeds :: get_ts_languages ());
2011-12-13 06:00:42 +00:00
2021-02-25 09:46:13 +00:00
} else if ( $type_hint == Config :: T_BOOL ) {
2019-02-21 19:08:21 +00:00
array_push ( $listed_boolean_prefs , $pref_name );
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
if ( $pref_name == Prefs :: PURGE_UNREAD_ARTICLES && Config :: get ( Config :: FORCE_ARTICLE_PURGE ) != 0 ) {
2021-02-21 20:18:32 +00:00
$is_disabled = true ;
$is_checked = true ;
2019-02-21 19:08:21 +00:00
} else {
2021-02-21 20:18:32 +00:00
$is_disabled = false ;
$is_checked = ( $value == " true " );
2019-02-21 19:08:21 +00:00
}
2011-12-13 06:00:42 +00:00
2021-02-21 20:18:32 +00:00
print \Controls\checkbox_tag ( $pref_name , $is_checked , " true " ,
[ " disabled " => $is_disabled ], " CB_ $pref_name " );
2011-12-13 06:00:42 +00:00
2021-03-02 16:21:21 +00:00
if ( $pref_name == Prefs :: DIGEST_ENABLE ) {
2021-03-05 12:16:41 +00:00
print \Controls\button_tag ( \Controls\icon ( " info " ) . " " . __ ( 'Preview' ), '' ,
[ 'onclick' => 'Helpers.Digest.preview()' , 'style' => 'margin-left : 10px' ]);
2021-03-02 16:21:21 +00:00
}
2021-11-14 20:12:37 +00:00
} else if ( in_array ( $pref_name , [ Prefs :: FRESH_ARTICLE_MAX_AGE ,
2024-01-09 08:45:40 +00:00
Prefs :: PURGE_OLD_DAYS , Prefs :: LONG_DATE_FORMAT , Prefs :: SHORT_DATE_FORMAT , Prefs :: DIGEST_MIN_SCORE ])) {
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
if ( $pref_name == Prefs :: PURGE_OLD_DAYS && Config :: get ( Config :: FORCE_ARTICLE_PURGE ) != 0 ) {
2021-02-21 20:18:32 +00:00
$attributes = [ " disabled " => true , " required " => true ];
2021-02-22 18:47:48 +00:00
$value = Config :: get ( Config :: FORCE_ARTICLE_PURGE );
2019-02-21 19:08:21 +00:00
} else {
2021-02-21 20:18:32 +00:00
$attributes = [ " required " => true ];
2019-02-21 19:08:21 +00:00
}
2011-12-13 06:00:42 +00:00
2021-02-25 09:46:13 +00:00
if ( $type_hint == Config :: T_INT )
2021-02-21 20:18:32 +00:00
print \Controls\number_spinner_tag ( $pref_name , $value , $attributes );
2019-02-21 19:08:21 +00:00
else
2021-02-21 20:18:32 +00:00
print \Controls\input_tag ( $pref_name , $value , " text " , $attributes );
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
} else if ( $pref_name == Prefs :: SSL_CERT_SERIAL ) {
2019-02-21 19:08:21 +00:00
2021-02-21 20:18:32 +00:00
print \Controls\input_tag ( $pref_name , $value , " text " , [ " readonly " => true ], " SSL_CERT_SERIAL " );
2019-02-21 19:08:21 +00:00
2021-03-01 07:20:21 +00:00
$cert_serial = htmlspecialchars ( self :: _get_ssl_certificate_id ());
2021-02-21 20:18:32 +00:00
$has_serial = ( $cert_serial ) ? true : false ;
2019-02-21 19:08:21 +00:00
2021-03-05 12:16:41 +00:00
print \Controls\button_tag ( \Controls\icon ( " security " ) . " " . __ ( 'Register' ), " " , [
2021-02-21 20:18:32 +00:00
" disabled " => ! $has_serial ,
" onclick " => " dijit.byId('SSL_CERT_SERIAL').attr('value', ' $cert_serial ') " ]);
2019-02-21 19:08:21 +00:00
2021-03-05 12:16:41 +00:00
print \Controls\button_tag ( \Controls\icon ( " clear " ) . " " . __ ( 'Clear' ), " " , [
2021-02-21 20:18:32 +00:00
" class " => " alt-danger " ,
" onclick " => " dijit.byId('SSL_CERT_SERIAL').attr('value', '') " ]);
2019-02-21 19:08:21 +00:00
2021-02-21 20:18:32 +00:00
print \Controls\button_tag ( \Controls\icon ( " help " ) . " " . __ ( " More info... " ), " " , [
" class " => " alt-info " ,
" onclick " => " window.open('https://tt-rss.org/wiki/SSL%20Certificate%20Authentication') " ]);
2019-02-21 19:08:21 +00:00
2021-03-02 16:21:21 +00:00
} else if ( $pref_name == Prefs :: DIGEST_PREFERRED_TIME ) {
2019-02-21 19:08:21 +00:00
print " <input dojoType= \" dijit.form.ValidationTextBox \"
id = \ " $pref_name\ " regexp = \ " [012]? \ d: \ d \ d \" placeHolder= \" 12:00 \"
name = \ " $pref_name\ " value = \ " $value\ " > " ;
$item [ 'help_text' ] .= " . " . T_sprintf ( " Current server time: %s " , date ( " H:i " ));
} else {
2021-02-25 09:46:13 +00:00
$regexp = ( $type_hint == Config :: T_INT ) ? 'regexp="^\d*$"' : '' ;
2019-02-21 19:08:21 +00:00
print " <input dojoType= \" dijit.form.ValidationTextBox \" $regexp name= \" $pref_name\ " value = \ " $value\ " > " ;
}
if ( $item [ 'help_text' ])
2019-03-08 07:11:57 +00:00
print " <div class='help-text text-muted'><label for='CB_ $pref_name '> " . $item [ 'help_text' ] . " </label></div> " ;
2019-02-21 19:08:21 +00:00
print " </fieldset> " ;
}
}
2011-12-13 06:00:42 +00:00
}
2021-02-16 11:23:00 +00:00
print \Controls\hidden_tag ( " boolean_prefs " , htmlspecialchars ( join ( " , " , $listed_boolean_prefs )));
2021-02-14 08:29:38 +00:00
}
2011-12-13 06:00:42 +00:00
2021-11-14 20:12:37 +00:00
private function index_prefs () : void {
2021-02-14 08:29:38 +00:00
?>
< form dojoType = 'dijit.form.Form' id = 'changeSettingsForm' >
2023-10-25 09:55:09 +00:00
< ? = \Controls\hidden_tag ( " op " , " Pref_Prefs " ) ?>
2021-02-16 11:23:00 +00:00
< ? = \Controls\hidden_tag ( " method " , " saveconfig " ) ?>
2021-02-18 09:27:26 +00:00
< script type = " dojo/method " event = " onSubmit " args = " evt, quit " >
2021-02-14 08:29:38 +00:00
if ( evt ) evt . preventDefault ();
if ( this . validate ()) {
2021-02-19 10:44:56 +00:00
xhr . post ( " backend.php " , this . getValues (), ( reply ) => {
2021-02-18 09:27:26 +00:00
if ( quit ) {
document . location . href = 'index.php' ;
} else {
2021-02-19 10:44:56 +00:00
if ( reply == 'PREFS_NEED_RELOAD' ) {
2021-02-18 09:27:26 +00:00
window . location . reload ();
2021-02-14 08:29:38 +00:00
} else {
2021-02-19 10:44:56 +00:00
Notify . info ( reply );
2021-02-14 08:29:38 +00:00
}
}
2021-02-18 09:27:26 +00:00
})
2021-02-14 08:29:38 +00:00
}
</ script >
< div dojoType = " dijit.layout.BorderContainer " gutters = " false " >
< div dojoType = " dijit.layout.ContentPane " region = " center " style = " overflow-y : auto " >
< ? php $this -> index_prefs_list () ?>
< ? php PluginHost :: getInstance () -> run_hooks ( PluginHost :: HOOK_PREFS_TAB_SECTION , " prefPrefsPrefsInside " ) ?>
</ div >
< div dojoType = " dijit.layout.ContentPane " region = " bottom " >
< div dojoType = " fox.form.ComboButton " type = " submit " class = " alt-primary " >
2021-03-05 12:16:41 +00:00
< span > < ? = __ ( 'Save configuration' ) ?> </span>
2021-02-14 08:29:38 +00:00
< div dojoType = " dijit.DropDownMenu " >
< div dojoType = " dijit.MenuItem " onclick = " dijit.byId('changeSettingsForm').onSubmit(null, true) " >
2021-03-05 12:16:41 +00:00
< ? = __ ( " Save and exit " ) ?>
2021-02-14 08:29:38 +00:00
</ div >
</ div >
</ div >
< button dojoType = " dijit.form.Button " onclick = " return Helpers.Profiles.edit() " >
2021-03-05 12:16:41 +00:00
< ? = \Controls\icon ( " settings " ) ?>
2021-02-14 08:29:38 +00:00
< ? = __ ( 'Manage profiles' ) ?>
</ button >
< button dojoType = " dijit.form.Button " class = " alt-danger " onclick = " return Helpers.Prefs.confirmReset() " >
2021-03-05 12:16:41 +00:00
< ? = \Controls\icon ( " clear " ) ?>
2021-02-14 08:29:38 +00:00
< ? = __ ( 'Reset to defaults' ) ?>
</ button >
< ? php PluginHost :: getInstance () -> run_hooks ( PluginHost :: HOOK_PREFS_TAB_SECTION , " prefPrefsPrefsOutside " ) ?>
</ div >
2013-04-04 10:53:36 +00:00
</ div >
2021-02-14 08:29:38 +00:00
</ form >
< ? php
}
2019-11-14 04:01:45 +00:00
2021-11-14 20:12:37 +00:00
function getPluginsList () : void {
2021-02-22 18:47:48 +00:00
$system_enabled = array_map ( " trim " , explode ( " , " , ( string ) Config :: get ( Config :: PLUGINS )));
2021-02-25 11:49:58 +00:00
$user_enabled = array_map ( " trim " , explode ( " , " , get_pref ( Prefs :: _ENABLED_PLUGINS )));
2021-02-14 08:29:38 +00:00
$tmppluginhost = new PluginHost ();
$tmppluginhost -> load_all ( $tmppluginhost :: KIND_ALL , $_SESSION [ " uid " ], true );
2012-12-24 20:45:10 +00:00
2021-03-06 15:14:25 +00:00
$rv = [];
2012-12-24 20:45:10 +00:00
foreach ( $tmppluginhost -> get_plugins () as $name => $plugin ) {
2012-12-25 06:02:08 +00:00
$about = $plugin -> about ();
2021-03-03 16:35:11 +00:00
$is_local = $tmppluginhost -> is_local ( $plugin );
2021-03-01 09:11:42 +00:00
$version = htmlspecialchars ( $this -> _get_plugin_version ( $plugin ));
2012-12-24 20:45:10 +00:00
2021-03-06 15:14:25 +00:00
array_push ( $rv , [
" name " => $name ,
" is_local " => $is_local ,
" system_enabled " => in_array ( $name , $system_enabled ),
" user_enabled " => in_array ( $name , $user_enabled ),
" has_data " => count ( $tmppluginhost -> get_all ( $plugin )) > 0 ,
" is_system " => ( bool )( $about [ 3 ] ? ? false ),
" version " => $version ,
" author " => $about [ 2 ] ? ? " " ,
" description " => $about [ 1 ] ? ? " " ,
" more_info " => $about [ 4 ] ? ? " " ,
]);
}
2021-03-03 16:35:11 +00:00
2021-03-06 15:14:25 +00:00
usort ( $rv , function ( $a , $b ) { return strcmp ( $a [ " name " ], $b [ " name " ]); });
2021-02-14 08:29:38 +00:00
2021-11-10 17:44:51 +00:00
print json_encode ([ 'plugins' => $rv , 'is_admin' => $_SESSION [ 'access_level' ] >= UserHelper :: ACCESS_LEVEL_ADMIN ]);
2021-02-14 08:29:38 +00:00
}
2021-11-14 20:12:37 +00:00
function index_plugins () : void {
2021-02-14 08:29:38 +00:00
?>
< form dojoType = " dijit.form.Form " id = " changePluginsForm " >
2012-12-24 20:45:10 +00:00
2023-10-25 09:55:09 +00:00
< ? = \Controls\hidden_tag ( " op " , " Pref_Prefs " ) ?>
2021-02-16 11:23:00 +00:00
< ? = \Controls\hidden_tag ( " method " , " setplugins " ) ?>
2012-12-24 20:45:10 +00:00
2021-02-14 08:29:38 +00:00
< div dojoType = " dijit.layout.BorderContainer " gutters = " false " >
2021-03-06 15:14:25 +00:00
< div region = " top " dojoType = 'fox.Toolbar' >
< div class = 'pull-right' >
< input name = " search " type = " search " onkeyup = 'Helpers.Plugins.search()' dojoType = " dijit.form.TextBox " >
< button dojoType = 'dijit.form.Button' onclick = 'Helpers.Plugins.search()' >
< ? = __ ( 'Search' ) ?>
</ button >
</ div >
< div dojoType = 'fox.form.DropDownButton' >
< span >< ? = __ ( 'Select' ) ?> </span>
< div dojoType = 'dijit.Menu' style = 'display: none' >
< div onclick = " Lists.select('prefs-plugin-list', true) "
dojoType = 'dijit.MenuItem' >< ? = __ ( 'All' ) ?> </div>
< div onclick = " Lists.select('prefs-plugin-list', false) "
dojoType = 'dijit.MenuItem' >< ? = __ ( 'None' ) ?> </div>
</ div >
</ div >
</ div >
2021-02-14 08:29:38 +00:00
< div dojoType = " dijit.layout.ContentPane " region = " center " style = " overflow-y : auto " >
2021-03-06 15:14:25 +00:00
< script type = " dojo/method " event = " onShow " >
Helpers . Plugins . reload ();
</ script >
<!-- < ? php
2021-02-14 08:29:38 +00:00
if ( ! empty ( $_SESSION [ " safe_mode " ])) {
print_error ( " You have logged in using safe mode, no user plugins will be actually enabled until you login again. " );
}
$feed_handler_whitelist = [ " Af_Comics " ];
2012-12-24 20:45:10 +00:00
2022-08-12 15:31:19 +00:00
$feed_handlers = [
... PluginHost :: getInstance () -> get_hooks ( PluginHost :: HOOK_FEED_FETCHED ),
... PluginHost :: getInstance () -> get_hooks ( PluginHost :: HOOK_FEED_PARSED ),
... PluginHost :: getInstance () -> get_hooks ( PluginHost :: HOOK_FETCH_FEED ),
];
2019-02-21 13:21:16 +00:00
2022-08-12 14:41:21 +00:00
$feed_handlers = array_filter ( $feed_handlers ,
fn ( $plugin ) => in_array ( get_class ( $plugin ), $feed_handler_whitelist ) === false );
2019-02-21 13:21:16 +00:00
2021-02-14 08:29:38 +00:00
if ( count ( $feed_handlers ) > 0 ) {
print_error (
T_sprintf ( " The following plugins use per-feed content hooks. This may cause excessive data usage and origin server load resulting in a ban of your instance: <b>%s</b> " ,
2022-08-12 14:41:21 +00:00
implode ( " , " , array_map ( fn ( $plugin ) => get_class ( $plugin ), $feed_handlers ))
2021-02-14 08:29:38 +00:00
) . " (<a href='https://tt-rss.org/wiki/FeedHandlerPlugins' target='_blank'> " . __ ( " More info... " ) . " </a>) "
);
}
2021-03-06 15:14:25 +00:00
?> -->
2018-12-04 12:33:20 +00:00
2021-03-06 15:14:25 +00:00
< ul id = " prefs-plugin-list " class = " prefs-plugin-list list-unstyled " >
2021-03-06 17:03:36 +00:00
< li class = 'text-center' >< ? = __ ( " Loading, please wait... " ) ?> </li>
2021-03-06 15:14:25 +00:00
</ ul >
2018-12-06 05:56:28 +00:00
2021-02-14 08:29:38 +00:00
</ div >
< div dojoType = " dijit.layout.ContentPane " region = " bottom " >
2021-03-06 15:14:25 +00:00
< button dojoType = 'dijit.form.Button' class = " alt-info pull-right " onclick = 'window.open("https://tt-rss.org/wiki/Plugins")' >
2021-03-05 12:16:41 +00:00
< i class = 'material-icons' > help </ i >
2021-03-06 15:14:25 +00:00
< ? = __ ( " More info " ) ?>
2021-02-14 08:29:38 +00:00
</ button >
2021-03-05 12:16:41 +00:00
2021-03-06 15:14:25 +00:00
< ? = \Controls\button_tag ( \Controls\icon ( " check " ) . " " . __ ( " Enable selected " ), " " , [ " class " => " alt-primary " ,
" onclick " => " Helpers.Plugins.enableSelected() " ]) ?>
< ? = \Controls\button_tag ( \Controls\icon ( " refresh " ), " " , [ " title " => __ ( " Reload " ), " onclick " => " Helpers.Plugins.reload() " ]) ?>
2021-11-10 17:44:51 +00:00
< ? php if ( $_SESSION [ " access_level " ] >= UserHelper :: ACCESS_LEVEL_ADMIN ) { ?>
2021-03-05 07:25:32 +00:00
< ? php if ( Config :: get ( Config :: CHECK_FOR_UPDATES ) && Config :: get ( Config :: CHECK_FOR_PLUGIN_UPDATES )) { ?>
2021-03-06 15:14:25 +00:00
< button class = 'alt-warning' dojoType = 'dijit.form.Button' onclick = " Helpers.Plugins.update() " >
2021-03-05 07:25:32 +00:00
< ? = \Controls\icon ( " update " ) ?>
< ? = __ ( " Check for updates " ) ?>
</ button >
< ? php } ?>
2021-03-03 16:07:39 +00:00
< ? php if ( Config :: get ( Config :: ENABLE_PLUGIN_INSTALLER )) { ?>
< button dojoType = 'dijit.form.Button' onclick = " Helpers.Plugins.install() " >
< ? = \Controls\icon ( " add " ) ?>
< ? = __ ( " Install plugin " ) ?>
</ button >
< ? php } ?>
2021-02-27 10:05:02 +00:00
< ? php } ?>
2021-02-14 08:29:38 +00:00
</ div >
</ div >
</ form >
< ? php
}
2021-11-14 20:12:37 +00:00
function index () : void {
2021-02-14 08:29:38 +00:00
?>
< div dojoType = 'dijit.layout.AccordionContainer' region = 'center' >
< div dojoType = 'dijit.layout.AccordionPane' title = " <i class='material-icons'>person</i> <?= __('Personal data / Authentication')?> " >
2021-02-14 09:25:41 +00:00
< script type = 'dojo/method' event = 'onSelected' args = 'evt' >
if ( this . domNode . querySelector ( '.loading' ))
window . setTimeout (() => {
2023-10-25 09:55:09 +00:00
xhr . post ( " backend.php " , { op : 'Pref_Prefs' , method : 'index_auth' }, ( reply ) => {
2021-02-19 10:44:56 +00:00
this . attr ( 'content' , reply );
2021-02-14 09:25:41 +00:00
});
}, 100 );
</ script >
< span class = 'loading' >< ? = __ ( " Loading, please wait... " ) ?> </span>
2021-02-14 08:29:38 +00:00
</ div >
< div dojoType = 'dijit.layout.AccordionPane' selected = 'true' title = " <i class='material-icons'>settings</i> <?= __('Preferences') ?> " >
< ? php $this -> index_prefs () ?>
</ div >
2021-03-06 15:14:25 +00:00
< div dojoType = 'dijit.layout.AccordionPane' style = 'padding : 0' title = " <i class='material-icons'>extension</i> <?= __('Plugins') ?> " >
< ? php $this -> index_plugins () ?>
2021-02-14 08:29:38 +00:00
</ div >
< ? php PluginHost :: getInstance () -> run_hooks ( PluginHost :: HOOK_PREFS_TAB , " prefPrefs " ) ?>
</ div >
< ? php
2011-12-13 06:00:42 +00:00
}
2012-08-23 16:23:19 +00:00
2021-11-14 20:12:37 +00:00
function _get_otp_qrcode_img () : ? string {
2021-02-26 16:16:17 +00:00
$secret = UserHelper :: get_otp_secret ( $_SESSION [ " uid " ]);
$login = UserHelper :: get_login_by_id ( $_SESSION [ " uid " ]);
2019-11-01 07:32:58 +00:00
2021-02-26 16:16:17 +00:00
if ( $secret && $login ) {
$qrcode = new \chillerlan\QRCode\QRCode ();
2019-11-01 07:32:58 +00:00
2021-02-26 16:16:17 +00:00
$otpurl = " otpauth://totp/ " . urlencode ( $login ) . " ?secret= $secret &issuer= " . urlencode ( " Tiny Tiny RSS " );
2019-11-01 07:32:58 +00:00
2021-02-26 16:16:17 +00:00
return $qrcode -> render ( $otpurl );
2019-11-01 07:32:58 +00:00
}
2021-11-14 20:12:37 +00:00
return null ;
2019-11-01 07:32:58 +00:00
}
2021-11-14 20:12:37 +00:00
function otpenable () : void {
2017-12-03 20:35:38 +00:00
$password = clean ( $_REQUEST [ " password " ]);
2021-02-26 16:16:17 +00:00
$otp_check = clean ( $_REQUEST [ " otp " ]);
2012-09-04 08:39:33 +00:00
2013-04-18 08:27:34 +00:00
$authenticator = PluginHost :: getInstance () -> get_plugin ( $_SESSION [ " auth_module " ]);
2012-12-27 11:14:44 +00:00
2021-11-14 20:12:37 +00:00
/** @var Auth_Internal|false $authenticator -- this is only here to make check_password() visible to static analyzer */
2012-09-04 08:39:33 +00:00
if ( $authenticator -> check_password ( $_SESSION [ " uid " ], $password )) {
2021-02-26 16:16:17 +00:00
if ( UserHelper :: enable_otp ( $_SESSION [ " uid " ], $otp_check )) {
print " OK " ;
} else {
print " ERROR: " . __ ( " Incorrect one time password " );
2012-09-04 08:39:33 +00:00
}
} else {
2013-04-16 17:07:26 +00:00
print " ERROR: " . __ ( " Incorrect password " );
2012-09-04 08:39:33 +00:00
}
}
2021-11-14 20:12:37 +00:00
function otpdisable () : void {
2017-12-03 20:35:38 +00:00
$password = clean ( $_REQUEST [ " password " ]);
2012-09-04 08:39:33 +00:00
2021-11-14 20:12:37 +00:00
/** @var Auth_Internal|false $authenticator -- this is only here to make check_password() visible to static analyzer */
2013-04-18 08:27:34 +00:00
$authenticator = PluginHost :: getInstance () -> get_plugin ( $_SESSION [ " auth_module " ]);
2012-09-04 08:39:33 +00:00
if ( $authenticator -> check_password ( $_SESSION [ " uid " ], $password )) {
2019-10-09 06:10:43 +00:00
$sth = $this -> pdo -> prepare ( " SELECT email, login FROM ttrss_users WHERE id = ? " );
$sth -> execute ([ $_SESSION [ 'uid' ]]);
if ( $row = $sth -> fetch ()) {
$mailer = new Mailer ();
2020-03-13 11:40:35 +00:00
$tpl = new Templator ();
2019-10-09 06:10:43 +00:00
2020-03-13 11:40:35 +00:00
$tpl -> readTemplateFromFile ( " otp_disabled_template.txt " );
2019-10-09 06:10:43 +00:00
$tpl -> setVariable ( 'LOGIN' , $row [ " login " ]);
2022-11-28 17:40:42 +00:00
$tpl -> setVariable ( 'TTRSS_HOST' , Config :: get_self_url ());
2019-10-09 06:10:43 +00:00
$tpl -> addBlock ( 'message' );
$tpl -> generateOutputToString ( $message );
$mailer -> mail ([ " to_name " => $row [ " login " ],
" to_address " => $row [ " email " ],
" subject " => " [tt-rss] OTP change notification " ,
" message " => $message ]);
}
2021-02-26 16:16:17 +00:00
UserHelper :: disable_otp ( $_SESSION [ " uid " ]);
2012-09-04 08:39:33 +00:00
print " OK " ;
} else {
print " ERROR: " . __ ( " Incorrect password " );
2012-09-03 14:33:46 +00:00
}
2012-09-04 08:39:33 +00:00
2012-09-03 14:33:46 +00:00
}
2012-12-24 20:45:10 +00:00
2021-11-14 20:12:37 +00:00
function setplugins () : void {
2022-07-03 10:40:04 +00:00
$plugins = array_filter ( $_REQUEST [ " plugins " ] ? ? [], 'clean' );
2012-12-24 20:45:10 +00:00
2021-03-07 09:28:24 +00:00
set_pref ( Prefs :: _ENABLED_PLUGINS , implode ( " , " , $plugins ));
2012-12-24 20:45:10 +00:00
}
2012-12-27 15:20:12 +00:00
2021-11-14 20:12:37 +00:00
function _get_plugin_version ( Plugin $plugin ) : string {
2021-03-01 09:11:42 +00:00
$about = $plugin -> about ();
if ( ! empty ( $about [ 0 ])) {
return T_sprintf ( " v%.2f, by %s " , $about [ 0 ], $about [ 2 ]);
2021-11-14 20:12:37 +00:00
}
2021-03-01 09:11:42 +00:00
2021-11-14 20:12:37 +00:00
$ref = new ReflectionClass ( get_class ( $plugin ));
2021-03-01 09:11:42 +00:00
2021-11-14 20:12:37 +00:00
$plugin_dir = dirname ( $ref -> getFileName ());
2021-03-01 09:11:42 +00:00
2021-11-14 20:12:37 +00:00
if ( basename ( $plugin_dir ) == " plugins " ) {
return " " ;
}
2021-03-01 10:43:37 +00:00
2021-11-14 20:12:37 +00:00
if ( is_dir ( " $plugin_dir /.git " )) {
$ver = Config :: get_version_from_git ( $plugin_dir );
return $ver [ " status " ] == 0 ? T_sprintf ( " v%s, by %s " , $ver [ " version " ], $about [ 2 ]) : $ver [ " version " ];
2021-03-01 09:11:42 +00:00
}
2021-11-14 20:12:37 +00:00
return " " ;
2021-03-01 09:11:42 +00:00
}
2021-11-14 20:12:37 +00:00
/**
* @ return array < int , array { 'plugin' : string , 'rv' : array { 'stdout' : false | string , 'stderr' : false | string , 'git_status' : int , 'need_update' : bool } | null } >
*/
static function _get_updated_plugins () : array {
2023-10-27 19:07:28 +00:00
$root_dir = Config :: get_self_dir ();
2021-02-27 16:14:13 +00:00
$plugin_dirs = array_filter ( glob ( " $root_dir /plugins.local/* " ), " is_dir " );
$rv = [];
foreach ( $plugin_dirs as $dir ) {
if ( is_dir ( " $dir /.git " )) {
$plugin_name = basename ( $dir );
array_push ( $rv , [ " plugin " => $plugin_name , " rv " => self :: _plugin_needs_update ( $root_dir , $plugin_name )]);
}
}
2022-08-12 14:41:21 +00:00
$rv = array_values ( array_filter ( $rv , fn ( $item ) => $item [ " rv " ][ " need_update " ]));
2021-02-27 16:14:13 +00:00
return $rv ;
}
2021-11-14 20:12:37 +00:00
/**
* @ return array { 'stdout' : false | string , 'stderr' : false | string , 'git_status' : int , 'need_update' : bool } | null
*/
private static function _plugin_needs_update ( string $root_dir , string $plugin_name ) : ? array {
2021-02-27 14:29:41 +00:00
$plugin_dir = " $root_dir /plugins.local/ " . basename ( $plugin_name );
2021-03-08 15:38:52 +00:00
$rv = null ;
2021-02-27 14:29:41 +00:00
if ( is_dir ( $plugin_dir ) && is_dir ( " $plugin_dir /.git " )) {
$pipes = [];
$descriptorspec = [
//0 => ["pipe", "r"], // STDIN
1 => [ " pipe " , " w " ], // STDOUT
2 => [ " pipe " , " w " ], // STDERR
];
$proc = proc_open ( " git fetch -q origin -a && git log HEAD..origin/master --oneline " , $descriptorspec , $pipes , $plugin_dir );
if ( is_resource ( $proc )) {
2021-03-08 15:38:52 +00:00
$rv = [
" stdout " => stream_get_contents ( $pipes [ 1 ]),
" stderr " => stream_get_contents ( $pipes [ 2 ]),
" git_status " => proc_close ( $proc ),
];
2021-03-07 17:11:54 +00:00
$rv [ " need_update " ] = ! empty ( $rv [ " stdout " ]);
2021-02-27 14:29:41 +00:00
}
}
return $rv ;
}
2021-11-14 20:12:37 +00:00
/**
* @ return array { 'stdout' : false | string , 'stderr' : false | string , 'git_status' : int }
*/
private function _update_plugin ( string $root_dir , string $plugin_name ) : array {
2021-02-27 10:05:02 +00:00
$plugin_dir = " $root_dir /plugins.local/ " . basename ( $plugin_name );
$rv = [];
if ( is_dir ( $plugin_dir ) && is_dir ( " $plugin_dir /.git " )) {
$pipes = [];
$descriptorspec = [
2021-02-27 14:29:41 +00:00
//0 => ["pipe", "r"], // STDIN
2021-02-27 10:05:02 +00:00
1 => [ " pipe " , " w " ], // STDOUT
2 => [ " pipe " , " w " ], // STDERR
];
2021-02-27 14:29:41 +00:00
$proc = proc_open ( " git fetch origin -a && git log HEAD..origin/master --oneline && git pull --ff-only origin master " , $descriptorspec , $pipes , $plugin_dir );
2021-02-27 10:05:02 +00:00
if ( is_resource ( $proc )) {
2021-03-07 17:11:54 +00:00
$rv [ " stdout " ] = stream_get_contents ( $pipes [ 1 ]);
$rv [ " stderr " ] = stream_get_contents ( $pipes [ 2 ]);
$rv [ " git_status " ] = proc_close ( $proc );
2021-02-27 10:05:02 +00:00
}
}
return $rv ;
}
2021-03-03 16:07:39 +00:00
// https://gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2#gistcomment-2036828
2021-11-14 20:12:37 +00:00
private function _recursive_rmdir ( string $dir , bool $keep_root = false ) : bool {
2021-03-03 16:07:39 +00:00
// Handle bad arguments.
if ( empty ( $dir ) || ! file_exists ( $dir )) {
return true ; // No such file/dir$dir exists.
} elseif ( is_file ( $dir ) || is_link ( $dir )) {
return unlink ( $dir ); // Delete file/link.
}
// Delete all children.
$files = new \RecursiveIteratorIterator (
new \RecursiveDirectoryIterator ( $dir , \RecursiveDirectoryIterator :: SKIP_DOTS ),
\RecursiveIteratorIterator :: CHILD_FIRST
);
foreach ( $files as $fileinfo ) {
$action = $fileinfo -> isDir () ? 'rmdir' : 'unlink' ;
if ( ! $action ( $fileinfo -> getRealPath ())) {
return false ; // Abort due to the failure.
}
}
return $keep_root ? true : rmdir ( $dir );
}
// https://stackoverflow.com/questions/7153000/get-class-name-from-file
2021-11-14 20:12:37 +00:00
private function _get_class_name_from_file ( string $file ) : string {
2021-03-03 16:07:39 +00:00
$tokens = token_get_all ( file_get_contents ( $file ));
for ( $i = 0 ; $i < count ( $tokens ); $i ++ ) {
if ( isset ( $tokens [ $i ][ 0 ]) && $tokens [ $i ][ 0 ] == T_CLASS ) {
for ( $j = $i + 1 ; $j < count ( $tokens ); $j ++ ) {
if ( isset ( $tokens [ $j ][ 1 ]) && $tokens [ $j ][ 1 ] != " " ) {
return $tokens [ $j ][ 1 ];
}
}
}
}
2021-11-14 20:12:37 +00:00
return " " ;
2021-03-03 16:07:39 +00:00
}
2021-11-14 20:12:37 +00:00
function uninstallPlugin () : void {
2021-11-10 17:44:51 +00:00
if ( $_SESSION [ " access_level " ] >= UserHelper :: ACCESS_LEVEL_ADMIN ) {
2021-03-03 16:35:11 +00:00
$plugin_name = basename ( clean ( $_REQUEST [ 'plugin' ]));
$status = 0 ;
2023-10-27 19:07:28 +00:00
$plugin_dir = Config :: get_self_dir () . " /plugins.local/ $plugin_name " ;
2021-03-03 16:35:11 +00:00
if ( is_dir ( $plugin_dir )) {
$status = $this -> _recursive_rmdir ( $plugin_dir );
}
print json_encode ([ 'status' => $status ]);
}
}
2021-11-14 20:12:37 +00:00
function installPlugin () : void {
2021-11-10 17:44:51 +00:00
if ( $_SESSION [ " access_level " ] >= UserHelper :: ACCESS_LEVEL_ADMIN && Config :: get ( Config :: ENABLE_PLUGIN_INSTALLER )) {
2021-03-03 16:35:11 +00:00
$plugin_name = basename ( clean ( $_REQUEST [ 'plugin' ]));
2021-03-03 16:07:39 +00:00
$all_plugins = $this -> _get_available_plugins ();
2023-10-27 19:07:28 +00:00
$plugin_dir = Config :: get_self_dir () . " /plugins.local " ;
2021-03-03 16:07:39 +00:00
$work_dir = " $plugin_dir /plugin-installer " ;
$rv = [ ];
if ( is_dir ( $work_dir ) || mkdir ( $work_dir )) {
foreach ( $all_plugins as $plugin ) {
if ( $plugin [ 'name' ] == $plugin_name ) {
$tmp_dir = tempnam ( $work_dir , $plugin_name );
if ( file_exists ( $tmp_dir )) {
unlink ( $tmp_dir );
$pipes = [];
$descriptorspec = [
1 => [ " pipe " , " w " ], // STDOUT
2 => [ " pipe " , " w " ], // STDERR
];
$proc = proc_open ( " git clone " . escapeshellarg ( $plugin [ 'clone_url' ]) . " " . $tmp_dir ,
$descriptorspec , $pipes , sys_get_temp_dir ());
$status = 0 ;
if ( is_resource ( $proc )) {
$rv [ " stdout " ] = stream_get_contents ( $pipes [ 1 ]);
$rv [ " stderr " ] = stream_get_contents ( $pipes [ 2 ]);
$status = proc_close ( $proc );
$rv [ " git_status " ] = $status ;
// yeah I know about mysterious RC = -1
if ( file_exists ( " $tmp_dir /init.php " )) {
$class_name = strtolower ( basename ( $this -> _get_class_name_from_file ( " $tmp_dir /init.php " )));
if ( $class_name ) {
$dst_dir = " $plugin_dir / $class_name " ;
if ( is_dir ( $dst_dir )) {
$rv [ 'result' ] = self :: PI_RES_ALREADY_INSTALLED ;
} else {
if ( rename ( $tmp_dir , " $plugin_dir / $class_name " )) {
$rv [ 'result' ] = self :: PI_RES_SUCCESS ;
}
}
} else {
$rv [ 'result' ] = self :: PI_ERR_NO_CLASS ;
}
} else {
$rv [ 'result' ] = self :: PI_ERR_NO_INIT_PHP ;
}
} else {
$rv [ 'result' ] = self :: PI_ERR_EXEC_FAILED ;
}
} else {
$rv [ 'result' ] = self :: PI_ERR_NO_TEMPDIR ;
}
// cleanup after failure
if ( $tmp_dir && is_dir ( $tmp_dir )) {
$this -> _recursive_rmdir ( $tmp_dir );
}
break ;
}
}
if ( empty ( $rv [ 'result' ]))
$rv [ 'result' ] = self :: PI_ERR_PLUGIN_NOT_FOUND ;
} else {
$rv [ " result " ] = self :: PI_ERR_NO_WORKDIR ;
}
print json_encode ( $rv );
}
}
2021-11-14 20:12:37 +00:00
/**
* @ return array < int , array { 'name' : string , 'description' : string , 'topics' : array < int , string > , 'html_url' : string , 'clone_url' : string , 'last_update' : string } >
*/
private function _get_available_plugins () : array {
2021-11-10 17:44:51 +00:00
if ( $_SESSION [ " access_level " ] >= UserHelper :: ACCESS_LEVEL_ADMIN && Config :: get ( Config :: ENABLE_PLUGIN_INSTALLER )) {
2021-11-14 20:12:37 +00:00
$content = json_decode ( UrlHelper :: fetch ([ 'url' => 'https://tt-rss.org/plugins.json' ]), true );
if ( $content ) {
return $content ;
}
2021-03-03 16:07:39 +00:00
}
2021-11-14 20:12:37 +00:00
return [];
2021-03-03 16:07:39 +00:00
}
2021-11-14 20:12:37 +00:00
function getAvailablePlugins () : void {
2021-11-10 17:44:51 +00:00
if ( $_SESSION [ " access_level " ] >= UserHelper :: ACCESS_LEVEL_ADMIN ) {
2021-03-04 16:50:19 +00:00
print json_encode ( $this -> _get_available_plugins ());
2021-11-14 20:12:37 +00:00
} else {
print " [] " ;
2021-03-03 16:07:39 +00:00
}
}
2021-11-14 20:12:37 +00:00
function checkForPluginUpdates () : void {
2021-11-10 17:44:51 +00:00
if ( $_SESSION [ " access_level " ] >= UserHelper :: ACCESS_LEVEL_ADMIN && Config :: get ( Config :: CHECK_FOR_UPDATES ) && Config :: get ( Config :: CHECK_FOR_PLUGIN_UPDATES )) {
2021-02-27 14:29:41 +00:00
$plugin_name = $_REQUEST [ " name " ] ? ? " " ;
2023-10-27 19:07:28 +00:00
$root_dir = Config :: get_self_dir ();
2021-02-27 14:29:41 +00:00
2021-11-14 20:12:37 +00:00
$rv = empty ( $plugin_name ) ? self :: _get_updated_plugins () : [
[ " plugin " => $plugin_name , " rv " => self :: _plugin_needs_update ( $root_dir , $plugin_name )],
];
2021-02-27 14:29:41 +00:00
print json_encode ( $rv );
}
}
2021-11-14 20:12:37 +00:00
function updateLocalPlugins () : void {
2021-11-10 17:44:51 +00:00
if ( $_SESSION [ " access_level " ] >= UserHelper :: ACCESS_LEVEL_ADMIN ) {
2021-02-28 18:50:05 +00:00
$plugins = explode ( " , " , $_REQUEST [ " plugins " ] ? ? " " );
2021-02-27 10:05:02 +00:00
2021-11-14 20:12:37 +00:00
if ( $plugins !== false ) {
$plugins = array_filter ( $plugins , 'strlen' );
}
2023-10-27 19:07:28 +00:00
$root_dir = Config :: get_self_dir ();
2021-02-27 10:05:02 +00:00
$rv = [];
2021-11-14 20:12:37 +00:00
if ( $plugins ) {
2021-02-28 18:50:05 +00:00
foreach ( $plugins as $plugin_name ) {
array_push ( $rv , [ " plugin " => $plugin_name , " rv " => $this -> _update_plugin ( $root_dir , $plugin_name )]);
}
2021-02-27 10:05:02 +00:00
} else {
$plugin_dirs = array_filter ( glob ( " $root_dir /plugins.local/* " ), " is_dir " );
foreach ( $plugin_dirs as $dir ) {
if ( is_dir ( " $dir /.git " )) {
$plugin_name = basename ( $dir );
2021-02-27 16:14:13 +00:00
$test = self :: _plugin_needs_update ( $root_dir , $plugin_name );
2021-02-27 14:35:00 +00:00
2021-11-14 20:12:37 +00:00
if ( ! empty ( $test [ " stdout " ]))
2021-02-27 14:35:00 +00:00
array_push ( $rv , [ " plugin " => $plugin_name , " rv " => $this -> _update_plugin ( $root_dir , $plugin_name )]);
2021-02-27 10:05:02 +00:00
}
}
}
print json_encode ( $rv );
}
}
2021-11-14 20:12:37 +00:00
function clearplugindata () : void {
2017-12-03 20:35:38 +00:00
$name = clean ( $_REQUEST [ " name " ]);
2012-12-27 15:20:12 +00:00
2013-04-18 08:27:34 +00:00
PluginHost :: getInstance () -> clear_data ( PluginHost :: getInstance () -> get_plugin ( $name ));
2012-12-27 15:20:12 +00:00
}
2013-04-01 08:30:34 +00:00
2021-11-14 20:12:37 +00:00
function customizeCSS () : void {
2021-02-25 11:49:58 +00:00
$value = get_pref ( Prefs :: USER_STYLESHEET );
2013-04-01 08:30:34 +00:00
$value = str_replace ( " <br/> " , " \n " , $value );
2021-02-12 06:02:44 +00:00
print json_encode ([ " value " => $value ]);
2013-04-01 08:30:34 +00:00
}
2021-11-14 20:12:37 +00:00
function activateprofile () : void {
2021-11-01 20:32:43 +00:00
$id = ( int ) ( $_REQUEST [ 'id' ] ? ? 0 );
2013-04-01 08:34:49 +00:00
2021-03-04 09:30:45 +00:00
$profile = ORM :: for_table ( 'ttrss_settings_profiles' )
-> where ( 'owner_uid' , $_SESSION [ 'uid' ])
-> find_one ( $id );
if ( $profile ) {
$_SESSION [ " profile " ] = $id ;
} else {
$_SESSION [ " profile " ] = null ;
}
2021-02-18 08:54:22 +00:00
}
2013-04-01 08:34:49 +00:00
2021-11-14 20:12:37 +00:00
function cloneprofile () : void {
2021-06-16 11:24:57 +00:00
$old_profile = $_REQUEST [ " old_profile " ] ? ? 0 ;
$new_title = clean ( $_REQUEST [ " new_title " ]);
if ( $old_profile && $new_title ) {
$new_profile = ORM :: for_table ( 'ttrss_settings_profiles' ) -> create ();
$new_profile -> title = $new_title ;
$new_profile -> owner_uid = $_SESSION [ 'uid' ];
if ( $new_profile -> save ()) {
$sth = $this -> pdo -> prepare ( " INSERT INTO ttrss_user_prefs
( owner_uid , pref_name , profile , value )
SELECT
: uid ,
pref_name ,
: new_profile ,
value
FROM ttrss_user_prefs
WHERE owner_uid = : uid AND profile = : old_profile " );
$sth -> execute ([
" uid " => $_SESSION [ " uid " ],
" new_profile " => $new_profile -> id ,
" old_profile " => $old_profile ,
]);
}
}
}
2021-11-14 20:12:37 +00:00
function remprofiles () : void {
2021-03-04 09:30:45 +00:00
$ids = $_REQUEST [ " ids " ] ? ? [];
2013-04-01 08:34:49 +00:00
2021-03-04 09:30:45 +00:00
ORM :: for_table ( 'ttrss_settings_profiles' )
-> where ( 'owner_uid' , $_SESSION [ 'uid' ])
-> where_in ( 'id' , $ids )
-> where_not_equal ( 'id' , $_SESSION [ 'profile' ] ? ? 0 )
-> delete_many ();
2021-02-18 08:54:22 +00:00
}
2013-04-01 08:34:49 +00:00
2021-11-14 20:12:37 +00:00
function addprofile () : void {
2021-02-18 08:54:22 +00:00
$title = clean ( $_REQUEST [ " title " ]);
2013-04-01 08:34:49 +00:00
2021-02-18 08:54:22 +00:00
if ( $title ) {
2021-03-04 09:30:45 +00:00
$profile = ORM :: for_table ( 'ttrss_settings_profiles' )
-> where ( 'owner_uid' , $_SESSION [ 'uid' ])
-> where ( 'title' , $title )
-> find_one ();
2013-04-01 08:34:49 +00:00
2021-03-04 09:30:45 +00:00
if ( ! $profile ) {
$profile = ORM :: for_table ( 'ttrss_settings_profiles' ) -> create ();
2013-04-01 08:34:49 +00:00
2021-03-04 09:30:45 +00:00
$profile -> title = $title ;
$profile -> owner_uid = $_SESSION [ 'uid' ];
$profile -> save ();
2021-02-18 08:54:22 +00:00
}
}
}
2013-04-01 08:34:49 +00:00
2021-11-14 20:12:37 +00:00
function saveprofile () : void {
2021-03-04 09:30:45 +00:00
$id = ( int ) $_REQUEST [ " id " ];
$title = clean ( $_REQUEST [ " value " ]);
2013-04-01 08:34:49 +00:00
2021-03-04 09:30:45 +00:00
if ( $title && $id ) {
$profile = ORM :: for_table ( 'ttrss_settings_profiles' )
-> where ( 'owner_uid' , $_SESSION [ 'uid' ])
-> find_one ( $id );
2013-04-01 08:34:49 +00:00
2021-03-04 09:30:45 +00:00
if ( $profile ) {
$profile -> title = $title ;
$profile -> save ();
}
2013-04-01 08:34:49 +00:00
}
2021-02-18 08:54:22 +00:00
}
// TODO: this maybe needs to be unified with Public::getProfiles()
2021-11-14 20:12:37 +00:00
function getProfiles () : void {
2021-02-18 08:54:22 +00:00
$rv = [];
2013-04-01 08:34:49 +00:00
2021-03-04 09:30:45 +00:00
$profiles = ORM :: for_table ( 'ttrss_settings_profiles' )
-> where ( 'owner_uid' , $_SESSION [ 'uid' ])
-> order_by_expr ( 'title' )
-> find_many ();
2013-04-01 08:34:49 +00:00
2021-02-18 08:54:22 +00:00
array_push ( $rv , [ " title " => __ ( " Default profile " ),
" id " => 0 ,
2021-06-16 11:24:57 +00:00
" initialized " => true ,
2021-02-18 08:54:22 +00:00
" active " => empty ( $_SESSION [ " profile " ])
]);
2013-04-01 08:34:49 +00:00
2021-03-04 09:30:45 +00:00
foreach ( $profiles as $profile ) {
$profile [ 'active' ] = ( $_SESSION [ " profile " ] ? ? 0 ) == $profile -> id ;
2021-06-16 11:24:57 +00:00
$num_settings = ORM :: for_table ( 'ttrss_user_prefs' )
-> where ( 'profile' , $profile -> id )
-> count ();
$profile [ 'initialized' ] = $num_settings > 0 ;
2021-03-04 09:30:45 +00:00
array_push ( $rv , $profile -> as_array ());
2021-02-18 08:54:22 +00:00
};
2021-02-12 07:35:13 +00:00
2021-02-18 08:54:22 +00:00
print json_encode ( $rv );
2013-04-01 08:34:49 +00:00
}
2021-11-14 20:12:37 +00:00
private function _get_short_desc ( string $pref_name ) : string {
2020-12-12 16:09:25 +00:00
if ( isset ( $this -> pref_help [ $pref_name ][ 0 ])) {
2013-04-02 12:20:06 +00:00
return $this -> pref_help [ $pref_name ][ 0 ];
}
return " " ;
}
2013-04-01 08:34:49 +00:00
2021-11-14 20:12:37 +00:00
private function _get_help_text ( string $pref_name ) : string {
2020-12-12 16:09:25 +00:00
if ( isset ( $this -> pref_help [ $pref_name ][ 1 ])) {
2013-04-02 12:20:06 +00:00
return $this -> pref_help [ $pref_name ][ 1 ];
}
return " " ;
}
2019-11-01 12:03:57 +00:00
2021-11-14 20:12:37 +00:00
private function appPasswordList () : void {
2021-02-14 08:39:26 +00:00
?>
< div dojoType = 'fox.Toolbar' >
< div dojoType = 'fox.form.DropDownButton' >
< span >< ? = __ ( 'Select' ) ?> </span>
< div dojoType = 'dijit.Menu' style = 'display: none' >
< div onclick = " Tables.select('app-password-list', true) "
dojoType = " dijit.MenuItem " >< ? = __ ( 'All' ) ?> </div>
< div onclick = " Tables.select('app-password-list', false) "
dojoType = " dijit.MenuItem " >< ? = __ ( 'None' ) ?> </div>
</ div >
</ div >
</ div >
2019-11-01 12:03:57 +00:00
2021-02-14 08:39:26 +00:00
< div class = 'panel panel-scrollable' >
< table width = '100%' id = 'app-password-list' >
< tr >
2021-03-09 06:04:13 +00:00
< th class = " checkbox " > </ th >
< th width = '50%' >< ? = __ ( " Description " ) ?> </th>
< th >< ? = __ ( " Created " ) ?> </th>
< th >< ? = __ ( " Last used " ) ?> </th>
2021-02-14 08:39:26 +00:00
</ tr >
< ? php
2021-03-02 05:08:48 +00:00
$passwords = ORM :: for_table ( 'ttrss_app_passwords' )
-> where ( 'owner_uid' , $_SESSION [ 'uid' ])
-> order_by_asc ( 'title' )
-> find_many ();
foreach ( $passwords as $pass ) { ?>
< tr data - row - id = '<?= $pass[' id '] ?>' >
2021-03-09 06:04:13 +00:00
< td class = " checkbox " >
2021-02-14 08:39:26 +00:00
< input onclick = 'Tables.onRowChecked(this)' dojoType = 'dijit.form.CheckBox' type = 'checkbox' >
</ td >
< td >
2021-03-02 05:08:48 +00:00
< ? = htmlspecialchars ( $pass [ " title " ]) ?>
2021-02-14 08:39:26 +00:00
</ td >
2021-03-09 06:04:13 +00:00
< td class = 'text-muted' >
2021-03-02 05:08:48 +00:00
< ? = TimeHelper :: make_local_datetime ( $pass [ 'created' ], false ) ?>
2021-02-14 08:39:26 +00:00
</ td >
2021-03-09 06:04:13 +00:00
< td class = 'text-muted' >
2021-03-02 05:08:48 +00:00
< ? = TimeHelper :: make_local_datetime ( $pass [ 'last_used' ], false ) ?>
2021-02-14 08:39:26 +00:00
</ td >
</ tr >
< ? php } ?>
</ table >
</ div >
< ? php
2019-11-01 12:03:57 +00:00
}
2021-11-14 20:12:37 +00:00
function deleteAppPasswords () : void {
2021-03-02 05:08:48 +00:00
$passwords = ORM :: for_table ( 'ttrss_app_passwords' )
-> where ( 'owner_uid' , $_SESSION [ 'uid' ])
-> where_in ( 'id' , $_REQUEST [ 'ids' ] ? ? [])
-> delete_many ();
2019-11-01 12:03:57 +00:00
$this -> appPasswordList ();
}
2021-11-14 20:12:37 +00:00
function generateAppPassword () : void {
2019-11-01 12:03:57 +00:00
$title = clean ( $_REQUEST [ 'title' ]);
$new_password = make_password ( 16 );
2021-03-01 12:24:18 +00:00
$new_salt = UserHelper :: get_salt ();
$new_password_hash = UserHelper :: hash_password ( $new_password , $new_salt , UserHelper :: HASH_ALGOS [ 0 ]);
2019-11-01 12:03:57 +00:00
print_warning ( T_sprintf ( " Generated password <strong>%s</strong> for %s. Please remember it for future reference. " , $new_password , $title ));
2021-03-02 05:08:48 +00:00
$password = ORM :: for_table ( 'ttrss_app_passwords' ) -> create ();
$password -> title = $title ;
$password -> owner_uid = $_SESSION [ 'uid' ];
$password -> pwd_hash = " $new_password_hash : $new_salt " ;
$password -> service = Auth_Base :: AUTH_SERVICE_API ;
$password -> created = Db :: NOW ();
2019-11-01 12:03:57 +00:00
2021-03-02 05:08:48 +00:00
$password -> save ();
2019-11-01 12:03:57 +00:00
$this -> appPasswordList ();
}
2021-03-01 07:20:21 +00:00
2021-11-14 20:12:37 +00:00
function previewDigest () : void {
2021-03-02 16:21:21 +00:00
print json_encode ( Digest :: prepare_headlines_digest ( $_SESSION [ " uid " ], 1 , 16 ));
}
2021-11-14 20:12:37 +00:00
static function _get_ssl_certificate_id () : string {
2021-03-01 07:20:21 +00:00
if ( $_SERVER [ " REDIRECT_SSL_CLIENT_M_SERIAL " ] ? ? false ) {
return sha1 ( $_SERVER [ " REDIRECT_SSL_CLIENT_M_SERIAL " ] .
$_SERVER [ " REDIRECT_SSL_CLIENT_V_START " ] .
$_SERVER [ " REDIRECT_SSL_CLIENT_V_END " ] .
$_SERVER [ " REDIRECT_SSL_CLIENT_S_DN " ]);
}
if ( $_SERVER [ " SSL_CLIENT_M_SERIAL " ] ? ? false ) {
return sha1 ( $_SERVER [ " SSL_CLIENT_M_SERIAL " ] .
$_SERVER [ " SSL_CLIENT_V_START " ] .
$_SERVER [ " SSL_CLIENT_V_END " ] .
$_SERVER [ " SSL_CLIENT_S_DN " ]);
}
return " " ;
}
2021-03-29 16:22:03 +00:00
2021-11-14 20:12:37 +00:00
private function format_otp_secret ( string $secret ) : string {
2021-03-29 16:22:03 +00:00
return implode ( " " , str_split ( $secret , 4 ));
}
2017-12-14 17:02:37 +00:00
}