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 $standalone = 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->standalone = 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 /* We are a standalone plugin.
93 */
94 if($this->standalone ) {
95 $this->is_account = TRUE;
96 }
98 /* Try to load client infos from the gosa support daemon
99 */
100 if(!empty($this->hostId)){
102 $list = $this->opsi->list_clients($this->hostId);
103 $err |= $this->opsi->is_error();
105 /* Walk through all returned opsi clients and try to detect
106 one that matches our hostId.
107 #FIXME Implement an opsi method which returns infos for only one opsi client, not all.
108 */
109 foreach($list as $entry){
110 if(preg_match("/^".normalizePreg($this->hostId)."$/i",$entry['NAME'][0]['VALUE'])){
111 $this->initially_was_account = TRUE;
112 $this->is_account = TRUE;
113 foreach(array(
114 "is_installed" => "LASTSEEN",
115 "description" => "DESCRIPTION",
116 "mac" => "MAC",
117 "note" => "NOTES") as $des => $src){
118 $des2 = "initial_".$des;
119 $this->$des2 = $this->$des = $entry[$src][0]['VALUE'];
120 }
121 break;
122 }
123 }
124 }
126 /* Read informations about available netboot products.
127 If not already done, before.
128 */
129 if(!$err && !count($this->a_availableNetbootProducts)){
130 $this->a_availableNetbootProducts = $this->opsi->get_netboot_products();
131 ksort($this->a_availableNetbootProducts);
132 $err |= $this->opsi->is_error();
133 }
135 /* Read informations about available netboot products.
136 If not already done, before.
137 */
138 if(!$err && !count($this->a_availableLocalProducts)) {
139 $this->a_availableLocalProducts = $this->opsi->get_local_products();
140 ksort($this->a_availableLocalProducts);
141 $err |= $this->opsi->is_error();
142 }
144 /* Get products selected by this host.
145 */
146 if(!$err && !empty($this->hostId)) {
147 $tmp = array_keys($this->opsi->get_netboot_products($this->hostId));
148 $err |= $this->opsi->is_error();
149 if(count($tmp) && !$err && !isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
150 $this->s_selectedNetbootProduct = $tmp[0];
152 /* Read configuration for "Netboot Products" */
153 if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct])){
154 $CFG = $this->opsi->get_product_properties($this->s_selectedNetbootProduct,$this->hostId);
155 $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'] = $CFG;
156 }
157 }
158 $err |= $this->opsi->is_error();
159 }
161 /* Get all selected local products
162 */
163 if(!$err && !empty($this->hostId) && !count($this->a_selectedLocalProducts)) {
164 $tmp = $this->opsi->get_local_products($this->hostId);
165 $err |= $this->opsi->is_error();
166 $this->a_selectedLocalProducts = $tmp;
167 }
169 /* Load product configuration for all already selected products.
170 */
171 if(!$err && !empty($this->hostId)) {
172 foreach($this->a_selectedLocalProducts as $name => $data){
173 if(!$err && !isset($this->a_selectedLocalProducts[$name]['CFG'])){
174 $CFG = $this->opsi->get_product_properties($name,$this->hostId);
175 $err |= $this->opsi->is_error();
176 $this->a_selectedLocalProducts[$name]['CFG'] = $CFG;
177 }
178 }
179 }
181 /* Check if everything went fine else reset everything and display a retry button
182 */
183 if($err){
184 $this->init_failed = TRUE;
186 }else{
188 /* Remember initial settings */
189 $this->a_initial_selectedLocalProducts = $this->a_selectedLocalProducts;
190 $this->s_initial_selectedNetbootProduct = $this->s_selectedNetbootProduct;
191 $this->a_initial_availableNetbootProducts = $this->a_availableNetbootProducts;
193 /* Ensure that a valid netboot is selected product is.
194 */
195 if(empty($this->s_selectedNetbootProduct)){
196 $this->s_selectedNetbootProduct = key($this->a_availableNetbootProducts);
197 }
198 }
199 }
202 /*! \brief Check given data.
203 @return Array Returns an array with all issues.
204 */
205 public function check()
206 {
207 return(array());
208 $messages = plugin::check();
210 if(empty($this->hostId)){
211 $messages[] = msgPool::required(_("Name"));
212 }elseif(!preg_match("/\./",$this->hostId)){
214 /* The hostId must contain a domain part
215 */
216 $messages[] = msgPool::invalid(_("Name"),$this->hostId,"",
217 _("The field 'Name' must contain a domain part!"));
218 }elseif(preg_match("/[^a-z0-9\.\-_]/",$this->hostId)){
219 $messages[] = msgPool::invalid(_("Name"),$this->hostId,"/[a-z0-9\.\-_]/");
220 }
222 /* Ensure that the mac address is valid
223 */
224 if(!tests::is_mac($this->mac) || empty($this->mac)){
225 $messages[] = msgPool::invalid(_("MAC address"),$this->mac,"","00:0C:7F:31:33:F1");
226 }
227 return($messages);
228 }
231 /*! \brief Create the html ui of this plugin
232 @return String HTML content.
233 */
234 public function execute()
235 {
236 $display ="";
238 /* The pluign initialization failed due to communication problems with the gosa daemon.
239 A retry button will be displayed here.
240 */
241 if($this->init_failed){
242 $smarty = get_smarty();
243 $smarty->assign("standalone ", $this->standalone );
244 $smarty->assign("init_failed",TRUE);
245 $smarty->assign("message",$this->opsi->get_error());
246 return($smarty->fetch(get_template_path("generic.tpl",TRUE,dirname(__FILE__))));
247 }
249 /* If we are not a stand alone opsi client, we must be a samba client
250 which has the opsi tab enabled.
251 Check if the opsi is added or removed and display state buttons.
252 */
253 if(!$this->standalone ){
254 if(isset($_POST['modify_state'])){
255 if($this->is_account){
256 $this->is_account= FALSE;
257 }elseif(!$this->is_account){
258 $this->is_account= TRUE;
259 }
260 }
261 if($this->is_account){
262 $display = $this->show_disable_header(msgPool::removeFeaturesButton(_("OPSI")),
263 msgPool::featuresEnabled(_("OPSI")));
264 }else{
265 $display = $this->show_enable_header(msgPool::addFeaturesButton(_("OPSI")),
266 msgPool::featuresDisabled(_("OPSI")));
267 return($display);
268 }
269 }
271 /* Check if we have a sub dialog opened
272 */
273 if(is_object($this->dialog)){
274 $this->dialog->save_object();
275 return($this->dialog->execute());
276 }
278 /* Create HTML output of this plugin
279 */
280 $smarty = get_smarty();
281 $smarty->assign("standalone", $this->standalone );
282 foreach($this->attributes as $attr){
283 $smarty->assign($attr,$this->$attr);
284 }
285 $smarty->assign("is_installed", $this->is_installed);
286 $smarty->assign("init_failed",FALSE);
287 $divSLP = new divSelectBox();
288 $divALP = new divSelectBox();
290 /* Create list of available local products
291 */
292 foreach($this->a_availableLocalProducts as $name => $data){
293 if(isset($this->a_selectedLocalProducts[$name])) continue;
295 $add_tab = array("string" => "<input type='image' src='images/back.png' name='add_lp_".$name."'>");
296 $name_tab = array("string" => $name);
297 $desc_tab = array("string" => "<div style='height: 14px;overflow:hidden;'>".$data['DESC']."</div>",
298 "attach" => "title='".$data['DESC']."' style='border-right:0px;'");
299 $divALP->AddEntry(array($add_tab,$name_tab,$desc_tab));
300 }
302 /* Create list of selected local products
303 */
304 ksort($this->a_selectedLocalProducts);
305 foreach($this->a_selectedLocalProducts as $name => $data){
307 $name_tab = array("string" => $name);
308 $desc_tab = array(
309 "string" => "<div style='height: 14px;overflow:hidden;'>".$data['DESC']."</div>",
310 "attach" => "title='".$data['DESC']."'");
312 /* Only display edit button, if there is something to edit
313 */
314 $edit = "<img src='images/empty.png' alt=' '>";
315 if(count($data['CFG'])){
316 $edit = "<input type='image' src='images/lists/edit.png' name='edit_lp_".$name."'>";
317 }
318 $del = "<input type='image' src='images/lists/trash.png' name='del_lp_".$name."'>";
320 $opt_tab = array("string" => $edit.$del,
321 "attach" => "style='border-right:0px; width: 40px; text-align:right;'");
322 $divSLP->AddEntry(array($name_tab,$desc_tab,$opt_tab));
323 }
325 /* Check if netboot product is configurable
326 */
327 $cfg_able =FALSE;
328 if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
329 $cfg_able = count($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG']);
330 }
332 $smarty->assign("netboot_configurable",$cfg_able);
333 $smarty->assign("hostId", $this->hostId);
334 $smarty->assign("divSLP", $divSLP->DrawList());
335 $smarty->assign("divALP", $divALP->DrawList());
336 $smarty->assign("SNP", $this->s_selectedNetbootProduct);
337 $smarty->assign("ANP", $this->a_availableNetbootProducts);
338 return($display.$smarty->fetch(get_template_path("generic.tpl",TRUE,dirname(__FILE__))));
339 }
342 /*! \brief Save modifications using the gosa support daemon.
343 */
344 public function save()
345 {
347 /* Check if we have to create a new opsi client
348 or just have to save client modifications.
349 */
350 if(!$this->initially_was_account && $this->is_account){
351 $res = $this->opsi->add_client($this->hostId,$this->mac,$this->note,$this->description);
352 if($this->opsi->is_error()){
353 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
354 return;
355 }
356 }else{
358 /* Update client modifcations.
359 -Only if necessary
360 */
361 if($this->note != $this->initial_note ||
362 $this->description != $this->initial_description ||
363 $this->mac != $this->initial_mac){
364 $this->opsi->modify_client($this->hostId,$this->mac,$this->note,$this->description);
365 if($this->opsi->is_error()){
366 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
367 return;
368 }
369 }
370 }
373 /***********
374 Detect local netboot product changes
375 - Check which products were removed.
376 - Check which products were added.
377 ***********/
380 /* Detect which products were removed an which added.
381 */
382 $del = array_diff_assoc($this->a_initial_selectedLocalProducts,$this->a_selectedLocalProducts);
383 $add = array_diff_assoc($this->a_selectedLocalProducts,$this->a_initial_selectedLocalProducts);
385 /* Remove products from client
386 */
387 foreach($del as $name => $data){
388 $this->opsi->del_product_from_client($name,$this->hostId);
389 if($this->opsi->is_error()){
390 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
391 return;
392 }
393 }
395 /* Add products to client
396 And set the product properties.
397 */
398 foreach($add as $name => $data){
399 $this->opsi->add_product_to_client($name,$this->hostId);
400 if($this->opsi->is_error()){
401 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
402 return;
403 }
404 if(!empty($data['CFG'])){
405 $this->opsi->set_product_properties($name,$data['CFG'],$this->hostId);
406 if($this->opsi->is_error()){
407 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
408 return;
409 }
410 }
411 }
413 /* Save local product properties
414 */
415 foreach($this->a_selectedLocalProducts as $name => $data){
416 if(isset($del[$name]) || isset($add[$name])) continue;
418 /* Update product properties if there are changes
419 */
420 $diffs = $this->get_config_changes($data['CFG'],$this->a_initial_selectedLocalProducts[$name]['CFG']);
421 if(count($diffs)){
422 $this->opsi->set_product_properties($name,$diffs,$this->hostId);
423 if($this->opsi->is_error()){
424 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
425 return;
426 }
427 }
428 }
430 /*********
431 Detect Netboot product changes
432 - Check if another netboot product was selected.
433 - Check if the product properties were changes.
434 *********/
436 /* Update used netboot product.
437 */
438 if($this->s_selectedNetbootProduct != $this->s_initial_selectedNetbootProduct){
439 if(!empty($this->s_initial_selectedNetbootProduct)){
440 $this->opsi->del_product_from_client($this->s_initial_selectedNetbootProduct,$this->hostId);
441 if($this->opsi->is_error()){
442 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
443 return;
444 }
445 }
446 $this->opsi->add_product_to_client($this->s_selectedNetbootProduct,$this->hostId);
447 if($this->opsi->is_error()){
448 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
449 return;
450 }
451 }
453 /* Check if we have to update the netboot product properties
454 This is the case, if this product is newly selected.
455 Or if there was at least one configuration attribute modified.
456 */
457 $cfg_1 = $cfg_2 = array();
458 if(isset($this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
459 $cfg_1 = $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
460 }
461 if(isset($this->a_initial_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'])){
462 $cfg_2 = $this->a_initial_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
463 }
464 $diffs = $this->get_config_changes($cfg_1,$cfg_2);
465 $to_update = array();
466 if( !$this->initially_was_account ||
467 $this->s_selectedNetbootProduct != $this->s_initial_selectedNetbootProduct){
468 $to_update = $this->a_availableNetbootProducts[$this->s_selectedNetbootProduct]['CFG'];
469 }elseif(count($diffs)){
470 $to_update = $diffs;
471 }
473 if(count($to_update)){
474 $name = $this->s_selectedNetbootProduct;
475 $this->opsi->set_product_properties($name,$to_update,$this->hostId);
476 if($this->opsi->is_error()){
477 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
478 return;
479 }
480 }
481 }
484 public function get_config_changes($c1,$c2)
485 {
486 /* Get key which are not present in both entries
487 */
488 $res = array();
489 foreach($c2 as $name => $value){
490 if(!isset($c1[$name]) || $c1[$name]['DEFAULT'] != $c2[$name]['DEFAULT']){
491 $res[$name] = $c2[$name];
492 }
493 }
494 foreach($c1 as $name => $value){
495 if(!isset($c2[$name]) || $c2[$name]['DEFAULT'] != $c1[$name]['DEFAULT']){
496 $res[$name] = $c1[$name];
497 }
498 }
499 return($res);
500 }
503 /*! \brief Removes the opsi client
504 */
505 public function remove_from_parent()
506 {
507 $this->opsi->del_client($this->hostId);
508 if($this->opsi->is_error()){
509 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
510 return;
511 }
512 }
515 /*! \brief Save html posts
516 */
517 public function save_object()
518 {
519 /* Init failed; reinit is triggered here.
520 */
521 if(isset($_POST['reinit']) && $this->init_failed){
522 $this->init();
523 }
525 /* Property are currently edited, close the dialog.
526 */
527 if(isset($_POST['cancel_properties']) && is_object($this->dialog)){
528 $this->dialog = NULL;
529 }
531 /* Save product property changes
532 */
533 if(isset($_POST['save_properties']) && ($this->dialog instanceof opsiProperties)){
534 $this->dialog->save_object();
535 $pro = $this->dialog->get_product();
536 $CFG = $this->dialog->get_cfg();
537 if(isset($this->a_selectedLocalProducts[$pro])){
538 $this->a_selectedLocalProducts[$pro]['CFG'] = $CFG;
539 $this->dialog = NULL;
540 }elseif($this->s_selectedNetbootProduct == $pro){
541 $this->a_availableNetbootProducts[$pro]['CFG'] = $CFG;
542 $this->dialog = NULL;
543 }else{
544 trigger_error("Fatal, unknown product was configured.");
545 }
546 }
548 /* Save html post
549 */
550 if(isset($_POST['opsiGeneric_posted'])){
552 plugin::save_object();
554 /* Get hostId
555 */
556 if(isset($_POST['hostId']) && $this->standalone ){
557 $this->hostId = get_post('hostId');
558 }
560 /* Send actions like 'install' or 'wake' to the si server
561 */
562 if(isset($_POST['opsi_action']) && isset($_POST['opsi_trigger_action']) && $this->standalone ){
563 $action = $_POST['opsi_action'];
564 if($action == "install"){
565 $this->install_client();
566 }
567 }
569 /* Get selected netboot product.
570 */
571 if(isset($_POST['opsi_netboot_product'])){
572 $SNP = trim($_POST['opsi_netboot_product']);
573 if(isset($this->a_availableNetbootProducts[$SNP])){
575 if(!isset($this->a_availableNetbootProducts[$SNP]['CFG'])){
576 $CFG = $this->opsi->get_product_properties($SNP);
577 $this->a_availableNetbootProducts[$SNP]['CFG'] = $CFG;
578 if($this->opsi->is_error()){
579 $this->init_failed = TRUE;
580 return;
581 }
582 }
583 $this->s_selectedNetbootProduct = $SNP;
584 }
585 }
587 /* Add/remove/edit local products
588 */
589 foreach($_POST as $name => $value){
591 /* Check if netboot product configuration is requested
592 */
593 if(preg_match("/^configure_netboot/",$name)){
594 $pro = $this->s_selectedNetbootProduct;
595 $cfg = $this->a_availableNetbootProducts[$pro]['CFG'];
596 $this->dialog = new opsiProperties($this->config,$pro,$cfg,$this->hostId);
597 break;
598 }
600 /* Add product
601 */
602 if(preg_match("/^add_lp_/",$name)){
603 $product = preg_replace("/^add_lp_(.*)_.$/","\\1",$name);
604 if(isset($this->a_availableLocalProducts[$product]) && !isset($this->a_selectedLocalProducts[$product])){
605 $this->a_selectedLocalProducts[$product] = $this->a_availableLocalProducts[$product];
606 $CFG = $this->opsi->get_product_properties($product);
607 if($this->opsi->is_error()){
608 $this->init_failed = TRUE;
609 return;
610 }
611 $this->a_selectedLocalProducts[$product]['CFG'] = $CFG;
612 }
613 break;
614 }
616 /* Delete product
617 */
618 if(preg_match("/^del_lp_/",$name)){
619 $product = preg_replace("/^del_lp_(.*)_.$/","\\1",$name);
620 if(isset($this->a_selectedLocalProducts[$product])){
621 unset($this->a_selectedLocalProducts[$product]);
622 }
623 break;
624 }
626 /* Edit a product
627 */
628 if(preg_match("/^edit_lp_/",$name)){
629 $product = preg_replace("/^edit_lp_(.*)_.$/","\\1",$name);
630 $this->dialog = new opsiProperties($this->config,
631 $product,$this->a_selectedLocalProducts[$product]['CFG'],$this->hostId);
632 break;
633 }
634 }
635 }
636 }
639 /* Triggers client installation
640 */
641 function install_client()
642 {
643 $this->opsi->send_action("install",$this->hostId,$this->mac);
644 if($this->opsi->is_error()){
645 msg_dialog::display(_("Error"),msgPool::siError($this->opsi->get_error()),ERROR_DIALOG);
646 }
647 }
650 /* Return plugin informations for acl handling */
651 static function plInfo()
652 {
653 return (array(
654 "plShortName" => _("Generic"),
655 "plDescription" => _("OPSI generic"),
656 "plSelfModify" => FALSE,
657 "plDepends" => array(),
658 "plPriority" => 1,
659 "plSection" => array("administration"),
660 "plCategory" => array("opsi" => array("description" => _("OPSI client"),
661 "objectClass" => "dummy_class_opsi")),
662 "plProvidedAcls"=> array()
663 ));
664 }
666 }
669 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
670 ?>