1 <?php
2 /*
3 * This code is part of GOsa (http://www.gosa-project.org)
4 * Copyright (C) 2003-2008 GONICUS GmbH
5 *
6 * ID: $$Id$$
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 */
23 /*! \brief The plugin base class
24 \author Cajus Pollmeier <pollmeier@gonicus.de>
25 \version 2.00
26 \date 24.07.2003
28 This is the base class for all plugins. It can be used standalone or
29 can be included by the tabs class. All management should be done
30 within this class. Extend your plugins from this class.
31 */
33 class plugin
34 {
35 /*! \brief The title shown in path menu while this plugin is visible.
36 */
37 var $pathTitle = "";
39 /*!
40 \brief Reference to parent object
42 This variable is used when the plugin is included in tabs
43 and keeps reference to the tab class. Communication to other
44 tabs is possible by 'name'. So the 'fax' plugin can ask the
45 'userinfo' plugin for the fax number.
47 \sa tab
48 */
49 var $parent= NULL;
51 /*!
52 \brief Configuration container
54 Access to global configuration
55 */
56 var $config= NULL;
58 /*!
59 \brief Mark plugin as account
61 Defines whether this plugin is defined as an account or not.
62 This has consequences for the plugin to be saved from tab
63 mode. If it is set to 'FALSE' the tab will call the delete
64 function, else the save function. Should be set to 'TRUE' if
65 the construtor detects a valid LDAP object.
67 \sa plugin::plugin()
68 */
69 var $is_account= FALSE;
70 var $initially_was_account= FALSE;
72 /*!
73 \brief Mark plugin as template
75 Defines whether we are creating a template or a normal object.
76 Has conseqences on the way execute() shows the formular and how
77 save() puts the data to LDAP.
79 \sa plugin::save() plugin::execute()
80 */
81 var $is_template= FALSE;
82 var $ignore_account= FALSE;
83 var $is_modified= FALSE;
85 /*!
86 \brief Represent temporary LDAP data
88 This is only used internally.
89 */
90 var $attrs= array();
92 /* Keep set of conflicting plugins */
93 var $conflicts= array();
95 /* Save unit tags */
96 var $gosaUnitTag= "";
97 var $skipTagging= FALSE;
99 /*!
100 \brief Used standard values
102 dn
103 */
104 var $dn= "";
105 var $uid= "";
106 var $sn= "";
107 var $givenName= "";
108 var $acl= "*none*";
109 var $dialog= FALSE;
110 var $snapDialog = NULL;
112 /* attribute list for save action */
113 var $attributes= array();
114 var $objectclasses= array();
115 var $is_new= TRUE;
116 var $saved_attributes= array();
118 var $acl_base= "";
119 var $acl_category= "";
120 var $read_only = FALSE; // Used when the entry is opened as "readonly" due to locks.
122 /* This can be set to render the tabulators in another stylesheet */
123 var $pl_notify= FALSE;
125 /* Object entry CSN */
126 var $entryCSN = "";
127 var $CSN_check_active = FALSE;
129 /* This variable indicates that this class can handle multiple dns at once. */
130 var $multiple_support = FALSE;
131 var $multi_attrs = array();
132 var $multi_attrs_all = array();
134 /* This aviable indicates, that we are currently in multiple edit handle */
135 var $multiple_support_active = FALSE;
136 var $selected_edit_values = array();
137 var $multi_boxes = array();
139 /*! \brief plugin constructor
141 If 'dn' is set, the node loads the given 'dn' from LDAP
143 \param dn Distinguished name to initialize plugin from
144 \sa plugin()
145 */
146 function plugin (&$config, $dn= NULL, $object= NULL)
147 {
149 $this->initTime = microtime(TRUE);
151 /* Configuration is fine, allways */
152 $this->config= &$config;
153 $this->dn= $dn;
155 // Ensure that we've a valid acl_category set.
156 if(empty($this->acl_category)){
157 $tmp = $this->plInfo();
158 if (isset($tmp['plCategory'])) {
159 $c = key($tmp['plCategory']);
160 if(is_numeric($c)){
161 $c = $tmp['plCategory'][0];
162 }
163 $this->acl_category = $c."/";
164 }
165 }
167 // Create statistic table entry
168 stats::log('plugin', $class = get_class($this), $category = array($this->acl_category), $action = 'open',
169 $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
171 /* Handle new accounts, don't read information from LDAP */
172 if ($dn == "new"){
173 return;
174 }
176 /* Check if this entry was opened in read only mode */
177 if(isset($_POST['open_readonly'])){
178 if(session::global_is_set("LOCK_CACHE")){
179 $cache = &session::get("LOCK_CACHE");
180 if(isset($cache['READ_ONLY'][$this->dn])){
181 $this->read_only = TRUE;
182 }
183 }
184 }
186 /* Save current dn as acl_base */
187 $this->acl_base= $dn;
189 /* Get LDAP descriptor */
190 if ($dn !== NULL){
192 /* Load data to 'attrs' and save 'dn' */
193 if ($object !== NULL){
194 $this->attrs= $object->attrs;
195 } else {
196 $ldap= $this->config->get_ldap_link();
197 $ldap->cat ($dn);
198 $this->attrs= $ldap->fetch();
199 }
201 /* Copy needed attributes */
202 foreach ($this->attributes as $val){
203 $found= array_key_ics($val, $this->attrs);
204 if ($found != ""){
205 $this->$val= $found[0];
206 }
207 }
209 /* gosaUnitTag loading... */
210 if (isset($this->attrs['gosaUnitTag'][0])){
211 $this->gosaUnitTag= $this->attrs['gosaUnitTag'][0];
212 }
214 /* Set the template flag according to the existence of objectClass
215 gosaUserTemplate */
216 if (isset($this->attrs['objectClass'])){
217 if (in_array_ics ("gosaUserTemplate", $this->attrs['objectClass'])){
218 $this->is_template= TRUE;
219 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
220 "found", "Template check");
221 }
222 }
224 /* Is Account? */
225 $found= TRUE;
226 foreach ($this->objectclasses as $obj){
227 if (preg_match('/top/i', $obj)){
228 continue;
229 }
230 if (!isset($this->attrs['objectClass']) || !in_array_ics ($obj, $this->attrs['objectClass'])){
231 $found= FALSE;
232 break;
233 }
234 }
235 if ($found){
236 $this->is_account= TRUE;
237 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
238 "found", "Object check");
239 }
241 /* Prepare saved attributes */
242 $this->saved_attributes= $this->attrs;
243 foreach ($this->saved_attributes as $index => $value){
244 if (is_numeric($index)){
245 unset($this->saved_attributes[$index]);
246 continue;
247 }
249 if (!in_array_ics($index, $this->attributes) && strcasecmp('objectClass', $index)){
250 unset($this->saved_attributes[$index]);
251 continue;
252 }
254 if (isset($this->saved_attributes[$index][0])){
255 if(!isset($this->saved_attributes[$index]["count"])){
256 $this->saved_attributes[$index]["count"] = count($this->saved_attributes[$index]);
257 }
258 if($this->saved_attributes[$index]["count"] == 1){
259 $tmp= $this->saved_attributes[$index][0];
260 unset($this->saved_attributes[$index]);
261 $this->saved_attributes[$index]= $tmp;
262 continue;
263 }
264 }
265 unset($this->saved_attributes["$index"]["count"]);
266 }
268 if(isset($this->attrs['gosaUnitTag'])){
269 $this->saved_attributes['gosaUnitTag'] = $this->attrs['gosaUnitTag'][0];
270 }
271 }
273 /* Save initial account state */
274 $this->initially_was_account= $this->is_account;
275 }
278 /*! \brief Generates the html output for this node
279 */
280 function execute()
281 {
282 /* This one is empty currently. Fabian - please fill in the docu code */
283 session::global_set('current_class_for_help',get_class($this));
285 /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
286 session::set('LOCK_VARS_TO_USE',array());
287 session::set('LOCK_VARS_USED_GET',array());
288 session::set('LOCK_VARS_USED_POST',array());
289 session::set('LOCK_VARS_USED_REQUEST',array());
291 pathNavigator::registerPlugin($this);
293 // Create statistic table entry
294 stats::log('plugin', $class = get_class($this), $category = array($this->acl_category), $action = 'view',
295 $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
296 }
298 /*! \brief Removes object from parent
299 */
300 function remove_from_parent()
301 {
302 /* include global link_info */
303 $ldap= $this->config->get_ldap_link();
305 /* Get current objectClasses in order to add the required ones */
306 $ldap->cat($this->dn);
307 $tmp= $ldap->fetch ();
308 $oc= array();
309 if (isset($tmp['objectClass'])){
310 $oc= $tmp['objectClass'];
311 unset($oc['count']);
312 }
314 /* Remove objectClasses from entry */
315 $ldap->cd($this->dn);
316 $this->attrs= array();
317 $this->attrs['objectClass']= array_remove_entries_ics($this->objectclasses,$oc);
319 /* Unset attributes from entry */
320 foreach ($this->attributes as $val){
321 $this->attrs["$val"]= array();
322 }
324 /* Unset account info */
325 $this->is_account= FALSE;
327 /* Do not write in plugin base class, this must be done by
328 children, since there are normally additional attribs,
329 lists, etc. */
330 /*
331 $ldap->modify($this->attrs);
332 */
333 if($this->initially_was_account){
334 $this->handle_pre_events('remove');
336 // Create statistic table entry
337 stats::log('plugin', $class = get_class($this), $category = array($this->acl_category), $action = 'remove',
338 $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
339 }
340 }
343 /*! \brief Save HTML posted data to object
344 */
345 function save_object()
346 {
347 /* Update entry CSN if it is empty. */
348 if(empty($this->entryCSN) && $this->CSN_check_active){
349 $this->entryCSN = getEntryCSN($this->dn);
350 }
352 /* Save values to object */
353 foreach ($this->attributes as $val){
354 if (isset ($_POST["$val"]) && $this->acl_is_writeable($val)){
356 /* Check for modifications */
357 $data= get_post($val);
358 if ($this->$val != $data){
359 $this->is_modified= TRUE;
360 }
361 $this->$val = $data;
363 /* Okay, how can I explain this fix ...
364 * In firefox, disabled option fields aren't selectable ... but in IE you can select these fileds.
365 * So IE posts these 'unselectable' option, with value = chr(194)
366 * chr(194) seems to be the in between the ...option> </option.. because there is no value=".." specified in these option fields
367 * This was added for W3c compliance, but now causes these ... ldap errors ...
368 * So we set these Fields to ""; a normal empty string, and we can check these values in plugin::check() again ...
369 */
370 if(isset($data[0]) && $data[0] == chr(194)) {
371 $data = "";
372 }
373 $this->$val= $data;
374 }
375 }
376 }
379 /*! \brief Save data to LDAP, depending on is_account we save or delete */
380 function save()
381 {
382 /* include global link_info */
383 $ldap= $this->config->get_ldap_link();
385 /* Save all plugins */
386 $this->entryCSN = "";
388 /* Start with empty array */
389 $this->attrs= array();
391 /* Get current objectClasses in order to add the required ones */
392 $ldap->cat($this->dn);
394 $tmp= $ldap->fetch ();
396 $oc= array();
397 if (isset($tmp['objectClass'])){
398 $oc= $tmp["objectClass"];
399 $this->is_new= FALSE;
400 unset($oc['count']);
401 } else {
402 $this->is_new= TRUE;
403 }
405 /* Load (minimum) attributes, add missing ones */
406 $this->attrs['objectClass']= gosa_array_merge($oc,$this->objectclasses);
408 /* Copy standard attributes */
409 foreach ($this->attributes as $val){
410 if ($this->$val != ""){
411 $this->attrs["$val"]= $this->$val;
412 } elseif (!$this->is_new) {
413 $this->attrs["$val"]= array();
414 }
415 }
417 /* Handle tagging */
418 $this->tag_attrs($this->attrs);
420 if($this->is_new){
421 $this->handle_pre_events('add');
423 // Create statistic table entry
424 stats::log('plugin', $class = get_class($this), $category = array($this->acl_category), $action = 'create',
425 $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
426 }else{
427 $this->handle_pre_events('modify');
429 // Create statistic table entry
430 stats::log('plugin', $class = get_class($this), $category = array($this->acl_category), $action = 'modify',
431 $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
432 }
433 }
436 /*! \brief Forward command execution requests
437 * to the hook execution method.
438 */
439 function handle_pre_events($mode, $addAttrs= array())
440 {
441 if(!in_array($mode, array('add','remove','modify'))){
442 trigger_error(sprintf("Invalid pre event type given %s! Valid types are [add,modify,remove].", $mode));
443 return;
444 }
445 switch ($mode){
446 case "add":
447 plugin::callHook($this,"PRECREATE", $addAttrs);
448 break;
450 case "modify":
451 plugin::callHook($this,"PREMODIFY", $addAttrs);
452 break;
454 case "remove":
455 plugin::callHook($this,"PREREMOVE", $addAttrs);
456 break;
457 }
458 }
461 function cleanup()
462 {
463 foreach ($this->attrs as $index => $value){
465 /* Convert arrays with one element to non arrays, if the saved
466 attributes are no array, too */
467 if (is_array($this->attrs[$index]) &&
468 count ($this->attrs[$index]) == 1 &&
469 isset($this->saved_attributes[$index]) &&
470 !is_array($this->saved_attributes[$index])){
472 $tmp= $this->attrs[$index][0];
473 $this->attrs[$index]= $tmp;
474 }
476 /* Remove emtpy arrays if they do not differ */
477 if (is_array($this->attrs[$index]) &&
478 count($this->attrs[$index]) == 0 &&
479 !isset($this->saved_attributes[$index])){
481 unset ($this->attrs[$index]);
482 continue;
483 }
485 /* Remove single attributes that do not differ */
486 if (!is_array($this->attrs[$index]) &&
487 isset($this->saved_attributes[$index]) &&
488 !is_array($this->saved_attributes[$index]) &&
489 $this->attrs[$index] == $this->saved_attributes[$index]){
491 unset ($this->attrs[$index]);
492 continue;
493 }
495 /* Remove arrays that do not differ */
496 if (is_array($this->attrs[$index]) &&
497 isset($this->saved_attributes[$index]) &&
498 is_array($this->saved_attributes[$index])){
500 if (!array_differs($this->attrs[$index],$this->saved_attributes[$index])){
501 unset ($this->attrs[$index]);
502 continue;
503 }
504 }
505 }
507 /* Update saved attributes and ensure that next cleanups will be successful too */
508 foreach($this->attrs as $name => $value){
509 $this->saved_attributes[$name] = $value;
510 }
511 }
513 /*! \brief Check formular input */
514 function check()
515 {
516 $message= array();
518 /* Skip if we've no config object */
519 if (!isset($this->config) || !is_object($this->config)){
520 return $message;
521 }
523 /* Find hooks entries for this class */
524 $command = $this->config->configRegistry->getPropertyValue(get_class($this),"check");
525 if ($command != ""){
527 if (!check_command($command)){
528 $message[]= msgPool::cmdnotfound("CHECK", get_class($this));
529 } else {
531 /* Generate "ldif" for check hook */
532 $ldif= "dn: $this->dn\n";
534 /* ... objectClasses */
535 foreach ($this->objectclasses as $oc){
536 $ldif.= "objectClass: $oc\n";
537 }
539 /* ... attributes */
540 foreach ($this->attributes as $attr){
541 if ($this->$attr == ""){
542 continue;
543 }
544 if (is_array($this->$attr)){
545 foreach ($this->$attr as $val){
546 $ldif.= "$attr: $val\n";
547 }
548 } else {
549 $ldif.= "$attr: ".$this->$attr."\n";
550 }
551 }
553 /* Append empty line */
554 $ldif.= "\n";
556 /* Feed "ldif" into hook and retrieve result*/
557 $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w"));
558 $fh= proc_open($command, $descriptorspec, $pipes);
559 if (is_resource($fh)) {
560 fwrite ($pipes[0], $ldif);
561 fclose($pipes[0]);
563 $result= stream_get_contents($pipes[1]);
564 if ($result != ""){
565 $message[]= $result;
566 }
568 fclose($pipes[1]);
569 fclose($pipes[2]);
570 proc_close($fh);
571 }
572 }
574 }
576 /* Check entryCSN */
577 if($this->CSN_check_active){
578 $current_csn = getEntryCSN($this->dn);
579 if($current_csn != $this->entryCSN && !empty($this->entryCSN) && !empty($current_csn)){
580 $this->entryCSN = $current_csn;
581 $message[] = _("The current object has been altered while beeing edited. If you save this entry, changes that have been made by others will be discarded!");
582 }
583 }
584 return ($message);
585 }
587 /* Adapt from template, using 'dn' */
588 function adapt_from_template($dn, $skip= array())
589 {
590 /* Include global link_info */
591 $ldap= $this->config->get_ldap_link();
593 /* Load requested 'dn' to 'attrs' */
594 $ldap->cat ($dn);
595 $this->attrs= $ldap->fetch();
597 /* Walk through attributes */
598 foreach ($this->attributes as $val){
600 /* Skip the ones in skip list */
601 if (in_array($val, $skip)){
602 continue;
603 }
605 if (isset($this->attrs["$val"][0])){
607 /* If attribute is set, replace dynamic parts:
608 %sn, %givenName and %uid. Fill these in our local variables. */
609 $value= $this->attrs["$val"][0];
611 foreach (array("sn", "givenName", "uid") as $repl){
612 if (preg_match("/%$repl/i", $value)){
613 $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
614 }
615 }
616 $this->$val= $value;
617 }
618 }
620 /* Is Account? */
621 $found= TRUE;
622 foreach ($this->objectclasses as $obj){
623 if (preg_match('/top/i', $obj)){
624 continue;
625 }
626 if (!in_array_ics ($obj, $this->attrs['objectClass'])){
627 $found= FALSE;
628 break;
629 }
630 }
631 if ($found){
632 $this->is_account= TRUE;
633 }
634 }
636 /* \brief Indicate whether a password change is needed or not */
637 function password_change_needed()
638 {
639 return FALSE;
640 }
643 /*! \brief Show header message for tab dialogs */
644 function show_enable_header($button_text, $text, $disabled= FALSE)
645 {
646 if (($disabled == TRUE) || (!$this->acl_is_createable())){
647 $state= "disabled";
648 } else {
649 $state= "";
650 }
651 $display = "<div class='plugin-enable-header'>\n";
652 $display.= "<p>$text</p>\n";
653 $display.= "<button type='submit' name=\"modify_state\" ".$state.">$button_text</button>\n";
654 $display.= "</div>\n";
656 return($display);
657 }
660 /*! \brief Show header message for tab dialogs */
661 function show_disable_header($button_text, $text, $disabled= FALSE)
662 {
663 if (($disabled == TRUE) || !$this->acl_is_removeable()){
664 $state= "disabled";
665 } else {
666 $state= "";
667 }
668 $display = "<div class='plugin-disable-header'>\n";
669 $display.= "<p>$text</p>\n";
670 $display.= "<button type='submit' name=\"modify_state\" ".$state.">$button_text</button>\n";
671 $display.= "</div>\n";
672 return($display);
673 }
677 /* Create unique DN */
678 function create_unique_dn2($data, $base)
679 {
680 $ldap= $this->config->get_ldap_link();
681 $base= preg_replace("/^,*/", "", $base);
683 /* Try to use plain entry first */
684 $dn= "$data,$base";
685 $attribute= preg_replace('/=.*$/', '', $data);
686 $ldap->cat ($dn, array('dn'));
687 if (!$ldap->fetch()){
688 return ($dn);
689 }
691 /* Look for additional attributes */
692 foreach ($this->attributes as $attr){
693 if ($attr == $attribute || $this->$attr == ""){
694 continue;
695 }
697 $dn= "$data+$attr=".$this->$attr.",$base";
698 $ldap->cat ($dn, array('dn'));
699 if (!$ldap->fetch()){
700 return ($dn);
701 }
702 }
704 /* None found */
705 return ("none");
706 }
709 /*! \brief Create unique DN */
710 function create_unique_dn($attribute, $base)
711 {
712 $ldap= $this->config->get_ldap_link();
713 $base= preg_replace("/^,*/", "", $base);
715 /* Try to use plain entry first */
716 $dn= "$attribute=".$this->$attribute.",$base";
717 $ldap->cat ($dn, array('dn'));
718 if (!$ldap->fetch()){
719 return ($dn);
720 }
722 /* Look for additional attributes */
723 foreach ($this->attributes as $attr){
724 if ($attr == $attribute || $this->$attr == ""){
725 continue;
726 }
728 $dn= "$attribute=".$this->$attribute."+$attr=".$this->$attr.",$base";
729 $ldap->cat ($dn, array('dn'));
730 if (!$ldap->fetch()){
731 return ($dn);
732 }
733 }
735 /* None found */
736 return ("none");
737 }
740 function rebind($ldap, $referral)
741 {
742 $credentials= LDAP::get_credentials($referral, $this->config->current['REFERRAL']);
743 if (ldap_bind($ldap, $credentials['ADMIN'], $this->config->get_credentials($credentials['PASSWORD']))) {
744 $this->error = "Success";
745 $this->hascon=true;
746 $this->reconnect= true;
747 return (0);
748 } else {
749 $this->error = "Could not bind to " . $credentials['ADMIN'];
750 return NULL;
751 }
752 }
755 /* Recursively copy ldap object */
756 function _copy($src_dn,$dst_dn)
757 {
758 $ldap=$this->config->get_ldap_link();
759 $ldap->cat($src_dn);
760 $attrs= $ldap->fetch();
762 /* Grummble. This really sucks. PHP ldap doesn't support rdn stuff. */
763 $ds= ldap_connect($this->config->current['SERVER']);
764 ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
765 if (function_exists("ldap_set_rebind_proc") && isset($this->config->current['REFERRAL'])) {
766 ldap_set_rebind_proc($ds, array(&$this, "rebind"));
767 }
769 $pwd = $this->config->get_credentials($this->config->current['ADMINPASSWORD']);
770 $r=ldap_bind($ds,$this->config->current['ADMINDN'], $pwd);
771 $sr=ldap_read($ds, LDAP::fix($src_dn), "objectClass=*");
773 /* Fill data from LDAP */
774 $new= array();
775 if ($sr) {
776 $ei=ldap_first_entry($ds, $sr);
777 if ($ei) {
778 foreach($attrs as $attr => $val){
779 if ($info = @ldap_get_values_len($ds, $ei, $attr)){
780 for ($i= 0; $i<$info['count']; $i++){
781 if ($info['count'] == 1){
782 $new[$attr]= $info[$i];
783 } else {
784 $new[$attr][]= $info[$i];
785 }
786 }
787 }
788 }
789 }
790 }
792 /* close conncetion */
793 ldap_unbind($ds);
795 /* Adapt naming attribute */
796 $dst_name= preg_replace("/^([^=]+)=.*$/", "\\1", $dst_dn);
797 $dst_val = preg_replace("/^[^=]+=([^,+]+).*,.*$/", "\\1", $dst_dn);
798 $new[$dst_name]= LDAP::fix($dst_val);
800 /* Check if this is a department.
801 * If it is a dep. && there is a , override in his ou
802 * change \2C to , again, else this entry can't be saved ...
803 */
804 if((isset($new['ou'])) &&( preg_match("/\\,/",$new['ou']))){
805 $new['ou'] = str_replace("\\\\,",",",$new['ou']);
806 }
808 /* Save copy */
809 $ldap->connect();
810 $ldap->cd($this->config->current['BASE']);
812 $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));
814 /* FAIvariable=.../..., cn=..
815 could not be saved, because the attribute FAIvariable was different to
816 the dn FAIvariable=..., cn=... */
818 if(!is_array($new['objectClass'])) $new['objectClass'] = array($new['objectClass']);
820 if(in_array_ics("FAIdebconfInfo",$new['objectClass'])){
821 $new['FAIvariable'] = $ldap->fix($new['FAIvariable']);
822 }
823 $ldap->cd($dst_dn);
824 $ldap->add($new);
826 if (!$ldap->success()){
827 trigger_error("Trying to save $dst_dn failed.",
828 E_USER_WARNING);
829 return(FALSE);
830 }
831 return(TRUE);
832 }
835 /* This is a workaround function. */
836 function copy($src_dn, $dst_dn)
837 {
838 /* Rename dn in possible object groups */
839 $ldap= $this->config->get_ldap_link();
840 $ldap->search('(&(objectClass=gosaGroupOfNames)(member='.@LDAP::prepare4filter($src_dn).'))',
841 array('cn'));
842 while ($attrs= $ldap->fetch()){
843 $og= new ogroup($this->config, $ldap->getDN());
844 unset($og->member[$src_dn]);
845 $og->member[$dst_dn]= $dst_dn;
846 $og->save ();
847 }
849 $ldap->cat($dst_dn);
850 $attrs= $ldap->fetch();
851 if (count($attrs)){
852 trigger_error("Trying to overwrite ".LDAP::fix($dst_dn).", which already exists.",
853 E_USER_WARNING);
854 return (FALSE);
855 }
857 $ldap->cat($src_dn);
858 $attrs= $ldap->fetch();
859 if (!count($attrs)){
860 trigger_error("Trying to move ".LDAP::fix($src_dn).", which does not seem to exist.",
861 E_USER_WARNING);
862 return (FALSE);
863 }
865 $ldap->cd($src_dn);
866 $ldap->search("objectClass=*",array("dn"));
867 while($attrs = $ldap->fetch()){
868 $src = $attrs['dn'];
869 $dst = preg_replace("/".preg_quote($src_dn, '/')."$/",$dst_dn,$attrs['dn']);
870 $this->_copy($src,$dst);
871 }
872 return (TRUE);
873 }
877 /*! \brief Rename/Move a given src_dn to the given dest_dn
878 *
879 * Move a given ldap object indentified by $src_dn to the
880 * given destination $dst_dn
881 *
882 * - Ensure that all references are updated (ogroups)
883 * - Update ACLs
884 * - Update accessTo
885 *
886 * \param string 'src_dn' the source DN.
887 * \param string 'dst_dn' the destination DN.
888 * \return boolean TRUE on success else FALSE.
889 */
890 function rename($src_dn, $dst_dn)
891 {
892 $start = microtime(1);
894 /* Try to move the source entry to the destination position */
895 $ldap = $this->config->get_ldap_link();
896 $ldap->cd($this->config->current['BASE']);
897 $ldap->create_missing_trees(preg_replace("/^[^,]+,/","",$dst_dn));
898 if (!$ldap->rename_dn($src_dn,$dst_dn)){
899 new log("debug","LDAP protocol v3 implementation error, ldap_rename failed, falling back to manual copy.","FROM: $src_dn -- TO: $dst_dn",array(),$ldap->get_error());
900 @DEBUG(DEBUG_LDAP,__LINE__,__FUNCTION__,__FILE__,"Rename failed FROM: $src_dn -- TO: $dst_dn",
901 "Ldap Protocol v3 implementation error, falling back to maunal method.");
902 return(FALSE);
903 }
905 /* Get list of users,groups and roles within this tree,
906 maybe we have to update ACL references.
907 */
908 $leaf_objs = get_list("(|(objectClass=posixGroup)(objectClass=gosaAccount)(objectClass=gosaRole))",array("all"),$dst_dn,
909 array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
910 foreach($leaf_objs as $obj){
911 $new_dn = $obj['dn'];
912 $old_dn = preg_replace("/".preg_quote(LDAP::convert($dst_dn), '/')."$/i",$src_dn,LDAP::convert($new_dn));
913 $this->update_acls($old_dn,$new_dn);
914 }
916 // Migrate objectgroups if needed
917 $ogroups = get_sub_list("(&(objectClass=gosaGroupOfNames)(member=".LDAP::prepare4filter(LDAP::fix($src_dn))."))",
918 "ogroups", array(get_ou("group", "ogroupRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
920 // Walk through all objectGroups
921 foreach($ogroups as $ogroup){
922 // Migrate old to new dn
923 $o_ogroup= new ogroup($this->config,$ogroup['dn']);
924 if (isset($o_ogroup->member[$src_dn])) {
925 unset($o_ogroup->member[$src_dn]);
926 }
927 $o_ogroup->member[$dst_dn]= $dst_dn;
929 // Save object group
930 $o_ogroup->save();
931 }
933 // Migrate rfc groups if needed
934 $groups = get_sub_list("(&(objectClass=posixGroup)(member=".LDAP::prepare4filter(LDAP::fix($src_dn))."))","groups", array(get_ou("core", "groupRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
936 // Walk through all POSIX groups
937 foreach($groups as $group){
939 // Migrate old to new dn
940 $o_group= new group($this->config,$group['dn']);
941 $o_group->save();
942 }
944 /* Update roles to use the new entry dn */
945 $roles = get_sub_list("(&(objectClass=organizationalRole)(roleOccupant=".LDAP::prepare4filter(LDAP::fix($src_dn))."))","roles", array(get_ou("roleGeneric", "roleRDN")),$this->config->current['BASE'],array("dn"), GL_SUBSEARCH | GL_NO_ACL_CHECK);
947 // Walk through all roles
948 foreach($roles as $role){
949 $role = new roleGeneric($this->config,$role['dn']);
950 $key= array_search($src_dn, $role->roleOccupant);
951 if($key !== FALSE){
952 $role->roleOccupant[$key] = $dst_dn;
953 $role->save();
954 }
955 }
957 // Update 'manager' attributes from gosaDepartment and inetOrgPerson
958 $filter = "(&(objectClass=inetOrgPerson)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn))."))";
959 $ocs = $ldap->get_objectclasses();
960 if(isset($ocs['gosaDepartment']['MAY']) && in_array('manager', $ocs['gosaDepartment']['MAY'])){
961 $filter = "(|".$filter."(&(objectClass=gosaDepartment)(manager=".LDAP::prepare4filter(LDAP::fix($src_dn)).")))";
962 }
963 $leaf_deps= get_list($filter,array("all"),$this->config->current['BASE'],
964 array("manager","dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
965 foreach($leaf_deps as $entry){
966 $update = array('manager' => $dst_dn);
967 $ldap->cd($entry['dn']);
968 $ldap->modify($update);
969 if(!$ldap->success()){
970 trigger_error(sprintf("Failed to update manager for %s: %s", bold($entry['dn']), $ldap->get_error()));
971 }
972 }
974 // Migrate 'dyn-groups' here. labeledURIObject
975 if(class_available('DynamicLdapGroup')) {
976 DynamicLdapGroup::moveDynGroup($this->config,$src_dn,$dst_dn);
977 }
979 /* Check if there are gosa departments moved.
980 If there were deps moved, the force reload of config->deps.
981 */
982 $leaf_deps= get_list("(objectClass=gosaDepartment)",array("all"),$dst_dn,
983 array("dn","objectClass"),GL_SUBSEARCH | GL_NO_ACL_CHECK);
985 if(count($leaf_deps)){
986 $this->config->get_departments();
987 $this->config->make_idepartments();
988 session::global_set("config",$this->config);
989 $ui =get_userinfo();
990 $ui->reset_acl_cache();
991 }
993 return(TRUE);
994 }
998 function move($src_dn, $dst_dn)
999 {
1000 /* Do not copy if only upper- lowercase has changed */
1001 if(strtolower($src_dn) == strtolower($dst_dn)){
1002 return(TRUE);
1003 }
1005 // Create statistic table entry
1006 stats::log('plugin', $class = get_class($this), $category = array($this->acl_category), $action = 'move',
1007 $amount = 1, $duration = (microtime(TRUE) - $this->initTime));
1009 /* Try to move the entry instead of copy & delete
1010 */
1011 if(TRUE){
1013 /* Try to move with ldap routines, if this was not successfull
1014 fall back to the old style copy & remove method
1015 */
1016 if($this->rename($src_dn, $dst_dn)){
1017 return(TRUE);
1018 }else{
1019 // See code below.
1020 }
1021 }
1023 /* Copy source to destination */
1024 if (!$this->copy($src_dn, $dst_dn)){
1025 return (FALSE);
1026 }
1028 /* Delete source */
1029 $ldap= $this->config->get_ldap_link();
1030 $ldap->rmdir_recursive($src_dn);
1031 if (!$ldap->success()){
1032 trigger_error("Trying to delete $src_dn failed.",
1033 E_USER_WARNING);
1034 return (FALSE);
1035 }
1037 return (TRUE);
1038 }
1041 /* \brief Move/Rename complete trees */
1042 function recursive_move($src_dn, $dst_dn)
1043 {
1044 /* Check if the destination entry exists */
1045 $ldap= $this->config->get_ldap_link();
1047 /* Check if destination exists - abort */
1048 $ldap->cat($dst_dn, array('dn'));
1049 if ($ldap->fetch()){
1050 trigger_error("recursive_move $dst_dn already exists.",
1051 E_USER_WARNING);
1052 return (FALSE);
1053 }
1055 $this->copy($src_dn, $dst_dn);
1057 /* Remove src_dn */
1058 $ldap->cd($src_dn);
1059 $ldap->recursive_remove($src_dn);
1060 return (TRUE);
1061 }
1064 function saveCopyDialog(){
1065 }
1068 function getCopyDialog(){
1069 return(array("string"=>"","status"=>""));
1070 }
1073 /*! \brief Prepare for Copy & Paste */
1074 function PrepareForCopyPaste($source)
1075 {
1076 $todo = $this->attributes;
1077 if(isset($this->CopyPasteVars)){
1078 $todo = array_merge($todo,$this->CopyPasteVars);
1079 }
1081 if(count($this->objectclasses)){
1082 $this->is_account = TRUE;
1083 foreach($this->objectclasses as $class){
1084 if(!in_array($class,$source['objectClass'])){
1085 $this->is_account = FALSE;
1086 }
1087 }
1088 }
1090 foreach($todo as $var){
1091 if (isset($source[$var])){
1092 if(isset($source[$var]['count'])){
1093 if($source[$var]['count'] > 1){
1094 $tmp= $source[$var];
1095 unset($tmp['count']);
1096 $this->$var = $tmp;
1097 }else{
1098 $this->$var = $source[$var][0];
1099 }
1100 }else{
1101 $this->$var= $source[$var];
1102 }
1103 }
1104 }
1105 }
1107 /*! \brief Get gosaUnitTag for the given DN
1108 If this is called from departmentGeneric, we have to skip this
1109 tagging procedure.
1110 */
1111 function tag_attrs(&$at, $dn= "", $tag= "", $show= false)
1112 {
1113 /* Skip tagging? */
1114 if($this->skipTagging){
1115 return;
1116 }
1118 /* No dn? Self-operation... */
1119 if ($dn == ""){
1120 $dn= $this->dn;
1122 /* No tag? Find it yourself... */
1123 if ($tag == ""){
1124 $len= strlen($dn);
1126 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "No tag for $dn - looking for one...", "Tagging");
1127 $relevant= array();
1128 foreach ($this->config->adepartments as $key => $ntag){
1130 /* This one is bigger than our dn, its not relevant... */
1131 if ($len < strlen($key)){
1132 continue;
1133 }
1135 /* This one matches with the latter part. Break and don't fix this entry */
1136 if (preg_match('/(^|,)'.preg_quote($key, '/').'$/', $dn)){
1137 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, "DEBUG: Possibly relevant: $key", "Tagging");
1138 $relevant[strlen($key)]= $ntag;
1139 continue;
1140 }
1142 }
1144 /* If we've some relevant tags to set, just get the longest one */
1145 if (count($relevant)){
1146 ksort($relevant);
1147 $tmp= array_keys($relevant);
1148 $idx= end($tmp);
1149 $tag= $relevant[$idx];
1150 $this->gosaUnitTag= $tag;
1151 }
1152 }
1153 }
1155 /*! \brief Add unit tag */
1156 /* Remove tags that may already be here... */
1157 remove_objectClass("gosaAdministrativeUnitTag", $at);
1158 if (isset($at['gosaUnitTag'])){
1159 unset($at['gosaUnitTag']);
1160 }
1162 /* Set tag? */
1163 if ($tag != ""){
1164 add_objectClass("gosaAdministrativeUnitTag", $at);
1165 $at['gosaUnitTag']= $tag;
1166 }
1168 /* Initially this object was tagged.
1169 - But now, it is no longer inside a tagged department.
1170 So force the remove of the tag.
1171 (objectClass was already removed obove)
1172 */
1173 if($tag == "" && $this->gosaUnitTag){
1174 $at['gosaUnitTag'] = array();
1175 }
1176 }
1179 /*! \brief Test for removability of the object
1180 *
1181 * Allows testing of conditions for removal of object. If removal should be aborted
1182 * the function needs to remove an error message.
1183 * */
1184 function allow_remove()
1185 {
1186 $reason= "";
1187 return $reason;
1188 }
1191 /*! \brief Test if snapshotting is enabled
1192 *
1193 * Test weither snapshotting is enabled or not. There will also be some errors posted,
1194 * if the configuration failed
1195 * \return TRUE if snapshots are enabled, and FALSE if it is disabled
1196 */
1197 function snapshotEnabled()
1198 {
1199 return $this->config->snapshotEnabled();
1200 }
1203 /*! \brief Return plugin informations for acl handling
1204 * See class_core.inc for examples.
1205 */
1206 static function plInfo()
1207 {
1208 return array();
1209 }
1212 function set_acl_base($base)
1213 {
1214 @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,"<b>".$base."</b>","<b>ACL-Base:</b> ");
1215 $this->acl_base= $base;
1216 }
1219 function set_acl_category($category)
1220 {
1221 @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,"<b>".$category."</b>(/".get_class($this).")","<b>ACL-Category:</b> ");
1222 $this->acl_category= "$category/";
1223 }
1226 function acl_is_writeable($attribute,$skip_write = FALSE)
1227 {
1228 if($this->read_only) return(FALSE);
1229 $ui= get_userinfo();
1230 return preg_match('/w/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute, $skip_write));
1231 }
1234 function acl_is_readable($attribute)
1235 {
1236 $ui= get_userinfo();
1237 return preg_match('/r/', $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute));
1238 }
1241 function acl_is_createable($base ="")
1242 {
1243 if($this->read_only) return(FALSE);
1244 $ui= get_userinfo();
1245 if($base == "") $base = $this->acl_base;
1246 return preg_match('/c/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1247 }
1250 function acl_is_removeable($base ="")
1251 {
1252 if($this->read_only) return(FALSE);
1253 $ui= get_userinfo();
1254 if($base == "") $base = $this->acl_base;
1255 return preg_match('/d/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1256 }
1259 function acl_is_moveable($base = "")
1260 {
1261 if($this->read_only) return(FALSE);
1262 $ui= get_userinfo();
1263 if($base == "") $base = $this->acl_base;
1264 return preg_match('/m/', $ui->get_permissions($base, $this->acl_category.get_class($this), '0'));
1265 }
1268 function acl_have_any_permissions()
1269 {
1270 }
1273 function getacl($attribute,$skip_write= FALSE)
1274 {
1275 $ui= get_userinfo();
1276 $skip_write |= $this->read_only;
1277 return $ui->get_permissions($this->acl_base, $this->acl_category.get_class($this), $attribute,$skip_write);
1278 }
1281 /*! \brief Returns a list of all available departments for this object.
1282 *
1283 * If this object is new, all departments we are allowed to create a new user in
1284 * are returned. If this is an existing object, return all deps.
1285 * We are allowed to move tis object too.
1286 * \return array [dn] => "..name" // All deps. we are allowed to act on.
1287 */
1288 function get_allowed_bases()
1289 {
1290 $ui = get_userinfo();
1291 $deps = array();
1293 /* Is this a new object ? Or just an edited existing object */
1294 if(!$this->initially_was_account && $this->is_account){
1295 $new = true;
1296 }else{
1297 $new = false;
1298 }
1300 foreach($this->config->idepartments as $dn => $name){
1301 if($new && $this->acl_is_createable($dn)){
1302 $deps[$dn] = $name;
1303 }elseif(!$new && $this->acl_is_moveable($dn)){
1304 $deps[$dn] = $name;
1305 }
1306 }
1308 /* Add current base */
1309 if(isset($this->base) && isset($this->config->idepartments[$this->base])){
1310 $deps[$this->base] = $this->config->idepartments[$this->base];
1311 }elseif(strtolower($this->dn) == strtolower($this->config->current['BASE'])){
1313 }else{
1314 trigger_error("Cannot return list of departments, no default base found in class ".get_class($this).". ".$this->base);
1315 }
1316 return($deps);
1317 }
1320 /* This function updates ACL settings if $old_dn was used.
1321 * \param string 'old_dn' specifies the actually used dn
1322 * \param string 'new_dn' specifies the destiantion dn
1323 */
1324 function update_acls($old_dn,$new_dn,$output_changes = FALSE)
1325 {
1326 /* Check if old_dn is empty. This should never happen */
1327 if(empty($old_dn) || empty($new_dn)){
1328 trigger_error("Failed to check acl dependencies, wrong dn given.");
1329 return;
1330 }
1332 /* Update userinfo if necessary */
1333 $ui = session::global_get('ui');
1334 if($ui->dn == $old_dn){
1335 $ui->dn = $new_dn;
1336 session::global_set('ui',$ui);
1337 new log("view","acl/".get_class($this),$this->dn,array(),"Updated current object dn from '".$old_dn."' to '".$new_dn."'");
1338 }
1340 /* Object was moved, ensure that all acls will be moved too */
1341 if($new_dn != $old_dn && $old_dn != "new"){
1343 /* get_ldap configuration */
1344 $update = array();
1345 $ldap = $this->config->get_ldap_link();
1346 $ldap->cd ($this->config->current['BASE']);
1347 $ldap->search("(&(objectClass=gosaAcl)(gosaAclEntry=*".base64_encode($old_dn)."*))",array("cn","gosaAclEntry"));
1348 while($attrs = $ldap->fetch()){
1349 $acls = array();
1350 $found = false;
1351 for($i = 0 ; $i < $attrs['gosaAclEntry']['count'] ; $i ++ ){
1352 $acl_parts = explode(":",$attrs['gosaAclEntry'][$i]);
1354 /* Roles uses antoher data storage order, members are stored int the third part,
1355 while the members in direct ACL assignments are stored in the second part.
1356 */
1357 $id = ($acl_parts[1] == "role") ? 3 : 2;
1359 /* Update member entries to use $new_dn instead of old_dn
1360 */
1361 $members = explode(",",$acl_parts[$id]);
1362 foreach($members as $key => $member){
1363 $member = base64_decode($member);
1364 if($member == $old_dn){
1365 $members[$key] = base64_encode($new_dn);
1366 $found = TRUE;
1367 }
1368 }
1370 /* Check if the selected role has to updated
1371 */
1372 if($acl_parts[1] == "role" && $acl_parts[2] == base64_encode($old_dn)){
1373 $acl_parts[2] = base64_encode($new_dn);
1374 $found = TRUE;
1375 }
1377 /* Build new acl string */
1378 $acl_parts[$id] = implode($members,",");
1379 $acls[] = implode($acl_parts,":");
1380 }
1382 /* Acls for this object must be adjusted */
1383 if($found){
1385 $debug_info= sprintf(_("Changing ACL DN from %s to %s"), bold($old_dn), bold($new_dn));
1386 @DEBUG (DEBUG_ACL, __LINE__, __FUNCTION__, __FILE__,$debug_info,"ACL");
1388 $update[$attrs['dn']] =array();
1389 foreach($acls as $acl){
1390 $update[$attrs['dn']]['gosaAclEntry'][] = $acl;
1391 }
1392 }
1393 }
1395 /* Write updated acls */
1396 foreach($update as $dn => $attrs){
1397 $ldap->cd($dn);
1398 $ldap->modify($attrs);
1399 }
1400 }
1401 }
1405 /*! \brief Enable the Serial ID check
1406 *
1407 * This function enables the entry Serial ID check. If an entry was edited while
1408 * we have edited the entry too, an error message will be shown.
1409 * To configure this check correctly read the FAQ.
1410 */
1411 function enable_CSN_check()
1412 {
1413 $this->CSN_check_active =TRUE;
1414 $this->entryCSN = getEntryCSN($this->dn);
1415 }
1418 /*! \brief Prepares the plugin to be used for multiple edit
1419 * Update plugin attributes with given array of attribtues.
1420 * \param array Array with attributes that must be updated.
1421 */
1422 function init_multiple_support($attrs,$all)
1423 {
1424 $ldap= $this->config->get_ldap_link();
1425 $this->multi_attrs = $attrs;
1426 $this->multi_attrs_all= $all;
1428 /* Copy needed attributes */
1429 foreach ($this->attributes as $val){
1430 $found= array_key_ics($val, $this->multi_attrs);
1432 if ($found != ""){
1433 if(isset($this->multi_attrs["$val"][0])){
1434 $this->$val= $this->multi_attrs["$val"][0];
1435 }
1436 }
1437 }
1438 }
1441 /*! \brief Enables multiple support for this plugin
1442 */
1443 function enable_multiple_support()
1444 {
1445 $this->ignore_account = TRUE;
1446 $this->multiple_support_active = TRUE;
1447 }
1450 /*! \brief Returns all values that have been modfied in multiple edit mode.
1451 \return array Cotaining all modified values.
1452 */
1453 function get_multi_edit_values()
1454 {
1455 $ret = array();
1456 foreach($this->attributes as $attr){
1457 if(in_array($attr,$this->multi_boxes)){
1458 $ret[$attr] = $this->$attr;
1459 }
1460 }
1461 return($ret);
1462 }
1465 /*! \brief Update class variables with values collected by multiple edit.
1466 */
1467 function set_multi_edit_values($attrs)
1468 {
1469 foreach($attrs as $name => $value){
1470 $this->$name = $value;
1471 }
1472 }
1475 /*! \brief Generates the html output for this node for multi edit*/
1476 function multiple_execute()
1477 {
1478 /* This one is empty currently. Fabian - please fill in the docu code */
1479 session::global_set('current_class_for_help',get_class($this));
1481 /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
1482 session::set('LOCK_VARS_TO_USE',array());
1483 session::set('LOCK_VARS_USED_GET',array());
1484 session::set('LOCK_VARS_USED_POST',array());
1485 session::set('LOCK_VARS_USED_REQUEST',array());
1487 return("Multiple edit is currently not implemented for this plugin.");
1488 }
1491 /*! \brief Save HTML posted data to object for multiple edit
1492 */
1493 function multiple_save_object()
1494 {
1495 if(empty($this->entryCSN) && $this->CSN_check_active){
1496 $this->entryCSN = getEntryCSN($this->dn);
1497 }
1499 /* Save values to object */
1500 $this->multi_boxes = array();
1501 foreach ($this->attributes as $val){
1503 /* Get selected checkboxes from multiple edit */
1504 if(isset($_POST["use_".$val])){
1505 $this->multi_boxes[] = $val;
1506 }
1508 if (isset ($_POST["$val"]) && $this->acl_is_writeable($val)){
1510 $data= $this->$val = get_post($val);
1511 if ($this->$val != $data){
1512 $this->is_modified= TRUE;
1513 }
1515 /* IE post fix */
1516 if(isset($data[0]) && $data[0] == chr(194)) {
1517 $data = "";
1518 }
1519 $this->$val= $data;
1520 }
1521 }
1522 }
1525 /*! \brief Returns all attributes of this plugin,
1526 to be able to detect multiple used attributes
1527 in multi_plugg::detect_multiple_used_attributes().
1528 @return array Attributes required for intialization of multi_plug
1529 */
1530 public function get_multi_init_values()
1531 {
1532 $attrs = $this->attrs;
1533 return($attrs);
1534 }
1537 /*! \brief Check given values in multiple edit
1538 \return array Error messages
1539 */
1540 function multiple_check()
1541 {
1542 $message = plugin::check();
1543 return($message);
1544 }
1546 function get_used_snapshot_bases()
1547 {
1548 return(array());
1549 }
1551 function is_modal_dialog()
1552 {
1553 return(isset($this->dialog) && $this->dialog);
1554 }
1557 /*! \brief Forward command execution requests
1558 * to the hook execution method.
1559 */
1560 function handle_post_events($mode, $addAttrs= array())
1561 {
1562 if(!in_array($mode, array('add','remove','modify'))){
1563 trigger_error(sprintf("Invalid post event type given %s! Valid types are [add,modify,remove].", bold($mode)));
1564 return;
1565 }
1566 switch ($mode){
1567 case "add":
1568 plugin::callHook($this,"POSTCREATE", $addAttrs);
1569 break;
1571 case "modify":
1572 plugin::callHook($this,"POSTMODIFY", $addAttrs);
1573 break;
1575 case "remove":
1576 plugin::callHook($this,"POSTREMOVE", $addAttrs);
1577 break;
1578 }
1579 }
1582 /*! \brief Calls external hooks which are defined for this plugin (gosa.conf)
1583 * Replaces placeholder by class values of this plugin instance.
1584 * @param Allows to a add special replacements.
1585 */
1586 static function callHook($plugin, $cmd, $addAttrs= array(), &$returnOutput = array(), &$returnCode = NULL)
1587 {
1588 global $config;
1589 $command = $config->configRegistry->getPropertyValue(get_class($plugin),$cmd);
1591 if ($command != ""){
1593 // Walk trough attributes list and add the plugins attributes.
1594 foreach ($plugin->attributes as $attr){
1595 if (!is_array($plugin->$attr)){
1596 $addAttrs[$attr] = $plugin->$attr;
1597 }
1598 }
1599 $ui = get_userinfo();
1600 $addAttrs['callerDN']=$ui->dn;
1601 $addAttrs['dn']=$plugin->dn;
1602 $addAttrs['location']=$config->current['NAME'];
1604 // Sort attributes by length, ensures correct replacement
1605 $tmp = array();
1606 foreach($addAttrs as $name => $value){
1607 $tmp[$name] = strlen($name);
1608 }
1609 arsort($tmp);
1611 // Now replace the placeholder
1612 foreach ($tmp as $name => $len){
1613 $value = $addAttrs[$name];
1614 $command= str_replace("%$name", "$value", $command);
1615 }
1617 // If there are still some %.. in our command, try to fill these with some other class vars
1618 if(preg_match("/%/",$command)){
1619 $attrs = get_object_vars($plugin);
1620 foreach($attrs as $name => $value){
1621 if(is_array($value)){
1622 $s = "";
1623 foreach($value as $val){
1624 if(is_string($val) || is_int($val) || is_float($val) || is_bool($val)){
1625 $s .= '"'.$val.'",';
1626 }
1627 }
1628 $value = '['.trim($s,',').']';
1629 }
1630 if(!is_string($value) && !is_int($value) && !is_float($value) && !is_bool($value)){
1631 continue;
1632 }
1633 $command= preg_replace("/%$name/", $value, $command);
1634 }
1635 }
1637 if (check_command($command)){
1639 @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,$command,"Execute");
1640 exec($command, $arr, $returnCode);
1641 $returnOutput = $arr;
1643 if($returnCode != 0){
1644 $str = implode("\n",$arr);
1645 @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Execution failed code: ".$returnCode);
1646 $message= msgPool::cmdexecfailed($cmd,$command, get_class($plugin));
1647 msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
1648 }elseif(is_array($arr)){
1649 $str = implode("\n",$arr);
1650 @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Result: ".$str);
1651 }
1652 } else {
1653 $message= msgPool::cmdinvalid($cmd,$command, get_class($plugin));
1654 msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
1655 }
1656 }
1657 }
1658 }
1660 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1661 ?>