1 <?php
4 /*! \brief The opsi client base class.
5 This class can be implemented in tow different ways:
6 * as standalone opsi client
7 * as part of the samba tabs
8 both types will be detected automatically.
10 This class allows to edit the properties of an opsi client
11 and its products.
12 */
13 class opsiGeneric extends plugin
14 {
15 /* Contains a list of all available netboot products
16 */
17 private $a_availableNetbootProducts = array();
18 private $a_initial_availableNetbootProducts = array();
19 private $s_selectedNetbootProduct = "";
20 private $s_initial_selectedNetbootProduct = "";
22 /* Contains a list of all available local products
23 */
24 private $a_availableLocalProducts = array();
25 private $a_selectedLocalProducts = array();
26 private $a_initial_selectedLocalProducts = array();
28 /* Internal veriables
29 */
30 private $opsi; // The opsi handle
31 public $parent = NULL; // The parent object (in case of samba)
33 private $hostId = ""; // The host Id of the currently edit opsi host
34 public $mac = ""; // The hosts mac address
35 public $note = ""; // A note
36 public $description = ""; // The client description
38 public $initial_mac = "";
39 public $initial_note = "";
40 public $initial_description = "";
42 private $init_failed = FALSE; // Is true if the opsi communication failed
43 private $parent_mode = TRUE; // Is true if this is a standlone plugin. (Not samba)
44 private $is_installed= FALSE; // Is true is the hast is already installed.
46 public $attributes = array("mac","note","description");
49 /*! \brief Initialize this class
50 @param Object The GOsa base config.
51 @param String The Id of the host that we want to edit.
52 @param Object The parent object. (in case of samba)
53 */
54 public function __construct($config,$hostId,&$parent = NULL)
55 {
56 /* Create opsi handle
57 */
58 $this->opsi = new opsi($config);
60 /* Check if we are are part of a windows workstation
61 */
62 $this->parent = $parent;
63 if($parent instanceof wingeneric){
64 $this->parent_mode = FALSE;
65 }
67 /* Get hostId
68 */
69 if($hostId != "new"){
70 if(preg_match("/^opsi:/",$hostId)){
71 $this->hostId = preg_replace("/^opsi:=([^,]*),.*$/","\\1",$hostId);
72 }elseif($this->parent instanceof wingeneric){
73 $this->hostId = $this->parent->cn;
74 $this->hostId = preg_replace('/\$$/',"",$this->hostId);
75 }
76 }
78 /* Try to plugin */
79 $this->init();
80 }
83 /*! \brief Try to load opsi client informations from the
84 gosa support daemon.
85 */
86 private function init()
87 {
88 $err = FALSE;
89 $this->init_failed = FALSE;
90 $this->initially_was_account = FALSE;
92 /* Try to load client infos from the gosa support daemon
93 */
94 if(! ($this->hostId == "new" || !empty($this->mac) )){
95 $list = $this->opsi->list_clients($this->hostId);
96 $err |= $this->opsi->is_error();
98 /* Walk through all returned opsi clients and try to detect
99 one that matches our hostId.
100 #FIXME Implement an opsi method which returns infos for only one opsi client, not all.
101 */
102 foreach($list as $entry){
103 if(preg_match("/^".normalizePreg($this->hostId)."$/i",$entry['NAME'][0]['VALUE'])){
104 $this->initially_was_account = TRUE;
105 foreach(array(
106 "is_installed" => "LASTSEEN",
107 "description" => "DESCRIPTION",
108 "mac" => "MAC",
109 "note" => "NOTES") as $des => $src){
110 $des2 = "initial_".$des;
111 $this->$des2 = $this->$des = $entry[$src][0]['VALUE'];
112 }
113 break;
114 }
115 }
116 }
118 /* Read informations about available netboot products.
119 If not already done, before.
120 */
121 if(!$err && !count($this->a_availableNetbootProducts)){
122 $this->a_availableNetbootProducts = $this->opsi->get_netboot_products();
123 ksort($this->a_availableNetbootProducts);
124 $err |= $this->opsi->is_error();
125 }
127 /* Read informations about available netboot products.
128 If not already done, before.
129 */
130 if(!$err && !count($this->a_availableLocalProducts)) {
131 $this->a_availableLocalProducts = $this->opsi->get_local_products();
132 ksort($this->a_availableLocalProducts);
133 $err |= $this->opsi->is_error();
134 }
136 /* Get products selected by this host.
137 */
138 if(!$err && !empty($this->hostId)) {
139 $tmp = array_keys($this->opsi->get_netboot_products($this->hostId));
140 $err |= $this->opsi->is_error();
141 if(count($tmp) && !$err && !isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
142 $this->s_selectedNetbootProduct = $tmp[0];
144 /* Read configuration for "Netboot Products" */
145 if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct])){
146 $CFG = $this->opsi->get_product_properties($this->s_selectedNetbootProduct,$this->hostId);
147 $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'] = $CFG;
148 }
149 }
150 $err |= $this->opsi->is_error();
151 }
153 /* Get all selected local products
154 */
155 if(!$err && !empty($this->hostId) && !count($this->a_selectedLocalProducts)) {
156 $tmp = $this->opsi->get_local_products($this->hostId);
157 $err |= $this->opsi->is_error();
158 $this->a_selectedLocalProducts = $tmp;
159 }
161 /* Load product configuration for all already selected products.
162 */
163 if(!$err && !empty($this->hostId)) {
164 foreach($this->a_selectedLocalProducts as $name => $data){
165 if(!$err && !isset($this->a_selectedLocalProducts[$name]['CFG'])){
166 $CFG = $this->opsi->get_product_properties($name,$this->hostId);
167 $err |= $this->opsi->is_error();
168 $this->a_selectedLocalProducts[$name]['CFG'] = $CFG;
169 }
170 }
171 }
173 /* Check if everything went fine else reset everything and display a retry button
174 */
175 if($err){
176 $this->init_failed = TRUE;
178 }else{
180 /* Remember initial settings */
181 $this->is_account = $this->initially_was_account;
182 $this->a_initial_selectedLocalProducts = $this->a_selectedLocalProducts;
183 $this->s_initial_selectedNetbootProduct = $this->s_selectedNetbootProduct;
184 $this->a_initial_availableNetbootProducts = $this->a_availableNetbootProducts;
186 /* Ensure that a valid netboot is selected product is.
187 */
188 if(empty($this->s_selectedNetbootProduct)){
189 $this->s_selectedNetbootProduct = key($this->a_availableNetbootProducts);
190 echo $this->s_selectedNetbootProduct;
191 }
193 }
194 }
197 /*! \brief Check given data.
198 @return Array Returns an array with all issues.
199 */
200 public function check()
201 {
202 return(array());
203 $messages = plugin::check();
205 if(empty($this->hostId)){
206 $messages[] = msgPool::required(_("Name"));
207 }elseif(!preg_match("/\./",$this->hostId)){
209 /* The hostId must contain a domain part
210 */
211 $messages[] = msgPool::invalid(_("Name"),$this->hostId,"",
212 _("The client name must contain a domain part (e.g. '.company.de')."));
213 }elseif(preg_match("/[^a-z0-9\.\-_]/",$this->hostId)){
214 $messages[] = msgPool::invalid(_("Name"),$this->hostId,"/[a-z0-9\.\-_]/");
215 }
217 /* Ensure that the mac address is valid
218 */
219 if(!tests::is_mac($this->mac) || empty($this->mac)){
220 $messages[] = msgPool::invalid(_("MAC address"),$this->mac,"","00:0C:7F:31:33:F1");
221 }
222 return($messages);
223 }
226 /*! \brief Create the html ui of this plugin
227 @return String HTML content.
228 */
229 public function execute()
230 {
231 $display ="";
233 /* The pluign initialization failed due to communication problems with the gosa daemon.
234 A retry button will be displayed here.
235 */
236 if($this->init_failed){
237 $smarty = get_smarty();
238 $smarty->assign("init_failed",TRUE);
239 $smarty->assign("message",$this->opsi->get_error());
240 return($smarty->fetch(get_template_path("generic.tpl",TRUE,dirname(__FILE__))));
241 }
243 /* If we are not a stand alone opsi client, we must be a samba client
244 which has the opsi tab enabled.
245 Check if the opsi is added or removed and display state buttons.
246 */
247 if(!$this->parent_mode){
248 if(isset($_POST['modify_state'])){
249 if($this->is_account){
250 $this->is_account= FALSE;
251 }elseif(!$this->is_account){
252 $this->is_account= TRUE;
253 }
254 }
255 if($this->is_account){
256 $display = $this->show_disable_header(msgPool::removeFeaturesButton(_("Opsi")),
257 msgPool::featuresEnabled(_("Opsi")));
258 }else{
259 $display = $this->show_enable_header(msgPool::addFeaturesButton(_("Opsi")),
260 msgPool::featuresDisabled(_("Opsi")));
261 return($display);
262 }
263 }
265 /* Check if we have a sub dialog opened
266 */
267 if(is_object($this->dialog)){
268 $this->dialog->save_object();
269 return($this->dialog->execute());
270 }
272 /* Create HTML output of this plugin
273 */
274 $smarty = get_smarty();
275 foreach($this->attributes as $attr){
276 $smarty->assign($attr,$this->$attr);
277 }
279 $smarty->assign("parent_mode", $this->parent_mode);
280 $smarty->assign("is_installed", $this->is_installed);
281 $smarty->assign("init_failed",FALSE);
282 $divSLP = new divSelectBox();
283 $divALP = new divSelectBox();
285 /* Create list of available local products
286 */
287 foreach($this->a_availableLocalProducts as $name => $data){
288 if(isset($this->a_selectedLocalProducts[$name])) continue;
290 $add_tab = array("string" => "<input type='image' src='images/back.png' name='add_lp_".$name."'>");
291 $name_tab = array("string" => $name);
292 $desc_tab = array("string" => "<div style='height: 14px;overflow:hidden;'>".$data['DESC']."</div>",
293 "attach" => "title='".$data['DESC']."' style='border-right:0px;'");
294 $divALP->AddEntry(array($add_tab,$name_tab,$desc_tab));
295 }
297 /* Create list of selected local products
298 */
299 ksort($this->a_selectedLocalProducts);
300 foreach($this->a_selectedLocalProducts as $name => $data){
302 $name_tab = array("string" => $name);
303 $desc_tab = array(
304 "string" => "<div style='height: 14px;overflow:hidden;'>".$data['DESC']."</div>",
305 "attach" => "title='".$data['DESC']."'");
307 /* Only display edit button, if there is something to edit
308 */
309 $edit = "<img src='images/empty.png' alt=' '>";
310 if(count($data['CFG'])){
311 $edit = "<input type='image' src='images/lists/edit.png' name='edit_lp_".$name."'>";
312 }
313 $del = "<input type='image' src='images/lists/trash.png' name='del_lp_".$name."'>";
315 $opt_tab = array("string" => $edit.$del,
316 "attach" => "style='border-right:0px; width: 40px; text-align:right;'");
317 $divSLP->AddEntry(array($name_tab,$desc_tab,$opt_tab));
318 }
320 /* Check if netboot product is configurable
321 */
322 $cfg_able =FALSE;
323 if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
324 $cfg_able = count($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG']);
325 }
327 $smarty->assign("netboot_configurable",$cfg_able);
328 $smarty->assign("hostId", $this->hostId);
329 $smarty->assign("divSLP", $divSLP->DrawList());
330 $smarty->assign("divALP", $divALP->DrawList());
331 $smarty->assign("SNP", $this->s_selectedNetbootProduct);
332 $smarty->assign("ANP", $this->a_availableNetbootProducts);
333 return($display.$smarty->fetch(get_template_path("generic.tpl",TRUE,dirname(__FILE__))));
334 }
337 /*! \brief Save modifications using the gosa support daemon.
338 */
339 public function save()
340 {
342 /* Check if we have to create a new opsi client
343 or just have to save client modifications.
344 */
345 if(!$this->initially_was_account && $this->is_account){
346 $res = $this->opsi->add_client($this->hostId,$this->mac,$this->note,$this->description);
347 if($this->opsi->is_error()){
348 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
349 return;
350 }
351 }else{
353 /* Update client modifcations.
354 -Only if necessary
355 */
356 if($this->note != $this->initial_note ||
357 $this->description != $this->initial_description ||
358 $this->mac != $this->initial_mac){
359 $this->opsi->modify_client($this->hostId,$this->mac,$this->note,$this->description);
360 if($this->opsi->is_error()){
361 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
362 return;
363 }
364 }
365 }
368 /***********
369 Detect local netboot product changes
370 - Check which products were removed.
371 - Check which products were added.
372 ***********/
375 /* Detect which products were removed an which added.
376 */
377 $del = array_diff_assoc($this->a_initial_selectedLocalProducts,$this->a_selectedLocalProducts);
378 $add = array_diff_assoc($this->a_selectedLocalProducts,$this->a_initial_selectedLocalProducts);
380 /* Remove products from client
381 */
382 foreach($del as $name => $data){
383 $this->opsi->del_product_from_client($name,$this->hostId);
384 if($this->opsi->is_error()){
385 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
386 return;
387 }
388 }
390 /* Add products to client
391 And set the product properties.
392 */
393 foreach($add as $name => $data){
394 $this->opsi->add_product_to_client($name,$this->hostId);
395 if($this->opsi->is_error()){
396 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
397 return;
398 }
399 if(!empty($data['CFG'])){
400 $this->opsi->set_product_properties($name,$data['CFG'],$this->hostId);
401 if($this->opsi->is_error()){
402 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
403 return;
404 }
405 }
406 }
408 /* Save local product properties
409 */
410 foreach($this->a_selectedLocalProducts as $name => $data){
411 if(isset($del[$name]) || isset($add[$name])) continue;
413 /* Update product properties if there are changes
414 */
415 $diffs = $this->get_config_changes($data['CFG'],$this->a_initial_selectedLocalProducts[$name]['CFG']);
416 if(count($diffs)){
417 $this->opsi->set_product_properties($name,$diffs,$this->hostId);
418 if($this->opsi->is_error()){
419 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
420 return;
421 }
422 }
423 }
425 /*********
426 Detect Netboot product changes
427 - Check if another netboot product was selected.
428 - Check if the product properties were changes.
429 *********/
431 /* Update used netboot product.
432 */
433 if($this->s_selectedNetbootProduct != $this->s_initial_selectedNetbootProduct){
434 if(!empty($this->s_initial_selectedNetbootProduct)){
435 $this->opsi->del_product_from_client($this->s_initial_selectedNetbootProduct,$this->hostId);
436 if($this->opsi->is_error()){
437 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
438 return;
439 }
440 }
441 $this->opsi->add_product_to_client($this->s_selectedNetbootProduct,$this->hostId);
442 if($this->opsi->is_error()){
443 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
444 return;
445 }
446 }
448 /* Check if we have to update the netboot product properties
449 This is the case, if this product is newly selected.
450 Or if there was at least one configuration attribute modified.
451 */
452 $cfg_1 = $cfg_2 = array();
453 if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
454 $cfg_1 = $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
455 }
456 if($this->a_initial_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG']){
457 $cfg_2 = $this->a_initial_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
458 }
459 $diffs = $this->get_config_changes($cfg_1,$cfg_2);
460 $to_update = array();
461 if( !$this->initially_was_account ||
462 $this->s_selectedNetbootProduct != $this->s_initial_selectedNetbootProduct){
463 $to_update = $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
464 }elseif(count($diffs)){
465 $to_update = $diffs;
466 }
468 if(count($to_update)){
469 $name = $this->s_selectedNetbootProduct;
470 $this->opsi->set_product_properties($name,$to_update,$this->hostId);
471 if($this->opsi->is_error()){
472 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
473 return;
474 }
475 }
476 }
479 public function get_config_changes($c1,$c2)
480 {
481 /* Get key which are not present in both entries
482 */
483 $res = array();
484 foreach($c2 as $name => $value){
485 if(!isset($c1[$name]) || $c1[$name]['DEFAULT'] != $c2[$name]['DEFAULT']){
486 $res[$name] = $c2[$name];
487 }
488 }
489 foreach($c1 as $name => $value){
490 if(!isset($c2[$name]) || $c2[$name]['DEFAULT'] != $c1[$name]['DEFAULT']){
491 $res[$name] = $c1[$name];
492 }
493 }
494 return($res);
495 }
498 /*! \brief Removes the opsi client
499 */
500 public function remove_from_parent()
501 {
502 $this->opsi->del_client($this->hostId);
503 if($this->opsi->is_error()){
504 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
505 return;
506 }
507 }
510 /*! \brief Save html posts
511 */
512 public function save_object()
513 {
514 /* Init failed; reinit is triggered here.
515 */
516 if(isset($_POST['reinit']) && $this->init_failed){
517 $this->init();
518 }
520 /* Property are currently edited, close the dialog.
521 */
522 if(isset($_POST['cancel_properties']) && is_object($this->dialog)){
523 $this->dialog = NULL;
524 }
526 /* Save product property changes
527 */
528 if(isset($_POST['save_properties']) && ($this->dialog instanceof opsiProperties)){
529 $this->dialog->save_object();
530 $pro = $this->dialog->get_product();
531 $CFG = $this->dialog->get_cfg();
532 if(isset($this->a_selectedLocalProducts[$pro])){
533 $this->a_selectedLocalProducts[$pro]['CFG'] = $CFG;
534 $this->dialog = NULL;
535 }elseif($this->s_selectedNetbootProduct == $pro){
536 $this->a_availableNetbootProducts[$pro]['CFG'] = $CFG;
537 $this->dialog = NULL;
538 }else{
539 trigger_error("Fatal, unknown product was configured.");
540 }
541 }
543 /* Save html post
544 */
545 if(isset($_POST['opsiGeneric_posted'])){
547 plugin::save_object();
549 /* Get hostId
550 */
551 if(isset($_POST['hostId']) && $this->parent_mode){
552 $this->hostId = get_post('hostId');
553 }
555 /* Send actions like 'install' or 'wake' to the si server
556 */
557 if(isset($_POST['opsi_action']) && isset($_POST['opsi_trigger_action']) && $this->parent_mode){
558 $action = $_POST['opsi_action'];
559 if($action == "install"){
560 $this->install_client();
561 }
562 }
564 /* Get selected netboot product.
565 */
566 if(isset($_POST['opsi_netboot_product'])){
567 $SNP = trim($_POST['opsi_netboot_product']);
568 if(isset($this->a_availableNetbootProducts[$SNP])){
570 if(!isset($this->a_availableNetbootProducts[$SNP]['CFG'])){
571 $CFG = $this->opsi->get_product_properties($SNP);
572 $this->a_availableNetbootProducts[$SNP]['CFG'] = $CFG;
573 if($this->opsi->is_error()){
574 $this->init_failed = TRUE;
575 return;
576 }
577 }
578 $this->s_selectedNetbootProduct = $SNP;
579 }
580 }
582 /* Add/remove/edit local products
583 */
584 foreach($_POST as $name => $value){
586 /* Check if netboot product configuration is requested
587 */
588 if(preg_match("/^configure_netboot/",$name)){
589 $pro = $this->s_selectedNetbootProduct;
590 $cfg = $this->a_availableNetbootProducts[$pro]['CFG'];
591 $this->dialog = new opsiProperties($this->config,$pro,$cfg,$this->hostId);
592 break;
593 }
595 /* Add product
596 */
597 if(preg_match("/^add_lp_/",$name)){
598 $product = preg_replace("/^add_lp_(.*)_.$/","\\1",$name);
599 if(isset($this->a_availableLocalProducts[$product]) && !isset($this->a_selectedLocalProducts[$product])){
600 $this->a_selectedLocalProducts[$product] = $this->a_availableLocalProducts[$product];
601 $CFG = $this->opsi->get_product_properties($product);
602 if($this->opsi->is_error()){
603 $this->init_failed = TRUE;
604 return;
605 }
606 $this->a_selectedLocalProducts[$product]['CFG'] = $CFG;
607 }
608 break;
609 }
611 /* Delete product
612 */
613 if(preg_match("/^del_lp_/",$name)){
614 $product = preg_replace("/^del_lp_(.*)_.$/","\\1",$name);
615 if(isset($this->a_selectedLocalProducts[$product])){
616 unset($this->a_selectedLocalProducts[$product]);
617 }
618 break;
619 }
621 /* Edit a product
622 */
623 if(preg_match("/^edit_lp_/",$name)){
624 $product = preg_replace("/^edit_lp_(.*)_.$/","\\1",$name);
625 $this->dialog = new opsiProperties($this->config,
626 $product,$this->a_selectedLocalProducts[$product]['CFG'],$this->hostId);
627 break;
628 }
629 }
630 }
631 }
634 /* Triggers client installation
635 */
636 function install_client()
637 {
638 $this->opsi->send_action("install",$this->hostId,$this->mac);
639 if($this->opsi->is_error()){
640 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
641 }
642 }
645 /* Return plugin informations for acl handling */
646 static function plInfo()
647 {
648 return (array(
649 "plShortName" => _("Generic"),
650 "plDescription" => _("Opsi generic"),
651 "plSelfModify" => FALSE,
652 "plDepends" => array(),
653 "plPriority" => 1,
654 "plSection" => array("administration"),
655 "plCategory" => array("opsi" => array("description" => _("Opsi client"),
656 "objectClass" => "dummy_class_opsi")),
657 "plProvidedAcls"=> array()
658 ));
659 }
661 }
664 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
665 ?>