[opw-web] Allow uploading attachments to projects
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [opw-web] Allow uploading attachments to projects
- Date: Mon, 10 Mar 2014 04:32:53 +0000 (UTC)
commit 7a192870c776302a003bfad02417a541bb02a558
Author: Owen W. Taylor <otaylor fishsoup net>
Date: Sun Mar 9 22:22:13 2014 -0400
Allow uploading attachments to projects
classes/class_db.php | 3 +-
classes/class_module.php | 1 +
lang/en-gb.php | 10 +
modules/mod_attachment.php | 203 ++++++++++++++++++++
modules/mod_view_projects.php | 34 ++++
schema.sql | 11 +
skins/easterngreen/html/tpl_attachment_add.html | 33 ++++
skins/easterngreen/html/tpl_header.html | 2 +-
skins/easterngreen/html/tpl_view_project.html | 11 +
.../html/tpl_view_project_attachment.html | 14 ++
10 files changed, 320 insertions(+), 2 deletions(-)
---
diff --git a/classes/class_db.php b/classes/class_db.php
index 7cd334c..3bdce45 100644
--- a/classes/class_db.php
+++ b/classes/class_db.php
@@ -15,7 +15,8 @@ class db
var $_TYPE_MAP = array(
's' => PDO::PARAM_STR,
'i' => PDO::PARAM_INT,
- 'b' => PDO::PARAM_BOOL
+ 'b' => PDO::PARAM_BOOL,
+ 'l' => PDO::PARAM_LOB
);
// Constructor
diff --git a/classes/class_module.php b/classes/class_module.php
index 440258b..a160cf0 100644
--- a/classes/class_module.php
+++ b/classes/class_module.php
@@ -31,6 +31,7 @@ class module
array('name' => 'manage_programs', 'access' => 'a'),
array('name' => 'manage_organizations', 'access' => 'a'),
array('name' => 'notifications', 'access' => 'a'),
+ array('name' => 'attachment', 'access' => 'u'),
);
}
diff --git a/lang/en-gb.php b/lang/en-gb.php
index c7a3551..1f0628b 100644
--- a/lang/en-gb.php
+++ b/lang/en-gb.php
@@ -282,5 +282,15 @@ $lang_data = array(
'processing_program' => 'Processing program #',
'program_no_mail' => 'No mails were sent for this program',
+ /* Module: attachment */
+ 'add_attachment' => "Add attachment",
+ 'attachment' => "Attachment",
+ 'attachment_description' => "Description",
+ 'upload_description_needed' => 'Please supply a description for the attachment',
+ 'upload_no_file' => 'Please select a file to upload',
+ 'upload_failed' => 'Attachment upload failed',
+ 'upload_too_large' => 'Attachment too large (maximum size is 1MB)',
+ 'upload_unknown_type' => 'Attachment type is unsupported (supported: PDF, ODT, TXT)',
+ 'confirm_delete_attachment' => 'Are you sure that you want to delete the attachment?'
);
diff --git a/modules/mod_attachment.php b/modules/mod_attachment.php
new file mode 100644
index 0000000..2e3d350
--- /dev/null
+++ b/modules/mod_attachment.php
@@ -0,0 +1,203 @@
+<?php
+/**
+* Pandora v1
+* @license GPLv3 - http://www.opensource.org/licenses/GPL-3.0
+* @copyright (c) 2012 KDE. All rights reserved.
+*/
+
+if (!defined('IN_PANDORA')) exit;
+
+$action = $core->variable('a', 'view');
+$program_id = 0 + $core->variable('prg', 0);
+$project_id = 0 + $core->variable('p', '');
+$attachment_id = 0 + $core->variable('i', '');
+$return_url = $core->variable('r', '');
+$description = $core->variable('description', '', false, true);
+
+$attachment_add = isset($_POST['attachment_add']);
+$confirm = isset($_POST['yes']);
+
+// Keeps things simple to require the program and project ID
+$user->restrict($program_id > 0);
+$user->restrict($project_id > 0);
+
+if (empty($return_url))
+ $return_url ="?q=view_projects&prg={$program_id}&p={$project_id}";
+
+function validate_ids($program_id, $project_id, $attachment_id, $require_owner)
+{
+ global $db, $user;
+
+ $sql = "SELECT COUNT(*) as count " .
+ "FROM {$db->prefix}participants prt ";
+
+ if ($attachment_id > 0)
+ $sql .= "LEFT JOIN {$db->prefix}attachments a " .
+ "ON a.project_id = prt.project_id ";
+
+ $sql .= "WHERE prt.project_id = :project_id AND " .
+ "prt.program_id = :program_id ";
+
+ if ($attachment_id > 0)
+ $sql .= "AND a.id = :attachment_id ";
+
+ if ($require_owner)
+ $sql .= "AND prt.username = :username " .
+ "AND prt.role = 's' ";
+
+ $row = $db->query($sql,
+ array('program_id' => $program_id,
+ 'project_id' => $project_id,
+ 'attachment_id' => $attachment_id,
+ 'username' => $user->username),
+ true);
+
+ return $row['count'] > 0;
+}
+
+if ($action == 'add') {
+ $user->restrict($project_id > 0);
+ $user->restrict(validate_ids($program_id, $project_id, 0, !$user->is_admin));
+
+ $error_message = '';
+
+ if ($attachment_add) {
+ if ($error_message === '') {
+ if ($description == '') {
+ $error_message = $lang->get('upload_description_needed');
+ }
+ }
+
+ if ($error_message === '') {
+ if ($_FILES['file']['error'] == 4)
+ $error_message = $lang->get('upload_no_file');
+ }
+
+ if ($error_message === '') {
+ if ($_FILES['file']['error'] != 0)
+ $error_message = $lang->get('upload_failed');
+ }
+
+ $size = $_FILES['file']['size'];
+ if ($error_message === '') {
+ if ($size > 1000000)
+ $error_message = $lang->get('upload_too_large');
+ }
+
+ $name = basename($_FILES['file']['name']);
+
+ if ($error_message === '') {
+ $content_type = '';
+ $matches = null;
+ if (preg_match('/\.(.*?)$/', $name, $matches)) {
+ $extension = strtolower($matches[1]);
+ if ($extension == 'pdf')
+ $content_type = 'application/pdf';
+ else if ($extension == 'txt')
+ $content_type = 'text/plain';
+ else if ($extension == 'odt')
+ $content_type = 'application/vnd.oasis.opendocument.text';
+ }
+
+ if ($content_type === '')
+ $error_message = $lang->get('upload_unknown_type');
+ }
+
+ if ($error_message === '') {
+ $fp = fopen($_FILES['file']['tmp_name'], 'rb');
+
+ $sql = "INSERT INTO {$db->prefix}attachments " .
+ "(project_id, name, description, content_type, size, data) " .
+ " VALUES (:project_id, :name, :description, :content_type, :size, :data)";
+
+ $db->query($sql, array('project_id' => $project_id,
+ 'name' => $name,
+ 'description' => $description,
+ 'content_type' => $content_type,
+ 'size' => $size,
+ 'l:data' => $fp));
+
+ $core->redirect($return_url);
+ }
+ }
+
+ // Assign skin data
+ $skin->assign(array(
+ 'description' => htmlspecialchars($description),
+ 'cancel_url' => htmlspecialchars($return_url),
+ 'error_message' => htmlspecialchars($error_message),
+ 'error_visibility' => $skin->visibility($error_message !== '')
+ ));
+
+ // Output the module
+ $module_title = $lang->get('add_attachment');
+ $module_data = $skin->output('tpl_attachment_add');
+
+} else if ($action == 'delete') {
+ $user->restrict($attachment_id > 0);
+ $user->restrict(validate_ids($program_id, $project_id, $attachment_id, !$user->is_admin));
+
+ // Deletion was confirmed
+ if ($confirm)
+ {
+ $sql = "DELETE FROM {$db->prefix}attachments " .
+ "WHERE id = ?";
+ $db->query($sql, $attachment_id);
+
+ $core->redirect($return_url);
+ }
+
+ // Assign confirm box data
+ $skin->assign(array(
+ 'message_title' => $lang->get('confirm_deletion'),
+ 'message_body' => $lang->get('confirm_delete_attachment'),
+ 'cancel_url' => htmlspecialchars($return_url)
+ ));
+
+ // Output the module
+ $module_title = $lang->get('confirm_deletion');
+ $module_data = $skin->output('tpl_confirm_box');
+
+} else if ($action == 'view') {
+ $user->restrict($attachment_id > 0);
+
+ $role = null;
+ $organization_id = null;
+ $user->get_role($program_id, $role, $organization_id);
+
+ // Mentors and admins can see all attachments, otherwise require the project owner
+ $user->restrict(validate_ids($program_id, $project_id, $attachment_id,
+ !($user->is_admin || $role == 'm')));
+
+ $sql = "SELECT name, content_type, size, data " .
+ "FROM {$db->prefix}attachments where id=?";
+
+ $stmt = $db->dbh->prepare($sql);
+ $stmt->execute(array($attachment_id));
+
+ $stmt->bindColumn(1, $name, PDO::PARAM_STR);
+ $stmt->bindColumn(2, $content_type, PDO::PARAM_STR);
+ $stmt->bindColumn(3, $size, PDO::PARAM_STR);
+ $stmt->bindColumn(4, $lob, PDO::PARAM_LOB);
+ $stmt->fetch(PDO::FETCH_BOUND);
+
+ $quoted_filename = '"' . preg_replace('/"/', '', $name) . '"';
+
+ header("Content-Type: $content_type");
+ header("Content-Disposition: filename=$quoted_filename");
+ header("Content-Length: $size");
+
+ // mysql PDO backend seems to load PDO's as a string, not stream
+ if (is_string($lob))
+ echo $lob;
+ else
+ fpassthru($lob);
+
+ exit;
+
+} else {
+ // Unknown action
+ $core->redirect($core->path());
+}
+
+?>
\ No newline at end of file
diff --git a/modules/mod_view_projects.php b/modules/mod_view_projects.php
index e074b06..b505276 100644
--- a/modules/mod_view_projects.php
+++ b/modules/mod_view_projects.php
@@ -531,6 +531,37 @@ else if ($action == 'view')
$can_approve = ($project_data['is_accepted'] == -1 || $project_data['is_accepted'] == 0) &&
$user->is_admin;
$can_reject = ($project_data['is_accepted'] == -1 || $project_data['is_accepted'] == 1) &&
$user->is_admin;
+ // Attachments
+
+ $can_view_attachments = $is_owner || $role == 'm' || $user->is_admin;
+ $can_delete_attachments = $is_owner || $user->is_admin;
+
+ if ($can_view_attachments) {
+ $sql = "SELECT * FROM {$db->prefix}attachments " .
+ "WHERE project_id = ?";
+ $attachment_data = $db->query($sql, $project_id);
+ } else {
+ $attachment_data = array();
+ }
+
+ $attachments_list = '';
+ foreach ($attachment_data as $row) {
+ $attachment_id = $row['id'];
+ $view_url = "?q=attachment&prg={$program_id}&p={$project_id}&i={$attachment_id}";
+ $delete_url = "?q=attachment&a=delete&prg={$program_id}&p={$project_id}&i={$attachment_id}";
+
+ $skin->assign(array(
+ 'attachment_id' => htmlspecialchars($row['id']),
+ 'name' => htmlspecialchars($row['name']),
+ 'view_url' => htmlspecialchars($view_url),
+ 'delete_url' => htmlspecialchars($delete_url),
+ 'description' => htmlspecialchars($row['description']),
+ 'delete_visibility' => $skin->visibility($can_delete_attachments),
+ ));
+
+ $attachments_list .= $skin->output('tpl_view_project_attachment');
+ }
+
// Assign final skin data
$skin->assign(array(
'program_id' => $program_id,
@@ -544,9 +575,12 @@ else if ($action == 'view')
'project_complete' => $complete,
'project_result' => $result,
'return_url' => $return_url,
+ 'attachments_list' => $attachments_list,
'success_message' => isset($success_message) ? $success_message : '',
'success_visibility' => $skin->visibility(empty($success_message), true),
'edit_visibility' => $skin->visibility($is_owner || $user->is_admin),
+ 'attach_visibility' => $skin->visibility($is_owner),
+ 'attachments_visibility' => $skin->visibility(count($attachment_data) > 0),
'delete_visibility' => $skin->visibility($user->is_admin),
'mentorship_visibility' => $skin->visibility($can_mentor),
'actions_visibility' => $skin->visibility($is_owner || $can_mentor || $user->is_admin),
diff --git a/schema.sql b/schema.sql
index 763865b..3dac88b 100644
--- a/schema.sql
+++ b/schema.sql
@@ -112,3 +112,14 @@ CREATE TABLE `opw_profiles` (
`is_admin` tinyint(1) DEFAULT 0,
PRIMARY KEY (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `opw_attachments` (
+ `id` mediumint(10) unsigned NOT NULL AUTO_INCREMENT,
+ `project_id` mediumint(10) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `description` varchar(255) NOT NULL,
+ `content_type` varchar(255) NOT NULL,
+ `size` mediumint(10) unsigned NOT NULL,
+ `data` MEDIUMBLOB NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
diff --git a/skins/easterngreen/html/tpl_attachment_add.html b/skins/easterngreen/html/tpl_attachment_add.html
new file mode 100644
index 0000000..154b547
--- /dev/null
+++ b/skins/easterngreen/html/tpl_attachment_add.html
@@ -0,0 +1,33 @@
+<h1>{{add_attachment}}</h1>
+<hr class="hr-head" />
+
+<div class="alert alert-error [[error_visibility]]">
+ <a class="close" data-dismiss="alert">×</a>
+ [[error_message]]
+</div>
+
+<div class="control-group">
+ <label class="control-label">{{attachment}}</label>
+ <div class="controls">
+ <input type="file" name="file" />
+ </div>
+</div>
+
+<div class="control-group">
+ <label class="control-label">{{attachment_description}}</label>
+ <div class="controls">
+ <input type="text" name="description" maxlength="255" class="input-xxlarge" value="[[description]]"
/>
+ </div>
+</div>
+
+<div class="form-actions">
+ <button type="submit" name="attachment_add" class="btn btn-primary">
+ <i class="icon-ok-sign icon-white"></i>
+ {{save}}
+ </button>
+
+ <a href="[[cancel_url]]" class="btn">
+ <i class="icon-remove icon-black"></i>
+ {{cancel}}
+ </a>
+</div>
diff --git a/skins/easterngreen/html/tpl_header.html b/skins/easterngreen/html/tpl_header.html
index f2c3865..54d6887 100644
--- a/skins/easterngreen/html/tpl_header.html
+++ b/skins/easterngreen/html/tpl_header.html
@@ -181,4 +181,4 @@ ocalization variable
<div class="container">
<div class="row">
<div class="span12">
- <form class="form-horizontal" method="post">
+ <form class="form-horizontal" method="post" enctype="multipart/form-data">
diff --git a/skins/easterngreen/html/tpl_view_project.html b/skins/easterngreen/html/tpl_view_project.html
index 11445a4..927a8c7 100644
--- a/skins/easterngreen/html/tpl_view_project.html
+++ b/skins/easterngreen/html/tpl_view_project.html
@@ -57,6 +57,12 @@
<div class="span8">[[project_result]]</div>
</div>
+ <h4 class="[[attachments_visibility]]">{{attachments}}</h4>
+
+ <table class="table">
+ [[attachments_list]]
+ </table>
+
<div class="form-actions [[actions_visibility]]">
<a href="?q=view_projects&a=editor&prg=[[program_id]]&p=[[project_id]]&r=view"
class="btn btn-primary [[edit_visibility]]">
@@ -74,5 +80,10 @@
<i class="icon-trash icon-white"></i>
{{delete}}
</a>
+
+ <a href="?q=attachment&a=add&prg=[[program_id]]&p=[[project_id]]"
+ class="btn [[add_visibility]]">
+ {{add_attachment}}
+ </a>
</div>
</div>
diff --git a/skins/easterngreen/html/tpl_view_project_attachment.html
b/skins/easterngreen/html/tpl_view_project_attachment.html
new file mode 100644
index 0000000..4b220be
--- /dev/null
+++ b/skins/easterngreen/html/tpl_view_project_attachment.html
@@ -0,0 +1,14 @@
+<tr>
+ <td>
+ <a href="[[view_url]]">[[name]]</a>
+ </td>
+ <td>
+ [[description]]
+ </td>
+ <td class="[[delete_visibility]]">
+ <a href="[[delete_url]]" title="{{delete_attachment}}">
+ <i class="icon-remove"></i>
+ </a>
+ </td>
+</tr>
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]