1 <?php
3 /*! \brief This class allows to manage backend config items and packages.
4 */
5 class newConfigManagement extends plugin
6 {
7 var $initTime;
8 var $plHeadline = "Config management";
9 var $plDescription = "Config management";
11 var $selectedContainer;
13 var $dataModel = NULL;
14 var $listing = NULL;
16 var $cfgItemMap = NULL;
18 var $addableContainerItems = array();
19 var $currentObject = NULL;
20 var $itemsPerMethod = NULL;
22 /*! \brief Initialize the plugin and finally update the data model.
23 */
24 function __construct($config, $dn)
25 {
26 $this->config = &$config;
27 $this->listing = new ConfigManagementListing($this->config, get_userinfo(), $this);
29 // Load the template engine and tell her what template
30 // to use for the HTML it produces.
31 $this->TemplateEngine = new TemplateEngine($config);
33 // Preset item config - with Distribution and Release objects.
34 $items = array();
35 $items['root']['container'] = array('Distribution');
36 $items['root']['name'] = '/';
37 $items['root']['description'] = _('Root');
39 // Define distribution paramters.
40 $dOpt1 = array('description' => _('Name'), 'default' => '', 'value' => '', 'required' => true,
41 'type' => 'string', 'display' => _('Name'));
42 $dOpt2 = array('description' => _('Distribution type'), 'default' => 'deb', 'value' => 'deb', 'required' => true,
43 'type' => 'combobox', 'display' => _('Distribution type'), 'values' => array("deb" => 'deb', "rpm" => 'rpm'));
44 $dOpt3 = array('description' => _('Mirror Url'), 'default' => '', 'value' => '', 'required' => false,
45 'type' => 'string', 'display' => _('Mirror Url'));
46 $dOpt4 = array('description' => _('Method'), 'default' => 'puppet', 'value' => 'puppet', 'required' => false,
47 'type' => 'combobox', 'display' => _('Installation method'), 'values'=>array('puppet'=>_('Puppet')));
49 // Define release parameters
50 $rOpt1 = array('description' => _('Name'), 'default' => '', 'value' => '', 'required' => true,
51 'type' => 'string', 'display' => _('Name'));
53 $items['Distribution']['container'] = array('Release');
54 $items['Distribution']['name'] = 'Distribution';
55 $items['Distribution']['description'] = _('Distribution');
56 $items['Distribution']['options']['name'] = $dOpt1;
57 $items['Distribution']['options']['mirror'] = $dOpt3;
58 $items['Distribution']['options']['installation_type'] = $dOpt2;
59 $items['Distribution']['options']['installation_method'] = $dOpt4;
61 $items['Release']['container'] = array('Release', '__CFG_ITEMS__');
62 $items['Release']['name'] = 'Release';
63 $items['Release']['description'] = _('Release');
64 $items['Release']['options']['name'] = $rOpt1;
66 $this->installationMethods = array();
67 $this->installationMethods['root']['description'] = _('root');
68 $this->installationMethods['root']['name'] = 'root';
69 $this->installationMethods['root']['title'] = _('root');
70 $this->installationMethods['root']['items']['Distribution'] = &$items['Distribution'];
71 $this->installationMethods['root']['items']['Release'] = &$items['Release'];
72 $this->installationMethods['root']['items']['root'] = &$items['root'];
74 // Request an update of the data model
75 $this->loadInstallationMethods();
76 $this->updateDataModel();
77 $this->listing->setListingTypes($this->getListingTypes());
78 }
81 /*! \brief Intializes this plugin
82 * All available installation methods will be loaded
83 */
84 function loadInstallationMethods()
85 {
86 // Reset erros
87 $this->rpcError = $this->initFailed = FALSE;
89 // Load configuration via rpc.
90 $rpc = $this->config->getRpcHandle();
91 $res = $rpc->getSupportedInstallMethods();
92 if(!$rpc->success()){
93 $this->rpcError = TRUE;
94 $this->errorMessage = $rpc->get_error();;
95 return;
96 }
98 // Populate install methods on success.
99 if(!count($res)){
100 $this->errorMessage = _("No selectable install methods returned!");
101 msg_dialog::display(_("Setup"), $this->errorMessage , ERROR_DIALOG);
102 $this->initFailed = TRUE;
103 return;
104 }else{
106 // Merge result with hard coded methods
107 $this->installationMethods = array_merge($this->installationMethods, $res);
109 // Walk through entries and create useful mappings.
110 $this->cfgItemMap = array();
111 $this->itemConfig = array();
112 $this->itemsPerMethod = array();
113 $rootElements = array();
114 foreach($this->installationMethods as $method => $items){
115 foreach($items['items'] as $itemName => $item){
116 $this->itemsPerMethod[$method][] = $itemName;
117 $this->cfgItemMap[$itemName] = $method;
118 $this->itemConfig[$itemName] = &$this->installationMethods[$method]['items'][$itemName];
120 // This enables us to create the first level of config items when
121 // a release is selected.
122 if($item['name'] == "/" && $itemName != 'root'){
123 $rootElements = array_merge($rootElements, $item['container']);
124 }
125 }
126 }
128 // Merge in root elements to releases.
129 foreach($this->itemConfig as $item => $data){
130 if(in_array('__CFG_ITEMS__', $data['container'])){
131 $this->itemConfig[$item]['container'] = array_merge($this->itemConfig[$item]['container'], $rootElements );
132 }
133 }
134 }
135 }
138 /*! \brief Updates all distributions, releases, packages and items in the dataModel
139 * Load information from the backend.
140 */
141 function updateDataModel()
142 {
143 // Recreate the data model, to have a clean and fresh instance.
144 $this->dataModel = new ConfigManagementDataModel();
146 // Load distributions
147 $rpc = $this->config->getRpcHandle();
148 $res = $rpc->getDistributions();
149 if(!$rpc->success()){
150 msg_dialog::display(_("Error"), sprintf(_("Failed to load distributions: %s"), $rpc->get_error()),ERROR_DIALOG);
151 return(NULL);
152 }else{
153 foreach($res as $dist){
154 $this->dataModel->addItem('Distribution','/root', $dist['name'], $dist);
156 if(isset($dist['releases'])){
158 // Sort releases by name length
159 $sort = array();
160 foreach($dist['releases'] as $id => $release){
161 $sort[strlen($release['name']) . $release['name']] = $id;
162 }
163 uksort($sort, "strnatcasecmp");
165 // Append release tags
166 foreach($sort as $id){
167 $release = $dist['releases'][$id];
168 $names = preg_split("/\//", $release['name']);
170 $rPath = "";
171 $distPath = "/root/{$dist['name']}";
172 foreach($names as $rName){
173 $rPath .= '/'.$rName;
174 $this->dataModel->addItem('Release',$distPath, $rName, $release);
175 $distPath .= $rPath;
176 }
177 }
178 }
179 }
180 }
181 }
184 /*! \brief Keep track of posted values and populate those
185 * which are interesting for us.
186 * Inspects the _POST and _GET values.
187 */
188 function save_object()
189 {
190 // Update the listing class, this is necessary to get post
191 // actions from it.
192 $this->listing->save_object();
194 // Get the selected distribution and release from the listing widget.
195 $cont = $this->listing->getSelectedContainer();
196 if(isset($_POST['ROOT'])){
197 $this->setCurrentContainer('/root');
198 }elseif(isset($_POST['BACK'])){
199 $path = $this->selectedContainer;
200 if($this->dataModel->itemExistsByPath($path)){
201 $data = $this->dataModel->getItemByPath($path);
202 if($data['parentPath']){
203 $this->setCurrentContainer($data['parentPath']);
204 }
205 }
206 }else{
207 $this->setCurrentContainer($cont);
208 }
209 }
212 /*! \brief Load extended sub-objecte like 'config items' or 'packages'
213 * for the given release path.
214 * @param String The release path to load sub-objects for.
215 * @return NULL
216 */
217 function updateItemList($path)
218 {
219 // Fist get Item and check if it is an release
220 if($this->dataModel->itemExistsByPath($path)){
221 $data = $this->dataModel->getItemByPath($path);
223 // Only releases can contain config-items.
224 if($data['type'] == 'Release' && $data['status'] != "fetched"){
227 // Request all config items for the selected release via rpc.
228 $rpc = $this->config->getRpcHandle();
229 $releasePath = $this->getReleasePart($path);
230 $res = $rpc->listConfigItems($releasePath);
231 if(!$rpc->success()){
232 msg_dialog::display(_("Error"),sprintf(_("Failed to load distributions: %s"),$rpc->get_error()),ERROR_DIALOG);
233 }else{
235 if(!$res) return;
237 // Sort entries by path length
238 $sLen = array();
239 foreach($res as $itemPath => $type){
240 $sLen[strlen($itemPath)."_".$itemPath] = $itemPath;
241 }
242 uksort($sLen, "strnatcasecmp");
244 // Walk through each entry and then try to add it to the model
245 foreach($sLen as $unused => $itemPath){
247 // Do not add the root element '/'
248 if($itemPath == "/") continue;
250 $type = $res[$itemPath];
252 // Append the items-path to the current path to create the
253 // effective item path in the data model.
254 $targetPath = trim($path."/".$itemPath);
256 // Remove trailing and duplicated slashes
257 $targetPath = rtrim($targetPath, '/');
258 $targetPath = preg_replace("/\/\/*/","/", $targetPath);
260 // Extract the items name
261 $name = preg_replace("/^.*\//","", $targetPath);
263 // Cleanup the path and then add the item.
264 $targetPath = preg_replace("/[^\/]*$/","", $targetPath);
265 $targetPath = rtrim($targetPath,'/');
266 $this->dataModel->addItem($type, $targetPath, $name,array(),'-' );
267 }
268 $this->dataModel->setItemStatus($path, 'fetched');
269 }
270 }
271 }
272 }
275 /*! \brief Sets the currently selected container and item path.
276 * @param String The path of the container to set.
277 * @param String The path of the item to set.
278 * @return
279 */
280 function setCurrentContainer($cont)
281 {
282 $this->selectedContainer = $cont;
284 // Update list of items within the selected container.
285 $this->updateItemList($this->selectedContainer);
287 // Transfer checked values back to the listing class.
288 $this->listing->setContainers($this->getContainerList());
289 $this->listing->setContainer($cont);
291 // Update the list of addable sub objects
292 $this->addableContainerItems = $this->getAddableContainersPerPath($cont);
293 }
296 function getAddableContainersPerPath($path)
297 {
298 $currentItem = $this->dataModel->getItemByPath($path);
299 $method = $this->getInstallationMethodPerPath($path);
301 // Get allowed items for the currently selected method
302 // merge in root elements, they are allowed everywhere.
303 $allowedItems = $this->itemsPerMethod[$method];
304 $allowedItems = array_merge($allowedItems, $this->itemsPerMethod['root']);
306 // Get addable items
307 $possibleItems = $this->itemConfig[$currentItem['type']]['container'];
308 return(array_unique(array_intersect($allowedItems, $possibleItems)));
309 }
312 function getInstallationMethodPerPath($path)
313 {
314 $path .= '/';
315 while(preg_match("/\//", $path)){
316 $path = preg_replace("/\/[^\/]*$/","",$path);
317 $item = $this->dataModel->getItemByPath($path);
318 if(isset($item['values']['installation_method'])){
319 return($item['values']['installation_method']);
320 }
321 }
322 return('root');
323 }
326 /*! \brief Generate the HTML content for this plugin.
327 * Actually renders the listing widget..
328 */
329 function execute()
330 {
331 // Get the selected release and store it in a session variable
332 // to allow the configFilter to access it and display the
333 // packages and items.
334 $res = $this->listing->execute();
335 $this->listing->setListingTypes($this->getListingTypes());
336 return($res);
337 }
340 /*! \brief Returns a list of items which will then be displayed
341 * in the management-list.
342 * (The management class calls this method from its execute())
343 * @return Array A list of items/objects for the listing.
344 */
345 function getItemsToBeDisplayed()
346 {
347 $path = $this->selectedContainer;
348 $item = $this->dataModel->getItemByPath($path);
349 return($item);
350 }
353 /*! \brief Returns a simply list of all distributions.
354 * This list will then be used to generate the entries of the
355 * ItemSelectors in the listing class.
356 */
357 function getContainerList()
358 {
359 $data = $this->dataModel->getItemByPath('/root');
360 $res = array();
361 $res["/root"] = array("name" => "/", "desc" => "");
362 $res = array_merge($res,$this->__recurseItem($data));
363 return($res);
364 }
367 /*! \brief Recursivly wlks through an item and collects all path and name info.
368 * The reult can then be used to fill the ItemSelector.
369 * @param Array The Item to recurse.
370 * @param Array The type of of objects to collect.
371 * @param String The parent path prefix which should be removed.
372 * @return Array An array containing Array[path] = name
373 */
374 function __recurseItem($item, $parent = "")
375 {
376 $res = array();
377 $path = preg_replace("/".preg_quote($parent,'/')."/","",$item['path']);
378 $res[$path] = array('name' => $item['name'],'desc'=>$item['type']);
379 if(count($item['children'])){
380 foreach($item['children'] as $child){
381 $res = array_merge($res, $this->__recurseItem($child, $parent));
382 }
383 }
384 return($res);
385 }
388 /*! \brief Returns a info list about all items we can manage,
389 * this used to fill the listings <objectType> settings.
390 * @return Array An array with item info.
391 */
392 function getListingTypes()
393 {
394 $types= array();
395 $types['Distribution']['objectClass'] = 'Distribution';
396 $types['Distribution']['label'] = _('Distribution');
397 $types['Distribution']['image'] = 'images/lists/edit.png';
398 $types['Distribution']['category'] = 'Device';
399 $types['Distribution']['class'] = 'Device';
401 $types['Release']['objectClass'] = 'Release';
402 $types['Release']['label'] = _('Release');
403 $types['Release']['image'] = 'images/lists/delete.png';
404 $types['Release']['category'] = 'Device';
405 $types['Release']['class'] = 'Device';
407 $types['Component']['objectClass'] = 'Component';
408 $types['Component']['label'] = _('Component');
409 $types['Component']['image'] = 'plugins/users/images/select_user.png';
410 $types['Component']['category'] = 'Device';
411 $types['Component']['class'] = 'Device';
413 foreach($this->installationMethods as $method => $items){
414 foreach($items['items'] as $itemName => $item){
415 $types[$itemName]['objectClass'] = $itemName;
416 $types[$itemName]['label'] = $item['name'];
417 $types[$itemName]['image'] = 'plugins/fai/images/fai_script.png';
418 $types[$itemName]['category'] = 'Device';
419 $types[$itemName]['class'] = 'Device';
420 }
421 }
423 return($types);
424 }
427 /*! \brief The plugins ACL and plugin-property definition.
428 * @return
429 */
430 public static function plInfo()
431 {
432 return (array(
433 "plShortName" => _("Config management"),
434 "plDescription" => _("Config management"),
435 "plSelfModify" => FALSE,
436 "plDepends" => array(),
437 "plPriority" => 0,
438 "plSection" => array("administration"),
439 "plCategory" => array(
440 "newConfigManagement" => array("description" => _("Config management"),
441 "objectClass" => "FAKE_OC_newConfigManagement")),
442 "plProvidedAcls"=> array()
443 ));
444 }
447 /*! \brief Acts on open requests.
448 * (This action is received from the ConfigManagementListing class.)
449 * @param Array The items ids. (May contain multiple ids)
450 * @return
451 */
452 function openEntry($ids)
453 {
454 $id = $ids[0];
455 $item = $this->dataModel->getItemById($id);
456 $this->setCurrentContainer($item['path']);
457 return;
458 }
462 /*! \brief Removes an entry from the listing.
463 */
464 function removeEntry($ids)
465 {
466 foreach($ids as $id){
467 $item = $this->dataModel->getItemById($id);
469 // Is an config item.
470 if($this->cfgItemMap[$item['type']] != 'root'){
471 $release = preg_replace("/^.*\//","", $this->getReleasePath($item['path']));
472 $path = $this->getItemPath($item['path']);
474 $rpc = $this->config->getRpcHandle();
475 $rpc->removeConfigItem($release, $path);
476 if(!$rpc->success()){
477 msg_dialog::display(_("Error"), sprintf(_("Failed to remove: %s"), $rpc->get_error()),ERROR_DIALOG);
478 return(NULL);
479 }else{
480 $this->dataModel->removeItem($item['path']);
481 }
482 }else{
483 echo $item['type']." - are not handled yet!";
484 }
485 }
486 }
488 function getUsedNamesForPath($path)
489 {
490 $item = $this->dataModel->getItemByPath($path);
491 $names = array();
492 foreach($item['children'] as $path => $data){
493 $names[] = $data['name'];
494 }
495 return($names);
496 }
499 /*! \brief Edits a selected list item.
500 */
501 function editEntry($ids)
502 {
503 $item = $this->dataModel->getItemById($ids[0]);
504 $release = preg_replace("/^.*\//","", $this->getReleasePath($item['path']));
505 $path = $this->getItemPath($item['path']);
506 $method = $this->cfgItemMap[$item['type']];
508 // Load item values on demand
509 if($this->cfgItemMap[$item['type']] != 'root'){
510 if($item['status'] == '-'){
511 $rpc = $this->config->getRpcHandle();
512 $item['values'] = $rpc->getConfigItem($release, $path);
513 $this->dataModel->setItemStatus($item['path'], 'fetched');
514 $this->dataModel->setItemValues($item['path'], $item['values']);
515 }
516 }
518 $this->TemplateEngine->load($this->itemConfig);
519 $this->TemplateEngine->setTemplate($method.".tpl");
520 $this->TemplateEngine->setValues($item['type'],$item['values']);
521 $this->listing->setDialogObject($this->TemplateEngine);
522 $this->currentObject = $item;
523 }
526 /*! \brief Initiates the creation of a new item
527 */
528 function newEntry($type)
529 {
530 // We've to add a config item
531 $this->TemplateEngine->load($this->itemConfig);
532 if($this->cfgItemMap[$type] != 'root'){
533 $method = $this->cfgItemMap[$type];
534 $this->TemplateEngine->setTemplate($method.".tpl");
535 $this->TemplateEngine->setValues($type,array());
536 $this->listing->setDialogObject($this->TemplateEngine);
537 $this->currentObject = NULL;
538 }else{
539 $this->TemplateEngine->setTemplate("root.tpl");
540 $this->TemplateEngine->setValues($type,array());
541 $this->listing->setDialogObject($this->TemplateEngine);
542 $this->currentObject = NULL;
543 }
544 }
547 /*! \brief Extracts the item-path out of a path.
548 * e.g. /debian/squeeze/test/module -> /test/module
549 */
550 function getItemPath($fullPath)
551 {
552 $fPath = $fullPath.'/';
553 while(preg_match("/\//", $fPath)){
554 $fPath = preg_replace("/\/[^\/]*$/","", $fPath);
555 $item = $this->dataModel->getItemByPath($fPath);
556 if(in_array($item['type'], array('Release', 'Distribution', 'root'))){
557 return(preg_replace("/".preg_quote($item['path'],'/')."/", "", $fullPath));
558 }
559 }
560 return(NULL);
561 }
564 /*! \brief Extracts the releaes path out of a path.
565 * e.g. /debian/squeeze/test/module -> /debian/squeeze
566 */
567 function getReleasePath($fullPath)
568 {
569 $fullPath.='/';
570 while(preg_match("/\//", $fullPath)){
571 $fullPath = preg_replace("/\/[^\/]*$/","", $fullPath);
572 $item = $this->dataModel->getItemByPath($fullPath);
573 if($item['type'] == 'Release'){
574 return($fullPath);
575 }
576 }
577 return(NULL);
578 }
581 /*! \brief Extracts the distribution path out of a path.
582 * e.g. /root/debian/squeeze/test/module -> /root/debian
583 */
584 function getDistributionPath($fullPath)
585 {
586 $fullPath.='/';
587 while(preg_match("/\//", $fullPath)){
588 $fullPath = preg_replace("/\/[^\/]*$/","", $fullPath);
589 $item = $this->dataModel->getItemByPath($fullPath);
590 if($item['type'] == 'Distribution'){
591 return($fullPath);
592 }
593 }
594 return(NULL);
595 }
598 /*! \brief Extracts the release-part out of a path.
599 * e.g. /root/debian/squeeze/test/module -> squeeze/test
600 */
601 function getReleasePart($path)
602 {
603 $rPath = $this->getReleasePath($path);
604 $dPath = $this->getDistributionPath($path);
605 return(preg_replace("/^".preg_quote($dPath, '/')."\/?/", "", $rPath));
606 }
609 function saveItemChanges()
610 {
611 // Save template engine modifications and validate values.
612 $this->TemplateEngine->save_object();
613 $msgs = $this->TemplateEngine->check();
615 // Get values to be saved
616 $values = array();
617 foreach($this->TemplateEngine->getWidgets() as $w){
618 $values[$w->getName()] = $w->getValue();
619 }
621 // No input error were found, now check that we do not use the same name twice.
622 if(!count($msgs)){
623 $usedNames = $this->getUsedNamesForPath($this->selectedContainer);
625 // Allow the item to keep its name.
626 if($this->currentObject != NULL && isset($this->currentObject['values']['name'])){
627 $usedNames = array_remove_entries(array($this->currentObject['values']['name']), $usedNames);
628 }
629 if(in_array($values['name'],$usedNames)){
630 $msgs[] = msgPool::duplicated(_("Name"));
631 }
632 }
634 // Display errors
635 if(count($msgs)){
636 msg_dialog::displayChecks($msgs);
637 return;
638 }
640 // Get the item type to be saved
641 $item = $this->currentObject;
642 $type = $this->TemplateEngine->getItemType();
643 if($this->cfgItemMap[$type] == 'root'){
645 // We've to create a new distribution
646 if($type == 'Distribution'){
647 $name = $values['name'];
648 $itype = $values['installation_type'];
649 $imethod = $values['installation_method'];
650 $mirror = $values['mirror'];
652 // Initiate the rpc request.
653 $rpc = $this->config->getRpcHandle();
654 $res = $rpc->createDistribution($name, $itype, array('mirror'=>$mirror, 'install_method' => $imethod));
655 if(!$rpc->success()){
656 msg_dialog::display(_("Error"), sprintf(_("Failed to save distributions: %s"), $rpc->get_error()),ERROR_DIALOG);
657 return(NULL);
658 }else{
660 // We've successfully added the item, now add it to the tree.
661 $this->dataModel->addItem($type, $this->selectedContainer, $values['name'],array(), '-' );
663 // Finally - close the dialog.
664 $this->listing->clearDialogObject();
665 }
668 $this->listing->clearDialogObject();
670 }else{
672 echo "{$type} Cannot be saved yet";
673 $this->listing->clearDialogObject();
674 return;
675 }
676 }
678 // Save a CONFIG-ITEM object.
679 if($this->cfgItemMap[$type] != 'root'){
681 // Get paths
682 $release = preg_replace("/^.*\//","", $this->getReleasePath($this->selectedContainer));
683 $newPath = $this->selectedContainer."/".$values['name'];
684 $newItemPath = $this->getItemPath($this->selectedContainer)."/".$values['name'];
685 if($item){
686 $oldPath = $item['path'];
687 $oldItemPath = $this->getItemPath($item['path']);
688 }
690 // If this is a new item, then create it now.
691 if($item == NULL){
693 // Add the new item
694 $rpc = $this->config->getRpcHandle();
695 $res = $rpc->setConfigItem($release, $newItemPath, $type, $values);
696 if(!$rpc->success()){
697 msg_dialog::display(_("Error"), sprintf(_("Failed to load distributions: %s"), $rpc->get_error()),ERROR_DIALOG);
698 return(NULL);
699 }else{
701 // We've successfully added the item, now add it to the tree.
702 $this->dataModel->addItem($type, $this->selectedContainer, $values['name'],array(), '-' );
704 // Finally - close the dialog.
705 $this->listing->clearDialogObject();
706 }
707 }else{
709 // Write the modifications back to the server.
710 $rpc = $this->config->getRpcHandle();
711 $res = $rpc->setConfigItem($release, $oldItemPath, $type, $values);
712 if(!$rpc->success()){
713 msg_dialog::display(_("Error"), sprintf(_("Failed to load distributions: %s"), $rpc->get_error()),ERROR_DIALOG);
714 return(NULL);
715 }else{
717 // Update the data model
718 $this->dataModel->setItemValues($oldPath, $values);
719 if($oldPath != $newPath){
720 $this->dataModel->moveItem($oldPath, $newPath);
721 }
722 $this->listing->clearDialogObject();
723 }
724 }
725 }
726 }
727 function remove_lock() {}
728 }
731 ?>