]>
git.wh0rd.org - tt-rss.git/blob - plugins/af_psql_trgm/init.php
2 class Af_Psql_Trgm
extends Plugin
{
4 /* @var PluginHost $host */
9 "Marks similar articles as read (requires pg_trgm)" ,
14 $similarity = ( float ) $_POST [ "similarity" ];
15 $min_title_length = ( int ) $_POST [ "min_title_length" ];
16 $enable_globally = checkbox_to_sql_bool ( $_POST [ "enable_globally" ]);
18 if ( $similarity < 0 ) $similarity = 0 ;
19 if ( $similarity > 1 ) $similarity = 1 ;
21 if ( $min_title_length < 0 ) $min_title_length = 0 ;
23 $similarity = sprintf ( "%.2f" , $similarity );
25 $this -> host
-> set ( $this , "similarity" , $similarity );
26 $this -> host
-> set ( $this , "min_title_length" , $min_title_length );
27 $this -> host
-> set ( $this , "enable_globally" , $enable_globally );
29 echo T_sprintf ( "Data saved ( %s , %d )" , $similarity , $enable_globally );
32 function init ( $host ) {
35 $host -> add_hook ( $host :: HOOK_ARTICLE_FILTER
, $this );
36 $host -> add_hook ( $host :: HOOK_PREFS_TAB
, $this );
37 $host -> add_hook ( $host :: HOOK_PREFS_EDIT_FEED
, $this );
38 $host -> add_hook ( $host :: HOOK_PREFS_SAVE_FEED
, $this );
39 $host -> add_hook ( $host :: HOOK_ARTICLE_BUTTON
, $this );
44 return file_get_contents ( __DIR__
. "/init.js" );
47 function showrelated () {
48 $id = ( int ) $_REQUEST [ 'param' ];
49 $owner_uid = $_SESSION [ "uid" ];
51 $sth = $this -> pdo
-> prepare ( "SELECT title FROM ttrss_entries, ttrss_user_entries
52 WHERE ref_id = id AND id = ? AND owner_uid = ?" );
53 $sth -> execute ([ $id , $owner_uid ]);
55 if ( $row = $sth -> fetch ()) {
57 $title = $row [ 'title' ];
59 print "<h2> $title </h2>" ;
61 $sth = $this -> pdo
-> prepare ( "SELECT ttrss_entries.id AS id,
63 ttrss_entries.title AS title,
65 ttrss_feeds.title AS feed_title,
66 SIMILARITY(ttrss_entries.title, ' $title ') AS sm
68 ttrss_entries, ttrss_user_entries LEFT JOIN ttrss_feeds ON (ttrss_feeds.id = feed_id)
70 ttrss_entries.id = ref_id AND
71 ttrss_user_entries.owner_uid = ? AND
72 ttrss_entries.id != ? AND
73 date_entered >= NOW() - INTERVAL '2 weeks'
75 sm DESC, date_entered DESC
78 $sth -> execute ([ $owner_uid , $id ]);
80 print "<ul class= \" browseFeedList \" style= \" border-width : 1px \" >" ;
82 while ( $line = $sth -> fetch ()) {
84 print "<div class='insensitive small' style='margin-left : 20px; float : right'>" .
85 smart_date_time ( strtotime ( $line [ "updated" ]))
88 $sm = sprintf ( "%.2f" , $line [ 'sm' ]);
89 print "<img src='images/score_high.png' title=' $sm '
90 style='vertical-align : middle'>" ;
92 $article_link = htmlspecialchars ( $line [ "link" ]);
93 print " <a target= \" _blank \" rel= \" noopener noreferrer \" href= \" $article_link\" >" .
94 $line [ "title" ]. "</a>" ;
96 print " (<a href= \" # \" onclick= \" viewfeed({feed:" . $line [ "feed_id" ]. "}) \" >" .
97 htmlspecialchars ( $line [ "feed_title" ]). "</a>)" ;
99 print " <span class='insensitive'>( $sm )</span>" ;
108 print "<div style='text-align : center'>" ;
109 print "<button dojoType= \" dijit.form.Button \" onclick= \" dijit.byId('trgmRelatedDlg').hide() \" >" . __ ( 'Close this window' ). "</button>" ;
115 function hook_article_button ( $line ) {
116 return "<img src= \" plugins/af_psql_trgm/button.png \"
117 style= \" cursor : pointer \" style= \" cursor : pointer \"
118 onclick= \" showTrgmRelated(" . $line [ "id" ]. ") \"
119 class='tagsPic' title='" . __ ( 'Show related articles' ). "'>" ;
122 function hook_prefs_tab ( $args ) {
123 if ( $args != "prefFeeds" ) return ;
125 print "<div dojoType= \" dijit.layout.AccordionPane \" title= \" " . __ ( 'Mark similar articles as read' ). " \" >" ;
127 if ( DB_TYPE
!= "pgsql" ) {
128 print_error ( "Database type not supported." );
131 $res = $this -> pdo
-> query ( "select 'similarity'::regproc" );
133 if (! $res -> fetch ()) {
134 print_error ( "pg_trgm extension not found." );
137 $similarity = $this -> host
-> get ( $this , "similarity" );
138 $min_title_length = $this -> host
-> get ( $this , "min_title_length" );
139 $enable_globally = $this -> host
-> get ( $this , "enable_globally" );
141 if (! $similarity ) $similarity = '0.75' ;
142 if (! $min_title_length ) $min_title_length = '32' ;
144 print "<form dojoType= \" dijit.form.Form \" >" ;
146 print "<script type= \" dojo/method \" event= \" onSubmit \" args= \" evt \" >
147 evt.preventDefault();
148 if (this.validate()) {
149 console.log(dojo.objectToQuery(this.getValues()));
150 new Ajax.Request('backend.php', {
151 parameters: dojo.objectToQuery(this.getValues()),
152 onComplete: function(transport) {
153 notify_info(transport.responseText);
160 print_hidden ( "op" , "pluginhandler" );
161 print_hidden ( "method" , "save" );
162 print_hidden ( "plugin" , "af_psql_trgm" );
164 print "<p>" . __ ( "PostgreSQL trigram extension returns string similarity as a floating point number (0-1). Setting it too low might produce false positives, zero disables checking." ) . "</p>" ;
165 print_notice ( "Enable the plugin for specific feeds in the feed editor." );
167 print "<h3>" . __ ( "Global settings" ) . "</h3>" ;
171 print "<tr><td width= \" 40% \" >" . __ ( "Minimum similarity:" ) . "</td>" ;
173 <input dojoType= \" dijit.form.ValidationTextBox \"
175 required= \" 1 \" name= \" similarity \" value= \" $similarity\" ></td></tr>" ;
176 print "<tr><td width= \" 40% \" >" . __ ( "Minimum title length:" ) . "</td>" ;
178 <input dojoType= \" dijit.form.ValidationTextBox \"
180 required= \" 1 \" name= \" min_title_length \" value= \" $min_title_length\" ></td></tr>" ;
181 print "<tr><td width= \" 40% \" >" . __ ( "Enable for all feeds:" ) . "</td>" ;
183 print_checkbox ( "enable_globally" , $enable_globally );
188 print "<p>" ; print_button ( "submit" , __ ( "Save" ));
191 $enabled_feeds = $this -> host
-> get ( $this , "enabled_feeds" );
192 if (! array ( $enabled_feeds )) $enabled_feeds = array ();
194 $enabled_feeds = $this -> filter_unknown_feeds ( $enabled_feeds );
195 $this -> host
-> set ( $this , "enabled_feeds" , $enabled_feeds );
197 if ( count ( $enabled_feeds ) > 0 ) {
198 print "<h3>" . __ ( "Currently enabled for (click to edit):" ) . "</h3>" ;
200 print "<ul class= \" browseFeedList \" style= \" border-width : 1px \" >" ;
201 foreach ( $enabled_feeds as $f ) {
203 "<img src='images/pub_set.png'
204 style='vertical-align : middle'> <a href='#'
205 onclick='editFeed( $f )'>" .
206 Feeds
:: getFeedTitle ( $f ) . "</a></li>" ;
215 function hook_prefs_edit_feed ( $feed_id ) {
216 print "<div class= \" dlgSec \" >" . __ ( "Similarity (pg_trgm)" ). "</div>" ;
217 print "<div class= \" dlgSecCont \" >" ;
219 $enabled_feeds = $this -> host
-> get ( $this , "enabled_feeds" );
220 if (! array ( $enabled_feeds )) $enabled_feeds = array ();
222 $key = array_search ( $feed_id , $enabled_feeds );
223 $checked = $key !== FALSE ?
"checked" : "" ;
225 print "<hr/><input dojoType= \" dijit.form.CheckBox \" type= \" checkbox \" id= \" trgm_similarity_enabled \"
226 name= \" trgm_similarity_enabled \"
227 $checked > <label for= \" trgm_similarity_enabled \" >" . __ ( 'Mark similar articles as read' ). "</label>" ;
232 function hook_prefs_save_feed ( $feed_id ) {
233 $enabled_feeds = $this -> host
-> get ( $this , "enabled_feeds" );
234 if (! is_array ( $enabled_feeds )) $enabled_feeds = array ();
236 $enable = checkbox_to_sql_bool ( $_POST [ "trgm_similarity_enabled" ]);
237 $key = array_search ( $feed_id , $enabled_feeds );
240 if ( $key === FALSE ) {
241 array_push ( $enabled_feeds , $feed_id );
244 if ( $key !== FALSE ) {
245 unset ( $enabled_feeds [ $key ]);
249 $this -> host
-> set ( $this , "enabled_feeds" , $enabled_feeds );
252 function hook_article_filter ( $article ) {
254 if ( DB_TYPE
!= "pgsql" ) return $article ;
256 $res = $this -> pdo
-> query ( "select 'similarity'::regproc" );
257 if (! $res -> fetch ()) return $article ;
259 $enable_globally = $this -> host
-> get ( $this , "enable_globally" );
261 if (! $enable_globally ) {
262 $enabled_feeds = $this -> host
-> get ( $this , "enabled_feeds" );
263 $key = array_search ( $article [ "feed" ][ "id" ], $enabled_feeds );
264 if ( $key === FALSE ) return $article ;
267 $similarity = ( float ) $this -> host
-> get ( $this , "similarity" );
268 if ( $similarity < 0.01 ) return $article ;
270 $min_title_length = ( int ) $this -> host
-> get ( $this , "min_title_length" );
271 if ( mb_strlen ( $article [ "title" ]) < $min_title_length ) return $article ;
273 $owner_uid = $article [ "owner_uid" ];
274 $entry_guid = $article [ "guid_hashed" ];
275 $title_escaped = $article [ "title" ];
277 // trgm does not return similarity=1 for completely equal strings
279 $sth = $this -> pdo
-> prepare ( "SELECT COUNT(id) AS nequal
280 FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND
281 date_entered >= NOW() - interval '3 days' AND
285 $sth -> execute ([ $title_escaped , $entry_guid , $owner_uid ]);
287 $row = $sth -> fetch ();
288 $nequal = $row [ 'nequal' ];
290 _debug ( "af_psql_trgm: num equals: $nequal " );
293 $article [ "force_catchup" ] = true ;
297 $sth = $this -> pdo
-> prepare ( "SELECT MAX(SIMILARITY(title, ?)) AS ms
298 FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND
299 date_entered >= NOW() - interval '1 day' AND
302 $sth -> execute ([ $title_escaped , $entry_guid , $owner_uid ]);
304 $row = $sth -> fetch ();
305 $similarity_result = $row [ 'ms' ];
307 _debug ( "af_psql_trgm: similarity result: $similarity_result " );
309 if ( $similarity_result >= $similarity ) {
310 $article [ "force_catchup" ] = true ;
317 function api_version () {
321 private function filter_unknown_feeds ( $enabled_feeds ) {
324 foreach ( $enabled_feeds as $feed ) {
326 $sth = $this -> pdo
-> prepare ( "SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ?" );
327 $sth -> execute ([ $feed , $_SESSION [ 'uid' ]]);
329 if ( $row = $sth -> fetch ()) {
330 array_push ( $tmp , $feed );