Revision history for DennysAttachmentsActionInfo


Revision [18623]

Last edited on 2008-01-28 00:12:17 by DennyShimkoski [Modified links pointing to docs server]

No Differences

Revision [11053]

Edited on 2005-09-16 01:48:59 by DennyShimkoski [Modified links pointing to docs server]
Additions:
=====Attachments No Good=====
You can view the history if you like, but this was the first action I wrote. Probably best to ignore this one. Basically, it has a lot of problems. :)
Deletions:
=====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...==
First we have to load the data model into MySQL:
%%(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)
<?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'))
{
$pretty_max_size = bytesToHumanReadableUsage($max_upload_size);
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 (Max Size $pretty_max_size): <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)
<?php
// should be located outside of document root -- chmod 755 -- no trailing slash!
$valid_hosts = array ('localhost'); // for referer check
$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)
<?php
include_once('actions/attachments.ini.php');
function referrer_okay($valid_hosts)
{
if (empty($_SERVER['HTTP_REFERER'])) return true;
foreach ($valid_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($valid_hosts))
{
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: '. filesize($file_path));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename=' . $file['filename']);
header('Connection: close');
@readfile($file_path);
exit;
}
}
break;
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.
{{image alt="Screenshot of the Attachments Action" url="http://bytebrite.com/img/dass.gif"}}
With any luck, you should be good to go! Please let me know if you encounter any problems.
==Author==
----
CategoryUserContributions


Revision [10435]

Edited on 2005-08-04 18:16:34 by DennyShimkoski [Modified links pointing to docs server]
Additions:
function referrer_okay($valid_hosts)
if ($file['page_tag'] == $this->tag && referrer_okay($valid_hosts))
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: '. filesize($file_path));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename=' . $file['filename']);
header('Connection: close');
@readfile($file_path);
exit;
break;
Deletions:
function referrer_okay()
global $valid_hosts;
if ($file['page_tag'] == $this->tag && referrer_okay())
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: '. filesize($file_path));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename=' . $file['filename']);
header('Connection: close');
@readfile($file_path);
exit;
break;


Revision [10434]

Edited on 2005-08-04 18:06:48 by DennyShimkoski [fixing bug (break statement)]
Additions:
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: '. filesize($file_path));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename=' . $file['filename']);
header('Connection: close');
@readfile($file_path);
exit;
break;
Deletions:
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: '. filesize($file_path));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename=' . $file['filename']);
header('Connection: close');
@readfile($file_path);
exit;


Revision [10371]

Edited on 2005-08-01 17:14:17 by DennyShimkoski [fixing bug (break statement)]
Additions:
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Length: '. filesize($file_path));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename=' . $file['filename']);
header('Connection: close');
@readfile($file_path);
exit;
Deletions:
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;


Revision [10370]

Edited on 2005-08-01 17:12:47 by DennyShimkoski [Removed mime magic. Added more download headers. Added Max Size display to upload form.]
Additions:
First we have to load the data model into MySQL:
$pretty_max_size = bytesToHumanReadableUsage($max_upload_size);
echo '<form id="attach_file" action="' . $base_url . '" method="post" enctype="multipart/form-data">';
echo "Attach File (Max Size $pretty_max_size): <input type=\"file\" name=\"file\"> ";
echo '<input type="submit" value="Upload">';
$valid_hosts = array ('localhost'); // for referer check
global $valid_hosts;
foreach ($valid_hosts as $host) if (preg_match("/$host/i", $_SERVER['HTTP_REFERER'])) return true;
Deletions:
To enable the mime magic stuff on Windows, you'll need to add these two lines to your php.ini file:
%%(php.ini)
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:
echo '<form id="attach_file" action="' . $base_url . '" method="post" enctype="multipart/form-data">';
echo 'Attach File: <input type="file" name="file"> ';
echo '<input type="submit" value="Upload">';
$hosts = array ('localhost');
foreach ($hosts as $host) if (preg_match("/$host/i", $_SERVER['HTTP_REFERER'])) return true;


Revision [10298]

Edited on 2005-07-31 01:24:04 by DennyShimkoski [Removed mime magic. Added more download headers. Added Max Size display to upload form.]
Additions:
----
CategoryUserContributions


Revision [10296]

Edited on 2005-07-31 01:20:37 by DennyShimkoski [Removed mime magic. Added more download headers. Added Max Size display to upload form.]
Additions:
Here's a screenshot of an anonymous user viewing a page with two attachments.
{{image alt="Screenshot of the Attachments Action" url="http://bytebrite.com/img/dass.gif"}}
With any luck, you should be good to go! Please let me know if you encounter any problems.
Deletions:
With any luck, you should be good to go!


Revision [10295]

Edited on 2005-07-31 01:10:22 by DennyShimkoski [Removed mime magic. Added more download headers. Added Max Size display to upload form.]
Additions:
=====Attachments Action Documentation=====
This action allows users to attach files to pages and delete them from the system.
There are no parameters at the present time.
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:
%%(php.ini)
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:
%%(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)
<?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)
<?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)
<?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());
}
?>
%%
%%{{attachments}}%%
With any luck, you should be good to go!
DennyShimkoski
Deletions:
[[WikkaDocumentation Wikka Documentation]]
----
=====xxxxx Action Documentation=====
//Included in Wikka since version X.X.X.X//
>>**See also**
Development: xxxxxAction.>>This is the documentation page for the xxxxx action.::c::
//This page is a **template**. It belongs to CategoryTemplate (which contains more handy templates). To create an **action documentation** page, [[http://wikka.jsnx.com/ActionInfoTemplate/clone clone this page]] to a page called **xxxxxActionInfo** (where xxxxx is the (capitalized) name of the action), replace all occurrences of 'xxxxx' with the name of the action and replace this paragraph with the actual content.//
__Note__: Please **remove** the "Wikka Documentation" link at the top, the CategoryDocumentation at the bottom and the "included in Wikka" note unless and until the action is part of the official Wikka distribution!
===Documentation===
Describe your action in one sentence.
""<table cellspacing="0" cellpadding="2" border="1">
<thead>
<tr><th scope="col">name</th><th scope="col">type</th><th scope="col">required?</th><th scope="col">default</th><th scope="col">description</th></tr>
</thead>
<tbody>
<tr><td>param1</td><td>string</td><td>optional</td><td>your_default</td><td>This is one of the params, your action uses. If yuopr action uses no params, remove this table</td></tr>
</tbody>
</table>""
//Place for a longer description.//
%%{{action_name [optional_param="xxx"]}}%%
Example:
you can put in an example here.
The name(s) of the person(s) who wrote this action.
----
CategoryDocumentation


Revision [10294]

The oldest known version of this page was created on 2005-07-31 00:33:13 by DennyShimkoski [Removed mime magic. Added more download headers. Added Max Size display to upload form.]
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki