source: trunk/Collection.py @ 1920

Revision 1920, 41.7 KB checked in by gabor, 12 years ago (diff)

fixed #738 spent 30h

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 Products.Archetypes.public import *
20from Products.ATReferenceBrowserWidget.ATReferenceBrowserWidget import ReferenceBrowserWidget
21from Products.CMFCore.permissions import ModifyPortalContent
22from Products.Archetypes.public import BaseContent, registerType, BaseFolder
23from Products.Archetypes.public import StringField
24from Products.Archetypes.public import TextAreaWidget
25from Products.Archetypes.atapi import DisplayList
26from Globals import InitializeClass
27from Products.CMFCore.utils import getToolByName
28from AccessControl import ClassSecurityInfo, Unauthorized
29from SharedMetadata import score
30
31from config import PROJECTNAME, ALL_CONTENT_TYPES, CONTENT_TYPES, DEFAULT_ICONS, ACTIVITY_TYPES, TOOLS_TYPES, REPOSITORY
32from Resources import Resource
33from permissions import MODIFY_CONTENT
34
35import sre
36from cStringIO import StringIO
37import zipfile
38from urllib2 import urlopen, HTTPError, URLError
39
40schema = BaseSchema + score + Schema((
41
42    StringField('description',
43        accessor='Description',
44        widget=TextAreaWidget(
45            label="Learning and teaching story",
46            description="",
47            label_msgid='label_learning_story',
48            description_msgid='description_description',
49            i18n_domain = "lemill",
50            visible={'view':'invisible','edit':'visible'},
51        ),
52    ),
53   
54    ReferenceField('refsToResources',
55        accessor = 'getRefsToResources',
56        relationship = 'References',
57        mutator = 'addRefsToResources',
58        allowed_types = ALL_CONTENT_TYPES,
59        multiValued = True,
60        widget = ReferenceBrowserWidget(
61            visible = {'view':'invisible', 'edit':'invisible'},
62            ),
63    ),
64    ReferenceField('relatedContent',
65        relationship = 'relatesToContent',
66        multiValued = True,
67        isMetadata = True,
68        languageIndependent = False,
69        index = 'KeywordIndex',
70        write_permission = ModifyPortalContent,
71        allowed_types= ALL_CONTENT_TYPES,
72        widget = ReferenceBrowserWidget(
73            allow_search = True,
74            allow_browse = True,
75            show_indexes = False,
76            force_close_on_insert = True,
77            startup_directory = "content",
78            size = 4,
79            i18n_domain = "lemill",
80            label = "Related Content",
81            label_msgid = "label_related_content",
82            description = "",
83            description_msgid = "help_story_related_content",
84            visible = {'edit' : 'invisible', 'view' : 'invisible' }
85            )
86        ),
87    ReferenceField('relatedMethods',
88        relationship = 'relatesToMethods',
89        multiValued = True,
90        isMetadata = True,
91        languageIndependent = False,
92        index = 'KeywordIndex',
93        write_permission = ModifyPortalContent,
94        allowed_types=('Document','Activity',),
95        widget = ReferenceBrowserWidget(
96            allow_search = True,
97            allow_browse = True,
98            show_indexes = False,
99            force_close_on_insert = True,
100            startup_directory = "methods",
101            size = 4,
102            i18n_domain = "lemill",
103            label = "Related Methods",
104            label_msgid = "label_related_methods",
105            description = "",
106            description_msgid = "help_story_related_methods",
107            visible = {'edit' : 'invisible', 'view' : 'invisible' }
108            )
109        ),
110    ReferenceField('relatedTools',
111        relationship = 'relatesToTools',
112        multiValued = True,
113        isMetadata = True,
114        languageIndependent = False,
115        index = 'KeywordIndex',
116        write_permission = ModifyPortalContent,
117        allowed_types=('Document','Tool',),
118        widget = ReferenceBrowserWidget(
119            allow_search = True,
120            allow_browse = True,
121            show_indexes = False,
122            force_close_on_insert = True,
123            startup_directory = "tools",
124            size = 4,
125
126            i18n_domain = "lemill",
127            label = "Related Tools",
128            label_msgid = "label_related_tools",
129            description = "",
130            description_msgid = "help_story_related_tools",
131            visible = {'edit' : 'invisible', 'view' : 'invisible' }
132            )
133        ),
134    ComputedField('goodStory',
135        index = 'FieldIndex',
136        expression = 'here.isThisGoodStory()',
137        isMetadata = True
138        )
139))
140
141schema = schema.copy()
142
143class Collection(Resource):
144    """Collection"""
145   
146    schema = schema
147    actions= (
148    {
149    'id':'view',
150    'name':'view',
151    'action':'string:${object_url}/collection_view',
152    'permission':('View',),
153    },
154    {
155    'id':'edit',
156    'name':'Edit',
157    'action':'string:${object_url}/base_edit',
158    'permission':('View',),
159    },
160    )
161    meta_type = "Collection"
162    archetype_name = "Collection" 
163    typeDescription="Collection of resources."
164    typeDescMsgId='description_collection'
165    global_allow = 1
166    security = ClassSecurityInfo()
167    _at_rename_after_creation = True
168
169    def at_post_create_script(self):
170        self.at_post_edit_script()
171
172    def at_post_edit_script(self):
173        self._renameAfterCreation()
174        self.setGoodStory()
175
176    def after_add_rename(self):
177        self._renameAfterCreation()
178       
179    def setGoodStory(self):
180        """ Update goodStory -flag """
181        # after trying to get field and then setting the field by recalculating self.isThisGoodStory()
182        # it appears to be *always* up-to-date. (field.get()==self.isThisGoodStory())
183        # so the problem is indexing.
184        self.reindexObject()
185               
186#        field = self.getField('goodStory')
187#        wasgood=field.get(self)
188#        isgood= self.isThisGoodStory()
189#        print 'setting story %s. wasgood:%s isgood:%s' % (self.getId(), wasgood, isgood)
190#        ct = getToolByName(self,'portal_catalog')
191#        print ct({'getGoodStory':True})
192#        self.reindexObject()
193#        print ct({'getGoodStory':True})
194#        if wasgood!=isgood:
195#            field.set(self, isgood)
196#            self.reindexObject()
197#            ct = self.getToolByName('portal_catalog')
198           
199
200    def getLargestCount(self, reftype='relatedContent'):
201        field = self.Schema().get(reftype)
202        rtype=field.relationship
203        f = self.getReferenceImpl(relationship=rtype)
204
205        highest = 1001
206        for a in f:
207            try:
208                highest = a.collection_position
209            except AttributeError:
210                pass
211        highest += 100
212        return highest
213
214
215    security.declareProtected(MODIFY_CONTENT,'addRelatedContent')
216    def addRelatedContent(self, ref, empty=0):
217        """ add reference to content """
218        self.addRefsToResources(ref, empty, reftype="relatedContent")
219        self.setGoodStory()
220
221    security.declareProtected(MODIFY_CONTENT,'addRelatedMethods')
222    def addRelatedMethods(self, ref, empty=0):
223        """ add reference to methods """
224        self.addRefsToResources(ref, empty, reftype="relatedMethods")
225        self.setGoodStory()
226
227    security.declareProtected(MODIFY_CONTENT,'addRelatedTools')
228    def addRelatedTools(self, ref, empty=0):
229        """ add reference to tools """
230        self.addRefsToResources(ref, empty, reftype="relatedTools")
231        self.setGoodStory()
232
233    security.declareProtected(MODIFY_CONTENT,'addRefsToResources')
234    def addRefsToResources(self, ref, empty=0, reftype=None):
235        """ add reference to resource """
236        from types import ListType, TupleType
237        if reftype:
238            field = self.Schema().get(reftype)
239        else:
240            if type(ref) == ListType or type(ref) == TupleType:
241                for x in ref: # if entry is a list of mixed type entries, run this method again for each component.
242                    self.addRefsToResources(x)
243                return 1
244            uc=getToolByName(self, 'uid_catalog')
245            obj=uc(UID=ref)
246            objtype=obj[0].portal_type
247            if objtype in CONTENT_TYPES:
248                reftype='relatedContent'
249            elif objtype in ACTIVITY_TYPES:
250                reftype='relatedMethods'
251            elif objtype in TOOLS_TYPES:
252                reftype='relatedTools'
253            else:
254                reftype='refsToResources'
255            field = self.Schema().get(reftype)
256
257        prev = field.getRaw(self)
258        new_value = []
259        if type(ref) == ListType or type(ref) == TupleType:
260            new_value = prev
261            if empty:
262                new_value = []
263            for x in ref:
264                new_value.append(x)
265        else:
266            new_value = prev+[ref,]
267        field.set(self, new_value, collection_position=self.getLargestCount(reftype))
268        self.setGoodStory()
269
270    def isThisGoodStory(self):
271        """ Check if there is content,method,tool and description """       
272        desc=self.Description()
273        c=self.getRelatedContent()
274        m=self.getRelatedMethods()
275        t=self.getRelatedTools()
276        wtool=getToolByName(self, 'portal_workflow')
277        r= wtool.getInfoFor(self,'review_state',None)!='deleted'
278        if desc and c and m and t and r:
279            return True
280        else:
281            return False
282
283    def getItemCount(self):
284        """ return how many items are in collection """
285        return len(self.getRelatedMethods())+len(self.getRelatedTools())+len(self.getRelatedContent())
286
287
288    def getRelatedContent(self):
289        """ make sure that related content comes in correct order even when programmer forgets to ask for it"""
290        return self.getResources()
291
292    def getSortedList(self, reftype='relatedContent'):
293        #f = self.getField('refsToResources')
294        field = self.Schema().get(reftype)
295        rtype=field.relationship
296        f = self.getReferenceImpl(relationship=rtype)
297        arr = []
298        err_base = 12345123
299        for a in f:
300            try:
301                i = a.collection_position
302            except AttributeError:
303                i = err_base + 100
304                a.collection_position = i
305            arr.append([i, a])
306        arr.sort()
307        return arr
308
309    def getBlurp(self):
310        """ Some 1000 chars or something FIXME TO FIND NICE PLACES TO CUT (<p><br> etc.)"""
311        lt = getToolByName(self, 'lemill_tool')
312        return lt.split_at_p_or_br(lt.shorten_link_names(lt.htmlify(self.Description())))
313
314    def getResources(self, reftype='relatedContent'):
315        """ ... """
316        #return self.getRefsToResources()
317        sorted = self.getSortedList(reftype)
318        arr = []
319        for x in sorted:
320            arr.append(x[1].getTargetObject())
321        # Only Tools and Methods should get sorted by name
322        if reftype != 'relatedContent':
323            mod_resources = [(x.Title(), x) for x in arr]
324            mod_resources.sort()
325            arr = [x[1] for x in mod_resources]
326        return arr
327
328
329    security.declareProtected(MODIFY_CONTENT,'delContent')
330    def delContent(self, objid,REQUEST=None):
331        """ front for delResources """
332        if objid:
333            self.delResources(objid, reftype='relatedContent')
334        if REQUEST:
335            return REQUEST.RESPONSE.redirect(self.absolute_url() + "/collection_edit")
336
337    security.declareProtected(MODIFY_CONTENT,'delMethods')
338    def delMethods(self, objid,REQUEST=None):
339        """ front for delResources """
340        if objid:
341            self.delResources(objid, reftype='relatedMethods')
342        if REQUEST:
343            return REQUEST.RESPONSE.redirect(self.absolute_url() + "/collection_edit")
344
345    security.declareProtected(MODIFY_CONTENT,'delTools')
346    def delTools(self, objid,REQUEST=None):
347        """ front for delResources """
348        if objid:
349            self.delResources(objid, reftype='relatedTools')
350        if REQUEST:
351            return REQUEST.RESPONSE.redirect(self.absolute_url() + "/collection_edit")
352
353    security.declareProtected(MODIFY_CONTENT,'delResources')
354    def delResources(self, objid, reftype='relatedContent'):
355        """ delete selected resource """
356        field = self.Schema().get(reftype)
357        objlist = field.get(self)
358
359        newlist = [o.UID() for o in objlist if o.id!=objid]
360        field.set(self, newlist)
361        # if collection is empty, delete it too
362#        if new == []:
363#            self.collections.delCollection(self.getId())
364#            return True
365#        else:
366#            return False
367        self.setGoodStory()
368        return False
369
370    def getCollectionContent(self):
371        """ Returns a list of content, methods and tools, ordered by their modification date """
372        objects=self.getRelatedContent()+self.getRelatedMethods()+self.getRelatedTools()
373        objects = [(x.ModificationDate(), x) for x in objects]
374        objects.sort()
375        return [x[1] for x in objects]
376       
377
378
379    security.declareProtected(MODIFY_CONTENT,'moveUpContent')
380    def moveUpContent(self, objid,REQUEST=None):
381        """ Front for more general moveUpResources """
382        if objid:
383            self.moveUpResources(objid, reftype='relatedContent')
384        return REQUEST.RESPONSE.redirect(self.absolute_url() + "/collection_edit")
385
386    security.declareProtected(MODIFY_CONTENT,'moveUpResources')
387    def moveUpResources(self, objid, reftype='relatedContent'):
388        """ move up """
389        field = self.getField(reftype)
390        sorted = self.getSortedList(reftype)
391        cur_pos = 0
392        count = 0
393        for x in sorted:
394            if x[1].getTargetObject().id == objid:
395                cur_pos = count
396                break
397            count += 1
398        if cur_pos > 0:
399            prev_uid = sorted[cur_pos-1][1].getTargetObject().UID()
400            prev_pos = sorted[cur_pos-1][1].collection_position
401            curr_uid = sorted[cur_pos][1].getTargetObject().UID()
402            curr_pos = sorted[cur_pos][1].collection_position
403
404            sorted.remove(sorted[cur_pos])
405            sorted.remove(sorted[cur_pos-1])
406            new_list = []
407            [ new_list.append(x[1].getTargetObject()) for x in sorted ]
408           
409            field.set(self, new_list)
410            new_list.append(curr_uid)
411            field.set(self, new_list, collection_position=prev_pos)
412            new_list.append(prev_uid)
413            field.set(self, new_list, collection_position=curr_pos)
414            #new_list = []
415            #for x in sorted:
416            #    new_list.append(x[1].getTargetObject())
417            #field.set(self, new_list)
418
419
420    security.declareProtected(MODIFY_CONTENT,'moveDownContent')
421    def moveDownContent(self, objid,REQUEST=None):
422        """ Front for more general moveDownResources """
423        if objid:
424            self.moveDownResources(objid, reftype='relatedContent')
425        return REQUEST.RESPONSE.redirect(self.absolute_url() + "/collection_edit")
426
427
428    security.declareProtected(MODIFY_CONTENT,'moveDownResources')
429    def moveDownResources(self, objid, reftype='relatedContent'):
430        """ move down """
431        field = self.getField(reftype)
432        sorted = self.getSortedList(reftype=reftype)
433        cur_pos = 0
434        count = 0
435        for x in sorted:
436            if x[1].getTargetObject().id == objid:
437                cur_pos = count
438                break
439            count += 1
440        if cur_pos+1 < len(sorted):
441            next_uid = sorted[cur_pos+1][1].getTargetObject().UID()
442            next_pos = sorted[cur_pos+1][1].collection_position
443            curr_uid = sorted[cur_pos][1].getTargetObject().UID()
444            curr_pos = sorted[cur_pos][1].collection_position
445
446            sorted.remove(sorted[cur_pos+1])
447            sorted.remove(sorted[cur_pos])
448            new_list = []
449            [ new_list.append(x[1].getTargetObject()) for x in sorted ]
450           
451            field.set(self, new_list)
452            new_list.append(curr_uid)
453            field.set(self, new_list, collection_position=next_pos)
454            new_list.append(next_uid)
455            field.set(self, new_list, collection_position=curr_pos)
456
457    def amIOwner(self):
458        """ check owner of object """
459        roles = self.portal_membership.getAuthenticatedMember().getRolesInContext(self)
460        return 'Owner' in roles
461
462
463    def manage_afterAdd(self, item, container):
464        #Replaces the left side portlets with the content type's own action portlet.
465        BaseContent.manage_afterAdd(self, item, container)
466        if not hasattr(item.aq_base, 'left_slots'):
467            self._setProperty('left_slots', ['here/portlet_%s_actions/macros/portlet' % item.meta_type.lower(),], 'lines')
468
469    security.declareProtected(MODIFY_CONTENT,'cleanRefsToResources')
470    def cleanRefsToResources(self):
471        """ Check if (deprecated) RefsToResources contains stuff that should be in relatedContent etc. """
472        if not self.Schema().get('relatedContent'): # update schema if old type collection
473            #print 'schema update required for %s' % self.getId()
474            self._updateSchema()
475        objlist=self.getRefsToResources()
476        field=self.getField('refsToResources')
477        newlist=[]
478        for o in objlist:
479            objtype=o.meta_type
480            if objtype in CONTENT_TYPES:
481                reftype='relatedContent'
482            elif objtype in ACTIVITY_TYPES:
483                reftype='relatedMethods'
484            elif objtype in TOOLS_TYPES:
485                reftype='relatedTools'
486            else:
487                reftype='keeper'
488
489            if reftype=='keeper':
490                newlist.append(o.UID())
491            else:
492                self.addRefsToResources(o.UID(),reftype=reftype)
493        field.set(self, newlist)
494
495    def getLearningStoryText(self):
496        """ will return the nice version for the story text """
497        lt = getToolByName(self, 'lemill_tool')
498        return lt.shorten_link_names(lt.htmlify(self.Description()))
499
500#Zip / SCORM download stuff begin --------------------------------------------------------------------------------
501    def download(self, SHARED='False', SCORM='False'):
502        """ Builds a zip. Set SHARED to True to make shared files appear only once in the zip. Set SCORM to True to generate an imsmanifest.xml file, too. """
503
504        def splitHTML(htmlPage, pageURL):
505
506            def flatten(seq):
507                res = []
508                for item in seq:
509                    if isinstance(item, (list, tuple)):
510                        res.extend(flatten(item))
511                    else:
512                        res.append(item)
513                return res
514
515            def combineNones(inputList, infoList):
516                previousInfo = ''
517                text = ''
518                list = range(len(infoList))
519                list.reverse()
520                for i in list:
521                    if infoList[i] == None:
522                        if previousInfo == None:
523                            text = inputList[i] + text
524                        else:
525                            previousInfo = None
526                            text = inputList[i]
527                        del infoList[i]
528                        del inputList[i]
529                    elif previousInfo == None:
530                        previousInfo = ''
531                        infoList.insert(i + 1, None)
532                        inputList.insert(i + 1, text)
533                if previousInfo == None:
534                    infoList.insert(0, None)
535                    inputList.insert(0, text)
536
537            def reqursiveRegularSplit(inputList, infoList, info, reg, first, next):
538                if isinstance(inputList, (list, tuple)):
539                    for i in range(len(inputList)):
540                        if not isinstance(infoList[i], str):
541                            inputList[i], infoList[i] = reqursiveRegularSplit(inputList[i], infoList[i], info, reg, first, next)
542                else:
543                    reg = sre.compile(reg, sre.DOTALL | sre.IGNORECASE)
544                    inputList = sre.split(reg, inputList)
545                    if len(inputList) > 1:
546                        infoList = []
547                        for i in range(len(inputList)):
548                            if i % next == first:
549                                infoList.append(info)
550                            else:
551                                infoList.append(None)
552                        combineNones(inputList, infoList)
553                    else:
554                        infoList = [infoList]
555                return inputList, infoList
556
557            splitHtml, infoList = reqursiveRegularSplit(htmlPage, None, 'base', '<base href="(.*?)".*?/>', 1, 2)
558            if len(splitHtml) > 1:
559                baseURL = splitHtml[-2]
560                del splitHtml[1::2]
561                del infoList[1::2]
562            else:
563                baseURL = pageURL
564            if not baseURL.endswith('/'):
565                baseURL += '/'
566
567            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'src', '(<link[^>]+href=")(.*?)("[^>]+rel="stylesheet")', 2, 4)
568            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'href', '(<a[^>]+)href="(.*?)"', 2, 3)
569            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'fvars', '(<embed[^>]*?flashvars=")(.*?)(")', 2, 4)
570            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'XMLmp3', '(<voiceover[^>]+src=")(.*?)(")', 2, 4)
571            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'src', '(<[^>]+src=")(.*?)(")', 2, 4)
572            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'src', '(<param.+?name="movie".*?value=")(.*?)(")', 2, 4)
573            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'fvars', r"(<script.*?>.*?AC_FL_RunContent\(.*?'flashvars', ')(.*?)((?<!\\)'.*?</script>)", 2, 4)
574            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'jsfname', "(<script.*?>.*?AC_FL_RunContent\(.*?'movie', ')(.*?)(')", 2, 4)
575            splitHtml, infoList = reqursiveRegularSplit(splitHtml, infoList, 'fvars', '(<param.+?name="flashvars".*?value=")(.*?)(")', 2, 4)
576
577            return flatten(splitHtml), flatten(infoList), baseURL
578
579        def getAbsoluteURL(url, info, baseURL=''):
580            flash = None
581            if info in ('src', 'jsfname', 'XMLmp3', 'href'):
582                if not url.startswith('http://'):
583                    url = baseURL + url
584                if info == 'jsfname':
585                    url += '.swf'
586            elif info == 'fvars':
587                if url.startswith('file='):
588                    url = url[5:-5]
589                    flash = 'mp3'
590                elif url.startswith('xml='):
591                    url = url[4:]
592                    flash = 'pilot'
593                elif url.startswith("config={videoFile: '"):
594                    url = url[20:-2]
595                    flash = 'flv1'
596                elif url.startswith("config={videoFile: \\'"):
597                    url = url[21:-3]
598                    flash = 'flv2'
599            url = url.replace('\\', '/')
600            url = url.replace('at_download/', '')
601            return url, flash
602
603        def splitFileName(fileName):
604            splitted = fileName.split('.')
605            if len(splitted) > 1:
606                return '.'.join(splitted[:-1]), '.' + splitted[-1]
607            return fileName, ''
608
609        def hasFileNameInSrcs(fileName, srcs):
610            for absURL in srcs:
611                src = srcs[absURL]
612                if src.has_key('fileName') and src['fileName'] == fileName:
613                    return src
614            return None
615
616        def findUniqueFileName(absURL, srcs):
617            fileName = absURL.split('?')[0].split('/')[-1]
618            src2 = hasFileNameInSrcs(fileName, srcs)
619            if src2 == None:
620                return fileName
621            else:
622                src2['counter'] += 1
623                fileName, extension = splitFileName(fileName)
624                return findUniqueFileName('%s(%d)%s' % (fileName, src2['counter'], extension), srcs)
625
626        def processHtml(splitHtml, infoList, srcs, hrefs, baseURL, htmlIndex):
627            for i in range(len(infoList)):
628                if infoList[i] in ('src', 'jsfname', 'fvars', 'XMLmp3', 'href'):
629                    absURL, flash = getAbsoluteURL(splitHtml[i], infoList[i], baseURL)
630                    if infoList[i] != 'href' or splitHtml[i].find('at_download') != -1:
631                        if srcs.has_key(absURL):
632                            srcs[absURL]['usedBy'].add(htmlIndex)
633                        else:
634                            srcs[absURL] = {}
635                            src = srcs[absURL]
636                            src['fileName'] = findUniqueFileName(absURL, srcs)
637                            src['finalURL'] = ''
638                            src['usedBy'] = set([htmlIndex])
639                            src['counter'] = 0
640                            if flash == 'mp3':
641                                src['extension'] = 'mp3'
642                            elif flash == 'pilot':
643                                src['extension'] = 'xml'
644                            elif flash in ('flv1', 'flv2'):
645                                src['extension'] = 'flv'
646                            elif infoList[i] == 'XMLmp3':
647                                src['extension'] = 'mp3'
648                            else:
649                                src['extension'] = ''
650                if infoList[i] == 'href':
651                    absURL, flash = getAbsoluteURL(splitHtml[i], infoList[i], baseURL)
652                    if hrefs.has_key(splitHtml[i]):
653                        hrefs[absURL]['usedBy'].add(htmlIndex)
654                    else:
655                        hrefs[absURL] = {}
656                        href = hrefs[absURL]
657                        href['finalURL'] = ''
658                        href['type'] = 0
659                        href['usedBy'] = set([htmlIndex])
660
661        def processPilotXMLs(srcs, baseURLs, baseDirs):
662            xmlDatas = {}
663            xmlList = [absURL for absURL in srcs if srcs[absURL]['extension'] == 'xml']
664            for absURL in xmlList:
665                try:
666                    src = srcs[absURL]
667                    file = urlopen(absURL)
668                    xml = file.read().decode('utf_16').encode('latin_1')
669                    file.close()
670                    xmlDatas[absURL]={}
671                    xmlData = xmlDatas[absURL]
672                    xmlData['split'], xmlData['info'], baseURL = splitHTML(xml, '')
673                    xmlData['fileName'] = src['fileName']
674                    xmlData['baseDirs'] = []
675                    for htmlIndex in src['usedBy']:
676                        processHtml(xmlData['split'], xmlData['info'], srcs, {}, baseURLs[htmlIndex], htmlIndex)
677                        xmlData['baseDirs'].append(baseDirs[htmlIndex])
678                except (HTTPError, URLError):
679                    print "File download error: " + absURL
680            return xmlDatas
681
682        def downloadFiles(srcs, zip, baseDirs = []):
683            def addExtension(fileName, extension):
684                name, ext = splitFileName(fileName)
685                if ext == '' and extension != '':
686                    fileName = '%s.%s' % (fileName, extension)
687                return fileName
688            for absURL in srcs:
689                src = srcs[absURL]
690                data = ''
691                mediaType = ''
692                subType = ''
693                try:
694                    if src['extension'] != 'xml':
695                        file = urlopen(absURL)
696                        data = file.read()
697                        mediaType, subType = file.headers.getheader('Content-Type').split('/')
698                        file.close()
699                        subType = subType.split(';')[0]
700                        if mediaType == 'image':
701                            if subType in ('jpeg', 'pjpeg'):
702                               src['extension'] = 'jpg'
703                            elif subType == 'x-ms-bmp':
704                               src['extension'] = 'bmp'
705                            elif subType in ('png', 'gif'):
706                                src['extension'] = subType
707                            elif subType == 'x-png':
708                                src['extension'] = 'png'
709                        elif mediaType == 'application' and subType == 'x-shockwave-flash':
710                            src['extension'] = 'swf'
711                        elif mediaType == 'video':
712                            if subType in ('x-msvideo', 'avi'):
713                                src['extension'] = 'avi'
714                            elif subType == 'x-ms-wmv':
715                                src['extension'] = 'wmv'
716                            elif subType == 'mpeg':
717                                src['extension'] = 'mpg'
718                            elif subType == 'mp4':
719                                src['extension'] = subType
720                            elif subType == 'quicktime':
721                                src['extension'] = 'mov'
722                        elif mediaType == 'audio' and subType == 'mpeg':
723                            src['extension'] = 'mp3'
724                except (HTTPError, URLError):
725                    print "File download error: " + absURL
726
727                fileName = addExtension(src['fileName'], src['extension'])
728                if len(src['usedBy']) > 1 and SHARED == 'True':
729                    src['finalURL'] = '_SharedFiles/' + fileName
730                    if src['extension'] != 'xml':
731                        zip.writestr(src['finalURL'], data)
732                else:
733                    src['finalURL'] = '_Files/' + fileName
734                    if src['extension'] != 'xml':
735                        for i in src['usedBy']:
736                            zip.writestr(baseDirs[i] + src['finalURL'], data)
737
738        def processHrefs(hrefs, srcs, htmlURLs, baseDirs, portalURL):
739            downloadURLs = []
740            for absURL in srcs:
741                fileName = absURL.split('?')[0].split('/')
742                domain = '/'.join(fileName[:-1])
743                fileName = fileName[-1]
744                downloadURLs.append('%s/at_download/%s' % (domain, fileName))
745            for absURL in hrefs:
746                href = hrefs[absURL]
747                if absURL in htmlURLs:
748                    href['finalURL'] = 'href="%sindex.html"' % baseDirs[htmlURLs.index(absURL)]
749                elif absURL + '/' in htmlURLs:
750                    href['finalURL'] = 'href="%sindex.html"' % baseDirs[htmlURLs.index(absURL + '/')]
751                elif absURL in srcs:
752                    href['finalURL'] = srcs[absURL]['finalURL']
753                    href['type'] = 1
754                elif absURL in downloadURLs:
755                    href['finalURL'] = srcs[absURL.replace('/at_download', '')]['finalURL']
756                    href['type'] = 1
757                elif absURL.startswith(portalURL) and not absURL.endswith('/CollectionRSS'):
758                    href['finalURL'] = ''
759                else:
760                    href['finalURL'] = 'href="%s"' % absURL
761
762        def updateHtmls(splitHtmls, infoLists, baseURLs, baseDirs, srcs, hrefs):
763            for i in range(len(splitHtmls)):
764                splitHtml = splitHtmls[i]
765                infoList = infoLists[i]
766                for j in range(len(splitHtml)):
767                    if infoList[j] in ('src', 'jsfname', 'fvars'):
768                        absURL, flash = getAbsoluteURL(splitHtml[j], infoList[j], baseURLs[i])
769                        src = srcs[absURL]
770                        if baseDirs[i] != '' and len(src['usedBy']) > 1 and SHARED == 'True':
771                            finalURL = '../../' + src['finalURL']
772                        else:
773                            finalURL = src['finalURL']
774                        if infoList[j] == 'src':
775                            splitHtml[j] = finalURL
776                        elif infoList[j] == 'jsfname':
777                            splitHtml[j] = '.'.join(finalURL.split('.')[:-1])
778                        elif infoList[j] == 'fvars':
779                            if flash == 'mp3':
780                                splitHtml[j] = 'file=%s' % finalURL
781                            elif flash == 'pilot':
782                                splitHtml[j] = 'xml=%s' % finalURL
783                            elif flash == 'flv1':
784                                splitHtml[j] = "config={videoFile: '../%s'}" % finalURL
785                            elif flash == 'flv2':
786                                splitHtml[j] = "config={videoFile: \\'../%s\\'}" % finalURL
787                    elif infoList[j] == 'href':
788                        absURL, flash = getAbsoluteURL(splitHtml[j], infoList[j], baseURLs[i])
789                        href = hrefs[absURL]
790                        if href['type'] == 1:
791                            if baseDirs[i] != '' and len(href['usedBy']) > 1 and SHARED == 'True':
792                                finalURL = 'href="../../%s"' % href['finalURL']
793                            else:
794                                finalURL = 'href="%s"' % href['finalURL']
795                        else:
796                            finalURL = href['finalURL']
797                        splitHtml[j] = finalURL
798
799        def updatePilotXMLs(xmlDatas, baseURLs, srcs):
800            for absURL in xmlDatas:
801                splitXML = xmlDatas[absURL]['split']
802                xmlInfoList = xmlDatas[absURL]['info']
803                for j in range(len(splitXML)):
804                    if xmlInfoList[j] in ('src', 'XMLmp3'):
805                        absURL2, flash = getAbsoluteURL(splitXML[j], xmlInfoList[j])
806                        src = srcs[absURL2]
807                        if len(src['usedBy']) > 1 and SHARED == 'True':
808                            splitXML[j] = '../../' + src['finalURL']
809                        else:
810                            splitXML[j] = src['finalURL']
811
812        def addHtmlsToZip(splitHtmls, zip, baseDirs = None):
813            i = 0
814            baseDir = ''
815            for splitHtml in splitHtmls:
816                if isinstance(baseDirs, (list, tuple)):
817                    baseDir = baseDirs[i]
818                zip.writestr(baseDir + 'index.html', ''.join(splitHtml))
819                i += 1
820
821        def addPilotXMLsToZip(xmlDatas, srcs, zip):
822            for absURL in xmlDatas:
823                xmlData = xmlDatas[absURL]
824                xml = ''.join(xmlData['split'])
825                for baseDir in xmlData['baseDirs']:
826                    zip.writestr(baseDir + srcs[absURL]['finalURL'], xml.decode('latin_1').encode('utf_16'))
827                    break
828
829        def addSCORMFiles(version, htmlList, srcs, zip):
830            def addMetadata(manifest, obj, tabs):
831                language = obj.Language()
832                if language == '':
833                    language = 'en'
834                manifest += '%s<metadata>\n' % (tabs * '\t')
835                manifest += '%s<schema>ADL SCORM</schema>\n' % ((tabs + 1) * '\t')
836                manifest += '%s<schemaversion>1.2</schemaversion>\n' % ((tabs + 1) * '\t')
837                manifest += '%s<imsmd:lom>\n' % ((tabs + 1) * '\t')
838                manifest += '%s<general>\n' % ((tabs + 2) * '\t')
839                manifest += '%s<title>\n' % ((tabs + 3) * '\t')
840                manifest += '%s<langstring xml:lang="%s">%s</langstring>\n' % (((tabs + 4) * '\t'), language, obj.TitleOrId())
841                manifest += '%s</title>\n' % ((tabs + 3) * '\t')
842                manifest += '%s<language>%s</language>\n'  % (((tabs + 3) * '\t'), language)
843                tags = obj.getTags()
844                for tag in tags:
845                    manifest += '%s<keyword>\n' % ((tabs + 3) * '\t')
846                    manifest += '%s<langstring xml:lang="%s">%s</langstring>\n' % (((tabs + 4) * '\t'), language, tag)
847                    manifest += '%s</keyword>\n' % ((tabs + 3) * '\t')
848                manifest += '%s</general>\n' % ((tabs + 2) * '\t')
849                manifest += '%s<lifecycle>\n' % ((tabs + 2) * '\t')
850                manifest += '%s<version>\n' % ((tabs + 3) * '\t')
851                manifest += '%s<langstring xml:lang="%s">%s</langstring>\n' % (((tabs + 4) * '\t'), language, obj.getLatestEditDate())
852                manifest += '%s</version>\n' % ((tabs + 3) * '\t')
853                manifest += '%s</lifecycle>\n' % ((tabs + 2) * '\t')
854                manifest += '%s</imsmd:lom>\n' % ((tabs + 1) * '\t')
855                manifest += '%s</metadata>\n\n' % (tabs * '\t')
856                return manifest
857
858            manifest = '<?xml version="1.0" encoding="utf-8"?>\n'
859            manifest += '<manifest identifier="%s" version="%s" xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2" xmlns:imsmd="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2" xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">\n\n' % (htmlList[0][0].getId(), version)
860            manifest = addMetadata(manifest, self, 1)
861            manifest += '\t<organizations default="ORGANIZATION_%s">\n' % htmlList[0][0].getId()
862            manifest += '\t\t<title>%s</title>\n' % htmlList[0][0].TitleOrId()
863            manifest += '\t\t<organization identifier="ORGANIZATION_%s" structure="linear">\n' % htmlList[0][0].getId()
864            for obj in htmlList:
865                manifest += '\t\t\t<item identifier="ITEM_%s" identifierref="RESOURCE_%s" isvisible="true">\n' % (obj[0].getId(), obj[0].getId())
866                manifest += '\t\t\t\t<title>%s</title>\n' % obj[0].TitleOrId()
867                manifest = addMetadata(manifest, obj[0], 4)
868                manifest += '\t\t\t</item>\n'
869            manifest += '\t\t</organization>\n'
870            manifest += '\t</organizations>\n\n'
871            manifest += '\t<resources>\n'
872            for i in range(len(htmlList)):
873                manifest += '\t\t<resource identifier="RESOURCE_%s" type="webcontent" adlcp:scormtype="sco" href="%sindex.html">\n' % (htmlList[i][0].getId(), htmlList[i][1])
874                manifest += '\t\t\t<file href="%sindex.html" />\n' % htmlList[i][1]
875                for absURL in srcs:
876                    src = srcs[absURL]
877                    if i in src['usedBy']:
878                        if len(src['usedBy']) > 1 and SHARED == 'True':
879                            finalURL = src['finalURL']
880                        else:
881                            finalURL = htmlList[i][1] + src['finalURL']
882                        manifest += '\t\t\t<file href="%s" />\n' % finalURL
883                manifest += '\t\t</resource>\n'
884            manifest += '\t</resources>\n\n'
885            manifest += '</manifest>'
886            zip.writestr('imsmanifest.xml', manifest)
887            for file in ('adlcp_rootv1p2.xsd', 'ims_xml.xsd', 'imscp_rootv1p1p2.xsd', 'imsmd_rootv1p2p1.xsd'):
888                f = open(REPOSITORY + file, 'r')
889                zip.writestr(file, f.read())
890                f.close()
891
892        splitHtmls = []
893        infoLists = []
894        baseURLs = []
895        baseDirs = []
896        htmlURLs = []
897        srcs = {}
898        hrefs = {}
899        times = []
900        htmlList = [(self, '')]
901        htmlIndex = 0
902        for resourceType in ('Content', 'Methods', 'Tools'):
903            for resource in self.getResources(reftype = 'related%s' % resourceType):
904                htmlList.append((resource, '%s/%s/' % (resourceType.lower(), resource.getId())))
905                times.append(resource.getLatestEditDate())
906
907        for html in htmlList:
908            splitHtml, infoList, baseURL = splitHTML(html[0].standalone_view(), self.portal_url())
909            splitHtmls.append(splitHtml)
910            infoLists.append(infoList)
911            baseURLs.append(baseURL)
912            baseDirs.append(html[1])
913            htmlURLs.append(html[0].absolute_url() + '/')
914            processHtml(splitHtml, infoList, srcs, hrefs, baseURL, htmlIndex)
915            htmlIndex += 1
916
917        xmlDatas = processPilotXMLs(srcs, baseURLs, baseDirs)
918        zipStr = StringIO()
919        zip = zipfile.ZipFile(zipStr, 'w', compression = zipfile.ZIP_DEFLATED)
920        downloadFiles(srcs, zip, baseDirs)
921        processHrefs(hrefs, srcs, htmlURLs, baseDirs, self.portal_url())
922        updateHtmls(splitHtmls, infoLists, baseURLs, baseDirs, srcs, hrefs)
923        updatePilotXMLs(xmlDatas, baseURLs, srcs)
924        addHtmlsToZip(splitHtmls, zip, baseDirs)
925        addPilotXMLsToZip(xmlDatas, srcs, zip)
926        if SCORM == 'True':
927            addSCORMFiles(str(max(times)), htmlList, srcs, zip)
928        zip.close()
929        response = self.REQUEST.RESPONSE
930        response.setHeader('Content-Type', 'application/zip')
931        response.setHeader('Content-Disposition', 'attachment; filename="%s.zip"' % self.getId())
932        return zipStr.getvalue()
933#Zip / SCORM download sruff end ----------------------------------------------------------------------------------
934
935    #XXX These additional meta-type='' and obj=None might not be needed, but I will leave them here just to be on the safe side.
936    def getDefaultIcon(self, meta_type='', obj=None):
937        """ Will return a portrait of the creator or his default icon """
938        owner = self.Creator()
939        mtool = getToolByName(self, 'portal_membership')
940        member_folder = mtool.getHomeFolder(owner)
941        given_image = member_folder.getCoverImageURL()
942        return given_image
943       
944
945registerType(Collection, PROJECTNAME)
946
947class CollectionsFolder(BaseFolder):
948    """ container for collection """
949    schema = BaseSchema
950    id = "collections"
951    meta_type = "CollectionsFolder"
952    archetype_name = "CollectionsFolder" 
953
954    actions= (
955    {
956    'id':'view',
957    'name':'view',
958    'action':'string:${object_url}/collections_list',
959    'permission':('View',),
960    },
961    )
962               
963registerType(CollectionsFolder, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.