2015-01-19 09:52:15 +00:00
< ? php
class Af_Psql_Trgm extends Plugin {
2017-12-03 07:26:38 +00:00
/* @var PluginHost $host */
2015-01-19 09:52:15 +00:00
private $host ;
2021-01-11 09:23:46 +00:00
private $default_similarity = 0.75 ;
private $default_min_length = 32 ;
2015-01-19 09:52:15 +00:00
function about () {
return array ( 1.0 ,
" Marks similar articles as read (requires pg_trgm) " ,
" fox " );
}
function save () {
2017-12-03 07:26:38 +00:00
$similarity = ( float ) $_POST [ " similarity " ];
$min_title_length = ( int ) $_POST [ " min_title_length " ];
2017-12-02 11:07:48 +00:00
$enable_globally = checkbox_to_sql_bool ( $_POST [ " enable_globally " ]);
2015-01-19 09:52:15 +00:00
if ( $similarity < 0 ) $similarity = 0 ;
if ( $similarity > 1 ) $similarity = 1 ;
if ( $min_title_length < 0 ) $min_title_length = 0 ;
$similarity = sprintf ( " %.2f " , $similarity );
$this -> host -> set ( $this , " similarity " , $similarity );
$this -> host -> set ( $this , " min_title_length " , $min_title_length );
2015-06-23 04:36:25 +00:00
$this -> host -> set ( $this , " enable_globally " , $enable_globally );
2015-01-19 09:52:15 +00:00
2015-06-23 04:36:25 +00:00
echo T_sprintf ( " Data saved (%s, %d) " , $similarity , $enable_globally );
2015-01-19 09:52:15 +00:00
}
function init ( $host ) {
$this -> host = $host ;
$host -> add_hook ( $host :: HOOK_ARTICLE_FILTER , $this );
$host -> add_hook ( $host :: HOOK_PREFS_TAB , $this );
$host -> add_hook ( $host :: HOOK_PREFS_EDIT_FEED , $this );
$host -> add_hook ( $host :: HOOK_PREFS_SAVE_FEED , $this );
2015-01-19 12:46:15 +00:00
$host -> add_hook ( $host :: HOOK_ARTICLE_BUTTON , $this );
2015-01-19 09:52:15 +00:00
}
2015-01-19 12:46:15 +00:00
function get_js () {
2015-01-19 11:59:33 +00:00
return file_get_contents ( __DIR__ . " /init.js " );
}
function showrelated () {
2017-12-03 07:26:38 +00:00
$id = ( int ) $_REQUEST [ 'param' ];
2015-01-19 11:59:33 +00:00
$owner_uid = $_SESSION [ " uid " ];
2017-12-03 07:26:38 +00:00
$sth = $this -> pdo -> prepare ( " SELECT title FROM ttrss_entries, ttrss_user_entries
WHERE ref_id = id AND id = ? AND owner_uid = ? " );
$sth -> execute ([ $id , $owner_uid ]);
2015-01-19 11:59:33 +00:00
2017-12-03 07:26:38 +00:00
if ( $row = $sth -> fetch ()) {
2015-01-19 11:59:33 +00:00
2017-12-03 07:26:38 +00:00
$title = $row [ 'title' ];
2015-01-19 11:59:33 +00:00
2018-12-06 09:13:59 +00:00
print " <p> $title </p> " ;
2017-12-03 07:26:38 +00:00
$sth = $this -> pdo -> prepare ( " SELECT ttrss_entries.id AS id,
2015-01-19 12:46:15 +00:00
feed_id ,
ttrss_entries . title AS title ,
updated , link ,
ttrss_feeds . title AS feed_title ,
2018-12-07 15:02:42 +00:00
SIMILARITY ( ttrss_entries . title , ? ) AS sm
2015-01-19 12:46:15 +00:00
FROM
ttrss_entries , ttrss_user_entries LEFT JOIN ttrss_feeds ON ( ttrss_feeds . id = feed_id )
WHERE
ttrss_entries . id = ref_id AND
2017-12-03 07:26:38 +00:00
ttrss_user_entries . owner_uid = ? AND
ttrss_entries . id != ? AND
2015-01-19 13:42:10 +00:00
date_entered >= NOW () - INTERVAL '2 weeks'
2015-01-19 12:46:15 +00:00
ORDER BY
sm DESC , date_entered DESC
LIMIT 10 " );
2015-01-19 11:59:33 +00:00
2018-12-07 15:02:42 +00:00
$sth -> execute ([ $title , $owner_uid , $id ]);
2017-12-03 07:26:38 +00:00
2018-12-07 11:03:33 +00:00
print " <ul class='panel panel-scrollable'> " ;
2015-01-19 11:59:33 +00:00
2017-12-03 07:26:38 +00:00
while ( $line = $sth -> fetch ()) {
2018-12-06 09:13:59 +00:00
print " <li style='display : flex'> " ;
print " <i class='material-icons'>bookmark_outline</i> " ;
2015-01-19 12:46:15 +00:00
2017-12-03 07:26:38 +00:00
$sm = sprintf ( " %.2f " , $line [ 'sm' ]);
$article_link = htmlspecialchars ( $line [ " link " ]);
2018-12-06 09:13:59 +00:00
print " <div style='flex-grow : 2'> " ;
2017-12-03 07:26:38 +00:00
print " <a target= \" _blank \" rel= \" noopener noreferrer \" href= \" $article_link\ " > " .
$line [ " title " ] . " </a> " ;
2015-01-19 12:46:15 +00:00
2018-12-02 05:57:22 +00:00
print " (<a href= \" # \" onclick= \" Feeds.open( { feed: " . $line [ " feed_id " ] . " }) \" > " .
2017-12-03 07:26:38 +00:00
htmlspecialchars ( $line [ " feed_title " ]) . " </a>) " ;
2015-01-19 12:46:15 +00:00
2018-12-06 09:13:59 +00:00
print " — $sm " ;
print " </div> " ;
2020-09-23 10:04:26 +00:00
print " <div style='text-align : right' class='text-muted'> " . TimeHelper :: smart_date_time ( strtotime ( $line [ " updated " ])) . " </div> " ;
2015-06-23 01:35:24 +00:00
2017-12-03 07:26:38 +00:00
print " </li> " ;
}
2015-01-19 11:59:33 +00:00
2017-12-03 07:26:38 +00:00
print " </ul> " ;
}
2015-01-19 11:59:33 +00:00
2021-02-12 06:02:44 +00:00
print " <footer class='text-center'>
< button dojoType = 'dijit.form.Button' type = 'submit' class = 'alt-primary' > " .__('Close this window'). " </ button >
</ footer > " ;
2015-01-19 11:59:33 +00:00
2015-01-19 12:46:15 +00:00
}
function hook_article_button ( $line ) {
2018-12-05 19:48:14 +00:00
return " <i style= \" cursor : pointer \" class='material-icons'
2018-12-03 07:51:14 +00:00
onclick = \ " Plugins.Psql_Trgm.showRelated( " . $line [ " id " ] . " ) \"
2018-12-06 09:13:59 +00:00
title = '".__(' Show related articles ')."' > bookmark_outline </ i > " ;
2015-01-19 12:46:15 +00:00
}
2015-01-19 11:59:33 +00:00
2015-01-19 09:52:15 +00:00
function hook_prefs_tab ( $args ) {
if ( $args != " prefFeeds " ) return ;
2020-09-23 10:04:26 +00:00
print " <div dojoType= \" dijit.layout.AccordionPane \"
2021-01-11 09:23:46 +00:00
title = \ " <i class='material-icons'>extension</i> " . __ ( 'Mark similar articles as read (af_psql_trgm)' ) . " \" > " ;
2015-01-19 09:52:15 +00:00
if ( DB_TYPE != " pgsql " ) {
print_error ( " Database type not supported. " );
2016-08-25 06:47:02 +00:00
} else {
2015-01-19 09:52:15 +00:00
2017-12-03 07:26:38 +00:00
$res = $this -> pdo -> query ( " select 'similarity'::regproc " );
2015-01-19 09:52:15 +00:00
2021-01-11 09:23:46 +00:00
if ( ! $res || ! $res -> fetch ()) {
2016-08-25 06:47:02 +00:00
print_error ( " pg_trgm extension not found. " );
2015-01-19 09:52:15 +00:00
}
2015-01-19 11:22:41 +00:00
2021-01-11 09:23:46 +00:00
$similarity = $this -> host -> get ( $this , " similarity " , $this -> default_similarity );
$min_title_length = $this -> host -> get ( $this , " min_title_length " , $this -> default_min_length );
2016-08-25 06:47:02 +00:00
$enable_globally = $this -> host -> get ( $this , " enable_globally " );
print " <form dojoType= \" dijit.form.Form \" > " ;
print " <script type= \" dojo/method \" event= \" onSubmit \" args= \" evt \" >
evt . preventDefault ();
if ( this . validate ()) {
console . log ( dojo . objectToQuery ( this . getValues ()));
new Ajax . Request ( 'backend.php' , {
parameters : dojo . objectToQuery ( this . getValues ()),
onComplete : function ( transport ) {
2018-12-02 17:56:30 +00:00
Notify . info ( transport . responseText );
2016-08-25 06:47:02 +00:00
}
});
//this.reset();
}
</ script > " ;
2017-02-10 11:36:21 +00:00
print_hidden ( " op " , " pluginhandler " );
print_hidden ( " method " , " save " );
print_hidden ( " plugin " , " af_psql_trgm " );
2016-08-25 06:47:02 +00:00
2019-02-22 09:48:02 +00:00
print " <h2> " . __ ( " Global settings " ) . " </h2> " ;
2016-08-25 06:47:02 +00:00
2019-02-22 09:48:02 +00:00
print_notice ( " Enable for specific feeds in the feed editor. " );
2016-08-25 06:47:02 +00:00
2019-02-22 09:48:02 +00:00
print " <fieldset> " ;
2016-08-25 06:47:02 +00:00
2019-02-22 09:48:02 +00:00
print " <label> " . __ ( " Minimum similarity: " ) . " </label> " ;
print " <input dojoType= \" dijit.form.NumberSpinner \"
placeholder = \ " 0.75 \" id='psql_trgm_similarity'
required = \ " 1 \" name= \" similarity \" value= \" $similarity\ " > " ;
print " <div dojoType='dijit.Tooltip' connectId='psql_trgm_similarity' position='below'> " .
__ ( " PostgreSQL trigram extension returns string similarity as a floating point number (0-1). Setting it too low might produce false positives, zero disables checking. " ) .
" </div> " ;
print " </fieldset><fieldset> " ;
print " <label> " . __ ( " Minimum title length: " ) . " </label> " ;
print " <input dojoType= \" dijit.form.NumberSpinner \"
2016-08-25 06:47:02 +00:00
placeholder = \ " 32 \"
2019-02-22 09:48:02 +00:00
required = \ " 1 \" name= \" min_title_length \" value= \" $min_title_length\ " > " ;
print " </fieldset><fieldset> " ;
print " <label class='checkbox'> " ;
2017-02-10 11:57:25 +00:00
print_checkbox ( " enable_globally " , $enable_globally );
2019-02-22 09:48:02 +00:00
print " " . __ ( " Enable for all feeds: " );
print " </label> " ;
2016-08-25 06:47:02 +00:00
2019-02-22 09:48:02 +00:00
print " </fieldset> " ;
2016-08-25 06:47:02 +00:00
2019-02-22 09:48:02 +00:00
print_button ( " submit " , __ ( " Save " ), " class='alt-primary' " );
2016-08-25 06:47:02 +00:00
print " </form> " ;
2021-01-11 09:23:46 +00:00
/* cleanup */
$enabled_feeds = $this -> filter_unknown_feeds (
$this -> get_stored_array ( " enabled_feeds " ));
2016-08-25 06:47:02 +00:00
$this -> host -> set ( $this , " enabled_feeds " , $enabled_feeds );
if ( count ( $enabled_feeds ) > 0 ) {
print " <h3> " . __ ( " Currently enabled for (click to edit): " ) . " </h3> " ;
2018-12-07 11:03:33 +00:00
print " <ul class= \" panel panel-scrollable list list-unstyled \" > " ;
2016-08-25 06:47:02 +00:00
foreach ( $enabled_feeds as $f ) {
print " <li> " .
2018-12-14 14:30:41 +00:00
" <i class='material-icons'>rss_feed</i> <a href='#'
2018-12-01 19:39:29 +00:00
onclick = 'CommonDialogs.editFeed($f)' > " .
move the following to Feeds:
+ static function catchup_feed($feed, $cat_view, $owner_uid = false, $mode = 'all', $search = false) {
+ static function getFeedArticles($feed, $is_cat = false, $unread_only = false,
+ static function subscribe_to_feed($url, $cat_id = 0,
+ static function getFeedIcon($id) {
+ static function getFeedTitle($id, $cat = false) {
+ static function getCategoryUnread($cat, $owner_uid = false) {
+ static function getCategoryChildrenUnread($cat, $owner_uid = false) {
2017-05-04 11:50:56 +00:00
Feeds :: getFeedTitle ( $f ) . " </a></li> " ;
2016-08-25 06:47:02 +00:00
}
print " </ul> " ;
2015-01-19 11:22:41 +00:00
}
}
2015-01-19 09:52:15 +00:00
print " </div> " ;
}
function hook_prefs_edit_feed ( $feed_id ) {
2021-01-11 09:23:46 +00:00
print " <header> " . __ ( " Similarity (af_psql_trgm) " ) . " </header> " ;
2019-02-22 07:48:56 +00:00
print " <section> " ;
2015-01-19 09:52:15 +00:00
2021-01-11 09:23:46 +00:00
$enabled_feeds = $this -> get_stored_array ( " enabled_feeds " );
$checked = in_array ( $feed_id , $enabled_feeds ) ? " checked " : " " ;
2015-01-19 09:52:15 +00:00
2019-02-20 11:37:59 +00:00
print " <fieldset> " ;
2019-02-22 07:48:56 +00:00
print " <label class='checkbox'><input dojoType='dijit.form.CheckBox' type='checkbox' id='trgm_similarity_enabled'
name = 'trgm_similarity_enabled' $checked > " .__('Mark similar articles as read'). " </ label > " ;
2019-02-20 11:37:59 +00:00
print " </fieldset> " ;
2015-01-19 09:52:15 +00:00
2019-02-22 07:48:56 +00:00
print " </section> " ;
2015-01-19 09:52:15 +00:00
}
function hook_prefs_save_feed ( $feed_id ) {
2021-01-11 09:23:46 +00:00
$enabled_feeds = $this -> get_stored_array ( " enabled_feeds " );
2015-01-19 09:52:15 +00:00
2021-02-08 08:47:41 +00:00
$enable = checkbox_to_sql_bool ( $_POST [ " trgm_similarity_enabled " ] ? ? " " );
2015-01-19 09:52:15 +00:00
$key = array_search ( $feed_id , $enabled_feeds );
if ( $enable ) {
2020-09-17 16:02:27 +00:00
if ( $key === false ) {
2015-01-19 09:52:15 +00:00
array_push ( $enabled_feeds , $feed_id );
}
} else {
2020-09-17 16:02:27 +00:00
if ( $key !== false ) {
2015-01-19 09:52:15 +00:00
unset ( $enabled_feeds [ $key ]);
}
}
$this -> host -> set ( $this , " enabled_feeds " , $enabled_feeds );
}
function hook_article_filter ( $article ) {
if ( DB_TYPE != " pgsql " ) return $article ;
2017-12-03 07:26:38 +00:00
$res = $this -> pdo -> query ( " select 'similarity'::regproc " );
2021-01-11 09:23:46 +00:00
if ( ! $res || ! $res -> fetch ()) return $article ;
2015-01-19 09:52:15 +00:00
2015-06-23 04:36:25 +00:00
$enable_globally = $this -> host -> get ( $this , " enable_globally " );
2021-01-11 09:23:46 +00:00
if ( ! $enable_globally &&
! in_array ( $article [ " feed " ][ " id " ],
$this -> get_stored_array ( " enabled_feeds " ))) {
return $article ;
2015-06-23 04:36:25 +00:00
}
2015-01-19 09:52:15 +00:00
2021-01-11 09:23:46 +00:00
$similarity = ( float ) $this -> host -> get ( $this , " similarity " , $this -> default_similarity );
2015-01-19 09:52:15 +00:00
2021-01-11 09:23:46 +00:00
if ( $similarity < 0.01 ) {
Debug :: log ( " af_psql_trgm: similarity is set too low ( $similarity ) " , Debug :: $LOG_EXTENDED );
return $article ;
}
$min_title_length = ( int ) $this -> host -> get ( $this , " min_title_length " , $this -> default_min_length );
if ( mb_strlen ( $article [ " title " ]) < $min_title_length ) {
Debug :: log ( " af_psql_trgm: article title is too short (min: $min_title_length ) " , Debug :: $LOG_EXTENDED );
return $article ;
}
2015-01-19 09:52:15 +00:00
$owner_uid = $article [ " owner_uid " ];
2015-07-15 10:15:00 +00:00
$entry_guid = $article [ " guid_hashed " ];
2017-12-03 07:26:38 +00:00
$title_escaped = $article [ " title " ];
2015-01-19 09:52:15 +00:00
2015-06-23 01:35:24 +00:00
// trgm does not return similarity=1 for completely equal strings
2021-01-11 09:23:46 +00:00
// this seems to be no longer the case (fixed in upstream?)
2015-06-23 01:35:24 +00:00
2021-01-11 09:23:46 +00:00
/* $sth = $this -> pdo -> prepare ( " SELECT COUNT(id) AS nequal
2015-06-23 01:35:24 +00:00
FROM ttrss_entries , ttrss_user_entries WHERE ref_id = id AND
2016-08-01 05:26:11 +00:00
date_entered >= NOW () - interval '3 days' AND
2017-12-03 07:26:38 +00:00
title = ? AND
guid != ? AND
owner_uid = ? " );
$sth -> execute ([ $title_escaped , $entry_guid , $owner_uid ]);
$row = $sth -> fetch ();
$nequal = $row [ 'nequal' ];
2015-06-23 01:35:24 +00:00
2018-11-30 05:34:29 +00:00
Debug :: log ( " af_psql_trgm: num equals: $nequal " , Debug :: $LOG_EXTENDED );
2015-06-23 01:35:24 +00:00
if ( $nequal != 0 ) {
$article [ " force_catchup " ] = true ;
return $article ;
2021-01-11 09:23:46 +00:00
} */
2015-06-23 01:35:24 +00:00
2017-12-03 07:26:38 +00:00
$sth = $this -> pdo -> prepare ( " SELECT MAX(SIMILARITY(title, ?)) AS ms
2015-01-19 09:52:15 +00:00
FROM ttrss_entries , ttrss_user_entries WHERE ref_id = id AND
date_entered >= NOW () - interval '1 day' AND
2017-12-03 07:26:38 +00:00
guid != ? AND
owner_uid = ? " );
$sth -> execute ([ $title_escaped , $entry_guid , $owner_uid ]);
2015-01-19 09:52:15 +00:00
2017-12-03 07:26:38 +00:00
$row = $sth -> fetch ();
$similarity_result = $row [ 'ms' ];
2015-01-19 09:52:15 +00:00
2021-01-11 09:23:46 +00:00
Debug :: log ( " af_psql_trgm: similarity result for $title_escaped : $similarity_result " , Debug :: $LOG_EXTENDED );
2015-01-19 09:52:15 +00:00
if ( $similarity_result >= $similarity ) {
2021-01-11 09:23:46 +00:00
Debug :: log ( " af_psql_trgm: marking article as read ( $similarity_result >= $similarity ) " , Debug :: $LOG_EXTENDED );
2015-01-19 09:52:15 +00:00
$article [ " force_catchup " ] = true ;
}
return $article ;
}
function api_version () {
return 2 ;
}
2015-06-11 14:17:19 +00:00
private function filter_unknown_feeds ( $enabled_feeds ) {
$tmp = array ();
foreach ( $enabled_feeds as $feed ) {
2017-12-03 07:26:38 +00:00
$sth = $this -> pdo -> prepare ( " SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ? " );
$sth -> execute ([ $feed , $_SESSION [ 'uid' ]]);
2015-06-11 14:17:19 +00:00
2017-12-03 07:26:38 +00:00
if ( $row = $sth -> fetch ()) {
2015-06-11 14:17:19 +00:00
array_push ( $tmp , $feed );
}
}
return $tmp ;
}
2021-01-11 09:23:46 +00:00
private function get_stored_array ( $name ) {
$tmp = $this -> host -> get ( $this , $name );
if ( ! is_array ( $tmp )) $tmp = [];
return $tmp ;
}
2019-02-20 11:37:59 +00:00
}