"""
w2lapp.read.py: Read existing entry and output as HTML or vCard

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: read.py,v 1.40 2002/02/16 19:32:17 michael Exp $
"""

import msbase,pyweblib.forms,ldaputil.base,pyweblib.httphelper, \
       w2lapp.core,w2lapp.cnf,w2lapp.gui,w2lapp.read,w2lapp.viewer
from msbase import listremove,intersection,union
from ldapoc import ldap_oc
try:
  from ldapoidreg import oid
except ImportError:
  oid = {}

def DisplayEntry(sid,form,ls,dn,entry,oid={},commandbutton=0):
  assert oid,'Empty OID registry'
  display_entry = msbase.CaseinsensitiveStringKeyDict(default='&nbsp;')
  # Convert all values in attributes of LDAP entry
  # to pretty-printable HTML
  for attrtype,values in entry.items():
    display_entry[attrtype] = [
      w2lapp.gui.DataStr(
        sid,form,ls,dn,
        attrtype,values[i],
        valueindex=i,
        commandbutton=commandbutton,
        oid_reg=oid
      )
      for i in range(len(values))
    ]
  return display_entry

def HTMLTemplateEntry(entry):
  template_entry = msbase.CaseinsensitiveStringKeyDict(default='&nbsp;')
  for attrtype,values in entry.items():
    template_entry[attrtype] = ', '.join(values)
  return template_entry

def getOperationAttrsTemplate(ls,accept_language):
  template_filename = w2lapp.gui.GetVariantFilename(
    w2lapp.cnf.GetParam(ls,'read_operationalattrstemplate'),
    accept_language
  )
  if template_filename:
    return open(template_filename,'r').read()
  else:
    return """<table>
<tr>
  <td width="50%%">
    Entry created %(createTimeStamp)s by:
    %(creatorsName)s
  </td>
  <td width="50%%">
    Last modified %(modifyTimeStamp)s by:
    %(modifiersName)s
  </td>
</tr>
</table>
"""

def PrintAttrList(sid,outf,form,dn,entry,attrs,read_attrs,Comment=''):
  if not attrs:
    return
  if Comment:
    outf.write('<h2>%s</h2>\n<dl class="ReadAttrList">\n' % (Comment))
  show_attrs = [
    a
    for a in attrs
    if a in read_attrs
  ]
  for attr in show_attrs:
    datalist = entry[attr]
    if attr.lower() in w2lapp.core.ldap_binaryattrkeys:
      dt_list = [w2lapp.gui.AttrNameStr(attr)]
      dt_list.append(w2lapp.gui.W2L_Anchor(
        form,'delete','Delete',sid,
        [('dn',dn),('delete_attr',attr)]
      ))
      dt_list.append(w2lapp.gui.W2L_Anchor(
	form,'read','Save to disk',sid,
        [
          ('dn',dn),
          ('read_attr',attr),
          ('read_attrmode','load'),
          ('read_attrmimetype','application/octet-stream'),
          ('read_attrindex','0'),
        ],
      ))
      dt_str = '<table>\n<tr>\n<td>%s</td>\n</tr>\n</table>' % (
        '</td>\n<td>|</td>\n<td>'.join(dt_list)
      )
    else:
      dt_str = w2lapp.gui.AttrNameStr(attr)
    outf.write('<dt>\n%s\n<dt>\n<dd>%s</dd>\n' % (
      dt_str,'<br>'.join(datalist))
    )
  outf.write('</dl>\n')


def w2l_Read(
  sid,outf,command,form,ls,dn,
  wanted_attrs=None,
  read_attrmode = 'view',
  read_attrmimetype = None,
):

  if 'read_output' in form.inputFieldNames and form.field['read_output'].value[0]:
    read_output = form.field['read_output'].value[0]
  else:
    read_output = 'template'

  # Determine how to get all attributes including the operational attributes
  if dn=='' and ls.supportsAllOpAttr:
    attrtypes = ['+']
  else:
    objectClasses = ls.getObjectClasses(dn)
    if 'subschema' in objectClasses:
      attrtypes = [
        'cn','dITStructureRules','nameForms','ditContentRules',
        'objectClasses','attributeTypes','matchingRules','matchingRuleUse'
      ]
    else:
      attrtypes = None

  # Read the whole entry
  search_result = ls.readEntry(dn,attrtypes)

  if not search_result:
    raise w2lapp.core.ErrorExitClass(ls,dn,'Empty search result.')
  # Save session into database
  w2lapp.core.session.storeSession(sid,ls)

  # Determine all attribute types
  all_attrs = search_result[0][1].keys()
  # Attribute herausloeschen, welche unsichtbar bleiben sollen
  all_attrs = listremove(
    all_attrs,
    w2lapp.cnf.GetParam(ls,'hiddenattrs',[]),
    ignorecase=1
  )

  # Specific attributes requested?
  if 'read_attr' in form.inputFieldNames:
    wanted_attrs = form.field['read_attr'].value
  else:
    if wanted_attrs is None:
      wanted_attrs = all_attrs

  entry = msbase.CaseinsensitiveStringKeyDict(search_result[0][1],None)

  if (not wanted_attrs) or len(wanted_attrs)>1 or \
     (not wanted_attrs[0].lower() in w2lapp.core.ldap_binaryattrkeys):
    operational_attrs_template = getOperationAttrsTemplate(ls,form.accept_language)
    operational_attrs = msbase.GrabKeys(operational_attrs_template)()
    operational_attrs.append('objectClass')
    operational_search_result = ls.readEntry(dn,operational_attrs)
    try:
      operational_entry = msbase.CaseinsensitiveStringKeyDict(operational_search_result[0][1],[])
    except IndexError:
      operational_entry = {}
    entry['objectClass'] = operational_entry.get('objectClass',[])

  if read_output in ['table','template']:

    if (len(wanted_attrs)==1) and \
       (wanted_attrs[0].lower() in w2lapp.core.ldap_binaryattrkeys):

      attr_type = wanted_attrs[0]
      if not entry.has_key(attr_type):
        raise w2lapp.core.ErrorExitClass(ls,dn,'Attribute not in entry.')

      # Send a single binary attribute with appropriate MIME-type
      if 'read_attrindex' in form.inputFieldNames:
        read_attrindex = int(form.field['read_attrindex'].value[0])
	if read_attrindex>=len(entry[attr_type]):
          raise w2lapp.core.ErrorExitClass(ls,dn,'Illegal index for multi-valued binary attribute.')
      else:
        read_attrindex = None
      if 'read_attrmode' in form.inputFieldNames:
        # Determine if user wants to view or download the binary attribute value
        read_attrmode = form.field['read_attrmode'].value[0]
      if (read_attrmode=='view') and \
         (w2lapp.viewer.viewer_func.has_key(attr_type)):
        # Nice displaying of binary attribute with viewer class
        w2lapp.gui.TopSection(
          sid,outf,form,ls,dn,
          w2lapp.core.utf2display(form.accept_charset,dn),
          w2lapp.gui.MainMenu(sid,form,ls,dn),
          context_menu_list=[]
        )
        outf.write('<div id=MessageDiv>\n')
        w2lapp.viewer.viewer_func[attr_type](
	  sid,outf,command,form,dn,attr_type,entry,read_attrindex
	)
	outf.write('</div>\n')
	w2lapp.gui.PrintFooter(outf,form)
      else:
        # Raw download with (hopefully) appropriate MIME-type
        if 'read_attrmimetype' in form.inputFieldNames:
          read_attrmimetype = form.field['read_attrmimetype'].value[0]
        w2lapp.viewer.DisplayBinaryAttribute(
          sid,outf,command,form,attr_type,entry,read_attrindex,read_attrmimetype
	)

    else:

      # Display the whole entry

      w2lapp.gui.TopSection(
        sid,outf,form,ls,dn,
        w2lapp.core.utf2display(form.accept_charset,dn),
        w2lapp.gui.MainMenu(sid,form,ls,dn),
        context_menu_list=w2lapp.gui.ContextMenuSingleEntry(sid,form,ls,dn)
      )

      objectclasses = entry.get('objectclass',entry.get('objectClass',[]))
      displayed_attrs=[]
      # List of already displayed attributes
      display_entry = DisplayEntry(
        sid,form,ls,dn,entry,w2lapp.read.oid,commandbutton=1
      )
      
      outf.write('<div id=MessageDiv>\n')

      if read_output=='template':
        # Determine relevant HTML templates
        read_template_dict = msbase.CaseinsensitiveStringKeyDict(
          w2lapp.cnf.GetParam(ls,'read_template',{})
        )
        template_oc = intersection(
          [ s.lower() for s in objectclasses ],
          [ s.lower() for s in read_template_dict.keys() ],
          ignorecase=1
        )
        # Templates defined => display the entry with the help of the template
        used_templates = []
	for oc in template_oc:
	  read_template_filename = w2lapp.gui.GetVariantFilename(read_template_dict[oc],form.accept_language)
          if not read_template_filename in used_templates:
            used_templates.append(read_template_filename)
            if read_template_filename:
	      try:
  	        template_str = open(read_template_filename,'r').read()
	      except IOError:
	        outf.write('<p>I/O error during reading template file for object class <strong>%s</strong>.</p>' % oc)
              else:
  	        outf.write(template_str % HTMLTemplateEntry(display_entry))
                displayed_attrs = msbase.union(displayed_attrs,msbase.GrabKeys(template_str)())
	    else:
              outf.write('<p>Template file for object class <strong>%s</strong> not found.</p>' % oc)

      if read_output=='template':
        # Remove attributes already displayed with HTML templates
        # and operational attributes displayed with 
        wanted_attrs = listremove(
          wanted_attrs,
          displayed_attrs+operational_attrs,
          ignorecase=1
        )

      if wanted_attrs:
	# display the attributes of the entry in a raw way
	required_attrs = []
	allowed_attrs = []
	for oc in objectclasses:
	  oc_def = ldap_oc.get(oc,{})
	  required_attrs = union(required_attrs,oc_def.get('requires',[]))
	  allowed_attrs = union(allowed_attrs,oc_def.get('allows',[]))
	required_attrs = intersection(
          [ s.lower() for s in required_attrs ],
          wanted_attrs,
          ignorecase=1
        )
	allowed_attrs = intersection(
          [ s.lower() for s in allowed_attrs ],
          wanted_attrs,
          ignorecase=1
        )
	nomatching_attrs = listremove(
          wanted_attrs,
          required_attrs+allowed_attrs+['objectClass'],
          ignorecase=1
        )
	required_attrs = listremove(required_attrs,['objectClass'],ignorecase=1)
	allowed_attrs = listremove(allowed_attrs,['objectClass'],ignorecase=1)

        if operational_entry.has_key('objectClass'):
          PrintAttrList(sid,outf,form,dn,operational_entry,['objectClass'],['objectClass'],'Object classes')
	PrintAttrList(sid,outf,form,dn,display_entry,required_attrs,all_attrs,'Required Attributes')
	PrintAttrList(sid,outf,form,dn,display_entry,allowed_attrs,all_attrs,'Allowed Attributes')
	PrintAttrList(sid,outf,form,dn,display_entry,nomatching_attrs,all_attrs,'Attributes not matching schema')


      # Display operational attributes with template as footer
      if read_output=='template':
        outf.write(operational_attrs_template % (HTMLTemplateEntry(DisplayEntry(
            sid,form,ls,dn,operational_entry,oid=w2lapp.read.oid,commandbutton=1
          ))))
      outf.write('</div>\n')

      w2lapp.gui.PrintFooter(outf,form)

  elif read_output=='vcard':

    objectclasses = entry.get('objectclass',entry.get('objectClass',[]))

    vcard_template_dict = msbase.CaseinsensitiveStringKeyDict(
      w2lapp.cnf.GetParam(ls,'vcard_template',{})
    )
    template_oc = intersection(
      [ s.lower() for s in objectclasses],
      [ s.lower() for s in vcard_template_dict.keys() ],
      ignorecase=1
    )

    if template_oc:

      # Templates defined => display the entry with the help of a template
      display_entry = msbase.CaseinsensitiveStringKeyDict(default='')
      display_entry['dn'] = dn.encode('iso-8859-1')
      attr_list = [
        attrtype
        for attrtype in entry.keys()
        if not attrtype in w2lapp.core.ldap_binaryattrkeys
      ]
      for attr in attr_list:
        itemlist = []
        try:
          for item in ldaputil.base.unicode_list(entry[attr],ls.charset):
            itemlist.append('\, '.join(item.encode('iso-8859-1').split('$')))
        except UnicodeError:
          for item in ldaputil.base.unicode_list(entry[attr],'iso-8859-1'):
            itemlist.append('\, '.join(item.encode('iso-8859-1').split('$')))
	display_entry[attr] = ';'.join(itemlist)

      oc = template_oc[0]
      vcard_template_filename = w2lapp.gui.GetVariantFilename(vcard_template_dict[oc],form.accept_language)
      if vcard_template_filename:
	try:
  	  template_str = open(vcard_template_filename,'r').read()
	except IOError:
	  raise w2lapp.core.ErrorExitClass(
            ls,dn,
            """I/O error during reading template file for 
            object class <strong>%s</strong>.""" % (oc)
          )
        else:
          pyweblib.httphelper.SendHeader(
            outf,'text/x-vcard',
            charset='ISO-8559-1',
            expires_offset=w2lapp.cnf.misc.sec_expire,
          )
  	  outf.write(template_str % display_entry)
      else:
        raise w2lapp.core.ErrorExitClass(
          ls,dn,
          """vCard template file for object class
          <strong>%s</strong> not found.""" % (oc)
        )

    else:
      raise w2lapp.core.ErrorExitClass(
        ls,dn,
        """No template file defined for object
        class(es) <strong>%s</strong>.""" % (', '.join(objectclasses))
      )
