source: trunk/LeMillTool.py @ 1982

Revision 1982, 31.3 KB checked in by jukka, 12 years ago (diff)

Fixed parsing with pilots.

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
39
40good_sites=["http://www.youtube.com/",
41    "http://video.google.com/",
42    "http://s3.amazonaws.com/slideshare/",
43    "http://www.macromedia.com/go/",
44    "http://odeo.com/",
45    "http://fpdownload.macromedia.com/",
46    "http://www.schooltube.com/",
47    "http://maps.google.com/maps/",
48    "http://ourmedia.org/players/1pixelout/audio-player.js",
49    "http://channels.ourmedia.org/",
50    "http://www.archive.org/"]
51
52urlfinder=re.compile(r"(http://\S*)", re.IGNORECASE)
53
54pattern=re.compile(r"""
55    (?P<html_open>(<|&lt;)[a-z].*?>|(&gt)) # opening html tags, those that begin with '<x', where x is a letter
56    |(?P<html_close></.*?>) # closing html tags, those that begin with '</'
57    |(?P<url>(?<!"|')http://\S*) # http://something, where http is not preceded with " or '
58    |(?P<bracket>\[.*?\]) # everything that is put inside brackets
59    |(?P<tex>\\\(.*?\\\)) # tex should be written inside \( ... \)
60    |(?P<tex_equation>\\begin\{(?P<tex_tag>.*?)\}(?P<tex_string>.*?)\\end\{(?P=tex_tag)\}) # detect \begin{smthing}...\end{smthing}
61    |(?P<linebreak>(?<!>)$) # detect linebreaks, unless they're after closed tag, f.ex !'<br/>  \n'   
62    |(?P<awordtoolong>[^ \t\n\r\f\v<>]{41}) # detect >40 char words,
63    |(?P<endfile>\Z) # detect end of a string, so open tags can be closed
64    """, re.IGNORECASE | re.VERBOSE | re.MULTILINE)
65
66# whitelist is for html-tags only
67whitelist=re.compile(r"""
68    (p
69    |a
70    |br
71    |b
72    |i
73    |h2
74    |pre
75    |li
76    |ul
77    |ol
78    |table
79    |tr
80    |th
81    |td
82    |sub
83    |sup)
84    """, re.IGNORECASE | re.VERBOSE)
85
86restricted=re.compile(r"""
87    (?P<img>img)
88    |(?P<embed>embed)
89    |(?P<object>object)
90    |(?P<iframe>iframe)
91    |(?P<script>script)
92    """, re.IGNORECASE | re.VERBOSE)
93
94
95
96class LeMillTool(PropertyManager, UniqueObject, SimpleItem):
97    """ Tool for miscallenous methods """
98
99    id = 'lemill_tool'
100    meta_type = 'LeMillTool'
101    security = ClassSecurityInfo()
102    plone_tool = 1
103    toolicon = 'skins/lemill/tool.gif'
104    __allow_access_to_unprotected_subobjects__ = 1
105
106
107   
108    def parse_text(self, text):
109        """ Parses all tags and other strings of interest in text, replaces them with displayable version """
110
111        if type(text)==list:
112            return [self.parse_text(t) for t in text] # recursion!
113   
114        # global regex 'pattern' contains expressions to find these cases and groups them by type       
115        # These are the methods to replace matched cases of certain type
116   
117        def html_open(match):
118            full_tag=match.group('html_open')
119            tag=full_tag[1:].split(' />',1)[0] # '<tag attrib="">' -> 'tag'
120            tag_match=re.match(whitelist,tag)
121            if tag_match:
122                tag=tag_match.group()
123                if not full_tag.endswith('/>'): # also deals with self-closing tags like <br/>
124                    open_tags.append(tag)
125                print 'kept open tag %s' % full_tag
126                return full_tag
127            tag_match=re.match(restricted, tag)           
128            if tag_match:
129                if self.isGoodEmbed(full_tag):
130                    if not full_tag.endswith('/>'): # also deals with self-closing tags like <br/>
131                       open_tags.append(tag_match.group())                   
132                    print 'kept open embed %s' % full_tag
133                    return full_tag
134                print 'BAD embed: %s' % full_tag
135                return ''
136            print 'BAD tag: %s' % full_tag
137            return ''
138   
139        def html_close(match):
140            full_tag=match.group('html_close')
141            tag=full_tag.strip('<>/').lower()
142            if tag in open_tags:
143                open_tags.remove(tag)
144                print 'closed tag %s' % full_tag
145                return full_tag
146            else:
147                print 'ignored closing tag %s' % full_tag
148                return ''
149   
150        def url(match):
151            url=match.group('url')
152            if open_tags:
153                return url
154            if len(url)>50:
155                link_text=self.shorten_url(url)
156            else:
157                link_text=url
158            return '<a href="%s">%s</a>' % (url,link_text)
159   
160        def bracket(match):
161            full_tag=match.group('bracket')
162            pc=getToolByName(self, 'portal_catalog')
163            link=full_tag[1:-1].split(' ',1) # [1:-1]: '[linky dinky tinky]'->'linky dinky tinky',
164            # split(' ',1): 'linky dinky tinky' -> ['linky','dinky tinky']
165            if len(link)==1:
166                link_name=link[0]
167            else:
168                link_name=link[1]
169            if link[0].startswith("http://"): # external links won't need any processing
170                link=link[0]
171            elif link[0]:               
172                matches=pc({'id':link[0].lower()}) # find things with this id
173                if matches:
174                    if len(matches)==1:                   
175                        link=matches[0].getURL()
176                        if len(link)==1:
177                            link_name = matches[0].Title
178                    else:
179                        for m in matches:
180                            if m.portal_type!='BlogPost':
181                                link=m.getURL()
182                                if len(link)==1:
183                                    link_name = m.Title
184                                continue
185                else:
186                    link=''
187            if link:
188                return '<a href="%s">%s</a>' % (link, link_name)
189            else:
190                return full_tag
191           
192        def tex(match):
193            code=match.group('tex')
194            lt=getToolByName(self, 'latex_tool')
195            img=lt.getImageFor(code, 17)   
196            return img
197
198        def tex_equation(match):
199            print 'match for TEX EQUATION'
200            full_tag=match.group('tex_equation')
201            tex_tag=match.group('tex_tag')
202            tex_string=match.group('tex_string')
203            lt=getToolByName(self, 'latex_tool')
204            print lt.getImageFor(full_tag, 17)
205            img='''
206            <table width="100%">
207                <tr>
208                    <td align="center">
209                        %s
210                    </td>
211                </tr>
212            </table>
213            ''' % str(lt.getImageFor(full_tag, 17))           
214   
215        def linebreak(match):
216            full_tag=match.group('linebreak')
217            if open_tags or match.group('tex') or match.group('tex_equation'):
218                return full_tag
219            else:
220                return '<br/>\n'
221
222        def awordtoolong(match):
223            full_tag=match.group('awordtoolong')
224            return self.shorten_url(full_tag)           
225
226        def endfile(match):
227            """ close tags that are left open """
228            full_tag=match.group('endfile')
229            closed_tags=full_tag # -> /Z
230            for tag in open_tags:
231                closed_tags='</%s>%s' % (tag, closed_tags) # </b>/Z -> </p></b>/Z
232            return closed_tags
233
234
235        # this method gets called for every string-of-interest and forwards to correct replacer-method
236        def replacements(match):   
237            # here the order of ifs affects the results: for example the 'linebreak' case shouldn't happen
238            # if we're inside tags. I'm not sure, must test this theory.
239
240            if match.group('html_open'):
241                return html_open(match)
242            elif match.group('html_close'):
243                return html_close(match)
244            elif match.group('url'):
245                return url(match)
246            elif match.group('bracket'):
247                return bracket(match)
248            elif match.group('tex'):
249                return tex(match)
250            elif match.group('tex_equation'):
251                return tex_equation(match)
252            elif match.group('linebreak'):
253                return linebreak(match)
254            elif match.group('awordtoolong'):
255                return awordtoolong(match)
256            elif match.group('endfile'):
257                return endfile(match)
258   
259        open_tags=[]       
260        return pattern.sub(replacements, text)
261
262
263    def isGoodEmbed(self, code):
264        """ Check if sent code is compatible with known nice sources """       
265       
266        #1. find urls
267        founds = urlfinder.findall(code)
268
269        #2. check if urls fit to profiles
270       
271        if not founds:
272            if code.startswith("<script"):
273                return False # We don't want javascript trickery, even if it is local
274               
275        for match in founds:
276            print match
277            permitted=False
278            for nice_site in good_sites:
279                if match.startswith(nice_site):
280                    permitted=True
281            if not permitted:
282                return False
283        return True
284
285   
286           
287    def shorten_url(self, url):
288        """ Assumes that the url is proper and too long """
289        maxlen=40
290        leftovers=url
291        nice_url=''
292        while len(leftovers)>maxlen:
293            nice_url='%s%s<br/>' % (nice_url, leftovers[:maxlen]) # 'blaablaa<br/>'+'leftovers'+'<br/>'
294            leftovers=leftovers[maxlen:]
295        return '%s%s' % (nice_url, leftovers)
296
297    security.declarePublic('createUniqueGroupId')
298    def createUniqueGroupId(self, basename):
299        #Groups should always use titles for display, but we have to start from something
300        basename=str(basename)
301        name=basename+'_group'
302        number=1
303        folder=self.community
304        while hasattr(folder, name):
305            name=basename+'_group'+str(number)
306            number=number+1
307        return name
308
309    def checkTitle(self, obj=None ,title='', objtype=''):
310        """ Checks if title and id are available (exceptions: pieces, translations, deleted items and redirectors) """
311
312        def findUniqueId(id):
313            pc=getToolByName(self, 'portal_catalog')
314            idx = 1
315            while idx <= 100:
316                new_id = "%s-%d" % (id, idx)
317                if not pc({'id':new_id}):
318                    return new_id
319                idx += 1   
320            return None
321
322        def zero_fill(matchobj):
323            return matchobj.group().zfill(8)
324
325
326        def moveToTrash(context,id):
327            portal_url = getToolByName(self, 'portal_url')
328            portal = portal_url.getPortalObject()
329            trash=portal.trash
330            trash.manage_pasteObjects(context.manage_cutObjects(id))
331            moved=getattr(trash, id)
332            moved.setId(moved.UID())
333            moved.unindexObject()
334
335        def makeSortableTitle(title):
336            sortabletitle = title.lower().strip()
337            # Replace numbers with zero filled numbers
338            num_sort_regex = re.compile('\d+')
339            sortabletitle = num_sort_regex.sub(zero_fill, sortabletitle)
340            return sortabletitle[:30]
341
342        pc=getToolByName(self, 'portal_catalog')
343        plone_tool = getToolByName(self, 'plone_utils', None)
344        sortabletitle = makeSortableTitle(title)
345        delete_list=[]
346        objUID='Fail'
347        ids_of_translations=[]
348        if obj:
349            objUID=obj.UID()
350            if hasattr(obj, 'getTranslationsOfOriginal'): # Make a list of translations                 
351                ids_of_translations=[x.id.rstrip('-0123456789') for x in obj.getTranslationsOfOriginal()]
352        # If we return True, but obj id is still reserved, plone machinery will give it id-1, id-2..
353        # So pieces always return true, but we still want them to overwrite deleted and redirectors.
354       
355        # Is title ok as a title?
356        matches=pc({'sortable_title':sortabletitle})
357        if matches:
358            for match in matches:
359                if match.portal_type=='Redirector' or match.review_state=='deleted':
360                    if match.UID not in [d.UID for d in delete_list]:
361                        delete_list.append(match)
362                elif match.UID==objUID:
363                    pass
364                elif objtype=='Piece' or match.portal_type=='Piece':
365                    pass
366                elif match.id.rstrip('-0123456789') in ids_of_translations:
367                    pass
368                else:
369                    return False
370
371        # Will this title make a good id?       
372        tempid=plone_tool.normalizeString(title)
373        matches=pc({'id':tempid})
374        if matches:
375            for match in matches:
376                if match.portal_type=='Redirector' or match.review_state=='deleted':
377                    if match.UID not in [d.UID for d in delete_list]:
378                        delete_list.append(match)
379                elif match.portal_type=='Piece' and objtype!='Piece':
380                    # if some piece is having this id, rename it instead.
381                    renamed=match.getObject()
382                    renamed.setId(findUniqueId(tempid))
383                    pass                                       
384                elif match.UID==objUID:
385                    pass
386                elif objtype=='Piece':
387                    pass
388                elif match.id.rstrip('-0123456789') in ids_of_translations:
389                    pass
390                else:
391                    return False
392
393        # If otherwise ok, delete Redirectors and Deleted objects from our way.
394        for o in delete_list:
395            delpath=o.location
396            delid=o.id
397            o=o.getObject()
398            context=o.aq_parent
399            if o.portal_type=='Redirector':
400                context.manage_delObjects([delid])
401            else:
402                moveToTrash(context,delid)           
403        return True         
404
405   
406
407    security.declarePublic('searchable_types')
408    def searchable_types(self):
409        return list(SEARCHABLE_TYPES)
410       
411    security.declarePublic('searchable_types')
412    def getTypeName(self, type):
413        return TYPE_NAMES.get(type, [type])[0]
414
415    def resize_image(self, image, to_width=120, to_height=120, format="PNG"):
416        # resize image to given width and height. StringIO, im will be returned
417        try:
418            from PIL import Image
419        except ImportError:
420            return None, image
421        import cStringIO
422        val = None
423        # attempt to get pure binary data
424        try:
425            val = image.read()
426        except AttributeError: # no read method
427            val = image
428        if hasattr(image, 'data'):
429            val = image.data
430            if not isinstance(val, type('')):
431                val = val.aq_base
432                from OFS.Image import Pdata
433                if isinstance(val, Pdata):
434                    #print "this is Pdata"
435                    val = str(val)
436        s = cStringIO.StringIO(val)
437        s.seek(0)
438        im = Image.open(s)
439        (width, height) = im.size
440        if width > to_width:
441            mod = float(to_width)/float(width)
442            width = width*mod
443            height = height*mod
444            im = im.resize((int(width),int(height)), Image.ANTIALIAS)
445        if height > to_height:
446            u_w = float(to_height)/float(height)
447            width = width*u_w
448            height = height*u_w
449            im = im.resize((int(width),int(height)), Image.ANTIALIAS)
450        s = cStringIO.StringIO()
451        im.save(s, "PNG")
452        s.seek(0)
453        return s, im
454
455    def getPrettyLanguage(self, lang_code):
456        if len(str(lang_code))<3:
457            ts=getToolByName(self,'translation_service')
458            languages = ts.availableLanguages()
459            if dict(languages).has_key(lang_code):
460                return dict(languages)[lang_code]
461            else:
462                return lang_code
463        else:
464            return lang_code
465
466    def savePiece(self, uid, values):
467        # Edit one piece
468        piece=self.uid_catalog(UID=uid)
469        piece=piece[0].getObject()
470        piece.edit(**values)
471        piece.at_post_edit_script()
472
473    def giveLanguageLink(self, REQUEST, lang, url, param):
474        """ Changes the language and redirects to the needed link """
475        self.portal_languages.setLanguageCookie(lang, REQUEST, None)
476        if param:
477            param = param.replace('_and_', '&')
478            return REQUEST.RESPONSE.redirect(url+'?'+param)
479        return REQUEST.RESPONSE.redirect(url)
480
481    def testLinkParameters(self, REQUEST):
482        """ Tests the query string for the language change """
483        stri = REQUEST.get('QUERY_STRING')
484        stri = stri.replace('&', '_and_')
485        return stri
486
487    def getStoryCandidate(self, only_with_story=True):
488        """ Random pick for one content, method and tool or story if only_with_story """
489        pc=getToolByName(self, 'portal_catalog')
490        if GOOD_STORIES_ONLY and only_with_story:
491            stories=pc({'getGoodStory':True})
492            if not stories:
493                return self.getStoryCandidate(only_with_story=False)
494            story=choice(stories)
495            story=story.getObject()
496            content=[x for x in story.getRelatedContent() if x.getHasCoverImage()]
497            method=[x for x in story.getRelatedMethods() if x.getHasCoverImage()]
498            tool=[x for x in story.getRelatedTools() if x.getHasCoverImage()]
499            if content:
500                content=choice(content)
501            if method:
502                method=choice(method)
503            if tool:
504                tool=choice(tool)
505        else:
506            full_results=pc({'review_state':'public','getHasCoverImage':True})
507            content=[x for x in full_results if x.meta_type in MATERIAL_TYPES]
508            method=[x for x in full_results if x.meta_type=='Activity']
509            tool=[x for x in full_results if x.meta_type=='Tool']
510            if content:
511                content=choice(content).getObject()
512            if method:
513                method=choice(method).getObject()
514            if tool:
515                tool=choice(tool).getObject()
516            if content and method and tool:
517                stories=pc({'getRawRelatedContent':content.UID(), 'getRawRelatedActivities':method.UID(), 'getRawRelatedTools':tool.UID()})
518                if stories:
519                    story=choice(stories)
520                    story=story.getObject()
521                else:
522                    story=None
523            else:
524                story=None
525        candidate = {'chcontent':content, 'method':method, 'tool':tool, 'story':story}
526        return candidate
527       
528       
529
530    security.declareProtected(MANAGE_PORTAL, 'manage_cleanAfterSchemaUpdate')
531    def manage_cleanAfterSchemaUpdate(self, admin=''):
532        """ Archetype update rewrites fields for objects
533        and so user running the update gets to be author.
534        We fix that by running update authors for all objects with special request to
535        ignore modifications this user in last hour. """
536        if not admin:
537            mtool = getToolByName(self, 'portal_membership')
538            admin = mtool.getAuthenticatedMember()
539            admin = admin.getId()
540        for x in chain(self.content.objectValues(CONTENT_TYPES),
541                        self.tools.objectValues(TOOLS_TYPES),
542                        self.activities.objectValues(ACTIVITY_TYPES)):
543            if isinstance(x, Resource):
544                x.recalculateAuthors(removeAdmin=admin)
545
546    security.declareProtected(MANAGE_PORTAL,'mergeWithAnother')
547    def mergeWithAnother(self,another):
548        """ Temporary utility method for merging another LeMill into this one."""
549        src=getattr(self,another).aq_inner
550        dst=self.aq_inner.aq_parent
551
552        dst_users = dst.community.objectIds('MemberFolder')
553        src_users = src.community.objectIds('MemberFolder')
554
555        print "Source users: %d, %s" % (len(src_users),str([x for x in src_users]))
556        print "Destination users: %d, %s" % (len(dst_users),str([x for x in dst_users]))
557
558        def fixWorkflow(obj,dobj):
559            transitions = {'public':'publish',
560                           'deleted':'delete',
561                           'draft':None,
562                           }
563            state = src.portal_workflow.getInfoFor(obj,'review_state',None)
564            dstate = dst.portal_workflow.getInfoFor(dobj,'review_state',None)
565            if state==dstate:
566                return
567            trans=transitions[state]
568            if trans:
569                dst.portal_workflow.doActionFor(dobj,trans)
570
571        for id in src_users:
572          if id in dst_users:
573            print "DUPLICATE: %s, %s/%s " % (id, getattr(dst.community,id).NiceName(), getattr(src.community,id).NiceName())
574          else:
575            print "creating: %s" % id
576            try:
577              dst.portal_registration.addMember(id=id,password=dst.portal_registration.generatePassword())
578            except ValueError:
579              # user id exists already - fine by us
580              pass
581            cb = src.community.manage_copyObjects(id)
582            dst.community.manage_pasteObjects(cb)
583            fixWorkflow(getattr(src.community,id),getattr(dst.community,id))
584            getattr(dst.community,id).changeOwnership(dst.acl_users.getUserById(id),1)
585
586        dst_users = dst.community.objectIds('MemberFolder')
587        src_users = src.community.objectIds('MemberFolder')
588
589        print "Source users: %d" % len(src_users)
590        print "Destination users: %d" % len(dst_users)
591
592        dst_grp = dst.community.objectIds('GroupBlog')
593        src_grp = src.community.objectIds('GroupBlog')
594
595        print "Source groups: %d" % len(src_grp)
596        print "Destination groups: %d" % len(dst_grp)
597
598        for id in src_grp:
599          if id in dst_grp:
600            print "DUPLICATE: %s" % id
601          else:
602            print "creating %s" % id
603            dst.portal_groups.addGroup(id=id)
604            s_g = src.portal_groups.getGroupById(id)
605            d_g = dst.portal_groups.getGroupById(id)
606            for m in s_g.getGroupMembers():
607                d_g.addMember(m.getId())
608            dst.community._delObject(id) # remove, because it just got created when the group was created
609            cb = src.community.manage_copyObjects(id)
610            dst.community.manage_pasteObjects(cb)
611            fixWorkflow(getattr(src.community,id),getattr(dst.community,id))
612            #TODO: ownership change
613            def __changeOwnerRecurse(s_obj,d_obj):
614                d_obj.changeOwnership(s_obj.getWrappedOwner(),0)
615                for id in d_obj.objectIds():
616                    __changeOwnerRecurse(getattr(s_obj,id),getattr(d_obj,id))
617            __changeOwnerRecurse(getattr(src.community,id),getattr(dst.community,id))
618
619        dst_cont = dst.content.objectIds(('Piece','LeMillReference','PresentationMaterial', 'MultimediaMaterial', 'PILOTMaterial'))
620        src_cont = src.content.objectIds(('Piece','LeMillReference','PresentationMaterial', 'MultimediaMaterial', 'PILOTMaterial'))
621
622        print "Source content: %d" % len(src_cont)
623        print "Destination content: %d" % len(dst_cont)
624
625        for id in src_cont:
626          if id in dst_cont:
627            print "DUPLICATE: %s" % id
628          else:
629            print "creating %s" % id
630            s = getattr(src.content,id)
631            # Fix ownerships
632            roles = getattr(s, '__ac_local_roles__')
633            for key in roles.keys():
634                if type(key)==tuple:
635                    del roles[key]
636                    s._p_changed=True
637            cb = src.content.manage_copyObjects(id)
638            dst.content.manage_pasteObjects(cb)
639            d = getattr(dst.content,id)
640            fixWorkflow(s,d)
641            try:
642                d.changeOwnership(s.getWrappedOwner(),1)
643            except AttributeError:
644                print "PROBLEM WITH OWNERSHIP OF %s" % id
645                pass # some problem with user
646
647        dst_met = dst.methods.objectIds('Activity')
648        src_met = src.methods.objectIds('Activity')
649
650        print "Source methods: %d" % len(src_met)
651        print "Destination methods: %d" % len(dst_met)
652
653        for id in src_met:
654          if id in dst_met:
655            print "DUPLICATE: %s" % id
656          else:
657            print "creating %s" % id
658            cb = src.methods.manage_copyObjects(id)
659            dst.methods.manage_pasteObjects(cb)
660            fixWorkflow(getattr(src.methods,id),getattr(dst.methods,id))
661            getattr(dst.methods,id).changeOwnership(getattr(src.methods,id).getWrappedOwner(),1)
662
663        dst_tool = dst.tools.objectIds('Tool')
664        src_tool = src.tools.objectIds('Tool')
665
666        print "Source tools: %d" % len(src_tool)
667        print "Destination tools: %d" % len(dst_tool)
668
669        for id in src_tool:
670          if id in dst_tool:
671            print "DUPLICATE: %s" % id
672          else:
673            print "creating %s" % id
674            cb = src.tools.manage_copyObjects(id)
675            dst.tools.manage_pasteObjects(cb)
676            fixWorkflow(getattr(src.tools,id),getattr(dst.tools,id))
677            getattr(dst.tools,id).changeOwnership(getattr(src.tools,id).getWrappedOwner(),1)
678
679        #TODO: password resets to all non-duplicate src_users
680
681        # Fix UID references
682        def __fixUID(uid):
683          if type(uid)==type('') and uid.isalnum() and len(uid)==32:
684            results = src.uid_catalog(UID=uid)
685            if len(results)==1:
686              obj = results[0].getObject()
687              print "Found object: %s" % repr(obj)
688              par = obj.aq_inner.aq_parent
689              return getattr(getattr(dst,par.getId()),obj.getId()).UID()
690          return uid
691
692        src_cont = src.content.objectIds(('LeMillReference','PresentationMaterial', 'MultimediaMaterial', 'PILOTMaterial'))
693
694        for id in src_cont:
695          s=getattr(src.content,id)
696          d=getattr(dst.content,id)
697          print "Processing %s." % repr(s)
698          d.setBodyText(map(__fixUID,s.getRawBodyText()))
699
700        def __fixRef(obj):
701          print "Found object: %s" % repr(obj)
702          par = obj.aq_inner.aq_parent
703          return getattr(getattr(dst,par.getId()),obj.getId())
704
705        for mfolder in src.community.objectValues('MemberFolder'):
706          d_mfolder = getattr(dst.community,mfolder.getId())
707
708          field = mfolder.Schema().get('listOfContacts')
709          values = [x.UID() for x in field.get(m)]
710          print "found %s" % repr(values)
711          if values:
712              values2 = map(__fixUID,values)
713              print "setting to %s" % repr(values2)
714              field.set(d_mfolder,values2)
715
716          for coll in mfolder.collections.objectValues('Collection'):
717            print "Collection %s." % repr(coll)
718            for typ in ('relatedContent','relatedMethods','relatedTools'):
719              try:
720                d = getattr(d_mfolder.collections,coll.getId())
721                field = coll.Schema().get(typ)
722                f = coll.getReferenceImpl(relationship=field.relationship)
723                vals = [(x.collection_position,x.getTargetObject()) for x in f]
724                rel2 = map(__fixRef,[x[1] for x in vals])
725                uids = [x.UID() for x in rel2]
726                print "%s to %s (%s)" % (repr(vals),repr(rel2),repr(uids))
727                field.set(d,None) # empty value
728                for i in range(len(rel2)):
729                    field.set(d,field.getRaw(d)+[rel2[i],],collection_position=vals[i][0])
730              except AttributeError:
731                pass # this collection doesn't exist - most likely because the account already existed prior to the merge
732
733        return "DONE"
734
735
736    def getTimeDifference(self,modtime):
737        """ Returns difference between now and given time in dictionary"""
738        now=time.time()
739        modtime=DateTime(modtime).timeTime()
740        diff=int(now-modtime)
741        dict={'largest_meaningful_unit':'seconds'}
742        for (unitname,seconds) in [('minutes',60),('hours',3600),('days',86400),('weeks',604800),('months',2592000)]:
743            dict[unitname]=diff/seconds
744            if diff/seconds:
745                dict['largest_meaningful_unit']=unitname
746        return dict
747
748
749    @staticmethod
750    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
751        """ Tries to split at first separator between min_len and max_len.
752            If it doesn't succeed, it moves on to the next separator... """
753        if len(s) < max_len:
754            return s
755
756        def find_pattern(s, pattern):
757            i = -1
758            while True:
759                i = s.find(pattern, i + 1)
760                if i == -1:
761                    break
762                yield i
763
764        def find_space(s):
765            gen = enumerate(s)
766            while True:
767                gen = dropwhile(lambda x: x[1] not in (' ', '<'), gen)
768                i, c = gen.next()
769                if c in ' \n\r\t':
770                    yield i
771                elif c == '<':
772                    gen = dropwhile(lambda x: x[1] != '>', gen)
773
774        for gen in chain((find_pattern(s, sep) for sep in seps if sep != ' '), [find_space(s)]):
775            l = [x for x in gen if min_len < x < max_len]
776            if l:
777                return s[:l[-1]]
778
779        return s
780     
781
782    def prioritizeResults(self, results):
783        """ First by language:
784            1)selected user interface language
785            2)by user profile's language selections
786            3)english
787            4)others
788            secondary sort by points
789            """
790        results=list(results)
791        ilanguage=getToolByName(self, 'portal_languages').getLanguageCookie()
792        hf=getToolByName(self, 'portal_membership').getHomeFolder()
793        if hf:
794            ulanguages=hf.getLanguage_skills()
795        else:
796            ulanguages=[]
797
798        def compfunc(a,b):
799            # this looks stupid, but hopefully we don't have to go very far in those ifs per run.
800            al=a.Language
801            bl=b.Language
802            if al==ilanguage and bl!=ilanguage:
803                return -1
804            if al!=ilanguage and bl==ilanguage:
805                return 1
806            if al in ulanguages and bl not in ulanguages:
807                return -1
808            if al not in ulanguages and bl in ulanguages:
809                return 1
810            if al=='en' and bl!='en':
811                return -1
812            if al!='en' and bl=='en':
813                return 1
814            return cmp(b.getScore, a.getScore)
815           
816        results.sort(compfunc)
817        return results       
818
819
820       
821
822InitializeClass(LeMillTool)
Note: See TracBrowser for help on using the repository browser.