Code

Added first fixes for use with existing DN's with an escaped comma inside
[gosa.git] / plugins / admin / groups / class_groupManagement.inc
index 1ee8e268c8c28a8d0765851d2cc0c53678ca05e6..05befca60da1a606b5102b542a8341c8dbb47680 100644 (file)
@@ -56,6 +56,9 @@ class groupManagement extends plugin
 
   function execute()
   {
+       /* Call parent execute */
+       plugin::execute();
+
     /* Save data */
     $groupfilter= get_global("groupfilter");
     $s_action   = "";
@@ -123,7 +126,6 @@ class groupManagement extends plugin
       $s_action="open";
       $s_entry = base64_decode($_GET['dep_id']);
       $groupfilter['depselect']= "".$this->config->departments[trim($s_entry)];
-      $this->reload();
     }
 
     // Edit if
@@ -135,19 +137,16 @@ class groupManagement extends plugin
     /* Department changed? */
     if(isset($_POST['depselect']) && $_POST['depselect']){
       $groupfilter['depselect']= $_POST['depselect'];
-      $this->reload();
     }
 
     /* Homebutton is posted */
     if($s_action=="home"){
       $groupfilter['depselect']= (preg_replace("/^[^,]+,/","",$this->ui->dn));
       $groupfilter['depselect']= (preg_replace("/^[^,]+,/","",$groupfilter['depselect']));
-      $this->reload();
     }
 
     if($s_action=="root"){
       $groupfilter['depselect']=($this->config->current['BASE']);
-      $this->reload();
     }
 
     /* If Backbutton is Posted */
@@ -160,19 +159,13 @@ class groupManagement extends plugin
       }else{
         $groupfilter['depselect']= $this->config->departments["/"];
       }
-      $this->reload();
     }
     register_global("groupfilter", $groupfilter);
 
     $smarty= get_smarty();
 
     /* Prepare formular */
-    if (!isset($this->grouptab) &&
-        !isset($_POST['new_group']) &&
-        !isset($_POST['delete_group']) &&
-        !isset($_POST['select_group'])){
-      $this->reload();
-    }
+    $this->reload();
 
     /* Check for exeeded sizelimit */
     if (($message= check_sizelimit()) != ""){
@@ -189,7 +182,9 @@ class groupManagement extends plugin
       /* Create new usertab object */
       $this->grouptab= new grouptabs($this->config,
           $this->config->data['TABS']['GROUPTABS'], $this->dn);
-      $this->grouptab->set_acl(array(':all'));
+      /* Set up the users ACL's for this 'dn' */
+      $acl= get_permissions ($groupfilter['depselect'], $this->ui->subtreeACL);
+      $this->grouptab->set_acl($acl);
     }
 
     /* Cancel dialogs */
@@ -243,7 +238,10 @@ class groupManagement extends plugin
 
       /* Check locking, save current plugin in 'back_plugin', so
          the dialog knows where to return. */
-      if (($user= get_lock($this->dn)) != ""){
+
+      $user = get_lock($this->dn);
+
+      if ($user != ""){
         return(gen_locked_message ($user, $this->dn));
       }
 
@@ -355,50 +353,48 @@ class groupManagement extends plugin
 
     // Managment
     $listhead = "<div style='background:#F0F0F9;padding:5px;'>".
-      " <input type='image' align='middle' src='images/list_back.png' title='"._("Go up one department")."' alt='"._("Up")."' name='dep_back'>&nbsp;".
-      " <input type='image' src='images/list_root.png' align='middle' title='"._("Go to root department")."' name='dep_root' alt='"._("Root")."'>&nbsp;".
-      " <input type='image' align='middle' src='images/list_home.png' title='"._("Go to users department")."'
- alt='"._("Home")."' name='dep_home'>&nbsp;".
-      " <img src='images/list_seperator.png' align='middle' alt='' height='16' width='1'>&nbsp;".
-      " <input type='image' align='middle' src='images/list_new_group.png' title='"._("Create new group")."'
- alt='"._("New")."' name='group_new'>&nbsp;".
-      " <img src='images/list_seperator.png' align='middle' alt='' height='16' width='1'>&nbsp;".
-      _("Current base")."&nbsp;<select name='depselect' onChange='mainform.submit()'>$options</select>".
-       " <input type='image' src='images/list_submit.png' align='middle' title='"._("Submit department")."'   name='submit_department' alt='"._("Submit")."'>&nbsp;".
+      " <input type='image' class='center' src='images/list_back.png' title='"._("Go up one department")."' alt='"._("Up")."' name='dep_back'>&nbsp;".
+      " <input type='image' class='center' src='images/list_root.png' title='"._("Go to root department")."' name='dep_root' alt='"._("Root")."'>&nbsp;".
+      " <input type='image' class='center' src='images/list_home.png' title='"._("Go to users department")."' alt='"._("Home")."' name='dep_home'>&nbsp;".
+      " <img class='center' src='images/list_seperator.png' alt='' height='16' width='1'>&nbsp;".
+      " <input type='image' class='center' src='images/list_new_group.png' title='"._("Create new group")."' alt='"._("New")."' name='group_new'>&nbsp;".
+      " <img class='center' src='images/list_seperator.png' alt='' height='16' width='1'>&nbsp;".
+      _("Current base")."&nbsp;<select name='depselect' onChange='mainform.submit()' class='center'>$options</select>".
+       " <input type='image' class='center' src='images/list_submit.png' title='"._("Submit department")."' name='submit_department' alt='"._("Submit")."'>&nbsp;".
       "</div>";
 
     
-    $actions = "<input type='image' src='images/edit.png' alt='"._("edit")."' name='group_edit_%KEY%' title='"._("Edit this entry")."'>";
-    $actions.= "<input type='image' src='images/edittrash.png' alt='"._("delete")."' name='group_del_%KEY%' title='"._("Delete this entry")."'>";
+    $actions = "<input class='center' type='image' src='images/edit.png' alt='"._("edit")."' name='group_edit_%KEY%' title='"._("Edit this entry")."'>";
+    $actions.= "<input class='center' type='image' src='images/edittrash.png' alt='"._("delete")."' name='group_del_%KEY%' title='"._("Delete this entry")."'>";
 
     // Defining Links
     $linkopen = "<a href='?plug=".$_GET['plug']."&amp;act=dep_open&amp;dep_id=%s'>%s</a>";
 
     // image Buttons 
     $editlink = "<a href='?plug=".$_GET['plug']."&amp;id=%s&amp;act=edit_entry'>%s</a>";
-    $userimg  = "<img src='images/select_groups.png' alt='User'    title='%s'>";
+    $userimg  = "<img class='center' src='images/select_groups.png' alt='User'    title='%s'>";
 
     // Extension images 
-    $posiximg = "<img src='images/select_groups.png'        alt='P'  title='"._("Posix")  ."'>";
-    $mailimg  = "<img src='images/mailto.png'             alt='M'  title='"._("Mail")   ."'>";
-    $sambaimg = "<img src='images/select_winstation.png'  alt='S'  title='"._("Samba")  ."'>";
-    $applimg  = "<img src='images/select_application.png' alt='A'  title='"._("Application")."'>"; 
-    $phoneimg  = "<img src='images/select_phone.png'      alt='Ph' title='"._("Phone")  ."'>"; 
+    $posiximg = "<img class='center' src='images/select_groups.png'        alt='P'  title='"._("Posix")  ."'>";
+    $mailimg  = "<img class='center' src='images/mailto.png'             alt='M'  title='"._("Mail")   ."'>";
+    $sambaimg = "<img class='center' src='images/select_winstation.png'  alt='S'  title='"._("Samba")  ."'>";
+    $applimg  = "<img class='center' src='images/select_application.png' alt='A'  title='"._("Application")."'>"; 
+    $phoneimg = "<img class='center' src='images/select_phone.png'      alt='Ph' title='"._("Phone")  ."'>"; 
+    $envimg   = "<img class='center' src='images/smallenv.png'      alt='E' title='"._("Environment")  ."'>"; 
     // Space
-    $empty    = "<img src='images/empty.png' style='width:16px;height:16px;' alt=''>";
+    $empty    = "<img class='center' src='images/empty.png' style='width:16px;height:16px;' alt=''>";
 
 
     $divlist = new divlist("groupstab");
     $divlist->SetHeader(array(
-          array("string" => "&nbsp;"),
-          array("string" => _("Groupname")." / "._("Department")),
-          array("string" => _("Properties"), "attach" => "style='width:100px;'"),
-          array("string" => _("Actions")    ,"attach" => "style='border:none'")
+          array("string" => "&nbsp;", "attach" => "style='text-align:center;width:20px;'"),
+          array("string" => _("Groupname")." / "._("Department"), "attach" => "style=''"),
+          array("string" => _("Properties"), "attach" => "style='width:136px;'"),
+          array("string" => _("Actions"), "attach" => "style='width:60px;border-right:0px;text-align:right;'")
           ));
 
-
     $divlist->SetSummary(_("This table displays all groups, in the selected tree."));
-    $divlist->SetEntriesPerPage(20);
+    $divlist->SetEntriesPerPage(0);
 
     foreach($this->departments as $key=> $val){
 
@@ -415,10 +411,10 @@ class groupManagement extends plugin
       }
 
 
-      $field1 = array("string" => "<img src='images/".$non_empty."folder.png' alt='department'>");
-      $field2 = array("string" => sprintf($linkopen,base64_encode($key),$val));
-      $field3 = array("string" => "&nbsp;");
-      $field4 = array("string" => "&nbsp;","attach"=>"style='text-align:right;border:none'");
+      $field1 = array("string" => "<img src='images/".$non_empty."folder.png' alt='department'>", "attach" => "style='text-align:center;width:20px;'");
+      $field2 = array("string" => sprintf($linkopen,base64_encode($key),$val), "attach" => "style=''");
+      $field3 = array("string" => "&nbsp;", "attach" => "style='width:136px;'");
+      $field4 = array("string" => "&nbsp;", "attach" => "style='width:60px;border-right:0px;text-align:right;'");
 
       $divlist->AddEntry(array($field1,$field2,$field3,$field4));
     }
@@ -431,6 +427,7 @@ class groupManagement extends plugin
         if(in_array("sambaGroupMapping",    $val['objectClass']))   $samba = $sambaimg;   else $samba   = $empty;
         if(in_array("gosaApplicationGroup", $val['objectClass']))   $appl  = $applimg;    else $appl    = $empty;
         if(in_array("goFonPickupGroup",     $val['objectClass']))   $phone = $phoneimg;   else $phone   = $empty;
+        if(in_array("gotoEnvironment",      $val['objectClass']))   $enviro= $envimg;     else $enviro  = $empty;
       }else{
         $posix=$mail=$samba=$appl=$phone=$empty;
       }
@@ -442,10 +439,10 @@ class groupManagement extends plugin
       }else{
         $desc = " - [ ".$val['description'][0]." ]";
       }      
-      $field1 = array("string" => sprintf($userimg,$val['dn']),"attach"=>"style='width:20px;align:middle;'");
-      $field2 = array("string" => sprintf($editlink,$key,($val['cn']['0'].$desc)),"attach"=>$title);
-      $field3 = array("string" => $posix."&nbsp;".$mail."&nbsp;".$samba."&nbsp;".$appl."&nbsp;".$phone);  
-      $field4 = array("string" => preg_replace("/%KEY%/", $key, $actions),"attach"=>"style='text-align:right;border:none;width:32px;'");
+      $field1 = array("string" => sprintf($userimg,$val['dn']), "attach" => "style='text-align:center;width:20px;'");
+      $field2 = array("string" => sprintf($editlink,$key,($val['cn']['0'].$desc)), "attach" => "style='' ".$title);
+      $field3 = array("string" => $posix."&nbsp;".$enviro."&nbsp;".$mail."&nbsp;".$samba."&nbsp;".$appl."&nbsp;".$phone, "attach" => "style='width:136px;'");  
+      $field4 = array("string" => preg_replace("/%KEY%/", $key, $actions), "attach" => "style='width:60px;border-right:0px;text-align:right;'");
       
       $divlist->AddEntry(array($field1,$field2,$field3,$field4));    
     }
@@ -454,6 +451,7 @@ class groupManagement extends plugin
     $smarty->assign("grouplisthead", $listhead);
     $smarty->assign("grouplist", $divlist->DrawList());
     $smarty->assign("search_image", get_template_path('images/search.png'));
+    $smarty->assign("searchu_image", get_template_path('images/search_user.png'));
     $smarty->assign("tree_image", get_template_path('images/tree.png'));
     $smarty->assign("infoimage", get_template_path('images/info.png'));
     $smarty->assign("launchimage", get_template_path('images/launch.png'));
@@ -470,9 +468,7 @@ class groupManagement extends plugin
     return($smarty->fetch(get_template_path('headpage.tpl', TRUE)));
   }
 
-
-
-  function reload2()
+  function reload($CreatePosixsList=false)
   {
     /* Get config */
     $groupfilter= get_global('groupfilter');
@@ -488,35 +484,22 @@ class groupManagement extends plugin
     }
 
     /* User filter? */
-    $filter= "";
+    $filter= "(objectclass=posixGroup)";
     $error= "";
     $error2= "";
     $this->grouplist= array();
 
     /* What are primary groups? */
     $primaries= array();
-    $ldap= $this->config->get_ldap_link(TRUE);
+    $ldap= $this->config->get_ldap_link();
     $ldap->cd($base);
     $ldap->search("(&(uid=$regex)(!(uid=*$))(objectClass=posixAccount)(gidNumber=*))", array("gidNumber", "cn"));
-    $error= $ldap->error;
+    show_ldap_error($ldap->get_error());
     while ($attrs= $ldap->fetch()){
       $primaries[$attrs['gidNumber'][0]]= $attrs['cn'][0];
     }
 
-    if ($groupfilter['primarygroups'] == "checked"){
-      $filter.= "(&(objectClass=posixGroup)(|";
-      foreach ($primaries as $gidNumber => $cn){
-        $filter.= "(gidNumber=$gidNumber)";
-      }
-      $filter.= "))";
-    } else {
-      $filter.= "(&(objectClass=posixGroup)(!(|";
-      foreach ($primaries as $gidNumber => $cn){
-        $filter.= "(gidNumber=$gidNumber)";
-      }
-      $filter.= ")))";
-    }
- /* Set filter depending on selection */
+    /* Set filter depending on selection */
     $sfilter= "";
     if ($groupfilter['appgroups'] == "checked"){
       $filter.= "(objectClass=gosaApplicationGroup)";
@@ -532,160 +515,49 @@ class groupManagement extends plugin
     if ($groupfilter['mailgroups'] == "checked"){
       $filter.= "(objectClass=gosaMailAccount)";
     }
-    if ($groupfilter['functionalgroups'] == "checked"){
-      $ldap->cd($base);
-      $ldap->set_size_limit($_SESSION['size_limit']);
-      $ldap->search("(&(cn=$regex)(objectClass=posixGroup)(!(|(objectClass=gosaMailAccount)(objectClass=gosaApplicationGroup)$sfilter)))", array("cn",      "gidNumber", "description"));
-      $error2= $ldap->error;
-      while ($attrs= $ldap->fetch()){
-        if (!isset($primaries[$attrs['gidNumber'][0]])){
-          if (isset($attrs["description"][0])){
-            $this->grouplist[$attrs["dn"]]= $attrs["cn"][0]." [".
-              $attrs["description"][0]."]";
-          } else {
-            $this->grouplist[$attrs["dn"]]= $attrs["cn"][0];
-          }
-        }
-      }
-    }
-
-    /* Generate grouplist */
-    if ($filter != ""){
-      $filter= "(&(cn=$regex)(objectClass=posixGroup)(|$filter))";
-      if ($groupfilter['guser'] != ""){
-        $filter= "(&(|(memberUID=" . $groupfilter['guser'] . ")(cn=" . $groupfilter['guser'] . "))$filter)";
-      }
-    }
-    if ($groupfilter['subsearch'] == "checked"){
-      $res= get_list($this->ui->subtreeACL, "$filter", TRUE, $base, array("cn", "description", "gidNumber"), TRUE);
-    } else {
-      $base= get_groups_ou().$base;
-      $res= get_list($this->ui->subtreeACL, "$filter", FALSE, $base, array("cn", "description", "gidNumber"), TRUE);
-    }
-    if (preg_match("/size limit/i", $error) || preg_match("/size limit/i", $error2)){
-      $_SESSION['limit_exceeded']= TRUE;
-    }
-
-   foreach ($res as $value){
-      if (isset($value["description"][0])){
-        $this->grouplist[$value["dn"]]= $value["cn"][0]." [".
-          $value["description"][0]."]";
-      } else {
-        $this->grouplist[$value["dn"]]= $value["cn"][0];
+    $functional= array();
+    $ldap->cd(get_groups_ou().$base);
+    $ldap->set_size_limit($_SESSION['size_limit']);
+    $ldap->search("(&(cn=$regex)(objectClass=posixGroup)(!(|(objectClass=gosaMailAccount)(objectClass=gosaApplicationGroup)$sfilter)))", array("cn", "gidNumber", "description"));
+    $error2= $ldap->error;
+    while ($attrs= $ldap->fetch()){
+      if (!isset($primaries[$attrs['gidNumber'][0]])){
+        $functional[$attrs['gidNumber'][0]]= $attrs['gidNumber'][0];
       }
     }
 
-    natcasesort ($this->grouplist);
-    reset ($this->grouplist);
-  }
-
-
-
-
-
-
 
 
 
 
 
 
-  function reload()
-  {
-    /* Get config */
-    $groupfilter= get_global('groupfilter');
-
-    /* Set base for all searches */
-    $base= $groupfilter['depselect'];
-
-    /* Regex filter? */
-    if ($groupfilter['regex'] != ""){
-      $regex= $groupfilter['regex'];
-    } else {
-      $regex= "*";
-    }
-
-    /* User filter? */
-    $filter= "";
-    $error= "";
-    $error2= "";
-    $this->grouplist= array();
-
-    /* What are primary groups? */
-    $primaries= array();
-    $ldap= $this->config->get_ldap_link(TRUE);
-    $ldap->cd($base);
-    $ldap->search("(&(uid=$regex)(!(uid=*$))(objectClass=posixAccount)(gidNumber=*))", array("gidNumber", "cn"));
-    $error= $ldap->error;
-    while ($attrs= $ldap->fetch()){
-      $primaries[$attrs['gidNumber'][0]]= $attrs['cn'][0];
-    }
-
-    if ($groupfilter['primarygroups'] == "checked"){
-      $filter.= "(&(objectClass=posixGroup)(|";
-      foreach ($primaries as $gidNumber => $cn){
-        $filter.= "(gidNumber=$gidNumber)";
-      }
-      $filter.= "))";
-    } else {
-      $filter.= "(&(objectClass=posixGroup)(!(|";
-      foreach ($primaries as $gidNumber => $cn){
-        $filter.= "(gidNumber=$gidNumber)";
-      }
-      $filter.= ")))";
-    }
-
-  
-    /* Set filter depending on selection */
-    $sfilter= "";
-    if ($groupfilter['appgroups'] == "checked"){
-      $filter.= "(objectClass=gosaApplicationGroup)";
-    }
-    if ($this->config->current['SAMBAVERSION'] == 3){
-      if ($groupfilter['primarygroups'] != "checked"){
-        $sfilter= "(objectClass=sambaGroupMapping)";
-      } elseif ($groupfilter['sambagroups'] == "checked"){
-        $filter.= "(objectClass=sambaGroupMapping)";
-      }
-    }
-
-    if ($groupfilter['mailgroups'] == "checked"){
-      $filter.= "(objectClass=gosaMailAccount)";
-    }
-    if ($groupfilter['functionalgroups'] == "checked"){
-      $ldap->cd($base);
-      $ldap->set_size_limit($_SESSION['size_limit']);
-      $ldap->search("(&(cn=$regex)(objectClass=posixGroup)(!(|(objectClass=gosaMailAccount)(objectClass=gosaApplicationGroup)$sfilter)))", array("cn", "gidNumber", "description"));
-      $error2= $ldap->error;
-      while ($attrs= $ldap->fetch()){
-        if (!isset($primaries[$attrs['gidNumber'][0]])){
-          $this->grouplist[$attrs['gidNumber'][0]]= $attrs;
-        }
-      }
-    }
 
     /*
      * We also need to search for the departments
      * So we are able to navigate like in konquerer
      */
-    $base2 = preg_replace("/ou=people,/i","",$base);
 
-    $res3 =  get_list2($this->ui->subtreeACL, "(&(|(ou=$regex)(description=$regex))(objectClass=gosaDepartment))",
-                              TRUE, $base2, array("ou", "description"), TRUE);
-
-    $this->departments= array();
-    $tmp = array();
-    foreach ($res3 as $value){
-      $tmp[strtolower($value['dn']).$value['dn']]=$value;
-    }
-    ksort($tmp);
-    foreach($tmp as $value){
-      if($value["description"][0]!=".."){
-        $this->departments[$value['dn']]=convert_department_dn2($value['dn'])." - [".$value["description"][0]."]";
-      }else{
-        $this->departments[$value['dn']]=$value["description"][0];
+    /* Only needed if we are in GroupManagement */
+    if(!$CreatePosixsList){
+      $res3 =  get_list2($this->ui->subtreeACL, "(&(|(ou=$regex)(description=$regex))(objectClass=gosaDepartment))",
+          FALSE, $base, array("ou", "description"), TRUE);
+      $this->departments= array();
+      $tmp = array();
+      foreach ($res3 as $value){
+        $tmp[strtolower($value['dn']).$value['dn']]=$value;
+      }
+      ksort($tmp);
+      foreach($tmp as $value){
+        if(isset($value["description"][0])){
+          $this->departments[$value['dn']]=convert_department_dn2($value['dn'])." - [".$value["description"][0]."]";
+        }else{
+          $this->departments[$value['dn']]= convert_department_dn2($value['dn']);//$value["description"][0];
+        }
       }
     }
+    /*  Ende department generation
+     */
 
     /* Generate grouplist */
     if ($filter != ""){
@@ -694,8 +566,11 @@ class groupManagement extends plugin
         $filter= "(&(|(memberUID=" . $groupfilter['guser'] . ")(cn=" . $groupfilter['guser'] . "))$filter)";
       }
     }
+
+    /* Depending on $CreatePosixsList we have to create different lists */
+
     if ($groupfilter['subsearch'] == "checked"){
-      $res= get_list($this->ui->subtreeACL, "$filter", TRUE, $base, array("cn", "description", "gidNumber"), TRUE);
+      $res= get_list($this->ui->subtreeACL, "$filter", FALSE, $base, array("cn", "description", "gidNumber"), TRUE);
     } else {
       $base= get_groups_ou().$base;
       $res= get_list($this->ui->subtreeACL, "$filter", FALSE, $base, array("cn", "description", "gidNumber"), TRUE);
@@ -705,21 +580,53 @@ class groupManagement extends plugin
     }
 
     foreach ($res as $value){
-      $this->grouplist[$value['gidNumber'][0]]= $value;
+      if ($groupfilter['functionalgroups'] != "checked" && isset($functional[$value['gidNumber'][0]])){
+        continue;
+      }
+      if ($groupfilter['primarygroups'] == "checked"){
+        $this->grouplist[$value['gidNumber'][0]]= $value;
+      } else {
+        if (!isset($primaries[$value['gidNumber'][0]])){
+          $this->grouplist[$value['gidNumber'][0]]= $value;
+        }
+      }
     }
 
+    $tmp=array();
 
+    /* If true ; this result is used for posix tab, group membership selection. */
+    if($CreatePosixsList){
 
-      $tmp=array();
-    foreach($this->grouplist as $tkey => $val ){
-      $tmp[strtolower($val['cn'][0]).$val['cn'][0]]=$val;
-    }
-    ksort($tmp);
-    $this->grouplist=array();
-    foreach($tmp as $val){
-      $this->grouplist[]=$val;
+      /* Addd to array */
+      foreach($this->grouplist as $tkey => $val ){
+        $tmp[strtolower($val['cn'][0]).$val['cn'][0]]=$val;
+      }
+      
+      /* Sort index */
+      ksort($tmp);
+
+      /* Recreate index array[dn]=cn[description]*/
+      $this->grouplist=array();
+      foreach($tmp as $val){
+        if(isset($val['description'])){
+          $this->grouplist[$val['dn']]=$val['cn'][0]."&nbsp;[".$val['description'][0]."]";
+        }else{
+          $this->grouplist[$val['dn']]=$val['cn'][0];
+        }
+      }
+    }else{
+  
+      /* Create result for group management listbox*/
+      foreach($this->grouplist as $tkey => $val ){
+        $tmp[strtolower($val['cn'][0]).$val['cn'][0]]=$val;
+      }
+      ksort($tmp);
+      $this->grouplist=array();
+      foreach($tmp as $val){
+        $this->grouplist[]=$val;
+      }
+      reset ($this->grouplist);
     }
-    reset ($this->grouplist);
   }
 
   function remove_from_parent()