view data/plugin/action/arnica_slides.py @ 478:2a915e70966f

arnica: wikiutil.escape fixes
author Reimar Bauer <rb.proj AT googlemail DOT com>
date Thu, 21 Jan 2010 21:13:42 +0100
parents 6b6cc67478ff
children 167888214d95
line wrap: on
line source
# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - arnica_slides action

    This action is used to rotate, move to bak or to slide through the images from arnica.

    Based on gallery2image, (c) 2005-2008 by MoinMoin:ReimarBauer.

    @copyright: 2008-2010 by MoinMoin:ReimarBauer
    @license: GNU GPL, see COPYING for details.
"""
import os, re

from MoinMoin import log
logging = log.getLogger(__name__)

from MoinMoin import config, wikiutil
from MoinMoin.Page import Page
from MoinMoin.action import ActionBase, AttachFile, cache
from MoinMoin.packages import packLine, unpackLine
from MoinMoin.parser.text_moin_wiki import Parser as WikiParser

try:
    import Image
except ImportError:
    Image = None

action_name = __name__.split('.')[-1]

def ensureversion(version):
    """ Ensures that the given version string is lower equal than the used MoinMoin release string. 
     
    @param version: required version of MoinMoin (e.g. "1.9.0")
    """
    from MoinMoin.version import release
    version_int = [int(x) for x in version.split(".")]
    # use a regex here to get only the numbers of the release string (e.g. ignore betaX)
    release = re.compile('\d+').findall(release)[0:3]
    release = [int(x) for x in release]
    return version_int <= release
            
def image_rotate(file_name, angle, img_type):
    """ rotates the image
    @param file_name: the name of the attachment
    @param angle: rotation angle
    @param img_type: type of image (PNG or JPEG)
    """
    # only image needs to be rotated, webnail and thumbnail
    # will be recreated by the arnica parser
    image_object = Image.open(file_name)
    # we save the old file (don't use .bak here. bak is used for image delete.)
    tmp_file = file_name + '.tmp'
    os.rename(file_name, tmp_file)
    try:
        image_object.rotate(angle).save(file_name, img_type)
    except IOError:
        # in case of trouble rename to the old file
        os.rename(tmp_file, file_name)
    else:
        # on success remove the backup file
        os.unlink(tmp_file)

def html_js(request, counter):
    """ javascript for the slides
    @param request: request object
    @param counter: index of first slide image
    """
    html = """
<script type="text/javascript">
<!-- Original:  Ricocheting (ricocheting@hotmail.com) -->
<!-- Web Site:  http://www.ricocheting.com -->

<!-- This script and many more are available free online at -->
<!-- The JavaScript Source!! http://javascript.internet.com -->

<!-- Modifications by MoinMoin:ReimarBauer -->
<!-- 2005-10-29 -->
<!-- Many thanks to Ricocheting, it is much easier as my own one. I like it -->
<!-- Some code added and replaced for the MoinMoin:arnica parser-->


<!-- Begin

var rotate_delay = 5; // delay in milliseconds (5000 = 5 secs)
var current = %(counter)s;
var theImages = new Array();
var preloaded_images = new Array();
var AttachFile_action = '?action=AttachFile&do=get&target=';
var AutoPlay = 'Stop';

function load_image(index) {
    var list = document.slideform.webnail_list.value;
    var value = list.split("|");
    if (preloaded_images[index] == false) {
        theImages[index].src = value[index];
        preloaded_images[index] = true;
    }
}

function arnica_preload() {
   var list = document.slideform.webnail_list.value;
   var value = list.split("|");
   var n = value.length;

   for (i = 0; i <  n-1; i++){
       theImages[i] = new Image();
       preloaded_images[i] = false;
   }
}

function arnica_getserver() {
     var value = document.URL;
     var text = value.split("/");
     return text[0]+'//'+text[2];
}

function arnica_add_comments() {
  var alias_text = document.slideform.alias.value;
  var exif_date_text = document.slideform.exif_date.value;
  var original_images = document.slideform.original_images.value;
  var index = document.slideform.slide.selectedIndex;
  var alias = alias_text.split("|");
  var exif = exif_date_text.split("|");
  images = original_images.split("|");

  document.getElementById("arnica_alias_text").innerHTML = '<a href=' + document.URL + AttachFile_action + images[index] + '>' + alias[index] +'</a>';
  document.getElementById("arnica_exif_date_text").innerHTML = exif[index];
}

function arnica_next_slide() {
   if (document.slideform.slide[current+1]) {
      load_image(current+1);
      document.images.show.src = theImages[current+1].src;
      document.slideform.slide.selectedIndex = ++current;
      arnica_add_comments();
   }
   else arnica_first_slide();
   document.getElementById("arnica_first_slide").innerHTML = '<a class="first_slide" onclick="arnica_first_slide();" name="fs"  title="first slide" >';
   document.getElementById("arnica_last_slide").innerHTML = '<a class="last_slide" onclick="arnica_last_slide();" name="fs"  title="last slide" >';
}

function arnica_previous_slide() {
   if (current-1 >= 0) {
      load_image(current-1);
      document.images.show.src = theImages[current-1].src;
      document.slideform.slide.selectedIndex = --current;
      arnica_add_comments();
   }
   else arnica_last_slide();
   document.getElementById("arnica_first_slide").innerHTML = '<a class="first_slide" onclick="arnica_first_slide();" name="fs"  title="first slide" >';
   document.getElementById("arnica_last_slide").innerHTML = '<a class="last_slide" onclick="arnica_last_slide();" name="fs"  title="last slide" >';
}

function arnica_first_slide() {
   current = 0;
   load_image(current);
   document.images.show.src = theImages[0].src;
   document.slideform.slide.selectedIndex = 0;
   arnica_add_comments();
   document.getElementById("arnica_first_slide").innerHTML = '<a class="first_slide_disabled" title="first slide" >';
   document.getElementById("arnica_last_slide").innerHTML = '<a class="last_slide" onclick="arnica_last_slide();" name="fs"  title="last slide" >';
}

function arnica_last_slide() {
   current = document.slideform.slide.length-1;
   load_image(current);
   document.images.show.src = theImages[current].src;
   document.slideform.slide.selectedIndex = current;
   arnica_add_comments();
   document.getElementById("arnica_first_slide").innerHTML = '<a class="first_slide" onclick="arnica_first_slide();" name="fs"  title="first slide" >';
   document.getElementById("arnica_last_slide").innerHTML = '<a class="last_slide_disabled" title="last slide" >';
}

function arnica_ap(text) {
   if (AutoPlay == "Stop") {
      document.getElementById("arnica_autoplay_slide").innerHTML =  '<a class="pause_slide" onclick="arnica_ap(text);" title="AutoPlay">';
      AutoPlay = "Start";
   } else {
      document.getElementById("arnica_autoplay_slide").innerHTML =  '<a class="stop_slide" onclick="arnica_ap(text);" title="AutoPlay">';
      AutoPlay = "Stop";
   }

   arnica_rotate();
}

function arnica_change() {
   current = document.slideform.slide.selectedIndex;
   load_image(current);
   document.images.show.src = theImages[current].src;
   arnica_add_comments();
   document.getElementById("arnica_first_slide").innerHTML = '<a class="first_slide" onclick="arnica_first_slide();" name="fs"  title="first slide" >';
   document.getElementById("arnica_last_slide").innerHTML = '<a class="last_slide" onclick="arnica_last_slide();" name="fs"  title="last slide" >';
}

function arnica_rotate() {
   if (AutoPlay == "Start") {
      current = (current == document.slideform.slide.length-1) ? 0 : current+1;
      load_image(current);
      document.images.show.src = theImages[current].src;
      document.slideform.slide.selectedIndex = current;
      arnica_add_comments();
      document.getElementById("arnica_first_slide").innerHTML = '<a class="first_slide" onclick="arnica_first_slide();" name="fs"  title="first slide" >';
      document.getElementById("arnica_last_slide").innerHTML = '<a class="last_slide" onclick="arnica_last_slide();" name="fs"  title="last slide" >';
      rotate_delay = document.slideform.duration.value * 1000.;
      window.setTimeout("arnica_rotate()", rotate_delay);
   }
}
//  End -->
</script>
""" % {
        'htdocs': request.cfg.url_prefix_static,
        'counter': counter,
    }
    return html

def html_slideform(request, pagename, alias, exif_date, images, original_images, idx):
    """ html code for the slideform
    @param request: request object
    @param pagename: pagename where the attachments are located
    @param alias: text alias for filename
    @param exif_date: date information
    @param images: list of webnail image IDs (either cache keys or attachment names)
    @param original_images: list of original image attachment names (thumbnails and webnails are derived from it)
    @param idx: index position of the image
    """
    image_url = images[idx]
    this_webnail_list = images

    this_webnail_list = "".join(["%s|" % url for url in this_webnail_list])

    link_to_image = AttachFile.getAttachUrl(pagename, original_images[idx], request)
    option_webnail = option_list(image_url, images, original_images, request)
    inner_table_style = ' style="border-style:none; margin:10px;"'
    navigation = ""
    if len(images) > 1:
        navigation = """\
<div class="navigation-animation">
<form name=slideform method="POST" action="">
<div>
<input type="hidden" name="flag" value="webnail">
<input type="hidden" name="webnail_list" value="%(this_webnail_list)s">
<input type="hidden" name="original_images" value="%(original_images)s">
<input type="hidden" name="webnail_name" value="%(this_webnail_name)s">
<input type="hidden" name="alias" value="%(this_alias_list)s">
<input type="hidden" name="exif_date" value="%(this_exif_date_list)s">
<label>Slide: <select name="slide" onChange="arnica_change();" >
%(option_webnails)s</select></label>
<label>Duration (sec):<input type="text" name="duration" value="3.0" size="3"></label>
</div>
</form>
</div>
<div class="navigation-button">
<span id="arnica_first_slide"><a class="first_slide" onclick="arnica_first_slide();" name="fs"  title="first slide" ></a></span>
<a class="previous_slide" onclick="arnica_previous_slide();"  title="previous slide"></a>
<span id="arnica_autoplay_slide"><a class="stop_slide" onclick="arnica_ap(this.value);" title="AutoPlay"></a></span>
<a class="next_slide" onClick="arnica_next_slide();"  title="next slide"></a>
<span id="arnica_last_slide"><a class="last_slide" onClick="arnica_last_slide();" title="last slide" ></a></span>
</div>
""" % {
        "this_webnail_list": wikiutil.escape(this_webnail_list),
        "this_webnail_name": wikiutil.escape(packLine(images)),
        "original_images": wikiutil.escape(packLine(original_images), quote=1),
        "this_alias_list": wikiutil.escape(packLine([wikiutil.renderText(request, WikiParser, alias_name.replace('<<BR>>', ' ')) for alias_name in alias]), quote=1),
        "this_exif_date_list": wikiutil.escape(packLine(exif_date), quote=1),
        "option_webnails": option_webnail,
    }

    html = """
<div class="arnica-slides">
%(navigation)s
<div class="image">
<img src="%(server)s%(this_image)s" name="show" alt="%(alt_text)s">
</div>
<div class="image-description">
<span id="arnica_alias_text"><a href=%(link_url)s>%(this_alias_text)s</a></span>,
<span id="arnica_exif_date_text">%(this_exif_date_text)s</span>
</div>
</div>
""" % {
        "server": request.getQualifiedURL(),
        "link_url": link_to_image,
        "alt_text": wikiutil.escape(alias[idx].replace('<<BR>>', ' '), quote=1),
        "this_alias_text": wikiutil.renderText(request, WikiParser, wikiutil.escape(alias[idx]).replace('<<BR>>', ' ').replace('&quot;', '"')),
        "this_exif_date_text": wikiutil.escape(exif_date[idx], quote=1),
        "this_image": wikiutil.escape(image_url),
        "pagename": pagename,
        "navigation": navigation,
    }
    return html

def option_list(this_image, filenames, original_images, request):
    """ generates the pulldown option list
    @param this_image: selected image on top
    @param filenames: list of filenames
    @param original_images: list of original image attachment names (thumbnails and webnails are derived from it)
    @param request: request object
    """
    # TODO: original image needs to be sent to the action and used instead of the alias
    options = []
    i = 0
    for filename in filenames:
        url = cache.url(request, filename)
        prefix = 'Webnail:'
        options.append('<option%(selected)s value="%(name)s">%(prefix)s %(alias)s' % {
            "selected": wikiutil.escape((url.split('_')[-1] == this_image.split('_')[-1]) and ' selected' or ''),
            "name": wikiutil.escape(this_image),
            "prefix": prefix,
            "alias": wikiutil.escape(original_images[i]),
        })
        i += 1
    return ''.join(options)

class arnica_slides(ActionBase):
    """ arnica_slides page action
    Note: the action name is the class name
    """
    def __init__(self, request, pagename):
        ActionBase.__init__(self, pagename, request)
        self.request = request
        self.pagename = pagename
        self.page = Page(request, pagename)
        self.method = 'POST'
        self.enctype = 'multipart/form-data'

    def is_excluded(self):
        """ Return True if action is excluded """
        return self.actionname in self.cfg.actions_excluded

    def is_allowed(self):
        """ Return True if read access is allowed """
        may = self.request.user.may
        return may.read(self.pagename)

    def do_action(self):
        """ Do the action and either return error msg or None, if there was no error. """
        return None

    def do_action_finish(self, success):
        """ Do the action and either return error msg or None, if there was no error. """
        return None

    def slide_show(self, pagename):
        """ run the slide show
        @param pagename: name of the page the slideshow is based on
        """
        _ = self.request.getText
        request = self.request
        if not request.user.may.read(pagename):
            return _("You are not allowed to view this page!"), "error"
        if not ensureversion('1.9.0'):
            target = request.form.get('target', [''])[0]
            images = request.form.get('images', [''])[0]
            original_images = request.form.get('original_images', [''])[0]
            all_description = request.form.get('alias', [''])[0]
            all_exif_date = request.form.get('exif_date', [''])[0]
        else:
            target = request.values.get('target', '')
            images = request.values.get('images', '')
            original_images = request.values.get('original_images', '')
            all_description = request.values.get('alias', '')
            all_exif_date = request.values.get('exif_date', '')


        if not (target and images and all_description and all_exif_date):
            return _("Missing required parameters!"), "error"

        images = unpackLine(images)
        images = images[1:]

        original_images = unpackLine(original_images)
        original_images = original_images[1:]

        all_description = unpackLine(all_description)
        all_description = all_description[1:]

        all_exif_date = unpackLine(all_exif_date)
        all_exif_date = all_exif_date[1:]

        # XXX Check that all lists have same length
        try:
            idx = images.index(target)
        except ValueError:
            return _("Attachment '%(filename)s' does not exist!") % {'filename': target}, "error"

        web = {}

        web['src'] = cache.url(request, target)
        web['title'] = target

        if not ensureversion('1.9.0'):
            mimetype = "text/html"
            request.emit_http_headers(["Content-Type: %s; charset=%s" % (mimetype, config.charset)])

        request.theme.send_title(pagename,
                                 pagename=pagename,
                                 body_onload="arnica_preload();",
                                 html_head=html_js(request, idx))

        request.write(request.formatter.startContent("content"))
        html = html_slideform(request, pagename, all_description, all_exif_date, images, original_images, idx)
        request.write(request.formatter.rawHTML(html))
        request.write(request.formatter.endContent())
        request.write(request.theme.send_footer(pagename))
        request.theme.send_closing_html()
        return None, None

    def render(self):
        """ executes the commands of the form data """
        _ = self.request.getText

        request = self.request
        if not ensureversion('1.9.0'):
            pagename = request.form.get('pagename', [self.pagename])[0]
            command = request.form.get('do', ['none'])[0]
            target = request.form.get('target', [''])[0]
        else:
            pagename = request.values.get('pagename', self.pagename)
            command = request.values.get('do', 'none')
            target = request.values.get('target', '')

        # View Slides
        if command == 'slide_show':
            return self.slide_show(pagename)

        # only users which are allowed to delete should use this tool
        elif request.user.may.delete(pagename):
            # only POST continues
            if not ensureversion('1.9.0'):
                if request.request_method != 'POST':
                    return _('Missing parameters or GET method used'), "error"
            else:
                if request.method != 'POST':
                    return _('Missing parameters or GET method used'), "error"

            filename, ext = os.path.splitext(target)
            img_type = "JPEG"
            thumbfile = "tmp.thumbnail_%s.jpg"  % filename
            webnail = "tmp.webnail_%s.jpg"  % filename

            if ext in ('.gif', '.png'):
                img_type = 'PNG'
                thumbfile = "tmp.thumbnail_%s.png" % filename
                webnail = "tmp.webnail_%s.png" % filename

            if command in ('delete', 'rotate_left', 'rotate_right', ):
                cache.remove(self.request, webnail)
                cache.remove(self.request, thumbfile)

            infile = AttachFile.getFilename(request, pagename, target)

            # removes attachment (moves to bak)
            if command == 'delete':
                bakfile = "%s.bak" % target
                try:
                    os.unlink("%(file)s.bak" % {"file": infile})
                except:
                    pass
                AttachFile.move_file(request, pagename, pagename, target, bakfile)
                return None, None

            elif command == 'rotate_left':
                image_rotate(infile, 90, img_type)
                return _('%(target)s rotated left 90 degrees') % {'target': target}, "info"

            elif command == 'rotate_right':
                image_rotate(infile, 270, img_type)
                return _('%(target)s rotated right 90 degrees') % {'target': target}, "info"

            else:
                # fallback
                return _('action not implemented: %s') % (command, ), "info"
        else:
            return _('Your are not allowed to change attachments on this page: %s') % (pagename, ), "error"


def execute(pagename, request):
    """ Main dispatcher for the arnica_slides action. """
    _ = request.getText
    request.formatter.page = Page(request, pagename)
    msg = None
    if not Image:
        msg = _('The action %(action)s needs Python Imaging Library (PIL) installed') % {'action': action_name}
        state = "error"
    if not msg:
        msg, state = arnica_slides(request, pagename).render()
    if msg:
        request.theme.add_msg(msg, state)
        Page(request, pagename).send_page()
    return