"""
w2lapp.addmodifyform.py: input form for adding and modifying an entry

web2ldap - a web-based LDAP Client,
see http://www.web2ldap.de for details

(c) by Michael Stroeder <michael@stroeder.com>

This module is distributed under the terms of the
GPL (GNU GENERAL PUBLIC LICENSE) Version 2
(see http://www.gnu.org/copyleft/gpl.html)

$Id: addmodifyform.py,v 1.29 2002/02/16 19:57:41 michael Exp $
"""

import sys,ldap,\
       msbase,pyweblib.forms,ldaputil.base,\
       w2lapp.core,w2lapp.cnf,w2lapp.gui
from ldaputil.base import ParentDN
from msbase import union,listremove
from ldapoc import ldap_oc

heading_msg = {'modifyform':'Modify entry','addform':'Add new entry'}


# Make a case-ignoring string-keyed copy of ldap_oc
ldap_oc_ci = msbase.CaseinsensitiveStringKeyDict(ldap_oc)


def ObjectClassForm(sid,outf,form,command,ls,dn,rdn):
  """Form for choosing object class(es)"""
  if command=='addform' and rdn:
    command_hidden_fields = [('dn',dn),('add_rdn',rdn)]
  else:
    command_hidden_fields = [('dn',dn)]
  Msg = {
    'addform':'Choose object class(es) for new entry or choose from quick-list.',
    'modifyform':'You may change the object class(es) for the entry.',
  }[command]
  outf.write('<p>%s</p>\n<table summary="Choosing object classes">\n<tr>\n' % (Msg))
  # Select list with all configured object classes
  outf.write('<td>%s</td>\n' % (
    w2lapp.gui.W2L_Form(
      form,command,'Input form',sid,
      command_hidden_fields,
      extrastr='<br>%s\n' % (
        form.field['ldap_oc'].inputHTML(None)
      )
    )
  ))
  if command=='addform':
    outf.write('<td>\n')
    default_oc_list = w2lapp.cnf.GetParam(ls,'addform_oc_list',[])
    for desc,rdnAttrType,oc_list in default_oc_list:
      quick_button_command_hidden_fields = [
        ('dn',dn),('add_rdn','%s=' % (rdnAttrType.encode()))
      ]
      quick_button_command_hidden_fields.extend(
        [ ('ldap_oc',oc) for oc in oc_list ]
      )
      outf.write('<p>\n%s | %s\n</p>\n' % (
        w2lapp.gui.W2L_Anchor(form,command,desc,sid,quick_button_command_hidden_fields),
        ', '.join(oc_list)
      ))
    outf.write('</td>\n')
  outf.write('</tr>\n</table>\n')
  return # ObjectClassForm()

def PrintInputTable(outf,form,ls,dn,attr_types,tableheader='',entry={}):
  outf.write("""<fieldset>
    <legend>%s</legend>
    <table summary="%s">
    """ % (tableheader,tableheader)
  )

  entry = ldaputil.base.sanitize_entry(entry)

  rdn_dict = ldaputil.base.rdn_dict(dn,ls.charset)
  attr_types.sort()
  for attr_type in attr_types:
    attr_values = entry.get(attr_type.lower(),entry.get(attr_type,['']))
    if not attr_values:
      attr_values = ['']
    attr_values.sort()
    for attr_value in attr_values:
      attr_value = unicode(attr_value,ls.charset)
      attr_value_disp = w2lapp.core.utf2display(form.accept_charset,attr_value)
      readonly = attr_value in rdn_dict.get(attr_type.lower(),rdn_dict.get(attr_type,[]))
      if readonly:
        input_field = '<input type="hidden" name="in_value" value="%s">%s' % (
          attr_value_disp,attr_value_disp
        )
      else:
        input_field = '<input name="in_value" value="%s" size="60">' % (
          attr_value_disp
        )
      outf.write("""<tr>
          <td>
            <input type="hidden" name="in_attrtype" value="%s">%s
          </td>
          <td>
            %s
          </td>
        </tr>
        """ % (
          attr_type,w2lapp.gui.AttrNameStr(attr_type),input_field
        )
      )
  outf.write('</table></fieldset>')
  return # PrintInputTable()


def w2l_AddModifyForm(sid,outf,command,form,ls,dn,Msg='',rdn_default='',entry={}):

  new_rdn_attrtype,objectclasses,dit_immutable = w2lapp.cnf.GetParam(
    ls,'dit',{}).get(dn,('',[],0)
  )
  if command=='addform':
    form.field['ldap_oc'].setDefault(objectclasses)

  if not dit_immutable:
    # User is allowed to change object class
    if entry:
      # Get objectclass(es) from entry parameter
      objectclasses = entry.get('objectClass',entry.get('objectclass',[]))
    elif 'ldap_oc' in form.inputFieldNames:
      # Read objectclass(es) from input form
      objectclasses = form.field['ldap_oc'].value
    else:
      # User did not specify ldap_oc
      # => output a form for selecting object class(es)
      # Determine which RDN should be printed as input
      if 'add_rdn' in form.inputFieldNames:
        add_rdn = form.field['add_rdn'].value[0]
      else:
        if dit_immutable:
          add_rdn = ''
        else:
          if rdn_default:
            add_rdn = rdn_default
          elif new_rdn_attrtype:
            add_rdn = '%s=' % (new_rdn_attrtype)
          else:
            add_rdn = ''
      if command=='modifyform':
        # read object classes from existing entry
        existing_objectclasses = ls.getObjectClasses(dn)
        # Set the selected defaults to existing object classes
	form.field['ldap_oc'].setDefault(
          ldaputil.base.unicode_list(existing_objectclasses,'ascii')
        )
      # Output the web page with object class input form
      w2lapp.gui.TopSection(
        sid,outf,form,ls,dn,
        'Object Class Selection',
        w2lapp.gui.MainMenu(sid,form,ls,dn),
        context_menu_list=[]
      )
      outf.write('<div id="InputDiv">\n<h1>%s</h1>' % (heading_msg[command]))
      ObjectClassForm(sid,outf,form,command,ls,dn,add_rdn)
      outf.write('</div>\n')
      w2lapp.gui.PrintFooter(outf,form)
      return


  if (command=='modifyform'):
    if entry:
      read_attrs = entry.keys()
    else:
      # Retrieve attribute types of the entry first
      # Get all attribute names of entries
      read_attrs = ls.getAttributeTypes(dn)
      # Falls nicht nur ein Attributinhalt gelesen werden soll,
      # so werden alle Binaerattribute, fuer die ein
      # MIME-Typ-Mapping angegeben wurde, rausgestrichen
      read_attrs = listremove(read_attrs,w2lapp.core.ldap_binaryattrkeys,ignorecase=1)
      # Read all non-binary attributes of entry
      entry = ls.readEntry(dn,read_attrs,no_cache=1)[0][1]

  ActionStr = '%s%s' % (command[0].upper(),command[1:-4])

  required_attrs = []
  allowed_attrs = []
  for oc in objectclasses:
    oc_def = ldap_oc_ci.get(oc,{})
    required_attrs = union(required_attrs,oc_def.get('requires',[]))
    allowed_attrs = union(allowed_attrs,oc_def.get('allows',[]))

  donottouch_attrs = ['objectclass']+w2lapp.cnf.GetParam(ls,'hiddenattrs',[])
  required_attrs = listremove(
    required_attrs,
    donottouch_attrs+w2lapp.core.ldap_binaryattrkeys,
    ignorecase=1
  )
  allowed_attrs = listremove(
    allowed_attrs,
    donottouch_attrs+w2lapp.core.ldap_binaryattrkeys+required_attrs,
    ignorecase=1
  )

  w2lapp.gui.TopSection(
    sid,outf,form,ls,dn,
    heading_msg[command],
    w2lapp.gui.MainMenu(sid,form,ls,dn),
    context_menu_list=[]
  )
  outf.write("""<div id="InputDiv">
    <h1>%s</h1>
    %s
    <form 
      action="%s/%s/%s"
      method="POST"
      enctype="application/x-www-form-urlencoded"
      accept-charset="%s"
    >
    <input type=hidden name="dn" value="%s">
    <input type="submit" value="%s"><input type="reset" value="Reset">
    """ % (
      heading_msg[command],
      Msg,
      form.script_name,command[:-4],
      sid,
      form.accept_charset,
      dn.encode(form.accept_charset),
      ActionStr
    )
  )

  if command=='addform':
    if not dit_immutable:
      f = form.field['add_rdn']
      if 'add_rdn' in form.inputFieldNames:
        f.setDefault(f.value[0])
      outf.write('<p>RDN: %s</p>' % f.inputHTML())

  # Print chosen objectclass(es) as hidden input
  for oc in objectclasses:
    outf.write('<input type="hidden" name="ldap_oc" value="%s">\n' % (oc))

  outf.write("""
<table
  id="InputTable"
  summary="Input form for LDAP entry data"
  rules="rows">
<tr><td>%s</td><td>%s</td></tr>
</table>
"""% ( \
    w2lapp.gui.AttrNameStr('objectclass'),
    '<br>'.join(objectclasses)
  ))

  PrintInputTable(outf,form,ls,dn,required_attrs,'Required attributes',entry)
  PrintInputTable(outf,form,ls,dn,allowed_attrs,'Allowed attributes',entry)
  if command=='modifyform':
    # Print attributes which does not appear in the schema but
    # do exist in the entry
    read_attrs = listremove(
      read_attrs,
      required_attrs+allowed_attrs+\
        [
          'objectClass','userPassword',
          'modifytimestamp','modifiersname',
          'creatorsname','createtimestamp'
        ],
      ignorecase=1
    )
    if read_attrs:
      PrintInputTable(outf,form,ls,dn,read_attrs,'Existing attributes not matching schema',entry)

  outf.write("""
    <fieldset>
      <legend>Raw LDIF data</legend>
      <textarea name="in_ldif" value="" wrap=off rows=15 cols=75></textarea>
      <p>
        Notes:
      </p>
      <ul>
        <li>Lines containing "dn:" will be ignored</li>
        <li>Only the first entry (until first empty line) will be accepted</li>
        <li>NON-ASCII characters are not allowed!</li>
        <li>maximum length is set to %d bytes</li>
      </ul>
    </fieldset>
</form>
</div>
""" % (w2lapp.cnf.misc.ldif_maxbytes))

  w2lapp.gui.PrintFooter(outf,form)

