1058 lines
34 KiB
Python
1058 lines
34 KiB
Python
# -*- coding: iso-8859-15 -*-
|
|
|
|
|
|
# Glasnost
|
|
# By: Odile Bénassy <obenassy@entrouvert.com>
|
|
# Romain Chantereau <rchantereau@entrouvert.com>
|
|
# Nicolas Clapiès <nclapies@easter-eggs.org>
|
|
# Pierre-Antoine Dejace <padejace@entrouvert.be>
|
|
# Thierry Dulieu <tdulieu@easter-eggs.com>
|
|
# Florent Monnier <monnier@codelutin.com>
|
|
# Cédric Musso <cmusso@easter-eggs.org>
|
|
# Frédéric Péters <fpeters@entrouvert.be>
|
|
# Benjamin Poussin <poussin@codelutin.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
# Sébastien Régnier <regnier@codelutin.com>
|
|
# Emmanuel Saracco <esaracco@easter-eggs.com>
|
|
#
|
|
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
|
|
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
|
|
# Emmanuel Saracco & Théridion
|
|
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
|
|
# Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
|
|
# Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
|
|
# Régnier, Emmanuel Saracco, Théridion & Vecam
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
|
|
from __future__ import nested_scopes
|
|
# lambdas are so much fun with nested scopes...
|
|
|
|
|
|
__doc__ = """Glasnost Web Tools"""
|
|
|
|
__version__ = '$Revision$'[11:-2]
|
|
|
|
|
|
import base64
|
|
import commands
|
|
import copy
|
|
import Cookie
|
|
from cStringIO import StringIO
|
|
import imp
|
|
import locale
|
|
import os
|
|
import posix
|
|
import re
|
|
from stat import *
|
|
import sys
|
|
import time
|
|
import traceback
|
|
import types
|
|
import urllib
|
|
import warnings
|
|
|
|
try:
|
|
from mod_python import apache
|
|
except ImportError:
|
|
apache = None
|
|
|
|
# zLOG is used by TAL that considers Zope is always there. How wrong.
|
|
sys.modules['zLOG'] = imp.new_module('zLOG')
|
|
|
|
import glasnost.common.context as context
|
|
import glasnost.common.faults as faults
|
|
import glasnost.common.tools_new as commonTools
|
|
import glasnost.common.parsers as parsers
|
|
import glasnost.common.xhtmlgenerator as X
|
|
|
|
from glasnost.proxy.tools import *
|
|
import glasnost.web.tools_new as webTools
|
|
|
|
Fault = faults.Fault
|
|
|
|
_cachedWebsByServerRole = {}
|
|
currentTemplates = {}
|
|
|
|
HTTP_CONTINUE = 100
|
|
HTTP_SWITCHING_PROTOCOLS = 101
|
|
HTTP_PROCESSING = 102
|
|
HTTP_OK = 200
|
|
HTTP_CREATED = 201
|
|
HTTP_ACCEPTED = 202
|
|
HTTP_NON_AUTHORITATIVE = 203
|
|
HTTP_NO_CONTENT = 204
|
|
HTTP_RESET_CONTENT = 205
|
|
HTTP_PARTIAL_CONTENT = 206
|
|
HTTP_MULTI_STATUS = 207
|
|
HTTP_MULTIPLE_CHOICES = 300
|
|
HTTP_MOVED_PERMANENTLY = 301
|
|
HTTP_MOVED_TEMPORARILY = 302
|
|
HTTP_SEE_OTHER = 303
|
|
HTTP_NOT_MODIFIED = 304
|
|
HTTP_USE_PROXY = 305
|
|
HTTP_TEMPORARY_REDIRECT = 307
|
|
HTTP_BAD_REQUEST = 400
|
|
HTTP_UNAUTHORIZED = 401
|
|
HTTP_PAYMENT_REQUIRED = 402
|
|
HTTP_FORBIDDEN = 403
|
|
HTTP_NOT_FOUND = 404
|
|
HTTP_METHOD_NOT_ALLOWED = 405
|
|
HTTP_NOT_ACCEPTABLE = 406
|
|
HTTP_PROXY_AUTHENTICATION_REQUIRED= 407
|
|
HTTP_REQUEST_TIME_OUT = 408
|
|
HTTP_CONFLICT = 409
|
|
HTTP_GONE = 410
|
|
HTTP_LENGTH_REQUIRED = 411
|
|
HTTP_PRECONDITION_FAILED = 412
|
|
HTTP_REQUEST_ENTITY_TOO_LARGE = 413
|
|
HTTP_REQUEST_URI_TOO_LARGE = 414
|
|
HTTP_UNSUPPORTED_MEDIA_TYPE = 415
|
|
HTTP_RANGE_NOT_SATISFIABLE = 416
|
|
HTTP_EXPECTATION_FAILED = 417
|
|
HTTP_UNPROCESSABLE_ENTITY = 422
|
|
HTTP_LOCKED = 423
|
|
HTTP_FAILED_DEPENDENCY = 424
|
|
HTTP_INTERNAL_SERVER_ERROR = 500
|
|
HTTP_NOT_IMPLEMENTED = 501
|
|
HTTP_BAD_GATEWAY = 502
|
|
HTTP_SERVICE_UNAVAILABLE = 503
|
|
HTTP_GATEWAY_TIME_OUT = 504
|
|
HTTP_VERSION_NOT_SUPPORTED = 505
|
|
HTTP_VARIANT_ALSO_VARIES = 506
|
|
HTTP_INSUFFICIENT_STORAGE = 507
|
|
HTTP_NOT_EXTENDED = 510
|
|
|
|
OK = 0
|
|
|
|
|
|
def accessForbidden(dontAskForLogin = 0):
|
|
userToken = context.getVar('userToken', default = '')
|
|
if userToken or dontAskForLogin:
|
|
layout = X.array()
|
|
# TODO: nice text telling the user he should move away...
|
|
return writePageLayout(layout, _('Access Forbidden!'), canCache = 0)
|
|
|
|
cleanedUpUri = cleanUpUnparsedUri([], 'http')
|
|
cleanedUpUri = cleanedUpUri.replace('/people/submit', '/').replace(
|
|
'/index.py', '/')
|
|
loginUrl = X.roleUrl('identities', action = 'login').add(
|
|
'nextUri', cleanedUpUri).add('access', 'forbidden')
|
|
hostNameAndPort = commonTools.makeHttpHostNameAndPort(
|
|
context.getVar('httpHostName'),
|
|
context.getVar('httpPort'))
|
|
if context.getVar('virtualHost').useHTTPS:
|
|
loginUrl = 'https://%(hostNameAndPort)s%(nameAndQuery)s' % {
|
|
'hostNameAndPort': hostNameAndPort,
|
|
'nameAndQuery': loginUrl,
|
|
}
|
|
return redirect(loginUrl)
|
|
|
|
def appendToUri(uri, item):
|
|
if '?' in uri:
|
|
separator = '&'
|
|
else:
|
|
separator = '?'
|
|
return uri + separator + item
|
|
|
|
|
|
def cleanUpUnparsedUri(trashList, protocol = None):
|
|
hostNameAndPort = commonTools.makeHttpHostNameAndPort(
|
|
context.getVar('httpHostName'),
|
|
context.getVar('httpPort'))
|
|
pathAndQuery = commonTools.makeHttpPathAndQuery(
|
|
context.getVar('httpPath'),
|
|
context.getVar('httpQuery'))
|
|
if pathAndQuery is None:
|
|
pathAndQuery = ''
|
|
if not hostNameAndPort:
|
|
uri = pathAndQuery
|
|
elif protocol == 'http':
|
|
uri = 'http://%(hostNameAndPort)s%(pathAndQuery)s' % {
|
|
'hostNameAndPort': hostNameAndPort,
|
|
'pathAndQuery': pathAndQuery,
|
|
}
|
|
elif protocol == 'https':
|
|
uri = 'https://%(hostNameAndPort)s%(pathAndQuery)s' % {
|
|
'hostNameAndPort': hostNameAndPort,
|
|
'pathAndQuery': pathAndQuery,
|
|
}
|
|
else:
|
|
uri = pathAndQuery
|
|
return cleanUpUri(uri, trashList)
|
|
|
|
|
|
def cleanUpUri(uri, trashList):
|
|
for s in trashList:
|
|
s = s + '='
|
|
i = uri.find(s)
|
|
if i >= 0:
|
|
j = uri.find('&', i + len(s))
|
|
if j < 0:
|
|
s = uri[i:]
|
|
if i > 0:
|
|
c = uri[i - 1]
|
|
if c in ['?', '&']:
|
|
uri = uri[:i - 1]
|
|
else:
|
|
uri = uri[:i] + uri[j + 1:]
|
|
return uri
|
|
|
|
|
|
def confirmDelete(deleteUrl, id = None, objectName = None):
|
|
backUrl = copy.copy(deleteUrl)
|
|
backUrl.action = None # Remove the 'delete' action.
|
|
|
|
if objectName is None:
|
|
objectName = N_('entry')
|
|
|
|
if not id:
|
|
message = _('Are you sure you want to delete the %s?') % _(objectName)
|
|
else:
|
|
message = _('Are you sure you want to delete the %s "%s" ?') % (
|
|
_(objectName),
|
|
getObjectLabelTranslated(id,
|
|
context.getVar('readLanguages')) )
|
|
|
|
layout = X.array()
|
|
layout += X.p(_class = 'alert')(message)
|
|
layout += X.buttonStandalone('delete', deleteUrl)
|
|
layout += X.buttonStandalone('cancel', backUrl)
|
|
|
|
return writePageLayout(layout, _('Confirm Deletion'), canCache = 0)
|
|
|
|
|
|
def failure(message, url):
|
|
layout = X.array()
|
|
layout += X.p(_class = 'alert')(message)
|
|
layout += X.buttonStandalone('ok', url)
|
|
return writePageLayout(layout, _('Failure'), canCache = 0)
|
|
|
|
|
|
def getDefaultDispatcherHostNameAndPort():
|
|
return context.getVar('defaultDispatcherId')[11:]
|
|
|
|
|
|
def getObject(objectId):
|
|
web = getWeb(objectId)
|
|
return web.getObject(objectId)
|
|
|
|
def getWeb(id):
|
|
context.push(dispatcherId = commonTools.extractDispatcherId(id))
|
|
try:
|
|
web = getWebForServerRole(commonTools.extractRole(id))
|
|
finally:
|
|
context.pull()
|
|
return web
|
|
|
|
|
|
def getWebForServerRole(serverRole):
|
|
# FIXME: We should clear _cachedWebsByServerRole when userToken value
|
|
# changes. Because different users can use the same process, or a user can
|
|
# login or logout.
|
|
|
|
if context.getVar('knownRoles') and \
|
|
not serverRole in context.getVar('knownRoles'):
|
|
return None
|
|
|
|
virtualHost = context.getVar('virtualHost')
|
|
if virtualHost is not None and virtualHost.customWebs and \
|
|
serverRole in virtualHost.customWebs.keys():
|
|
serverRoleWeb = virtualHost.customWebs[serverRole].lower()
|
|
else:
|
|
serverRoleWeb = serverRole
|
|
serverRoleWeb = serverRoleWeb.replace('-', '')
|
|
|
|
if _cachedWebsByServerRole.has_key(serverRoleWeb):
|
|
return _cachedWebsByServerRole[serverRoleWeb]
|
|
|
|
# Code inspired from the module knee.
|
|
import glasnost.web
|
|
webFileNames = os.listdir(glasnost.web.__path__[0])
|
|
for webFileName in webFileNames:
|
|
if webFileName.endswith('Web.py') \
|
|
and webFileName[:-6].lower() == serverRoleWeb:
|
|
webName = webFileName[:-3]
|
|
if hasattr(glasnost.web, webName):
|
|
module = getattr(glasnost.web, webName)
|
|
else:
|
|
moduleTriplet = imp.find_module(
|
|
webName, glasnost.web.__path__)
|
|
try:
|
|
module = imp.load_module(
|
|
'glasnost.web.%s' % webName, moduleTriplet[0],
|
|
moduleTriplet[1], moduleTriplet[2])
|
|
finally:
|
|
if moduleTriplet[0]:
|
|
moduleTriplet[0].close()
|
|
setattr(glasnost.web, webName, module)
|
|
web = module.__dict__[webName]()
|
|
break
|
|
else:
|
|
web = getProxyForServerRole(serverRole)
|
|
_cachedWebsByServerRole[serverRoleWeb] = web
|
|
return _cachedWebsByServerRole[serverRoleWeb]
|
|
|
|
|
|
def isButtonSelected(buttonName, keywords):
|
|
selected = 0
|
|
if keywords.has_key('button') and keywords['button'] == buttonName \
|
|
or keywords.has_key(buttonName) and keywords[buttonName] \
|
|
or keywords.has_key(buttonName + '.x') and keywords[buttonName + '.x']:
|
|
selected = 1
|
|
if keywords.has_key('button') and keywords['button'] == buttonName:
|
|
del keywords['button']
|
|
if keywords.has_key(buttonName):
|
|
del keywords[buttonName]
|
|
if keywords.has_key(buttonName + '.x'):
|
|
del keywords[buttonName + '.x']
|
|
if keywords.has_key(buttonName + '.y'):
|
|
del keywords[buttonName + '.y']
|
|
return selected
|
|
|
|
|
|
def isTypeOfMimeType(mimeType, type):
|
|
if mimeType is None or len(mimeType) < len(type):
|
|
return 0
|
|
return mimeType.startswith(type)
|
|
|
|
|
|
def pageNotFound():
|
|
from mod_python import apache
|
|
raise apache.SERVER_RETURN, HTTP_NOT_FOUND
|
|
|
|
|
|
def redirect(url):
|
|
if type(url) == types.InstanceType and hasattr(url, 'getAsUrl'):
|
|
url = url.getAsUrl()
|
|
else:
|
|
url = str(url)
|
|
req = context.getVar('req')
|
|
req.err_headers_out['Location'] = url
|
|
setHttpCookie()
|
|
req.status = HTTP_MOVED_TEMPORARILY
|
|
req.send_http_header()
|
|
return OK
|
|
|
|
|
|
def redirectPermanently(url):
|
|
if type(url) == types.InstanceType and hasattr(url, 'getAsUrl'):
|
|
url = url.getAsUrl()
|
|
else:
|
|
url = str(url)
|
|
req = context.getVar('req')
|
|
req.err_headers_out['location'] = url
|
|
req.status = HTTP_MOVED_PERMANENTLY
|
|
setHttpCookie()
|
|
req.send_http_header()
|
|
return OK
|
|
|
|
|
|
def rememberObject(id):
|
|
identitiesWeb = getWebForServerRole('identities')
|
|
identitiesWeb.rememberId(id)
|
|
|
|
|
|
def repairMimeType(fieldValue, fileName):
|
|
# FIXME: Integrate this function in uploadFiles object
|
|
# to correct mime type sent by the web browser.
|
|
def getFileExt(fileName):
|
|
return fileName[fileName.rfind('.')+1:]
|
|
|
|
mimeTypesByExt = {
|
|
'avi': 'video/x-msvideo',
|
|
'bz2': 'application/bzip2',
|
|
'doc': 'application/msword',
|
|
'mpeg': 'video/mpeg ',
|
|
'mpg': 'video/mpeg ',
|
|
'mov': 'video/quicktime',
|
|
'pdf': 'application/pdf',
|
|
'ppt': 'application/vnd.ms-powerpoint',
|
|
'ps': 'application/postscript',
|
|
'qt': 'video/quicktime',
|
|
'tar': 'application/x-tar',
|
|
'tex': 'application/tex',
|
|
'xls': 'application/vnd.ms-excel',
|
|
'xml': 'text/xml ',
|
|
'xsl': 'text/xml',
|
|
'Z': 'application/zoo',
|
|
}
|
|
|
|
if len(fieldValue) < 12:
|
|
return
|
|
if fieldValue[12:] == 'octet-stream':
|
|
ext = getFileExt()
|
|
raise str(ext) + ' - ' + fieldValue + ' - ' + fileName
|
|
if mimeTypesByExt.has_key(ext):
|
|
fieldValue = mimeTypesByExt[getFileExt()]
|
|
if fieldValue is not None:
|
|
return fielValue
|
|
return fieldValue
|
|
|
|
|
|
def replaceSpecialTags(text):
|
|
text = text.replace('[{glasnost:defaultdispatchernameandport}]',
|
|
getDefaultDispatcherHostNameAndPort())
|
|
|
|
text = text.replace(
|
|
'[{glasnost:localuri}]/',
|
|
context.getVar('httpScriptDirectoryPath'))
|
|
text = text.replace(
|
|
'[{glasnost:localuri}]',
|
|
context.getVar('httpScriptDirectoryPath'))
|
|
|
|
def aliasLabelReplacer(matchObject):
|
|
alias = matchObject.group('alias')
|
|
name = ''
|
|
if 'pagenames' in context.getVar('knownRoles'):
|
|
id = getProxyForServerRole('pagenames').getIdByName(alias)
|
|
if id:
|
|
try:
|
|
name = getObjectLabelTranslated(
|
|
id, context.getVar('readLanguages'))
|
|
except faults.MissingItem:
|
|
name = _('(missing item)')
|
|
name = name.replace('&', '&')
|
|
name = name.replace('<', '<')
|
|
name = name.replace('"', '"')
|
|
return name
|
|
|
|
text = re.sub(
|
|
r'\[{glasnost:aliaslabel:(?P<host>.*?):(?P<alias>.*?)}\]',
|
|
aliasLabelReplacer, text)
|
|
|
|
def aliasReplacer(matchObject):
|
|
alias = matchObject.group('alias')
|
|
action = matchObject.group('action')
|
|
return X.aliasUrl(alias, action).getAsUrl()
|
|
|
|
text = re.sub(
|
|
r'\[{glasnost:alias:(?P<host>.*?):(?P<alias>.*?)(:(?P<action>.*?))?}\]',
|
|
aliasReplacer, text)
|
|
|
|
def imageReplacer(matchObject):
|
|
host = matchObject.group('host')
|
|
serverRole = matchObject.group('serverRole')
|
|
localId = matchObject.group('localId')
|
|
objectId = 'glasnost://%s/%s/%s' % (host, serverRole, localId)
|
|
web = getWebForServerRole(serverRole)
|
|
if web is None:
|
|
return ''
|
|
fileName = ''
|
|
try:
|
|
object = web.getPartialObject(objectId, ['dataFileName'])
|
|
if hasattr(object, 'dataFileName') and object.dataFileName:
|
|
fileName = '/%s' % object.dataFileName
|
|
except faults.Fault:
|
|
pass
|
|
return X.idUrl(objectId, 'image%s' % fileName).getAsUrl()
|
|
|
|
text = re.sub(
|
|
'\[\{glasnost\:image\:(?P<host>.*?)\:(?P<serverRole>.*?)\:(?P<localId>.*?)\}\]',
|
|
imageReplacer, text)
|
|
|
|
def labelReplacer(matchObject):
|
|
serverRole = matchObject.group('serverRole')
|
|
localId = matchObject.group('localId')
|
|
host = matchObject.group('host')
|
|
try:
|
|
name = getObjectLabelTranslated(
|
|
'glasnost://%s/%s/%s' % (
|
|
host, serverRole, localId),
|
|
context.getVar('readLanguages'))
|
|
except faults.MissingItem:
|
|
name = _('(missing item)')
|
|
name = name.replace('&', '&')
|
|
name = name.replace('<', '<')
|
|
name = name.replace('"', '"')
|
|
return name
|
|
|
|
text = re.sub(
|
|
'\[\{glasnost\:label\:(?P<host>.*?)\:(?P<serverRole>.*?)\:(?P<localId>.*?)\}\]',
|
|
labelReplacer, text)
|
|
|
|
def partialIdReplacer(matchObject):
|
|
host = matchObject.group('host')
|
|
serverRole = matchObject.group('serverRole')
|
|
localId = matchObject.group('localId')
|
|
action = matchObject.group('action')
|
|
computedId = 'glasnost://%s/%s/%s' % (host, serverRole, localId)
|
|
return X.idUrl(computedId, action).getAsUrl()
|
|
|
|
text = re.sub(
|
|
r'\[{glasnost:partialid:(?P<host>.*?):(?P<serverRole>.*?):'\
|
|
'(?P<localId>.*?)(:(?P<action>.*))?}\]',
|
|
partialIdReplacer, text)
|
|
|
|
def thumbnailReplacer(matchObject):
|
|
host = matchObject.group('host')
|
|
serverRole = matchObject.group('serverRole')
|
|
localId = matchObject.group('localId')
|
|
width = matchObject.group('width')
|
|
height = matchObject.group('height')
|
|
web = getWebForServerRole(serverRole)
|
|
if web is None:
|
|
return ''
|
|
return X.roleUrl(web.serverRole, 'thumbnail').add(
|
|
'id',
|
|
'glasnost://%s/%s/%s' % (host, serverRole, localId)).add(
|
|
'width', width).add('height', height).getAsUrl()
|
|
text = re.sub(
|
|
'\[\{glasnost\:thumbnail\:(?P<host>.*?)\:(?P<serverRole>.*?)\:(?P<localId>.*?)'
|
|
'\:width=(?P<width>.*?)\:height=(?P<height>.*?)\}\]',
|
|
thumbnailReplacer, text)
|
|
|
|
canUseCookie = context.getVar('canUseCookie', default = 0)
|
|
|
|
tag = urllib.quote('[{glasnost:sessiontoken}]')
|
|
sessionToken = context.getVar('sessionToken')
|
|
if not canUseCookie and sessionToken is not None:
|
|
text = text.replace(tag, sessionToken)
|
|
else:
|
|
text = text.replace('?sessionToken=' + tag + '&', '?')
|
|
text = text.replace('?sessionToken=' + tag, '')
|
|
text = text.replace('&sessionToken=' + tag, '')
|
|
|
|
return text
|
|
|
|
|
|
def setHttpCookie():
|
|
if not context.getVar('canUseCookie', default = 0):
|
|
return
|
|
req = context.getVar('req')
|
|
oldCookie = context.getVar('cookie')
|
|
cookie = Cookie.SimpleCookie()
|
|
cookieContent = {}
|
|
sessionToken = context.getVar('sessionToken')
|
|
if sessionToken is not None:
|
|
cookieContent['sessionToken'] = sessionToken
|
|
for k, v in cookieContent.items():
|
|
cookie[k] = v
|
|
cookie[k]['path'] = '/'
|
|
if not cookieContent:
|
|
if oldCookie:
|
|
for k, v in oldCookie.items():
|
|
cookie[k] = v
|
|
cookie[k]['max-age'] = 0
|
|
else:
|
|
cookie = None
|
|
|
|
if cookie != oldCookie:
|
|
for morsel in cookie.values():
|
|
req.headers_out.add('Set-Cookie', morsel.output(header = '')[1:])
|
|
context.setVar('cookie', cookie)
|
|
|
|
|
|
def success(message, url):
|
|
layout = X.array()
|
|
layout += X.p(_class = 'alert')(message)
|
|
layout += X.buttonStandalone('ok', url)
|
|
return writePageLayout(layout, _('Success'), canCache = 0)
|
|
|
|
|
|
import WebAPI
|
|
|
|
def getTemplateVars():
|
|
userId = context.getVar('userId', default = '')
|
|
if userId:
|
|
userSet = [userId]
|
|
else:
|
|
userSet = None
|
|
|
|
if context.getVar('virtualHost').useHTTPS:
|
|
cleanedUpUri = cleanUpUnparsedUri([], 'https')
|
|
else:
|
|
cleanedUpUri = cleanUpUnparsedUri([])
|
|
cleanedUpUri = cleanedUpUri.replace('/people/submit', '/').replace(
|
|
'/index.py', '/')
|
|
loginUrl = X.roleUrl('identities', action = 'login').add(
|
|
'nextUri', cleanedUpUri)
|
|
hostNameAndPort = commonTools.makeHttpHostNameAndPort(
|
|
context.getVar('httpHostName'),
|
|
context.getVar('httpPort'))
|
|
if context.getVar('virtualHost').useHTTPS:
|
|
loginUrl = 'https://%(hostNameAndPort)s%(nameAndQuery)s' % {
|
|
'hostNameAndPort': hostNameAndPort,
|
|
'nameAndQuery': loginUrl,
|
|
}
|
|
|
|
logoutUrl = X.roleUrl('identities', action = 'logout')
|
|
|
|
httpScriptDirectoryPath = context.getVar('httpScriptDirectoryPath')
|
|
|
|
aboutButton = X.buttonStandalone('about', X.roleUrl('about')).getAsXml()
|
|
aboutUrl = X.roleUrl('about').getAsUrl()
|
|
|
|
logoutButton = X.buttonStandalone('logout', logoutUrl).getAsXml()
|
|
|
|
if userId:
|
|
prefsButton = X.buttonStandalone('prefs', X.idUrl(userId)).getAsXml()
|
|
prefsUrl = X.idUrl(userId).getAsUrl()
|
|
|
|
loginButton = X.buttonStandalone('login', loginUrl).getAsXml()
|
|
|
|
# FIXME: should take the favourite identities method
|
|
newAccountUrl = X.roleUrl('identities', action = 'newAccount')
|
|
newAccountButton = X.buttonStandalone(
|
|
'new-account', newAccountUrl).getAsXml()
|
|
newAccountUrl = newAccountUrl.getAsUrl()
|
|
|
|
shortNewsLabel = _('Short News')
|
|
|
|
if userId:
|
|
try:
|
|
userName = getObject(userId).getLabel()
|
|
except (faults.MissingItem, faults.UserAccessDenied):
|
|
userName = _('Unknown')
|
|
else:
|
|
userName = ''
|
|
|
|
return locals()
|
|
|
|
def writePageException(exception):
|
|
req = context.getVar('req')
|
|
exception = X.convertStringToXml(exception)
|
|
req.write("""<html><head><title>Error</title></head>
|
|
<body>
|
|
<h1>Error processing this page</h1>
|
|
<pre>
|
|
%s
|
|
</pre>
|
|
</body>
|
|
</html>""" % exception)
|
|
|
|
|
|
def writePageLayout(layout, title, canCache = 1):
|
|
dict = getTemplateVars()
|
|
dict['X'] = X
|
|
dict['title'] = title
|
|
|
|
req = context.getVar('req')
|
|
req.content_type = 'text/html'
|
|
|
|
setHttpCookie()
|
|
req.send_http_header()
|
|
|
|
if req.method == 'HEAD':
|
|
return OK
|
|
|
|
if req.caching:
|
|
req.openCachePage()
|
|
|
|
# we need to chdir for included files
|
|
#os.chdir(context.getVar('webDirectoryPath'))
|
|
os.chdir( context.getVar('templatesDirectoryPath') )
|
|
|
|
dict['contextualHeaders'] = '\n'.join(context.getVar('htmlHeaders'))
|
|
dict['body'] = layout.getAsXml()
|
|
|
|
try:
|
|
import GlasnostNewTal
|
|
except ImportError:
|
|
pass
|
|
|
|
fileName = context.getVar('templateFileName')
|
|
if not currentTemplates.has_key(fileName):
|
|
# caching this in currentTemplates improves rendering time by about
|
|
# 1.2 seconds on my (fpeters) computer. Not bad
|
|
### (benchmark on 2002-11-16)
|
|
templateDirectoryName = context.getVar('templateDirectoryName')
|
|
path = context.getVar('templatesDirectoryPath')
|
|
fallbackPath = context.getVar('fallbackTemplatesDirectoryPath')
|
|
|
|
try:
|
|
fd = open(os.path.join(path, templateDirectoryName, fileName))
|
|
except IOError:
|
|
fd = open(os.path.join(fallbackPath, 'default', fileName))
|
|
# should raise 404 if it fails
|
|
|
|
from TAL.HTMLTALParser import HTMLTALParser
|
|
from glasnost.web.GlasnostTALGenerator import GlasnostTALGenerator
|
|
t = HTMLTALParser(gen = GlasnostTALGenerator(xml=0))
|
|
t.parseString(fd.read())
|
|
### with python-simpletal, it would be
|
|
### t = GlasnostNewTal.compileHTMLTemplate(fd)
|
|
|
|
currentTemplates[fileName] = t
|
|
|
|
t = currentTemplates[fileName]
|
|
|
|
program, macros = t.getCode()
|
|
# this takes roughly 0.01 seconds, not worth caching
|
|
|
|
from GlasnostTALInterpreter import GlasnostTALInterpreter
|
|
from TAL.HTMLParser import HTMLParseError
|
|
from GlasnostTALEngine import GlasnostTALEngine, TALError
|
|
engine = GlasnostTALEngine(macros)
|
|
context.setVar('talEngine', engine)
|
|
engine.locals = WebAPI.getAPIDict()
|
|
engine.locals['currentObject'] = context.getVar('currentObject')
|
|
|
|
for k, v in dict.items():
|
|
engine.locals[k] = v
|
|
|
|
interp = GlasnostTALInterpreter(program, macros, engine, stream=req, wrap=80)
|
|
try:
|
|
interp()
|
|
except TALError:
|
|
if not context.getVar('debug'):
|
|
raise
|
|
info = sys.exc_info()
|
|
exception = traceback.format_exception_only(
|
|
info[0], info[1])[0].strip()
|
|
writePageException(exception)
|
|
|
|
### with python-simpletal:
|
|
### ctx = GlasnostNewTal.Context()
|
|
### for k, v in dict.items() + WebAPI.getAPIDict().items():
|
|
### ctx.addGlobal(k, v)
|
|
### interpreter = GlasnostNewTal.TemplateInterpreter()
|
|
### interpreter.initialise(ctx, req)
|
|
### t.expand(ctx, req, interpreter = interpreter)
|
|
|
|
if req.caching:
|
|
req.closeCachePage()
|
|
return OK
|
|
|
|
|
|
def getAppropriateTalFile(serverRole, idValue, action):
|
|
if idValue:
|
|
id = commonTools.splitId(idValue)
|
|
serverRole = id[1]
|
|
fileNames = (
|
|
'%s-%s.%s.%s.tal' % (serverRole, id[0], id[2], action),
|
|
# articles-our.website.url.211.print.tal
|
|
'%s-%s.%s.tal' % (serverRole, id[0], action),
|
|
# articles-our.website.url.print.tal
|
|
'%s-%s.%s.tal' % (serverRole, id[2], action),
|
|
# articles-211.print.tal
|
|
'%s.%s.tal' % (serverRole, action),
|
|
# articles.print.tal
|
|
'%s-%s.%s.%s.xtal' % (serverRole, id[0], id[2], action),
|
|
# articles-our.website.url.211.print.xtal
|
|
'%s-%s.%s.xtal' % (serverRole, id[0], action),
|
|
# articles-our.website.url.print.xtal
|
|
'%s-%s.%s.xtal' % (serverRole, id[2], action),
|
|
# articles-211.print.xtal
|
|
'%s.%s.xtal' % (serverRole, action),
|
|
# articles.print.xtal
|
|
)
|
|
else:
|
|
id = ('',)
|
|
fileNames = (
|
|
'%s-%s.all.%s.tal' % (serverRole, id[0], action),
|
|
# articles-our.website.url.211.print.tal
|
|
'%s-all.%s.tal' % (serverRole, action),
|
|
# articles-all.print.tal
|
|
'%s-%s.all.%s.xtal' % (serverRole, id[0], action),
|
|
# articles-our.website.url.211.print.xtal
|
|
'%s-all.%s.xtal' % (serverRole, action),
|
|
# articles-all.print.xtal
|
|
)
|
|
|
|
directories = context.getVar('webDirectoryPaths') + \
|
|
[context.getVar('templatesDirectoryPath')]
|
|
for directory in directories:
|
|
if not directory:
|
|
continue
|
|
for fileName in fileNames:
|
|
fileName = directory + '/' + fileName
|
|
if os.path.exists(fileName):
|
|
return fileName
|
|
|
|
return None
|
|
|
|
|
|
def getAppropriatePyFile(serverRole, idValue, action):
|
|
if idValue:
|
|
id = commonTools.splitId(idValue)
|
|
serverRole = id[1]
|
|
fileNames = (
|
|
'%s-%s.%s.%s.py' % (serverRole, id[0], id[2], action),
|
|
# articles-our.website.url.211.print.py
|
|
'%s-%s.%s.py' % (serverRole, id[0], action),
|
|
# articles-our.website.url.print.py
|
|
'%s-%s.%s.py' % (serverRole, id[2], action),
|
|
# articles-211.print.py
|
|
'%s.%s.py' % (serverRole, action),
|
|
# articles.print.py
|
|
)
|
|
else:
|
|
id = ('',)
|
|
fileNames = (
|
|
'%s-%s.all.%s.py' % (serverRole, id[0], action),
|
|
# articles-our.website.url.211.print.py
|
|
'%s-all.%s.py' % (serverRole, action),
|
|
# articles-all.print.py
|
|
)
|
|
|
|
directoryPaths = context.getVar('scriptsDirectoryPaths')
|
|
for directoryPath in directoryPaths:
|
|
for fileName in fileNames:
|
|
filePath = os.path.join(directoryPath, fileName)
|
|
if os.path.exists(filePath):
|
|
return filePath
|
|
return None
|
|
|
|
|
|
def getAppropriateFunction(serverRole, idValue, action):
|
|
if not action:
|
|
action = 'default'
|
|
|
|
web = context.getVar('web')
|
|
|
|
talFileName = getAppropriateTalFile(serverRole, idValue, action)
|
|
|
|
if talFileName:
|
|
context.setVar('TALFile', open(talFileName).read())
|
|
context.setVar(
|
|
'TALFileIsXTAL', talFileName.endswith('.xtal'))
|
|
if idValue:
|
|
return web.viewTal
|
|
else:
|
|
return web.viewAllTal
|
|
|
|
pyFileName = getAppropriatePyFile(serverRole, idValue, action)
|
|
|
|
if pyFileName:
|
|
context.setVar('PyFile', pyFileName)
|
|
if idValue:
|
|
return web.viewPy
|
|
else:
|
|
return web.viewAllPy
|
|
|
|
method = context.getVar('httpMethod')
|
|
if action == 'default':
|
|
if idValue:
|
|
if method == 'DELETE':
|
|
action = 'delete'
|
|
else:
|
|
action = 'use'
|
|
else:
|
|
action = 'viewAll'
|
|
|
|
if context.getVar('xmlPost', default = None) and action == 'use':
|
|
action = 'postXml'
|
|
|
|
if hasattr(web, action):
|
|
function = getattr(web, action)
|
|
if not hasattr(function, 'isPublicForWeb'):
|
|
return None
|
|
if method in ('GET', 'HEAD', 'POST') and function.isPublicForWeb == 1:
|
|
return function
|
|
if type(function.isPublicForWeb) in (list, tuple) and \
|
|
method in function.isPublicForWeb:
|
|
return function
|
|
elif method not in ('GET', 'HEAD', 'POST'):
|
|
raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED
|
|
return None
|
|
|
|
|
|
def getGotoObjectsLabelsAndLinks():
|
|
labelsAndLinks = []
|
|
|
|
# Standard servers.
|
|
roles = context.getVar('knownRoles')
|
|
for role in roles:
|
|
try:
|
|
web = getWebForServerRole(role)
|
|
except: # TODO: tighter check.
|
|
continue
|
|
if not web:
|
|
continue
|
|
if not web.canViewAll():
|
|
continue
|
|
labelsAndLinks.append((_(web.objectsNameCapitalized), X.roleUrl(role)))
|
|
|
|
# Cards Models.
|
|
cardsWeb = getWebForServerRole('cards')
|
|
if cardsWeb is not None:
|
|
cardIds = cardsWeb.getAdmin().prototypeIds
|
|
if cardIds:
|
|
cardLabels = cardsWeb.getObjectLabelsTranslated(
|
|
cardIds, context.getVar('readLanguages'))
|
|
for cardId, cardLabel in cardLabels.items():
|
|
labelsAndLinks.append((cardLabel,
|
|
X.idUrl(cardId, 'implementations')))
|
|
|
|
labelsAndLinks.sort(lambda x, y: locale.strcoll(x[0], y[0]))
|
|
return labelsAndLinks
|
|
|
|
|
|
def getNewObjectLabelsAndLinks():
|
|
labelsAndLinks = []
|
|
|
|
# Standard servers.
|
|
roles = context.getVar('knownRoles')
|
|
for role in roles:
|
|
try:
|
|
web = getWebForServerRole(role)
|
|
except: # TODO: tighter check.
|
|
continue
|
|
if not web:
|
|
continue
|
|
try:
|
|
if not web.canAddObject():
|
|
continue
|
|
except: # TODO: tighter check.
|
|
continue
|
|
labelsAndLinks.append((_(web.objectNameCapitalized),
|
|
X.roleUrl(role, 'edit')))
|
|
|
|
# Cards Models.
|
|
cardsWeb = getWebForServerRole('cards')
|
|
if cardsWeb is not None:
|
|
cardIds = cardsWeb.getAdmin().prototypeIds
|
|
if cardIds:
|
|
cardLabels = cardsWeb.getObjectLabelsTranslated(
|
|
cardIds, context.getVar('readLanguages'))
|
|
for cardId, cardLabel in cardLabels.items():
|
|
labelsAndLinks.append((cardLabel, X.idUrl(cardId, 'new')))
|
|
|
|
labelsAndLinks.sort(lambda x, y: locale.strcoll(x[0], y[0]))
|
|
return labelsAndLinks
|
|
|
|
|
|
def processTALFile(fileName, file = None, xtal = None, **keywords):
|
|
req = context.getVar('req')
|
|
if xtal:
|
|
req.content_type = 'text/xml'
|
|
else:
|
|
req.content_type = 'text/html'
|
|
setHttpCookie()
|
|
req.send_http_header()
|
|
if req.method == 'HEAD':
|
|
return OK
|
|
|
|
if req.caching:
|
|
req.openCachePage()
|
|
|
|
for dir in context.getVar('webDirectoryPaths'):
|
|
os.chdir(dir)
|
|
if os.path.exists(fileName):
|
|
file = open(fileName).read()
|
|
break
|
|
|
|
from TAL.TALParser import TALParser
|
|
from TAL.HTMLTALParser import HTMLTALParser
|
|
from glasnost.web.GlasnostTALGenerator import GlasnostTALGenerator
|
|
if xtal:
|
|
t = TALParser(gen = GlasnostTALGenerator(xml=1))
|
|
else:
|
|
t = HTMLTALParser(gen = GlasnostTALGenerator(xml=0))
|
|
t.parseString(file)
|
|
program, macros = t.getCode()
|
|
|
|
from GlasnostTALInterpreter import GlasnostTALInterpreter
|
|
from GlasnostTALEngine import GlasnostTALEngine, TALError
|
|
engine = GlasnostTALEngine(macros)
|
|
context.setVar('talEngine', engine)
|
|
engine.locals = WebAPI.getAPIDict()
|
|
engine.locals['currentObject'] = None
|
|
for k, v in keywords.items():
|
|
engine.locals[k] = v
|
|
dict = getTemplateVars()
|
|
for k, v in dict.items():
|
|
engine.locals[k] = v
|
|
|
|
interp = GlasnostTALInterpreter(program, macros, engine, stream=req, wrap=80)
|
|
try:
|
|
interp()
|
|
except TALError:
|
|
if not context.getVar('debug'):
|
|
raise
|
|
info = sys.exc_info()
|
|
exception = traceback.format_exception_only(
|
|
info[0], info[1])[0].strip()
|
|
writePageException(exception)
|
|
|
|
if req.caching:
|
|
req.closeCachePage()
|
|
return OK
|
|
|
|
|
|
def processXTALFile(fileName, file = None, **keywords):
|
|
return processTALFile(fileName, file = file, xtal = 1, **keywords)
|
|
|
|
|
|
def spellcheck(text, language = 'en'):
|
|
if not context.getVar('enableSpellChecking'):
|
|
return text
|
|
|
|
# generate a temp file name
|
|
warnings.filterwarnings('ignore','tmpnam')
|
|
tmpnam = posix.tmpnam()
|
|
|
|
# write text out to a temporary file
|
|
tmp = open(tmpnam,'w')
|
|
tmp.write(text)
|
|
tmp.close()
|
|
|
|
# call out to aspell program
|
|
status, aspell = commands.getstatusoutput(
|
|
'aspell --lang=%s --home-dir=/tmp/ -H -a < %s' % (
|
|
language, tmpnam))
|
|
os.remove(tmpnam)
|
|
if status != 0:
|
|
return text
|
|
|
|
# split out all questionable words and non-html text
|
|
aspell = re.compile('^& (\w+) \d+ \d+: (.*)', re.M).findall(aspell)
|
|
text = re.compile('(<.*?>|&.*?;)', re.S).split(text)
|
|
spanSpellcheck = re.compile('<span class="spellcheck".*?</span>')
|
|
|
|
i = 0
|
|
while i < len(text):
|
|
if text[i].endswith('/>') and i%2-1:
|
|
text.insert(i+1, '')
|
|
text.insert(i, '')
|
|
i += 2
|
|
i += 1
|
|
|
|
# substitute questionable words in text with a titled span
|
|
alreadySeen = []
|
|
for found, suggest in aspell:
|
|
if found in alreadySeen:
|
|
continue
|
|
alreadySeen.append(found)
|
|
match = re.compile(r'\b%s\b' % found)
|
|
replace = '<span class="spellcheck" title="%s">%s</span>' % (
|
|
suggest, found)
|
|
for i in range(0, len(text), 2):
|
|
t, n = match.subn(replace, text[i])
|
|
if not n:
|
|
continue
|
|
parts = spanSpellcheck.split(t)
|
|
parts.reverse()
|
|
del text[i]
|
|
for p in parts[:-1]:
|
|
text.insert(i, p)
|
|
text.insert(i, replace)
|
|
text.insert(i, parts[-1])
|
|
|
|
# return the concatenated result
|
|
return ''.join(text)
|
|
|
|
|
|
def xmlEncode(string):
|
|
for c in string:
|
|
if ord(c) > 128:
|
|
string = string.replace(c, '&#%s;' % ord(c))
|
|
return string
|
|
|