Revision [20129]

This is an old revision of WikiFile made by ChewBakka on 2008-07-16 01:50:39.

 

WikiFile action and handler


In Wikka, almost everything is a page.

For example, a category is a page to which other pages link. If you want to start a new category, you do not need to learn something new - just create the category page and link your pages to it.

Here I would like to present a solution where a file is represented by a page too. I will first give a description on how to use it, then the necessary pieces of code.

You can also think of files as attachments; any wikka page can have at most one attachment.

I did everything in Wikka 1.1.6.2 so I can not tell whether it will work in older versions.

How to use it


Example: assume you have a photograph of the Millennium Falcon in a file named IMG_1234.JPG on your computer. You want to put it in the wiki.

First you create a page for the image, say MillenniumFalconOnTatooine, as follows, and save it.

MillenniumFalconOnTatooine
===== Millennium Falcon photograph =====
Photograph of the Millennium Falcon, last year on Tatooine.
There was lots of sand, as always.
{{file}}


The page now shows a form which allows you to upload the file.

After uploading, you see the image in the page, and below the image another upload form is visible, in case you want to replace the photo with another one later.

In order to display the image on other pages, include the following modified file action in the other page:

Code for embedding an image from another page
{{file page="MillenniumFalconOnTatooine"}}


This will display the image on another page.

And of cause all of this works with other file types too. If a file is not an image, then the file action displays an URL through which you can download the file.

How to install it


We are going to add two program files to the Wikka software, and create a folder.

In your Wikka root directory, there is a folder named actions which contains several php files. Save the following code (use the grab button) as file.php in the actions folder.

actions/file.php (line 1)
  1. <?php
  2. /**
  3.  * WikiFile action
  4.  *
  5.  * Display the page's file, either inline or as link.
  6.  *
  7.  * If the file is an image, then it is displayed inline.
  8.  * For other file types, a download link is printed.
  9.  * Note that at most one file can be stored in a page.
  10.  *
  11.  * Syntax:
  12.  * {{file [page="SomeWikkaName"]}}
  13.  *
  14.  * @package  Actions
  15.  * @name     File
  16.  *
  17.  * @author   {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  18.  *
  19.  * @input    string $page: name of the page whose file is to be displayed.
  20.  *           optional; default is the current page itself.
  21.  */
  22.  
  23. /**
  24.  * Return an array describing the file which is stored in a page.
  25.  *
  26.  * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  27.  * @input   $tag name of the page; default = current page.
  28.  * @output  Array or (if no file in page) null. Keys:
  29.  *          extension     file extension, e.g. 'png', 'jpg', 'ogg'
  30.  *          content-type  mime content type, e.g. 'image/png'
  31.  *          uploaded      when and who,. e.g. '2007-01-31 ChewBakka'
  32.  *          image         'yes' or 'no'
  33.  *          width         Width in pixels (images only)
  34.  *          height        Height in pixels (images only)
  35.  *          filename      PageName.ext
  36.  *          path          upload_path/PageName.extension
  37.  *          url           'http://..../PageName/file'
  38.  */
  39. if( !function_exists('getFile') ) // for pages which include multiple files
  40. {
  41.    function getFile( $tag, $wikka )
  42.    {
  43.       $data = null; // this variable will be returned
  44.       $metafile = $wikka->config['upload_path'].'/'.$tag.'.file';  // metadata
  45.       if (file_exists($metafile) && $contents = file_get_contents($metafile))
  46.          {
  47.          $lines = explode( "\n", $contents );
  48.          // Lines look like "key: value" -> convert into array
  49.          $data = array(
  50.             'extension'    => '',
  51.             'content-type' => '',
  52.             'uploaded'     => '<unknown>        <unknown>',
  53.             'image'        => 'false',
  54.             'width'        => '',
  55.             'height'       => '',
  56.             'filename'     => '',
  57.             'path'         => '',
  58.             'url'          => ''
  59.          );
  60.          foreach ($lines as $line)
  61.          {
  62.             $a = explode( ':', trim($line), 2 );
  63.             if( count($a) == 2 )
  64.             {
  65.                $data[ $a[0] ] = trim( $a[1] );
  66.             }
  67.          }
  68.          // Convenient attributes which can not be stored permanently
  69.          $data['filename'] = $tag . '.' . $data['extension'];
  70.          $data['path'] = $wikka->config['upload_path'] .'/' . $data['filename'];
  71.          $data['url'] = $wikka->config['base_url'] . $tag .  '/file';
  72.          // Final check: file must exist.
  73.          if (! file_exists( $data['path'] ) )
  74.          {
  75.             $data = null;
  76.          }
  77.       }
  78.       return $data;
  79.    }
  80. }
  81.  
  82.  
  83.  
  84. /**
  85.  * Here the real action starts
  86.  */
  87.  
  88. // Initialize variables
  89. $output = ''; // The HTML code that is being generated by this action
  90. $uploadform = False; // Show an upload form instead of the file?
  91. $tag = $this->GetPageTag(); // The page that the user requested
  92.  
  93. if (is_array($vars) && array_key_exists('page', $vars))
  94. {
  95.    $tag = $vars['page'];
  96. }
  97.  
  98.  
  99. $data = getFile($tag,$this);
  100.  
  101. // Check whether current user has read access and if there is a file.
  102. if ($this->HasAccess( 'read', $tag ) && $data )
  103. {
  104.    if ($data['image'] == 'true')
  105.    {
  106.       // Image file - create an <img> tag
  107.       $output =  '<a href="'  . $tag . '">'
  108.               .  '<img src="' . $data['url']    . '"'
  109.               .  ' width="'   . $data['width']  . '"'
  110.               .  ' height="'  . $data['height'] . '"'
  111.               .  ' alt="'     . $tag            . '"'
  112.               .  ' border="0">'
  113.               .  '</a>';
  114.    }
  115.    else
  116.    {
  117.       // Plain file - create a download link
  118.       $output = '<a href="' . $data['url'] . '">' . $data['url'] . '</a>';
  119.    }
  120. }
  121.  
  122. if ($this->HasAccess( 'write', $tag ) && $tag == $this->GetPageTag())
  123. {
  124.    
  125.    // If on the same page, also show an upload form
  126.  
  127.    $headline = 'Attach a file to this page';
  128.    $buttonlabel = 'Attach selected file';
  129.    if( $data )
  130.    {
  131.       $headline    = 'Replace above file/image with another one'
  132.                    . '<br />'
  133.                    . '(Note: if you do this, the new file/image will appear '
  134.                    . 'INSTEAD of the existing file/image everywhere in the wiki!)'
  135.                    ;
  136.       $buttonlabel = 'REPLACE FILE/IMAGE (THINK TWICE!!!)';
  137.    }
  138.  
  139.    $miniref = $this->Href('file', $tag);
  140.  
  141.    $output = $output
  142.            . '<br />' . $headline . ' <br />'
  143.           . '<form action="' . $miniref . '" method="POST" enctype="multipart/form-data">'
  144.                 . (!$this->config["rewrite_mode"] ? '<input type="hidden" name="wakka" value="' . $miniref . '" />' : '')
  145.            . '<input name="file" type="file" size="72">'
  146.            . '<input type="submit" value="' . $buttonlabel . '">'
  147.            . '</form><br />'
  148.            ;
  149. }
  150.  
  151. print $this->ReturnSafeHTML($output);
  152.  
  153. ?>


Also in your Wikka root directory, there is a folder named handlers, which in turn contains a folder names page which contains several php files. Save the following code (use the grab button) as file.php in the handlers/page folder. (Yes, we have two files with the same name, but in different locations and with different function).
handlers/page/file.php (line 1)
  1. <?php
  2.  
  3. /**
  4.  * WikiFile handler
  5.  *
  6.  * @package Handlers
  7.  * @name    File
  8.  *
  9.  * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  10.  *
  11.  * @input   ?action=get returns the file via http
  12.  *          uploading a file causes storage of the file "in the page",
  13.  *          overwriting any previous page file.
  14.  *
  15.  */
  16.  
  17.  
  18.  
  19. /**
  20.  * mimeTypeFromExtension()
  21.  * Return mime type for the given file extension.
  22.  * Slow, but useful if PHP's mime_content_type() cannot be used.
  23.  * Uses Wikka's mime_types.txt and some hardcoded associations.
  24.  *
  25.  * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  26.  * @input   $extension file extension, e.g. 'png', 'jpg', 'ogg'
  27.  * @output  mime content type, e.g. 'image/png', or empty string.
  28.  */
  29. function mimeTypeFromExtension( $extension, $wikka )
  30. {
  31.    // Quick resonse for most frequently used file types
  32.    if ($extension == 'png') return 'image/png';
  33.    if ($extension == 'jpg') return 'image/jpeg';
  34.    if ($extension == 'ogg') return 'application/ogg';
  35.    // If not recoginzed, use mime_types.txt
  36.    if (file_exists( $wikka->config['mime_types'] ))
  37.    {
  38.       foreach (explode("\n",file_get_contents($wikka->config['mime_types'])) as $line)
  39.       {
  40.          $line = trim($line);
  41.          if ($line != '' && substr($line,0,1) != '#' && strpos($line,$extension))
  42.          {
  43.             $a = explode( ' ', str_replace(chr(9),' ',$line) );
  44.             if (in_array( $extension, $a ))
  45.             {
  46.                return $a[0];
  47.             }
  48.          }
  49.       }
  50.    }
  51.    return ''; // if nothing was found
  52. }
  53.  
  54.  
  55.  
  56. /**
  57.  * saveFile()
  58.  * Store an http-uploaded file in the current page.
  59.  * Any previous file will be replaced with the new file.
  60.  *
  61.  * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  62.  * @input   $uploaded_file An item from PHP's $_FILES array (see there)
  63.  * @output  None
  64.  */
  65. function saveFile( $uploaded_file, $wikka )
  66. {
  67.    $pathinfo = pathinfo( $uploaded_file['name'] );
  68.    $extension = strtolower( $pathinfo['extension'] );
  69.    $pathname = $wikka->config['upload_path'] . '/' . $wikka->tag;
  70.    $path = $pathname . '.' . $extension;
  71.    $contenttyp = '';
  72.    if (move_uploaded_file( $uploaded_file['tmp_name'], $path ))
  73.    {
  74.       if( function_exists('mime_content_type') )
  75.       {
  76.          $contenttype = mime_content_type($path);
  77.       }
  78.       if( ! $contenttype  )
  79.       {
  80.          $contenttype = mimeTypeFromExtension( $extension );
  81.       }
  82.       // build an array with metadata
  83.       $data = array(
  84.          'extension'    => $extension,
  85.          'content-type' => $contenttype,
  86.          'uploaded'     => date('Y-m-d H:i') . ' ' . $wikka->GetUserName(),
  87.          'image'        => 'false'
  88.       );
  89.       if( substr($data['content-type'],0,6) == 'image/'  )
  90.       {
  91.          $data['image'] = 'true';
  92.          $size = getimagesize($path);
  93.          $data['width']  = $size[0];
  94.          $data['height'] = $size[1];
  95.       }
  96.       // Create metadata file
  97.       $contents = '';
  98.       foreach ($data as $key => $value)
  99.       {
  100.          $contents .= ($key . ': ' . $value . "\n");
  101.       }
  102.       file_put_contents( $pathname . '.file', $contents );
  103.    }
  104. }
  105.  
  106.  
  107.  
  108. /**
  109.  * Return an array describing the file which is stored in a page.
  110.  *
  111.  * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  112.  * @input   $tag name of the page; default = current page.
  113.  * @output  Array or (if no file in page) null. Keys:
  114.  *          extension     file extension, e.g. 'png', 'jpg', 'ogg'
  115.  *          content-type  mime content type, e.g. 'image/png'
  116.  *          uploaded      when and who,. e.g. '2007-01-31 ChewBakka'
  117.  *          image         'yes' or 'no'
  118.  *          width         Width in pixels (images only)
  119.  *          height        Height in pixels (images only)
  120.  *          filename      PageName.ext
  121.  *          path          upload_path/PageName.extension
  122.  *          url           'http://..../PageName/file'
  123.  */
  124. function getFile( $tag, $wikka )
  125. {
  126.    $data = null; // this variable will be returned
  127.    $metafile = $wikka->config['upload_path'].'/'.$tag.'.file';  // metadata
  128.    if (file_exists($metafile) && $contents = file_get_contents($metafile))
  129.       {
  130.       $lines = explode( "\n", $contents );
  131.       // Lines look like "key: value" -> convert into array
  132.       $data = array(
  133.          'extension'    => '',
  134.          'content-type' => '',
  135.          'uploaded'     => '<unknown>        <unknown>',
  136.          'image'        => 'false',
  137.          'width'        => '',
  138.          'height'       => '',
  139.          'filename'     => '',
  140.          'path'         => '',
  141.          'url'          => ''
  142.       );
  143.       foreach ($lines as $line)
  144.       {
  145.          $a = explode( ':', trim($line), 2 );
  146.          if( count($a) == 2 )
  147.          {
  148.             $data[ $a[0] ] = trim( $a[1] );
  149.          }
  150.       }
  151.       // Convenient attributes which can not be stored permanently
  152.       $data['filename'] = $tag . '.' . $data['extension'];
  153.       $data['path'] = $wikka->config['upload_path'] .'/' . $data['filename'];
  154.       $data['url'] = $wikka->config['base_url'] . $tag .  '/file';
  155.       // Final check: file must exist.
  156.       if (! file_exists( $data['path'] ) )
  157.       {
  158.          $data = null;
  159.       }
  160.    }
  161.    return $data;
  162. }
  163.  
  164.  
  165.  
  166. /**
  167.  * Here the real handler starts
  168.  */
  169.  
  170. $handled = False;
  171.  
  172. if (isset($_FILES['file']))
  173. {
  174.    // User uploaded a file
  175.    if ($this->HasAccess('write'))
  176.    {
  177.       $uploadedfile = $_FILES['file'];
  178.       if ($uploadedfile['error'] > 0)
  179.       {
  180.          // redirect to page
  181.          $this->redirect( $this->Href(), 'Transmitted file was damaged' );
  182.       }
  183.       else
  184.       {
  185.          saveFile( $uploadedfile, $this );
  186.          $this->Redirect( $this->Href() );
  187.       }
  188.       $handled = True;
  189.    }
  190. }
  191. else
  192. {
  193.    // No upload => return the file
  194.    if ($this->HasAccess('read') && $data = getFile($this->GetPageTag(),$this))
  195.    {
  196.       header( 'Content-Type: ' . $data['content-type'] );
  197.       if ($data['image'] != 'true')
  198.       {
  199.          header( 'Content-Disposition: attachment; filename="' . $data['filename'] . '"');
  200.       }
  201.       @readfile($data['path']);
  202.       exit();
  203.       $handled = True;
  204.    }
  205. }
  206.  
  207. if( !$handled )
  208. {
  209.    $this->Redirect( $this->Href(), 'Unknown' );
  210. }
  211. ?>


And now for the really last step - read your wikka.config.php, it should contain a line like this:
wikka.config.php (line 40)
  1.     // ...
  2.     'upload_path' => 'uploads',
  3.     // ...

This means that the Wikka root folder should contain a folder named uploads; please create this folder if it does not exist.
Should the above line not be in your config file, please add the line too.

Ready! If something does not work yet, leave me a comment. --ChewBakka

How it works


Behind the scenes, an uploaded file is stored in the uploads directory along with a metadata file. In the example above, Wikka stores the uploaded file IMG_1234.JPG as MillenniumFalconOnTatooine.jpg along with MillenniumFalconOnTatooine.file which contains the metadata. The latter is a plain ascii file which tells Wikka that the actual file is a jpg file.

History


2007-Feb-05
handlers/page/file.php: bugfix (switch without break, now if/elseif); sends filename with download (makes download easier)
libs/Wakka.class.php: supports *.*.gz

2008-Jul-15
Complete rewrite.
Only ACTION and HANDLER; no more Wikka.class.php modifications.


Credits


While developing the above code, I browsed through many Wikka sources in order to learn how to write a good extension, and how to document it. They were too many to remember, so I just want to say thanks to all the people who made Wikka.


Category: CategoryUserContributions User contributions
There are no comments on this page.
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki