]> git.mxchange.org Git - friendica.git/blob - src/Worker/CronJobs.php
19e6fbaf737e07efd146d7c5f6faf6a157392133
[friendica.git] / src / Worker / CronJobs.php
1 <?php
2 /**
3  * @file src/worker/CronJobs.php
4  */
5 namespace Friendica\Worker;
6
7 use Friendica\App;
8 use Friendica\BaseObject;
9 use Friendica\Core\Cache;
10 use Friendica\Core\Config;
11 use Friendica\Database\DBA;
12 use Friendica\Database\PostUpdate;
13 use Friendica\Model\Contact;
14 use Friendica\Model\GContact;
15 use Friendica\Model\Photo;
16 use Friendica\Model\User;
17 use Friendica\Network\Probe;
18 use Friendica\Protocol\PortableContact;
19 use Friendica\Util\Proxy as ProxyUtils;
20
21 require_once 'include/dba.php';
22 require_once 'mod/nodeinfo.php';
23
24 class CronJobs
25 {
26         public static function execute($command = '')
27         {
28                 $a = BaseObject::getApp();
29
30                 // No parameter set? So return
31                 if ($command == '') {
32                         return;
33                 }
34
35                 logger("Starting cronjob " . $command, LOGGER_DEBUG);
36
37                 // Call possible post update functions
38                 // see src/Database/PostUpdate.php for more details
39                 if ($command == 'post_update') {
40                         PostUpdate::update();
41                         return;
42                 }
43
44                 // update nodeinfo data
45                 if ($command == 'nodeinfo') {
46                         nodeinfo_cron();
47                         return;
48                 }
49
50                 // Expire and remove user entries
51                 if ($command == 'expire_and_remove_users') {
52                         self::expireAndRemoveUsers();
53                         return;
54                 }
55
56                 if ($command == 'update_contact_birthdays') {
57                         Contact::updateBirthdays();
58                         return;
59                 }
60
61                 if ($command == 'update_photo_albums') {
62                         self::updatePhotoAlbums();
63                         return;
64                 }
65
66                 // Clear cache entries
67                 if ($command == 'clear_cache') {
68                         self::clearCache($a);
69                         return;
70                 }
71
72                 // Repair missing Diaspora values in contacts
73                 if ($command == 'repair_diaspora') {
74                         self::repairDiaspora($a);
75                         return;
76                 }
77
78                 // Repair entries in the database
79                 if ($command == 'repair_database') {
80                         self::repairDatabase();
81                         return;
82                 }
83
84                 logger("Xronjob " . $command . " is unknown.", LOGGER_DEBUG);
85
86                 return;
87         }
88
89         /**
90          * @brief Update the cached values for the number of photo albums per user
91          */
92         private static function updatePhotoAlbums()
93         {
94                 $r = q("SELECT `uid` FROM `user` WHERE NOT `account_expired` AND NOT `account_removed`");
95                 if (!DBA::isResult($r)) {
96                         return;
97                 }
98
99                 foreach ($r as $user) {
100                         Photo::clearAlbumCache($user['uid']);
101                 }
102         }
103
104         /**
105          * @brief Expire and remove user entries
106          */
107         private static function expireAndRemoveUsers()
108         {
109                 // expire any expired regular accounts. Don't expire forums.
110                 $condition = ["NOT `account_expired` AND `account_expires_on` > ? AND `account_expires_on` < UTC_TIMESTAMP() AND `page-flags` = 0", NULL_DATE];
111                 DBA::update('user', ['account_expired' => true], $condition);
112
113                 // Remove any freshly expired account
114                 $users = DBA::select('user', ['uid'], ['account_expired' => true, 'account_removed' => false]);
115                 while ($user = DBA::fetch($users)) {
116                         User::remove($user['uid']);
117                 }
118
119                 // delete user records for recently removed accounts
120                 $users = DBA::select('user', ['uid'], ["`account_removed` AND `account_expires_on` < UTC_TIMESTAMP() - INTERVAL 3 DAY"]);
121                 while ($user = DBA::fetch($users)) {
122                         DBA::delete('user', ['uid' => $user['uid']]);
123                 }
124         }
125
126         /**
127          * @brief Clear cache entries
128          *
129          * @param App $a
130          */
131         private static function clearCache(App $a)
132         {
133                 $last = Config::get('system', 'cache_last_cleared');
134
135                 if ($last) {
136                         $next = $last + (3600); // Once per hour
137                         $clear_cache = ($next <= time());
138                 } else {
139                         $clear_cache = true;
140                 }
141
142                 if (!$clear_cache) {
143                         return;
144                 }
145
146                 // clear old cache
147                 Cache::clear();
148
149                 // clear old item cache files
150                 clear_cache();
151
152                 // clear cache for photos
153                 clear_cache($a->get_basepath(), $a->get_basepath() . "/photo");
154
155                 // clear smarty cache
156                 clear_cache($a->get_basepath() . "/view/smarty3/compiled", $a->get_basepath() . "/view/smarty3/compiled");
157
158                 // clear cache for image proxy
159                 if (!Config::get("system", "proxy_disabled")) {
160                         clear_cache($a->get_basepath(), $a->get_basepath() . "/proxy");
161
162                         $cachetime = Config::get('system', 'proxy_cache_time');
163
164                         if (!$cachetime) {
165                                 $cachetime = ProxyUtils::DEFAULT_TIME;
166                         }
167
168                         $condition = ['`uid` = 0 AND `resource-id` LIKE "pic:%" AND `created` < NOW() - INTERVAL ? SECOND', $cachetime];
169                         DBA::delete('photo', $condition);
170                 }
171
172                 // Delete the cached OEmbed entries that are older than three month
173                 DBA::delete('oembed', ["`created` < NOW() - INTERVAL 3 MONTH"]);
174
175                 // Delete the cached "parse_url" entries that are older than three month
176                 DBA::delete('parsed_url', ["`created` < NOW() - INTERVAL 3 MONTH"]);
177
178                 // Maximum table size in megabyte
179                 $max_tablesize = intval(Config::get('system', 'optimize_max_tablesize')) * 1000000;
180                 if ($max_tablesize == 0) {
181                         $max_tablesize = 100 * 1000000; // Default are 100 MB
182                 }
183                 if ($max_tablesize > 0) {
184                         // Minimum fragmentation level in percent
185                         $fragmentation_level = intval(Config::get('system', 'optimize_fragmentation')) / 100;
186                         if ($fragmentation_level == 0) {
187                                 $fragmentation_level = 0.3; // Default value is 30%
188                         }
189
190                         // Optimize some tables that need to be optimized
191                         $r = q("SHOW TABLE STATUS");
192                         foreach ($r as $table) {
193
194                                 // Don't optimize tables that are too large
195                                 if ($table["Data_length"] > $max_tablesize) {
196                                         continue;
197                                 }
198
199                                 // Don't optimize empty tables
200                                 if ($table["Data_length"] == 0) {
201                                         continue;
202                                 }
203
204                                 // Calculate fragmentation
205                                 $fragmentation = $table["Data_free"] / ($table["Data_length"] + $table["Index_length"]);
206
207                                 logger("Table " . $table["Name"] . " - Fragmentation level: " . round($fragmentation * 100, 2), LOGGER_DEBUG);
208
209                                 // Don't optimize tables that needn't to be optimized
210                                 if ($fragmentation < $fragmentation_level) {
211                                         continue;
212                                 }
213
214                                 // So optimize it
215                                 logger("Optimize Table " . $table["Name"], LOGGER_DEBUG);
216                                 q("OPTIMIZE TABLE `%s`", DBA::escape($table["Name"]));
217                         }
218                 }
219
220                 Config::set('system', 'cache_last_cleared', time());
221         }
222
223         /**
224          * @brief Repair missing values in Diaspora contacts
225          *
226          * @param App $a
227          */
228         private static function repairDiaspora(App $a)
229         {
230                 $starttime = time();
231
232                 $r = q("SELECT `id`, `url` FROM `contact`
233                         WHERE `network` = '%s' AND (`batch` = '' OR `notify` = '' OR `poll` = '' OR pubkey = '')
234                                 ORDER BY RAND() LIMIT 50", DBA::escape(NETWORK_DIASPORA));
235                 if (!DBA::isResult($r)) {
236                         return;
237                 }
238
239                 foreach ($r AS $contact) {
240                         // Quit the loop after 3 minutes
241                         if (time() > ($starttime + 180)) {
242                                 return;
243                         }
244
245                         if (!PortableContact::reachable($contact["url"])) {
246                                 continue;
247                         }
248
249                         $data = Probe::uri($contact["url"]);
250                         if ($data["network"] != NETWORK_DIASPORA) {
251                                 continue;
252                         }
253
254                         logger("Repair contact " . $contact["id"] . " " . $contact["url"], LOGGER_DEBUG);
255                         q("UPDATE `contact` SET `batch` = '%s', `notify` = '%s', `poll` = '%s', pubkey = '%s' WHERE `id` = %d",
256                                 DBA::escape($data["batch"]), DBA::escape($data["notify"]), DBA::escape($data["poll"]), DBA::escape($data["pubkey"]),
257                                 intval($contact["id"]));
258                 }
259         }
260
261         /**
262          * @brief Do some repairs in database entries
263          *
264          */
265         private static function repairDatabase()
266         {
267                 // Sometimes there seem to be issues where the "self" contact vanishes.
268                 // We haven't found the origin of the problem by now.
269                 $r = q("SELECT `uid` FROM `user` WHERE NOT EXISTS (SELECT `uid` FROM `contact` WHERE `contact`.`uid` = `user`.`uid` AND `contact`.`self`)");
270                 if (DBA::isResult($r)) {
271                         foreach ($r AS $user) {
272                                 logger('Create missing self contact for user ' . $user['uid']);
273                                 Contact::createSelfFromUserId($user['uid']);
274                         }
275                 }
276
277                 // Set the parent if it wasn't set. (Shouldn't happen - but does sometimes)
278                 // This call is very "cheap" so we can do it at any time without a problem
279                 q("UPDATE `item` INNER JOIN `item` AS `parent` ON `parent`.`uri` = `item`.`parent-uri` AND `parent`.`uid` = `item`.`uid` SET `item`.`parent` = `parent`.`id` WHERE `item`.`parent` = 0");
280
281                 // There was an issue where the nick vanishes from the contact table
282                 q("UPDATE `contact` INNER JOIN `user` ON `contact`.`uid` = `user`.`uid` SET `nick` = `nickname` WHERE `self` AND `nick`=''");
283
284                 // Update the global contacts for local users
285                 $r = q("SELECT `uid` FROM `user` WHERE `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`");
286                 if (DBA::isResult($r)) {
287                         foreach ($r AS $user) {
288                                 GContact::updateForUser($user["uid"]);
289                         }
290                 }
291
292                 /// @todo
293                 /// - remove thread entries without item
294                 /// - remove sign entries without item
295                 /// - remove children when parent got lost
296                 /// - set contact-id in item when not present
297         }
298 }