2020-10-15 08:02:17 +02:00
< ? php
/**
2021-03-29 08:40:20 +02:00
* @ copyright Copyright ( C ) 2010 - 2021 , the Friendica project
2020-10-15 08:02:17 +02:00
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
*/
namespace Friendica\Worker ;
use Friendica\Core\Logger ;
2021-03-02 07:50:12 +01:00
use Friendica\Core\Worker ;
2021-03-03 08:07:29 +01:00
use Friendica\Database\Database ;
2020-10-15 08:02:17 +02:00
use Friendica\Database\DBA ;
2021-03-02 07:50:12 +01:00
use Friendica\Database\DBStructure ;
2020-10-15 08:02:17 +02:00
use Friendica\DI ;
2021-02-08 08:48:36 +01:00
use Friendica\Model\Item ;
2021-03-02 07:50:12 +01:00
use Friendica\Model\Post ;
2020-10-15 08:02:17 +02:00
class ExpirePosts
{
/**
2021-03-02 07:50:12 +01:00
* Expire posts and remove unused item - uri entries
*
* @ return void
2020-10-15 08:02:17 +02:00
*/
public static function execute ()
2021-03-02 07:50:12 +01:00
{
self :: deleteExpiredOriginPosts ();
2021-03-03 08:07:29 +01:00
self :: deleteOrphanedEntries ();
2021-03-02 07:50:12 +01:00
self :: deleteUnusedItemUri ();
self :: deleteExpiredExternalPosts ();
2021-03-03 12:01:16 +01:00
if ( DI :: config () -> get ( 'system' , 'add_missing_posts' )) {
self :: addMissingEntries ();
}
2021-03-03 08:07:29 +01:00
2021-03-02 08:06:22 +01:00
// Set the expiry for origin posta
Worker :: add ( PRIORITY_LOW , 'Expire' );
2021-03-02 07:50:12 +01:00
// update nodeinfo data after everything is cleaned up
2021-03-02 08:06:22 +01:00
Worker :: add ( PRIORITY_LOW , 'NodeInfo' );
2021-03-02 07:50:12 +01:00
}
/**
* Delete expired origin posts and orphaned post related table entries
*
* @ return void
*/
private static function deleteExpiredOriginPosts ()
{
2021-03-03 08:07:29 +01:00
Logger :: notice ( 'Delete expired posts' );
2021-03-02 07:50:12 +01:00
// physically remove anything that has been deleted for more than two months
$condition = [ " `gravity` = ? AND `deleted` AND `changed` < UTC_TIMESTAMP() - INTERVAL 60 DAY " , GRAVITY_PARENT ];
$rows = Post :: select ([ 'guid' , 'uri-id' , 'uid' ], $condition );
while ( $row = Post :: fetch ( $rows )) {
Logger :: info ( 'Delete expired item' , [ 'uri-id' => $row [ 'uri-id' ], 'guid' => $row [ 'guid' ]]);
Post\User :: delete ([ 'parent-uri-id' => $row [ 'uri-id' ], 'uid' => $row [ 'uid' ]]);
}
DBA :: close ( $rows );
2021-03-03 08:07:29 +01:00
Logger :: notice ( 'Delete expired posts - done' );
}
2021-03-02 07:50:12 +01:00
2021-03-03 08:07:29 +01:00
/**
* Delete orphaned entries in the post related tables
*
* @ return void
*/
private static function deleteOrphanedEntries ()
{
Logger :: notice ( 'Delete orphaned entries' );
2021-03-02 07:50:12 +01:00
2021-03-03 08:07:29 +01:00
// "post-user" is the leading table. So we delete every entry that isn't found there
$tables = [ 'item' , 'post' , 'post-content' , 'post-thread' , 'post-thread-user' ];
foreach ( $tables as $table ) {
if (( $table == 'item' ) && ! DBStructure :: existsTable ( 'item' )) {
continue ;
}
2021-03-02 07:50:12 +01:00
2021-03-03 08:07:29 +01:00
Logger :: notice ( 'Start collecting orphaned entries' , [ 'table' => $table ]);
$uris = DBA :: select ( $table , [ 'uri-id' ], [ " NOT `uri-id` IN (SELECT `uri-id` FROM `post-user`) " ]);
$affected_count = 0 ;
Logger :: notice ( 'Deleting orphaned entries - start' , [ 'table' => $table ]);
while ( $rows = DBA :: toArray ( $uris , false , 100 )) {
$ids = array_column ( $rows , 'uri-id' );
DBA :: delete ( $table , [ 'uri-id' => $ids ]);
$affected_count += DBA :: affectedRows ();
}
DBA :: close ( $uris );
Logger :: notice ( 'Orphaned entries deleted' , [ 'table' => $table , 'rows' => $affected_count ]);
}
Logger :: notice ( 'Delete orphaned entries - done' );
}
2021-03-02 07:50:12 +01:00
2021-03-03 08:07:29 +01:00
/**
* Add missing entries in some post related tables
*
* @ return void
*/
private static function addMissingEntries ()
{
Logger :: notice ( 'Adding missing entries' );
$rows = 0 ;
$userposts = DBA :: select ( 'post-user' , [], [ " `uri-id` not in (select `uri-id` from `post`) " ], [ 'group_by' => [ 'uri-id' ]]);
while ( $fields = DBA :: fetch ( $userposts )) {
$post_fields = DBStructure :: getFieldsForTable ( 'post' , $fields );
DBA :: insert ( 'post' , $post_fields , Database :: INSERT_IGNORE );
$rows ++ ;
}
DBA :: close ( $userposts );
if ( $rows > 0 ) {
Logger :: notice ( 'Added post entries' , [ 'rows' => $rows ]);
} else {
Logger :: notice ( 'No post entries added' );
}
$rows = 0 ;
$userposts = DBA :: select ( 'post-user' , [], [ " `gravity` = ? AND `uri-id` not in (select `uri-id` from `post-thread`) " , GRAVITY_PARENT ], [ 'group_by' => [ 'uri-id' ]]);
while ( $fields = DBA :: fetch ( $userposts )) {
$post_fields = DBStructure :: getFieldsForTable ( 'post-thread' , $fields );
$post_fields [ 'commented' ] = $post_fields [ 'changed' ] = $post_fields [ 'created' ];
DBA :: insert ( 'post-thread' , $post_fields , Database :: INSERT_IGNORE );
$rows ++ ;
}
DBA :: close ( $userposts );
if ( $rows > 0 ) {
Logger :: notice ( 'Added post-thread entries' , [ 'rows' => $rows ]);
} else {
Logger :: notice ( 'No post-thread entries added' );
}
$rows = 0 ;
$userposts = DBA :: select ( 'post-user' , [], [ " `gravity` = ? AND `id` not in (select `post-user-id` from `post-thread-user`) " , GRAVITY_PARENT ]);
while ( $fields = DBA :: fetch ( $userposts )) {
$post_fields = DBStructure :: getFieldsForTable ( 'post-thread-user' , $fields );
$post_fields [ 'commented' ] = $post_fields [ 'changed' ] = $post_fields [ 'created' ];
DBA :: insert ( 'post-thread-user' , $post_fields , Database :: INSERT_IGNORE );
$rows ++ ;
}
DBA :: close ( $userposts );
if ( $rows > 0 ) {
Logger :: notice ( 'Added post-thread-user entries' , [ 'rows' => $rows ]);
} else {
Logger :: notice ( 'No post-thread-user entries added' );
}
2021-03-02 07:50:12 +01:00
}
/**
* Delete unused item - uri entries
*/
private static function deleteUnusedItemUri ()
{
// We have to avoid deleting newly created "item-uri" entries.
// So we fetch a post that had been stored yesterday and only delete older ones.
$item = Post :: selectFirst ([ 'uri-id' ], [ " `uid` = ? AND `received` < UTC_TIMESTAMP() - INTERVAL ? DAY " , 0 , 1 ],
[ 'order' => [ 'received' => true ]]);
if ( empty ( $item [ 'uri-id' ])) {
Logger :: warning ( 'No item with uri-id found - we better quit here' );
return ;
}
2021-03-03 08:07:29 +01:00
Logger :: notice ( 'Start collecting orphaned URI-ID' , [ 'last-id' => $item [ 'uri-id' ]]);
2021-03-02 07:50:12 +01:00
$uris = DBA :: select ( 'item-uri' , [ 'id' ], [ " `id` < ?
2021-03-26 21:36:22 +01:00
AND NOT EXISTS ( SELECT `uri-id` FROM `post-user` WHERE `uri-id` = `item-uri` . `id` )
AND NOT EXISTS ( SELECT `parent-uri-id` FROM `post-user` WHERE `parent-uri-id` = `item-uri` . `id` )
AND NOT EXISTS ( SELECT `thr-parent-id` FROM `post-user` WHERE `thr-parent-id` = `item-uri` . `id` )
AND NOT EXISTS ( SELECT `external-id` FROM `post-user` WHERE `external-id` = `item-uri` . `id` ) " , $item['uri-id'] ]);
2021-03-02 07:50:12 +01:00
2021-03-03 08:07:29 +01:00
Logger :: notice ( 'Start deleting orphaned URI-ID' , [ 'last-id' => $item [ 'uri-id' ]]);
2021-03-02 07:50:12 +01:00
$affected_count = 0 ;
while ( $rows = DBA :: toArray ( $uris , false , 100 )) {
$ids = array_column ( $rows , 'id' );
DBA :: delete ( 'item-uri' , [ 'id' => $ids ]);
$affected_count += DBA :: affectedRows ();
Logger :: info ( 'Deleted' , [ 'rows' => $affected_count ]);
}
DBA :: close ( $uris );
Logger :: notice ( 'Orphaned URI-ID entries removed' , [ 'rows' => $affected_count ]);
}
/**
* Delete old external post entries
*/
private static function deleteExpiredExternalPosts ()
2020-10-15 08:02:17 +02:00
{
$expire_days = DI :: config () -> get ( 'system' , 'dbclean-expire-days' );
$expire_days_unclaimed = DI :: config () -> get ( 'system' , 'dbclean-expire-unclaimed' );
if ( empty ( $expire_days_unclaimed )) {
$expire_days_unclaimed = $expire_days ;
}
2020-10-17 10:16:17 +02:00
$limit = DI :: config () -> get ( 'system' , 'dbclean-expire-limit' );
if ( empty ( $limit )) {
return ;
}
2020-10-15 08:02:17 +02:00
if ( ! empty ( $expire_days )) {
2021-02-28 18:56:56 +01:00
Logger :: notice ( 'Start collecting expired threads' , [ 'expiry_days' => $expire_days ]);
$uris = DBA :: select ( 'item-uri' , [ 'id' ], [ " `id` IN
( SELECT `uri-id` FROM `post-thread` WHERE `received` < UTC_TIMESTAMP () - INTERVAL ? DAY
AND NOT `uri-id` IN ( SELECT `uri-id` FROM `post-thread-user`
WHERE ( `mention` OR `starred` OR `wall` OR `pinned` ) AND `uri-id` = `post-thread` . `uri-id` )
AND NOT `uri-id` IN ( SELECT `uri-id` FROM `post-category`
WHERE `uri-id` = `post-thread` . `uri-id` )
AND NOT `uri-id` IN ( SELECT `uri-id` FROM `post-media`
WHERE `uri-id` = `post-thread` . `uri-id` )
AND NOT `uri-id` IN ( SELECT `parent-uri-id` FROM `post-user` INNER JOIN `contact` ON `contact` . `id` = `contact-id` AND `notify_new_posts`
WHERE `parent-uri-id` = `post-thread` . `uri-id` )
AND NOT `uri-id` IN ( SELECT `parent-uri-id` FROM `post-user`
2021-04-01 06:58:53 +02:00
WHERE ( `origin` OR `event-id` != 0 OR `post-type` IN ( ? , ? )) AND `parent-uri-id` = `post-thread` . `uri-id` )
2021-02-28 18:56:56 +01:00
AND NOT `uri-id` IN ( SELECT `uri-id` FROM `post-content`
WHERE `resource-id` != 0 AND `uri-id` = `post-thread` . `uri-id` )) " ,
2021-04-01 06:58:53 +02:00
$expire_days , Item :: PT_PERSONAL_NOTE , Item :: PT_IMAGE ]);
2020-10-15 08:02:17 +02:00
2021-02-28 18:56:56 +01:00
Logger :: notice ( 'Start deleting expired threads' );
$affected_count = 0 ;
while ( $rows = DBA :: toArray ( $uris , false , 100 )) {
$ids = array_column ( $rows , 'id' );
DBA :: delete ( 'item-uri' , [ 'id' => $ids ]);
$affected_count += DBA :: affectedRows ();
}
DBA :: close ( $uris );
2020-10-17 14:39:42 +02:00
2021-02-28 18:56:56 +01:00
Logger :: notice ( 'Deleted expired threads' , [ 'rows' => $affected_count ]);
2020-10-15 08:02:17 +02:00
}
if ( ! empty ( $expire_days_unclaimed )) {
2021-02-28 18:56:56 +01:00
Logger :: notice ( 'Start collecting unclaimed public items' , [ 'expiry_days' => $expire_days_unclaimed ]);
$uris = DBA :: select ( 'item-uri' , [ 'id' ], [ " `id` IN
( SELECT `uri-id` FROM `post-user` WHERE `gravity` = ? AND `uid` = ? AND `received` < UTC_TIMESTAMP () - INTERVAL ? DAY
AND NOT `uri-id` IN ( SELECT `parent-uri-id` FROM `post-user` AS `i` WHERE `i` . `uid` != ?
AND `i` . `parent-uri-id` = `post-user` . `uri-id` )
AND NOT `uri-id` IN ( SELECT `parent-uri-id` FROM `post-user` AS `i` WHERE `i` . `uid` = ?
AND `i` . `parent-uri-id` = `post-user` . `uri-id` AND `i` . `received` > UTC_TIMESTAMP () - INTERVAL ? DAY )) " ,
GRAVITY_PARENT , 0 , $expire_days_unclaimed , 0 , 0 , $expire_days_unclaimed ]);
2020-10-15 08:02:17 +02:00
2021-02-28 18:56:56 +01:00
Logger :: notice ( 'Start deleting unclaimed public items' );
$affected_count = 0 ;
while ( $rows = DBA :: toArray ( $uris , false , 100 )) {
$ids = array_column ( $rows , 'id' );
DBA :: delete ( 'item-uri' , [ 'id' => $ids ]);
$affected_count += DBA :: affectedRows ();
}
DBA :: close ( $uris );
Logger :: notice ( 'Deleted unclaimed public items' , [ 'rows' => $affected_count ]);
2020-10-15 08:02:17 +02:00
}
}
}