Revision [19037]

This is an old revision of WikiFile made by ChewBakka on 2008-01-28 00:13:52.

 

WikiFile action


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.

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


Once installed in your Wikka wiki, users will find it very easy to put files in the wiki.

There are no folders and file names - 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 along with explaining text which is writen in the page as usual, and can be embedded in other pages by just referencing the file's page name. 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
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.

How to install it


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

Let us begin with the most easy step. 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.
  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. ?>


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).
  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. if ($_REQUEST['action'] == 'get')
  21. {
  22.     // ?action=get
  23.     if ($this->HasAccess('read') && $data = $this->GetFile())
  24.     {
  25.         header( 'Content-Type: ' . $data['content-type'] );
  26.         if ($data['image'] != 'true')
  27.         {
  28.             header( 'Content-Disposition: attachment; filename="' . $data['filename'] . '"');
  29.         }
  30.         @readfile($data['path']);
  31.         exit();
  32.     }
  33. }
  34. elseif (isset($_FILES['file']))
  35. {
  36.     // User uploaded a file
  37.     if ($has_write_access)
  38.     {
  39.         $uploadedfile = $_FILES['file'];
  40.         if ($uploadedfile['error'] > 0)
  41.         {
  42.             // redirect to page
  43.             $this->redirect( $this->Href(), 'Transmitted file was damaged' );
  44.         }
  45.         else
  46.         {
  47.             $this->SaveFile( $uploadedfile );
  48.             $this->Redirect( $this->Href() );
  49.         }
  50.     }
  51. }
  52. else
  53. {
  54.     // Display file info and an upload form
  55.     if ($has_read_access)
  56.     {
  57.         $upload_form = ( $has_write_access ?
  58.             $this->FormOpen( 'file', $this->GetPageTag(), 'POST', true ) .
  59.             '<input name="file" type="file" size="72">' .
  60.             '<input type="submit" value="upload">' .
  61.             $this->FormClose() . ' <br />'
  62.             : '' );
  63.         if ($data = $this->GetFile())
  64.         {
  65.             // Page contains a file
  66.             print '<p>';
  67.             print 'This page contains a ' . $data['extension'] . ' file.';
  68.             if ($data['image'] == 'true')
  69.             {
  70.                 print ' This is an image, ';
  71.                 print $data['width']  . ' pixels wide and ';
  72.                 print $data['height'] . ' pixels high.';
  73.             }
  74.             $a = '<a href="' . $data['url'] . '">' . $data['url'] . '</a>';
  75.             print ' ' . substr($data['uploaded'],17) . ' has uploaded it on '; // user
  76.             print substr($data['uploaded'],0,16) . '.<br />'; // date and time
  77.             print 'It can be downloaded using the URL '  . $this->ReturnSafeHTML($a) . '.<br />';
  78.             print 'However, for using it in a Wikka page just place the following code in the page:<br />';
  79.             print '{{file page="' . $this->GetPageTag() . '"}}<br />';
  80.             print '</p>';
  81.             if( $has_write_access )
  82.             {
  83.                 print '<p>You can <em>replace</em> it with another file using this form:</p>';
  84.                 print $upload_form;
  85.             }
  86.         }
  87.         else
  88.         {
  89.             // Page does not contain a file
  90.             print '<p>There is no file in this page.</p>';
  91.             if( $has_write_access )
  92.             {
  93.                 print '<p>You can store one file in this page using this form:</p>';
  94.                 print $upload_form;
  95.             }
  96.         }
  97.     }
  98.     else
  99.     {
  100.         // no read access to page => no access to file
  101.         print '<div class="page"><em>Sorry, you are nor allowed to view this page.</em></div>';
  102.     }
  103. }
  104. ?>


Next, we will modify two existing Wikka program files. Be sure to keep backup copies of them!

Ok, return to the actions folder and open the footer.php file in your favorite text editor. Add three lines as described in the code box; 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.     // footer.php should begin with the above lines.
  6.     // Insert the following three lines here:
  7.     echo ($this->HasAccess("write") || $this->HasAccess("read"))
  8.         ? '<a href="' . $this->href("file"). '" title="Click to view/edit the file in this page">File</a> ::' . "\n"
  9.         : '';
  10.     // The rest of footer.php remains unchanged below
  11.     // ...


In the libs folder there is a huge file named Wakka.class.php. Open it and find the lines that start with function FormOpen (line no. 692) and end with a closing }. (Be careful since the next function, FormClose, comes right after FormOpen). Replace the FormOpen function with this extended version (it supports the encoding attribute which is necessary for file uploads):
  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.     }


And finally, insert the following four functions at the end of the Wakka.class.php (but before the final closing } brace):
  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'     => '<unknown>        <unknown>',
  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.         $uploaded_file['name'] = strtolower( $uploaded_file['name'] );
  122.         $pathinfo = pathinfo( $uploaded_file['name'] );
  123.         $extension = $pathinfo['extension'];
  124.         if ($extension=='gz') {
  125.             if( count($a = explode( '.', $uploaded_file['name'])) > 2)
  126.             {
  127.                 $extension = $a[count($a)-2] . '.gz';
  128.             }          
  129.         }
  130.         $pathname = $this->config['upload_path'] . '/' . $this->tag;
  131.         $path = $pathname . '.' . $extension;
  132.         if (move_uploaded_file( $uploaded_file['tmp_name'], $path ))
  133.         {
  134.             $contenttype = mime_content_type($path);
  135.             if( ! $contenttype  )
  136.             {
  137.                 $contenttype = $this->MimeTypeFromExtension( $extension );
  138.             }
  139.             if (substr($path,-3)=='.gz')
  140.             {
  141.                 $contenttype = 'application/octet-stream';
  142.             }
  143.             // build an array with metadata
  144.             $data = array(
  145.                 'extension'    => $extension,
  146.                 'content-type' => $contenttype,
  147.                 'uploaded'     => date('Y-m-d H:i') . ' ' . $this->GetUserName(),
  148.                 'image'        => 'false'
  149.             );
  150.             if( substr($data['content-type'],0,6) == 'image/'  )
  151.             {
  152.                 $data['image'] = 'true';
  153.                 $size = getimagesize($path);
  154.                 $data['width']  = $size[0];
  155.                 $data['height'] = $size[1];
  156.             }
  157.             // Create metadata file
  158.             $contents = '';
  159.             foreach ($data as $key => $value)
  160.             {
  161.                 $contents .= ($key . ': ' . $value . "\n");
  162.             }
  163.             file_put_contents( $pathname . '.file', $contents );
  164.         }
  165.     }


And now for the really last step - read your wikka.config.php, it should contain a line like this:
  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

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