root/trunk/Collection.py

Revision 3193, 56.1 kB (checked in by jukka, 2 weeks ago)

Fixed #2046, comments and commenting is included into main view.

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
19 from Products.Archetypes.public import *
20 from Products.ATReferenceBrowserWidget.ATReferenceBrowserWidget import ReferenceBrowserWidget
21 from Products.CMFCore.permissions import ModifyPortalContent
22 from Products.Archetypes.atapi import DisplayList
23 from Globals import InitializeClass
24 from Products.CMFCore.utils import getToolByName
25 from AccessControl import ClassSecurityInfo, Unauthorized
26 from LargeSectionFolder import LeMillFolder
27 from Products.ZCatalog.CatalogBrains import AbstractCatalogBrain
28 from Acquisition import aq_base, aq_inner, aq_parent
29
30
31 from config import PROJECTNAME, ALL_CONTENT_TYPES, CONTENT_TYPES, DEFAULT_ICONS, ACTIVITY_TYPES, TOOLS_TYPES, REPOSITORY, TTF_FONTS, to_unicode
32 from Resource import Resource
33 from Schemata import resource_schema
34 from permissions import ModerateContent, MODIFY_CONTENT, ACCESS_CONTENT, VIEW
35 from FieldsWidgets import WYSIWYMField, LeVisualWidget
36 from cStringIO import StringIO
37 import os, urllib, shutil, sys, zipfile, sre, random, time
38 import urlparse, tempfile, urllib2
39 from types import ListType, TupleType
40
41 try:
42     import ho.pisa as pisa
43     from reportlab.pdfbase import pdfmetrics, ttfonts
44     pdf_enabled=True
45 except ImportError:
46     pdf_enabled=False
47
48
49 collection_schema =  Schema((
50     WYSIWYMField('description',
51         accessor='Description',
52         widget=LeVisualWidget(
53             label="Learning and teaching story",
54             description="",
55             label_msgid='label_learning_story',
56             description_msgid='description_description',
57             i18n_domain = "lemill",
58             visible={'view':'invisible','edit':'visible'},
59         ),
60     ),
61    
62     ReferenceField('relatedContent',
63         relationship = 'relatesToContent',
64         multiValued = True,
65         isMetadata = True,
66         languageIndependent = False,
67         index = None,
68         write_permission = ModifyPortalContent,
69         allowed_types= ALL_CONTENT_TYPES,
70         widget = ReferenceBrowserWidget(
71             allow_search = True,
72             allow_browse = True,
73             show_indexes = False,
74             force_close_on_insert = True,
75             startup_directory = "content",
76             size = 4,
77             i18n_domain = "lemill",
78             label = "Related Content",
79             label_msgid = "label_related_content",
80             description = "",
81             description_msgid = "help_story_related_content",
82             visible = {'edit' : 'invisible', 'view' : 'invisible' }
83             )
84         ),
85     ReferenceField('relatedMethods',
86         relationship = 'relatesToMethods',
87         multiValued = True,
88         isMetadata = True,
89         languageIndependent = False,
90         index = None,
91         write_permission = ModifyPortalContent,
92         allowed_types=('Document','Activity',),
93         widget = ReferenceBrowserWidget(
94             allow_search = True,
95             allow_browse = True,
96             show_indexes = False,
97             force_close_on_insert = True,
98             startup_directory = "methods",
99             size = 4,
100             i18n_domain = "lemill",
101             label = "Related Methods",
102             label_msgid = "label_related_methods",
103             description = "",
104             description_msgid = "help_story_related_methods",
105             visible = {'edit' : 'invisible', 'view' : 'invisible' }
106             )
107         ),
108     ReferenceField('relatedTools',
109         relationship = 'relatesToTools',
110         multiValued = True,
111         isMetadata = True,
112         languageIndependent = False,
113         index = None,
114         write_permission = ModifyPortalContent,
115         allowed_types=('Document','Tool',),
116         widget = ReferenceBrowserWidget(
117             allow_search = True,
118             allow_browse = True,
119             show_indexes = False,
120             force_close_on_insert = True,
121             startup_directory = "tools",
122             size = 4,
123
124             i18n_domain = "lemill",
125             label = "Related Tools",
126             label_msgid = "label_related_tools",
127             description = "",
128             description_msgid = "help_story_related_tools",
129             visible = {'edit' : 'invisible', 'view' : 'invisible' }
130             )
131         ),
132     ReferenceField('relatedCollections',
133         relationship = 'relatesToCollections',
134         multiValued = True,
135         isMetadata = True,
136         languageIndependent = False,
137         index = None,
138         write_permission = ModifyPortalContent,
139         allowed_types=('Document','Collection',),
140         widget = ReferenceBrowserWidget(
141             allow_search = True,
142             allow_browse = True,
143             show_indexes = False,
144             force_close_on_insert = True,
145             startup_directory = "community",
146             size = 4,
147             i18n_domain = "lemill",
148             label = "Related Collections",
149             description = "",
150             visible = {'edit' : 'invisible', 'view' : 'invisible' }
151             )
152         ),
153     BooleanField('goodStory',
154         index = 'FieldIndex',
155         default=False,
156         isMetadata = True
157         ),
158     ComputedField('language',
159         index='FieldIndex:schema',
160         accessor='Language',
161         expression = 'here.combineCollectionLanguage()',
162         isMetadata = True
163         )
164 ))
165
166 schema = resource_schema + collection_schema
167
168 schema = schema.copy()
169
170 class Collection(Resource):
171     """Collection"""
172    
173     schema = schema
174     actions= (
175     {
176     'id':'view',
177     'name':'view',
178     'action':'string:${object_url}/collection_view',
179     'permission':('View',),
180     },
181     {
182     'id':'edit',
183     'name':'Edit',
184     'action':'string:${object_url}/base_edit',
185     'permission':('View',),
186     },
187     )
188     meta_type = "Collection"
189     archetype_name = "Collection" 
190     typeDescription="Collection of resources."
191     typeDescMsgId='description_collection'
192     global_allow = 1
193     portlet = 'here/portlet_collection_actions/macros/portlet'
194     security = ClassSecurityInfo()
195     _at_rename_after_creation = True
196
197     def at_post_create_script(self):
198         self.at_post_edit_script()
199
200     def at_post_edit_script(self):
201         self._renameAfterCreation()
202         self. updateStory()
203
204     def after_add_rename(self):
205         self._renameAfterCreation()
206
207
208     security.declarePrivate('_processForm')
209     def _processForm(self, data=1, metadata=None, REQUEST=None, values=None):
210         request = REQUEST or self.REQUEST
211         if values:
212             form = values
213         else:
214             form = request.form
215         # Instead of using schema fields, we call them manually
216         # (because there are only two)     
217         title_field=self.getField('title')
218         try:
219             title, other = title_field.widget.process_form(self, title_field, form,validating=False)
220         except TypeError:
221             title, other = title_field.widget.process_form(self, title_field, form)
222
223         self.setTitle(title)
224         desc_field=self.getField('description')
225         try:
226             desc, other = desc_field.widget.process_form(self, desc_field, form,validating=False)
227         except TypeError:
228             desc, other = desc_field.widget.process_form(self, desc_field, form)
229         self.setDescription(desc)
230         # Delete & reorder items
231         for (reftype,ordered_by) in [('relatedContent','order_'), ('relatedMethods',''), ('relatedTools',''),('relatedCollections','col_order_')]:
232             sorted=self.getSortedList(reftype)
233             if ordered_by:
234                 new_list=[]
235                 for index, item in enumerate(sorted):
236                     order=form.get(ordered_by+str(index), 100)
237                     new_list.append((order, item))
238                 new_list.sort()
239                 new_list=[item.targetUID for (order, item) in new_list]
240                 self._setOrderedReferences(new_list, reftype)
241             # check if deleted
242             for item in sorted:
243                 delete_flag=int(form.get('%s_deleted' % item.targetUID, 0))
244                 if delete_flag:
245                     self._delete(item.targetUID, reftype)
246         self.reindexObject()
247
248     security.declarePrivate('_delete')
249     def _delete(self, uid, reftype):
250         field=self.getField(reftype)
251         newlist = [u for u in field.getRaw(self) if u!=uid]
252         field.set(self, newlist)
253         ltool=getToolByName(self, 'lemill_tool')
254         obj=ltool.getObjectByUID(uid)
255         if obj:
256             obj.recalculateScore()
257             obj.reindexObject()
258
259     def setPermissions(self):
260         # Setting View permission
261         self.manage_permission(VIEW, ('Manager','Owner','Member','Authenticated','Anonymous'), acquire=0)
262         # Setting Edit permission
263         self.manage_permission(MODIFY_CONTENT, ('Manager','Owner'), acquire=0)
264         # Setting Review permission
265         self.manage_permission(ModerateContent, ('Manager','Owner','Reviewer'), acquire=0)
266         # Setting Access permission
267         self.manage_permission(ACCESS_CONTENT, ('Manager','Owner','Member','Authenticated','Anonymous'), acquire=0)
268                        
269     def detectLanguage(self):
270         """ Returns the most used language in content objects of this collection """
271         languages={}
272         for obj in self.getRelatedContent():
273             this_l=obj.Language()
274             if this_l:
275                 languages[this_l]=languages.get(this_l,0)+1
276         maxval=max(languages.values())
277         for key in languages.keys():
278             if languages[key]==maxval:
279                 return key
280         return None                   
281
282     def getMetaDescription(self):
283         """ Biography is a good description, but if not filled, then skills, interests or subject areas  """
284         desc= self.Description()
285         if desc:
286             desc_len=0
287             desc_result=[]
288             ltool = getToolByName(self, 'lemill_tool')       
289             desc=ltool.stripHTML(desc)
290             for p in desc.split('\n'):
291                 if p: # ignore empty lines
292                     desc_len+=len(p)
293                     desc_result.append(p)
294                 if desc_len > 140:
295                     break
296             return '\n'.join(desc_result)
297         lutool = getToolByName(self, 'lemill_usertool')       
298         return "%s's collection of LeMill resources" % lutool.linkTo(self.Creator(), disabled=True)
299
300
301     def getPathInsideCollection(self, obj):
302         """ Returns a path in form portal_url/member/collection/content_section/content_subfolder/content_id """
303         if isinstance(obj, AbstractCatalogBrain):
304             obj_url=obj.getURL().split('/')
305             coll_url=self.absolute_url().split('/')
306             portal_url=getToolByName(self, 'portal_url')().split('/')
307             return '/'.join(coll_url+obj_url[len(portal_url):])
308
309         path= getattr(obj.__class__, 'default_location','')
310         if path:
311             return '/'.join((self.absolute_url(), path, obj.id))
312         else:
313             return '/'.join((self.absolute_url, obj.id))
314
315
316     def getReferenceType(self, object):
317         """ Which reference should be used for this object? Returns string. """
318         objtype=object.portal_type
319         if objtype in CONTENT_TYPES:
320             return 'relatedContent'
321         elif objtype in ACTIVITY_TYPES:
322             return 'relatedMethods'
323         elif objtype in TOOLS_TYPES:
324             return 'relatedTools'
325         elif objtype == 'Collection':
326             return 'relatedCollections'
327         else:
328             return 'refsToResources'
329
330
331     def getEmbedCode(self):
332         """ Builds a nice embed code """
333         return '''<iframe src="%s/collection_clean_view" width="100%%" height="550px" scrolling="auto" frameborder="1"></iframe>''' % self.absolute_url()
334
335        
336     security.declareProtected(MODIFY_CONTENT, 'add')
337     def add(self, obj):
338         """ Add object to collection """
339         if type(obj) == ListType or type(obj) == TupleType:
340             for x in obj:
341                 self.add(x)
342             return None
343         UID=obj.UID()
344         reftype=self.getReferenceType(obj)
345         field = self.Schema().get(reftype)
346         value = field.getRaw(self)
347         value.append(UID)
348         self._setOrderedReferences(value, reftype)
349         self.updateStory()
350         obj.recalculateScore()
351         obj.reindexObject()
352         self.notifyCollectionAdd(obj)
353
354     def updateStory(self):
355         """ Recalculate goodStory-field, changed to be done manually because
356         a) to avoid infinite looping
357         b) to avoid unnecessary and costly updating of this field """
358         old=self.getGoodStory()
359         new=self.isThisGoodStory()
360         if old != new:
361             self.setGoodStory(new)
362             self.reindexObject(['getGoodStory'])
363             for obj in self.getContent()+self.getMethods()+self.getTools():
364                 obj.recalculateScore()
365                 obj.reindexObject(['getScore'])
366
367
368     def isThisGoodStory(self):
369         """ Check if there is content,method,tool and description """       
370         if self.state=='deleted':
371             return False
372         if len(self.Description())<200:
373             return False
374         #import pdb;pdb.set_trace()
375         good_content = False
376         good_methods = False
377         good_tools = False       
378         c=self.getRelatedContent()
379         for obj in c:
380             if obj.getHasCoverImage() and obj.state == 'public':
381                 good_content = True
382         if not good_content:
383             return False
384         m=self.getRelatedMethods()
385         for obj in m:
386             if obj.getHasCoverImage() and obj.state == 'public':
387                 good_methods = True
388         if not good_methods:
389             return False
390         t=self.getRelatedTools()
391         for obj in t:
392             if obj.getHasCoverImage() and obj.state == 'public':
393                 good_tools = True
394         return good_tools
395
396     def getItemCount(self):
397         """ return how many items are in collection """
398         return len(self.getContent()+self.getMethods()+self.getTools()+self.getCollections())
399
400
401     def getContent(self):
402         """ make sure that related content comes in correct order even when programmer forgets to ask for it"""
403         return self.getResources('relatedContent')
404
405     def getMethods(self):
406         """ don't get deleted methods """
407         return self.getResources('relatedMethods')
408        
409     def getTools(self):
410         """ don't get deleted tools """
411         return self.getResources('relatedTools')
412
413     def getCollections(self):
414         """ don't get deleted collections """
415         return self.getResources('relatedCollections')
416
417     def getSortedList(self, reftype='relatedContent'):
418         field = self.Schema().get(reftype)
419         if reftype == 'relatedMethods' or reftype=='relatedTools':
420             sortable = [(ref.targetId(), ref) for ref in self.getReferenceImpl(relationship=field.relationship)]
421         else:             
422             sortable = [(getattr(ref,'collection_position',100), ref) for ref in self.getReferenceImpl(relationship=field.relationship)]
423         sortable.sort()
424         return [ref for i, ref in sortable if ref]
425
426     def getBlurp(self):
427         """ 200-300 character version of description """
428         desc=self.Description()
429         if desc:
430             lt = getToolByName(self, 'lemill_tool')
431             return lt.cropText(lt.parse_text(desc))
432         else:
433             return ''
434
435     def getResources(self, reftype='relatedContent', private_materials=True, pdf_types=False):
436         """ ... """
437         user = getToolByName(self, 'lemill_usertool').getAuthenticatedId()
438         manager=self.amIManager()       
439         resources = []
440         for ref in self.getSortedList(reftype):
441             print 'ref:', ref
442             target = ref.getTargetObject()
443             if not target:
444                 continue
445             if pdf_types and target.meta_type not in ['MultimediaMaterial', 'ExerciseMaterial', 'LessonPlan', 'SchoolProjectMaterial', 'LeMillReference', 'Activity', 'Tool']:
446                 continue
447             if target.state=='draft' or target.state=='public':
448                 resources.append(target)
449             elif private_materials and target.state=='private' and (user==target.Creator() or manager):
450                 resources.append(target)
451         return resources
452
453
454     def getResourcesMetadata(self, reftype='relatedContent'):
455         """ ... """
456         pc=getToolByName(self, 'portal_catalog')
457         user = getToolByName(self, 'lemill_usertool').getAuthenticatedId()
458         manager=self.amIManager()       
459         results=[]
460         for ref in self.getSortedList(reftype):
461             md=pc(UID=ref.targetUID)
462             if md:
463                 md=md[0]
464             else:
465                 continue
466             if md.getState=='draft' or md.getState=='public':
467                 results.append(md)
468             elif md.getState=='private' and (md.Creator==user or manager):
469                 results.append(md)
470         return results
471
472     def isResourceInCollection(self, resource):
473         """ Returns True if the resource belongs to this collection, False otherwise. """
474         objects = self.getRelatedContent() + self.getRelatedMethods() + self.getRelatedTools() + self.getRelatedCollections()
475         return resource in objects
476
477
478    
479     def getCollectionContent(self):
480         """ Returns a list of content, methods, tools and collections for RSS """
481         objects=self.getContent()+self.getMethods()+self.getTools()+self.getCollections()
482         return objects     
483
484
485     def _setOrderedReferences(self, ordered_list, reftype='relatedContent'):
486         """ setter that stores the index of each element in list with reference object. ordered_list = list of uids """
487         field = self.getField(reftype)
488         field.set(self, ordered_list)
489         refs = self.getReferenceImpl(relationship=field.relationship)
490         for ref in refs:
491             i = ordered_list.index(ref.targetUID)
492             ref.collection_position=i
493            
494     def getLearningStoryText(self):
495         """ will return the nice version for the story text """
496         lt = getToolByName(self, 'lemill_tool')
497         return lt.parse_text(self.Description())
498
499
500     # These additional meta-type='' and obj=None might not be needed, but I will leave them here just to be on the safe side.
501     def getDefaultIcon(self, meta_type='', obj=None):
502         """ Will return a portrait of the creator or his default icon """
503         try:
504             mf=self.getMemberFolder()
505         except AttributeError:
506             owner=self.Creator
507             lutool = getToolByName(self, 'lemill_usertool')
508             mf=lutool.getMemberFolder(owner)
509         return mf.getCoverImageURL()
510
511     def getCollection(self):
512         return self
513
514     def isCollection(self):
515         return True
516
517     def getLatestModificationDate(self):
518         """ Looks to latest modification date of all resources """
519         return max([x.ModificationDate for x in self.getAllRelatedResources()]+[self.ModificationDate()])
520
521     def getAllRelatedResources(self):
522         return self.getResourcesMetadata(reftype='relatedContent') + self.getResourcesMetadata(reftype='relatedMethods') + self.getResourcesMetadata(reftype='relatedTools')+self.getResourcesMetadata(reftype='relatedCollections')
523
524     def getNextAndPrevLinks(self, item):
525         """ Returns a tuple where (prev, next) are urls to next and previous items in collection """
526         prev=''
527         next=''
528         resources = self.getAllRelatedResources()
529         resources_ids=[r.getId for r in resources]   
530         try:
531             i = resources_ids.index(item.getId())
532         except ValueError:
533             return (prev,next) # No link if item is not in resources
534         if i>0:
535             prev=resources[i-1]
536             prev=self.getPathInsideCollection(prev)
537         if i<len(resources)-1:
538             next=resources[i+1]
539             next=self.getPathInsideCollection(next)
540         return (prev, next)
541
542     def getRelatedContentMetadata(self, **kw):
543         """ Returns catalog objects """
544         pc = getToolByName(self, 'portal_catalog')
545         uids = self.getRawRelatedContent()
546         query = {'UID':uids}
547         query.update(kw)
548         objlist = pc(query)
549         return objlist
550
551     def getRelatedMethodsMetadata(self, **kw):
552         """ Returns catalog objects """
553         pc = getToolByName(self, 'portal_catalog')
554         uids = self.getRawRelatedMethods()
555         query = {'UID':uids}
556         query.update(kw)
557         objlist = pc(query)
558         return objlist
559
560     def getRelatedToolsMetadata(self, **kw):
561         """ Returns catalog objects """
562         pc = getToolByName(self, 'portal_catalog')
563         uids = self.getRawRelatedTools()
564         query = {'UID':uids}
565         query.update(kw)
566         objlist = pc(query)
567         return objlist
568
569     def getRelatedCollectionsMetadata(self, **kw):
570         """ Returns catalog objects """
571         pc = getToolByName(self, 'portal_catalog')
572         uids = self.getRawRelatedCollections()
573         query = {'UID':uids}
574         query.update(kw)
575         objlist = pc(query)
576         return objlist
577
578     def combineCollectionLanguage(self):# Languages of subcollections might need to be considered
579         """ Returns the list of languages used by resourcs in collection"""
580         pc=getToolByName(self, 'portal_catalog')
581         languages = {}
582         language = 'en'
583         objectUIDs = self.getRawRelatedContent() + self.getRawRelatedMethods() + self.getRawRelatedTools()
584         objects = pc({'UID':objectUIDs})
585         for object in objects:
586             # Empty language string gets omited
587             if object.Language != '':
588                 if object.Language not in languages.keys():
589                     languages[object.Language] = 1
590                 else:
591                     languages[object.Language] = languages[object.Language] + 1
592         if languages:
593             sorter = []
594             for k in languages.keys():
595                 sorter.append([languages[k], k])
596             sorter.sort(cmp=lambda x,y: cmp(x[0], y[0]))
597             sorter.reverse()
598             language = sorter[0][1]
599         return language
600
601
602     ################# Embedding collections #################     
603
604
605     def getRandomObject(self):
606         """ Chooses one object to represent the whole collection """
607         objectUIDs = self.getRawRelatedContent() + self.getRawRelatedMethods() + self.getRawRelatedTools()
608         pc = getToolByName(self, 'portal_catalog')
609         results = pc({'UID':objectUIDs, 'getState':['public', 'draft'], 'getHasCoverImage': True})
610         if results:
611             return random.choice(results).getObject()
612         else:
613             return None
614
615     def getCollectionInfo(self):
616         """ Provides data for embedded collection """
617         d={}
618         obj=self.getRandomObject()
619         if obj:
620             d['coverUrl'] = obj.getCoverImageURL()
621         else:
622             d['coverUrl'] = ''
623         d['collectionUrl'] = self.absolute_url()
624         pu = getToolByName(self,'portal_url')
625         d['listStyle'] = "line-height: 1.5em;margin: 0.5em 0 0 1.5em;padding: 0;list-style-image: url(%s/bullet.gif);list-style-type: square" % pu()
626         d['collectionTitle'] = self.Title()
627         d['content_n'] = len(self.getResources(reftype='relatedContent', private_materials=False))
628         d['methods_n'] = len(self.getRelatedMethods())
629         d['tools_n'] = len(self.getRelatedTools())
630         d['collections_n'] = len(self.getRawRelatedCollections())
631         short_desc=self.getBlurp()
632         if short_desc:
633             if short_desc.endswith('</p>'):
634                 short_desc = short_desc[:-4]
635             if short_desc.startswith('<p>'):
636                 short_desc = short_desc[3:]
637         d['collectionDescription'] = short_desc or ''
638         return d
639
640
641
642     ################ Notifications ##############################
643
644     security.declarePrivate('notifyCollectionAdd')
645     def notifyCollectionAdd(self, obj):
646         lutool = getToolByName(self, 'lemill_usertool')
647         auth_mf = lutool.getMemberFolder()
648         if obj.Creator() != lutool.getAuthenticatedId() and obj.meta_type in self.getFeaturedTypes():
649             mf = lutool.getMemberFolder(obj.Creator())
650             # See if notification is checked
651             if mf and mf.canNotify('resource_added_to_collection'):
652                 language=lutool.getCommunicationLanguage(mf)
653                 dict={'name':to_unicode(auth_mf.getNicename()),
654                     'title':to_unicode(obj.Title()),
655                     'url':to_unicode(self.absolute_url())}               
656                 msg = self.translate("%(name)s has added your resource '%(title)s' to a collection: %(url)s",domain="lemill",target_language=language) % dict
657                 ltool = getToolByName(self, 'lemill_tool')
658                 ltool.mailNotification(msg=msg, recipient=mf.getEmail(), language=language)
659
660
661     ########## Collectiong PDF file generation section ##########
662
663     def isPDFEnabled(self):
664         """ Do we want to show link to pdf download? """
665         return pdf_enabled
666
667     def downloadPage(self):
668         """ Just returns collection_pdf -page. We don't have links to this template to avoid bots"""
669         return self.collection_page()
670
671
672     def downloadPDF(self):
673         """ This method is the front-end for pisa, PDF file generator """
674         if not pdf_enabled:
675             return None
676         image_cache={}
677         portal=self.portal_url
678         portal_url=self.portal_url()
679
680         def getFSSPath(piece, size_name):
681             """Get path to actual file"""
682             field=piece.getField('image')
683             stor=field.getStorage(piece)
684             info = stor.getFSSInfo(size_name, piece)   
685             if info is None:
686                 return ''   
687             strategy = stor.getStorageStrategy(size_name, piece)
688             props = stor.getStorageStrategyProperties(size_name, piece, info)
689             path = strategy.getValueFilePath(**props)
690             return path
691
692
693         class Callback(pisa.pisaLinkLoader):
694             """ Our own version of pisa's callback method. If links are local, get them from zope and if they use
695                 FileSystemStorage, point to that file instead of building a temp."""               
696             def __del__(self):
697                 # Please DO NOT remove originals from file system :/
698                 for path in self.tfileList:
699                     if path.split('/')[-1].startswith('pisa-'):
700                         os.remove(path)
701
702                    
703             def getFileName(self, name, relative=None):
704                 name=name.replace("\n","")
705                 name=name.replace(" ", "%20")
706                 if name.startswith('fonts/'):
707                     return os.path.join(TTF_FONTS, name.split('/')[-1])
708                 if name in image_cache:
709                     return image_cache[name]
710                 url = urlparse.urljoin(relative or self.src, name)
711                 url_path=url.split('/')
712                 if url_path[-1] in ['image_large', 'image_small','image_slide'] and url.startswith(portal_url):
713                     portal_path=portal_url.split('/')
714                     url_path=url_path[len(portal_path):]
715                     obj=portal.unrestrictedTraverse(url_path)                   
716                     piece=portal.unrestrictedTraverse(url_path[:-1])
717                     size_name=url_path[-1]
718                     path=getFSSPath(piece, size_name)
719                     image_cache[name]=path
720                     self.tfileList.append(path)
721                     return path
722                 path = urlparse.urlsplit(url)[2]
723                 suffix = ""
724                 if "." in path:
725                     new_suffix = "." + path.split(".")[-1].lower()
726                     if new_suffix in (".css", ".gif", ".jpg", ".png"):
727                         suffix = new_suffix                   
728                 path = tempfile.mktemp(prefix="pisa-", suffix = suffix) 
729                 tfile = file(path, "wb")
730                 if url.startswith(portal_url):
731                     portal_path=portal_url.split('/')
732                     url_path=url_path[len(portal_path):]
733                     obj=portal.unrestrictedTraverse(url_path)                   
734                     if hasattr(obj, 'data') and obj.data:
735                         data=StringIO(str(obj.data))                 
736                     elif hasattr(obj, '_data') and obj._data:
737                         data=StringIO(str(obj._data))
738                     else:
739                         data=StringIO(str(obj))
740                     while True:
741                         d=data.read(1024)
742                         if not d:
743                             break
744                         tfile.write(d)
745                     data.close()
746                     tfile.close()
747                 else:
748                     ufile = urllib.urlopen(url)
749                     while True:
750                         data = ufile.read(1024)
751                         if not data:
752                             break
753                         tfile.write(data)
754                     ufile.close()
755                     tfile.close()
756                 self.tfileList.append(path)
757                 image_cache[name]=path
758                 return path
759         # This is a hack not to get over keyError with 'defavusans' font face
760         #pdfmetrics.registerFont(ttfonts.TTFont("DejaVuSans", os.path.join(TTF_FONTS, "DejaVuSans.ttf")))
761         filename = ".".join((self.id,'pdf'))
762         portal_url=self.portal_url()
763         portal=self.portal_url
764         result_stringio = StringIO()
765         url= '/'.join((self.absolute_url(), 'collection_pdf'))
766         source_text=self.collection_pdf()
767         pdf = pisa.CreatePDF(
768             src=source_text,
769             dest=result_stringio,
770             path=url,
771             link_callback=Callback(url).getFileName,
772             quiet=True,
773             #xhtml=True,
774             #encoding='utf-8',
775             )
776
777         if not pdf.err:
778             response = self.REQUEST.RESPONSE
779             response.setHeader('Content-Type', 'application/pdf')
780             response.setHeader('Content-Disposition', 'attachment; filename=%s' % filename)
781             result_stringio.seek(0)
782             return result_stringio.read()
783         else:
784             ltool = getToolByName(self, "lemill_tool")
785             ltool.addPortalMessage("A problem occured, PDF could not be generated.", type="stop")
786             return self.REQUEST.RESPONSE.redirect(self.absolute_url())
787         # removed   <div id="footerContent"><pdf:pagenumber /></div>
788
789
790 #Zip / SCORM download stuff begin --------------------------------------------------------------------------------
791     def download(self, PACKAGE_TYPE="HTML"):
792         """ Builds a zip. Set PACKAGE_TYPE to SCORM to generate an imsmanifest.xml file. """
793
794         if PACKAGE_TYPE == "PDF":
795             return self.downloadPDF()
796
797         elif PACKAGE_TYPE == "PAGE":
798             return self.downloadPage()
799
800         else:
801             # we'll fix zips and scorms later
802             return
803
804         def splitHTML(htmlPage, pageURL):
805
806             def flatten(seq):
807                 res = []
808                 for item in seq:
809                     if isinstance(item, (list, tuple)):
810                         res.extend(flatten(item))
811                     else:
812                         res.append(item)
813                 return res
814
815             def combineNones(inputList, infoList):
816                 previousInfo = ''
817                 text = ''
818                 list = range(len(infoList))
819                 list.reverse()
820                 for i in list:
821                     if infoList[i] == None:
822                         if previousInfo == None:
823                             text = inputList[i] + text
824                         else:
825                             previousInfo = None
826                             text = inputList[i]
827                         del infoList[i]
828                         del inputList[i]
829                     elif previousInfo == None:
830                         previousInfo = ''
831                         infoList.insert(i + 1, None)
832                         inputList.insert(i + 1, text)
833                 if previousInfo == None:
834                     infoList.insert(0, None)
835                     inputList.insert(0, text)
836
837             def recursiveSplit(inputList, infoList, info, reg, first, next, delete = -1):
838                 if isinstance(inputList, (list, tuple)):
839                     for i in range(len(inputList)):
840                         if not isinstance(infoList[i], str):
841                             inputList[i], infoList[i] = recursiveSplit(inputList[i], infoList[i], info, reg, first, next, delete)
842                 else:
843                     reg = sre.compile(reg, sre.DOTALL | sre.IGNORECASE)
844                     inputList = sre.split(reg, inputList)
845                     if len(inputList) > 1:
846                         infoList = []
847                         for i in range(len(inputList)):
848                             mod = i % next
849                             if mod == first:
850                                 infoList.append(info)
851                             else:
852                                 if mod == delete:
853                                     inputList[i] = ''
854                                 infoList.append(None)
855                         combineNones(inputList, infoList)
856                     else:
857                         infoList = [infoList]
858                 return inputList, infoList
859
860             splitHtml, infoList = recursiveSplit(htmlPage, None, 'base', '<base href="(.*?)".*?/>', 1, 2)
861             if len(splitHtml) > 1:
862                 baseURL = splitHtml[-2]
863                 del splitHtml[1::2]
864                 del infoList[1::2]
865             else:
866                 baseURL = pageURL
867             if not baseURL.endswith('/'):
868                 baseURL += '/'
869
870             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'src', '(<link[^>]+href=")(.*?)("[^>]+rel="stylesheet")', 2, 4)
871             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'href', '(<a[^>]+)href="(.*?)"', 2, 3)
872             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'fvars', '(<object.*?<embed[^>]*?src="(mp3player.swf|FlowPlayer.swf|player.swf)"[^>]*?flashvars=")(.*?)(".*?</object>)', 3, 5, 2)
873             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'XMLmp3', '(<voiceover[^>]+src=")(.*?)(")', 2, 4)
874             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'src', '(<[^>]+src=")(.*?)(")', 2, 4)
875             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'src', '(<param.+?name="movie".*?value=")(.*?)(")', 2, 4)
876             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'fvars', r"(<script.*?>.*?AC_FL_RunContent\(.*?'flashvars', ')(.*?)((?<!\\)'.*?</script>)", 2, 4)
877             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'jsfname', "(<script.*?>.*?AC_FL_RunContent\(.*?'movie', ')(.*?)(')", 2, 4)
878             splitHtml, infoList = recursiveSplit(splitHtml, infoList, 'fvars', '(<param.+?name="flashvars".*?value=")(.*?)(")', 2, 4)
879             return flatten(splitHtml), flatten(infoList), baseURL
880
881         def getAbsoluteURL(url, info, baseURL=''):
882             flash = None
883             if info in ('src', 'jsfname', 'XMLmp3', 'href'):
884                 if not url.startswith('http://'):
885                     url = baseURL + url
886             elif info == 'fvars':
887                 if url.startswith('file='):
888                     url = url[5:-5]
889                     flash = 'mp3'
890                 elif url.startswith('xml='):
891                     url = url[4:]
892                     flash = 'pilot'
893                 elif url.startswith("config={videoFile: '"):
894                     url = url[20:-2]
895                     flash = 'flv1'
896                 elif url.startswith("config={videoFile: \\'"):
897                     url = url[21:-3]
898                     flash = 'flv2'
899             url = url.replace('\\', '/')
900             url = url.replace('at_download/', '')
901             return url, flash
902
903         def splitFileName(fileName):
904             splitted = fileName.split('.')
905             if len(splitted) > 1:
906                 return '.'.join(splitted[:-1]), '.' + splitted[-1]
907             return fileName, ''
908
909         def hasFileNameInSrcs(fileName, srcs):
910             for absURL in srcs:
911                 src = srcs[absURL]
912                 if src.has_key('fileName') and src['fileName'] == fileName:
913                     return src
914             return None
915
916         def findUniqueFileName(absURL, srcs):
917             fileName = absURL.split('?')[0].split('/')[-1]
918             src2 = hasFileNameInSrcs(fileName, srcs)
919             if src2 == None:
920                 return fileName
921             else:
922                 src2['counter'] += 1
923                 fileName, extension = splitFileName(fileName)
924                 return findUniqueFileName('%s(%d)%s' % (fileName, src2['counter'], extension), srcs)
925
926         def processHtml(splitHtml, infoList, srcs, hrefs, baseURL, htmlIndex):
927             for i in range(len(infoList)):
928                 if infoList[i] in ('src', 'jsfname', 'fvars', 'XMLmp3', 'href'):
929                     absURL, flash = getAbsoluteURL(splitHtml[i], infoList[i], baseURL)
930                     if infoList[i] != 'href' or splitHtml[i].find('at_download') != -1:
931                         if not absURL.startswith('http://') or absURL.startswith(self.portal_url()):
932                             if srcs.has_key(absURL):
933                                 srcs[absURL]['usedBy'].add(htmlIndex)
934                             else:
935                                 srcs[absURL] = {}
936                                 src = srcs[absURL]
937                                 src['fileName'] = findUniqueFileName(absURL, srcs)
938                                 src['finalURL'] = ''
939                                 src['usedBy'] = set([htmlIndex])
940                                 src['counter'] = 0
941                                 if flash == 'mp3':
942                                     src['extension'] = 'mp3'
943                                 elif flash == 'pilot':
944                                     src['extension'] = 'xml'
945                                 elif flash in ('flv1', 'flv2'):
946                                     src['extension'] = 'flv'
947                                 elif infoList[i] == 'XMLmp3':
948                                     src['extension'] = 'mp3'
949                                 else:
950                                     src['extension'] = ''
951                         else:
952                             infoList[i] = None
953                 if infoList[i] == 'href':
954                     absURL, flash = getAbsoluteURL(splitHtml[i], infoList[i], baseURL)
955                     if hrefs.has_key(splitHtml[i]):
956                         hrefs[absURL]['usedBy'].add(htmlIndex)
957                     else:
958                         hrefs[absURL] = {}
959                         href = hrefs[absURL]
960                         href['finalURL'] = ''
961                         href['type'] = 0
962                         href['usedBy'] = set([htmlIndex])
963
964         def processPilotXMLs(srcs, baseURLs, baseDirs):
965             xmlDatas = {}
966             xmlList = [absURL for absURL in srcs if srcs[absURL]['extension'] == 'xml']
967             for absURL in xmlList:
968                 src = srcs[absURL]
969                 xml = self.aq_parent.restrictedTraverse(absURL.split(self.portal_url() + '/')[1])().decode('utf_16').encode('utf_8')
970                 xmlDatas[absURL]={}
971                 xmlData = xmlDatas[absURL]
972                 xmlData['split'], xmlData['info'], baseURL = splitHTML(xml, '')
973                 xmlData['fileName'] = src['fileName']
974                 xmlData['baseDirs'] = []
975                 for htmlIndex in src['usedBy']:
976                     processHtml(xmlData['split'], xmlData['info'], srcs, {}, baseURLs[htmlIndex], htmlIndex)
977                     xmlData['baseDirs'].append(baseDirs[htmlIndex])
978             return xmlDatas
979
980         def downloadFiles(srcs, zip, baseDirs = []):
981             def addExtension(fileName, extension):
982                 name, ext = splitFileName(fileName)
983                 if ext == '' and extension != '':
984                     fileName = '%s.%s' % (fileName, extension)
985                 return fileName
986             for absURL in srcs:
987                 src = srcs[absURL]
988                 data = ''
989                 mediaType = ''
990                 subType = ''
991                 try:
992                     if src['extension'] != 'xml':
993                         dataObj = self.aq_parent.restrictedTraverse(absURL.split(self.portal_url() + '/')[1].split('?')[0])
994                         mediaType, subType = dataObj.getContentType().split('/')
995                         subType = subType.split(';')[0]
996                         if mediaType == 'image':
997                             data = dataObj.data.__str__()
998                             if subType in ('jpeg', 'pjpeg'):
999                                src['extension'] = 'jpg'
1000                             elif subType == 'x-ms-bmp':
1001                                src['extension'] = 'bmp'
1002                             elif subType in ('png', 'gif'):
1003                                 src['extension'] = subType
1004                             elif subType == 'x-png':
1005                                 src['extension'] = 'png'
1006                         else:
1007                             data = str(dataObj)
1008                             if mediaType == 'application' and subType == 'x-shockwave-flash':
1009                                 src['extension'] = 'swf'
1010                             elif mediaType == 'video':
1011                                 if subType in ('x-msvideo', 'avi'):
1012                                     src['extension'] = 'avi'
1013                                 elif subType == 'x-ms-wmv':
1014                                     src['extension'] = 'wmv'
1015                                 elif subType == 'mpeg':
1016                                     src['extension'] = 'mpg'
1017                                 elif subType == 'mp4':
1018                                     src['extension'] = subType
1019                                 elif subType == 'quicktime':
1020                                     src['extension'] = 'mov'
1021                             elif mediaType == 'audio' and subType == 'mpeg':
1022                                 src['extension'] = 'mp3'
1023                 except (AttributeError):
1024                     # restrictedTraverse error, see if that can be one of the special cases of internal images
1025                     # Let us try to read the file and see if that can be our case
1026                     try:
1027                         portal_url = getToolByName(self, 'portal_url')
1028                         portal_url = portal_url()
1029                         import urllib2
1030                         # If this is our own object, we try to deal with it (might be unneeded, but let's play it safe)
1031                         if absURL.lower().startswith(portal_url):
1032                             # getting the file data behind url, then reading the file data
1033                             dataObj = urllib2.urlopen(absURL)
1034                             data = dataObj.read()
1035                             # we don't really need mediaType here, just need to know the file extension
1036                             # mostly this should handle cases of system internal images
1037                             subType = absURL.split('/')[-1:][0].split('.')[-1:][0]
1038                             src['extension'] = subType
1039                         else:
1040                             # This is not one of our urls, so we do nothing to handle the situation
1041                             print "restrictedTraverse error: " + absURL
1042                     except (AttributeError):
1043                         # This is not one of our special cases that we can handle
1044                         print "restrictedTraverse error: " + absURL
1045
1046                 fileName = addExtension(src['fileName'], src['extension'])
1047                 src['finalURL'] = '_Files/' + fileName
1048                 if src['extension'] != 'xml':
1049                     for i in src['usedBy']:
1050                         zip.writestr(baseDirs[i] + src['finalURL'], data)
1051
1052         def processHrefs(hrefs, srcs, htmlURLs, baseDirs, portalURL):
1053             def checkURLCases(absURL, portalURL):
1054                 #Might need to import these from some more general place
1055                 places = ['/content/pieces/', '/content/references/', '/content/printresources/', '/content/lessonplans/', '/content/pilots/', '/content/presentations/', '/content/schoolprojects/', '/content/webpages/', '/content/exercises/'] + ['/methods/', '/tools/']
1056                 for place in places:
1057                     if absURL.count(portalURL + place) > 0:
1058                         return True
1059                 return False
1060             downloadURLs = []
1061             for absURL in srcs:
1062                 fileName = absURL.split('?')[0].split('/')
1063                 domain = '/'.join(fileName[:-1])
1064                 fileName = fileName[-1]
1065                 downloadURLs.append('%s/at_download/%s' % (domain, fileName))
1066             for absURL in hrefs:
1067                 href = hrefs[absURL]
1068                 if absURL in htmlURLs:
1069                     href['finalURL'] = 'href="%sindex.html"' % baseDirs[htmlURLs.index(absURL)]
1070                 elif absURL + '/' in htmlURLs:
1071                     href['finalURL'] = 'href="%sindex.html"' % baseDirs[htmlURLs.index(absURL + '/')]
1072                 elif absURL in srcs:
1073                     href['finalURL'] = srcs[absURL]['finalURL']
1074                     href['type'] = 1
1075                 elif absURL in downloadURLs:
1076                     href['finalURL'] = srcs[absURL.replace('/at_download', '')]['finalURL']
1077                     href['type'] = 1
1078                 elif absURL.startswith(portalURL) and not (absURL.endswith('/CollectionRSS') or checkURLCases(absURL, portalURL)):
1079                     href['finalURL'] = ''
1080                 else:
1081                     href['finalURL'] = 'href="%s"' % absURL
1082
1083         def updateHtmls(splitHtmls, infoLists, baseURLs, baseDirs, srcs, hrefs):
1084             for i in range(len(splitHtmls)):
1085                 splitHtml = splitHtmls[i]
1086                 infoList = infoLists[i]
1087                 for j in range(len(splitHtml)):
1088                     if infoList[j] in ('src', 'jsfname', 'fvars'):
1089                         absURL, flash = getAbsoluteURL(splitHtml[j], infoList[j], baseURLs[i])
1090                         src = srcs[absURL]
1091                         finalURL = src['finalURL']
1092                         if infoList[j] == 'src':
1093                             splitHtml[j] = finalURL
1094                         elif infoList[j] == 'jsfname':
1095                             splitHtml[j] = finalURL
1096                         elif infoList[j] == 'fvars':
1097                             if flash == 'mp3':
1098                                 splitHtml[j] = 'file=%s' % finalURL
1099                             elif flash == 'pilot':
1100                                 splitHtml[j] = 'xml=%s' % finalURL
1101                             elif flash == 'flv1':
1102                                 splitHtml[j] = "config={videoFile: '../%s'}" % finalURL
1103                             elif flash == 'flv2':
1104                                 splitHtml[j] = "config={videoFile: \\'../%s\\'}" % finalURL
1105                     elif infoList[j] == 'href':
1106                         absURL, flash = getAbsoluteURL(splitHtml[j], infoList[j], baseURLs[i])
1107                         href = hrefs[absURL]
1108                         if href['type'] == 1:
1109                             finalURL = 'href="%s"' % href['finalURL']
1110                         else:
1111                             finalURL = href['finalURL']
1112                         splitHtml[j] = finalURL
1113
1114         def updatePilotXMLs(xmlDatas, baseURLs, srcs):
1115             for absURL in xmlDatas:
1116                 splitXML = xmlDatas[absURL]['split']
1117                 xmlInfoList = xmlDatas[absURL]['info']
1118                 for j in range(len(splitXML)):
1119                     if xmlInfoList[j] in ('src', 'XMLmp3'):
1120                         absURL2, flash = getAbsoluteURL(splitXML[j], xmlInfoList[j])
1121                         src = srcs[absURL2]
1122                         splitXML[j] = src['finalURL']
1123
1124         def addHtmlsToZip(splitHtmls, zip, baseDirs = None):
1125             i = 0
1126             baseDir = ''
1127             for splitHtml in splitHtmls:
1128                 if isinstance(baseDirs, (list, tuple)):
1129                     baseDir = baseDirs[i]
1130                 zip.writestr(baseDir + 'index.html', ''.join(splitHtml))
1131                 i += 1
1132
1133         def addPilotXMLsToZip(xmlDatas, srcs, zip):
1134             for absURL in xmlDatas:
1135                 xmlData = xmlDatas[absURL]
1136                 xml = ''.join(xmlData['split'])
1137                 for baseDir in xmlData['baseDirs']:
1138                     zip.writestr(baseDir + srcs[absURL]['finalURL'], xml.decode('utf_8').encode('utf_16'))
1139                     break
1140
1141         def addSCORMFiles(version, htmlList, srcs, zip):
1142             def addMetadata(manifest, obj, tabs):
1143                 language = obj.Language()
1144                 if language == '':
1145                     language = 'en'
1146                 tab_str=''.join(['\t']*tabs)                   
1147                 manifest.append(''.join((tab_str,'<metadata>')))
1148                 manifest.append(''.join((tab_str,'\t<schema>ADL SCORM</schema>')))
1149                 manifest.append(''.join((tab_str,'\t<schemaversion>1.2</schemaversion>')))
1150                 manifest.append(''.join((tab_str,'\t<imsmd:lom>')))
1151                 manifest.append(''.join((tab_str,'\t\t<general>')))
1152                 manifest.append(''.join((tab_str,'\t\t\t<title>')))
1153                 manifest.append(''.join((tab_str,'\t\t\t\t<langstring xml:lang="',language,'">',obj.TitleOrId(),'</langstring>')))
1154                 manifest.append(''.join((tab_str,'\t\t\t</title>')))
1155                 manifest.append(''.join((tab_str,'\t\t\t<language>',language,'</language>')))
1156                 tags = obj.getTags()
1157                 for tag in tags:
1158                     manifest.append(''.join((tab_str,'\t\t\t<keyword>')))
1159                     manifest.append(''.join((tab_str,'\t\t\t\t<langstring xml:lang="',language,'">',tag,'</langstring>')))
1160                     manifest.append(''.join((tab_str,'\t\t\t</keyword>')))
1161                 manifest.append(''.join((tab_str,'\t\t</general>')))
1162                 manifest.append(''.join((tab_str,'\t\t<lifecycle>')))
1163                 manifest.append(''.join((tab_str,'\t\t\t<version>')))
1164                 manifest.append(''.join((tab_str,'\t\t\t\t<langstring xml:lang="',language,'">',str(obj.getLatestEditDate()),'</langstring>')))
1165                 manifest.append(''.join((tab_str,'\t\t\t</version>')))
1166                 manifest.append(''.join((tab_str,'\t\t</lifecycle>')))
1167                 manifest.append(''.join((tab_str,'\t</imsmd:lom>')))
1168                 manifest.append(''.join((tab_str,'</metadata>')))
1169                 return manifest
1170
1171             manifest=[]
1172             manifest.append('<?xml version="1.0" encoding="utf-8"?>')
1173             manifest.append('<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">' % (htmlList[0][0].getId(), version))
1174             manifest = addMetadata(manifest, self, 1)
1175             manifest.append('\t<organizations default="ORGANIZATION_%s">' % htmlList[0][0].getId())
1176             manifest.append('\t\t<title>%s</title>' % htmlList[0][0].TitleOrId())
1177             manifest.append('\t\t<organization identifier="ORGANIZATION_%s" structure="linear">' % htmlList[0][0].getId())
1178             for obj in htmlList:
1179                 manifest.append('\t\t\t<item identifier="ITEM_%s" identifierref="RESOURCE_%s" isvisible="true">' % (obj[0].getId(), obj[0].getId()))
1180                 manifest.append('\t\t\t\t<title>%s</title>' % obj[0].TitleOrId())
1181                 manifest = addMetadata(manifest, obj[0], 4)
1182                 manifest.append('\t\t\t</item>')
1183             manifest.append('\t\t</organization>')
1184             manifest.append('\t</organizations>')
1185             manifest.append('\t<resources>')
1186             for i in range(len(htmlList)):
1187                 manifest.append('\t\t<resource identifier="RESOURCE_%s" type="webcontent" adlcp:scormtype="sco" href="%sindex.html">' % (htmlList[i][0].getId(), htmlList[i][1]))
1188                 manifest.append('\t\t\t<file href="%sindex.html" />' % htmlList[i][1])
1189                 for absURL in srcs:
1190                     src = srcs[absURL]
1191                     if i in src['usedBy']:
1192                         finalURL = htmlList[i][1] + src['finalURL']
1193                         manifest.append('\t\t\t<file href="%s" />' % finalURL)
1194                 manifest.append('\t\t</resource>')
1195             manifest.append('\t</resources>')
1196             manifest.append('</manifest>')
1197             zip.writestr('imsmanifest.xml', '\n'.join(manifest))
1198             for file in ('adlcp_rootv1p2.xsd', 'ims_xml.xsd', 'imscp_rootv1p1p2.xsd', 'imsmd_rootv1p2p1.xsd'):
1199                 f = open(REPOSITORY + file, 'r')
1200                 zip.writestr(file, f.read())
1201                 f.close()
1202
1203         splitHtmls = []
1204         infoLists = []
1205         baseURLs = []
1206         baseDirs = []
1207         htmlURLs = []
1208         srcs = {}
1209         hrefs = {}
1210         times = []
1211         htmlList = [(self, '')]
1212         htmlIndex = 0
1213         #XXX This one could be moving in from some differet place, so that we would not forget to update it when new types get created
1214         content_subtypes = {'Piece':'pieces','LeMillReference':'references','PresentationMaterial':'presentations','PILOTMaterial':'pilots','MultimediaMaterial':'webpages','ExerciseMaterial':'exercises','LessonPlan':'lessonplans','SchoolProjectMaterial':'schoolprojects','LeMillPrintResource':'printresources'}
1215         for resourceType in ('Content', 'Methods', 'Tools'):
1216             for resource in self.getResources(reftype = 'related' + resourceType):
1217                 if resourceType == 'Content':
1218                     if resource.meta_type in content_subtypes.keys():
1219                         htmlList.append((resource, '/'.join((resourceType.lower(), content_subtypes[resource.meta_type], resource.getId(),''))))
1220                 else:
1221                     htmlList.append((resource, '/'.join((resourceType.lower(), resource.getId(),''))))
1222                 times.append(resource.getLatestEditDate())
1223
1224         for html in htmlList:
1225             splitHtml, infoList, baseURL = splitHTML(html[0].standalone_view(), self.absolute_url())
1226             splitHtmls.append(splitHtml)
1227             infoLists.append(infoList)
1228             baseURLs.append(baseURL)
1229             baseDirs.append(html[1])
1230             htmlURLs.append('/'.join((self.absolute_url(), html[1])))
1231             processHtml(splitHtml, infoList, srcs, hrefs, baseURL, htmlIndex)
1232             htmlIndex += 1
1233
1234         xmlDatas = processPilotXMLs(srcs, baseURLs, baseDirs)
1235         zipStr = StringIO()
1236         zip = zipfile.ZipFile(zipStr, 'w', compression = zipfile.ZIP_DEFLATED)
1237         downloadFiles(srcs, zip, baseDirs)
1238         processHrefs(hrefs, srcs, htmlURLs, baseDirs, self.portal_url())
1239         updateHtmls(splitHtmls, infoLists, baseURLs, baseDirs, srcs, hrefs)
1240         updatePilotXMLs(xmlDatas, baseURLs, srcs)
1241         addHtmlsToZip(splitHtmls, zip, baseDirs)
1242         addPilotXMLsToZip(xmlDatas, srcs, zip)
1243         if PACKAGE_TYPE == 'SCORM':
1244             addSCORMFiles(str(max(times)), htmlList, srcs, zip)
1245         zip.close()
1246         response = self.REQUEST.RESPONSE
1247         response.setHeader('Content-Type', 'application/zip')
1248         response.setHeader('Content-Disposition', 'attachment; filename="%s.zip"' % self.getId())
1249         return zipStr.getvalue()
1250 #Zip / SCORM download sruff end ----------------------------------------------------------------------------------
1251
1252
1253 registerType(Collection, PROJECTNAME)
1254
1255 class CollectionsFolder(LeMillFolder, BaseFolder):
1256     """ container for collection """
1257     schema = BaseSchema
1258     id = "collections"
1259     meta_type = "CollectionsFolder"
1260     archetype_name = "CollectionsFolder" 
1261
1262     actions= (
1263     {
1264     'id':'view',
1265     'name':'view',
1266     'action':'string:${object_url}/collections_list',
1267     'permission':('View',),
1268     },
1269     )
1270
1271     aliases = {
1272         '(Default)' : 'collections_list',
1273         'view'      : 'collections_list',
1274         'edit'      : 'collections_list'
1275     }
1276                
1277 registerType(CollectionsFolder, PROJECTNAME)
Note: See TracBrowser for help on using the browser.