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