Test coverage for vnccollab.theme.browser.livesearch
1: from DateTime import DateTime
1: from zope.component import getMultiAdapter
1: from Products.CMFCore.utils import getToolByName
1: from Products.CMFPlone import PloneMessageFactory as _
1: from Products.CMFPlone.utils import safe_unicode
1: from Products.PythonScripts.standard import url_quote_plus
1: from Products.PythonScripts.standard import html_quote
1: from Products.CMFPlone.browser.navtree import getNavigationRoot
1: from Products.Five import BrowserView
1: from vnccollab.common.livesearch import get_query
2: class LiveSearchReplyView(BrowserView):
1: def result(self):
9: context = self.context
9: request = self.request
9: response = self.request.response
9: q = request.get('q')
9: limit = int(request.get('limit', 10))
9: path = request.get('path', None)
9: ploneUtils = getToolByName(context, 'plone_utils')
9: portal_url = getToolByName(context, 'portal_url')()
9: pretty_title_or_id = ploneUtils.pretty_title_or_id
#plone_view = context.restrictedTraverse('@@plone')
#portal_state = context.restrictedTraverse('@@plone_portal_state')
9: portalProperties = getToolByName(context, 'portal_properties')
9: siteProperties = getattr(portalProperties, 'site_properties', None)
9: useViewAction = []
9: if siteProperties is not None:
9: useViewAction = siteProperties.getProperty('typesUseViewActionInListings', [])
# SIMPLE CONFIGURATION
#USE_ICON = True
9: MAX_TITLE = 29
9: MAX_DESCRIPTION = 93
# generate a result set for the query
9: catalog = context.portal_catalog
9: mtool = getToolByName(context, 'portal_membership')
9: friendly_types = ploneUtils.getUserFriendlyTypes()
9: def quotestring(s):
18: return '"%s"' % s
9: def quote_bad_chars(s):
9: bad_chars = ["(", ")"]
27: for char in bad_chars:
18: s = s.replace(char, quotestring(char))
9: return s
9: def pretty_date(when):
36: result = ('%s %s, %s') % (DateTime(when).strftime('%B'), DateTime(when).strftime('%d'), DateTime(when).strftime('%Y'))
36: return result
9: searchable_text = q
9: multispace = u'\u3000'.encode('utf-8')
54: for char in ('?', '-', '+', '*', multispace):
45: q = q.replace(char, ' ')
9: r = q.split()
9: r = " AND ".join(r)
9: r = quote_bad_chars(r) + '*'
9: searchterms = url_quote_plus(r)
9: site_encoding = context.plone_utils.getSiteEncoding()
9: params = {'SearchableText': r,
9: 'portal_type': friendly_types,
9: 'sort_limit': limit + 1}
9: if path is None:
# useful for subsides
8: params['path'] = getNavigationRoot(context)
else:
1: params['path'] = path
# search limit+1 results to know if limit is exceeded
9: params = get_query(searchable_text, params)
9: results = catalog(**params)
9: searchterm_query = '?searchterm=%s' % url_quote_plus(q)
#request = context.request
#response = request.response
9: response.setHeader('Content-Type', 'text/xml;charset=%s' % site_encoding)
# replace named entities with their numbered counterparts, in the xml the named ones are not correct
# ↓ --> ↓
# … --> …
9: legend_livesearch = _('legend_livesearch', default='LiveSearch ↓')
9: label_no_results_found = _('label_no_results_found', default='No matching results found.')
9: label_advanced_search = _('label_advanced_search', default='Advanced Search…')
9: label_show_all = _('label_show_all', default='Show all items')
9: ts = getToolByName(context, 'translation_service')
9: output = []
9: def write(s):
549: output.append(safe_unicode(s))
9: if not results:
2: write('''<fieldset class="livesearchContainer">''')
2: write('''<legend id="livesearchLegend">%s</legend>''' % ts.translate(legend_livesearch, context=request))
2: write('''<div class="LSIEFix">''')
2: write('''<div id="LSNothingFound">%s</div>''' % ts.translate(label_no_results_found, context=request))
2: write('''<ul class="ls-foot">''')
2: write('''<li class="LSRow lsrow-adv-search">''')
2: write('<b></b><a href="%s" style="font-weight:normal">%s</a>' %
2: (portal_url + '/@@search',
2: ts.translate(label_advanced_search, context=request)))
2: write('''</li>''')
2: write('''</ul>''')
2: write('''</div>''')
2: write('''</fieldset>''')
else:
7: write('''<fieldset class="livesearchContainer">''')
7: write('''<legend id="livesearchLegend">%s</legend>''' % ts.translate(legend_livesearch, context=request))
7: write('''<div class="LSIEFix">''')
7: write('''<ul class="LSTable">''')
43: for result in results[:limit]:
# breadcrumbs
36: obj = result.getObject()
36: breadcrumbs_view = getMultiAdapter((obj, request), name='breadcrumbs_view')
36: breadcrumbs = breadcrumbs_view.breadcrumbs()
36: ls_breadcrumb = ''
36: breadcrumbs_size = len(breadcrumbs) - 1
36: if breadcrumbs_size > 0:
73: for ls_key in breadcrumbs[:breadcrumbs_size]:
42: ls_breadcrumb += ('''<a href="%s">%s</a> > ''' % (ls_key['absolute_url'], ls_key['Title']))
36: is_folderish = result.is_folderish
36: if is_folderish:
12: length_size = len(obj)
else:
24: length_size = result.getObjSize
#icon = plone_view.getIcon(result)
36: img_class = '%s-icon' % ploneUtils.normalizeString(result.portal_type)
36: member = mtool.getMemberById(result.Creator)
36: if member is not None:
36: fullname = member.getProperty('fullname')
else:
>>>>>> fullname = ''
36: itemUrl = result.getURL()
36: if result.portal_type in useViewAction:
>>>>>> itemUrl += '/view'
36: itemUrl = itemUrl + searchterm_query
36: write('''<li class="LSRow">''')
#write(icon.html_tag() or '')
36: write('''<div class="%s ls-content-icon"></div>''' % (img_class))
36: write('''<div class="ls-details">''')
36: full_title = safe_unicode(pretty_title_or_id(result))
36: if len(full_title) > MAX_TITLE:
5: display_title = ''.join((full_title[:MAX_TITLE], '...'))
else:
31: display_title = full_title
36: full_title = full_title.replace('"', '"')
#klass = 'contenttype-%s' % ploneUtils.normalizeString(result.portal_type)
36: klass = 'ls-content-title'
36: write('''<a href="%s" title="%s" class="%s">%s</a>''' % (itemUrl, full_title, klass, display_title))
36: display_description = safe_unicode(result.Description)
36: if len(display_description) > MAX_DESCRIPTION:
4: display_description = ''.join((display_description[:MAX_DESCRIPTION], '...'))
# need to quote it, to avoid injection of html containing javascript and other evil stuff
36: display_description = html_quote(display_description)
36: write('''<div class="LSDescr">%s</div>''' % (display_description))
36: if breadcrumbs_size > 0:
31: write('''<div class="LSBreadcrumb">in %s</div>''' % (ls_breadcrumb[:-3]))
else:
5: write('''<div class="LSBreadcrumb">in Home</div>''')
36: write('''<div class="LSMeta">''')
36: display_type = html_quote(safe_unicode(result.Type))
36: write('''<span class="LSType">%s</span>''' % (display_type))
36: if result.Type == 'File' or result.Type == 'Image':
>>>>>> write('''<span class="LSType"> • %s</span>''' % (length_size))
36: elif result.Type == 'Folder':
12: write('''<span class="LSType"> • %s item(s)</span>''' % (length_size))
36: display_creator = html_quote(safe_unicode(fullname))
36: if len(display_creator) > 0:
>>>>>> write(''' • Create by <a href="%s/author/%s" class="LSCreator">%s</a>''' %
>>>>>> (portal_url, member.getProperty('id'), display_creator))
36: display_modified = html_quote(safe_unicode((pretty_date(result.modified))))
36: write('''<span class="LSModified">on %s</span>''' % (display_modified))
36: write('''</div>''')
36: write('''</div>''')
36: write('''</li>''')
36: full_title, display_title, display_description, display_type = None, None, None, None
7: write('''</ul><ul class="ls-foot">''')
7: if len(results) > limit:
# add a more... row
2: write('''<li class="LSRow lsrow-show-all">''')
2: searchquery = '@@search?SearchableText=%s&path=%s' % (searchterms, params['path'])
2: write('<b></b><a href="%s" style="font-weight:normal">%s</a>' % (
2: searchquery,
2: ts.translate(label_show_all, context=request)))
2: write('''</li>''')
7: write('''<li class="LSRow lsrow-adv-search">''')
7: write('<b></b><a href="%s" style="font-weight:normal">%s</a>' %
7: (portal_url + '/@@search',
7: ts.translate(label_advanced_search, context=request)))
7: write('''</li>''')
7: write('''</ul>''')
7: write('''</div>''')
7: write('''</fieldset>''')
9: return '\n'.join(output).encode(site_encoding)