1 <?php
2 /*
3 This code is part of GOsa (https://gosa.gonicus.de)
4 Copyright (C) 2003 Cajus Pollmeier
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
21 /*! \brief The plugin base class
22 \author Cajus Pollmeier <pollmeier@gonicus.de>
23 \version 2.00
24 \date 24.07.2003
26 This is the base class for all plugins. It can be used standalone or
27 can be included by the tabs class. All management should be done
28 within this class. Extend your plugins from this class.
29 */
31 class plugin
32 {
33 /*!
34 \brief Reference to parent object
36 This variable is used when the plugin is included in tabs
37 and keeps reference to the tab class. Communication to other
38 tabs is possible by 'name'. So the 'fax' plugin can ask the
39 'userinfo' plugin for the fax number.
41 \sa tab
42 */
43 var $parent= NULL;
45 /*!
46 \brief Configuration container
48 Access to global configuration
49 */
50 var $config= NULL;
52 /*!
53 \brief Mark plugin as account
55 Defines whether this plugin is defined as an account or not.
56 This has consequences for the plugin to be saved from tab
57 mode. If it is set to 'FALSE' the tab will call the delete
58 function, else the save function. Should be set to 'TRUE' if
59 the construtor detects a valid LDAP object.
61 \sa plugin::plugin()
62 */
63 var $is_account= FALSE;
64 var $initially_was_account= FALSE;
66 /*!
67 \brief Mark plugin as template
69 Defines whether we are creating a template or a normal object.
70 Has conseqences on the way execute() shows the formular and how
71 save() puts the data to LDAP.
73 \sa plugin::save() plugin::execute()
74 */
75 var $is_template= FALSE;
76 var $ignore_account= FALSE;
77 var $is_modified= FALSE;
79 /*!
80 \brief Represent temporary LDAP data
82 This is only used internally.
83 */
84 var $attrs= array();
87 /*!
88 \brief Used standard values
90 dn
91 */
92 var $dn= "";
93 var $uid= "";
94 var $sn= "";
95 var $givenName= "";
96 var $acl= "*none*";
97 var $dialog= FALSE;
99 /* attribute list for save action */
100 var $attributes= array();
101 var $objectclasses= array();
102 var $new= TRUE;
103 var $saved_attributes= array();
105 /*! \brief plugin constructor
107 If 'dn' is set, the node loads the given 'dn' from LDAP
109 \param dn Distinguished name to initialize plugin from
110 \sa plugin()
111 */
112 function plugin ($config, $dn= NULL)
113 {
114 /* Configuration is fine, allways */
115 $this->config= $config;
116 $this->dn= $dn;
118 /* Handle new accounts, don't read information from LDAP */
119 if ($dn == "new"){
120 return;
121 }
123 /* Get LDAP descriptor */
124 $ldap= $this->config->get_ldap_link();
125 if ($dn != NULL){
127 /* Load data to 'attrs' and save 'dn' */
128 $ldap->cat ($dn);
129 $this->attrs= $ldap->fetch();
131 /* Copy needed attributes */
132 foreach ($this->attributes as $val){
133 #if (isset($this->attrs["$val"][0])){
134 $found= array_key_ics($val, $this->attrs);
135 if ($found != ""){
136 $this->$val= $this->attrs["$found"][0];
137 }
138 }
140 /* Set the template flag according to the existence of objectClass
141 gosaUserTemplate */
142 if (isset($this->attrs['objectClass'])){
143 if (in_array ("gosaUserTemplate", $this->attrs['objectClass'])){
144 $this->is_template= TRUE;
145 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
146 "found", "Template check");
147 }
148 }
150 /* Is Account? */
151 error_reporting(0);
152 $found= TRUE;
153 foreach ($this->objectclasses as $obj){
154 if (preg_match('/top/i', $obj)){
155 continue;
156 }
157 if (!isset($this->attrs['objectClass']) || !in_array_ics ($obj, $this->attrs['objectClass'])){
158 $found= FALSE;
159 break;
160 }
161 }
162 error_reporting(E_ALL);
163 if ($found){
164 $this->is_account= TRUE;
165 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__,
166 "found", "Object check");
167 }
169 /* Prepare saved attributes */
170 $this->saved_attributes= $this->attrs;
171 foreach ($this->saved_attributes as $index => $value){
172 if (preg_match('/^[0-9]+$/', $index)){
173 unset($this->saved_attributes[$index]);
174 continue;
175 }
176 if (!in_array($index, $this->attributes) && $index != "objectClass"){
177 unset($this->saved_attributes[$index]);
178 continue;
179 }
180 if ($this->saved_attributes[$index]["count"] == 1){
181 $tmp= $this->saved_attributes[$index][0];
182 unset($this->saved_attributes[$index]);
183 $this->saved_attributes[$index]= $tmp;
184 continue;
185 }
187 unset($this->saved_attributes["$index"]["count"]);
188 }
189 }
191 /* Save initial account state */
192 $this->initially_was_account= $this->is_account;
193 }
195 /*! \brief execute plugin
197 Generates the html output for this node
198 */
199 function execute()
200 {
201 # This one is empty currently. Fabian - please fill in the docu code
202 $_SESSION['current_class_for_help'] = get_class($this);
203 /* Reset Lock message POST/GET check array, to prevent perg_match errors*/
204 $_SESSION['LOCK_VARS_TO_USE'] =array();
205 }
207 /* remove object from parent */
208 function remove_from_parent()
209 {
210 /* include global link_info */
211 $ldap= $this->config->get_ldap_link();
213 /* Get current objectClasses in order to add the required ones */
214 $ldap->cat($this->dn);
215 $tmp= $ldap->fetch ();
216 if (isset($tmp['objectClass'])){
217 $oc= $tmp['objectClass'];
218 } else {
219 $oc= array("count" => 0);
220 }
222 /* Remove objectClasses from entry */
223 $ldap->cd($this->dn);
224 $this->attrs= array();
225 $this->attrs['objectClass']= array();
226 for ($i= 0; $i<$oc["count"]; $i++){
227 if (!in_array_ics($oc[$i], $this->objectclasses)){
228 $this->attrs['objectClass'][]= $oc[$i];
229 }
230 }
232 /* Unset attributes from entry */
233 foreach ($this->attributes as $val){
234 $this->attrs["$val"]= array();
235 }
237 /* Unset account info */
238 $this->is_account= FALSE;
240 /* Do not write in plugin base class, this must be done by
241 children, since there are normally additional attribs,
242 lists, etc. */
243 /*
244 $ldap->modify($this->attrs);
245 */
246 }
249 /* Save data to object */
250 function save_object()
251 {
252 /* Save values to object */
253 foreach ($this->attributes as $val){
254 if (chkacl ($this->acl, "$val") == "" && isset ($_POST["$val"])){
255 /* Check for modifications */
256 if (get_magic_quotes_gpc()) {
257 $data= stripcslashes($_POST["$val"]);
258 } else {
259 $data= $this->$val = $_POST["$val"];
260 }
261 if ($this->$val != $data){
262 $this->is_modified= TRUE;
263 }
265 /* Okay, how can I explain this fix ...
266 * In firefox, disabled option fields aren't selectable ... but in IE you can select these fileds.
267 * So IE posts these 'unselectable' option, with value = chr(194)
268 * chr(194) seems to be the in between the ...option> </option.. because there is no value=".." specified in these option fields
269 * This was added for W3c compliance, but now causes these ... ldap errors ...
270 * So we set these Fields to ""; a normal empty string, and we can check these values in plugin::check() again ...
271 */
272 if(isset($data[0]) && $data[0] == chr(194)) {
273 $data = "";
274 }
275 $this->$val= $data;
276 }
277 }
278 }
281 /* Save data to LDAP, depending on is_account we save or delete */
282 function save()
283 {
284 /* include global link_info */
285 $ldap= $this->config->get_ldap_link();
287 /* Start with empty array */
288 $this->attrs= array();
290 /* Get current objectClasses in order to add the required ones */
291 $ldap->cat($this->dn);
293 $tmp= $ldap->fetch ();
295 if (isset($tmp['objectClass'])){
296 $oc= $tmp["objectClass"];
297 $this->new= FALSE;
298 } else {
299 $oc= array("count" => 0);
300 $this->new= TRUE;
301 }
303 /* Load (minimum) attributes, add missing ones */
304 $this->attrs['objectClass']= $this->objectclasses;
305 for ($i= 0; $i<$oc["count"]; $i++){
306 if (!in_array_ics($oc[$i], $this->objectclasses)){
307 $this->attrs['objectClass'][]= $oc[$i];
308 }
309 }
311 /* Copy standard attributes */
312 foreach ($this->attributes as $val){
313 if ($this->$val != ""){
314 $this->attrs["$val"]= $this->$val;
315 } elseif (!$this->new) {
316 $this->attrs["$val"]= array();
317 }
318 }
320 }
323 function cleanup()
324 {
325 foreach ($this->attrs as $index => $value){
327 /* Convert arrays with one element to non arrays, if the saved
328 attributes are no array, too */
329 if (is_array($this->attrs[$index]) &&
330 count ($this->attrs[$index]) == 1 &&
331 isset($this->saved_attributes[$index]) &&
332 !is_array($this->saved_attributes[$index])){
334 $tmp= $this->attrs[$index][0];
335 $this->attrs[$index]= $tmp;
336 }
338 /* Remove emtpy arrays if they do not differ */
339 if (is_array($this->attrs[$index]) &&
340 count($this->attrs[$index]) == 0 &&
341 !isset($this->saved_attributes[$index])){
343 unset ($this->attrs[$index]);
344 continue;
345 }
347 /* Remove single attributes that do not differ */
348 if (!is_array($this->attrs[$index]) &&
349 isset($this->saved_attributes[$index]) &&
350 !is_array($this->saved_attributes[$index]) &&
351 $this->attrs[$index] == $this->saved_attributes[$index]){
353 unset ($this->attrs[$index]);
354 continue;
355 }
357 /* Remove arrays that do not differ */
358 if (is_array($this->attrs[$index]) &&
359 isset($this->saved_attributes[$index]) &&
360 is_array($this->saved_attributes[$index])){
362 if (!array_differs($this->attrs[$index],$this->saved_attributes[$index])){
363 unset ($this->attrs[$index]);
364 continue;
365 }
366 }
367 }
368 }
370 /* Check formular input */
371 function check()
372 {
373 $message= array();
375 /* Skip if we've no config object */
376 if (!isset($this->config)){
377 return $message;
378 }
380 /* Find hooks entries for this class */
381 $command= search_config($this->config->data['MENU'], get_class($this), "CHECK");
382 if ($command == "" && isset($this->config->data['TABS'])){
383 $command= search_config($this->config->data['TABS'], get_class($this), "CHECK");
384 }
386 if ($command != ""){
388 if (!check_command($command)){
389 $message[]= sprintf(_("Command '%s', specified as CHECK hook for plugin '%s' doesn't seem to exist."), $command,
390 get_class($this));
391 } else {
393 /* Generate "ldif" for check hook */
394 $ldif= "dn: $this->dn\n";
396 /* ... objectClasses */
397 foreach ($this->objectclasses as $oc){
398 $ldif.= "objectClass: $oc\n";
399 }
401 /* ... attributes */
402 foreach ($this->attributes as $attr){
403 if ($this->$attr == ""){
404 continue;
405 }
406 if (is_array($this->$attr)){
407 foreach ($this->$attr as $val){
408 $ldif.= "$attr: $val\n";
409 }
410 } else {
411 $ldif.= "$attr: ".$this->$attr."\n";
412 }
413 }
415 /* Append empty line */
416 $ldif.= "\n";
418 /* Feed "ldif" into hook and retrieve result*/
419 $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w"));
420 $fh= proc_open($command, $descriptorspec, $pipes);
421 if (is_resource($fh)) {
422 fwrite ($pipes[0], $ldif);
423 fclose($pipes[0]);
425 $result= stream_get_contents($pipes[1]);
426 if ($result != ""){
427 $message[]= $result;
428 }
430 fclose($pipes[1]);
431 fclose($pipes[2]);
432 proc_close($fh);
433 }
434 }
436 }
438 return ($message);
439 }
441 /* Adapt from template, using 'dn' */
442 function adapt_from_template($dn)
443 {
444 /* Include global link_info */
445 $ldap= $this->config->get_ldap_link();
447 /* Load requested 'dn' to 'attrs' */
448 $ldap->cat ($dn);
449 $this->attrs= $ldap->fetch();
451 /* Walk through attributes */
452 foreach ($this->attributes as $val){
454 if (isset($this->attrs["$val"][0])){
456 /* If attribute is set, replace dynamic parts:
457 %sn, %givenName and %uid. Fill these in our local variables. */
458 $value= $this->attrs["$val"][0];
460 foreach (array("sn", "givenName", "uid") as $repl){
461 if (preg_match("/%$repl/i", $value)){
462 $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
463 }
464 }
465 $this->$val= $value;
466 }
467 }
469 /* Is Account? */
470 $found= TRUE;
471 foreach ($this->objectclasses as $obj){
472 if (preg_match('/top/i', $obj)){
473 continue;
474 }
475 if (!in_array_ics ($obj, $this->attrs['objectClass'])){
476 $found= FALSE;
477 break;
478 }
479 }
480 if ($found){
481 $this->is_account= TRUE;
482 }
483 }
485 /* Indicate whether a password change is needed or not */
486 function password_change_needed()
487 {
488 return FALSE;
489 }
491 /* Show header message for tab dialogs */
492 function show_header($button_text, $text, $disabled= FALSE)
493 {
494 if ($disabled == TRUE){
495 $state= "disabled";
496 } else {
497 $state= "";
498 }
499 $display= "<table summary=\"\" width=\"100%\"><tr>\n<td colspan=2><p><b>$text</b></p>\n";
500 $display.= "<input type=submit value=\"$button_text\" name=\"modify_state\" ".
501 chkacl($this->acl, "all")." ".$state.
502 "><p class=\"seperator\"> </p></td></tr></table>";
504 return($display);
505 }
507 function postcreate()
508 {
509 /* Find postcreate entries for this class */
510 $command= search_config($this->config->data['MENU'], get_class($this), "POSTCREATE");
511 if ($command == "" && isset($this->config->data['TABS'])){
512 $command= search_config($this->config->data['TABS'], get_class($this), "POSTCREATE");
513 }
515 if ($command != ""){
516 /* Walk through attribute list */
517 foreach ($this->attributes as $attr){
518 if (!is_array($this->$attr)){
519 $command= preg_replace("/%$attr/", $this->$attr, $command);
520 }
521 }
522 $command= preg_replace("/%dn/", $this->dn, $command);
523 if (check_command($command)){
524 @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,
525 $command, "Execute");
527 exec($command);
528 } else {
529 $message= sprintf(_("Command '%s', specified as POSTCREATE for plugin '%s' doesn't seem to exist."), $command, get_class($this));
530 print_red ($message);
531 }
532 }
533 }
535 function postmodify()
536 {
537 /* Find postcreate entries for this class */
538 $command= search_config($this->config->data['MENU'], get_class($this), "POSTMODIFY");
539 if ($command == "" && isset($this->config->data['TABS'])){
540 $command= search_config($this->config->data['TABS'], get_class($this), "POSTMODIFY");
541 }
543 if ($command != ""){
544 /* Walk through attribute list */
545 foreach ($this->attributes as $attr){
546 if (!is_array($this->$attr)){
547 $command= preg_replace("/%$attr/", $this->$attr, $command);
548 }
549 }
550 $command= preg_replace("/%dn/", $this->dn, $command);
551 if (check_command($command)){
552 @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,
553 $command, "Execute");
555 exec($command);
556 } else {
557 $message= sprintf(_("Command '%s', specified as POSTMODIFY for plugin '%s' doesn't seem to exist."), $command, get_class($this));
558 print_red ($message);
559 }
560 }
561 }
563 function postremove()
564 {
565 /* Find postremove entries for this class */
566 $command= search_config($this->config->data['MENU'], get_class($this), "POSTREMOVE");
567 if ($command == "" && isset($this->config->data['TABS'])){
568 $command= search_config($this->config->data['TABS'], get_class($this), "POSTREMOVE");
569 }
571 if ($command != ""){
572 /* Walk through attribute list */
573 foreach ($this->attributes as $attr){
574 if (!is_array($this->$attr)){
575 $command= preg_replace("/%$attr/", $this->$attr, $command);
576 }
577 }
578 $command= preg_replace("/%dn/", $this->dn, $command);
579 if (check_command($command)){
580 @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__,
581 $command, "Execute");
583 exec($command);
584 } else {
585 $message= sprintf(_("Command '%s', specified as POSTREMOVE for plugin '%s' doesn't seem to exist."), $command, get_class($this));
586 print_red ($message);
587 }
588 }
589 }
591 /* Create unique DN */
592 function create_unique_dn($attribute, $base)
593 {
594 $ldap= $this->config->get_ldap_link();
595 $base= preg_replace("/^,*/", "", $base);
597 /* Try to use plain entry first */
598 $dn= "$attribute=".$this->$attribute.",$base";
599 $ldap->cat ($dn);
600 if (!$ldap->fetch()){
601 return ($dn);
602 }
604 /* Look for additional attributes */
605 foreach ($this->attributes as $attr){
606 if ($attr == $attribute || $this->$attr == ""){
607 continue;
608 }
610 $dn= "$attribute=".$this->$attribute."+$attr=".$this->$attr.",$base";
611 $ldap->cat ($dn);
612 if (!$ldap->fetch()){
613 return ($dn);
614 }
615 }
617 /* None found */
618 return ("none");
619 }
621 function rebind($ldap, $referral)
622 {
623 $credentials= LDAP::get_credentials($referral, $this->config->current['REFERRAL']);
624 if (ldap_bind($ldap, $credentials['ADMIN'], $credentials['PASSWORD'])) {
625 $this->error = "Success";
626 $this->hascon=true;
627 $this->reconnect= true;
628 return (0);
629 } else {
630 $this->error = "Could not bind to " . $credentials['ADMIN'];
631 return NULL;
632 }
633 }
635 /* This is a workaround function. */
636 function copy($src_dn, $dst_dn)
637 {
638 /* Rename dn in possible object groups */
639 $ldap= $this->config->get_ldap_link();
640 $ldap->search('(&(objectClass=gosaGroupOfNames)(member='.$src_dn.'))',
641 array('cn'));
642 while ($attrs= $ldap->fetch()){
643 $og= new ogroup($this->config, $ldap->getDN());
644 unset($og->member[$src_dn]);
645 $og->member[$dst_dn]= $dst_dn;
646 $og->save ();
647 }
649 $ldap->cat($dst_dn);
650 $attrs= $ldap->fetch();
651 if (count($attrs)){
652 trigger_error("Trying to overwrite $dst_dn, which already exists.",
653 E_USER_WARNING);
654 return (FALSE);
655 }
657 $ldap->cat($src_dn);
658 $attrs= array();
659 $attrs= $ldap->fetch();
660 if (!count($attrs)){
661 trigger_error("Trying to move $src_dn, which does not seem to exist.",
662 E_USER_WARNING);
663 return (FALSE);
664 }
666 /* Grummble. This really sucks. PHP ldap doesn't support rdn stuff. */
667 $ds= ldap_connect($this->config->current['SERVER']);
668 ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
669 if (function_exists("ldap_set_rebind_proc") && isset($this->config->current['REFERRAL'])) {
670 ldap_set_rebind_proc($ds, array(&$this, "rebind"));
671 }
673 $r=ldap_bind($ds,$this->config->current['ADMIN'], $this->config->current['PASSWORD']);
674 error_reporting (0);
675 $sr=ldap_read($ds, $ldap->fix($src_dn), "objectClass=*");
677 /* Fill data from LDAP */
678 $new= array();
679 if ($sr) {
680 $ei=ldap_first_entry($ds, $sr);
681 if ($ei) {
682 foreach($attrs as $attr => $val){
683 if ($info = ldap_get_values_len($ds, $ei, $attr)){
684 for ($i= 0; $i<$info['count']; $i++){
685 if ($info['count'] == 1){
686 $new[$attr]= $info[$i];
687 } else {
688 $new[$attr][]= $info[$i];
689 }
690 }
691 }
692 }
693 }
694 }
696 /* close conncetion */
697 error_reporting (E_ALL);
698 ldap_unbind($ds);
700 /* Adapt naming attribute */
701 $dst_name= preg_replace("/^([^=]+)=.*$/", "\\1", $dst_dn);
702 $dst_val = preg_replace("/^[^=]+=([^,+]+).*,.*$/", "\\1", $dst_dn);
703 $new[$dst_name]= $dst_val;
705 /* Check if this is a department.
706 * If it is a dep. && there is a , override in his ou
707 * change \2C to , again, else this entry can't be saved ...
708 */
709 if((isset($new['ou'])) &&( preg_match("/\\\\2C/",$new['ou']))){
710 $new['ou'] = preg_replace("/\\\\2C/",",",$new['ou']);
711 }
713 /* Save copy */
714 $ldap->connect();
715 $ldap->cd($this->config->current['BASE']);
716 $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $dst_dn));
717 $ldap->cd($dst_dn);
718 $ldap->add($new);
720 if ($ldap->error != "Success"){
721 trigger_error("Trying to save $dst_dn failed.",
722 E_USER_WARNING);
723 return(FALSE);
724 }
726 return (TRUE);
727 }
730 function move($src_dn, $dst_dn)
731 {
732 /* Copy source to destination */
733 if (!$this->copy($src_dn, $dst_dn)){
734 return (FALSE);
735 }
737 /* Delete source */
738 $ldap= $this->config->get_ldap_link();
739 $ldap->rmdir($src_dn);
740 if ($ldap->error != "Success"){
741 trigger_error("Trying to delete $src_dn failed.",
742 E_USER_WARNING);
743 return (FALSE);
744 }
746 return (TRUE);
747 }
750 /* Move/Rename complete trees */
751 function recursive_move($src_dn, $dst_dn)
752 {
753 /* Check if the destination entry exists */
754 $ldap= $this->config->get_ldap_link();
756 /* Check if destination exists - abort */
757 $ldap->cat($dst_dn);
758 if ($ldap->fetch()){
759 trigger_error("recursive_move $dst_dn already exists.",
760 E_USER_WARNING);
761 return (FALSE);
762 }
764 /* Perform a search for all objects to be moved */
765 $objects= array();
766 $ldap->cd($src_dn);
767 $ldap->search("(objectClass=*)", array("dn"));
768 while($attrs= $ldap->fetch()){
769 $dn= $attrs['dn'];
770 $objects[$dn]= strlen($dn);
771 }
773 /* Sort objects by indent level */
774 asort($objects);
775 reset($objects);
777 /* Copy objects from small to big indent levels by replacing src_dn by dst_dn */
778 foreach ($objects as $object => $len){
779 $src= $object;
780 $dst= preg_replace("/$src_dn$/", "$dst_dn", $object);
781 if (!$this->copy($src, $dst)){
782 return (FALSE);
783 }
784 }
786 /* Remove src_dn */
787 $ldap->cd($src_dn);
788 $ldap->recursive_remove();
789 return (TRUE);
790 }
793 function handle_post_events($mode)
794 {
795 switch ($mode){
796 case "add":
797 $this->postcreate();
798 break;
800 case "modify":
801 $this->postmodify();
802 break;
804 case "remove":
805 $this->postremove();
806 break;
807 }
808 }
810 function saveCopyDialog(){
811 }
813 function getCopyDialog(){
814 return(array("string"=>"","status"=>""));
815 }
817 function PrepareForCopyPaste($source){
818 $todo = $this->attributes;
819 if(isset($this->CopyPasteVars)){
820 $todo = array_merge($todo,$this->CopyPasteVars);
821 }
822 $todo[] = "is_account";
823 foreach($todo as $var){
824 $this->$var = $source->$var;
825 }
826 }
828 }
829 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
830 ?>