4
FCKeditor - The text editor for Internet - http://www.fckeditor.net
5
Copyright (C) 2003-2007 Frederico Caldeira Knabben
9
Licensed under the terms of any of the following licenses at your
12
- GNU General Public License Version 2 or later (the "GPL")
13
http://www.gnu.org/licenses/gpl.html
15
- GNU Lesser General Public License Version 2.1 or later (the "LGPL")
16
http://www.gnu.org/licenses/lgpl.html
18
- Mozilla Public License Version 1.1 or later (the "MPL")
19
http://www.mozilla.org/MPL/MPL-1.1.html
29
Zope Version: (Zope 2.8.1-final, python 2.3.5, linux2)
30
Python Version: 2.3.5 (#4, Mar 10 2005, 01:40:25)
31
[GCC 3.3.3 20040412 (Red Hat Linux 3.3.3-7)]
32
System Platform: linux2
36
Author Notes (04 December 2005):
37
This module has gone through quite a few phases of change. Obviously,
38
I am only supporting that part of the code that I use. Initially
39
I had the upload directory as a part of zope (ie. uploading files
40
directly into Zope), before realising that there were too many
41
complex intricacies within Zope to deal with. Zope is one ugly piece
42
of code. So I decided to complement Zope by an Apache server (which
43
I had running anyway, and doing nothing). So I mapped all uploads
44
from an arbitrary server directory to an arbitrary web directory.
45
All the FCKeditor uploading occurred this way, and I didn't have to
46
stuff around with fiddling with Zope objects and the like (which are
47
terribly complex and something you don't want to do - trust me).
49
Maybe a Zope expert can touch up the Zope components. In the end,
50
I had FCKeditor loaded in Zope (probably a bad idea as well), and
51
I replaced the connector.py with an alias to a server module.
52
Right now, all Zope components will simple remain as is because
53
I've had enough of Zope.
55
See notes right at the end of this file for how I aliased out of Zope.
57
Anyway, most of you probably wont use Zope, so things are pretty
58
simple in that regard.
60
Typically, SERVER_DIR is the root of WEB_DIR (not necessarily).
61
Most definitely, SERVER_USERFILES_DIR points to WEB_USERFILES_DIR.
72
Converts the special characters '<', '>', and '&'.
74
RFC 1866 specifies that these characters be represented
75
in HTML as < > and & respectively. In Python
76
1.5 we use the new string.replace() function for speed.
78
def escape(text, replace=string.replace):
79
text = replace(text, '&', '&') # must be done 1st
80
text = replace(text, '<', '<')
81
text = replace(text, '>', '>')
82
text = replace(text, '"', '"')
88
Creates a new instance of an FCKeditorConnector, and runs it
90
def getFCKeditorConnector(context=None):
91
# Called from Zope. Passes the context through
92
connector = FCKeditorConnector(context=context)
93
return connector.run()
99
A wrapper around the request object
100
Can handle normal CGI request, or a Zope request
103
class FCKeditorRequest(object):
104
def __init__(self, context=None):
105
if (context is not None):
108
r = cgi.FieldStorage()
109
self.context = context
113
if (self.context is not None):
117
def has_key(self, key):
118
return self.request.has_key(key)
120
def get(self, key, default=None):
123
value = self.request.get(key, default)
125
if key in self.request.keys():
126
value = self.request[key].value
136
class FCKeditorConnector(object):
137
# Configuration for FCKEditor
138
# can point to another server here, if linked correctly
139
#WEB_HOST = "http://127.0.0.1/"
141
SERVER_DIR = "/var/www/html/"
143
WEB_USERFILES_FOLDER = WEB_HOST + "upload/"
144
SERVER_USERFILES_FOLDER = SERVER_DIR + "upload/"
146
# Allow access (Zope)
147
__allow_access_to_unprotected_subobjects__ = 1
149
parentFolderRe = re.compile("[\/][^\/]+[\/]?$")
154
def __init__(self, context=None):
155
# The given root path will NOT be shown to the user
156
# Only the userFilesPath will be shown
158
# Instance Attributes
159
self.context = context
160
self.request = FCKeditorRequest(context=context)
161
self.rootPath = self.SERVER_DIR
162
self.userFilesFolder = self.SERVER_USERFILES_FOLDER
163
self.webUserFilesFolder = self.WEB_USERFILES_FOLDER
165
# Enables / Disables the connector
166
self.enabled = False # Set to True to enable this connector
168
# These are instance variables
169
self.zopeRootContext = None
170
self.zopeUploadContext = None
172
# Copied from php module =)
173
self.allowedExtensions = {
179
self.deniedExtensions = {
180
"File": [ "html","htm","php","php2","php3","php4","php5","phtml","pwml","inc","asp","aspx","ascx","jsp","cfm","cfc","pl","bat","exe","com","dll","vbs","js","reg","cgi","htaccess","asis","sh","shtml","shtm","phtm" ],
181
"Image": [ "html","htm","php","php2","php3","php4","php5","phtml","pwml","inc","asp","aspx","ascx","jsp","cfm","cfc","pl","bat","exe","com","dll","vbs","js","reg","cgi","htaccess","asis","sh","shtml","shtm","phtm" ],
182
"Flash": [ "html","htm","php","php2","php3","php4","php5","phtml","pwml","inc","asp","aspx","ascx","jsp","cfm","cfc","pl","bat","exe","com","dll","vbs","js","reg","cgi","htaccess","asis","sh","shtml","shtm","phtm" ],
183
"Media": [ "html","htm","php","php2","php3","php4","php5","phtml","pwml","inc","asp","aspx","ascx","jsp","cfm","cfc","pl","bat","exe","com","dll","vbs","js","reg","cgi","htaccess","asis","sh","shtml","shtm","phtm" ]
187
Zope specific functions
190
# The context object is the zope object
191
if (self.context is not None):
195
def getZopeRootContext(self):
196
if self.zopeRootContext is None:
197
self.zopeRootContext = self.context.getPhysicalRoot()
198
return self.zopeRootContext
200
def getZopeUploadContext(self):
201
if self.zopeUploadContext is None:
202
folderNames = self.userFilesFolder.split("/")
203
c = self.getZopeRootContext()
204
for folderName in folderNames:
205
if (folderName <> ""):
207
self.zopeUploadContext = c
208
return self.zopeUploadContext
211
Generic manipulation functions
213
def getUserFilesFolder(self):
214
return self.userFilesFolder
216
def getWebUserFilesFolder(self):
217
return self.webUserFilesFolder
219
def getAllowedExtensions(self, resourceType):
220
return self.allowedExtensions[resourceType]
222
def getDeniedExtensions(self, resourceType):
223
return self.deniedExtensions[resourceType]
225
def removeFromStart(self, string, char):
226
return string.lstrip(char)
228
def removeFromEnd(self, string, char):
229
return string.rstrip(char)
231
def convertToXmlAttribute(self, value):
236
def convertToPath(self, path):
237
if (path[-1] <> "/"):
242
def getUrlFromPath(self, resourceType, path):
243
if (resourceType is None) or (resourceType == ''):
245
self.removeFromEnd(self.getUserFilesFolder(), '/'),
250
self.getUserFilesFolder(),
256
def getWebUrlFromPath(self, resourceType, path):
257
if (resourceType is None) or (resourceType == ''):
259
self.removeFromEnd(self.getWebUserFilesFolder(), '/'),
264
self.getWebUserFilesFolder(),
270
def removeExtension(self, fileName):
271
index = fileName.rindex(".")
272
newFileName = fileName[0:index]
275
def getExtension(self, fileName):
276
index = fileName.rindex(".") + 1
277
fileExtension = fileName[index:]
280
def getParentFolder(self, folderPath):
281
parentFolderPath = self.parentFolderRe.sub('', folderPath)
282
return parentFolderPath
287
Purpose: works out the folder map on the server
289
def serverMapFolder(self, resourceType, folderPath):
290
# Get the resource type directory
291
resourceTypeFolder = "%s%s/" % (
292
self.getUserFilesFolder(),
295
# Ensure that the directory exists
296
self.createServerFolder(resourceTypeFolder)
298
# Return the resource type directory combined with the
302
self.removeFromStart(folderPath, '/')
308
Purpose: physically creates a folder on the server
310
def createServerFolder(self, folderPath):
311
# Check if the parent exists
312
parentFolderPath = self.getParentFolder(folderPath)
313
if not(os.path.exists(parentFolderPath)):
314
errorMsg = self.createServerFolder(parentFolderPath)
315
if errorMsg is not None:
317
# Check if this exists
318
if not(os.path.exists(folderPath)):
320
os.chmod(folderPath, 0755)
323
if os.path.isdir(folderPath):
326
raise "createServerFolder: Non-folder of same name already exists"
333
Purpose: returns the root path on the server
335
def getRootPath(self):
341
Purpose: to prepare the headers for the xml to return
343
def setXmlHeaders(self):
344
#now = self.context.BS_get_now()
346
self.setHeader("Content-Type", "text/xml")
347
#self.setHeader("Expires", yesterday)
348
#self.setHeader("Last-Modified", now)
349
#self.setHeader("Cache-Control", "no-store, no-cache, must-revalidate")
353
def setHeader(self, key, value):
355
self.context.REQUEST.RESPONSE.setHeader(key, value)
357
print "%s: %s" % (key, value)
360
def printHeaders(self):
361
# For non-Zope requests, we need to print an empty line
362
# to denote the end of headers
363
if (not(self.isZope())):
369
Purpose: returns the xml header
371
def createXmlHeader(self, command, resourceType, currentFolder):
374
# Create the XML document header
375
s += """<?xml version="1.0" encoding="utf-8" ?>"""
376
# Create the main connector node
377
s += """<Connector command="%s" resourceType="%s">""" % (
381
# Add the current folder node
382
s += """<CurrentFolder path="%s" url="%s" />""" % (
383
self.convertToXmlAttribute(currentFolder),
384
self.convertToXmlAttribute(
385
self.getWebUrlFromPath(
396
Purpose: returns the xml footer
398
def createXmlFooter(self):
399
s = """</Connector>"""
405
Purpose: in the event of an error, return an xml based error
407
def sendError(self, number, text):
410
# Create the XML document header
411
s += """<?xml version="1.0" encoding="utf-8" ?>"""
412
s += """<Connector>"""
413
s += """<Error number="%s" text="%s" />""" % (number, text)
414
s += """</Connector>"""
420
Purpose: command to recieve a list of folders
422
def getFolders(self, resourceType, currentFolder):
424
return self.getZopeFolders(resourceType, currentFolder)
426
return self.getNonZopeFolders(resourceType, currentFolder)
428
def getZopeFolders(self, resourceType, currentFolder):
429
# Open the folders node
432
zopeFolder = self.findZopeFolder(resourceType, currentFolder)
433
for (name, o) in zopeFolder.objectItems(["Folder"]):
434
s += """<Folder name="%s" />""" % (
435
self.convertToXmlAttribute(name)
437
# Close the folders node
438
s += """</Folders>"""
441
def getNonZopeFolders(self, resourceType, currentFolder):
442
# Map the virtual path to our local server
443
serverPath = self.serverMapFolder(resourceType, currentFolder)
444
# Open the folders node
447
for someObject in os.listdir(serverPath):
448
someObjectPath = os.path.join(serverPath, someObject)
449
if os.path.isdir(someObjectPath):
450
s += """<Folder name="%s" />""" % (
451
self.convertToXmlAttribute(someObject)
453
# Close the folders node
454
s += """</Folders>"""
460
Purpose: command to recieve a list of folders and files
462
def getFoldersAndFiles(self, resourceType, currentFolder):
464
return self.getZopeFoldersAndFiles(resourceType, currentFolder)
466
return self.getNonZopeFoldersAndFiles(resourceType, currentFolder)
468
def getNonZopeFoldersAndFiles(self, resourceType, currentFolder):
469
# Map the virtual path to our local server
470
serverPath = self.serverMapFolder(resourceType, currentFolder)
471
# Open the folders / files node
472
folders = """<Folders>"""
473
files = """<Files>"""
474
for someObject in os.listdir(serverPath):
475
someObjectPath = os.path.join(serverPath, someObject)
476
if os.path.isdir(someObjectPath):
477
folders += """<Folder name="%s" />""" % (
478
self.convertToXmlAttribute(someObject)
480
elif os.path.isfile(someObjectPath):
481
size = os.path.getsize(someObjectPath)
482
files += """<File name="%s" size="%s" />""" % (
483
self.convertToXmlAttribute(someObject),
484
os.path.getsize(someObjectPath)
486
# Close the folders / files node
487
folders += """</Folders>"""
488
files += """</Files>"""
493
def getZopeFoldersAndFiles(self, resourceType, currentFolder):
494
folders = self.getZopeFolders(resourceType, currentFolder)
495
files = self.getZopeFiles(resourceType, currentFolder)
499
def getZopeFiles(self, resourceType, currentFolder):
500
# Open the files node
503
zopeFolder = self.findZopeFolder(resourceType, currentFolder)
504
for (name, o) in zopeFolder.objectItems(["File","Image"]):
505
s += """<File name="%s" size="%s" />""" % (
506
self.convertToXmlAttribute(name),
507
((o.get_size() / 1024) + 1)
509
# Close the files node
513
def findZopeFolder(self, resourceType, folderName):
514
# returns the context of the resource / folder
515
zopeFolder = self.getZopeUploadContext()
516
folderName = self.removeFromStart(folderName, "/")
517
folderName = self.removeFromEnd(folderName, "/")
518
if (resourceType <> ""):
520
zopeFolder = zopeFolder[resourceType]
522
zopeFolder.manage_addProduct["OFSP"].manage_addFolder(id=resourceType, title=resourceType)
523
zopeFolder = zopeFolder[resourceType]
524
if (folderName <> ""):
525
folderNames = folderName.split("/")
526
for folderName in folderNames:
527
zopeFolder = zopeFolder[folderName]
533
Purpose: command to create a new folder
535
def createFolder(self, resourceType, currentFolder):
537
return self.createZopeFolder(resourceType, currentFolder)
539
return self.createNonZopeFolder(resourceType, currentFolder)
541
def createZopeFolder(self, resourceType, currentFolder):
542
# Find out where we are
543
zopeFolder = self.findZopeFolder(resourceType, currentFolder)
546
if self.request.has_key("NewFolderName"):
547
newFolder = self.request.get("NewFolderName", None)
548
zopeFolder.manage_addProduct["OFSP"].manage_addFolder(id=newFolder, title=newFolder)
551
error = """<Error number="%s" originalDescription="%s" />""" % (
553
self.convertToXmlAttribute(errorMsg)
557
def createNonZopeFolder(self, resourceType, currentFolder):
560
if self.request.has_key("NewFolderName"):
561
newFolder = self.request.get("NewFolderName", None)
562
currentFolderPath = self.serverMapFolder(
567
newFolderPath = currentFolderPath + newFolder
568
errorMsg = self.createServerFolder(newFolderPath)
569
if (errorMsg is not None):
575
error = """<Error number="%s" originalDescription="%s" />""" % (
577
self.convertToXmlAttribute(errorMsg)
584
Purpose: helper function to extrapolate the filename
586
def getFileName(self, filename):
587
for splitChar in ["/", "\\"]:
588
array = filename.split(splitChar)
596
Purpose: command to upload files to server
598
def fileUpload(self, resourceType, currentFolder):
600
return self.zopeFileUpload(resourceType, currentFolder)
602
return self.nonZopeFileUpload(resourceType, currentFolder)
604
def zopeFileUpload(self, resourceType, currentFolder, count=None):
605
zopeFolder = self.findZopeFolder(resourceType, currentFolder)
606
file = self.request.get("NewFile", None)
607
fileName = self.getFileName(file.filename)
608
fileNameOnly = self.removeExtension(fileName)
609
fileExtension = self.getExtension(fileName).lower()
611
nid = "%s.%s.%s" % (fileNameOnly, count, fileExtension)
616
zopeFolder.manage_addProduct['OFSP'].manage_addFile(
626
self.zopeFileUpload(resourceType, currentFolder, count)
629
def nonZopeFileUpload(self, resourceType, currentFolder):
632
if self.request.has_key("NewFile"):
633
# newFile has all the contents we need
634
newFile = self.request.get("NewFile", "")
636
newFileName = newFile.filename
637
newFileNameOnly = self.removeExtension(newFileName)
638
newFileExtension = self.getExtension(newFileName).lower()
639
allowedExtensions = self.getAllowedExtensions(resourceType)
640
deniedExtensions = self.getDeniedExtensions(resourceType)
641
if (allowedExtensions is not None):
644
if (newFileExtension in allowedExtensions):
646
elif (deniedExtensions is not None):
649
if (newFileExtension in deniedExtensions):
652
# No extension limitations
658
self.zopeFileUpload(resourceType, currentFolder)
660
# Upload to operating system
661
# Map the virtual path to the local server path
662
currentFolderPath = self.serverMapFolder(
668
newFilePath = "%s%s" % (
672
if os.path.exists(newFilePath):
674
newFilePath = "%s%s(%s).%s" % (
683
fileHandle = open(newFilePath,'w')
686
#line = newFile.file.readline()
687
line = newFile.readline()
689
fileHandle.write("%s" % line)
691
os.chmod(newFilePath, 0777)
694
newFileName = "Extension not allowed"
697
newFileName = "No File"
701
<script type="text/javascript">
702
window.parent.frames["frmUpload"].OnUploadCompleted(%s,"%s");
706
newFileName.replace('"',"'")
713
# Check if this is disabled
714
if not(self.enabled):
715
return self.sendError(1, "This connector is disabled. Please check the connector configurations and try again")
716
# Make sure we have valid inputs
718
(self.request.has_key("Command")) and
719
(self.request.has_key("Type")) and
720
(self.request.has_key("CurrentFolder"))
724
command = self.request.get("Command", None)
726
resourceType = self.request.get("Type", None)
727
# folder syntax must start and end with "/"
728
currentFolder = self.request.get("CurrentFolder", None)
729
if (currentFolder[-1] <> "/"):
731
if (currentFolder[0] <> "/"):
732
currentFolder = "/" + currentFolder
733
# Check for invalid paths
734
if (".." in currentFolder):
735
return self.sendError(102, "")
736
# File upload doesn't have to return XML, so intercept
738
if (command == "FileUpload"):
739
return self.fileUpload(resourceType, currentFolder)
741
s += self.createXmlHeader(command, resourceType, currentFolder)
742
# Execute the command
743
if (command == "GetFolders"):
745
elif (command == "GetFoldersAndFiles"):
746
f = self.getFoldersAndFiles
747
elif (command == "CreateFolder"):
748
f = self.createFolder
752
s += f(resourceType, currentFolder)
753
s += self.createXmlFooter()
758
# Running from command line
759
if __name__ == '__main__':
760
# To test the output, uncomment the standard headers
761
#print "Content-Type: text/html"
763
print getFCKeditorConnector()
766
Running from zope, you will need to modify this connector.
767
If you have uploaded the FCKeditor into Zope (like me), you need to
768
move this connector out of Zope, and replace the "connector" with an
769
alias as below. The key to it is to pass the Zope context in, as
770
we then have a like to the Zope context.
772
## Script (Python) "connector.py"
773
##bind container=container
774
##bind context=context
777
##bind subpath=traverse_subpath
778
##parameters=*args, **kws
781
import Products.connector as connector
782
return connector.getFCKeditorConnector(context=context).run()