]>
git.wh0rd.org - tt-rss.git/blob - classes/pref/prefs.php
3 class Pref_Prefs
extends Handler_Protected
{
5 private $pref_help = array ();
6 private $pref_sections = array ();
8 function csrf_ignore ( $method ) {
9 $csrf_ignored = array ( "index" , "updateself" , "customizecss" , "editprefprofiles" );
11 return array_search ( $method , $csrf_ignored ) !== false ;
14 function __construct ( $args ) {
15 parent
:: __construct ( $args );
17 $this -> pref_sections
= array (
24 $this -> pref_help
= array (
25 "ALLOW_DUPLICATE_POSTS" => array ( __ ( "Allow duplicate articles" ), "" ),
26 "BLACKLISTED_TAGS" => array ( __ ( "Blacklisted tags" ), __ ( "When auto-detecting tags in articles these tags will not be applied (comma-separated list)." )),
27 "CDM_AUTO_CATCHUP" => array ( __ ( "Automatically mark articles as read" ), __ ( "This option enables marking articles as read automatically while you scroll article list." )),
28 "CDM_EXPANDED" => array ( __ ( "Automatically expand articles in combined mode" ), "" ),
29 "COMBINED_DISPLAY_MODE" => array ( __ ( "Combined feed display" ), __ ( "Display expanded list of feed articles, instead of separate displays for headlines and article content" )),
30 "CONFIRM_FEED_CATCHUP" => array ( __ ( "Confirm marking feed as read" ), "" ),
31 "DEFAULT_ARTICLE_LIMIT" => array ( __ ( "Amount of articles to display at once" ), "" ),
32 "DEFAULT_UPDATE_INTERVAL" => array ( __ ( "Default feed update interval" ), __ ( "Shortest interval at which a feed will be checked for updates regardless of update method" )),
33 "DIGEST_CATCHUP" => array ( __ ( "Mark articles in e-mail digest as read" ), "" ),
34 "DIGEST_ENABLE" => array ( __ ( "Enable e-mail digest" ), __ ( "This option enables sending daily digest of new (and unread) headlines on your configured e-mail address" )),
35 "DIGEST_PREFERRED_TIME" => array ( __ ( "Try to send digests around specified time" ), __ ( "Uses UTC timezone" )),
36 "ENABLE_API_ACCESS" => array ( __ ( "Enable API access" ), __ ( "Allows external clients to access this account through the API" )),
37 "ENABLE_FEED_CATS" => array ( __ ( "Enable feed categories" ), "" ),
38 "FEEDS_SORT_BY_UNREAD" => array ( __ ( "Sort feeds by unread articles count" ), "" ),
39 "FRESH_ARTICLE_MAX_AGE" => array ( __ ( "Maximum age of fresh articles (in hours)" ), "" ),
40 "HIDE_READ_FEEDS" => array ( __ ( "Hide feeds with no unread articles" ), "" ),
41 "HIDE_READ_SHOWS_SPECIAL" => array ( __ ( "Show special feeds when hiding read feeds" ), "" ),
42 "LONG_DATE_FORMAT" => array ( __ ( "Long date format" ), __ ( "The syntax used is identical to the PHP <a href='http://php.net/manual/function.date.php'>date()</a> function." )),
43 "ON_CATCHUP_SHOW_NEXT_FEED" => array ( __ ( "On catchup show next feed" ), __ ( "Automatically open next feed with unread articles after marking one as read" )),
44 "PURGE_OLD_DAYS" => array ( __ ( "Purge articles after this number of days (0 - disables)" ), "" ),
45 "PURGE_UNREAD_ARTICLES" => array ( __ ( "Purge unread articles" ), "" ),
46 "REVERSE_HEADLINES" => array ( __ ( "Reverse headline order (oldest first)" ), "" ),
47 "SHORT_DATE_FORMAT" => array ( __ ( "Short date format" ), "" ),
48 "SHOW_CONTENT_PREVIEW" => array ( __ ( "Show content preview in headlines list" ), "" ),
49 "SORT_HEADLINES_BY_FEED_DATE" => array ( __ ( "Sort headlines by feed date" ), __ ( "Use feed-specified date to sort headlines instead of local import date." )),
50 "SSL_CERT_SERIAL" => array ( __ ( "Login with an SSL certificate" ), __ ( "Click to register your SSL client certificate with tt-rss" )),
51 "STRIP_IMAGES" => array ( __ ( "Do not embed media in articles" ), "" ),
52 "STRIP_UNSAFE_TAGS" => array ( __ ( "Strip unsafe tags from articles" ), __ ( "Strip all but most common HTML tags when reading articles." )),
53 "USER_STYLESHEET" => array ( __ ( "Customize stylesheet" ), __ ( "Customize CSS stylesheet to your liking" )),
54 "USER_TIMEZONE" => array ( __ ( "Time zone" ), "" ),
55 "VFEED_GROUP_BY_FEED" => array ( __ ( "Group headlines in virtual feeds" ), __ ( "Special feeds, labels, and categories are grouped by originating feeds" )),
56 "USER_LANGUAGE" => array ( __ ( "Language" )),
57 "USER_CSS_THEME" => array ( __ ( "Theme" ), __ ( "Select one of the available CSS themes" ))
61 function changepassword () {
63 $old_pw = clean ( $_POST [ "old_password" ]);
64 $new_pw = clean ( $_POST [ "new_password" ]);
65 $con_pw = clean ( $_POST [ "confirm_password" ]);
68 print "ERROR: " . format_error ( "Old password cannot be blank." );
73 print "ERROR: " . format_error ( "New password cannot be blank." );
77 if ( $new_pw != $con_pw ) {
78 print "ERROR: " . format_error ( "Entered passwords do not match." );
82 $authenticator = PluginHost
:: getInstance ()-> get_plugin ( $_SESSION [ "auth_module" ]);
84 if ( method_exists ( $authenticator , "change_password" )) {
85 print format_notice ( $authenticator -> change_password ( $_SESSION [ "uid" ], $old_pw , $new_pw ));
87 print "ERROR: " . format_error ( "Function not supported by authentication module." );
91 function saveconfig () {
92 $boolean_prefs = explode ( "," , clean ( $_POST [ "boolean_prefs" ]));
94 foreach ( $boolean_prefs as $pref ) {
95 if (! isset ( $_POST [ $pref ])) $_POST [ $pref ] = 'false' ;
100 foreach ( array_keys ( $_POST ) as $pref_name ) {
102 $value = $_POST [ $pref_name ];
104 if ( $pref_name == 'DIGEST_PREFERRED_TIME' ) {
105 if ( get_pref ( 'DIGEST_PREFERRED_TIME' ) != $value ) {
107 $sth = $this -> pdo
-> prepare ( "UPDATE ttrss_users SET
108 last_digest_sent = NULL WHERE id = ?" );
109 $sth -> execute ([ $_SESSION [ 'uid' ]]);
114 if ( $pref_name == "USER_LANGUAGE" ) {
115 if ( $_SESSION [ "language" ] != $value ) {
120 set_pref ( $pref_name , $value );
124 print "PREFS_NEED_RELOAD" ;
126 print __ ( "The configuration was saved." );
130 function changeemail () {
132 $email = clean ( $_POST [ "email" ]);
133 $full_name = clean ( $_POST [ "full_name" ]);
134 $active_uid = $_SESSION [ "uid" ];
136 $sth = $this -> pdo
-> prepare ( "UPDATE ttrss_users SET email = ?,
137 full_name = ? WHERE id = ?" );
138 $sth -> execute ([ $email , $full_name , $active_uid ]);
140 print __ ( "Your personal data has been saved." );
145 function resetconfig () {
147 $_SESSION [ "prefs_op_result" ] = "reset-to-defaults" ;
149 $sth = $this -> pdo
-> prepare ( "DELETE FROM ttrss_user_prefs
150 WHERE (profile = :profile OR (:profile IS NULL AND profile IS NULL))
151 AND owner_uid = :uid" );
152 $sth -> execute ([ ":profile" => $_SESSION [ 'profile' ], ":uid" => $_SESSION [ 'uid' ]]);
154 initialize_user_prefs ( $_SESSION [ "uid" ], $_SESSION [ "profile" ]);
156 echo __ ( "Your preferences are now set to default values." );
161 global $access_level_names ;
163 $prefs_blacklist = array ( "ALLOW_DUPLICATE_POSTS" , "STRIP_UNSAFE_TAGS" , "REVERSE_HEADLINES" ,
164 "SORT_HEADLINES_BY_FEED_DATE" , "DEFAULT_ARTICLE_LIMIT" ,
165 "FEEDS_SORT_BY_UNREAD" , "CDM_EXPANDED" );
167 /* "FEEDS_SORT_BY_UNREAD", "HIDE_READ_FEEDS", "REVERSE_HEADLINES" */
169 $profile_blacklist = array ( "ALLOW_DUPLICATE_POSTS" , "PURGE_OLD_DAYS" ,
170 "PURGE_UNREAD_ARTICLES" , "DIGEST_ENABLE" , "DIGEST_CATCHUP" ,
171 "BLACKLISTED_TAGS" , "ENABLE_API_ACCESS" , "UPDATE_POST_ON_CHECKSUM_CHANGE" ,
172 "DEFAULT_UPDATE_INTERVAL" , "USER_TIMEZONE" , "SORT_HEADLINES_BY_FEED_DATE" ,
173 "SSL_CERT_SERIAL" , "DIGEST_PREFERRED_TIME" );
175 $digest_options = array ( "DIGEST_ENABLE" , "DIGEST_CATCHUP" , "DIGEST_PREFERRED_TIME" );
177 $purge_options = array ( "PURGE_UNREAD_ARTICLES" , "PURGE_OLD_DAYS" );
179 $_SESSION [ "prefs_op_result" ] = "" ;
181 print "<div dojoType= \" dijit.layout.AccordionContainer \" region= \" center \" >" ;
182 print "<div dojoType= \" dijit.layout.AccordionPane \" title= \" " . __ ( 'Personal data / Authentication' ). " \" >" ;
184 print "<form dojoType= \" dijit.form.Form \" id= \" changeUserdataForm \" >" ;
186 print "<script type= \" dojo/method \" event= \" onSubmit \" args= \" evt \" >
187 evt.preventDefault();
188 if (this.validate()) {
189 notify_progress('Saving data...', true);
191 new Ajax.Request('backend.php', {
192 parameters: dojo.objectToQuery(this.getValues()),
193 onComplete: function(transport) {
194 notify_callback2(transport);
200 print "<table width= \" 100% \" class= \" prefPrefsList \" >" ;
202 print "<h2>" . __ ( "Personal data" ) . "</h2>" ;
204 $sth = $this -> pdo
-> prepare ( "SELECT email,full_name,otp_enabled,
205 access_level FROM ttrss_users
207 $sth -> execute ([ $_SESSION [ "uid" ]]);
208 $row = $sth -> fetch ();
210 $email = htmlspecialchars ( $row [ "email" ]);
211 $full_name = htmlspecialchars ( $row [ "full_name" ]);
212 $otp_enabled = sql_bool_to_bool ( $row [ "otp_enabled" ]);
214 print "<tr><td width= \" 40% \" >" . __ ( 'Full name' ). "</td>" ;
215 print "<td class= \" prefValue \" ><input dojoType= \" dijit.form.ValidationTextBox \" name= \" full_name \" required= \" 1 \"
216 value= \" $full_name\" ></td></tr>" ;
218 print "<tr><td width= \" 40% \" >" . __ ( 'E-mail' ). "</td>" ;
219 print "<td class= \" prefValue \" ><input dojoType= \" dijit.form.ValidationTextBox \" name= \" email \" required= \" 1 \" value= \" $email\" ></td></tr>" ;
221 if (! SINGLE_USER_MODE
&& ! $_SESSION [ "hide_hello" ]) {
223 $access_level = $row [ "access_level" ];
224 print "<tr><td width= \" 40% \" >" . __ ( 'Access level' ). "</td>" ;
225 print "<td>" . $access_level_names [ $access_level ] . "</td></tr>" ;
230 print_hidden ( "op" , "pref-prefs" );
231 print_hidden ( "method" , "changeemail" );
233 print "<p><button dojoType= \" dijit.form.Button \" type= \" submit \" class= \" btn-primary \" >" .
234 __ ( "Save data" ). "</button>" ;
238 if ( $_SESSION [ "auth_module" ]) {
239 $authenticator = PluginHost
:: getInstance ()-> get_plugin ( $_SESSION [ "auth_module" ]);
241 $authenticator = false ;
244 if ( $authenticator && method_exists ( $authenticator , "change_password" )) {
246 print "<h2>" . __ ( "Password" ) . "</h2>" ;
248 print "<div style='display : none' id='pwd_change_infobox'></div>" ;
250 print "<form dojoType= \" dijit.form.Form \" >" ;
252 print "<script type= \" dojo/method \" event= \" onSubmit \" args= \" evt \" >
253 evt.preventDefault();
254 if (this.validate()) {
255 notify_progress('Changing password...', true);
257 new Ajax.Request('backend.php', {
258 parameters: dojo.objectToQuery(this.getValues()),
259 onComplete: function(transport) {
261 if (transport.responseText.indexOf('ERROR: ') == 0) {
263 $('pwd_change_infobox').innerHTML =
264 transport.responseText.replace('ERROR: ', '');
267 $('pwd_change_infobox').innerHTML =
268 transport.responseText.replace('ERROR: ', '');
270 var warn = $('default_pass_warning');
271 if (warn) Element.hide(warn);
274 new Effect.Appear('pwd_change_infobox');
282 print_notice ( __ ( "Changing your current password will disable OTP." ));
285 print "<table width= \" 100% \" class= \" prefPrefsList \" >" ;
287 print "<tr><td width= \" 40% \" >" . __ ( "Old password" ). "</td>" ;
288 print "<td class= \" prefValue \" ><input dojoType= \" dijit.form.ValidationTextBox \" type= \" password \" required= \" 1 \" name= \" old_password \" ></td></tr>" ;
290 print "<tr><td width= \" 40% \" >" . __ ( "New password" ). "</td>" ;
292 print "<td class= \" prefValue \" ><input dojoType= \" dijit.form.ValidationTextBox \" type= \" password \" required= \" 1 \"
293 name= \" new_password \" ></td></tr>" ;
295 print "<tr><td width= \" 40% \" >" . __ ( "Confirm password" ). "</td>" ;
297 print "<td class= \" prefValue \" ><input dojoType= \" dijit.form.ValidationTextBox \" type= \" password \" required= \" 1 \" name= \" confirm_password \" ></td></tr>" ;
301 print_hidden ( "op" , "pref-prefs" );
302 print_hidden ( "method" , "changepassword" );
304 print "<p><button dojoType= \" dijit.form.Button \" type= \" submit \" class= \" btn-primary \" >" .
305 __ ( "Change password" ). "</button>" ;
309 if ( $_SESSION [ "auth_module" ] == "auth_internal" ) {
311 print "<h2>" . __ ( "One time passwords / Authenticator" ) . "</h2>" ;
315 print_notice ( __ ( "One time passwords are currently enabled. Enter your current password below to disable." ));
317 print "<form dojoType= \" dijit.form.Form \" >" ;
319 print "<script type= \" dojo/method \" event= \" onSubmit \" args= \" evt \" >
320 evt.preventDefault();
321 if (this.validate()) {
322 notify_progress('Disabling OTP', true);
324 new Ajax.Request('backend.php', {
325 parameters: dojo.objectToQuery(this.getValues()),
326 onComplete: function(transport) {
328 if (transport.responseText.indexOf('ERROR: ') == 0) {
329 notify_error(transport.responseText.replace('ERROR: ', ''));
331 window.location.reload();
338 print "<table width= \" 100% \" class= \" prefPrefsList \" >" ;
340 print "<tr><td width= \" 40% \" >" . __ ( "Enter your password" ). "</td>" ;
342 print "<td class= \" prefValue \" ><input dojoType= \" dijit.form.ValidationTextBox \" type= \" password \" required= \" 1 \"
343 name= \" password \" ></td></tr>" ;
347 print_hidden ( "op" , "pref-prefs" );
348 print_hidden ( "method" , "otpdisable" );
350 print "<p><button dojoType= \" dijit.form.Button \" type= \" submit \" >" .
351 __ ( "Disable OTP" ). "</button>" ;
355 } else if ( function_exists ( "imagecreatefromstring" )) {
357 print_warning ( __ ( "You will need a compatible Authenticator to use this. Changing your password would automatically disable OTP." ));
359 print "<p>" . __ ( "Scan the following code by the Authenticator application:" ). "</p>" ;
361 $csrf_token = $_SESSION [ "csrf_token" ];
363 print "<img src= \" backend.php?op=pref-prefs&method=otpqrcode&csrf_token= $csrf_token\" >" ;
365 print "<form dojoType= \" dijit.form.Form \" id= \" changeOtpForm \" >" ;
367 print_hidden ( "op" , "pref-prefs" );
368 print_hidden ( "method" , "otpenable" );
370 print "<script type= \" dojo/method \" event= \" onSubmit \" args= \" evt \" >
371 evt.preventDefault();
372 if (this.validate()) {
373 notify_progress('Saving data...', true);
375 new Ajax.Request('backend.php', {
376 parameters: dojo.objectToQuery(this.getValues()),
377 onComplete: function(transport) {
379 if (transport.responseText.indexOf('ERROR:') == 0) {
380 notify_error(transport.responseText.replace('ERROR:', ''));
382 window.location.reload();
389 print "<table width= \" 100% \" class= \" prefPrefsList \" >" ;
391 print "<tr><td width= \" 40% \" >" . __ ( "Enter your password" ). "</td>" ;
393 print "<td class= \" prefValue \" ><input dojoType= \" dijit.form.ValidationTextBox \" type= \" password \" required= \" 1 \"
394 name= \" password \" ></td></tr>" ;
396 print "<tr><td width= \" 40% \" >" . __ ( "Enter the generated one time password" ). "</td>" ;
398 print "<td class= \" prefValue \" ><input dojoType= \" dijit.form.ValidationTextBox \" autocomplete= \" off \"
400 name= \" otp \" ></td></tr>" ;
402 print "<tr><td colspan= \" 2 \" >" ;
404 print "</td></tr><tr><td colspan= \" 2 \" >" ;
409 print "<p><button dojoType= \" dijit.form.Button \" type= \" submit \" class= \" btn-primary \" >" .
410 __ ( "Enable OTP" ). "</button>" ;
416 print_notice ( __ ( "PHP GD functions are required for OTP support." ));
423 PluginHost
:: getInstance ()-> run_hooks ( PluginHost
:: HOOK_PREFS_TAB_SECTION
,
424 "hook_prefs_tab_section" , "prefPrefsAuth" );
426 print "</div>" ; #pane
428 print "<div dojoType= \" dijit.layout.AccordionPane \" selected= \" true \" title= \" " . __ ( 'Preferences' ). " \" >" ;
430 print "<form dojoType= \" dijit.form.Form \" id= \" changeSettingsForm \" >" ;
432 print "<script type= \" dojo/method \" event= \" onSubmit \" args= \" evt, quit \" >
433 if (evt) evt.preventDefault();
434 if (this.validate()) {
435 console.log(dojo.objectToQuery(this.getValues()));
437 new Ajax.Request('backend.php', {
438 parameters: dojo.objectToQuery(this.getValues()),
439 onComplete: function(transport) {
440 var msg = transport.responseText;
444 if (msg == 'PREFS_NEED_RELOAD') {
445 window.location.reload();
454 print '<div dojoType="dijit.layout.BorderContainer" gutters="false">' ;
456 print '<div dojoType="dijit.layout.ContentPane" region="center" style="overflow-y : auto">' ;
458 if ( $_SESSION [ "profile" ]) {
459 print_notice ( __ ( "Some preferences are only available in default profile." ));
462 if ( $_SESSION [ "profile" ]) {
463 initialize_user_prefs ( $_SESSION [ "uid" ], $_SESSION [ "profile" ]);
465 initialize_user_prefs ( $_SESSION [ "uid" ]);
468 $sth = $this -> pdo
-> prepare ( "SELECT DISTINCT
469 ttrss_user_prefs.pref_name,value,type_name,
470 ttrss_prefs_sections.order_id,
472 FROM ttrss_prefs,ttrss_prefs_types,ttrss_prefs_sections,ttrss_user_prefs
473 WHERE type_id = ttrss_prefs_types.id AND
474 (profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND
475 section_id = ttrss_prefs_sections.id AND
476 ttrss_user_prefs.pref_name = ttrss_prefs.pref_name AND
478 ORDER BY ttrss_prefs_sections.order_id,pref_name" );
479 $sth -> execute ([ ":uid" => $_SESSION [ 'uid' ], ":profile" => $_SESSION [ 'profile' ]]);
483 $active_section = "" ;
485 $listed_boolean_prefs = array ();
487 while ( $line = $sth -> fetch ()) {
489 if ( in_array ( $line [ "pref_name" ], $prefs_blacklist )) {
493 $type_name = $line [ "type_name" ];
494 $pref_name = $line [ "pref_name" ];
495 $section_name = $this -> getSectionName ( $line [ "section_id" ]);
496 $value = $line [ "value" ];
498 $short_desc = $this -> getShortDesc ( $pref_name );
499 $help_text = $this -> getHelpText ( $pref_name );
501 if (! $short_desc ) continue ;
503 if ( $_SESSION [ "profile" ] && in_array ( $line [ "pref_name" ],
504 $profile_blacklist )) {
508 /* Hide options from the user that are disabled in config.php. */
509 if ( FORCE_ARTICLE_PURGE
&& in_array ( $pref_name , $purge_options ))
511 if ( DIGEST_SUBJECT
=== false && in_array ( $pref_name , $digest_options ))
514 if ( $active_section != $line [ "section_id" ]) {
516 if ( $active_section != "" ) {
520 print "<table width= \" 100% \" class= \" prefPrefsList \" >" ;
522 $active_section = $line [ "section_id" ];
524 print "<tr><td colspan= \" 3 \" ><h3>" . $section_name . "</h3></td></tr>" ;
531 print "<td width= \" 40% \" class= \" prefName \" id= \" $pref_name\" >" ;
532 print "<label for='CB_ $pref_name '>" ;
536 if ( $help_text ) print "<div class= \" prefHelp \" >" . __ ( $help_text ). "</div>" ;
540 print "<td class= \" prefValue \" >" ;
542 if ( $pref_name == "USER_LANGUAGE" ) {
543 print_select_hash ( $pref_name , $value , get_translations (),
544 "style='width : 220px; margin : 0px' dojoType='dijit.form.Select'" );
546 } else if ( $pref_name == "USER_TIMEZONE" ) {
548 $timezones = explode ( " \n " , file_get_contents ( "lib/timezones.txt" ));
550 print_select ( $pref_name , $value , $timezones , 'dojoType="dijit.form.FilteringSelect"' );
551 } else if ( $pref_name == "USER_STYLESHEET" ) {
553 print "<button dojoType= \" dijit.form.Button \"
554 onclick= \" customizeCSS() \" >" . __ ( 'Customize' ) . "</button>" ;
556 } else if ( $pref_name == "USER_CSS_THEME" ) {
558 $themes = array_merge ( glob ( "themes/*.php" ), glob ( "themes/*.css" ), glob ( "themes.local/*.css" ));
559 $themes = array_map ( "basename" , $themes );
560 $themes = array_filter ( $themes , "theme_valid" );
563 if (! theme_valid ( $value )) $value = "default.php" ;
565 print_select ( $pref_name , $value , $themes ,
566 'dojoType="dijit.form.Select"' );
569 } else if ( $pref_name == "DEFAULT_UPDATE_INTERVAL" ) {
571 global $update_intervals_nodefault ;
573 print_select_hash ( $pref_name , $value , $update_intervals_nodefault ,
574 'dojoType="dijit.form.Select"' );
576 } else if ( $type_name == "bool" ) {
578 array_push ( $listed_boolean_prefs , $pref_name );
580 $checked = ( $value == "true" ) ?
"checked= \" checked \" " : "" ;
582 print "<input type='checkbox' name=' $pref_name ' $checked
583 dojoType='dijit.form.CheckBox' id='CB_ $pref_name ' value='1'>" ;
585 } else if ( array_search ( $pref_name , array ( 'FRESH_ARTICLE_MAX_AGE' ,
586 'PURGE_OLD_DAYS' , 'LONG_DATE_FORMAT' , 'SHORT_DATE_FORMAT' )) !== false ) {
588 $regexp = ( $type_name == 'integer' ) ?
'regexp="^\d*$"' : '' ;
590 print "<input dojoType= \" dijit.form.ValidationTextBox \"
591 required= \" 1 \" $regexp
592 name= \" $pref_name\" value= \" $value\" >" ;
594 } else if ( $pref_name == "SSL_CERT_SERIAL" ) {
596 print "<input dojoType= \" dijit.form.ValidationTextBox \"
597 id= \" SSL_CERT_SERIAL \" readonly= \" 1 \"
598 name= \" $pref_name\" value= \" $value\" >" ;
600 $cert_serial = htmlspecialchars ( get_ssl_certificate_id ());
601 $has_serial = ( $cert_serial ) ?
"false" : "true" ;
605 print " <button dojoType= \" dijit.form.Button \" disabled= \" $has_serial\"
606 onclick= \" insertSSLserial(' $cert_serial ') \" >" .
607 __ ( 'Register' ) . "</button>" ;
609 print " <button dojoType= \" dijit.form.Button \"
610 onclick= \" insertSSLserial('') \" >" .
611 __ ( 'Clear' ) . "</button>" ;
613 } else if ( $pref_name == 'DIGEST_PREFERRED_TIME' ) {
614 print "<input dojoType= \" dijit.form.ValidationTextBox \"
615 id= \" $pref_name\" regexp= \" [012]?\d:\d\d \" placeHolder= \" 12:00 \"
616 name= \" $pref_name\" value= \" $value\" ><div class= \" insensitive \" >" .
617 T_sprintf ( "Current server time: %s (UTC)" , date ( "H:i" )) . "</div>" ;
619 $regexp = ( $type_name == 'integer' ) ?
'regexp="^\d*$"' : '' ;
621 print "<input dojoType= \" dijit.form.ValidationTextBox \"
623 name= \" $pref_name\" value= \" $value\" >" ;
635 $listed_boolean_prefs = htmlspecialchars ( join ( "," , $listed_boolean_prefs ));
637 print_hidden ( "boolean_prefs" , " $listed_boolean_prefs " );
639 PluginHost
:: getInstance ()-> run_hooks ( PluginHost
:: HOOK_PREFS_TAB_SECTION
,
640 "hook_prefs_tab_section" , "prefPrefsPrefsInside" );
642 print '</div>' ; # inside pane
643 print '<div dojoType="dijit.layout.ContentPane" region="bottom">' ;
645 print_hidden ( "op" , "pref-prefs" );
646 print_hidden ( "method" , "saveconfig" );
648 print "<div dojoType= \" dijit.form.ComboButton \" type= \" submit \" class= \" btn-primary \" >
649 <span>" . __ ( 'Save configuration' ). "</span>
650 <div dojoType= \" dijit.DropDownMenu \" >
651 <div dojoType= \" dijit.MenuItem \"
652 onclick= \" dijit.byId('changeSettingsForm').onSubmit(null, true) \" >" .
653 __ ( "Save and exit preferences" ). "</div>
657 print "<button dojoType= \" dijit.form.Button \" onclick= \" return editProfiles() \" >" .
658 __ ( 'Manage profiles' ). "</button> " ;
660 print "<button dojoType= \" dijit.form.Button \" class= \" btn-danger \" onclick= \" return validatePrefsReset() \" >" .
661 __ ( 'Reset to defaults' ). "</button>" ;
665 PluginHost
:: getInstance ()-> run_hooks ( PluginHost
:: HOOK_PREFS_TAB_SECTION
,
666 "hook_prefs_tab_section" , "prefPrefsPrefsOutside" );
669 print '</div>' ; # inner pane
670 print '</div>' ; # border container
672 print "</div>" ; #pane
674 print "<div dojoType= \" dijit.layout.AccordionPane \" title= \" " . __ ( 'Plugins' ). " \" >" ;
676 print_notice ( __ ( "You will need to reload Tiny Tiny RSS for plugin changes to take effect." ));
678 if ( ini_get ( "open_basedir" ) && function_exists ( "curl_init" ) && ! defined ( "NO_CURL" )) {
679 print_warning ( "Your PHP configuration has open_basedir restrictions enabled. Some plugins relying on CURL for functionality may not work correctly." );
682 print "<form dojoType= \" dijit.form.Form \" id= \" changePluginsForm \" >" ;
684 print "<script type= \" dojo/method \" event= \" onSubmit \" args= \" evt \" >
685 evt.preventDefault();
686 if (this.validate()) {
687 notify_progress('Saving data...', true);
689 new Ajax.Request('backend.php', {
690 parameters: dojo.objectToQuery(this.getValues()),
691 onComplete: function(transport) {
693 if (confirm(__('Selected plugins have been enabled. Reload?'))) {
694 window.location.reload();
701 print_hidden ( "op" , "pref-prefs" );
702 print_hidden ( "method" , "setplugins" );
704 print "<table width='100%' class='prefPluginsList'>" ;
706 print "<tr><td colspan='5'><h3>" . __ ( "System plugins" ). "</h3>" .
707 format_notice ( __ ( "System plugins are enabled in <strong>config.php</strong> for all users." )).
710 print "<tr class= \" title \" >
711 <td width= \" 5% \" > </td>
712 <td width='10%'>" . __ ( 'Plugin' ). "</td>
713 <td width=''>" . __ ( 'Description' ). "</td>
714 <td width='5%'>" . __ ( 'Version' ). "</td>
715 <td width='10%'>" . __ ( 'Author' ). "</td></tr>" ;
717 $system_enabled = array_map ( "trim" , explode ( "," , PLUGINS
));
718 $user_enabled = array_map ( "trim" , explode ( "," , get_pref ( "_ENABLED_PLUGINS" , $_SESSION [ 'uid' ])));
720 $tmppluginhost = new PluginHost ();
721 $tmppluginhost -> load_all ( $tmppluginhost :: KIND_ALL
, $_SESSION [ "uid" ], true );
722 $tmppluginhost -> load_data ( true );
724 foreach ( $tmppluginhost -> get_plugins () as $name => $plugin ) {
725 $about = $plugin -> about ();
728 if ( in_array ( $name , $system_enabled )) {
729 $checked = "checked='1'" ;
736 print "<td align='center'><input disabled='1'
737 dojoType= \" dijit.form.CheckBox \" $checked
738 type= \" checkbox \" ></td>" ;
740 $plugin_icon = $checked ?
"plugin.png" : "plugin_disabled.png" ;
742 print "<td><label><img src='images/ $plugin_icon ' alt=''> $name </label></td>" ;
743 print "<td>" . htmlspecialchars ( $about [ 1 ]);
745 print " — <a target= \" _blank \" rel= \" noopener noreferrer \" class= \" visibleLink \"
746 href= \" " . htmlspecialchars ( $about [ 4 ]). " \" >" . __ ( "more info" ). "</a>" ;
749 print "<td>" . htmlspecialchars ( sprintf ( "%.2f" , $about [ 0 ])) . "</td>" ;
750 print "<td>" . htmlspecialchars ( $about [ 2 ]) . "</td>" ;
752 if ( count ( $tmppluginhost -> get_all ( $plugin )) > 0 ) {
753 if ( in_array ( $name , $system_enabled )) {
754 print "<td><a href='#' onclick= \" clearPluginData(' $name ') \"
755 class='visibleLink'>" . __ ( "Clear data" ). "</a></td>" ;
764 print "<tr><td colspan='4'><h3>" . __ ( "User plugins" ). "</h3></td></tr>" ;
766 print "<tr class= \" title \" >
767 <td width= \" 5% \" > </td>
768 <td width='10%'>" . __ ( 'Plugin' ). "</td>
769 <td width=''>" . __ ( 'Description' ). "</td>
770 <td width='5%'>" . __ ( 'Version' ). "</td>
771 <td width='10%'>" . __ ( 'Author' ). "</td></tr>" ;
774 foreach ( $tmppluginhost -> get_plugins () as $name => $plugin ) {
775 $about = $plugin -> about ();
779 if ( in_array ( $name , $system_enabled )) {
780 $checked = "checked='1'" ;
781 $disabled = "disabled='1'" ;
783 } else if ( in_array ( $name , $user_enabled )) {
784 $checked = "checked='1'" ;
786 $rowclass = "Selected" ;
793 print "<tr class=' $rowclass '>" ;
795 $plugin_icon = $checked ?
"plugin.png" : "plugin_disabled.png" ;
797 print "<td align='center'><input id='FPCHK- $name ' name='plugins[]' value=' $name ' onclick='toggleSelectRow2(this);'
798 dojoType= \" dijit.form.CheckBox \" $checked $disabled
799 type= \" checkbox \" ></td>" ;
801 print "<td><label for='FPCHK- $name '><img src='images/ $plugin_icon ' alt=''> $name </label></td>" ;
802 print "<td><label for='FPCHK- $name '>" . htmlspecialchars ( $about [ 1 ]) . "</label>" ;
804 print " — <a target= \" _blank \" rel= \" noopener noreferrer \" class= \" visibleLink \"
805 href= \" " . htmlspecialchars ( $about [ 4 ]). " \" >" . __ ( "more info" ). "</a>" ;
809 print "<td>" . htmlspecialchars ( sprintf ( "%.2f" , $about [ 0 ])) . "</td>" ;
810 print "<td>" . htmlspecialchars ( $about [ 2 ]) . "</td>" ;
812 if ( count ( $tmppluginhost -> get_all ( $plugin )) > 0 ) {
813 if ( in_array ( $name , $system_enabled ) ||
in_array ( $name , $user_enabled )) {
814 print "<td><a href='#' onclick= \" clearPluginData(' $name ') \" class='visibleLink'>" . __ ( "Clear data" ). "</a></td>" ;
828 print "<p><button dojoType= \" dijit.form.Button \" type= \" submit \" >" .
829 __ ( "Enable selected plugins" ). "</button></p>" ;
833 print "</div>" ; #pane
835 PluginHost
:: getInstance ()-> run_hooks ( PluginHost
:: HOOK_PREFS_TAB
,
836 "hook_prefs_tab" , "prefPrefs" );
838 print "</div>" ; #container
841 function toggleAdvanced () {
842 $_SESSION [ "prefs_show_advanced" ] = ! $_SESSION [ "prefs_show_advanced" ];
845 function otpqrcode () {
846 require_once "lib/phpqrcode/phpqrcode.php" ;
848 $sth = $this -> pdo
-> prepare ( "SELECT login,salt,otp_enabled
851 $sth -> execute ([ $_SESSION [ 'uid' ]]);
853 if ( $row = $sth -> fetch ()) {
855 $base32 = new \OTPHP\
Base32 ();
857 $login = $row [ "login" ];
858 $otp_enabled = sql_bool_to_bool ( $row [ "otp_enabled" ]);
861 $secret = $base32 -> encode ( sha1 ( $row [ "salt" ]));
863 QRcode
:: png ( "otpauth://totp/" . urlencode ( $login ).
864 "?secret= $secret &issuer=" . urlencode ( "Tiny Tiny RSS" ));
870 function otpenable () {
872 $password = clean ( $_REQUEST [ "password" ]);
873 $otp = clean ( $_REQUEST [ "otp" ]);
875 $authenticator = PluginHost
:: getInstance ()-> get_plugin ( $_SESSION [ "auth_module" ]);
877 if ( $authenticator -> check_password ( $_SESSION [ "uid" ], $password )) {
879 $sth = $this -> pdo
-> prepare ( "SELECT salt
882 $sth -> execute ([ $_SESSION [ 'uid' ]]);
884 if ( $row = $sth -> fetch ()) {
886 $base32 = new \OTPHP\
Base32 ();
888 $secret = $base32 -> encode ( sha1 ( $row [ "salt" ]));
889 $topt = new \OTPHP\
TOTP ( $secret );
891 $otp_check = $topt -> now ();
893 if ( $otp == $otp_check ) {
894 $sth = $this -> pdo
-> prepare ( "UPDATE ttrss_users
895 SET otp_enabled = true WHERE id = ?" );
897 $sth -> execute ([ $_SESSION [ 'uid' ]]);
901 print "ERROR:" . __ ( "Incorrect one time password" );
906 print "ERROR:" . __ ( "Incorrect password" );
911 static function isdefaultpassword () {
912 $authenticator = PluginHost
:: getInstance ()-> get_plugin ( $_SESSION [ "auth_module" ]);
914 if ( $authenticator &&
915 method_exists ( $authenticator , "check_password" ) &&
916 $authenticator -> check_password ( $_SESSION [ "uid" ], "password" )) {
924 function otpdisable () {
925 $password = clean ( $_REQUEST [ "password" ]);
927 $authenticator = PluginHost
:: getInstance ()-> get_plugin ( $_SESSION [ "auth_module" ]);
929 if ( $authenticator -> check_password ( $_SESSION [ "uid" ], $password )) {
931 $sth = $this -> pdo
-> prepare ( "UPDATE ttrss_users SET otp_enabled = false WHERE
933 $sth -> execute ([ $_SESSION [ 'uid' ]]);
937 print "ERROR: " . __ ( "Incorrect password" );
942 function setplugins () {
943 if ( is_array ( clean ( $_REQUEST [ "plugins" ])))
944 $plugins = join ( "," , clean ( $_REQUEST [ "plugins" ]));
948 set_pref ( "_ENABLED_PLUGINS" , $plugins , $_SESSION [ "uid" ]);
951 function clearplugindata () {
952 $name = clean ( $_REQUEST [ "name" ]);
954 PluginHost
:: getInstance ()-> clear_data ( PluginHost
:: getInstance ()-> get_plugin ( $name ));
957 function customizeCSS () {
958 $value = get_pref ( "USER_STYLESHEET" );
960 $value = str_replace ( "<br/>" , " \n " , $value );
962 print_notice ( T_sprintf ( "You can override colors, fonts and layout of your currently selected theme with custom CSS declarations here. <a target= \" _blank \" class= \" visibleLink \" href= \" %s\" >This file</a> can be used as a baseline." , "css/tt-rss.css" ));
964 print_hidden ( "op" , "rpc" );
965 print_hidden ( "method" , "setpref" );
966 print_hidden ( "key" , "USER_STYLESHEET" );
968 print "<table width='100%'><tr><td>" ;
969 print "<textarea dojoType= \" dijit.form.SimpleTextarea \"
970 style='font-size : 12px; width : 98%; height: 200px;'
971 placeHolder='body#ttrssMain { font-size : 14px; };'
972 name='value'> $value </textarea>" ;
973 print "</td></tr></table>" ;
975 print "<div class='dlgButtons'>" ;
976 print "<button dojoType= \" dijit.form.Button \"
977 onclick= \" dijit.byId('cssEditDlg').execute() \" >" . __ ( 'Save' ). "</button> " ;
978 print "<button dojoType= \" dijit.form.Button \"
979 onclick= \" dijit.byId('cssEditDlg').hide() \" >" . __ ( 'Cancel' ). "</button>" ;
984 function editPrefProfiles () {
985 print "<div dojoType= \" dijit.Toolbar \" >" ;
987 print "<div dojoType= \" dijit.form.DropDownButton \" >" .
988 "<span>" . __ ( 'Select' ). "</span>" ;
989 print "<div dojoType= \" dijit.Menu \" style= \" display: none; \" >" ;
990 print "<div onclick= \" selectTableRows('prefFeedProfileList', 'all') \"
991 dojoType= \" dijit.MenuItem \" >" . __ ( 'All' ). "</div>" ;
992 print "<div onclick= \" selectTableRows('prefFeedProfileList', 'none') \"
993 dojoType= \" dijit.MenuItem \" >" . __ ( 'None' ). "</div>" ;
994 print "</div></div>" ;
996 print "<div style= \" float : right \" >" ;
998 print "<input name= \" newprofile \" dojoType= \" dijit.form.ValidationTextBox \"
1000 <button dojoType= \" dijit.form.Button \"
1001 onclick= \" dijit.byId('profileEditDlg').addProfile() \" >" .
1002 __ ( 'Create profile' ). "</button></div>" ;
1006 $sth = $this -> pdo
-> prepare ( "SELECT title,id FROM ttrss_settings_profiles
1007 WHERE owner_uid = ? ORDER BY title" );
1008 $sth -> execute ([ $_SESSION [ 'uid' ]]);
1010 print "<div class= \" prefProfileHolder \" >" ;
1012 print "<form id= \" profile_edit_form \" onsubmit= \" return false \" >" ;
1014 print "<table width= \" 100% \" class= \" prefFeedProfileList \"
1015 cellspacing= \" 0 \" id= \" prefFeedProfileList \" >" ;
1017 print "<tr class= \" placeholder \" id= \" FCATR-0 \" >" ; #odd
1019 print "<td width='5%' align='center'><input
1021 onclick='toggleSelectRow2(this);'
1022 dojoType= \" dijit.form.CheckBox \"
1023 type= \" checkbox \" ></td>" ;
1025 if (! $_SESSION [ "profile" ]) {
1026 $is_active = __ ( "(active)" );
1031 print "<td><span>" .
1032 __ ( "Default profile" ) . " $is_active </span></td>" ;
1038 while ( $line = $sth -> fetch ()) {
1040 $profile_id = $line [ "id" ];
1041 $this_row_id = "id= \" FCATR- $profile_id\" " ;
1043 print "<tr class= \" placeholder \" $this_row_id >" ;
1045 $edit_title = htmlspecialchars ( $line [ "title" ]);
1047 print "<td width='5%' align='center'><input
1048 onclick='toggleSelectRow2(this);'
1049 id='FCATC- $profile_id '
1050 dojoType= \" dijit.form.CheckBox \"
1051 type= \" checkbox \" ></td>" ;
1053 if ( $_SESSION [ "profile" ] == $line [ "id" ]) {
1054 $is_active = __ ( "(active)" );
1059 print "<td><span dojoType= \" dijit.InlineEditBox \"
1060 width= \" 300px \" autoSave= \" false \"
1061 profile-id= \" $profile_id\" >" . $edit_title .
1062 "<script type= \" dojo/method \" event= \" onChange \" args= \" item \" >
1066 content: {op: 'rpc', method: 'saveprofile',
1068 id: this.srcNodeRef.getAttribute('profile-id')},
1069 load: function(response) {
1070 elem.attr('value', response);
1074 </span> $is_active </td>" ;
1085 print "<div class='dlgButtons'>
1086 <div style='float : left'>
1087 <button class= \" btn-danger \" dojoType= \" dijit.form.Button \" onclick= \" dijit.byId('profileEditDlg').removeSelected() \" >" .
1088 __ ( 'Remove selected profiles' ). "</button>
1089 <button dojoType= \" dijit.form.Button \" onclick= \" dijit.byId('profileEditDlg').activateProfile() \" >" .
1090 __ ( 'Activate profile' ). "</button>
1093 print "<button dojoType= \" dijit.form.Button \" onclick= \" dijit.byId('profileEditDlg').hide() \" >" .
1094 __ ( 'Close this window' ). "</button>" ;
1099 private function getShortDesc ( $pref_name ) {
1100 if ( isset ( $this -> pref_help
[ $pref_name ])) {
1101 return $this -> pref_help
[ $pref_name ][ 0 ];
1106 private function getHelpText ( $pref_name ) {
1107 if ( isset ( $this -> pref_help
[ $pref_name ])) {
1108 return $this -> pref_help
[ $pref_name ][ 1 ];
1113 private function getSectionName ( $id ) {
1114 if ( isset ( $this -> pref_sections
[ $id ])) {
1115 return $this -> pref_sections
[ $id ];