source: trunk/LeMillTool.py @ 1751

Revision 1751, 28.4 KB checked in by jukka, 13 years ago (diff)

Worked on #1365, #1366. Spent 12h. Major refactoring of browsing views. Should be working except portfolios and tests. Code should be much, much clearer, less insane and faster.

Line 
1# Copyright 2006 by the LeMill Team (see AUTHORS)
2#
3# This file is part of LeMill.
4#
5# LeMill is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# LeMill is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with LeMill; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19from OFS.SimpleItem import SimpleItem
20from OFS.PropertyManager import PropertyManager
21from Products.CMFCore.utils import UniqueObject
22from Globals import InitializeClass
23from AccessControl import ClassSecurityInfo
24from Products.CMFCore.utils import getToolByName
25from config import TYPE_NAMES, SEARCHABLE_TYPES, CONTENT_TYPES, COMMUNITY_TYPES, TOOLS_TYPES, ACTIVITY_TYPES, MATERIAL_TYPES, GOOD_STORIES_ONLY
26from permissions import MANAGE_PORTAL
27from itertools import chain
28from Products.LeMill.Resources import Resource
29from random import choice
30import datetime
31from DateTime import DateTime
32import time, re
33from Products.LeMill import LeMillMessageFactory as _
34
35
36import sre
37from itertools import chain, dropwhile
38
39brackets=sre.compile('\[(.*?)\]')
40hreflinks=sre.compile('<a href="(.*?)">(.*?)</a>')
41httplinks=sre.compile('(?<!"|>)(http://[^ \t\n\r<>"]+)') # ignore those already in tags
42lessthan=sre.compile('(<)(?!a |/a)') # ignore links, aka those beginning with '<a' or '</a'
43greaterthan=sre.compile('(?<!" |/a|.")(>)') # ignore links, aka those ending with '" >','">' or '/a>'
44
45class LeMillTool(PropertyManager, UniqueObject, SimpleItem):
46    """ Tool for miscallenous methods """
47
48    id = 'lemill_tool'
49    meta_type = 'LeMillTool'
50    security = ClassSecurityInfo()
51    plone_tool = 1
52    toolicon = 'skins/lemill/tool.gif'
53    __allow_access_to_unprotected_subobjects__ = 1
54
55
56    security.declarePublic('createUniqueGroupId')
57    def createUniqueGroupId(self, basename):
58        #Groups should always use titles for display, but urls use ids so lets make ids understandable & unique.
59        # basename should be user's id or material's id, we add _group+numbers after that.
60        basename=str(basename)
61        grouptool=getToolByName(self, 'portal_groups')
62        folder=grouptool.getGroupWorkspacesFolder()
63        name=basename+'_group'
64        number=1
65        while name in folder.objectIds():
66            name=basename+'_group'+str(number)
67            number=number+1
68        return name
69
70
71    def checkTitle(self, obj=None ,title='', objtype=''):
72
73        def findUniqueId(id):
74            pc=getToolByName(self, 'portal_catalog')
75            idx = 1
76            while idx <= 100:
77                new_id = "%s-%d" % (id, idx)
78                if not pc({'id':new_id}):
79                    return new_id
80                idx += 1   
81            return None
82
83        def zero_fill(matchobj):
84            return matchobj.group().zfill(8)
85
86
87        def moveToTrash(context,id):
88            portal_url = getToolByName(self, 'portal_url')
89            portal = portal_url.getPortalObject()
90            trash=portal.trash
91            trash.manage_pasteObjects(context.manage_cutObjects(id))
92            moved=getattr(trash, id)
93            moved.setId(moved.UID())
94            moved.unindexObject()
95
96        pc=getToolByName(self, 'portal_catalog')
97        plone_tool = getToolByName(self, 'plone_utils', None)
98        sortabletitle = title.lower().strip()
99        # Replace numbers with zero filled numbers
100        num_sort_regex = re.compile('\d+')
101        sortabletitle = num_sort_regex.sub(zero_fill, sortabletitle)
102        sortabletitle = sortabletitle[:30]
103        delete_list=[]
104        objUID='Fail'
105        if obj:
106            objUID=obj.UID()
107       
108        # If we return True, but obj id is still reserved, plone machinery will give it id-1, id-2..
109        # So pieces always return true, but we still want them to overwrite deleted and redirectors.
110       
111        # Is title ok as a title?
112        matches=pc({'sortable_title':sortabletitle})
113        if matches:
114            for match in matches:
115                if match.portal_type=='Redirector' or match.review_state=='deleted':
116                    if match.UID not in [d.UID for d in delete_list]:
117                        delete_list.append(match)
118                elif match.UID==objUID:
119                    pass
120                elif objtype=='Piece' or match.portal_type=='Piece':
121                    pass
122                else:
123                    return False
124
125        # Will this title make a good id?       
126        tempid=plone_tool.normalizeString(title)
127        matches=pc({'id':tempid})
128        if matches:
129            for match in matches:
130                if match.portal_type=='Redirector' or match.review_state=='deleted':
131                    if match.UID not in [d.UID for d in delete_list]:
132                        delete_list.append(match)
133                elif match.portal_type=='Piece' and objtype!='Piece':
134                    # if some piece is having this id, rename it instead.
135                    renamed=match.getObject()
136                    renamed.setId(findUniqueId(tempid))
137                    pass                   
138                   
139                elif match.UID==objUID:
140                    pass
141                elif objtype=='Piece':
142                    pass
143                else:
144                    return False
145
146        # If otherwise ok, delete Redirectors and Deleted objects from our way.
147        for o in delete_list:
148            delpath=o.location
149            delid=o.id
150            o=o.getObject()
151            context=o.aq_parent
152            if o.portal_type=='Redirector':
153                context.manage_delObjects([delid])
154            else:
155                moveToTrash(context,delid)           
156        return True         
157
158   
159    security.declarePublic('render')
160    def render(self,body):
161 
162        body="\n\n%s\n\n" % str(body)
163       
164    body = body.replace('\r\n', '\n').replace('\r', '\n')
165
166        body = re.sub(r'\n\n\*\s', r'\n\n<ul><li>', body)
167        body = re.sub(r'\n\*\s(.*)\n\n', r'</li><li>\1</li></ul>\n\n', body)
168        body = re.sub(r'\n\*\s', r'</li><li>', body)
169
170        body = re.sub(r'\n\n\#\s', r'\n\n<ol><li>', body)
171        body = re.sub(r'\n\#\s(.*)\n\n', r'</li><li>\1</li></ol>\n\n', body)
172        body = re.sub(r'\n\#\s', r'</li><li>', body)
173
174
175        body = body.replace('\n\n<ul>',"</p><ul>")
176        body = body.replace('</ul>\n\n',"</ul><p>")
177        body = body.replace('\n\n<ol>',"</p><ol>")
178        body = body.replace('</ol>\n\n',"</ol><p>")
179        body = body.replace('\n\n',"</p><p>")
180        body = body.replace('\n',"<br />")
181   
182        body = re.sub(r'\A</p>', '', body)
183        body = re.sub(r'<p>\Z', '', body)
184
185        body = re.sub(r'\[(\S+?)\s(.*?)\]', r'<a href="\1">\2</a>', body)
186       
187        #raise 'FOO', str(body)
188        body=re.sub(r'([\W>])\*(\S.*?\S)\*([\W<])', r'\1<b>\2</b>\3',body)
189        body=re.sub(r'([\W>])_(\S.*?\S)_([\W<])', r'\1<i>\2</i>\3',body)
190        body = re.sub(r'\A\s(.*)\s\Z',r'<p>\1</p>',body)
191        return body
192
193    security.declarePublic('listrender')
194    def listrender(self, body):
195        # like render, but without all of the stuff related to new lines
196        body = re.sub(r'\[(\S+?)\s(.*?)\]', r'<a href="\1">\2</a>', body)
197        body=re.sub(r'([\W>])\*(\S.*?\S)\*([\W<])', r'\1<b>\2</b>\3',body)
198        body=re.sub(r'([\W>])_(\S.*?\S)_([\W<])', r'\1<i>\2</i>\3',body)
199        body = re.sub(r'\A\s(.*)\s\Z',r'<p>\1</p>',body)
200        body = re.sub(r'<?p>','', body)
201        return body
202
203    security.declarePublic('searchable_types')
204    def searchable_types(self):
205        return list(SEARCHABLE_TYPES)
206       
207    security.declarePublic('searchable_types')
208    def getTypeName(self, type):
209        return TYPE_NAMES.get(type, [type])[0]
210
211    def resize_image(self, image, to_width=120, to_height=120, format="PNG"):
212        # resize image to given width and height. StringIO, im will be returned
213        try:
214            from PIL import Image
215        except ImportError:
216            return None, image
217        import cStringIO
218        val = None
219        # attempt to get pure binary data
220        try:
221            val = image.read()
222        except AttributeError: # no read method
223            val = image
224        if hasattr(image, 'data'):
225            val = image.data
226            if not isinstance(val, type('')):
227                val = val.aq_base
228                from OFS.Image import Pdata
229                if isinstance(val, Pdata):
230                    #print "this is Pdata"
231                    val = str(val)
232        s = cStringIO.StringIO(val)
233        s.seek(0)
234        im = Image.open(s)
235        (width, height) = im.size
236        if width > to_width:
237            mod = float(to_width)/float(width)
238            width = width*mod
239            height = height*mod
240            im = im.resize((int(width),int(height)), Image.ANTIALIAS)
241        if height > to_height:
242            u_w = float(to_height)/float(height)
243            width = width*u_w
244            height = height*u_w
245            im = im.resize((int(width),int(height)), Image.ANTIALIAS)
246        s = cStringIO.StringIO()
247        im.save(s, "PNG")
248        s.seek(0)
249        return s, im
250
251    def getPrettyLanguage(self, lang_code):
252        if len(str(lang_code))<3:
253            ts=getToolByName(self,'translation_service')
254            languages = ts.availableLanguages()
255            if dict(languages).has_key(lang_code):
256                return dict(languages)[lang_code]
257            else:
258                return lang_code
259        else:
260            return lang_code
261
262    def savePiece(self, uid, values):
263        # Edit one piece
264        putils= getToolByName(self, 'plone_utils')
265        problem=''
266        piece=self.uid_catalog(UID=uid)
267        piece=piece[0].getObject()
268        if values.has_key('title') and values['title']:
269            if not piece.checkTitle(values['title']):
270                msg = _(u"Title ${title} is already in use.", mapping={u'title' : self.title_or_id()})
271                putils.addPortalMessage(msg)
272                del values['title']               
273        piece.edit(**values)
274        piece.at_post_edit_script()
275
276    def giveLanguageLink(self, REQUEST, lang, url, param):
277        """ Changes the language and redirects to the needed link """
278        self.portal_languages.setLanguageCookie(lang, REQUEST, None)
279        if param:
280            param = param.replace('_and_', '&')
281            return REQUEST.RESPONSE.redirect(url+'?'+param)
282        return REQUEST.RESPONSE.redirect(url)
283
284    def testLinkParameters(self, REQUEST):
285        """ Tests the query string for the language change """
286        stri = REQUEST.get('QUERY_STRING')
287        stri = stri.replace('&', '_and_')
288        return stri
289
290    def getStoryCandidate(self, only_with_story=True):
291        """ Random pick for one content, method and tool or story if only_with_story """
292        pc=getToolByName(self, 'portal_catalog')
293        if GOOD_STORIES_ONLY and only_with_story:
294            stories=pc({'getGoodStory':True})
295            if not stories:
296                return self.getStoryCandidate(only_with_story=False)
297            story=choice(stories)
298            story=story.getObject()
299            content=[x for x in story.getRelatedContent() if x.getHasCoverImage()]
300            method=[x for x in story.getRelatedMethods() if x.getHasCoverImage()]
301            tool=[x for x in story.getRelatedTools() if x.getHasCoverImage()]
302            if content:
303                content=choice(content)
304            if method:
305                method=choice(method)
306            if tool:
307                tool=choice(tool)
308        else:
309            full_results=pc({'review_state':'public','getHasCoverImage':True})
310            content=[x for x in full_results if x.meta_type in MATERIAL_TYPES]
311            method=[x for x in full_results if x.meta_type=='Activity']
312            tool=[x for x in full_results if x.meta_type=='Tool']
313            if content:
314                content=choice(content).getObject()
315            if method:
316                method=choice(method).getObject()
317            if tool:
318                tool=choice(tool).getObject()
319            if content and method and tool:
320                stories=pc({'getRawRelatedContent':content.UID(), 'getRawRelatedActivities':method.UID(), 'getRawRelatedTools':tool.UID()})
321                if stories:
322                    story=choice(stories)
323                    story=story.getObject()
324                else:
325                    story=None
326            else:
327                story=None
328        candidate = {'chcontent':content, 'method':method, 'tool':tool, 'story':story}
329        return candidate
330       
331       
332
333    security.declareProtected(MANAGE_PORTAL, 'manage_cleanAfterSchemaUpdate')
334    def manage_cleanAfterSchemaUpdate(self, admin=''):
335        """ Archetype update rewrites fields for objects
336        and so user running the update gets to be author.
337        We fix that by running update authors for all objects with special request to
338        ignore modifications this user in last hour. """
339        if not admin:
340            mtool = getToolByName(self, 'portal_membership')
341            admin = mtool.getAuthenticatedMember()
342            admin = admin.getId()
343        for x in chain(self.content.objectValues(CONTENT_TYPES),
344                        self.tools.objectValues(TOOLS_TYPES),
345                        self.activities.objectValues(ACTIVITY_TYPES)):
346            if isinstance(x, Resource):
347                x.recalculateAuthors(removeAdmin=admin)
348
349    security.declareProtected(MANAGE_PORTAL,'mergeWithAnother')
350    def mergeWithAnother(self,another):
351        """ Temporary utility method for merging another LeMill into this one."""
352        src=getattr(self,another).aq_inner
353        dst=self.aq_inner.aq_parent
354
355        dst_users = dst.community.objectIds('MemberFolder')
356        src_users = src.community.objectIds('MemberFolder')
357
358        print "Source users: %d, %s" % (len(src_users),str([x for x in src_users]))
359        print "Destination users: %d, %s" % (len(dst_users),str([x for x in dst_users]))
360
361        def fixWorkflow(obj,dobj):
362            transitions = {'public':'publish',
363                           'deleted':'delete',
364                           'draft':None,
365                           }
366            state = src.portal_workflow.getInfoFor(obj,'review_state',None)
367            dstate = dst.portal_workflow.getInfoFor(dobj,'review_state',None)
368            if state==dstate:
369                return
370            trans=transitions[state]
371            if trans:
372                dst.portal_workflow.doActionFor(dobj,trans)
373
374        for id in src_users:
375          if id in dst_users:
376            print "DUPLICATE: %s, %s/%s " % (id, getattr(dst.community,id).NiceName(), getattr(src.community,id).NiceName())
377          else:
378            print "creating: %s" % id
379            try:
380              dst.portal_registration.addMember(id=id,password=dst.portal_registration.generatePassword())
381            except ValueError:
382              # user id exists already - fine by us
383              pass
384            cb = src.community.manage_copyObjects(id)
385            dst.community.manage_pasteObjects(cb)
386            fixWorkflow(getattr(src.community,id),getattr(dst.community,id))
387            getattr(dst.community,id).changeOwnership(dst.acl_users.getUserById(id),1)
388
389        dst_users = dst.community.objectIds('MemberFolder')
390        src_users = src.community.objectIds('MemberFolder')
391
392        print "Source users: %d" % len(src_users)
393        print "Destination users: %d" % len(dst_users)
394
395        dst_grp = dst.community.objectIds('GroupBlog')
396        src_grp = src.community.objectIds('GroupBlog')
397
398        print "Source groups: %d" % len(src_grp)
399        print "Destination groups: %d" % len(dst_grp)
400
401        for id in src_grp:
402          if id in dst_grp:
403            print "DUPLICATE: %s" % id
404          else:
405            print "creating %s" % id
406            dst.portal_groups.addGroup(id=id)
407            s_g = src.portal_groups.getGroupById(id)
408            d_g = dst.portal_groups.getGroupById(id)
409            for m in s_g.getGroupMembers():
410                d_g.addMember(m.getId())
411            dst.community._delObject(id) # remove, because it just got created when the group was created
412            cb = src.community.manage_copyObjects(id)
413            dst.community.manage_pasteObjects(cb)
414            fixWorkflow(getattr(src.community,id),getattr(dst.community,id))
415            #TODO: ownership change
416            def __changeOwnerRecurse(s_obj,d_obj):
417                d_obj.changeOwnership(s_obj.getWrappedOwner(),0)
418                for id in d_obj.objectIds():
419                    __changeOwnerRecurse(getattr(s_obj,id),getattr(d_obj,id))
420            __changeOwnerRecurse(getattr(src.community,id),getattr(dst.community,id))
421
422        dst_cont = dst.content.objectIds(('Piece','LeMillReference','PresentationMaterial', 'MultimediaMaterial', 'PILOTMaterial'))
423        src_cont = src.content.objectIds(('Piece','LeMillReference','PresentationMaterial', 'MultimediaMaterial', 'PILOTMaterial'))
424
425        print "Source content: %d" % len(src_cont)
426        print "Destination content: %d" % len(dst_cont)
427
428        for id in src_cont:
429          if id in dst_cont:
430            print "DUPLICATE: %s" % id
431          else:
432            print "creating %s" % id
433            s = getattr(src.content,id)
434            # Fix ownerships
435            roles = getattr(s, '__ac_local_roles__')
436            for key in roles.keys():
437                if type(key)==tuple:
438                    del roles[key]
439                    s._p_changed=True
440            cb = src.content.manage_copyObjects(id)
441            dst.content.manage_pasteObjects(cb)
442            d = getattr(dst.content,id)
443            fixWorkflow(s,d)
444            try:
445                d.changeOwnership(s.getWrappedOwner(),1)
446            except AttributeError:
447                print "PROBLEM WITH OWNERSHIP OF %s" % id
448                pass # some problem with user
449
450        dst_met = dst.methods.objectIds('Activity')
451        src_met = src.methods.objectIds('Activity')
452
453        print "Source methods: %d" % len(src_met)
454        print "Destination methods: %d" % len(dst_met)
455
456        for id in src_met:
457          if id in dst_met:
458            print "DUPLICATE: %s" % id
459          else:
460            print "creating %s" % id
461            cb = src.methods.manage_copyObjects(id)
462            dst.methods.manage_pasteObjects(cb)
463            fixWorkflow(getattr(src.methods,id),getattr(dst.methods,id))
464            getattr(dst.methods,id).changeOwnership(getattr(src.methods,id).getWrappedOwner(),1)
465
466        dst_tool = dst.tools.objectIds('Tool')
467        src_tool = src.tools.objectIds('Tool')
468
469        print "Source tools: %d" % len(src_tool)
470        print "Destination tools: %d" % len(dst_tool)
471
472        for id in src_tool:
473          if id in dst_tool:
474            print "DUPLICATE: %s" % id
475          else:
476            print "creating %s" % id
477            cb = src.tools.manage_copyObjects(id)
478            dst.tools.manage_pasteObjects(cb)
479            fixWorkflow(getattr(src.tools,id),getattr(dst.tools,id))
480            getattr(dst.tools,id).changeOwnership(getattr(src.tools,id).getWrappedOwner(),1)
481
482        #TODO: password resets to all non-duplicate src_users
483
484        # Fix UID references
485        def __fixUID(uid):
486          if type(uid)==type('') and uid.isalnum() and len(uid)==32:
487            results = src.uid_catalog(UID=uid)
488            if len(results)==1:
489              obj = results[0].getObject()
490              print "Found object: %s" % repr(obj)
491              par = obj.aq_inner.aq_parent
492              return getattr(getattr(dst,par.getId()),obj.getId()).UID()
493          return uid
494
495        src_cont = src.content.objectIds(('LeMillReference','PresentationMaterial', 'MultimediaMaterial', 'PILOTMaterial'))
496
497        for id in src_cont:
498          s=getattr(src.content,id)
499          d=getattr(dst.content,id)
500          print "Processing %s." % repr(s)
501          d.setBodyText(map(__fixUID,s.getRawBodyText()))
502
503        def __fixRef(obj):
504          print "Found object: %s" % repr(obj)
505          par = obj.aq_inner.aq_parent
506          return getattr(getattr(dst,par.getId()),obj.getId())
507
508        for mfolder in src.community.objectValues('MemberFolder'):
509          d_mfolder = getattr(dst.community,mfolder.getId())
510
511          field = mfolder.Schema().get('listOfContacts')
512          values = [x.UID() for x in field.get(m)]
513          print "found %s" % repr(values)
514          if values:
515              values2 = map(__fixUID,values)
516              print "setting to %s" % repr(values2)
517              field.set(d_mfolder,values2)
518
519          for coll in mfolder.collections.objectValues('Collection'):
520            print "Collection %s." % repr(coll)
521            for typ in ('relatedContent','relatedMethods','relatedTools'):
522              try:
523                d = getattr(d_mfolder.collections,coll.getId())
524                field = coll.Schema().get(typ)
525                f = coll.getReferenceImpl(relationship=field.relationship)
526                vals = [(x.collection_position,x.getTargetObject()) for x in f]
527                rel2 = map(__fixRef,[x[1] for x in vals])
528                uids = [x.UID() for x in rel2]
529                print "%s to %s (%s)" % (repr(vals),repr(rel2),repr(uids))
530                field.set(d,None) # empty value
531                for i in range(len(rel2)):
532                    field.set(d,field.getRaw(d)+[rel2[i],],collection_position=vals[i][0])
533              except AttributeError:
534                pass # this collection doesn't exist - most likely because the account already existed prior to the merge
535
536        return "DONE"
537
538
539    def getTimeDifference(self,modtime):
540        """ Returns difference between now and given time in dictionary"""
541        now=time.time()
542        modtime=DateTime(modtime).timeTime()
543        diff=int(now-modtime)
544        dict={'largest_meaningful_unit':'seconds'}
545        for (unitname,seconds) in [('minutes',60),('hours',3600),('days',86400),('weeks',604800),('months',2592000)]:
546            dict[unitname]=diff/seconds
547            if diff/seconds:
548                dict['largest_meaningful_unit']=unitname
549        return dict
550
551
552    def htmlify(self,s):
553        """ does NOT put a <p> at the beginning and </p> at the end... """
554        s=self.parse_bracket_links(s) # convert [Objectname name]:s to links
555        s=httplinks.sub('<a href="\\1">\\1</a>',s) # convert plain http://something:s to links
556        for tag in ('<p>','<br />','<li>','<h2>','<pre>','<i>','<b>','<object ', '<embed '):
557            if s.find(tag) != -1: # if it already looks like html, do no more
558                return s
559        s=lessthan.sub('&lt;',s) # convert <:s
560        s=greaterthan.sub('&gt;',s) # convert >:s
561        return s.replace('\r', '').replace('\n\n', '</p><p>')\
562                .replace('\n', '<br />').replace('<p><br />', '<p>')\
563                .replace('<p></p>', '') # replace line changes with paragraph tags etc.
564
565    @staticmethod
566    def shorten_link_names(s, max_len=60):
567        l = hreflinks.split(s)   # [stuff, link, link_name, stuff, link, link_name, stuff,...]
568        assert len(l) % 3 == 1
569
570        new = []
571        for stuff, link, link_name in zip(l[::3], l[1::3], l[2::3]):    # this 'zip...' stuff groups the list by 3 element                # works only if there are at least 4 slashes without the trailing slash
572            link=link[:link.find('"')] # ignore othere attributes, f.ex 'http://something/url" target="_self'
573            if link == link_name and len(link_name) > max_len and link_name[:-1].count('/') > 3:
574                start = link_name.find('/', 7)      # after 'http://'
575                end = link_name.rfind('/', 0, -1)   # skips possible trailing '/'
576                ln = '%s/...%s' % (link_name[:start], link_name[end:])
577                if len(ln) < len(link_name):
578                    link_name = ln
579            new.append('%s<a href="%s">%s</a>' % (stuff, link, link_name))
580
581        assert len(new) == len(l) // 3
582        return ''.join(new + [l[-1]])
583
584
585    def parse_bracket_links(self, s):
586        pc=getToolByName(self, 'portal_catalog')
587        l = brackets.split(s) # [stuff, link, stuff..]
588        new = []
589        for stuff, linkstr in zip(l[::2], l[1::2]):
590            link=linkstr.split(' ',1) # whitespace split
591            if len(link)==1:
592                link_name=link[0]
593            else:
594                link_name=link[1]
595            if link[0].startswith("http://"):
596                link=link[0]
597            else:               
598                matches=pc({'id':link[0].lower()})
599                if matches:
600                    if len(matches)==1:                   
601                        link=matches[0].getURL()
602                    else:
603                        for m in matches:
604                            if m.portal_type!='BlogPost':
605                                link=m.getURL()
606                                continue
607                else:
608                    link=''
609            if link:
610                new.append('%s<a href="%s">%s</a>' % (stuff, link, link_name))
611            else:
612                new.append('%s[%s]' % (stuff,linkstr))
613        return ''.join(new + [l[-1]])
614
615    @staticmethod
616    def split_at_p_or_br(s, min_len=200, max_len=300, seps=('</p>', '<br />', '<')): # don't use ' ' for sep, it's automatically used as a fallback
617        """ Tries to split at first separator between min_len and max_len.
618            If it doesn't succeed, it moves on to the next separator... """
619        if len(s) < max_len:
620            return s
621
622        def find_pattern(s, pattern):
623            i = -1
624            while True:
625                i = s.find(pattern, i + 1)
626                if i == -1:
627                    break
628                yield i
629
630        def find_space(s):
631            gen = enumerate(s)
632            while True:
633                gen = dropwhile(lambda x: x[1] not in (' ', '<'), gen)
634                i, c = gen.next()
635                if c == ' ':
636                    yield i
637                elif c == '<':
638                    gen = dropwhile(lambda x: x[1] != '>', gen)
639
640        for gen in chain((find_pattern(s, sep) for sep in seps if sep != ' '), [find_space(s)]):
641            l = [x for x in gen if min_len < x < max_len]
642            if l:
643                return s[:l[-1]]
644
645        return s
646
647    def doWeMigrate(self,old,new):
648        """ Returns True if old version is smaller. """
649        # If the version numbers are equal, then True is returned
650        if old == new:
651            return True
652        # This one will check if the new version is larger that the old one
653        old_ver = old.split('.')
654        new_ver = new.split('.')
655        old_len = len(old_ver)
656        new_len = len(new_ver)
657
658        if old_len >= new_len:
659            length = old_len
660        else:
661            length = new_len
662
663        if old_len > new_len:
664            for k in range(old_len - new_len):
665                new_ver.append('0')
666        if old_len < new_len:
667            for k in range(new_len - old_len):
668                old_ver.append('0')
669
670        for a in range(0,length):
671            if int(new_ver[a]) > int(old_ver[a]):
672                return True
673            if int(old_ver[a]) > int(new_ver[a]):
674                return False
675       
676        # Check the modified versions with same length if they are equal
677        if new_ver == old_ver:
678            return True
679
680    def prioritizeResults(self, results):
681        """ First by language:
682            1)selected user interface language
683            2)by user profile's language selections
684            3)english
685            4)others
686            secondary sort by points
687            """
688        ilanguage=getToolByName(self, 'portal_languages').getLanguageCookie()
689        ulanguages=getToolByName(self, 'portal_membership').getHomeFolder().getLanguage_skills()
690
691        def compfunc(a,b):
692            # this looks stupid, but hopefully we don't have to go very far in those ifs per run.
693            al=a.Language
694            bl=b.Language
695            if al==ilanguage and bl!=ilanguage:
696                return -1
697            if al!=ilanguage and bl==ilanguage:
698                return 1
699            if al in ulanguages and bl not in ulanguages:
700                return -1
701            if al not in ulanguages and bl in ulanguages:
702                return 1
703            if al=='en' and bl!='en':
704                return -1
705            if al!='en' and bl=='en':
706                return 1
707            return cmp(b.score, a.score)
708           
709        return results.sort(compfunc)
710       
711                               
712
713
714
715InitializeClass(LeMillTool)
Note: See TracBrowser for help on using the repository browser.