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

Revision 333, 16.7 KB checked in by brent, 4 years ago (diff)

Fixed test.html.html loink error and added file views.

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        for x in ssprops.getProperty('css_images'):
76            iurl = '%s/%s' %(portal_url, x)
77            surl = '%s/%s/%s' %(cssurl, skinname, x)
78            path = self._getObjPath(surl, portal_url, dpath)
79            raw = self._httpget(iurl)
80            self._writeFile(path, raw)
81
82        # Deploy Javascript
83        jsreg = context.portal_javascripts
84        jsurl = jsreg.absolute_url()
85        for x in jsreg.getEvaluatedResources(context):
86            if x.getInline() != True:           
87                surl = '%s/%s/%s' %(jsurl, skinname, x.getId())
88                path = self._getObjPath(surl, portal_url, dpath)
89                surl = '%s/%s/%s' %(jsurl, url_quote(skinname), x.getId())
90                raw = self._httpget(surl)
91                self._writeFile(path, raw)   
92
93        # Deploy site actions
94        site_actions = context.portal_actions.site_actions.listActions()
95        for x in site_actions:
96            if x.id not in ssprops.getProperty('actions_to_ignore') and x.visible == True:
97                action_url = x.url_expr.split('/')[-1]
98                url = '%s/%s' % (portal_url, action_url)
99                path = self._getObjPath(url, portal_url, dpath)
100                path += '.html'
101                raw = self._httpget(url)       
102                self._writeFile(path, raw)
103       
104    def traverse(self, brain, dpath, ssprops):
105        """ Traverse the site. """
106        brains = brain.portal_catalog.searchResults(
107            path={'query':brain.getPath(), 'depth':1},
108                  review_state=ssprops.getProperty('states_to_add'))
109        for br in brains:
110            self.deployObject(br.getURL(),
111                              br.portal_url,
112                              br.Type,
113                              dpath,
114                              ssprops,
115                              folderish=br.is_folderish)
116            if br.is_folderish:
117                self.traverse(br, dpath, ssprops)
118           
119    def deployObject(self, url, portal, ctype, dpath, ssprops, folderish=False, hastext=False):
120        """ Deploy an object """
121        path = self._getObjPath(url, portal.portal_url(), dpath)
122        if folderish:
123            print '@@@ '+  url + '/index.html'
124            self.processDocument(url, portal, dpath, ssprops, True)
125        elif ctype in ['Page']:
126            # Fix this so that it deals with the case where you have both a file and a file.html in the
127            # same folder
128            print '*** '+  url
129            self.processDocument(url, portal, dpath, ssprops)
130        else:
131            raw = self._httpget(url)
132            self._writeFile(path, raw)
133            aurl = urlparse(url)
134            aurl = urlunparse((aurl[0], aurl[1], aurl[2] + '-view.html', aurl[3], aurl[4], aurl[5]))
135            print 'vvv '+  aurl
136            self.processDocument(url + '/view', portal, dpath, ssprops, alturl=aurl)
137
138    def _getDeploymentPath(self, sp):
139        """ Get the default static path location. """
140        dpath = os_split(Globals.BobobaseName)[0]
141        return os_join(dpath, sp)
142       
143    def _getObjPath(self, url, portal_url, dpath):
144        """ Get the object path based on the deployment path. """
145        if portal_url + '/' in url:
146            objpath = url.replace(portal_url + '/', '')
147        else:
148            objpath = url.replace(portal_url, '')
149        path = dpath
150        for x in objpath.split('/'):
151            path = os_join(path, x)
152        return path
153   
154    def _createDirectory(self, path):
155        """ Create a directory on the filesystem """     
156        if not os_lexists(path):
157            os_makedirs(path)
158           
159    def _httpget(self, url):
160        """ Get html for the url """
161        try:
162            f = urlopen(url)
163            data = f.read()
164            f.close()
165            return data
166        except HTTPError, e:           
167            print '!!! Error : %s %s for url: %s' % (e.code, e.msg, e.filename)
168            return ''
169           
170    def _writeFile(self, fn, data):
171        self._createDirectory(os_split(fn)[0])
172        f = open(fn, 'w')
173        f.write(data)
174        f.close()
175   
176    def processDocument(self, url, portal, dpath, ssprops, isFolderish=False, alturl=None):
177        if alturl:
178            aurl = alturl
179        else:
180            aurl = url
181        path = self._getObjPath(aurl, portal.portal_url(), dpath)
182        raw = self._httpget(url)
183        soup = BeautifulSoup(raw)
184        self.deployDocumentActions(portal, aurl, dpath, soup, ssprops)
185        if isFolderish:
186            curl = aurl + '/index.html'
187        else:
188            curl = aurl
189        body = self.runDocumentFilters(portal, curl, soup, ssprops)
190        mpath = path
191        if isFolderish:
192            mpath += '/index.html'
193        elif '.htm' not in mpath:
194            mpath += '.html'
195        self._writeFile(mpath, body)
196
197    def deployDocumentActions(self, portal, current, dpath, soup, ssprops):
198        raw_da = soup.find('div', {'class':'documentActions'})
199        if raw_da:
200            da = portal.portal_actions.document_actions.listActions()
201            for x in da:
202                if x.id not in ssprops.getProperty('actions_to_ignore'):
203                    act = raw_da.find('li', id='document-action-%s' %x.id)
204                    if act:
205                        link = act.find('a')
206                        if link and link.has_key('href'):
207                            url = link['href']
208                            upath = urlparse(link['href'])
209                            if upath[2].split('/')[-1] in ssprops.getProperty('views_to_add'):
210                                mpath = self._getObjPath(url, portal.portal_url(), dpath)
211                                mpath = os.path.split(mpath)
212                                raw = self._httpget(url)
213                                asoup = BeautifulSoup(raw)
214                                body = self.runDocumentFilters(portal, portal.portal_url(), asoup, ssprops)
215                                mpath = '%s-%s.html' %(mpath[0], mpath[1])
216                                self._writeFile(mpath, body)
217
218    def runViewFilters(self, id, portal, current, soup, ssprops):
219        self.filterIgnoredSections(soup, ssprops)
220        self.filterIgnoredActions(soup, ssprops)
221        self.filterCSSLinks(soup, current)
222        links = self.getDocumentLinks(soup)
223        for x in links:
224            orig = x['href']
225            x['href'] = self.filterDocumentLink(x['href'],
226                                                current,
227                                                portal,
228                                                ssprops.getProperty('views_to_add'))
229            print '   (view-%s) %s => %s' %(id, orig, x['href'])
230        return soup.prettify()
231
232    def filterViewLink(self, link, current, views):
233        lnk = link
234        lnk = self._convertLinkToAbsolute(lnk, current)
235        lnk = self._convertViewLink(lnk, views)
236        lnk = self._convertLinkToRelative(lnk, current)
237        return lnk
238
239    def _convertViewLink(self, link, views):
240        result = link
241        url = urlparse(link)
242        path = url[2].split('/')
243        if path[-1] in views:
244            if len(path) > 1:
245                path[-2] += '-%s.html' %path[-1]
246                path = path[:-1]
247                result = urlunparse((url[0], url[1], '/'.join(path), url[3], url[4], url[5]))
248        return result
249   
250    def runDocumentFilters(self, portal, current, soup, ssprops):
251        self.filterBaseTag(soup, current)
252        self.filterIgnoredSections(soup, ssprops)
253        self.filterIgnoredActions(soup, ssprops)
254        self.filterDocActionImages(soup, portal.portal_url(), current)
255        self.filterCSSLinks(soup, current)
256        self.filterJSLinks(soup, current)               
257        links = self.getDocumentLinks(soup)
258        for x in links:
259            orig = x['href']
260            x['href'] = self.filterDocumentLink(x['href'],
261                                                current,
262                                                portal,
263                                                ssprops.getProperty('views_to_add'))
264            print '   %s => %s' %(orig, x['href'])
265
266        return soup.prettify()
267
268    def filterBaseTag(self, soup, current):
269        base = soup.findAll('base')
270        for x in base:
271            if x.has_key('href'):
272                url = x['href']
273                url = url.split(');')[0]
274                url = self._convertLinkToRelative(url, current)
275                x['href'] = url               
276
277    def filterIgnoredActions(self, soup, ssprops):
278        ftags = soup.findAll('div') + soup.findAll('li')
279        for x in ftags:
280            if x.has_key('id'):
281                id = x['id']
282                if 'document-action' in id:
283                    act = id.split('-')[-1]
284                    if act in ssprops.getProperty('actions_to_ignore'):
285                        x.extract()
286
287    def filterIgnoredSections(self, soup, ssprops):
288        for x in ssprops.getProperty('sections_to_ignore'):
289            tag = soup.find('div', id=x)
290            if tag:
291                tag.extract()
292
293    def filterDocActionImages(self, soup, portal_url, current):
294        tags = soup.findAll('li')
295        for x in tags:
296            if x.has_key('id'):
297                id = x['id']
298                if 'document-action' in id:                   
299                    for z in x.findAll('img'):
300                        src = z['src']
301                        surl = '%s/%s' % (portal_url, src)
302                        url = self._convertLinkToRelative(surl, current)                       
303                        z['src'] = url
304
305    def filterCSSLinks(self, soup, current):
306        #There are 2 cases, importing stylesheets, and linked stylesheets, process both
307        styles = soup.findAll('style', type="text/css")
308        for x in styles:
309            body = x.contents[0]
310            if '@import' in body:
311                url = body.split('url(')[-1]
312                url = url.split(');')[0]
313                url = self._convertLinkToRelative(url, current)
314                x.contents[0].replaceWith('<!-- @import url(%s); -->' %url)         
315        styles = soup.findAll('link', type="text/css")
316        for x in styles:
317            if x.has_key('href'):
318                url = x['href']
319                url = url.split(');')[0]
320                url = self._convertLinkToRelative(url, current)
321                x['href'] = url
322
323    def filterJSLinks(self, soup, current):
324        scripts = soup.findAll('script', type="text/javascript")
325        for x in scripts:
326            if x.has_key('src') == True:
327                url = x['src']
328                url = url.split(');')[0]
329                url = self._convertLinkToRelative(url, current)
330                x['src'] = url
331
332    def getDocumentLinks(self, soup):
333        tags = soup.findAll('a')
334        links = []
335        for tag in tags:
336            if tag.has_key('href'):
337                url = urlparse(tag['href'])
338                if not url[1] or 'localhost' in url[1]:
339                    links.append(tag)
340        return links
341
342    def filterDocumentLink(self, link, current, portal, views):
343        lnk = link
344        url = urlparse(lnk)
345        if url[2] and 'javascript' != url[0]:
346            lnk = self._convertLinkToAbsolute(lnk, current)
347            lnk = self._convertObjectLink(lnk, portal, views)
348            lnk = self._convertLinkToRelative(lnk, current)
349        return lnk
350
351    def _convertLinkToAbsolute(self, link, current):
352        result = link
353        c = urlparse(current)
354        hr = urlparse(link)
355        if not hr[0] and not hr[1]:
356            cp = c[2].split('/')
357            hp = hr[2].split('/')
358            p = []
359            for y in hp:
360                if '.' == y:
361                    pass
362                elif '..' == y:
363                    cp = cp[:-1]
364                else:
365                    p.append(y)
366            result = urlunparse((c[0], c[1], '/'.join(cp + p), hr[3], hr[4], hr[5]))
367        return result
368
369    def _convertObjectLink(self, link, portal, views):
370        # This only works for absoulute links
371        result = link
372        hr = urlparse(link)
373        p = urlparse(portal.portal_url())
374        if p[1] == hr[1]:
375            h = hr[2].split('/')
376            view = ''
377            if h[-1] in views:
378                view = h[-1]
379                h = h[:-1]
380            results = portal.portal_catalog.searchResults(query={'path':'/'.join(h),}, id=h[-1])
381            if results:
382                path = ''
383                if results[0].is_folderish:
384                    if view:
385                        path = '/'.join(h) + '/index' + '-%s.html' %view
386                    else:
387                        path = '/'.join(h) + '/index.html'
388                else:
389                    if view:
390                        path = '/'.join(h) + '-%s.html' %view
391                    elif h:
392                        path = '/'.join(h)
393                        if 'Page' == results[0].Type and '.htm' not in path:
394                            path += '.html'
395                result = urlunparse((hr[0], hr[1], path, hr[3], hr[4], hr[5]))
396            elif link == portal.portal_url():
397                # Link points to site root
398                result = urlunparse((hr[0], hr[1], '/'.join(h) + '/index.html', hr[3], hr[4], hr[5]))
399        return result
400
401    def _convertLinkToRelative(self, link, current):
402        # This will break if the last item in the url path is a folder
403        # Make sure you rewrite the link path before you call this function
404        hr = urlparse(link)
405        c = urlparse(current)
406        if c[1] == hr[1]:
407            url1 = c[2].split('/')
408            url2 = hr[2].split('/')
409            index = 0
410            while url1[index:] and url2[index:] and url1[index] == url2[index]:
411                index += 1
412            p = []
413            for y in range(len(url1[index+1:])):
414                p.append('..')
415            p = p + url2[index:]
416            return urlunparse(('', '', '/'.join(p), hr[3], hr[4], hr[5]))
417       
Note: See TracBrowser for help on using the repository browser.