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 = TRUE;
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 }
191 }
192 }
195 /*! \brief Check given data.
196 @return Array Returns an array with all issues.
197 */
198 public function check()
199 {
200 return(array());
201 $messages = plugin::check();
203 if(empty($this->hostId)){
204 $messages[] = msgPool::required(_("Name"));
205 }elseif(!preg_match("/\./",$this->hostId)){
207 /* The hostId must contain a domain part
208 */
209 $messages[] = msgPool::invalid(_("Name"),$this->hostId,"",
210 _("The client name must contain a domain part (e.g. '.company.de')."));
211 }elseif(preg_match("/[^a-z0-9\.\-_]/",$this->hostId)){
212 $messages[] = msgPool::invalid(_("Name"),$this->hostId,"/[a-z0-9\.\-_]/");
213 }
215 /* Ensure that the mac address is valid
216 */
217 if(!tests::is_mac($this->mac) || empty($this->mac)){
218 $messages[] = msgPool::invalid(_("MAC address"),$this->mac,"","00:0C:7F:31:33:F1");
219 }
220 return($messages);
221 }
224 /*! \brief Create the html ui of this plugin
225 @return String HTML content.
226 */
227 public function execute()
228 {
229 $display ="";
231 /* The pluign initialization failed due to communication problems with the gosa daemon.
232 A retry button will be displayed here.
233 */
234 if($this->init_failed){
235 $smarty = get_smarty();
236 $smarty->assign("init_failed",TRUE);
237 $smarty->assign("message",$this->opsi->get_error());
238 return($smarty->fetch(get_template_path("generic.tpl",TRUE,dirname(__FILE__))));
239 }
241 /* If we are not a stand alone opsi client, we must be a samba client
242 which has the opsi tab enabled.
243 Check if the opsi is added or removed and display state buttons.
244 */
245 if(!$this->parent_mode){
246 if(isset($_POST['modify_state'])){
247 if($this->is_account){
248 $this->is_account= FALSE;
249 }elseif(!$this->is_account){
250 $this->is_account= TRUE;
251 }
252 }
253 if($this->is_account){
254 $display = $this->show_disable_header(msgPool::removeFeaturesButton(_("Opsi")),
255 msgPool::featuresEnabled(_("Opsi")));
256 }else{
257 $display = $this->show_enable_header(msgPool::addFeaturesButton(_("Opsi")),
258 msgPool::featuresDisabled(_("Opsi")));
259 return($display);
260 }
261 }
263 /* Check if we have a sub dialog opened
264 */
265 if(is_object($this->dialog)){
266 $this->dialog->save_object();
267 return($this->dialog->execute());
268 }
270 /* Create HTML output of this plugin
271 */
272 $smarty = get_smarty();
273 foreach($this->attributes as $attr){
274 $smarty->assign($attr,$this->$attr);
275 }
277 $smarty->assign("parent_mode", $this->parent_mode);
278 $smarty->assign("is_installed", $this->is_installed);
279 $smarty->assign("init_failed",FALSE);
280 $divSLP = new divSelectBox();
281 $divALP = new divSelectBox();
283 /* Create list of available local products
284 */
285 foreach($this->a_availableLocalProducts as $name => $data){
286 if(isset($this->a_selectedLocalProducts[$name])) continue;
288 $add_tab = array("string" => "<input type='image' src='images/back.png' name='add_lp_".$name."'>");
289 $name_tab = array("string" => $name);
290 $desc_tab = array("string" => "<div style='height: 14px;overflow:hidden;'>".$data['DESC']."</div>",
291 "attach" => "title='".$data['DESC']."' style='border-right:0px;'");
292 $divALP->AddEntry(array($add_tab,$name_tab,$desc_tab));
293 }
295 /* Create list of selected local products
296 */
297 ksort($this->a_selectedLocalProducts);
298 foreach($this->a_selectedLocalProducts as $name => $data){
300 $name_tab = array("string" => $name);
301 $desc_tab = array(
302 "string" => "<div style='height: 14px;overflow:hidden;'>".$data['DESC']."</div>",
303 "attach" => "title='".$data['DESC']."'");
305 /* Only display edit button, if there is something to edit
306 */
307 $edit = "<img src='images/empty.png' alt=' '>";
308 if(count($data['CFG'])){
309 $edit = "<input type='image' src='images/lists/edit.png' name='edit_lp_".$name."'>";
310 }
311 $del = "<input type='image' src='images/lists/trash.png' name='del_lp_".$name."'>";
313 $opt_tab = array("string" => $edit.$del,
314 "attach" => "style='border-right:0px; width: 40px; text-align:right;'");
315 $divSLP->AddEntry(array($name_tab,$desc_tab,$opt_tab));
316 }
318 /* Check if netboot product is configurable
319 */
320 $cfg_able =FALSE;
321 if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
322 $cfg_able = count($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG']);
323 }
325 $smarty->assign("netboot_configurable",$cfg_able);
326 $smarty->assign("hostId", $this->hostId);
327 $smarty->assign("divSLP", $divSLP->DrawList());
328 $smarty->assign("divALP", $divALP->DrawList());
329 $smarty->assign("SNP", $this->s_selectedNetbootProduct);
330 $smarty->assign("ANP", $this->a_availableNetbootProducts);
331 return($display.$smarty->fetch(get_template_path("generic.tpl",TRUE,dirname(__FILE__))));
332 }
335 /*! \brief Save modifications using the gosa support daemon.
336 */
337 public function save()
338 {
340 /* Check if we have to create a new opsi client
341 or just have to save client modifications.
342 */
343 if(!$this->initially_was_account && $this->is_account){
344 $res = $this->opsi->add_client($this->hostId,$this->mac,$this->note,$this->description);
345 if($this->opsi->is_error()){
346 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
347 return;
348 }
349 }else{
351 /* Update client modifcations.
352 -Only if necessary
353 */
354 if($this->note != $this->initial_note ||
355 $this->description != $this->initial_description ||
356 $this->mac != $this->initial_mac){
357 $this->opsi->modify_client($this->hostId,$this->mac,$this->note,$this->description);
358 if($this->opsi->is_error()){
359 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
360 return;
361 }
362 }
363 }
366 /***********
367 Detect local netboot product changes
368 - Check which products were removed.
369 - Check which products were added.
370 ***********/
373 /* Detect which products were removed an which added.
374 */
375 $del = array_diff_assoc($this->a_initial_selectedLocalProducts,$this->a_selectedLocalProducts);
376 $add = array_diff_assoc($this->a_selectedLocalProducts,$this->a_initial_selectedLocalProducts);
378 /* Remove products from client
379 */
380 foreach($del as $name => $data){
381 $this->opsi->del_product_from_client($name,$this->hostId);
382 if($this->opsi->is_error()){
383 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
384 return;
385 }
386 }
388 /* Add products to client
389 And set the product properties.
390 */
391 foreach($add as $name => $data){
392 $this->opsi->add_product_to_client($name,$this->hostId);
393 if($this->opsi->is_error()){
394 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
395 return;
396 }
397 if(!empty($data['CFG'])){
398 $this->opsi->set_product_properties($name,$data['CFG'],$this->hostId);
399 if($this->opsi->is_error()){
400 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
401 return;
402 }
403 }
404 }
406 /* Save local product properties
407 */
408 foreach($this->a_selectedLocalProducts as $name => $data){
409 if(isset($del[$name]) || isset($add[$name])) continue;
411 /* Update product properties if there are changes
412 */
413 $diffs = $this->get_config_changes($data['CFG'],$this->a_initial_selectedLocalProducts[$name]['CFG']);
414 if(count($diffs)){
415 $this->opsi->set_product_properties($name,$diffs,$this->hostId);
416 if($this->opsi->is_error()){
417 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
418 return;
419 }
420 }
421 }
423 /*********
424 Detect Netboot product changes
425 - Check if another netboot product was selected.
426 - Check if the product properties were changes.
427 *********/
429 /* Update used netboot product.
430 */
431 if($this->s_selectedNetbootProduct != $this->s_initial_selectedNetbootProduct){
432 if(!empty($this->s_initial_selectedNetbootProduct)){
433 $this->opsi->del_product_from_client($this->s_initial_selectedNetbootProduct,$this->hostId);
434 if($this->opsi->is_error()){
435 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
436 return;
437 }
438 }
439 $this->opsi->add_product_to_client($this->s_selectedNetbootProduct,$this->hostId);
440 if($this->opsi->is_error()){
441 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
442 return;
443 }
444 }
446 /* Check if we have to update the netboot product properties
447 This is the case, if this product is newly selected.
448 Or if there was at least one configuration attribute modified.
449 */
450 $cfg_1 = $cfg_2 = array();
451 if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
452 $cfg_1 = $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
453 }
454 if(isset($this->a_initial_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
455 $cfg_2 = $this->a_initial_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
456 }
457 $diffs = $this->get_config_changes($cfg_1,$cfg_2);
458 $to_update = array();
459 if( !$this->initially_was_account ||
460 $this->s_selectedNetbootProduct != $this->s_initial_selectedNetbootProduct){
461 $to_update = $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
462 }elseif(count($diffs)){
463 $to_update = $diffs;
464 }
466 if(count($to_update)){
467 $name = $this->s_selectedNetbootProduct;
468 $this->opsi->set_product_properties($name,$to_update,$this->hostId);
469 if($this->opsi->is_error()){
470 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
471 return;
472 }
473 }
474 }
477 public function get_config_changes($c1,$c2)
478 {
479 /* Get key which are not present in both entries
480 */
481 $res = array();
482 foreach($c2 as $name => $value){
483 if(!isset($c1[$name]) || $c1[$name]['DEFAULT'] != $c2[$name]['DEFAULT']){
484 $res[$name] = $c2[$name];
485 }
486 }
487 foreach($c1 as $name => $value){
488 if(!isset($c2[$name]) || $c2[$name]['DEFAULT'] != $c1[$name]['DEFAULT']){
489 $res[$name] = $c1[$name];
490 }
491 }
492 return($res);
493 }
496 /*! \brief Removes the opsi client
497 */
498 public function remove_from_parent()
499 {
500 $this->opsi->del_client($this->hostId);
501 if($this->opsi->is_error()){
502 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
503 return;
504 }
505 }
508 /*! \brief Save html posts
509 */
510 public function save_object()
511 {
512 /* Init failed; reinit is triggered here.
513 */
514 if(isset($_POST['reinit']) && $this->init_failed){
515 $this->init();
516 }
518 /* Property are currently edited, close the dialog.
519 */
520 if(isset($_POST['cancel_properties']) && is_object($this->dialog)){
521 $this->dialog = NULL;
522 }
524 /* Save product property changes
525 */
526 if(isset($_POST['save_properties']) && ($this->dialog instanceof opsiProperties)){
527 $this->dialog->save_object();
528 $pro = $this->dialog->get_product();
529 $CFG = $this->dialog->get_cfg();
530 if(isset($this->a_selectedLocalProducts[$pro])){
531 $this->a_selectedLocalProducts[$pro]['CFG'] = $CFG;
532 $this->dialog = NULL;
533 }elseif($this->s_selectedNetbootProduct == $pro){
534 $this->a_availableNetbootProducts[$pro]['CFG'] = $CFG;
535 $this->dialog = NULL;
536 }else{
537 trigger_error("Fatal, unknown product was configured.");
538 }
539 }
541 /* Save html post
542 */
543 if(isset($_POST['opsiGeneric_posted'])){
545 plugin::save_object();
547 /* Get hostId
548 */
549 if(isset($_POST['hostId']) && $this->parent_mode){
550 $this->hostId = get_post('hostId');
551 }
553 /* Send actions like 'install' or 'wake' to the si server
554 */
555 if(isset($_POST['opsi_action']) && isset($_POST['opsi_trigger_action']) && $this->parent_mode){
556 $action = $_POST['opsi_action'];
557 if($action == "install"){
558 $this->install_client();
559 }
560 }
562 /* Get selected netboot product.
563 */
564 if(isset($_POST['opsi_netboot_product'])){
565 $SNP = trim($_POST['opsi_netboot_product']);
566 if(isset($this->a_availableNetbootProducts[$SNP])){
568 if(!isset($this->a_availableNetbootProducts[$SNP]['CFG'])){
569 $CFG = $this->opsi->get_product_properties($SNP);
570 $this->a_availableNetbootProducts[$SNP]['CFG'] = $CFG;
571 if($this->opsi->is_error()){
572 $this->init_failed = TRUE;
573 return;
574 }
575 }
576 $this->s_selectedNetbootProduct = $SNP;
577 }
578 }
580 /* Add/remove/edit local products
581 */
582 foreach($_POST as $name => $value){
584 /* Check if netboot product configuration is requested
585 */
586 if(preg_match("/^configure_netboot/",$name)){
587 $pro = $this->s_selectedNetbootProduct;
588 $cfg = $this->a_availableNetbootProducts[$pro]['CFG'];
589 $this->dialog = new opsiProperties($this->config,$pro,$cfg,$this->hostId);
590 break;
591 }
593 /* Add product
594 */
595 if(preg_match("/^add_lp_/",$name)){
596 $product = preg_replace("/^add_lp_(.*)_.$/","\\1",$name);
597 if(isset($this->a_availableLocalProducts[$product]) && !isset($this->a_selectedLocalProducts[$product])){
598 $this->a_selectedLocalProducts[$product] = $this->a_availableLocalProducts[$product];
599 $CFG = $this->opsi->get_product_properties($product);
600 if($this->opsi->is_error()){
601 $this->init_failed = TRUE;
602 return;
603 }
604 $this->a_selectedLocalProducts[$product]['CFG'] = $CFG;
605 }
606 break;
607 }
609 /* Delete product
610 */
611 if(preg_match("/^del_lp_/",$name)){
612 $product = preg_replace("/^del_lp_(.*)_.$/","\\1",$name);
613 if(isset($this->a_selectedLocalProducts[$product])){
614 unset($this->a_selectedLocalProducts[$product]);
615 }
616 break;
617 }
619 /* Edit a product
620 */
621 if(preg_match("/^edit_lp_/",$name)){
622 $product = preg_replace("/^edit_lp_(.*)_.$/","\\1",$name);
623 $this->dialog = new opsiProperties($this->config,
624 $product,$this->a_selectedLocalProducts[$product]['CFG'],$this->hostId);
625 break;
626 }
627 }
628 }
629 }
632 /* Triggers client installation
633 */
634 function install_client()
635 {
636 $this->opsi->send_action("install",$this->hostId,$this->mac);
637 if($this->opsi->is_error()){
638 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
639 }
640 }
643 /* Return plugin informations for acl handling */
644 static function plInfo()
645 {
646 return (array(
647 "plShortName" => _("Generic"),
648 "plDescription" => _("Opsi generic"),
649 "plSelfModify" => FALSE,
650 "plDepends" => array(),
651 "plPriority" => 1,
652 "plSection" => array("administration"),
653 "plCategory" => array("opsi" => array("description" => _("Opsi client"),
654 "objectClass" => "dummy_class_opsi")),
655 "plProvidedAcls"=> array()
656 ));
657 }
659 }
662 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
663 ?>