Revision [16043]

This is an old revision of WikiFile made by ChewBakka on 2007-02-03 10:14:59.

 

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.

Usage

In the wiki, there are no file names any more - any file is "contained" in a particular page and referenced using only the page name. If the file is an image, it is visible in the page (through the { {file} } action) along with explaining text which is writen in the page as usual. The access rights for viewing and modifying files are just the page rights.

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 and embed it in a general page about the Millennium Falcon. First you create another page for the image itself, say MillenniumFalconOnTatooine, and type
Photograph of the Millennium Falcon, last year on Tatooine.
{ {file} }
into it, and save it.

The page footer reads Edit page :: File :: Stats :: Referrers and so on - note the addition of File. Clicking on it opens the file handler, which displays informations about the file (if there is already a file in the page) and an upload form;

Note that uploading a file into a page which already contains a file causes the old file to be replaced with the new file - there can be only one file per page.

Now you can include it in the MillenniumFalcon page and any other page. Just write { {file page="MillenniumFalconOnTatooine"} } into it, and it shows up in the page.

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

Code

Below are five pieces of code. The files Wakka.class.php and footer.php are parts of the Wikka software; I have extended them without changing existing functions (hopefully). I have also added two files: actions/file.php and handlers/page/file.php. I did everything in Wikka 1.1.6.2 so I can not tell whether it will work in older versions.

Behind the scenes, an uploaded file is stored in the uploads directory along with a metadata file. In the example above, Wikka stores the uploade 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.

Here comes the modification of actions/footer.php; lines 5,6,7 are added, anything else remains unchanged. This adds File:: to the footer.
  1. <div class="footer">
  2. <?php
  3.     echo $this->FormOpen("", "TextSearch", "get");
  4.     echo $this->HasAccess("write") ? "<a href=\"".$this->href("edit")."\" title=\"Click to edit this page\">Edit page</a> ::\n" : "";
  5.     echo ($this->HasAccess("write") || $this->HasAccess("read"))
  6.         ? '<a href="' . $this->href("file"). '" title="Click to view/edit the file in this page">File</a> ::' . "\n"
  7.         : '';
  8.     // ...


In Wakka.class.php I have extended the FormOpen function, because the html form must include an encoding attribute if you want to upload files. I have added a fourth parameter named $withFiles. This is the extended function:
  1.     function FormOpen($method = "", $tag = "", $formMethod = "post", $withFile = false )
  2.     {
  3.         $enctype = '';
  4.         if ($withFile) $enctype = ' enctype="multipart/form-data"';
  5.         $result = "<form action=\"".$this->Href($method, $tag)."\" method=\"".$formMethod."\"$enctype>\n";
  6.         if (!$this->config["rewrite_mode"]) $result .= "<input type=\"hidden\" name=\"wakka\" value=\"".$this->MiniHref($method, $tag)."\" />\n";
  7.         return $result;
  8.     }


Also in Wakka.class.php I have added four functions which perform the most basic file handling without access right checking. (One function is actually only a workaround for probllems with mime_content_type() which I experienced). I have just added them and the end of the class, and they look like this:
  1.     /**
  2.      * Return mime type for the given file extension.
  3.      * Slow, but useful if PHP's mime_content_type() cannot be used.
  4.      * Uses Wikka's mime_types.txt and some hardcoded associations.
  5.      *
  6.      * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  7.      * @input   $extension file extension, e.g. 'png', 'jpg', 'ogg'
  8.      * @output  mime content type, e.g. 'image/png', or empty string.
  9.      */
  10.     function MimeTypeFromExtension( $extension )
  11.     {
  12.         // Quick resonse for most frequently used file types
  13.         if ($extension == 'png') return 'image/png';
  14.         if ($extension == 'jpg') return 'image/jpeg';
  15.         if ($extension == 'ogg') return 'application/ogg';
  16.         // If not recoginzed, use mime_types.txt
  17.         if (file_exists( $this->config['mime_types'] ))
  18.         {
  19.             foreach (explode("\n",file_get_contents($this->config['mime_types'])) as $line)
  20.             {
  21.                 $line = trim($line);
  22.                 if ($line != '' && substr($line,0,1) != '#' && strpos($line,$extension))
  23.                 {
  24.                     $a = explode( ' ', str_replace(chr(9),' ',$line) );
  25.                     if (in_array( $extension, $a ))
  26.                     {
  27.                         return $a[0];
  28.                     }
  29.                 }
  30.             }
  31.         }
  32.         return ''; // if nothing was found
  33.     }
  34.  
  35.     /**
  36.      * Return an array describing the file which is stored in a page.
  37.      *
  38.      * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  39.      * @input   $tag name of the page; default = current page.
  40.      * @output  Array or (if no file in page) null. Keys:
  41.      *          extension     file extension, e.g. 'png', 'jpg', 'ogg'
  42.      *          content-type  mime content type, e.g. 'image/png'
  43.      *          uploaded      when and who,. e.g. '2007-01-31 ChewBakka'
  44.      *          image         'yes' or 'no'
  45.      *          width         Width in pixels (images only)
  46.      *          height        Height in pixels (images only)
  47.      *          filename      PageName.extension
  48.      *          path          upload_path/PageName.extension
  49.      *          url           'http://..../PageName/file?action=get'
  50.      */
  51.     function GetFile( $tag = '' )
  52.     {
  53.         $data = null; // this variable will be returned
  54.         if (!$tag) $tag = $this->tag;
  55.         $metafile = $this->config['upload_path'].'/'.$tag.'.file';  // metadata
  56.         if (file_exists($metafile) && $contents = file_get_contents($metafile))
  57.         {
  58.             $lines = explode( "\n", $contents );
  59.             // Lines look like "key: value" -> convert into array
  60.             $data = array(
  61.                 'extension'    => '',
  62.                 'content-type' => '',
  63.                 'uploaded'     => '',
  64.                 'image'        => 'false',
  65.                 'width'        => '',
  66.                 'height'       => '',
  67.                 'filename'     => '',
  68.                 'path'         => '',
  69.                 'url'          => ''
  70.             );
  71.             foreach ($lines as $line)
  72.             {
  73.                 $a = explode( ":", trim($line), 2 );
  74.                 if( count($a) == 2 )
  75.                 {
  76.                     $data[ $a[0] ] = trim( $a[1] );
  77.                 }
  78.             }
  79.             // Convenient attributes which can not be stored permanently
  80.             $data['filename'] = $tag . '.' . $data['extension'];
  81.             $data['path'] = $this->config['upload_path'] .'/' . $data['filename'];
  82.             $data['url'] = $this->config['base_url'] . $tag .  '/file' .
  83.                 ($this->config['rewrite_mode']=='1' ? '?' : '&') .
  84.                 'action=get';
  85.             // Final check: file must exist.
  86.             if (! file_exists( $data['path'] ) )
  87.             {
  88.                 $data = null;
  89.             }
  90.         }
  91.         return $data;
  92.     }
  93.  
  94.     /**
  95.      * Remove the file from the current page.
  96.      *
  97.      * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  98.      * @input   none
  99.      * @output  none
  100.      */
  101.     function RemoveFile()
  102.     {
  103.         if ($data = $this->GetFile())
  104.         {
  105.             unlink( $this->config['upload_path'] . '/' . $this->tag . '.file' );
  106.             unlink( $this->config['upload_path'] . '/' . $data['filename'] );
  107.         }
  108.     }
  109.  
  110.     /**
  111.      * Store an http-uploaded file in the current page.
  112.      * Any previous file will be replaced with the new file.
  113.      *
  114.      * @author  {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  115.      * @input   $uploaded_file An item from PHP's $_FILES array (see there)
  116.      * @output  None
  117.      */
  118.     function SaveFile( $uploaded_file )
  119.     {
  120.         $this->RemoveFile();
  121.         $pathinfo = pathinfo( $uploaded_file['name'] );
  122.         $extension = strtolower( $pathinfo['extension'] );
  123.         $pathname = $this->config['upload_path'] . '/' . $this->tag;
  124.         $path = $pathname . '.' . $extension;
  125.         if (move_uploaded_file( $uploaded_file['tmp_name'], $path ))
  126.         {
  127.             $contenttype = mime_content_type($path);
  128.             if( ! $contenttype  )
  129.             {
  130.                 $contenttype = $this->MimeTypeFromExtension( $extension );
  131.             }
  132.             // build an array with metadata
  133.             $data = array(
  134.                 'extension'    => $extension,
  135.                 'content-type' => $contenttype,
  136.                 'uploaded'     => date('Y-m-d H:i') . ' ' . $this->GetUserName(),
  137.                 'image'        => 'false'
  138.             );
  139.             if( substr($data['content-type'],0,6) == 'image/'  )
  140.             {
  141.                 $data['image'] = 'true';
  142.                 $size = getimagesize($path);
  143.                 $data['width']  = $size[0];
  144.                 $data['height'] = $size[1];
  145.             }
  146.             // Create metadata file
  147.             $contents = '';
  148.             foreach ($data as $key => $value)
  149.             {
  150.                 $contents .= ($key . ': ' . $value . "\n");
  151.             }
  152.             file_put_contents( $pathname . '.file', $contents );
  153.         }
  154.     }


The remaining two code pieces are two new files. Both are named file.php, but one is a handler and the other one is an action.

Here comes handlers/page/file.php:
  1. <?php
  2.  
  3. /**
  4.  * Display file info and a small upload form.
  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. $has_read_access  = $this->HasAccess('read');
  18. $has_write_access = $this->HasAccess('write') && $has_read_access;
  19.  
  20. switch (True)
  21. {
  22.     case $_REQUEST['action'] == 'get':
  23.         // ?action=get
  24.         if ($this->HasAccess('read') && $data = $this->GetFile())
  25.         {
  26.             header('Content-Type: ' . $data['content-type'] );
  27.             @readfile($data['path']);
  28.             exit();
  29.         }
  30.     case isset($_FILES['file']):
  31.         // User uploaded a file
  32.         if ($has_write_access)
  33.         {
  34.             $uploadedfile = $_FILES['file'];
  35.             if ($uploadedfile['error'] > 0)
  36.             {
  37.                 // redirect to page
  38.                 $this->redirect( $this->Href(), 'Transmitted file was damaged' );
  39.             }
  40.             else
  41.             {
  42.                 $this->SaveFile( $uploadedfile );
  43.                 $this->Redirect( $this->Href() );
  44.             }
  45.         }
  46.     default:
  47.         // Display file info and an upload form
  48.         $data = null;
  49.         if ($has_read_access)
  50.         {
  51.             if ($data = $this->GetFile())
  52.             {
  53.                 print '<h3>Existing file</h3><p>';
  54.                 foreach ($data as $key => $value)
  55.                 {
  56.                     if ($key != 'url')
  57.                     {
  58.                         print htmlspecialchars(htmlentities($key))   . ': ' .
  59.                               htmlspecialchars(htmlentities($value)) . '<br />';
  60.                     }
  61.                 }
  62.                 $a = '<a href="' . $data['url'] . '">' . $data['url'] . '</a>';
  63.                 print 'url: ' . $this->ReturnSafeHTML($a) . '<br />';
  64.                 print 'Wikka syntax: {{file page="' . $this->GetPageTag() . '"}}' . '<br />';
  65.                 print '</p>';
  66.             }
  67.             else
  68.             {
  69.                 print '<p>There is no file yet</p>';
  70.             }
  71.         }
  72.         else
  73.         {
  74.             print '<div class="page"><em>Sorry, you are nor allowed to view this file.</em></div>';
  75.         }
  76.         if ($has_write_access)
  77.         {
  78.             print '<h3>Upload form</h3><p>';
  79.             print $this->FormOpen( 'file', $this->GetPageTag(), 'POST', true );
  80.             print '<input name="file" type="file" size="72">';
  81.             print '<input type="submit" value="upload">';
  82.             print $this->FormClose();
  83.             if ($data) print '<p>Note: the uploaded file will <em>replace</em> the existing file.</p>';
  84.         }
  85.         else
  86.         {
  87.             print '<div class="page"><em>Sorry, you are nor allowed to put a file here.</em></div>';
  88.         }
  89. }
  90.  
  91. ?>


And finally actions/file.php
  1. <?php
  2. /**
  3.  * Display the page's file, either inline or as link.
  4.  *
  5.  * If the file is an image, then it is displayed inline.
  6.  * For other file types, a download link is printed.
  7.  * Note that at most one file can be stored in a page.
  8.  *
  9.  * Syntax:
  10.  *  {{file [page="SomeWikkaName"]}}
  11.  *
  12.  * @package  Actions
  13.  * @name     File
  14.  *
  15.  * @author   {@link http://wikkawiki.org/ChewBakka ChewBakka} (first draft)
  16.  *
  17.  * @input    string $page: name of the page whose file is to be displayed.
  18.  *           optional; default is the current page itself.
  19.  */
  20.  
  21. $tag = (is_array($vars) && array_key_exists('page',$vars)) ? $vars['page'] : $this->GetPageTag();
  22.  
  23. $output = '';
  24.  
  25. // Check whether current has read access and if there is a file.
  26. if ($this->HasAccess( 'read', $tag ) && $data = $this->GetFile($tag))
  27. {
  28.     if ($data['image'] == 'true')
  29.     {
  30.         // Image file - create an <img> tag
  31.         $output = '<img src="'    . $data['url']    . '"' .
  32.                       ' width="'  . $data['width']  . '"' .
  33.                       ' height="' . $data['height'] . '"' .
  34.                       ' alt="'    . $tag            . '">';
  35.     }
  36.     else
  37.     {
  38.         // Plain file - create a download link
  39.         $output = '<a href="' . $data['url'] . '">' . $data['url'] . '</a>';
  40.     }
  41. }
  42.  
  43. print $this->ReturnSafeHTML($output);
  44.  
  45. ?>


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.


This page is in the category CategoryUserContributions.
There are no comments on this page.
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki