]> git.mxchange.org Git - friendica.git/blob - src/Module/Admin/Federation.php
Merge pull request #11477 from annando/avatar-file-cache
[friendica.git] / src / Module / Admin / Federation.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2022, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Module\Admin;
23
24 use Friendica\Core\Protocol;
25 use Friendica\Core\Renderer;
26 use Friendica\Database\DBA;
27 use Friendica\DI;
28 use Friendica\Model\GServer;
29 use Friendica\Module\BaseAdmin;
30
31 class Federation extends BaseAdmin
32 {
33         protected function content(array $request = []): string
34         {
35                 parent::content();
36
37                 // get counts on active federation systems this node is knowing
38                 // We list the more common systems by name. The rest is counted as "other"
39                 $systems = [
40                         'friendica'    => ['name' => 'Friendica', 'color' => '#ffc018'], // orange from the logo
41                         'birdsitelive' => ['name' => 'BirdsiteLIVE', 'color' => '#1b6ec2'], // Color from the page
42                         'bookwyrm'     => ['name' => 'BookWyrm', 'color' => '#00d1b2'], // Color from the page
43                         'diaspora'     => ['name' => 'Diaspora', 'color' => '#a1a1a1'], // logo is black and white, makes a gray
44                         'funkwhale'    => ['name' => 'Funkwhale', 'color' => '#4082B4'], // From the homepage
45                         'gnusocial'    => ['name' => 'GNU Social/Statusnet', 'color' => '#a22430'], // dark red from the logo
46                         'gotosocial'   => ['name' => 'GoToSocial', 'color' => '#df8958'], // Some color from their mascot
47                         'hometown'     => ['name' => 'Hometown', 'color' => '#1f70c1'], // Color from the Patreon page
48                         'hubzilla'     => ['name' => 'Hubzilla/Red Matrix', 'color' => '#43488a'], // blue from the logo
49                         'lemmy'        => ['name' => 'Lemmy', 'color' => '#00c853'], // Green from the page
50                         'mastodon'     => ['name' => 'Mastodon', 'color' => '#1a9df9'], // blue from the Mastodon logo
51                         'misskey'      => ['name' => 'Misskey', 'color' => '#ccfefd'], // Font color of the homepage
52                         'mobilizon'    => ['name' => 'Mobilizon', 'color' => '#ffd599'], // Background color of parts of the homepage
53                         'nextcloud'    => ['name' => 'Nextcloud', 'color' => '#1cafff'], // Logo color
54                         'mistpark'     => ['name' => 'Nomad projects (Mistpark, Osada, Roadhouse, Zap)', 'color' => '#348a4a'], // Green like the Mistpark green
55                         'owncast'      => ['name' => 'Owncast', 'color' => '#007bff'], // Font color of the homepage
56                         'peertube'     => ['name' => 'Peertube', 'color' => '#ffad5c'], // One of the logo colors
57                         'pixelfed'     => ['name' => 'Pixelfed', 'color' => '#11da47'], // One of the logo colors
58                         'pleroma'      => ['name' => 'Pleroma', 'color' => '#E46F0F'], // Orange from the text that is used on Pleroma instances
59                         'plume'        => ['name' => 'Plume', 'color' => '#7765e3'], // From the homepage
60                         'relay'        => ['name' => 'ActivityPub Relay', 'color' => '#888888'], // Grey like the second color of the ActivityPub logo
61                         'socialhome'   => ['name' => 'SocialHome', 'color' => '#52056b'], // lilac from the Django Image used at the Socialhome homepage
62                         'wordpress'    => ['name' => 'WordPress', 'color' => '#016087'], // Background color of the homepage
63                         'write.as'     => ['name' => 'Write.as', 'color' => '#00ace3'], // Border color of the homepage
64                         'writefreely'  => ['name' => 'WriteFreely', 'color' => '#292929'], // Font color of the homepage
65                         'other'        => ['name' => DI::l10n()->t('Other'), 'color' => '#F1007E'], // ActivityPub main color
66                 ];
67
68                 $platforms = array_keys($systems);
69
70                 $counts = [];
71                 foreach ($platforms as $platform) {
72                         $counts[$platform] = [];
73                 }
74
75                 $total    = 0;
76                 $users    = 0;
77                 $month    = 0;
78                 $halfyear = 0;
79                 $posts    = 0;
80
81                 $gservers = DBA::p("SELECT COUNT(*) AS `total`, SUM(`registered-users`) AS `users`,
82                         SUM(IFNULL(`local-posts`, 0) + IFNULL(`local-comments`, 0)) AS `posts`,
83                         SUM(IFNULL(`active-month-users`, `active-week-users`)) AS `month`,
84                         SUM(IFNULL(`active-halfyear-users`, `active-week-users`)) AS `halfyear`, `platform`,
85                         ANY_VALUE(`network`) AS `network`, MAX(`version`) AS `version`
86                         FROM `gserver` WHERE NOT `failed` AND `detection-method` != ? AND NOT `network` IN (?, ?) GROUP BY `platform`", GServer::DETECT_MANUAL, Protocol::PHANTOM, Protocol::FEED);
87                 while ($gserver = DBA::fetch($gservers)) {
88                         $total    += $gserver['total'];
89                         $users    += $gserver['users'];
90                         $month    += $gserver['month'];
91                         $halfyear += $gserver['halfyear'];
92                         $posts    += $gserver['posts'];
93
94                         $versionCounts = [];
95                         $versions = DBA::p("SELECT COUNT(*) AS `total`, `version` FROM `gserver`
96                                 WHERE NOT `failed` AND `platform` = ? AND `detection-method` != ? AND NOT `network` IN (?, ?)
97                                 GROUP BY `version` ORDER BY `version`", $gserver['platform'], GServer::DETECT_MANUAL, Protocol::PHANTOM, Protocol::FEED);
98                         while ($version = DBA::fetch($versions)) {
99                                 $version['version'] = str_replace(["\n", "\r", "\t"], " ", $version['version']);
100
101                                 if (in_array($gserver['platform'], ['Red Matrix', 'redmatrix', 'red'])) {
102                                         $version['version'] = 'Red ' . $version['version'];
103                                 } elseif (in_array($gserver['platform'], ['osada', 'mistpark', 'roadhouse', 'zap'])) {
104                                         $version['version'] = $gserver['platform'] . ' ' . $version['version'];
105                                 } elseif (in_array($gserver['platform'], ['activityrelay', 'pub-relay', 'selective-relay', 'aoderelay'])) {
106                                         $version['version'] = $gserver['platform'] . '-' . $version['version'];
107                                 }
108
109                                 $versionCounts[] = $version;
110                         }
111                         DBA::close($versions);
112
113                         $platform = $gserver['platform'] = strtolower($gserver['platform']);
114
115                         if ($platform == 'friendika') {
116                                 $platform = 'friendica';
117                         } elseif (in_array($platform, ['red matrix', 'redmatrix', 'red'])) {
118                                 $platform = 'hubzilla';
119                         } elseif (in_array($platform, ['mistpark', 'osada', 'roadhouse', 'zap'])) {
120                                 $platform = 'mistpark';
121                         } elseif(stristr($platform, 'pleroma')) {
122                                 $platform = 'pleroma';
123                         } elseif(stristr($platform, 'statusnet')) {
124                                 $platform = 'gnusocial';
125                         } elseif(stristr($platform, 'wordpress')) {
126                                 $platform = 'wordpress';
127                         } elseif (in_array($platform, ['activityrelay', 'pub-relay', 'selective-relay', 'aoderelay'])) {
128                                 $platform = 'relay';
129                         } elseif (!in_array($platform, $platforms)) {
130                                 $platform = 'other';
131                         }
132
133                         if ($platform != $gserver['platform']) {
134                                 if ($platform == 'other') {
135                                         $versionCounts = $counts[$platform][1] ?? [];
136                                         $versionCounts[] = ['version' => $gserver['platform'] ?: DI::l10n()->t('unknown'), 'total' => $gserver['total']];
137                                         $gserver['version'] = '';
138                                 } else {
139                                         $versionCounts = array_merge($versionCounts, $counts[$platform][1] ?? []);
140                                 }
141
142                                 $gserver['platform']  = $platform;
143                                 $gserver['total']    += $counts[$platform][0]['total'] ?? 0;
144                                 $gserver['users']    += $counts[$platform][0]['users'] ?? 0;
145                                 $gserver['month']    += $counts[$platform][0]['month'] ?? 0;
146                                 $gserver['halfyear'] += $counts[$platform][0]['halfyear'] ?? 0;
147                                 $gserver['posts']    += $counts[$platform][0]['posts'] ?? 0;
148                         }
149
150                         if ($platform == 'friendica') {
151                                 $versionCounts = self::reformaFriendicaVersions($versionCounts);
152                         } elseif ($platform == 'pleroma') {
153                                 $versionCounts = self::reformaPleromaVersions($versionCounts);
154                         } elseif ($platform == 'diaspora') {
155                                 $versionCounts = self::reformaDiasporaVersions($versionCounts);
156                         } elseif ($platform == 'relay') {
157                                 $versionCounts = self::reformatRelayVersions($versionCounts);
158                         } elseif (in_array($platform, ['funkwhale', 'mastodon', 'mobilizon', 'misskey', 'gotosocial'])) {
159                                 $versionCounts = self::removeVersionSuffixes($versionCounts);
160                         }
161
162                         if (!in_array($platform, ['other', 'relay', 'mistpark'])) {
163                                 $versionCounts = self::sortVersion($versionCounts);
164                         } else {
165                                 ksort($versionCounts);
166                         }
167
168                         $gserver['platform']    = $systems[$platform]['name'];
169                         $gserver['totallbl']    = DI::l10n()->t('%s total systems', number_format($gserver['total']));
170                         $gserver['monthlbl']    = DI::l10n()->t('%s active users last month', number_format($gserver['month']));
171                         $gserver['halfyearlbl'] = DI::l10n()->t('%s active users last six months', number_format($gserver['halfyear']));
172                         $gserver['userslbl']    = DI::l10n()->t('%s registered users', number_format($gserver['users']));
173                         $gserver['postslbl']    = DI::l10n()->t('%s locally created posts and comments', number_format($gserver['posts']));
174
175                         if (($gserver['users'] > 0) && ($gserver['posts'] > 0)) {
176                                 $gserver['postsuserlbl'] = DI::l10n()->t('%s posts per user', number_format($gserver['posts'] / $gserver['users'], 1));
177                         } else {
178                                 $gserver['postsuserlbl'] = '';
179                         }
180                         if (($gserver['users'] > 0) && ($gserver['total'] > 0)) {
181                                 $gserver['userssystemlbl'] = DI::l10n()->t('%s users per system', number_format($gserver['users'] / $gserver['total'], 1));
182                         } else {
183                                 $gserver['userssystemlbl'] = '';
184                         }
185
186                         $counts[$platform] = [$gserver, $versionCounts, str_replace([' ', '%', '.'], '', $platform), $systems[$platform]['color']];
187                 }
188                 DBA::close($gserver);
189
190                 // some helpful text
191                 $intro = DI::l10n()->t('This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of.');
192
193                 // load the template, replace the macros and return the page content
194                 $t = Renderer::getMarkupTemplate('admin/federation.tpl');
195                 return Renderer::replaceMacros($t, [
196                         '$title' => DI::l10n()->t('Administration'),
197                         '$page' => DI::l10n()->t('Federation Statistics'),
198                         '$intro' => $intro,
199                         '$counts' => $counts,
200                         '$version' => FRIENDICA_VERSION,
201                         '$legendtext' => DI::l10n()->t('Currently this node is aware of %s nodes (%s active users last month, %s active users last six months, %s registered users in total) from the following platforms:', number_format($total), number_format($month), number_format($halfyear), number_format($users)),
202                 ]);
203         }
204
205         /**
206          * early friendica versions have the format x.x.xxxx where xxxx is the
207          * DB version stamp; those should be operated out and versions be combined
208          *
209          * @param array $versionCounts list of version numbers
210          * @return array with cleaned version numbers
211          */
212         private static function reformaFriendicaVersions(array $versionCounts)
213         {
214                 $newV = [];
215                 $newVv = [];
216                 foreach ($versionCounts as $vv) {
217                         $newVC = $vv['total'];
218                         $newVV = $vv['version'];
219                         $lastDot = strrpos($newVV, '.');
220                         $firstDash = strpos($newVV, '-');
221                         $len = strlen($newVV) - 1;
222                         if (($lastDot == $len - 4) && (!strrpos($newVV, '-rc') == $len - 3) && (!$firstDash == $len - 1)) {
223                                 $newVV = substr($newVV, 0, $lastDot);
224                         }
225                         if (isset($newV[$newVV])) {
226                                 $newV[$newVV] += $newVC;
227                         } else {
228                                 $newV[$newVV] = $newVC;
229                         }
230                 }
231                 foreach ($newV as $key => $value) {
232                         array_push($newVv, ['total' => $value, 'version' => $key]);
233                 }
234                 $versionCounts = $newVv;
235
236                 return $versionCounts;
237         }
238
239         /**
240          * in the DB the Diaspora versions have the format x.x.x.x-xx the last
241          * part (-xx) should be removed to clean up the versions from the "head
242          * commit" information and combined into a single entry for x.x.x.x
243          *
244          * @param array $versionCounts list of version numbers
245          * @return array with cleaned version numbers
246          */
247         private static function reformaDiasporaVersions(array $versionCounts)
248         {
249                 $newV = [];
250                 $newVv = [];
251                 foreach ($versionCounts as $vv) {
252                         $newVC = $vv['total'];
253                         $newVV = $vv['version'];
254                         $posDash = strpos($newVV, '-');
255                         if ($posDash) {
256                                 $newVV = substr($newVV, 0, $posDash);
257                         }
258                         if (isset($newV[$newVV])) {
259                                 $newV[$newVV] += $newVC;
260                         } else {
261                                 $newV[$newVV] = $newVC;
262                         }
263                 }
264                 foreach ($newV as $key => $value) {
265                         array_push($newVv, ['total' => $value, 'version' => $key]);
266                 }
267                 $versionCounts = $newVv;
268
269                 return $versionCounts;
270         }
271
272         /**
273          * Clean up Pleroma version numbers
274          *
275          * @param array $versionCounts list of version numbers
276          * @return array with cleaned version numbers
277          */
278         private static function reformaPleromaVersions(array $versionCounts)
279         {
280                 $compacted = [];
281                 foreach ($versionCounts as $key => $value) {
282                         $version = $versionCounts[$key]['version'];
283                         $parts = explode(' ', trim($version));
284                         do {
285                                 $part = array_pop($parts);
286                         } while (!empty($parts) && ((strlen($part) >= 40) || (strlen($part) <= 3)));
287                         // only take the x.x.x part of the version, not the "release" after the dash
288                         if (!empty($part) && strpos($part, '-')) {
289                                 $part = explode('-', $part)[0];
290                         }
291                         if (!empty($part)) {
292                                 if (empty($compacted[$part])) {
293                                         $compacted[$part] = $versionCounts[$key]['total'];
294                                 } else {
295                                         $compacted[$part] += $versionCounts[$key]['total'];
296                                 }
297                         }
298                 }
299
300                 $versionCounts = [];
301                 foreach ($compacted as $version => $pl_total) {
302                         $versionCounts[] = ['version' => $version, 'total' => $pl_total];
303                 }
304
305                 return $versionCounts;
306         }
307
308         /**
309          * Clean up version numbers
310          *
311          * @param array $versionCounts list of version numbers
312          * @return array with cleaned version numbers
313          */
314         private static function removeVersionSuffixes(array $versionCounts)
315         {
316                 $compacted = [];
317                 foreach ($versionCounts as $key => $value) {
318                         $version = $versionCounts[$key]['version'];
319
320                         foreach ([' ', '+', '-', '#', '_', '~'] as $delimiter) {
321                                 $parts = explode($delimiter, trim($version));
322                                 $version = array_shift($parts);
323                         }
324
325                         if (empty($compacted[$version])) {
326                                 $compacted[$version] = $versionCounts[$key]['total'];
327                         } else {
328                                 $compacted[$version] += $versionCounts[$key]['total'];
329                         }
330                 }
331
332                 $versionCounts = [];
333                 foreach ($compacted as $version => $pl_total) {
334                         $versionCounts[] = ['version' => $version, 'total' => $pl_total];
335                 }
336
337                 return $versionCounts;
338         }
339
340         /**
341          * Clean up relay version numbers
342          *
343          * @param array $versionCounts list of version numbers
344          * @return array with cleaned version numbers
345          */
346         private static function reformatRelayVersions(array $versionCounts)
347         {
348                 $compacted = [];
349                 foreach ($versionCounts as $key => $value) {
350                         $version = $versionCounts[$key]['version'];
351
352                         $parts = explode(' ', trim($version));
353                         $version = array_shift($parts);
354
355                         if (empty($compacted[$version])) {
356                                 $compacted[$version] = $versionCounts[$key]['total'];
357                         } else {
358                                 $compacted[$version] += $versionCounts[$key]['total'];
359                         }
360                 }
361
362                 $versionCounts = [];
363                 foreach ($compacted as $version => $pl_total) {
364                         $versionCounts[] = ['version' => $version, 'total' => $pl_total];
365                 }
366
367                 return $versionCounts;
368         }
369
370         /**
371          * Reformat, sort and compact version numbers
372          *
373          * @param array $versionCounts list of version numbers
374          * @return array with reformatted version numbers
375          */
376         private static function sortVersion(array $versionCounts)
377         {
378                 //
379                 // clean up version numbers
380                 //
381                 // some platforms do not provide version information, add a unkown there
382                 // to the version string for the displayed list.
383                 foreach ($versionCounts as $key => $value) {
384                         if ($versionCounts[$key]['version'] == '') {
385                                 $versionCounts[$key] = ['total' => $versionCounts[$key]['total'], 'version' => DI::l10n()->t('unknown')];
386                         }
387                 }
388
389                 // Assure that the versions are sorted correctly
390                 $v2 = [];
391                 $versions = [];
392                 foreach ($versionCounts as $vv) {
393                         $version = trim(strip_tags($vv["version"]));
394                         $v2[$version] = $vv;
395                         $versions[] = $version;
396                 }
397
398                 usort($versions, 'version_compare');
399
400                 $versionCounts = [];
401                 foreach ($versions as $version) {
402                         $versionCounts[] = $v2[$version];
403                 }
404
405                 return $versionCounts;
406         }
407 }