class_servicesManagement.inc 18.6 KB
Newer Older
1
2
3
<?php
/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
4

5
  Copyright (C) 2003  Cajus Pollmeier
6
  Copyright (C) 2011-2019  FusionDirectory
7
8
9
10
11
12
13
14
15
16
17
18
19

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
20
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
21
22
*/

23
class servicesManagement extends management implements SimpleTab
24
{
25
  static public $actionStatus = [
26
27
28
29
    'start'   => 'running',
    'stop'    => 'stopped',
    'restart' => 'running',
    'status'  => '',
30
  ];
31

32
  protected $skipCpHandler     = TRUE;
33

34
35
  public static $skipSnapshots = TRUE;

36
37
  public static $skipTemplates = TRUE;

38
39
  protected $skipConfiguration = TRUE;

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  /* Vars needed for simpleTabs */
  public $is_account      = TRUE;
  public $ignore_account  = TRUE;
  public $dn;
  public $is_template     = FALSE;
  public $parent          = NULL;
  public $acl_base        = '';
  public $acl_category    = '';

  protected $plugins      = [];
  protected $current      = '';
  protected $dialog       = FALSE;
  protected $backup       = NULL;

  var $read_only      = FALSE;
  var $acl;
  var $cn;

  /* Default columns */
  public static $columns = [
60
61
    ['Column',        ['attributes' => 'ServiceStatusColumn', 'label' => '!']],
    ['LinkColumn',    ['attributes' => 'Message',             'label' => 'Description']],
62
63
    ['ActionsColumn', ['label' => 'Actions']],
  ];
64

65
  static function plInfo (): array
Benoit Mortier's avatar
Benoit Mortier committed
66
  {
67
    return [
68
69
      'plShortName'   => _('Services'),
      'plDescription' => _('Server services'),
70
      'plIcon'        => '',
71
      'plObjectType'  => ['server'],
72
      'plPrority'     => 5,
73
      'plSubTabs'     => 'SERVERSERVICE',
Benoit Mortier's avatar
Benoit Mortier committed
74

75
76
      'plProvidedAcls'  => []
    ];
Benoit Mortier's avatar
Benoit Mortier committed
77
78
  }

79
  function __construct (string $dn, $object, $parent = NULL)
80
  {
81
    global $config;
82

83
    $this->dn     = $dn;
84
85
    if (isset($object->attrs)) {
      $this->attrs = $object->attrs;
86
    }
87
    $this->parent = $parent;
88

89
90
91
    /* Initialize acl base */
    $this->set_acl_base($this->dn);

92
    // Initialize list of used and useable services.
93
94
95
    foreach ($config->data['TABS']['SERVERSERVICE'] as $plug) {
      if (class_available($plug['CLASS'])) {
        $name = $plug['CLASS'];
96

97
        $this->plugins[$name] = new $name($dn, $this);
98
99
100
      }
    }

101
102
103
    uasort($this->plugins,
      function($a, $b)
      {
104
        return strcmp($a->DisplayName ?? get_class($a), $b->DisplayName ?? get_class($b));
105
106
107
      }
    );

108
    parent::__construct([]);
109
110
  }

111
  protected function setUpListing ()
112
  {
113
114
115
    /* Set baseMode to FALSE */
    $this->listing  = new managementListing($this, FALSE);
  }
116

117
118
119
120
121
122
123
  protected function setUpFilter ()
  {
    $this->filter   = new servicesManagementFilter($this);
  }

  protected function configureActions ()
  {
124
    $this->updateActionMenu();
125

126
    $this->registerAction(
127
      new ServiceAction(
128
129
        'edit', _('Edit service'), 'geticon.php?context=actions&icon=document-edit&size=16',
        '1', 'editEntry',
130
        ['w'],
131
132
133
134
        FALSE, TRUE
      )
    );

135
    $this->registerAction(
136
      new ServiceAction(
137
138
        'remove', _('Remove'), 'geticon.php?context=actions&icon=edit-delete&size=16',
        '+', 'removeService',
139
        ['d']
140
141
142
143
144
145
146
147
      )
    );

    $this->actions['remove']->setSeparator(TRUE);

    // Add export actions
    $exportMenu = [];
    foreach ($this->exporters as $action => $exporter) {
148
      $exportMenu[] = new ServiceAction(
149
150
151
152
153
154
155
156
157
158
159
160
161
162
        $action, $exporter['label'], $exporter['image'],
        '0', 'export'
      );
    }
    $this->registerAction(
      new SubMenuAction(
        'export', _('Export list'), 'geticon.php?context=actions&icon=document-export&size=16',
        $exportMenu
      )
    );

    $this->actions['export']->setSeparator(TRUE);

    $this->registerAction(
163
      new ServiceAction(
164
165
        'status', _('Get status'), 'geticon.php?context=actions&icon=view-refresh&size=16',
        '1', 'updateServiceStatus',
166
        ['simpleServiceStatus:w'],
167
168
169
170
171
        FALSE, TRUE
      )
    );

    $this->registerAction(
172
      new ServiceAction(
173
174
        'start', _('Start'), 'geticon.php?context=actions&icon=task-start&size=16',
        '1', 'updateServiceStatus',
175
        ['simpleServiceStart:w'],
176
177
178
179
180
        FALSE, TRUE
      )
    );

    $this->registerAction(
181
      new ServiceAction(
182
183
        'stop', _('Stop'), 'geticon.php?context=actions&icon=task-stop&size=16',
        '1', 'updateServiceStatus',
184
        ['simpleServiceStop:w'],
185
186
187
188
189
        FALSE, TRUE
      )
    );

    $this->registerAction(
190
      new ServiceAction(
191
192
        'restart', _('Restart'), 'geticon.php?context=actions&icon=view-refresh&size=16',
        '1', 'updateServiceStatus',
193
        ['simpleServiceRestart:w'],
194
195
196
197
198
199
        FALSE, TRUE
      )
    );

    $this->registerAction(new HiddenAction('saveService',   'saveService'));
    $this->registerAction(new HiddenAction('cancelService', 'cancelEdit'));
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224

    $this->actions['status']->setEnableFunction([$this, 'enableServiceAction']);
    $this->actions['start']->setEnableFunction([$this, 'enableServiceAction']);
    $this->actions['stop']->setEnableFunction([$this, 'enableServiceAction']);
    $this->actions['restart']->setEnableFunction([$this, 'enableServiceAction']);
  }

  public function enableServiceAction ($action, ListingEntry $entry = NULL): bool
  {
    if ($entry === NULL) {
      return FALSE;
    } else {
      switch ($action) {
        case 'status':
          return $entry['AllowStatus'][0];
        case 'start':
          return $entry['AllowStart'][0];
        case 'stop':
          return $entry['AllowStop'][0];
        case 'restart':
          return $entry['AllowRestart'][0];
        default:
          return FALSE;
      }
    }
225
226
  }

227
  protected function updateActionMenu ()
228
  {
229
    $category = (empty($this->acl_category) ? 'server' : $this->acl_category);
230
    $createMenu   = [];
231
    $available_services = $this->getAllUnusedServices();
232
233
    foreach ($available_services as $class => $label) {
      $infos = pluglist::pluginInfos($class);
234
235
236
      $createMenu[] = new Action(
        'new_'.$class, $label, ($infos['plIcon'] ?? 'geticon.php?context=actions&icon=document-new&size=16'),
        '0', 'newService',
237
        [$category.'/'.$class.'/c']
238
      );
239
    }
240
241
242
243
244
245
    $this->registerAction(
      new SubMenuAction(
        'new', _('Create'), 'geticon.php?context=actions&icon=document-new&size=16',
        $createMenu
      )
    );
246
247
248
  }

  /*! \brief    Filter extra POST and GET variables for this plugin.
249
   */
250
  function detectPostActions (): array
251
  {
252
    $action = parent::detectPostActions();
253
    if (isset($_POST['SaveService'])) {
254
      $action['action'] = 'saveService';
255
    } elseif (isset($_POST['CancelService'])) {
256
      $action['action'] = 'cancelService';
257
258
    }
    return $action;
259
260
261
262
263
  }

  /*! \brief  Edit an existing service here.
   *          Somebody clicked on the paper and pencil icon.
   */
264
  function editEntry (array $action)
265
  {
266
    $s_entry = array_pop($action['targets']);
267
268
269
    if (!isset($this->plugins[$s_entry])) {
      throw new FusionDirectoryException('No service "'.$s_entry.'" in '.json_encode(array_keys($this->plugins)));
    }
270
    if ($this->plugins[$s_entry]->acl_is_readable('')) {
271
272
      $this->backup       = get_object_vars($this->plugins[$s_entry]);
      $this->dialogObject = $this->plugins[$s_entry];
273
      $this->dialog       = TRUE;
274
      $this->current      = $s_entry;
275
276
277
    }
  }

278
  /*! \brief  Editing an object was canceled.
279
280
   *          Close dialogs/tabs and remove locks.
   */
281
  function cancelEdit ()
282
  {
283
    if (($this->backup == NULL) && $this->current) {
284
      $this->plugins[$this->current] = new $this->current($this->dn, $this);
285
      $this->plugins[$this->current]->set_acl_base($this->acl_base);
286
      $this->plugins[$this->current]->set_acl_category(preg_replace('/\/$/', '', $this->acl_category));
287
288
    } elseif (is_array($this->backup)) {
      foreach ($this->backup as $name => $value) {
289
290
291
292
        $this->plugins[$this->current]->$name = $value;
      }
    }
    $this->backup   = NULL;
293
    $this->current  = '';
294
295
296
    $this->closeDialogs();
  }

297
  /*! \brief  Let the user create a new service
298
   */
299
  function newService (array $action)
300
301
  {
    $this->closeDialogs();
302
303
304
305
    $serv = $action['subaction'];
    if (!isset($this->plugins[$serv])) {
      throw new FusionDirectoryException('Unknown service '.$serv);
    }
306
307
308
309
    $this->plugins[$serv]->is_account = TRUE;
    $this->dialogObject               = $this->plugins[$serv];
    $this->current                    = $serv;
    $this->dialog                     = TRUE;
310
311
312
313
  }

  /*! \brief  Save the currently edited service.
   */
314
  function saveService (array $action)
315
316
317
  {
    $this->dialogObject->save_object();
    $msgs = $this->dialogObject->check();
318
319
    if (count($msgs)) {
      foreach ($msgs as $msg) {
320
        msg_dialog::display(_('Error'), $msg, ERROR_DIALOG);
321
      }
322
    } else {
323
324
325
      $this->plugins[$this->current] = $this->dialogObject;
      $this->closeDialogs();
      $this->backup = NULL;
326
      $this->updateActionMenu();
327
328
329
    }
  }

330
  /*!\brief   Close all opened dialogs
331
332
   *          And reset "dialog open" flags to display bottom buttons again.
   */
333
  function closeDialogs ()
334
  {
335
    parent::closeDialogs();
336
    $this->dialog = FALSE;
337
    set_object_info($this->dn);
338
339
340
341
  }

  /*! \brief    Remove the selected service(s)
   */
342
  function removeService (array $action)
343
  {
344
    foreach ($action['targets'] as $s_entry) {
345
      $new_obj = new $s_entry($this->dn, $this);
346
      $new_obj->set_acl_base($this->acl_base);
347
      $new_obj->set_acl_category(preg_replace('/\/$/', '', $this->acl_category));
348
      $tmp = $new_obj->getListEntry();
349

350
      /* Check if we are allowed to remove this service */
351
      if ($tmp['AllowRemove']) {
352
353
        $this->plugins[$s_entry]              = $new_obj;
        $this->plugins[$s_entry]->is_account  = FALSE;
354
355
356
357
      }
    }
  }

358
359
  function updateServicesVars ($service)
  {
360
    foreach (['cn','dn'] as $var) {
361
362
363
364
      if (isset($this->$var)) {
        $this->plugins[$service]->$var = $this->$var;
      }
    }
365
366
367
368
  }

  /*! \brief    Updates the status for a list of services.
   */
369
  function updateServiceStatus (array $action)
370
  {
371
    /* Skip if this is a new server */
372
373
    if ($this->dn == 'new') {
      msg_dialog::display(_('Information'), _('Cannot update service status until it has been saved!'), INFO_DIALOG);
374
375
376
377
      return;
    }

    /* Is this an existing action */
378
379
    if (!isset(static::$actionStatus[$action['action']])) {
      msg_dialog::display(_('Error'), sprintf(_('Unknown action "%s"'), $action['action']), ERROR_DIALOG);
380
381
382
      return;
    }

383
    /* Handle state changes for services */
384
385
    foreach ($action['targets'] as $service) {
      $this->updateSingleServiceStatus($action['action'], $service);
386
387
388
389
390
391
    }
  }

  /*! \brief    Updates the status of a service and
   *             calls an external hook if specified in fusiondirectory.conf
   */
392
  private function updateSingleServiceStatus (string $action, string $service)
393
394
395
396
397
398
399
  {
    if ($this->plugins[$service]->is_account) {
      $this->updateServicesVars($service);

      $s_daemon = new supportDaemon();
      if ($s_daemon->is_error()) {
        msg_dialog::display(
400
          sprintf(_('Could not get execute action %s on service %s.'), $action, $service),
401
402
403
          msgPool::siError($s_daemon->get_error()), ERROR_DIALOG
        );
      } else {
404
        $target = $this->parent->getBaseObject()->macAddress;
405
406
407
408
409
410
411
        if (empty($target)) {
          msg_dialog::display(
            sprintf(_('Could not get execute action %s on service %s.'), $action, $service),
            _('This server has no mac address.'), ERROR_DIALOG
          );
          return;
        }
412
413
414
        if (is_array($target)) {
          $target = $target[0];
        }
415
        if ($action == 'status') {
416
          $res = $s_daemon->append_call('Service.is_running', $target, ['args' => [$service]]);
417

418
419
          if ($s_daemon->is_error()) {
            msg_dialog::display(
420
              sprintf(_('Could not get execute action %s on service %s.'), $action, $service),
421
422
423
              msgPool::siError($s_daemon->get_error()), ERROR_DIALOG
            );
          } else {
424
            $this->plugins[$service]->setStatus($res == 'yes' ? 'running' : 'stopped');
425
426
          }
        } else {
427
          $res = $s_daemon->append_call('Service.manage', $target, ['args' => [$service, $action]]);
428
429
430

          if ($s_daemon->is_error()) {
            msg_dialog::display(
431
              sprintf(_('Could not get execute action %s on service %s.'), $action, $service),
432
433
              msgPool::siError($s_daemon->get_error()), ERROR_DIALOG
            );
434
          } elseif (preg_match('/^done/', $res)) {
435
            $this->plugins[$service]->setStatus(static::$actionStatus[$action]);
436
          }
437
        }
438
        return $res;
439
440
441
442
443
      }
    }
  }

  /*! \brief   Returns a list of all used services
444
   *            CLASSNAME => _($this->plugins[*]->DisplayName);
445
   */
446
  function getAllUsedServices (): array
447
  {
448
    $ret = [];
449
450
    foreach ($this->plugins as $name => $obj) {
      if ($obj->is_account) {
451
        $ret[$name] = ($obj->DisplayName ?? $name);
452
453
      }
    }
454
    return $ret;
455
456
457
458
  }

  /*! \brief    Returns a list of all unused services.
   */
459
  function getAllUnusedServices (): array
460
461
  {
    $tmp = $this->getAllUsedServices();
462
    $pool_of_ocs = [];
463
    foreach ($tmp as $name => $value) {
464
      $pool_of_ocs[] = get_class($this->plugins[$name]);
465
      if (isset($this->plugins[$name]->conflicts)) {
466
        $pool_of_ocs = array_merge($pool_of_ocs, $this->plugins[$name]->conflicts);
467
468
469
      }
    }

470
    $ret = [];
471
    foreach ($this->plugins as $name => $obj) {
472
473
474
      if (!$obj->acl_is_createable()) {
        continue;
      }
475
476

      /* Skip all pluigns that will lead into conflicts */
477
      $conflicts = ($obj->conflicts ?? []);
478
      $conflicts[] = get_class($obj);
479
      if (count(array_uintersect($conflicts, $pool_of_ocs, 'strcasecmp'))) {
480
        continue;
481
482
      }

483
      $ret[$name] = ($obj->DisplayName ?? $name);
484
    }
485
    return $ret;
486
487
  }

488
489
490
491
492
493
494
495
496
  /*! \brief    Returns the services list.
   *
   * Used in the filter class for services class_servicesManagementFilter.inc
   */
  public function getServiceList ()
  {
    return $this->plugins;
  }

497
498
499
500
501
502
503
504
505
506
  /*! \brief Returns service if activated, FALSE otherwise */
  public function getServiceObject (string $tab)
  {
    if (isset($this->plugins[$tab]) && $this->plugins[$tab]->is_account) {
      return $this->plugins[$tab];
    } else {
      return FALSE;
    }
  }

507
508
  /*! \brief    No checks here.
   */
509
  public function check (): array
510
  {
511
    return [];
512
513
514
515
  }

  /*! \brief    Keep posted form values in opened dialogs
   */
516
  function save_object ()
517
  {
518
    // save_object of the dialog is called in management::execute
519
520
  }

521
  /*! \brief Remove all active services
522
   */
523
  public function remove (bool $fulldelete = FALSE): array
524
  {
525
    $errors = [];
526
527
528
    foreach ($this->plugins as $name => $obj) {
      $this->updateServicesVars($name);
      if ($this->plugins[$name]->initially_was_account) {
529
        $result = $this->plugins[$name]->remove($fulldelete);
530
531
532
        if (!empty($result)) {
          $errors = array_merge($errors, $result);
        }
533
534
      }
    }
535
    return $errors;
536
537
538
539
  }

  /*! \brief    Save all active services
   */
540
  public function save (): array
541
  {
542
    $errors = [];
543
544
    foreach ($this->plugins as $name => $obj) {
      $this->updateServicesVars($name);
545

546
      if ($this->plugins[$name]->is_account) {
547
        $result = $this->plugins[$name]->save();
548
      } elseif ($this->plugins[$name]->initially_was_account) {
549
        $result = $this->plugins[$name]->remove(FALSE);
550
551
552
      }
      if (!empty($result)) {
        $errors = array_merge($errors, $result);
553
554
      }
    }
555
    return $errors;
556
557
  }

558
559
  /*! \brief    Prepare active services to be copied.
   */
560
  public function resetCopyInfos ()
561
562
  {
    $this->dn = 'new';
563
564
    foreach ($this->plugins as $obj) {
      $obj->resetCopyInfos();
565
566
567
    }
  }

568
  /*! \brief    Forward plugin acls
569
   */
570
  public function set_acl_base (string $base)
571
572
  {
    $this->acl_base = $base;
573
574
    foreach ($this->plugins as $obj) {
      $obj->set_acl_base($base);
575
576
577
    }
  }

578
  /*! \brief    Forward plugin acls
579
   */
580
  public function set_acl_category (string $category)
581
582
  {
    $this->acl_category = $category;
583
584
    foreach ($this->plugins as $obj) {
      $obj->set_acl_category($category);
585
586
    }
  }
587

588
  public function setTemplate (bool $bool)
589
590
591
592
593
594
  {
    foreach ($this->plugins as &$plugin) {
      $plugin->setTemplate($bool);
    }
    unset($plugin);
  }
595

596
  public function getRequiredAttributes (): array
597
  {
598
    return [];
599
600
  }

601
  public function adapt_from_template (array $attrs, array $skip = [])
602
  {
603
604
    foreach ($this->plugins as $obj) {
      $obj->adapt_from_template($attrs, $skip);
605
606
    }
  }
607

608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
  /*!
   * \brief Can we delete the object
   *
   * Only used on main tab
   *
   * \param string $base
   */
  public function acl_is_removeable (string $base = NULL): bool
  {
    foreach ($this->plugins as $name => $obj) {
      $this->updateServicesVars($name);
      if ($obj->initially_was_account && !$obj->acl_is_removeable($base)) {
        return FALSE;
      }
    }
    return TRUE;
  }

  /*!
   * \brief Sets whether the opened objet has an edit button
   *
   * \param bool $needEditMode
   */
  public function setNeedEditMode (bool $needEditMode)
  {
  }

  /*!
   * \brief Is there a modal dialog opened
   */
  public function is_modal_dialog (): bool
  {
    return $this->dialogOpened();
  }

  /*!
   * \brief Returns TRUE if this attribute should be asked in the creation by template dialog
645
   */
646
  public function showInTemplate (string $attr, array $templateAttrs): bool
647
  {
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
    return FALSE;
  }

  /*!
   * \brief Deserialize values
   */
  public function deserializeValues (array $values, bool $checkAcl = TRUE)
  {
  }

  /*!
   * \brief Get the acl permissions for an attribute or the plugin itself
   */
  public function aclGetPermissions ($attribute = '0', string $base = NULL, bool $skipWrite = FALSE): string
  {
    return '';
  }

  /*!
   * \brief Merge in objectClasses needed by this tab
   *
   *  Used by prepare_save and template::apply
   */
  public function mergeObjectClasses (array $oc): array
  {
    foreach ($this->plugins as $plugin) {
      if ($plugin->is_account || $plugin->ignore_account) {
        $oc = $plugin->mergeObjectClasses($oc);
      }
    }
    return $oc;
679
  }
680
}