source: trunk/LeMillTool.py @ 1989

Revision 1989, 32.8 KB checked in by jukka, 12 years ago (diff)

Added attribute whitelist.

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