source: serversideredirectplugin/0.11/tracserversideredirect/plugin.py @ 14859

Last change on this file since 14859 was 14859, checked in by Ryan J Ollos, 9 years ago

0.4: PEP-0008 changes.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id URL Author Date Rev
  • Property svn:mime-type set to text/x-python
File size: 9.0 KB
Line 
1# -*- coding: utf-8 -*-
2""" ServerSideRedirectPlugin for Trac
3
4    Copyright (c) 2008 Martin Scharrer <martin@scharrer-online.de>
5    This is Free Software under the BSD or GPL v3 or later license.
6    $Id: plugin.py 14859 2015-08-06 02:34:50Z rjollos $
7"""
8
9__url__ = ur"$URL: //trac-hacks.org/svn/serversideredirectplugin/0.11/tracserversideredirect/plugin.py $"[6:-2]
10__author__ = ur"$Author: rjollos $"[9:-2]
11__revision__ = ur"$Rev: 14859 $"[6:-2]
12__date__ = ur"$Date: 2015-08-06 02:34:50 +0000 (Thu, 06 Aug 2015) $"[7:-2]
13
14import re
15
16from genshi.builder import tag
17from trac.core import *
18from trac.mimeview.api import Context
19from trac.web.api import IRequestHandler, IRequestFilter, RequestDone
20from trac.wiki.api import IWikiMacroProvider
21from trac.wiki.model import WikiPage
22
23from tracextracturl.extracturl import extract_url
24
25MACRO = re.compile(r'.*\[\[[rR]edirect\((.*)\)\]\]')
26
27
28class ServerSideRedirectPlugin(Component):
29    """This Trac plug-in implements a server sided redirect functionality.
30The user interface is the wiki macro `Redirect` (alternatively `redirect`).
31
32== Description ==
33Website: https://trac-hacks.org/wiki/ServerSideRedirectPlugin
34
35`$Id: plugin.py 14859 2015-08-06 02:34:50Z rjollos $`
36
37This plug-in allow to place a redirect macro at the start of any wiki
38page which will cause an server side redirect when the wiki page is
39viewed.
40
41This plug-in is compatible (i.e. can be used) with the client side
42redirect macro TracRedirect but doesn't depend on it. Because the
43redirect is caused by the server (using a HTTP redirect request to the
44browser) it is much faster and less noticeable for the user. The
45back-link feature of TracRedirect can also be used for server side
46redirected pages because both generate the same URL attributes.
47
48To edit a redirecting wiki page access its URL with `?action=edit`
49appended. To view the page either use `?action=view`, which will print
50the redirect target (if TracRedirect isn't active, which will redirect
51the wiki using client side code), or `?redirect=no` which disables
52redirection of both the ServerSideRedirectPlugin and TracRedirect
53plug-in.
54
55Direct after the redirect target is added (or modified) Trac will
56automatically reload it, as it does with all wiki pages. This plug-in
57will detect this and not redirect but display the wiki page with the
58redirect target URL printed to provide feedback about the successful
59change. However, further visits will trigger the redirect.
60
61== Usage Examples ==
62The following 'macro' at the begin of the wiki page will cause a
63redirect to the ''!OtherWikiPage''.
64{{{
65[[redirect(OtherWikiPage)]]
66[[Redirect(OtherWikiPage)]]
67}}}
68Any other [TracLinks TracLink] can be used:
69{{{
70[[redirect(wiki:OtherWikiPage)]]
71[[Redirect(wiki:OtherWikiPage)]]
72[[redirect(source:/trunk/file.py)]]
73[[Redirect(source:/trunk/file.py)]]
74[[redirect(http://www.example.com/)]]
75[[Redirect(http://www.example.com/)]]
76}}}
77    """
78    implements(IRequestHandler, IRequestFilter, IWikiMacroProvider)
79
80    redirect_target = ''
81
82    def expand_macro(self, formatter, name, content):
83        """Print redirect notice after edit."""
84
85        target = extract_url(self.env, formatter.context, content)
86        if not target:
87            target = formatter.context.req.href.wiki(content)
88
89        return tag.div(
90            tag.strong('This page redirects to: '),
91            tag.a(content, href=target),
92            class_='system-message',
93            id='notice'
94        )
95
96    def get_macros(self):
97        """Provide but do not redefine the 'redirect' macro."""
98        get = self.env.config.get
99        if get('components', 'redirect.*') == 'enabled' or \
100                get('components', 'redirect.redirect.*') == 'enabled' or \
101                get('components',
102                    'redirect.redirect.tracredirect') == 'enabled':
103            return ['Redirect']
104        else:
105            return ['redirect', 'Redirect']
106
107    def get_macro_description(self, name):
108        if name == 'Redirect':
109            return self.__doc__
110        else:
111            return "See macro `Redirect`."
112
113    # IRequestHandler methods
114
115    def match_request(self, req):
116        """Only handle request when selected from `pre_process_request`."""
117        return False
118
119    def process_request(self, req):
120        """Redirect to pre-selected target."""
121        if self.redirect_target or self._check_redirect(req):
122            target = self.redirect_target
123
124            # Check for self-redirect:
125            if target and target == req.href(req.path_info):
126                message = tag.div('Please ',
127                                  tag.a("change the redirect target",
128                                        href=target + "?action=edit"),
129                                  ' to another page.',
130                                  class_="system-message")
131                data = {
132                    'title': "Page redirects to itself!",
133                    'message': message,
134                    'type': 'TracError'
135                }
136                req.send_error(data['title'], status=409,
137                               env=self.env, data=data)
138                raise RequestDone
139
140            # Check for redirect pair, i.e. A->B, B->A
141            redirected_from = req.args.get('redirectedfrom', '')
142            if target and target == req.href.wiki(redirected_from):
143                message = tag.div(
144                    'Please change the redirect target from either ',
145                    tag.a("this page",
146                          href=req.href(req.path_info, action="edit")),
147                    ' or ',
148                    tag.a("the redirecting page",
149                          href=target + "?action=edit"),
150                    '.', class_="system-message")
151                data = {
152                    'title': "Redirect target redirects back to this page!",
153                    'message': message,
154                    'type': 'TracError'
155                }
156                req.send_error(data['title'], status=409,
157                               env=self.env, data=data)
158                raise RequestDone
159
160            # Add back link information for internal links:
161            if target and target[0] == '/':
162                redirectfrom = "redirectedfrom=" + req.path_info[6:]
163                # anchor should be the last in url
164                # according to http://trac.edgewall.org/ticket/8072
165                tgt, query, anchor = self.split_link(target)
166                if not query:
167                    query = "?" + redirectfrom
168                else:
169                    query += "&" + redirectfrom
170                target = tgt + query + anchor
171            req.redirect(target)
172            raise RequestDone
173        raise TracError("Invalid redirect target!")
174
175    def _check_redirect(self, req):
176        """Checks if the request should be redirected."""
177        if req.path_info == '/' or req.path_info == '/wiki':
178            wiki = 'WikiStart'
179        elif not req.path_info.startswith('/wiki/'):
180            return False
181        else:
182            wiki = req.path_info[6:]
183
184        wp = WikiPage(self.env, wiki, req.args.get('version'))
185
186        if not wp.exists:
187            return False
188
189        # Check for redirect "macro":
190        m = MACRO.match(wp.text)
191        if not m:
192            return False
193        wikitarget = m.groups()[0]
194        ctxt = Context.from_request(req)
195        self.redirect_target = extract_url(self.env, ctxt, wikitarget)
196        if not self.redirect_target:
197            self.redirect_target = req.href.wiki(wikitarget)
198        return True
199
200    # IRequestFilter methods
201
202    def pre_process_request(self, req, handler):
203        from trac.wiki.web_ui import WikiModule
204        args = req.args
205
206        if not isinstance(handler, WikiModule):
207            return handler
208        if not req.path_info.startswith('/wiki/') and \
209                not req.path_info == '/wiki' and not req.path_info == '/':
210            self.log.debug("SSR: no redirect: Path is not a wiki path")
211            return handler
212        if req.method != 'GET':
213            self.log.debug("SSR: no redirect: No GET request")
214            return handler
215        if 'action' in args:
216            self.log.debug("SSR: no redirect: action=" + args['action'])
217            return handler
218        if 'version' in args:
219            self.log.debug("SSR: no redirect: version=...")
220            return handler
221        if 'redirect' in args and args['redirect'].lower() == 'no':
222            self.log.debug("SSR: no redirect: redirect=no")
223            return handler
224        if req.environ.get('HTTP_REFERER', '').find('action=edit') != -1:
225            self.log.debug("SSR: no redirect: HTTP_REFERER includes "
226                           "action=edit")
227            return handler
228        if self._check_redirect(req):
229            self.log.debug("SSR: redirect!")
230            return self
231        self.log.debug("SSR: no redirect: No redirect macro found.")
232        return handler
233
234    def post_process_request(self, req, template, data, content_type):
235        return template, data, content_type
236
237    # Internal methods
238
239    def split_link(self, target):
240        """Split a target along "?" and "#" in `(path, query, fragment)`."""
241        query = fragment = ''
242        idx = target.find('#')
243        if idx >= 0:
244            target, fragment = target[:idx], target[idx:]
245        idx = target.find('?')
246        if idx >= 0:
247            target, query = target[:idx], target[idx:]
248        return target, query, fragment
Note: See TracBrowser for help on using the repository browser.