source: enpraxis.staticsite/trunk/enpraxis/staticsite/utilities/staticsiteutility.py @ 354

Revision 354, 18.7 KB checked in by david, 4 years ago (diff)

splitting corner cases into their own functions; added base_url back in, as JS is dependent on it for exiting presentation view properly

Line 
1import os
2import re
3import string
4import Globals
5from BeautifulSoup import BeautifulSoup
6from os import makedirs as os_makedirs
7from os.path import join as os_join
8from os.path import lexists as os_lexists
9from os.path import split as os_split
10
11from urllib2 import urlopen
12from urllib2 import HTTPError
13from urlparse import urlparse, urlunparse
14from Products.PythonScripts.standard import url_quote
15
16from OFS.SimpleItem import SimpleItem
17from zope.interface import implements
18from interfaces import IStaticSiteUtility
19from zope.component import getUtility, getMultiAdapter
20
21class StaticSiteUtility(SimpleItem):
22    """ Deploy a static site """
23
24    implements(IStaticSiteUtility)
25
26    def deploySite(self, context):
27        """ Deploy the site """
28        ssprops = context.portal_url.portal_properties.staticsite_properties
29        dpath = self._getDeploymentPath(ssprops.getProperty('deployment_path'))
30        # Deploy default objects
31        self.deploySiteStructure(context, ssprops, dpath)
32        # Deploy site object
33        self.deployObject(context.absolute_url(), context, context.Type(), dpath, ssprops, True)
34        # Deploy objects based on catalog search
35        brains = context.portal_catalog.searchResults(
36            path={'query':'/'.join(context.getPhysicalPath()),
37                  'depth':1,},
38            review_state=ssprops.getProperty('states_to_add'))
39        for brain in brains:
40            self.deployObject(brain.getURL(),
41                              context,
42                              brain.Type,
43                              dpath,
44                              ssprops,
45                              folderish=brain.is_folderish)
46            self.traverse(brain, dpath, ssprops)
47
48    def deploySiteStructure(self, context, ssprops, dpath):
49        """ Get the base framework and resources needed to build the chrome locally  """
50        portal_url = context.portal_url()
51        portal_catalog = context.portal_catalog
52        url = urlparse(portal_url)
53        urlpath = url[2].split('/')
54
55        # Deploy base files that are used sitewide in the chrome
56        for x in ssprops.getProperty('base_files'):
57            objurl = urlunparse((url[0], url[1], '/'.join(urlpath + [x]), url[3], url[4], url[5]))
58            path = self._getObjPath(objurl, portal_url, dpath)
59            raw = self._httpget(objurl)
60            self._writeFile(path, raw)
61
62        # Deploy CSS
63        cssreg = context.portal_css
64        cssurl = cssreg.absolute_url()
65        skinname = context.getCurrentSkinName()
66        for x in cssreg.getEvaluatedResources(context):
67            if x.getRendering() != 'inline':
68                surl = '%s/%s/%s' %(cssurl, skinname, x.getId())
69                path = self._getObjPath(surl, portal_url, dpath)
70                surl = '%s/%s/%s' %(cssurl, url_quote(skinname), x.getId())
71                raw = self._httpget(surl)
72                self._writeFile(path, raw)
73
74        # Deploy CSS Images into the appropriate skin folder
75        css_images = ssprops.getProperty('css_images')
76        if len(css_images) > 0:
77            for x in css_images:
78                iurl = '%s/%s' %(portal_url, x)
79                surl = '%s/%s/%s' %(cssurl, skinname, x)
80                path = self._getObjPath(surl, portal_url, dpath)
81                raw = self._httpget(iurl)
82                self._writeFile(path, raw)
83
84        # Deploy Javascript
85        jsreg = context.portal_javascripts
86        jsurl = jsreg.absolute_url()
87        for x in jsreg.getEvaluatedResources(context):
88            if x.getInline() != True:           
89                surl = '%s/%s/%s' %(jsurl, skinname, x.getId())
90                path = self._getObjPath(surl, portal_url, dpath)
91                surl = '%s/%s/%s' %(jsurl, url_quote(skinname), x.getId())
92                raw = self._httpget(surl)
93                self._writeFile(path, raw)   
94
95        # Deploy site actions
96        site_actions = context.portal_actions.site_actions.listActions()
97        for x in site_actions:
98            if x.id not in ssprops.getProperty('actions_to_ignore') and x.visible == True:
99                action_url = x.url_expr.split('/')[-1]
100                url = '%s/%s' % (portal_url, action_url)
101                path = self._getObjPath(url, portal_url, dpath)
102                path += '.html'
103                raw = self._httpget(url)       
104                self._writeFile(path, raw)
105       
106    def traverse(self, brain, dpath, ssprops):
107        """ Traverse the site. """
108        brains = brain.portal_catalog.searchResults(
109            path={'query':brain.getPath(), 'depth':1},
110                  review_state=ssprops.getProperty('states_to_add'))
111        for br in brains:
112            self.deployObject(br.getURL(),
113                              br.portal_url,
114                              br.Type,
115                              dpath,
116                              ssprops,
117                              folderish=br.is_folderish)
118            if br.is_folderish:
119                self.traverse(br, dpath, ssprops)
120           
121    def deployObject(self, url, portal, ctype, dpath, ssprops, folderish=False, hastext=False):
122        """ Deploy an object """
123        path = self._getObjPath(url, portal.portal_url(), dpath)
124        if folderish:
125            print '@@@ '+  url + '/index.html'
126            self.processDocument(url, portal, dpath, ssprops, True)
127        elif ctype in ['Page']:
128            # Fix this so that it deals with the case where you have both a file and a file.html in the
129            # same folder
130            print '*** '+  url
131            self.processDocument(url, portal, dpath, ssprops)
132        else:
133            raw = self._httpget(url)
134            self._writeFile(path, raw)
135            aurl = urlparse(url)
136            aurl = urlunparse((aurl[0], aurl[1], aurl[2] + '-view.html', aurl[3], aurl[4], aurl[5]))
137            print 'vvv '+  aurl
138            self.processDocument(url + '/view', portal, dpath, ssprops, alturl=aurl)
139            if ctype in ['Image']:
140                aurl = urlparse(url)
141                aurl = urlunparse((aurl[0], aurl[1], aurl[2] + '-image_view_fullscreen.html', aurl[3], aurl[4], aurl[5]))
142                print 'iii '+  aurl
143                self.processDocument(url + '/image_view_fullscreen', portal, dpath, ssprops, alturl=aurl)
144
145
146    def _getDeploymentPath(self, sp):
147        """ Get the default static path location. """
148        dpath = os_split(Globals.BobobaseName)[0]
149        return os_join(dpath, sp)
150       
151    def _getObjPath(self, url, portal_url, dpath):
152        """ Get the object path based on the deployment path. """
153        if portal_url + '/' in url:
154            objpath = url.replace(portal_url + '/', '')
155        else:
156            objpath = url.replace(portal_url, '')
157        path = dpath
158        for x in objpath.split('/'):
159            path = os_join(path, x)
160        return path
161   
162    def _createDirectory(self, path):
163        """ Create a directory on the filesystem """     
164        if not os_lexists(path):
165            os_makedirs(path)
166           
167    def _httpget(self, url):
168        """ Get html for the url """
169        try:
170            f = urlopen(url)
171            data = f.read()
172            f.close()
173            return data
174        except HTTPError, e:           
175            print '!!! Error : %s %s for url: %s' % (e.code, e.msg, e.filename)
176            return ''
177           
178    def _writeFile(self, fn, data):
179        self._createDirectory(os_split(fn)[0])
180        f = open(fn, 'w')
181        f.write(data)
182        f.close()
183   
184    def processDocument(self, url, portal, dpath, ssprops, isFolderish=False, alturl=None):
185        if alturl:
186            aurl = alturl
187        else:
188            aurl = url
189        path = self._getObjPath(aurl, portal.portal_url(), dpath)
190        raw = self._httpget(url)
191        soup = BeautifulSoup(raw)
192        self.deployDocumentActions(portal, aurl, dpath, soup, ssprops)
193        if isFolderish:
194            curl = aurl + '/index.html'
195        else:
196            curl = aurl
197        body = self.runDocumentFilters(portal, curl, soup, ssprops)
198        mpath = path
199        if isFolderish:
200            mpath += '/index.html'
201        elif '.htm' not in mpath:
202            mpath += '.html'
203        self._writeFile(mpath, body)
204
205    def deployDocumentActions(self, portal, current, dpath, soup, ssprops):
206        raw_da = soup.find('div', {'class':'documentActions'})
207        if raw_da:
208            da = portal.portal_actions.document_actions.listActions()
209            for x in da:
210                if x.id not in ssprops.getProperty('actions_to_ignore'):
211                    act = raw_da.find('li', id='document-action-%s' %x.id)
212                    if act:
213                        link = act.find('a')
214                        if link and link.has_key('href'):
215                            url = link['href']
216                            upath = urlparse(link['href'])
217                            if upath[2].split('/')[-1] in ssprops.getProperty('views_to_add'):
218                                mpath = self._getObjPath(url, portal.portal_url(), dpath)
219                                mpath = os.path.split(mpath)
220                                raw = self._httpget(url)
221                                asoup = BeautifulSoup(raw)
222                                body = self.runDocumentFilters(portal, current, asoup, ssprops)
223                                mpath = '%s-%s.html' %(mpath[0], mpath[1])
224                                self._writeFile(mpath, body)
225
226    def runViewFilters(self, id, portal, current, soup, ssprops):
227        self.filterIgnoredSections(soup, ssprops)
228        self.filterIgnoredActions(soup, ssprops)
229        self.filterCSSLinks(soup, current)
230        links = self.getDocumentLinks(soup)
231        for x in links:
232            orig = x['href']
233            x['href'] = self.filterDocumentLink(x['href'],
234                                                current,
235                                                portal,
236                                                ssprops.getProperty('views_to_add'))
237            print '   (view-%s) %s => %s' %(id, orig, x['href'])
238        return soup.prettify()
239
240    def filterViewLink(self, link, current, views):
241        lnk = link
242        lnk = self._convertLinkToAbsolute(lnk, current)
243        lnk = self._convertViewLink(lnk, views)
244        lnk = self._convertLinkToRelative(lnk, current)
245        return lnk
246
247    def _convertViewLink(self, link, views):
248        result = link
249        url = urlparse(link)
250        path = url[2].split('/')
251        if path[-1] in views:
252            if len(path) > 1:
253                path[-2] += '-%s.html' %path[-1]
254                path = path[:-1]
255                result = urlunparse((url[0], url[1], '/'.join(path), url[3], url[4], url[5]))
256        return result
257   
258    def runDocumentFilters(self, portal, current, soup, ssprops):
259        self.filterBaseTag(soup, current)
260        self.filterIgnoredSections(soup, ssprops)
261        self.filterIgnoredActions(soup, ssprops)
262        self.filterDocActionImages(soup, portal.portal_url(), current)
263        self.filterCSSLinks(soup, current)
264        self.filterIEFixesCSS(soup, current)
265        self.filterJSLinks(soup, current)
266        self.filterS5BaseUrl(soup, current)       
267        self.filterImageFullscreenBackLink(soup, current)
268        links = self.getDocumentLinks(soup)
269        for x in links:
270            orig = x['href']
271            x['href'] = self.filterDocumentLink(x['href'],
272                                                current,
273                                                portal,
274                                                ssprops.getProperty('views_to_add'))
275            print '   %s => %s' %(orig, x['href'])
276
277        return soup.prettify()
278
279    def filterBaseTag(self, soup, current):
280        base = soup.findAll('base')
281        for x in base:
282            if x.has_key('href'):
283                url = x['href']
284                url = url.split(');')[0]
285                url = self._convertLinkToRelative(url, current)
286                x['href'] = url               
287
288    def filterIgnoredActions(self, soup, ssprops):
289        ftags = soup.findAll('div') + soup.findAll('li')
290        for x in ftags:
291            if x.has_key('id'):
292                id = x['id']
293                if 'document-action' in id:
294                    act = id.split('-')[-1]
295                    if act in ssprops.getProperty('actions_to_ignore'):
296                        x.extract()
297
298    def filterIgnoredSections(self, soup, ssprops):
299        for x in ssprops.getProperty('sections_to_ignore'):
300            tag = soup.find(id=x)
301            if tag:
302                tag.extract()
303
304    def filterDocActionImages(self, soup, portal_url, current):
305        tags = soup.findAll('li')
306        for x in tags:
307            if x.has_key('id'):
308                id = x['id']
309                if 'document-action' in id:                   
310                    for z in x.findAll('img'):
311                        src = z['src']
312                        surl = '%s/%s' % (portal_url, src)
313                        url = self._convertLinkToRelative(surl, current)                       
314                        z['src'] = url
315
316    def filterCSSLinks(self, soup, current):
317        #There are 3 cases, importing stylesheets, and linked stylesheets, and IE commented styles, process all
318        styles = soup.findAll('style', type="text/css")
319        for x in styles:
320            body = x.contents[0]
321            if '@import' in body:
322                url = body.split('url(')[-1]
323                url = url.split(');')[0]
324                url = self._convertLinkToRelative(url, current)
325                x.contents[0].replaceWith('<!-- @import url(%s); -->' %url)         
326        styles = soup.findAll('link', type="text/css")
327        for x in styles:
328            if '.htm' not in current:
329                current += '/index.html'
330            if x.has_key('href'):
331                url = x['href']
332                url = self._convertLinkToRelative(url, current)
333                x['href'] = url
334
335
336    def filterIEFixesCSS(self, soup, current):
337        ie_css = soup.find(text=re.compile("IEFixes.css"))
338        if ie_css:
339            url = ie_css.split('url(')[-1]
340            url = url.split(');')[0]
341            nurl = self._convertLinkToRelative(url, current)
342            ie_css.replaceWith('''<!--[if IE]>
343                                  <style type="text/css" media="all">@import url(%s);</style>
344                                  <![endif]-->''' %nurl)       
345
346    def filterJSLinks(self, soup, current):
347        scripts = soup.findAll('script', type="text/javascript")
348        for x in scripts:
349            if x.has_key('src') == True:
350                url = x['src']
351                url = url.split(');')[0]
352                url = self._convertLinkToRelative(url, current)
353                x['src'] = url               
354
355    def filterS5BaseUrl(self, soup, current):
356        scripts = soup.findAll('script', type="text/javascript")
357        for x in scripts:
358            if len(x.contents) > 0 and 'base' in x.contents[0]:
359                base_url = x.contents[0].split('url="')[-1]
360                base_url = base_url.split('";')[0]
361                if '.htm' not in base_url:
362                    base_url += '.html'
363                url = self._convertLinkToRelative(base_url, current)
364                x.contents[0].replaceWith('var base_url="%s";' % url)
365 
366       
367    def getDocumentLinks(self, soup):
368        tags = soup.findAll('a') + soup.findAll('link')
369        links = []
370        for tag in tags:
371            if tag.has_key('href'):
372                url = urlparse(tag['href'])
373                if not url[1] or 'localhost' in url[1]:
374                    links.append(tag)
375        return links
376
377    def filterImageFullscreenBackLink(self, soup, current):
378        if 'image_view_fullscreen' in current:
379            back = soup.find('a')
380            if back:
381                lt = back.find('span')
382                if lt:
383                    lt.contents[0].replaceWith('Back to Image')
384                if back.has_key('href'):
385                    back['href'] = current.replace('image_view_fullscreen.html', 'view.html')
386       
387
388    def filterDocumentLink(self, link, current, portal, views):       
389        lnk = link
390        url = urlparse(lnk)
391        if url[2] and 'javascript' != url[0]:
392            lnk = self._convertLinkToAbsolute(lnk, current)
393            lnk = self._convertObjectLink(lnk, portal, views)
394            lnk = self._convertLinkToRelative(lnk, current)
395        return lnk
396
397    def _convertLinkToAbsolute(self, link, current):
398        result = link
399        c = urlparse(current)
400        hr = urlparse(link)
401        if not hr[0] and not hr[1]:
402            cp = c[2].split('/')
403            hp = hr[2].split('/')
404            p = []
405            for y in hp:
406                if '.' == y:
407                    pass
408                elif '..' == y:
409                    cp = cp[:-1]
410                else:
411                    p.append(y)
412            result = urlunparse((c[0], c[1], '/'.join(cp + p), hr[3], hr[4], hr[5]))
413        return result
414
415    def _convertObjectLink(self, link, portal, views):
416        # This only works for absolute links
417        result = link
418        hr = urlparse(link)
419        p = urlparse(portal.portal_url())
420        if p[1] == hr[1]:
421            h = hr[2].split('/')
422            view = ''
423            if h[-1] in views:
424                view = h[-1]
425                h = h[:-1]
426            results = portal.portal_catalog.searchResults(query={'path':'/'.join(h),}, id=h[-1])
427            if results:
428                path = ''
429                if results[0].is_folderish:
430                    if view:
431                        path = '/'.join(h) + '/index' + '-%s.html' %view
432                    else:
433                        path = '/'.join(h) + '/index.html'
434                else:
435                    if view:
436                        path = '/'.join(h) + '-%s.html' %view
437                    elif h:
438                        path = '/'.join(h)
439                        if 'Page' == results[0].Type and '.htm' not in path:
440                            path += '.html'
441                result = urlunparse((hr[0], hr[1], path, hr[3], hr[4], hr[5]))
442            elif link == portal.portal_url():
443                # Link points to site root
444                result = urlunparse((hr[0], hr[1], '/'.join(h) + '/index.html', hr[3], hr[4], hr[5]))
445        return result
446
447    def _convertLinkToRelative(self, link, current):
448        # This will break if the last item in the url path is a folder
449        # Make sure you rewrite the link path before you call this function
450        hr = urlparse(link)
451        c = urlparse(current)
452        if c[1] == hr[1]:
453            url1 = c[2].split('/')
454            url2 = hr[2].split('/')
455            index = 0
456            while url1[index:] and url2[index:] and url1[index] == url2[index]:
457                index += 1
458            p = []
459            for y in range(len(url1[index+1:])):
460                p.append('..')
461            p = p + url2[index:]
462            return urlunparse(('', '', '/'.join(p), hr[3], hr[4], hr[5]))
463       
Note: See TracBrowser for help on using the repository browser.