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