Attachment 'Gallery2-1.3.5-13.py'

Download

   1 # -*- coding: iso-8859-1 -*-
   2 """
   3     MoinMoin - Gallery2 parser
   4 
   5     PURPOSE:
   6         This parser is used to visualize a couple of images as a thumbnail gallery.
   7         Optional a description of an image could be added including WikiName.
   8         On default the image name and it's creation date is shown.
   9         If you click on a thumbnail you get the webnails shown. By a menue you are able to toggle between the slides.
  10 
  11     CALLING SEQUENCE:
  12       {{{
  13       #!Gallery2 [columns=columns],[filter=filter],[mode=mode],
  14                  [show_text=show_text],[show_date=show_date], [show_tools=show_tools],
  15                  [sort_by_name=sort_by_name],[sort_by_date=sort_by_date], [sort_by_alias=sort_by_alias],
  16                  [reverse_sort=reverse_sort],
  17                  [only_items=only_items],[template_itemlist=template_itemlist],
  18                  [album=album],[album_name=album_name],[front_image=front_image],
  19                  [thumbnail_width=thumbnail_width],[webnail_width=webnail_width],[text_width=text_width],
  20                  [image_for_webnail=image_for_webnail],
  21                  [sequence_name=sequence_name], [sequence_type=sequence_type],
  22                  [border_thick=border_thick],[renew=renew],[help=help]
  23       * [image1.jpg alias]
  24       * [image2.jpg alias]
  25       }}}
  26 
  27     KEYWORD PARAMETERS:
  28         columns:           number of columns for thumbnails
  29         filter:            regex to select images
  30         show_text:         default is 1 description is shown
  31                            any other means no description
  32         show_date:         default is 1 date info from exif header if available is shown
  33         show_tools:        default is 1 icon toolbar is show any other disables this
  34         sort_by_name:      default is 1, the images are sorted by name, but not if only_items is 1
  35         sort_by_date:      default is 0, if set to 1 the images are sorted to the modification time
  36         sort_by_alias      default is 0, if set to 1 and only_items set to 1 it is used to order the images by the alias name
  37         reverse_sort:      default is 0, if set to 1 the file list is reversed
  38                            any other means no description
  39         mode:              default is 1 this means description below the image
  40                            any other number means description right of image
  41         only_items:        default is 0 if it is set to 1 only images which are described in listitem are shown
  42                            dependend on the order of the items
  43         template_itemlist: default is 0, if set to 1 an item list is shown which could be copied into the script. 
  44         album:             default is 0 if set to 1 only the first image of a series is shown but slideshow over all images 
  45         album_name:        useful for album. default is 'album' use it as short name for the album. 
  46         front_image:       Useful for album.  default is ''. The first image is shown in front of the album and slideshow.
  47                            If set to an existing image name this is shown in front of album and slideshow. 
  48                            The slide show could start by this somewhere.
  49         border_thick:      default is 1 this is the thickness in pixeln of the outer frame
  50         renew:             default is 0 if set to 1 then all selected thumbnails_* and webnails_* removed.
  51                            Afterwards they are new created.
  52         thumbnail_width:   default is 128
  53         webnail_width:     default is 640
  54         text_width:        default is 140		   
  55         image_for_webnail  default is 0 if set to 1 then the image is shown as preview and not the webnail
  56         help:              default is 0 if set a copy of the CALLING SEQUENCE is shown, 
  57                            (there are some new ideas around to show help to an user so this will be later replaced)
  58         sequence_name:     (ONLY POSIX) default is ''  if set this name is used for an image 
  59                            sequence with an duration of one image per second.
  60                            While for gif and png files the fli format is used a mpg file is created for jpegs.
  61                            For more info see the description on PROCEDURE below
  62                            Because mpeg creation could be take much cpu time this is done as background process. 
  63         sequence_type:     (ONLY POSIX) default is '' by this for png/gif fli format and the original image is used
  64                            and for jpg the mpeg format and the webnails are used. It could be set to fli or mpg. 
  65                            If it is set to mpg then always the original images are used.
  66                            mpg creation takes much more time and the file could be much bigger as a standard fli file      
  67                                                       
  68 
  69     OPTIONAL INPUTS:
  70         itemlist : if it is used and only_items is 1 then only the images in this list are ahown.
  71                    The alias text is used as description of the image instead of the file name
  72 
  73 
  74     EXAMPLE:
  75 = GalleryTest =
  76 
  77 == all images shown, one is decribed ==
  78 {{{
  79 { { {
  80 #!Gallery2
  81 * [100_1185.JPG Bremen, SpaceCenter]
  82 } } }
  83 }}}
  84 
  85 Result: [[BR]]
  86  {{{
  87 #!Gallery2
  88 * [100_1185.JPG Bremen, SpaceCenter]
  89 }}}
  90 
  91 == only thumbnails and only_items ==
  92 {{{
  93 { { {
  94 #!Gallery2 show_text=0,show_tools=0,show_date=0,columns=2,only_items=1
  95  * [100_1185.JPG Bremen, SpaceCenter]
  96  * [100_1194.JPG Bremen]
  97 } } }
  98 }}}
  99 
 100 Result: [[BR]]
 101  {{{
 102 #!Gallery2 show_text=0,show_tools=0,show_date=0,columns=2,only_items=1
 103  * [100_1185.JPG Bremen, SpaceCenter]
 104  * [100_1194.JPG Bremen]
 105 }}}
 106 
 107 == only_items by two columns and text right ==
 108 
 109 {{{
 110 { { {
 111 #!Gallery2 mode=2,columns=2,only_items=1
 112  * [100_1185.JPG Bremen, SpaceCenter]
 113  * [100_1194.JPG Bremen]
 114 } } }
 115 }}}
 116 
 117 Result: [[BR]]
 118  {{{
 119 #!Gallery2 mode=2,columns=2,only_items=1
 120  * [100_1185.JPG Bremen, SpaceCenter]
 121  * [100_1194.JPG Bremen, behind SpaceCenter]
 122 }}}
 123 
 124 ----
 125 
 126 == only_items by two columns, date supressed ==
 127 
 128 {{{
 129 { { {
 130 #!Gallery2 columns=2,only_items=1,show_date=0
 131  * [100_1185.JPG Bremen, SpaceCenter]
 132  * [100_1194.JPG Bremen, behind SpaceCenter]
 133 } } }
 134 }}}
 135 
 136 Result: [[BR]]
 137  {{{
 138 #!Gallery2 columns=2,only_items=1,show_date=0
 139  * [100_1185.JPG Bremen, SpaceCenter]
 140  * [100_1194.JPG Bremen, behind SpaceCenter]
 141 }}}
 142 
 143 
 144 == filter regex used, mode 2, icons and date supressed, one column and border_thick=5 ==
 145 {{{
 146 { { {
 147 #!Gallery2 columns=1,filter=100_118[0-5],mode=2,show_date=0,show_tools=0,border_thick=5
 148 } } }
 149 }}}
 150 
 151 Result: [[BR]]
 152  {{{
 153 #!Gallery2 columns=1,filter=100_118[0-7],mode=2,show_date=0,show_tools=0,border_thick=5
 154 }}}
 155 
 156 == other macro calls ==
 157 {{{
 158 { { {
 159 #!Gallery2 only_items=1,show_date=0
 160  * [100_1189.JPG [[MiniPage(||Bremen||SpaceCenter||\n|| ||SpaceJump||)]]]
 161 } } }
 162 }}}
 163 
 164 Result: [[BR]]
 165  {{{
 166 #!Gallery2 only_items=1,show_date=0
 167  * [100_1189.JPG [[MiniPage(||Bremen||SpaceCenter||\n|| ||SpaceJump||)]]]
 168 }}}
 169 
 170 == renew means always new thumbnails and webnails of selection ==
 171 {{{
 172 { { {
 173 #!Gallery2 only_items=1,show_date=0,show_tools=0,renew=1
 174  * [100_1189.JPG [[MiniPage(||["Bremen"]||SpaceCenter||\n|| ||SpaceJump||)]]]
 175 } } }
 176 }}}
 177 
 178 Result: [[BR]]
 179  {{{
 180 #!Gallery2 only_items=1,show_date=0,renew=1
 181  * [100_1189.JPG [[MiniPage(||["Bremen"]||SpaceCenter||\n|| ||SpaceJump||)]]]
 182 }}}
 183 
 184 == template_itemlist ==
 185 {{{
 186 { { {
 187 #!Gallery2 template_itemlist=1
 188 * [100_1185.JPG Bremen, SpaceCenter]
 189 } } }
 190 }}}
 191 
 192 Result: [[BR]]
 193  {{{
 194 #!Gallery2 template_itemlist=1
 195 * [100_1185.JPG Bremen, SpaceCenter]
 196 }}}
 197 
 198 == help to show Calling Sequence ==
 199  {{{
 200 { { {
 201 #!Gallery2 help=1
 202 } } }
 203 }}}
 204 
 205 Result: [[BR]]
 206 {{{
 207 #!Gallery2 help=1
 208 }}}
 209 
 210 
 211     PROCEDURE:
 212     
 213       HOWTO:
 214       Download some images to a page and start with the examples.
 215       Aliasing of the filenames are done by adding an itemlist, see example.
 216 
 217       NEEDS:
 218       This routine requires the Action macro gallery2Image which is used to rotate or delete a
 219       selected image. The actual version is gallery2image-1.3.5-10.py.
 220       Only users which have the rights to delete are able to execute this action macro. 
 221       The icons of these are only shown if you have enough rights. 
 222       Furthermore it requires:
 223        * the PIL (Python Imaging Library).
 224        * the EXIF routine from http://home.cfl.rr.com/genecash/digital_camera.html
 225       
 226       and on POSIX systems:
 227        * ppm2fli: see  http://vento.pi.tu-berlin.de/fli.html
 228        * jpeg2mpeg: see http://www.stillhq.com/jpeg2mpeg
 229 
 230       At the moment I have added the EXIF routine to the parsers dir.
 231       It's not the best place but during developing it is nice to have it there
 232       If you put it to another place you have to change the line
 233       from MoinMoin.parser import EXIF too.
 234 
 235       The gallery2image macro does not take care on the EXIF header. This is lost by rotating.
 236       If a file is deleted by this macro it is moved to a bak file.
 237       
 238       ABOUT FLI/MPG:
 239       The sequence feature is at the moment only for servers using posix platforms available. 
 240       If one has an idea how to do it on other platforms I like to implement it.
 241       
 242       Sometimes people want to have the image sequence in one file playable without internet.
 243       Scientific data could be best animated by the common flc/fli format which was
 244       introduced by autodesk.
 245       If png/gif files are used fli output is generated. 
 246       These files could be played by quicktime, mplayer, xanim and several others
 247       For additional informations on this format look at:
 248       http://woodshole.er.usgs.gov/operations/modeling/flc.html
 249       http://vento.pi.tu-berlin.de/fli.html
 250       The largest X and largest Y dimension is used and the images are centered to these.
 251       The maximum size is 1280x1024 for this format so it is scaled to this if the images are larger
 252       It is not the best for true color images if they have very different color tables.
 253       Creating a sequence in this format does not take much cpu time while it could be very 
 254       long time for a mpg file.
 255                           
 256       If the parameter sequnece_name is used with jpg then the webnails of the images are used, 
 257       because they are normally in a size defined  viewable on screen. 
 258       The files are written into a mpg file using these libraries:
 259       Follow this inforamtion for installation at http://www.stillhq.com/jpeg2mpeg
 260       Because mpeg creation with a duration of 1 second does take a lot of cpu time this is 
 261       done as background process
 262       You have to take care yourself on equal dimensions of all images in a sequence of jpg files
 263       
 264       If you don't like the defaults you could ommit them by using the sequence_type parameter.
 265       By this it is possible to write fli files from jpg or mpg files from png/gif files. 
 266       Always if choosen the original images are used for mpg creation. 
 267       But if they are larger as 1280x1024 they are scaled down
 268       Probably you could got poor quality if you try mpg for png/gif files.
 269       The resize mechanism is not used for jpg files by this parameter the original files ae used.
 270       
 271       
 272       It does not go well if you mix up in a call png, gif and jpg files.
 273       
 274       GENERAL:
 275       Please remove the Version number from the code!
 276       
 277       If you want to upload many files at once please look
 278       at FeatureRequests/UploadMultipleAttachmentFiles/RulesForUnzip
 279 
 280     RESTRICTIONS:
 281       The mnovie mode is at the moment implemented for POSIX only.  
 282       As soon as  I know how to do it on other platforms I like to add it
 283       If you rotate an image at the moment the exif is destroyed. PIL ignores the exif header.
 284       This is not a quite big problem normally files with an EXIF header are right rotated.
 285 
 286     Required Images:
 287       I have put them to wiki/modern/img/ dir. The icons were created by me. License: GPL
 288 
 289     attachment:to_bak.png
 290     attachment:to_left.png
 291     attachment:to_right.png
 292     attachment:to_slide.png
 293     attachment:to_full.png
 294 
 295     HISTORY:
 296     While recognizing how to write MiniPage I got the idea to write a Gallery Parser.
 297     We have used in our wikis in the past the Gallery macro of SimonRyan.
 298     I have tried to modify it a bit to change it for 1.3 but my python skills weren't enough
 299     or it was easier to write it completly new.
 300     So this one shows now a way how a Gallery could be used by the parser and an action Macro.
 301     Probably it is a good example for others who like to know how to do this
 302 
 303     MODIFICATION HISTORY:
 304         Version 1.3.3.-1
 305         @copyright: 2005 by Reimar Bauer (R.Bauer@fz-juelich.de)
 306         @license: GNU GPL, see COPYING for details.
 307         2005-03-26: Version 1.3.3-2 keyword renew added
 308                     creation of thumbnails and webnails in two calls splitted
 309                     Version 1.3.3-3 bug fixed if itemlist is given to describe only some of the images
 310                                     but only_items is not set to 1
 311                                     Example code changed
 312         2005-03-27: Version 1.3.3-4 Action macro added and the form to call it. User which have rights to delete
 313                                     could use the functions of gallery2Image.
 314         2005-08-03: Version 1.3.3-5 theme path for icons corrected and a platform independent path joining
 315                                     os.unlink removed as suggested by CraigJohnson
 316                                     sort_by_name is default if not only_items is 1
 317                                     optional sort_by_date could be used
 318                                     keyword template_itemlist added 
 319                                     keyword help added
 320                                     extra frame by mode=2 removed 
 321         2005-08-06: Version 1.3.5-6 slideshow mode added
 322                                     keyword image_for_webnail added
 323         2005-08-13: Version 1.3.5-7 syntax changed from GET to POST
 324                                     forms instead of links
 325                                     filenames from images submitted to gallery2image too
 326                                     new keyword sort_by_alias
 327                                     internal code clean up
 328                                     this version needs: gallery2image-1.3.5-5.py
 329         2005-08-14: Version 1.3.5-8 (TW) cleanup                                    
 330         2005-08-14: Version 1.3.5-9 html code for tables changed
 331                                     because of the ugly extra space of form elements
 332                                     div tag removed so now we use the page style
 333                                     slide show action goes to right webnail now
 334                                     this version needs: gallery2image-1.3.5-5.py
 335        2005-08-17: Version 1.3.5-10 html code separated in functions     
 336                                     structure of code changed, now you see the thumbnails after creation
 337                                     bug removed if quote is given but file does not exist
 338        2005-09-02: Version 1.3.5-11 keyword album, album_name and front_image added          
 339                                     image urls changed to complete server url      
 340        2005-11-12: Version 1.3.5-12 bug fixed for image_for_webnail=1            
 341                                     bug fixed at last cell table end tr instead of td
 342                                     bug fixed don't render a filename as WikiName
 343                                     bug fixed " is allowed in alias name
 344                                     bug fixed ' is allowed in alias name
 345                                     bug fixed linebreak by a space in alias  
 346                                     not quite a bug but makes it very difficult to code in 
 347                                     gallery2image so additional id removed in alias name
 348       2005-11-17: Version 1.3.5-13  implementation of sequence video clips at first step for posix only 
 349                                     sequence_type could be used to ommit the autoselection
 350                                     fli/flc files are used for gif and png files,  mpeg files for
 351                                     jpeg files. Duration on both is 1 image/second 
 352                                     feature added of recognising the right url pattern (http opr https)
 353 """
 354 Dependencies = []
 355 from MoinMoin.action import AttachFile
 356 from MoinMoin import wikiutil, config
 357 from MoinMoin.Page import Page
 358 
 359 import os, string, re, Image, StringIO, codecs
 360 
 361 from MoinMoin.parser import EXIF
 362 
 363 from MoinMoin.parser import wiki
 364 
 365 def server(request):
 366 
 367   if request.is_ssl:
 368      url_pattern = 'https'
 369   else:
 370      url_pattern = 'http'
 371         
 372   return "%(url_pattern)s://%(server_name)s:%(server_port)s" %   {
 373                              "url_pattern":url_pattern,
 374                              "server_name":request.server_name, 
 375                              "server_port":str(request.server_port)}
 376 
 377 def show_tools_restricted(pagename,this_target,thumbnail_width,full,alias,target,exif_date,request):
 378     if request.user.may.delete(pagename):
 379         return tools_restricted_html(pagename,this_target,thumbnail_width,full,alias,target,exif_date,request)
 380     else:
 381         return ''    
 382 
 383 def tools_restricted_html(pagename,this_target,thumbnail_width,full,alias,target,exif_date,request):
 384     text='''
 385                    <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 386                         <td>
 387                             <input type="hidden" name="action" value="gallery2image">
 388                             <input type="hidden" name="do" value="RL">
 389                             <input type="hidden" name="target" value="%(this_target)s">
 390                             <input type="image" value="submit" src="%(server)s/wiki/modern/img/to_left.png" title="rotate to left">
 391                         </td>
 392                     </form>
 393                     <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 394                         <td>
 395                             <input type="hidden" name="action" value="gallery2image">
 396                             <input type="hidden" name="do" value="RR">
 397                             <input type="hidden" name="target" value="%(this_target)s">
 398                             <input type="image"  value="submit" src="%(server)s/wiki/modern/img/to_right.png" title="rotate to right" >
 399                         </td>
 400                     </form>
 401                     <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 402                         <td>
 403                             <input type="hidden" name="action" value="gallery2image">
 404                             <input type="hidden" name="do" value="RM">
 405                             <input type="hidden" name="target" value="%(this_target)s">
 406                             <input type="image" value="submit" src="%(server)s/wiki/modern/img/to_bak.png" title="move to bak" >
 407                         </td>
 408                     </form>''' %  {
 409             "server" : server(request), 
 410             'baseurl': request.getScriptname(),
 411             "pagename":pagename,
 412             "this_target":this_target}                   
 413     return text                
 414     
 415 def tools_html(pagename,this_image,thumbnail_width,full,alias,target,exif_date,request):
 416     text='''
 417             <TABLE align="center" width="%(thumbnail_width)s">
 418                 <TR>
 419                     <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 420                         <td>
 421                             <input type="hidden" name="action" value="AttachFile">
 422                             <input type="hidden" name="do" value="get">
 423                             <input type="hidden" name="target" value='%(this_target)s'>
 424                             <input type="image" value="submit" src="%(server)s/wiki/modern/img/to_full.png" title="load image">
 425                         </td>
 426                     </form>
 427                     <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 428                         <td>
 429                             <input type="hidden" name="action" value="gallery2image">
 430                             <input type="hidden" name="do" value="VS">
 431                             <input type="hidden" name="full" value='%(full)s'>
 432                             <input type="hidden" name="alias" value='%(alias)s'>
 433                             <input type="hidden" name="target" value='%(target)s`>
 434                             <input type="hidden" name="exif_date" value='%(exif_date)s'>
 435                             <input type="image" value="submit" src="%(server)s/wiki/modern/img/to_slide.png" title="slide_show" >
 436                        </td>
 437                     </form>
 438                     %(show_tools_restricted)s
 439                 </TR>
 440             </TABLE>'''   %  {
 441             "server" : server(request),
 442             'baseurl': request.getScriptname(),
 443             "pagename":pagename,
 444             "thumbnail_width":thumbnail_width,
 445             "full":full,
 446             "alias":alias,
 447             "exif_date":exif_date,
 448             "target":target,
 449             "this_target":this_image,
 450             "show_tools_restricted":show_tools_restricted(pagename,this_image,thumbnail_width,full,alias,target,exif_date,request)
 451             } 
 452    
 453     return text
 454 
 455 def show_alias_mode2(show_alias,thumbnail_width,this_alias,text_width):
 456    if show_alias == '1': 
 457        return '''
 458         <td valign="top" width="%(text_width)s">
 459            %(this_alias)s
 460         </td>''' % {
 461         "this_alias":this_alias,
 462         "text_width":text_width}
 463    else:
 464       return ''     
 465         
 466 def show_date_mode2(show_date,this_exif_date):    
 467    if show_date == '1': 
 468        return '''
 469    <td>
 470      <p>%(this_exif_date)s</p>
 471    </td>''' % {
 472    "this_exif_date":this_exif_date }
 473    else:
 474         return '';
 475                
 476 def show_tools_mode2(show_tools,pagename,this_target,thumbnail_width,full,alias,target,exif_date,request):  
 477     if show_tools == '1' :
 478         return "<td align=""center""> %s </td>" % tools_html(pagename,this_target,thumbnail_width,full,alias,target,exif_date,request)
 479     else:
 480         return ''
 481       
 482 
 483 
 484 def mode2_html(pagename,border_thick,width,thumbnail_width,text_width,full,this_image,alias,this_alias,exif_date,this_exif_date,target,this_target,submit,show_tools,show_date,show_alias,request):
 485     text='''
 486     <tr valign="center">
 487         <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 488             <td align="center" valign="center" width="%(thumbnail_width)s">
 489                 <input type="hidden" name="action" value="gallery2image">
 490                 <input type="hidden" name="do" value="VS">
 491                 <input type="hidden" name="full" value='%(full)s'>
 492                 <input type="hidden" name="alias" value='%(alias)s'>
 493                 <input type="hidden" name="exif_date" value='%(exif_date)s'>
 494                 <input type="hidden" name="target" value='%(target)s'>
 495                 <input type="image" value="submit" src="%(server)s%(submit)s">
 496             </td>
 497         </form>
 498             %(alias_html)s 
 499     </tr>
 500     <tr>%(tools_html)s%(date_html)s</tr>'''%       {   
 501      "server" : server(request),
 502      "baseurl": request.getScriptname(),
 503      "pagename":pagename,
 504      "thumbnail_width":thumbnail_width,
 505      "full":full,
 506      "alias":alias,
 507      "exif_date":exif_date,
 508      "target":target,
 509      "submit":submit,
 510      "tools_html":show_tools_mode2(show_tools,pagename,this_image,thumbnail_width,full,alias,target,exif_date,request),
 511      "date_html": show_date_mode2(show_date,this_exif_date),
 512      "alias_html": show_alias_mode2(show_alias,thumbnail_width,this_alias,text_width)
 513      }
 514     
 515     return text
 516 
 517 def show_tools_mode1(show_tools,pagename,this_image,thumbnail_width,full,alias,target,exif_date,request):  
 518     if show_tools == '1' :
 519         text="<tr><td align=""center"">%s </td></tr>" % tools_html(pagename,this_image,thumbnail_width,full,alias,target,exif_date,request)
 520     else:
 521         text=''
 522     return text        
 523 
 524 def show_date_mode1(show_date,this_exif_date):    
 525    if show_date == '1': 
 526        return '''
 527     <TR>
 528         <td>%(this_exif_date)s</td>
 529     </TR>''' % {
 530     "this_exif_date":this_exif_date}
 531    else:
 532        return ''
 533 
 534 def show_alias_mode1(show_alias,thumbnail_width,this_alias,text_width):
 535    if show_alias == '1': 
 536        return '''
 537     <TR>
 538         <td align="left" width="%(thumbnail_width)s"> %(this_alias)s</td>
 539     </TR>''' % {
 540     "thumbnail_width": thumbnail_width,
 541     "this_alias":this_alias}
 542    else:
 543        return ''     
 544        
 545 def mode1_html(pagename,border_thick,width,thumbnail_width,text_width,full,this_image,alias,this_alias,exif_date,this_exif_date,target,this_target,submit,
 546                show_tools,show_date,show_alias,request):
 547    text='''
 548 <table width="%(thumbnail_width)s" align="center" valign="center">
 549     <TR align="center" valign="center">
 550         <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 551             <td align="center" valign="middle" width="%(thumbnail_width)s">
 552                 <input type="hidden" name="action" value="gallery2image">
 553                 <input type="hidden" name="do" value="VS">
 554                 <input type="hidden" name="full" value='%(full)s'>
 555                 <input type="hidden" name="alias" value='%(alias)s'>
 556                 <input type="hidden" name="exif_date" value='%(exif_date)s'>
 557                 <input type="hidden" name="target" value='%(target)s'>
 558                 <input type="image" value="submit" src="%(server)s%(submit)s" >
 559             </td>
 560         </form>
 561     </TR>
 562       %(alias_html)s
 563       %(date_html)s
 564       %(tools_html)s
 565 </table>'''%       {  
 566      "server" : server(request), 
 567      "baseurl": request.getScriptname() ,
 568      "pagename":pagename,
 569      "full":full,
 570      "alias":alias,
 571      "exif_date":exif_date,
 572      "target":target,
 573      "submit":submit,
 574      "thumbnail_width":thumbnail_width,
 575      "tools_html": show_tools_mode1(show_tools,pagename,this_image,thumbnail_width,full,alias,target,exif_date,request),  
 576      "date_html":show_date_mode1(show_date,this_exif_date),
 577      "alias_html": show_alias_mode1(show_alias,thumbnail_width,this_alias,text_width)
 578      }
 579 
 580    return text
 581 
 582 def get_files(kw,path,files,quotes,request):
 583     web=[]
 584     full=[]
 585     thumb=[]
 586     exif_date=[]
 587     img_type=[]
 588     description=[]
 589     
 590     
 591     ddict={}
 592     n=len(quotes['image'])
 593     if n > 0 :
 594         i = 0
 595         for txt in quotes['image']:
 596             ddict[txt]=quotes['alias'][i]
 597             i += 1    
 598             
 599     video_type = ''
 600     source_type = ''
 601     for attfile in files:
 602         # only files not thumb or webnails
 603         if attfile.find('thumbnail_') == -1 and attfile.find('webnail_') == -1:
 604             # only images
 605             if wikiutil.isPicture(attfile):
 606                 description.append(ddict.get(attfile, attfile))
 607                 full.append(attfile)
 608                 
 609                 fname, ext = os.path.splitext(attfile)
 610                 if ext in ('.gif', '.png'):
 611                     img_type.append('PNG')
 612                     webnail = 'webnail_%s.png' % fname
 613                     thumbfile = 'thumbnail_%s.png' % fname
 614                     video_type = 'fli'
 615                     source_type = ext[1:]
 616                 else:
 617                     img_type.append("JPEG")
 618                     webnail = 'webnail_%s.jpg' % fname
 619                     thumbfile = 'thumbnail_%s.jpg' % fname
 620                     video_type = 'mpg'
 621                     source_type = 'jpg'    
 622                 
 623                     
 624                 infile = os.path.join(path, attfile)
 625                 if os.path.exists(infile):
 626                      web.append(webnail)
 627                      thumb.append(thumbfile)
 628                      
 629                 f = open(infile, 'rb')
 630                 tags = EXIF.process_file(f)
 631                 if tags.has_key('EXIF DateTimeOriginal'):
 632                     date = str(tags['EXIF DateTimeOriginal'])
 633                     date = date.replace(':', '-', 2)
 634                 else:
 635                     date = '--'
 636                 exif_date.append(date)
 637                 f.close() 
 638     if kw['sequence_type'] != '': 
 639           video_type = kw['sequence_type']
 640           
 641     return thumb,web,full,video_type, exif_date,img_type,source_type,description  
 642     
 643 def to_htmltext(text):        
 644   
 645     if text.find ("'"):   
 646         text = text.split("'")
 647         text = '&#39;'.join(text)
 648        
 649     return text
 650 
 651 def to_wikiname(request,formatter,text):
 652   ##taken from MiniPage
 653     out=StringIO.StringIO()
 654     request.redirect(out)
 655     wikiizer = wiki.Parser(text.strip(),request)
 656     wikiizer.format(formatter)
 657     result=out.getvalue()
 658     request.redirect()
 659     del out
 660     
 661    
 662     
 663     result = result.replace('<a id="line-1"></a>','')
 664     result = result.replace('<p>','')
 665     result = result.replace('</p>','')
 666     result = result.strip() 
 667     return result
 668     
 669         
 670 def get_quotes(self,formatter):
 671     quotes = self.raw.split('\n')
 672     quotes = [quote.strip() for quote in quotes]
 673     quotes = [quote[2:] for quote in quotes if quote.startswith('* ')]
 674 
 675     
 676     image=[]
 677     text=[]
 678     
 679     for line in quotes:
 680         im, na=line[1:-1].split(' ',1)
 681         na = na.strip()
 682         na = to_htmltext(na)
 683         na = to_wikiname(self.request,formatter,na)
 684         text.append(na)
 685         image.append(im.strip())
 686 
 687     return {
 688         'alias': text,
 689         'image': image,
 690     }
 691 
 692 
 693 
 694 class Parser:
 695         
 696     def __init__(self, raw, request, **kw):
 697         self.raw = raw
 698         self.request = request
 699         self.form = request.form
 700         self._ = request.getText
 701         self.kw = {
 702             'sort_by_date': '0',
 703             'sort_by_name': '1',
 704             'sort_by_alias': '0',
 705             'album': '0',
 706             'album_name': 'album',
 707             'front_image':'',
 708             'template_itemlist': '0', 
 709             'reverse_sort': '0',
 710             'border_thick': '1',
 711             'columns': '4',
 712             'filter': '.',
 713             'mode': '1',
 714             'help': '0',
 715             'show_text': '1',
 716             'show_date': '1',
 717             'show_tools': '1',
 718             'only_items': '0',
 719             'image_for_webnail': '0',
 720             'renew': '0',
 721             'thumbnail_width': '128',
 722             'webnail_width': '640',
 723             'text_width': '140',
 724             'sequence_name' : '',
 725             'sequence_type' : '',
 726         }
 727        
 728 
 729         for arg in kw.get('format_args','').split(','):
 730 
 731             if arg.find('=') > -1:
 732                 key, value=arg.split('=')
 733                 self.kw[key]=wikiutil.escape(value, quote=1)
 734 
 735 
 736         self.kw['width']=str((int(self.kw['thumbnail_width'])+int(self.kw['text_width'])))
 737 
 738 
 739     def format(self, formatter):
 740         kw=self.kw
 741         Dict = {}
 742         quotes=get_quotes(self,formatter)
 743         current_pagename=formatter.page.page_name
 744         attachment_path = AttachFile.getAttachDir(self.request, current_pagename, create=1)
 745  
 746         if kw['help'] == '1':
 747             self.request.write('''
 748 <br>
 749 {{{<br>
 750 #!Gallery2 [columns=columns],[filter=filter],[mode=mode],<br>
 751            [show_text=show_text],[show_date=show_date], [show_tools=show_tools],<br>
 752            [sort_by_name=sort_by_name],[sort_by_date=sort_by_date],[sort_by_alias=sort_by_alias]<br>
 753            [reverse_sort=reverse_sort],<br>
 754            [only_items=only_items],[template_itemlist=template_itemlist],<br>
 755            [album=album],[album_name=album_name],[front_image=front_image],<br>
 756            [thumbnail_width=thumbnail_width],[webnail_width=webnail_width],[text_width=text_width],<br>
 757            [image_for_webnail=image_for_webnail],<br>
 758            [sequence_name=sequence_name],[sequence_type=sequence_type]<br>
 759            [border_thick=border_thick],[renew=renew],[help=help]<br>
 760  * [image1.jpg alias]<br>
 761  * [image2.jpg alias]<br>
 762 }}}<br>''')
 763             return    
 764             
 765         
 766         if kw['only_items'] == '1':
 767             all_files=quotes['image']
 768             result=[]
 769             for attfile in all_files:
 770                 infile=os.path.join(attachment_path,attfile)
 771                 if os.path.exists(infile):
 772                    result.append(attfile) 
 773             all_files = result        
 774             
 775             if kw['sort_by_alias'] == '1':
 776                 new_ordered_files=[]
 777                 alias_text=quotes['alias']
 778                 
 779                 i=0
 780                 for attfile in all_files:
 781                     infile=os.path.join(attachment_path,attfile)
 782                     ft_file=str(os.path.getmtime(infile))+os.tmpnam()
 783                     Dict[alias_text[i]]=attfile
 784                     i += 1
 785        
 786                 keys = Dict.keys()   
 787                 keys.sort()
 788                 for txt in keys:
 789                     new_ordered_files.append(Dict[txt])
 790                    
 791 
 792                 all_files=new_ordered_files
 793                 Dict.clear()
 794            
 795         else:
 796             all_files=os.listdir(attachment_path)
 797 
 798         result = []
 799 
 800         for test in all_files:
 801            if re.match(kw['filter'], test):
 802               result.append(test)
 803         all_files=result
 804         
 805         if not all_files:
 806             self.request.write("<br><br><h1>No matching image file found!</h1>")
 807             return
 808             
 809         
 810 
 811         if kw['sort_by_name'] == '1' and kw['only_items'] == '0': 
 812             all_files.sort()
 813           
 814         if kw['sort_by_date']=='1': 
 815            for attfile in all_files:
 816                infile=os.path.join(attachment_path,attfile)
 817                ft_file=str(os.path.getmtime(infile))+os.tmpnam()
 818                Dict[ft_file]=attfile
 819                
 820            keys = Dict.keys()
 821            keys.sort()
 822            file_mdate=[]
 823            for txt in keys:
 824                file_mdate.append(Dict[txt])
 825            all_files=file_mdate
 826            Dict.clear()
 827          
 828         if kw['reverse_sort']=='1': 
 829              all_files.reverse()   
 830 
 831  
 832         cells=[]
 833         cell_name=[]
 834         img=[]
 835         
 836         thumb, web, full, video_type, exif_date, imgtype, source_type, description = get_files(kw, attachment_path, all_files, quotes, self.request)
 837         
 838         if kw['template_itemlist'] == '1':
 839             self.request.write('Copy the following listitems into the script. Replace alias with the label you want. Afterwards disable template_itemlist by setting it to 0:<BR>')
 840             for attfile in full : 
 841                 self.request.write(' * [%(attfile)s %(date)s]<br>' % {
 842                                    'attfile' : attfile,
 843                                    'date'    : 'alias'
 844                                     })
 845                                     
 846         
 847         i = 0  
 848         z = 1
 849         cols = int(kw['columns'])
 850         
 851            
 852         n = len(full)
 853         if  kw['album'] == '0' :
 854             self.request.write("<table align='center' border='%s' >" % self.kw['border_thick'])
 855             if kw['mode'] == '1' or cols > 1   :
 856                 self.request.write('<TR valign="bottom">')      
 857                 self.request.write('<TD>') 
 858  
 859                            
 860         if kw['album'] == '1' :
 861             if kw['front_image'] == '' :
 862                 front_image = full[0]  
 863             else:
 864                 front_image =  kw['front_image']   
 865             ii = 0
 866             for tst in full : 
 867                 if tst == front_image :
 868                     break 
 869                 ii += 1   
 870             
 871         
 872         for attfile in full :
 873             if  kw['album'] == '1' :
 874                 if tst == front_image :
 875                    i = ii
 876              
 877                                  
 878             this_description=description[i]
 879             this_exif_date=exif_date[i]
 880             this_webnail=web[i]
 881             this_imgtype=imgtype[i]
 882             this_thumbfile=thumb[i]
 883             
 884            
 885             thumbf=os.path.join(attachment_path,this_thumbfile)
 886             webf=os.path.join(attachment_path,this_webnail)            
 887             
 888             
 889             if kw['renew'] == '1':
 890                 if os.path.exists(thumbf):
 891                    os.unlink(thumbf)
 892                 if os.path.exists(webf):
 893                    os.unlink(webf)
 894                    
 895             if not os.path.exists(webf) or not os.path.exists(thumbf):
 896                 infile = os.path.join(attachment_path,attfile)
 897                 im = Image.open(infile)
 898                 
 899                 if not os.path.exists(webf):
 900                     im.thumbnail(((int(kw['webnail_width'])),((int(kw['webnail_width'])))), Image.ANTIALIAS)
 901                     if kw['image_for_webnail'] == '1' :
 902                        os.link(os.path.join(attachment_path,attfile),webf)
 903                     else:
 904                        im.save(webf, this_imgtype)
 905                 if not os.path.exists(thumbf):
 906                     im.thumbnail(((int(kw['thumbnail_width'])),((int(kw['thumbnail_width'])))),
 907                                    Image.ANTIALIAS)
 908                     im.save(thumbf, this_imgtype)       
 909                     
 910                     
 911             if kw['image_for_webnail'] == '1' :     
 912                  this_webnailimg = attfile
 913                  webimg = full
 914             else: 
 915                  this_webnailimg = this_webnail
 916                  webimg = web
 917                  
 918            
 919             if kw['mode'] == '1':
 920                 text = mode1_html(current_pagename,
 921                         kw['border_thick'],
 922                         kw['width'],
 923                         kw['thumbnail_width'],
 924                         kw['text_width'],
 925                         attfile + "," + ','.join(full),
 926                         attfile,
 927                         this_description + '!,!' + '!,!'.join(description),
 928                         this_description,
 929                         to_htmltext(this_exif_date + ',' + ','.join(exif_date)),
 930                         to_htmltext(this_exif_date),
 931                         this_webnailimg + ',' + ','.join(webimg),
 932                         this_webnailimg,
 933                         AttachFile.getAttachUrl(current_pagename, this_thumbfile, self.request),
 934                         kw['show_tools'],
 935                         kw['show_date'],
 936                         kw['show_text'],
 937                         self.request
 938                         )
 939                 self.request.write(''.join(text))    
 940                         
 941             if kw['mode'] == '2':
 942                 text = mode2_html(current_pagename,
 943                         kw['border_thick'],
 944                         kw['width'],
 945                         kw['thumbnail_width'],
 946                         kw['text_width'],
 947                         attfile + "," + ','.join(full),
 948                         attfile,
 949                         this_description + '!,!' + '!,!'.join(description),
 950                         this_description,
 951                         to_htmltext(this_exif_date + ',' + ','.join(exif_date)),
 952                         to_htmltext(this_exif_date),
 953                         this_webnailimg + ',' + ','.join(webimg),
 954                         this_webnailimg,
 955                         AttachFile.getAttachUrl(current_pagename, this_thumbfile, self.request),
 956                         kw['show_tools'],
 957                         kw['show_date'],
 958                         kw['show_text'],
 959                         self.request
 960                         )
 961             
 962                 if cols > 1 : self.request.write('<table valign="bottom">')
 963                 self.request.write(''.join(text))
 964                 if cols > 1 : self.request.write('</table>')
 965             
 966             if kw['mode'] == '1' or cols > 1:    
 967                 if kw['album'] == '0' :
 968                     if  z < cols :
 969                         self.request.write('</TD>')
 970                         if z <  n and  i < n - 1 :
 971                             self.request.write('<TD>')
 972                         if i == n - 1 :    
 973                             self.request.write('</TR>')
 974                     else: 
 975                         self.request.write('</TD>')
 976                         self.request.write('</TR>')
 977                         if i < n - 1 :
 978                             self.request.write('<TR valign="bottom">')
 979                             self.request.write('<TD>')
 980                             
 981             i += 1         
 982             z += 1
 983             if z > cols :
 984                 z = 1
 985             
 986             if kw['album'] == '1' :
 987                 self.request.write("%(n)s images (%(album_name)s)" % {"n": str(n), "album_name":kw['album_name']})
 988                 break
 989         if kw['album'] == '0' :        
 990             if i < n :
 991                 self.request.write('</TD>')
 992                 self.request.write('</TR>')
 993             self.request.write('</table>')       
 994             
 995         ############################################
 996         ##TODO: syntax change to formatter - later #
 997         ############################################
 998         
 999         if os.name !=  'posix':
1000            text = '<p><FONT color="Red" >sequence_name feature not defined for this platform: %(platform)s </FONT><p>' % {"platform":os.name}
1001            self.request.write(text)  
1002            return
1003             
1004         if  kw['sequence_name'] != '' :       
1005             
1006             ppm2fli = '/usr/local/bin/ppm2fli'
1007             video_file = os.path.join(attachment_path,kw['sequence_name']) 
1008             
1009             if video_type == 'fli' : 
1010                
1011                if kw['renew'] == '1':
1012                   if os.path.exists(video_file + '.fli'):
1013                      os.unlink(video_file + '.fli')
1014                liste_file = '%(flcfile)s.txt' % { 'flcfile': video_file}     
1015                if not os.path.exists(video_file+'.fli'):
1016                    
1017                    data = open(liste_file,'w')
1018                    x=[]
1019                    y=[]
1020                    for attfile in full :
1021                        file = os.path.join(attachment_path,attfile)   
1022                        im = Image.open(file)
1023                        size = im.size
1024                        x.append(size[0])
1025                        y.append(size[1])
1026                        data.write("%(file)s\n" % {'file':attfile})
1027                    data.close()
1028          
1029                    
1030                    if max(x) > 1280.0:
1031                       this_x = 1280.0
1032                       f = this_x / max(x)
1033                       this_y = max(y) *  f
1034                    else: 
1035                       this_x = max(x)
1036                       this_y = max(y)   
1037                       
1038                    if this_y > 1024.0:
1039                        new_y = 1024.0
1040                        f =   new_y /this_y
1041                        this_y = new_y
1042                        this_x =  this_x * f
1043                    
1044                    sz = '%(szx)sx%(szy)s' % { 'szx': long(this_x),
1045                                               'szy': long(this_y) }
1046 #wir mueesen in den pfad wechseln    
1047 #wenn Zeit ist ppm12fli überarbeiten                                  
1048                    cmd = ''
1049                    if source_type == 'png':         
1050                       cmd = 'cd "%(attachment_path)s" &&  %(ppm2fli)s  -g %(sz)s -fpngtopnm -s 1000 %(flcfile)s.txt %(flcfile)s.fli > /dev/null ' % {
1051                                  'ppm2fli': ppm2fli,
1052                                  'flcfile': kw['sequence_name'] ,
1053                                  'attachment_path': attachment_path,
1054                                  'sz': sz }
1055                                  
1056                    if source_type == 'gif':             
1057                       cmd = 'cd "%(attachment_path)s" && %(ppm2fli)s  -g %(sz)s -fgiftopnm -s 1000 %(flcfile)s.txt %(flcfile)s.fli > /dev/null ' % {
1058                                  'ppm2fli': ppm2fli,
1059                                  'flcfile': kw['sequence_name'] ,
1060                                  'attachment_path': attachment_path,
1061                                  'sz': sz }
1062                    
1063                    if source_type == 'jpg':    
1064                               
1065                       cmd = 'cd "%(attachment_path)s" && %(ppm2fli)s  -g %(sz)s -fjpegtopnm -s 1000 %(flcfile)s.txt %(flcfile)s.fli > /dev/null' % {
1066                                  'ppm2fli': ppm2fli,
1067                                  'flcfile': kw['sequence_name'] ,
1068                                  'attachment_path': attachment_path,
1069                                  'sz': sz }
1070                      
1071                            
1072                    if cmd != '':
1073                       os.system(cmd)    
1074                
1075                if os.path.exists(video_file+'.fli'):
1076                   if os.path.exists(liste_file):
1077                      os.unlink(liste_file) # liste entfernen wenn file da ist
1078                   dict = {}
1079                   dict['src'] = AttachFile.getAttachUrl(current_pagename,'%(flcfile)s.fli'  % { 
1080                      'flcfile': kw['sequence_name']},self.request)
1081                   image_link = '%(flcfile)s.fli' % { 'flcfile':kw['sequence_name']}
1082                
1083                   self.request.write('<BR>')
1084                   text = formatter.url(1,dict['src'] ) + image_link + formatter.url(0)
1085                   self.request.write('Download this image sequence %(text)s for your archive' % { 'text': text})
1086             
1087                       
1088             if video_type == 'mpg':      
1089             #  http://www.stillhq.com/jpeg2mpeg
1090                if kw['renew'] == '1':
1091                   if os.path.exists(video_file + '.mpg'):
1092                      os.unlink(video_file + '.mpg')
1093                if not os.path.exists(os.path.join(attachment_path,'%(file)s.mpg' % {'file':kw['sequence_name']})):
1094                    if kw['sequence_type'] == 'mpg':
1095                       liste = string.join(full,' ')
1096                    else:
1097                       liste = string.join(web,' ')
1098                       
1099                    cmd = 'cd "%(attachment_path)s" && convert -delay 100  %(liste)s %(file)s.mpg > /dev/null &' % {
1100                                     'liste':liste,
1101                                     'file':kw['sequence_name'] ,
1102                                     'attachment_path':attachment_path}
1103                    os.system(cmd)  
1104                    self.request.write('<P><FONT color="Red" > MPEG creation as background process started </FONT></P>')
1105                if os.path.exists(os.path.join(attachment_path,'%(file)s.mpg' % {'file':kw['sequence_name']})):
1106                   dict = {}
1107                   dict['src'] = AttachFile.getAttachUrl(current_pagename,'%(file)s.mpg'  % { 
1108                      'file': kw['sequence_name']},self.request)
1109                      
1110                   image_link = '%(file)s.mpg' % { 'file':kw['sequence_name']}
1111                
1112                   self.request.write('<BR>')
1113                   text = formatter.url(1,dict['src'] ) + image_link + formatter.url(0)
1114                   self.request.write('Download this image sequence %(text)s for your archive' % { 'text': text})
1115               
1116               
1117          ## the sequence block needs to be refactored by better names
1118         
1119        

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2006-08-06 08:57:54, 41.8 KB) [[attachment:EXIF.py]]
  • [get | view] (2005-03-24 20:14:37, 10.6 KB) [[attachment:Gallery2-1.3.3-1.py]]
  • [get | view] (2005-03-26 08:39:13, 11.6 KB) [[attachment:Gallery2-1.3.3-2.py]]
  • [get | view] (2005-03-26 12:41:49, 13.1 KB) [[attachment:Gallery2-1.3.3-3.py]]
  • [get | view] (2005-03-27 20:23:01, 19.0 KB) [[attachment:Gallery2-1.3.3-4.py]]
  • [get | view] (2005-08-03 19:30:10, 23.2 KB) [[attachment:Gallery2-1.3.3-5.py]]
  • [get | view] (2005-08-18 07:58:38, 31.9 KB) [[attachment:Gallery2-1.3.5-10.py]]
  • [get | view] (2005-09-02 19:55:13, 34.1 KB) [[attachment:Gallery2-1.3.5-11.py]]
  • [get | view] (2005-11-13 18:09:11, 35.4 KB) [[attachment:Gallery2-1.3.5-12.py]]
  • [get | view] (2005-11-18 20:13:04, 46.2 KB) [[attachment:Gallery2-1.3.5-13.py]]
  • [get | view] (2005-12-03 15:33:06, 46.6 KB) [[attachment:Gallery2-1.3.5-14.py]]
  • [get | view] (2006-01-01 09:20:19, 43.3 KB) [[attachment:Gallery2-1.3.5-15.py]]
  • [get | view] (2005-08-07 15:46:28, 26.9 KB) [[attachment:Gallery2-1.3.5-6.py]]
  • [get | view] (2005-08-13 15:13:59, 28.7 KB) [[attachment:Gallery2-1.3.5-7.py]]
  • [get | view] (2005-08-14 13:02:00, 27.5 KB) [[attachment:Gallery2-1.3.5-8.py]]
  • [get | view] (2005-08-14 14:38:32, 28.7 KB) [[attachment:Gallery2-1.3.5-9.py]]
  • [get | view] (2006-08-06 08:45:47, 41.8 KB) [[attachment:Gallery2-1.5.4-16.py]]
  • [get | view] (2006-08-22 20:29:39, 42.0 KB) [[attachment:Gallery2-1.5.4-18.py]]
  • [get | view] (2006-08-06 08:57:36, 514.8 KB) [[attachment:example.swf]]
  • [get | view] (2005-08-17 18:10:27, 11.3 KB) [[attachment:gallery2image_test.py]]
  • [get | view] (2005-08-10 16:49:16, 1.3 KB) [[attachment:patchpullfromdir.diff]]
  • [get | view] (2006-08-17 16:32:50, 41.9 KB) [[attachment:text_x_gallery2-1.6.0-17.py]]
  • [get | view] (2006-08-22 20:23:06, 42.1 KB) [[attachment:text_x_gallery2-1.6.0-18.py]]
  • [get | view] (2008-02-06 10:08:05, 42.2 KB) [[attachment:text_x_gallery2-1.6.0-19.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.