Code

Ticket #2440: php.py

File php.py, 3.6 KB (added by django@…, 8 years ago)

custom php template loader. Add to TEMPLATE_LOADERS

Line 
1from django.conf import settings
2from django.template import TemplateDoesNotExist
3from django.template.loader import find_template_source
4from django.core.cache import cache
5from subprocess import Popen, PIPE
6import sys, os, threading
7
8def _getdefault(name, default=None):
9    try:
10        default = getattr(settings, name)
11    except: pass
12    return default
13
14## these should be set in the settings.py file
15PHP_BIN = _getdefault('PHP_BIN', '/usr/bin/php')
16PHP_ARGS = _getdefault('PHP_ARGS', ['-q',])
17PHP_IN_SHELL = _getdefault('PHP_IN_SHELL', True)
18PHP_THREAD_IO = _getdefault('PHP_THREAD_IO', sys.platform == 'win32')
19PHP_BUFFER_SIZE = _getdefault('PHP_BUFFER_SIZE',
20                              sys.platform == 'win32' and -1 or 4096)
21PHP_CACHE_SECONDS = _getdefault('PHP_CACHE_SECONDS', None)
22
23class PipeThread(threading.Thread):
24    """This is needed for Win32 where the buffer can lock if you
25    pass the windows shell buffer size and do not read the output pending
26    from the running app"""
27    def __init__(self, fin, mode='read'):
28        self.fin = fin
29        self.sout = ""
30        self.mode = mode
31        threading.Thread.__init__(self)
32    def run(self):
33        if self.mode == 'read':
34            self.sout = self.fin.read()
35        else:
36            self.fin.write(self.sout)
37    def read(self):
38        return self.sout
39    def write(self, data):
40        self.sout = data
41    def close(self):
42        ## WARNING! This can cause a crash on windows depending on the FD
43        ##          If its an stdout or stderr FD then this will crash
44        if self.mode != 'read':
45            self.fin.close()
46       
47def runphp(source):
48    php = Popen([PHP_BIN,] + PHP_ARGS, shell=PHP_IN_SHELL,
49          bufsize=1<<12, universal_newlines=True, ## restriction of templates
50          stdin=PIPE, stdout=PIPE, #stderr=PIPE,
51          close_fds= sys.platform != 'win32' and True or False)
52
53    ## RED_FLAG: check for returncode after reading stdout and if its an error
54    ##           (and we are TEMPLATE_DEBUG), then raise a special error with
55    ##           the contents of stderr... wee...
56    if not PHP_THREAD_IO:
57        php.stdin.write(source)
58        php.stdin.close()  # or we block on stdout.read()
59        page = php.stdout.read()
60        php.stdout.close() # just in case we are windows
61    else:
62        phpin = PipeThread(php.stdin, 'write')
63        phpin.write(source)
64        phpin.start()
65        phpout = PipeThread(php.stdout)
66        phpout.start()
67        phpin.close()
68        retcode = php.wait()
69        phpout.join(1)
70        page = phpout.read()
71    return page
72
73def load_template_source(template_name, template_dirs=None):
74    """all php template requests must start with 'php:' to keep namespaces
75    distinct and as an added security measure
76    (and it would be recursive otherwise)"""
77    if len(template_name) <= 4 or template_name[:4] != 'php:':
78        raise TemplateDoesNotExist, (
79            "Not a PHP template request: %s" % template_name)
80    if PHP_CACHE_SECONDS:
81        page_and_origin_name = cache.get(template_name)
82        if page_and_origin_name: return page_and_origin_name
83    try:
84        source, origin = find_template_source(template_name[4:], template_dirs)
85    except TemplateDoesNotExist:
86        raise TemplateDoesNotExist, template_name
87   
88    page = runphp(source)
89   
90    page_and_origin_name =  (page, origin and origin.name or template_name)
91    if PHP_CACHE_SECONDS:
92        cache.set(template_name, page_and_origin_name, PHP_CACHE_SECONDS)
93    return page_and_origin_name
94load_template_source.is_usable=True