Revision [10296]

This is an old revision of DennysAttachmentsActionInfo made by DennyShimkoski on 2005-07-31 01:20:37.

 

Attachments Action Documentation


Short description
This action allows users to attach files to pages and delete them from the system.

Parameters
There are no parameters at the present time.

Long description

Permission to upload and delete files is determined by each page's ACL. Basically, if you're an admin, you have free reign over all files. If you're a registered user, you have free reign over your own files (i.e., nobody else can delete them but you). If you allow anonymous users to post, their files can be deleted by anybody at will, even other anonymous users.

All files are stored in one directory. If you keep this directory out of your document root, there is little opportunity for somebody to upload code and execute it.

It relies on php's mime_content_type() function, so you'll need to be sure that's working on your machine. I'll run through this in the setup below.

This code has been tested on Windows 2000, PHP 4.3.8, MySQL 4.0.18, and WikkaWiki 1.1.6.0.

Setting up...

To enable the mime magic stuff on Windows, you'll need to add these two lines to your php.ini file:

extension=php_mime_magic.dll
mime_magic.magicfile = "c:\dev\php\magic.mime"

Alternatively, if you don't want to use mime magic, you can change a setting in the handler file at the very bottom of this code listing.

Next up, we have to load the data model into MySQL:

CREATE TABLE `wikka_files` (
  `id` int(11) NOT NULL auto_increment,
  `page_tag` varchar(75) NOT NULL default '',
  `filename` varchar(100) NOT NULL default '',
  `user` varchar(75) NOT NULL default '',
  `submit_time` datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (`id`)
) TYPE=MyISAM AUTO_INCREMENT=1;


Now the following code must be saved in the actions directory as attachments.php

<?php

include_once('attachments.ini.php');

if (! function_exists('bytesToHumanReadableUsage')) {
        /**
        * Converts bytes to a human readable string
        * @param int $bytes Number of bytes
        * @param int $precision Number of decimal places to include in return string
        * @param array $names Custom usage strings
        * @return string formatted string rounded to $precision
        */

        function bytesToHumanReadableUsage($bytes, $precision = 2, $names = '')
        {
           if (!is_numeric($bytes) || $bytes < 0) {
               return false;
           }
       
           for ($level = 0; $bytes >= 1024; $level++) {    
               $bytes /= 1024;      
           }
   
           switch ($level)
           {
               case 0:
                   $suffix = (isset($names[0])) ? $names[0] : 'Bytes';
                   break;
               case 1:
                   $suffix = (isset($names[1])) ? $names[1] : 'KB';
                   break;
               case 2:
                   $suffix = (isset($names[2])) ? $names[2] : 'MB';
                   break;
               case 3:
                   $suffix = (isset($names[3])) ? $names[3] : 'GB';
                   break;      
               case 4:
                   $suffix = (isset($names[4])) ? $names[4] : 'TB';
                   break;                            
               default:
                   $suffix = (isset($names[$level])) ? $names[$level] : '';
                   break;
           }
   
           if (empty($suffix)) {
               trigger_error('Unable to find suffix for case ' . $level);
               return false;
           }
   
           return round($bytes, $precision) . ' ' . $suffix;
        }
}

// handle uploads
if ($this->HasAccess('write') && isset($_FILES['file']))
{
    $file =& $_FILES['file'];
    switch($file['error'])
    {
        case UPLOAD_ERR_OK:
            if ($file['size'] > $max_upload_size)
            {
                echo '<p class="error">Attempted file upload was too big.  Maximum allowed size is ' . bytesToHumanReadableUsage($max_upload_size) . '.</p>';
                unlink($file['tmp_name']);
            }
            else
            {
                $filename = stripslashes(str_replace("'", '', $file['name']));
                $file_exists = $this->LoadSingle("SELECT * FROM $table_name WHERE page_tag = '$this->tag' AND filename = '$file[name]'");
                if (!$file_exists)
                {
                    mysql_query("INSERT INTO $table_name (page_tag, filename, user, submit_time) VALUES ('$this->tag', '$file[name]', '$current_user', NOW())");
                    if ($id = mysql_insert_id())
                    {
                        if (!move_uploaded_file($file['tmp_name'], "$upload_path/$id"))
                        {
                            mysql_query("DELETE FROM $table_name WHERE id = $id");
                            echo '<p class="error">There was an error uploading your file (failed while moving file).</p>';
                        }
                    }
                    else
                    {
                        echo '<p class="error">There was an error uploading your file (failed while inserting record).</p>';
                    }
                }
                else
                {
                    echo '<p class="error">There is already a file named "' . $filename . '". Please rename before uploading or delete the existing file below.</p>';
                }
            }
            break;
        case UPLOAD_ERR_INI_SIZE:
        case UPLOAD_ERR_FORM_SIZE:
            echo '<p class="error">Attempted file upload was too big. Maximum allowed size is '.bytesToHumanReadableUsage($max_upload_size).'.</p>';
            break;
        case UPLOAD_ERR_PARTIAL:
            echo '<p class="error">File upload incomplete! Please try again.</p>';
            break;
        case UPLOAD_ERR_NO_FILE:
            echo '<p class="error">No file selected.</p>';
    }
}

// show upload form
if ($this->HasAccess('write'))
{
    echo '<form id="attach_file" action="' . $base_url . '" method="post" enctype="multipart/form-data">';
    echo "<input type=\"hidden\" name=\"$page_var\" value=\"$this->tag\">";
    echo "<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"$max_upload_size\">";
    echo 'Attach File: <input type="file" name="file"> ';
    echo '<input type="submit" value="Upload">';
    echo '</form>';
}

// show attachments
$sql = "SELECT id, filename, DATE_FORMAT(submit_time, '%m/%d/%y') as date, user FROM $table_name WHERE page_tag = '$this->tag' ORDER BY submit_time DESC";
$result = mysql_query($sql);
if (mysql_num_rows($result))
{
    echo '<style> #attachments th, #attachments td {padding:5px} #attachments th {padding-bottom:0px}</style>';
    echo '<table id="attachments"><tr><th>Attachment</th><th>Size</th><th>Date</th><th>User</th></tr>';
    while ($row = mysql_fetch_array($result))
    {
        $url = $this->Href('attachments', $this->tag, "action=download&id=$row[id]");
        $delete = $this->IsAdmin() || ($current_user == $row['user'] && $this->HasAccess('write')) || $row['user'] == 'Anonymous' ? '<a href="' . $this->Href('attachments', $this->tag, "action=delete&id=$row[id]") . '">[x]</a>' : '[x]';
        $filename = "$delete<a href=\"$url\">$row[filename]</a>";
        $size = bytesToHumanReadableUsage(filesize("$upload_path/{$row[id]}"));
        $user = $row['user'] != 'Anonymous' ? '<a href="'. $this->Href('', $row['user']) . "\">$row[user]</a>" : $row['user'];
        echo "<tr><td>$filename</td><td>$size</td><td>$row[date]</td><td>$user</td></tr>";
    }
    echo '</table>';
}

?>


Save this in the actions directory as well, this time as attachments.ini.php. Be sure to adjust the values to the particulars of your environment.

<?php
// should be located outside of document root -- chmod 755 -- no trailing slash!
$upload_path = '/dev/www/wikifiles';
$max_upload_size = '2097152'; // 2 Megabyte
$base_url = 'http://localhost/wikka/wikka.php';
$page_var = 'wakka';
$table_name = $this->config['table_prefix'] . 'files';
$current_user = ($this->GetUser() != null) ? $this->GetUserName() : 'Anonymous';
?>


Lastly, save the following code in the handlers/page directory as attachments.php

<?php

include_once('actions/attachments.ini.php');

function referrer_okay()
{
    $hosts = array ('localhost');
    if (empty($_SERVER['HTTP_REFERER'])) return true;
    foreach ($hosts as $host) if (preg_match("/$host/i", $_SERVER['HTTP_REFERER'])) return true;
    return false;
}

if (!isset($_REQUEST['id']) || !ctype_digit($_REQUEST['id'])) return;
$file = $this->LoadSingle("SELECT * FROM $table_name WHERE id = $_REQUEST[id]");
if (!$file) return;
$file_path = "$upload_path/{$file[id]}";

switch ($_REQUEST['action'])
{
    case 'download':
        if ($this->HasAccess('read'))
        {
            if ($file['page_tag'] == $this->tag && referrer_okay())
            {
                header('Content-Length: '. filesize($file_path));
                header('Content-Type: ' . mime_content_type($file_path));
// comment the line above out and uncomment the following line
// if you have problems getting mime magic working on your machine
//              header('Content-Type: application/x-download');
                header('Content-Disposition: attachment; filename=' . $file['filename']);
                header('Connection: close');
                @readfile($file_path);
                exit;
            }
        }

   case 'delete':
        if ($this->IsAdmin()
            || ($current_user == $file['user'] && $this->HasAccess('write'))
            || $row['user'] == 'Anonymous')
        {
            mysql_query("DELETE FROM $table_name WHERE id = $_REQUEST[id]");
            @unlink($file_path);
        }
        $this->Redirect($this->Href());
}

?>


Usage:

{{attachments}}


Here's a screenshot of an anonymous user viewing a page with two attachments.

Screenshot of the Attachments Action

With any luck, you should be good to go! Please let me know if you encounter any problems.

Author
DennyShimkoski
There are no comments on this page.
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki