source: serversideredirectplugin/1.0/tracserversideredirect/plugin.py

Last change on this file was 15264, checked in by Ryan J Ollos, 8 years ago

Remove unnecessary svn:mime-type on py files

svn:mime-type was set to "plain" for many files.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id URL Author Date Rev
File size: 8.3 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2008-2010 Martin Scharrer <martin@scharrer-online.de>
4# Copyright (C) 2015 Ryan J Ollos <ryan.j.ollos@gmail.com>
5# All rights reserved.
6#
7# This software is licensed as described in the file COPYING, which
8# you should have received as part of this distribution.
9
10import re
11
12from genshi.builder import tag
13from trac.core import *
14from trac.util.text import stripws
15from trac.util.translation import _, tag_
16from trac.web.api import IRequestFilter, IRequestHandler
17from trac.web.chrome import web_context
18from trac.wiki.api import IWikiMacroProvider
19from trac.wiki.formatter import concat_path_query_fragment
20from trac.wiki.model import WikiPage
21from trac.wiki.web_ui import WikiModule
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
35This plug-in allow to place a redirect macro at the start of any wiki
36page which will cause an server side redirect when the wiki page is
37viewed.
38
39This plug-in is compatible (i.e. can be used) with the client side
40redirect macro TracRedirect but doesn't depend on it. Because the
41redirect is caused by the server (using a HTTP redirect request to the
42browser) it is much faster and less noticeable for the user. The
43back-link feature of TracRedirect can also be used for server side
44redirected pages because both generate the same URL attributes.
45
46To edit a redirecting wiki page access its URL with `?action=edit`
47appended. To view the page either use `?action=view`, which will print
48the redirect target (if TracRedirect isn't active, which will redirect
49the wiki using client side code), or `?redirect=no` which disables
50redirection of both the ServerSideRedirectPlugin and TracRedirect
51plug-in.
52
53Direct after the redirect target is added (or modified) Trac will
54automatically reload it, as it does with all wiki pages. This plug-in
55will detect this and not redirect but display the wiki page with the
56redirect target URL printed to provide feedback about the successful
57change. However, further visits will trigger the redirect.
58
59== Usage Examples ==
60The following 'macro' at the begin of the wiki page will cause a
61redirect to the ''!OtherWikiPage''.
62{{{
63[[redirect(OtherWikiPage)]]
64[[Redirect(OtherWikiPage)]]
65}}}
66Any other [TracLinks TracLink] can be used:
67{{{
68[[redirect(wiki:OtherWikiPage)]]
69[[Redirect(wiki:OtherWikiPage)]]
70[[redirect(source:/trunk/file.py)]]
71[[Redirect(source:/trunk/file.py)]]
72[[redirect(http://www.example.com/)]]
73[[Redirect(http://www.example.com/)]]
74}}}
75    """
76    implements(IRequestHandler, IRequestFilter, IWikiMacroProvider)
77
78    def expand_macro(self, formatter, name, content):
79        """Print redirect notice after edit."""
80
81        content = stripws(content)
82        target = extract_url(self.env, formatter.context, content)
83        if not target:
84            target = formatter.req.href.wiki(content)
85
86        return tag.div(
87            tag.strong('This page redirects to: '),
88            tag.a(content, href=target),
89            class_='system-message',
90            id='notice'
91        )
92
93    def get_macros(self):
94        """Provide but do not redefine the 'redirect' macro."""
95        get = self.env.config.get
96        if get('components', 'redirect.*') == 'enabled' or \
97                get('components', 'redirect.redirect.*') == 'enabled' or \
98                get('components',
99                    'redirect.redirect.tracredirect') == 'enabled':
100            return ['Redirect']
101        else:
102            return ['redirect', 'Redirect']
103
104    def get_macro_description(self, name):
105        if name == 'Redirect':
106            return self.__doc__
107        else:
108            return "See macro `Redirect`."
109
110    # IRequestHandler methods
111
112    def match_request(self, req):
113        """Only handle request when selected from `pre_process_request`."""
114        return False
115
116    def process_request(self, req):
117        """Redirect to pre-selected target."""
118        target = req.args.get('redirect-target')
119        if target:
120            # Check for self-redirect:
121            if target and target == req.href(req.path_info):
122                change = tag.a(_("change"), href=target + "?action=edit")
123                message = tag_("Please %(change)s the redirect target to "
124                               "another page.", change=change)
125                data = {
126                    'title': "Page redirects to itself!",
127                    'message': message,
128                    'type': 'TracError'
129                }
130                req.send_error(data['title'], status=409,
131                               env=self.env, data=data)
132
133            # Check for redirect pair, i.e. A->B, B->A
134            redirected_from = req.args.get('redirectedfrom', '')
135            if target and target == req.href.wiki(redirected_from):
136                this = tag.a(_("this"),
137                             href=req.href(req.path_info, action="edit"))
138                redirecting = tag.a(_("redirecting"),
139                                    href=target + "?action=edit")
140                message = tag_("Please change the redirect target on "
141                               "%(this)s page or the %(redirecting)s page.",
142                               this=this, redirecting=redirecting)
143                data = {
144                    'title': "Redirect target redirects back to this page!",
145                    'message': message,
146                    'type': 'TracError'
147                }
148                req.send_error(data['title'], status=409,
149                               env=self.env, data=data)
150
151            # Add back link information for internal links:
152            if target and target[0] == '/':
153                redirectfrom = "redirectedfrom=" + req.path_info[6:]
154                target = concat_path_query_fragment(target, redirectfrom)
155            req.redirect(target)
156        raise TracError("Invalid redirect target!")
157
158    def _get_redirect(self, req):
159        """Checks if the request should be redirected."""
160        if req.path_info in ('/', '/wiki'):
161            wiki = 'WikiStart'
162        elif not req.path_info.startswith('/wiki/'):
163            return None
164        else:
165            wiki = req.path_info[6:]
166
167        wp = WikiPage(self.env, wiki, req.args.get('version'))
168
169        if not wp.exists:
170            return None
171
172        # Check for redirect "macro":
173        m = MACRO.match(wp.text)
174        if not m:
175            return None
176        wikitarget = stripws(m.group(1))
177        ctxt = web_context(req)
178        redirect_target = extract_url(self.env, ctxt, wikitarget)
179        if not redirect_target:
180            redirect_target = req.href.wiki(wikitarget)
181        return redirect_target
182
183    # IRequestFilter methods
184
185    def pre_process_request(self, req, handler):
186        if not isinstance(handler, WikiModule):
187            return handler
188
189        args = req.args
190        if not req.path_info.startswith('/wiki/') and \
191                not req.path_info == '/wiki' and not req.path_info == '/':
192            self.log.debug("SSR: no redirect: Path is not a wiki path")
193            return handler
194        if req.method != 'GET':
195            self.log.debug("SSR: no redirect: No GET request")
196            return handler
197        if 'action' in args:
198            self.log.debug("SSR: no redirect: action=" + args['action'])
199            return handler
200        if 'version' in args:
201            self.log.debug("SSR: no redirect: version=...")
202            return handler
203        if 'redirect' in args and args['redirect'].lower() == 'no':
204            self.log.debug("SSR: no redirect: redirect=no")
205            return handler
206        if req.environ.get('HTTP_REFERER', '').find('action=edit') != -1:
207            self.log.debug("SSR: no redirect: HTTP_REFERER includes "
208                           "action=edit")
209            return handler
210        target = self._get_redirect(req)
211        if target:
212            self.log.debug("SSR: redirect!")
213            req.args['redirect-target'] = target
214            return self
215        self.log.debug("SSR: no redirect: No redirect macro found.")
216        return handler
217
218    def post_process_request(self, req, template, data, content_type):
219        return template, data, content_type
Note: See TracBrowser for help on using the repository browser.