source: trunk/LeMillTool.py @ 1986

Revision 1986, 31.9 KB checked in by jukka, 12 years ago (diff)

Parsing works fine, except kupu's view of page is ugly.

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