]> git.mxchange.org Git - friendica.git/blob - mod/admin.php
f5bd1d4f590b30c6a68de4c917f0f46c5705f011
[friendica.git] / mod / admin.php
1 <?php
2 /**
3  * @file mod/admin.php
4  *
5  * @brief Friendica admin
6  */
7 use Friendica\App;
8 use Friendica\Content\Feature;
9 use Friendica\Core\System;
10 use Friendica\Core\Config;
11 use Friendica\Core\Worker;
12 use Friendica\Database\DBM;
13 use Friendica\Database\DBStructure;
14 use Friendica\Model\Contact;
15 use Friendica\Model\User;
16
17 require_once 'include/enotify.php';
18 require_once 'include/text.php';
19 require_once 'include/items.php';
20
21 /**
22  * @brief Process send data from the admin panels subpages
23  *
24  * This function acts as relais for processing the data send from the subpages
25  * of the admin panel. Depending on the 1st parameter of the url (argv[1])
26  * specialized functions are called to process the data from the subpages.
27  *
28  * The function itself does not return anything, but the subsequencely function
29  * return the HTML for the pages of the admin panel.
30  *
31  * @param App $a
32  *
33  */
34 function admin_post(App $a)
35 {
36         if (!is_site_admin()) {
37                 return;
38         }
39
40         // do not allow a page manager to access the admin panel at all.
41
42         if (x($_SESSION, 'submanage') && intval($_SESSION['submanage'])) {
43                 return;
44         }
45
46         // urls
47         if ($a->argc > 1) {
48                 switch ($a->argv[1]) {
49                         case 'site':
50                                 admin_page_site_post($a);
51                                 break;
52                         case 'users':
53                                 admin_page_users_post($a);
54                                 break;
55                         case 'plugins':
56                                 if ($a->argc > 2 &&
57                                         is_file("addon/" . $a->argv[2] . "/" . $a->argv[2] . ".php")) {
58                                         @include_once("addon/" . $a->argv[2] . "/" . $a->argv[2] . ".php");
59                                         if (function_exists($a->argv[2] . '_plugin_admin_post')) {
60                                                 $func = $a->argv[2] . '_plugin_admin_post';
61                                                 $func($a);
62                                         }
63                                 }
64                                 goaway('admin/plugins/' . $a->argv[2]);
65                                 return; // NOTREACHED
66                                 break;
67                         case 'themes':
68                                 if ($a->argc < 2) {
69                                         if (is_ajax()) {
70                                                 return;
71                                         }
72                                         goaway('admin/');
73                                         return;
74                                 }
75
76                                 $theme = $a->argv[2];
77                                 if (is_file("view/theme/$theme/config.php")) {
78
79                                         function __call_theme_admin_post(App $a, $theme)
80                                         {
81                                                 $orig_theme = $a->theme;
82                                                 $orig_page = $a->page;
83                                                 $orig_session_theme = $_SESSION['theme'];
84                                                 require_once("view/theme/$theme/theme.php");
85                                                 require_once("view/theme/$theme/config.php");
86                                                 $_SESSION['theme'] = $theme;
87
88
89                                                 $init = $theme . "_init";
90                                                 if (function_exists($init)) {
91                                                         $init($a);
92                                                 }
93                                                 if (function_exists("theme_admin_post")) {
94                                                         $admin_form = theme_admin_post($a);
95                                                 }
96
97                                                 $_SESSION['theme'] = $orig_session_theme;
98                                                 $a->theme = $orig_theme;
99                                                 $a->page = $orig_page;
100                                                 return $admin_form;
101                                         }
102                                         __call_theme_admin_post($a, $theme);
103                                 }
104                                 info(t('Theme settings updated.'));
105                                 if (is_ajax()) {
106                                         return;
107                                 }
108                                 goaway('admin/themes/' . $theme);
109                                 return;
110                                 break;
111                         case 'features':
112                                 admin_page_features_post($a);
113                                 break;
114                         case 'logs':
115                                 admin_page_logs_post($a);
116                                 break;
117                         case 'dbsync':
118                                 admin_page_dbsync_post($a);
119                                 break;
120                         case 'contactblock':
121                                 admin_page_contactblock_post($a);
122                                 break;
123                         case 'blocklist':
124                                 admin_page_blocklist_post($a);
125                                 break;
126                         case 'deleteitem':
127                                 admin_page_deleteitem_post($a);
128                                 break;
129                 }
130         }
131
132         goaway('admin');
133         return; // NOTREACHED
134 }
135
136 /**
137  * @brief Generates content of the admin panel pages
138  *
139  * This function generates the content for the admin panel. It consists of the
140  * aside menu (same for the entire admin panel) and the code for the soecified
141  * subpage of the panel.
142  *
143  * The structure of the adress is: /admin/subpage/details though "details" is
144  * only necessary for some subpages, like themes or addons where it is the name
145  * of one theme resp. addon from which the details should be shown. Content for
146  * the subpages is generated in separate functions for each of the subpages.
147  *
148  * The returned string hold the generated HTML code of the page.
149  *
150  * @param App $a
151  * @return string
152  */
153 function admin_content(App $a)
154 {
155         if (!is_site_admin()) {
156                 return login(false);
157         }
158
159         if (x($_SESSION, 'submanage') && intval($_SESSION['submanage'])) {
160                 return "";
161         }
162
163         // APC deactivated, since there are problems with PHP 5.5
164         //if (function_exists("apc_delete")) {
165         //      $toDelete = new APCIterator('user', APC_ITER_VALUE);
166         //      apc_delete($toDelete);
167         //}
168         // Header stuff
169         $a->page['htmlhead'] .= replace_macros(get_markup_template('admin/settings_head.tpl'), array());
170
171         /*
172          * Side bar links
173          */
174         $aside_tools = array();
175         // array(url, name, extra css classes)
176         // not part of $aside to make the template more adjustable
177         $aside_sub = array(
178                 'site'         => array("admin/site/"        , t("Site")                 , "site"),
179                 'users'        => array("admin/users/"       , t("Users")                , "users"),
180                 'plugins'      => array("admin/plugins/"     , t("Plugins")              , "plugins"),
181                 'themes'       => array("admin/themes/"      , t("Themes")               , "themes"),
182                 'features'     => array("admin/features/"    , t("Additional features")  , "features"),
183                 'dbsync'       => array("admin/dbsync/"      , t('DB updates')           , "dbsync"),
184                 'queue'        => array("admin/queue/"       , t('Inspect Queue')        , "queue"),
185                 'contactblock' => array("admin/contactblock/", t('Contact Blocklist')    , "contactblock"),
186                 'blocklist'    => array("admin/blocklist/"   , t('Server Blocklist')     , "blocklist"),
187                 'federation'   => array("admin/federation/"  , t('Federation Statistics'), "federation"),
188                 'deleteitem'   => array("admin/deleteitem/"  , t('Delete Item')          , 'deleteitem'),
189         );
190
191         /* get plugins admin page */
192
193         $r = q("SELECT `name` FROM `addon` WHERE `plugin_admin` = 1 ORDER BY `name`");
194         $aside_tools['plugins_admin'] = array();
195         foreach ($r as $h) {
196                 $plugin = $h['name'];
197                 $aside_tools['plugins_admin'][] = array("admin/plugins/" . $plugin, $plugin, "plugin");
198                 // temp plugins with admin
199                 $a->plugins_admin[] = $plugin;
200         }
201
202         $aside_tools['logs'] = array("admin/logs/", t("Logs"), "logs");
203         $aside_tools['viewlogs'] = array("admin/viewlogs/", t("View Logs"), 'viewlogs');
204         $aside_tools['diagnostics_probe'] = array('probe/', t('probe address'), 'probe');
205         $aside_tools['diagnostics_webfinger'] = array('webfinger/', t('check webfinger'), 'webfinger');
206
207         $t = get_markup_template('admin/aside.tpl');
208         $a->page['aside'] .= replace_macros($t, array(
209                 '$admin' => $aside_tools,
210                 '$subpages' => $aside_sub,
211                 '$admtxt' => t('Admin'),
212                 '$plugadmtxt' => t('Plugin Features'),
213                 '$logtxt' => t('Logs'),
214                 '$diagnosticstxt' => t('diagnostics'),
215                 '$h_pending' => t('User registrations waiting for confirmation'),
216                 '$admurl' => "admin/"
217         ));
218
219         // Page content
220         $o = '';
221         // urls
222         if ($a->argc > 1) {
223                 switch ($a->argv[1]) {
224                         case 'site':
225                                 $o = admin_page_site($a);
226                                 break;
227                         case 'users':
228                                 $o = admin_page_users($a);
229                                 break;
230                         case 'plugins':
231                                 $o = admin_page_plugins($a);
232                                 break;
233                         case 'themes':
234                                 $o = admin_page_themes($a);
235                                 break;
236                         case 'features':
237                                 $o = admin_page_features($a);
238                                 break;
239                         case 'logs':
240                                 $o = admin_page_logs($a);
241                                 break;
242                         case 'viewlogs':
243                                 $o = admin_page_viewlogs($a);
244                                 break;
245                         case 'dbsync':
246                                 $o = admin_page_dbsync($a);
247                                 break;
248                         case 'queue':
249                                 $o = admin_page_queue($a);
250                                 break;
251                         case 'federation':
252                                 $o = admin_page_federation($a);
253                                 break;
254                         case 'contactblock':
255                                 $o = admin_page_contactblock($a);
256                                 break;
257                         case 'blocklist':
258                                 $o = admin_page_blocklist($a);
259                                 break;
260                         case 'deleteitem':
261                                 $o = admin_page_deleteitem($a);
262                                 break;
263                         default:
264                                 notice(t("Item not found."));
265                 }
266         } else {
267                 $o = admin_page_summary($a);
268         }
269
270         if (is_ajax()) {
271                 echo $o;
272                 killme();
273                 return '';
274         } else {
275                 return $o;
276         }
277 }
278
279 /**
280  * @brief Subpage to modify the server wide block list via the admin panel.
281  *
282  * This function generates the subpage of the admin panel to allow the
283  * modification of the node wide block/black list to block entire
284  * remote servers from communication with this node. The page allows
285  * adding, removing and editing of entries from the blocklist.
286  *
287  * @param App $a
288  * @return string
289  */
290 function admin_page_blocklist(App $a)
291 {
292         $blocklist = Config::get('system', 'blocklist');
293         $blocklistform = array();
294         if (is_array($blocklist)) {
295                 foreach ($blocklist as $id => $b) {
296                         $blocklistform[] = array(
297                                 'domain' => array("domain[$id]", t('Blocked domain'), $b['domain'], '', t('The blocked domain'), 'required', '', ''),
298                                 'reason' => array("reason[$id]", t("Reason for the block"), $b['reason'], t('The reason why you blocked this domain.') . '(' . $b['domain'] . ')', 'required', '', ''),
299                                 'delete' => array("delete[$id]", t("Delete domain") . ' (' . $b['domain'] . ')', False, t("Check to delete this entry from the blocklist"))
300                         );
301                 }
302         }
303         $t = get_markup_template('admin/blocklist.tpl');
304         return replace_macros($t, array(
305                 '$title' => t('Administration'),
306                 '$page' => t('Server Blocklist'),
307                 '$intro' => t('This page can be used to define a black list of servers from the federated network that are not allowed to interact with your node. For all entered domains you should also give a reason why you have blocked the remote server.'),
308                 '$public' => t('The list of blocked servers will be made publically available on the /friendica page so that your users and people investigating communication problems can find the reason easily.'),
309                 '$addtitle' => t('Add new entry to block list'),
310                 '$newdomain' => array('newentry_domain', t('Server Domain'), '', t('The domain of the new server to add to the block list. Do not include the protocol.'), 'required', '', ''),
311                 '$newreason' => array('newentry_reason', t('Block reason'), '', t('The reason why you blocked this domain.'), 'required', '', ''),
312                 '$submit' => t('Add Entry'),
313                 '$savechanges' => t('Save changes to the blocklist'),
314                 '$currenttitle' => t('Current Entries in the Blocklist'),
315                 '$thurl' => t('Blocked domain'),
316                 '$threason' => t('Reason for the block'),
317                 '$delentry' => t('Delete entry from blocklist'),
318                 '$entries' => $blocklistform,
319                 '$baseurl' => System::baseUrl(true),
320                 '$confirm_delete' => t('Delete entry from blocklist?'),
321                 '$form_security_token' => get_form_security_token("admin_blocklist")
322         ));
323 }
324
325 /**
326  * @brief Process send data from Admin Blocklist Page
327  *
328  * @param App $a
329  */
330 function admin_page_blocklist_post(App $a)
331 {
332         if (!x($_POST, "page_blocklist_save") && (!x($_POST['page_blocklist_edit']))) {
333                 return;
334         }
335
336         check_form_security_token_redirectOnErr('/admin/blocklist', 'admin_blocklist');
337
338         if (x($_POST['page_blocklist_save'])) {
339                 //  Add new item to blocklist
340                 $blocklist = Config::get('system', 'blocklist');
341                 $blocklist[] = array(
342                         'domain' => notags(trim($_POST['newentry_domain'])),
343                         'reason' => notags(trim($_POST['newentry_reason']))
344                 );
345                 Config::set('system', 'blocklist', $blocklist);
346                 info(t('Server added to blocklist.') . EOL);
347         } else {
348                 // Edit the entries from blocklist
349                 $blocklist = array();
350                 foreach ($_POST['domain'] as $id => $domain) {
351                         // Trimming whitespaces as well as any lingering slashes
352                         $domain = notags(trim($domain, "\x00..\x1F/"));
353                         $reason = notags(trim($_POST['reason'][$id]));
354                         if (!x($_POST['delete'][$id])) {
355                                 $blocklist[] = array(
356                                         'domain' => $domain,
357                                         'reason' => $reason
358                                 );
359                         }
360                 }
361                 Config::set('system', 'blocklist', $blocklist);
362                 info(t('Site blocklist updated.') . EOL);
363         }
364         goaway('admin/blocklist');
365
366         return; // NOTREACHED
367 }
368
369 /**
370  * @brief Process data send by the contact block admin page
371  *
372  * @param App $a
373  */
374 function admin_page_contactblock_post(App $a)
375 {
376         $contact_url = x($_POST, 'contact_url') ? $_POST['contact_url'] : '';
377         $contacts    = x($_POST, 'contacts')    ? $_POST['contacts']    : [];
378
379         check_form_security_token_redirectOnErr('/admin/contactblock', 'admin_contactblock');
380
381         if (x($_POST, 'page_contactblock_block')) {
382                 $contact_id = Contact::getIdForURL($contact_url, 0);
383                 if ($contact_id) {
384                         Contact::block($contact_id);
385                         notice(t('The contact has been blocked from the node'));
386                 } else {
387                         notice(t('Could not find any contact entry for this URL (%s)', $contact_url));
388                 }
389         }
390         if (x($_POST, 'page_contactblock_unblock')) {
391                 foreach ($contacts as $uid) {
392                         Contact::unblock($uid);
393                 }
394                 notice(tt("%s contact unblocked", "%s contacts unblocked", count($contacts)));
395         }
396         goaway('admin/contactblock');
397         return; // NOTREACHED
398 }
399
400 /**
401  * @brief Admin panel for server-wide contact block
402  *
403  * @param App $a
404  * @return string
405  */
406 function admin_page_contactblock(App $a)
407 {
408         $condition = ['uid' => 0, 'blocked' => true];
409
410         $total = dba::count('contact', $condition);
411
412         $a->set_pager_total($total);
413         $a->set_pager_itemspage(30);
414
415         $statement = dba::select('contact', [], $condition, ['limit' => [$a->pager['start'], $a->pager['itemspage']]]);
416
417         $contacts = dba::inArray($statement);
418
419         $t = get_markup_template('admin/contactblock.tpl');
420         $o = replace_macros($t, array(
421                 // strings //
422                 '$title'       => t('Administration'),
423                 '$page'        => t('Remote Contact Blocklist'),
424                 '$description' => t('This page allows you to prevent any message from a remote contact to reach your node.'),
425                 '$submit'      => t('Block Remote Contact'),
426                 '$select_all'  => t('select all'),
427                 '$select_none' => t('select none'),
428                 '$block'       => t('Block'),
429                 '$unblock'     => t('Unblock'),
430                 '$no_data'     => t('No remote contact is blocked from this node.'),
431
432                 '$h_contacts'  => t('Blocked Remote Contacts'),
433                 '$h_newblock'  => t('Block New Remote Contact'),
434                 '$th_contacts' => [t('Photo'), t('Name'), t('Address'), t('Profile URL')],
435
436                 '$form_security_token' => get_form_security_token("admin_contactblock"),
437
438                 // values //
439                 '$baseurl'    => System::baseUrl(true),
440
441                 '$contacts'   => $contacts,
442                 '$total_contacts' => tt('%s total blocked contact', '%s total blocked contacts', $total),
443                 '$paginate'   => paginate($a),
444                 '$contacturl' => ['contact_url', t("Profile URL"), '', t("URL of the remote contact to block.")],
445         ));
446         return $o;
447 }
448
449 /**
450  * @brief Subpage where the admin can delete an item from their node given the GUID
451  *
452  * This subpage of the admin panel offers the nodes admin to delete an item from
453  * the node, given the GUID or the display URL such as http://example.com/display/123456.
454  * The item will then be marked as deleted in the database and processed accordingly.
455  *
456  * @param App $a
457  * @return string
458  */
459 function admin_page_deleteitem(App $a)
460 {
461         $t = get_markup_template('admin/deleteitem.tpl');
462
463         return replace_macros($t, array(
464                 '$title' => t('Administration'),
465                 '$page' => t('Delete Item'),
466                 '$submit' => t('Delete this Item'),
467                 '$intro1' => t('On this page you can delete an item from your node. If the item is a top level posting, the entire thread will be deleted.'),
468                 '$intro2' => t('You need to know the GUID of the item. You can find it e.g. by looking at the display URL. The last part of http://example.com/display/123456 is the GUID, here 123456.'),
469                 '$deleteitemguid' => array('deleteitemguid', t("GUID"), '', t("The GUID of the item you want to delete."), 'required', 'autofocus'),
470                 '$baseurl' => System::baseUrl(),
471                 '$form_security_token' => get_form_security_token("admin_deleteitem")
472         ));
473 }
474
475 /**
476  * @brief Process send data from Admin Delete Item Page
477  *
478  * The GUID passed through the form should be only the GUID. But we also parse
479  * URLs like the full /display URL to make the process more easy for the admin.
480  *
481  * @param App $a
482  */
483 function admin_page_deleteitem_post(App $a)
484 {
485         if (!x($_POST['page_deleteitem_submit'])) {
486                 return;
487         }
488
489         check_form_security_token_redirectOnErr('/admin/deleteitem/', 'admin_deleteitem');
490
491         if (x($_POST['page_deleteitem_submit'])) {
492                 $guid = trim(notags($_POST['deleteitemguid']));
493                 // The GUID should not include a "/", so if there is one, we got an URL
494                 // and the last part of it is most likely the GUID.
495                 if (strpos($guid, '/')) {
496                         $guid = substr($guid, strrpos($guid, '/') + 1);
497                 }
498                 // Now that we have the GUID get all IDs of the associated entries in the
499                 // item table of the DB and drop those items, which will also delete the
500                 // associated threads.
501                 $r = dba::select('item', array('id'), array('guid' => $guid));
502                 while ($row = dba::fetch($r)) {
503                         drop_item($row['id'], false);
504                 }
505                 dba::close($r);
506         }
507
508         info(t('Item marked for deletion.') . EOL);
509         goaway('admin/deleteitem');
510         return; // NOTREACHED
511 }
512
513 /**
514  * @brief Subpage with some stats about "the federation" network
515  *
516  * This function generates the "Federation Statistics" subpage for the admin
517  * panel. The page lists some numbers to the part of "The Federation" known to
518  * the node. This data includes the different connected networks (e.g.
519  * Diaspora, Hubzilla, GNU Social) and the used versions in the different
520  * networks.
521  *
522  * The returned string contains the HTML code of the subpage for display.
523  *
524  * @param App $a
525  * @return string
526  */
527 function admin_page_federation(App $a)
528 {
529         // get counts on active friendica, diaspora, redmatrix, hubzilla, gnu
530         // social and statusnet nodes this node is knowing
531         //
532         // We are looking for the following platforms in the DB, "Red" should find
533         // all variants of that platform ID string as the q() function is stripping
534         // off one % two of them are needed in the query
535         // Add more platforms if you like, when one returns 0 known nodes it is not
536         // displayed on the stats page.
537         $platforms = array('Friendi%%a', 'Diaspora', '%%red%%', 'Hubzilla', 'BlaBlaNet', 'GNU Social', 'StatusNet', 'Mastodon', 'Pleroma');
538         $colors = array(
539                 'Friendi%%a' => '#ffc018', // orange from the logo
540                 'Diaspora'   => '#a1a1a1', // logo is black and white, makes a gray
541                 '%%red%%'    => '#c50001', // fire red from the logo
542                 'Hubzilla'   => '#43488a', // blue from the logo
543                 'BlaBlaNet'  => '#3B5998', // blue from the navbar at blablanet-dot-com
544                 'GNU Social' => '#a22430', // dark red from the logo
545                 'StatusNet'  => '#789240', // the green from the logo (red and blue have already others
546                 'Mastodon'   => '#1a9df9', // blue from the Mastodon logo
547                 'Pleroma'    => '#E46F0F'  // Orange from the text that is used on Pleroma instances
548         );
549         $counts = array();
550         $total = 0;
551         $users = 0;
552
553         foreach ($platforms as $p) {
554                 // get a total count for the platform, the name and version of the
555                 // highest version and the protocol tpe
556                 $c = q('SELECT COUNT(*) AS `total`, SUM(`registered-users`) AS `users`, ANY_VALUE(`platform`) AS `platform`,
557                                 ANY_VALUE(`network`) AS `network`, MAX(`version`) AS `version` FROM `gserver`
558                                 WHERE `platform` LIKE "%s" AND `last_contact` >= `last_failure`
559                                 ORDER BY `version` ASC;', $p);
560                 $total += $c[0]['total'];
561                 $users += $c[0]['users'];
562
563                 // what versions for that platform do we know at all?
564                 // again only the active nodes
565                 $v = q('SELECT COUNT(*) AS `total`, `version` FROM `gserver`
566                                 WHERE `last_contact` >= `last_failure` AND `platform` LIKE "%s"
567                                 GROUP BY `version`
568                                 ORDER BY `version`;', $p);
569
570                 //
571                 // clean up version numbers
572                 //
573                 // some platforms do not provide version information, add a unkown there
574                 // to the version string for the displayed list.
575                 foreach ($v as $key => $value) {
576                         if ($v[$key]['version'] == '') {
577                                 $v[$key] = array('total' => $v[$key]['total'], 'version' => t('unknown'));
578                         }
579                 }
580                 // in the DB the Diaspora versions have the format x.x.x.x-xx the last
581                 // part (-xx) should be removed to clean up the versions from the "head
582                 // commit" information and combined into a single entry for x.x.x.x
583                 if ($p == 'Diaspora') {
584                         $newV = array();
585                         $newVv = array();
586                         foreach ($v as $vv) {
587                                 $newVC = $vv['total'];
588                                 $newVV = $vv['version'];
589                                 $posDash = strpos($newVV, '-');
590                                 if ($posDash) {
591                                         $newVV = substr($newVV, 0, $posDash);
592                                 }
593                                 if (isset($newV[$newVV])) {
594                                         $newV[$newVV] += $newVC;
595                                 } else {
596                                         $newV[$newVV] = $newVC;
597                                 }
598                         }
599                         foreach ($newV as $key => $value) {
600                                 array_push($newVv, array('total' => $value, 'version' => $key));
601                         }
602                         $v = $newVv;
603                 }
604
605                 // early friendica versions have the format x.x.xxxx where xxxx is the
606                 // DB version stamp; those should be operated out and versions be
607                 // conbined
608                 if ($p == 'Friendi%%a') {
609                         $newV = array();
610                         $newVv = array();
611                         foreach ($v as $vv) {
612                                 $newVC = $vv['total'];
613                                 $newVV = $vv['version'];
614                                 $lastDot = strrpos($newVV, '.');
615                                 $len = strlen($newVV) - 1;
616                                 if (($lastDot == $len - 4) && (!strrpos($newVV, '-rc') == $len - 3)) {
617                                         $newVV = substr($newVV, 0, $lastDot);
618                                 }
619                                 if (isset($newV[$newVV])) {
620                                         $newV[$newVV] += $newVC;
621                                 } else {
622                                         $newV[$newVV] = $newVC;
623                                 }
624                         }
625                         foreach ($newV as $key => $value) {
626                                 array_push($newVv, array('total' => $value, 'version' => $key));
627                         }
628                         $v = $newVv;
629                 }
630
631                 foreach ($v as $key => $vv)
632                         $v[$key]["version"] = trim(strip_tags($vv["version"]));
633
634                 // the 3rd array item is needed for the JavaScript graphs as JS does
635                 // not like some characters in the names of variables...
636                 $counts[$p] = array($c[0], $v, str_replace(array(' ', '%'), '', $p), $colors[$p]);
637         }
638
639         // some helpful text
640         $intro = 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.');
641         $hint = t('The <em>Auto Discovered Contact Directory</em> feature is not enabled, it will improve the data displayed here.');
642
643         // load the template, replace the macros and return the page content
644         $t = get_markup_template('admin/federation.tpl');
645         return replace_macros($t, array(
646                 '$title' => t('Administration'),
647                 '$page' => t('Federation Statistics'),
648                 '$intro' => $intro,
649                 '$hint' => $hint,
650                 '$autoactive' => Config::get('system', 'poco_completion'),
651                 '$counts' => $counts,
652                 '$version' => FRIENDICA_VERSION,
653                 '$legendtext' => sprintf(t('Currently this node is aware of %d nodes with %d registered users from the following platforms:'), $total, $users),
654                 '$baseurl' => System::baseUrl(),
655         ));
656 }
657
658 /**
659  * @brief Admin Inspect Queue Page
660  *
661  * Generates a page for the admin to have a look into the current queue of
662  * postings that are not deliverabke. Shown are the name and url of the
663  * recipient, the delivery network and the dates when the posting was generated
664  * and the last time tried to deliver the posting.
665  *
666  * The returned string holds the content of the page.
667  *
668  * @param App $a
669  * @return string
670  */
671 function admin_page_queue(App $a)
672 {
673         // get content from the queue table
674         $r = q("SELECT `c`.`name`, `c`.`nurl`, `q`.`id`, `q`.`network`, `q`.`created`, `q`.`last`
675                         FROM `queue` AS `q`, `contact` AS `c`
676                         WHERE `c`.`id` = `q`.`cid`
677                         ORDER BY `q`.`cid`, `q`.`created`;");
678
679         $t = get_markup_template('admin/queue.tpl');
680         return replace_macros($t, array(
681                 '$title' => t('Administration'),
682                 '$page' => t('Inspect Queue'),
683                 '$count' => sizeof($r),
684                 'id_header' => t('ID'),
685                 '$to_header' => t('Recipient Name'),
686                 '$url_header' => t('Recipient Profile'),
687                 '$network_header' => t('Network'),
688                 '$created_header' => t('Created'),
689                 '$last_header' => t('Last Tried'),
690                 '$info' => t('This page lists the content of the queue for outgoing postings. These are postings the initial delivery failed for. They will be resend later and eventually deleted if the delivery fails permanently.'),
691                 '$entries' => $r,
692         ));
693 }
694
695 /**
696  * @brief Admin Summary Page
697  *
698  * The summary page is the "start page" of the admin panel. It gives the admin
699  * a first overview of the open adminastrative tasks.
700  *
701  * The returned string contains the HTML content of the generated page.
702  *
703  * @param App $a
704  * @return string
705  */
706 function admin_page_summary(App $a)
707 {
708         // are there MyISAM tables in the DB? If so, trigger a warning message
709         $r = q("SELECT `engine` FROM `information_schema`.`tables` WHERE `engine` = 'myisam' AND `table_schema` = '%s' LIMIT 1", dbesc(dba::database_name()));
710         $showwarning = false;
711         $warningtext = array();
712         if (DBM::is_result($r)) {
713                 $showwarning = true;
714                 $warningtext[] = sprintf(t('Your DB still runs with MyISAM tables. You should change the engine type to InnoDB. As Friendica will use InnoDB only features in the future, you should change this! See <a href="%s">here</a> for a guide that may be helpful converting the table engines. You may also use the command <tt>php scripts/dbstructure.php toinnodb</tt> of your Friendica installation for an automatic conversion.<br />'), 'https://dev.mysql.com/doc/refman/5.7/en/converting-tables-to-innodb.html');
715         }
716         // Check if github.com/friendica/master/VERSION is higher then
717         // the local version of Friendica. Check is opt-in, source may be master or devel branch
718         if (Config::get('system', 'check_new_version_url', 'none') != 'none') {
719                 $gitversion = Config::get('system', 'git_friendica_version');
720                 if (version_compare(FRIENDICA_VERSION, $gitversion) < 0) {
721                         $warningtext[] = sprintf(t('There is a new version of Friendica available for download. Your current version is %1$s, upstream version is %2$s'), $FRIENDICA_VERSION, $gitversion);
722                         $showwarning = true;
723                 }
724         }
725
726         if (Config::get('system', 'dbupdate', DB_UPDATE_NOT_CHECKED) == DB_UPDATE_NOT_CHECKED) {
727                 DBStructure::update(false, true);
728         }
729         if (Config::get('system', 'dbupdate') == DB_UPDATE_FAILED) {
730                 $showwarning = true;
731                 $warningtext[] = t('The database update failed. Please run "php scripts/dbstructure.php update" from the command line and have a look at the errors that might appear.');
732         }
733
734         $last_worker_call = Config::get('system', 'last_poller_execution', false);
735         if (!$last_worker_call) {
736                 $showwarning = true;
737                 $warningtext[] = t('The worker was never executed. Please check your database structure!');
738         } elseif ((strtotime(datetime_convert()) - strtotime($last_worker_call)) > 60 * 60) {
739                 $showwarning = true;
740                 $warningtext[] = sprintf(t('The last worker execution was on %s UTC. This is older than one hour. Please check your crontab settings.'), $last_worker_call);
741         }
742
743         $r = q("SELECT `page-flags`, COUNT(`uid`) AS `count` FROM `user` GROUP BY `page-flags`");
744         $accounts = array(
745                 array(t('Normal Account'), 0),
746                 array(t('Automatic Follower Account'), 0),
747                 array(t('Public Forum Account'), 0),
748                 array(t('Automatic Friend Account'), 0),
749                 array(t('Blog Account'), 0),
750                 array(t('Private Forum Account'), 0)
751         );
752
753         $users = 0;
754         foreach ($r as $u) {
755                 $accounts[$u['page-flags']][1] = $u['count'];
756                 $users+= $u['count'];
757         }
758
759         logger('accounts: ' . print_r($accounts, true), LOGGER_DATA);
760
761         $r = q("SELECT COUNT(`id`) AS `count` FROM `register`");
762         $pending = $r[0]['count'];
763
764         $r = q("SELECT COUNT(*) AS `total` FROM `queue` WHERE 1");
765         $queue = (($r) ? $r[0]['total'] : 0);
766
767         $r = q("SELECT COUNT(*) AS `total` FROM `workerqueue` WHERE NOT `done`");
768         $workerqueue = (($r) ? $r[0]['total'] : 0);
769
770         // We can do better, but this is a quick queue status
771
772         $queues = array('label' => t('Message queues'), 'queue' => $queue, 'workerq' => $workerqueue);
773
774
775         $t = get_markup_template('admin/summary.tpl');
776         return replace_macros($t, array(
777                 '$title' => t('Administration'),
778                 '$page' => t('Summary'),
779                 '$queues' => $queues,
780                 '$users' => array(t('Registered users'), $users),
781                 '$accounts' => $accounts,
782                 '$pending' => array(t('Pending registrations'), $pending),
783                 '$version' => array(t('Version'), FRIENDICA_VERSION),
784                 '$baseurl' => System::baseUrl(),
785                 '$platform' => FRIENDICA_PLATFORM,
786                 '$codename' => FRIENDICA_CODENAME,
787                 '$build' => Config::get('system', 'build'),
788                 '$plugins' => array(t('Active plugins'), $a->plugins),
789                 '$showwarning' => $showwarning,
790                 '$warningtext' => $warningtext
791         ));
792 }
793
794 /**
795  * @brief Process send data from Admin Site Page
796  *
797  * @param App $a
798  */
799 function admin_page_site_post(App $a)
800 {
801         check_form_security_token_redirectOnErr('/admin/site', 'admin_site');
802
803         if (!empty($_POST['republish_directory'])) {
804                 Worker::add(PRIORITY_LOW, 'Directory');
805                 return;
806         }
807
808         if (!x($_POST, "page_site")) {
809                 return;
810         }
811
812         // relocate
813         if (x($_POST, 'relocate') && x($_POST, 'relocate_url') && $_POST['relocate_url'] != "") {
814                 $new_url = $_POST['relocate_url'];
815                 $new_url = rtrim($new_url, "/");
816
817                 $parsed = @parse_url($new_url);
818                 if (!$parsed || (!x($parsed, 'host') || !x($parsed, 'scheme'))) {
819                         notice(t("Can not parse base url. Must have at least <scheme>://<domain>"));
820                         goaway('admin/site');
821                 }
822
823                 /* steps:
824                  * replace all "baseurl" to "new_url" in config, profile, term, items and contacts
825                  * send relocate for every local user
826                  * */
827
828                 $old_url = System::baseUrl(true);
829
830                 // Generate host names for relocation the addresses in the format user@address.tld
831                 $new_host = str_replace("http://", "@", normalise_link($new_url));
832                 $old_host = str_replace("http://", "@", normalise_link($old_url));
833
834                 function update_table($table_name, $fields, $old_url, $new_url)
835                 {
836                         global $a;
837
838                         $dbold = dbesc($old_url);
839                         $dbnew = dbesc($new_url);
840
841                         $upd = array();
842                         foreach ($fields as $f) {
843                                 $upd[] = "`$f` = REPLACE(`$f`, '$dbold', '$dbnew')";
844                         }
845
846                         $upds = implode(", ", $upd);
847
848
849
850                         $q = sprintf("UPDATE %s SET %s;", $table_name, $upds);
851                         $r = q($q);
852                         if (!$r) {
853                                 notice("Failed updating '$table_name': " . dba::errorMessage());
854                                 goaway('admin/site');
855                         }
856                 }
857                 // update tables
858                 // update profile links in the format "http://server.tld"
859                 update_table("profile", array('photo', 'thumb'), $old_url, $new_url);
860                 update_table("term", array('url'), $old_url, $new_url);
861                 update_table("contact", array('photo', 'thumb', 'micro', 'url', 'nurl', 'alias', 'request', 'notify', 'poll', 'confirm', 'poco', 'avatar'), $old_url, $new_url);
862                 update_table("gcontact", array('url', 'nurl', 'photo', 'server_url', 'notify', 'alias'), $old_url, $new_url);
863                 update_table("item", array('owner-link', 'owner-avatar', 'author-link', 'author-avatar', 'body', 'plink', 'tag'), $old_url, $new_url);
864
865                 // update profile addresses in the format "user@server.tld"
866                 update_table("contact", array('addr'), $old_host, $new_host);
867                 update_table("gcontact", array('connect', 'addr'), $old_host, $new_host);
868
869                 // update config
870                 $a->set_baseurl($new_url);
871                 Config::set('system', 'url', $new_url);
872
873                 // send relocate
874                 $users = q("SELECT `uid` FROM `user` WHERE `account_removed` = 0 AND `account_expired` = 0");
875
876                 foreach ($users as $user) {
877                         Worker::add(PRIORITY_HIGH, 'Notifier', 'relocate', $user['uid']);
878                 }
879
880                 info("Relocation started. Could take a while to complete.");
881
882                 goaway('admin/site');
883         }
884         // end relocate
885
886         $sitename               =       ((x($_POST,'sitename'))                 ? notags(trim($_POST['sitename']))              : '');
887         $hostname               =       ((x($_POST,'hostname'))                 ? notags(trim($_POST['hostname']))              : '');
888         $sender_email           =       ((x($_POST,'sender_email'))             ? notags(trim($_POST['sender_email']))          : '');
889         $banner                 =       ((x($_POST,'banner'))                   ? trim($_POST['banner'])                        : false);
890         $shortcut_icon          =       ((x($_POST,'shortcut_icon'))            ? notags(trim($_POST['shortcut_icon']))         : '');
891         $touch_icon             =       ((x($_POST,'touch_icon'))               ? notags(trim($_POST['touch_icon']))            : '');
892         $info                   =       ((x($_POST,'info'))                     ? trim($_POST['info'])                          : false);
893         $language               =       ((x($_POST,'language'))                 ? notags(trim($_POST['language']))              : '');
894         $theme                  =       ((x($_POST,'theme'))                    ? notags(trim($_POST['theme']))                 : '');
895         $theme_mobile           =       ((x($_POST,'theme_mobile'))             ? notags(trim($_POST['theme_mobile']))          : '');
896         $maximagesize           =       ((x($_POST,'maximagesize'))             ? intval(trim($_POST['maximagesize']))          :  0);
897         $maximagelength         =       ((x($_POST,'maximagelength'))           ? intval(trim($_POST['maximagelength']))        :  MAX_IMAGE_LENGTH);
898         $jpegimagequality       =       ((x($_POST,'jpegimagequality'))         ? intval(trim($_POST['jpegimagequality']))      :  JPEG_QUALITY);
899
900
901         $register_policy        =       ((x($_POST,'register_policy'))          ? intval(trim($_POST['register_policy']))       :  0);
902         $daily_registrations    =       ((x($_POST,'max_daily_registrations'))  ? intval(trim($_POST['max_daily_registrations']))       :0);
903         $abandon_days           =       ((x($_POST,'abandon_days'))             ? intval(trim($_POST['abandon_days']))          :  0);
904
905         $register_text          =       ((x($_POST,'register_text'))            ? notags(trim($_POST['register_text']))         : '');
906
907         $allowed_sites          =       ((x($_POST,'allowed_sites'))            ? notags(trim($_POST['allowed_sites']))         : '');
908         $allowed_email          =       ((x($_POST,'allowed_email'))            ? notags(trim($_POST['allowed_email']))         : '');
909         $block_public           =       ((x($_POST,'block_public'))             ? True                                          : False);
910         $force_publish          =       ((x($_POST,'publish_all'))              ? True                                          : False);
911         $global_directory       =       ((x($_POST,'directory'))                ? notags(trim($_POST['directory']))             : '');
912         $newuser_private                =       ((x($_POST,'newuser_private'))          ? True                                  : False);
913         $enotify_no_content             =       ((x($_POST,'enotify_no_content'))       ? True                                  : False);
914         $private_addons                 =       ((x($_POST,'private_addons'))           ? True                                  : False);
915         $disable_embedded               =       ((x($_POST,'disable_embedded'))         ? True                                  : False);
916         $allow_users_remote_self        =       ((x($_POST,'allow_users_remote_self'))  ? True                                  : False);
917
918         $no_multi_reg           =       ((x($_POST,'no_multi_reg'))             ? True                                          : False);
919         $no_openid              =       !((x($_POST,'no_openid'))               ? True                                          : False);
920         $no_regfullname         =       !((x($_POST,'no_regfullname'))          ? True                                          : False);
921         $community_page_style   =       ((x($_POST,'community_page_style'))     ? intval(trim($_POST['community_page_style']))  : 0);
922         $max_author_posts_community_page        =       ((x($_POST,'max_author_posts_community_page'))  ? intval(trim($_POST['max_author_posts_community_page']))       : 0);
923
924         $verifyssl              =       ((x($_POST,'verifyssl'))                ? True                                          : False);
925         $proxyuser              =       ((x($_POST,'proxyuser'))                ? notags(trim($_POST['proxyuser']))             : '');
926         $proxy                  =       ((x($_POST,'proxy'))                    ? notags(trim($_POST['proxy']))                 : '');
927         $timeout                =       ((x($_POST,'timeout'))                  ? intval(trim($_POST['timeout']))               : 60);
928         $maxloadavg             =       ((x($_POST,'maxloadavg'))               ? intval(trim($_POST['maxloadavg']))            : 50);
929         $maxloadavg_frontend    =       ((x($_POST,'maxloadavg_frontend'))      ? intval(trim($_POST['maxloadavg_frontend']))   : 50);
930         $min_memory             =       ((x($_POST,'min_memory'))               ? intval(trim($_POST['min_memory']))            : 0);
931         $optimize_max_tablesize =       ((x($_POST,'optimize_max_tablesize'))   ? intval(trim($_POST['optimize_max_tablesize'])): 100);
932         $optimize_fragmentation =       ((x($_POST,'optimize_fragmentation'))   ? intval(trim($_POST['optimize_fragmentation'])): 30);
933         $poco_completion        =       ((x($_POST,'poco_completion'))          ? intval(trim($_POST['poco_completion']))       : false);
934         $poco_requery_days      =       ((x($_POST,'poco_requery_days'))        ? intval(trim($_POST['poco_requery_days']))     : 7);
935         $poco_discovery         =       ((x($_POST,'poco_discovery'))           ? intval(trim($_POST['poco_discovery']))        : 0);
936         $poco_discovery_since   =       ((x($_POST,'poco_discovery_since'))     ? intval(trim($_POST['poco_discovery_since']))  : 30);
937         $poco_local_search      =       ((x($_POST,'poco_local_search'))        ? intval(trim($_POST['poco_local_search']))     : false);
938         $nodeinfo               =       ((x($_POST,'nodeinfo'))                 ? intval(trim($_POST['nodeinfo']))              : false);
939         $dfrn_only              =       ((x($_POST,'dfrn_only'))                ? True                                          : False);
940         $ostatus_disabled       =       !((x($_POST,'ostatus_disabled'))        ? True                                          : False);
941         $ostatus_full_threads   =       ((x($_POST,'ostatus_full_threads'))     ? True                                          : False);
942         $diaspora_enabled       =       ((x($_POST,'diaspora_enabled'))         ? True                                          : False);
943         $ssl_policy             =       ((x($_POST,'ssl_policy'))               ? intval($_POST['ssl_policy'])                  : 0);
944         $force_ssl              =       ((x($_POST,'force_ssl'))                ? True                                          : False);
945         $hide_help              =       ((x($_POST,'hide_help'))                ? True                                          : False);
946         $suppress_tags          =       ((x($_POST,'suppress_tags'))            ? True                                          : False);
947         $itemcache              =       ((x($_POST,'itemcache'))                ? notags(trim($_POST['itemcache']))             : '');
948         $itemcache_duration     =       ((x($_POST,'itemcache_duration'))       ? intval($_POST['itemcache_duration'])          : 0);
949         $max_comments           =       ((x($_POST,'max_comments'))             ? intval($_POST['max_comments'])                : 0);
950         $temppath               =       ((x($_POST,'temppath'))                 ? notags(trim($_POST['temppath']))              : '');
951         $basepath               =       ((x($_POST,'basepath'))                 ? notags(trim($_POST['basepath']))              : '');
952         $singleuser             =       ((x($_POST,'singleuser'))               ? notags(trim($_POST['singleuser']))            : '');
953         $proxy_disabled         =       ((x($_POST,'proxy_disabled'))           ? True                                          : False);
954         $only_tag_search        =       ((x($_POST,'only_tag_search'))          ? True                                          : False);
955         $rino                   =       ((x($_POST,'rino'))                     ? intval($_POST['rino'])                        : 0);
956         $check_new_version_url  =       ((x($_POST, 'check_new_version_url'))   ?       notags(trim($_POST['check_new_version_url']))   : 'none');
957         $worker_queues          =       ((x($_POST,'worker_queues'))            ? intval($_POST['worker_queues'])               : 4);
958         $worker_dont_fork       =       ((x($_POST,'worker_dont_fork'))         ? True                                          : False);
959         $worker_fastlane        =       ((x($_POST,'worker_fastlane'))          ? True                                          : False);
960         $worker_frontend        =       ((x($_POST,'worker_frontend'))          ? True                                          : False);
961
962         // Has the directory url changed? If yes, then resubmit the existing profiles there
963         if ($global_directory != Config::get('system', 'directory') && ($global_directory != '')) {
964                 Config::set('system', 'directory', $global_directory);
965                 Worker::add(PRIORITY_LOW, 'Directory');
966         }
967
968         if ($a->get_path() != "") {
969                 $diaspora_enabled = false;
970         }
971         if ($ssl_policy != intval(Config::get('system', 'ssl_policy'))) {
972                 if ($ssl_policy == SSL_POLICY_FULL) {
973                         q("UPDATE `contact` SET
974                                 `url`     = REPLACE(`url`    , 'http:' , 'https:'),
975                                 `photo`   = REPLACE(`photo`  , 'http:' , 'https:'),
976                                 `thumb`   = REPLACE(`thumb`  , 'http:' , 'https:'),
977                                 `micro`   = REPLACE(`micro`  , 'http:' , 'https:'),
978                                 `request` = REPLACE(`request`, 'http:' , 'https:'),
979                                 `notify`  = REPLACE(`notify` , 'http:' , 'https:'),
980                                 `poll`    = REPLACE(`poll`   , 'http:' , 'https:'),
981                                 `confirm` = REPLACE(`confirm`, 'http:' , 'https:'),
982                                 `poco`    = REPLACE(`poco`   , 'http:' , 'https:')
983                                 WHERE `self` = 1"
984                         );
985                         q("UPDATE `profile` SET
986                                 `photo`   = REPLACE(`photo`  , 'http:' , 'https:'),
987                                 `thumb`   = REPLACE(`thumb`  , 'http:' , 'https:')
988                                 WHERE 1 "
989                         );
990                 } elseif ($ssl_policy == SSL_POLICY_SELFSIGN) {
991                         q("UPDATE `contact` SET
992                                 `url`     = REPLACE(`url`    , 'https:' , 'http:'),
993                                 `photo`   = REPLACE(`photo`  , 'https:' , 'http:'),
994                                 `thumb`   = REPLACE(`thumb`  , 'https:' , 'http:'),
995                                 `micro`   = REPLACE(`micro`  , 'https:' , 'http:'),
996                                 `request` = REPLACE(`request`, 'https:' , 'http:'),
997                                 `notify`  = REPLACE(`notify` , 'https:' , 'http:'),
998                                 `poll`    = REPLACE(`poll`   , 'https:' , 'http:'),
999                                 `confirm` = REPLACE(`confirm`, 'https:' , 'http:'),
1000                                 `poco`    = REPLACE(`poco`   , 'https:' , 'http:')
1001                                 WHERE `self` = 1"
1002                         );
1003                         q("UPDATE `profile` SET
1004                                 `photo`   = REPLACE(`photo`  , 'https:' , 'http:'),
1005                                 `thumb`   = REPLACE(`thumb`  , 'https:' , 'http:')
1006                                 WHERE 1 "
1007                         );
1008                 }
1009         }
1010         Config::set('system', 'ssl_policy', $ssl_policy);
1011         Config::set('system', 'maxloadavg', $maxloadavg);
1012         Config::set('system', 'maxloadavg_frontend', $maxloadavg_frontend);
1013         Config::set('system', 'min_memory', $min_memory);
1014         Config::set('system', 'optimize_max_tablesize', $optimize_max_tablesize);
1015         Config::set('system', 'optimize_fragmentation', $optimize_fragmentation);
1016         Config::set('system', 'poco_completion', $poco_completion);
1017         Config::set('system', 'poco_requery_days', $poco_requery_days);
1018         Config::set('system', 'poco_discovery', $poco_discovery);
1019         Config::set('system', 'poco_discovery_since', $poco_discovery_since);
1020         Config::set('system', 'poco_local_search', $poco_local_search);
1021         Config::set('system', 'nodeinfo', $nodeinfo);
1022         Config::set('config', 'sitename', $sitename);
1023         Config::set('config', 'hostname', $hostname);
1024         Config::set('config', 'sender_email', $sender_email);
1025         Config::set('system', 'suppress_tags', $suppress_tags);
1026         Config::set('system', 'shortcut_icon', $shortcut_icon);
1027         Config::set('system', 'touch_icon', $touch_icon);
1028
1029         if ($banner == "") {
1030                 // don't know why, but del_config doesn't work...
1031                 q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1", dbesc("system"), dbesc("banner")
1032                 );
1033         } else {
1034                 Config::set('system', 'banner', $banner);
1035         }
1036
1037         if ($info == "") {
1038                 Config::delete('config', 'info');
1039         } else {
1040                 Config::set('config', 'info', $info);
1041         }
1042         Config::set('system', 'language', $language);
1043         Config::set('system', 'theme', $theme);
1044
1045         if ($theme_mobile == '---') {
1046                 Config::delete('system', 'mobile-theme');
1047         } else {
1048                 Config::set('system', 'mobile-theme', $theme_mobile);
1049         }
1050         if ($singleuser == '---') {
1051                 Config::delete('system', 'singleuser');
1052         } else {
1053                 Config::set('system', 'singleuser', $singleuser);
1054         }
1055         Config::set('system', 'maximagesize', $maximagesize);
1056         Config::set('system', 'max_image_length', $maximagelength);
1057         Config::set('system', 'jpeg_quality', $jpegimagequality);
1058
1059         Config::set('config', 'register_policy', $register_policy);
1060         Config::set('system', 'max_daily_registrations', $daily_registrations);
1061         Config::set('system', 'account_abandon_days', $abandon_days);
1062         Config::set('config', 'register_text', $register_text);
1063         Config::set('system', 'allowed_sites', $allowed_sites);
1064         Config::set('system', 'allowed_email', $allowed_email);
1065         Config::set('system', 'block_public', $block_public);
1066         Config::set('system', 'publish_all', $force_publish);
1067         Config::set('system', 'newuser_private', $newuser_private);
1068         Config::set('system', 'enotify_no_content', $enotify_no_content);
1069         Config::set('system', 'disable_embedded', $disable_embedded);
1070         Config::set('system', 'allow_users_remote_self', $allow_users_remote_self);
1071         Config::set('system', 'check_new_version_url', $check_new_version_url);
1072
1073         Config::set('system', 'block_extended_register', $no_multi_reg);
1074         Config::set('system', 'no_openid', $no_openid);
1075         Config::set('system', 'no_regfullname', $no_regfullname);
1076         Config::set('system', 'community_page_style', $community_page_style);
1077         Config::set('system', 'max_author_posts_community_page', $max_author_posts_community_page);
1078         Config::set('system', 'verifyssl', $verifyssl);
1079         Config::set('system', 'proxyuser', $proxyuser);
1080         Config::set('system', 'proxy', $proxy);
1081         Config::set('system', 'curl_timeout', $timeout);
1082         Config::set('system', 'dfrn_only', $dfrn_only);
1083         Config::set('system', 'ostatus_disabled', $ostatus_disabled);
1084         Config::set('system', 'ostatus_full_threads', $ostatus_full_threads);
1085         Config::set('system', 'diaspora_enabled', $diaspora_enabled);
1086
1087         Config::set('config', 'private_addons', $private_addons);
1088
1089         Config::set('system', 'force_ssl', $force_ssl);
1090         Config::set('system', 'hide_help', $hide_help);
1091
1092         if ($itemcache != '') {
1093                 $itemcache = App::realpath($itemcache);
1094         }
1095
1096         Config::set('system', 'itemcache', $itemcache);
1097         Config::set('system', 'itemcache_duration', $itemcache_duration);
1098         Config::set('system', 'max_comments', $max_comments);
1099
1100         if ($temppath != '') {
1101                 $temppath = App::realpath($temppath);
1102         }
1103
1104         Config::set('system', 'temppath', $temppath);
1105
1106         if ($basepath != '') {
1107                 $basepath = App::realpath($basepath);
1108         }
1109
1110         Config::set('system', 'basepath', $basepath);
1111         Config::set('system', 'proxy_disabled', $proxy_disabled);
1112         Config::set('system', 'only_tag_search', $only_tag_search);
1113         Config::set('system', 'worker_queues', $worker_queues);
1114         Config::set('system', 'worker_dont_fork', $worker_dont_fork);
1115         Config::set('system', 'worker_fastlane', $worker_fastlane);
1116         Config::set('system', 'frontend_worker', $worker_frontend);
1117         Config::set('system', 'rino_encrypt', $rino);
1118
1119         info(t('Site settings updated.') . EOL);
1120         goaway('admin/site');
1121         return; // NOTREACHED
1122 }
1123
1124 /**
1125  * @brief Generate Admin Site subpage
1126  *
1127  * This function generates the main configuration page of the admin panel.
1128  *
1129  * @param  App $a
1130  * @return string
1131  */
1132 function admin_page_site(App $a)
1133 {
1134         /* Installed langs */
1135         $lang_choices = get_available_languages();
1136
1137         if (strlen(Config::get('system', 'directory_submit_url')) &&
1138                 !strlen(Config::get('system', 'directory'))) {
1139                 Config::set('system', 'directory', dirname(Config::get('system', 'directory_submit_url')));
1140                 Config::delete('system', 'directory_submit_url');
1141         }
1142
1143         /* Installed themes */
1144         $theme_choices = array();
1145         $theme_choices_mobile = array();
1146         $theme_choices_mobile["---"] = t("No special theme for mobile devices");
1147         $files = glob('view/theme/*');
1148         if ($files) {
1149
1150                 $allowed_theme_list = Config::get('system', 'allowed_themes');
1151
1152                 foreach ($files as $file) {
1153                         if (intval(file_exists($file . '/unsupported'))) {
1154                                 continue;
1155                         }
1156
1157                         $f = basename($file);
1158
1159                         // Only show allowed themes here
1160                         if (($allowed_theme_list != '') && !strstr($allowed_theme_list, $f)) {
1161                                 continue;
1162                         }
1163
1164                         $theme_name = ((file_exists($file . '/experimental')) ? sprintf("%s - \x28Experimental\x29", $f) : $f);
1165
1166                         if (file_exists($file . '/mobile')) {
1167                                 $theme_choices_mobile[$f] = $theme_name;
1168                         } else {
1169                                 $theme_choices[$f] = $theme_name;
1170                         }
1171                 }
1172         }
1173
1174         /* Community page style */
1175         $community_page_style_choices = array(
1176                 CP_NO_COMMUNITY_PAGE => t("No community page"),
1177                 CP_USERS_ON_SERVER => t("Public postings from users of this site"),
1178                 CP_GLOBAL_COMMUNITY => t("Global community page")
1179         );
1180
1181         /* OStatus conversation poll choices */
1182         $ostatus_poll_choices = array(
1183                 "-2" => t("Never"),
1184                 "-1" => t("At post arrival"),
1185                 "0" => t("Frequently"),
1186                 "60" => t("Hourly"),
1187                 "720" => t("Twice daily"),
1188                 "1440" => t("Daily")
1189         );
1190
1191         $poco_discovery_choices = array(
1192                 "0" => t("Disabled"),
1193                 "1" => t("Users"),
1194                 "2" => t("Users, Global Contacts"),
1195                 "3" => t("Users, Global Contacts/fallback"),
1196         );
1197
1198         $poco_discovery_since_choices = array(
1199                 "30" => t("One month"),
1200                 "91" => t("Three months"),
1201                 "182" => t("Half a year"),
1202                 "365" => t("One year"),
1203         );
1204
1205         /* get user names to make the install a personal install of X */
1206         $user_names = array();
1207         $user_names['---'] = t('Multi user instance');
1208         $users = q("SELECT `username`, `nickname` FROM `user`");
1209         foreach ($users as $user) {
1210                 $user_names[$user['nickname']] = $user['username'];
1211         }
1212
1213         /* Banner */
1214         $banner = Config::get('system', 'banner');
1215         if ($banner == false) {
1216                 $banner = '<a href="https://friendi.ca"><img id="logo-img" src="images/friendica-32.png" alt="logo" /></a><span id="logo-text"><a href="https://friendi.ca">Friendica</a></span>';
1217         }
1218         $banner = htmlspecialchars($banner);
1219         $info = Config::get('config', 'info');
1220         $info = htmlspecialchars($info);
1221
1222         // Automatically create temporary paths
1223         get_temppath();
1224         get_itemcachepath();
1225
1226         //echo "<pre>"; var_dump($lang_choices); die("</pre>");
1227
1228         /* Register policy */
1229         $register_choices = array(
1230                 REGISTER_CLOSED => t("Closed"),
1231                 REGISTER_APPROVE => t("Requires approval"),
1232                 REGISTER_OPEN => t("Open")
1233         );
1234
1235         $ssl_choices = array(
1236                 SSL_POLICY_NONE => t("No SSL policy, links will track page SSL state"),
1237                 SSL_POLICY_FULL => t("Force all links to use SSL"),
1238                 SSL_POLICY_SELFSIGN => t("Self-signed certificate, use SSL for local links only (discouraged)")
1239         );
1240
1241         $check_git_version_choices = array(
1242                 "none" => t("Don't check"),
1243                 "master" => t("check the stable version"),
1244                 "develop" => t("check the development version")
1245         );
1246
1247         if ($a->config['hostname'] == "") {
1248                 $a->config['hostname'] = $a->get_hostname();
1249         }
1250         $diaspora_able = ($a->get_path() == "");
1251
1252         $optimize_max_tablesize = Config::get('system', 'optimize_max_tablesize', 100);
1253
1254         if ($optimize_max_tablesize < -1) {
1255                 $optimize_max_tablesize = -1;
1256         }
1257
1258         if ($optimize_max_tablesize == 0) {
1259                 $optimize_max_tablesize = 100;
1260         }
1261
1262         $t = get_markup_template('admin/site.tpl');
1263         return replace_macros($t, array(
1264                 '$title' => t('Administration'),
1265                 '$page' => t('Site'),
1266                 '$submit' => t('Save Settings'),
1267                 '$republish' => t('Republish users to directory'),
1268                 '$registration' => t('Registration'),
1269                 '$upload' => t('File upload'),
1270                 '$corporate' => t('Policies'),
1271                 '$advanced' => t('Advanced'),
1272                 '$portable_contacts' => t('Auto Discovered Contact Directory'),
1273                 '$performance' => t('Performance'),
1274                 '$worker_title' => t('Worker'),
1275                 '$relocate' => t('Relocate - WARNING: advanced function. Could make this server unreachable.'),
1276                 '$baseurl' => System::baseUrl(true),
1277                 // name, label, value, help string, extra data...
1278                 '$sitename'             => array('sitename', t("Site name"), $a->config['sitename'],''),
1279                 '$hostname'             => array('hostname', t("Host name"), $a->config['hostname'], ""),
1280                 '$sender_email'         => array('sender_email', t("Sender Email"), $a->config['sender_email'], t("The email address your server shall use to send notification emails from."), "", "", "email"),
1281                 '$banner'               => array('banner', t("Banner/Logo"), $banner, ""),
1282                 '$shortcut_icon'        => array('shortcut_icon', t("Shortcut icon"), Config::get('system','shortcut_icon'),  t("Link to an icon that will be used for browsers.")),
1283                 '$touch_icon'           => array('touch_icon', t("Touch icon"), Config::get('system','touch_icon'),  t("Link to an icon that will be used for tablets and mobiles.")),
1284                 '$info'                 => array('info', t('Additional Info'), $info, sprintf(t('For public servers: you can add additional information here that will be listed at %s/servers.'), get_server())),
1285                 '$language'             => array('language', t("System language"), Config::get('system','language'), "", $lang_choices),
1286                 '$theme'                => array('theme', t("System theme"), Config::get('system','theme'), t("Default system theme - may be over-ridden by user profiles - <a href='#' id='cnftheme'>change theme settings</a>"), $theme_choices),
1287                 '$theme_mobile'         => array('theme_mobile', t("Mobile system theme"), Config::get('system', 'mobile-theme', '---'), t("Theme for mobile devices"), $theme_choices_mobile),
1288                 '$ssl_policy'           => array('ssl_policy', t("SSL link policy"), (string) intval(Config::get('system','ssl_policy')), t("Determines whether generated links should be forced to use SSL"), $ssl_choices),
1289                 '$force_ssl'            => array('force_ssl', t("Force SSL"), Config::get('system','force_ssl'), t("Force all Non-SSL requests to SSL - Attention: on some systems it could lead to endless loops.")),
1290                 '$hide_help'            => array('hide_help', t("Hide help entry from navigation menu"), Config::get('system','hide_help'), t("Hides the menu entry for the Help pages from the navigation menu. You can still access it calling /help directly.")),
1291                 '$singleuser'           => array('singleuser', t("Single user instance"), Config::get('system', 'singleuser', '---'), t("Make this instance multi-user or single-user for the named user"), $user_names),
1292                 '$maximagesize'         => array('maximagesize', t("Maximum image size"), Config::get('system','maximagesize'), t("Maximum size in bytes of uploaded images. Default is 0, which means no limits.")),
1293                 '$maximagelength'       => array('maximagelength', t("Maximum image length"), Config::get('system','max_image_length'), t("Maximum length in pixels of the longest side of uploaded images. Default is -1, which means no limits.")),
1294                 '$jpegimagequality'     => array('jpegimagequality', t("JPEG image quality"), Config::get('system','jpeg_quality'), t("Uploaded JPEGS will be saved at this quality setting [0-100]. Default is 100, which is full quality.")),
1295
1296                 '$register_policy'      => array('register_policy', t("Register policy"), $a->config['register_policy'], "", $register_choices),
1297                 '$daily_registrations'  => array('max_daily_registrations', t("Maximum Daily Registrations"), Config::get('system', 'max_daily_registrations'), t("If registration is permitted above, this sets the maximum number of new user registrations to accept per day.  If register is set to closed, this setting has no effect.")),
1298                 '$register_text'        => array('register_text', t("Register text"), $a->config['register_text'], t("Will be displayed prominently on the registration page.")),
1299                 '$abandon_days'         => array('abandon_days', t('Accounts abandoned after x days'), Config::get('system','account_abandon_days'), t('Will not waste system resources polling external sites for abandonded accounts. Enter 0 for no time limit.')),
1300                 '$allowed_sites'        => array('allowed_sites', t("Allowed friend domains"), Config::get('system','allowed_sites'), t("Comma separated list of domains which are allowed to establish friendships with this site. Wildcards are accepted. Empty to allow any domains")),
1301                 '$allowed_email'        => array('allowed_email', t("Allowed email domains"), Config::get('system','allowed_email'), t("Comma separated list of domains which are allowed in email addresses for registrations to this site. Wildcards are accepted. Empty to allow any domains")),
1302                 '$block_public'         => array('block_public', t("Block public"), Config::get('system','block_public'), t("Check to block public access to all otherwise public personal pages on this site unless you are currently logged in.")),
1303                 '$force_publish'        => array('publish_all', t("Force publish"), Config::get('system','publish_all'), t("Check to force all profiles on this site to be listed in the site directory.")),
1304                 '$global_directory'     => array('directory', t("Global directory URL"), Config::get('system','directory'), t("URL to the global directory. If this is not set, the global directory is completely unavailable to the application.")),
1305                 '$newuser_private'      => array('newuser_private', t("Private posts by default for new users"), Config::get('system','newuser_private'), t("Set default post permissions for all new members to the default privacy group rather than public.")),
1306                 '$enotify_no_content'   => array('enotify_no_content', t("Don't include post content in email notifications"), Config::get('system','enotify_no_content'), t("Don't include the content of a post/comment/private message/etc. in the email notifications that are sent out from this site, as a privacy measure.")),
1307                 '$private_addons'       => array('private_addons', t("Disallow public access to addons listed in the apps menu."), Config::get('config','private_addons'), t("Checking this box will restrict addons listed in the apps menu to members only.")),
1308                 '$disable_embedded'     => array('disable_embedded', t("Don't embed private images in posts"), Config::get('system','disable_embedded'), t("Don't replace locally-hosted private photos in posts with an embedded copy of the image. This means that contacts who receive posts containing private photos will have to authenticate and load each image, which may take a while.")),
1309                 '$allow_users_remote_self' => array('allow_users_remote_self', t('Allow Users to set remote_self'), Config::get('system','allow_users_remote_self'), t('With checking this, every user is allowed to mark every contact as a remote_self in the repair contact dialog. Setting this flag on a contact causes mirroring every posting of that contact in the users stream.')),
1310                 '$no_multi_reg'         => array('no_multi_reg', t("Block multiple registrations"),  Config::get('system','block_extended_register'), t("Disallow users to register additional accounts for use as pages.")),
1311                 '$no_openid'            => array('no_openid', t("OpenID support"), !Config::get('system','no_openid'), t("OpenID support for registration and logins.")),
1312                 '$no_regfullname'       => array('no_regfullname', t("Fullname check"), !Config::get('system','no_regfullname'), t("Force users to register with a space between firstname and lastname in Full name, as an antispam measure")),
1313                 '$community_page_style' => array('community_page_style', t("Community Page Style"), Config::get('system','community_page_style'), t("Type of community page to show. 'Global community' shows every public posting from an open distributed network that arrived on this server."), $community_page_style_choices),
1314                 '$max_author_posts_community_page' => array('max_author_posts_community_page', t("Posts per user on community page"), Config::get('system','max_author_posts_community_page'), t("The maximum number of posts per user on the community page. (Not valid for 'Global Community')")),
1315                 '$ostatus_disabled'     => array('ostatus_disabled', t("Enable OStatus support"), !Config::get('system','ostatus_disabled'), t("Provide built-in OStatus \x28StatusNet, GNU Social etc.\x29 compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed.")),
1316                 '$ostatus_full_threads' => array('ostatus_full_threads', t("Only import OStatus threads from our contacts"), Config::get('system','ostatus_full_threads'), t("Normally we import every content from our OStatus contacts. With this option we only store threads that are started by a contact that is known on our system.")),
1317                 '$ostatus_not_able'     => t("OStatus support can only be enabled if threading is enabled."),
1318                 '$diaspora_able'        => $diaspora_able,
1319                 '$diaspora_not_able'    => t("Diaspora support can't be enabled because Friendica was installed into a sub directory."),
1320                 '$diaspora_enabled'     => array('diaspora_enabled', t("Enable Diaspora support"), Config::get('system','diaspora_enabled'), t("Provide built-in Diaspora network compatibility.")),
1321                 '$dfrn_only'            => array('dfrn_only', t('Only allow Friendica contacts'), Config::get('system','dfrn_only'), t("All contacts must use Friendica protocols. All other built-in communication protocols disabled.")),
1322                 '$verifyssl'            => array('verifyssl', t("Verify SSL"), Config::get('system','verifyssl'), t("If you wish, you can turn on strict certificate checking. This will mean you cannot connect (at all) to self-signed SSL sites.")),
1323                 '$proxyuser'            => array('proxyuser', t("Proxy user"), Config::get('system','proxyuser'), ""),
1324                 '$proxy'                => array('proxy', t("Proxy URL"), Config::get('system','proxy'), ""),
1325                 '$timeout'              => array('timeout', t("Network timeout"), (x(Config::get('system','curl_timeout'))?Config::get('system','curl_timeout'):60), t("Value is in seconds. Set to 0 for unlimited (not recommended).")),
1326                 '$maxloadavg'           => array('maxloadavg', t("Maximum Load Average"), ((intval(Config::get('system','maxloadavg')) > 0)?Config::get('system','maxloadavg'):50), t("Maximum system load before delivery and poll processes are deferred - default 50.")),
1327                 '$maxloadavg_frontend'  => array('maxloadavg_frontend', t("Maximum Load Average (Frontend)"), ((intval(Config::get('system','maxloadavg_frontend')) > 0)?Config::get('system','maxloadavg_frontend'):50), t("Maximum system load before the frontend quits service - default 50.")),
1328                 '$min_memory'           => array('min_memory', t("Minimal Memory"), ((intval(Config::get('system','min_memory')) > 0)?Config::get('system','min_memory'):0), t("Minimal free memory in MB for the worker. Needs access to /proc/meminfo - default 0 (deactivated).")),
1329                 '$optimize_max_tablesize'=> array('optimize_max_tablesize', t("Maximum table size for optimization"), $optimize_max_tablesize, t("Maximum table size (in MB) for the automatic optimization - default 100 MB. Enter -1 to disable it.")),
1330                 '$optimize_fragmentation'=> array('optimize_fragmentation', t("Minimum level of fragmentation"), ((intval(Config::get('system','optimize_fragmentation')) > 0)?Config::get('system','optimize_fragmentation'):30), t("Minimum fragmenation level to start the automatic optimization - default value is 30%.")),
1331
1332                 '$poco_completion'      => array('poco_completion', t("Periodical check of global contacts"), Config::get('system','poco_completion'), t("If enabled, the global contacts are checked periodically for missing or outdated data and the vitality of the contacts and servers.")),
1333                 '$poco_requery_days'    => array('poco_requery_days', t("Days between requery"), Config::get('system','poco_requery_days'), t("Number of days after which a server is requeried for his contacts.")),
1334                 '$poco_discovery'       => array('poco_discovery', t("Discover contacts from other servers"), (string) intval(Config::get('system','poco_discovery')), t("Periodically query other servers for contacts. You can choose between 'users': the users on the remote system, 'Global Contacts': active contacts that are known on the system. The fallback is meant for Redmatrix servers and older friendica servers, where global contacts weren't available. The fallback increases the server load, so the recommened setting is 'Users, Global Contacts'."), $poco_discovery_choices),
1335                 '$poco_discovery_since' => array('poco_discovery_since', t("Timeframe for fetching global contacts"), (string) intval(Config::get('system','poco_discovery_since')), t("When the discovery is activated, this value defines the timeframe for the activity of the global contacts that are fetched from other servers."), $poco_discovery_since_choices),
1336                 '$poco_local_search'    => array('poco_local_search', t("Search the local directory"), Config::get('system','poco_local_search'), t("Search the local directory instead of the global directory. When searching locally, every search will be executed on the global directory in the background. This improves the search results when the search is repeated.")),
1337
1338                 '$nodeinfo'             => array('nodeinfo', t("Publish server information"), Config::get('system','nodeinfo'), t("If enabled, general server and usage data will be published. The data contains the name and version of the server, number of users with public profiles, number of posts and the activated protocols and connectors. See <a href='http://the-federation.info/'>the-federation.info</a> for details.")),
1339
1340                 '$check_new_version_url' => array('check_new_version_url', t("Check upstream version"), Config::get('system', 'check_new_version_url'), t("Enables checking for new Friendica versions at github. If there is a new version, you will be informed in the admin panel overview."), $check_git_version_choices),
1341                 '$suppress_tags'        => array('suppress_tags', t("Suppress Tags"), Config::get('system','suppress_tags'), t("Suppress showing a list of hashtags at the end of the posting.")),
1342                 '$itemcache'            => array('itemcache', t("Path to item cache"), Config::get('system','itemcache'), t("The item caches buffers generated bbcode and external images.")),
1343                 '$itemcache_duration'   => array('itemcache_duration', t("Cache duration in seconds"), Config::get('system','itemcache_duration'), t("How long should the cache files be hold? Default value is 86400 seconds (One day). To disable the item cache, set the value to -1.")),
1344                 '$max_comments'         => array('max_comments', t("Maximum numbers of comments per post"), Config::get('system','max_comments'), t("How much comments should be shown for each post? Default value is 100.")),
1345                 '$temppath'             => array('temppath', t("Temp path"), Config::get('system','temppath'), t("If you have a restricted system where the webserver can't access the system temp path, enter another path here.")),
1346                 '$basepath'             => array('basepath', t("Base path to installation"), Config::get('system','basepath'), t("If the system cannot detect the correct path to your installation, enter the correct path here. This setting should only be set if you are using a restricted system and symbolic links to your webroot.")),
1347                 '$proxy_disabled'       => array('proxy_disabled', t("Disable picture proxy"), Config::get('system','proxy_disabled'), t("The picture proxy increases performance and privacy. It shouldn't be used on systems with very low bandwith.")),
1348                 '$only_tag_search'      => array('only_tag_search', t("Only search in tags"), Config::get('system','only_tag_search'), t("On large systems the text search can slow down the system extremely.")),
1349
1350                 '$relocate_url'         => array('relocate_url', t("New base url"), System::baseUrl(), t("Change base url for this server. Sends relocate message to all Friendica and Diaspora* contacts of all users.")),
1351
1352                 '$rino'                 => array('rino', t("RINO Encryption"), intval(Config::get('system','rino_encrypt')), t("Encryption layer between nodes."), array("Disabled", "RINO1 (deprecated)", "RINO2")),
1353
1354                 '$worker_queues'        => array('worker_queues', t("Maximum number of parallel workers"), Config::get('system','worker_queues'), t("On shared hosters set this to 2. On larger systems, values of 10 are great. Default value is 4.")),
1355                 '$worker_dont_fork'     => array('worker_dont_fork', t("Don't use 'proc_open' with the worker"), Config::get('system','worker_dont_fork'), t("Enable this if your system doesn't allow the use of 'proc_open'. This can happen on shared hosters. If this is enabled you should increase the frequency of worker calls in your crontab.")),
1356                 '$worker_fastlane'      => array('worker_fastlane', t("Enable fastlane"), Config::get('system','worker_fastlane'), t("When enabed, the fastlane mechanism starts an additional worker if processes with higher priority are blocked by processes of lower priority.")),
1357                 '$worker_frontend'      => array('worker_frontend', t('Enable frontend worker'), Config::get('system','frontend_worker'), sprintf(t('When enabled the Worker process is triggered when backend access is performed (e.g. messages being delivered). On smaller sites you might want to call %s/worker on a regular basis via an external cron job. You should only enable this option if you cannot utilize cron/scheduled jobs on your server.'), System::baseUrl())),
1358
1359                 '$form_security_token'  => get_form_security_token("admin_site")
1360         ));
1361 }
1362
1363 /**
1364  * @brief Generates admin panel subpage for DB syncronization
1365  *
1366  * This page checks if the database of friendica is in sync with the specs.
1367  * Should this not be the case, it attemps to sync the structure and notifies
1368  * the admin if the automatic process was failing.
1369  *
1370  * The returned string holds the HTML code of the page.
1371  *
1372  * @param App $a
1373  * @return string
1374  * */
1375 function admin_page_dbsync(App $a)
1376 {
1377         $o = '';
1378
1379         if ($a->argc > 3 && intval($a->argv[3]) && $a->argv[2] === 'mark') {
1380                 Config::set('database', 'update_' . intval($a->argv[3]), 'success');
1381                 $curr = Config::get('system', 'build');
1382                 if (intval($curr) == intval($a->argv[3])) {
1383                         Config::set('system', 'build', intval($curr) + 1);
1384                 }
1385                 info(t('Update has been marked successful') . EOL);
1386                 goaway('admin/dbsync');
1387         }
1388
1389         if (($a->argc > 2) && (intval($a->argv[2]) || ($a->argv[2] === 'check'))) {
1390                 $retval = DBStructure::update(false, true);
1391                 if (!$retval) {
1392                         $o .= sprintf(t("Database structure update %s was successfully applied."), DB_UPDATE_VERSION) . "<br />";
1393                         Config::set('database', 'dbupdate_' . DB_UPDATE_VERSION, 'success');
1394                 } else {
1395                         $o .= sprintf(t("Executing of database structure update %s failed with error: %s"), DB_UPDATE_VERSION, $retval) . "<br />";
1396                 }
1397                 if ($a->argv[2] === 'check') {
1398                         return $o;
1399                 }
1400         }
1401
1402         if ($a->argc > 2 && intval($a->argv[2])) {
1403                 require_once 'update.php';
1404                 $func = 'update_' . intval($a->argv[2]);
1405                 if (function_exists($func)) {
1406                         $retval = $func();
1407                         if ($retval === UPDATE_FAILED) {
1408                                 $o .= sprintf(t("Executing %s failed with error: %s"), $func, $retval);
1409                         } elseif ($retval === UPDATE_SUCCESS) {
1410                                 $o .= sprintf(t('Update %s was successfully applied.', $func));
1411                                 Config::set('database', $func, 'success');
1412                         } else {
1413                                 $o .= sprintf(t('Update %s did not return a status. Unknown if it succeeded.'), $func);
1414                         }
1415                 } else {
1416                         $o .= sprintf(t('There was no additional update function %s that needed to be called.'), $func) . "<br />";
1417                         Config::set('database', $func, 'success');
1418                 }
1419                 return $o;
1420         }
1421
1422         $failed = array();
1423         $r = q("SELECT `k`, `v` FROM `config` WHERE `cat` = 'database' ");
1424         if (DBM::is_result($r)) {
1425                 foreach ($r as $rr) {
1426                         $upd = intval(substr($rr['k'], 7));
1427                         if ($upd < 1139 || $rr['v'] === 'success') {
1428                                 continue;
1429                         }
1430                         $failed[] = $upd;
1431                 }
1432         }
1433         if (!count($failed)) {
1434                 $o = replace_macros(get_markup_template('structure_check.tpl'), array(
1435                         '$base' => System::baseUrl(true),
1436                         '$banner' => t('No failed updates.'),
1437                         '$check' => t('Check database structure'),
1438                 ));
1439         } else {
1440                 $o = replace_macros(get_markup_template('failed_updates.tpl'), array(
1441                         '$base' => System::baseUrl(true),
1442                         '$banner' => t('Failed Updates'),
1443                         '$desc' => t('This does not include updates prior to 1139, which did not return a status.'),
1444                         '$mark' => t('Mark success (if update was manually applied)'),
1445                         '$apply' => t('Attempt to execute this update step automatically'),
1446                         '$failed' => $failed
1447                 ));
1448         }
1449
1450         return $o;
1451 }
1452
1453 /**
1454  * @brief Process data send by Users admin page
1455  *
1456  * @param App $a
1457  */
1458 function admin_page_users_post(App $a)
1459 {
1460         $pending     = (x($_POST, 'pending')           ? $_POST['pending']           : array());
1461         $users       = (x($_POST, 'user')              ? $_POST['user']               : array());
1462         $nu_name     = (x($_POST, 'new_user_name')     ? $_POST['new_user_name']     : '');
1463         $nu_nickname = (x($_POST, 'new_user_nickname') ? $_POST['new_user_nickname'] : '');
1464         $nu_email    = (x($_POST, 'new_user_email')    ? $_POST['new_user_email']    : '');
1465         $nu_language = Config::get('system', 'language');
1466
1467         check_form_security_token_redirectOnErr('/admin/users', 'admin_users');
1468
1469         if (!($nu_name === "") && !($nu_email === "") && !($nu_nickname === "")) {
1470                 try {
1471                         $result = User::create([
1472                                 'username' => $nu_name,
1473                                 'email' => $nu_email,
1474                                 'nickname' => $nu_nickname,
1475                                 'verified' => 1,
1476                                 'language' => $nu_language
1477                         ]);
1478                 } catch (Exception $ex) {
1479                         notice($ex->getMessage());
1480                         return;
1481                 }
1482
1483                 $user = $result['user'];
1484                 $preamble = deindent(t('
1485                         Dear %1$s,
1486                                 the administrator of %2$s has set up an account for you.'));
1487                 $body = deindent(t('
1488                         The login details are as follows:
1489
1490                         Site Location:  %1$s
1491                         Login Name:             %2$s
1492                         Password:               %3$s
1493
1494                         You may change your password from your account "Settings" page after logging
1495                         in.
1496
1497                         Please take a few moments to review the other account settings on that page.
1498
1499                         You may also wish to add some basic information to your default profile
1500                         (on the "Profiles" page) so that other people can easily find you.
1501
1502                         We recommend setting your full name, adding a profile photo,
1503                         adding some profile "keywords" (very useful in making new friends) - and
1504                         perhaps what country you live in; if you do not wish to be more specific
1505                         than that.
1506
1507                         We fully respect your right to privacy, and none of these items are necessary.
1508                         If you are new and do not know anybody here, they may help
1509                         you to make some new and interesting friends.
1510
1511                         Thank you and welcome to %4$s.'));
1512
1513                 $preamble = sprintf($preamble, $user['username'], $a->config['sitename']);
1514                 $body = sprintf($body, System::baseUrl(), $user['email'], $result['password'], $a->config['sitename']);
1515
1516                 notification(array(
1517                         'type' => SYSTEM_EMAIL,
1518                         'to_email' => $user['email'],
1519                         'subject' => sprintf(t('Registration details for %s'), $a->config['sitename']),
1520                         'preamble' => $preamble,
1521                         'body' => $body));
1522         }
1523
1524         if (x($_POST, 'page_users_block')) {
1525                 foreach ($users as $uid) {
1526                         q("UPDATE `user` SET `blocked` = 1-`blocked` WHERE `uid` = %s", intval($uid)
1527                         );
1528                 }
1529                 notice(sprintf(tt("%s user blocked/unblocked", "%s users blocked/unblocked", count($users)), count($users)));
1530         }
1531         if (x($_POST, 'page_users_delete')) {
1532                 foreach ($users as $uid) {
1533                         User::remove($uid);
1534                 }
1535                 notice(sprintf(tt("%s user deleted", "%s users deleted", count($users)), count($users)));
1536         }
1537
1538         if (x($_POST, 'page_users_approve')) {
1539                 require_once("mod/regmod.php");
1540                 foreach ($pending as $hash) {
1541                         user_allow($hash);
1542                 }
1543         }
1544         if (x($_POST, 'page_users_deny')) {
1545                 require_once("mod/regmod.php");
1546                 foreach ($pending as $hash) {
1547                         user_deny($hash);
1548                 }
1549         }
1550         goaway('admin/users');
1551         return; // NOTREACHED
1552 }
1553
1554 /**
1555  * @brief Admin panel subpage for User management
1556  *
1557  * This function generates the admin panel page for user management of the
1558  * node. It offers functionality to add/block/delete users and offers some
1559  * statistics about the userbase.
1560  *
1561  * The returned string holds the HTML code of the page.
1562  *
1563  * @param App $a
1564  * @return string
1565  */
1566 function admin_page_users(App $a)
1567 {
1568         if ($a->argc > 2) {
1569                 $uid = $a->argv[3];
1570                 $user = q("SELECT `username`, `blocked` FROM `user` WHERE `uid` = %d", intval($uid));
1571                 if (count($user) == 0) {
1572                         notice('User not found' . EOL);
1573                         goaway('admin/users');
1574                         return ''; // NOTREACHED
1575                 }
1576                 switch ($a->argv[2]) {
1577                         case "delete":
1578                                 check_form_security_token_redirectOnErr('/admin/users', 'admin_users', 't');
1579                                 // delete user
1580                                 User::remove($uid);
1581
1582                                 notice(sprintf(t("User '%s' deleted"), $user[0]['username']) . EOL);
1583                                 break;
1584                         case "block":
1585                                 check_form_security_token_redirectOnErr('/admin/users', 'admin_users', 't');
1586                                 q("UPDATE `user` SET `blocked` = %d WHERE `uid` = %s", intval(1 - $user[0]['blocked']), intval($uid)
1587                                 );
1588                                 notice(sprintf(($user[0]['blocked'] ? t("User '%s' unblocked") : t("User '%s' blocked")), $user[0]['username']) . EOL);
1589                                 break;
1590                 }
1591                 goaway('admin/users');
1592                 return ''; // NOTREACHED
1593         }
1594
1595         /* get pending */
1596         $pending = q("SELECT `register`.*, `contact`.`name`, `user`.`email`
1597                                  FROM `register`
1598                                  INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
1599                                  INNER JOIN `user` ON `register`.`uid` = `user`.`uid`;");
1600
1601
1602         /* get users */
1603         $total = q("SELECT COUNT(*) AS `total` FROM `user` WHERE 1");
1604         if (count($total)) {
1605                 $a->set_pager_total($total[0]['total']);
1606                 $a->set_pager_itemspage(100);
1607         }
1608
1609         /* ordering */
1610         $valid_orders = array(
1611                 'contact.name',
1612                 'user.email',
1613                 'user.register_date',
1614                 'user.login_date',
1615                 'lastitem_date',
1616                 'user.page-flags'
1617         );
1618
1619         $order = "contact.name";
1620         $order_direction = "+";
1621         if (x($_GET, 'o')) {
1622                 $new_order = $_GET['o'];
1623                 if ($new_order[0] === "-") {
1624                         $order_direction = "-";
1625                         $new_order = substr($new_order, 1);
1626                 }
1627
1628                 if (in_array($new_order, $valid_orders)) {
1629                         $order = $new_order;
1630                 }
1631                 if (x($_GET, 'd')) {
1632                         $new_direction = $_GET['d'];
1633                 }
1634         }
1635         $sql_order = "`" . str_replace('.', '`.`', $order) . "`";
1636         $sql_order_direction = ($order_direction === "+") ? "ASC" : "DESC";
1637
1638         $users = q("SELECT `user`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`account_expired`, `contact`.`last-item` AS `lastitem_date`
1639                                 FROM `user`
1640                                 INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
1641                                 WHERE `user`.`verified`
1642                                 ORDER BY $sql_order $sql_order_direction LIMIT %d, %d", intval($a->pager['start']), intval($a->pager['itemspage'])
1643         );
1644
1645         //echo "<pre>$users"; killme();
1646
1647         $adminlist = explode(",", str_replace(" ", "", $a->config['admin_email']));
1648         $_setup_users = function ($e) use ($adminlist) {
1649                 $accounts = array(
1650                         t('Normal Account'),
1651                         t('Automatic Follower Account'),
1652                         t('Public Forum Account'),
1653                         t('Automatic Friend Account')
1654                 );
1655                 $e['page-flags'] = $accounts[$e['page-flags']];
1656                 $e['register_date'] = relative_date($e['register_date']);
1657                 $e['login_date'] = relative_date($e['login_date']);
1658                 $e['lastitem_date'] = relative_date($e['lastitem_date']);
1659                 //$e['is_admin'] = ($e['email'] === $a->config['admin_email']);
1660                 $e['is_admin'] = in_array($e['email'], $adminlist);
1661                 $e['is_deletable'] = (intval($e['uid']) != local_user());
1662                 $e['deleted'] = ($e['account_removed'] ? relative_date($e['account_expires_on']) : False);
1663                 return $e;
1664         };
1665         $users = array_map($_setup_users, $users);
1666
1667
1668         // Get rid of dashes in key names, Smarty3 can't handle them
1669         // and extracting deleted users
1670
1671         $tmp_users = array();
1672         $deleted = array();
1673
1674         while (count($users)) {
1675                 $new_user = array();
1676                 foreach (array_pop($users) as $k => $v) {
1677                         $k = str_replace('-', '_', $k);
1678                         $new_user[$k] = $v;
1679                 }
1680                 if ($new_user['deleted']) {
1681                         array_push($deleted, $new_user);
1682                 } else {
1683                         array_push($tmp_users, $new_user);
1684                 }
1685         }
1686         //Reversing the two array, and moving $tmp_users to $users
1687         array_reverse($deleted);
1688         while (count($tmp_users)) {
1689                 array_push($users, array_pop($tmp_users));
1690         }
1691
1692         $th_users = array_map(null, array(t('Name'), t('Email'), t('Register date'), t('Last login'), t('Last item'), t('Account')), $valid_orders
1693         );
1694
1695         $t = get_markup_template('admin/users.tpl');
1696         $o = replace_macros($t, array(
1697                 // strings //
1698                 '$title' => t('Administration'),
1699                 '$page' => t('Users'),
1700                 '$submit' => t('Add User'),
1701                 '$select_all' => t('select all'),
1702                 '$h_pending' => t('User registrations waiting for confirm'),
1703                 '$h_deleted' => t('User waiting for permanent deletion'),
1704                 '$th_pending' => array(t('Request date'), t('Name'), t('Email')),
1705                 '$no_pending' => t('No registrations.'),
1706                 '$pendingnotetext' => t('Note from the user'),
1707                 '$approve' => t('Approve'),
1708                 '$deny' => t('Deny'),
1709                 '$delete' => t('Delete'),
1710                 '$block' => t('Block'),
1711                 '$unblock' => t('Unblock'),
1712                 '$siteadmin' => t('Site admin'),
1713                 '$accountexpired' => t('Account expired'),
1714
1715                 '$h_users' => t('Users'),
1716                 '$h_newuser' => t('New User'),
1717                 '$th_deleted' => array(t('Name'), t('Email'), t('Register date'), t('Last login'), t('Last item'), t('Deleted since')),
1718                 '$th_users' => $th_users,
1719                 '$order_users' => $order,
1720                 '$order_direction_users' => $order_direction,
1721
1722                 '$confirm_delete_multi' => t('Selected users will be deleted!\n\nEverything these users had posted on this site will be permanently deleted!\n\nAre you sure?'),
1723                 '$confirm_delete' => t('The user {0} will be deleted!\n\nEverything this user has posted on this site will be permanently deleted!\n\nAre you sure?'),
1724
1725                 '$form_security_token' => get_form_security_token("admin_users"),
1726
1727                 // values //
1728                 '$baseurl' => System::baseUrl(true),
1729
1730                 '$pending' => $pending,
1731                 'deleted' => $deleted,
1732                 '$users' => $users,
1733                 '$newusername' => array('new_user_name', t("Name"), '', t("Name of the new user.")),
1734                 '$newusernickname' => array('new_user_nickname', t("Nickname"), '', t("Nickname of the new user.")),
1735                 '$newuseremail' => array('new_user_email', t("Email"), '', t("Email address of the new user."), '', '', 'email'),
1736         ));
1737         $o .= paginate($a);
1738         return $o;
1739 }
1740
1741 /**
1742  * @brief Plugins admin page
1743  *
1744  * This function generates the admin panel page for managing plugins on the
1745  * friendica node. If a plugin name is given a single page showing the details
1746  * for this addon is generated. If no name is given, a list of available
1747  * plugins is shown.
1748  *
1749  * The template used for displaying the list of plugins and the details of the
1750  * plugin are the same as used for the templates.
1751  *
1752  * The returned string returned hulds the HTML code of the page.
1753  *
1754  * @param App $a
1755  * @return string
1756  */
1757 function admin_page_plugins(App $a)
1758 {
1759         /*
1760          * Single plugin
1761          */
1762         if ($a->argc == 3) {
1763                 $plugin = $a->argv[2];
1764                 if (!is_file("addon/$plugin/$plugin.php")) {
1765                         notice(t("Item not found."));
1766                         return '';
1767                 }
1768
1769                 if (x($_GET, "a") && $_GET['a'] == "t") {
1770                         check_form_security_token_redirectOnErr('/admin/plugins', 'admin_themes', 't');
1771
1772                         // Toggle plugin status
1773                         $idx = array_search($plugin, $a->plugins);
1774                         if ($idx !== false) {
1775                                 unset($a->plugins[$idx]);
1776                                 uninstall_plugin($plugin);
1777                                 info(sprintf(t("Plugin %s disabled."), $plugin));
1778                         } else {
1779                                 $a->plugins[] = $plugin;
1780                                 install_plugin($plugin);
1781                                 info(sprintf(t("Plugin %s enabled."), $plugin));
1782                         }
1783                         Config::set("system", "addon", implode(", ", $a->plugins));
1784                         goaway('admin/plugins');
1785                         return ''; // NOTREACHED
1786                 }
1787
1788                 // display plugin details
1789                 require_once('library/markdown.php');
1790
1791                 if (in_array($plugin, $a->plugins)) {
1792                         $status = "on";
1793                         $action = t("Disable");
1794                 } else {
1795                         $status = "off";
1796                         $action = t("Enable");
1797                 }
1798
1799                 $readme = Null;
1800                 if (is_file("addon/$plugin/README.md")) {
1801                         $readme = file_get_contents("addon/$plugin/README.md");
1802                         $readme = Markdown($readme, false);
1803                 } elseif (is_file("addon/$plugin/README")) {
1804                         $readme = "<pre>" . file_get_contents("addon/$plugin/README") . "</pre>";
1805                 }
1806
1807                 $admin_form = "";
1808                 if (is_array($a->plugins_admin) && in_array($plugin, $a->plugins_admin)) {
1809                         @require_once("addon/$plugin/$plugin.php");
1810                         $func = $plugin . '_plugin_admin';
1811                         $func($a, $admin_form);
1812                 }
1813
1814                 $t = get_markup_template('admin/plugins_details.tpl');
1815
1816                 return replace_macros($t, array(
1817                         '$title' => t('Administration'),
1818                         '$page' => t('Plugins'),
1819                         '$toggle' => t('Toggle'),
1820                         '$settings' => t('Settings'),
1821                         '$baseurl' => System::baseUrl(true),
1822
1823                         '$plugin' => $plugin,
1824                         '$status' => $status,
1825                         '$action' => $action,
1826                         '$info' => get_plugin_info($plugin),
1827                         '$str_author' => t('Author: '),
1828                         '$str_maintainer' => t('Maintainer: '),
1829
1830                         '$admin_form' => $admin_form,
1831                         '$function' => 'plugins',
1832                         '$screenshot' => '',
1833                         '$readme' => $readme,
1834
1835                         '$form_security_token' => get_form_security_token("admin_themes"),
1836                 ));
1837         }
1838
1839         /*
1840          * List plugins
1841          */
1842         if (x($_GET, "a") && $_GET['a'] == "r") {
1843                 check_form_security_token_redirectOnErr(System::baseUrl() . '/admin/plugins', 'admin_themes', 't');
1844                 reload_plugins();
1845                 info("Plugins reloaded");
1846                 goaway(System::baseUrl() . '/admin/plugins');
1847         }
1848
1849         $plugins = array();
1850         $files = glob("addon/*/");
1851         if ($files) {
1852                 foreach ($files as $file) {
1853                         if (is_dir($file)) {
1854                                 list($tmp, $id) = array_map("trim", explode("/", $file));
1855                                 $info = get_plugin_info($id);
1856                                 $show_plugin = true;
1857
1858                                 // If the addon is unsupported, then only show it, when it is enabled
1859                                 if ((strtolower($info["status"]) == "unsupported") && !in_array($id, $a->plugins)) {
1860                                         $show_plugin = false;
1861                                 }
1862
1863                                 // Override the above szenario, when the admin really wants to see outdated stuff
1864                                 if (Config::get("system", "show_unsupported_addons")) {
1865                                         $show_plugin = true;
1866                                 }
1867
1868                                 if ($show_plugin) {
1869                                         $plugins[] = array($id, (in_array($id, $a->plugins) ? "on" : "off"), $info);
1870                                 }
1871                         }
1872                 }
1873         }
1874
1875         $t = get_markup_template('admin/plugins.tpl');
1876         return replace_macros($t, array(
1877                 '$title' => t('Administration'),
1878                 '$page' => t('Plugins'),
1879                 '$submit' => t('Save Settings'),
1880                 '$reload' => t('Reload active plugins'),
1881                 '$baseurl' => System::baseUrl(true),
1882                 '$function' => 'plugins',
1883                 '$plugins' => $plugins,
1884                 '$pcount' => count($plugins),
1885                 '$noplugshint' => sprintf(t('There are currently no plugins available on your node. You can find the official plugin repository at %1$s and might find other interesting plugins in the open plugin registry at %2$s'), 'https://github.com/friendica/friendica-addons', 'http://addons.friendi.ca'),
1886                 '$form_security_token' => get_form_security_token("admin_themes"),
1887         ));
1888 }
1889
1890 /**
1891  * @param array $themes
1892  * @param string $th
1893  * @param int $result
1894  */
1895 function toggle_theme(&$themes, $th, &$result)
1896 {
1897         for ($x = 0; $x < count($themes); $x ++) {
1898                 if ($themes[$x]['name'] === $th) {
1899                         if ($themes[$x]['allowed']) {
1900                                 $themes[$x]['allowed'] = 0;
1901                                 $result = 0;
1902                         } else {
1903                                 $themes[$x]['allowed'] = 1;
1904                                 $result = 1;
1905                         }
1906                 }
1907         }
1908 }
1909
1910 /**
1911  * @param array $themes
1912  * @param string $th
1913  * @return int
1914  */
1915 function theme_status($themes, $th)
1916 {
1917         for ($x = 0; $x < count($themes); $x ++) {
1918                 if ($themes[$x]['name'] === $th) {
1919                         if ($themes[$x]['allowed']) {
1920                                 return 1;
1921                         } else {
1922                                 return 0;
1923                         }
1924                 }
1925         }
1926         return 0;
1927 }
1928
1929 /**
1930  * @param array $themes
1931  * @return string
1932  */
1933 function rebuild_theme_table($themes)
1934 {
1935         $o = '';
1936         if (count($themes)) {
1937                 foreach ($themes as $th) {
1938                         if ($th['allowed']) {
1939                                 if (strlen($o)) {
1940                                         $o .= ',';
1941                                 }
1942                                 $o .= $th['name'];
1943                         }
1944                 }
1945         }
1946         return $o;
1947 }
1948
1949 /**
1950  * @brief Themes admin page
1951  *
1952  * This function generates the admin panel page to control the themes available
1953  * on the friendica node. If the name of a theme is given as parameter a page
1954  * with the details for the theme is shown. Otherwise a list of available
1955  * themes is generated.
1956  *
1957  * The template used for displaying the list of themes and the details of the
1958  * themes are the same as used for the plugins.
1959  *
1960  * The returned string contains the HTML code of the admin panel page.
1961  *
1962  * @param App $a
1963  * @return string
1964  */
1965 function admin_page_themes(App $a)
1966 {
1967         $allowed_themes_str = Config::get('system', 'allowed_themes');
1968         $allowed_themes_raw = explode(',', $allowed_themes_str);
1969         $allowed_themes = array();
1970         if (count($allowed_themes_raw)) {
1971                 foreach ($allowed_themes_raw as $x) {
1972                         if (strlen(trim($x))) {
1973                                 $allowed_themes[] = trim($x);
1974                         }
1975                 }
1976         }
1977
1978         $themes = array();
1979         $files = glob('view/theme/*');
1980         if ($files) {
1981                 foreach ($files as $file) {
1982                         $f = basename($file);
1983
1984                         // Is there a style file?
1985                         $theme_files = glob('view/theme/' . $f . '/style.*');
1986
1987                         // If not then quit
1988                         if (count($theme_files) == 0) {
1989                                 continue;
1990                         }
1991
1992                         $is_experimental = intval(file_exists($file . '/experimental'));
1993                         $is_supported = 1 - (intval(file_exists($file . '/unsupported')));
1994                         $is_allowed = intval(in_array($f, $allowed_themes));
1995
1996                         if ($is_allowed || $is_supported || Config::get("system", "show_unsupported_themes")) {
1997                                 $themes[] = array('name' => $f, 'experimental' => $is_experimental, 'supported' => $is_supported, 'allowed' => $is_allowed);
1998                         }
1999                 }
2000         }
2001
2002         if (!count($themes)) {
2003                 notice(t('No themes found.'));
2004                 return '';
2005         }
2006
2007         /*
2008          * Single theme
2009          */
2010
2011         if ($a->argc == 3) {
2012                 $theme = $a->argv[2];
2013                 if (!is_dir("view/theme/$theme")) {
2014                         notice(t("Item not found."));
2015                         return '';
2016                 }
2017
2018                 if (x($_GET, "a") && $_GET['a'] == "t") {
2019                         check_form_security_token_redirectOnErr('/admin/themes', 'admin_themes', 't');
2020
2021                         // Toggle theme status
2022
2023                         toggle_theme($themes, $theme, $result);
2024                         $s = rebuild_theme_table($themes);
2025                         if ($result) {
2026                                 install_theme($theme);
2027                                 info(sprintf('Theme %s enabled.', $theme));
2028                         } else {
2029                                 uninstall_theme($theme);
2030                                 info(sprintf('Theme %s disabled.', $theme));
2031                         }
2032
2033                         Config::set('system', 'allowed_themes', $s);
2034                         goaway('admin/themes');
2035                         return ''; // NOTREACHED
2036                 }
2037
2038                 // display theme details
2039                 require_once 'library/markdown.php';
2040
2041                 if (theme_status($themes, $theme)) {
2042                         $status = "on";
2043                         $action = t("Disable");
2044                 } else {
2045                         $status = "off";
2046                         $action = t("Enable");
2047                 }
2048
2049                 $readme = Null;
2050                 if (is_file("view/theme/$theme/README.md")) {
2051                         $readme = file_get_contents("view/theme/$theme/README.md");
2052                         $readme = Markdown($readme, false);
2053                 } elseif (is_file("view/theme/$theme/README")) {
2054                         $readme = "<pre>" . file_get_contents("view/theme/$theme/README") . "</pre>";
2055                 }
2056
2057                 $admin_form = "";
2058                 if (is_file("view/theme/$theme/config.php")) {
2059
2060                         function __get_theme_admin_form(App $a, $theme)
2061                         {
2062                                 $orig_theme = $a->theme;
2063                                 $orig_page = $a->page;
2064                                 $orig_session_theme = $_SESSION['theme'];
2065                                 require_once("view/theme/$theme/theme.php");
2066                                 require_once("view/theme/$theme/config.php");
2067                                 $_SESSION['theme'] = $theme;
2068
2069
2070                                 $init = $theme . "_init";
2071                                 if (function_exists($init)) {
2072                                         $init($a);
2073                                 }
2074                                 if (function_exists("theme_admin")) {
2075                                         $admin_form = theme_admin($a);
2076                                 }
2077
2078                                 $_SESSION['theme'] = $orig_session_theme;
2079                                 $a->theme = $orig_theme;
2080                                 $a->page = $orig_page;
2081                                 return $admin_form;
2082                         }
2083                         $admin_form = __get_theme_admin_form($a, $theme);
2084                 }
2085
2086                 $screenshot = array(get_theme_screenshot($theme), t('Screenshot'));
2087                 if (!stristr($screenshot[0], $theme)) {
2088                         $screenshot = null;
2089                 }
2090
2091                 $t = get_markup_template('admin/plugins_details.tpl');
2092                 return replace_macros($t, array(
2093                         '$title' => t('Administration'),
2094                         '$page' => t('Themes'),
2095                         '$toggle' => t('Toggle'),
2096                         '$settings' => t('Settings'),
2097                         '$baseurl' => System::baseUrl(true),
2098                         '$plugin' => $theme,
2099                         '$status' => $status,
2100                         '$action' => $action,
2101                         '$info' => get_theme_info($theme),
2102                         '$function' => 'themes',
2103                         '$admin_form' => $admin_form,
2104                         '$str_author' => t('Author: '),
2105                         '$str_maintainer' => t('Maintainer: '),
2106                         '$screenshot' => $screenshot,
2107                         '$readme' => $readme,
2108
2109                         '$form_security_token' => get_form_security_token("admin_themes"),
2110                 ));
2111         }
2112
2113
2114         // reload active themes
2115         if (x($_GET, "a") && $_GET['a'] == "r") {
2116                 check_form_security_token_redirectOnErr(System::baseUrl() . '/admin/themes', 'admin_themes', 't');
2117                 if ($themes) {
2118                         foreach ($themes as $th) {
2119                                 if ($th['allowed']) {
2120                                         uninstall_theme($th['name']);
2121                                         install_theme($th['name']);
2122                                 }
2123                         }
2124                 }
2125                 info("Themes reloaded");
2126                 goaway(System::baseUrl() . '/admin/themes');
2127         }
2128
2129         /*
2130          * List themes
2131          */
2132
2133         $xthemes = array();
2134         if ($themes) {
2135                 foreach ($themes as $th) {
2136                         $xthemes[] = array($th['name'], (($th['allowed']) ? "on" : "off"), get_theme_info($th['name']));
2137                 }
2138         }
2139
2140         $t = get_markup_template('admin/plugins.tpl');
2141         return replace_macros($t, array(
2142                 '$title'               => t('Administration'),
2143                 '$page'                => t('Themes'),
2144                 '$submit'              => t('Save Settings'),
2145                 '$reload'              => t('Reload active themes'),
2146                 '$baseurl'             => System::baseUrl(true),
2147                 '$function'            => 'themes',
2148                 '$plugins'             => $xthemes,
2149                 '$pcount'              => count($themes),
2150                 '$noplugshint'         => sprintf(t('No themes found on the system. They should be paced in %1$s'),'<code>/view/themes</code>'),
2151                 '$experimental'        => t('[Experimental]'),
2152                 '$unsupported'         => t('[Unsupported]'),
2153                 '$form_security_token' => get_form_security_token("admin_themes"),
2154         ));
2155 }
2156
2157 /**
2158  * @brief Prosesses data send by Logs admin page
2159  *
2160  * @param App $a
2161  */
2162 function admin_page_logs_post(App $a)
2163 {
2164         if (x($_POST, "page_logs")) {
2165                 check_form_security_token_redirectOnErr('/admin/logs', 'admin_logs');
2166
2167                 $logfile   = ((x($_POST,'logfile'))   ? notags(trim($_POST['logfile']))  : '');
2168                 $debugging = ((x($_POST,'debugging')) ? true                             : false);
2169                 $loglevel  = ((x($_POST,'loglevel'))  ? intval(trim($_POST['loglevel'])) : 0);
2170
2171                 Config::set('system', 'logfile', $logfile);
2172                 Config::set('system', 'debugging', $debugging);
2173                 Config::set('system', 'loglevel', $loglevel);
2174         }
2175
2176         info(t("Log settings updated."));
2177         goaway('admin/logs');
2178         return; // NOTREACHED
2179 }
2180
2181 /**
2182  * @brief Generates admin panel subpage for configuration of the logs
2183  *
2184  * This function take the view/templates/admin_logs.tpl file and generates a
2185  * page where admin can configure the logging of friendica.
2186  *
2187  * Displaying the log is separated from the log config as the logfile can get
2188  * big depending on the settings and changing settings regarding the logs can
2189  * thus waste bandwidth.
2190  *
2191  * The string returned contains the content of the template file with replaced
2192  * macros.
2193  *
2194  * @param App $a
2195  * @return string
2196  */
2197 function admin_page_logs(App $a)
2198 {
2199         $log_choices = array(
2200                 LOGGER_NORMAL   => 'Normal',
2201                 LOGGER_TRACE    => 'Trace',
2202                 LOGGER_DEBUG    => 'Debug',
2203                 LOGGER_DATA     => 'Data',
2204                 LOGGER_ALL      => 'All'
2205         );
2206
2207         if (ini_get('log_errors')) {
2208                 $phplogenabled = t('PHP log currently enabled.');
2209         } else {
2210                 $phplogenabled = t('PHP log currently disabled.');
2211         }
2212
2213         $t = get_markup_template('admin/logs.tpl');
2214
2215         return replace_macros($t, array(
2216                 '$title' => t('Administration'),
2217                 '$page' => t('Logs'),
2218                 '$submit' => t('Save Settings'),
2219                 '$clear' => t('Clear'),
2220                 '$baseurl' => System::baseUrl(true),
2221                 '$logname' => Config::get('system', 'logfile'),
2222                 // name, label, value, help string, extra data...
2223                 '$debugging' => array('debugging', t("Enable Debugging"), Config::get('system', 'debugging'), ""),
2224                 '$logfile' => array('logfile', t("Log file"), Config::get('system', 'logfile'), t("Must be writable by web server. Relative to your Friendica top-level directory.")),
2225                 '$loglevel' => array('loglevel', t("Log level"), Config::get('system', 'loglevel'), "", $log_choices),
2226                 '$form_security_token' => get_form_security_token("admin_logs"),
2227                 '$phpheader' => t("PHP logging"),
2228                 '$phphint' => t("To enable logging of PHP errors and warnings you can add the following to the .htconfig.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."),
2229                 '$phplogcode' => "error_reporting(E_ERROR | E_WARNING | E_PARSE);\nini_set('error_log','php.out');\nini_set('log_errors','1');\nini_set('display_errors', '1');",
2230                 '$phplogenabled' => $phplogenabled,
2231         ));
2232 }
2233
2234 /**
2235  * @brief Generates admin panel subpage to view the Friendica log
2236  *
2237  * This function loads the template view/templates/admin_viewlogs.tpl to
2238  * display the systemlog content. The filename for the systemlog of friendica
2239  * is relative to the base directory and taken from the config entry 'logfile'
2240  * in the 'system' category.
2241  *
2242  * Displaying the log is separated from the log config as the logfile can get
2243  * big depending on the settings and changing settings regarding the logs can
2244  * thus waste bandwidth.
2245  *
2246  * The string returned contains the content of the template file with replaced
2247  * macros.
2248  *
2249  * @param App $a
2250  * @return string
2251  */
2252 function admin_page_viewlogs(App $a)
2253 {
2254         $t = get_markup_template('admin/viewlogs.tpl');
2255         $f = Config::get('system', 'logfile');
2256         $data = '';
2257
2258         if (!file_exists($f)) {
2259                 $data = t("Error trying to open <strong>$f</strong> log file.\r\n<br/>Check to see if file $f exist and is readable.");
2260         } else {
2261                 $fp = fopen($f, 'r');
2262                 if (!$fp) {
2263                         $data = t("Couldn't open <strong>$f</strong> log file.\r\n<br/>Check to see if file $f is readable.");
2264                 } else {
2265                         $fstat = fstat($fp);
2266                         $size = $fstat['size'];
2267                         if ($size != 0) {
2268                                 if ($size > 5000000 || $size < 0) {
2269                                         $size = 5000000;
2270                                 }
2271                                 $seek = fseek($fp, 0 - $size, SEEK_END);
2272                                 if ($seek === 0) {
2273                                         $data = escape_tags(fread($fp, $size));
2274                                         while (!feof($fp)) {
2275                                                 $data .= escape_tags(fread($fp, 4096));
2276                                         }
2277                                 }
2278                         }
2279                         fclose($fp);
2280                 }
2281         }
2282         return replace_macros($t, array(
2283                 '$title' => t('Administration'),
2284                 '$page' => t('View Logs'),
2285                 '$data' => $data,
2286                 '$logname' => Config::get('system', 'logfile')
2287         ));
2288 }
2289
2290 /**
2291  * @brief Prosesses data send by the features admin page
2292  *
2293  * @param App $a
2294  */
2295 function admin_page_features_post(App $a)
2296 {
2297         check_form_security_token_redirectOnErr('/admin/features', 'admin_manage_features');
2298
2299         logger('postvars: ' . print_r($_POST, true), LOGGER_DATA);
2300
2301         $arr = array();
2302         $features = Feature::get(false);
2303
2304         foreach ($features as $fname => $fdata) {
2305                 foreach (array_slice($fdata, 1) as $f) {
2306                         $feature = $f[0];
2307                         $feature_state = 'feature_' . $feature;
2308                         $featurelock = 'featurelock_' . $feature;
2309
2310                         if (x($_POST, $feature_state)) {
2311                                 $val = intval($_POST[$feature_state]);
2312                         } else {
2313                                 $val = 0;
2314                         }
2315                         Config::set('feature', $feature, $val);
2316
2317                         if (x($_POST, $featurelock)) {
2318                                 Config::set('feature_lock', $feature, $val);
2319                         } else {
2320                                 Config::delete('feature_lock', $feature);
2321                         }
2322                 }
2323         }
2324
2325         goaway('admin/features');
2326         return; // NOTREACHED
2327 }
2328
2329 /**
2330  * @brief Subpage for global additional feature management
2331  *
2332  * This functin generates the subpage 'Manage Additional Features'
2333  * for the admin panel. At this page the admin can set preferences
2334  * for the user settings of the 'additional features'. If needed this
2335  * preferences can be locked through the admin.
2336  *
2337  * The returned string contains the HTML code of the subpage 'Manage
2338  * Additional Features'
2339  *
2340  * @param App $a
2341  * @return string
2342  */
2343 function admin_page_features(App $a)
2344 {
2345         if ((argc() > 1) && (argv(1) === 'features')) {
2346                 $arr = array();
2347                 $features = Feature::get(false);
2348
2349                 foreach ($features as $fname => $fdata) {
2350                         $arr[$fname] = array();
2351                         $arr[$fname][0] = $fdata[0];
2352                         foreach (array_slice($fdata, 1) as $f) {
2353                                 $set = Config::get('feature', $f[0], $f[3]);
2354                                 $arr[$fname][1][] = array(
2355                                         array('feature_' . $f[0], $f[1], $set, $f[2], array(t('Off'), t('On'))),
2356                                         array('featurelock_' . $f[0], sprintf(t('Lock feature %s'), $f[1]), (($f[4] !== false) ? "1" : ''), '', array(t('Off'), t('On')))
2357                                 );
2358                         }
2359                 }
2360
2361                 $tpl = get_markup_template('admin/settings_features.tpl');
2362                 $o .= replace_macros($tpl, array(
2363                         '$form_security_token' => get_form_security_token("admin_manage_features"),
2364                         '$title' => t('Manage Additional Features'),
2365                         '$features' => $arr,
2366                         '$submit' => t('Save Settings'),
2367                 ));
2368
2369                 return $o;
2370         }
2371 }