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